Packet streams: avoid double free if silc_id_id2str fails.
[silc.git] / lib / silccore / silcpacket.c
index 5c584d190f2fec507d9e421858d5f45ee9c05ca7..614dc894650ce0e334051a508d1c052cef6e0d44 100644 (file)
@@ -4,7 +4,7 @@
 
   Author: Pekka Riikonen <priikone@silcnet.org>
 
-  Copyright (C) 1997 - 2006 Pekka Riikonen
+  Copyright (C) 1997 - 2008 Pekka Riikonen
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
 
 /************************** Types and definitions ***************************/
 
+/* Per scheduler (which usually means per thread) data.  We put per scheduler
+   data here for accessing without locking.  SILC Schedule dictates that
+   tasks are dispatched in one thread, hence the per scheduler context. */
+typedef struct {
+  SilcSchedule schedule;                /* The scheduler */
+  SilcPacketEngine engine;              /* Packet engine */
+  SilcDList inbufs;                     /* Data inbut buffer list */
+  SilcUInt32 stream_count;              /* Number of streams using this */
+} *SilcPacketEngineContext;
+
 /* Packet engine */
 struct SilcPacketEngineStruct {
+  SilcMutex lock;                       /* Engine lock */
   SilcRng rng;                          /* RNG for engine */
+  SilcHashTable contexts;               /* Per scheduler contexts */
   SilcPacketCallbacks *callbacks;       /* Packet callbacks */
   void *callback_context;               /* Context for callbacks */
   SilcList streams;                     /* All streams in engine */
   SilcList packet_pool;                 /* Free list for received packets */
-  SilcMutex lock;                       /* Engine lock */
-  SilcBool local_is_router;
+  SilcHashTable udp_remote;             /* UDP remote streams, or NULL */
+  unsigned int local_is_router    : 1;
 };
 
-/* Packet procesor context */
+/* Packet processor context */
 typedef struct SilcPacketProcessStruct {
-  SilcInt32 priority;                   /* Priority */
   SilcPacketType *types;                /* Packets to process */
   SilcPacketCallbacks *callbacks;       /* Callbacks or NULL */
   void *callback_context;
+  SilcInt32 priority;                   /* Priority */
 } *SilcPacketProcess;
 
+/* UDP remote stream tuple */
+typedef struct {
+  char *remote_ip;                      /* Remote IP address */
+  SilcUInt16 remote_port;               /* Remote port */
+} *SilcPacketRemoteUDP;
+
 /* Packet stream */
 struct SilcPacketStreamStruct {
   struct SilcPacketStreamStruct *next;
-  SilcPacketEngine engine;              /* Packet engine */
+  SilcPacketEngineContext sc;           /* Per scheduler context */
   SilcStream stream;                    /* Underlaying stream */
-  SilcMutex lock;                       /* Stream lock */
-  SilcDList process;                    /* Packet processors, it set */
+  SilcMutex lock;                       /* Packet stream lock */
+  SilcDList process;                    /* Packet processors, or NULL */
+  SilcPacketRemoteUDP remote_udp;       /* UDP remote stream tuple, or NULL */
   void *stream_context;                         /* Stream context */
-  SilcBufferStruct inbuf;               /* In buffer */
   SilcBufferStruct outbuf;              /* Out buffer */
-  SilcUInt32 send_psn;                  /* Sending sequence */
-  SilcCipher send_key;                  /* Sending key */
-  SilcHmac send_hmac;                   /* Sending HMAC */
-  SilcUInt32 receive_psn;               /* Receiving sequence */
-  SilcCipher receive_key;               /* Receiving key */
-  SilcHmac receive_hmac;                /* Receiving HMAC */
+  SilcBuffer inbuf;                     /* Inbuf from inbuf list or NULL */
+  SilcCipher send_key[2];               /* Sending key */
+  SilcHmac send_hmac[2];                /* Sending HMAC */
+  SilcCipher receive_key[2];            /* Receiving key */
+  SilcHmac receive_hmac[2];             /* Receiving HMAC */
   unsigned char *src_id;                /* Source ID */
   unsigned char *dst_id;                /* Destination ID */
+  SilcUInt32 send_psn;                  /* Sending sequence */
+  SilcUInt32 receive_psn;               /* Receiving sequence */
+  SilcAtomic8 refcnt;                   /* Reference counter */
+  SilcUInt8 sid;                        /* Security ID, set if IV included */
   unsigned int src_id_len  : 6;
   unsigned int src_id_type : 2;
   unsigned int dst_id_len  : 6;
   unsigned int dst_id_type : 2;
-  SilcUInt8 refcnt;                     /* Reference counter */
   unsigned int is_router   : 1;                 /* Set if router stream */
   unsigned int destroyed   : 1;                 /* Set if destroyed */
   unsigned int iv_included : 1;          /* Set if IV included */
+  unsigned int udp         : 1;          /* UDP remote stream */
 };
 
 /* Initial size of stream buffers */
