/* 
 * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
 *
 * 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; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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.
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 */
#include "test.h"
#include <pjsip_ua.h>
#include <pjsip.h>
#include <pjlib.h>

#define THIS_FILE   "regc_test.c"


/************************************************************************/
/* A module to inject error into outgoing sending operation */
static pj_status_t mod_send_on_tx_request(pjsip_tx_data *tdata);

static struct 
{
    pjsip_module mod;
    unsigned     count;
    unsigned     count_before_reject;
} send_mod = 
{
    {
        NULL, NULL,                         /* prev, next.              */
        { "mod-send", 8 },                  /* Name.                    */
        -1,                                 /* Id                       */
        PJSIP_MOD_PRIORITY_TRANSPORT_LAYER,         /* Priority                 */
        NULL,                               /* load()                   */
        NULL,                               /* start()                  */
        NULL,                               /* stop()                   */
        NULL,                               /* unload()                 */
        NULL,                               /* on_rx_request()          */
        NULL,                               /* on_rx_response()         */
        &mod_send_on_tx_request,                    /* on_tx_request.           */
        NULL,                               /* on_tx_response()         */
        NULL,                               /* on_tsx_state()           */
    },
    0,
    0xFFFF
};


static pj_status_t mod_send_on_tx_request(pjsip_tx_data *tdata)
{
    PJ_UNUSED_ARG(tdata);

    if (++send_mod.count > send_mod.count_before_reject)
        return PJ_ECANCELLED;
    else
        return PJ_SUCCESS;
}


/************************************************************************/
/* Registrar for testing */
static pj_bool_t regs_rx_request(pjsip_rx_data *rdata);

enum contact_op
{
    NONE,       /* don't put Contact header         */
    EXACT,      /* return exact contact             */
    MODIFIED,   /* return modified Contact header   */
};

struct registrar_cfg
{
    pj_bool_t       respond;        /* should it respond at all         */
    unsigned        status_code;    /* final response status code       */
    pj_bool_t       authenticate;   /* should we authenticate?          */
    enum contact_op contact_op;     /* What should we do with Contact   */
    unsigned        expires_param;  /* non-zero to put in expires param */
    unsigned        expires;        /* non-zero to put in Expires header*/

    pj_str_t        more_contacts;  /* Additional Contact headers to put*/
};

static struct registrar
{
    pjsip_module            mod;
    struct registrar_cfg    cfg;
    unsigned                response_cnt;
} registrar = 
{
    {
        NULL, NULL,                         /* prev, next.              */
        { "registrar", 9 },                 /* Name.                    */
        -1,                                 /* Id                       */
        PJSIP_MOD_PRIORITY_APPLICATION,     /* Priority                 */
        NULL,                               /* load()                   */
        NULL,                               /* start()                  */
        NULL,                               /* stop()                   */
        NULL,                               /* unload()                 */
        &regs_rx_request,                   /* on_rx_request()          */
        NULL,                               /* on_rx_response()         */
        NULL,                               /* on_tx_request.           */
        NULL,                               /* on_tx_response()         */
        NULL,                               /* on_tsx_state()           */
    }
};

static pj_bool_t regs_rx_request(pjsip_rx_data *rdata)
{
    pjsip_msg *msg = rdata->msg_info.msg;
    pjsip_hdr hdr_list;
    int code;
    pj_status_t status;

    if (msg->line.req.method.id != PJSIP_REGISTER_METHOD)
        return PJ_FALSE;

    if (!registrar.cfg.respond)
        return PJ_TRUE;

    pj_list_init(&hdr_list);

    if (registrar.cfg.authenticate && 
        pjsip_msg_find_hdr(msg, PJSIP_H_AUTHORIZATION, NULL)==NULL) 
    {
        pjsip_generic_string_hdr *hwww;
        const pj_str_t hname = pj_str("WWW-Authenticate");
        const pj_str_t hvalue = pj_str("Digest realm=\"test\"");

        hwww = pjsip_generic_string_hdr_create(rdata->tp_info.pool, &hname, 
                                               &hvalue);
        pj_list_push_back(&hdr_list, hwww);

        code = 401;

    } else {
        if (registrar.cfg.contact_op == EXACT ||
            registrar.cfg.contact_op == MODIFIED) 
        {
            pjsip_hdr *hsrc;

            for (hsrc=msg->hdr.next; hsrc!=&msg->hdr; hsrc=hsrc->next) {
                pjsip_contact_hdr *hdst;

                if (hsrc->type != PJSIP_H_CONTACT)
                    continue;

                hdst = (pjsip_contact_hdr*)
                       pjsip_hdr_clone(rdata->tp_info.pool, hsrc);

                if (hdst->expires==0)
                    continue;

                if (registrar.cfg.contact_op == MODIFIED) {
                    if (PJSIP_URI_SCHEME_IS_SIP(hdst->uri) ||
                        PJSIP_URI_SCHEME_IS_SIPS(hdst->uri))
                    {
                        pjsip_sip_uri *sip_uri = (pjsip_sip_uri*)
                                                 pjsip_uri_get_uri(hdst->uri);
                        sip_uri->host = pj_str("x-modified-host");
                        sip_uri->port = 1;
                    }
                }

                if (registrar.cfg.expires_param)
                    hdst->expires = registrar.cfg.expires_param;

                pj_list_push_back(&hdr_list, hdst);
            }
        }

        if (registrar.cfg.more_contacts.slen) {
            pjsip_generic_string_hdr *hcontact;
            const pj_str_t hname = pj_str("Contact");

            hcontact = pjsip_generic_string_hdr_create(rdata->tp_info.pool, &hname, 
                                                       &registrar.cfg.more_contacts);
            pj_list_push_back(&hdr_list, hcontact);
        }

        if (registrar.cfg.expires) {
            pjsip_expires_hdr *hexp;

            hexp = pjsip_expires_hdr_create(rdata->tp_info.pool, 
                                            registrar.cfg.expires);
            pj_list_push_back(&hdr_list, hexp);
        }

        registrar.response_cnt++;

        code = registrar.cfg.status_code;
    }

    status = pjsip_endpt_respond(endpt, NULL, rdata, code, NULL,
                                 &hdr_list, NULL, NULL);
    pj_assert(status == PJ_SUCCESS);

    return (status == PJ_SUCCESS);
}


