/* 
 * 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.h>
#include <pjlib.h>

#define POOL_SIZE       8000
#if defined(PJ_DEBUG) && PJ_DEBUG!=0
#   define LOOP         10000
#else
#   define LOOP         100000
#endif
#define AVERAGE_MSG_LEN 800
#define THIS_FILE       "msg_test.c"

static pjsip_msg *create_msg0(pj_pool_t *pool);
static pjsip_msg *create_msg1(pj_pool_t *pool);

#define STATUS_PARTIAL          1
#define STATUS_SYNTAX_ERROR     2

#define FLAG_DETECT_ONLY        1
#define FLAG_PARSE_ONLY         4
#define FLAG_PRINT_ONLY         8

struct test_msg
{
    char         msg[1024];
    pjsip_msg *(*creator)(pj_pool_t *pool);
    pj_size_t    len;
    int          expected_status;
} test_array[] = 
{
{
    /* 'Normal' message with all headers. */
    "INVITE sip:user@foo SIP/2.0\n"
    "from: Hi I'm Joe <sip:joe.user@bar.otherdomain.com>;tag=123457890123456\r"
    "To: Fellow User <sip:user@foo.bar.domain.com>\r\n"
    "Call-ID: 12345678901234567890@bar\r\n"
    "Content-Length: 0\r\n"
    "CSeq: 123456 INVITE\n"
    "Contact: <sip:joe@bar> ; q=0.5;expires=3600,sip:user@host;q=0.500\r"
    "  ,sip:user2@host2\n"
    "Content-Type: text/html ; charset=ISO-8859-4\r"
    "Route: <sip:bigbox3.site3.atlanta.com;lr>,\r\n"
    "  <sip:server10.biloxi.com;lr>\r"
    "Record-Route: <sip:server10.biloxi.com>,\r\n" /* multiple routes+folding*/
    "  <sip:bigbox3.site3.atlanta.com;lr>\n"
    "v: SIP/2.0/SCTP bigbox3.site3.atlanta.com;branch=z9hG4bK77ef4c230\n"
    "Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bKnashds8\n" /* folding. */
    " ;received=192.0.2.1\r\n"
    "Via: SIP/2.0/UDP 10.2.1.1, SIP/2.0/TCP 192.168.1.1\n"
    "Organization: \r"
    "Max-Forwards: 70\n"
    "X-Header: \r\n"        /* empty header */
    "P-Associated-URI:\r\n" /* empty header without space */
    "\r\n",
    &create_msg0,
    0,
    PJ_SUCCESS
},
{
    /* Typical response message. */
    "SIP/2.0 200 OK\r\n"
    "Via: SIP/2.0/SCTP server10.biloxi.com;branch=z9hG4bKnashds8;rport;received=192.0.2.1\r\n"
    "Via: SIP/2.0/UDP bigbox3.site3.atlanta.com;branch=z9hG4bK77ef4c2312983.1;received=192.0.2.2\r\n"
    "Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK776asdhds ;received=192.0.2.3\r\n"
    "Route: <sip:proxy.sipprovider.com>\r\n"
    "Route: <sip:proxy.supersip.com:5060>\r\n"
    "Max-Forwards: 70\r\n"
    "To: Bob <sip:bob@biloxi.com>;tag=a6c85cf\r\n"
    "From: Alice <sip:alice@atlanta.com>;tag=1928301774\r\n"
    "Call-ID: a84b4c76e66710@pc33.atlanta.com\r\n"
    "CSeq: 314159 INVITE\r\n"
    "Contact: <sips:bob@192.0.2.4>\r\n"
    "Content-Type: application/sdp\r\n"
    "Content-Length: 150\r\n"
    "\r\n"
    "v=0\r\n"
    "o=alice 53655765 2353687637 IN IP4 pc33.atlanta.com\r\n"
    "s=-\r\n"
    "t=0 0\r\n"
    "c=IN IP4 pc33.atlanta.com\r\n"
    "m=audio 3456 RTP/AVP 0 1 3 99\r\n"
    "a=rtpmap:0 PCMU/8000\r\n",
    &create_msg1,
    0,
    PJ_SUCCESS
},
{
    /* Torture message from RFC 4475
     * 3.1.1.1 A short tortuous INVITE
     */
    "INVITE sip:vivekg@chair-dnrc.example.com;unknownparam SIP/2.0\n"
    "TO :\n"
    " sip:vivekg@chair-dnrc.example.com ;   tag    = 1918181833n\n"
    "from   : \"J Rosenberg \\\\\\\"\"       <sip:jdrosen@example.com>\n"
    "  ;\n"
    "  tag = 98asjd8\n"
    "MaX-fOrWaRdS: 0068\n"
    "Call-ID: wsinv.ndaksdj@192.0.2.1\n"
    "Content-Length   : 150\n"
    "cseq: 0009\n"
    "  INVITE\n"
    "Via  : SIP  /   2.0\n"
    " /UDP\n"
    "    192.0.2.2;rport;branch=390skdjuw\n"
    "s :\n"
    "NewFangledHeader:   newfangled value\n"
    " continued newfangled value\n"
    "UnknownHeaderWithUnusualValue: ;;,,;;,;\n"
    "Content-Type: application/sdp\n"
    "Route:\n"
    " <sip:services.example.com;lr;unknownwith=value;unknown-no-value>\n"
    "v:  SIP  / 2.0  / TCP     spindle.example.com   ;\n"
    "  branch  =   z9hG4bK9ikj8  ,\n"
    " SIP  /    2.0   / UDP  192.168.255.111   ; branch=\n"
    " z9hG4bK30239\n"
    "m:\"Quoted string \\\"\\\"\" <sip:jdrosen@example.com> ; newparam =\n"
    "      newvalue ;\n"
    "  secondparam ; q = 0.33\r\n"
    "\r\n"
    "v=0\r\n"
    "o=mhandley 29739 7272939 IN IP4 192.0.2.3\r\n"
    "s=-\r\n"
    "c=IN IP4 192.0.2.4\r\n"
    "t=0 0\r\n"
    "m=audio 49217 RTP/AVP 0 12\r\n"
    "m=video 3227 RTP/AVP 31\r\n"
    "a=rtpmap:31 LPC\r\n",
    NULL,
    0,
    PJ_SUCCESS
},
{
    /* Torture message from RFC 4475
     * 3.1.1.2 Wide Range of Valid Characters
     */
    "!interesting-Method0123456789_*+`.%indeed'~ sip:1_unusual.URI~(to-be!sure)&isn't+it$/crazy?,/;;*:&it+has=1,weird!*pas$wo~d_too.(doesn't-it)@example.com SIP/2.0\n"
    "Via: SIP/2.0/UDP host1.example.com;rport;branch=z9hG4bK-.!%66*_+`'~\n"
    "To: \"BEL:\\\x07 NUL:\\\x00 DEL:\\\x7F\" <sip:1_unusual.URI~(to-be!sure)&isn't+it$/crazy?,/;;*@example.com>\n"
    "From: token1~` token2'+_ token3*%!.- <sip:mundane@example.com> ;fromParam''~+*_!.-%=\"\xD1\x80\xD0\xB0\xD0\xB1\xD0\xBE\xD1\x82\xD0\xB0\xD1\x8E\xD1\x89\xD0\xB8\xD0\xB9\";tag=_token~1'+`*%!-.\n"
    "Call-ID: intmeth.word%ZK-!.*_+'@word`~)(><:\\/\"][?}{\n"
    "CSeq: 139122385 !interesting-Method0123456789_*+`.%indeed'~\n"
    "Max-Forwards: 255\n"
    "extensionHeader-!.%*+_`'~: \xEF\xBB\xBF\xE5\xA4\xA7\xE5\x81\x9C\xE9\x9B\xBB\n"
    "Content-Length: 0\r\n\r\n",
    NULL,
    641,
    PJ_SUCCESS
},
{
    /* Torture message from RFC 4475
     * 3.1.1.3 Valid Use of the % Escaping Mechanism
     */
    "INVITE sip:sips%3Auser%40example.com@example.net SIP/2.0\n"
    "To: sip:%75se%72@example.com\n"
    "From: <sip:I%20have%20spaces@example.net>;tag=1234\n"
    "Max-Forwards: 87\n"
    "i: esc01.239409asdfakjkn23onasd0-3234\n"
    "CSeq: 234234 INVITE\n"
    "Via: SIP/2.0/UDP host5.example.net;rport;branch=z9hG4bKkdjuw\n"
    "C: application/sdp\n"
    "Contact:\n"
    "  <sip:cal%6Cer@192.168.0.2:5060;%6C%72;n%61me=v%61lue%25%34%31>\n"
    "Content-Length: 150\r\n"
    "\r\n"
    "v=0\r\n"
    "o=mhandley 29739 7272939 IN IP4 192.0.2.1\r\n"
    "s=-\r\n"
    "c=IN IP4 192.0.2.1\r\n"
    "t=0 0\r\n"
    "m=audio 49217 RTP/AVP 0 12\r\n"
    "m=video 3227 RTP/AVP 31\r\n"
    "a=rtpmap:31 LPC\r\n",
    NULL,
    0,
    PJ_SUCCESS
},
{
    /* Torture message from RFC 4475
     * 3.1.1.4 Escaped Nulls in URIs
     */
    "REGISTER sip:example.com SIP/2.0\r\n"
    "To: sip:null-%00-null@example.com\r\n"
    "From: sip:null-%00-null@example.com;tag=839923423\r\n"
    "Max-Forwards: 70\r\n"
    "Call-ID: escnull.39203ndfvkjdasfkq3w4otrq0adsfdfnavd\r\n"
    "CSeq: 14398234 REGISTER\r\n"
    "Via: SIP/2.0/UDP host5.example.com;rport;branch=z9hG4bKkdjuw\r\n"
    "Contact: <sip:%00@host5.example.com>\r\n"
    "Contact: <sip:%00%00@host5.example.com>\r\n"
    "L:0\r\n"
    "\r\n",
    NULL,
    0,
    PJ_SUCCESS
},
{
    /* Torture message from RFC 4475
     * 3.1.1.5 Use of % When It Is Not an Escape
     */
    "RE%47IST%45R sip:registrar.example.com SIP/2.0\r\n"
    "To: \"%Z%45\" <sip:resource@example.com>\r\n"
    "From: \"%Z%45\" <sip:resource@example.com>;tag=f232jadfj23\r\n"
    "Call-ID: esc02.asdfnqwo34rq23i34jrjasdcnl23nrlknsdf\r\n"
    "Via: SIP/2.0/TCP host.example.com;rport;branch=z9hG4bK209%fzsnel234\r\n"
    "CSeq: 29344 RE%47IST%45R\r\n"
    "Max-Forwards: 70\r\n"
    "Contact: <sip:alias1@host1.example.com>\r\n"
    "C%6Fntact: <sip:alias2@host2.example.com>\r\n"
    "Contact: <sip:alias3@host3.example.com>\r\n"
    "l: 0\r\n"
    "\r\n",
    NULL,
    0,
    PJ_SUCCESS
}
};

