updates.
[silc.git] / lib / silcclient / client_resume.c
diff --git a/lib/silcclient/client_resume.c b/lib/silcclient/client_resume.c
new file mode 100644 (file)
index 0000000..0d0d24b
--- /dev/null
@@ -0,0 +1,521 @@
+/*
+
+  client_resume.c
+
+  Author: Pekka Riikonen <priikone@silcnet.org>
+
+  Copyright (C) 2002 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
+  the Free Software Foundation; version 2 of the License.
+
+  This program 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 General Public License for more details.
+
+*/
+/* $Id$ */
+
+#include "silcincludes.h"
+#include "silcclient.h"
+#include "client_internal.h"
+
+SILC_CLIENT_CMD_REPLY_FUNC(resume);
+SILC_CLIENT_CMD_FUNC(resume_identify);
+SILC_CLIENT_CMD_FUNC(resume_cmode);
+SILC_CLIENT_CMD_FUNC(resume_users);
+
+/* Generates the session detachment data. This data can be used later
+   to resume back to the server. */
+
+SilcBuffer silc_client_get_detach_data(SilcClient client,
+                                      SilcClientConnection conn)
+{
+  SilcBuffer detach;
+  SilcHashTableList htl;
+  SilcChannelUser chu;
+  int ch_count;
+
+  SILC_LOG_DEBUG(("Creating detachment data"));
+
+  ch_count = silc_hash_table_count(conn->local_entry->channels);
+
+  /* Save the nickname, Client ID and user mode in SILC network */
+  detach = silc_buffer_alloc_size(2 + strlen(conn->nickname) +
+                                 2 + conn->local_id_data_len + 4 + 4);
+  silc_buffer_format(detach,
+                    SILC_STR_UI_SHORT(strlen(conn->nickname)),
+                    SILC_STR_UI_XNSTRING(conn->nickname,
+                                         strlen(conn->nickname)),
+                    SILC_STR_UI_SHORT(conn->local_id_data_len),
+                    SILC_STR_UI_XNSTRING(conn->local_id_data,
+                                         conn->local_id_data_len),
+                    SILC_STR_UI_INT(conn->local_entry->mode),
+                    SILC_STR_UI_INT(ch_count),
+                    SILC_STR_END);
+
+  /* Save all joined channels */
+  silc_hash_table_list(conn->local_entry->channels, &htl);
+  while (silc_hash_table_get(&htl, NULL, (void **)&chu)) {
+    unsigned char *chid = silc_id_id2str(chu->channel->id, SILC_ID_CHANNEL);
+    SilcUInt16 chid_len = silc_id_get_len(chu->channel->id, SILC_ID_CHANNEL);
+
+    detach = silc_buffer_realloc(detach, detach->truelen + 2 +
+                                strlen(chu->channel->channel_name) +
+                                2 + chid_len + 4);
+    silc_buffer_pull(detach, detach->len);
+    silc_buffer_pull_tail(detach, 2 + strlen(chu->channel->channel_name) +
+                         2 + chid_len + 4);
+    silc_buffer_format(detach,
+                      SILC_STR_UI_SHORT(strlen(chu->channel->channel_name)),
+                      SILC_STR_UI_XNSTRING(chu->channel->channel_name,
+                                          strlen(chu->channel->channel_name)),
+                      SILC_STR_UI_SHORT(chid_len),
+                      SILC_STR_UI_XNSTRING(chid, chid_len),
+                      SILC_STR_UI_INT(chu->channel->mode),
+                      SILC_STR_END);
+    silc_free(chid);
+  }
+  silc_hash_table_list_reset(&htl);
+
+  silc_buffer_push(detach, detach->data - detach->head);
+
+  SILC_LOG_HEXDUMP(("Detach data"), detach->data, detach->len);
+
+  return detach;
+}
+
+/* Processes the detachment data. This creates channels and other
+   stuff according the data found in the the connection parameters.
+   This doesn't actually resolve any detailed information from the
+   server.  To do that call silc_client_resume_session function. 
+   This returns the old detached session client ID. */
+
+bool silc_client_process_detach_data(SilcClient client,
+                                    SilcClientConnection conn,
+                                    unsigned char **old_id,
+                                    SilcUInt16 *old_id_len)
+{
+  SilcBufferStruct detach;
+  SilcUInt32 ch_count;
+  int i, len;
+
+  SILC_LOG_DEBUG(("Start"));
+
+  silc_free(conn->nickname);
+  silc_buffer_set(&detach, conn->params.detach_data, 
+                 conn->params.detach_data_len);
+
+  SILC_LOG_HEXDUMP(("Detach data"), detach.data, detach.len);
+
+  /* Take the old client ID from the detachment data */
+  len = silc_buffer_unformat(&detach,
+                            SILC_STR_UI16_NSTRING_ALLOC(&conn->nickname, 
+                                                        NULL),
+                            SILC_STR_UI16_NSTRING_ALLOC(old_id, old_id_len),
+                            SILC_STR_UI_INT(NULL),
+                            SILC_STR_UI_INT(&ch_count),
+                            SILC_STR_END);
+  if (len == -1)
+    return FALSE;
+
+  silc_buffer_pull(&detach, len);
+
+  for (i = 0; i < ch_count; i++) {
+    char *channel;
+    unsigned char *chid;
+    SilcUInt16 chid_len;
+    SilcUInt32 ch_mode;
+    SilcChannelID *channel_id;
+    SilcChannelEntry channel_entry;
+
+    len = silc_buffer_unformat(&detach,
+                              SILC_STR_UI16_NSTRING_ALLOC(&channel, NULL),
+                              SILC_STR_UI16_NSTRING(&chid, &chid_len),
+                              SILC_STR_UI_INT(&ch_mode),
+                              SILC_STR_END);
+    if (len == -1)
+      return FALSE;
+
+    /* Add new channel */
+    channel_id = silc_id_str2id(chid, chid_len, SILC_ID_CHANNEL);
+    channel_entry = silc_client_get_channel_by_id(client, conn, channel_id);
+    if (!channel_entry) {
+      channel_entry = silc_client_add_channel(client, conn, channel, ch_mode,
+                                             channel_id);
+    } else {
+      silc_free(channel);
+      silc_free(channel_id);
+    }
+
+    silc_buffer_pull(&detach, len);
+  }
+  silc_buffer_push(&detach, detach.data - detach.head);
+
+  return TRUE;
+}
+
+/* Generic command reply callback */
+
+SILC_CLIENT_CMD_REPLY_FUNC(resume)
+{
+  SilcClientCommandReplyContext cmd = (SilcClientCommandReplyContext)context;
+
+  SILC_LOG_DEBUG(("Start"));
+
+  if (cmd->callback)
+    (*cmd->callback)(cmd->context, cmd);
+}
+
+/* Resume session context */
+typedef struct {
+  SilcClient client;
+  SilcClientConnection conn;
+  SilcClientResumeSessionCallback callback;
+  void *context;
+  SilcUInt32 channel_count;
+} *SilcClientResumeSession;
+
+/* This function is used to perform the resuming procedure after the
+   client has connected to the server properly and has received the
+   Client ID for the resumed session.  This resolves all channels
+   that the resumed client is joined, joined users, users modes
+   and channel modes.  The `callback' is called after this procedure
+   is completed. */
+
+void silc_client_resume_session(SilcClient client,
+                               SilcClientConnection conn,
+                               SilcClientResumeSessionCallback callback,
+                               void *context)
+{
+  SilcClientResumeSession session;
+  SilcIDCacheList list;
+  SilcIDCacheEntry entry;
+  SilcChannelEntry channel;
+  SilcBuffer tmp;
+  int i;
+  bool ret;
+
+  SILC_LOG_DEBUG(("Resuming detached session"));
+
+  session = silc_calloc(1, sizeof(*session));
+  if (!session) {
+    callback(client, conn, FALSE, context);
+    return;
+  }
+  session->client = client;
+  session->conn = conn;
+  session->callback = callback;
+  session->context = context;
+
+  /* First, send UMODE commandto get our own user mode in the network */
+  SILC_LOG_DEBUG(("Sending UMODE"));
+  tmp = silc_id_payload_encode(conn->local_entry->id, SILC_ID_CLIENT);
+  silc_client_command_send(client, conn, SILC_COMMAND_UMODE,
+                          conn->cmd_ident, 1, 1, tmp->data, tmp->len);
+  silc_buffer_free(tmp);
+
+  /* Second, send IDENTIFY command of all channels we know about.  These
+     are the channels we've joined to according our detachment data. */
+  if (silc_idcache_get_all(conn->channel_cache, &list)) {
+    unsigned char **res_argv = NULL;
+    SilcUInt32 *res_argv_lens = NULL, *res_argv_types = NULL, res_argc = 0;
+
+    session->channel_count = silc_idcache_list_count(list);
+
+    ret = silc_idcache_list_first(list, &entry);
+    while (ret) {
+      channel = entry->context;
+      tmp = silc_id_payload_encode(channel->id, SILC_ID_CHANNEL);
+      res_argv = silc_realloc(res_argv, sizeof(*res_argv) * (res_argc + 1));
+      res_argv_lens = silc_realloc(res_argv_lens, sizeof(*res_argv_lens) *
+                                  (res_argc + 1));
+      res_argv_types = silc_realloc(res_argv_types, sizeof(*res_argv_types) *
+                                   (res_argc + 1));
+      res_argv[res_argc] = silc_memdup(tmp->data, tmp->len);
+      res_argv_lens[res_argc] = tmp->len;
+      res_argv_types[res_argc] = res_argc + 5;
+      res_argc++;
+      silc_buffer_free(tmp);
+      ret = silc_idcache_list_next(list, &entry);
+    }
+    silc_idcache_list_free(list);
+
+    if (res_argc) {
+      /* Send the IDENTIFY command */
+      SILC_LOG_DEBUG(("Sending IDENTIFY"));
+      silc_client_command_register(client, SILC_COMMAND_IDENTIFY, NULL, NULL,
+                                  silc_client_command_reply_resume,
+                                  0, ++conn->cmd_ident);
+      tmp = silc_command_payload_encode(SILC_COMMAND_IDENTIFY,
+                                       res_argc, res_argv, res_argv_lens,
+                                       res_argv_types, conn->cmd_ident);
+      silc_client_command_pending(conn, SILC_COMMAND_IDENTIFY, 
+                                 conn->cmd_ident,
+                                 silc_client_command_resume_identify,
+                                 session);
+      silc_client_packet_send(client, conn->sock, SILC_PACKET_COMMAND, 
+                             NULL, 0, NULL, NULL, tmp->data, tmp->len, TRUE);
+
+      for (i = 0; i < res_argc; i++)
+       silc_free(res_argv[i]);
+      silc_free(res_argv);
+      silc_free(res_argv_lens);
+      silc_free(res_argv_types);
+      silc_buffer_free(tmp);
+    }
+  }
+
+  /* Now, we wait for replies to come back and then continue with USERS,
+     CMODE and TOPIC commands. */
+}
+
+/* Received identify reply for a channel entry */
+
+SILC_CLIENT_CMD_FUNC(resume_identify)
+{
+  SilcClientResumeSession session = context;
+  SilcClientCommandReplyContext cmd = context2;
+  SilcClient client = session->client;
+  SilcClientConnection conn = session->conn;
+  unsigned char *tmp;
+  SilcUInt32 tmp_len;
+  SilcChannelEntry channel = NULL;
+  SilcChannelID *channel_id;
+  SilcIDPayload idp;
+  SilcIdType id_type;
+
+  SILC_LOG_DEBUG(("Start"));
+
+  tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
+  if (!tmp)
+    goto err;
+
+  if (cmd->error != SILC_STATUS_OK) {
+    /* Delete unknown channel from our cache */
+    if (cmd->error == SILC_STATUS_ERR_NO_SUCH_CHANNEL_ID) {
+      channel_id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
+      if (channel_id) {
+       channel = silc_client_get_channel_by_id(client, conn, channel_id);
+       if (channel)
+         silc_client_del_channel(client, conn, channel);
+       silc_free(channel_id);
+      }
+    }
+    goto err;
+  }
+
+  idp = silc_id_payload_parse(tmp, tmp_len);
+  if (!idp) {
+    return;
+  }
+  id_type = silc_id_payload_get_type(idp);
+
+  switch (id_type) {
+  case SILC_ID_CHANNEL:
+    channel_id = silc_id_payload_get_id(idp);
+    channel = silc_client_get_channel_by_id(client, conn, channel_id);
+    silc_free(channel_id);
+    break;
+  default:
+    silc_id_payload_free(idp);
+    goto err;
+    break;
+  }
+
+  /* Now, send CMODE command for this channel.  We send only this one
+     because this will return also error if we are not currently joined
+     on this channel, plus we get the channel mode.  USERS and TOPIC
+     commands are called after this returns. */
+  if (channel) {
+    SILC_LOG_DEBUG(("Sending CMODE"));
+    silc_client_command_register(client, SILC_COMMAND_CMODE, NULL, NULL,
+                                silc_client_command_reply_resume, 0,
+                                ++conn->cmd_ident);
+    silc_client_command_send(client, conn, SILC_COMMAND_CMODE,
+                            conn->cmd_ident, 1, 1, tmp, tmp_len);
+    silc_client_command_pending(conn, SILC_COMMAND_CMODE, conn->cmd_ident,
+                               silc_client_command_resume_cmode, session);
+  }
+
+  silc_id_payload_free(idp);
+
+  if (cmd->status != SILC_STATUS_OK &&
+      cmd->status != SILC_STATUS_LIST_END)
+    return;
+
+  /* Unregister this command reply */
+  silc_client_command_unregister(client, SILC_COMMAND_IDENTIFY, NULL, 
+                                silc_client_command_reply_resume,
+                                cmd->ident);
+  return;
+
+ err:
+  session->channel_count--;
+  if (!session->channel_count)
+    session->callback(session->client, session->conn, FALSE,
+                     session->context);
+}
+
+/* Received cmode to channel entry */
+
+SILC_CLIENT_CMD_FUNC(resume_cmode)
+{
+  SilcClientResumeSession session = context;
+  SilcClientCommandReplyContext cmd = context2;
+  SilcClient client = session->client;
+  SilcClientConnection conn = session->conn;
+  unsigned char *tmp;
+  SilcChannelID *channel_id;
+  SilcChannelEntry channel;
+  SilcUInt32 len;
+
+  SILC_LOG_DEBUG(("Start"));
+
+  /* Unregister this command reply */
+  silc_client_command_unregister(client, SILC_COMMAND_CMODE, NULL, 
+                                silc_client_command_reply_resume,
+                                cmd->ident);
+
+  if (cmd->error != SILC_STATUS_OK)
+    goto err;
+
+  /* Take Channel ID */
+  tmp = silc_argument_get_arg_type(cmd->args, 2, &len);
+  if (!tmp)
+    goto err;
+  channel_id = silc_id_payload_parse_id(tmp, len, NULL);
+  if (!channel_id)
+    goto err;
+
+  /* Get the channel entry */
+  channel = silc_client_get_channel_by_id(cmd->client, conn, channel_id);
+  if (channel) {
+
+    /* Get channel mode */
+    tmp = silc_argument_get_arg_type(cmd->args, 3, NULL);
+    if (tmp)
+      SILC_GET32_MSB(channel->mode, tmp);
+
+    tmp = silc_argument_get_arg_type(cmd->args, 2, &len);
+
+    /* And now, we will send USERS to get users on the channel */
+    SILC_LOG_DEBUG(("Sending USERS"));
+    silc_client_command_register(client, SILC_COMMAND_USERS, NULL, NULL,
+                                silc_client_command_reply_users_i, 0,
+                                ++conn->cmd_ident);
+    silc_client_command_send(client, conn, SILC_COMMAND_USERS,
+                            conn->cmd_ident, 1, 1, tmp, len);
+    silc_client_command_pending(conn, SILC_COMMAND_USERS, conn->cmd_ident,
+                               silc_client_command_resume_users, session);
+  }
+
+  silc_free(channel_id);
+  return;
+
+ err:
+  session->channel_count--;
+  if (!session->channel_count)
+    session->callback(session->client, session->conn, FALSE,
+                     session->context);
+}
+
+/* Received users reply to a channel entry */
+
+SILC_CLIENT_CMD_FUNC(resume_users)
+{
+  SilcClientResumeSession session = context;
+  SilcClientCommandReplyContext cmd = context2;
+  SilcClient client = session->client;
+  SilcClientConnection conn = session->conn;
+  SilcBufferStruct client_id_list, client_mode_list;
+  unsigned char *tmp;
+  SilcUInt32 tmp_len, list_count;
+  SilcChannelEntry channel;
+  SilcChannelID *channel_id = NULL;
+
+  SILC_LOG_DEBUG(("Start"));
+
+  /* Unregister this command reply */
+  silc_client_command_unregister(client, SILC_COMMAND_USERS, NULL, 
+                                silc_client_command_reply_users_i,
+                                cmd->ident);
+
+  if (cmd->error != SILC_STATUS_OK)
+    goto err;
+
+  /* Get channel ID */
+  tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
+  if (!tmp) {
+    COMMAND_REPLY_ERROR;
+    goto err;
+  }
+  channel_id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
+  if (!channel_id) {
+    COMMAND_REPLY_ERROR;
+    goto err;
+  }
+
+  /* Get the list count */
+  tmp = silc_argument_get_arg_type(cmd->args, 3, &tmp_len);
+  if (!tmp) {
+    COMMAND_REPLY_ERROR;
+    goto err;
+  }
+  SILC_GET32_MSB(list_count, tmp);
+
+  /* Get Client ID list */
+  tmp = silc_argument_get_arg_type(cmd->args, 4, &tmp_len);
+  if (!tmp) {
+    COMMAND_REPLY_ERROR;
+    goto err;
+  }
+  silc_buffer_set(&client_id_list, tmp, tmp_len);
+
+  /* Get client mode list */
+  tmp = silc_argument_get_arg_type(cmd->args, 5, &tmp_len);
+  if (!tmp) {
+    COMMAND_REPLY_ERROR;
+    goto err;
+  }
+  silc_buffer_set(&client_mode_list, tmp, tmp_len);
+
+  /* Get channel entry */
+  channel = silc_client_get_channel_by_id(cmd->client, conn, channel_id);
+  if (!channel)
+    goto err;
+
+  /* Send fake JOIN command reply to application */
+  client->internal->ops->command_reply(client, conn, cmd->payload, TRUE,
+                                      SILC_COMMAND_JOIN, cmd->status,
+                                      channel->channel_name, channel,
+                                      channel->mode, 0, 
+                                      NULL, NULL, NULL, NULL, 
+                                      channel->hmac, list_count,
+                                      &client_id_list, client_mode_list);
+
+  /* Send TOPIC for this channel to get the topic */
+  SILC_LOG_DEBUG(("Sending TOPIC"));
+  tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
+  silc_client_command_send(client, conn, SILC_COMMAND_TOPIC,
+                          conn->cmd_ident, 1, 1, tmp, tmp_len);
+
+  /* Call the completion callback after we've got reply to all of
+     our channels */
+  session->channel_count--;
+  if (!session->channel_count)
+    session->callback(session->client, session->conn, TRUE,
+                     session->context);
+
+  silc_free(channel_id);
+  return;
+
+ err:
+  silc_free(channel_id);
+  session->channel_count--;
+  if (!session->channel_count)
+    session->callback(session->client, session->conn, FALSE,
+                     session->context);
+}