/************************************************************************/
/* Client registration test session */
struct client
{
    /* Result/expected result */
    int         error;
    int         code;
    pj_bool_t   have_reg;
    unsigned    expiration;
    unsigned    contact_cnt;
    pj_bool_t   auth;

    /* Commands */
    pj_bool_t   destroy_on_cb;

    /* Status */
    pj_bool_t   done;

    /* Additional results */
    unsigned    interval;
    unsigned    next_reg;
};

/* regc callback */
static void client_cb(struct pjsip_regc_cbparam *param)
{
    struct client *client = (struct client*) param->token;
    pjsip_regc_info info;
    pj_status_t status;

    client->done = PJ_TRUE;

    status = pjsip_regc_get_info(param->regc, &info);
    pj_assert(status == PJ_SUCCESS);
    PJ_UNUSED_ARG(status);

    client->error = (param->status != PJ_SUCCESS);
    client->code = param->code;

    if (client->error)
        return;

    client->have_reg = info.auto_reg &&
                       info.interval != PJSIP_EXPIRES_NOT_SPECIFIED &&
                       param->expiration != PJSIP_EXPIRES_NOT_SPECIFIED;
    client->expiration = param->expiration;
    client->contact_cnt = param->contact_cnt;
    client->interval = info.interval;
    client->next_reg = info.next_reg;

    if (client->destroy_on_cb)
        pjsip_regc_destroy(param->regc);
}


