/*

  server_backup.c

  Author: Pekka Riikonen <priikone@silcnet.org>

  Copyright (C) 2001 - 2014 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 "serverincludes.h"
#include "server_internal.h"

SILC_TASK_CALLBACK(silc_server_protocol_backup_done);
SILC_TASK_CALLBACK(silc_server_backup_announce_watches);

static void silc_server_backup_connect_primary(SilcServer server,
					       SilcServerEntry server_entry,
					       void *context);


/************************** Types and Definitions ***************************/

/* Backup router */
typedef struct {
  SilcServerEntry server;
  SilcIDIP ip;
  SilcUInt16 port;
  SilcBool local;
} SilcServerBackupEntry;

/* Holds IP address and port of the primary router that was replaced
   by backup router. */
typedef struct {
  SilcIDIP ip;
  SilcUInt16 port;
  SilcServerEntry server;	/* Backup router that replaced the primary */
} SilcServerBackupReplaced;

/* Backup context */
struct SilcServerBackupStruct {
  SilcServerBackupEntry *servers;
  SilcUInt32 servers_count;
  SilcServerBackupReplaced **replaced;
  SilcUInt32 replaced_count;
};

typedef struct {
  SilcUInt8 session;
  SilcBool connected;
  SilcServerEntry server_entry;
} SilcServerBackupProtocolSession;

/* Backup resuming protocol context  */
typedef struct {
  SilcServer server;
  SilcPacketStream sock;
  SilcUInt8 type;
  SilcUInt8 session;
  SilcServerBackupProtocolSession *sessions;
  SilcUInt32 sessions_count;
  SilcUInt32 initiator_restart;
  long start;
  int state;
  unsigned int responder        : 1;
  unsigned int received_failure : 1;
  unsigned int timeout          : 1;
  unsigned int error            : 1;
} *SilcServerBackupProtocolContext;


/********************* Backup Configuration Routines ************************/

/* Adds the `backup_server' to be one of our backup router. This can be
   called multiple times to set multiple backup routers. The `ip' and `port'
   is the IP and port that the `backup_router' will replace if the `ip'
   will become unresponsive. If `local' is TRUE then the `backup_server' is
   in the local cell, if FALSE it is in some other cell. */

void silc_server_backup_add(SilcServer server, SilcServerEntry backup_server,
			    const char *ip, int port, SilcBool local)
{
  int i;

  if (!ip)
    return;

  if (!server->backup) {
    server->backup = silc_calloc(1, sizeof(*server->backup));
    if (!server->backup)
      return;
  }

  /* See if already added */
  for (i = 0; i < server->backup->servers_count; i++) {
    if (server->backup->servers[i].server == backup_server)
      return;
  }

  SILC_LOG_DEBUG(("Backup router %s will replace %s",
		  backup_server->data.sconn ?
		  backup_server->data.sconn->remote_host : "(me)", ip));

  for (i = 0; i < server->backup->servers_count; i++) {
    if (!server->backup->servers[i].server) {
      server->backup->servers[i].server = backup_server;
      server->backup->servers[i].local = local;
      server->backup->servers[i].port = SILC_SWAB_16(port);
      memset(server->backup->servers[i].ip.data, 0,
	     sizeof(server->backup->servers[i].ip.data));
      silc_net_addr2bin(ip, server->backup->servers[i].ip.data,
			sizeof(server->backup->servers[i].ip.data));
      return;
    }
  }

  i = server->backup->servers_count;
  server->backup->servers = silc_realloc(server->backup->servers,
					 sizeof(*server->backup->servers) *
					 (i + 1));
  server->backup->servers[i].server = backup_server;
  server->backup->servers[i].local = local;
  server->backup->servers[i].port = SILC_SWAB_16(port);
  memset(server->backup->servers[i].ip.data, 0,
	 sizeof(server->backup->servers[i].ip.data));
  silc_net_addr2bin(ip, server->backup->servers[i].ip.data,
		    sizeof(server->backup->servers[i].ip.data));
  server->backup->servers_count++;
}

/* Returns backup router for IP and port in `server_id' or NULL if there
   does not exist backup router. */

SilcServerEntry silc_server_backup_get(SilcServer server,
				       SilcServerID *server_id)
{
  int i;

  if (!server->backup)
    return NULL;

  for (i = 0; i < server->backup->servers_count; i++) {
    if (server->backup->servers[i].server &&
	server->backup->servers[i].port == server_id->port &&
	!memcmp(server->backup->servers[i].ip.data, server_id->ip.data,
		sizeof(server_id->ip.data))) {
      SILC_LOG_DEBUG(("Found backup router %s for %s",
		      server->backup->servers[i].server->server_name,
		      silc_id_render(server_id, SILC_ID_SERVER)));
      return server->backup->servers[i].server;
    }
  }

  return NULL;
}

/* Deletes the backup server `server_entry'. */

void silc_server_backup_del(SilcServer server, SilcServerEntry server_entry)
{
  int i;

  if (!server->backup)
    return;

  for (i = 0; i < server->backup->servers_count; i++) {
    if (server->backup->servers[i].server == server_entry) {
      SILC_LOG_DEBUG(("Removing %s as backup router",
		      silc_id_render(server->backup->servers[i].server->id,
				     SILC_ID_SERVER)));
      server->backup->servers[i].server = NULL;
      memset(server->backup->servers[i].ip.data, 0,
	     sizeof(server->backup->servers[i].ip.data));
    }
  }
}

/* Frees all data allocated for backup routers.  Call this after deleting
   all backup routers and when new routers are added no more, for example
   when shutting down the server. */

void silc_server_backup_free(SilcServer server)
{
  int i;

  if (!server->backup)
    return;

  /* Delete existing servers if caller didn't do it */
  for (i = 0; i < server->backup->servers_count; i++) {
    if (server->backup->servers[i].server)
      silc_server_backup_del(server, server->backup->servers[i].server);
  }

  silc_free(server->backup->servers);
  silc_free(server->backup);
  server->backup = NULL;
}

/* Marks the IP address and port from the `server_id' as  being replaced
   by backup router indicated by the `server'. If the router connects at
   a later time we can check whether it has been replaced by an backup
   router. */

