/*
 * $Id$
 *
 * Copyright (C) 2012 Smile Communications, jason.penton@smilecoms.com
 * Copyright (C) 2012 Smile Communications, richard.good@smilecoms.com
 * 
 * The initial version of this code was written by Dragos Vingarzan
 * (dragos(dot)vingarzan(at)fokus(dot)fraunhofer(dot)de and the
 * Fruanhofer Institute. It was and still is maintained in a separate
 * branch of the original SER. We are therefore migrating it to
 * Kamailio/SR and look forward to maintaining it from here on out.
 * 2011/2012 Smile Communications, Pty. Ltd.
 * ported/maintained/improved by 
 * Jason Penton (jason(dot)penton(at)smilecoms.com and
 * Richard Good (richard(dot)good(at)smilecoms.com) as part of an 
 * effort to add full IMS support to Kamailio/SR using a new and
 * improved architecture
 * 
 * NB: Alot of this code was originally part of OpenIMSCore,
 * FhG Fokus. 
 * Copyright (C) 2004-2006 FhG Fokus
 * Thanks for great work! This is an effort to 
 * break apart the various CSCF functions into logically separate
 * components. We hope this will drive wider use. We also feel
 * that in this way the architecture is more complete and thereby easier
 * to manage in the Kamailio/SR environment
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * Kamailio 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; either version 2 of the License, or
 * (at your option) any later version
 *
 * Kamailio 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.
 *
 * You should have received a copy of the GNU General Public License 
 * along with this program; if not, write to the Free Software 
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 */

#ifndef USRLOC_H
#define USRLOC_H

#include <time.h>
#include "ul_callback.h"
#include "../../core/qvalue.h"
#include "../../core/str.h"
#include "../../modules/tm/dlg.h"
#include "../cdp/diameter_ims_code_avp.h"

#define DEFAULT_DBG_FILE "/var/log/usrloc_debug"
#define MAX_CONTACTS_PER_IMPU 100

/* DB modes */
#define NO_DB         0
#define WRITE_THROUGH 1
#define WRITE_BACK    2		//not implemented yet
#define DB_ONLY	      3		//not implemented yet

/*IMPU states*/
#define IMS_USER_NOT_REGISTERED 0		/** User not registered */
#define IMS_USER_REGISTERED 1			/** User registered */
#define IMS_USER_UNREGISTERED -1		/** User unregistered (not registered but with services for unregistered state) */

/** Conjunctive Normal Format */
#define IFC_CNF 1
/** Disjunctive Normal Format */
#define IFC_DNF 0

#define IFC_UNKNOWN -1				/** unknown SPT type */
#define IFC_REQUEST_URI 1			/** SPT for checking the Request-URI */
#define IFC_METHOD 2				/** SPT for checking the Method */
#define IFC_SIP_HEADER 3			/** SPT for checking a SIP Header */
#define IFC_SESSION_CASE 4			/** SPT for checking the Session Case */
#define IFC_SESSION_DESC 5			/** SPT for checking a SDP line */

#define IFC_ORIGINATING_SESSION 0	/** Session case originating */
#define IFC_TERMINATING_SESSION 1	/** Session case terminating */
#define IFC_TERMINATING_UNREGISTERED 2	/** Session case terminating to unregistered user*/

#define IFC_INITIAL_REGISTRATION 	1		/** Initial Registration */
#define IFC_RE_REGISTRATION 		1<<1	/** Re-Registration */
#define IFC_DE_REGISTRATION 		1<<2	/** De-Registration */

#define IFC_NO_DEFAULT_HANDLING -1	/** No default handling */
#define IFC_SESSION_CONTINUED 0		/** Session should continue on failure to contact AS */
#define IFC_SESSION_TERMINATED 1	/** Session should be terminated on failure to contact AS */


/*forward declaration necessary for udomain*/
struct udomain;
typedef struct udomain udomain_t;


typedef struct _subscriber_data {
	int event;
	int expires;
	int version;
	str* callid;
	str* ftag;
	str* ttag;
	unsigned int local_cseq;
	str* record_route;
	str* sockinfo_str;
	str* presentity_uri;
	str *watcher_uri;
	str* watcher_contact;
} subscriber_data_t;

