The silc_client_connect_to_[server|client] and
authorPekka Riikonen <priikone@silcnet.org>
Tue, 12 Dec 2006 18:43:03 +0000 (18:43 +0000)
committerPekka Riikonen <priikone@silcnet.org>
Tue, 12 Dec 2006 18:43:03 +0000 (18:43 +0000)
silc_client_key_exchange now returns SilcAsyncOperation.
Fixed client library stopping in case of disconnect, abortion,
stopping, etc.
The silc_client_init now takes the running callback as argument
(remove it from client ops).
The silc_client_stop now takes stopped callback as argument.

12 files changed:
lib/silcclient/README
lib/silcclient/client.c
lib/silcclient/client_channel.c
lib/silcclient/client_connect.c
lib/silcclient/client_entry.c
lib/silcclient/client_internal.h
lib/silcclient/client_keyagr.c
lib/silcclient/client_prvmsg.c
lib/silcclient/client_register.c
lib/silcclient/command.c
lib/silcclient/command_reply.c
lib/silcclient/silcclient.h

index 66a14e7d072148aa85b4744ca57376063a251623..dc0fcae7f11ff08cef98733ad8e077f942478266 100644 (file)
@@ -76,3 +76,26 @@ When to use FSM semaphore signalling?
 
    The call cannot fail.  Semaphores need not be uninitialized and the same
    context may be reused.