static struct
{
    int flag;
    pj_highprec_t detect_len, parse_len, print_len;
    pj_timestamp  detect_time, parse_time, print_time;
} var;

static pj_status_t test_entry( pj_pool_t *pool, struct test_msg *entry )
{
    pjsip_msg *parsed_msg, *ref_msg = NULL;
    static pjsip_msg *print_msg;
    pj_status_t status = PJ_SUCCESS;
    pj_ssize_t len;
    pj_str_t str1, str2;
    pjsip_hdr *hdr1, *hdr2;
    pj_timestamp t1, t2;
    pjsip_parser_err_report err_list;
    pj_size_t msg_size;
    char msgbuf1[PJSIP_MAX_PKT_LEN];
    char msgbuf2[PJSIP_MAX_PKT_LEN];
    enum { BUFLEN = 512 };

    if (entry->len==0)
        entry->len = pj_ansi_strlen(entry->msg);

    if (var.flag & FLAG_PARSE_ONLY)
        goto parse_msg;

    if (var.flag & FLAG_PRINT_ONLY) {
        if (print_msg == NULL)
            print_msg = entry->creator(pool);
        goto print_msg;
    }

    /* Detect message. */
    var.detect_len = var.detect_len + entry->len;
    pj_get_timestamp(&t1);
    status = pjsip_find_msg(entry->msg, entry->len, PJ_FALSE, &msg_size);
    if (status != PJ_SUCCESS) {
        if (status!=PJSIP_EPARTIALMSG || 
            entry->expected_status!=STATUS_PARTIAL)
        {
            app_perror("   error: unable to detect message", status);
            return -5;
        }
    }
    if (msg_size != entry->len) {
        PJ_LOG(3,(THIS_FILE, "   error: size mismatch"));
        return -6;
    }
    pj_get_timestamp(&t2);
    pj_sub_timestamp(&t2, &t1);
    pj_add_timestamp(&var.detect_time, &t2);

    if (var.flag & FLAG_DETECT_ONLY)
        return PJ_SUCCESS;
    
    /* Parse message. */
parse_msg:
    var.parse_len = var.parse_len + entry->len;
    pj_get_timestamp(&t1);
    pj_list_init(&err_list);
    parsed_msg = pjsip_parse_msg(pool, entry->msg, entry->len, &err_list);
    if (parsed_msg == NULL) {
        if (entry->expected_status != STATUS_SYNTAX_ERROR) {
            status = -10;
            if (err_list.next != &err_list) {
                PJ_LOG(3,(THIS_FILE, "   Syntax error in line %d col %d",
                              err_list.next->line, err_list.next->col));
            }
            goto on_return;
        }
        status = -11;
        goto on_return;
    }
    pj_get_timestamp(&t2);
    pj_sub_timestamp(&t2, &t1);
    pj_add_timestamp(&var.parse_time, &t2);

    if ((var.flag & FLAG_PARSE_ONLY) || entry->creator==NULL)
        return PJ_SUCCESS;

    /* Create reference message. */
    ref_msg = entry->creator(pool);
    if (ref_msg == NULL) {
        status = -12;
        goto on_return;
    }

    /* Create buffer for comparison. */
    str1.ptr = (char*)pj_pool_alloc(pool, BUFLEN);
    str2.ptr = (char*)pj_pool_alloc(pool, BUFLEN);

    /* Compare message type. */
    if (parsed_msg->type != ref_msg->type) {
        status = -20;
        goto on_return;
    }

    /* Compare request or status line. */
    if (parsed_msg->type == PJSIP_REQUEST_MSG) {
        pjsip_method *m1 = &parsed_msg->line.req.method;
        pjsip_method *m2 = &ref_msg->line.req.method;

        if (pjsip_method_cmp(m1, m2) != 0) {
            status = -30;
            goto on_return;
        }
        status = pjsip_uri_cmp(PJSIP_URI_IN_REQ_URI,
                               parsed_msg->line.req.uri, 
                               ref_msg->line.req.uri);
        if (status != PJ_SUCCESS) {
            app_perror("   error: request URI mismatch", status);
            status = -31;
            goto on_return;
        }
    } else {
        if (parsed_msg->line.status.code != ref_msg->line.status.code) {
            PJ_LOG(3,(THIS_FILE, "   error: status code mismatch"));
            status = -32;
            goto on_return;
        }
        if (pj_strcmp(&parsed_msg->line.status.reason, 
                      &ref_msg->line.status.reason) != 0) 
        {
            PJ_LOG(3,(THIS_FILE, "   error: status text mismatch"));
            status = -33;
            goto on_return;
        }
    }

    /* Compare headers. */
    hdr1 = parsed_msg->hdr.next;
    hdr2 = ref_msg->hdr.next;

    while (hdr1 != &parsed_msg->hdr && hdr2 != &ref_msg->hdr) {
        len = pjsip_hdr_print_on(hdr1, str1.ptr, BUFLEN);
        if (len < 0) {
            status = -40;
            goto on_return;
        }
        str1.ptr[len] = '\0';
        str1.slen = len;

        len = pjsip_hdr_print_on(hdr2, str2.ptr, BUFLEN);
        if (len < 0) {
            status = -50;
            goto on_return;
        }
        str2.ptr[len] = '\0';
        str2.slen = len;

        if (pj_strcmp(&str1, &str2) != 0) {
            status = -60;
            PJ_LOG(3,(THIS_FILE, "   error: header string mismatch:\n"
                          "   h1='%s'\n"
                          "   h2='%s'\n",
                          str1.ptr, str2.ptr));
            goto on_return;
        }

        hdr1 = hdr1->next;
        hdr2 = hdr2->next;
    }

    if (hdr1 != &parsed_msg->hdr || hdr2 != &ref_msg->hdr) {
        status = -70;
        goto on_return;
    }

    /* Compare body? */
    if (parsed_msg->body==NULL || ref_msg->body==NULL)
        goto print_msg;

    /* Compare msg body length. */
    if (parsed_msg->body->len != ref_msg->body->len) {
        status = -80;
        goto on_return;
    }

    /* Compare msg body content type. */
    if (pj_strcmp(&parsed_msg->body->content_type.type,
                  &ref_msg->body->content_type.type) != 0) {
        status = -90;
        goto on_return;
    }
    if (pj_strcmp(&parsed_msg->body->content_type.subtype,
                  &ref_msg->body->content_type.subtype) != 0) {
        status = -100;
        goto on_return;
    }

    /* Compare body content. */
    str1.slen = parsed_msg->body->print_body(parsed_msg->body,
                                             msgbuf1, sizeof(msgbuf1));
    if (str1.slen < 1) {
        status = -110;
        goto on_return;
    }
    str1.ptr = msgbuf1;

    str2.slen = ref_msg->body->print_body(ref_msg->body,
                                          msgbuf2, sizeof(msgbuf2));
    if (str2.slen < 1) {
        status = -120;
        goto on_return;
    }
    str2.ptr = msgbuf2;

    if (pj_strcmp(&str1, &str2) != 0) {
        status = -140;
        goto on_return;
    }
    
    /* Print message. */
print_msg:
    var.print_len = var.print_len + entry->len;
    pj_get_timestamp(&t1);
    if (var.flag & FLAG_PRINT_ONLY)
        ref_msg = print_msg;
    len = pjsip_msg_print(ref_msg, msgbuf1, PJSIP_MAX_PKT_LEN);
    if (len < 1) {
        status = -150;
        goto on_return;
    }
    pj_get_timestamp(&t2);
    pj_sub_timestamp(&t2, &t1);
    pj_add_timestamp(&var.print_time, &t2);


    status = PJ_SUCCESS;

on_return:
    return status;
}