void silc_server_backup_replaced_add(SilcServer server,
				     SilcServerID *server_id,
				     SilcServerEntry server_entry)
{
  int i;
  SilcServerBackupReplaced *r = silc_calloc(1, sizeof(*r));;

  if (!r)
    return;
  if (!server->backup)
    server->backup = silc_calloc(1, sizeof(*server->backup));
  if (!server->backup)
    return;
  if (!server->backup->replaced) {
    server->backup->replaced =
      silc_calloc(1, sizeof(*server->backup->replaced));
    server->backup->replaced_count = 1;
  }
  if (!server->backup->replaced)
    return;

  SILC_LOG_DEBUG(("Replacing router %s with %s",
		  silc_id_render(server_id, SILC_ID_SERVER),
		  server_entry->server_name));

  memcpy(&r->ip, &server_id->ip, sizeof(server_id->ip));
  r->server = server_entry;

  for (i = 0; i < server->backup->replaced_count; i++) {
    if (!server->backup->replaced[i]) {
      server->backup->replaced[i] = r;
      return;
    }
  }

  i = server->backup->replaced_count;
  server->backup->replaced = silc_realloc(server->backup->replaced,
					  sizeof(*server->backup->replaced) *
					  (i + 1));
  server->backup->replaced[i] = r;
  server->backup->replaced_count++;
}

/* Checks whether the IP address and port from the `server_id' has been
   replaced by an backup router. If it has been then this returns TRUE
   and the bacup router entry to the `server' pointer if non-NULL. Returns
   FALSE if the router is not replaced by backup router. */

SilcBool silc_server_backup_replaced_get(SilcServer server,
					 SilcServerID *server_id,
					 SilcServerEntry *server_entry)
{
  int i;

  if (!server->backup || !server->backup->replaced)
    return FALSE;

  for (i = 0; i < server->backup->replaced_count; i++) {
    if (!server->backup->replaced[i])
      continue;
    if (!memcmp(server->backup->replaced[i]->ip.data, server_id->ip.data,
		sizeof(server_id->ip.data))) {
      if (server_entry)
	*server_entry = server->backup->replaced[i]->server;
      SILC_LOG_DEBUG(("Router %s is replaced by %s",
		      silc_id_render(server_id, SILC_ID_SERVER),
		      server->backup->replaced[i]->server->server_name));
      return TRUE;
    }
  }

  SILC_LOG_DEBUG(("Router %s is not replaced by backup router",
		  silc_id_render(server_id, SILC_ID_SERVER)));
  return FALSE;
}

/* Deletes a replaced host by the set `server_entry. */

void silc_server_backup_replaced_del(SilcServer server,
				     SilcServerEntry server_entry)
{
  int i;

  if (!server->backup || !server->backup->replaced)
    return;

  for (i = 0; i < server->backup->replaced_count; i++) {
    if (!server->backup->replaced[i])
      continue;
    if (server->backup->replaced[i]->server == server_entry) {
      silc_free(server->backup->replaced[i]);
      server->backup->replaced[i] = NULL;
    }
  }
}

/* Broadcast the received packet indicated by `packet' to all of our backup
   routers. All router wide information is passed using broadcast packets.
   That is why all backup routers need to get this data too. It is expected
   that the caller already knows that the `packet' is broadcast packet. */

void silc_server_backup_broadcast(SilcServer server,
				  SilcPacketStream sender,
				  SilcPacket packet)
{
  SilcServerEntry backup;
  SilcPacketStream sock;
  int i;

  if (!server->backup || server->server_type != SILC_ROUTER)
    return;

  SILC_LOG_DEBUG(("Broadcasting received packet to backup routers"));

  for (i = 0; i < server->backup->servers_count; i++) {
    backup = server->backup->servers[i].server;

    if (!backup || backup->connection == sender ||
	server->backup->servers[i].local == FALSE)
      continue;
    if (server->backup->servers[i].server == server->id_entry)
      continue;

    sock = backup->connection;
    silc_server_packet_route(server, sock, packet);
  }
}

/* A generic routine to send data to all backup routers. If the `sender'
   is provided it will indicate the original sender of the packet and the
   packet won't be resent to that entity. The `data' is the data that will
   be assembled to packet context before sending. The packet will be
   encrypted this function. If the `force_send' is TRUE the data is sent
   immediately and not put to queue. If `local' is TRUE then the packet
   will be sent only to local backup routers inside the cell. If false the
   packet can go from one cell to the other. This function has no effect
   if there are no any backup routers. */

void silc_server_backup_send(SilcServer server,
			     SilcServerEntry sender,
			     SilcPacketType type,
			     SilcPacketFlags flags,
			     unsigned char *data,
			     SilcUInt32 data_len,
			     SilcBool force_send,
			     SilcBool local)
{
  SilcServerEntry backup;
  int i;

  if (!server->backup || server->server_type != SILC_ROUTER)
    return;

  for (i = 0; i < server->backup->servers_count; i++) {
    backup = server->backup->servers[i].server;
    if (!backup || sender == backup)
      continue;
    if (local && server->backup->servers[i].local == FALSE)
      continue;
    if (server->backup->servers[i].server == server->id_entry)
      continue;

    silc_server_packet_send(server, backup->connection, type, flags,
			    data, data_len);
  }
}

/* Same as silc_server_backup_send but sets a specific Destination ID to
   the packet. The Destination ID is indicated by the `dst_id' and the
   ID type `dst_id_type'. For example, packets destined to channels must
   be sent using this function. */

void silc_server_backup_send_dest(SilcServer server,
				  SilcServerEntry sender,
				  SilcPacketType type,
				  SilcPacketFlags flags,
				  void *dst_id,
				  SilcIdType dst_id_type,
				  unsigned char *data,
				  SilcUInt32 data_len,
				  SilcBool force_send,
				  SilcBool local)
{
  SilcServerEntry backup;
  int i;

  if (!server->backup || server->server_type != SILC_ROUTER)
    return;

  for (i = 0; i < server->backup->servers_count; i++) {
    backup = server->backup->servers[i].server;
    if (!backup || sender == backup)
      continue;
    if (local && server->backup->servers[i].local == FALSE)
      continue;
    if (server->backup->servers[i].server == server->id_entry)
      continue;

    silc_server_packet_send_dest(server, backup->connection, type, flags,
				 dst_id, dst_id_type, data, data_len);
  }
}

