/*
 * This file is part of the Sofia-SIP package
 *
 * Copyright (C) 2005 Nokia Corporation.
 *
 * Contact: Pekka Pessi <pekka.pessi@nokia.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 */

#ifndef NTA_INTERNAL_H
/** Defined when <nta_internal.h> has been included. */
#define NTA_INTERNAL_H 

/**@internal @file nta_internal.h
 *
 * @brief Internals of NTA objects.
 *
 * @author Pekka Pessi <Pekka.Pessi@nokia.com>
 *
 * @date Created: Tue Jul 18 09:18:32 2000 ppessi
 */

/* Resolver context type */
#define SRES_CONTEXT_T    nta_outgoing_t

/* We are customer of tport_t */
#define TP_AGENT_T        nta_agent_t
#define TP_MAGIC_T        sip_via_t 
#define TP_CLIENT_T       nta_outgoing_t

#include <sofia-sip/nta.h>
#include <sofia-sip/nta_tport.h>
#include <sofia-sip/tport.h>

#if HAVE_SOFIA_SRESOLV
#include <sofia-sip/sresolv.h>
#endif

#include <sofia-sip/htable.h>

#if HAVE_SMIME
#include "smimec.h"
#endif

SOFIA_BEGIN_DECLS

/** A sip_flag telling that this message is internally generated. */
#define NTA_INTERNAL_MSG (1<<15)

/** Resolving order */
enum nta_res_order_e
{
  nta_res_ip6_ip4,
  nta_res_ip4_ip6,
  nta_res_ip6_only,
  nta_res_ip4_only
};

HTABLE_DECLARE_WITH(leg_htable, lht, nta_leg_t, size_t, hash_value_t);
HTABLE_DECLARE_WITH(outgoing_htable, oht, nta_outgoing_t, size_t, hash_value_t);
HTABLE_DECLARE_WITH(incoming_htable, iht, nta_incoming_t, size_t, hash_value_t);

typedef struct outgoing_queue_t {
  nta_outgoing_t **q_tail;
  nta_outgoing_t  *q_head;
  size_t           q_length;
  unsigned         q_timeout;
} outgoing_queue_t;

typedef struct incoming_queue_t {
  nta_incoming_t **q_tail;
  nta_incoming_t  *q_head;
  size_t           q_length;
  unsigned         q_timeout;
} incoming_queue_t;

typedef struct nta_compressor nta_compressor_t;

struct nta_agent_s
{
  su_home_t             sa_home[1];
  su_root_t            *sa_root;
  su_timer_t           *sa_timer;
  nta_agent_magic_t    *sa_magic;
  nta_message_f        *sa_callback;     

  nta_update_magic_t   *sa_update_magic;
  nta_update_tport_f   *sa_update_tport;

  su_duration_t         sa_next; /**< Timestamp for next agent_timer. */
  su_time_t             sa_now;	 /**< Timestamp in microsecond resolution. */
  uint32_t              sa_millisec; /**< Timestamp in milliseconds resolution. */

  uint32_t              sa_nw_updates; /* Shall we enable network detector? */

  uint32_t              sa_flags;	/**< Message flags */
  msg_mclass_t const   *sa_mclass;

  sip_contact_t        *sa_contact;
  sip_via_t            *sa_vias;   /**< @Via headers for all transports */
  sip_via_t            *sa_public_vias;   /**< @Vias for public transports */
  sip_contact_t        *sa_aliases;/**< List of aliases for agent */

  uint64_t              sa_branch; /**< Counter for generating branch parameter */
  uint64_t              sa_tags;   /**< Counter for generating tag parameters */

  char const           *sa_2543_tag; /**< Fixed tag added to @To when responding */

#if HAVE_SOFIA_SRESOLV
  sres_resolver_t      *sa_resolver; /**< DNS resolver */
#endif

  tport_t              *sa_tports;
  
  /* Default outbound proxy */
  url_t                *sa_default_proxy;

#if HAVE_SMIME
  sm_object_t          *sa_smime;
#else
  void                 *sa_smime;
#endif


  /** Request error mask */
  unsigned              sa_bad_req_mask;
  /** Response error mask */
  unsigned              sa_bad_resp_mask;

  /** Maximum size of incoming messages */
  size_t                sa_maxsize;
  
  /** Maximum size of proceeding queue */
  size_t                sa_max_proceeding;
  
  /** Maximum size of outgoing UDP requests */
  unsigned              sa_udp_mtu;