static pjsip_msg *create_msg0(pj_pool_t *pool)
{

    pjsip_msg *msg;
    pjsip_name_addr *name_addr;
    pjsip_sip_uri *url;
    pjsip_fromto_hdr *fromto;
    pjsip_cid_hdr *cid;
    pjsip_clen_hdr *clen;
    pjsip_cseq_hdr *cseq;
    pjsip_contact_hdr *contact;
    pjsip_ctype_hdr *ctype;
    pjsip_routing_hdr *routing;
    pjsip_via_hdr *via;
    pjsip_generic_string_hdr *generic;
    pjsip_param *prm;
    pj_str_t str;

    msg = pjsip_msg_create(pool, PJSIP_REQUEST_MSG);

    /* "INVITE sip:user@foo SIP/2.0\n" */
    pjsip_method_set(&msg->line.req.method, PJSIP_INVITE_METHOD);
    url = pjsip_sip_uri_create(pool, 0);
    msg->line.req.uri = (pjsip_uri*)url;
    pj_strdup2(pool, &url->user, "user");
    pj_strdup2(pool, &url->host, "foo");

    /* "From: Hi I'm Joe <sip:joe.user@bar.otherdomain.com>;tag=123457890123456\r" */
    fromto = pjsip_from_hdr_create(pool);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)fromto);
    pj_strdup2(pool, &fromto->tag, "123457890123456");
    name_addr = pjsip_name_addr_create(pool);
    fromto->uri = (pjsip_uri*)name_addr;
    pj_strdup2(pool, &name_addr->display, "Hi I'm Joe");
    url = pjsip_sip_uri_create(pool, 0);
    name_addr->uri = (pjsip_uri*)url;
    pj_strdup2(pool, &url->user, "joe.user");
    pj_strdup2(pool, &url->host, "bar.otherdomain.com");

    /* "To: Fellow User <sip:user@foo.bar.domain.com>\r\n" */
    fromto = pjsip_to_hdr_create(pool);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)fromto);
    name_addr = pjsip_name_addr_create(pool);
    fromto->uri = (pjsip_uri*)name_addr;
    pj_strdup2(pool, &name_addr->display, "Fellow User");
    url = pjsip_sip_uri_create(pool, 0);
    name_addr->uri = (pjsip_uri*)url;
    pj_strdup2(pool, &url->user, "user");
    pj_strdup2(pool, &url->host, "foo.bar.domain.com");

    /* "Call-ID: 12345678901234567890@bar\r\n" */
    cid = pjsip_cid_hdr_create(pool);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)cid);
    pj_strdup2(pool, &cid->id, "12345678901234567890@bar");

    /* "Content-Length: 0\r\n" */
    clen = pjsip_clen_hdr_create(pool);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)clen);
    clen->len = 0;

    /* "CSeq: 123456 INVITE\n" */
    cseq = pjsip_cseq_hdr_create(pool);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)cseq);
    cseq->cseq = 123456;
    pjsip_method_set(&cseq->method, PJSIP_INVITE_METHOD);

    /* "Contact: <sip:joe@bar>;q=0.5;expires=3600*/
    contact = pjsip_contact_hdr_create(pool);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)contact);
    contact->q1000 = 500;
    contact->expires = 3600;
    name_addr = pjsip_name_addr_create(pool);
    contact->uri = (pjsip_uri*)name_addr;
    url = pjsip_sip_uri_create(pool, 0);
    name_addr->uri = (pjsip_uri*)url;
    pj_strdup2(pool, &url->user, "joe");
    pj_strdup2(pool, &url->host, "bar");

    /*, sip:user@host;q=0.500\r" */
    contact = pjsip_contact_hdr_create(pool);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)contact);
    contact->q1000 = 500;
    name_addr = pjsip_name_addr_create(pool);
    contact->uri = (pjsip_uri*)name_addr;
    url = pjsip_sip_uri_create(pool, 0);
    name_addr->uri = (pjsip_uri*)url;
    pj_strdup2(pool, &url->user, "user");
    pj_strdup2(pool, &url->host, "host");

    /* "  ,sip:user2@host2\n" */
    contact = pjsip_contact_hdr_create(pool);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)contact);
    name_addr = pjsip_name_addr_create(pool);
    contact->uri = (pjsip_uri*)name_addr;
    url = pjsip_sip_uri_create(pool, 0);
    name_addr->uri = (pjsip_uri*)url;
    pj_strdup2(pool, &url->user, "user2");
    pj_strdup2(pool, &url->host, "host2");

    /* "Content-Type: text/html; charset=ISO-8859-4\r" */
    ctype = pjsip_ctype_hdr_create(pool);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)ctype);
    pj_strdup2(pool, &ctype->media.type, "text");
    pj_strdup2(pool, &ctype->media.subtype, "html");
    prm = PJ_POOL_ALLOC_T(pool, pjsip_param);
    prm->name = pj_str("charset");
    prm->value = pj_str("ISO-8859-4");
    pj_list_push_back(&ctype->media.param, prm);

    /* "Route: <sip:bigbox3.site3.atlanta.com;lr>,\r\n" */
    routing = pjsip_route_hdr_create(pool);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)routing);
    url = pjsip_sip_uri_create(pool, 0);
    routing->name_addr.uri = (pjsip_uri*)url;
    pj_strdup2(pool, &url->host, "bigbox3.site3.atlanta.com");
    url->lr_param = 1;

    /* "  <sip:server10.biloxi.com;lr>\r" */
    routing = pjsip_route_hdr_create(pool);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)routing);
    url = pjsip_sip_uri_create(pool, 0);
    routing->name_addr.uri = (pjsip_uri*)url;
    pj_strdup2(pool, &url->host, "server10.biloxi.com");
    url->lr_param = 1;

    /* "Record-Route: <sip:server10.biloxi.com>,\r\n" */
    routing = pjsip_rr_hdr_create(pool);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)routing);
    url = pjsip_sip_uri_create(pool, 0);
    routing->name_addr.uri = (pjsip_uri*)url;
    pj_strdup2(pool, &url->host, "server10.biloxi.com");
    url->lr_param = 0;

    /* "  <sip:bigbox3.site3.atlanta.com;lr>\n" */
    routing = pjsip_rr_hdr_create(pool);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)routing);
    url = pjsip_sip_uri_create(pool, 0);
    routing->name_addr.uri = (pjsip_uri*)url;
    pj_strdup2(pool, &url->host, "bigbox3.site3.atlanta.com");
    url->lr_param = 1;

    /* "Via: SIP/2.0/SCTP bigbox3.site3.atlanta.com;branch=z9hG4bK77ef4c230\n" */
    via = pjsip_via_hdr_create(pool);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)via);
    pj_strdup2(pool, &via->transport, "SCTP");
    pj_strdup2(pool, &via->sent_by.host, "bigbox3.site3.atlanta.com");
    pj_strdup2(pool, &via->branch_param, "z9hG4bK77ef4c230");

    /* "Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bKnashds8\n"
        " ;received=192.0.2.1\r\n" */
    via = pjsip_via_hdr_create(pool);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)via);
    pj_strdup2(pool, &via->transport, "UDP");
    pj_strdup2(pool, &via->sent_by.host, "pc33.atlanta.com");
    pj_strdup2(pool, &via->branch_param, "z9hG4bKnashds8");
    pj_strdup2(pool, &via->recvd_param, "192.0.2.1");


    /* "Via: SIP/2.0/UDP 10.2.1.1, */ 
    via = pjsip_via_hdr_create(pool);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)via);
    pj_strdup2(pool, &via->transport, "UDP");
    pj_strdup2(pool, &via->sent_by.host, "10.2.1.1");
    
    
    /*SIP/2.0/TCP 192.168.1.1\n" */
    via = pjsip_via_hdr_create(pool);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)via);
    pj_strdup2(pool, &via->transport, "TCP");
    pj_strdup2(pool, &via->sent_by.host, "192.168.1.1");

    /* "Organization: \r" */
    str.ptr = "Organization";
    str.slen = 12;
    generic = pjsip_generic_string_hdr_create(pool, &str, NULL);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)generic);
    generic->hvalue.ptr = NULL;
    generic->hvalue.slen = 0;

    /* "Max-Forwards: 70\n" */
    str.ptr = "Max-Forwards";
    str.slen = 12;
    generic = pjsip_generic_string_hdr_create(pool, &str, NULL);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)generic);
    str.ptr = "70";
    str.slen = 2;
    generic->hvalue = str;

    /* "X-Header: \r\n" */
    str.ptr = "X-Header";
    str.slen = 8;
    generic = pjsip_generic_string_hdr_create(pool, &str, NULL);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)generic);
    str.ptr = NULL;
    str.slen = 0;
    generic->hvalue = str;

    /* P-Associated-URI:\r\n */
    str.ptr = "P-Associated-URI";
    str.slen = 16;
    generic = pjsip_generic_string_hdr_create(pool, &str, NULL);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)generic);
    str.ptr = NULL;
    str.slen = 0;
    generic->hvalue = str;

    return msg;
}