/* Send the START_USE indication to remote connection.  If `failure' is
   TRUE then this sends SILC_PACKET_FAILURE.  Otherwise it sends
   SILC_PACKET_RESUME_ROUTER. */

void silc_server_backup_send_start_use(SilcServer server,
				       SilcPacketStream sock,
				       SilcBool failure)
{
  unsigned char data[4];

  SILC_LOG_DEBUG(("Sending START_USE (%s)",
		  failure ? "failure" : "success"));

  if (failure) {
    SILC_PUT32_MSB(SILC_SERVER_BACKUP_START_USE, data);
    silc_server_packet_send(server, sock, SILC_PACKET_FAILURE, 0,
			    data, 4);
  } else {
    data[0] = SILC_SERVER_BACKUP_START_USE;
    data[1] = 0;
    silc_server_packet_send(server, sock,
			    SILC_PACKET_RESUME_ROUTER, 0,
			    data, 2);
  }
}

/* Send the REPLACED indication to remote router.  This is send by the
   primary router (remote router) of the primary router that came back
   online.  This is not sent by backup router or any other server. */

void silc_server_backup_send_replaced(SilcServer server,
				      SilcPacketStream sock)
{
  unsigned char data[4];

  SILC_LOG_DEBUG(("Sending REPLACED"));

  data[0] = SILC_SERVER_BACKUP_REPLACED;
  data[1] = 0;
  silc_server_packet_send(server, sock,
			  SILC_PACKET_RESUME_ROUTER, 0,
			  data, 2);
}


/************************ Backup Resuming Protocol **************************/

/* Timeout callback for protocol */

SILC_TASK_CALLBACK(silc_server_backup_timeout)
{
  SilcServerBackupProtocolContext ctx = context;
  SilcServer server = app_context;

  SILC_LOG_INFO(("Timeout occurred during backup resuming protocol"));
  ctx->timeout = TRUE;
  ctx->error = TRUE;
  silc_schedule_task_add_timeout(server->schedule,
				 silc_server_protocol_backup_done, context,
				 0, 0);
}

/* Callback to start the protocol as responder */

SILC_TASK_CALLBACK(silc_server_backup_responder_start)
{
  SilcServerBackupProtocolContext proto_ctx = context;
  SilcPacketStream sock = proto_ctx->sock;
  SilcIDListData idata = silc_packet_get_context(sock);
  SilcServer server = app_context;

  /* If other protocol is executing at the same time, start with timeout. */
  if (idata->sconn->op) {
    SILC_LOG_DEBUG(("Other protocol is executing, wait for it to finish"));
    silc_schedule_task_add_timeout(server->schedule,
				   silc_server_backup_responder_start,
				   proto_ctx, 2, 0);
    return;
  }

  /* Register protocol timeout */
  silc_schedule_task_add_timeout(server->schedule,
				 silc_server_backup_timeout,
				 proto_ctx, 30, 0);

  /* Run the backup resuming protocol */
  silc_schedule_task_add_timeout(server->schedule,
				 silc_server_protocol_backup,
				 proto_ctx, 0, 0);
}

/* Callback to send START_USE to backup to check whether using backup
   is ok. */

SILC_TASK_CALLBACK(silc_server_backup_check_status)
{
  SilcPacketStream sock = context;
  SilcServer server = app_context;

  /* Check whether we are still using backup */
  if (!server->backup_primary)
    return;

  silc_server_backup_send_start_use(server, sock, FALSE);
  silc_packet_stream_unref(sock);
}

typedef struct {
  SilcServer server;
  SilcPacketStream sock;
  SilcPacket packet;
} *SilcServerBackupPing;

/* PING command reply callback */

void silc_server_backup_ping_reply(void *context, void *reply)
{
  SilcServerBackupPing pc = context;
  SilcServerCommandReplyContext cmdr = reply;

  if (cmdr && !silc_command_get_status(cmdr->payload, NULL, NULL)) {
    /* Timeout error occurred, the primary is really down. */
    SilcPacketStream primary = SILC_PRIMARY_ROUTE(pc->server);

    SILC_LOG_DEBUG(("PING timeout, primary is down"));

    if (primary) {
      silc_server_free_sock_user_data(pc->server, primary, NULL);
      silc_server_close_connection(pc->server, primary);
    }

    /* Reprocess the RESUME_ROUTER packet */
    silc_server_backup_resume_router(pc->server, pc->sock, pc->packet);
  } else {
    /* The primary is not down, refuse to serve the server as primary */
    SILC_LOG_DEBUG(("PING received, primary is up"));
    silc_server_backup_send_start_use(pc->server, pc->sock, TRUE);
    silc_packet_free(pc->packet);
  }

  silc_packet_stream_unref(pc->sock);
  silc_free(pc);
}

/* Processes incoming RESUME_ROUTER packet. This can give the packet
   for processing to the protocol handler or allocate new protocol if
   start command is received. */