  /** SIP T1 - initial interval of retransmissions (500 ms) */
  unsigned              sa_t1;
  /** SIP T2 - maximum interval of retransmissions (4000 ms) */
  unsigned              sa_t2;
  /** SIP T4 - clear message time (5000 ms) */
  unsigned              sa_t4;

  /** SIP T1X64 - transaction lifetime (32 s) */
  unsigned              sa_t1x64;

  /** Progress timer - interval between provisional responses sent */
  unsigned              sa_progress;

  /** SIP timer C - interval between provisional responses receivedxs */
  unsigned              sa_timer_c;

  /** Graylisting period */
  unsigned              sa_graylist;
  /** Blacklisting period */
  unsigned              sa_blacklist;

    /** NTA is used to test packet drop */
  unsigned              sa_drop_prob : 10;
  /** NTA is acting as an User Agent server */
  unsigned              sa_is_a_uas : 1;
  /** Process requests outside dialog statelessly */
  unsigned              sa_is_stateless : 1;
  /** Let application provide @Via headers */
  unsigned              sa_user_via:1;
  /** Respond with "100 Trying" if application has not responded. */
  unsigned              sa_extra_100:1;
  /** The "100 Trying" provisional answers are passed to the application */
  unsigned              sa_pass_100:1;
  /** If true, a "408 Request Timeout" message is generated when outgoing
      request expires. */
  unsigned              sa_timeout_408:1;
  /** If true, a "408 Request Timeout" responses are passed to client. */
  unsigned              sa_pass_408:1;
  /** If true, a "482 Request Merged" response is sent to merged requests. */
  unsigned              sa_merge_482 : 1;
  /** If true, send a CANCEL to an INVITE without an provisional response. */
  unsigned              sa_cancel_2543 : 1;
  /** If true, reply with 487 response when a CANCEL is received. */
  unsigned              sa_cancel_487 : 1;
  /** If true, use unique tags. */
  unsigned              sa_tag_3261 : 1;
  /** If true, include 100rel in INVITE requests. */
  unsigned              sa_invite_100rel : 1;
  /** If true, insert @Timestamp in requests. */
  unsigned              sa_timestamp : 1;

  /** If true, transports support IPv4. */
  unsigned              sa_tport_ip4 : 1;
  /** If true, transports support IPv6. */
  unsigned              sa_tport_ip6 : 1;
  /** If true, transports support UDP. */
  unsigned              sa_tport_udp : 1;
  /** If true, transports support TCP. */
  unsigned              sa_tport_tcp : 1;
  /** If true, transports support SCTP. */
  unsigned              sa_tport_sctp : 1;
  /** If true, transports support TLS. */
  unsigned              sa_tport_tls : 1;

  /** If true, use NAPTR lookup */
  unsigned              sa_use_naptr : 1;
  /** If true, use SRV lookup */
  unsigned              sa_use_srv : 1;

  /** If true, transports use threadpool */
  unsigned              sa_tport_threadpool : 1;

  /** If true, use rport at client */
  unsigned              sa_rport:1;
  /** If true, use rport at server */
  unsigned              sa_server_rport:1;
  /** If true, use rport with tcp, too */
  unsigned              sa_tcp_rport:1;

  /** If true, automatically create compartments */
  unsigned              sa_auto_comp:1;

  /** Set when executing timer */
  unsigned              sa_in_timer:1;
  
  /** Set if application has set value for timer C */
  unsigned              sa_use_timer_c:1;

  unsigned              :0;

  /** Messages memory preload. */
  unsigned              sa_preload;

  /** Name of SigComp algorithm */
  char const           *sa_algorithm;
  /** Options for SigComp. */
  char const           *sa_sigcomp_options;
  char const* const    *sa_sigcomp_option_list;
  char const           *sa_sigcomp_option_free;

  nta_compressor_t     *sa_compressor;

  /** Resolving order (AAAA/A) */
  enum nta_res_order_e  sa_res_order;

  /** @MaxForwards */

  sip_max_forwards_t    sa_max_forwards[1];