static pjsip_msg *create_msg1(pj_pool_t *pool)
{
    pjsip_via_hdr *via;
    pjsip_route_hdr *route;
    pjsip_name_addr *name_addr;
    pjsip_sip_uri *url;
    pjsip_max_fwd_hdr *max_fwd;
    pjsip_to_hdr *to;
    pjsip_from_hdr *from;
    pjsip_contact_hdr *contact;
    pjsip_ctype_hdr *ctype;
    pjsip_cid_hdr *cid;
    pjsip_clen_hdr *clen;
    pjsip_cseq_hdr *cseq;
    pjsip_msg *msg = pjsip_msg_create(pool, PJSIP_RESPONSE_MSG);
    pjsip_msg_body *body;

    //"SIP/2.0 200 OK\r\n"
    msg->line.status.code = 200;
    msg->line.status.reason = pj_str("OK");

    //"Via: SIP/2.0/SCTP server10.biloxi.com;branch=z9hG4bKnashds8;rport;received=192.0.2.1\r\n"
    via = pjsip_via_hdr_create(pool);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)via);
    via->transport = pj_str("SCTP");
    via->sent_by.host = pj_str("server10.biloxi.com");
    via->branch_param = pj_str("z9hG4bKnashds8");
    via->rport_param = 0;
    via->recvd_param = pj_str("192.0.2.1");

    //"Via: SIP/2.0/UDP bigbox3.site3.atlanta.com;branch=z9hG4bK77ef4c2312983.1;received=192.0.2.2\r\n"
    via = pjsip_via_hdr_create(pool);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)via);
    via->transport = pj_str("UDP");
    via->sent_by.host = pj_str("bigbox3.site3.atlanta.com");
    via->branch_param = pj_str("z9hG4bK77ef4c2312983.1");
    via->recvd_param = pj_str("192.0.2.2");

    //"Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bK776asdhds ;received=192.0.2.3\r\n"
    via = pjsip_via_hdr_create(pool);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)via);
    via->transport = pj_str("UDP");
    via->sent_by.host = pj_str("pc33.atlanta.com");
    via->branch_param = pj_str("z9hG4bK776asdhds");
    via->recvd_param = pj_str("192.0.2.3");

    //"Route: <sip:proxy.sipprovider.com>\r\n"
    route = pjsip_route_hdr_create(pool);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)route);
    url = pjsip_sip_uri_create(pool, PJ_FALSE);
    route->name_addr.uri = (pjsip_uri*)url;
    url->host = pj_str("proxy.sipprovider.com");
    
    //"Route: <sip:proxy.supersip.com:5060>\r\n"
    route = pjsip_route_hdr_create(pool);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)route);
    url = pjsip_sip_uri_create(pool, PJ_FALSE);
    route->name_addr.uri = (pjsip_uri*)url;
    url->host = pj_str("proxy.supersip.com");
    url->port = 5060;

    //"Max-Forwards: 70\r\n"
    max_fwd = pjsip_max_fwd_hdr_create(pool, 70);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)max_fwd);

    //"To: Bob <sip:bob@biloxi.com>;tag=a6c85cf\r\n"
    to = pjsip_to_hdr_create(pool);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)to);
    name_addr = pjsip_name_addr_create(pool);
    name_addr->display = pj_str("Bob");
    to->uri = (pjsip_uri*)name_addr;
    url = pjsip_sip_uri_create(pool, PJ_FALSE);
    name_addr->uri = (pjsip_uri*)url;
    url->user = pj_str("bob");
    url->host = pj_str("biloxi.com");
    to->tag = pj_str("a6c85cf");

    //"From: Alice <sip:alice@atlanta.com>;tag=1928301774\r\n"
    from = pjsip_from_hdr_create(pool);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)from);
    name_addr = pjsip_name_addr_create(pool);
    name_addr->display = pj_str("Alice");
    from->uri = (pjsip_uri*)name_addr;
    url = pjsip_sip_uri_create(pool, PJ_FALSE);
    name_addr->uri = (pjsip_uri*)url;
    url->user = pj_str("alice");
    url->host = pj_str("atlanta.com");
    from->tag = pj_str("1928301774");

    //"Call-ID: a84b4c76e66710@pc33.atlanta.com\r\n"
    cid = pjsip_cid_hdr_create(pool);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)cid);
    cid->id = pj_str("a84b4c76e66710@pc33.atlanta.com");

    //"CSeq: 314159 INVITE\r\n"
    cseq = pjsip_cseq_hdr_create(pool);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)cseq);
    cseq->cseq = 314159;
    pjsip_method_set(&cseq->method, PJSIP_INVITE_METHOD);

    //"Contact: <sips:bob@192.0.2.4>\r\n"
    contact = pjsip_contact_hdr_create(pool);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)contact);
    name_addr = pjsip_name_addr_create(pool);
    contact->uri = (pjsip_uri*)name_addr;
    url = pjsip_sip_uri_create(pool, PJ_TRUE);
    name_addr->uri = (pjsip_uri*)url;
    url->user = pj_str("bob");
    url->host = pj_str("192.0.2.4");

    //"Content-Type: application/sdp\r\n"
    ctype = pjsip_ctype_hdr_create(pool);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)ctype);
    ctype->media.type = pj_str("application");
    ctype->media.subtype = pj_str("sdp");

    //"Content-Length: 150\r\n"
    clen = pjsip_clen_hdr_create(pool);
    pjsip_msg_add_hdr(msg, (pjsip_hdr*)clen);
    clen->len = 150;

    // Body
    body = PJ_POOL_ZALLOC_T(pool, pjsip_msg_body);
    msg->body = body;
    body->content_type.type = pj_str("application");
    body->content_type.subtype = pj_str("sdp");
    body->data = (void*)
        "v=0\r\n"
        "o=alice 53655765 2353687637 IN IP4 pc33.atlanta.com\r\n"
        "s=-\r\n"
        "t=0 0\r\n"
        "c=IN IP4 pc33.atlanta.com\r\n"
        "m=audio 3456 RTP/AVP 0 1 3 99\r\n"
        "a=rtpmap:0 PCMU/8000\r\n";
    body->len = (unsigned)pj_ansi_strlen((const char*) body->data);
    body->print_body = &pjsip_print_text_body;

    return msg;
}

/*****************************************************************************/

static pj_status_t simple_test(void)
{
    char stbuf[] = "SIP/2.0 180 Ringing like it never rings before";
    unsigned i;
    pjsip_status_line st_line;
    pj_status_t status;

    PJ_LOG(3,(THIS_FILE, "  simple test.."));
    
    status = pjsip_parse_status_line(stbuf, pj_ansi_strlen(stbuf), &st_line);
    if (status != PJ_SUCCESS)
        return status;

    for (i=0; i<PJ_ARRAY_SIZE(test_array); ++i) {
        pj_pool_t *pool;
        pool = pjsip_endpt_create_pool(endpt, NULL, POOL_SIZE, POOL_SIZE);
        status = test_entry( pool, &test_array[i] );
        pjsip_endpt_release_pool(endpt, pool);

        if (status != PJ_SUCCESS)
            return status;
    }

    return PJ_SUCCESS;
}


#if INCLUDE_BENCHMARKS
static int msg_benchmark(unsigned *p_detect, unsigned *p_parse, 
                         unsigned *p_print)
{
    pj_pool_t *pool;
    int i, loop;
    pj_timestamp zero;
    pj_time_val elapsed;
    pj_highprec_t avg_detect, avg_parse, avg_print, kbytes;
    pj_status_t status = PJ_SUCCESS;

    pj_bzero(&var, sizeof(var));
    zero.u64 = 0;

    for (loop=0; loop<LOOP; ++loop) {
        for (i=0; i<(int)PJ_ARRAY_SIZE(test_array); ++i) {
            pool = pjsip_endpt_create_pool(endpt, NULL, POOL_SIZE, POOL_SIZE);
            status = test_entry( pool, &test_array[i] );
            pjsip_endpt_release_pool(endpt, pool);

            if (status != PJ_SUCCESS)
                return status;
        }
    }

    kbytes = var.detect_len;
    pj_highprec_mod(kbytes, 1000000);
    pj_highprec_div(kbytes, 100000);
    elapsed = pj_elapsed_time(&zero, &var.detect_time);
    avg_detect = pj_elapsed_usec(&zero, &var.detect_time);
    pj_highprec_mul(avg_detect, AVERAGE_MSG_LEN);
    pj_highprec_div(avg_detect, var.detect_len);
    avg_detect = 1000000 / avg_detect;

    PJ_LOG(3,(THIS_FILE, 
              "    %u.%u MB detected in %ld.%03lds (avg=%d msg detection/sec)", 
              (unsigned)(var.detect_len/1000000), (unsigned)kbytes,
              elapsed.sec, elapsed.msec,
              (unsigned)avg_detect));
    *p_detect = (unsigned)avg_detect;

    kbytes = var.parse_len;
    pj_highprec_mod(kbytes, 1000000);
    pj_highprec_div(kbytes, 100000);
    elapsed = pj_elapsed_time(&zero, &var.parse_time);
    avg_parse = pj_elapsed_usec(&zero, &var.parse_time);
    pj_highprec_mul(avg_parse, AVERAGE_MSG_LEN);
    pj_highprec_div(avg_parse, var.parse_len);
    avg_parse = 1000000 / avg_parse;

    PJ_LOG(3,(THIS_FILE, 
              "    %u.%u MB parsed in %ld.%03lds (avg=%d msg parsing/sec)", 
              (unsigned)(var.parse_len/1000000), (unsigned)kbytes,
              elapsed.sec, elapsed.msec,
              (unsigned)avg_parse));
    *p_parse = (unsigned)avg_parse;

    kbytes = var.print_len;
    pj_highprec_mod(kbytes, 1000000);
    pj_highprec_div(kbytes, 100000);
    elapsed = pj_elapsed_time(&zero, &var.print_time);
    avg_print = pj_elapsed_usec(&zero, &var.print_time);
    pj_highprec_mul(avg_print, AVERAGE_MSG_LEN);
    pj_highprec_div(avg_print, var.print_len);
    avg_print = 1000000 / avg_print;

    PJ_LOG(3,(THIS_FILE, 
              "    %u.%u MB printed in %ld.%03lds (avg=%d msg print/sec)", 
              (unsigned)(var.print_len/1000000), (unsigned)kbytes,
              elapsed.sec, elapsed.msec,
              (unsigned)avg_print));

    *p_print = (unsigned)avg_print;
    return status;
}
#endif  /* INCLUDE_BENCHMARKS */

/*****************************************************************************/
/* Test various header parsing and production */
static int hdr_test_success(pjsip_hdr *h);
static int hdr_test_accept0(pjsip_hdr *h);
static int hdr_test_accept1(pjsip_hdr *h);
static int hdr_test_accept2(pjsip_hdr *h);
static int hdr_test_allow0(pjsip_hdr *h);
static int hdr_test_authorization(pjsip_hdr *h);
static int hdr_test_cid(pjsip_hdr *h);
static int hdr_test_contact0(pjsip_hdr *h);
static int hdr_test_contact1(pjsip_hdr *h);
static int hdr_test_contact_q0(pjsip_hdr *h);
static int hdr_test_contact_q1(pjsip_hdr *h);
static int hdr_test_contact_q2(pjsip_hdr *h);
static int hdr_test_contact_q3(pjsip_hdr *h);
static int hdr_test_contact_q4(pjsip_hdr *h);
static int hdr_test_content_length(pjsip_hdr *h);
static int hdr_test_content_type(pjsip_hdr *h);
static int hdr_test_from(pjsip_hdr *h);
static int hdr_test_proxy_authenticate(pjsip_hdr *h);
static int hdr_test_record_route(pjsip_hdr *h);
static int hdr_test_supported(pjsip_hdr *h);
static int hdr_test_supported_with_leading_comma(pjsip_hdr *h);
static int hdr_test_supported_with_trailing_comma(pjsip_hdr *h);
static int hdr_test_supported_with_multiple_commas(pjsip_hdr *h);
static int hdr_test_supported_with_middle_commas(pjsip_hdr *h);
static int hdr_test_to(pjsip_hdr *h);
static int hdr_test_via(pjsip_hdr *h);
static int hdr_test_via_ipv6_1(pjsip_hdr *h);
static int hdr_test_via_ipv6_2(pjsip_hdr *h);
static int hdr_test_via_ipv6_3(pjsip_hdr *h);
static int hdr_test_retry_after1(pjsip_hdr *h);
static int hdr_test_subject_utf(pjsip_hdr *h);


