Use zero timeouts.
[silc.git] / lib / silcutil / silcsocketstream.c
1 /*
2
3   silcsocketstream.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 2005 - 2006 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 "silc.h"
21
22 /************************** Types and definitions ***************************/
23
24 #define SILC_IS_SOCKET_STREAM(s) (s->ops == &silc_socket_stream_ops)
25
26 const SilcStreamOps silc_socket_stream_ops;
27 const SilcStreamOps silc_socket_udp_stream_ops;
28
29 /* Platform specific functions */
30 int silc_socket_stream_read(SilcStream stream, unsigned char *buf,
31                             SilcUInt32 buf_len);
32 int silc_socket_stream_write(SilcStream stream, const unsigned char *data,
33                              SilcUInt32 data_len);
34 SilcBool silc_socket_stream_close(SilcStream stream);
35 void silc_socket_stream_destroy(SilcStream stream);
36 int silc_socket_udp_stream_read(SilcStream stream, unsigned char *buf,
37                                 SilcUInt32 buf_len);
38 int silc_socket_udp_stream_write(SilcStream stream, const unsigned char *data,
39                                  SilcUInt32 data_len);
40
41 /* Internal async host lookup context. */
42 typedef struct {
43   SilcSocketStream stream;
44   SilcSocketStreamStatus status;
45   SilcSocketStreamCallback callback;
46   SilcAsyncOperation op;
47   void *context;
48   unsigned int require_fqdn : 1;
49   unsigned int aborted      : 1;
50 } *SilcSocketHostLookup;
51
52
53 /************************ Static utility functions **************************/
54
55 /* The IO process callback that calls the notifier callback to upper
56    layer. */
57
58 SILC_TASK_CALLBACK(silc_socket_stream_io)
59 {
60   SilcSocketStream stream = context;
61
62   if (!stream->notifier)
63     return;
64
65   switch (type) {
66   case SILC_TASK_WRITE:
67     stream->notifier(stream, SILC_STREAM_CAN_WRITE, stream->notifier_context);
68     break;
69
70   case SILC_TASK_READ:
71     stream->notifier(stream, SILC_STREAM_CAN_READ, stream->notifier_context);
72     break;
73
74   default:
75     break;
76   }
77 }
78
79 /* Finishing timeout callback that will actually call the user specified
80    host lookup callback.  This is executed back in the calling thread and
81    not in the lookup thread. */
82
83 SILC_TASK_CALLBACK(silc_socket_host_lookup_finish)
84 {
85   SilcSocketHostLookup lookup = context;
86   SilcSocketStream stream = lookup->stream;
87
88   if (lookup->aborted) {
89     SILC_LOG_DEBUG(("Socket stream creation was aborted"));
90     silc_net_close_connection(stream->sock);
91     silc_free(stream->ip);
92     silc_free(stream->hostname);
93     silc_free(stream);
94     silc_free(lookup);
95     return;
96   }
97
98   if (lookup->status != SILC_SOCKET_OK) {
99     SILC_LOG_DEBUG(("Socket stream failed"));
100     silc_net_close_connection(stream->sock);
101     silc_free(stream->ip);
102     silc_free(stream->hostname);
103     silc_free(stream);
104     stream = lookup->stream = NULL;
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, 0);
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
167 /******************************* Public API *********************************/
168
169 /* Creates TCP socket stream */
170
171 SilcAsyncOperation
172 silc_socket_tcp_stream_create(int sock, SilcBool lookup,
173                               SilcBool require_fqdn,
174                               SilcSchedule schedule,
175                               SilcSocketStreamCallback callback,
176                               void *context)
177 {
178   SilcSocketStream stream;
179   SilcSocketHostLookup l;
180
181   stream = silc_calloc(1, sizeof(*stream));
182   if (!stream) {
183     if (callback)
184       callback(SILC_SOCKET_NO_MEMORY, NULL, context);
185     return NULL;
186   }
187
188   SILC_LOG_DEBUG(("Creating TCP socket stream %p", stream));
189
190   stream->ops = &silc_socket_stream_ops;
191   stream->sock = sock;
192   stream->schedule = schedule;
193
194   l = silc_calloc(1, sizeof(*l));
195   if (!l) {
196     silc_free(stream);
197     if (callback)
198       callback(SILC_SOCKET_NO_MEMORY, NULL, context);
199     return NULL;
200   }
201
202   l->stream = stream;
203   l->callback = callback;
204   l->context = context;
205   l->require_fqdn = require_fqdn;
206
207   if (lookup) {
208     /* Start asynchronous IP, hostname and port lookup process */
209     l->op = silc_async_alloc(silc_socket_host_lookup_abort, NULL, l);
210     if (!l->op) {
211       silc_free(stream);
212       silc_free(l);
213       if (callback)
214         callback(SILC_SOCKET_ERROR, NULL, context);
215       return NULL;
216     }
217
218     /* Lookup in thread */
219     SILC_LOG_DEBUG(("Starting async host lookup"));
220     silc_thread_create(silc_socket_host_lookup_start, l, FALSE);
221     return l->op;
222   } else {
223     /* No lookup */
224     l->status = SILC_SOCKET_OK;
225     silc_socket_host_lookup_finish(schedule,
226                                    silc_schedule_get_context(schedule),
227                                    0, 0, l);
228     return NULL;
229   }
230 }
231
232 /* Creates UDP socket stream */
233
234 SilcStream silc_socket_udp_stream_create(int sock, SilcBool ipv6,
235                                          SilcSchedule schedule)
236 {
237   SilcSocketStream stream;
238
239   stream = silc_calloc(1, sizeof(*stream));
240   if (!stream)
241     return NULL;
242
243   SILC_LOG_DEBUG(("Creating UDP socket stream %p", stream));
244
245   stream->ops = &silc_socket_udp_stream_ops;
246   stream->sock = sock;
247   stream->schedule = schedule;
248   stream->ipv6 = ipv6;
249
250   return (SilcStream)stream;
251 }
252
253 /* Returns socket stream information */
254
255 SilcBool silc_socket_stream_get_info(SilcStream stream,
256                                      int *sock, const char **hostname,
257                                      const char **ip, SilcUInt16 *port)
258 {
259   SilcSocketStream socket_stream = stream;
260
261   if (!SILC_IS_SOCKET_STREAM(socket_stream))
262     return FALSE;
263
264   if (sock)
265     *sock = socket_stream->sock;
266   if (hostname) {
267     if (!socket_stream->hostname)
268       return FALSE;
269     *hostname = socket_stream->hostname;
270   }
271   if (ip) {
272     if (!socket_stream->ip)
273       return FALSE;
274     *ip = socket_stream->ip;
275   }
276   if (port) {
277     if (!socket_stream->port)
278       return FALSE;
279     *port = socket_stream->port;
280   }
281
282   return TRUE;
283 }
284
285 /* Set socket information */
286
287 SilcBool silc_socket_stream_set_info(SilcStream stream,
288                                      const char *hostname,
289                                      const char *ip, SilcUInt16 port)
290 {
291   SilcSocketStream socket_stream = stream;
292
293   if (!SILC_IS_SOCKET_STREAM(socket_stream))
294     return FALSE;
295
296   if (hostname) {
297     silc_free(socket_stream->hostname);
298     socket_stream->hostname = strdup(hostname);
299     if (!socket_stream->hostname)
300       return FALSE;
301   }
302   if (ip) {
303     silc_free(socket_stream->ip);
304     socket_stream->ip = strdup(ip);
305     if (!socket_stream->ip)
306       return FALSE;
307     if (!socket_stream->hostname) {
308       socket_stream->hostname = strdup(ip);
309       if (!socket_stream->hostname)
310         return FALSE;
311     }
312   }
313   if (port)
314     socket_stream->port = port;
315
316   return TRUE;
317 }
318
319 /* Return socket errno */
320
321 int silc_socket_stream_get_error(SilcStream stream)
322 {
323   SilcSocketStream socket_stream = stream;
324
325   if (!SILC_IS_SOCKET_STREAM(socket_stream))
326     return 0;
327
328   return socket_stream->sock_error;
329 }
330
331 /* Set QoS for socket stream */
332
333 SilcBool silc_socket_stream_set_qos(SilcStream stream,
334                                     SilcUInt32 read_rate,
335                                     SilcUInt32 read_limit_bytes,
336                                     SilcUInt32 limit_sec,
337                                     SilcUInt32 limit_usec)
338 {
339   SilcSocketStream socket_stream = stream;
340
341   if (!SILC_IS_SOCKET_STREAM(socket_stream))
342     return FALSE;
343
344   SILC_LOG_DEBUG(("Setting QoS for socket stream"));
345
346   if (socket_stream->qos && !read_rate && !read_limit_bytes &&
347       !limit_sec && !limit_usec) {
348     silc_schedule_task_del_by_context(socket_stream->schedule,
349                                       socket_stream->qos);
350     silc_free(socket_stream->qos);
351     socket_stream->qos = NULL;
352     return TRUE;
353   }
354
355   if (!socket_stream->qos) {
356     socket_stream->qos = silc_calloc(1, sizeof(*socket_stream->qos));
357     if (!socket_stream->qos)
358       return FALSE;
359   }
360
361   socket_stream->qos->read_rate = read_rate;
362   socket_stream->qos->read_limit_bytes = read_limit_bytes;
363   socket_stream->qos->limit_sec = limit_sec;
364   socket_stream->qos->limit_usec = limit_usec;
365   memset(&socket_stream->qos->next_limit, 0,
366          sizeof(socket_stream->qos->next_limit));
367   socket_stream->qos->cur_rate = 0;
368   socket_stream->qos->sock = socket_stream;
369
370   socket_stream->qos->buffer = silc_malloc(read_limit_bytes);
371   if (!socket_stream->qos->buffer)
372     return FALSE;
373
374   return TRUE;
375 }
376
377 /* Closes socket */
378
379 SilcBool silc_socket_stream_close(SilcStream stream)
380 {
381   SilcSocketStream socket_stream = stream;
382
383   if (!SILC_IS_SOCKET_STREAM(socket_stream))
384     return FALSE;
385
386   silc_schedule_unset_listen_fd(socket_stream->schedule, socket_stream->sock);
387   silc_net_close_connection(socket_stream->sock);
388
389   return TRUE;
390 }
391
392 /* Destroys the stream */
393
394 void silc_socket_stream_destroy(SilcStream stream)
395 {
396   SilcSocketStream socket_stream = stream;
397
398   if (!SILC_IS_SOCKET_STREAM(socket_stream))
399     return;
400
401   silc_socket_stream_close(socket_stream);
402   silc_free(socket_stream->ip);
403   silc_free(socket_stream->hostname);
404   silc_schedule_task_del_by_fd(socket_stream->schedule, socket_stream->sock);
405
406   if (socket_stream->qos) {
407     silc_schedule_task_del_by_context(socket_stream->schedule,
408                                       socket_stream->qos);
409     if (socket_stream->qos->buffer) {
410       memset(socket_stream->qos->buffer, 0,
411              socket_stream->qos->read_limit_bytes);
412       silc_free(socket_stream->qos->buffer);
413     }
414     silc_free(socket_stream->qos);
415   }
416
417   silc_schedule_wakeup(socket_stream->schedule);
418
419   silc_free(socket_stream);
420 }
421
422 /* Sets stream notification callback for the stream */
423
424 void silc_socket_stream_notifier(SilcStream stream,
425                                  SilcSchedule schedule,
426                                  SilcStreamNotifier callback,
427                                  void *context)
428 {
429   SilcSocketStream socket_stream = stream;
430
431   if (!SILC_IS_SOCKET_STREAM(socket_stream))
432     return;
433
434   SILC_LOG_DEBUG(("Setting stream notifier callback"));
435
436   socket_stream->notifier = callback;
437   socket_stream->notifier_context = context;
438   socket_stream->schedule = schedule;
439
440   if (socket_stream->notifier) {
441     /* Add the socket to scheduler.  Safe to call if already added. */
442     silc_schedule_task_add_fd(socket_stream->schedule, socket_stream->sock,
443                               silc_socket_stream_io, socket_stream);
444
445     /* Initially set socket for reading */
446     silc_schedule_set_listen_fd(socket_stream->schedule, socket_stream->sock,
447                                 SILC_TASK_READ, FALSE);
448     silc_schedule_wakeup(socket_stream->schedule);
449   } else {
450     /* Unschedule the socket */
451     silc_schedule_unset_listen_fd(socket_stream->schedule,
452                                   socket_stream->sock);
453     silc_schedule_task_del_by_fd(socket_stream->schedule,
454                                  socket_stream->sock);
455     silc_schedule_wakeup(socket_stream->schedule);
456   }
457 }
458
459 /* Return associated scheduler */
460
461 SilcSchedule silc_socket_stream_get_schedule(SilcStream stream)
462 {
463   SilcSocketStream socket_stream = stream;
464
465   if (!SILC_IS_SOCKET_STREAM(socket_stream))
466     return NULL;
467
468   return socket_stream->schedule;
469 }
470
471 /* SILC Socket Stream ops.  Functions are implemented under the
472    platform specific subdirectories. */
473 const SilcStreamOps silc_socket_stream_ops =
474 {
475   silc_socket_stream_read,
476   silc_socket_stream_write,
477   silc_socket_stream_close,
478   silc_socket_stream_destroy,
479   silc_socket_stream_notifier,
480   silc_socket_stream_get_schedule,
481 };
482 const SilcStreamOps silc_socket_udp_stream_ops =
483 {
484   silc_socket_udp_stream_read,
485   silc_socket_udp_stream_write,
486   silc_socket_stream_close,
487   silc_socket_stream_destroy,
488   silc_socket_stream_notifier,
489   silc_socket_stream_get_schedule,
490 };