  /* Statistics */
  struct {
    usize_t as_recv_msg;
    usize_t as_recv_request;
    usize_t as_recv_response;
    usize_t as_bad_message;
    usize_t as_bad_request;
    usize_t as_bad_response;
    usize_t as_drop_request;
    usize_t as_drop_response;
    usize_t as_client_tr;
    usize_t as_server_tr;
    usize_t as_dialog_tr;
    usize_t as_acked_tr;
    usize_t as_canceled_tr;
    usize_t as_trless_request;
    usize_t as_trless_to_tr;
    usize_t as_trless_response;
    usize_t as_trless_200;
    usize_t as_merged_request;
    usize_t as_sent_msg;
    usize_t as_sent_request;
    usize_t as_sent_response;
    usize_t as_retry_request;
    usize_t as_retry_response;
    usize_t as_recv_retry;
    usize_t as_tout_request;
    usize_t as_tout_response;
  }                  sa_stats[1];

  /** Hash of dialogs. */
  leg_htable_t          sa_dialogs[1];
  /** Default leg */
  nta_leg_t            *sa_default_leg;
  /** Hash of legs without dialogs. */
  leg_htable_t          sa_defaults[1];
  /** Hash table for outgoing transactions */
  outgoing_htable_t     sa_outgoing[1];
  nta_outgoing_t       *sa_default_outgoing;
  /** Hash table for incoming transactions */
  incoming_htable_t     sa_incoming[1]; 
  nta_incoming_t       *sa_default_incoming;

  /* Queues (states) for outgoing client transactions */
  struct {
    /** Queue for retrying client transactions */
    nta_outgoing_t   *re_list;
    nta_outgoing_t  **re_t1;	        /**< Special place for T1 timer */
    size_t            re_length;	/**< Length of sa_out.re_list */

    outgoing_queue_t  delayed[1]; 
    outgoing_queue_t  resolving[1]; 

    outgoing_queue_t  trying[1];	/* Timer F/E */
    outgoing_queue_t  completed[1];	/* Timer K */
    outgoing_queue_t  terminated[1];

    /* Special queues (states) for outgoing INVITE transactions */
    outgoing_queue_t  inv_calling[1];	/* Timer B/A */
    outgoing_queue_t  inv_proceeding[1]; /* Timer C */
    outgoing_queue_t  inv_completed[1];	/* Timer D */

    /* Temporary queue for transactions waiting to be freed */
    outgoing_queue_t *free;
  } sa_out;

  /* Queues (states) for incoming server transactions */
  struct {
    /** Queue for retransmitting response of server transactions */
    nta_incoming_t   *re_list;
    nta_incoming_t  **re_t1;	        /**< Special place for T1 timer */
    size_t            re_length;

    incoming_queue_t  proceeding[1];	/**< Request received */
    incoming_queue_t  preliminary[1];   /**< 100rel sent  */
    incoming_queue_t  completed[1];	/**< Final answer sent (non-invite). */
    incoming_queue_t  inv_completed[1];	/**< Final answer sent (INVITE). */
    incoming_queue_t  inv_confirmed[1];	/**< Final answer sent, ACK recvd. */
    incoming_queue_t  terminated[1];	/**< Terminated, ready to free. */
    incoming_queue_t  final_failed[1];   
  } sa_in;

  /* Special task for freeing memory */
  su_clone_r          sa_terminator;
};

struct nta_leg_s
{
  su_home_t         leg_home[1];
  hash_value_t      leg_hash;
  unsigned          leg_dialog : 1;
  unsigned          leg_stateless : 1;   /**< Process requests statelessly */
#ifdef NTA_STRICT_ROUTING
  unsigned          leg_contact_set : 1;
#else
  unsigned          leg_loose_route : 1; /**< Topmost route in set is LR */
#endif
  unsigned          leg_local_is_to : 1; /**< Backwards-compatibility. */
  unsigned          leg_tagged : 1; /**< Tagged after creation.
				     *
				     * Request missing To tag matches it
				     * even after tagging.
				     */
  unsigned:0;
  nta_request_f    *leg_callback;
  nta_leg_magic_t  *leg_magic;
  nta_agent_t      *leg_agent;
  /** Leg URL.
   *
   * This is the URL used to match incoming requests.
   */
  url_t const      *leg_url;
  char const       *leg_method;	/**< Method for this dialog. */

  uint32_t	    leg_seq;    /**< Sequence number for next transaction */
  uint32_t	    leg_rseq;   /**< Remote sequence number */
  sip_call_id_t	   *leg_id;	/**< Call ID */
  sip_from_t   	   *leg_remote;	/**< Remote address (@To/@From) */
  sip_to_t     	   *leg_local;	/**< Local address (@From/@To) */