#define GENERIC_PARAM        "p0=a;p1=\"ab:;cd\";p2=ab%3acd;p3"
#define GENERIC_PARAM_PARSED "p0=a;p1=\"ab:;cd\";p2=ab%3acd;p3"
#define PARAM_CHAR           "][/:&+$"
#define SIMPLE_ADDR_SPEC     "sip:host"
#define ADDR_SPEC            SIMPLE_ADDR_SPEC ";"PARAM_CHAR"="PARAM_CHAR ";p1=\";\""
#define NAME_ADDR            "<" ADDR_SPEC ">"

#define HDR_FLAG_PARSE_FAIL 1
#define HDR_FLAG_DONT_PRINT 2

struct hdr_test_t
{
    char *hname;
    char *hshort_name;
    char *hcontent;
    int  (*test)(pjsip_hdr*);
    unsigned flags;
} hdr_test_data[] =
{
    {
        /* Empty Accept */
        "Accept", NULL,
        "",
        &hdr_test_accept0
    },

    {
        /* Overflowing generic string header */
        "Accept", NULL,
        "a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, " \
        "a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, " \
        "a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, " \
        "a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, " \
        "a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, " \
        "a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, " \
        "a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, " \
        "a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a",
        &hdr_test_success,
        HDR_FLAG_PARSE_FAIL
    },

    {
        /* Normal Accept */
        "Accept", NULL,
        "application/*, text/plain",
        &hdr_test_accept1
    },

    {
        /* Accept with params */
        "Accept", NULL,
        "application/*;p1=v1, text/plain",
        &hdr_test_accept2
    },

    {
        /* Empty Allow */
        "Allow", NULL,
        "",
        &hdr_test_allow0,
    },

    {
        /* Authorization, testing which params should be quoted */
        "Authorization", NULL,
        "Digest username=\"username\", realm=\"realm\", nonce=\"nonce\", "  \
                "uri=\"sip:domain\", response=\"RESPONSE\", algorithm=MD5, "    \
                "cnonce=\"CNONCE\", opaque=\"OPAQUE\", qop=auth, nc=00000001",
        &hdr_test_authorization
    },

    {
        /* Call ID */
        "Call-ID", "i",
        "-.!%*_+`'~()<>:\\\"/[]?{}",
        &hdr_test_cid,
    },

    {
        /* Parameter belong to hparam */
        "Contact", "m",
        SIMPLE_ADDR_SPEC ";p1=v1",
        &hdr_test_contact0,
        HDR_FLAG_DONT_PRINT
    },

    {
        /* generic-param in Contact header */
        "Contact", "m",
        NAME_ADDR ";" GENERIC_PARAM,
        &hdr_test_contact1
    },

    {
        /* q=0 parameter in Contact header */
        "Contact", "m",
        NAME_ADDR ";q=0",
        &hdr_test_contact_q0,
        HDR_FLAG_DONT_PRINT
    },

    {
        /* q=0.5 parameter in Contact header */
        "Contact", "m",
        NAME_ADDR ";q=0.5",
        &hdr_test_contact_q1
    },

    {
        /* q=1 parameter in Contact header */
        "Contact", "m",
        NAME_ADDR ";q=1",
        &hdr_test_contact_q2
    },

    {
        /* q=1.0 parameter in Contact header */
        "Contact", "m",
        NAME_ADDR ";q=1.0",
        &hdr_test_contact_q3,
        HDR_FLAG_DONT_PRINT
    },

    {
        /* q=1.1 parameter in Contact header */
        "Contact", "m",
        NAME_ADDR ";q=1.15",
        &hdr_test_contact_q4
    },

    {
        /* Content-Length */
        "Content-Length", "l",
        "10",
        &hdr_test_content_length
    },

    {
        /* Content-Type, with generic-param */
        "Content-Type", "c",
        "application/sdp" ";" GENERIC_PARAM,
        &hdr_test_content_type,
        HDR_FLAG_DONT_PRINT
    },

    {
        /* From, testing parameters and generic-param */
        "From", "f",
        NAME_ADDR ";" GENERIC_PARAM,
        &hdr_test_from
    },

    {
        /* Proxy-Authenticate, testing which params should be quoted */
        "Proxy-Authenticate", NULL,
        "Digest realm=\"realm\",domain=\"sip:domain\",nonce=\"nonce\","  \
                "opaque=\"opaque\",stale=true,algorithm=MD5,qop=\"auth\"",
        &hdr_test_proxy_authenticate
    },

    {
        /* Record-Route, param belong to header */
        "Record-Route", NULL,
        NAME_ADDR ";" GENERIC_PARAM,
        &hdr_test_record_route
    },

    {
        /* Empty Supported */
        "Supported", "k",
        "",
        &hdr_test_supported
    },

    {
        /* Supported with leading comma (buggy MizuDroid format) */
        "Supported", "k",
        ",outbound",
        &hdr_test_supported_with_leading_comma,
        HDR_FLAG_DONT_PRINT
    },

    {
        /* Supported with trailing comma */
        "Supported", "k",
        "outbound,",
        &hdr_test_supported_with_trailing_comma,
        HDR_FLAG_DONT_PRINT
    },

    {
        /* Supported with multiple consecutive commas at beginning */
        "Supported", "k",
        ",,,outbound,path",
        &hdr_test_supported_with_multiple_commas,
        HDR_FLAG_DONT_PRINT
    },

    {
        /* Supported with multiple consecutive commas in middle */
        "Supported", "k",
        "outbound,,,path",
        &hdr_test_supported_with_middle_commas,
        HDR_FLAG_DONT_PRINT
    },

    {
        /* To */
        "To", "t",
        NAME_ADDR ";" GENERIC_PARAM,
        &hdr_test_to
    },

    {
        /* Via */
        "Via", "v",
        "SIP/2.0/XYZ host" ";" GENERIC_PARAM,
        &hdr_test_via
    },

    {
        /* Via with IPv6 */
        "Via", "v",
        "SIP/2.0/UDP [::1]",
        &hdr_test_via_ipv6_1
    },

    {
        /* Via with IPv6 */
        "Via", "v",
        "SIP/2.0/UDP [::1]:5061",
        &hdr_test_via_ipv6_2
    },

    {
        /* Via with IPv6 */
        "Via", "v",
        "SIP/2.0/UDP [::1];rport=5061;received=::2",
        &hdr_test_via_ipv6_3
    },

    {
        /* Retry-After header with comment */
        "Retry-After", NULL,
        "10(Already Pending Register)",
        &hdr_test_retry_after1
    },

    {
        /* Non-ASCII UTF-8 characters in Subject */
        "Subject", NULL,
        "\xC0\x81",
        &hdr_test_subject_utf
    }
};

static int hdr_test_success(pjsip_hdr *h)
{
    PJ_UNUSED_ARG(h);
    return 0;
}

/* "" */
static int hdr_test_accept0(pjsip_hdr *h)
{
    pjsip_accept_hdr *hdr = (pjsip_accept_hdr*)h;

    if (h->type != PJSIP_H_ACCEPT)
        return -1010;

    if (hdr->count != 0)
        return -1020;

    return 0;
}

/* "application/ *, text/plain\r\n" */
static int hdr_test_accept1(pjsip_hdr *h)
{
    pjsip_accept_hdr *hdr = (pjsip_accept_hdr*)h;

    if (h->type != PJSIP_H_ACCEPT)
        return -1110;

    if (hdr->count != 2)
        return -1120;

    if (pj_strcmp2(&hdr->values[0], "application/*"))
        return -1130;

    if (pj_strcmp2(&hdr->values[1], "text/plain"))
        return -1140;

    return 0;
}

/* "application/ *;p1=v1, text/plain\r\n" */
static int hdr_test_accept2(pjsip_hdr *h)
{
    pjsip_accept_hdr *hdr = (pjsip_accept_hdr*)h;

    if (h->type != PJSIP_H_ACCEPT)
        return -1210;

    if (hdr->count != 2)
        return -1220;

    if (pj_strcmp2(&hdr->values[0], "application/*;p1=v1"))
        return -1230;

    if (pj_strcmp2(&hdr->values[1], "text/plain"))
        return -1240;

    return 0;
}

/* "" */
static int hdr_test_allow0(pjsip_hdr *h)
{
    pjsip_allow_hdr *hdr = (pjsip_allow_hdr*)h;

    if (h->type != PJSIP_H_ALLOW)
        return -1310;

    if (hdr->count != 0)
        return -1320;

    return 0;

}


/*
        "Digest username=\"username\", realm=\"realm\", nonce=\"nonce\", "  \
                "uri=\"sip:domain\", response=\"RESPONSE\", algorithm=MD5, "    \
                "cnonce=\"CNONCE\", opaque=\"OPAQUE\", qop=auth, nc=00000001",
 */
static int hdr_test_authorization(pjsip_hdr *h)
{
    pjsip_authorization_hdr *hdr = (pjsip_authorization_hdr*)h;

    if (h->type != PJSIP_H_AUTHORIZATION)
        return -1410;

    if (pj_strcmp2(&hdr->scheme, "Digest"))
        return -1420;

    if (pj_strcmp2(&hdr->credential.digest.username, "username"))
        return -1421;

    if (pj_strcmp2(&hdr->credential.digest.realm, "realm"))
        return -1422;

    if (pj_strcmp2(&hdr->credential.digest.nonce, "nonce"))
        return -1423;

    if (pj_strcmp2(&hdr->credential.digest.uri, "sip:domain"))
        return -1424;

    if (pj_strcmp2(&hdr->credential.digest.response, "RESPONSE"))
        return -1425;

    if (pj_strcmp2(&hdr->credential.digest.algorithm, "MD5"))
        return -1426;

    if (pj_strcmp2(&hdr->credential.digest.cnonce, "CNONCE"))
        return -1427;

    if (pj_strcmp2(&hdr->credential.digest.opaque, "OPAQUE"))
        return -1428;

    if (pj_strcmp2(&hdr->credential.digest.qop, "auth"))
        return -1429;

    if (pj_strcmp2(&hdr->credential.digest.nc, "00000001"))
        return -1430;

    return 0;
}


