/* Copyright (c) 2001, Matej Pfajfar.
 * Copyright (c) 2001-2004, Roger Dingledine.
 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
 * Copyright (c) 2007-2018, The Tor Project, Inc. */
/* See LICENSE for licensing information */

/**
 * \file dispatch_cfg.c
 * \brief Create and configure a dispatch_cfg_t.
 *
 * A dispatch_cfg_t object is used to configure a set of messages and
 * associated information before creating a dispatch_t.
 */

#define DISPATCH_PRIVATE

#include "orconfig.h"
#include "lib/dispatch/dispatch_cfg.h"
#include "lib/dispatch/dispatch_cfg_st.h"
#include "lib/dispatch/dispatch.h"
#include "lib/dispatch/dispatch_st.h"

#include "lib/container/smartlist.h"
#include "lib/malloc/malloc.h"

/**
 * Create and return a new dispatch_cfg_t.
 **/
dispatch_cfg_t *
dcfg_new(void)
{
  dispatch_cfg_t *cfg = tor_malloc(sizeof(dispatch_cfg_t));
  cfg->type_by_msg = smartlist_new();
  cfg->chan_by_msg = smartlist_new();
  cfg->fns_by_type = smartlist_new();
  cfg->recv_by_msg = smartlist_new();
  return cfg;
}

/**
 * Associate a message with a datatype.  Return 0 on success, -1 if a
 * different type was previously associated with the message ID.
 **/
int
dcfg_msg_set_type(dispatch_cfg_t *cfg, message_id_t msg,
                  msg_type_id_t type)
{
  smartlist_grow(cfg->type_by_msg, msg+1);
  msg_type_id_t *oldval = smartlist_get(cfg->type_by_msg, msg);
  if (oldval != NULL && *oldval != type) {
    return -1;
  }
  if (!oldval)
    smartlist_set(cfg->type_by_msg, msg, tor_memdup(&type, sizeof(type)));
  return 0;
}

/**
 * Associate a message with a channel.  Return 0 on success, -1 if a
 * different channel was previously associated with the message ID.
 **/
int
dcfg_msg_set_chan(dispatch_cfg_t *cfg, message_id_t msg,
                  channel_id_t chan)
{
  smartlist_grow(cfg->chan_by_msg, msg+1);
  channel_id_t *oldval = smartlist_get(cfg->chan_by_msg, msg);
  if (oldval != NULL && *oldval != chan) {
    return -1;
  }
  if (!oldval)
    smartlist_set(cfg->chan_by_msg, msg, tor_memdup(&chan, sizeof(chan)));
  return 0;
}

/**
 * Associate a set of functions with a datatype. Return 0 on success, -1 if
 * different functions were previously associated with the type.
 **/
int
dcfg_type_set_fns(dispatch_cfg_t *cfg, msg_type_id_t type,
                  const dispatch_typefns_t *fns)
{
  smartlist_grow(cfg->fns_by_type, type+1);
  dispatch_typefns_t *oldfns = smartlist_get(cfg->fns_by_type, type);
  if (oldfns && (oldfns->free_fn != fns->free_fn ||
                 oldfns->fmt_fn != fns->fmt_fn))
    return -1;
  if (!oldfns)
    smartlist_set(cfg->fns_by_type, type, tor_memdup(fns, sizeof(*fns)));
  return 0;
}

/**
 * Associate a receiver with a message ID.  Multiple receivers may be
 * associated with a single messasge ID.
 *
 * Return 0 on success, on failure.
 **/
int
dcfg_add_recv(dispatch_cfg_t *cfg, message_id_t msg,
              subsys_id_t sys, recv_fn_t fn)
{
  smartlist_grow(cfg->recv_by_msg, msg+1);
  smartlist_t *receivers = smartlist_get(cfg->recv_by_msg, msg);
  if (!receivers) {
    receivers = smartlist_new();
    smartlist_set(cfg->recv_by_msg, msg, receivers);
  }

  dispatch_rcv_t *rcv = tor_malloc(sizeof(dispatch_rcv_t));
  rcv->sys = sys;
  rcv->enabled = true;
  rcv->fn = fn;
  smartlist_add(receivers, (void*)rcv);
  return 0;
}

/** Helper: release all storage held by <b>cfg</b>. */
void
dcfg_free_(dispatch_cfg_t *cfg)
{
  if (!cfg)
    return;

  SMARTLIST_FOREACH(cfg->type_by_msg, msg_type_id_t *, id, tor_free(id));
  SMARTLIST_FOREACH(cfg->chan_by_msg, channel_id_t *, id, tor_free(id));
  SMARTLIST_FOREACH(cfg->fns_by_type, dispatch_typefns_t *, f, tor_free(f));
  smartlist_free(cfg->type_by_msg);
  smartlist_free(cfg->chan_by_msg);
  smartlist_free(cfg->fns_by_type);
  SMARTLIST_FOREACH_BEGIN(cfg->recv_by_msg, smartlist_t *, receivers) {
    if (!receivers)
      continue;
    SMARTLIST_FOREACH(receivers, dispatch_rcv_t *, rcv, tor_free(rcv));
    smartlist_free(receivers);
  } SMARTLIST_FOREACH_END(receivers);
  smartlist_free(cfg->recv_by_msg);

  tor_free(cfg);
}