void silc_server_backup_resume_router(SilcServer server,
				      SilcPacketStream sock,
				      SilcPacket packet)
{
  SilcIDListData idata = silc_packet_get_context(sock);
  SilcServerEntry router = (SilcServerEntry)idata;
  SilcUInt8 type, session;
  SilcServerBackupProtocolContext ctx;
  int i, ret;

  SILC_LOG_DEBUG(("Received RESUME_ROUTER packet"));

  if (idata->conn_type == SILC_CONN_CLIENT ||
      idata->conn_type == SILC_CONN_UNKNOWN) {
    SILC_LOG_DEBUG(("Bad packet received"));
    silc_packet_free(packet);
    return;
  }

  ret = silc_buffer_unformat(&packet->buffer,
			     SILC_STR_UI_CHAR(&type),
			     SILC_STR_UI_CHAR(&session),
			     SILC_STR_END);
  if (ret < 0) {
    SILC_LOG_ERROR(("Malformed resume router packet received"));
    silc_packet_free(packet);
    return;
  }

  /* Check whether this packet is used to tell us that server will start
     using us as primary router. */
  if (type == SILC_SERVER_BACKUP_START_USE) {
    SilcBuffer idp;
    SilcServerBackupPing pc;

    /* If we are normal server then backup router has sent us back
       this reply and we use the backup as primary router now. */
    if (server->server_type == SILC_SERVER) {
      /* Nothing to do here actually, since we have switched already. */
      SILC_LOG_DEBUG(("Received successful START_USE from backup router"));
      silc_packet_free(packet);
      return;
    }

    /* Backup router following. */

    /* If we are marked as router then the primary is down and we send
       success START_USE back to the server. */
    if (server->server_type == SILC_ROUTER) {
      SILC_LOG_DEBUG(("Sending success START_USE back"));
      silc_server_backup_send_start_use(server, sock, FALSE);
      silc_packet_free(packet);
      return;
    }

    /* We have just lost primary, send success START_USE back */
    if (server->standalone) {
      SILC_LOG_DEBUG(("We are stanalone, sending success START_USE back"));
      silc_server_backup_send_start_use(server, sock, FALSE);
      silc_packet_free(packet);
      return;
    }

    /* We are backup router. This server claims that our primary is down.
       We will check this ourselves by sending PING command to the primary. */
    SILC_LOG_DEBUG(("Sending PING to detect status of primary router"));
    idp = silc_id_payload_encode(server->router->id, SILC_ID_SERVER);
    silc_server_send_command(server, SILC_PRIMARY_ROUTE(server),
			     SILC_COMMAND_PING, ++server->cmd_ident, 1,
			     1, idp->data, silc_buffer_len(idp));
    silc_buffer_free(idp);

    /* Reprocess this packet after received reply from router */
    pc = silc_calloc(1, sizeof(*pc));
    if (!pc) {
      silc_server_backup_send_start_use(server, sock, FALSE);
      silc_packet_free(packet);
      return;
    }
    pc->server = server;
    pc->sock = sock;
    pc->packet = packet;
    silc_packet_stream_ref(sock);
    silc_server_command_pending_timed(server, SILC_COMMAND_PING,
				      server->cmd_ident,
				      silc_server_backup_ping_reply, pc, 15);
    return;
  }

  /* Start the resuming protocol if requested. */
  if (type == SILC_SERVER_BACKUP_START) {
    /* We have received a start for resuming protocol.  We are either
       primary router that came back online or normal server. */
    SilcServerBackupProtocolContext proto_ctx;
    unsigned char data[4];

    /* If backup had closed the connection earlier we won't allow resuming
       since we (primary router) have never gone away. */
    if (server->server_type == SILC_ROUTER && !server->backup_router &&
	server->backup_closed) {
      SILC_LOG_DEBUG(("Backup resuming not allowed since we are still "
		      "primary router"));
      SILC_LOG_INFO(("Backup resuming not allowed since we are still "
		     "primary router"));
      SILC_PUT32_MSB(SILC_SERVER_BACKUP_START, data);
      silc_server_packet_send(server, sock, SILC_PACKET_FAILURE, 0,
			      data, 4);
      server->backup_closed = FALSE;
      silc_packet_free(packet);
      return;
    }

    proto_ctx = silc_calloc(1, sizeof(*proto_ctx));
    if (!proto_ctx) {
      SILC_PUT32_MSB(SILC_SERVER_BACKUP_START, data);
      silc_server_packet_send(server, sock, SILC_PACKET_FAILURE, 0,
			      data, 4);
      server->backup_closed = FALSE;
      silc_packet_free(packet);
      return;
    }
    proto_ctx->server = server;
    proto_ctx->sock = sock;
    proto_ctx->responder = TRUE;
    proto_ctx->type = type;
    proto_ctx->session = session;
    proto_ctx->start = time(0);
    silc_packet_stream_ref(sock);
    router->backup = TRUE;
    router->backup_proto = proto_ctx;

    SILC_LOG_DEBUG(("Starting backup resuming protocol as responder"));
    SILC_LOG_INFO(("Starting backup resuming protocol"));

    /* Start protocol immediately */
    silc_schedule_task_add_timeout(server->schedule,
				   silc_server_backup_responder_start,
				   proto_ctx, 0, 1);
    return;
  }

  /* If we are router and the packet is coming from our primary router
     then it means we have been replaced by an backup router in our cell. */
  if (type == SILC_SERVER_BACKUP_REPLACED &&
      server->server_type == SILC_ROUTER &&
      idata->conn_type == SILC_CONN_ROUTER &&
      SILC_PRIMARY_ROUTE(server) == sock) {
    /* We have been replaced by an backup router in our cell. We must
       mark our primary router connection disabled since we are not allowed
       to use it at this moment. */
    SILC_LOG_INFO(("We are replaced by an backup router in this cell, will "
		   "wait until backup resuming protocol is executed"));
    idata->status |= SILC_IDLIST_STATUS_DISABLED;
    silc_packet_free(packet);
    return;
  }

  /* Activate the shared protocol context for this socket connection
     if necessary */
  if (type == SILC_SERVER_BACKUP_RESUMED &&
      idata->conn_type == SILC_CONN_ROUTER && !router->backup &&
      idata->status & SILC_IDLIST_STATUS_DISABLED) {
    SilcServerEntry backup_router;

    if (silc_server_backup_replaced_get(server, router->id, &backup_router)) {
      ctx = backup_router->backup_proto;
      if (ctx->sock)
	silc_packet_stream_unref(ctx->sock);
      router->backup = TRUE;
      router->backup_proto = ctx;
      ctx->sock = sock;
      silc_packet_stream_ref(sock);
    }
  }

  /* Call the resuming protocol if the protocol is active. */
  if (router->backup) {
    ctx = router->backup_proto;
    ctx->type = type;

    for (i = 0; i < ctx->sessions_count; i++) {
      if (session == ctx->sessions[i].session) {
	ctx->session = session;
	silc_schedule_task_add_timeout(server->schedule,
				       silc_server_protocol_backup,
				       ctx, 0, 1);
	silc_packet_free(packet);
	return;
      }
    }

    /* If RESUMED received the session ID is zero, execute the protocol. */
    if (type == SILC_SERVER_BACKUP_RESUMED) {
      silc_schedule_task_add_timeout(server->schedule,
				     silc_server_protocol_backup,
				     ctx, 0, 1);
      silc_packet_free(packet);
      return;
    }

    SILC_LOG_ERROR(("Unknown backup resuming session %d", session));
    silc_packet_free(packet);
    return;
  }

  silc_packet_free(packet);
}

/* Task that is called after backup router has connected back to
   primary router and we are starting the resuming protocol */