/* Generic client test session */
static struct client client_result;
static int do_test(const char *title,
                   const struct registrar_cfg *srv_cfg, 
                   const struct client *client_cfg,
                   const pj_str_t *registrar_uri,
                   unsigned contact_cnt,
                   const pj_str_t contacts[],
                   unsigned expires,
                   pj_bool_t leave_session,
                   pjsip_regc **p_regc)
{
    pjsip_regc *regc;
    unsigned i;
    const pj_str_t aor = pj_str("<sip:regc-test@pjsip.org>");
    pjsip_tx_data *tdata;
    pj_status_t status;

    PJ_LOG(3,(THIS_FILE, "  %s", title));

    /* Modify registrar settings */
    pj_memcpy(&registrar.cfg, srv_cfg, sizeof(*srv_cfg));

    pj_bzero(&client_result, sizeof(client_result));
    client_result.destroy_on_cb = client_cfg->destroy_on_cb;

    status = pjsip_regc_create(endpt, &client_result, &client_cb, &regc);
    if (status != PJ_SUCCESS)
        return -100;

    status = pjsip_regc_init(regc, registrar_uri, &aor, &aor, contact_cnt,
                             contacts, expires ? expires : 60);
    if (status != PJ_SUCCESS) {
        pjsip_regc_destroy(regc);
        return -110;
    }

    if (client_cfg->auth) {
        pjsip_cred_info cred;

        pj_bzero(&cred, sizeof(cred));
        cred.realm = pj_str("*");
        cred.scheme = pj_str("digest");
        cred.username = pj_str("user");
        cred.data_type = PJSIP_CRED_DATA_PLAIN_PASSWD;
        cred.data = pj_str("password");

        status = pjsip_regc_set_credentials(regc, 1, &cred);
        if (status != PJ_SUCCESS) {
            pjsip_regc_destroy(regc);
            return -115;
        }
    }

    /* Register */
    status = pjsip_regc_register(regc, PJ_TRUE, &tdata);
    if (status != PJ_SUCCESS) {
        pjsip_regc_destroy(regc);
        return -120;
    }
    status = pjsip_regc_send(regc, tdata);

    /* That's it, wait until the callback is sent */
    for (i=0; i<600 && !client_result.done; ++i) {
        flush_events(100);
    }

    if (!client_result.done) {
        PJ_LOG(3,(THIS_FILE, "    error: test has timed out"));
        pjsip_regc_destroy(regc);
        return -200;
    }

    /* Destroy the regc, we're done with the test, unless we're
     * instructed to leave the session open.
     */
    if (!leave_session && !client_cfg->destroy_on_cb)
        pjsip_regc_destroy(regc);

    /* Compare results with expected results */

    if (client_result.error != client_cfg->error) {
        PJ_LOG(3,(THIS_FILE, "    error: expecting err=%d, got err=%d",
                  client_cfg->error, client_result.error));
        return -210;
    }
    if (client_result.code != client_cfg->code &&
        client_cfg->code != 502 && client_cfg->code != 503 &&
        client_result.code != 502 && client_result.code != 503)
    {
        PJ_LOG(3,(THIS_FILE, "    error: expecting code=%d, got code=%d",
                  client_cfg->code, client_result.code));
        return -220;
    }
    if (client_result.expiration != client_cfg->expiration) {
        PJ_LOG(3,(THIS_FILE, "    error: expecting expiration=%d, got expiration=%d",
                  client_cfg->expiration, client_result.expiration));
        return -240;
    }
    if (client_result.contact_cnt != client_cfg->contact_cnt) {
        PJ_LOG(3,(THIS_FILE, "    error: expecting contact_cnt=%d, got contact_cnt=%d",
                  client_cfg->contact_cnt, client_result.contact_cnt));
        return -250;
    }
    if (client_result.have_reg != client_cfg->have_reg) {
        PJ_LOG(3,(THIS_FILE, "    error: expecting have_reg=%d, got have_reg=%d",
                  client_cfg->have_reg, client_result.have_reg));
        return -260;
    }
    if (client_result.have_reg && client_result.interval != client_result.expiration) {
        PJ_LOG(3,(THIS_FILE, "    error: interval (%d) is different than expiration (%d)",
                  client_result.interval, client_result.expiration));
        return -270;
    }
    if (client_result.interval > 0 && client_result.next_reg < 1) {
        PJ_LOG(3,(THIS_FILE, "    error: next_reg=%d, expecting positive number because interval is %d",
                  client_result.next_reg, client_result.interval));
        return -280;
    }

    /* Looks like everything is okay. */
    if (leave_session) {
        *p_regc = regc;
    }

    return 0;
}


/************************************************************************/
/* Customized tests */

/* Check that client is sending register refresh */
static int keep_alive_test(const pj_str_t *registrar_uri)
{
    enum { TIMEOUT = 40 };
    struct registrar_cfg server_cfg = 
        /* respond      code    auth      contact  exp_prm expires more_contacts */
        { PJ_TRUE,      200,    PJ_FALSE, EXACT,   TIMEOUT, 0,      {NULL, 0}};
    struct client client_cfg = 
        /* error        code    have_reg    expiration  contact_cnt auth?    destroy*/
        { PJ_FALSE,     200,    PJ_TRUE,    TIMEOUT,    1,          PJ_FALSE,PJ_FALSE};
    pj_str_t contact = pj_str("<sip:c@C>");


    pjsip_regc *regc;
    unsigned i;
    int ret;

    ret = do_test("register refresh (takes ~40 secs)", &server_cfg, &client_cfg, registrar_uri,
                  1, &contact, TIMEOUT, PJ_TRUE, &regc);
    if (ret != 0)
        return ret;

    /* Reset server response_cnt */
    registrar.response_cnt = 0;

    /* Wait until keep-alive/refresh is done */
    for (i=0; i<(TIMEOUT-1)*10 && registrar.response_cnt==0; ++i) {
        flush_events(100);
    }

    if (registrar.response_cnt==0) {
        PJ_LOG(3,(THIS_FILE, "    error: no refresh is received"));
        return -400;
    }

    if (client_result.error) {
        PJ_LOG(3,(THIS_FILE, "    error: got error"));
        return -410;
    }
    if (client_result.code != 200) {
        PJ_LOG(3,(THIS_FILE, "    error: expecting code=%d, got code=%d",
                  200, client_result.code));
        return -420;
    }
    if (client_result.expiration != TIMEOUT) {
        PJ_LOG(3,(THIS_FILE, "    error: expecting expiration=%d, got expiration=%d",
                  TIMEOUT, client_result.expiration));
        return -440;
    }
    if (client_result.contact_cnt != 1) {
        PJ_LOG(3,(THIS_FILE, "    error: expecting contact_cnt=%d, got contact_cnt=%d",
                  TIMEOUT, client_result.contact_cnt));
        return -450;
    }
    if (client_result.have_reg == 0) {
        PJ_LOG(3,(THIS_FILE, "    error: expecting have_reg=%d, got have_reg=%d",
                  1, client_result.have_reg));
        return -460;
    }
    if (client_result.interval != TIMEOUT) {
        PJ_LOG(3,(THIS_FILE, "    error: interval (%d) is different than expiration (%d)",
                  client_result.interval, TIMEOUT));
        return -470;
    }
    if (client_result.expiration > 0 && client_result.next_reg < 1) {
        PJ_LOG(3,(THIS_FILE, "    error: next_reg=%d, expecting positive number because expiration is %d",
                  client_result.next_reg, client_result.expiration));
        return -480;
    }

    /* Success */
    pjsip_regc_destroy(regc);
    return 0;
}