  sip_route_t      *leg_route;  /**< @Route for outgoing requests. */
  sip_contact_t    *leg_target; /**< Remote destination (from @Contact). */
};

#define leg_has_id(leg) ((leg)->leg_id != NULL)

struct nta_incoming_s
{
  su_home_t            *irq_home;
  hash_value_t          irq_hash;
  nta_agent_t          *irq_agent;
  nta_ack_cancel_f     *irq_callback;
  nta_incoming_magic_t *irq_magic;

  /* Timeout/state queue */
  nta_incoming_t      **irq_prev;
  nta_incoming_t       *irq_next;
  incoming_queue_t     *irq_queue;
  
  /* Retry queue */
  nta_incoming_t      **irq_rprev;
  nta_incoming_t       *irq_rnext;

  sip_method_t        	irq_method;
  sip_request_t        *irq_rq;
  sip_from_t           *irq_from;
  sip_to_t             *irq_to;
  char const           *irq_tag;
  sip_cseq_t           *irq_cseq;
  sip_call_id_t        *irq_call_id;
  sip_via_t            *irq_via;
  sip_record_route_t   *irq_record_route;
  char const           *irq_branch;

  uint32_t              irq_rseq;

  sip_timestamp_t      *irq_timestamp;
  su_time_t             irq_received;

  su_duration_t       	irq_timeout;    /**< Timer H, I, J */
  su_duration_t       	irq_retry;      /**< Timer G */
  unsigned short      	irq_interval;	/**< Next timer  */

  short               	irq_status;

  unsigned              irq_retries : 8;
  unsigned              irq_default : 1;    /**< Default transaction */
  unsigned              irq_canceled : 1;   /**< Transaction is canceled */
  unsigned              irq_completed : 1;  /**< Transaction is completed */
  unsigned              irq_confirmed : 1;  /**< Response has been acked */
  unsigned              irq_terminated :1;  /**< Transaction is terminated */
  unsigned              irq_final_failed:1; /**< Sending final response failed */
  unsigned              irq_destroyed :1;   /**< Transaction is destroyed */
  unsigned              irq_in_callback:1;  /**< Callback is being invoked */
  unsigned              irq_reliable_tp:1;  /**< Transport is reliable */
  unsigned              irq_sigcomp_zap:1;  /**< Reset SigComp */
  unsigned              irq_must_100rel:1;  /**< 100rel is required */
  unsigned              irq_tag_set:1;      /**< Tag is not from request */
  unsigned              :0;

  tp_name_t             irq_tpn[1];
  tport_t              *irq_tport;
  struct sigcomp_compartment *irq_cc;
  msg_t		       *irq_request;
  msg_t		       *irq_request2;       /**< ACK/CANCEL */
  msg_t		       *irq_response;

  nta_reliable_t       *irq_reliable;       /**< List of reliable responses */
};

struct nta_reliable_s
{
  nta_reliable_t       *rel_next;
  nta_incoming_t       *rel_irq;
  nta_prack_f          *rel_callback;
  nta_reliable_magic_t *rel_magic;
  uint32_t              rel_rseq;
  unsigned short        rel_status;
  unsigned              rel_pracked : 1;
  unsigned              rel_precious : 1;
  msg_t                *rel_response;
  msg_t                *rel_unsent;
};

typedef struct sipdns_resolver sipdns_resolver_t;

struct nta_outgoing_s
{
  hash_value_t          orq_hash;    /**< Hash value */
  nta_agent_t          *orq_agent;
  nta_response_f       *orq_callback;
  nta_outgoing_magic_t *orq_magic;

  /* Timeout/state queue */
  nta_outgoing_t      **orq_prev;
  nta_outgoing_t       *orq_next;
  outgoing_queue_t     *orq_queue;
  
  /* Retry queue */
  nta_outgoing_t      **orq_rprev;
  nta_outgoing_t       *orq_rnext;

  sip_method_t        	orq_method;
  char const           *orq_method_name;
  url_t const          *orq_url;        /**< Original RequestURI */

  sip_from_t const     *orq_from;
  sip_to_t const       *orq_to;
  char const           *orq_tag;        /**< Tag from final response. */

  sip_cseq_t const     *orq_cseq;
  sip_call_id_t const  *orq_call_id;

  msg_t		       *orq_request;
  msg_t                *orq_response;

  su_time_t             orq_sent;       /**< When request was sent? */
  unsigned              orq_delay;      /**< RTT estimate */