typedef struct _reg_subscriber {
    int event;
    time_t expires; /**< Time of expiration		 			*/
    int version; /**< Last version sent to this subs.	*/

    str watcher_uri;
    str watcher_contact;
    str presentity_uri;
    
    unsigned int local_cseq;
    str call_id;
    str from_tag;
    str to_tag;
    str record_route;
    str sockinfo_str;

    struct _reg_subscriber *next; /**< the next subscriber in the list		*/
    struct _reg_subscriber *prev; /**< the previous subscriber in the list	*/
} reg_subscriber;

/*!
 * \brief States for in-memory contacts in regards to contact storage handler (db, in-memory, ldap etc)
 */
typedef enum cstate {
    CS_NEW, /*!< New contact - not flushed yet */
    CS_SYNC, /*!< Synchronized contact with the database */
    CS_DIRTY /*!< Update contact - not flushed yet */
} cstate_t;

typedef enum contact_state {
    CONTACT_VALID,
    CONTACT_DELETE_PENDING,
    CONTACT_EXPIRE_PENDING_NOTIFY,
    CONTACT_DELETED,
    CONTACT_DELAYED_DELETE,
	CONTACT_NOTIFY_READY /**< Prevents deletion of a contact before construction of notify body */
} contact_state_t;

/*! \brief Valid contact is a contact that either didn't expire yet or is permanent */
#define VALID_CONTACT(c, t)   (((c->expires>t) || (c->expires==0)) && c->state!=CONTACT_DELETED && c->state!=CONTACT_DELETE_PENDING && c->state!=CONTACT_EXPIRE_PENDING_NOTIFY && c->state!=CONTACT_DELAYED_DELETE && c->state!=CONTACT_NOTIFY_READY)

#define VALID_UE_TYPE(c, t)   ((t==0) || (t==1 && c->is_3gpp) || (t==2 && !c->is_3gpp))

struct hslot;
/*!< Hash table slot */
struct socket_info;

/** SPT for checking a SIP Header */
typedef struct _ims_sip_header {
    str header; /**< name of the header to match	*/
    str content; /**< regex to match             	*/
    short type; /**< if known header, precalculated	*/
} ims_sip_header;

/** SPT for checking a SDP line */
typedef struct _ims_session_desc {
    str line; /**< name of line from description */
    str content; /**< regex to match                */
} ims_session_desc;

/** Service Point Trigger Structure */
typedef struct _ims_spt {
    char condition_negated; /**< if to negate entire condition	*/
    int group; /**< group to which it belongs		*/
    char type; /**< type of condition				*/

    union {
        str request_uri; /**< Request URI regex				*/
        str method; /**< the SIP method should be this	*/
        ims_sip_header sip_header; /**< match of a certain SIP header	*/
        char session_case; /**< session direction and case		*/
        ims_session_desc session_desc; /**< session description match 		*/
    };
    /**< union for SPT 					*/
    char registration_type; /**< set of registration types		*/
} ims_spt;

/** Trigger Point Structure */

typedef struct _ims_trigger_point {
    char condition_type_cnf; /**< if it's CNF or DNF     		*/
    ims_spt *spt; /**< service point triggers 1..n 		*/
    unsigned short spt_cnt; /**< number of service point triggers 	*/
} ims_trigger_point;

/** Application Server Structure */
typedef struct _ims_application_server {
    str server_name; /**< SIP URL of the app server                      */
    char default_handling; /**< enum SESSION_CONTINUED SESSION_TERMINATED 0..1 */
    str service_info; /**< optional info to be sent to AS 0..1            */
    int include_register_request;
    int include_register_response;
} ims_application_server;

/** Public Identity Structure */
typedef struct {
    char barring; /**< Barring state									*/
    str public_identity; /**< Public Identity string							*/
    str wildcarded_psi; /** if exists is the wildcarded psi					*/
} ims_public_identity;