+
+Finishing threads when closing connection
+
+   When closing SilcClientConnection all threads must first be finished
+   before the connection machine is finished.  This is done by finishing
+   all running command threads.  That will also finish all waiting packet
+   threads as they are always waiting for a command.  If any thread is
+   waiting for something else than a command (such as event threads) they
+   must be explicitly finished.  The threads are finished by continuing
+   them synchronously.  The threads will detect that we are disconnected
+   (see below).  SILC_FSM_YIELD must be returned in st_close() as that
+   gives the FSM scheduler time to finish the threads first.  After that
+   the machine can be finished.
+
+   Also, any thread that runs in SilcClientConnection machine must always
+   after returning from wait state to check if we are disconnected by doing
+
+     if (conn->internal->disconnected)
+       xxx;
+
+   If disconnected the thread must finish immediately by returning
+   SILC_FSM_FINISH.
+
index dd9797449ed5677a05f925d392b624fa2ef524c9..0c001e82d4f4df6e537bcfc1f2e28461bffe8e65 100644 (file)
@@ -37,6 +37,8 @@ static void silc_client_connection_destructor(SilcFSM fsm,
   SilcClientConnection conn = fsm_context;
   SilcFSMThread thread = destructor_context;
 
+  SILC_LOG_DEBUG(("Connection %p finished", conn));
+
   /* Delete connection */
   silc_client_del_connection(conn->client, conn);
 
@@ -44,6 +46,25 @@ static void silc_client_connection_destructor(SilcFSM fsm,
   silc_fsm_finish(thread);
 }
 
+/* Connection thread FSM destructor.  This was the thread where the connection
+   machine was running (may be real thread).  From here we notify client
+   that the connection thread has finished. */
+
+static void silc_client_connection_finished(SilcFSMThread fsm,
+                                           void *fsm_context,
+                                           void *destructor_context)
+{
+  SilcClient client = silc_fsm_get_state_context(fsm);
+
+  /* Signal client that we have finished */
+  silc_atomic_sub_int16(&client->internal->conns, 1);
+  client->internal->connection_closed = TRUE;
+  SILC_FSM_SEMA_POST(&client->internal->wait_event);
+
+  silc_fsm_free(fsm);
+}
+
+
 /* Packet FSM thread destructor */
 
 static void silc_client_packet_destructor(SilcFSMThread thread,
@@ -118,8 +139,10 @@ static void silc_client_packet_eos(SilcPacketEngine engine,
   SILC_LOG_DEBUG(("Remote disconnected connection"));
 
   /* Call connection callback */
-  conn->callback(client, conn, SILC_CLIENT_CONN_DISCONNECTED, 0, NULL,
-                conn->callback_context);
+  if (!conn->internal->callback_called)
+    conn->callback(client, conn, SILC_CLIENT_CONN_DISCONNECTED, 0, NULL,
+                  conn->callback_context);
+  conn->internal->callback_called = TRUE;
 
   /* Signal to close connection */
   if (!conn->internal->disconnected) {
@@ -155,6 +178,21 @@ void silc_client_fsm_destructor(SilcFSM fsm, void *fsm_context,
   silc_fsm_free(fsm);
 }
 
+/* Connect abort operation */
+
+static void silc_client_connect_abort(SilcAsyncOperation op, void *context)
+{
+  SilcClientConnection conn = context;
+
+  SILC_LOG_DEBUG(("Connection %p aborted by application", conn));
+  conn->internal->aborted = TRUE;
+
+  /* Signal to close connection */
+  if (!conn->internal->disconnected) {
+    conn->internal->disconnected = TRUE;
+    SILC_FSM_SEMA_POST(&conn->internal->wait_event);
+  }
+}
 
 /************************** Connection's machine ****************************/
 
@@ -237,9 +275,8 @@ SILC_FSM_STATE(silc_client_connection_st_run)
   if (conn->internal->disconnected) {
     /** Event: disconnected */
     SILC_LOG_DEBUG(("Event: disconnected"));
-    conn->internal->disconnected = FALSE;
     silc_fsm_next(fsm, silc_client_connection_st_close);
-    return SILC_FSM_CONTINUE;
+    return SILC_FSM_YIELD;
   }
 
   /* NOT REACHED */
@@ -344,7 +381,7 @@ SILC_FSM_STATE(silc_client_connection_st_packet)
   return SILC_FSM_CONTINUE;
 }
 
-/* Disconnection even to close remote connection.  We close the connection
+/* Disconnection event to close remote connection.  We close the connection
    and finish the connection machine in this state.  The connection context
    is deleted in the machine destructor.  The connection callback must be
    already called back to application before getting here. */
@@ -352,12 +389,40 @@ SILC_FSM_STATE(silc_client_connection_st_packet)
 SILC_FSM_STATE(silc_client_connection_st_close)
 {
   SilcClientConnection conn = fsm_context;
+  SilcClientCommandContext cmd;
 
-  SILC_LOG_DEBUG(("Closing remote connection"));
+  /* Finish running command threads.  This will also finish waiting packet
+     thread, as they are always waiting for some command.  If any thread is
+     waiting something else than command, they must be finished explicitly. */
+  if (silc_list_count(conn->internal->pending_commands)) {
+    SILC_LOG_DEBUG(("Finish pending commands"));
+    silc_list_start(conn->internal->pending_commands);
+    while ((cmd = silc_list_get(conn->internal->pending_commands))) {
+      if (silc_fsm_is_started(&cmd->thread)) {
+        cmd->verbose = FALSE;
+        silc_fsm_continue_sync(&cmd->thread);
+      }
+    }
+
+    /* Give threads time to finish */
+    return SILC_FSM_YIELD;
+  }
 
   /* Abort ongoing events */
-  if (conn->internal->op)
+  if (conn->internal->op) {
+    SILC_LOG_DEBUG(("Abort event"));
     silc_async_abort(conn->internal->op, NULL, NULL);
+    conn->internal->op = NULL;
+  }
+
+  /* If event thread is running, finish it. */
+  if (silc_fsm_is_started(&conn->internal->event_thread)) {
+    SILC_LOG_DEBUG(("Finish event thread"));
+    silc_fsm_continue_sync(&conn->internal->event_thread);
+    return SILC_FSM_YIELD;
+  }
+
+  SILC_LOG_DEBUG(("Closing remote connection"));
 
   /* Close connection */
   silc_packet_stream_destroy(conn->stream);
@@ -415,8 +480,10 @@ SILC_FSM_STATE(silc_client_disconnect)
                          silc_buffer_len(&packet->buffer));
 
   /* Call connection callback */
-  conn->callback(client, conn, SILC_CLIENT_CONN_DISCONNECTED, status,
-                message, conn->callback_context);
+  if (!conn->internal->callback_called)
+    conn->callback(client, conn, SILC_CLIENT_CONN_DISCONNECTED, status,
+                  message, conn->callback_context);
+  conn->internal->callback_called = TRUE;
 
   silc_free(message);
   silc_packet_free(packet);
@@ -443,11 +510,30 @@ SILC_FSM_STATE(silc_client_st_run)
 
   /* Process events */
 
-  if (client->internal->run_callback && client->internal->ops->running) {
+  if (client->internal->run_callback && client->internal->running) {
     /* Call running callbcak back to application */
-    SILC_LOG_DEBUG(("We are running, call running callback"));
+    SILC_LOG_DEBUG(("We are up, call running callback"));
     client->internal->run_callback = FALSE;
-    client->internal->ops->running(client, client->application);
+    client->internal->running(client, client->internal->running_context);
+    return SILC_FSM_CONTINUE;
+  }
+
+  if (client->internal->connection_closed) {
+    /* A connection finished */
+    SILC_LOG_DEBUG(("Event: connection closed"));
+    client->internal->connection_closed = FALSE;
+    if (silc_atomic_get_int16(&client->internal->conns) == 0 &&
+       client->internal->stop)
+      SILC_FSM_SEMA_POST(&client->internal->wait_event);
+    return SILC_FSM_CONTINUE;
+  }
+
+  if (client->internal->stop) {
+    /* Stop client libarry.  If we have running connections, wait until
+       they finish first. */
+    SILC_LOG_DEBUG(("Event: stop"));
+    if (silc_atomic_get_int16(&client->internal->conns) == 0)
+      silc_fsm_next(fsm, silc_client_st_stop);
     return SILC_FSM_CONTINUE;
   }
 
@@ -456,6 +542,25 @@ SILC_FSM_STATE(silc_client_st_run)
   return SILC_FSM_CONTINUE;
 }
 
+/* Stop event.  Stops the client library. */
+
+SILC_FSM_STATE(silc_client_st_stop)
+{
+  SilcClient client = fsm_context;
+
+  SILC_LOG_DEBUG(("Client stopped"));
+
+  /* Stop scheduler */
+  silc_schedule_stop(client->schedule);
+  silc_client_commands_unregister(client);
+
+  /* Call stopped callback to application */
+  if (client->internal->running)
+    client->internal->running(client, client->internal->running_context);
+
+  return SILC_FSM_FINISH;
+}
+
 /******************************* Private API ********************************/
 
 /* Adds new connection.  Creates the connection context and returns it. */
@@ -535,18 +640,29 @@ silc_client_add_connection(SilcClient client,
 
   conn->internal->ftp_sessions = silc_dlist_init();
 
+  /* Initiatlize our async operation so that application may abort us
+     while were connecting. */
+  conn->internal->cop = silc_async_alloc(silc_client_connect_abort,
+                                        NULL, conn);
+  if (!conn->internal->cop) {
+    silc_client_del_connection(client, conn);
+    return NULL;
+  }
+
   /* Run the connection state machine.  If threads are in use the machine
      is always run in a real thread. */
   thread = silc_fsm_thread_alloc(&client->internal->fsm, conn,
-                                silc_client_fsm_destructor, NULL,
+                                silc_client_connection_finished, NULL,
                                 client->internal->params->threads);
   if (!thread) {
     silc_client_del_connection(client, conn);
     return NULL;
   }
+  silc_fsm_set_state_context(thread, client);
   silc_fsm_start(thread, silc_client_connection_st_start);
 
   SILC_LOG_DEBUG(("New connection %p", conn));
+  silc_atomic_add_int16(&client->internal->conns, 1);
 
   return conn;
 }
@@ -559,7 +675,6 @@ void silc_client_del_connection(SilcClient client, SilcClientConnection conn)
   SilcList list;
   SilcIDCacheEntry entry;
   SilcFSMThread thread;
-  SilcClientCommandContext cmd;
 
   SILC_LOG_DEBUG(("Freeing connection %p", conn));
 
@@ -597,11 +712,6 @@ void silc_client_del_connection(SilcClient client, SilcClientConnection conn)
   while ((thread = silc_list_get(conn->internal->thread_pool)))
     silc_fsm_free(thread);
 
-  /* Free pending commands */
-  silc_list_start(conn->internal->pending_commands);
-  while ((cmd = silc_list_get(conn->internal->pending_commands)))
-    silc_client_command_free(cmd);
-
   silc_free(conn->remote_host);
   silc_buffer_free(conn->internal->local_idp);
   silc_buffer_free(conn->internal->remote_idp);
@@ -624,18 +734,19 @@ void silc_client_del_connection(SilcClient client, SilcClientConnection conn)
    to remote SILC server.  Performs key exchange also.  Returns the
    connection context to the connection callback. */
 
-SilcBool silc_client_connect_to_server(SilcClient client,
-                                      SilcClientConnectionParams *params,
-                                      SilcPublicKey public_key,
-                                      SilcPrivateKey private_key,
-                                      char *remote_host, int port,
-                                      SilcClientConnectCallback callback,
-                                      void *context)
+SilcAsyncOperation
+silc_client_connect_to_server(SilcClient client,
+                             SilcClientConnectionParams *params,
+                             SilcPublicKey public_key,
+                             SilcPrivateKey private_key,
+                             char *remote_host, int port,
+                             SilcClientConnectCallback callback,
+                             void *context)
 {
   SilcClientConnection conn;
 
   if (!client || !remote_host)
-    return FALSE;
+    return NULL;
 
   /* Add new connection */
   conn = silc_client_add_connection(client, SILC_CONN_SERVER, params,
@@ -643,7 +754,7 @@ SilcBool silc_client_connect_to_server(SilcClient client,
                                    port, callback, context);
   if (!conn) {
     callback(client, NULL, SILC_CLIENT_CONN_ERROR, 0, NULL, context);
-    return FALSE;
+    return NULL;
   }
 
   client->internal->ops->say(client, conn, SILC_CLIENT_MESSAGE_AUDIT,
@@ -652,24 +763,25 @@ SilcBool silc_client_connect_to_server(SilcClient client,
 
   /* Signal connection machine to start connecting */
   conn->internal->connect = TRUE;
-  return TRUE;
+  return conn->internal->cop;
 }
 
 /* Connects to remote client.  Performs key exchange also.  Returns the
    connection context to the connection callback. */
 
-SilcBool silc_client_connect_to_client(SilcClient client,
-                                      SilcClientConnectionParams *params,
-                                      SilcPublicKey public_key,
-                                      SilcPrivateKey private_key,
-                                      char *remote_host, int port,
-                                      SilcClientConnectCallback callback,
-                                      void *context)
+SilcAsyncOperation
+silc_client_connect_to_client(SilcClient client,
+                             SilcClientConnectionParams *params,
+                             SilcPublicKey public_key,
+                             SilcPrivateKey private_key,
+                             char *remote_host, int port,
+                             SilcClientConnectCallback callback,
+                             void *context)
 {
   SilcClientConnection conn;
 
   if (!client || !remote_host)
-    return FALSE;
+    return NULL;
 
   /* Add new connection */
   conn = silc_client_add_connection(client, SILC_CONN_CLIENT, params,
@@ -677,37 +789,38 @@ SilcBool silc_client_connect_to_client(SilcClient client,
                                    port, callback, context);
   if (!conn) {
     callback(client, NULL, SILC_CLIENT_CONN_ERROR, 0, NULL, context);
-    return FALSE;
+    return NULL;
   }
 
   /* Signal connection machine to start connecting */
   conn->internal->connect = TRUE;
-  return TRUE;
+  return conn->internal->cop;
 }
 
 /* Starts key exchange in the remote stream indicated by `stream'.  This
    creates the connection context and returns it in the connection callback. */
 
-SilcBool silc_client_key_exchange(SilcClient client,
-                                 SilcClientConnectionParams *params,
-                                 SilcPublicKey public_key,
-                                 SilcPrivateKey private_key,
-                                 SilcStream stream,
-                                 SilcConnectionType conn_type,
-                                 SilcClientConnectCallback callback,
-                                 void *context)
+SilcAsyncOperation
+silc_client_key_exchange(SilcClient client,
+                        SilcClientConnectionParams *params,
+                        SilcPublicKey public_key,
+                        SilcPrivateKey private_key,
+                        SilcStream stream,
+                        SilcConnectionType conn_type,
+                        SilcClientConnectCallback callback,
+                        void *context)
 {
   SilcClientConnection conn;
   const char *host;
   SilcUInt16 port;
 
   if (!client || !stream)
-    return FALSE;
+    return NULL;
 
   if (!silc_socket_stream_get_info(stream, NULL, &host, NULL, &port)) {
     SILC_LOG_ERROR(("Socket stream does not have remote host name set"));
     callback(client, NULL, SILC_CLIENT_CONN_ERROR, 0, NULL, context);
-    return FALSE;
+    return NULL;
   }
 
   /* Add new connection */
@@ -716,13 +829,13 @@ SilcBool silc_client_key_exchange(SilcClient client,
                                    (char *)host, port, callback, context);
   if (!conn) {
     callback(client, NULL, SILC_CLIENT_CONN_ERROR, 0, NULL, context);
-    return FALSE;
+    return NULL;
   }
   conn->stream = (void *)stream;
 
   /* Signal connection to start key exchange */
   conn->internal->key_exchange = TRUE;
-  return TRUE;
+  return conn->internal->cop;
 }
 
 /* Closes remote connection */
@@ -1045,6 +1158,8 @@ SilcClient silc_client_alloc(SilcClientOperations *ops,
     nickname_format[sizeof(new_client->internal->
                           params->nickname_format) - 1] = 0;
 
+  silc_atomic_init16(&new_client->internal->conns, 0);
+
   return new_client;
 }
 
@@ -1052,6 +1167,8 @@ SilcClient silc_client_alloc(SilcClientOperations *ops,
 
 void silc_client_free(SilcClient client)
 {
+  silc_schedule_uninit(client->schedule);
+
   if (client->rng)
     silc_rng_free(client->rng);
 
@@ -1062,6 +1179,7 @@ void silc_client_free(SilcClient client)
     silc_hmac_unregister_all();
   }
 
+  silc_atomic_uninit16(&client->internal->conns);
   silc_free(client->username);
   silc_free(client->hostname);
   silc_free(client->realname);
@@ -1076,7 +1194,8 @@ void silc_client_free(SilcClient client)
    client. Returns FALSE if error occured, TRUE otherwise. */
 
 SilcBool silc_client_init(SilcClient client, const char *username,
-                         const char *hostname, const char *realname)
+                         const char *hostname, const char *realname,
+                         SilcClientRunning running, void *context)
 {
   SILC_LOG_DEBUG(("Initializing client"));
 
@@ -1153,6 +1272,8 @@ SilcBool silc_client_init(SilcClient client, const char *username,
   silc_client_commands_register(client);
 
   /* Initialize and start the client FSM */
+  client->internal->running = running;
+  client->internal->running_context = context;
   silc_fsm_init(&client->internal->fsm, client, NULL, NULL, client->schedule);
   silc_fsm_sema_init(&client->internal->wait_event, &client->internal->fsm, 0);
   silc_fsm_start_sync(&client->internal->fsm, silc_client_st_run);
@@ -1164,20 +1285,6 @@ SilcBool silc_client_init(SilcClient client, const char *username,
   return TRUE;
 }
 
-/* Stops the client. This is called to stop the client and thus to stop
-   the program. */
-
-void silc_client_stop(SilcClient client)
-{
-  SILC_LOG_DEBUG(("Stopping client"));
-
-  silc_schedule_stop(client->schedule);
-  silc_schedule_uninit(client->schedule);
-  silc_client_commands_unregister(client);
-
-  SILC_LOG_DEBUG(("Client stopped"));
-}
-
 /* Starts the SILC client FSM machine and blocks here.  When this returns
    the client has ended. */
 
@@ -1194,5 +1301,22 @@ void silc_client_run(SilcClient client)
 
 void silc_client_run_one(SilcClient client)
 {
-  silc_schedule_one(client->schedule, 0);
+  if (silc_fsm_is_started(&client->internal->fsm))
+    silc_schedule_one(client->schedule, 0);
+}
+
+/* Stops the client. This is called to stop the client and thus to stop
+   the program. */
+
+void silc_client_stop(SilcClient client, SilcClientStopped stopped,
+                     void *context)
+{
+  SILC_LOG_DEBUG(("Stopping client"));
+
+  client->internal->running = (SilcClientRunning)stopped;
+  client->internal->running_context = context;
+
+  /* Signal to stop */
+  client->internal->stop = TRUE;
+  SILC_FSM_SEMA_POST(&client->internal->wait_event);
 }
index c7a414463b3a809940a60ba9c1062a8e1b4c4737..bb5c071e4c49eb69e8affdc51c5b8dc2e41fc4ab 100644 (file)
@@ -47,6 +47,8 @@ SilcBool silc_client_send_channel_message(SilcClient client,
     return FALSE;
   if (flags & SILC_MESSAGE_FLAG_SIGNED && !hash)
     return FALSE;
+  if (conn->internal->disconnected)
+    return FALSE;
 
   chu = silc_client_on_channel(channel, conn->local_entry);
   if (!chu) {
index 98b4372f3a698bb71f0a1e0ea255fa37b1a8d036..8c1491943b9f91518a6a19c66528cd0923eebd97 100644 (file)
@@ -32,6 +32,12 @@ static void silc_client_connect_callback(SilcNetStatus status,
   SilcClientConnection conn = silc_fsm_get_context(fsm);
   SilcClient client = conn->client;
 
+  if (conn->internal->aborted) {
+    silc_fsm_next(fsm, silc_client_st_connect_error);
+    SILC_FSM_CALL_CONTINUE(fsm);
+    return;
+  }
+
   conn->internal->op = NULL;
   if (conn->internal->verbose) {
     switch (status) {
@@ -121,6 +127,14 @@ static void silc_client_ke_verify_key(SilcSKE ske,
   SilcClient client = conn->client;
   VerifyKeyContext verify;
 
+  if (conn->internal->aborted) {
+    completion(ske, SILC_SKE_STATUS_UNSUPPORTED_PUBLIC_KEY,
+              completion_context);
+    silc_fsm_next(fsm, silc_client_st_connect_error);
+    SILC_FSM_CALL_CONTINUE(fsm);
+    return;
+  }
+
   /* If we provided repository for SKE and we got here the key was not
      found from the repository. */
   if (conn->internal->params.repository &&
@@ -164,6 +178,13 @@ static void silc_client_ke_completion(SilcSKE ske,
   SilcCipher send_key, receive_key;
   SilcHmac hmac_send, hmac_receive;
 
+  if (conn->internal->aborted) {
+    silc_ske_free_rekey_material(rekey);
+    silc_fsm_next(fsm, silc_client_st_connect_error);
+    SILC_FSM_CALL_CONTINUE(fsm);
+    return;
+  }
+
   conn->internal->op = NULL;
   if (status != SILC_SKE_STATUS_OK) {
     /* Key exchange failed */
@@ -307,6 +328,13 @@ static void silc_client_connect_auth_completion(SilcConnAuth connauth,
   SilcClientConnection conn = silc_fsm_get_context(fsm);
   SilcClient client = conn->client;
 
+  if (conn->internal->aborted) {
+    silc_fsm_next(fsm, silc_client_st_connect_error);
+    SILC_FSM_CALL_CONTINUE(fsm);
+    return;
+  }
+
+  conn->internal->op = NULL;
   silc_connauth_free(connauth);
 
   if (!success) {
@@ -400,6 +428,12 @@ SILC_FSM_STATE(silc_client_st_connect_set_stream)
   SilcClientConnection conn = fsm_context;
   SilcClient client = conn->client;
 
+  if (conn->internal->disconnected) {
+    /** Disconnected */
+    silc_fsm_next(fsm, silc_client_st_connect_error);
+    return SILC_FSM_CONTINUE;
+  }
+
   /* Create packet stream */
   conn->stream = silc_packet_stream_create(client->internal->packet_engine,
                                           conn->internal->schedule,
@@ -484,6 +518,12 @@ SILC_FSM_STATE(silc_client_st_connect_setup_udp)
 
   SILC_LOG_DEBUG(("Setup UDP SILC session"));
 
+  if (conn->internal->disconnected) {
+    /** Disconnected */
+    silc_fsm_next(fsm, silc_client_st_connect_error);
+    return SILC_FSM_CONTINUE;
+  }
+
   /* Create new UDP stream */
   prop = silc_ske_get_security_properties(conn->internal->ske);
   stream = silc_net_udp_connect(conn->internal->params.local_ip,
@@ -522,6 +562,12 @@ SILC_FSM_STATE(silc_client_st_connect_auth)
 
   SILC_LOG_DEBUG(("Get authentication data"));
 
+  if (conn->internal->disconnected) {
+    /** Disconnected */
+    silc_fsm_next(fsm, silc_client_st_connect_error);
+    return SILC_FSM_CONTINUE;
+  }
+
   silc_fsm_next(fsm, silc_client_st_connect_auth_start);
 
   /* If authentication data not provided, ask from application */
@@ -549,6 +595,12 @@ SILC_FSM_STATE(silc_client_st_connect_auth_start)
 
   SILC_LOG_DEBUG(("Starting connection authentication protocol"));
 
+  if (conn->internal->disconnected) {
+    /** Disconnected */
+    silc_fsm_next(fsm, silc_client_st_connect_error);
+    return SILC_FSM_CONTINUE;
+  }
+
   /* Allocate connection authentication protocol */
   connauth = silc_connauth_alloc(conn->internal->schedule,
                                 conn->internal->ske,
@@ -579,11 +631,17 @@ SILC_FSM_STATE(silc_client_st_connected)
   SilcClientConnection conn = fsm_context;
   SilcClient client = conn->client;
 
-  SILC_LOG_DEBUG(("Connection established"));
-
   silc_ske_free(conn->internal->ske);
   conn->internal->ske = NULL;
 
+  if (conn->internal->disconnected) {
+    /** Disconnected */
+    silc_fsm_next(fsm, silc_client_st_connect_error);
+    return SILC_FSM_CONTINUE;
+  }
+
+  SILC_LOG_DEBUG(("Connection established"));
+
   /* Install rekey timer */
   silc_schedule_task_add_timeout(conn->internal->schedule,
                                 silc_client_rekey_timer, conn,
@@ -648,6 +706,7 @@ SILC_TASK_CALLBACK(silc_client_rekey_timer)
   SilcClientConnection conn = context;
 
   /* Signal to start rekey */
+  conn->internal->rekey_responder = FALSE;
   conn->internal->rekeying = TRUE;
   SILC_FSM_SEMA_POST(&conn->internal->wait_event);
 
@@ -666,6 +725,9 @@ SILC_FSM_STATE(silc_client_st_rekey)
 
   SILC_LOG_DEBUG(("Rekey"));
 
+  if (conn->internal->disconnected)
+    return SILC_FSM_FINISH;
+
   /* Allocate SKE */
   conn->internal->ske =
     silc_ske_alloc(client->rng, conn->internal->schedule,
index 980daea66746322437b3119d7f1fcd7026a99e7a..1d3189ba04f89791e06dfef82c9aeba0ed6f02f6 100644 (file)
@@ -750,7 +750,7 @@ SilcClientEntry silc_client_add_client(SilcClient client,
   silc_mutex_unlock(conn->internal->lock);
   silc_client_ref_client(client, conn, client_entry);
 
-  SILC_LOG_DEBUG(("Added"));
+  SILC_LOG_DEBUG(("Added %p", client_entry));
 
   return client_entry;
 }
@@ -1324,7 +1324,7 @@ SilcChannelEntry silc_client_add_channel(SilcClient client,
   silc_mutex_unlock(conn->internal->lock);
   silc_client_ref_channel(client, conn, channel);
 
-  SILC_LOG_DEBUG(("Added"));
+  SILC_LOG_DEBUG(("Added %p", channel));
 
   return channel;
 }
@@ -1702,7 +1702,7 @@ SilcServerEntry silc_client_add_server(SilcClient client,
   silc_mutex_unlock(conn->internal->lock);
   silc_client_ref_server(client, conn, server_entry);
 
-  SILC_LOG_DEBUG(("Added"));
+  SILC_LOG_DEBUG(("Added %p", server_entry));
 
   return server_entry;
 }
@@ -1784,6 +1784,9 @@ void silc_client_ref_server(SilcClient client, SilcClientConnection conn,
                            SilcServerEntry server_entry)
 {
   silc_atomic_add_int8(&server_entry->internal.refcnt, 1);
+  SILC_LOG_DEBUG(("Server %p refcnt %d->%d", server_entry,
+                 silc_atomic_get_int8(&server_entry->internal.refcnt) - 1,
+                 silc_atomic_get_int8(&server_entry->internal.refcnt)));
 }
 
 /* Release reference of server entry */
@@ -1791,7 +1794,13 @@ void silc_client_ref_server(SilcClient client, SilcClientConnection conn,
 void silc_client_unref_server(SilcClient client, SilcClientConnection conn,
                              SilcServerEntry server_entry)
 {
-  silc_client_del_server(client, conn, server_entry);
+  if (server_entry) {
+    SILC_LOG_DEBUG(("Server %p refcnt %d->%d", server_entry,
+                   silc_atomic_get_int8(&server_entry->internal.refcnt),
+                   silc_atomic_get_int8(&server_entry->internal.refcnt)
+                   - 1));
+    silc_client_del_server(client, conn, server_entry);
+  }
 }
 
 /* Free server entry list */
index 0acee44df7bbbdfe4c7593bebee14bbc3b15cd06..d3437edfc061c6cfd638c59688c2c8d7bd709ac3 100644 (file)
@@ -145,9 +145,14 @@ struct SilcClientInternalStruct {
   SilcMutex lock;                       /* Client lock */
   SilcList commands;                    /* Registered commands */
   char *silc_client_version;            /* Version set by application */
+  SilcClientRunning running;            /* Running/Stopped callback */
+  void *running_context;                /* Context for runnign callback */
+  SilcAtomic16 conns;                   /* Number of connections in client */
 
   /* Events */
-  unsigned int run_callback    : 1;     /* Call running callback */
+  unsigned int stop              : 1;   /* Stop client */
+  unsigned int run_callback      : 1;   /* Call running/stopped callback */
+  unsigned int connection_closed : 1;   /* A connection closed */
 };
 
 /* Internal context for conn->internal in SilcClientConnection. */
@@ -169,6 +174,7 @@ struct SilcClientConnectionInternalStruct {
   SilcBuffer local_idp;                         /* Local ID Payload */
   SilcBuffer remote_idp;                /* Remote ID Payload */
   SilcAsyncOperation op;                /* Protocols async operation */
+  SilcAsyncOperation cop;               /* Async operation for application */
 
   SilcIDCache client_cache;             /* Client entry cache */
   SilcIDCache channel_cache;            /* Channel entry cache */
@@ -188,6 +194,8 @@ struct SilcClientConnectionInternalStruct {
   unsigned int verbose            : 1;   /* Notify application */
   unsigned int registering        : 1;  /* Set when registering to network */
   unsigned int rekey_responder    : 1;   /* Set when rekeying as responder */
+  unsigned int callback_called    : 1;   /* Set when connect callback called */
+  unsigned int aborted            : 1;  /* Set when aborted by application */
 
   SilcClientAway *away;
   SilcClientConnAuthRequest connauth;
@@ -203,6 +211,7 @@ SILC_FSM_STATE(silc_client_connection_st_packet);
 SILC_FSM_STATE(silc_client_connection_st_close);
 SILC_FSM_STATE(silc_client_error);
 SILC_FSM_STATE(silc_client_disconnect);
+SILC_FSM_STATE(silc_client_st_stop);
 
 void silc_client_del_connection(SilcClient client, SilcClientConnection conn);
 SilcBool silc_client_del_client(SilcClient client, SilcClientConnection conn,
index 52dca47c040be11f66ac6e3782fbc0fd95c6b742..d5f9048508475838629877f37912da4637a3851b 100644 (file)
@@ -362,6 +362,8 @@ void silc_client_send_key_agreement(SilcClient client,
 
   if (!client_entry)
     return;
+  if (conn->internal->disconnected)
+    return;
 
   if (client_entry->internal.ke) {
     completion(client, conn, client_entry, SILC_KEY_AGREEMENT_ALREADY_STARTED,
index 3b45718a669a922040d7de9bf94ee342ada0b2dd..deb5d9a87e7a3c5ea4f213a5132aa4644b264d18 100644 (file)
@@ -41,6 +41,8 @@ SilcBool silc_client_send_private_message(SilcClient client,
     return FALSE;
   if (flags & SILC_MESSAGE_FLAG_SIGNED && !hash)
     return FALSE;
+  if (conn->internal->disconnected)
+    return FALSE;
 
   SILC_LOG_DEBUG(("Sending private message"));
 
index 977118032b854e7fdfb8d373b60a425acdafd948..42cb36772e682fa5a670193c1126e7bb7cffb1ef 100644 (file)
@@ -186,6 +186,18 @@ SILC_FSM_STATE(silc_client_st_register_complete)
   SilcClientConnection conn = fsm_context;
   SilcClient client = conn->client;
 
+  if (conn->internal->aborted) {
+    /** Aborted */
+    silc_fsm_next(fsm, silc_client_st_register_error);
+    return SILC_FSM_CONTINUE;
+  }
+
+  if (conn->internal->disconnected) {
+    /** Disconnected */
+    silc_fsm_next(fsm, silc_client_st_register_error);
+    return SILC_FSM_CONTINUE;
+  }
+
   if (!conn->local_id) {
     if (conn->internal->retry_count++ >= SILC_CLIENT_RETRY_COUNT) {
       /** Timeout, ID not received */
@@ -255,8 +267,10 @@ SILC_FSM_STATE(silc_client_st_register_error)
   }
 
   /* Call connect callback */
-  conn->callback(client, conn, SILC_CLIENT_CONN_ERROR, 0, NULL,
-                conn->callback_context);
+  if (conn->internal->callback_called)
+    conn->callback(client, conn, SILC_CLIENT_CONN_ERROR, 0, NULL,
+                  conn->callback_context);
+  conn->internal->callback_called = TRUE;
 
   silc_schedule_task_del_by_all(conn->internal->schedule, 0,
                                silc_client_connect_timeout, conn);
index 7ffa52fa36f471a90d84ecfcb6f5014447a41bfb..602c527805f3c90f23a7ecd769a256e9e1c6f8e3 100644 (file)
@@ -203,7 +203,14 @@ static void silc_client_command_destructor(SilcFSMThread thread,
                                           void *fsm_context,
                                           void *destructor_context)
 {
-  silc_client_command_free(fsm_context);
+  SilcClientCommandContext cmd = fsm_context;
+  SilcClientConnection conn = cmd->conn;
+
+  /* Removes commands that aren't waiting for reply but are waiting
+     for something.  They may not have been removed yet. */
+  silc_list_del(conn->internal->pending_commands, cmd);
+
+  silc_client_command_free(cmd);
 }
 
 /* Add a command pending a command reply.  Used internally by the library. */
@@ -254,6 +261,9 @@ static SilcUInt16 silc_client_command_send_vap(SilcClient client,
 
   SILC_LOG_DEBUG(("Send command %s", silc_get_command_name(command)));
 
+  if (conn->internal->disconnected)
+    return 0;
+
   if (!cmd->cmd_ident)
     cmd->cmd_ident = silc_client_cmd_ident(conn);
 
@@ -297,6 +307,9 @@ silc_client_command_send_arg_array(SilcClient client,
 
   SILC_LOG_DEBUG(("Send command %s", silc_get_command_name(command)));
 
+  if (conn->internal->disconnected)
+    return 0;
+
   if (!cmd->cmd_ident)
     cmd->cmd_ident = silc_client_cmd_ident(conn);
 
@@ -352,12 +365,6 @@ void silc_client_command_free(SilcClientCommandContext cmd)
   SilcClientCommandReplyCallback cb;
   int i;
 
-  /* If command is running, finish it.  Destructor will free the context. */
-  if (silc_fsm_is_started(&cmd->thread)) {
-    silc_fsm_finish(&cmd->thread);
-    return;
-  }
-
   for (i = 0; i < cmd->argc; i++)
     silc_free(cmd->argv[i]);
   silc_free(cmd->argv);
@@ -1077,12 +1084,16 @@ SILC_FSM_STATE(silc_client_command_quit_final)
   SilcClientConnection conn = cmd->conn;
   SilcClient client = conn->client;
 
+  SILC_LOG_DEBUG(("Quitting"));
+
   /* Notify application */
   COMMAND(SILC_STATUS_OK);
 
   /* Call connection callback */
-  conn->callback(client, conn, SILC_CLIENT_CONN_DISCONNECTED,
-                0, NULL, conn->callback_context);
+  if (!conn->internal->callback_called)
+    conn->callback(client, conn, SILC_CLIENT_CONN_DISCONNECTED,
+                  0, NULL, conn->callback_context);
+  conn->internal->callback_called = TRUE;
 
   /* Signal to close connection */
   if (!conn->internal->disconnected) {
index ea1f74b0f2cd98e8da17d5759e04a0b2e046f366..0e952e9fff8068c9768a5d8854bc78c2ee1ba9c4 100644 (file)
@@ -195,6 +195,12 @@ SILC_FSM_STATE(silc_client_command_reply_timeout)
   SilcClientConnection conn = cmd->conn;
   SilcArgumentPayload args = NULL;
 
+  if (conn->internal->disconnected) {
+    SILC_LOG_DEBUG(("Command %s canceled", silc_get_command_name(cmd->cmd)));
+    silc_list_del(conn->internal->pending_commands, cmd);
+    return SILC_FSM_FINISH;
+  }
+
   SILC_LOG_DEBUG(("Command %s timeout", silc_get_command_name(cmd->cmd)));
 
   /* Timeout, reply not received in timely fashion */
index f6fdb7588efbcdec18861ac18b9d1385c162b978..ee1e0edd039bd308d52cdac1854d0826233010bf 100644 (file)
@@ -83,6 +83,39 @@ typedef enum {
 } SilcClientConnectionStatus;
 /***/
 
+/****f* silcclient/SilcClientAPI/SilcClientRunning
+ *
+ * SYNOPSIS
+ *
+ *    typedef void (*SilcClientRunning)(SilcClient client, void *context);
+ *
+ * DESCRIPTION
+ *
+ *    The callback given as argument to silc_client_init function.  Once
+ *    this is called the client library is running and application may
+ *    start using the Client library API.
+ *
+ ***/
+typedef void (*SilcClientRunning)(SilcClient client, void *context);
+
+/****f* silcclient/SilcClientAPI/SilcClientStopped
+ *
+ * SYNOPSIS
+ *
+ *    typedef void (*SilcClientStopped)(SilcClient client, void *context);
+ *
+ * DESCRIPTION
+ *
+ *    The callback given as argument to silc_client_stop.  Once this is
+ *    called the client library has stopped and can be freed by calling
+ *    silc_client_free.  Note that this won't be called if there are
+ *    active connections in the client.  Connections must first be closed
+ *    by calling silc_client_close_connection or by sending QUIT command to
+ *    the server connection.
+ *
+ ***/
+typedef void (*SilcClientStopped)(SilcClient client, void *context);
+
 /****f* silcclient/SilcClientAPI/SilcClientConnectCallback
  *
  * SYNOPSIS
@@ -594,11 +627,6 @@ typedef struct {
   void (*detach)(SilcClient client, SilcClientConnection conn,
                 const unsigned char *detach_data,
                 SilcUInt32 detach_data_len);
-
-  /* Called when the client library is up and running.  After this callback
-     is called the application may start using the client library APIs. */
-  void (*running)(SilcClient client, void *application);
-
 } SilcClientOperations;
 /***/
 
@@ -763,7 +791,8 @@ void silc_client_free(SilcClient client);
  * SYNOPSIS
  *
  *    SilcBool silc_client_init(SilcClient client, const char *username,
- *                              const char *hostname, const char *realname);
+ *                              const char *hostname, const char *realname,
+ *                              SilcClientRunning running, void *context);
  *
  * DESCRIPTION
  *
@@ -776,9 +805,15 @@ void silc_client_free(SilcClient client);
  *    in the operating system, `hostname' is the client's host name and
  *    the `realname' is the user's real name.
  *
+ *    The `running' callback is called after the client is running after
+ *    silc_client_run or silc_client_run_one has been called.  Application
+ *    may start using the Client library API after that.  Setting the
+ *    callback is optional, but recommended.
+ *
  ***/
 SilcBool silc_client_init(SilcClient client, const char *username,
-                         const char *hostname, const char *realname);
+                         const char *hostname, const char *realname,
+                         SilcClientRunning running, void *context);
 
 /****f* silcclient/SilcClientAPI/silc_client_run
  *
@@ -788,7 +823,7 @@ SilcBool silc_client_init(SilcClient client, const char *username,
  *
  * DESCRIPTION
  *
- *    Runs the client. This starts the scheduler from the utility library.
+ *    Runs the client.  This starts the scheduler from the utility library.
  *    When this functions returns the execution of the application is over.
  *    The client must be initialized before calling this.
  *
@@ -814,21 +849,30 @@ void silc_client_run(SilcClient client);
  ***/
 void silc_client_run_one(SilcClient client);
 
+
 /****f* silcclient/SilcClientAPI/silc_client_stop
  *
  * SYNOPSIS
  *
- *    void silc_client_stop(SilcClient client);
+ *    void silc_client_stop(SilcClient client, SilcClientStopped stopped,
+ *                          void *context);
  *
  * DESCRIPTION
  *
  *    Stops the client. This is called to stop the client and thus to stop
  *    the program.  The client context must be freed with the silc_client_free
- *    function.
+ *    function.  All connections that exist in this client must be closed
+ *    before calling this function.  Connections can be closed by calling
+ *    silc_client_close_connection.
+ *
+ *    The `stopped' will be called once the client and all connections have
+ *    finished.  The client may be freed after that.  Note that the `stopped'
+ *    won't be called before all connections have finished.  Setting the
+ *    callback is optional.
  *
  ***/
-void silc_client_stop(SilcClient client);
-
+void silc_client_stop(SilcClient client, SilcClientStopped stopped,
+                     void *context);
 
 /* Connecting functions */
 
@@ -935,7 +979,7 @@ typedef struct {
  *
  * SYNOPSIS
  *
- *    SilcBool
+ *    SilcAsyncOperation
  *    silc_client_connect_to_server(SilcClient client,
  *                                  SilcClientConnectionParams *params,
  *                                  SilcPublicKey public_key,
@@ -959,23 +1003,25 @@ typedef struct {
  *    the silc_client_key_exchange after creating the connection to start
  *    key exchange and authentication with the server.
  *
- *    Returns when connecting is started and FALSE if connection was not
- *    created at all.
+ *    Returns SilcAsyncOperation which can be used to cancel the connecting,
+ *    or NULL on error.  Note that the returned pointer becomes invalid
+ *    after the `callback' is called.
  *
  ***/
-SilcBool silc_client_connect_to_server(SilcClient client,
-                                      SilcClientConnectionParams *params,
-                                      SilcPublicKey public_key,
-                                      SilcPrivateKey private_key,
-                                      char *remote_host, int port,
-                                      SilcClientConnectCallback callback,
-                                      void *context);
+SilcAsyncOperation
+silc_client_connect_to_server(SilcClient client,
+                             SilcClientConnectionParams *params,
+                             SilcPublicKey public_key,
+                             SilcPrivateKey private_key,
+                             char *remote_host, int port,
+                             SilcClientConnectCallback callback,
+                             void *context);
 
 /****f* silcclient/SilcClientAPI/silc_client_connect_to_client
  *
  * SYNOPSIS
  *
- *    SilcBool
+ *    SilcAsyncOperation
  *    silc_client_connect_to_client(SilcClient client,
  *                                  SilcClientConnectionParams *params,
  *                                  SilcPublicKey public_key,
@@ -998,23 +1044,25 @@ SilcBool silc_client_connect_to_server(SilcClient client,
  *    the silc_client_key_exchange after creating the connection to start
  *    key exchange with the client.
  *
- *    Returns when connecting is started and FALSE if connection was not
- *    created at all.
+ *    Returns SilcAsyncOperation which can be used to cancel the connecting,
+ *    or NULL on error.  Note that the returned pointer becomes invalid
+ *    after the `callback' is called.
  *
  ***/
-SilcBool silc_client_connect_to_client(SilcClient client,
-                                      SilcClientConnectionParams *params,
-                                      SilcPublicKey public_key,
-                                      SilcPrivateKey private_key,
-                                      char *remote_host, int port,
-                                      SilcClientConnectCallback callback,
-                                      void *context);
+SilcAsyncOperation
+silc_client_connect_to_client(SilcClient client,
+                             SilcClientConnectionParams *params,
+                             SilcPublicKey public_key,
+                             SilcPrivateKey private_key,
+                             char *remote_host, int port,
+                             SilcClientConnectCallback callback,
+                             void *context);
 
 /****f* silcclient/SilcClientAPI/silc_client_key_exchange
  *
  * SYNOPSIS
  *
- *    SilcBool
+ *    SilcAsyncOperation
  *    silc_client_key_exchange(SilcClient client,
  *                             SilcClientConnectionParams *params,
  *                             SilcPublicKey public_key,
@@ -1047,8 +1095,9 @@ SilcBool silc_client_connect_to_client(SilcClient client,
  *    disconnects.  The `conn_type' is the type of session this is going to
  *    be.
  *
- *    Returns TRUE when key exchange is started and FALSE if it is not started
- *    at all.
+ *    Returns SilcAsyncOperation which can be used to cancel the connecting,
+ *    or NULL on error.  Note that the returned pointer becomes invalid
+ *    after the `callback' is called.
  *
  * EXAMPLE
  *
@@ -1074,14 +1123,15 @@ SilcBool silc_client_connect_to_client(SilcClient client,
  *    }
  *
  ***/
-SilcBool silc_client_key_exchange(SilcClient client,
-                                 SilcClientConnectionParams *params,
-                                 SilcPublicKey public_key,
-                                 SilcPrivateKey private_key,
-                                 SilcStream stream,
-                                 SilcConnectionType conn_type,
-                                 SilcClientConnectCallback callback,
-                                 void *context);
+SilcAsyncOperation
+silc_client_key_exchange(SilcClient client,
+                        SilcClientConnectionParams *params,
+                        SilcPublicKey public_key,
+                        SilcPrivateKey private_key,
+                        SilcStream stream,
+                        SilcConnectionType conn_type,
+                        SilcClientConnectCallback callback,
+                        void *context);
 
 /****f* silcclient/SilcClientAPI/silc_client_close_connection
  *