/*
    "-.!%*_+`'~()<>:\\\"/[]?{}\r\n"
 */
static int hdr_test_cid(pjsip_hdr *h)
{
    pjsip_cid_hdr *hdr = (pjsip_cid_hdr*)h;

    if (h->type != PJSIP_H_CALL_ID)
        return -1510;

    if (pj_strcmp2(&hdr->id, "-.!%*_+`'~()<>:\\\"/[]?{}"))
        return -1520;

    return 0;
}

/*
 #define SIMPLE_ADDR_SPEC    "sip:host"
 */
static int test_simple_addr_spec(pjsip_uri *uri)
{
    pjsip_sip_uri *sip_uri = (pjsip_sip_uri *)pjsip_uri_get_uri(uri);

    if (!PJSIP_URI_SCHEME_IS_SIP(uri))
        return -900;

    if (pj_strcmp2(&sip_uri->host, "host"))
        return -910;

    if (sip_uri->port != 0)
        return -920;

    return 0;
}

/* 
#define PARAM_CHAR          "][/:&+$"
#define SIMPLE_ADDR_SPEC    "sip:host"
#define ADDR_SPEC            SIMPLE_ADDR_SPEC ";"PARAM_CHAR"="PARAM_CHAR ";p1=\";\""
#define NAME_ADDR           "<" ADDR_SPEC ">"
 */
static int nameaddr_test(void *uri)
{
    pjsip_sip_uri *sip_uri=(pjsip_sip_uri *)pjsip_uri_get_uri((pjsip_uri*)uri);
    pjsip_param *param;
    int rc;

    if (!PJSIP_URI_SCHEME_IS_SIP(uri))
        return -930;

    rc = test_simple_addr_spec((pjsip_uri*)sip_uri);
    if (rc != 0)
        return rc;

    if (pj_list_size(&sip_uri->other_param) != 2)
        return -940;

    param = sip_uri->other_param.next;

    if (pj_strcmp2(&param->name, PARAM_CHAR))
        return -942;

    if (pj_strcmp2(&param->value, PARAM_CHAR))
        return -943;

    param = param->next;
    if (pj_strcmp2(&param->name, "p1"))
        return -942;
    if (pj_strcmp2(&param->value, "\";\""))
        return -943;

    return 0;
}

/*
#define GENERIC_PARAM  "p0=a;p1=\"ab:;cd\";p2=ab%3acd;p3"
 */
static int generic_param_test(pjsip_param *param_head)
{
    pjsip_param *param;

    if (pj_list_size(param_head) != 4)
        return -950;

    param = param_head->next;

    if (pj_strcmp2(&param->name, "p0"))
        return -952;
    if (pj_strcmp2(&param->value, "a"))
        return -953;

    param = param->next;
    if (pj_strcmp2(&param->name, "p1"))
        return -954;
    if (pj_strcmp2(&param->value, "\"ab:;cd\""))
        return -955;

    param = param->next;
    if (pj_strcmp2(&param->name, "p2"))
        return -956;
    if (pj_strcmp2(&param->value, "ab%3acd"))
        return -957;

    param = param->next;
    if (pj_strcmp2(&param->name, "p3"))
        return -958;
    if (pj_strcmp2(&param->value, ""))
        return -959;

    return 0;
}



/*
    SIMPLE_ADDR_SPEC ";p1=v1\r\n"
 */
static int hdr_test_contact0(pjsip_hdr *h)
{
    pjsip_contact_hdr *hdr = (pjsip_contact_hdr*)h;
    pjsip_param *param;
    int rc;

    if (h->type != PJSIP_H_CONTACT)
        return -1610;

    rc = test_simple_addr_spec(hdr->uri);
    if (rc != 0)
        return rc;

    if (pj_list_size(&hdr->other_param) != 1)
        return -1620;

    param = hdr->other_param.next;

    if (pj_strcmp2(&param->name, "p1"))
        return -1630;

    if (pj_strcmp2(&param->value, "v1"))
        return -1640;

    return 0;
}

/*
    NAME_ADDR GENERIC_PARAM "\r\n",    
 */
static int hdr_test_contact1(pjsip_hdr *h)
{
    pjsip_contact_hdr *hdr = (pjsip_contact_hdr*)h;
    int rc;

    if (h->type != PJSIP_H_CONTACT)
        return -1710;

    rc = nameaddr_test(hdr->uri);
    if (rc != 0)
        return rc;

    rc = generic_param_test(&hdr->other_param);
    if (rc != 0)
        return rc;

    return 0;
}

/*
    NAME_ADDR ";q=0"
 */
static int hdr_test_contact_q0(pjsip_hdr *h)
{
    pjsip_contact_hdr *hdr = (pjsip_contact_hdr*)h;
    int rc;

    if (h->type != PJSIP_H_CONTACT)
        return -1710;

    rc = nameaddr_test(hdr->uri);
    if (rc != 0)
        return rc;

    if (hdr->q1000 != 0)
        return -1711;

    return 0;
}

/*
    NAME_ADDR ";q=0.5"
 */
static int hdr_test_contact_q1(pjsip_hdr *h)
{
    pjsip_contact_hdr *hdr = (pjsip_contact_hdr*)h;
    int rc;

    if (h->type != PJSIP_H_CONTACT)
        return -1710;

    rc = nameaddr_test(hdr->uri);
    if (rc != 0)
        return rc;

    if (hdr->q1000 != 500)
        return -1712;

    return 0;
}

/*
    NAME_ADDR ";q=1"
 */
static int hdr_test_contact_q2(pjsip_hdr *h)
{
    pjsip_contact_hdr *hdr = (pjsip_contact_hdr*)h;
    int rc;

    if (h->type != PJSIP_H_CONTACT)
        return -1710;

    rc = nameaddr_test(hdr->uri);
    if (rc != 0)
        return rc;

    if (hdr->q1000 != 1000)
        return -1713;

    return 0;
}

/*
    NAME_ADDR ";q=1.0"
 */
static int hdr_test_contact_q3(pjsip_hdr *h)
{
    pjsip_contact_hdr *hdr = (pjsip_contact_hdr*)h;
    int rc;

    if (h->type != PJSIP_H_CONTACT)
        return -1710;

    rc = nameaddr_test(hdr->uri);
    if (rc != 0)
        return rc;

    if (hdr->q1000 != 1000)
        return -1714;

    return 0;
}

/*
    NAME_ADDR ";q=1.15"
 */
static int hdr_test_contact_q4(pjsip_hdr *h)
{
    pjsip_contact_hdr *hdr = (pjsip_contact_hdr*)h;
    int rc;

    if (h->type != PJSIP_H_CONTACT)
        return -1710;

    rc = nameaddr_test(hdr->uri);
    if (rc != 0)
        return rc;

    if (hdr->q1000 != 1150)
        return -1715;

    return 0;
}

/*
    "10"
 */
static int hdr_test_content_length(pjsip_hdr *h)
{
    pjsip_clen_hdr *hdr = (pjsip_clen_hdr*)h;

    if (h->type != PJSIP_H_CONTENT_LENGTH)
        return -1810;

    if (hdr->len != 10)
        return -1820;

    return 0;
}

/*
    "application/sdp" GENERIC_PARAM,
 */
static int hdr_test_content_type(pjsip_hdr *h)
{
    pjsip_ctype_hdr *hdr = (pjsip_ctype_hdr*)h;
    const pjsip_param *prm;

    if (h->type != PJSIP_H_CONTENT_TYPE)
        return -1910;

    if (pj_strcmp2(&hdr->media.type, "application"))
        return -1920;

    if (pj_strcmp2(&hdr->media.subtype, "sdp"))
        return -1930;

    /* Currently, if the media parameter contains escaped characters,
     * pjsip will print the parameter unescaped.
     */
    prm = hdr->media.param.next;
    if (prm == &hdr->media.param) return -1940;
    if (pj_strcmp2(&prm->name, "p0")) return -1941;
    if (pj_strcmp2(&prm->value, "a")) return -1942;

    prm = prm->next;
    if (prm == &hdr->media.param) return -1950;
    if (pj_strcmp2(&prm->name, "p1")) { PJ_LOG(3,("", "%.*s", (int)prm->name.slen, prm->name.ptr)); return -1951; }
    if (pj_strcmp2(&prm->value, "\"ab:;cd\"")) { PJ_LOG(3,("", "%.*s", (int)prm->value.slen, prm->value.ptr)); return -1952; }

    prm = prm->next;
    if (prm == &hdr->media.param) return -1960;
    if (pj_strcmp2(&prm->name, "p2")) return -1961;
    if (pj_strcmp2(&prm->value, "ab%3acd")) return -1962;

    prm = prm->next;
    if (prm == &hdr->media.param) return -1970;
    if (pj_strcmp2(&prm->name, "p3")) return -1971;
    if (pj_strcmp2(&prm->value, "")) return -1972;

    return 0;
}

/*
    NAME_ADDR GENERIC_PARAM,
 */
static int hdr_test_from(pjsip_hdr *h)
{
    pjsip_from_hdr *hdr = (pjsip_from_hdr*)h;
    int rc;

    if (h->type != PJSIP_H_FROM)
        return -2010;

    rc = nameaddr_test(hdr->uri);
    if (rc != 0)
        return rc;

    rc = generic_param_test(&hdr->other_param);
    if (rc != 0)
        return rc;

    return 0;
}

/*
        "Digest realm=\"realm\", domain=\"sip:domain\", nonce=\"nonce\", "  \
                "opaque=\"opaque\", stale=true, algorithm=MD5, qop=\"auth\"",
 */