/** Initial Filter Criteria Structure */
typedef struct _ims_filter_criteria {
    int priority; /**< checking priority, lower means more important */
    ims_trigger_point *trigger_point; /**< definition of trigger 0..1 */
    ims_application_server application_server; /**< target of the trigger   */
    char *profile_part_indicator; /**< profile part indicator 0..1 */
} ims_filter_criteria;

/** CoreNetwork Service Authorization */
typedef struct _ims_cn_service_auth {
    int subscribed_media_profile_id; /* must be >=0 */
} ims_cn_service_auth;

/** Service Profile Structure */
typedef struct {
    ims_public_identity *public_identities; /**< array of public identities		*/
    unsigned short public_identities_cnt; /**< number of public identities	*/
    ims_filter_criteria *filter_criteria; /**< vector of filter criteria 0..n */
    unsigned short filter_criteria_cnt; /**< size of the vector above		*/
    ims_cn_service_auth *cn_service_auth; /**< core net. services auth. 0..1	*/
    int *shared_ifc_set; /**< shared ifc set ids 0..n 		*/
    unsigned short shared_ifc_set_cnt; /**< size of above vector 			*/
} ims_service_profile;

/** User Subscription Structure */
typedef struct ims_subscription_s {
    str private_identity; /**< private identity 				*/
    struct hslot_sp* slot; /*!< Collision slot in the hash table array we belong to */
    int sl;                 /*!< slot number we belong to */
    int wpsi; /** This is not in the standards
	 	 	 	 	 	 	 	 	 	 	 	0 normal user or distinct psi inside
	 	 	 	 	 	 	 	 	 	 	 	1 wildcarded psi
	 	 	 	 	 	 	 	 	 	 	 **/
    ims_service_profile *service_profiles; /**< array of service profiles		*/
    unsigned short service_profiles_cnt; /**< size of the array above		*/

    int ref_count; /**< referenced count 				*/
    gen_lock_t *lock; /**< lock for operations on it 		*/
    struct ims_subscription_s* next;
    struct ims_subscription_s* prev;
} ims_subscription;

/** IPSec SA Information */
typedef struct ipsec {
    unsigned int spi_uc; /**< SPI Client to use					*/
    unsigned int spi_us; /**< SPI Server to use					*/
    unsigned int spi_pc; /**< SPI Client to use					*/
    unsigned int spi_ps; /**< SPI Server to use					*/
    unsigned short port_uc; /**< Port UE Client						*/
    unsigned short port_us; /**< Port UE Server						*/

    str ealg; /**< Cypher Algorithm - ESP				*/
    str r_ealg; /**< received Cypher Algorithm - ESP	*/
    str ck; /**< Cypher Key							*/
    str alg; /**< Integrity Algorithm - AH			*/
    str r_alg; /**<received Integrity Algorithm - AH	*/
    str ik; /**< Integrity Key						*/
    str prot; /**< Protocol (ah/esp) */
    str mod; /**< Mode (transport/tunnel) */
} ipsec_t;

typedef enum sec_type {
    SECURITY_NONE = 0,
    SECURITY_IPSEC = 1,
} security_type;

typedef struct security {
    str sec_header; /**< Security Header value 				*/
    security_type type; /**< Type of security in use			*/

    union {
        ipsec_t *ipsec; /**< IPSec SA information, if any		*/
    } data;
    float q;
} security_t;

/*! \brief Structure to hold dialog data used when this contact is part of a confirmed dialog so we can tear down the dialog if the contact is removed */
typedef struct contact_dialog_data {
    unsigned int h_entry;
    unsigned int h_id;

    struct contact_dialog_data* next; /*!< Next contact in the linked list */
    struct contact_dialog_data* prev; /*!< Previous contact in the linked list */
} contact_dialog_data_t;


typedef struct impu_contact_holder {
    unsigned int numcontacts;
    unsigned int num3gppcontacts;
    struct impu_contact *head;
    struct impu_contact *tail;
} impu_contact_holder_t;

typedef struct impu_contact {
    struct ucontact *contact;
    struct impu_contact *next;
    struct impu_contact *prev;
} impu_contact_t;

