updates.
[silc.git] / lib / silcclient / client_resume.c
1 /*
2
3   client_resume.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 2002 Pekka Riikonen
8
9   This program is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; version 2 of the License.
12
13   This program is distributed in the hope that it will be useful,
14   but WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16   GNU General Public License for more details.
17
18 */
19 /* $Id$ */
20
21 #include "silcincludes.h"
22 #include "silcclient.h"
23 #include "client_internal.h"
24
25 SILC_CLIENT_CMD_REPLY_FUNC(resume);
26 SILC_CLIENT_CMD_FUNC(resume_identify);
27 SILC_CLIENT_CMD_FUNC(resume_cmode);
28 SILC_CLIENT_CMD_FUNC(resume_users);
29
30 #define RESUME_CALL_COMPLETION(client, session, s)                      \
31 do {                                                                    \
32   session->success = s;                                                 \
33   silc_schedule_task_add(client->schedule, 0,                           \
34                          silc_client_resume_call_completion, session,   \
35                          0, 1, SILC_TASK_TIMEOUT, SILC_TASK_PRI_LOW);   \
36 } while(0)
37
38 /* Generates the session detachment data. This data can be used later
39    to resume back to the server. */
40
41 SilcBuffer silc_client_get_detach_data(SilcClient client,
42                                        SilcClientConnection conn)
43 {
44   SilcBuffer detach;
45   SilcHashTableList htl;
46   SilcChannelUser chu;
47   int ch_count;
48
49   SILC_LOG_DEBUG(("Creating detachment data"));
50
51   ch_count = silc_hash_table_count(conn->local_entry->channels);
52
53   /* Save the nickname, Client ID and user mode in SILC network */
54   detach = silc_buffer_alloc_size(2 + strlen(conn->nickname) +
55                                   2 + conn->local_id_data_len + 4 + 4);
56   silc_buffer_format(detach,
57                      SILC_STR_UI_SHORT(strlen(conn->nickname)),
58                      SILC_STR_UI_XNSTRING(conn->nickname,
59                                           strlen(conn->nickname)),
60                      SILC_STR_UI_SHORT(conn->local_id_data_len),
61                      SILC_STR_UI_XNSTRING(conn->local_id_data,
62                                           conn->local_id_data_len),
63                      SILC_STR_UI_INT(conn->local_entry->mode),
64                      SILC_STR_UI_INT(ch_count),
65                      SILC_STR_END);
66
67   /* Save all joined channels */
68   silc_hash_table_list(conn->local_entry->channels, &htl);
69   while (silc_hash_table_get(&htl, NULL, (void **)&chu)) {
70     unsigned char *chid = silc_id_id2str(chu->channel->id, SILC_ID_CHANNEL);
71     SilcUInt16 chid_len = silc_id_get_len(chu->channel->id, SILC_ID_CHANNEL);
72
73     detach = silc_buffer_realloc(detach, detach->truelen + 2 +
74                                  strlen(chu->channel->channel_name) +
75                                  2 + chid_len + 4);
76     silc_buffer_pull(detach, detach->len);
77     silc_buffer_pull_tail(detach, 2 + strlen(chu->channel->channel_name) +
78                           2 + chid_len + 4);
79     silc_buffer_format(detach,
80                        SILC_STR_UI_SHORT(strlen(chu->channel->channel_name)),
81                        SILC_STR_UI_XNSTRING(chu->channel->channel_name,
82                                            strlen(chu->channel->channel_name)),
83                        SILC_STR_UI_SHORT(chid_len),
84                        SILC_STR_UI_XNSTRING(chid, chid_len),
85                        SILC_STR_UI_INT(chu->channel->mode),
86                        SILC_STR_END);
87     silc_free(chid);
88   }
89   silc_hash_table_list_reset(&htl);
90
91   silc_buffer_push(detach, detach->data - detach->head);
92
93   SILC_LOG_HEXDUMP(("Detach data"), detach->data, detach->len);
94
95   return detach;
96 }
97
98 /* Processes the detachment data. This creates channels and other
99    stuff according the data found in the the connection parameters.
100    This doesn't actually resolve any detailed information from the
101    server.  To do that call silc_client_resume_session function. 
102    This returns the old detached session client ID. */
103
104 bool silc_client_process_detach_data(SilcClient client,
105                                      SilcClientConnection conn,
106                                      unsigned char **old_id,
107                                      SilcUInt16 *old_id_len)
108 {
109   SilcBufferStruct detach;
110   SilcUInt32 ch_count;
111   int i, len;
112
113   SILC_LOG_DEBUG(("Start"));
114
115   silc_free(conn->nickname);
116   silc_buffer_set(&detach, conn->params.detach_data, 
117                   conn->params.detach_data_len);
118
119   SILC_LOG_HEXDUMP(("Detach data"), detach.data, detach.len);
120
121   /* Take the old client ID from the detachment data */
122   len = silc_buffer_unformat(&detach,
123                              SILC_STR_UI16_NSTRING_ALLOC(&conn->nickname, 
124                                                          NULL),
125                              SILC_STR_UI16_NSTRING_ALLOC(old_id, old_id_len),
126                              SILC_STR_UI_INT(NULL),
127                              SILC_STR_UI_INT(&ch_count),
128                              SILC_STR_END);
129   if (len == -1)
130     return FALSE;
131
132   silc_buffer_pull(&detach, len);
133
134   for (i = 0; i < ch_count; i++) {
135     char *channel;
136     unsigned char *chid;
137     SilcUInt16 chid_len;
138     SilcUInt32 ch_mode;
139     SilcChannelID *channel_id;
140     SilcChannelEntry channel_entry;
141
142     len = silc_buffer_unformat(&detach,
143                                SILC_STR_UI16_NSTRING_ALLOC(&channel, NULL),
144                                SILC_STR_UI16_NSTRING(&chid, &chid_len),
145                                SILC_STR_UI_INT(&ch_mode),
146                                SILC_STR_END);
147     if (len == -1)
148       return FALSE;
149
150     /* Add new channel */
151     channel_id = silc_id_str2id(chid, chid_len, SILC_ID_CHANNEL);
152     channel_entry = silc_client_get_channel_by_id(client, conn, channel_id);
153     if (!channel_entry) {
154       channel_entry = silc_client_add_channel(client, conn, channel, ch_mode,
155                                               channel_id);
156     } else {
157       silc_free(channel);
158       silc_free(channel_id);
159     }
160
161     silc_buffer_pull(&detach, len);
162   }
163   silc_buffer_push(&detach, detach.data - detach.head);
164
165   return TRUE;
166 }
167
168
169 /* Resume session context */
170 typedef struct {
171   SilcClient client;
172   SilcClientConnection conn;
173   SilcClientResumeSessionCallback callback;
174   void *context;
175   SilcUInt32 channel_count;
176   bool success;
177 } *SilcClientResumeSession;
178
179 /* Generic command reply callback */
180
181 SILC_CLIENT_CMD_REPLY_FUNC(resume)
182 {
183   SilcClientCommandReplyContext cmd = (SilcClientCommandReplyContext)context;
184
185   SILC_LOG_DEBUG(("Start"));
186
187   if (cmd->callback)
188     (*cmd->callback)(cmd->context, cmd);
189 }
190
191 /* Completion calling callback */
192
193 SILC_TASK_CALLBACK(silc_client_resume_call_completion)
194 {
195   SilcClientResumeSession session = context;
196   session->callback(session->client, session->conn, session->success,
197                     session->context);
198 }
199
200 /* This function is used to perform the resuming procedure after the
201    client has connected to the server properly and has received the
202    Client ID for the resumed session.  This resolves all channels
203    that the resumed client is joined, joined users, users modes
204    and channel modes.  The `callback' is called after this procedure
205    is completed. */
206
207 void silc_client_resume_session(SilcClient client,
208                                 SilcClientConnection conn,
209                                 SilcClientResumeSessionCallback callback,
210                                 void *context)
211 {
212   SilcClientResumeSession session;
213   SilcIDCacheList list;
214   SilcIDCacheEntry entry;
215   SilcChannelEntry channel;
216   SilcBuffer tmp;
217   int i;
218   bool ret;
219
220   SILC_LOG_DEBUG(("Resuming detached session"));
221
222   session = silc_calloc(1, sizeof(*session));
223   if (!session) {
224     callback(client, conn, FALSE, context);
225     return;
226   }
227   session->client = client;
228   session->conn = conn;
229   session->callback = callback;
230   session->context = context;
231
232   /* First, send UMODE commandto get our own user mode in the network */
233   SILC_LOG_DEBUG(("Sending UMODE"));
234   tmp = silc_id_payload_encode(conn->local_entry->id, SILC_ID_CLIENT);
235   silc_client_command_send(client, conn, SILC_COMMAND_UMODE,
236                            conn->cmd_ident, 1, 1, tmp->data, tmp->len);
237   silc_buffer_free(tmp);
238
239   /* Second, send IDENTIFY command of all channels we know about.  These
240      are the channels we've joined to according our detachment data. */
241   if (silc_idcache_get_all(conn->channel_cache, &list)) {
242     unsigned char **res_argv = NULL;
243     SilcUInt32 *res_argv_lens = NULL, *res_argv_types = NULL, res_argc = 0;
244
245     session->channel_count = silc_idcache_list_count(list);
246
247     ret = silc_idcache_list_first(list, &entry);
248     while (ret) {
249       channel = entry->context;
250       tmp = silc_id_payload_encode(channel->id, SILC_ID_CHANNEL);
251       res_argv = silc_realloc(res_argv, sizeof(*res_argv) * (res_argc + 1));
252       res_argv_lens = silc_realloc(res_argv_lens, sizeof(*res_argv_lens) *
253                                    (res_argc + 1));
254       res_argv_types = silc_realloc(res_argv_types, sizeof(*res_argv_types) *
255                                     (res_argc + 1));
256       res_argv[res_argc] = silc_memdup(tmp->data, tmp->len);
257       res_argv_lens[res_argc] = tmp->len;
258       res_argv_types[res_argc] = res_argc + 5;
259       res_argc++;
260       silc_buffer_free(tmp);
261       ret = silc_idcache_list_next(list, &entry);
262     }
263     silc_idcache_list_free(list);
264
265     if (res_argc) {
266       /* Send the IDENTIFY command */
267       SILC_LOG_DEBUG(("Sending IDENTIFY"));
268       silc_client_command_register(client, SILC_COMMAND_IDENTIFY, NULL, NULL,
269                                    silc_client_command_reply_resume,
270                                    0, ++conn->cmd_ident);
271       tmp = silc_command_payload_encode(SILC_COMMAND_IDENTIFY,
272                                         res_argc, res_argv, res_argv_lens,
273                                         res_argv_types, conn->cmd_ident);
274       silc_client_command_pending(conn, SILC_COMMAND_IDENTIFY, 
275                                   conn->cmd_ident,
276                                   silc_client_command_resume_identify,
277                                   session);
278       silc_client_packet_send(client, conn->sock, SILC_PACKET_COMMAND, 
279                               NULL, 0, NULL, NULL, tmp->data, tmp->len, TRUE);
280
281       for (i = 0; i < res_argc; i++)
282         silc_free(res_argv[i]);
283       silc_free(res_argv);
284       silc_free(res_argv_lens);
285       silc_free(res_argv_types);
286       silc_buffer_free(tmp);
287     }
288   }
289
290   /* Now, we wait for replies to come back and then continue with USERS,
291      CMODE and TOPIC commands. */
292 }
293
294 /* Received identify reply for a channel entry */
295
296 SILC_CLIENT_CMD_FUNC(resume_identify)
297 {
298   SilcClientResumeSession session = context;
299   SilcClientCommandReplyContext cmd = context2;
300   SilcClient client = session->client;
301   SilcClientConnection conn = session->conn;
302   unsigned char *tmp;
303   SilcUInt32 tmp_len;
304   SilcChannelEntry channel = NULL;
305   SilcChannelID *channel_id;
306   SilcIDPayload idp;
307   SilcIdType id_type;
308
309   SILC_LOG_DEBUG(("Start"));
310
311   tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
312   if (!tmp)
313     goto err;
314
315   if (cmd->error != SILC_STATUS_OK) {
316     /* Delete unknown channel from our cache */
317     if (cmd->error == SILC_STATUS_ERR_NO_SUCH_CHANNEL_ID) {
318       channel_id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
319       if (channel_id) {
320         channel = silc_client_get_channel_by_id(client, conn, channel_id);
321         if (channel)
322           silc_client_del_channel(client, conn, channel);
323         silc_free(channel_id);
324       }
325     }
326     goto err;
327   }
328
329   idp = silc_id_payload_parse(tmp, tmp_len);
330   if (!idp) {
331     return;
332   }
333   id_type = silc_id_payload_get_type(idp);
334
335   switch (id_type) {
336   case SILC_ID_CHANNEL:
337     channel_id = silc_id_payload_get_id(idp);
338     channel = silc_client_get_channel_by_id(client, conn, channel_id);
339     silc_free(channel_id);
340     break;
341   default:
342     silc_id_payload_free(idp);
343     goto err;
344     break;
345   }
346
347   /* Now, send CMODE command for this channel.  We send only this one
348      because this will return also error if we are not currently joined
349      on this channel, plus we get the channel mode.  USERS and TOPIC
350      commands are called after this returns. */
351   if (channel) {
352     SILC_LOG_DEBUG(("Sending CMODE"));
353     silc_client_command_register(client, SILC_COMMAND_CMODE, NULL, NULL,
354                                  silc_client_command_reply_resume, 0,
355                                  ++conn->cmd_ident);
356     silc_client_command_send(client, conn, SILC_COMMAND_CMODE,
357                              conn->cmd_ident, 1, 1, tmp, tmp_len);
358     silc_client_command_pending(conn, SILC_COMMAND_CMODE, conn->cmd_ident,
359                                 silc_client_command_resume_cmode, session);
360   }
361
362   silc_id_payload_free(idp);
363
364   if (cmd->status != SILC_STATUS_OK &&
365       cmd->status != SILC_STATUS_LIST_END)
366     return;
367
368   /* Unregister this command reply */
369   silc_client_command_unregister(client, SILC_COMMAND_IDENTIFY, NULL, 
370                                  silc_client_command_reply_resume,
371                                  cmd->ident);
372   return;
373
374  err:
375   session->channel_count--;
376   if (!session->channel_count)
377     RESUME_CALL_COMPLETION(client, session, FALSE);
378 }
379
380 /* Received cmode to channel entry */
381
382 SILC_CLIENT_CMD_FUNC(resume_cmode)
383 {
384   SilcClientResumeSession session = context;
385   SilcClientCommandReplyContext cmd = context2;
386   SilcClient client = session->client;
387   SilcClientConnection conn = session->conn;
388   unsigned char *tmp;
389   SilcChannelID *channel_id;
390   SilcChannelEntry channel;
391   SilcUInt32 len;
392
393   SILC_LOG_DEBUG(("Start"));
394
395   /* Unregister this command reply */
396   silc_client_command_unregister(client, SILC_COMMAND_CMODE, NULL, 
397                                  silc_client_command_reply_resume,
398                                  cmd->ident);
399
400   if (cmd->error != SILC_STATUS_OK)
401     goto err;
402
403   /* Take Channel ID */
404   tmp = silc_argument_get_arg_type(cmd->args, 2, &len);
405   if (!tmp)
406     goto err;
407   channel_id = silc_id_payload_parse_id(tmp, len, NULL);
408   if (!channel_id)
409     goto err;
410
411   /* Get the channel entry */
412   channel = silc_client_get_channel_by_id(cmd->client, conn, channel_id);
413   if (channel) {
414
415     /* Get channel mode */
416     tmp = silc_argument_get_arg_type(cmd->args, 3, NULL);
417     if (tmp)
418       SILC_GET32_MSB(channel->mode, tmp);
419
420     tmp = silc_argument_get_arg_type(cmd->args, 2, &len);
421
422     /* And now, we will send USERS to get users on the channel */
423     SILC_LOG_DEBUG(("Sending USERS"));
424     silc_client_command_register(client, SILC_COMMAND_USERS, NULL, NULL,
425                                  silc_client_command_reply_users_i, 0,
426                                  ++conn->cmd_ident);
427     silc_client_command_send(client, conn, SILC_COMMAND_USERS,
428                              conn->cmd_ident, 1, 1, tmp, len);
429     silc_client_command_pending(conn, SILC_COMMAND_USERS, conn->cmd_ident,
430                                 silc_client_command_resume_users, session);
431   }
432
433   silc_free(channel_id);
434   return;
435
436  err:
437   session->channel_count--;
438   if (!session->channel_count)
439     RESUME_CALL_COMPLETION(client, session, FALSE);
440 }
441
442 /* Received users reply to a channel entry */
443
444 SILC_CLIENT_CMD_FUNC(resume_users)
445 {
446   SilcClientResumeSession session = context;
447   SilcClientCommandReplyContext cmd = context2;
448   SilcClient client = session->client;
449   SilcClientConnection conn = session->conn;
450   SilcBufferStruct client_id_list, client_mode_list;
451   unsigned char *tmp;
452   SilcUInt32 tmp_len, list_count;
453   SilcChannelEntry channel;
454   SilcChannelID *channel_id = NULL;
455
456   SILC_LOG_DEBUG(("Start"));
457
458   /* Unregister this command reply */
459   silc_client_command_unregister(client, SILC_COMMAND_USERS, NULL, 
460                                  silc_client_command_reply_users_i,
461                                  cmd->ident);
462
463   if (cmd->error != SILC_STATUS_OK)
464     goto err;
465
466   /* Get channel ID */
467   tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
468   if (!tmp) {
469     COMMAND_REPLY_ERROR;
470     goto err;
471   }
472   channel_id = silc_id_payload_parse_id(tmp, tmp_len, NULL);
473   if (!channel_id) {
474     COMMAND_REPLY_ERROR;
475     goto err;
476   }
477
478   /* Get the list count */
479   tmp = silc_argument_get_arg_type(cmd->args, 3, &tmp_len);
480   if (!tmp) {
481     COMMAND_REPLY_ERROR;
482     goto err;
483   }
484   SILC_GET32_MSB(list_count, tmp);
485
486   /* Get Client ID list */
487   tmp = silc_argument_get_arg_type(cmd->args, 4, &tmp_len);
488   if (!tmp) {
489     COMMAND_REPLY_ERROR;
490     goto err;
491   }
492   silc_buffer_set(&client_id_list, tmp, tmp_len);
493
494   /* Get client mode list */
495   tmp = silc_argument_get_arg_type(cmd->args, 5, &tmp_len);
496   if (!tmp) {
497     COMMAND_REPLY_ERROR;
498     goto err;
499   }
500   silc_buffer_set(&client_mode_list, tmp, tmp_len);
501
502   /* Get channel entry */
503   channel = silc_client_get_channel_by_id(cmd->client, conn, channel_id);
504   if (!channel)
505     goto err;
506
507   /* Send fake JOIN command reply to application */
508   client->internal->ops->command_reply(client, conn, cmd->payload, TRUE,
509                                        SILC_COMMAND_JOIN, cmd->status,
510                                        channel->channel_name, channel,
511                                        channel->mode, 0, 
512                                        NULL, NULL, NULL, NULL, 
513                                        channel->hmac, list_count,
514                                        &client_id_list, client_mode_list);
515
516   /* Send TOPIC for this channel to get the topic */
517   SILC_LOG_DEBUG(("Sending TOPIC"));
518   tmp = silc_argument_get_arg_type(cmd->args, 2, &tmp_len);
519   silc_client_command_send(client, conn, SILC_COMMAND_TOPIC,
520                            conn->cmd_ident, 1, 1, tmp, tmp_len);
521
522   /* Call the completion callback after we've got reply to all of
523      our channels */
524   session->channel_count--;
525   if (!session->channel_count)
526     RESUME_CALL_COMPLETION(client, session, TRUE);
527
528   silc_free(channel_id);
529   return;
530
531  err:
532   silc_free(channel_id);
533   session->channel_count--;
534   if (!session->channel_count)
535     RESUME_CALL_COMPLETION(client, session, FALSE);
536 }