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