@@ -80,7 +101,7 @@ struct SilcPacketStreamStruct {
 
 /* Minimum length of SILC Packet Header. */
 #define SILC_PACKET_MIN_HEADER_LEN 16
-#define SILC_PACKET_MIN_HEADER_LEN_IV 32
+#define SILC_PACKET_MIN_HEADER_LEN_IV 32 + 1
 
 /* Maximum padding length */
 #define SILC_PACKET_MAX_PADLEN 128
@@ -128,127 +149,310 @@ do {                                                                       \
 /* EOS callback */
 #define SILC_PACKET_CALLBACK_EOS(s)                                    \
 do {                                                                   \
-  (s)->engine->callbacks->eos((s)->engine, s,                          \
-                             (s)->engine->callback_context,            \
-                             (s)->stream_context);                     \
+  (s)->sc->engine->callbacks->eos((s)->sc->engine, s,                  \
+                                 (s)->sc->engine->callback_context,    \
+                                 (s)->stream_context);                 \
 } while(0)
 
 /* Error callback */
 #define SILC_PACKET_CALLBACK_ERROR(s, err)                             \
 do {                                                                   \
-  (s)->engine->callbacks->error((s)->engine, s, err,                   \
-                               (s)->engine->callback_context,          \
-                               (s)->stream_context);                   \
+  (s)->sc->engine->callbacks->error((s)->sc->engine, s, err,           \
+                                   (s)->sc->engine->callback_context,  \
+                                   (s)->stream_context);               \
 } while(0)
 
+static SilcBool silc_packet_dispatch(SilcPacket packet);
+static void silc_packet_read_process(SilcPacketStream stream);
+static inline SilcBool silc_packet_send_raw(SilcPacketStream stream,
+                                           SilcPacketType type,
+                                           SilcPacketFlags flags,
+                                           SilcIdType src_id_type,
+                                           unsigned char *src_id,
+                                           SilcUInt32 src_id_len,
+                                           SilcIdType dst_id_type,
+                                           unsigned char *dst_id,
+                                           SilcUInt32 dst_id_len,
+                                           const unsigned char *data,
+                                           SilcUInt32 data_len,
+                                           SilcCipher cipher,
+                                           SilcHmac hmac);
 
 /************************ Static utility functions **************************/
 
-static void silc_packet_read_process(SilcPacketStream stream);
+/* Injects packet to new stream created with silc_packet_stream_add_remote. */
 
-/* Our stream IO notifier callback. */
+SILC_TASK_CALLBACK(silc_packet_stream_inject_packet)
+{
+  SilcPacket packet = context;
+  SilcPacketStream stream = packet->stream;
 
-static void silc_packet_stream_io(SilcStream stream, SilcStreamStatus status,
-                                 void *context)
+  SILC_LOG_DEBUG(("Injecting packet %p to stream %p", packet, packet->stream));
+
+  silc_mutex_lock(stream->lock);
+  if (!stream->destroyed)
+    silc_packet_dispatch(packet);
+  silc_mutex_unlock(stream->lock);
+  silc_packet_stream_unref(stream);
+}
+
+/* Write data to the stream.  Must be called with ps->lock locked.  Unlocks
+   the lock inside this function, unless no_unlock is TRUE.  Unlocks always
+   in case it returns FALSE. */
+
+static inline SilcBool silc_packet_stream_write(SilcPacketStream ps,
+                                               SilcBool no_unlock)
 {
-  SilcPacketStream ps = context;
-  int ret;
+  SilcStream stream;
+  SilcBool connected;
+  int i;
 
-  silc_mutex_lock(ps->lock);
+  if (ps->udp)
+    stream = ((SilcPacketStream)ps->stream)->stream;
+  else
+    stream = ps->stream;
+
+  if (ps->udp && silc_socket_stream_is_udp(stream, &connected)) {
+    if (!connected) {
+      /* Connectionless UDP stream */
+      while (silc_buffer_len(&ps->outbuf) > 0) {
+       i = silc_net_udp_send(stream, ps->remote_udp->remote_ip,
+                             ps->remote_udp->remote_port,
+                             ps->outbuf.data, silc_buffer_len(&ps->outbuf));
+       if (silc_unlikely(i == -2)) {
+         /* Error */
+         silc_buffer_reset(&ps->outbuf);
+         SILC_PACKET_CALLBACK_ERROR(ps, SILC_PACKET_ERR_WRITE);
+         return FALSE;
+       }
 
-  if (ps->destroyed) {
-    silc_mutex_unlock(ps->lock);
-    return;
-  }
+       if (silc_unlikely(i == -1)) {
+         /* Cannot write now, write later. */
+         if (!no_unlock)
+           silc_mutex_unlock(ps->lock);
+         return TRUE;
+       }
 
-  switch (status) {
+       /* Wrote data */
+       silc_buffer_pull(&ps->outbuf, i);
+      }
 
-  case SILC_STREAM_CAN_WRITE:
-    if (!silc_buffer_headlen(&ps->outbuf)) {
+      silc_buffer_reset(&ps->outbuf);
+      if (!no_unlock)
+       silc_mutex_unlock(ps->lock);
+
+      return TRUE;
+    }
+  }
+
+  /* Write the data to the stream */
+  while (silc_buffer_len(&ps->outbuf) > 0) {
+    i = silc_stream_write(stream, ps->outbuf.data,
+                         silc_buffer_len(&ps->outbuf));
+    if (silc_unlikely(i == 0)) {
+      /* EOS */
+      silc_buffer_reset(&ps->outbuf);
       silc_mutex_unlock(ps->lock);
-      return;
+      SILC_PACKET_CALLBACK_EOS(ps);
+      return FALSE;
     }
 
-    SILC_LOG_DEBUG(("Writing pending data to stream"));
+    if (silc_unlikely(i == -2)) {
+      /* Error */
+      silc_buffer_reset(&ps->outbuf);
+      silc_mutex_unlock(ps->lock);
+      SILC_PACKET_CALLBACK_ERROR(ps, SILC_PACKET_ERR_WRITE);
+      return FALSE;
+    }
 
-    /* Write pending data to stream */
-    while (silc_buffer_len(&ps->outbuf) > 0) {
-      ret = silc_stream_write(ps->stream, ps->outbuf.data,
-                             silc_buffer_len(&ps->outbuf));
-      if (ret == 0) {
-       /* EOS */
-       silc_buffer_reset(&ps->outbuf);
+    if (silc_unlikely(i == -1)) {
+      /* Cannot write now, write later. */
+      if (!no_unlock)
        silc_mutex_unlock(ps->lock);
-       SILC_PACKET_CALLBACK_EOS(ps);
-       return;
+      return TRUE;
+    }
+
+    /* Wrote data */
+    silc_buffer_pull(&ps->outbuf, i);
+  }
+
+  silc_buffer_reset(&ps->outbuf);
+  if (!no_unlock)
+    silc_mutex_unlock(ps->lock);
+
+  return TRUE;
+}
+
+/* Reads data from stream.  Must be called with ps->lock locked.  If this
+   returns FALSE the lock has been unlocked.  If this returns packet stream
+   to `ret_ps' its lock has been acquired and `ps' lock has been unlocked.
+   It is returned if the stream is UDP and remote UDP stream exists for
+   the sender of the packet. */
+
+static inline SilcBool silc_packet_stream_read(SilcPacketStream ps,
+                                              SilcPacketStream *ret_ps)
+{
+  SilcStream stream = ps->stream;
+  SilcBuffer inbuf;
+  SilcBool connected;
+  int ret;
+
+  /* Get inbuf.  If there is already some data for this stream in the buffer
+     we already have it.  Otherwise get the current one from list, it will
+     include the data. */
+  inbuf = ps->inbuf;
+  if (!inbuf) {
+    silc_dlist_start(ps->sc->inbufs);
+    inbuf = silc_dlist_get(ps->sc->inbufs);
+    if (!inbuf) {
+      /* Allocate new data input buffer */
+      inbuf = silc_buffer_alloc(SILC_PACKET_DEFAULT_SIZE * 65);
+      if (!inbuf) {
+        silc_mutex_unlock(ps->lock);
+        return FALSE;
       }
+      silc_buffer_reset(inbuf);
+      silc_dlist_add(ps->sc->inbufs, inbuf);
+    }
+  }
 
-      if (ret == -2) {
-       /* Error */
-       silc_buffer_reset(&ps->outbuf);
+  /* Make sure there is enough room to read */
+  if (SILC_PACKET_DEFAULT_SIZE * 2 > silc_buffer_taillen(inbuf))
+    silc_buffer_realloc(inbuf, silc_buffer_truelen(inbuf) +
+                       (SILC_PACKET_DEFAULT_SIZE * 2));
+
+  if (silc_socket_stream_is_udp(stream, &connected)) {
+    if (!connected) {
+      /* Connectionless UDP stream, read one UDP packet */
+      char remote_ip[64], tuple[64];
+      int remote_port;
+      SilcPacketStream remote;
+
+      ret = silc_net_udp_receive(stream, remote_ip, sizeof(remote_ip),
+                                &remote_port, inbuf->tail,
+                                silc_buffer_taillen(inbuf));
+
+      if (silc_unlikely(ret < 0)) {
        silc_mutex_unlock(ps->lock);
-       SILC_PACKET_CALLBACK_ERROR(ps, SILC_PACKET_ERR_WRITE);
-       return;
+       if (ret == -1) {
+         /* Cannot read now, do it later. */
+         return FALSE;
+       }
+
+       /* Error */
+       silc_buffer_reset(inbuf);
+       SILC_PACKET_CALLBACK_ERROR(ps, SILC_PACKET_ERR_READ);
+       return FALSE;
       }
 
-      if (ret == -1) {
-       /* Cannot write now, write later. */
+      /* See if remote packet stream exist for this sender */
+      silc_snprintf(tuple, sizeof(tuple), "%d%s", remote_port, remote_ip);
+      silc_mutex_lock(ps->sc->engine->lock);
+      if (silc_hash_table_find(ps->sc->engine->udp_remote, tuple, NULL,
+                              (void *)&remote)) {
+       silc_mutex_unlock(ps->sc->engine->lock);
+       SILC_LOG_DEBUG(("UDP packet from %s:%d for stream %p", remote_ip,
+                       remote_port, remote));
        silc_mutex_unlock(ps->lock);
-       return;
+       silc_mutex_lock(remote->lock);
+       *ret_ps = remote;
+       return TRUE;
       }
+      silc_mutex_unlock(ps->sc->engine->lock);
+
+      /* Unknown sender */
+      if (!ps->remote_udp) {
+       ps->remote_udp = silc_calloc(1, sizeof(*ps->remote_udp));
+       if (silc_unlikely(!ps->remote_udp)) {
+         silc_mutex_unlock(ps->lock);
+         SILC_PACKET_CALLBACK_ERROR(ps, SILC_PACKET_ERR_NO_MEMORY);
+         return FALSE;
+       }
+      }
+
+      /* Save sender IP and port */
+      silc_free(ps->remote_udp->remote_ip);
+      ps->remote_udp->remote_ip = strdup(remote_ip);
+      ps->remote_udp->remote_port = remote_port;
+
+      silc_buffer_pull_tail(inbuf, ret);
+      return TRUE;
+    }
+  }
 
-      /* Wrote data */
-      silc_buffer_pull(&ps->outbuf, ret);
+  /* Read data from the stream */
+  ret = silc_stream_read(stream, inbuf->tail, silc_buffer_taillen(inbuf));
+  if (silc_unlikely(ret <= 0)) {
+    silc_mutex_unlock(ps->lock);
+    if (ret == 0) {
+      /* EOS */
+      silc_buffer_reset(inbuf);
+      SILC_PACKET_CALLBACK_EOS(ps);
+      return FALSE;
+    }
+
+    if (ret == -1) {
+      /* Cannot read now, do it later. */
+      return FALSE;
     }
 
-    silc_buffer_reset(&ps->outbuf);
+    /* Error */
+    silc_buffer_reset(inbuf);
+    SILC_PACKET_CALLBACK_ERROR(ps, SILC_PACKET_ERR_READ);
+    return FALSE;
+  }
+
+  silc_buffer_pull_tail(inbuf, ret);
+  return TRUE;
+}
 
+/* Our stream IO notifier callback. */
+
+static void silc_packet_stream_io(SilcStream stream, SilcStreamStatus status,
+                                 void *context)
+{
+  SilcPacketStream remote = NULL, ps = context;
+
+  silc_mutex_lock(ps->lock);
+
+  if (silc_unlikely(ps->destroyed)) {
     silc_mutex_unlock(ps->lock);
-    break;
+    return;
+  }
 
+  switch (status) {
   case SILC_STREAM_CAN_READ:
-    SILC_LOG_DEBUG(("Reading data from stream"));
-
-    /* Make sure we have fair amount of free space in inbuf */
-    if (silc_buffer_taillen(&ps->inbuf) < SILC_PACKET_DEFAULT_SIZE)
-      if (!silc_buffer_realloc(&ps->inbuf, silc_buffer_truelen(&ps->inbuf) +
-                              SILC_PACKET_DEFAULT_SIZE * 2)) {
-       silc_mutex_unlock(ps->lock);
-       return;
-      }
+    /* Reading is locked also with stream->lock because we may be reading
+       at the same time other thread is writing to same underlaying stream. */
+    SILC_LOG_DEBUG(("Reading data from stream %p, ps %p", ps->stream, ps));
 
     /* Read data from stream */
-    ret = silc_stream_read(ps->stream, ps->inbuf.tail,
-                          silc_buffer_taillen(&ps->inbuf));
-
-    if (ret == 0) {
-      /* EOS */
-      silc_buffer_reset(&ps->inbuf);
-      silc_mutex_unlock(ps->lock);
-      SILC_PACKET_CALLBACK_EOS(ps);
+    if (!silc_packet_stream_read(ps, &remote))
       return;
-    }
 
-    if (ret == -2) {
-      /* Error */
-      silc_buffer_reset(&ps->inbuf);
+    /* Now process the data */
+    silc_packet_stream_ref(ps);
+    if (!remote) {
+      silc_packet_read_process(ps);
       silc_mutex_unlock(ps->lock);
-      SILC_PACKET_CALLBACK_ERROR(ps, SILC_PACKET_ERR_READ);
-      return;
+    } else {
+      silc_packet_read_process(remote);
+      silc_mutex_unlock(remote->lock);
     }
+    silc_packet_stream_unref(ps);
+    break;
 
-    if (ret == -1) {
-      /* Cannot read now, do it later. */
-      silc_buffer_pull(&ps->inbuf, silc_buffer_len(&ps->inbuf));
+  case SILC_STREAM_CAN_WRITE:
+    SILC_LOG_DEBUG(("Writing pending data to stream %p, ps %p",
+                   ps->stream, ps));
+
+    if (silc_unlikely(!silc_buffer_headlen(&ps->outbuf))) {
       silc_mutex_unlock(ps->lock);
       return;
     }
 
-    /* Now process the data */
-    silc_buffer_pull_tail(&ps->inbuf, ret);
-    silc_packet_read_process(ps);
-
-    silc_mutex_unlock(ps->lock);
+    /* Write pending data to stream */
+    silc_packet_stream_write(ps, FALSE);
     break;
 
   default:
@@ -276,13 +480,13 @@ static SilcPacket silc_packet_alloc(SilcPacketEngine engine)
     silc_mutex_unlock(engine->lock);
 
     packet = silc_calloc(1, sizeof(*packet));
-    if (!packet)
+    if (silc_unlikely(!packet))
       return NULL;
 
     SILC_LOG_DEBUG(("Allocating new packet %p", packet));
 
     tmp = silc_malloc(SILC_PACKET_DEFAULT_SIZE);
-    if (!tmp) {
+    if (silc_unlikely(!tmp)) {
       silc_free(packet);
       return NULL;
     }
@@ -302,6 +506,33 @@ static SilcPacket silc_packet_alloc(SilcPacketEngine engine)
   return packet;
 }
 
+/* UDP remote stream hash table destructor */
+
+static void silc_packet_engine_hash_destr(void *key, void *context,
+                                         void *user_context)
+{
+  silc_free(key);
+}
+
+/* Per scheduler context hash table destructor */
+
+static void silc_packet_engine_context_destr(void *key, void *context,
+                                            void *user_context)
+{
+  SilcPacketEngineContext sc = context;
+  SilcBuffer buffer;
+
+  silc_dlist_start(sc->inbufs);
+  while ((buffer = silc_dlist_get(sc->inbufs))) {
+    silc_buffer_clear(buffer);
+    silc_buffer_free(buffer);
+    silc_dlist_del(sc->inbufs, buffer);
+  }
+
+  silc_dlist_uninit(sc->inbufs);
+  silc_free(sc);
+}
+
 
 /******************************** Packet API ********************************/
 
@@ -328,6 +559,14 @@ silc_packet_engine_start(SilcRng rng, SilcBool router,
   if (!engine)
     return NULL;
 
+  engine->contexts = silc_hash_table_alloc(0, silc_hash_ptr, NULL, NULL, NULL,
+                                          silc_packet_engine_context_destr,
+                                          engine, TRUE);
+  if (!engine->contexts) {
+    silc_free(engine);
+    return NULL;
+  }
+
   engine->rng = rng;
   engine->local_is_router = router;
   engine->callbacks = callbacks;
@@ -339,12 +578,16 @@ silc_packet_engine_start(SilcRng rng, SilcBool router,
   silc_list_init(engine->packet_pool, struct SilcPacketStruct, next);
   for (i = 0; i < 5; i++) {
     packet = silc_calloc(1, sizeof(*packet));
-    if (!packet)
+    if (!packet) {
+      silc_packet_engine_stop(engine);
       return NULL;
+    }
 
     tmp = silc_malloc(SILC_PACKET_DEFAULT_SIZE);
-    if (!tmp)
+    if (!tmp) {
+      silc_packet_engine_stop(engine);
       return NULL;
+    }
     silc_buffer_set(&packet->buffer, tmp, SILC_PACKET_DEFAULT_SIZE);
     silc_buffer_reset(&packet->buffer);
 
@@ -359,17 +602,79 @@ silc_packet_engine_start(SilcRng rng, SilcBool router,
 
 void silc_packet_engine_stop(SilcPacketEngine engine)
 {
+  SilcPacket packet;
 
   SILC_LOG_DEBUG(("Stopping packet engine"));
 
   if (!engine)
     return;
 
-  /* XXX */
+  /* Free packet free list */
+  silc_list_start(engine->packet_pool);
+  while ((packet = silc_list_get(engine->packet_pool))) {
+    silc_buffer_purge(&packet->buffer);
+    silc_free(packet);
+  }
 
+  silc_hash_table_free(engine->contexts);
+  silc_mutex_free(engine->lock);
   silc_free(engine);
 }
 
+static const char * const packet_error[] = {
+  "Cannot read from stream",
+  "Cannot write to stream",
+  "Packet MAC failed",
+  "Packet decryption failed",
+  "Unknown SID",
+  "Packet is malformed",
+  "System out of memory",
+};
+
+/* Return packet error string */
+
+const char *silc_packet_error_string(SilcPacketError error)
+{
+  if (error < SILC_PACKET_ERR_READ || error > SILC_PACKET_ERR_NO_MEMORY)
+    return "<invalid error code>";
+  return packet_error[error];
+}
+
+/* Return list of packet streams in the engine */
+
+SilcDList silc_packet_engine_get_streams(SilcPacketEngine engine)
+{
+  SilcDList list;
+  SilcPacketStream ps;
+
+  list = silc_dlist_init();
+  if (!list)
+    return NULL;
+
+  silc_mutex_lock(engine->lock);
+  silc_list_start(engine->streams);
+  while ((ps = silc_list_get(engine->streams))) {
+    silc_packet_stream_ref(ps);
+    silc_dlist_add(list, ps);
+  }
+  silc_mutex_unlock(engine->lock);
+
+  return list;
+}
+
+/* Free list returned by silc_packet_engine_get_streams */
+
+void silc_packet_engine_free_streams_list(SilcDList streams)
+{
+  SilcPacketStream ps;
+
+  silc_dlist_start(streams);
+  while ((ps = silc_dlist_get(streams)))
+    silc_packet_stream_unref(ps);
+
+  silc_dlist_uninit(streams);
+}
+
 /* Create new packet stream */
 
 SilcPacketStream silc_packet_stream_create(SilcPacketEngine engine,
@@ -377,6 +682,7 @@ SilcPacketStream silc_packet_stream_create(SilcPacketEngine engine,
                                           SilcStream stream)
 {
   SilcPacketStream ps;
+  SilcBuffer inbuf;
   void *tmp;
 
   SILC_LOG_DEBUG(("Creating new packet stream"));
@@ -388,35 +694,184 @@ SilcPacketStream silc_packet_stream_create(SilcPacketEngine engine,
   if (!ps)
     return NULL;
 
-  ps->engine = engine;
   ps->stream = stream;
-  ps->refcnt++;
+  silc_atomic_init8(&ps->refcnt, 1);
+  silc_mutex_alloc(&ps->lock);
 
-  /* Allocate buffers */
-  tmp = silc_malloc(SILC_PACKET_DEFAULT_SIZE);
-  if (!tmp)
-    return NULL;
-  silc_buffer_set(&ps->inbuf, tmp, SILC_PACKET_DEFAULT_SIZE);
-  silc_buffer_reset(&ps->inbuf);
+  /* Allocate out buffer */
   tmp = silc_malloc(SILC_PACKET_DEFAULT_SIZE);
-  if (!tmp)
+  if (!tmp) {
+    silc_packet_stream_destroy(ps);
     return NULL;
+  }
   silc_buffer_set(&ps->outbuf, tmp, SILC_PACKET_DEFAULT_SIZE);
   silc_buffer_reset(&ps->outbuf);
 
   /* Initialize packet procesors list */
   ps->process = silc_dlist_init();
+  if (!ps->process) {
+    silc_packet_stream_destroy(ps);
+    return NULL;
+  }
+
+  silc_mutex_lock(engine->lock);
+
+  /* Add per scheduler context */
+  if (!silc_hash_table_find(engine->contexts, schedule, NULL,
+                           (void *)&ps->sc)) {
+    ps->sc = silc_calloc(1, sizeof(*ps->sc));
+    if (!ps->sc) {
+      silc_mutex_unlock(engine->lock);
+      silc_packet_stream_destroy(ps);
+      return NULL;
+    }
+    ps->sc->engine = engine;
+    ps->sc->schedule = schedule;
+
+    /* Allocate data input buffer */
+    inbuf = silc_buffer_alloc(SILC_PACKET_DEFAULT_SIZE * 65);
+    if (!inbuf) {
+      silc_free(ps->sc);
+      ps->sc = NULL;
+      silc_mutex_unlock(engine->lock);
+      silc_packet_stream_destroy(ps);
+      return NULL;
+    }
+    silc_buffer_reset(inbuf);
+
+    ps->sc->inbufs = silc_dlist_init();
+    if (!ps->sc->inbufs) {
+      silc_buffer_free(inbuf);
+      silc_free(ps->sc);
+      ps->sc = NULL;
+      silc_mutex_unlock(engine->lock);
+      silc_packet_stream_destroy(ps);
+      return NULL;
+    }
+    silc_dlist_add(ps->sc->inbufs, inbuf);
+
+    /* Add to per scheduler context hash table */
+    if (!silc_hash_table_add(engine->contexts, schedule, ps->sc)) {
+      silc_buffer_free(inbuf);
+      silc_dlist_del(ps->sc->inbufs, inbuf);
+      silc_free(ps->sc);
+      ps->sc = NULL;
+      silc_mutex_unlock(engine->lock);
+      silc_packet_stream_destroy(ps);
+      return NULL;
+    }
+  }
+  ps->sc->stream_count++;
+
+  /* Add the packet stream to engine */
+  silc_list_add(engine->streams, ps);
+
+  /* If this is UDP stream, allocate UDP remote stream hash table */
+  if (!engine->udp_remote && silc_socket_stream_is_udp(stream, NULL))
+    engine->udp_remote = silc_hash_table_alloc(0, silc_hash_string, NULL,
+                                              silc_hash_string_compare, NULL,
+                                              silc_packet_engine_hash_destr,
+                                              NULL, TRUE);
+
+  silc_mutex_unlock(engine->lock);
+
+  /* Set IO notifier callback.  This schedules this stream for I/O. */
+  if (!silc_stream_set_notifier(ps->stream, schedule,
+                               silc_packet_stream_io, ps)) {
+    SILC_LOG_DEBUG(("Cannot set stream notifier for packet stream"));
+    silc_packet_stream_destroy(ps);
+    return NULL;
+  }
+
+  SILC_LOG_DEBUG(("Created packet stream %p", ps));
+
+  return ps;
+}
+
+/* Add new remote packet stream for UDP packet streams */
+
+SilcPacketStream silc_packet_stream_add_remote(SilcPacketStream stream,
+                                              const char *remote_ip,
+                                              SilcUInt16 remote_port,
+                                              SilcPacket packet)
+{
+  SilcPacketEngine engine = stream->sc->engine;
+  SilcPacketStream ps;
+  char *tuple;
+  void *tmp;
+
+  SILC_LOG_DEBUG(("Adding UDP remote %s:%d to packet stream %p",
+                 remote_ip, remote_port, stream));
+
+  if (!stream || !remote_ip || !remote_port)
+    return NULL;
 
-  /* Set IO notifier callback */
-  silc_stream_set_notifier(ps->stream, schedule, silc_packet_stream_io, ps);
+  if (!silc_socket_stream_is_udp(stream->stream, NULL)) {
+    SILC_LOG_ERROR(("Stream is not UDP stream, cannot add remote IP"));
+    return NULL;
+  }
+
+  ps = silc_calloc(1, sizeof(*ps));
+  if (!ps)
+    return NULL;
+  ps->sc = stream->sc;
 
+  silc_atomic_init8(&ps->refcnt, 1);
   silc_mutex_alloc(&ps->lock);
 
-  /* Add to engine */
+  /* Set the UDP packet stream as underlaying stream */
+  silc_packet_stream_ref(stream);
+  ps->stream = (SilcStream)stream;
+  ps->udp = TRUE;
+
+  /* Allocate out buffer */
+  tmp = silc_malloc(SILC_PACKET_DEFAULT_SIZE);
+  if (!tmp) {
+    silc_packet_stream_destroy(ps);
+    return NULL;
+  }
+  silc_buffer_set(&ps->outbuf, tmp, SILC_PACKET_DEFAULT_SIZE);
+  silc_buffer_reset(&ps->outbuf);
+
+  /* Initialize packet procesors list */
+  ps->process = silc_dlist_init();
+  if (!ps->process) {
+    silc_packet_stream_destroy(ps);
+    return NULL;
+  }
+
+  /* Add to engine with this IP and port pair */
+  tuple = silc_format("%d%s", remote_port, remote_ip);
   silc_mutex_lock(engine->lock);
-  silc_list_add(engine->streams, ps);
+  if (!tuple || !silc_hash_table_add(engine->udp_remote, tuple, ps)) {
+    silc_mutex_unlock(engine->lock);
+    silc_packet_stream_destroy(ps);
+    return NULL;
+  }
   silc_mutex_unlock(engine->lock);
 
+  /* Save remote IP and port pair */
+  ps->remote_udp = silc_calloc(1, sizeof(*ps->remote_udp));
+  if (!ps->remote_udp) {
+    silc_packet_stream_destroy(ps);
+    return NULL;
+  }
+  ps->remote_udp->remote_port = remote_port;
+  ps->remote_udp->remote_ip = strdup(remote_ip);
+  if (!ps->remote_udp->remote_ip) {
+    silc_packet_stream_destroy(ps);
+    return NULL;
+  }
+
+  if (packet) {
+    /* Inject packet to the new stream */
+    packet->stream = ps;
+    silc_packet_stream_ref(ps);
+    silc_schedule_task_add_timeout(silc_stream_get_schedule(stream->stream),
+                                  silc_packet_stream_inject_packet, packet,
+                                  0, 0);
+  }
+
   return ps;
 }
 
@@ -424,37 +879,111 @@ SilcPacketStream silc_packet_stream_create(SilcPacketEngine engine,
 
 void silc_packet_stream_destroy(SilcPacketStream stream)
 {
+  SilcPacketEngine engine;
+
   if (!stream)
     return;
 
-  if (stream->refcnt > 1) {
+  if (silc_atomic_sub_int8(&stream->refcnt, 1) > 0) {
+    if (stream->destroyed)
+      return;
     stream->destroyed = TRUE;
+
+    SILC_LOG_DEBUG(("Marking packet stream %p destroyed", stream));
+
+    /* Close the underlaying stream */
+    if (!stream->udp && stream->stream)
+      silc_stream_close(stream->stream);
     return;
   }
 
   SILC_LOG_DEBUG(("Destroying packet stream %p", stream));
 
-  /* Delete from engine */
-  silc_mutex_lock(stream->engine->lock);
-  silc_list_del(stream->engine->streams, stream);
-  silc_mutex_unlock(stream->engine->lock);
+  if (!stream->udp) {
+    /* Delete from engine */
+    if (stream->sc) {
+      engine = stream->sc->engine;
+      silc_mutex_lock(engine->lock);
+      silc_list_del(engine->streams, stream);
+
+      /* Remove per scheduler context, if it is not used anymore */
+      stream->sc->stream_count--;
+      if (!stream->sc->stream_count)
+       silc_hash_table_del(engine->contexts, stream->sc->schedule);
+
+      silc_mutex_unlock(engine->lock);
+    }
+
+    /* Destroy the underlaying stream */
+    if (stream->stream)
+      silc_stream_destroy(stream->stream);
+  } else {
+    /* Delete from UDP remote hash table */
+    char tuple[64];
+    engine = stream->sc->engine;
+    silc_snprintf(tuple, sizeof(tuple), "%d%s",
+                 stream->remote_udp->remote_port,
+                 stream->remote_udp->remote_ip);
+    silc_mutex_lock(engine->lock);
+    silc_hash_table_del(engine->udp_remote, tuple);
+    silc_mutex_unlock(engine->lock);
+
+    silc_free(stream->remote_udp->remote_ip);
+    silc_free(stream->remote_udp);
+
+    /* Unreference the underlaying packet stream */
+    silc_packet_stream_unref((SilcPacketStream)stream->stream);
+  }
 
   /* Clear and free buffers */
-  silc_buffer_clear(&stream->inbuf);
   silc_buffer_clear(&stream->outbuf);
-  silc_buffer_purge(&stream->inbuf);
   silc_buffer_purge(&stream->outbuf);
 
-  /* XXX */
-
-  /* Destroy the underlaying stream */
-  silc_stream_destroy(stream->stream);
+  if (stream->process) {
+    SilcPacketProcess p;
+    silc_dlist_start(stream->process);
+    while ((p = silc_dlist_get(stream->process))) {
+      silc_free(p->types);
+      silc_free(p);
+      silc_dlist_del(stream->process, p);
+    }
+    silc_dlist_uninit(stream->process);
+  }
 
-  silc_dlist_uninit(stream->process);
+  /* Destroy ciphers and HMACs */
+  if (stream->send_key[0])
+    silc_cipher_free(stream->send_key[0]);
+  if (stream->receive_key[0])
+    silc_cipher_free(stream->receive_key[0]);
+  if (stream->send_hmac[0])
+    silc_hmac_free(stream->send_hmac[0]);
+  if (stream->receive_hmac[0])
+    silc_hmac_free(stream->receive_hmac[0]);
+  if (stream->send_key[1])
+    silc_cipher_free(stream->send_key[1]);
+  if (stream->receive_key[1])
+    silc_cipher_free(stream->receive_key[1]);
+  if (stream->send_hmac[1])
+    silc_hmac_free(stream->send_hmac[1]);
+  if (stream->receive_hmac[1])
+    silc_hmac_free(stream->receive_hmac[1]);
+
+  /* Free IDs */
+  silc_free(stream->src_id);
+  silc_free(stream->dst_id);
+
+  silc_atomic_uninit8(&stream->refcnt);
   silc_mutex_free(stream->lock);
   silc_free(stream);
 }
 
+/* Return TRUE if the stream is valid */
+
+SilcBool silc_packet_stream_is_valid(SilcPacketStream stream)
+{
+  return stream->destroyed == FALSE;
+}
+
 /* Marks as router stream */
 
 void silc_packet_stream_set_router(SilcPacketStream stream)
@@ -501,6 +1030,7 @@ static SilcBool silc_packet_stream_link_va(SilcPacketStream stream,
     stream->process = silc_dlist_init();
     if (!stream->process) {
       silc_mutex_unlock(stream->lock);
+      silc_free(p);
       return FALSE;
     }
   }
@@ -583,6 +1113,7 @@ void silc_packet_stream_unlink(SilcPacketStream stream,
     if (p->callbacks == callbacks &&
        p->callback_context == callback_context) {
       silc_dlist_del(stream->process, p);
+      silc_free(p->types);
       silc_free(p);
       break;
     }
@@ -597,31 +1128,56 @@ void silc_packet_stream_unlink(SilcPacketStream stream,
   silc_packet_stream_unref(stream);
 }
 
+/* Returns TRUE if stream is UDP stream */
+
+SilcBool silc_packet_stream_is_udp(SilcPacketStream stream)
+{
+  return stream->udp || silc_socket_stream_is_udp(stream->stream, NULL);
+}
+
+/* Return packet sender IP and port for UDP packet stream */
+
+SilcBool silc_packet_get_sender(SilcPacket packet,
+                               const char **sender_ip,
+                               SilcUInt16 *sender_port)
+{
+  if (!packet->stream->remote_udp)
+    return FALSE;
+
+  *sender_ip = packet->stream->remote_udp->remote_ip;
+  *sender_port = packet->stream->remote_udp->remote_port;
+
+  return TRUE;
+}
+
 /* Reference packet stream */
 
 void silc_packet_stream_ref(SilcPacketStream stream)
 {
-  silc_mutex_lock(stream->lock);
-  stream->refcnt++;
-  silc_mutex_unlock(stream->lock);
+  silc_atomic_add_int8(&stream->refcnt, 1);
+  SILC_LOG_DEBUG(("Stream %p, refcnt %d->%d", stream,
+                 silc_atomic_get_int8(&stream->refcnt) - 1,
+                 silc_atomic_get_int8(&stream->refcnt)));
 }
 
 /* Unreference packet stream */
 
 void silc_packet_stream_unref(SilcPacketStream stream)
 {
-  silc_mutex_lock(stream->lock);
-  stream->refcnt--;
-  silc_mutex_unlock(stream->lock);
-  if (stream->refcnt == 0)
-    silc_packet_stream_destroy(stream);
+  SILC_LOG_DEBUG(("Stream %p, refcnt %d->%d", stream,
+                 silc_atomic_get_int8(&stream->refcnt),
+                 silc_atomic_get_int8(&stream->refcnt) - 1));
+  if (silc_atomic_sub_int8(&stream->refcnt, 1) > 0)
+    return;
+  silc_atomic_add_int8(&stream->refcnt, 1);
+  silc_packet_stream_destroy(stream);
 }
 
 /* Return engine */
 
 SilcPacketEngine silc_packet_get_engine(SilcPacketStream stream)
 {
-  return stream->engine;
+  return stream->sc->engine;
 }
 
 /* Set application context for packet stream */
@@ -644,71 +1200,116 @@ void *silc_packet_get_context(SilcPacketStream stream)
   return context;
 }
 
-/* Return underlaying stream */
+/* Change underlaying stream */
 
-SilcStream silc_packet_stream_get_stream(SilcPacketStream stream)
+void silc_packet_stream_set_stream(SilcPacketStream ps,
+                                  SilcStream stream)
 {
-  return stream->stream;
+  if (ps->stream)
+    silc_stream_set_notifier(ps->stream, ps->sc->schedule, NULL, NULL);
+  ps->stream = stream;
+  silc_stream_set_notifier(ps->stream, ps->sc->schedule, silc_packet_stream_io,
+                          ps);
 }
 
-/* Set ciphers for packet stream */
+/* Return underlaying stream */
 
-void silc_packet_set_ciphers(SilcPacketStream stream, SilcCipher send,
-                            SilcCipher receive)
+SilcStream silc_packet_stream_get_stream(SilcPacketStream stream)
 {
-  SILC_LOG_DEBUG(("Setting new ciphers to packet stream"));
-  silc_mutex_lock(stream->lock);
-  stream->send_key = send;
-  stream->receive_key = receive;
-  silc_mutex_unlock(stream->lock);
+  return stream->stream;
 }
 
-/* Return current ciphers from packet stream */
+/* Set keys. */
 
-SilcBool silc_packet_get_ciphers(SilcPacketStream stream, SilcCipher *send,
-                                SilcCipher *receive)
+SilcBool silc_packet_set_keys(SilcPacketStream stream, SilcCipher send_key,
+                              SilcCipher receive_key, SilcHmac send_hmac,
+                              SilcHmac receive_hmac, SilcBool rekey)
 {
-  if (!stream->send_key && !stream->receive_key)
-    return FALSE;
-
-  silc_mutex_lock(stream->lock);
-
-  if (send)
-    *send = stream->send_key;
-  if (receive)
-    *receive = stream->receive_key;
+  SILC_LOG_DEBUG(("Setting new keys to packet stream %p", stream));
+
+  /* If doing rekey, send REKEY_DONE packet */
+  if (rekey) {
+    /* This will take stream lock. */
+    if (!silc_packet_send_raw(stream, SILC_PACKET_REKEY_DONE, 0,
+                             stream->src_id_type, stream->src_id,
+                             stream->src_id_len, stream->dst_id_type,
+                             stream->dst_id, stream->dst_id_len,
+                             NULL, 0, stream->send_key[0],
+                             stream->send_hmac[0]))
+      return FALSE;
 
-  silc_mutex_unlock(stream->lock);
+    /* Write the packet to the stream */
+    if (!silc_packet_stream_write(stream, TRUE))
+      return FALSE;
+  } else {
+    silc_mutex_lock(stream->lock);
+  }
 
-  return TRUE;
-}
+  /* In case IV Included is set, save the old keys */
+  if (stream->iv_included) {
+    if (stream->send_key[1] && send_key) {
+      silc_cipher_free(stream->send_key[1]);
+      stream->send_key[1] = stream->send_key[0];
+    }
+    if (stream->receive_key[1] && receive_key) {
+      silc_cipher_free(stream->receive_key[1]);
+      stream->receive_key[1] = stream->receive_key[0];
+    }
+    if (stream->send_hmac[1] && send_hmac) {
+      silc_hmac_free(stream->send_hmac[1]);
+      stream->send_hmac[1] = stream->send_hmac[0];
+    }
+    if (stream->receive_hmac[1] && receive_hmac) {
+      silc_hmac_free(stream->receive_hmac[1]);
+      stream->receive_hmac[1] = stream->receive_hmac[0];
+    }
+  } else {
+    if (stream->send_key[0] && send_key)
+      silc_cipher_free(stream->send_key[0]);
+    if (stream->receive_key[0] && receive_key)
+      silc_cipher_free(stream->receive_key[0]);
+    if (stream->send_hmac[0] && send_hmac)
+      silc_hmac_free(stream->send_hmac[0]);
+    if (stream->receive_hmac[0] && receive_hmac)
+      silc_hmac_free(stream->receive_hmac[0]);
+  }
 
-/* Set HMACs for packet stream */
+  /* Set keys */
+  if (send_key)
+    stream->send_key[0] = send_key;
+  if (receive_key)
+    stream->receive_key[0] = receive_key;
+  if (send_hmac)
+    stream->send_hmac[0] = send_hmac;
+  if (receive_hmac)
+    stream->receive_hmac[0] = receive_hmac;
 
-void silc_packet_set_hmacs(SilcPacketStream stream, SilcHmac send,
-                          SilcHmac receive)
-{
-  SILC_LOG_DEBUG(("Setting new HMACs to packet stream"));
-  silc_mutex_lock(stream->lock);
-  stream->send_hmac = send;
-  stream->receive_hmac = receive;
   silc_mutex_unlock(stream->lock);
+  return TRUE;
 }
 
-/* Return current HMACs from packet stream */
+/* Return current ciphers from packet stream */
 
-SilcBool silc_packet_get_hmacs(SilcPacketStream stream, SilcHmac *send,
-                              SilcHmac *receive)
+SilcBool silc_packet_get_keys(SilcPacketStream stream,
+                             SilcCipher *send_key,
+                             SilcCipher *receive_key,
+                             SilcHmac *send_hmac,
+                             SilcHmac *receive_hmac)
 {
-  if (!stream->send_hmac && !stream->receive_hmac)
+  if (!stream->send_key[0] && !stream->receive_key[0] &&
+      !stream->send_hmac[0] && !stream->receive_hmac[0])
     return FALSE;
 
   silc_mutex_lock(stream->lock);
 
-  if (send)
-    *send = stream->send_hmac;
-  if (receive)
-    *receive = stream->receive_hmac;
+  if (send_key)
+    *send_key = stream->send_key[0];
+  if (receive_key)
+    *receive_key = stream->receive_key[0];
+  if (send_hmac)
+    *send_hmac = stream->send_hmac[0];
+  if (receive_hmac)
+    *receive_hmac = stream->receive_hmac[0];
 
   silc_mutex_unlock(stream->lock);
 
@@ -727,12 +1328,13 @@ SilcBool silc_packet_set_ids(SilcPacketStream stream,
   if (!src_id && !dst_id)
     return FALSE;
 
-  SILC_LOG_DEBUG(("Setting new IDs to packet stream"));
-
   silc_mutex_lock(stream->lock);
 
   if (src_id) {
+    SILC_LOG_DEBUG(("Setting source ID to packet stream %p", stream));
+
     silc_free(stream->src_id);
+    stream->src_id = NULL;
     if (!silc_id_id2str(src_id, src_id_type, tmp, sizeof(tmp), &len)) {
       silc_mutex_unlock(stream->lock);
       return FALSE;
@@ -747,7 +1349,10 @@ SilcBool silc_packet_set_ids(SilcPacketStream stream,
   }
 
   if (dst_id) {
+    SILC_LOG_DEBUG(("Setting destination ID to packet stream %p", stream));
+
     silc_free(stream->dst_id);
+    stream->dst_id = NULL;
     if (!silc_id_id2str(dst_id, dst_id_type, tmp, sizeof(tmp), &len)) {
       silc_mutex_unlock(stream->lock);
       return FALSE;
@@ -761,8 +1366,46 @@ SilcBool silc_packet_set_ids(SilcPacketStream stream,
     stream->dst_id_len = len;
   }
 
-  silc_mutex_unlock(stream->lock);
+  silc_mutex_unlock(stream->lock);
+
+  return TRUE;
+}
+
+/* Return IDs from the packet stream */
+
+SilcBool silc_packet_get_ids(SilcPacketStream stream,
+                            SilcBool *src_id_set, SilcID *src_id,
+                            SilcBool *dst_id_set, SilcID *dst_id)
+{
+  if (src_id && stream->src_id)
+    if (!silc_id_str2id2(stream->src_id, stream->src_id_len,
+                        stream->src_id_type, src_id))
+      return FALSE;
+
+  if (stream->src_id && src_id_set)
+    *src_id_set = TRUE;
+
+  if (dst_id && stream->dst_id)
+    if (!silc_id_str2id2(stream->dst_id, stream->dst_id_len,
+                        stream->dst_id_type, dst_id))
+      return FALSE;
+
+  if (stream->dst_id && dst_id_set)
+    *dst_id_set = TRUE;
+
+  return TRUE;
+}
+
+/* Adds Security ID (SID) */
+
+SilcBool silc_packet_set_sid(SilcPacketStream stream, SilcUInt8 sid)
+{
+  if (!stream->iv_included)
+    return FALSE;
+
+  SILC_LOG_DEBUG(("Set packet stream %p SID to %d", stream, sid));
 
+  stream->sid = sid;
   return TRUE;
 }
 
@@ -774,21 +1417,21 @@ void silc_packet_free(SilcPacket packet)
 
   SILC_LOG_DEBUG(("Freeing packet %p", packet));
 
-#if defined(SILC_DEBUG)
   /* Check for double free */
-  assert(packet->stream != NULL);
-#endif /* SILC_DEBUG */
-
-  silc_mutex_lock(stream->engine->lock);
+  SILC_ASSERT(packet->stream != NULL);
 
   packet->stream = NULL;
   packet->src_id = packet->dst_id = NULL;
   silc_buffer_reset(&packet->buffer);
 
+  silc_mutex_lock(stream->sc->engine->lock);
+
   /* Put the packet back to freelist */
-  silc_list_add(stream->engine->packet_pool, packet);
+  silc_list_add(stream->sc->engine->packet_pool, packet);
+  if (silc_list_count(stream->sc->engine->packet_pool) == 1)
+    silc_list_start(stream->sc->engine->packet_pool);
 
-  silc_mutex_unlock(stream->engine->lock);
+  silc_mutex_unlock(stream->sc->engine->lock);
 }
 
 /****************************** Packet Sending ******************************/
@@ -796,10 +1439,10 @@ void silc_packet_free(SilcPacket packet)
 /* Prepare outgoing data buffer for packet sending.  Returns the
    pointer to that buffer into the `packet'. */
 
-static SilcBool silc_packet_send_prepare(SilcPacketStream stream,
-                                        SilcUInt32 totlen,
-                                        SilcHmac hmac,
-                                        SilcBuffer packet)
+static inline SilcBool silc_packet_send_prepare(SilcPacketStream stream,
+                                               SilcUInt32 totlen,
+                                               SilcHmac hmac,
+                                               SilcBuffer packet)
 {
   unsigned char *oldptr;
   unsigned int mac_len = hmac ? silc_hmac_len(hmac) : 0;
@@ -807,7 +1450,7 @@ static SilcBool silc_packet_send_prepare(SilcPacketStream stream,
   totlen += mac_len;
 
   /* Allocate more space if needed */
-  if (silc_buffer_taillen(&stream->outbuf) < totlen) {
+  if (silc_unlikely(silc_buffer_taillen(&stream->outbuf) < totlen)) {
     if (!silc_buffer_realloc(&stream->outbuf,
                             silc_buffer_truelen(&stream->outbuf) + totlen))
       return FALSE;
@@ -823,28 +1466,73 @@ static SilcBool silc_packet_send_prepare(SilcPacketStream stream,
   return TRUE;
 }
 
-/* Internal routine to send packet */
-
-static SilcBool silc_packet_send_raw(SilcPacketStream stream,
-                                    SilcPacketType type,
-                                    SilcPacketFlags flags,
-                                    SilcIdType src_id_type,
-                                    unsigned char *src_id,
-                                    SilcUInt32 src_id_len,
-                                    SilcIdType dst_id_type,
-                                    unsigned char *dst_id,
-                                    SilcUInt32 dst_id_len,
-                                    const unsigned char *data,
-                                    SilcUInt32 data_len,
-                                    SilcCipher cipher,
-                                    SilcHmac hmac)
-{
-  unsigned char tmppad[SILC_PACKET_MAX_PADLEN], iv[32], psn[4];
+/* Increments counter when encrypting in counter mode. */
+
+static inline void silc_packet_send_ctr_increment(SilcPacketStream stream,
+                                                 SilcCipher cipher,
+                                                 unsigned char *ret_iv)
+{
+  unsigned char *iv = silc_cipher_get_iv(cipher);
+  SilcUInt32 pc1, pc2;
+
+  /* Reset block counter */
+  memset(iv + 12, 0, 4);
+
+  /* If IV Included flag, return the 64-bit IV for inclusion in packet */
+  if (stream->iv_included) {
+    /* Get new nonce */
+    ret_iv[0] = silc_rng_get_byte_fast(stream->sc->engine->rng);
+    ret_iv[1] = ret_iv[0] + iv[4];
+    ret_iv[2] = ret_iv[0] ^ ret_iv[1];
+    ret_iv[3] = ret_iv[0] + ret_iv[2];
+
+    /* Increment 32-bit packet counter */
+    SILC_GET32_MSB(pc1, iv + 8);
+    pc1++;
+    SILC_PUT32_MSB(pc1, ret_iv + 4);
+
+    SILC_LOG_HEXDUMP(("IV"), ret_iv, 8);
+
+    /* Set new nonce to counter block */
+    memcpy(iv + 4, ret_iv, 8);
+  } else {
+    /* Increment 64-bit packet counter */
+    SILC_GET32_MSB(pc1, iv + 4);
+    SILC_GET32_MSB(pc2, iv + 8);
+    if (++pc2 == 0)
+      ++pc1;
+    SILC_PUT32_MSB(pc1, iv + 4);
+    SILC_PUT32_MSB(pc2, iv + 8);
+  }
+
+  SILC_LOG_HEXDUMP(("Counter Block"), iv, 16);
+}
+
+/* Internal routine to assemble outgoing packet.  Assembles and encryptes
+   the packet.  The silc_packet_stream_write needs to be called to send it
+   after this returns TRUE. */
+
+static inline SilcBool silc_packet_send_raw(SilcPacketStream stream,
+                                           SilcPacketType type,
+                                           SilcPacketFlags flags,
+                                           SilcIdType src_id_type,
+                                           unsigned char *src_id,
+                                           SilcUInt32 src_id_len,
+                                           SilcIdType dst_id_type,
+                                           unsigned char *dst_id,
+                                           SilcUInt32 dst_id_len,
+                                           const unsigned char *data,
+                                           SilcUInt32 data_len,
+                                           SilcCipher cipher,
+                                           SilcHmac hmac)
+{
+  unsigned char tmppad[SILC_PACKET_MAX_PADLEN], iv[33], psn[4];
   int block_len = (cipher ? silc_cipher_get_block_len(cipher) : 0);
-  int i, enclen, truelen, padlen, ivlen = 0, psnlen = 0;
+  int i, enclen, truelen, padlen = 0, ivlen = 0, psnlen = 0;
+  SilcBool ctr;
   SilcBufferStruct packet;
 
-  SILC_LOG_DEBUG(("Sending packet %s (%d) flags %d, src %d dst %d,"
+  SILC_LOG_DEBUG(("Sending packet %s (%d) flags %d, src %d dst %d, "
                  "data len %d", silc_get_packet_name(type), stream->send_psn,
                  flags, src_id_type, dst_id_type, data_len));
 
@@ -856,34 +1544,65 @@ static SilcBool silc_packet_send_raw(SilcPacketStream stream,
   enclen = truelen = (data_len + SILC_PACKET_HEADER_LEN +
                      src_id_len + dst_id_len);
 
-  /* If IV is included, the IV and sequence number is added to packet */
-  if (stream->iv_included && cipher) {
-    ivlen = block_len;
-    psnlen = sizeof(psn);
-    memcpy(iv, silc_cipher_get_iv(cipher), block_len);
+  /* If using CTR mode, increment the counter */
+  ctr = (cipher && silc_cipher_get_mode(cipher) == SILC_CIPHER_MODE_CTR);
+  if (ctr) {
+    silc_packet_send_ctr_increment(stream, cipher, iv + 1);
+
+    /* If IV is included, the SID, IV and sequence number is added to packet */
+    if (stream->iv_included && cipher) {
+      psnlen = sizeof(psn);
+      ivlen = 8 + 1;
+      iv[0] = stream->sid;
+    }
+  } else {
+    /* If IV is included, the SID, IV and sequence number is added to packet */
+    if (stream->iv_included && cipher) {
+      psnlen = sizeof(psn);
+      ivlen = block_len + 1;
+      iv[0] = stream->sid;
+      memcpy(iv + 1, silc_cipher_get_iv(cipher), block_len);
+    }
   }
 
   /* We automatically figure out the packet structure from the packet
      type and flags, and calculate correct length.  Private messages with
      private keys and channel messages are special packets as their
      payload is encrypted already. */
-  if ((type == SILC_PACKET_PRIVATE_MESSAGE &&
-       flags & SILC_PACKET_FLAG_PRIVMSG_KEY) ||
-      type == SILC_PACKET_CHANNEL_MESSAGE) {
-
+  if (type == SILC_PACKET_PRIVATE_MESSAGE &&
+      flags & SILC_PACKET_FLAG_PRIVMSG_KEY) {
     /* Padding is calculated from header + IDs */
-    SILC_PACKET_PADLEN((SILC_PACKET_HEADER_LEN + src_id_len + dst_id_len +
-                       psnlen), block_len, padlen);
+    if (!ctr)
+      SILC_PACKET_PADLEN((SILC_PACKET_HEADER_LEN + src_id_len + dst_id_len +
+                         psnlen), block_len, padlen);
 
     /* Length to encrypt, header + IDs + padding. */
     enclen = (SILC_PACKET_HEADER_LEN + src_id_len + dst_id_len +
              padlen + psnlen);
-  } else {
 
+  } else if (type == SILC_PACKET_CHANNEL_MESSAGE) {
+    if (stream->sc->engine->local_is_router && stream->is_router) {
+      /* Channel messages between routers are encrypted as normal packets.
+        Padding is calculated from true length of the packet. */
+      if (!ctr)
+       SILC_PACKET_PADLEN(truelen + psnlen, block_len, padlen);
+
+      enclen += padlen + psnlen;
+    } else {
+      /* Padding is calculated from header + IDs */
+      if (!ctr)
+       SILC_PACKET_PADLEN((SILC_PACKET_HEADER_LEN + src_id_len + dst_id_len +
+                           psnlen), block_len, padlen);
+
+      /* Length to encrypt, header + IDs + padding. */
+      enclen = (SILC_PACKET_HEADER_LEN + src_id_len + dst_id_len +
+               padlen + psnlen);
+    }
+  } else {
     /* Padding is calculated from true length of the packet */
     if (flags & SILC_PACKET_FLAG_LONG_PAD)
       SILC_PACKET_PADLEN_MAX(truelen + psnlen, block_len, padlen);
-    else
+    else if (!ctr)
       SILC_PACKET_PADLEN(truelen + psnlen, block_len, padlen);
 
     enclen += padlen + psnlen;
@@ -894,13 +1613,13 @@ static SilcBool silc_packet_send_raw(SilcPacketStream stream,
 
   /* Get random padding */
   for (i = 0; i < padlen; i++) tmppad[i] =
-                                silc_rng_get_byte_fast(stream->engine->rng);
+    silc_rng_get_byte_fast(stream->sc->engine->rng);
 
   silc_mutex_lock(stream->lock);
 
   /* Get packet pointer from the outgoing buffer */
-  if (!silc_packet_send_prepare(stream, truelen + padlen + ivlen + psnlen,
-                               hmac, &packet)) {
+  if (silc_unlikely(!silc_packet_send_prepare(stream, truelen + padlen + ivlen
+                                             + psnlen, hmac, &packet))) {
     silc_mutex_unlock(stream->lock);
     return FALSE;
   }
@@ -910,8 +1629,8 @@ static SilcBool silc_packet_send_raw(SilcPacketStream stream,
   /* Create the packet.  This creates the SILC header, adds padding, and
      the actual packet data. */
   i = silc_buffer_format(&packet,
-                        SILC_STR_UI_XNSTRING(iv, ivlen),
-                        SILC_STR_UI_XNSTRING(psn, psnlen),
+                        SILC_STR_DATA(iv, ivlen),
+                        SILC_STR_DATA(psn, psnlen),
                         SILC_STR_UI_SHORT(truelen),
                         SILC_STR_UI_CHAR(flags),
                         SILC_STR_UI_CHAR(type),
@@ -920,13 +1639,13 @@ static SilcBool silc_packet_send_raw(SilcPacketStream stream,
                         SILC_STR_UI_CHAR(src_id_len),
                         SILC_STR_UI_CHAR(dst_id_len),
                         SILC_STR_UI_CHAR(src_id_type),
-                        SILC_STR_UI_XNSTRING(src_id, src_id_len),
+                        SILC_STR_DATA(src_id, src_id_len),
                         SILC_STR_UI_CHAR(dst_id_type),
-                        SILC_STR_UI_XNSTRING(dst_id, dst_id_len),
-                        SILC_STR_UI_XNSTRING(tmppad, padlen),
-                        SILC_STR_UI_XNSTRING(data, data_len),
+                        SILC_STR_DATA(dst_id, dst_id_len),
+                        SILC_STR_DATA(tmppad, padlen),
+                        SILC_STR_DATA(data, data_len),
                         SILC_STR_END);
-  if (i < 0) {
+  if (silc_unlikely(i < 0)) {
     silc_mutex_unlock(stream->lock);
     return FALSE;
   }
@@ -935,10 +1654,12 @@ static SilcBool silc_packet_send_raw(SilcPacketStream stream,
                   silc_buffer_data(&packet), silc_buffer_len(&packet));
 
   /* Encrypt the packet */
-  if (cipher) {
+  if (silc_likely(cipher)) {
     SILC_LOG_DEBUG(("Encrypting packet"));
-    if (!silc_cipher_encrypt(cipher, packet.data + ivlen,
-                            packet.data + ivlen, enclen, NULL)) {
+    silc_cipher_set_iv(cipher, NULL);
+    if (silc_unlikely(!silc_cipher_encrypt(cipher, packet.data + ivlen,
+                                          packet.data + ivlen, enclen,
+                                          NULL))) {
       SILC_LOG_ERROR(("Packet encryption failed"));
       silc_mutex_unlock(stream->lock);
       return FALSE;
@@ -946,7 +1667,7 @@ static SilcBool silc_packet_send_raw(SilcPacketStream stream,
   }
 
   /* Compute HMAC */
-  if (hmac) {
+  if (silc_likely(hmac)) {
     SilcUInt32 mac_len;
 
     /* MAC is computed from the entire encrypted packet data, and put
@@ -959,38 +1680,6 @@ static SilcBool silc_packet_send_raw(SilcPacketStream stream,
     stream->send_psn++;
   }
 
-  /* Write the packet to the stream */
-  while (silc_buffer_len(&stream->outbuf) > 0) {
-    i = silc_stream_write(stream->stream, stream->outbuf.data,
-                         silc_buffer_len(&stream->outbuf));
-    if (i == 0) {
-      /* EOS */
-      silc_buffer_reset(&stream->outbuf);
-      silc_mutex_unlock(stream->lock);
-      SILC_PACKET_CALLBACK_EOS(stream);
-      return FALSE;
-    }
-
-    if (i == -2) {
-      /* Error */
-      silc_buffer_reset(&stream->outbuf);
-      silc_mutex_unlock(stream->lock);
-      SILC_PACKET_CALLBACK_ERROR(stream, SILC_PACKET_ERR_WRITE);
-      return FALSE;
-    }
-
-    if (i == -1) {
-      /* Cannot write now, write later. */
-      silc_mutex_unlock(stream->lock);
-      return TRUE;
-    }
-
-    /* Wrote data */
-    silc_buffer_pull(&stream->outbuf, i);
-  }
-  silc_buffer_reset(&stream->outbuf);
-
-  silc_mutex_unlock(stream->lock);
   return TRUE;
 }
 
@@ -1000,16 +1689,21 @@ SilcBool silc_packet_send(SilcPacketStream stream,
                          SilcPacketType type, SilcPacketFlags flags,
                          const unsigned char *data, SilcUInt32 data_len)
 {
-  return silc_packet_send_raw(stream, type, flags,
-                             stream->src_id_type,
-                             stream->src_id,
-                             stream->src_id_len,
-                             stream->dst_id_type,
-                             stream->dst_id,
-                             stream->dst_id_len,
-                             data, data_len,
-                             stream->send_key,
-                             stream->send_hmac);
+  SilcBool ret;
+
+  ret = silc_packet_send_raw(stream, type, flags,
+                            stream->src_id_type,
+                            stream->src_id,
+                            stream->src_id_len,
+                            stream->dst_id_type,
+                            stream->dst_id,
+                            stream->dst_id_len,
+                            data, data_len,
+                            stream->send_key[0],
+                            stream->send_hmac[0]);
+
+  /* Write the packet to the stream */
+  return ret ? silc_packet_stream_write(stream, FALSE) : FALSE;
 }
 
 /* Sends a packet, extended routine */
@@ -1023,6 +1717,7 @@ SilcBool silc_packet_send_ext(SilcPacketStream stream,
 {
   unsigned char src_id_data[32], dst_id_data[32];
   SilcUInt32 src_id_len, dst_id_len;
+  SilcBool ret;
 
   if (src_id)
     if (!silc_id_id2str(src_id, src_id_type, src_id_data,
@@ -1033,32 +1728,90 @@ SilcBool silc_packet_send_ext(SilcPacketStream stream,
                        sizeof(dst_id_data), &dst_id_len))
       return FALSE;
 
-  return silc_packet_send_raw(stream, type, flags,
-                             src_id ? src_id_type : stream->src_id_type,
-                             src_id ? src_id_data : stream->src_id,
-                             src_id ? src_id_len : stream->src_id_len,
-                             dst_id ? dst_id_type : stream->dst_id_type,
-                             dst_id ? dst_id_data : stream->dst_id,
-                             dst_id ? dst_id_len : stream->dst_id_len,
-                             data, data_len,
-                             cipher ? cipher : stream->send_key,
-                             hmac ? hmac : stream->send_hmac);
+  ret = silc_packet_send_raw(stream, type, flags,
+                            src_id ? src_id_type : stream->src_id_type,
+                            src_id ? src_id_data : stream->src_id,
+                            src_id ? src_id_len : stream->src_id_len,
+                            dst_id ? dst_id_type : stream->dst_id_type,
+                            dst_id ? dst_id_data : stream->dst_id,
+                            dst_id ? dst_id_len : stream->dst_id_len,
+                            data, data_len,
+                            cipher ? cipher : stream->send_key[0],
+                            hmac ? hmac : stream->send_hmac[0]);
+
+  /* Write the packet to the stream */
+  return ret ? silc_packet_stream_write(stream, FALSE) : FALSE;
+}
+
+/* Sends packet after formatting the arguments to buffer */
+
+SilcBool silc_packet_send_va(SilcPacketStream stream,
+                            SilcPacketType type, SilcPacketFlags flags, ...)
+{
+  SilcBufferStruct buf;
+  SilcBool ret;
+  va_list va;
+
+  va_start(va, flags);
+
+  memset(&buf, 0, sizeof(buf));
+  if (silc_buffer_format_vp(&buf, va) < 0) {
+    va_end(va);
+    return FALSE;
+  }
+
+  ret = silc_packet_send(stream, type, flags, silc_buffer_data(&buf),
+                        silc_buffer_len(&buf));
+
+  silc_buffer_purge(&buf);
+  va_end(va);
+
+  return ret;
 }
 
+/* Sends packet after formatting the arguments to buffer, extended routine */
+
+SilcBool silc_packet_send_va_ext(SilcPacketStream stream,
+                                SilcPacketType type, SilcPacketFlags flags,
+                                SilcIdType src_id_type, void *src_id,
+                                SilcIdType dst_id_type, void *dst_id,
+                                SilcCipher cipher, SilcHmac hmac, ...)
+{
+  SilcBufferStruct buf;
+  SilcBool ret;
+  va_list va;
+
+  va_start(va, hmac);
+
+  memset(&buf, 0, sizeof(buf));
+  if (silc_buffer_format_vp(&buf, va) < 0) {
+    va_end(va);
+    return FALSE;
+  }
+
+  ret = silc_packet_send_ext(stream, type, flags, src_id_type, src_id,
+                            dst_id_type, dst_id, silc_buffer_data(&buf),
+                            silc_buffer_len(&buf), cipher, hmac);
+
+  silc_buffer_purge(&buf);
+  va_end(va);
+
+  return TRUE;
+}
 
 /***************************** Packet Receiving *****************************/
 
 /* Checks MAC in the packet. Returns TRUE if MAC is Ok. */
 
-static SilcBool silc_packet_check_mac(SilcHmac hmac,
-                                     const unsigned char *data,
-                                     SilcUInt32 data_len,
-                                     const unsigned char *packet_mac,
-                                     const unsigned char *packet_seq,
-                                     SilcUInt32 sequence)
+static inline SilcBool silc_packet_check_mac(SilcHmac hmac,
+                                            const unsigned char *data,
+                                            SilcUInt32 data_len,
+                                            const unsigned char *packet_mac,
+                                            const unsigned char *packet_seq,
+                                            SilcUInt32 sequence)
 {
   /* Check MAC */
-  if (hmac) {
+  if (silc_likely(hmac)) {
     unsigned char mac[32], psn[4];
     SilcUInt32 mac_len;
 
@@ -1077,7 +1830,7 @@ static SilcBool silc_packet_check_mac(SilcHmac hmac,
     silc_hmac_final(hmac, mac, &mac_len);
 
     /* Compare the MAC's */
-    if (memcmp(packet_mac, mac, mac_len)) {
+    if (silc_unlikely(memcmp(packet_mac, mac, mac_len))) {
       SILC_LOG_DEBUG(("MAC failed"));
       return FALSE;
     }
@@ -1088,26 +1841,54 @@ static SilcBool silc_packet_check_mac(SilcHmac hmac,
   return TRUE;
 }
 
+/* Increments/sets counter when decrypting in counter mode. */
+
+static inline void silc_packet_receive_ctr_increment(SilcPacketStream stream,
+                                                    unsigned char *iv,
+                                                    unsigned char *packet_iv)
+{
+  SilcUInt32 pc1, pc2;
+
+  /* If IV Included flag, set the IV from packet to block counter. */
+  if (stream->iv_included) {
+    memcpy(iv + 4, packet_iv, 8);
+  } else {
+    /* Increment 64-bit packet counter. */
+    SILC_GET32_MSB(pc1, iv + 4);
+    SILC_GET32_MSB(pc2, iv + 8);
+    if (++pc2 == 0)
+      ++pc1;
+    SILC_PUT32_MSB(pc1, iv + 4);
+    SILC_PUT32_MSB(pc2, iv + 8);
+  }
+
+  /* Reset block counter */
+  memset(iv + 12, 0, 4);
+
+  SILC_LOG_HEXDUMP(("Counter Block"), iv, 16);
+}
+
 /* Decrypts SILC packet.  Handles both normal and special packet decryption.
    Return 0 when packet is normal and 1 when it it special, -1 on error. */
 
-static int silc_packet_decrypt(SilcCipher cipher, SilcHmac hmac,
-                              SilcUInt32 sequence, SilcBuffer buffer,
-                              SilcBool normal)
+static inline int silc_packet_decrypt(SilcCipher cipher, SilcHmac hmac,
+                                     SilcUInt32 sequence, SilcBuffer buffer,
+                                     SilcBool normal)
 {
   if (normal == TRUE) {
-    if (cipher) {
+    if (silc_likely(cipher)) {
       /* Decrypt rest of the packet */
       SILC_LOG_DEBUG(("Decrypting the packet"));
-      if (!silc_cipher_decrypt(cipher, buffer->data, buffer->data,
-                              silc_buffer_len(buffer), NULL))
+      if (silc_unlikely(!silc_cipher_decrypt(cipher, buffer->data,
+                                            buffer->data,
+                                            silc_buffer_len(buffer), NULL)))
        return -1;
     }
     return 0;
 
   } else {
     /* Decrypt rest of the header plus padding */
-    if (cipher) {
+    if (silc_likely(cipher)) {
       SilcUInt16 len;
       SilcUInt32 block_len = silc_cipher_get_block_len(cipher);
 
@@ -1121,13 +1902,13 @@ static int silc_packet_decrypt(SilcCipher cipher, SilcHmac hmac,
             block_len);
       silc_buffer_pull(buffer, block_len);
 
-      if (len > silc_buffer_len(buffer)) {
+      if (silc_unlikely(len > silc_buffer_len(buffer))) {
        SILC_LOG_ERROR(("Garbage in header of packet, bad packet length, "
                        "packet dropped"));
        return -1;
       }
-      if (!silc_cipher_decrypt(cipher, buffer->data, buffer->data,
-                              len, NULL))
+      if (silc_unlikely(!silc_cipher_decrypt(cipher, buffer->data,
+                                            buffer->data, len, NULL)))
        return -1;
     }
 
@@ -1139,52 +1920,59 @@ static int silc_packet_decrypt(SilcCipher cipher, SilcHmac hmac,
    parsed. The buffer sent must be already decrypted before calling this
    function. */
 
-static SilcBool silc_packet_parse(SilcPacket packet)
+static inline SilcBool silc_packet_parse(SilcPacket packet)
 {
   SilcBuffer buffer = &packet->buffer;
   SilcUInt8 padlen = (SilcUInt8)buffer->data[4];
   SilcUInt8 src_id_len, dst_id_len, src_id_type, dst_id_type;
-  int len, ret;
+  int ret;
 
   SILC_LOG_DEBUG(("Parsing incoming packet"));
 
   /* Parse the buffer.  This parses the SILC header of the packet. */
-  len = silc_buffer_unformat(buffer,
+  ret = silc_buffer_unformat(buffer,
+                            SILC_STR_ADVANCE,
                             SILC_STR_OFFSET(6),
                             SILC_STR_UI_CHAR(&src_id_len),
                             SILC_STR_UI_CHAR(&dst_id_len),
                             SILC_STR_UI_CHAR(&src_id_type),
                             SILC_STR_END);
-  if (len == -1) {
-    SILC_LOG_ERROR(("Malformed packet header, packet dropped"));
+  if (silc_unlikely(ret == -1)) {
+    if (!packet->stream->udp &&
+       !silc_socket_stream_is_udp(packet->stream->stream, NULL))
+      SILC_LOG_ERROR(("Malformed packet header, packet dropped"));
     return FALSE;
   }
 
-  if (src_id_len > SILC_PACKET_MAX_ID_LEN ||
-      dst_id_len > SILC_PACKET_MAX_ID_LEN) {
-    SILC_LOG_ERROR(("Bad ID lengths in packet (%d and %d)",
-                   packet->src_id_len, packet->dst_id_len));
+  if (silc_unlikely(src_id_len > SILC_PACKET_MAX_ID_LEN ||
+                   dst_id_len > SILC_PACKET_MAX_ID_LEN)) {
+    if (!packet->stream->udp &&
+       !silc_socket_stream_is_udp(packet->stream->stream, NULL))
+      SILC_LOG_ERROR(("Bad ID lengths in packet (%d and %d)",
+                     packet->src_id_len, packet->dst_id_len));
     return FALSE;
   }
 
   ret = silc_buffer_unformat(buffer,
-                            SILC_STR_OFFSET(len),
-                            SILC_STR_UI_XNSTRING(&packet->src_id,
-                                                 src_id_len),
+                            SILC_STR_ADVANCE,
+                            SILC_STR_DATA(&packet->src_id, src_id_len),
                             SILC_STR_UI_CHAR(&dst_id_type),
-                            SILC_STR_UI_XNSTRING(&packet->dst_id,
-                                                 dst_id_len),
+                            SILC_STR_DATA(&packet->dst_id, dst_id_len),
                             SILC_STR_OFFSET(padlen),
                             SILC_STR_END);
-  if (ret == -1) {
-    SILC_LOG_ERROR(("Malformed packet header, packet dropped"));
+  if (silc_unlikely(ret == -1)) {
+    if (!packet->stream->udp &&
+       !silc_socket_stream_is_udp(packet->stream->stream, NULL))
+      SILC_LOG_ERROR(("Malformed packet header, packet dropped"));
     return FALSE;
   }
 
-  if (src_id_type > SILC_ID_CHANNEL ||
-      dst_id_type > SILC_ID_CHANNEL) {
-    SILC_LOG_ERROR(("Bad ID types in packet (%d and %d)",
-                   src_id_type, dst_id_type));
+  if (silc_unlikely(src_id_type > SILC_ID_CHANNEL ||
+                   dst_id_type > SILC_ID_CHANNEL)) {
+    if (!packet->stream->udp &&
+       !silc_socket_stream_is_udp(packet->stream->stream, NULL))
+      SILC_LOG_ERROR(("Bad ID types in packet (%d and %d)",
+                     src_id_type, dst_id_type));
     return FALSE;
   }
 
@@ -1193,50 +1981,39 @@ static SilcBool silc_packet_parse(SilcPacket packet)
   packet->src_id_type = src_id_type;
   packet->dst_id_type = dst_id_type;
 
-  SILC_LOG_HEXDUMP(("Parsed packet, len %d", silc_buffer_len(buffer)),
-                  buffer->data, silc_buffer_len(buffer));
-
-  /* Pull SILC header and padding from packet to get the data payload */
-  silc_buffer_pull(buffer, SILC_PACKET_HEADER_LEN +
-                  packet->src_id_len + packet->dst_id_len + padlen);
+  SILC_LOG_HEXDUMP(("Parsed packet, len %d", silc_buffer_headlen(buffer) +
+                  silc_buffer_len(buffer)), buffer->head,
+                  silc_buffer_headlen(buffer) + silc_buffer_len(buffer));
 
-  SILC_LOG_DEBUG(("Incoming packet type: %d (%s)", packet->type,
-                 silc_get_packet_name(packet->type)));
+  SILC_LOG_DEBUG(("Incoming packet type: %d (%s), flags %d", packet->type,
+                 silc_get_packet_name(packet->type), packet->flags));
 
   return TRUE;
 }
 
-/* Dispatch packet to application.  Called with stream->lock locked. */
+/* Dispatch packet to application.  Called with stream->lock locked.
+   Returns FALSE if the stream was destroyed while dispatching a packet. */
 
-static void silc_packet_dispatch(SilcPacket packet)
+static SilcBool silc_packet_dispatch(SilcPacket packet)
 {
   SilcPacketStream stream = packet->stream;
   SilcPacketProcess p;
   SilcBool default_sent = FALSE;
   SilcPacketType *pt;
 
-  /* Parse the packet */
-  if (!silc_packet_parse(packet)) {
-    silc_mutex_unlock(packet->stream->lock);
-    SILC_PACKET_CALLBACK_ERROR(stream, SILC_PACKET_ERR_MALFORMED);
-    silc_mutex_lock(packet->stream->lock);
-    silc_packet_free(packet);
-    return;
-  }
-
   /* Dispatch packet to all packet processors that want it */
 
-  if (!stream->process) {
+  if (silc_likely(!stream->process)) {
     /* Send to default processor as no others exist */
     SILC_LOG_DEBUG(("Dispatching packet to default callbacks"));
-    silc_mutex_unlock(packet->stream->lock);
-    if (!stream->engine->callbacks->
-       packet_receive(stream->engine, stream, packet,
-                      stream->engine->callback_context,
-                      stream->stream_context))
+    silc_mutex_unlock(stream->lock);
+    if (silc_unlikely(!stream->sc->engine->callbacks->
+                     packet_receive(stream->sc->engine, stream, packet,
+                                    stream->sc->engine->callback_context,
+                                    stream->stream_context)))
       silc_packet_free(packet);
-    silc_mutex_lock(packet->stream->lock);
-    return;
+    silc_mutex_lock(stream->lock);
+    return stream->destroyed == FALSE;
   }
 
   silc_dlist_start(stream->process);
@@ -1247,43 +2024,43 @@ static void silc_packet_dispatch(SilcPacket packet)
     if (!default_sent && p->priority <= 0) {
       SILC_LOG_DEBUG(("Dispatching packet to default callbacks"));
       default_sent = TRUE;
-      silc_mutex_unlock(packet->stream->lock);
-      if (stream->engine->callbacks->
-         packet_receive(stream->engine, stream, packet,
-                        stream->engine->callback_context,
+      silc_mutex_unlock(stream->lock);
+      if (stream->sc->engine->callbacks->
+         packet_receive(stream->sc->engine, stream, packet,
+                        stream->sc->engine->callback_context,
                         stream->stream_context)) {
-       silc_mutex_lock(packet->stream->lock);
-       return;
+       silc_mutex_lock(stream->lock);
+       return stream->destroyed == FALSE;
       }
-      silc_mutex_lock(packet->stream->lock);
+      silc_mutex_lock(stream->lock);
     }
 
     /* Send to processor */
     if (!p->types) {
       /* Send all packet types */
       SILC_LOG_DEBUG(("Dispatching packet to %p callbacks", p->callbacks));
-      silc_mutex_unlock(packet->stream->lock);
-      if (p->callbacks->packet_receive(stream->engine, stream, packet,
+      silc_mutex_unlock(stream->lock);
+      if (p->callbacks->packet_receive(stream->sc->engine, stream, packet,
                                       p->callback_context,
                                       stream->stream_context)) {
-       silc_mutex_lock(packet->stream->lock);
-       return;
+       silc_mutex_lock(stream->lock);
+       return stream->destroyed == FALSE;
       }
-      silc_mutex_lock(packet->stream->lock);
+      silc_mutex_lock(stream->lock);
     } else {
       /* Send specific types */
       for (pt = p->types; *pt; pt++) {
        if (*pt != packet->type)
          continue;
        SILC_LOG_DEBUG(("Dispatching packet to %p callbacks", p->callbacks));
-       silc_mutex_unlock(packet->stream->lock);
-       if (p->callbacks->packet_receive(stream->engine, stream, packet,
+       silc_mutex_unlock(stream->lock);
+       if (p->callbacks->packet_receive(stream->sc->engine, stream, packet,
                                         p->callback_context,
                                         stream->stream_context)) {
-         silc_mutex_lock(packet->stream->lock);
-         return;
+         silc_mutex_lock(stream->lock);
+         return stream->destroyed == FALSE;
        }
-       silc_mutex_lock(packet->stream->lock);
+       silc_mutex_lock(stream->lock);
        break;
       }
     }
@@ -1292,19 +2069,20 @@ static void silc_packet_dispatch(SilcPacket packet)
   if (!default_sent) {
     /* Send to default processor as it has not been sent yet */
     SILC_LOG_DEBUG(("Dispatching packet to default callbacks"));
-    silc_mutex_unlock(packet->stream->lock);
-    if (stream->engine->callbacks->
-       packet_receive(stream->engine, stream, packet,
-                      stream->engine->callback_context,
+    silc_mutex_unlock(stream->lock);
+    if (stream->sc->engine->callbacks->
+       packet_receive(stream->sc->engine, stream, packet,
+                      stream->sc->engine->callback_context,
                       stream->stream_context)) {
-      silc_mutex_lock(packet->stream->lock);
-      return;
+      silc_mutex_lock(stream->lock);
+      return stream->destroyed == FALSE;
     }
-    silc_mutex_lock(packet->stream->lock);
+    silc_mutex_lock(stream->lock);
   }
 
   /* If we got here, no one wanted the packet, so drop it */
   silc_packet_free(packet);
+  return stream->destroyed == FALSE;
 }
 
 /* Process incoming data and parse packets.  Called with stream->lock
@@ -1312,44 +2090,96 @@ static void silc_packet_dispatch(SilcPacket packet)
 
 static void silc_packet_read_process(SilcPacketStream stream)
 {
+  SilcBuffer inbuf;
+  SilcCipher cipher;
+  SilcHmac hmac;
   SilcPacket packet;
+  SilcUInt8 sid;
   SilcUInt16 packetlen;
   SilcUInt32 paddedlen, mac_len, block_len, ivlen, psnlen;
   unsigned char tmp[SILC_PACKET_MIN_HEADER_LEN], *header;
   unsigned char iv[SILC_CIPHER_MAX_IV_SIZE], *packet_seq = NULL;
-  SilcBool normal = TRUE;
+  SilcBool normal;
   int ret;
 
+  /* Get inbuf.  If there is already some data for this stream in the buffer
+     we already have it.  Otherwise get the current one from list, it will
+     include the data. */
+  inbuf = stream->inbuf;
+  if (!inbuf) {
+    silc_dlist_start(stream->sc->inbufs);
+    inbuf = silc_dlist_get(stream->sc->inbufs);
+  }
+
   /* Parse the packets from the data */
-  while (silc_buffer_len(&stream->inbuf) > 0) {
+  while (silc_buffer_len(inbuf) > 0) {
     ivlen = psnlen = 0;
+    cipher = stream->receive_key[0];
+    hmac = stream->receive_hmac[0];
+    normal = FALSE;
 
-    if (silc_buffer_len(&stream->inbuf) <
-       stream->iv_included ? SILC_PACKET_MIN_HEADER_LEN :
-       SILC_PACKET_MIN_HEADER_LEN_IV) {
+    if (silc_unlikely(silc_buffer_len(inbuf) <
+                     (stream->iv_included ? SILC_PACKET_MIN_HEADER_LEN_IV :
+                      SILC_PACKET_MIN_HEADER_LEN))) {
       SILC_LOG_DEBUG(("Partial packet in queue, waiting for the rest"));
+      silc_dlist_del(stream->sc->inbufs, inbuf);
+      stream->inbuf = inbuf;
       return;
     }
 
-    if (stream->receive_hmac)
-      mac_len = silc_hmac_len(stream->receive_hmac);
+    if (silc_likely(hmac))
+      mac_len = silc_hmac_len(hmac);
     else
       mac_len = 0;
 
     /* Decrypt first block of the packet to get the length field out */
-    if (stream->receive_key) {
-      block_len = silc_cipher_get_block_len(stream->receive_key);
+    if (silc_likely(cipher)) {
+      block_len = silc_cipher_get_block_len(cipher);
 
       if (stream->iv_included) {
-       /* IV is included in the ciphertext */
-       memcpy(iv, stream->inbuf.data, block_len);
-       ivlen = block_len;
+       /* SID, IV and sequence number is included in the ciphertext */
+       sid = (SilcUInt8)inbuf->data[0];
+
+       if (silc_cipher_get_mode(cipher) == SILC_CIPHER_MODE_CTR) {
+         /* Set the CTR mode IV from packet to counter block */
+         memcpy(iv, silc_cipher_get_iv(cipher), block_len);
+         silc_packet_receive_ctr_increment(stream, iv, inbuf->data + 1);
+         ivlen = 8 + 1;
+       } else {
+         /* Get IV from packet */
+         memcpy(iv, inbuf->data + 1, block_len);
+         ivlen = block_len + 1;
+       }
        psnlen = 4;
-      } else
-       memcpy(iv, silc_cipher_get_iv(stream->receive_key), block_len);
 
-      silc_cipher_decrypt(stream->receive_key, stream->inbuf.data + ivlen,
-                         tmp, block_len, iv);
+       /* Check SID, and get correct decryption key */
+       if (sid != stream->sid) {
+         /* If SID is recent get the previous key and use it */
+         if (sid > 0 && stream->sid > 0 && stream->sid - 1 == sid &&
+             stream->receive_key[1] && !stream->receive_hmac[1]) {
+           cipher = stream->receive_key[1];
+           hmac = stream->receive_hmac[1];
+         } else {
+           /* The SID is unknown, drop rest of the data in buffer */
+           SILC_LOG_DEBUG(("Unknown Security ID %d in packet, expected %d",
+                           sid, stream->sid));
+           silc_mutex_unlock(stream->lock);
+           SILC_PACKET_CALLBACK_ERROR(stream, SILC_PACKET_ERR_UNKNOWN_SID);
+           silc_mutex_lock(stream->lock);
+           goto out;
+         }
+       }
+      } else {
+       memcpy(iv, silc_cipher_get_iv(cipher), block_len);
+
+       /* If using CTR mode, increment the counter */
+       if (silc_cipher_get_mode(cipher) == SILC_CIPHER_MODE_CTR)
+         silc_packet_receive_ctr_increment(stream, iv, NULL);
+      }
+
+      if (silc_cipher_get_mode(cipher) == SILC_CIPHER_MODE_CTR)
+       silc_cipher_set_iv(cipher, NULL);
+      silc_cipher_decrypt(cipher, inbuf->data + ivlen, tmp, block_len, iv);
 
       header = tmp;
       if (stream->iv_included) {
@@ -1358,58 +2188,61 @@ static void silc_packet_read_process(SilcPacketStream stream)
        header += 4;
       }
     } else {
+      /* Unencrypted packet */
       block_len = SILC_PACKET_MIN_HEADER_LEN;
-      header = stream->inbuf.data;
+      header = inbuf->data;
     }
 
     /* Get packet length and full packet length with padding */
     SILC_PACKET_LENGTH(header, packetlen, paddedlen);
 
     /* Sanity checks */
-    if (packetlen < SILC_PACKET_MIN_LEN) {
-      SILC_LOG_ERROR(("Received too short packet"));
+    if (silc_unlikely(packetlen < SILC_PACKET_MIN_LEN)) {
+      if (!stream->udp && !silc_socket_stream_is_udp(stream->stream, NULL))
+       SILC_LOG_ERROR(("Received too short packet"));
       silc_mutex_unlock(stream->lock);
       SILC_PACKET_CALLBACK_ERROR(stream, SILC_PACKET_ERR_MALFORMED);
       silc_mutex_lock(stream->lock);
       memset(tmp, 0, sizeof(tmp));
-      silc_buffer_reset(&stream->inbuf);
-      return;
+      goto out;
     }
 
-    if (silc_buffer_len(&stream->inbuf) < paddedlen + ivlen + mac_len) {
+    if (silc_buffer_len(inbuf) < paddedlen + ivlen + mac_len) {
       SILC_LOG_DEBUG(("Received partial packet, waiting for the rest "
                      "(%d bytes)",
-                     paddedlen + mac_len - silc_buffer_len(&stream->inbuf)));
+                     paddedlen + mac_len - silc_buffer_len(inbuf)));
       memset(tmp, 0, sizeof(tmp));
+      silc_dlist_del(stream->sc->inbufs, inbuf);
+      stream->inbuf = inbuf;
       return;
     }
 
     /* Check MAC of the packet */
-    if (!silc_packet_check_mac(stream->receive_hmac, stream->inbuf.data,
-                              paddedlen + ivlen,
-                              stream->inbuf.data + ivlen + paddedlen,
-                              packet_seq, stream->receive_psn)) {
+    if (silc_unlikely(!silc_packet_check_mac(hmac, inbuf->data,
+                                            paddedlen + ivlen,
+                                            inbuf->data + ivlen +
+                                            paddedlen, packet_seq,
+                                            stream->receive_psn))) {
       silc_mutex_unlock(stream->lock);
       SILC_PACKET_CALLBACK_ERROR(stream, SILC_PACKET_ERR_MAC_FAILED);
       silc_mutex_lock(stream->lock);
       memset(tmp, 0, sizeof(tmp));
-      silc_buffer_reset(&stream->inbuf);
-      return;
+      goto out;
     }
 
     /* Get packet */
-    packet = silc_packet_alloc(stream->engine);
-    if (!packet) {
+    packet = silc_packet_alloc(stream->sc->engine);
+    if (silc_unlikely(!packet)) {
       silc_mutex_unlock(stream->lock);
       SILC_PACKET_CALLBACK_ERROR(stream, SILC_PACKET_ERR_NO_MEMORY);
       silc_mutex_lock(stream->lock);
       memset(tmp, 0, sizeof(tmp));
-      silc_buffer_reset(&stream->inbuf);
-      return;
+      goto out;
     }
+    packet->stream = stream;
 
     /* Allocate more space to packet buffer, if needed */
-    if (silc_buffer_truelen(&packet->buffer) < paddedlen) {
+    if (silc_unlikely(silc_buffer_truelen(&packet->buffer) < paddedlen)) {
       if (!silc_buffer_realloc(&packet->buffer,
                               silc_buffer_truelen(&packet->buffer) +
                               (paddedlen -
@@ -1419,8 +2252,7 @@ static void silc_packet_read_process(SilcPacketStream stream)
        silc_mutex_lock(stream->lock);
        silc_packet_free(packet);
        memset(tmp, 0, sizeof(tmp));
-       silc_buffer_reset(&stream->inbuf);
-       return;
+       goto out;
       }
     }
 
@@ -1428,7 +2260,7 @@ static void silc_packet_read_process(SilcPacketStream stream)
     packet->flags = (SilcPacketFlags)header[2];
     packet->type = (SilcPacketType)header[3];
 
-    if (stream->engine->local_is_router) {
+    if (stream->sc->engine->local_is_router) {
       if (packet->type == SILC_PACKET_PRIVATE_MESSAGE &&
          (packet->flags & SILC_PACKET_FLAG_PRIVMSG_KEY))
        normal = FALSE;
@@ -1446,26 +2278,26 @@ static void silc_packet_read_process(SilcPacketStream stream)
 
     SILC_LOG_HEXDUMP(("Incoming packet (%d) len %d",
                      stream->receive_psn, paddedlen + ivlen + mac_len),
-                    stream->inbuf.data, paddedlen + ivlen + mac_len);
+                    inbuf->data, paddedlen + ivlen + mac_len);
 
     /* Put the decrypted part, and rest of the encrypted data, and decrypt */
     silc_buffer_pull_tail(&packet->buffer, paddedlen);
     silc_buffer_put(&packet->buffer, header, block_len - psnlen);
     silc_buffer_pull(&packet->buffer, block_len - psnlen);
-    silc_buffer_put(&packet->buffer, (stream->inbuf.data + ivlen +
+    silc_buffer_put(&packet->buffer, (inbuf->data + ivlen +
                                      psnlen + (block_len - psnlen)),
                    paddedlen - ivlen - psnlen - (block_len - psnlen));
-    if (stream->receive_key) {
-      silc_cipher_set_iv(stream->receive_key, iv);
-      ret = silc_packet_decrypt(stream->receive_key, stream->receive_hmac,
-                               stream->receive_psn, &packet->buffer, normal);
-      if (ret < 0) {
+    if (silc_likely(cipher)) {
+      silc_cipher_set_iv(cipher, iv);
+      ret = silc_packet_decrypt(cipher, hmac, stream->receive_psn,
+                               &packet->buffer, normal);
+      if (silc_unlikely(ret < 0)) {
        silc_mutex_unlock(stream->lock);
        SILC_PACKET_CALLBACK_ERROR(stream, SILC_PACKET_ERR_DECRYPTION_FAILED);
        silc_mutex_lock(stream->lock);
        silc_packet_free(packet);
        memset(tmp, 0, sizeof(tmp));
-       return;
+       goto out;
       }
 
       stream->receive_psn++;
@@ -1473,16 +2305,32 @@ static void silc_packet_read_process(SilcPacketStream stream)
     silc_buffer_push(&packet->buffer, block_len);
 
     /* Pull the packet from inbuf thus we'll get the next one in the inbuf. */
-    silc_buffer_pull(&stream->inbuf, paddedlen + mac_len);
+    silc_buffer_pull(inbuf, paddedlen + mac_len);
+
+    /* Parse the packet */
+    if (silc_unlikely(!silc_packet_parse(packet))) {
+      silc_mutex_unlock(stream->lock);
+      SILC_PACKET_CALLBACK_ERROR(stream, SILC_PACKET_ERR_MALFORMED);
+      silc_mutex_lock(stream->lock);
+      silc_packet_free(packet);
+      memset(tmp, 0, sizeof(tmp));
+      goto out;
+    }
 
     /* Dispatch the packet to application */
-    packet->stream = stream;
-    silc_packet_dispatch(packet);
+    if (!silc_packet_dispatch(packet))
+      break;
   }
 
-  silc_buffer_reset(&stream->inbuf);
-}
+ out:
+  /* Add inbuf back to free list, if we owned it. */
+  if (stream->inbuf) {
+    silc_dlist_add(stream->sc->inbufs, inbuf);
+    stream->inbuf = NULL;
+  }
 
+  silc_buffer_reset(inbuf);
+}
 
 /****************************** Packet Waiting ******************************/
 
@@ -1505,6 +2353,9 @@ typedef struct {
   SilcMutex wait_lock;
   SilcCond wait_cond;
   SilcList packet_queue;
+  unsigned char id[28];
+  unsigned int id_type     : 2;
+  unsigned int id_len      : 5;
   unsigned int stopped     : 1;
 } *SilcPacketWait;
 
@@ -1519,10 +2370,17 @@ silc_packet_wait_packet_receive(SilcPacketEngine engine,
 {
   SilcPacketWait pw = callback_context;
 
+  /* If source ID is specified check for it */
+  if (pw->id_len) {
+    if (pw->id_type != packet->src_id_type ||
+       memcmp(pw->id, packet->src_id, pw->id_len))
+      return FALSE;
+  }
+
   /* Signal the waiting thread for a new packet */
   silc_mutex_lock(pw->wait_lock);
 
-  if (pw->stopped) {
+  if (silc_unlikely(pw->stopped)) {
     silc_mutex_unlock(pw->wait_lock);
     return FALSE;
   }
@@ -1537,7 +2395,8 @@ silc_packet_wait_packet_receive(SilcPacketEngine engine,
 
 /* Initialize packet waiting */
 
-void *silc_packet_wait_init(SilcPacketStream stream, ...)
+void *silc_packet_wait_init(SilcPacketStream stream,
+                           const SilcID *source_id, ...)
 {
   SilcPacketWait pw;
   SilcBool ret;
@@ -1559,7 +2418,7 @@ void *silc_packet_wait_init(SilcPacketStream stream, ...)
   }
 
   /* Link to the packet stream for the requested packet types */
-  va_start(ap, stream);
+  va_start(ap, source_id);
   ret = silc_packet_stream_link_va(stream, &silc_packet_wait_cbs, pw,
                                   10000000, ap);
   va_end(ap);
@@ -1573,6 +2432,14 @@ void *silc_packet_wait_init(SilcPacketStream stream, ...)
   /* Initialize packet queue */
   silc_list_init(pw->packet_queue, struct SilcPacketStruct, next);
 
+  if (source_id) {
+    SilcUInt32 id_len;
+    silc_id_id2str(SILC_ID_GET_ID(*source_id), source_id->type, pw->id,
+                  sizeof(pw->id), &id_len);
+    pw->id_type = source_id->type;
+    pw->id_len = id_len;
+  }
+
   return (void *)pw;
 }
 
@@ -1588,6 +2455,7 @@ void silc_packet_wait_uninit(void *waiter, SilcPacketStream stream)
   pw->stopped = TRUE;
   silc_cond_broadcast(pw->wait_cond);
   silc_mutex_unlock(pw->wait_lock);
+  silc_thread_yield();
 
   /* Re-acquire lock and free resources */
   silc_mutex_lock(pw->wait_lock);
@@ -1615,7 +2483,7 @@ int silc_packet_wait(void *waiter, int timeout, SilcPacket *return_packet)
 
   /* Wait here until packet has arrived */
   while (silc_list_count(pw->packet_queue) == 0) {
-    if (pw->stopped) {
+    if (silc_unlikely(pw->stopped)) {
       silc_mutex_unlock(pw->wait_lock);
       return -1;
     }
@@ -1631,3 +2499,304 @@ int silc_packet_wait(void *waiter, int timeout, SilcPacket *return_packet)
 
   return ret == TRUE ? 1 : 0;
 }
+
+/************************** Packet Stream Wrapper ***************************/
+
+/* Packet stream wrapper receive callback */
+static SilcBool
+silc_packet_wrap_packet_receive(SilcPacketEngine engine,
+                               SilcPacketStream stream,
+                               SilcPacket packet,
+                               void *callback_context,
+                               void *stream_context);
+
+const SilcStreamOps silc_packet_stream_ops;
+
+/* Packet stream wrapper context */
+typedef struct {
+  const SilcStreamOps *ops;
+  SilcPacketStream stream;
+  SilcMutex lock;
+  void *waiter;                          /* Waiter context in blocking mode */
+  SilcPacketWrapCoder coder;
+  void *coder_context;
+  SilcBuffer encbuf;
+  SilcStreamNotifier callback;
+  void *context;
+  SilcList in_queue;
+  SilcPacketType type;
+  SilcPacketFlags flags;
+  unsigned int closed        : 1;
+  unsigned int blocking      : 1;
+  unsigned int read_more     : 1;
+} *SilcPacketWrapperStream;
+
+/* Packet wrapper callbacks */
+static SilcPacketCallbacks silc_packet_wrap_cbs =
+{
+  silc_packet_wrap_packet_receive, NULL, NULL
+};
+
+/* Packet stream wrapper receive callback, non-blocking mode */
+
+static SilcBool
+silc_packet_wrap_packet_receive(SilcPacketEngine engine,
+                               SilcPacketStream stream,
+                               SilcPacket packet,
+                               void *callback_context,
+                               void *stream_context)
+{
+  SilcPacketWrapperStream pws = callback_context;
+
+  if (pws->closed || !pws->callback)
+    return FALSE;
+
+  silc_mutex_lock(pws->lock);
+  silc_list_add(pws->in_queue, packet);
+  silc_mutex_unlock(pws->lock);
+
+  /* Call notifier callback */
+  pws->callback((SilcStream)pws, SILC_STREAM_CAN_READ, pws->context);
+
+  return TRUE;
+}
+
+/* Task callback to notify more data is available for reading */
+
+SILC_TASK_CALLBACK(silc_packet_wrap_read_more)
+{
+  SilcPacketWrapperStream pws = context;
+
+  if (pws->closed || !pws->callback)
+    return;
+
+  /* Call notifier callback */
+  pws->callback((SilcStream)pws, SILC_STREAM_CAN_READ, pws->context);
+}
+
+/* Read SILC packet */
+
+int silc_packet_wrap_read(SilcStream stream, unsigned char *buf,
+                         SilcUInt32 buf_len)
+{
+  SilcPacketWrapperStream pws = stream;
+  SilcPacket packet;
+  SilcBool read_more = FALSE;
+  int len;
+
+  if (pws->closed)
+    return -2;
+
+  if (pws->blocking) {
+    /* Block until packet is received */
+    if ((silc_packet_wait(pws->waiter, 0, &packet)) < 0)
+      return -2;
+    if (pws->closed)
+      return -2;
+  } else {
+    /* Non-blocking mode */
+    silc_mutex_lock(pws->lock);
+    if (!silc_list_count(pws->in_queue)) {
+      silc_mutex_unlock(pws->lock);
+      return -1;
+    }
+
+    silc_list_start(pws->in_queue);
+    packet = silc_list_get(pws->in_queue);
+    silc_list_del(pws->in_queue, packet);
+    silc_mutex_unlock(pws->lock);
+  }
+
+  /* Call decoder if set */
+  if (pws->coder && !pws->read_more)
+    pws->coder(stream, SILC_STREAM_CAN_READ, &packet->buffer,
+              pws->coder_context);
+
+  len = silc_buffer_len(&packet->buffer);
+  if (len > buf_len) {
+    len = buf_len;
+    read_more = TRUE;
+  }
+
+  /* Read data */
+  memcpy(buf, packet->buffer.data, len);
+
+  if (read_more && !pws->blocking) {
+    /* More data will be available (in blocking mode not supported). */
+    silc_buffer_pull(&packet->buffer, len);
+    silc_list_insert(pws->in_queue, NULL, packet);
+    silc_schedule_task_add_timeout(pws->stream->sc->schedule,
+                                  silc_packet_wrap_read_more, pws, 0, 0);
+    pws->read_more = TRUE;
+    return len;
+  }
+
+  pws->read_more = FALSE;
+  silc_packet_free(packet);
+  return len;
+}
+
+/* Write SILC packet */
+
+int silc_packet_wrap_write(SilcStream stream, const unsigned char *data,
+                          SilcUInt32 data_len)
+{
+  SilcPacketWrapperStream pws = stream;
+  SilcBool ret = FALSE;
+
+  /* Call encoder if set */
+  if (pws->coder) {
+    silc_buffer_reset(pws->encbuf);
+    ret = pws->coder(stream, SILC_STREAM_CAN_WRITE, pws->encbuf,
+                    pws->coder_context);
+  }
+
+  /* Send the SILC packet */
+  if (ret) {
+    if (!silc_packet_send_va(pws->stream, pws->type, pws->flags,
+                            SILC_STR_DATA(silc_buffer_data(pws->encbuf),
+                                          silc_buffer_len(pws->encbuf)),
+                            SILC_STR_DATA(data, data_len),
+                            SILC_STR_END))
+      return -2;
+  } else {
+    if (!silc_packet_send(pws->stream, pws->type, pws->flags, data, data_len))
+      return -2;
+  }
+
+  return data_len;
+}
+
+/* Close stream */
+
+SilcBool silc_packet_wrap_close(SilcStream stream)
+{
+  SilcPacketWrapperStream pws = stream;
+
+  if (pws->closed)
+    return TRUE;
+
+  if (pws->blocking) {
+    /* Close packet waiter */
+    silc_packet_wait_uninit(pws->waiter, pws->stream);
+  } else {
+    /* Unlink */
+    if (pws->callback)
+      silc_packet_stream_unlink(pws->stream, &silc_packet_wrap_cbs, pws);
+  }
+  pws->closed = TRUE;
+
+  return TRUE;
+}
+
+/* Destroy wrapper stream */
+
+void silc_packet_wrap_destroy(SilcStream stream)
+
+{
+  SilcPacketWrapperStream pws = stream;
+  SilcPacket packet;
+
+  SILC_LOG_DEBUG(("Destroying wrapped packet stream %p", pws));
+
+  silc_stream_close(stream);
+  silc_list_start(pws->in_queue);
+  while ((packet = silc_list_get(pws->in_queue)))
+    silc_packet_free(packet);
+  if (pws->lock)
+    silc_mutex_free(pws->lock);
+  if (pws->encbuf)
+    silc_buffer_free(pws->encbuf);
+  silc_packet_stream_unref(pws->stream);
+
+  silc_free(pws);
+}
+
+/* Link stream to receive packets */
+
+SilcBool silc_packet_wrap_notifier(SilcStream stream,
+                                  SilcSchedule schedule,
+                                  SilcStreamNotifier callback,
+                                  void *context)
+{
+  SilcPacketWrapperStream pws = stream;
+
+  if (pws->closed || pws->blocking)
+    return FALSE;
+
+  /* Link to receive packets */
+  if (callback)
+    silc_packet_stream_link(pws->stream, &silc_packet_wrap_cbs, pws,
+                           100000, pws->type, -1);
+  else
+    silc_packet_stream_unlink(pws->stream, &silc_packet_wrap_cbs, pws);
+
+  pws->callback = callback;
+  pws->context = context;
+
+  return TRUE;
+}
+
+/* Return schedule */
+
+SilcSchedule silc_packet_wrap_get_schedule(SilcStream stream)
+{
+  return NULL;
+}
+
+/* Wraps packet stream into SilcStream. */
+
+SilcStream silc_packet_stream_wrap(SilcPacketStream stream,
+                                   SilcPacketType type,
+                                   SilcPacketFlags flags,
+                                  SilcBool blocking_mode,
+                                  SilcPacketWrapCoder coder,
+                                  void *context)
+{
+  SilcPacketWrapperStream pws;
+
+  pws = silc_calloc(1, sizeof(*pws));
+  if (!pws)
+    return NULL;
+
+  SILC_LOG_DEBUG(("Wrapping packet stream %p to stream %p", stream, pws));
+
+  pws->ops = &silc_packet_stream_ops;
+  pws->stream = stream;
+  pws->type = type;
+  pws->flags = flags;
+  pws->blocking = blocking_mode;
+  pws->coder = coder;
+  pws->coder_context = context;
+
+  /* Allocate small amount for encoder buffer. */
+  if (pws->coder)
+    pws->encbuf = silc_buffer_alloc(8);
+
+  if (pws->blocking) {
+    /* Blocking mode.  Use packet waiter to do the thing. */
+    pws->waiter = silc_packet_wait_init(pws->stream, NULL, pws->type, -1);
+    if (!pws->waiter) {
+      silc_free(pws);
+      return NULL;
+    }
+  } else {
+    /* Non-blocking mode */
+    silc_mutex_alloc(&pws->lock);
+    silc_list_init(pws->in_queue, struct SilcPacketStruct, next);
+  }
+
+  silc_packet_stream_ref(stream);
+
+  return (SilcStream)pws;
+}
+
+const SilcStreamOps silc_packet_stream_ops =
+{
+  silc_packet_wrap_read,
+  silc_packet_wrap_write,
+  silc_packet_wrap_close,
+  silc_packet_wrap_destroy,
+  silc_packet_wrap_notifier,
+  silc_packet_wrap_get_schedule,
+};