diff src/protocols/sametime/meanwhile/channel.c @ 10969:3ef77720e577

[gaim-migrate @ 12790] importing meanwhile library for use in the sametime plugin committer: Tailor Script <tailor@pidgin.im>
author Christopher O'Brien <siege@pidgin.im>
date Sun, 05 Jun 2005 02:50:13 +0000
parents
children 0110fc7c6a8a
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/sametime/meanwhile/channel.c	Sun Jun 05 02:50:13 2005 +0000
@@ -0,0 +1,878 @@
+
+/*
+  Meanwhile - Unofficial Lotus Sametime Community Client Library
+  Copyright (C) 2004  Christopher (siege) O'Brien
+  
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Library General Public
+  License as published by the Free Software Foundation; either
+  version 2 of the License, or (at your option) any later version.
+  
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Library General Public License for more details.
+  
+  You should have received a copy of the GNU Library General Public
+  License along with this library; if not, write to the Free
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#include <glib.h>
+#include <glib/ghash.h>
+#include <glib/glist.h>
+#include <string.h>
+
+#include "mw_channel.h"
+#include "mw_cipher.h"
+#include "mw_debug.h"
+#include "mw_error.h"
+#include "mw_message.h"
+#include "mw_service.h"
+#include "mw_session.h"
+#include "mw_util.h"
+
+
+/** @todo reorganize this file, stuff is just strewn about */
+
+
+struct mwChannel {
+
+  /** session this channel belongs to */
+  struct mwSession *session;
+
+  enum mwChannelState state;
+
+  /** creator for incoming channel, target for outgoing channel */
+  struct mwLoginInfo user;
+
+  /* similar to data from the CreateCnl message in 8.4.1.7 */
+  guint32 reserved;    /**< special, unknown meaning */
+  guint32 id;          /**< channel ID */
+  guint32 service;     /**< service ID */
+  guint32 proto_type;  /**< service protocol type */
+  guint32 proto_ver;   /**< service protocol version */
+  guint32 options;     /**< channel options */
+
+  struct mwOpaque addtl_create;
+  struct mwOpaque addtl_accept;
+
+  /** all those supported ciphers */
+  GHashTable *supported;
+
+  /** cipher information determined at channel acceptance */
+  struct mwCipherInstance *cipher;
+
+  /** statistics table */
+  GHashTable *stats;
+
+  GSList *outgoing_queue;     /**< queued outgoing messages */
+  GSList *incoming_queue;     /**< queued incoming messages */
+
+  struct mw_datum srvc_data;  /**< service-specific data */
+};
+
+
+struct mwChannelSet {
+  struct mwSession *session;  /**< owning session */
+  GHashTable *map;            /**< map of all channels, by ID */
+  guint32 counter;            /**< counter for outgoing ID */
+};
+
+
+static void flush_channel(struct mwChannel *);
+
+
+static const char *state_str(enum mwChannelState state) {
+  switch(state) {
+  case mwChannel_NEW:      return "new";
+  case mwChannel_INIT:     return "initializing";
+  case mwChannel_WAIT:     return "waiting";
+  case mwChannel_OPEN:     return "open";
+  case mwChannel_DESTROY:  return "closing";
+  case mwChannel_ERROR:    return "error";
+
+  case mwChannel_UNKNOWN:  /* fall through */
+  default:                 return "UNKNOWN";
+  }
+}
+
+
+static void state(struct mwChannel *chan, enum mwChannelState state) {
+  g_return_if_fail(chan != NULL);
+
+  if(chan->state == state) return;
+
+  chan->state = state;
+  g_message("channel 0x%08x state: %s", chan->id, state_str(state));
+}
+
+
+static gpointer get_stat(struct mwChannel *chan,
+			 enum mwChannelStatField field) {
+
+  return g_hash_table_lookup(chan->stats, (gpointer) field);
+}
+
+
+static void set_stat(struct mwChannel *chan, enum mwChannelStatField field,
+		     gpointer val) {
+
+  g_hash_table_insert(chan->stats, (gpointer) field, val);
+}
+
+
+#define incr_stat(chan, field, incr) \
+  set_stat(chan, field, get_stat(chan, field) + incr)
+
+
+#define timestamp_stat(chan, field) \
+  set_stat(chan, field, (gpointer) time(NULL))
+
+
+static void sup_free(gpointer a) {
+  mwCipherInstance_free(a);
+}
+
+
+struct mwCipherInstance *get_supported(struct mwChannel *chan, guint16 id) {
+  guint32 cid = (guint32) id;
+  return g_hash_table_lookup(chan->supported, GUINT_TO_POINTER(cid));
+}
+
+
+void put_supported(struct mwChannel *chan, struct mwCipherInstance *ci) {
+  struct mwCipher *cipher = mwCipherInstance_getCipher(ci);
+  guint32 cid = (guint32) mwCipher_getType(cipher);
+  g_hash_table_insert(chan->supported, GUINT_TO_POINTER(cid), ci);
+}
+
+
+struct mwChannel *mwChannel_newIncoming(struct mwChannelSet *cs, guint32 id) {
+  struct mwChannel *chan;
+
+  g_return_val_if_fail(cs != NULL, NULL);
+  g_return_val_if_fail(cs->session != NULL, NULL);
+
+  chan = g_new0(struct mwChannel, 1);
+  chan->state = mwChannel_NEW;
+  chan->session = cs->session;
+  chan->id = id;
+
+  chan->stats = g_hash_table_new(g_direct_hash, g_direct_equal);
+
+  chan->supported = g_hash_table_new_full(g_direct_hash, g_direct_equal,
+					  NULL, sup_free);
+
+  g_hash_table_insert(cs->map, GUINT_TO_POINTER(id), chan);
+
+  state(chan, mwChannel_WAIT);
+
+  return chan;
+}
+
+
+struct mwChannel *mwChannel_newOutgoing(struct mwChannelSet *cs) {
+  guint32 id;
+  struct mwChannel *chan;
+
+  g_return_val_if_fail(cs != NULL, NULL);
+  g_return_val_if_fail(cs->map != NULL, NULL);
+
+  /* grab the next id, and try to make sure there isn't already a
+     channel using it */
+  do {
+    id = ++cs->counter;
+  } while(g_hash_table_lookup(cs->map, GUINT_TO_POINTER(id)));
+  
+  chan = mwChannel_newIncoming(cs, id);
+  state(chan, mwChannel_INIT);
+
+  return chan;
+}
+
+
+guint32 mwChannel_getId(struct mwChannel *chan) {
+  g_return_val_if_fail(chan != NULL, 0);
+  return chan->id;
+}
+
+
+struct mwSession *mwChannel_getSession(struct mwChannel *chan) {
+  g_return_val_if_fail(chan != NULL, NULL);
+  return chan->session;
+}
+
+
+guint32 mwChannel_getServiceId(struct mwChannel *chan) {
+  g_return_val_if_fail(chan != NULL, 0);
+  return chan->service;
+}
+
+
+struct mwService *mwChannel_getService(struct mwChannel *chan) {
+  g_return_val_if_fail(chan != NULL, NULL);
+  return mwSession_getService(chan->session, chan->service);
+}
+
+
+void mwChannel_setService(struct mwChannel *chan, struct mwService *srvc) {
+  g_return_if_fail(chan != NULL);
+  g_return_if_fail(srvc != NULL);
+  g_return_if_fail(chan->state == mwChannel_INIT);
+  chan->service = mwService_getType(srvc);
+}
+
+
+gpointer mwChannel_getServiceData(struct mwChannel *chan) {
+  g_return_val_if_fail(chan != NULL, NULL);
+  return mw_datum_get(&chan->srvc_data);
+}
+
+
+void mwChannel_setServiceData(struct mwChannel *chan,
+			      gpointer data, GDestroyNotify clean) {
+
+  g_return_if_fail(chan != NULL);
+  mw_datum_set(&chan->srvc_data, data, clean);
+}
+
+
+void mwChannel_removeServiceData(struct mwChannel *chan) {
+  g_return_if_fail(chan != NULL);
+  mw_datum_clear(&chan->srvc_data);
+}
+
+
+guint32 mwChannel_getProtoType(struct mwChannel *chan) {
+  g_return_val_if_fail(chan != NULL, 0x00);
+  return chan->proto_type;
+}
+
+
+void mwChannel_setProtoType(struct mwChannel *chan, guint32 proto_type) {
+  g_return_if_fail(chan != NULL);
+  g_return_if_fail(chan->state == mwChannel_INIT);
+  chan->proto_type = proto_type;
+}
+
+
+guint32 mwChannel_getProtoVer(struct mwChannel *chan) {
+  g_return_val_if_fail(chan != NULL, 0x00);
+  return chan->proto_ver;
+}
+
+
+void mwChannel_setProtoVer(struct mwChannel *chan, guint32 proto_ver) {
+  g_return_if_fail(chan != NULL);
+  g_return_if_fail(chan->state == mwChannel_INIT);
+  chan->proto_ver = proto_ver;
+}
+
+
+guint32 mwChannel_getOptions(struct mwChannel *chan) {
+  g_return_val_if_fail(chan != NULL, 0x00);
+  return chan->options;
+}
+
+
+void mwChannel_setOptions(struct mwChannel *chan, guint32 options) {
+  g_return_if_fail(chan != NULL);
+  g_return_if_fail(chan->state == mwChannel_INIT);
+  chan->options = options;
+}
+
+
+struct mwLoginInfo *mwChannel_getUser(struct mwChannel *chan) {
+  g_return_val_if_fail(chan != NULL, NULL);
+  return &chan->user;
+}
+
+
+struct mwOpaque *mwChannel_getAddtlCreate(struct mwChannel *chan) {
+  g_return_val_if_fail(chan != NULL, NULL);
+  return &chan->addtl_create;
+}
+
+
+struct mwOpaque *mwChannel_getAddtlAccept(struct mwChannel *chan) {
+  g_return_val_if_fail(chan != NULL, NULL);
+  return &chan->addtl_accept;
+}
+
+
+struct mwCipherInstance *mwChannel_getCipherInstance(struct mwChannel *chan) {
+  g_return_val_if_fail(chan != NULL, NULL);
+  return chan->cipher;
+}
+
+
+enum mwChannelState mwChannel_getState(struct mwChannel *chan) {
+  g_return_val_if_fail(chan != NULL, mwChannel_UNKNOWN);
+  return chan->state;
+}
+
+
+gpointer mwChannel_getStatistic(struct mwChannel *chan,
+				enum mwChannelStatField stat) {
+  
+  g_return_val_if_fail(chan != NULL, 0);
+  g_return_val_if_fail(chan->stats != NULL, 0);
+
+  return get_stat(chan, stat);
+}
+
+
+/* send a channel create message */
+int mwChannel_create(struct mwChannel *chan) {
+  struct mwMsgChannelCreate *msg;
+  GList *list, *l;
+  int ret;
+
+  g_return_val_if_fail(chan != NULL, -1);
+  g_return_val_if_fail(chan->state == mwChannel_INIT, -1);
+  g_return_val_if_fail(mwChannel_isOutgoing(chan), -1);
+
+  msg = (struct mwMsgChannelCreate *)
+    mwMessage_new(mwMessage_CHANNEL_CREATE);
+
+  msg->channel = chan->id;
+  msg->target.user = g_strdup(chan->user.user_id);
+  msg->target.community = g_strdup(chan->user.community);
+  msg->service = chan->service;
+  msg->proto_type = chan->proto_type;
+  msg->proto_ver = chan->proto_ver;
+  msg->options = chan->options;
+  mwOpaque_clone(&msg->addtl, &chan->addtl_create);
+
+  for(list = l = mwChannel_getSupportedCipherInstances(chan); l; l = l->next) {
+    struct mwEncryptItem *ei = mwCipherInstance_newItem(l->data);
+    msg->encrypt.items = g_list_append(msg->encrypt.items, ei);
+  }
+  if(list) {
+    msg->encrypt.mode = 0x1000;
+    msg->encrypt.extra = 0x1000;
+  }
+  g_list_free(list);
+  
+  ret = mwSession_send(chan->session, MW_MESSAGE(msg));
+  mwMessage_free(MW_MESSAGE(msg));
+
+  state(chan, (ret)? mwChannel_ERROR: mwChannel_WAIT);
+
+  return ret;
+}
+
+
+static void channel_open(struct mwChannel *chan) {
+  state(chan, mwChannel_OPEN);
+  timestamp_stat(chan, mwChannelStat_OPENED_AT);
+  flush_channel(chan);
+}
+
+
+int mwChannel_accept(struct mwChannel *chan) {
+  struct mwSession *session;
+  struct mwMsgChannelAccept *msg;
+  int ret;
+
+  g_return_val_if_fail(chan != NULL, -1);
+  g_return_val_if_fail(mwChannel_isIncoming(chan), -1);
+  g_return_val_if_fail(chan->state == mwChannel_WAIT, -1);
+
+  session = chan->session;
+  g_return_val_if_fail(session != NULL, -1);
+
+  msg = (struct mwMsgChannelAccept *)
+    mwMessage_new(mwMessage_CHANNEL_ACCEPT);
+
+  msg->head.channel = chan->id;
+  msg->service = chan->service;
+  msg->proto_type = chan->proto_type;
+  msg->proto_ver = chan->proto_ver;
+  mwOpaque_clone(&msg->addtl, &chan->addtl_accept);
+
+  /* if nobody selected a cipher, we'll just pick the first that comes
+     handy */
+  if(chan->supported) {
+    GList *l = mwChannel_getSupportedCipherInstances(chan);
+    if(l) {
+      mwChannel_selectCipherInstance(chan, l->data);
+      g_list_free(l);
+    } else {
+      mwChannel_selectCipherInstance(chan, NULL);
+    }
+  }
+
+  if(chan->cipher) {
+    mwCipherInstance_accept(chan->cipher);
+
+    msg->encrypt.item = mwCipherInstance_newItem(chan->cipher);
+
+    /** @todo figure out encrypt modes */
+    msg->encrypt.mode = 0x1000;
+    msg->encrypt.extra = 0x1000;
+  }
+
+  ret = mwSession_send(session, MW_MESSAGE(msg));
+  mwMessage_free(MW_MESSAGE(msg));
+
+  if(ret) {
+    state(chan, mwChannel_ERROR);
+  } else {
+    channel_open(chan);
+  }
+
+  return ret;
+}
+
+
+static void channel_free(struct mwChannel *chan) {
+  struct mwSession *s;
+  struct mwMessage *msg;
+  GSList *l;
+
+  /* maybe no warning in the future */
+  g_return_if_fail(chan != NULL);
+
+  s = chan->session;
+
+  mwLoginInfo_clear(&chan->user);
+  mwOpaque_clear(&chan->addtl_create);
+  mwOpaque_clear(&chan->addtl_accept);
+
+  if(chan->supported) {
+    g_hash_table_destroy(chan->supported);
+    chan->supported = NULL;
+  }
+
+  if(chan->stats) {
+    g_hash_table_destroy(chan->stats);
+    chan->stats = NULL;
+  }
+  
+  mwCipherInstance_free(chan->cipher);
+
+  /* clean up the outgoing queue */
+  for(l = chan->outgoing_queue; l; l = l->next) {
+    msg = (struct mwMessage *) l->data;
+    l->data = NULL;
+    mwMessage_free(msg);
+  }
+  g_slist_free(chan->outgoing_queue);
+
+  /* clean up the incoming queue */
+  for(l = chan->incoming_queue; l; l = l->next) {
+    msg = (struct mwMessage *) l->data;
+    l->data = NULL;
+    mwMessage_free(msg);
+  }
+  g_slist_free(chan->incoming_queue);
+
+  g_free(chan);
+}
+
+
+int mwChannel_destroy(struct mwChannel *chan,
+		      guint32 reason, struct mwOpaque *info) {
+
+  struct mwMsgChannelDestroy *msg;
+  struct mwSession *session;
+  struct mwChannelSet *cs;
+  int ret;
+
+  /* may make this not a warning in the future */
+  g_return_val_if_fail(chan != NULL, 0);
+
+  state(chan, reason? mwChannel_ERROR: mwChannel_DESTROY);
+
+  session = chan->session;
+  g_return_val_if_fail(session != NULL, -1);
+
+  cs = mwSession_getChannels(session);
+  g_return_val_if_fail(cs != NULL, -1);
+
+  /* compose the message */
+  msg = (struct mwMsgChannelDestroy *)
+    mwMessage_new(mwMessage_CHANNEL_DESTROY);
+  msg->head.channel = chan->id;
+  msg->reason = reason;
+  if(info) mwOpaque_clone(&msg->data, info);
+
+  /* remove the channel from the channel set */
+  g_hash_table_remove(cs->map, GUINT_TO_POINTER(chan->id));
+  
+  /* send the message */
+  ret = mwSession_send(session, (struct mwMessage *) msg);
+  mwMessage_free(MW_MESSAGE(msg));
+
+  return ret;
+}
+
+
+static void queue_outgoing(struct mwChannel *chan,
+			   struct mwMsgChannelSend *msg) {
+
+  g_info("queue_outgoing, channel 0x%08x", chan->id);
+  chan->outgoing_queue = g_slist_append(chan->outgoing_queue, msg);
+}
+
+
+static int channel_send(struct mwChannel *chan,
+			struct mwMsgChannelSend *msg) {
+
+  int ret = 0;
+
+  /* if the channel is open, send and free the message. Otherwise,
+     queue the message to be sent once the channel is finally
+     opened */
+
+  if(chan->state == mwChannel_OPEN) {
+    ret = mwSession_send(chan->session, (struct mwMessage *) msg);
+    mwMessage_free(MW_MESSAGE(msg));
+
+  } else {
+    queue_outgoing(chan, msg);
+  }
+
+  return ret;
+}
+
+
+int mwChannel_sendEncrypted(struct mwChannel *chan,
+			    guint32 type, struct mwOpaque *data,
+			    gboolean encrypt) {
+
+  struct mwMsgChannelSend *msg;
+
+  g_return_val_if_fail(chan != NULL, -1);
+
+  msg = (struct mwMsgChannelSend *) mwMessage_new(mwMessage_CHANNEL_SEND);
+  msg->head.channel = chan->id;
+  msg->type = type;
+
+  mwOpaque_clone(&msg->data, data);
+
+  if(encrypt && chan->cipher) {
+    msg->head.options = mwMessageOption_ENCRYPT;
+    mwCipherInstance_encrypt(chan->cipher, &msg->data);
+  }
+
+  return channel_send(chan, msg);  
+}
+
+
+int mwChannel_send(struct mwChannel *chan, guint32 type,
+		   struct mwOpaque *data) {
+
+  return mwChannel_sendEncrypted(chan, type, data, TRUE);
+}
+
+
+static void queue_incoming(struct mwChannel *chan,
+			   struct mwMsgChannelSend *msg) {
+
+  /* we clone the message, because session_process will clear it once
+     we return */
+
+  struct mwMsgChannelSend *m = g_new0(struct mwMsgChannelSend, 1);
+  m->head.type = msg->head.type;
+  m->head.options = msg->head.options;
+  m->head.channel = msg->head.channel;
+  mwOpaque_clone(&m->head.attribs, &msg->head.attribs);
+
+  m->type = msg->type;
+  mwOpaque_clone(&m->data, &msg->data);
+
+  g_info("queue_incoming, channel 0x%08x", chan->id);
+  chan->incoming_queue = g_slist_append(chan->incoming_queue, m);
+}
+
+
+static void channel_recv(struct mwChannel *chan,
+			 struct mwMsgChannelSend *msg) {
+
+  struct mwService *srvc;
+  srvc = mwChannel_getService(chan);
+
+  incr_stat(chan, mwChannelStat_MSG_RECV, 1);
+
+  if(msg->head.options & mwMessageOption_ENCRYPT) {
+    struct mwOpaque data = { 0, 0 };
+    mwOpaque_clone(&data, &msg->data);
+
+    mwCipherInstance_decrypt(chan->cipher, &data);
+    mwService_recv(srvc, chan, msg->type, &data);
+    mwOpaque_clear(&data);
+    
+  } else {
+    mwService_recv(srvc, chan, msg->type, &msg->data);
+  }
+}
+
+
+static void flush_channel(struct mwChannel *chan) {
+  GSList *l;
+
+  for(l = chan->incoming_queue; l; l = l->next) {
+    struct mwMsgChannelSend *msg = (struct mwMsgChannelSend *) l->data;
+    l->data = NULL;
+
+    channel_recv(chan, msg);
+    mwMessage_free(MW_MESSAGE(msg));
+  }
+  g_slist_free(chan->incoming_queue);
+  chan->incoming_queue = NULL;
+
+  for(l = chan->outgoing_queue; l; l = l->next) {
+    struct mwMessage *msg = (struct mwMessage *) l->data;
+    l->data = NULL;
+
+    mwSession_send(chan->session, msg);
+    mwMessage_free(msg);
+  }
+  g_slist_free(chan->outgoing_queue);
+  chan->outgoing_queue = NULL;
+}
+
+
+void mwChannel_recv(struct mwChannel *chan, struct mwMsgChannelSend *msg) {
+  if(chan->state == mwChannel_OPEN) {
+    channel_recv(chan, msg);
+
+  } else {
+    queue_incoming(chan, msg);
+  }
+}
+
+
+struct mwChannel *mwChannel_find(struct mwChannelSet *cs, guint32 chan) {
+  g_return_val_if_fail(cs != NULL, NULL);
+  g_return_val_if_fail(cs->map != NULL, NULL);
+  return g_hash_table_lookup(cs->map, GUINT_TO_POINTER(chan));
+}
+
+
+void mwChannelSet_free(struct mwChannelSet *cs) {
+  if(! cs) return;
+  if(cs->map) g_hash_table_destroy(cs->map);
+  g_free(cs);
+}
+
+
+struct mwChannelSet *mwChannelSet_new(struct mwSession *s) {
+  struct mwChannelSet *cs = g_new0(struct mwChannelSet, 1);
+  cs->session = s;
+
+  /* for some reason, g_int_hash/g_int_equal cause a SIGSEGV */
+  cs->map = g_hash_table_new_full(g_direct_hash, g_direct_equal,
+				  NULL, (GDestroyNotify) channel_free);
+  return cs;
+}
+
+
+void mwChannel_recvCreate(struct mwChannel *chan,
+			  struct mwMsgChannelCreate *msg) {
+
+  struct mwSession *session;
+  GList *list;
+  struct mwService *srvc;
+  
+  g_return_if_fail(chan != NULL);
+  g_return_if_fail(msg != NULL);
+  g_return_if_fail(chan->id == msg->channel);
+
+  session = chan->session;
+  g_return_if_fail(session != NULL);
+
+  if(mwChannel_isOutgoing(chan)) {
+    g_warning("channel 0x%08x not an incoming channel", chan->id);
+    mwChannel_destroy(chan, ERR_REQUEST_INVALID, NULL);
+    return;
+  }
+
+  for(list = msg->encrypt.items; list; list = list->next) {
+    struct mwEncryptItem *ei = list->data;
+    struct mwCipher *cipher;
+    struct mwCipherInstance *ci;
+
+    g_message("channel offered cipher id 0x%04x", ei->id);
+    cipher = mwSession_getCipher(session, ei->id);
+    if(! cipher) {
+      g_message("no such cipher found in session");
+      continue;
+    }
+
+    ci = mwCipher_newInstance(cipher, chan);
+    mwCipherInstance_offered(ci, ei);
+    mwChannel_addSupportedCipherInstance(chan, ci);
+  }
+
+  mwLoginInfo_clone(&chan->user, &msg->creator);
+  chan->service = msg->service;
+  chan->proto_type = msg->proto_type;
+  chan->proto_ver = msg->proto_ver;
+  
+  srvc = mwSession_getService(session, msg->service);
+  if(srvc) {
+    mwService_recvCreate(srvc, chan, msg);
+
+  } else {
+    mwChannel_destroy(chan, ERR_SERVICE_NO_SUPPORT, NULL);
+  }  
+}
+
+
+void mwChannel_recvAccept(struct mwChannel *chan,
+			  struct mwMsgChannelAccept *msg) {
+
+  struct mwService *srvc;
+
+  g_return_if_fail(chan != NULL);
+  g_return_if_fail(msg != NULL);
+  g_return_if_fail(chan->id == msg->head.channel);
+
+  if(mwChannel_isIncoming(chan)) {
+    g_warning("channel 0x%08x not an outgoing channel", chan->id);
+    mwChannel_destroy(chan, ERR_REQUEST_INVALID, NULL);
+    return;
+  }
+
+  if(chan->state != mwChannel_WAIT) {
+    g_warning("channel 0x%08x state not WAIT: %s",
+	      chan->id, state_str(chan->state));
+    mwChannel_destroy(chan, ERR_REQUEST_INVALID, NULL);
+    return;
+  }
+
+  mwLoginInfo_clone(&chan->user, &msg->acceptor);
+
+  srvc = mwSession_getService(chan->session, chan->service);
+  if(! srvc) {
+    g_warning("no service: 0x%08x", chan->service);
+    mwChannel_destroy(chan, ERR_SERVICE_NO_SUPPORT, NULL);
+    return;
+  }
+
+  if(! msg->encrypt.mode || ! msg->encrypt.item) {
+    mwChannel_selectCipherInstance(chan, NULL);
+
+  } else {
+    guint16 cid = msg->encrypt.item->id;
+    struct mwCipherInstance *ci = get_supported(chan, cid);
+
+    if(! ci) {
+      g_warning("not an offered cipher: 0x%04x", cid);
+      mwChannel_destroy(chan, ERR_REQUEST_INVALID, NULL);
+      return;
+    }
+
+    mwCipherInstance_accepted(ci, msg->encrypt.item);
+    mwChannel_selectCipherInstance(chan, ci);
+  }
+
+  /* mark it as open for the service */
+  state(chan, mwChannel_OPEN);
+
+  /* let the service know */
+  mwService_recvAccept(srvc, chan, msg);
+
+  /* flush it if the service didn't just immediately close it */
+  if(mwChannel_isState(chan, mwChannel_OPEN)) {
+    channel_open(chan);
+  }
+}
+
+
+void mwChannel_recvDestroy(struct mwChannel *chan,
+			   struct mwMsgChannelDestroy *msg) {
+
+  struct mwChannelSet *cs;
+  struct mwService *srvc;
+
+  g_return_if_fail(chan != NULL);
+  g_return_if_fail(msg != NULL);
+  g_return_if_fail(chan->id == msg->head.channel);
+
+  state(chan, msg->reason? mwChannel_ERROR: mwChannel_DESTROY);
+
+  srvc = mwChannel_getService(chan);
+  if(srvc) mwService_recvDestroy(srvc, chan, msg);
+
+  cs = mwSession_getChannels(chan->session);
+  g_return_if_fail(cs != NULL);
+  g_return_if_fail(cs->map != NULL);
+
+  g_hash_table_remove(cs->map, GUINT_TO_POINTER(chan->id));
+}
+
+
+void mwChannel_populateSupportedCipherInstances(struct mwChannel *chan) {
+  struct mwSession *session;
+  GList *list;
+
+  g_return_if_fail(chan != NULL);
+
+  session = chan->session;
+  g_return_if_fail(session != NULL);
+
+  for(list = mwSession_getCiphers(session); list; list = list->next) {
+    struct mwCipherInstance *ci = mwCipher_newInstance(list->data, chan);
+    if(! ci) continue;
+    put_supported(chan, ci);
+  }
+}
+
+
+void mwChannel_addSupportedCipherInstance(struct mwChannel *chan,
+					  struct mwCipherInstance *ci) {
+  g_return_if_fail(chan != NULL);
+  g_message("channel 0x%08x added cipher %s", chan->id,
+	    NSTR(mwCipher_getName(mwCipherInstance_getCipher(ci))));
+  put_supported(chan, ci);
+}
+
+
+static void collect(gpointer a, gpointer b, gpointer c) {
+  GList **list = c;
+  *list = g_list_append(*list, b);
+}
+
+
+GList *mwChannel_getSupportedCipherInstances(struct mwChannel *chan) {
+  GList *list = NULL;
+
+  g_return_val_if_fail(chan != NULL, NULL);
+  g_hash_table_foreach(chan->supported, collect, &list);
+
+  return list;
+}
+
+
+void mwChannel_selectCipherInstance(struct mwChannel *chan,
+				    struct mwCipherInstance *ci) {
+  struct mwCipher *c;
+
+  g_return_if_fail(chan != NULL);
+  g_return_if_fail(chan->supported != NULL);
+
+  chan->cipher = ci;
+  if(ci) {
+    guint cid;
+
+    c = mwCipherInstance_getCipher(ci);
+    cid = mwCipher_getType(c);
+
+    g_hash_table_steal(chan->supported, GUINT_TO_POINTER(cid));
+
+    g_message("channel 0x%08x selected cipher %s",
+	      chan->id, NSTR(mwCipher_getName(c)));
+  } else {
+    g_message("channel 0x%08x selected no cipher", chan->id);
+  }
+
+  g_hash_table_destroy(chan->supported);
+  chan->supported = NULL;
+}
+
+