  su_duration_t         orq_retry;	/**< Timer A, E */
  su_duration_t       	orq_timeout;	/**< Timer B, D, F, K */

  unsigned short      	orq_interval;	/**< Next timer A/E */

  unsigned short      	orq_status;
  unsigned char         orq_retries;    /**< Number of tries this far */
  unsigned orq_default : 1;	        /**< This is default transaction */
  unsigned orq_inserted : 1;
  unsigned orq_resolved : 1;
  unsigned orq_prepared : 1; /**< outgoing_prepare() called */
  unsigned orq_canceled : 1;
  unsigned orq_terminated : 1;
  unsigned orq_destroyed : 1;
  unsigned orq_completed : 1;
  unsigned orq_delayed : 1;
  unsigned orq_stripped_uri : 1;
  unsigned orq_user_tport : 1;	/**< Application provided tport - don't retry */
  unsigned orq_try_tcp_instead : 1;
  unsigned orq_try_udp_instead : 1;
  unsigned orq_reliable : 1; /**< Transport is reliable */
  unsigned orq_ack_error : 1; /**< ACK is sent by NTA */
  /* Attributes */
  unsigned orq_user_via : 1;
  unsigned orq_stateless : 1;
  unsigned orq_pass_100 : 1;
  unsigned orq_sigcomp_new:1;	/**< Create compartment if needed */
  unsigned orq_sigcomp_zap:1;	/**< Reset SigComp after completing */
  unsigned orq_must_100rel : 1;
  unsigned orq_timestamp : 1;	/**< Insert @Timestamp header. */
  unsigned orq_100rel:1;	/**< Support 100rel */
  unsigned : 0;	/* pad */

#if HAVE_SOFIA_SRESOLV
  sipdns_resolver_t    *orq_resolver;
#endif
  enum nta_res_order_e  orq_res_order;  /**< AAAA/A first? */

  url_t                *orq_route;      /**< Route URL */
  tp_name_t             orq_tpn[1];     /**< Where to send request */
  char const           *orq_scheme;     /**< Transport URL type */

  tport_t              *orq_tport;
  struct sigcomp_compartment *orq_cc;
  tagi_t               *orq_tags;       /**< Tport tag items */
  int                   orq_pending;    /**< Request is pending in tport */

  char const           *orq_branch;	/**< Transaction branch */
  char const           *orq_via_branch;	/**< @Via branch */

  int                  *orq_status2b;   /**< Delayed response */

  nta_outgoing_t       *orq_cancel;     /**< CANCEL transaction */

  uint32_t              orq_rseq;       /**< Latest incoming rseq */
};

/* Virtual function table for plugging in SigComp */
typedef struct
{
  int ncv_size;
  char const *ncv_name;

  nta_compressor_t *(*ncv_init_agent)(nta_agent_t *sa, 
				     char const * const *options);

  void (*ncv_deinit_agent)(nta_agent_t *sa, nta_compressor_t *);

  struct sigcomp_compartment *(*ncv_compartment)(nta_agent_t *sa,
						 tport_t *tport, 
						 nta_compressor_t *msc,
						 tp_name_t const *tpn,
						 char const * const *options,
						 int new_if_needed);

  int (*ncv_accept_compressed)(nta_agent_t *sa,
			       nta_compressor_t *msc,
			       tport_compressor_t *sc,
			       msg_t *msg,
			       struct sigcomp_compartment *cc);

  int (*ncv_close_compressor)(nta_agent_t *sa,
			      struct sigcomp_compartment *cc);
  int (*ncv_zap_compressor)(nta_agent_t *sa,
			    struct sigcomp_compartment *cc);

  struct sigcomp_compartment *(*ncv_compartment_ref)
    (struct sigcomp_compartment *);

  void (*ncv_compartment_unref)(struct sigcomp_compartment *);
 
} nta_compressor_vtable_t;

extern nta_compressor_vtable_t *nta_compressor_vtable;

SOFIAPUBFUN nta_compressor_t *nta_agent_init_sigcomp(nta_agent_t *sa);
SOFIAPUBFUN void nta_agent_deinit_sigcomp(nta_agent_t *sa);

/* ====================================================================== */
/* Debug log settings */

#define SU_LOG   nta_log

#ifdef SU_DEBUG_H
#error <su_debug.h> included directly.
#endif
#include <sofia-sip/su_debug.h>
SOFIAPUBVAR su_log_t nta_log[];

SOFIA_END_DECLS

#endif /* NTA_INTERNAL_H */