/* Send error on refresh */
static int refresh_error(const pj_str_t *registrar_uri,
                         pj_bool_t destroy_on_cb)
{
    enum { TIMEOUT = 40 };
    struct registrar_cfg server_cfg = 
        /* respond      code    auth      contact  exp_prm expires more_contacts */
        { PJ_TRUE,      200,    PJ_FALSE, EXACT,   TIMEOUT, 0,      {NULL, 0}};
    struct client client_cfg = 
        /* error        code    have_reg    expiration  contact_cnt auth?    destroy*/
        { PJ_FALSE,     200,    PJ_TRUE,    TIMEOUT,    1,          PJ_FALSE,PJ_FALSE};
    pj_str_t contact = pj_str("<sip:c@C>");

    pjsip_regc *regc;
    unsigned i;
    int ret;

    ret = do_test("refresh error (takes ~40 secs)", &server_cfg, &client_cfg, registrar_uri,
                  1, &contact, TIMEOUT, PJ_TRUE, &regc);
    if (ret != 0)
        return ret;

    /* Reset server response_cnt */
    registrar.response_cnt = 0;

    /* inject error for transmission */
    send_mod.count = 0;
    send_mod.count_before_reject = 0;

    /* reconfigure client */
    client_result.done = PJ_FALSE;
    client_result.destroy_on_cb = destroy_on_cb;

    /* Wait until keep-alive/refresh is done */
    for (i=0; i<TIMEOUT*10 && !client_result.done; ++i) {
        flush_events(100);
    }

    send_mod.count_before_reject = 0xFFFF;

    if (!destroy_on_cb)
        pjsip_regc_destroy(regc);

    if (!client_result.done) {
        PJ_LOG(3,(THIS_FILE, "    error: test has timed out"));
        return -500;
    }

    /* Expecting error */
    if (client_result.error==PJ_FALSE && client_result.code/100==2) {
        PJ_LOG(3,(THIS_FILE, "    error: expecting error got successfull result"));
        return -510;
    }

    return PJ_SUCCESS;
}