SILC_TASK_CALLBACK(silc_server_backup_connected_later)
{
  SilcServerBackupProtocolContext proto_ctx =
    (SilcServerBackupProtocolContext)context;
  SilcServer server = proto_ctx->server;

  SILC_LOG_DEBUG(("Starting backup resuming protocol as initiator"));
  SILC_LOG_INFO(("Starting backup resuming protocol"));

  /* Register protocol timeout */
  silc_schedule_task_add_timeout(server->schedule,
				 silc_server_backup_timeout,
				 proto_ctx, 30, 0);

  /* Run the backup resuming protocol */
  silc_schedule_task_add_timeout(server->schedule,
				 silc_server_protocol_backup,
				 proto_ctx, 0, 0);
}

SILC_TASK_CALLBACK(silc_server_backup_connected_again)
{
  SilcServer server = app_context;
  SilcServerConfigRouter *primary;

  SILC_LOG_DEBUG(("Reconnecting"));

  if (server->server_shutdown)
    return;

  primary = silc_server_config_get_primary_router(server);
  if (primary) {
    if (!silc_server_find_socket_by_host(server, SILC_CONN_ROUTER,
					 primary->host, primary->port))
      silc_server_create_connection(server, TRUE, FALSE,
				    primary->host, primary->port,
				    silc_server_backup_connected,
				    context);
  }
}

/* Called when we've established connection back to our primary router
   when we've acting as backup router and have replaced the primary router
   in the cell. This function will start the backup resuming protocol. */

void silc_server_backup_connected(SilcServer server,
				  SilcServerEntry server_entry,
				  void *context)
{
  SilcServerBackupProtocolContext proto_ctx;
  SilcPacketStream sock;

  if (!server_entry) {
    /* Try again */
    SILC_LOG_DEBUG(("Connecting failed"));
    silc_schedule_task_add_timeout(server->schedule,
				   silc_server_backup_connected_again,
				   context, 5, 0);
    return;
  }

  sock = server_entry->connection;
  proto_ctx = silc_calloc(1, sizeof(*proto_ctx));
  if (!proto_ctx)
    return;
  proto_ctx->server = server;
  proto_ctx->sock = sock;
  proto_ctx->responder = FALSE;
  proto_ctx->type = SILC_SERVER_BACKUP_START;
  proto_ctx->start = time(0);
  silc_packet_stream_ref(sock);

  /* Start through scheduler */
  silc_schedule_task_add_timeout(server->schedule,
				 silc_server_backup_connected_later,
				 proto_ctx, 0, 1);
}

SILC_TASK_CALLBACK(silc_server_backup_connect_primary_again)
{
  SilcServer server = app_context;
  SilcServerConfigRouter *primary;

  primary = silc_server_config_get_primary_router(server);
  if (primary) {
    if (!silc_server_find_socket_by_host(server, SILC_CONN_ROUTER,
					 primary->host, primary->port))
      silc_server_create_connection(server, TRUE, FALSE,
				    primary->host, primary->port,
				    silc_server_backup_connect_primary,
				    context);
  }
}

/* Called when normal server has connected to its primary router after
   backup router has sent the START packet in reusming protocol. We will
   move the protocol context from the backup router connection to the
   primary router. */

static void silc_server_backup_connect_primary(SilcServer server,
					       SilcServerEntry server_entry,
					       void *context)
{
  SilcPacketStream backup_router = context;
  SilcIDListData idata = silc_packet_get_context(backup_router);
  SilcServerEntry router = (SilcServerEntry)idata;
  SilcServerBackupProtocolContext ctx;
  SilcPacketStream sock;
  unsigned char data[2];

  if (!server_entry) {
    /* Try again */
    silc_schedule_task_add_timeout(server->schedule,
				   silc_server_backup_connect_primary_again,
				   context, 0, 0);
    return;
  }

  if (!router->backup || !server_entry->connection) {
    silc_packet_stream_unref(backup_router);
    return;
  }

  ctx = router->backup_proto;
  sock = server_entry->connection;
  idata = (SilcIDListData)server_entry;

  SILC_LOG_DEBUG(("Sending CONNECTED packet (session %d)", ctx->session));
  SILC_LOG_INFO(("Sending CONNECTED (session %d) to backup router",
		ctx->session));

  /* Send the CONNECTED packet back to the backup router. */
  data[0] = SILC_SERVER_BACKUP_CONNECTED;
  data[1] = ctx->session;
  silc_server_packet_send(server, backup_router,
			  SILC_PACKET_RESUME_ROUTER, 0, data, 2);

  /* The primary connection is disabled until it sends the RESUMED packet
     to us. */
  idata->status |= SILC_IDLIST_STATUS_DISABLED;

  /* Move this protocol context from this backup router connection to
     the primary router connection since it will send the subsequent
     packets in this protocol. We don't talk with backup router
     anymore. */
  if (ctx->sock)
    silc_packet_stream_unref(ctx->sock);
  ctx->sock = sock;
  silc_packet_stream_ref(sock);
  server_entry->backup = TRUE;
  server_entry->backup_proto = ctx;
  router->backup = FALSE;
  router->backup_proto = NULL;

  /* Unref */
  silc_packet_stream_unref(backup_router);
}

/* Timeout callback used by the backup router to send the ENDING packet
   to primary router to indicate that it can now resume as being primary
   router. All CONNECTED packets has been received when we reach this. */

SILC_TASK_CALLBACK(silc_server_backup_send_resumed)
{
  SilcServerBackupProtocolContext ctx = context;
  SilcServer server = ctx->server;
  unsigned char data[2];
  int i;

  SILC_LOG_DEBUG(("Start"));

  for (i = 0; i < ctx->sessions_count; i++)
    if (ctx->sessions[i].server_entry == silc_packet_get_context(ctx->sock))
      ctx->session = ctx->sessions[i].session;

  /* We've received all the CONNECTED packets and now we'll send the
     ENDING packet to the new primary router. */
  data[0] = SILC_SERVER_BACKUP_ENDING;
  data[1] = ctx->session;
  silc_server_packet_send(server, ctx->sock, SILC_PACKET_RESUME_ROUTER, 0,
			  data, sizeof(data));

  /* The protocol will go to END state. */
  ctx->state = 250;
}

/* Backup resuming protocol. This protocol is executed when the primary
   router wants to resume its position as being primary router. */

