Added SILC Thread Queue API
[silc.git] / lib / silcutil / silcsocketstream.c
1 /*
2
3   silcsocketstream.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 2005 - 2007 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 /* Stream operation functions (platform specific) */
25 int silc_socket_stream_read(SilcStream stream, unsigned char *buf,
26                             SilcUInt32 buf_len);
27 int silc_socket_stream_write(SilcStream stream, const unsigned char *data,
28                              SilcUInt32 data_len);
29 SilcBool silc_socket_stream_close(SilcStream stream);
30 void silc_socket_stream_destroy(SilcStream stream);
31 int silc_socket_udp_stream_read(SilcStream stream, unsigned char *buf,
32                                 SilcUInt32 buf_len);
33 int silc_socket_udp_stream_write(SilcStream stream, const unsigned char *data,
34                                  SilcUInt32 data_len);
35 SilcBool silc_socket_stream_close(SilcStream stream);
36 void silc_socket_stream_destroy(SilcStream stream);
37 SilcBool silc_socket_stream_notifier(SilcStream stream,
38                                      SilcSchedule schedule,
39                                      SilcStreamNotifier callback,
40                                      void *context);
41 SilcSchedule silc_socket_stream_get_schedule(SilcStream stream);
42
43 /* Internal async host lookup context. */
44 typedef struct {
45   SilcSocketStream stream;
46   SilcResult status;
47   SilcSocketStreamCallback callback;
48   SilcAsyncOperation op;
49   void *context;
50   unsigned int require_fqdn : 1;
51   unsigned int aborted      : 1;
52 } *SilcSocketHostLookup;
53
54
55 /************************ Static utility functions **************************/
56
57 /* Finishing timeout callback that will actually call the user specified
58    host lookup callback.  This is executed back in the calling thread and
59    not in the lookup thread. */
60
61 SILC_TASK_CALLBACK(silc_socket_host_lookup_finish)
62 {
63   SilcSocketHostLookup lookup = context;
64   SilcSocketStream stream = lookup->stream;
65
66   if (lookup->aborted) {
67     SILC_LOG_DEBUG(("Socket stream creation was aborted"));
68     stream->schedule = NULL;
69     silc_socket_stream_destroy(stream);
70     silc_free(lookup);
71     return;
72   }
73
74   if (lookup->status != SILC_OK) {
75     SILC_LOG_DEBUG(("Socket stream lookup failed"));
76     stream->schedule = NULL;
77     silc_socket_stream_destroy(stream);
78     stream = lookup->stream = NULL;
79   }
80
81   /* Return the created socket stream to the caller */
82   if (lookup->callback)
83     lookup->callback(lookup->status, stream, lookup->context);
84
85   if (lookup->op)
86     silc_async_free(lookup->op);
87   silc_free(lookup);
88 }
89
90 /* The thread function that performs the actual lookup. */
91
92 static void *silc_socket_host_lookup_start(void *context)
93 {
94   SilcSocketHostLookup lookup = (SilcSocketHostLookup)context;
95   SilcSocketStream stream = lookup->stream;
96   SilcSchedule schedule = stream->schedule;
97
98   stream->port = silc_net_get_remote_port(stream->sock);
99
100   silc_net_check_host_by_sock(stream->sock, &stream->hostname, &stream->ip);
101   if (!stream->ip) {
102     lookup->status = SILC_ERR_UNKNOWN_IP;
103     goto out;
104   }
105
106   if (!stream->hostname && lookup->require_fqdn) {
107     lookup->status = SILC_ERR_UNKNOWN_HOST;
108     goto out;
109   }
110
111   if (!stream->hostname) {
112     stream->hostname = silc_strdup(stream->ip);
113     if (!stream->hostname) {
114       lookup->status = SILC_ERR_OUT_OF_MEMORY;
115       goto out;
116     }
117   }
118
119   lookup->status = SILC_OK;
120
121  out:
122   silc_schedule_task_add_timeout(schedule, silc_socket_host_lookup_finish,
123                                  lookup, 0, 0);
124   silc_schedule_wakeup(schedule);
125   return NULL;
126 }
127
128 /* Abort callback for stream creation. */
129
130 static void silc_socket_host_lookup_abort(SilcAsyncOperation op,
131                                           void *context)
132 {
133   SilcSocketHostLookup lookup = context;
134
135   /* The host lookup is done in thread.  We'll let it finish in its own
136      good time and handle the abortion after it finishes. */
137   lookup->aborted = TRUE;
138 }
139
140
141 /******************************* Public API *********************************/
142
143 /* Creates TCP socket stream */
144
145 SilcAsyncOperation
146 silc_socket_tcp_stream_create(SilcSocket sock, SilcBool lookup,
147                               SilcBool require_fqdn,
148                               SilcSchedule schedule,
149                               SilcSocketStreamCallback callback,
150                               void *context)
151 {
152   SilcSocketStream stream;
153   SilcSocketHostLookup l;
154
155   if (!schedule) {
156     schedule = silc_schedule_get_global();
157     if (!schedule) {
158       silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
159       if (callback)
160         callback(silc_errno, NULL, context);
161       return NULL;
162     }
163   }
164
165   if (!sock) {
166     silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
167     if (callback)
168       callback(silc_errno, NULL, context);
169     return NULL;
170   }
171
172   stream = silc_calloc(1, sizeof(*stream));
173   if (!stream) {
174     if (callback)
175       callback(silc_errno, NULL, context);
176     return NULL;
177   }
178
179   SILC_LOG_DEBUG(("Creating TCP socket stream %p, sock %lu", stream, sock));
180
181   stream->ops = &silc_socket_stream_ops;
182   stream->sock = sock;
183   stream->schedule = schedule;
184   stream->connected = TRUE;
185
186   l = silc_calloc(1, sizeof(*l));
187   if (!l) {
188     silc_free(stream);
189     if (callback)
190       callback(silc_errno, NULL, context);
191     return NULL;
192   }
193
194   l->stream = stream;
195   l->callback = callback;
196   l->context = context;
197   l->require_fqdn = require_fqdn;
198
199   if (lookup) {
200     /* Start asynchronous IP, hostname and port lookup process */
201     l->op = silc_async_alloc(silc_socket_host_lookup_abort, NULL, l);
202     if (!l->op) {
203       silc_free(stream);
204       silc_free(l);
205       if (callback)
206         callback(silc_errno, NULL, context);
207       return NULL;
208     }
209
210     /* Lookup in thread */
211     SILC_LOG_DEBUG(("Starting async host lookup"));
212     silc_thread_create(silc_socket_host_lookup_start, l, FALSE);
213     return l->op;
214   } else {
215     /* No lookup */
216     l->status = SILC_OK;
217     silc_socket_host_lookup_finish(schedule,
218                                    silc_schedule_get_context(schedule),
219                                    0, 0, l);
220     return NULL;
221   }
222 }
223
224 /* Creates UDP socket stream */
225
226 SilcStream silc_socket_udp_stream_create(SilcSocket sock, SilcBool ipv6,
227                                          SilcBool connected,
228                                          SilcSchedule schedule)
229 {
230   SilcSocketStream stream;
231
232   if (!schedule) {
233     schedule = silc_schedule_get_global();
234     if (!schedule) {
235       silc_set_errno(SILC_ERR_INVALID_ARGUMENT);
236       return NULL;
237     }
238   }
239
240   stream = silc_calloc(1, sizeof(*stream));
241   if (!stream)
242     return NULL;
243
244   SILC_LOG_DEBUG(("Creating UDP socket stream %p", stream));
245
246   stream->ops = &silc_socket_udp_stream_ops;
247   stream->sock = sock;
248   stream->schedule = schedule;
249   stream->ipv6 = ipv6;
250   stream->connected = connected;
251
252   return (SilcStream)stream;
253 }
254
255 /* Returns TRUE if the stream is UDP stream */
256
257 SilcBool silc_socket_stream_is_udp(SilcStream stream, SilcBool *connected)
258 {
259   SilcSocketStream socket_stream = stream;
260
261   if (!SILC_IS_SOCKET_STREAM_UDP(socket_stream))
262     return FALSE;
263
264   if (connected)
265     *connected = socket_stream->connected;
266
267   return TRUE;
268 }
269
270 /* Returns socket stream information */
271
272 SilcBool silc_socket_stream_get_info(SilcStream stream,
273                                      SilcSocket *sock, const char **hostname,
274                                      const char **ip, SilcUInt16 *port)
275 {
276   SilcSocketStream socket_stream = stream;
277
278   if (!SILC_IS_SOCKET_STREAM(socket_stream) &&
279       !SILC_IS_SOCKET_STREAM_UDP(socket_stream))
280     return FALSE;
281
282   if (sock)
283     *sock = socket_stream->sock;
284   if (port) {
285     if (!socket_stream->port)
286       return FALSE;
287     *port = socket_stream->port;
288   }
289   if (ip) {
290     if (!socket_stream->ip)
291       return FALSE;
292     *ip = socket_stream->ip;
293   }
294   if (hostname) {
295     if (!socket_stream->hostname)
296       return FALSE;
297     *hostname = socket_stream->hostname;
298   }
299
300   return TRUE;
301 }
302
303 /* Set socket information */
304
305 SilcBool silc_socket_stream_set_info(SilcStream stream,
306                                      const char *hostname,
307                                      const char *ip, SilcUInt16 port)
308 {
309   SilcSocketStream socket_stream = stream;
310
311   if (!SILC_IS_SOCKET_STREAM(socket_stream) &&
312       !SILC_IS_SOCKET_STREAM_UDP(socket_stream))
313     return FALSE;
314
315   if (hostname) {
316     silc_free(socket_stream->hostname);
317     socket_stream->hostname = silc_strdup(hostname);
318     if (!socket_stream->hostname)
319       return FALSE;
320   }
321   if (ip) {
322     silc_free(socket_stream->ip);
323     socket_stream->ip = silc_strdup(ip);
324     if (!socket_stream->ip)
325       return FALSE;
326     if (!socket_stream->hostname) {
327       socket_stream->hostname = silc_strdup(ip);
328       if (!socket_stream->hostname)
329         return FALSE;
330     }
331   }
332   if (port)
333     socket_stream->port = port;
334
335   return TRUE;
336 }
337
338 /* Set QoS for socket stream */
339
340 SilcBool silc_socket_stream_set_qos(SilcStream stream,
341                                     SilcUInt32 read_rate,
342                                     SilcUInt32 read_limit_bytes,
343                                     SilcUInt32 limit_sec,
344                                     SilcUInt32 limit_usec)
345 {
346   SilcSocketStream socket_stream = stream;
347
348   if (!SILC_IS_SOCKET_STREAM(socket_stream) &&
349       !SILC_IS_SOCKET_STREAM_UDP(socket_stream))
350     return FALSE;
351
352   SILC_LOG_DEBUG(("Setting QoS for socket stream"));
353
354   if (socket_stream->qos && !read_rate && !read_limit_bytes &&
355       !limit_sec && !limit_usec) {
356     silc_schedule_task_del_by_context(socket_stream->schedule,
357                                       socket_stream->qos);
358     silc_free(socket_stream->qos);
359     socket_stream->qos = NULL;
360     return TRUE;
361   }
362
363   if (!socket_stream->qos) {
364     socket_stream->qos = silc_calloc(1, sizeof(*socket_stream->qos));
365     if (!socket_stream->qos)
366       return FALSE;
367   }
368
369   socket_stream->qos->read_rate = read_rate;
370   socket_stream->qos->read_limit_bytes = read_limit_bytes;
371   socket_stream->qos->limit_sec = limit_sec;
372   socket_stream->qos->limit_usec = limit_usec;
373   memset(&socket_stream->qos->next_limit, 0,
374          sizeof(socket_stream->qos->next_limit));
375   socket_stream->qos->cur_rate = 0;
376   socket_stream->qos->sock = socket_stream;
377
378   socket_stream->qos->buffer = silc_malloc(read_limit_bytes);
379   if (!socket_stream->qos->buffer)
380     return FALSE;
381
382   return TRUE;
383 }
384
385 /* Return associated scheduler */
386
387 SilcSchedule silc_socket_stream_get_schedule(SilcStream stream)
388 {
389   SilcSocketStream socket_stream = stream;
390
391   if (!SILC_IS_SOCKET_STREAM(socket_stream) &&
392       !SILC_IS_SOCKET_STREAM_UDP(socket_stream))
393     return NULL;
394
395   return socket_stream->schedule;
396 }
397
398 /* SILC Socket Stream ops.  Functions are implemented under the
399    platform specific subdirectories. */
400 const SilcStreamOps silc_socket_stream_ops =
401 {
402   silc_socket_stream_read,
403   silc_socket_stream_write,
404   silc_socket_stream_close,
405   silc_socket_stream_destroy,
406   silc_socket_stream_notifier,
407   silc_socket_stream_get_schedule,
408 };
409 const SilcStreamOps silc_socket_udp_stream_ops =
410 {
411   silc_socket_udp_stream_read,
412   silc_socket_udp_stream_write,
413   silc_socket_stream_close,
414   silc_socket_stream_destroy,
415   silc_socket_stream_notifier,
416   silc_socket_stream_get_schedule,
417 };