/* Send error on refresh */
static int update_test(const pj_str_t *registrar_uri)
{
    enum { TIMEOUT = 40 };
    struct registrar_cfg server_cfg = 
        /* respond      code    auth      contact  exp_prm expires more_contacts */
        { PJ_TRUE,      200,    PJ_FALSE, EXACT,   TIMEOUT, 0,      {NULL, 0}};
    struct client client_cfg = 
        /* error        code    have_reg    expiration  contact_cnt auth?    destroy*/
        { PJ_FALSE,     200,    PJ_TRUE,    TIMEOUT,    1,          PJ_FALSE,PJ_FALSE};
    pj_str_t contacts[] = {
        { "<sip:a>", 7 },
        { "<sip:b>", 7 },
        { "<sip:c>", 7 }
    };

    pjsip_regc *regc;
    pjsip_contact_hdr *h1, *h2;
    pjsip_sip_uri *u1;
    unsigned i;
    pj_status_t status;
    pjsip_tx_data *tdata = NULL;
    int ret = 0;

    /* initially only has 1 contact */
    ret = do_test("update test", &server_cfg, &client_cfg, registrar_uri,
                  1, &contacts[0], TIMEOUT, PJ_TRUE, &regc);
    if (ret != 0) {
        return -600;
    }

    /*****
     * replace the contact with new one 
     */
    PJ_LOG(3,(THIS_FILE, "   replacing contact"));
    status = pjsip_regc_update_contact(regc, 1, &contacts[1]);
    if (status != PJ_SUCCESS) {
        ret = -610;
        goto on_return;
    }

    status = pjsip_regc_register(regc, PJ_TRUE, &tdata);
    if (status != PJ_SUCCESS) {
        ret = -620;
        goto on_return;
    }

    /* Check that the REGISTER contains two Contacts:
     *  - <sip:a>;expires=0,
     *  - <sip:b>
     */
    h1 = (pjsip_contact_hdr*) 
          pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, NULL);
    if (!h1) {
        ret = -630;
        goto on_return;
    }
    if ((void*)h1->next == (void*)&tdata->msg->hdr)
        h2 = NULL;
    else
        h2 = (pjsip_contact_hdr*) 
              pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, h1->next);
    if (!h2) {
        ret = -640;
        goto on_return;
    }
    /* must not have other Contact header */
    if ((void*)h2->next != (void*)&tdata->msg->hdr &&
        pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, h2->next) != NULL)
    {
        ret = -645;
        goto on_return;
    }

    u1 = (pjsip_sip_uri*) pjsip_uri_get_uri(h1->uri);

    if (*u1->host.ptr == 'a') {
        if (h1->expires != 0) {
            ret = -650;
            goto on_return;
        }
        if (h2->expires == 0) {
            ret = -660;
            goto on_return;
        }

    } else {
        pj_assert(*u1->host.ptr == 'b');
        if (h1->expires == 0) {
            ret = -670;
            goto on_return;
        }
        if (h2->expires != 0) {
            ret = -680;
            goto on_return;
        }
    }

    /* Destroy tdata */
    pjsip_tx_data_dec_ref(tdata);
    tdata = NULL;



    /** 
     * First loop, it will update with more contacts. Second loop
     * should do nothing.
     */
    for (i=0; i<2; ++i) {
        if (i==0)
            PJ_LOG(3,(THIS_FILE, "   replacing with more contacts"));
        else
            PJ_LOG(3,(THIS_FILE, "   updating contacts with same contacts"));

        status = pjsip_regc_update_contact(regc, 2, &contacts[1]);
        if (status != PJ_SUCCESS) {
            ret = -710;
            goto on_return;
        }

        status = pjsip_regc_register(regc, PJ_TRUE, &tdata);
        if (status != PJ_SUCCESS) {
            ret = -720;
            goto on_return;
        }

        /* Check that the REGISTER contains two Contacts:
         *  - <sip:b>
         *  - <sip:c>
         */
        h1 = (pjsip_contact_hdr*) 
              pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, NULL);
        if (!h1) {
            ret = -730;
            goto on_return;
        }
        if ((void*)h1->next == (void*)&tdata->msg->hdr)
            h2 = NULL;
        else
            h2 = (pjsip_contact_hdr*) 
                  pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, h1->next);
        if (!h2) {
            ret = -740;
            goto on_return;
        }
        /* must not have other Contact header */
        if ((void*)h2->next != (void*)&tdata->msg->hdr &&
            pjsip_msg_find_hdr(tdata->msg, PJSIP_H_CONTACT, h2->next) != NULL)
        {
            ret = -745;
            goto on_return;
        }

        /* both contacts must not have expires=0 parameter */
        if (h1->expires == 0) {
            ret = -750;
            goto on_return;
        }
        if (h2->expires == 0) {
            ret = -760;
            goto on_return;
        }

        /* Destroy tdata */
        pjsip_tx_data_dec_ref(tdata);
        tdata = NULL;
    }

on_return:
    if (tdata) pjsip_tx_data_dec_ref(tdata);
    pjsip_regc_destroy(regc);
    return ret;
}


/* send error on authentication */
static int auth_send_error(const pj_str_t *registrar_uri,
                           pj_bool_t destroy_on_cb)
{
    enum { TIMEOUT = 40 };
    struct registrar_cfg server_cfg = 
        /* respond      code    auth      contact  exp_prm expires more_contacts */
        { PJ_TRUE,      200,    PJ_TRUE,  EXACT,   75,     0,       {NULL, 0}};
    struct client client_cfg = 
        /* error        code    have_reg    expiration  contact_cnt auth?    destroy*/
        { PJ_TRUE,      401,    PJ_FALSE,   TIMEOUT,    0,          PJ_TRUE, PJ_TRUE};
    pj_str_t contact = pj_str("<sip:c@C>");

    pjsip_regc *regc;
    int ret;

    client_cfg.destroy_on_cb = destroy_on_cb;

    /* inject error for second request retry */
    send_mod.count = 0;
    send_mod.count_before_reject = 1;

    ret = do_test("auth send error", &server_cfg, &client_cfg, registrar_uri,
                  1, &contact, TIMEOUT, PJ_TRUE, &regc);

    send_mod.count_before_reject = 0xFFFF;

    return ret;
}




/************************************************************************/
enum
{
    OFF     = 1,
    ON      = 2,
    ON_OFF  = 3,
};