/*! \brief Main structure for handling of registered Contact data */
typedef struct ucontact {
    gen_lock_t *lock;           /**< we have to lock the contact as it is shared by many impu structs and has reference conting	*/
    struct contact_hslot* slot; /*!< Collision slot in the hash table array we belong to */
    unsigned int sl; 			/*!< Hash slot number we belong to */
    int ref_count;
    int is_3gpp;
    contact_state_t state;
    str domain; /*!< Pointer to domain name (NULL terminated) */
    str aor; /*!< Pointer to the AOR string in record structure*/
    str c; /*!< Contact address */
    param_t *params; /*!< Params header details> */
    str received; /*!< IP+port+protocol we received the REGISTER from */
    str path; /*!< Path header */
    time_t expires; /*!< Expires parameter */
    qvalue_t q; /*!< q parameter */
    str callid; /*!< Call-ID header field of registration */
    int cseq; /*!< CSeq value */
//    cstate_t state; /*!< State of the contact (\ref cstate) */
    unsigned int flags; /*!< Various flags (NAT, ping type, etc) */
    unsigned int cflags; /*!< Custom contact flags (from script) */
    str user_agent; /*!< User-Agent header field */
    struct socket_info *sock; /*!< received socket */
    time_t last_modified; /*!< When the record was last modified */
    unsigned int methods; /*!< Supported methods */

    struct socket_info *si_ps;
    struct socket_info *sipc;
    security_t *security_temp; /**< Security-Client Information		*/
    security_t *security; /**< Security-Client Information		*/

    struct ulcb_head_list* cbs;	/**< individual callbacks per contact */
    
    struct contact_dialog_data *first_dialog_data;
    struct contact_dialog_data *last_dialog_data;

    struct ucontact* next; /*!< Next contact in the linked list */
    struct ucontact* prev; /*!< Previous contact in the linked list */
} ucontact_t;

/*! \brief Informations related to a contact (used mainly for passing data around */
typedef struct ucontact_info {
    str received; /*!< Received interface */
    str* path; /*!< Path informations */
    time_t expires; /*!< Contact expires */
    qvalue_t q; /*!< Q-value */
    str* callid; /*!< call-ID */
    param_t *params;
    int cseq; /*!< CSEQ number */
    unsigned int flags; /*!< message flags */
    unsigned int cflags; /*!< contact flags */
    str *user_agent; /*!< user agent header */
    struct socket_info *sock; /*!< socket informations */

    unsigned int methods; /*!< supported methods */
    time_t last_modified; /*!< last modified */
} ucontact_info_t;

typedef struct udomain_head {
    str* name;
} udomain_head_t;

/** Enumeration for public identity Registration States */
enum pi_reg_states {
    IMPU_NOT_REGISTERED = 0, 	/**< User not-registered, no profile stored	*/
    IMPU_REGISTERED = 1, 		/**< User registered						*/
    IMPU_UNREGISTERED = -1 		/**< User not-registered, profile stored	*/
};

static inline char* get_impu_regstate_as_string(enum pi_reg_states reg_state) {
    switch (reg_state) {
        case IMPU_NOT_REGISTERED:
            return "not registered";
        case IMPU_REGISTERED:
            return "registered";
        case IMPU_UNREGISTERED:
            return "unregistered";
        default:
            return "unknown";
    }
}

static inline char* get_contact_state_as_string(enum contact_state c_state) {
    switch(c_state) {
        case CONTACT_VALID:
            return "Contact valid";
        case CONTACT_DELETE_PENDING:
            return "Contact in delete pending";
        case CONTACT_EXPIRE_PENDING_NOTIFY:
            return "Contact expired with pending NOTIFY";
        case CONTACT_DELETED:
            return "Contact deleted";
        case CONTACT_DELAYED_DELETE:
            return "Contact with delayed delete";
		case CONTACT_NOTIFY_READY:
			return "Contact expired with prepared NOTIFY";
        default:
            return "unknown";
    }
}

typedef struct _contact_ptr {
    ucontact_t* contact;
    struct contact_ptr* prev;
    struct contact_ptr* next;
} contact_ptr;