SILC_TASK_CALLBACK(silc_server_protocol_backup)
{
  SilcServerBackupProtocolContext ctx = context;
  SilcServer server = ctx->server;
  SilcServerEntry server_entry = NULL;
  SilcPacketStream sock = NULL;
  unsigned char data[2];
  SilcDList list;
  int i;

  if (!ctx->state)
    ctx->state = 1;

  switch(ctx->state) {
  case 1:
    if (ctx->responder == FALSE) {
      /*
       * Initiator (backup router)
       */

      /* Send the START packet to primary router and normal servers. The
	 packet will indicate to the primary router that it has been replaced
	 by us.  For normal servers it means that we will be resigning as
	 being primary router shortly. */
      list = silc_packet_engine_get_streams(server->packet_engine);
      if (!list)
	return;

      silc_dlist_start(list);
      while ((sock = silc_dlist_get(list))) {
	server_entry = silc_packet_get_context(sock);

	if (!server_entry || server_entry == server->id_entry ||
	    (server_entry->data.conn_type != SILC_CONN_ROUTER &&
	     server_entry->data.conn_type != SILC_CONN_SERVER))
	  continue;

	if (server_entry->data.status & SILC_IDLIST_STATUS_DISABLED)
	  continue;

	ctx->sessions = silc_realloc(ctx->sessions,
				     sizeof(*ctx->sessions) *
				     (ctx->sessions_count + 1));
	ctx->sessions[ctx->sessions_count].session = ctx->sessions_count;
	ctx->sessions[ctx->sessions_count].connected = FALSE;
	ctx->sessions[ctx->sessions_count].server_entry = server_entry;

	SILC_LOG_DEBUG(("Sending START to %s (session %d)",
			server_entry->server_name, ctx->sessions_count));
	SILC_LOG_INFO(("Expecting CONNECTED from %s (session %d)",
		       server_entry->server_name, ctx->sessions_count));

	/* This connection is performing this protocol too now */
	server_entry->backup = TRUE;
	server_entry->backup_proto = ctx;

	data[0] = SILC_SERVER_BACKUP_START;
	data[1] = ctx->sessions_count;
	silc_server_packet_send(server, sock, SILC_PACKET_RESUME_ROUTER, 0,
				data, sizeof(data));
	ctx->sessions_count++;
      }
      silc_packet_engine_free_streams_list(list);

      /* Announce data to the new primary to be. */
      silc_server_announce_servers(server, TRUE, 0, ctx->sock);
      silc_server_announce_clients(server, 0, ctx->sock);
      silc_server_announce_channels(server, 0, ctx->sock);

      ctx->state++;

    } else {
      /*
       * Responder (all servers and routers)
       */
      SilcServerConfigRouter *primary;

      /* We should have received START packet */
      if (ctx->type != SILC_SERVER_BACKUP_START) {
	SILC_LOG_ERROR(("Bad resume router packet START %d", ctx->type));
	break;
      }

      /* Connect to the primary router that was down that is now supposed
	 to be back online. We send the CONNECTED packet after we've
	 established the connection to the primary router. */
      primary = silc_server_config_get_primary_router(server);
      if (primary && server->backup_primary &&
	  !silc_server_num_sockets_by_remote(server,
					     silc_net_is_ip(primary->host) ?
					     primary->host : NULL,
					     silc_net_is_ip(primary->host) ?
					     NULL : primary->host,
					     primary->port,
					     SILC_CONN_ROUTER)) {
	SILC_LOG_DEBUG(("Received START (session %d), reconnect to router",
			ctx->session));
	silc_packet_stream_ref(ctx->sock);
	silc_server_create_connection(server, TRUE, FALSE,
				      primary->host, primary->port,
				      silc_server_backup_connect_primary,
				      ctx->sock);
      } else {
	/* Nowhere to connect just return the CONNECTED packet */
	SILC_LOG_DEBUG(("Received START (session %d), send CONNECTED back",
			ctx->session));
	SILC_LOG_INFO(("Sending CONNECTED (session %d) to backup router",
		      ctx->session));

	/* Send the CONNECTED packet back to the backup router. */
	data[0] = SILC_SERVER_BACKUP_CONNECTED;
	data[1] = ctx->session;
	silc_server_packet_send(server, ctx->sock,
				SILC_PACKET_RESUME_ROUTER, 0,
				data, sizeof(data));
      }

      /* Add this resuming session */
      ctx->sessions = silc_realloc(ctx->sessions,
				   sizeof(*ctx->sessions) *
				   (ctx->sessions_count + 1));
      ctx->sessions[ctx->sessions_count].session = ctx->session;
      ctx->sessions_count++;

      /* Normal server goes directly to the END state. */
      if (server->server_type == SILC_ROUTER &&
	  (!server->router ||
	   server->router->data.status & SILC_IDLIST_STATUS_DISABLED))
	ctx->state++;
      else
	ctx->state = 250;
    }
    break;

  case 2:
    if (ctx->responder == FALSE) {
      /*
       * Initiator (backup router)
       */

      /* We should have received CONNECTED packet */
      if (ctx->type != SILC_SERVER_BACKUP_CONNECTED) {
	SILC_LOG_ERROR(("Bad resume router packet CONNECTED %d", ctx->type));
	break;
      }

      for (i = 0; i < ctx->sessions_count; i++) {
	if (ctx->sessions[i].session == ctx->session) {
	  ctx->sessions[i].connected = TRUE;
	  SILC_LOG_INFO(("Received CONNECTED from %s (session %d)",
			 ctx->sessions[i].server_entry->server_name,
			 ctx->session));
	  SILC_LOG_DEBUG(("Received CONNECTED (session %d)", ctx->session));
	  break;
	}
      }

      /* See if all returned CONNECTED, if not, then continue waiting. */
      for (i = 0; i < ctx->sessions_count; i++) {
	if (!ctx->sessions[i].connected)
	  return;
      }

      SILC_LOG_INFO(("All sessions have returned CONNECTED packets, "
		     "continuing"));
      SILC_LOG_DEBUG(("Sending ENDING packet to primary router"));

      /* The ENDING is sent with timeout, and then we continue to the
	 END state in the protocol. */
      silc_schedule_task_add_timeout(server->schedule,
				     silc_server_backup_send_resumed,
				     ctx, 1, 0);
      return;

    } else {
      /*
       * Responder (primary router)
       */

      /* We should have been received ENDING packet */
      if (ctx->type != SILC_SERVER_BACKUP_ENDING) {
	SILC_LOG_ERROR(("Bad resume router packet ENDING %d", ctx->type));
	break;
      }

      SILC_LOG_DEBUG(("Received ENDING packet, we are going to resume now"));

      /* Switch announced informations to our primary router of using the
	 backup router. */
      silc_server_local_servers_toggle_enabled(server, TRUE);
      silc_server_update_servers_by_server(server,
					   silc_packet_get_context(ctx->sock),
 					   server->router);
      silc_server_update_clients_by_server(server,
					   silc_packet_get_context(ctx->sock),
					   server->router, TRUE);

      /* We as primary router now must send RESUMED packets to all servers
	 and routers so that they know we are back.   For backup router we
	 send the packet last so that we give the backup as much time as
	 possible to deal with message routing at this critical moment. */
      list = silc_packet_engine_get_streams(server->packet_engine);
      if (!list)
	return;

      silc_dlist_start(list);
      while ((sock = silc_dlist_get(list))) {
	server_entry = silc_packet_get_context(sock);

	if (!server_entry || server_entry == server->id_entry ||
	    (server_entry->data.conn_type != SILC_CONN_ROUTER &&
	     server_entry->data.conn_type != SILC_CONN_SERVER))
	  continue;

	/* Send to backup last */
	if (sock == ctx->sock)
	  continue;

	server_entry = silc_packet_get_context(sock);
	server_entry->data.status &= ~SILC_IDLIST_STATUS_DISABLED;

	SILC_LOG_DEBUG(("Sending RESUMED to %s", server_entry->server_name));
	SILC_LOG_INFO(("Sending RESUMED to %s", server_entry->server_name));

	/* This connection is performing this protocol too now */
	server_entry->backup = TRUE;
	server_entry->backup_proto = ctx;

	data[0] = SILC_SERVER_BACKUP_RESUMED;
	data[1] = 0;
	silc_server_packet_send(server, sock, SILC_PACKET_RESUME_ROUTER, 0,
				data, sizeof(data));
      }

      /* Now send the same packet to backup */
      if (sock != ctx->sock) {
	sleep(1);
	sock = ctx->sock;
	server_entry = silc_packet_get_context(sock);
	server_entry->data.status &= ~SILC_IDLIST_STATUS_DISABLED;

	SILC_LOG_DEBUG(("Sending RESUMED to %s", server_entry->server_name));
	SILC_LOG_INFO(("Sending RESUMED to %s", server_entry->server_name));

	/* This connection is performing this protocol too now */
	server_entry->backup = TRUE;
	server_entry->backup_proto = ctx;

	data[0] = SILC_SERVER_BACKUP_RESUMED;
	data[1] = 0;
	silc_server_packet_send(server, sock, SILC_PACKET_RESUME_ROUTER, 0,
				data, sizeof(data));
      }
      silc_packet_engine_free_streams_list(list);

      /* We are now resumed and are back as primary router in the cell. */
      SILC_LOG_INFO(("We are now the primary router of our cell again"));
      server->wait_backup = FALSE;

      /* Announce WATCH list a little later */
      silc_packet_stream_ref(ctx->sock);
      silc_schedule_task_add_timeout(server->schedule,
				     silc_server_backup_announce_watches,
				     ctx->sock, 4, 0);

      /* For us this is the end of this protocol. */
      silc_schedule_task_add_timeout(server->schedule,
				     silc_server_protocol_backup_done,
				     ctx, 0, 1);
    }
    break;

  case 250:
    {
      /*
       * Responder (backup router, servers, and remote router)
       */
      SilcServerEntry router, backup_router;

      /* We should have been received RESUMED from our primary router. */
      if (ctx->type != SILC_SERVER_BACKUP_RESUMED) {
	SILC_LOG_ERROR(("Bad resume router packet RESUMED %d", ctx->type));
	break;
      }

      SILC_LOG_INFO(("Received RESUMED from new primary router"));

      /* If we are the backup router, mark that we are no longer primary
	 but are back to backup router status. */
      if (server->backup_router)
	server->server_type = SILC_BACKUP_ROUTER;

      /* We have now new primary router. All traffic goes there from now on. */
      router = silc_packet_get_context(ctx->sock);
      if (silc_server_backup_replaced_get(server, router->id,
					  &backup_router)) {

	if (backup_router == server->router) {
	  /* We have new primary router now */
	  server->id_entry->router = router;
	  server->router = router;
	  SILC_LOG_INFO(("Switching back to primary router %s",
			 server->router->server_name));
	} else {
	  /* We are connected to new primary and now continue using it */
	  SILC_LOG_INFO(("Resuming the use of primary router %s",
			 router->server_name));
	}
	server->backup_primary = FALSE;
	sock = router->connection;

	/* Update the client entries of the backup router to the new
	   router */
	silc_server_local_servers_toggle_enabled(server, FALSE);
	router->data.status &= ~SILC_IDLIST_STATUS_DISABLED;
	silc_server_update_servers_by_server(server, backup_router, router);
	silc_server_update_clients_by_server(
				   server, NULL, router,
				   server->server_type == SILC_BACKUP_ROUTER);
	if (server->server_type == SILC_SERVER)
	  silc_server_update_channels_by_server(server, backup_router, router);
 	silc_server_backup_replaced_del(server, backup_router);
      }

      /* Send notify about primary router going down to local operators */
      SILC_SERVER_SEND_OPERS(server, FALSE, TRUE,
			     SILC_NOTIFY_TYPE_NONE,
			     ("%s resumed the use of primary router %s",
			      server->server_name,
			      server->router->server_name));

      /* Protocol has ended, call the final callback */
      silc_schedule_task_add_timeout(server->schedule,
				     silc_server_protocol_backup_done,
				     ctx, 0, 1);
    }
    break;

  case 251:
    /* Protocol has ended, call the final callback */
    silc_schedule_task_add_timeout(server->schedule,
				   silc_server_protocol_backup_done,
				   ctx, 0, 1);
    break;

  case 252:
    /* Protocol has ended, call the final callback */
    SILC_LOG_ERROR(("Error during backup resume: received Failure"));
    ctx->received_failure = TRUE;
    silc_schedule_task_add_timeout(server->schedule,
				   silc_server_protocol_backup_done,
				   ctx, 0, 1);
    break;

  default:
    break;
  }
}

