815bbdb1796fc9c43e9d798b127e9cc75c2c66cb
[silc.git] / lib / silcutil / silcsocketstream.c
1 /*
2
3   silcsocketstream.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 2005 Pekka Riikonen
8
9   This program is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; version 2 of the License.
12
13   This program is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   GNU General Public License for more details.
17
18 */
19
20 #include "silcincludes.h"
21
22 #define SILC_IS_SOCKET_STREAM(s) (s->ops == &silc_socket_stream_ops)
23
24 const SilcStreamOps silc_socket_stream_ops;
25
26 /* Platform specific functions */
27 int silc_socket_stream_read(SilcStream stream, unsigned char *buf,
28                             SilcUInt32 buf_len);
29 int silc_socket_stream_write(SilcStream stream, const unsigned char *data,
30                              SilcUInt32 data_len);
31 SilcBool silc_socket_stream_close(SilcStream stream);
32 void silc_socket_stream_destroy(SilcStream stream);
33
34 /* Internal async host lookup context. */
35 typedef struct {
36   SilcSocketStream stream;
37   SilcSocketStreamStatus status;
38   SilcSocketStreamCallback callback;
39   SilcAsyncOperation op;
40   void *context;
41   unsigned int require_fqdn : 1;
42   unsigned int aborted      : 1;
43 } *SilcSocketHostLookup;
44
45 /* The IO process callback that calls the notifier callback to upper
46    layer. */
47
48 SILC_TASK_CALLBACK(silc_socket_stream_io)
49 {
50   SilcSocketStream stream = context;
51
52   if (!stream->notifier)
53     return;
54
55   switch (type) {
56   case SILC_TASK_WRITE:
57     stream->notifier(stream, SILC_STREAM_CAN_WRITE, stream->notifier_context);
58     break;
59
60   case SILC_TASK_READ:
61     stream->notifier(stream, SILC_STREAM_CAN_READ, stream->notifier_context);
62     break;
63
64   default:
65     break;
66   }
67 }
68
69 /* Finishing timeout callback that will actually call the user specified
70    host lookup callback.  This is executed back in the calling thread and
71    not in the lookup thread. */
72
73 SILC_TASK_CALLBACK(silc_socket_host_lookup_finish)
74 {
75   SilcSocketHostLookup lookup = context;
76   SilcSocketStream stream = lookup->stream;
77
78   if (lookup->aborted) {
79     SILC_LOG_DEBUG(("Socket stream creation was aborted"));
80     silc_net_close_connection(stream->sock);
81     silc_free(stream->ip);
82     silc_free(stream->hostname);
83     silc_free(stream);
84     silc_free(lookup);
85     return;
86   }
87
88   if (lookup->status != SILC_SOCKET_OK) {
89     SILC_LOG_DEBUG(("Socket stream failed"));
90     silc_net_close_connection(stream->sock);
91     silc_free(stream->ip);
92     silc_free(stream->hostname);
93     silc_free(stream);
94     stream = lookup->stream = NULL;
95   }
96
97   /* Add the socket to scheduler */
98   if (stream) {
99     silc_schedule_task_add_fd(stream->schedule, stream->sock,
100                               silc_socket_stream_io, stream);
101
102     /* Initially set socket for reading */
103     silc_schedule_set_listen_fd(stream->schedule, stream->sock,
104                                 SILC_TASK_READ, FALSE);
105   }
106
107   /* Return the created socket stream to the caller */
108   if (lookup->callback)
109     lookup->callback(lookup->status, stream, lookup->context);
110
111   if (lookup->op)
112     silc_async_free(lookup->op);
113   silc_free(lookup);
114 }
115
116 /* The thread function that performs the actual lookup. */
117
118 static void *silc_socket_host_lookup_start(void *context)
119 {
120   SilcSocketHostLookup lookup = (SilcSocketHostLookup)context;
121   SilcSocketStream stream = lookup->stream;
122   SilcSchedule schedule = stream->schedule;
123
124   stream->port = silc_net_get_remote_port(stream->sock);
125
126   silc_net_check_host_by_sock(stream->sock, &stream->hostname, &stream->ip);
127   if (!stream->ip) {
128     lookup->status = SILC_SOCKET_UNKNOWN_IP;
129     goto out;
130   }
131
132   if (!stream->hostname && lookup->require_fqdn) {
133     lookup->status = SILC_SOCKET_UNKNOWN_HOST;
134     goto out;
135   }
136
137   if (!stream->hostname) {
138     stream->hostname = strdup(stream->ip);
139     if (!stream->hostname) {
140       lookup->status = SILC_SOCKET_NO_MEMORY;
141       goto out;
142     }
143   }
144
145   lookup->status = SILC_SOCKET_OK;
146
147  out:
148   silc_schedule_task_add_timeout(schedule, silc_socket_host_lookup_finish,
149                                  lookup, 0, 1);
150   silc_schedule_wakeup(schedule);
151   return NULL;
152 }
153
154 /* Abort callback for stream creation. */
155
156 static void silc_socket_host_lookup_abort(SilcAsyncOperation op,
157                                           void *context)
158 {
159   SilcSocketHostLookup lookup = context;
160
161   /* The host lookup is done in thread.  We'll let it finish in its own
162      good time and handle the abortion after it finishes. */
163   lookup->aborted = TRUE;
164 }
165
166 /* Creates socket stream */
167
168 SilcAsyncOperation
169 silc_socket_stream_create(int sock, SilcBool lookup, SilcBool require_fqdn,
170                           SilcSchedule schedule,
171                           SilcSocketStreamCallback callback,
172                           void *context)
173 {
174   SilcSocketStream stream;
175   SilcSocketHostLookup l;
176
177   stream = silc_calloc(1, sizeof(*stream));
178   if (!stream) {
179     if (callback)
180       callback(SILC_SOCKET_NO_MEMORY, NULL, context);
181     return NULL;
182   }
183
184   SILC_LOG_DEBUG(("Creating new socket stream %p", stream));
185
186   stream->ops = &silc_socket_stream_ops;
187   stream->sock = sock;
188   stream->schedule = schedule;
189
190   l = silc_calloc(1, sizeof(*l));
191   if (!l) {
192     silc_free(stream);
193     if (callback)
194       callback(SILC_SOCKET_NO_MEMORY, NULL, context);
195     return NULL;
196   }
197
198   l->stream = stream;
199   l->callback = callback;
200   l->context = context;
201   l->require_fqdn = require_fqdn;
202
203   if (lookup) {
204     /* Start asynchronous IP, hostname and port lookup process */
205     l->op = silc_async_alloc(silc_socket_host_lookup_abort, NULL, l);
206     if (!l->op) {
207       silc_free(stream);
208       silc_free(l);
209       if (callback)
210         callback(SILC_SOCKET_ERROR, NULL, context);
211       return NULL;
212     }
213
214     /* Lookup in thread */
215     SILC_LOG_DEBUG(("Starting async host lookup"));
216     silc_thread_create(silc_socket_host_lookup_start, l, FALSE);
217     return l->op;
218   } else {
219     /* No lookup */
220     l->status = SILC_SOCKET_OK;
221     silc_socket_host_lookup_finish(schedule,
222                                    silc_schedule_get_context(schedule),
223                                    0, 0, l);
224     return NULL;
225   }
226 }
227
228 /* Returns socket stream information */
229
230 SilcBool silc_socket_stream_get_info(SilcStream stream,
231                                  int *sock, const char **hostname,
232                                  const char **ip, SilcUInt16 *port)
233 {
234   SilcSocketStream socket_stream = stream;
235
236   if (!SILC_IS_SOCKET_STREAM(socket_stream))
237     return FALSE;
238
239   if (sock)
240     *sock = socket_stream->sock;
241   if (hostname)
242     *hostname = socket_stream->hostname;
243   if (ip)
244     *ip = socket_stream->ip;
245   if (port)
246     *port = socket_stream->port;
247
248   return TRUE;
249 }
250
251 /* Set socket information */
252
253 SilcBool silc_socket_stream_set_info(SilcStream stream,
254                                  const char *hostname,
255                                  const char *ip, SilcUInt16 port)
256 {
257   SilcSocketStream socket_stream = stream;
258
259   if (!SILC_IS_SOCKET_STREAM(socket_stream))
260     return FALSE;
261
262   if (hostname) {
263     silc_free(socket_stream->hostname);
264     socket_stream->hostname = strdup(hostname);
265     if (!socket_stream->hostname)
266       return FALSE;
267   }
268   if (ip) {
269     silc_free(socket_stream->ip);
270     socket_stream->ip = strdup(ip);
271     if (!socket_stream->ip)
272       return FALSE;
273   }
274   if (port)
275     socket_stream->port = port;
276
277   return TRUE;
278 }
279
280 /* Return socket errno */
281
282 int silc_socket_stream_get_error(SilcStream stream)
283 {
284   SilcSocketStream socket_stream = stream;
285
286   if (!SILC_IS_SOCKET_STREAM(socket_stream))
287     return 0;
288
289   return socket_stream->sock_error;
290 }
291
292 /* Set QoS for socket stream */
293
294 SilcBool silc_socket_stream_set_qos(SilcStream stream,
295                                 SilcUInt32 read_rate,
296                                 SilcUInt32 read_limit_bytes,
297                                 SilcUInt32 limit_sec,
298                                 SilcUInt32 limit_usec)
299 {
300   SilcSocketStream socket_stream = stream;
301
302   if (!SILC_IS_SOCKET_STREAM(socket_stream))
303     return FALSE;
304
305   SILC_LOG_DEBUG(("Setting QoS for socket stream"));
306
307   if (socket_stream->qos && !read_rate && !read_limit_bytes &&
308       !limit_sec && !limit_usec) {
309     silc_schedule_task_del_by_context(socket_stream->schedule,
310                                       socket_stream->qos);
311     silc_free(socket_stream->qos);
312     socket_stream->qos = NULL;
313     return TRUE;
314   }
315
316   if (!socket_stream->qos) {
317     socket_stream->qos = silc_calloc(1, sizeof(*socket_stream->qos));
318     if (!socket_stream->qos)
319       return FALSE;
320   }
321
322   socket_stream->qos->read_rate = read_rate;
323   socket_stream->qos->read_limit_bytes = read_limit_bytes;
324   socket_stream->qos->limit_sec = limit_sec;
325   socket_stream->qos->limit_usec = limit_usec;
326   memset(&socket_stream->qos->next_limit, 0,
327          sizeof(socket_stream->qos->next_limit));
328   socket_stream->qos->cur_rate = 0;
329   socket_stream->qos->sock = socket_stream;
330
331   socket_stream->qos->buffer = silc_malloc(read_limit_bytes);
332   if (!socket_stream->qos->buffer)
333     return FALSE;
334
335   return TRUE;
336 }
337
338 /* Closes socket */
339
340 SilcBool silc_socket_stream_close(SilcStream stream)
341 {
342   SilcSocketStream socket_stream = stream;
343
344   if (!SILC_IS_SOCKET_STREAM(socket_stream))
345     return FALSE;
346
347   silc_schedule_unset_listen_fd(socket_stream->schedule, socket_stream->sock);
348   silc_net_close_connection(socket_stream->sock);
349
350   return TRUE;
351 }
352
353 /* Destroys the stream */
354
355 void silc_socket_stream_destroy(SilcStream stream)
356 {
357   SilcSocketStream socket_stream = stream;
358
359   if (!SILC_IS_SOCKET_STREAM(socket_stream))
360     return;
361
362   silc_socket_stream_close(socket_stream);
363   silc_free(socket_stream->ip);
364   silc_free(socket_stream->hostname);
365
366   if (socket_stream->qos) {
367     silc_schedule_task_del_by_context(socket_stream->schedule,
368                                       socket_stream->qos);
369     if (socket_stream->qos->buffer) {
370       memset(socket_stream->qos->buffer, 0,
371              socket_stream->qos->read_limit_bytes);
372       silc_free(socket_stream->qos->buffer);
373     }
374     silc_free(socket_stream->qos);
375   }
376
377   silc_free(socket_stream);
378 }
379
380 /* Sets stream notification callback for the stream */
381
382 void silc_socket_stream_notifier(SilcStream stream,
383                                  SilcStreamNotifier callback,
384                                  void *context)
385 {
386   SilcSocketStream socket_stream = stream;
387
388   if (!SILC_IS_SOCKET_STREAM(socket_stream))
389     return;
390
391   SILC_LOG_DEBUG(("Setting stream notifier callback"));
392
393   socket_stream->notifier = callback;
394   socket_stream->notifier_context = context;
395 }
396
397 /* SILC Socket Stream ops.  Functions are implemented under the
398    platform specific subdirectories. */
399 const SilcStreamOps silc_socket_stream_ops =
400 {
401   silc_socket_stream_read,
402   silc_socket_stream_write,
403   silc_socket_stream_close,
404   silc_socket_stream_destroy,
405   silc_socket_stream_notifier,
406 };