static int hdr_test_proxy_authenticate(pjsip_hdr *h)
{
    pjsip_proxy_authenticate_hdr *hdr = (pjsip_proxy_authenticate_hdr*)h;

    if (h->type != PJSIP_H_PROXY_AUTHENTICATE)
        return -2110;

    if (pj_strcmp2(&hdr->scheme, "Digest"))
        return -2120;

    if (pj_strcmp2(&hdr->challenge.digest.realm, "realm"))
        return -2130;

    if (pj_strcmp2(&hdr->challenge.digest.domain, "sip:domain"))
        return -2140;

    if (pj_strcmp2(&hdr->challenge.digest.nonce, "nonce"))
        return -2150;

    if (pj_strcmp2(&hdr->challenge.digest.opaque, "opaque"))
        return -2160;

    if (hdr->challenge.digest.stale != 1)
        return -2170;

    if (pj_strcmp2(&hdr->challenge.digest.algorithm, "MD5"))
        return -2180;

    if (pj_strcmp2(&hdr->challenge.digest.qop, "auth"))
        return -2190;

    return 0;
}

/*
    NAME_ADDR GENERIC_PARAM,
 */
static int hdr_test_record_route(pjsip_hdr *h)
{
    pjsip_rr_hdr *hdr = (pjsip_rr_hdr*)h;
    int rc;

    if (h->type != PJSIP_H_RECORD_ROUTE)
        return -2210;

    rc = nameaddr_test(&hdr->name_addr);
    if (rc != 0)
        return rc;

    rc = generic_param_test(&hdr->other_param);
    if (rc != 0)
        return rc;

    return 0;

}

/*
    " \r\n"
 */
static int hdr_test_supported(pjsip_hdr *h)
{
    pjsip_supported_hdr *hdr = (pjsip_supported_hdr*)h;

    if (h->type != PJSIP_H_SUPPORTED)
        return -2310;

    if (hdr->count != 0)
        return -2320;

    return 0;
}

/*
    ",outbound"
    Test for handling leading comma (buggy MizuDroid format)
 */
static int hdr_test_supported_with_leading_comma(pjsip_hdr *h)
{
    pjsip_supported_hdr *hdr = (pjsip_supported_hdr*)h;

    if (h->type != PJSIP_H_SUPPORTED)
        return -2330;

    if (hdr->count != 1)
        return -2331;

    if (pj_strcmp2(&hdr->values[0], "outbound") != 0)
        return -2332;

    return 0;
}

/*
    "outbound,"
    Test for handling trailing comma
 */
static int hdr_test_supported_with_trailing_comma(pjsip_hdr *h)
{
    pjsip_supported_hdr *hdr = (pjsip_supported_hdr*)h;

    if (h->type != PJSIP_H_SUPPORTED)
        return -2340;

    if (hdr->count != 1)
        return -2341;

    if (pj_strcmp2(&hdr->values[0], "outbound") != 0)
        return -2342;

    return 0;
}

/*
    ",,,outbound,path"
    Test for handling multiple consecutive commas at beginning
 */
static int hdr_test_supported_with_multiple_commas(pjsip_hdr *h)
{
    pjsip_supported_hdr *hdr = (pjsip_supported_hdr*)h;

    if (h->type != PJSIP_H_SUPPORTED)
        return -2350;

    if (hdr->count != 2)
        return -2351;

    if (pj_strcmp2(&hdr->values[0], "outbound") != 0)
        return -2352;

    if (pj_strcmp2(&hdr->values[1], "path") != 0)
        return -2353;

    return 0;
}

/*
    "outbound,,,path"
    Test for handling multiple consecutive commas in middle
 */
static int hdr_test_supported_with_middle_commas(pjsip_hdr *h)
{
    pjsip_supported_hdr *hdr = (pjsip_supported_hdr*)h;

    if (h->type != PJSIP_H_SUPPORTED)
        return -2360;

    if (hdr->count != 2)
        return -2361;

    if (pj_strcmp2(&hdr->values[0], "outbound") != 0)
        return -2362;

    if (pj_strcmp2(&hdr->values[1], "path") != 0)
        return -2363;

    return 0;
}

/*
    NAME_ADDR GENERIC_PARAM,
 */
static int hdr_test_to(pjsip_hdr *h)
{
    pjsip_to_hdr *hdr = (pjsip_to_hdr*)h;
    int rc;

    if (h->type != PJSIP_H_TO)
        return -2410;

    rc = nameaddr_test(hdr->uri);
    if (rc != 0)
        return rc;

    rc = generic_param_test(&hdr->other_param);
    if (rc != 0)
        return rc;

    return 0;
}

/*
    "SIP/2.0 host" GENERIC_PARAM
 */
static int hdr_test_via(pjsip_hdr *h)
{
    pjsip_via_hdr *hdr = (pjsip_via_hdr*)h;
    int rc;

    if (h->type != PJSIP_H_VIA)
        return -2510;

    if (pj_strcmp2(&hdr->transport, "XYZ"))
        return -2515;

    if (pj_strcmp2(&hdr->sent_by.host, "host"))
        return -2520;

    if (hdr->sent_by.port != 0)
        return -2530;

    rc = generic_param_test(&hdr->other_param);
    if (rc != 0)
        return rc;

    return 0;
}


/*
    "SIP/2.0/UDP [::1]"
 */
static int hdr_test_via_ipv6_1(pjsip_hdr *h)
{
    pjsip_via_hdr *hdr = (pjsip_via_hdr*)h;

    if (h->type != PJSIP_H_VIA)
        return -2610;

    if (pj_strcmp2(&hdr->transport, "UDP"))
        return -2615;

    if (pj_strcmp2(&hdr->sent_by.host, "::1"))
        return -2620;

    if (hdr->sent_by.port != 0)
        return -2630;

    return 0;
}

/* "SIP/2.0/UDP [::1]:5061" */
static int hdr_test_via_ipv6_2(pjsip_hdr *h)
{
    pjsip_via_hdr *hdr = (pjsip_via_hdr*)h;

    if (h->type != PJSIP_H_VIA)
        return -2710;

    if (pj_strcmp2(&hdr->transport, "UDP"))
        return -2715;

    if (pj_strcmp2(&hdr->sent_by.host, "::1"))
        return -2720;

    if (hdr->sent_by.port != 5061)
        return -2730;

    return 0;
}

/* "SIP/2.0/UDP [::1];rport=5061;received=::2" */
static int hdr_test_via_ipv6_3(pjsip_hdr *h)
{
    pjsip_via_hdr *hdr = (pjsip_via_hdr*)h;

    if (h->type != PJSIP_H_VIA)
        return -2810;

    if (pj_strcmp2(&hdr->transport, "UDP"))
        return -2815;

    if (pj_strcmp2(&hdr->sent_by.host, "::1"))
        return -2820;

    if (hdr->sent_by.port != 0)
        return -2830;

    if (pj_strcmp2(&hdr->recvd_param, "::2"))
        return -2840;

    if (hdr->rport_param != 5061)
        return -2850;

    return 0;
}

/* "10(Already Pending Register)" */
static int hdr_test_retry_after1(pjsip_hdr *h)
{
    pjsip_retry_after_hdr *hdr = (pjsip_retry_after_hdr*)h;

    if (h->type != PJSIP_H_RETRY_AFTER)
        return -2910;

    if (hdr->ivalue != 10)
        return -2920;
    
    if (pj_strcmp2(&hdr->comment, "Already Pending Register"))
        return -2930;

    return 0;
}

/* Subject: \xC0\x81 */
static int hdr_test_subject_utf(pjsip_hdr *h)
{
    pjsip_subject_hdr *hdr = (pjsip_subject_hdr*)h;

    if (pj_strcmp2(&h->name, "Subject"))
        return -2950;

    if (pj_strcmp2(&hdr->hvalue, "\xC0\x81"))
        return -2960;

    return 0;
}

static int hdr_test(void)
{
    unsigned i;

    PJ_LOG(3,(THIS_FILE, "  testing header parsing.."));

    for (i=0; i<PJ_ARRAY_SIZE(hdr_test_data); ++i) {
        struct hdr_test_t  *test = &hdr_test_data[i];
        pj_str_t hname;
        pj_ssize_t len;
        int parsed_len;
        pj_pool_t *pool;
        pjsip_hdr *parsed_hdr1=NULL, *parsed_hdr2=NULL;
        char *input, *output;
#if defined(PJSIP_UNESCAPE_IN_PLACE) && PJSIP_UNESCAPE_IN_PLACE!=0
        static char hcontent[1024];
#else
        char *hcontent;
#endif
        int rc;

        pool = pjsip_endpt_create_pool(endpt, NULL, POOL_SIZE, POOL_SIZE);

        /* Parse the header */
        hname = pj_str(test->hname);
        len = strlen(test->hcontent);
#if defined(PJSIP_UNESCAPE_IN_PLACE) && PJSIP_UNESCAPE_IN_PLACE!=0
        PJ_ASSERT_RETURN(len < sizeof(hcontent), PJSIP_EMSGTOOLONG);
        pj_ansi_strxcpy(hcontent, test->hcontent, sizeof(hcontent));
#else
        hcontent = test->hcontent;
#endif
        
        parsed_hdr1 = (pjsip_hdr*) pjsip_parse_hdr(pool, &hname, 
                                                   hcontent, len, 
                                                   &parsed_len);
        if (parsed_hdr1 == NULL) {
            if (test->flags & HDR_FLAG_PARSE_FAIL) {
                pj_pool_release(pool);
                continue;
            }
            PJ_LOG(3,(THIS_FILE, "    error parsing header %s: %s", test->hname, test->hcontent));
            return -500;
        }

        /* Test the parsing result */
        if (test->test && (rc=test->test(parsed_hdr1)) != 0) {
            PJ_LOG(3,(THIS_FILE, "    validation failed for header %s: %s", test->hname, test->hcontent));
            PJ_LOG(3,(THIS_FILE, "    error code is %d", rc));
            return -502;
        }

#if 1
        /* Parse with hshortname, if present */
        if (test->hshort_name) {
            hname = pj_str(test->hshort_name);
            len = strlen(test->hcontent);
#if defined(PJSIP_UNESCAPE_IN_PLACE) && PJSIP_UNESCAPE_IN_PLACE!=0
            PJ_ASSERT_RETURN(len < sizeof(hcontent), PJSIP_EMSGTOOLONG);
            pj_ansi_strxcpy(hcontent, test->hcontent, sizeof(hcontent));
#else
            hcontent = test->hcontent;
#endif

            parsed_hdr2 = (pjsip_hdr*) pjsip_parse_hdr(pool, &hname, hcontent, len, &parsed_len);
            if (parsed_hdr2 == NULL) {
                PJ_LOG(3,(THIS_FILE, "    error parsing header %s: %s", test->hshort_name, test->hcontent));
                return -510;
            }
        }
#endif

        if (test->flags & HDR_FLAG_DONT_PRINT) {
            pj_pool_release(pool);
            continue;
        }

        /* Print the original header */
        input = (char*) pj_pool_alloc(pool, 1024);
        len = pj_ansi_snprintf(input, 1024, "%s: %s", test->hname, test->hcontent);
        if (len < 1 || len >= 1024)
            return -520;

        /* Print the parsed header*/
        output = (char*) pj_pool_alloc(pool, 1024);
        len = pjsip_hdr_print_on(parsed_hdr1, output, 1024);
        if (len < 0 || len >= 1024) {
            PJ_LOG(3,(THIS_FILE, "    header too long: %s: %s", test->hname, test->hcontent));
            return -530;
        }
        output[len] = 0;

        if (strcmp(input, output) != 0) {
            PJ_LOG(3,(THIS_FILE, "    header character by character comparison failed."));
            PJ_LOG(3,(THIS_FILE, "    original header=|%s|", input));
            PJ_LOG(3,(THIS_FILE, "    parsed header  =|%s|", output));
            return -540;
        }

        pj_pool_release(pool);
    }

    return 0;
}