/* Final resuming protocol completion callback */

SILC_TASK_CALLBACK(silc_server_protocol_backup_done)
{
  SilcServerBackupProtocolContext ctx = context;
  SilcServer server = ctx->server;
  SilcDList list;
  SilcServerEntry server_entry;
  SilcPacketStream sock;
  SilcBool error;

  silc_schedule_task_del_by_context(server->schedule, ctx);

  error = ctx->error;

  if (error)
    SILC_LOG_ERROR(("Error occurred during backup router resuming protcool"));

  if (server->server_shutdown)
    return;

  /* Remove this protocol from all server entries that has it */
  list = silc_packet_engine_get_streams(server->packet_engine);
  if (!list)
    return;

  silc_dlist_start(list);
  while ((sock = silc_dlist_get(list))) {
    server_entry = silc_packet_get_context(sock);
    if (!server_entry)
      continue;

    if (server_entry->data.conn_type != SILC_CONN_ROUTER &&
	server_entry->data.conn_type != SILC_CONN_SERVER)
      continue;

    if (server_entry->backup_proto == ctx) {
      if (error) {

	if (server->server_type == SILC_SERVER &&
	    server_entry->server_type == SILC_ROUTER)
	  continue;

	/* Backup router */
	if (SILC_PRIMARY_ROUTE(server) == sock && server->backup_router) {
	  if (ctx->sock == sock) {
	    silc_packet_stream_unref(sock);
	    ctx->sock = NULL;
	  }

	  /* If failed after 10 attempts, it won't work, give up */
	  if (ctx->initiator_restart > 10)
	    ctx->received_failure = TRUE;

	  if (!ctx->received_failure) {
	    /* Protocol error, probably timeout. Just restart the protocol. */
	    SilcServerBackupProtocolContext proto_ctx;

	    /* Restart the protocol. */
	    proto_ctx = silc_calloc(1, sizeof(*proto_ctx));
	    if (!proto_ctx)
	      continue;
	    proto_ctx->server = server;
	    proto_ctx->sock = sock;
	    proto_ctx->responder = FALSE;
	    proto_ctx->type = SILC_SERVER_BACKUP_START;
	    proto_ctx->start = time(0);
	    proto_ctx->initiator_restart = ctx->initiator_restart + 1;
	    silc_packet_stream_ref(sock);

	    /* Start through scheduler */
	    silc_schedule_task_add_timeout(server->schedule,
					   silc_server_backup_connected_later,
					   proto_ctx, 5, 0);
	  } else {
	    /* If failure was received, switch back to normal backup router.
	       For some reason primary wouldn't accept that we were supposed
	       to perfom resuming protocol. */
	    server->server_type = SILC_BACKUP_ROUTER;
	    silc_server_local_servers_toggle_enabled(server, FALSE);
	    server_entry->data.status &= ~SILC_IDLIST_STATUS_DISABLED;
	    silc_server_update_servers_by_server(server, server->id_entry,
						 silc_packet_get_context(sock));
	    silc_server_update_clients_by_server(server, NULL,
						 silc_packet_get_context(sock),
						 TRUE);

	    /* Announce our clients and channels to the router */
	    silc_server_announce_clients(server, 0, sock);
	    silc_server_announce_channels(server, 0, sock);

	    /* Announce WATCH list a little later */
	    silc_packet_stream_ref(sock);
	    silc_schedule_task_add_timeout(server->schedule,
					   silc_server_backup_announce_watches,
					   sock, 5, 0);
	  }

	  continue;
	}
      }

      server_entry->data.status &= ~SILC_IDLIST_STATUS_DISABLED;
    }
  }
  silc_packet_engine_free_streams_list(list);

  if (!error) {
    SILC_LOG_INFO(("Backup resuming protocol ended successfully"));

    if (ctx->type == SILC_SERVER_BACKUP_RESUMED && server->router) {
      /* Announce all of our information to the router. */
      if (server->server_type == SILC_ROUTER)
	silc_server_announce_servers(server, FALSE, 0,
				     server->router->connection);

      /* Announce our clients and channels to the router */
      silc_server_announce_clients(server, 0, server->router->connection);
      silc_server_announce_channels(server, 0, server->router->connection);

      /* Announce WATCH list a little later */
      silc_packet_stream_ref(server->router->connection);
      silc_schedule_task_add_timeout(server->schedule,
				     silc_server_backup_announce_watches,
				     server->router->connection, 4, 0);
    }
  } else {
    /* Error */

    if (server->server_type == SILC_SERVER) {
      /* If we are still using backup router Send confirmation to backup
	 that using it is still ok and continue sending traffic there.
	 The backup will reply with error if it's not ok. */
      if (server->router && server->backup_primary) {
	/* Send START_USE just in case using backup wouldn't be ok. */
	silc_server_backup_send_start_use(server, server->router->connection,
					  FALSE);

	/* Check couple of times same START_USE just in case. */
	silc_packet_stream_ref(server->router->connection);
	silc_schedule_task_add_timeout(server->schedule,
				       silc_server_backup_check_status,
				       server->router->connection,
				       5, 1);
	silc_packet_stream_ref(server->router->connection);
	silc_schedule_task_add_timeout(server->schedule,
				       silc_server_backup_check_status,
				       server->router->connection,
				       20, 1);
	silc_packet_stream_ref(server->router->connection);
	silc_schedule_task_add_timeout(server->schedule,
				       silc_server_backup_check_status,
				       server->router->connection,
				       60, 1);
      }
    }
  }

  if (ctx->sock) {
    SilcServerEntry r = silc_packet_get_context(ctx->sock);
    if (r) {
      r->backup = FALSE;
      r->backup_proto = NULL;
    }
    silc_packet_stream_unref(ctx->sock);
  }
  silc_free(ctx->sessions);
  silc_free(ctx);
}

SILC_TASK_CALLBACK(silc_server_backup_announce_watches)
{
  SilcPacketStream sock = context;
  SilcServer server = app_context;
  if (silc_packet_stream_is_valid(sock))
    silc_server_announce_watches(server, sock);
  silc_packet_stream_unref(sock);
}