int regc_test(void)
{
    struct test_rec {
        unsigned                 check_contact;
        unsigned                 add_xuid_param;

        const char              *title;
        char                    *alt_registrar;
        unsigned                 contact_cnt;
        char                    *contacts[4];
        unsigned                 expires;
        struct registrar_cfg     server_cfg;
        struct client            client_cfg;
    } test_rec[] = 
    {
        /* immediate error */
        {
            OFF,                            /* check_contact    */
            OFF,                            /* add_xuid_param   */
            "immediate error",              /* title            */
            "sip:unresolved-host-xyy",      /* alt_registrar    */
            1,                              /* contact cnt      */
            { "sip:user@127.0.0.1:5060" },  /* contacts[]       */
            600,                            /* expires          */

            /* registrar config: */
            /* respond  code    auth      contact   exp_prm expires more_contacts */
            { PJ_FALSE, 200,    PJ_FALSE, NONE,     0,      0,      {NULL, 0}},

            /* client expected results: */
            /* error    code    have_reg    expiration  contact_cnt auth?*/
            { PJ_FALSE, 502,    PJ_FALSE,   600,        0,          PJ_FALSE}
        },

        /* timeout test */
        {
            OFF,                            /* check_contact    */
            OFF,                            /* add_xuid_param   */
            "timeout test (takes ~32 secs)",/* title            */
            NULL,                           /* alt_registrar    */
            1,                              /* contact cnt      */
            { "sip:user@127.0.0.1:5060" },  /* contacts[]       */
            600,                            /* expires          */

            /* registrar config: */
            /* respond  code    auth      contact   exp_prm expires more_contacts */
            { PJ_FALSE, 200,    PJ_FALSE, NONE,     0,      0,      {NULL, 0}},

            /* client expected results: */
            /* error    code    have_reg    expiration  contact_cnt auth? */
            { PJ_FALSE, 408,    PJ_FALSE,   600,        0,          PJ_FALSE}
        },

        /* Basic successful registration scenario:
         * a good registrar returns the Contact header as is and
         * add expires parameter. In this test no additional bindings
         * are returned.
         */
        {
            ON_OFF,                         /* check_contact    */
            ON_OFF,                         /* add_xuid_param   */
            "basic",                        /* title            */
            NULL,                           /* alt_registrar    */
            1,                              /* contact cnt      */
            { "<sip:user@127.0.0.1:5060;transport=udp;x-param=1234>" },  /* contacts[]  */
            600,                            /* expires          */

            /* registrar config: */
            /* respond  code    auth      contact  exp_prm expires more_contacts */
            { PJ_TRUE,  200,    PJ_FALSE, EXACT,    75,     65,     {NULL, 0}},

            /* client expected results: */
            /* error    code    have_reg    expiration  contact_cnt auth?*/
            { PJ_FALSE, 200,    PJ_TRUE,    75,         1,          PJ_FALSE}
        },

        /* Basic successful registration scenario with authentication
         */
        {
            ON_OFF,                         /* check_contact    */
            ON_OFF,                         /* add_xuid_param   */
            "authentication",               /* title            */
            NULL,                           /* alt_registrar    */
            1,                              /* contact cnt      */
            { "<sip:user@127.0.0.1:5060;transport=udp;x-param=1234>" },  /* contacts[]  */
            600,                            /* expires          */

            /* registrar config: */
            /* respond  code    auth      contact  exp_prm expires more_contacts */
            { PJ_TRUE,  200,    PJ_TRUE,  EXACT,    75,     65,     {NULL, 0}},

            /* client expected results: */
            /* error    code    have_reg    expiration  contact_cnt auth?*/
            { PJ_FALSE, 200,    PJ_TRUE,    75,         1,          PJ_TRUE}
        },

        /* a good registrar returns the Contact header as is and
         * add expires parameter. Also it adds bindings from other
         * clients in this test.
         */
        {
            ON_OFF,                         /* check_contact    */
            ON,                             /* add_xuid_param   */
            "more bindings in response",    /* title            */
            NULL,                           /* alt_registrar    */
            1,                              /* contact cnt      */
            { "<sip:user@127.0.0.1:5060;transport=udp>" },  /* contacts[]       */
            600,                            /* expires          */

            /* registrar config: */
            /* respond  code    auth      contact  exp_prm expires more_contacts */
            { PJ_TRUE,  200,    PJ_FALSE, EXACT,   75,      65,     {"<sip:a@a>;expires=70", 0}},

            /* client expected results: */
            /* error    code    have_reg    expiration  contact_cnt auth?*/
            { PJ_FALSE, 200,    PJ_TRUE,    75,         2,          PJ_FALSE}
        },


        /* a bad registrar returns modified Contact header, but it
         * still returns all parameters intact. In this case
         * the expiration is taken from the expires param because
         * of matching xuid param or because the number of
         * Contact header matches.
         */
        {
            ON_OFF,                         /* check_contact    */
            ON_OFF,                         /* add_xuid_param   */
            "registrar modifies Contact header",    /* title            */
            NULL,                           /* alt_registrar    */
            1,                              /* contact cnt      */
            { "<sip:user@127.0.0.1:5060>" },  /* contacts[]     */
            600,                            /* expires          */

            /* registrar config: */
            /* respond  code    auth      contact   exp_prm expires more_contacts */
            { PJ_TRUE,  200,    PJ_FALSE, MODIFIED, 75,     65,     {NULL, 0}},

            /* client expected results: */
            /* error    code    have_reg    expiration  contact_cnt auth?*/
            { PJ_FALSE, 200,    PJ_TRUE,    75,         1,          PJ_FALSE}
        },


        /* a bad registrar returns modified Contact header, but it
         * still returns all parameters intact. In addition it returns
         * bindings from other clients.
         *
         * In this case the expiration is taken from the expires param 
         * because add_xuid_param is enabled.
         */
        {
            ON_OFF,                         /* check_contact    */
            ON,                             /* add_xuid_param   */
            "registrar modifies Contact header and add bindings",    /* title           */
            NULL,                           /* alt_registrar    */
            1,                              /* contact cnt      */
            { "<sip:user@127.0.0.1:5060>" },  /* contacts[]     */
            600,                            /* expires          */

            /* registrar config: */
            /* respond  code    auth      contact   exp_prm expires more_contacts */
            { PJ_TRUE,  200,    PJ_FALSE, MODIFIED, 75,     65,     {"<sip:a@a>;expires=70", 0}},

            /* client expected results: */
            /* error    code    have_reg    expiration  contact_cnt auth?*/
            { PJ_FALSE, 200,    PJ_TRUE,    75,         2,          PJ_FALSE}
        },


        /* a bad registrar returns completely different Contact and
         * all parameters are gone. In this case the expiration is 
         * also taken from the expires param since the number of 
         * header matches.
         */
        {
            ON_OFF,                         /* check_contact    */
            ON_OFF,                         /* add_xuid_param   */
            "registrar replaces Contact header",    /* title            */
            NULL,                           /* alt_registrar    */
            1,                              /* contact cnt      */
            { "<sip:user@127.0.0.1:5060>" },  /* contacts[]     */
            600,                            /* expires          */

            /* registrar config: */
            /* respond  code    auth      contact   exp_prm expires more_contacts */
            { PJ_TRUE,  202,    PJ_FALSE, NONE,     0,      65,     {"<sip:a@A>;expires=75", 0}},

            /* client expected results: */
            /* error    code    have_reg    expiration  contact_cnt auth?*/
            { PJ_FALSE, 202,    PJ_TRUE,    75,         1,          PJ_FALSE}
        },


        /* a bad registrar returns completely different Contact (and
         * all parameters are gone) and it also includes bindings from
         * other clients.
         * In this case the expiration is taken from the Expires header.
         */
        {
            ON_OFF,                         /* check_contact    */
            ON_OFF,                         /* add_xuid_param   */
            " as above with additional bindings",    /* title           */
            NULL,                           /* alt_registrar    */
            1,                              /* contact cnt      */
            { "<sip:user@127.0.0.1:5060>" },  /* contacts[]     */
            600,                            /* expires          */

            /* registrar config: */
            /* respond  code    auth      contact   exp_prm expires more_contacts */
            { PJ_TRUE,  200,    PJ_FALSE, NONE,     0,      65,     {"<sip:a@A>;expires=75, <sip:b@B;expires=70>", 0}},

            /* client expected results: */
            /* error    code    have_reg    expiration  contact_cnt auth?*/
            { PJ_FALSE, 200,    PJ_TRUE,    65,         2,          PJ_FALSE}
        },

        /* the registrar doesn't return any bindings, but for some
         * reason it includes an Expires header.
         * In this case the expiration is taken from the Expires header.
         */
        {
            ON_OFF,                         /* check_contact    */
            ON_OFF,                         /* add_xuid_param   */
            "no Contact but with Expires",  /* title            */
            NULL,                           /* alt_registrar    */
            1,                              /* contact cnt      */
            { "<sip:user@127.0.0.1:5060>" },  /* contacts[]     */
            600,                            /* expires          */

            /* registrar config: */
            /* respond  code    auth      contact   exp_prm expires more_contacts */
            { PJ_TRUE,  200,    PJ_FALSE, NONE,     0,      65,     {NULL, 0}},

            /* client expected results: */
            /* error    code    have_reg    expiration  contact_cnt auth?*/
            { PJ_FALSE, 200,    PJ_TRUE,    65,         0,          PJ_FALSE}
        },

        /* Neither Contact header nor Expires header are present.
         * In this case the expiration is taken from the request.
         */
        {
            ON_OFF,                         /* check_contact    */
            ON_OFF,                         /* add_xuid_param   */
            "no Contact and no Expires",    /* title            */
            NULL,                           /* alt_registrar    */
            1,                              /* contact cnt      */
            { "<sip:user@127.0.0.1:5060>" },/* contacts[]       */
            600,                            /* expires          */

            /* registrar config: */
            /* respond  code    auth      contact   exp_prm expires more_contacts */
            { PJ_TRUE,  200,    PJ_FALSE, NONE,     0,      0,      {NULL, 0}},

            /* client expected results: */
            /* error    code    have_reg    expiration  contact_cnt auth?*/
            { PJ_FALSE, 200,    PJ_TRUE,    600,        0,          PJ_FALSE}
        },
    };

    unsigned i;
    pj_sockaddr_in addr;
    pjsip_transport *udp = NULL;
    pj_uint16_t port; 
    char registrar_uri_buf[80];
    pj_str_t registrar_uri;
    int rc = 0;

    pj_sockaddr_in_init(&addr, 0, 0);

    /* Acquire existing transport, if any */
    rc = pjsip_endpt_acquire_transport(endpt, PJSIP_TRANSPORT_UDP, &addr, sizeof(addr), NULL, &udp);
    if (rc == PJ_SUCCESS) {
        port = pj_sockaddr_get_port(&udp->local_addr);
        pjsip_transport_dec_ref(udp);
        udp = NULL;
    } else {
        rc = pjsip_udp_transport_start(endpt, NULL, NULL, 1, &udp);
        if (rc != PJ_SUCCESS) {
            app_perror("   error creating UDP transport", rc);
            rc = -2;
            goto on_return;
        }

        port = pj_sockaddr_get_port(&udp->local_addr);
    }

    /* Register registrar module */
    rc = pjsip_endpt_register_module(endpt, &registrar.mod);
    if (rc != PJ_SUCCESS) {
        app_perror("   error registering module", rc);
        rc = -3;
        goto on_return;
    }

    /* Register send module */
    rc = pjsip_endpt_register_module(endpt, &send_mod.mod);
    if (rc != PJ_SUCCESS) {
        app_perror("   error registering module", rc);
        rc = -3;
        goto on_return;
    }

    pj_ansi_snprintf(registrar_uri_buf, sizeof(registrar_uri_buf),
                    "sip:127.0.0.1:%d", (int)port);
    registrar_uri = pj_str(registrar_uri_buf);

    for (i=0; i<PJ_ARRAY_SIZE(test_rec); ++i) {
        struct test_rec *t = &test_rec[i];
        unsigned j, x;
        pj_str_t reg_uri;
        pj_str_t contacts[8];

        /* Fill in the registrar address if it's not specified */
        if (t->alt_registrar == NULL) {
            reg_uri = registrar_uri;
        } else {
            reg_uri = pj_str(t->alt_registrar);
        }

        /* Build contact pj_str_t's */
        for (j=0; j<t->contact_cnt; ++j) {
            contacts[j] = pj_str(t->contacts[j]);
        }

        /* Normalize more_contacts field */
        if (t->server_cfg.more_contacts.ptr)
            t->server_cfg.more_contacts.slen = strlen(t->server_cfg.more_contacts.ptr);

        /* Do tests with three combinations:
         *  - check_contact on/off
         *  - add_xuid_param on/off
         *  - destroy_on_callback on/off
         */
        for (x=1; x<=2; ++x) {
            unsigned y;

            if ((t->check_contact & x) == 0)
                continue;

            pjsip_cfg()->regc.check_contact = (x-1);

            for (y=1; y<=2; ++y) {
                unsigned z;

                if ((t->add_xuid_param & y) == 0)
                    continue;

                pjsip_cfg()->regc.add_xuid_param = (y-1);

                for (z=0; z<=1; ++z) {
                    char new_title[200];

                    t->client_cfg.destroy_on_cb = z;

                    pj_ansi_snprintf(new_title, sizeof(new_title),
                            "%s [check=%d, xuid=%d, destroy=%d]", 
                            t->title, pjsip_cfg()->regc.check_contact,
                            pjsip_cfg()->regc.add_xuid_param, z);
                    rc = do_test(new_title, &t->server_cfg, &t->client_cfg, 
                                 &reg_uri, t->contact_cnt, contacts, 
                                 t->expires, PJ_FALSE, NULL);
                    if (rc != 0)
                        goto on_return;
                }

            }
        }

        /* Sleep between test groups to avoid using up too many
         * active transactions.
         */
        pj_thread_sleep(1000);
    }

    /* keep-alive test */
    rc = keep_alive_test(&registrar_uri);
    if (rc != 0)
        goto on_return;

    /* Send error on refresh without destroy on callback */
    rc = refresh_error(&registrar_uri, PJ_FALSE);
    if (rc != 0)
        goto on_return;

    /* Send error on refresh, destroy on callback */
    rc = refresh_error(&registrar_uri, PJ_TRUE);
    if (rc != 0)
        goto on_return;

    /* Updating contact */
    rc = update_test(&registrar_uri);
    if (rc != 0)
        goto on_return;

    /* Send error during auth, don't destroy on callback */
    rc = auth_send_error(&registrar_uri, PJ_FALSE);
    if (rc != 0)
        goto on_return;

    /* Send error during auth, destroy on callback */
    rc = auth_send_error(&registrar_uri, PJ_FALSE);
    if (rc != 0)
        goto on_return;

on_return:
    if (registrar.mod.id != -1) {
        pjsip_endpt_unregister_module(endpt, &registrar.mod);
    }
    if (send_mod.mod.id != -1) {
        pjsip_endpt_unregister_module(endpt, &send_mod.mod);
    }
    if (udp) {
        pjsip_transport_dec_ref(udp);
    }
    return rc;
}