typedef struct _contact_ptr_list {
    int n;
    gen_lock_t* lock;
    struct contact_ptr* first; 
} contact_ptr_list;

/*! \brief
 * Basic hash table element
 */
typedef struct impurecord {
    str* domain; 				/*!< Pointer to domain we belong to (null terminated string) */
    int is_primary;				/*!< first IMPU (in implicit set this is the one that will trigger a SAR, if no implicit set - we should still be safe with first) */
    str public_identity; 			/*!< Address of record */
    str private_identity;                       /*!< Subscription to which we be long */
    unsigned int aorhash; 			/*!< Hash over address of record */
    int barring;
    enum pi_reg_states reg_state;
    ims_subscription *s; 			/**< subscription to which it belongs 		*/
    str ccf1, ccf2, ecf1, ecf2; 	/**< charging functions						*/
    impu_contact_holder_t linked_contacts;
    reg_subscriber *shead, *stail; 	/**< list of subscribers attached			*/
    time_t expires; 				/*!< timer when this IMPU expires - currently only used for unreg IMPU */
    int send_sar_on_delete;			/* used to distinguish between explicit contact removal and contact expiry - SAR only sent on contact expiry*/

    struct hslot* slot; 			/*!< Collision slot in the hash table array we belong to */
    struct ulcb_head_list* cbs;		/**< individual callbacks per impurecord */
    struct impurecord* prev; 		/*!< Next item in the hash entry */
    struct impurecord* next; 		/*!< Previous item in the hash entry */
} impurecord_t;

/** a parcel for transporting impurecord information */
typedef struct impurecord_info {
    int barring;
    enum pi_reg_states reg_state;
    ims_subscription *s;
    str *ccf1, *ccf2, *ecf1, *ecf2;
} impurecord_info_t;

typedef struct contact_list {
    struct contact_hslot* slot;
    int size;
    int max_collisions;
//    stat_var *contacts;        /*!< no of contacts in table */
}contact_list_t;

typedef struct ims_subscription_list {
    struct hslot_sp* slot;
    int size;               /* size of list (slots) */
    int subscriptions;      /* total number of subscriptions in storage */
    int max_collisions;
}ims_subscription_list_t;

typedef int (*insert_impurecord_t)(struct udomain* _d, str* public_identity, str* private_identity, int reg_state, int barring,
        ims_subscription** s, str* ccf1, str* ccf2, str* ecf1, str* ecf2,
        struct impurecord** _r);

typedef int (*get_impurecord_t)(struct udomain* _d, str* _aor, struct impurecord** _r);

typedef int (*delete_impurecord_t)(struct udomain* _d, str* _aor, struct impurecord* _r);

typedef int (*update_impurecord_t)(struct udomain* _d, str* public_identity, impurecord_t* impu_rec, int reg_state, int send_sar_on_delete, int barring, int is_primary, ims_subscription** s, str* ccf1, str* ccf2, str* ecf1, str* ecf2, struct impurecord** _r);

typedef void (*lock_contact_slot_t)(str* contact_uri);

typedef void (*unlock_contact_slot_t)(str* contact_uri);

typedef void (*lock_contact_slot_i_t)(int sl);

typedef void (*unlock_contact_slot_i_t)(int sl);

typedef void (*lock_subscription_t)(ims_subscription* s);

typedef void (*unlock_subscription_t)(ims_subscription* s);
typedef void (*unref_subscription_t) (ims_subscription* s);
typedef void (*ref_subscription_t) (ims_subscription* s);

typedef int (*update_ucontact_t)(struct impurecord* _r, struct ucontact* _c, struct ucontact_info* _ci);

typedef int (*expire_ucontact_t)(struct impurecord* _r, struct ucontact* _c);

typedef int (*unlink_contact_from_impu_t)(struct impurecord* _r, struct ucontact* _c, int write_to_db, int is_explicit);

typedef int (*link_contact_to_impu_t)(struct impurecord* _r, struct ucontact* _c, int wirte_to_db);

typedef int (*insert_ucontact_t)(struct impurecord* _r, str* _contact, struct ucontact_info* _ci, struct ucontact** _c);