/*****************************************************************************/
/* Test that parsing a complete SIP message with a malformed registered header
 * value does not crash and always produces a valid (non-NULL) hname pointer
 * in every error-report entry.
 *
 * This directly exercises the longjmp/volatile clobbering fix in
 * int_parse_msg() (sip_parser.c): before that fix, longjmp() could restore
 * the CPU registers holding hname.ptr/hname.slen to their setjmp-time
 * values, yielding a garbage pointer that later caused SIGSEGV when the
 * error was logged via pj_ansi_snprintf(..., "%.*s", ..., err->hname.ptr).
 */
static pj_status_t malformed_hdr_test(void)
{
    /* Each test case is a complete SIP message that contains at least one
     * header whose value is malformed enough to cause the registered handler
     * to throw a PJ_THROW exception.
     */
    static const struct {
        const char *desc;
        const char *msg;
        const char *expected_hname; /* expected hname in first error entry */
    } cases[] = {
        {
            /* Max-Forwards handler calls parse_generic_int_hdr() which calls
             * pj_scan_get(DIGIT_SPEC,...).  "notanumber" has no digits, so
             * pj_scan_get() throws immediately.
             */
            "Max-Forwards: notanumber",
            "INVITE sip:bob@biloxi.com SIP/2.0\r\n"
            "Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bKnashds8\r\n"
            "Max-Forwards: notanumber\r\n"
            "To: Bob <sip:bob@biloxi.com>\r\n"
            "From: Alice <sip:alice@atlanta.com>;tag=1928301774\r\n"
            "Call-ID: malformed-mf@atlanta.com\r\n"
            "CSeq: 1 INVITE\r\n"
            "Content-Length: 0\r\n"
            "\r\n",
            "Max-Forwards"
        },
        {
            /* Content-Length handler also calls pj_scan_get(DIGIT_SPEC,...).
             * Same throw path as above but with a different header.
             */
            "Content-Length: notanumber",
            "SIP/2.0 200 OK\r\n"
            "Via: SIP/2.0/UDP pc33.atlanta.com;branch=z9hG4bKnashds8\r\n"
            "To: Bob <sip:bob@biloxi.com>;tag=abc\r\n"
            "From: Alice <sip:alice@atlanta.com>;tag=1928301774\r\n"
            "Call-ID: malformed-cl@atlanta.com\r\n"
            "CSeq: 1 INVITE\r\n"
            "Content-Length: notanumber\r\n"
            "\r\n",
            "Content-Length"
        },
    };

    unsigned i;

    PJ_LOG(3,(THIS_FILE, "  testing malformed header error reporting.."));

    for (i = 0; i < PJ_ARRAY_SIZE(cases); ++i) {
        pj_pool_t *pool;
        pjsip_parser_err_report err_list;
        pjsip_parser_err_report *err;
        pj_size_t len;

        pool = pjsip_endpt_create_pool(endpt, NULL, POOL_SIZE, POOL_SIZE);
        pj_list_init(&err_list);
        len = pj_ansi_strlen(cases[i].msg);

        /* Must not crash (SIGSEGV was the pre-fix symptom). */
        /* Cast away const: pjsip_parse_msg() requires a mutable buffer
         * (it modifies it during scanning), and the test data is read-only.
         */
        pjsip_parse_msg(pool, (char *)cases[i].msg, len, &err_list);

        /* The malformed header value must have produced at least one
         * error report entry.
         */
        if (pj_list_empty(&err_list)) {
            PJ_LOG(3,(THIS_FILE,
                      "    error: no parse error reported for: %s",
                      cases[i].desc));
            pjsip_endpt_release_pool(endpt, pool);
            return -600;
        }

        /* Every error entry must carry a non-NULL hname.ptr.
         * Passing NULL to %.*s (as done in sip_transport.c) is
         * undefined behavior even when the length field is 0.
         */
        for (err = err_list.next; err != &err_list; err = err->next) {
            if (err->hname.ptr == NULL) {
                PJ_LOG(3,(THIS_FILE,
                          "    error: err_info->hname.ptr is NULL for: %s",
                          cases[i].desc));
                pjsip_endpt_release_pool(endpt, pool);
                return -610;
            }
        }

        /* The first error entry must identify the offending header. */
        err = err_list.next;
        if (pj_strcmp2(&err->hname, cases[i].expected_hname) != 0) {
            PJ_LOG(3,(THIS_FILE,
                      "    error: wrong hname '%.*s', expected '%s' for: %s",
                      (int)err->hname.slen, err->hname.ptr,
                      cases[i].expected_hname, cases[i].desc));
            pjsip_endpt_release_pool(endpt, pool);
            return -620;
        }

        pjsip_endpt_release_pool(endpt, pool);
    }

    return PJ_SUCCESS;
}


/*****************************************************************************/

int msg_test(void)
{
    enum { COUNT = 1, DETECT=0, PARSE=1, PRINT=2 };
    struct {
        unsigned detect;
        unsigned parse;
        unsigned print;
    } run[COUNT];
    unsigned i, max, avg_len;
    char desc[250];
    pj_status_t status;

    status = hdr_test();
    if (status != 0)
        return status;

    status = simple_test();
    if (status != PJ_SUCCESS)
        return status;

    status = malformed_hdr_test();
    if (status != PJ_SUCCESS)
        return status;

#if INCLUDE_BENCHMARKS
    for (i=0; i<COUNT; ++i) {
        PJ_LOG(3,(THIS_FILE, "  benchmarking (%d of %d)..", i+1, COUNT));
        status = msg_benchmark(&run[i].detect, &run[i].parse, &run[i].print);
        if (status != PJ_SUCCESS)
            return status;
    }

    /* Calculate average message length */
    for (i=0, avg_len=0; i<PJ_ARRAY_SIZE(test_array); ++i) {
        avg_len += (unsigned)test_array[i].len;
    }
    avg_len /= PJ_ARRAY_SIZE(test_array);


    /* Print maximum detect/sec */
    for (i=0, max=0; i<COUNT; ++i)
        if (run[i].detect > max) max = run[i].detect;

    PJ_LOG(3,("", "  Maximum message detection/sec=%u", max));

    pj_ansi_snprintf(desc, sizeof(desc),
                          "Number of SIP messages "
                          "can be pre-parse by <tt>pjsip_find_msg()</tt> "
                          "per second (tested with %d message sets with "
                          "average message length of "
                          "%d bytes)", (int)PJ_ARRAY_SIZE(test_array), avg_len);
    report_ival("msg-detect-per-sec", max, "msg/sec", desc);

    /* Print maximum parse/sec */
    for (i=0, max=0; i<COUNT; ++i)
        if (run[i].parse > max) max = run[i].parse;

    PJ_LOG(3,("", "  Maximum message parsing/sec=%u", max));

    pj_ansi_snprintf(desc, sizeof(desc),
                          "Number of SIP messages "
                          "can be <b>parsed</b> by <tt>pjsip_parse_msg()</tt> "
                          "per second (tested with %d message sets with "
                          "average message length of "
                          "%d bytes)", (int)PJ_ARRAY_SIZE(test_array), avg_len);
    report_ival("msg-parse-per-sec", max, "msg/sec", desc);

    /* Msg parsing bandwidth */
    report_ival("msg-parse-bandwidth-mb", avg_len*max/1000000, "MB/sec",
                "Message parsing bandwidth in megabytes (number of megabytes"
                " worth of SIP messages that can be parsed per second). "
                "The value is derived from msg-parse-per-sec above.");


    /* Print maximum print/sec */
    for (i=0, max=0; i<COUNT; ++i)
        if (run[i].print > max) max = run[i].print;

    PJ_LOG(3,("", "  Maximum message print/sec=%u", max));

    pj_ansi_snprintf(desc, sizeof(desc),
                          "Number of SIP messages "
                          "can be <b>printed</b> by <tt>pjsip_msg_print()</tt>"
                          " per second (tested with %d message sets with "
                          "average message length of "
                          "%d bytes)", (int)PJ_ARRAY_SIZE(test_array), avg_len);

    report_ival("msg-print-per-sec", max, "msg/sec", desc);

    /* Msg print bandwidth */
    report_ival("msg-printed-bandwidth-mb", avg_len*max/1000000, "MB/sec",
                "Message print bandwidth in megabytes (total size of "
                "SIP messages printed per second). "
                "The value is derived from msg-print-per-sec above.");

#endif  /* INCLUDE_BENCHMARKS */

    return PJ_SUCCESS;
}