typedef int (*delete_ucontact_t)(struct ucontact* _c);

typedef int (*get_ucontact_t)(str* _c, str* _callid, str* _path, int _cseq, struct ucontact** _co);

typedef void (*release_ucontact_t)(struct ucontact* _c);

typedef int (*add_dialog_data_to_contact_t)(struct ucontact* _c, unsigned int h_entry, unsigned int h_id);

typedef int (*remove_dialog_data_from_contact_t)(struct ucontact* _c, unsigned int h_entry, unsigned int h_id);

typedef void (*lock_udomain_t)(struct udomain* _d, str *_aor);

typedef void (*unlock_udomain_t)(struct udomain* _d, str *_aor);

typedef int (*register_udomain_t)(const char* _n, struct udomain** _d);

typedef int (*get_all_ucontacts_t)(void* buf, int len, unsigned int flags, unsigned int part_idx, unsigned int part_max);

typedef int (*get_udomain_t)(const char* _n, udomain_t** _d);

typedef int (*update_subscriber_t)(impurecord_t* urec, reg_subscriber** _reg_subscriber,
        int *expires, int *local_cseq, int *version);

typedef void (*external_delete_subscriber_t)(reg_subscriber *s, udomain_t* _t, int lock_domain);

typedef int (*get_subscriber_t)(impurecord_t* urec, str *watcher_contact, str *presentity_uri, int event, reg_subscriber** reg_subscriber);

typedef int (*add_subscriber_t)(impurecord_t* urec,
		subscriber_data_t* subscriber_data, reg_subscriber** _reg_subscriber, int db_load);

typedef int (*get_impus_from_subscription_as_string_t)(udomain_t* _d, impurecord_t* impu_rec, int barring, str** impus, int* num_impus, int is_shm);

typedef str (*get_presentity_from_subscriber_dialog_t)(str *callid, str *to_tag, str *from_tag);

/*! usrloc API export structure */
typedef struct usrloc_api {
    int use_domain; /*! use_domain module parameter */
    int db_mode; /*! db_mode module parameter */
    unsigned int nat_flag; /*! nat_flag module parameter */

    register_udomain_t register_udomain;
    get_udomain_t get_udomain;
    lock_udomain_t lock_udomain;
    unlock_udomain_t unlock_udomain;

    insert_impurecord_t insert_impurecord;
    delete_impurecord_t delete_impurecord;
    get_impurecord_t get_impurecord;
    update_impurecord_t update_impurecord;

    lock_contact_slot_t lock_contact_slot;
    unlock_contact_slot_t unlock_contact_slot;
    lock_contact_slot_i_t lock_contact_slot_i;
    unlock_contact_slot_i_t unlock_contact_slot_i;
    lock_subscription_t lock_subscription;
    unlock_subscription_t unlock_subscription;
    unref_subscription_t unref_subscription;
    ref_subscription_t ref_subscription;
    
    insert_ucontact_t insert_ucontact;
    delete_ucontact_t delete_ucontact;
    get_ucontact_t get_ucontact;
    release_ucontact_t release_ucontact;
    get_all_ucontacts_t get_all_ucontacts;
    update_ucontact_t update_ucontact;
    expire_ucontact_t expire_ucontact;
    unlink_contact_from_impu_t unlink_contact_from_impu;
    link_contact_to_impu_t link_contact_to_impu;
    //update_user_profile_t update_user_profile;
    
    add_dialog_data_to_contact_t add_dialog_data_to_contact;
    remove_dialog_data_from_contact_t remove_dialog_data_from_contact;

    add_subscriber_t add_subscriber;
    update_subscriber_t update_subscriber;
    external_delete_subscriber_t external_delete_subscriber;
    get_subscriber_t get_subscriber;

    get_impus_from_subscription_as_string_t get_impus_from_subscription_as_string;

    register_ulcb_t register_ulcb;
    
    get_presentity_from_subscriber_dialog_t get_presentity_from_subscriber_dialog;
    
} usrloc_api_t;

/*! usrloc API export bind function */
typedef int (*bind_usrloc_t)(usrloc_api_t* api);

#endif
