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


#define THIS_FILE   "srv_resolver_test.c"

////////////////////////////////////////////////////////////////////////////
/*
 * TODO: create various invalid DNS packets.
 */


////////////////////////////////////////////////////////////////////////////


#define ACTION_REPLY    0
#define ACTION_IGNORE   -1
#define ACTION_CB       -2

#undef s6_addr32
#define s6_addr32(addr, idx) *((pj_uint32_t *)(addr.s6_addr + idx*4))

static struct server_t
{
    pj_sock_t        sock;
    pj_uint16_t      port;
    pj_thread_t     *thread;

    /* Action:
     *  0:    reply with the response in resp.
     * -1:    ignore query (to simulate timeout).
     * other: reply with that error
     */
    int             action;

    pj_dns_parsed_packet    resp;
    void                  (*action_cb)(const pj_dns_parsed_packet *pkt,
                                       pj_dns_parsed_packet **p_res);

    unsigned        pkt_count;

} g_server[2];

static pj_pool_t *pool;
static pj_dns_resolver *resolver;
static pj_bool_t thread_quit;
static pj_timer_heap_t *timer_heap;
static pj_ioqueue_t *ioqueue;
static pj_thread_t *poll_thread;
static pj_sem_t *sem;
static pj_dns_settings set;

#define MAX_LABEL   32

struct label_tab
{
    unsigned count;

    struct {
        unsigned pos;
        pj_str_t label;
    } a[MAX_LABEL];
};

static void write16(pj_uint8_t *p, pj_uint16_t val)
{
    p[0] = (pj_uint8_t)(val >> 8);
    p[1] = (pj_uint8_t)(val & 0xFF);
}

static void write32(pj_uint8_t *p, pj_uint32_t val)
{
    val = pj_htonl(val);
    pj_memcpy(p, &val, 4);
}

static int print_name(pj_uint8_t *pkt, int size,
                      pj_uint8_t *pos, const pj_str_t *name,
                      struct label_tab *tab)
{
    pj_uint8_t *p = pos;
    const char *endlabel, *endname;
    unsigned i;
    pj_str_t label;

    /* Check if name is in the table */
    for (i=0; i<tab->count; ++i) {
        if (pj_strcmp(&tab->a[i].label, name)==0)
            break;
    }

    if (i != tab->count) {
        write16(p, (pj_uint16_t)(tab->a[i].pos | (0xc0 << 8)));
        return 2;
    } else {
        if (tab->count < MAX_LABEL) {
            tab->a[tab->count].pos = (unsigned)(p-pkt);
            tab->a[tab->count].label.ptr = (char*)(p+1);
            tab->a[tab->count].label.slen = name->slen;
            ++tab->count;
        }
    }

    endlabel = name->ptr;
    endname = name->ptr + name->slen;

    label.ptr = (char*)name->ptr;

    while (endlabel != endname) {

        while (endlabel != endname && *endlabel != '.')
            ++endlabel;

        label.slen = (endlabel - label.ptr);

        if (size < label.slen+1)
            return -1;

        *p = (pj_uint8_t)label.slen;
        pj_memcpy(p+1, label.ptr, label.slen);

        size -= (int)(label.slen+1);
        p += (label.slen+1);

        if (endlabel != endname && *endlabel == '.')
            ++endlabel;
        label.ptr = (char*)endlabel;
    }

    if (size == 0)
        return -1;

    *p++ = '\0';

    return (int)(p-pos);
}

static int print_rr(pj_uint8_t *pkt, int size, pj_uint8_t *pos,
                    const pj_dns_parsed_rr *rr, struct label_tab *tab)
{
    pj_uint8_t *p = pos;
    int len;

    len = print_name(pkt, size, pos, &rr->name, tab);
    if (len < 0)
        return -1;

    p += len;
    size -= len;

    if (size < 8)
        return -1;

    pj_assert(rr->dnsclass == 1);

    write16(p+0, (pj_uint16_t)rr->type);        /* type     */
    write16(p+2, (pj_uint16_t)rr->dnsclass);    /* class    */
    write32(p+4, rr->ttl);                      /* TTL      */

    p += 8;
    size -= 8;

    if (rr->type == PJ_DNS_TYPE_A) {

        if (size < 6)
            return -1;

        /* RDLEN is 4 */
        write16(p, 4);

        /* Address */
        pj_memcpy(p+2, &rr->rdata.a.ip_addr, 4);

        p += 6;
        size -= 6;

    } else if (rr->type == PJ_DNS_TYPE_AAAA) {

        if (size < 18)
            return -1;

        /* RDLEN is 16 */
        write16(p, 16);

        /* Address */
        pj_memcpy(p+2, &rr->rdata.aaaa.ip_addr, 16);

        p += 18;
        size -= 18;

    } else if (rr->type == PJ_DNS_TYPE_CNAME ||
               rr->type == PJ_DNS_TYPE_NS ||
               rr->type == PJ_DNS_TYPE_PTR) {

        if (size < 4)
            return -1;

        len = print_name(pkt, size-2, p+2, &rr->rdata.cname.name, tab);
        if (len < 0)
            return -1;

        write16(p, (pj_uint16_t)len);

        p += (len + 2);
        size -= (len + 2);

    } else if (rr->type == PJ_DNS_TYPE_SRV) {

        if (size < 10)
            return -1;

        write16(p+2, rr->rdata.srv.prio);   /* Priority */
        write16(p+4, rr->rdata.srv.weight); /* Weight */
        write16(p+6, rr->rdata.srv.port);   /* Port */

        /* Target */
        len = print_name(pkt, size-8, p+8, &rr->rdata.srv.target, tab);
        if (len < 0)
            return -1;

        /* RDLEN */
        write16(p, (pj_uint16_t)(len + 6));

        p += (len + 8);
        size -= (len + 8);

    } else {
        pj_assert(!"Not supported");
        return -1;
    }

    return (int)(p-pos);
}

static int print_packet(const pj_dns_parsed_packet *rec, pj_uint8_t *pkt,
                        int size)
{
    pj_uint8_t *p = pkt;
    struct label_tab tab;
    int i, len;

    tab.count = 0;

#if 0
    pj_enter_critical_section();
    PJ_LOG(3,(THIS_FILE, "Sending response:"));
    pj_dns_dump_packet(rec);
    pj_leave_critical_section();
#endif

    pj_assert(sizeof(pj_dns_hdr)==12);
    if (size < (int)sizeof(pj_dns_hdr))
        return -1;

    /* Initialize header */
    write16(p+0,  rec->hdr.id);
    write16(p+2,  rec->hdr.flags);
    write16(p+4,  rec->hdr.qdcount);
    write16(p+6,  rec->hdr.anscount);
    write16(p+8,  rec->hdr.nscount);
    write16(p+10, rec->hdr.arcount);

    p = pkt + sizeof(pj_dns_hdr);
    size -= sizeof(pj_dns_hdr);

    /* Print queries */
    for (i=0; i<rec->hdr.qdcount; ++i) {

        len = print_name(pkt, size, p, &rec->q[i].name, &tab);
        if (len < 0)
            return -1;

        p += len;
        size -= len;

        if (size < 4)
            return -1;

        /* Set type */
        write16(p+0, (pj_uint16_t)rec->q[i].type);

        /* Set class (IN=1) */
        pj_assert(rec->q[i].dnsclass == 1);
        write16(p+2, rec->q[i].dnsclass);

        p += 4;
    }

    /* Print answers */
    for (i=0; i<rec->hdr.anscount; ++i) {
        len = print_rr(pkt, size, p, &rec->ans[i], &tab);
        if (len < 0)
            return -1;

        p += len;
        size -= len;
    }

    /* Print NS records */
    for (i=0; i<rec->hdr.nscount; ++i) {
        len = print_rr(pkt, size, p, &rec->ns[i], &tab);
        if (len < 0)
            return -1;

        p += len;
        size -= len;
    }

    /* Print additional records */
    for (i=0; i<rec->hdr.arcount; ++i) {
        len = print_rr(pkt, size, p, &rec->arr[i], &tab);
        if (len < 0)
            return -1;

        p += len;
        size -= len;
    }

    return (int)(p - pkt);
}


static int server_thread(void *p)
{
    struct server_t *srv = (struct server_t*)p;

    while (!thread_quit) {
        pj_fd_set_t rset;
        pj_time_val timeout = {0, 500};
        pj_sockaddr src_addr;
        pj_dns_parsed_packet *req;
        char pkt[1024];
        pj_ssize_t pkt_len;
        int rc, src_len;

        PJ_FD_ZERO(&rset);
        PJ_FD_SET(srv->sock, &rset);

        rc = pj_sock_select((int)(srv->sock+1), &rset, NULL, NULL, &timeout);
        if (rc != 1)
            continue;

        src_len = sizeof(src_addr);
        pkt_len = sizeof(pkt);
        rc = pj_sock_recvfrom(srv->sock, pkt, &pkt_len, 0, 
                              &src_addr, &src_len);
        if (rc != 0) {
            app_perror("Server error receiving packet", rc);
            continue;
        }

        PJ_LOG(5,(THIS_FILE, "Server %ld processing packet", srv - &g_server[0]));
        srv->pkt_count++;

        rc = pj_dns_parse_packet(pool, pkt, (unsigned)pkt_len, &req);
        if (rc != PJ_SUCCESS) {
            app_perror("server error parsing packet", rc);
            continue;
        }

        /* Verify packet */
        if (req->hdr.qdcount != 1) {
            PJ_LOG(5,(THIS_FILE, "server receive multiple queries in a packet"));
            continue;
        }

        if (req->q[0].dnsclass != 1) {
            PJ_LOG(5,(THIS_FILE, "server receive query with invalid DNS class"));
            continue;
        }

        /* Simulate network RTT */
        pj_thread_sleep(50);

        if (srv->action == ACTION_IGNORE) {
            continue;
        } else if (srv->action == ACTION_REPLY) {
            srv->resp.hdr.id = req->hdr.id;
            pkt_len = print_packet(&srv->resp, (pj_uint8_t*)pkt, sizeof(pkt));
            pj_sock_sendto(srv->sock, pkt, &pkt_len, 0, &src_addr, src_len);
        } else if (srv->action == ACTION_CB) {
            pj_dns_parsed_packet *resp;
            (*srv->action_cb)(req, &resp);
            resp->hdr.id = req->hdr.id;
            pkt_len = print_packet(resp, (pj_uint8_t*)pkt, sizeof(pkt));
            pj_sock_sendto(srv->sock, pkt, &pkt_len, 0, &src_addr, src_len);
        } else if (srv->action > 0) {
            req->hdr.flags |= PJ_DNS_SET_RCODE(srv->action);
            pkt_len = print_packet(req, (pj_uint8_t*)pkt, sizeof(pkt));
            pj_sock_sendto(srv->sock, pkt, &pkt_len, 0, &src_addr, src_len);
        }
    }

    return 0;
}

static int poll_worker_thread(void *p)
{
    PJ_UNUSED_ARG(p);

    while (!thread_quit) {
        pj_time_val delay = {0, 100};
        pj_timer_heap_poll(timer_heap, NULL);
        pj_ioqueue_poll(ioqueue, &delay);
    }

    return 0;
}

static void destroy(void);

static int init(pj_bool_t use_ipv6)
{
    pj_status_t status;
    pj_str_t nameservers[2];
    pj_uint16_t ports[2];
    int i;

    if (use_ipv6) {
        nameservers[0] = pj_str("::1");
        nameservers[1] = pj_str("::1");
    } else {
        nameservers[0] = pj_str("127.0.0.1");
        nameservers[1] = pj_str("127.0.0.1");
    }
    ports[0] = 5553;
    ports[1] = 5554;

    g_server[0].port = ports[0];
    g_server[1].port = ports[1];

    pool = pj_pool_create(mem, NULL, 2000, 2000, NULL);

    status = pj_sem_create(pool, NULL, 0, 2, &sem);
    pj_assert(status == PJ_SUCCESS);

    thread_quit = PJ_FALSE;

    for (i=0; i<2; ++i) {
        pj_sockaddr addr;

        status = pj_sock_socket((use_ipv6? pj_AF_INET6() : pj_AF_INET()),
                                pj_SOCK_DGRAM(), 0, &g_server[i].sock);
        if (status != PJ_SUCCESS)
            return -10;

        pj_sockaddr_init((use_ipv6? pj_AF_INET6() : pj_AF_INET()),
                         &addr, NULL, (pj_uint16_t)g_server[i].port);

        status = pj_sock_bind(g_server[i].sock, &addr, pj_sockaddr_get_len(&addr));
        if (status != PJ_SUCCESS)
            return -20;

        status = pj_thread_create(pool, NULL, &server_thread, &g_server[i],
                                  0, 0, &g_server[i].thread);
        if (status != PJ_SUCCESS)
            return -30;
    }

    status = pj_timer_heap_create(pool, 16, &timer_heap);
    pj_assert(status == PJ_SUCCESS);

    status = pj_ioqueue_create(pool, 16, &ioqueue);
    pj_assert(status == PJ_SUCCESS);

    status = pj_dns_resolver_create(mem, NULL, 0, timer_heap, ioqueue, &resolver);
    if (status != PJ_SUCCESS)
        return -40;

    pj_dns_resolver_get_settings(resolver, &set);
    set.good_ns_ttl = 20;
    set.bad_ns_ttl = 20;
    pj_dns_resolver_set_settings(resolver, &set);

    status = pj_dns_resolver_set_ns(resolver, 2, nameservers, ports);
    pj_assert(status == PJ_SUCCESS);

    status = pj_thread_create(pool, NULL, &poll_worker_thread, NULL, 0, 0, &poll_thread);
    pj_assert(status == PJ_SUCCESS);

    return 0;
}


static void destroy(void)
{
    int i;

    thread_quit = PJ_TRUE;

    for (i=0; i<2; ++i) {
        pj_thread_join(g_server[i].thread);
        pj_sock_close(g_server[i].sock);
    }

    pj_thread_join(poll_thread);

    pj_dns_resolver_destroy(resolver, PJ_FALSE);
    pj_ioqueue_destroy(ioqueue);
    pj_timer_heap_destroy(timer_heap);

    pj_sem_destroy(sem);
    pj_pool_release(pool);
}


////////////////////////////////////////////////////////////////////////////
/* DNS A parser tests */
static int a_parser_test(void)
{
    pj_dns_parsed_packet pkt;
    pj_dns_a_record rec;
    pj_status_t rc;

    PJ_LOG(3,(THIS_FILE, "  DNS A record parser tests"));

    pkt.q = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_query);
    pkt.ans = (pj_dns_parsed_rr*)
              pj_pool_calloc(pool, 32, sizeof(pj_dns_parsed_rr));

    /* Simple answer with direct A record, but with addition of
     * a CNAME and another A to confuse the parser.
     */
    PJ_LOG(3,(THIS_FILE, "    A RR with duplicate CNAME/A"));
    pkt.hdr.flags = 0;
    pkt.hdr.qdcount = 1;
    pkt.q[0].type = PJ_DNS_TYPE_A;
    pkt.q[0].dnsclass = 1;
    pkt.q[0].name = pj_str("ahost");
    pkt.hdr.anscount = 3;

    /* This is the RR corresponding to the query */
    pkt.ans[0].name = pj_str("ahost");
    pkt.ans[0].type = PJ_DNS_TYPE_A;
    pkt.ans[0].dnsclass = 1;
    pkt.ans[0].ttl = 1;
    pkt.ans[0].rdata.a.ip_addr.s_addr = 0x01020304;

    /* CNAME to confuse the parser */
    pkt.ans[1].name = pj_str("ahost");
    pkt.ans[1].type = PJ_DNS_TYPE_CNAME;
    pkt.ans[1].dnsclass = 1;
    pkt.ans[1].ttl = 1;
    pkt.ans[1].rdata.cname.name = pj_str("bhost");

    /* DNS A RR to confuse the parser */
    pkt.ans[2].name = pj_str("bhost");
    pkt.ans[2].type = PJ_DNS_TYPE_A;
    pkt.ans[2].dnsclass = 1;
    pkt.ans[2].ttl = 1;
    pkt.ans[2].rdata.a.ip_addr.s_addr = 0x0203;


    rc = pj_dns_parse_a_response(&pkt, &rec);
    pj_assert(rc == PJ_SUCCESS);
    pj_assert(pj_strcmp2(&rec.name, "ahost")==0);
    pj_assert(rec.alias.slen == 0);
    pj_assert(rec.addr_count == 1);
    pj_assert(rec.addr[0].s_addr == 0x01020304);

    /* Answer with the target corresponds to a CNAME entry, but not
     * as the first record, and with additions of some CNAME and A
     * entries to confuse the parser.
     */
    PJ_LOG(3,(THIS_FILE, "    CNAME RR with duplicate CNAME/A"));
    pkt.hdr.flags = 0;
    pkt.hdr.qdcount = 1;
    pkt.q[0].type = PJ_DNS_TYPE_A;
    pkt.q[0].dnsclass = 1;
    pkt.q[0].name = pj_str("ahost");
    pkt.hdr.anscount = 4;

    /* This is the DNS A record for the alias */
    pkt.ans[0].name = pj_str("ahostalias");
    pkt.ans[0].type = PJ_DNS_TYPE_A;
    pkt.ans[0].dnsclass = 1;
    pkt.ans[0].ttl = 1;
    pkt.ans[0].rdata.a.ip_addr.s_addr = 0x02020202;

    /* CNAME entry corresponding to the query */
    pkt.ans[1].name = pj_str("ahost");
    pkt.ans[1].type = PJ_DNS_TYPE_CNAME;
    pkt.ans[1].dnsclass = 1;
    pkt.ans[1].ttl = 1;
    pkt.ans[1].rdata.cname.name = pj_str("ahostalias");

    /* Another CNAME to confuse the parser */
    pkt.ans[2].name = pj_str("ahost");
    pkt.ans[2].type = PJ_DNS_TYPE_CNAME;
    pkt.ans[2].dnsclass = 1;
    pkt.ans[2].ttl = 1;
    pkt.ans[2].rdata.cname.name = pj_str("ahostalias2");

    /* Another DNS A to confuse the parser */
    pkt.ans[3].name = pj_str("ahostalias2");
    pkt.ans[3].type = PJ_DNS_TYPE_A;
    pkt.ans[3].dnsclass = 1;
    pkt.ans[3].ttl = 1;
    pkt.ans[3].rdata.a.ip_addr.s_addr = 0x03030303;

    rc = pj_dns_parse_a_response(&pkt, &rec);
    pj_assert(rc == PJ_SUCCESS);
    pj_assert(pj_strcmp2(&rec.name, "ahost")==0);
    pj_assert(pj_strcmp2(&rec.alias, "ahostalias")==0);
    pj_assert(rec.addr_count == 1);
    pj_assert(rec.addr[0].s_addr == 0x02020202);

    /*
     * No query section.
     */
    PJ_LOG(3,(THIS_FILE, "    No query section"));
    pkt.hdr.qdcount = 0;
    pkt.hdr.anscount = 0;

    rc = pj_dns_parse_a_response(&pkt, &rec);
    pj_assert(rc == PJLIB_UTIL_EDNSINANSWER);

    /*
     * No answer section.
     */
    PJ_LOG(3,(THIS_FILE, "    No answer section"));
    pkt.hdr.flags = 0;
    pkt.hdr.qdcount = 1;
    pkt.q[0].type = PJ_DNS_TYPE_A;
    pkt.q[0].dnsclass = 1;
    pkt.q[0].name = pj_str("ahost");
    pkt.hdr.anscount = 0;

    rc = pj_dns_parse_a_response(&pkt, &rec);
    pj_assert(rc == PJLIB_UTIL_EDNSNOANSWERREC);

    /*
     * Answer doesn't match query.
     */
    PJ_LOG(3,(THIS_FILE, "    Answer doesn't match query"));
    pkt.hdr.flags = 0;
    pkt.hdr.qdcount = 1;
    pkt.q[0].type = PJ_DNS_TYPE_A;
    pkt.q[0].dnsclass = 1;
    pkt.q[0].name = pj_str("ahost");
    pkt.hdr.anscount = 1;

    /* An answer that doesn't match the query */
    pkt.ans[0].name = pj_str("ahostalias");
    pkt.ans[0].type = PJ_DNS_TYPE_A;
    pkt.ans[0].dnsclass = 1;
    pkt.ans[0].ttl = 1;
    pkt.ans[0].rdata.a.ip_addr.s_addr = 0x02020202;

    rc = pj_dns_parse_a_response(&pkt, &rec);
    pj_assert(rc == PJLIB_UTIL_EDNSNOANSWERREC);


    /*
     * DNS CNAME that doesn't have corresponding DNS A.
     */
    PJ_LOG(3,(THIS_FILE, "    CNAME with no matching DNS A RR (1)"));
    pkt.hdr.flags = 0;
    pkt.hdr.qdcount = 1;
    pkt.q[0].type = PJ_DNS_TYPE_A;
    pkt.q[0].dnsclass = 1;
    pkt.q[0].name = pj_str("ahost");
    pkt.hdr.anscount = 1;

    /* The CNAME */
    pkt.ans[0].name = pj_str("ahost");
    pkt.ans[0].type = PJ_DNS_TYPE_CNAME;
    pkt.ans[0].dnsclass = 1;
    pkt.ans[0].ttl = 1;
    pkt.ans[0].rdata.cname.name = pj_str("ahostalias");

    rc = pj_dns_parse_a_response(&pkt, &rec);
    pj_assert(rc == PJLIB_UTIL_EDNSNOANSWERREC);


    /*
     * DNS CNAME that doesn't have corresponding DNS A.
     */
    PJ_LOG(3,(THIS_FILE, "    CNAME with no matching DNS A RR (2)"));
    pkt.hdr.flags = 0;
    pkt.hdr.qdcount = 1;
    pkt.q[0].type = PJ_DNS_TYPE_A;
    pkt.q[0].dnsclass = 1;
    pkt.q[0].name = pj_str("ahost");
    pkt.hdr.anscount = 2;

    /* The CNAME */
    pkt.ans[0].name = pj_str("ahost");
    pkt.ans[0].type = PJ_DNS_TYPE_CNAME;
    pkt.ans[0].dnsclass = 1;
    pkt.ans[0].ttl = 1;
    pkt.ans[0].rdata.cname.name = pj_str("ahostalias");

    /* DNS A record, but the name doesn't match */
    pkt.ans[1].name = pj_str("ahost");
    pkt.ans[1].type = PJ_DNS_TYPE_A;
    pkt.ans[1].dnsclass = 1;
    pkt.ans[1].ttl = 1;
    pkt.ans[1].rdata.a.ip_addr.s_addr = 0x01020304;

    rc = pj_dns_parse_a_response(&pkt, &rec);
    pj_assert(rc == PJLIB_UTIL_EDNSNOANSWERREC);
    PJ_UNUSED_ARG(rc);

    return 0;
}


////////////////////////////////////////////////////////////////////////////
/* DNS A/AAAA parser tests */
static int addr_parser_test(void)
{
    pj_dns_parsed_packet pkt;
    pj_dns_addr_record rec;
    pj_status_t rc;

    PJ_LOG(3,(THIS_FILE, "  DNS A/AAAA record parser tests"));

    pkt.q = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_query);
    pkt.ans = (pj_dns_parsed_rr*)
              pj_pool_calloc(pool, 32, sizeof(pj_dns_parsed_rr));

    /* Simple answer with direct A record, but with addition of
     * a CNAME and another A to confuse the parser.
     */
    PJ_LOG(3,(THIS_FILE, "    A RR with duplicate CNAME/A"));
    pkt.hdr.flags = 0;
    pkt.hdr.qdcount = 1;
    pkt.q[0].type = PJ_DNS_TYPE_A;
    pkt.q[0].dnsclass = 1;
    pkt.q[0].name = pj_str("ahost");
    pkt.hdr.anscount = 4;

    /* This is the RR corresponding to the query */
    pkt.ans[0].name = pj_str("ahost");
    pkt.ans[0].type = PJ_DNS_TYPE_A;
    pkt.ans[0].dnsclass = 1;
    pkt.ans[0].ttl = 1;
    pkt.ans[0].rdata.a.ip_addr.s_addr = 0x01020304;

    /* CNAME to confuse the parser */
    pkt.ans[1].name = pj_str("ahost");
    pkt.ans[1].type = PJ_DNS_TYPE_CNAME;
    pkt.ans[1].dnsclass = 1;
    pkt.ans[1].ttl = 1;
    pkt.ans[1].rdata.cname.name = pj_str("bhost");

    /* DNS A RR to confuse the parser */
    pkt.ans[2].name = pj_str("bhost");
    pkt.ans[2].type = PJ_DNS_TYPE_A;
    pkt.ans[2].dnsclass = 1;
    pkt.ans[2].ttl = 1;
    pkt.ans[2].rdata.a.ip_addr.s_addr = 0x0203;

    /* Additional RR corresponding to the query, DNS AAAA RR */
    pkt.ans[3].name = pj_str("ahost");
    pkt.ans[3].type = PJ_DNS_TYPE_AAAA;
    pkt.ans[3].dnsclass = 1;
    pkt.ans[3].ttl = 1;
    s6_addr32(pkt.ans[3].rdata.aaaa.ip_addr, 0) = 0x01020304;


    rc = pj_dns_parse_addr_response(&pkt, &rec);
    pj_assert(rc == PJ_SUCCESS);
    pj_assert(pj_strcmp2(&rec.name, "ahost")==0);
    pj_assert(rec.alias.slen == 0);
    pj_assert(rec.addr_count == 2);
    pj_assert(rec.addr[0].af==pj_AF_INET() && rec.addr[0].ip.v4.s_addr == 0x01020304);
    pj_assert(rec.addr[1].af==pj_AF_INET6() && s6_addr32(rec.addr[1].ip.v6, 0) == 0x01020304);

    /* Answer with the target corresponds to a CNAME entry, but not
     * as the first record, and with additions of some CNAME and A
     * entries to confuse the parser.
     */
    PJ_LOG(3,(THIS_FILE, "    CNAME RR with duplicate CNAME/A"));
    pkt.hdr.flags = 0;
    pkt.hdr.qdcount = 1;
    pkt.q[0].type = PJ_DNS_TYPE_A;
    pkt.q[0].dnsclass = 1;
    pkt.q[0].name = pj_str("ahost");
    pkt.hdr.anscount = 4;

    /* This is the DNS A record for the alias */
    pkt.ans[0].name = pj_str("ahostalias");
    pkt.ans[0].type = PJ_DNS_TYPE_A;
    pkt.ans[0].dnsclass = 1;
    pkt.ans[0].ttl = 1;
    pkt.ans[0].rdata.a.ip_addr.s_addr = 0x02020202;

    /* CNAME entry corresponding to the query */
    pkt.ans[1].name = pj_str("ahost");
    pkt.ans[1].type = PJ_DNS_TYPE_CNAME;
    pkt.ans[1].dnsclass = 1;
    pkt.ans[1].ttl = 1;
    pkt.ans[1].rdata.cname.name = pj_str("ahostalias");

    /* Another CNAME to confuse the parser */
    pkt.ans[2].name = pj_str("ahost");
    pkt.ans[2].type = PJ_DNS_TYPE_CNAME;
    pkt.ans[2].dnsclass = 1;
    pkt.ans[2].ttl = 1;
    pkt.ans[2].rdata.cname.name = pj_str("ahostalias2");

    /* Another DNS A to confuse the parser */
    pkt.ans[3].name = pj_str("ahostalias2");
    pkt.ans[3].type = PJ_DNS_TYPE_A;
    pkt.ans[3].dnsclass = 1;
    pkt.ans[3].ttl = 1;
    pkt.ans[3].rdata.a.ip_addr.s_addr = 0x03030303;

    rc = pj_dns_parse_addr_response(&pkt, &rec);
    pj_assert(rc == PJ_SUCCESS);
    pj_assert(pj_strcmp2(&rec.name, "ahost")==0);
    pj_assert(pj_strcmp2(&rec.alias, "ahostalias")==0);
    pj_assert(rec.addr_count == 1);
    pj_assert(rec.addr[0].ip.v4.s_addr == 0x02020202);

    /*
     * No query section.
     */
    PJ_LOG(3,(THIS_FILE, "    No query section"));
    pkt.hdr.qdcount = 0;
    pkt.hdr.anscount = 0;

    rc = pj_dns_parse_addr_response(&pkt, &rec);
    pj_assert(rc == PJLIB_UTIL_EDNSINANSWER);

    /*
     * No answer section.
     */
    PJ_LOG(3,(THIS_FILE, "    No answer section"));
    pkt.hdr.flags = 0;
    pkt.hdr.qdcount = 1;
    pkt.q[0].type = PJ_DNS_TYPE_A;
    pkt.q[0].dnsclass = 1;
    pkt.q[0].name = pj_str("ahost");
    pkt.hdr.anscount = 0;

    rc = pj_dns_parse_addr_response(&pkt, &rec);
    pj_assert(rc == PJLIB_UTIL_EDNSNOANSWERREC);

    /*
     * Answer doesn't match query.
     */
    PJ_LOG(3,(THIS_FILE, "    Answer doesn't match query"));
    pkt.hdr.flags = 0;
    pkt.hdr.qdcount = 1;
    pkt.q[0].type = PJ_DNS_TYPE_A;
    pkt.q[0].dnsclass = 1;
    pkt.q[0].name = pj_str("ahost");
    pkt.hdr.anscount = 1;

    /* An answer that doesn't match the query */
    pkt.ans[0].name = pj_str("ahostalias");
    pkt.ans[0].type = PJ_DNS_TYPE_A;
    pkt.ans[0].dnsclass = 1;
    pkt.ans[0].ttl = 1;
    pkt.ans[0].rdata.a.ip_addr.s_addr = 0x02020202;

    rc = pj_dns_parse_addr_response(&pkt, &rec);
    pj_assert(rc == PJLIB_UTIL_EDNSNOANSWERREC);


    /*
     * DNS CNAME that doesn't have corresponding DNS A.
     */
    PJ_LOG(3,(THIS_FILE, "    CNAME with no matching DNS A RR (1)"));
    pkt.hdr.flags = 0;
    pkt.hdr.qdcount = 1;
    pkt.q[0].type = PJ_DNS_TYPE_A;
    pkt.q[0].dnsclass = 1;
    pkt.q[0].name = pj_str("ahost");
    pkt.hdr.anscount = 1;

    /* The CNAME */
    pkt.ans[0].name = pj_str("ahost");
    pkt.ans[0].type = PJ_DNS_TYPE_CNAME;
    pkt.ans[0].dnsclass = 1;
    pkt.ans[0].ttl = 1;
    pkt.ans[0].rdata.cname.name = pj_str("ahostalias");

    rc = pj_dns_parse_addr_response(&pkt, &rec);
    pj_assert(rc == PJLIB_UTIL_EDNSNOANSWERREC);


    /*
     * DNS CNAME that doesn't have corresponding DNS A.
     */
    PJ_LOG(3,(THIS_FILE, "    CNAME with no matching DNS A RR (2)"));
    pkt.hdr.flags = 0;
    pkt.hdr.qdcount = 1;
    pkt.q[0].type = PJ_DNS_TYPE_A;
    pkt.q[0].dnsclass = 1;
    pkt.q[0].name = pj_str("ahost");
    pkt.hdr.anscount = 2;

    /* The CNAME */
    pkt.ans[0].name = pj_str("ahost");
    pkt.ans[0].type = PJ_DNS_TYPE_CNAME;
    pkt.ans[0].dnsclass = 1;
    pkt.ans[0].ttl = 1;
    pkt.ans[0].rdata.cname.name = pj_str("ahostalias");

    /* DNS A record, but the name doesn't match */
    pkt.ans[1].name = pj_str("ahost");
    pkt.ans[1].type = PJ_DNS_TYPE_A;
    pkt.ans[1].dnsclass = 1;
    pkt.ans[1].ttl = 1;
    pkt.ans[1].rdata.a.ip_addr.s_addr = 0x01020304;

    rc = pj_dns_parse_addr_response(&pkt, &rec);
    pj_assert(rc == PJLIB_UTIL_EDNSNOANSWERREC);
    PJ_UNUSED_ARG(rc);

    return 0;
}


////////////////////////////////////////////////////////////////////////////
/* Simple DNS test */
#define IP_ADDR0    0x00010203

static void dns_callback(void *user_data,
                         pj_status_t status,
                         pj_dns_parsed_packet *resp)
{
    PJ_UNUSED_ARG(user_data);

    pj_sem_post(sem);

    PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, return);
    PJ_ASSERT_ON_FAIL(resp, return);
    PJ_ASSERT_ON_FAIL(resp->hdr.anscount == 1, return);
    PJ_ASSERT_ON_FAIL(resp->ans[0].type == PJ_DNS_TYPE_A, return);
    PJ_ASSERT_ON_FAIL(resp->ans[0].rdata.a.ip_addr.s_addr == IP_ADDR0, return);

}


static int simple_test(void)
{
    pj_str_t name = pj_str("helloworld");
    pj_dns_parsed_packet *r;
    pj_status_t status;

    PJ_LOG(3,(THIS_FILE, "  simple successful test"));

    g_server[0].pkt_count = 0;
    g_server[1].pkt_count = 0;

    g_server[0].action = ACTION_REPLY;
    r = &g_server[0].resp;
    r->hdr.qdcount = 1;
    r->hdr.anscount = 1;
    r->q = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_query);
    r->q[0].type = PJ_DNS_TYPE_A;
    r->q[0].dnsclass = 1;
    r->q[0].name = name;
    r->ans = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_rr);
    r->ans[0].type = PJ_DNS_TYPE_A;
    r->ans[0].dnsclass = 1;
    r->ans[0].name = name;
    r->ans[0].rdata.a.ip_addr.s_addr = IP_ADDR0;

    g_server[1].action = ACTION_REPLY;
    r = &g_server[1].resp;
    r->hdr.qdcount = 1;
    r->hdr.anscount = 1;
    r->q = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_query);
    r->q[0].type = PJ_DNS_TYPE_A;
    r->q[0].dnsclass = 1;
    r->q[0].name = name;
    r->ans = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_rr);
    r->ans[0].type = PJ_DNS_TYPE_A;
    r->ans[0].dnsclass = 1;
    r->ans[0].name = name;
    r->ans[0].rdata.a.ip_addr.s_addr = IP_ADDR0;

    status = pj_dns_resolver_start_query(resolver, &name, PJ_DNS_TYPE_A, 0,
                                         &dns_callback, NULL, NULL);
    if (status != PJ_SUCCESS)
        return -1000;

    pj_sem_wait(sem);
    pj_thread_sleep(1000);


    /* Both servers must get packet */
    pj_assert(g_server[0].pkt_count == 1);
    pj_assert(g_server[1].pkt_count == 1);

    return 0;
}


////////////////////////////////////////////////////////////////////////////
/* DNS nameserver fail-over test */

static void dns_callback_1b(void *user_data,
                            pj_status_t status,
                            pj_dns_parsed_packet *resp)
{
    PJ_UNUSED_ARG(user_data);
    PJ_UNUSED_ARG(resp);

    pj_sem_post(sem);

    PJ_ASSERT_ON_FAIL(status==PJ_STATUS_FROM_DNS_RCODE(PJ_DNS_RCODE_NXDOMAIN),
                      return);
}




/* DNS test */
static int dns_test(void)
{
    pj_str_t name = pj_str("name00");
    pj_status_t status;

    PJ_LOG(3,(THIS_FILE, "  simple error response test"));

    g_server[0].pkt_count = 0;
    g_server[1].pkt_count = 0;

    g_server[0].action = PJ_DNS_RCODE_NXDOMAIN;
    g_server[1].action = PJ_DNS_RCODE_NXDOMAIN;

    status = pj_dns_resolver_start_query(resolver, &name, PJ_DNS_TYPE_A, 0,
                                         &dns_callback_1b, NULL, NULL);
    if (status != PJ_SUCCESS)
        return -1000;

    pj_sem_wait(sem);
    pj_thread_sleep(1000);

    /* Now only one of the servers should get packet, since both servers are
     * in STATE_ACTIVE state
     */
    pj_assert(g_server[0].pkt_count + g_server[1].pkt_count == 1);

    /* Wait to allow active period to complete and get into probing state */
    PJ_LOG(3,(THIS_FILE, "  waiting for active NS to expire (%d sec)",
                         set.good_ns_ttl));
    pj_thread_sleep(set.good_ns_ttl * 1000);

    /* 
     * Fail-over test 
     */
    PJ_LOG(3,(THIS_FILE, "  failing server0"));
    g_server[0].action = ACTION_IGNORE;
    g_server[1].action = PJ_DNS_RCODE_NXDOMAIN;

    g_server[0].pkt_count = 0;
    g_server[1].pkt_count = 0;

    name = pj_str("name01");
    status = pj_dns_resolver_start_query(resolver, &name, PJ_DNS_TYPE_A, 0,
                                         &dns_callback_1b, NULL, NULL);
    if (status != PJ_SUCCESS)
        return -1000;

    pj_sem_wait(sem);

    /* Both servers must get packet as both are in probing state */
    pj_assert(g_server[0].pkt_count >= 1 && g_server[1].pkt_count == 1);

    /*
     * Check that both servers still receive requests, since they are
     * in probing & active state.
     */
    PJ_LOG(3,(THIS_FILE, "  checking both NS during probing period"));
    g_server[0].action = ACTION_IGNORE;
    g_server[1].action = PJ_DNS_RCODE_NXDOMAIN;

    g_server[0].pkt_count = 0;
    g_server[1].pkt_count = 0;

    name = pj_str("name02");
    status = pj_dns_resolver_start_query(resolver, &name, PJ_DNS_TYPE_A, 0,
                                         &dns_callback_1b, NULL, NULL);
    if (status != PJ_SUCCESS)
        return -1000;

    pj_sem_wait(sem);
    pj_thread_sleep(1000);

    /* Both servers must get packet as both are in probing & active state */
    pj_assert(g_server[0].pkt_count >= 1 && g_server[1].pkt_count == 1);

    /* Wait to allow probing period to complete, server 0 will be in bad state */
    PJ_LOG(3,(THIS_FILE, "  waiting for probing state to end (%d sec)",
                         set.qretr_delay * 
                         (set.qretr_count+2) / 1000));
    pj_thread_sleep(set.qretr_delay * (set.qretr_count + 2));


    /*
     * Now only server 1 should get requests.
     */
    PJ_LOG(3,(THIS_FILE, "  verifying only good NS is used"));
    g_server[0].action = PJ_DNS_RCODE_NXDOMAIN;
    g_server[1].action = PJ_DNS_RCODE_NXDOMAIN;

    g_server[0].pkt_count = 0;
    g_server[1].pkt_count = 0;

    name = pj_str("name03");
    status = pj_dns_resolver_start_query(resolver, &name, PJ_DNS_TYPE_A, 0,
                                         &dns_callback_1b, NULL, NULL);
    if (status != PJ_SUCCESS)
        return -1000;

    pj_sem_wait(sem);
    pj_thread_sleep(1000);

    /* Only server 1 get the request */
    pj_assert(g_server[0].pkt_count == 0);
    pj_assert(g_server[1].pkt_count == 1);

    /* Wait to allow active & bad period to complete, both will be in probing state */
    PJ_LOG(3,(THIS_FILE, "  waiting for active NS to expire (%d sec)",
                         set.good_ns_ttl));
    pj_thread_sleep(set.good_ns_ttl * 1000);

    /*
     * Now fail server 1 to switch to server 0
     */
    g_server[0].action = PJ_DNS_RCODE_NXDOMAIN;
    g_server[1].action = ACTION_IGNORE;

    g_server[0].pkt_count = 0;
    g_server[1].pkt_count = 0;

    name = pj_str("name04");
    status = pj_dns_resolver_start_query(resolver, &name, PJ_DNS_TYPE_A, 0,
                                         &dns_callback_1b, NULL, NULL);
    if (status != PJ_SUCCESS)
        return -1000;

    pj_sem_wait(sem);

    /* Wait to allow probing period to complete, server 0 remains active, server 1 will be bad */
    PJ_LOG(3,(THIS_FILE, "  waiting for probing state (%d sec)",
                         set.qretr_delay * (set.qretr_count+2) / 1000));
    pj_thread_sleep(1000 + set.qretr_delay * (set.qretr_count + 2));

    /*
     * Now only server 0 should get requests.
     */
    PJ_LOG(3,(THIS_FILE, "  verifying good NS"));
    g_server[0].action = PJ_DNS_RCODE_NXDOMAIN;
    g_server[1].action = ACTION_IGNORE;

    g_server[0].pkt_count = 0;
    g_server[1].pkt_count = 0;

    name = pj_str("name05");
    status = pj_dns_resolver_start_query(resolver, &name, PJ_DNS_TYPE_A, 0,
                                         &dns_callback_1b, NULL, NULL);
    if (status != PJ_SUCCESS)
        return -1000;

    pj_sem_wait(sem);
    pj_thread_sleep(1000);

    /* Only good NS should get request */
    pj_assert(g_server[0].pkt_count == 1);
    pj_assert(g_server[1].pkt_count == 0);


    return 0;
}


////////////////////////////////////////////////////////////////////////////
/* Resolver test, normal, with CNAME */
#define IP_ADDR1    0x02030405
#define PORT1       50061

static void action1_1(const pj_dns_parsed_packet *pkt,
                      pj_dns_parsed_packet **p_res)
{
    pj_dns_parsed_packet *res;
    char *target = "sip.somedomain.com";

    res = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_packet);

    if (res->q == NULL) {
        res->q = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_query);
    }
    if (res->ans == NULL) {
        res->ans = (pj_dns_parsed_rr*) 
                  pj_pool_calloc(pool, 4, sizeof(pj_dns_parsed_rr));
    }

    res->hdr.qdcount = 1;
    res->q[0].type = pkt->q[0].type;
    res->q[0].dnsclass = pkt->q[0].dnsclass;
    res->q[0].name = pkt->q[0].name;

    if (pkt->q[0].type == PJ_DNS_TYPE_SRV) {

        pj_assert(pj_strcmp2(&pkt->q[0].name, "_sip._udp.somedomain.com")==0);

        res->hdr.anscount = 1;
        res->ans[0].type = PJ_DNS_TYPE_SRV;
        res->ans[0].dnsclass = 1;
        res->ans[0].name = res->q[0].name;
        res->ans[0].ttl = 1;
        res->ans[0].rdata.srv.prio = 1;
        res->ans[0].rdata.srv.weight = 2;
        res->ans[0].rdata.srv.port = PORT1;
        res->ans[0].rdata.srv.target = pj_str(target);

    } else if (pkt->q[0].type == PJ_DNS_TYPE_A) {
        char *alias = "sipalias.somedomain.com";

        pj_assert(pj_strcmp2(&res->q[0].name, target)==0);

        res->hdr.anscount = 2;
        res->ans[0].type = PJ_DNS_TYPE_CNAME;
        res->ans[0].dnsclass = 1;
        res->ans[0].ttl = 1000; /* resolver should select minimum TTL */
        res->ans[0].name = res->q[0].name;
        res->ans[0].rdata.cname.name = pj_str(alias);

        res->ans[1].type = PJ_DNS_TYPE_A;
        res->ans[1].dnsclass = 1;
        res->ans[1].ttl = 1;
        res->ans[1].name = pj_str(alias);
        res->ans[1].rdata.a.ip_addr.s_addr = IP_ADDR1;

    } else if (pkt->q[0].type == PJ_DNS_TYPE_AAAA) {
        char *alias = "sipalias.somedomain.com";

        pj_assert(pj_strcmp2(&res->q[0].name, target)==0);

        res->hdr.anscount = 2;
        res->ans[0].type = PJ_DNS_TYPE_CNAME;
        res->ans[0].dnsclass = 1;
        res->ans[0].ttl = 1000; /* resolver should select minimum TTL */
        res->ans[0].name = res->q[0].name;
        res->ans[0].rdata.cname.name = pj_str(alias);

        res->ans[1].type = PJ_DNS_TYPE_AAAA;
        res->ans[1].dnsclass = 1;
        res->ans[1].ttl = 1;
        res->ans[1].name = pj_str(alias);
        s6_addr32(res->ans[1].rdata.aaaa.ip_addr, 0) = IP_ADDR1;
        s6_addr32(res->ans[1].rdata.aaaa.ip_addr, 1) = IP_ADDR1;
        s6_addr32(res->ans[1].rdata.aaaa.ip_addr, 2) = IP_ADDR1;
        s6_addr32(res->ans[1].rdata.aaaa.ip_addr, 3) = IP_ADDR1;
    }

    *p_res = res;
}

static void srv_cb_1(void *user_data,
                     pj_status_t status,
                     const pj_dns_srv_record *rec)
{
    PJ_UNUSED_ARG(user_data);

    pj_sem_post(sem);

    PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, return);
    PJ_ASSERT_ON_FAIL(rec->count == 1, return);
    PJ_ASSERT_ON_FAIL(rec->entry[0].priority == 1, return);
    PJ_ASSERT_ON_FAIL(rec->entry[0].weight == 2, return);
    PJ_ASSERT_ON_FAIL(pj_strcmp2(&rec->entry[0].server.name, "sip.somedomain.com")==0,
                      return);
    PJ_ASSERT_ON_FAIL(pj_strcmp2(&rec->entry[0].server.alias, "sipalias.somedomain.com")==0,
                      return);

    /* IPv4 only */
    PJ_ASSERT_ON_FAIL(rec->entry[0].server.addr_count == 1, return);
    PJ_ASSERT_ON_FAIL(rec->entry[0].server.addr[0].ip.v4.s_addr == IP_ADDR1, return);
    PJ_ASSERT_ON_FAIL(rec->entry[0].port == PORT1, return);

    
}


static void srv_cb_1b(void *user_data,
                      pj_status_t status,
                      const pj_dns_srv_record *rec)
{
    PJ_UNUSED_ARG(user_data);

    pj_sem_post(sem);

    PJ_ASSERT_ON_FAIL(status==PJ_STATUS_FROM_DNS_RCODE(PJ_DNS_RCODE_NXDOMAIN),
                      return);
    PJ_ASSERT_ON_FAIL(rec->count == 0, return);
}


static void srv_cb_1c(void *user_data,
                      pj_status_t status,
                      const pj_dns_srv_record *rec)
{
    PJ_UNUSED_ARG(user_data);

    pj_sem_post(sem);

    PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, return);
    PJ_ASSERT_ON_FAIL(rec->count == 1, return);
    PJ_ASSERT_ON_FAIL(rec->entry[0].priority == 1, return);
    PJ_ASSERT_ON_FAIL(rec->entry[0].weight == 2, return);

    PJ_ASSERT_ON_FAIL(pj_strcmp2(&rec->entry[0].server.name, "sip.somedomain.com")==0,
                      return);
    PJ_ASSERT_ON_FAIL(pj_strcmp2(&rec->entry[0].server.alias, "sipalias.somedomain.com")==0,
                      return);
    PJ_ASSERT_ON_FAIL(rec->entry[0].port == PORT1, return);

    /* IPv4 and IPv6 */
    PJ_ASSERT_ON_FAIL(rec->entry[0].server.addr_count == 2, return);
    PJ_ASSERT_ON_FAIL(rec->entry[0].server.addr[0].af == pj_AF_INET() &&
                      rec->entry[0].server.addr[0].ip.v4.s_addr == IP_ADDR1, return);
    PJ_ASSERT_ON_FAIL(rec->entry[0].server.addr[1].af == pj_AF_INET6() &&
                      s6_addr32(rec->entry[0].server.addr[1].ip.v6, 0) == IP_ADDR1, return);
}


static void srv_cb_1d(void *user_data,
                      pj_status_t status,
                      const pj_dns_srv_record *rec)
{
    PJ_UNUSED_ARG(user_data);

    pj_sem_post(sem);

    PJ_ASSERT_ON_FAIL(status == PJ_SUCCESS, return);
    PJ_ASSERT_ON_FAIL(rec->count == 1, return);
    PJ_ASSERT_ON_FAIL(rec->entry[0].priority == 1, return);
    PJ_ASSERT_ON_FAIL(rec->entry[0].weight == 2, return);

    PJ_ASSERT_ON_FAIL(pj_strcmp2(&rec->entry[0].server.name, "sip.somedomain.com")==0,
                      return);
    PJ_ASSERT_ON_FAIL(pj_strcmp2(&rec->entry[0].server.alias, "sipalias.somedomain.com")==0,
                      return);
    PJ_ASSERT_ON_FAIL(rec->entry[0].port == PORT1, return);

    /* IPv6 only */
    PJ_ASSERT_ON_FAIL(rec->entry[0].server.addr_count == 1, return);
    PJ_ASSERT_ON_FAIL(rec->entry[0].server.addr[0].af == pj_AF_INET6() &&
                      s6_addr32(rec->entry[0].server.addr[0].ip.v6, 0) == IP_ADDR1, return);
}


static int srv_resolver_test(void)
{
    pj_status_t status;
    pj_str_t domain = pj_str("somedomain.com");
    pj_str_t res_name = pj_str("_sip._udp.");

    /* Last servers state: server 0=active, server 1=bad*/

    /* Successful scenario */
    PJ_LOG(3,(THIS_FILE, "  srv_resolve(): success scenario"));

    g_server[0].action = ACTION_CB;
    g_server[0].action_cb = &action1_1;
    g_server[1].action = ACTION_CB;
    g_server[1].action_cb = &action1_1;

    g_server[0].pkt_count = 0;
    g_server[1].pkt_count = 0;

    status = pj_dns_srv_resolve(&domain, &res_name, 5061, pool, resolver, PJ_TRUE,
                                NULL, &srv_cb_1, NULL);
    pj_assert(status == PJ_SUCCESS);

    pj_sem_wait(sem);

    /* Because of previous tests, only NS 1 should get the request */
    pj_assert(g_server[0].pkt_count == 2);  /* 2 because of SRV and A resolution */
    pj_assert(g_server[1].pkt_count == 0);


    /* Wait until cache expires */
    PJ_LOG(3,(THIS_FILE, "  waiting for cache to expire (~1 secs).."));
    pj_thread_sleep(1000 + 100);


    /* DNS SRV option PJ_DNS_SRV_RESOLVE_AAAA */
    PJ_LOG(3,(THIS_FILE, "  srv_resolve(): option PJ_DNS_SRV_RESOLVE_AAAA"));

    g_server[0].action = ACTION_CB;
    g_server[0].action_cb = &action1_1;
    g_server[1].action = ACTION_CB;
    g_server[1].action_cb = &action1_1;

    g_server[0].pkt_count = 0;
    g_server[1].pkt_count = 0;

    status = pj_dns_srv_resolve(&domain, &res_name, 5061, pool, resolver,
                                PJ_DNS_SRV_RESOLVE_AAAA,
                                NULL, &srv_cb_1c, NULL);
    pj_assert(status == PJ_SUCCESS);

    pj_sem_wait(sem);
    pj_thread_sleep(1000);

    /* DNS SRV option PJ_DNS_SRV_RESOLVE_AAAA_ONLY */
    PJ_LOG(3,(THIS_FILE, "  srv_resolve(): option PJ_DNS_SRV_RESOLVE_AAAA_ONLY"));

    g_server[0].action = ACTION_CB;
    g_server[0].action_cb = &action1_1;
    g_server[1].action = ACTION_CB;
    g_server[1].action_cb = &action1_1;

    g_server[0].pkt_count = 0;
    g_server[1].pkt_count = 0;

    status = pj_dns_srv_resolve(&domain, &res_name, 5061, pool, resolver,
                                PJ_DNS_SRV_RESOLVE_AAAA_ONLY,
                                NULL, &srv_cb_1d, NULL);
    pj_assert(status == PJ_SUCCESS);

    pj_sem_wait(sem);
    pj_thread_sleep(1000);


    /* Successful scenario */
    PJ_LOG(3,(THIS_FILE, "  srv_resolve(): parallel queries"));
    g_server[0].pkt_count = 0;
    g_server[1].pkt_count = 0;

    status = pj_dns_srv_resolve(&domain, &res_name, 5061, pool, resolver, PJ_TRUE,
                                NULL, &srv_cb_1, NULL);
    pj_assert(status == PJ_SUCCESS);


    status = pj_dns_srv_resolve(&domain, &res_name, 5061, pool, resolver, PJ_TRUE,
                                NULL, &srv_cb_1, NULL);
    pj_assert(status == PJ_SUCCESS);

    pj_sem_wait(sem);
    pj_sem_wait(sem);

    /* Only server one should get a query */
    pj_assert(g_server[0].pkt_count == 2);  /* 2 because of SRV and A resolution */
    pj_assert(g_server[1].pkt_count == 0);

    /* Since TTL is one, subsequent queries should fail */
    PJ_LOG(3,(THIS_FILE, "  srv_resolve(): cache expires scenario"));

    pj_thread_sleep(1000 + 100);

    g_server[0].action = PJ_DNS_RCODE_NXDOMAIN;
    g_server[1].action = PJ_DNS_RCODE_NXDOMAIN;

    status = pj_dns_srv_resolve(&domain, &res_name, 5061, pool, resolver, PJ_TRUE,
                                NULL, &srv_cb_1b, NULL);
    pj_assert(status == PJ_SUCCESS);

    pj_sem_wait(sem);
    pj_thread_sleep(1000);

    return status;
}


////////////////////////////////////////////////////////////////////////////
/* Fallback because there's no SRV in answer */
#define TARGET      "domain2.com"
#define IP_ADDR2    0x02030405
#define PORT2       50062

static void action2_1(const pj_dns_parsed_packet *pkt,
                      pj_dns_parsed_packet **p_res)
{
    pj_dns_parsed_packet *res;

    res = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_packet);

    res->q = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_query);
    res->ans = (pj_dns_parsed_rr*) 
               pj_pool_calloc(pool, 4, sizeof(pj_dns_parsed_rr));

    res->hdr.qdcount = 1;
    res->q[0].type = pkt->q[0].type;
    res->q[0].dnsclass = pkt->q[0].dnsclass;
    res->q[0].name = pkt->q[0].name;

    if (pkt->q[0].type == PJ_DNS_TYPE_SRV) {

        pj_assert(pj_strcmp2(&pkt->q[0].name, "_sip._udp." TARGET)==0);

        res->hdr.anscount = 1;
        res->ans[0].type = PJ_DNS_TYPE_A;    // <-- this will cause the fallback
        res->ans[0].dnsclass = 1;
        res->ans[0].name = res->q[0].name;
        res->ans[0].ttl = 1;
        res->ans[0].rdata.srv.prio = 1;
        res->ans[0].rdata.srv.weight = 2;
        res->ans[0].rdata.srv.port = PORT2;
        res->ans[0].rdata.srv.target = pj_str("sip01." TARGET);

    } else if (pkt->q[0].type == PJ_DNS_TYPE_A) {
        char *alias = "sipalias01." TARGET;

        pj_assert(pj_strcmp2(&res->q[0].name, TARGET)==0);

        res->hdr.anscount = 2;
        res->ans[0].type = PJ_DNS_TYPE_CNAME;
        res->ans[0].dnsclass = 1;
        res->ans[0].name = res->q[0].name;
        res->ans[0].ttl = 1;
        res->ans[0].rdata.cname.name = pj_str(alias);

        res->ans[1].type = PJ_DNS_TYPE_A;
        res->ans[1].dnsclass = 1;
        res->ans[1].name = pj_str(alias);
        res->ans[1].ttl = 1;
        res->ans[1].rdata.a.ip_addr.s_addr = IP_ADDR2;

    } else if (pkt->q[0].type == PJ_DNS_TYPE_AAAA) {
        char *alias = "sipalias01." TARGET;

        pj_assert(pj_strcmp2(&res->q[0].name, TARGET)==0);

        res->hdr.anscount = 2;
        res->ans[0].type = PJ_DNS_TYPE_CNAME;
        res->ans[0].dnsclass = 1;
        res->ans[0].name = res->q[0].name;
        res->ans[0].ttl = 1;
        res->ans[0].rdata.cname.name = pj_str(alias);

        res->ans[1].type = PJ_DNS_TYPE_AAAA;
        res->ans[1].dnsclass = 1;
        res->ans[1].ttl = 1;
        res->ans[1].name = pj_str(alias);
        s6_addr32(res->ans[1].rdata.aaaa.ip_addr, 0) = IP_ADDR2;
        s6_addr32(res->ans[1].rdata.aaaa.ip_addr, 1) = IP_ADDR2;
        s6_addr32(res->ans[1].rdata.aaaa.ip_addr, 2) = IP_ADDR2;
        s6_addr32(res->ans[1].rdata.aaaa.ip_addr, 3) = IP_ADDR2;
    }

    *p_res = res;
}

#define SRV_CB_CHECK(cond, err) if(!(cond)) { *cb_err=err; goto on_return; }

static void srv_cb_2(void *user_data,
                     pj_status_t status,
                     const pj_dns_srv_record *rec)
{
    int *cb_err = (int*)user_data;

    SRV_CB_CHECK(status == PJ_SUCCESS, -10);
    SRV_CB_CHECK(rec->count == 1, -20);
    SRV_CB_CHECK(rec->entry[0].priority == 0, -30);
    SRV_CB_CHECK(rec->entry[0].weight == 0, -40);
    SRV_CB_CHECK(pj_strcmp2(&rec->entry[0].server.name, TARGET)==0, -50);
    SRV_CB_CHECK(pj_strcmp2(&rec->entry[0].server.alias,
                            "sipalias01." TARGET)==0, -60);
    SRV_CB_CHECK(rec->entry[0].port == PORT2, -70);

    /* IPv4 only */
    SRV_CB_CHECK(rec->entry[0].server.addr_count == 1, -80);
    SRV_CB_CHECK(rec->entry[0].server.addr[0].af == pj_AF_INET() &&
                 rec->entry[0].server.addr[0].ip.v4.s_addr == IP_ADDR2, -90);

on_return:
    pj_sem_post(sem);
}

static void srv_cb_2a(void *user_data,
                      pj_status_t status,
                      const pj_dns_srv_record *rec)
{
    int *cb_err = (int*)user_data;

    SRV_CB_CHECK(status == PJ_SUCCESS, -10);
    SRV_CB_CHECK(rec->count == 1, -20);
    SRV_CB_CHECK(rec->entry[0].priority == 0, -30);
    SRV_CB_CHECK(rec->entry[0].weight == 0, -40);
    SRV_CB_CHECK(pj_strcmp2(&rec->entry[0].server.name, TARGET)==0, -50);
    SRV_CB_CHECK(pj_strcmp2(&rec->entry[0].server.alias,
                            "sipalias01." TARGET)==0, -60);
    SRV_CB_CHECK(rec->entry[0].port == PORT2, -70);

    /* IPv4 and IPv6 */
    SRV_CB_CHECK(rec->entry[0].server.addr_count == 2, -80);
    SRV_CB_CHECK(rec->entry[0].server.addr[0].af == pj_AF_INET() &&
                 rec->entry[0].server.addr[0].ip.v4.s_addr == IP_ADDR2, -90);
    SRV_CB_CHECK(rec->entry[0].server.addr[1].af == pj_AF_INET6() &&
                 s6_addr32(rec->entry[0].server.addr[1].ip.v6, 0) == IP_ADDR2,
                 -100);

on_return:
    pj_sem_post(sem);
}

static void srv_cb_2b(void *user_data,
                      pj_status_t status,
                      const pj_dns_srv_record *rec)
{
    int *cb_err = (int*)user_data;

    SRV_CB_CHECK(status == PJ_SUCCESS, -10);
    SRV_CB_CHECK(rec->count == 1, -20);
    SRV_CB_CHECK(rec->entry[0].priority == 0, -30);
    SRV_CB_CHECK(rec->entry[0].weight == 0, -40);
    SRV_CB_CHECK(pj_strcmp2(&rec->entry[0].server.name, TARGET)==0, -50);
    SRV_CB_CHECK(pj_strcmp2(&rec->entry[0].server.alias,
                            "sipalias01." TARGET)==0, -60);
    SRV_CB_CHECK(rec->entry[0].port == PORT2, -70);

    /* IPv6 only */
    SRV_CB_CHECK(rec->entry[0].server.addr_count == 1, -80);
    SRV_CB_CHECK(rec->entry[0].server.addr[0].af == pj_AF_INET6() &&
                 s6_addr32(rec->entry[0].server.addr[0].ip.v6, 0) == IP_ADDR2,
                 -90);

on_return:
    pj_sem_post(sem);
}

static int srv_resolver_fallback_test(void)
{
    pj_status_t status;
    pj_str_t domain = pj_str(TARGET);
    pj_str_t res_name = pj_str("_sip._udp.");
    int cb_err = 0;

    /* Fallback test */
    PJ_LOG(3,(THIS_FILE, "  srv_resolve(): fallback test"));

    g_server[0].action = ACTION_CB;
    g_server[0].action_cb = &action2_1;
    g_server[1].action = ACTION_CB;
    g_server[1].action_cb = &action2_1;

    status = pj_dns_srv_resolve(&domain, &res_name, PORT2, pool, resolver, PJ_TRUE,
                                &cb_err, &srv_cb_2, NULL);
    if (status != PJ_SUCCESS) {
        app_perror("   srv_resolve error", status);
        return -10;
    }

    pj_sem_wait(sem);

    if (cb_err != 0) {
        PJ_LOG(3,("test", "   srv_resolve cb error, code=%d", cb_err));
        return -20;
    }

    /* Subsequent query should just get the response from the cache */
    PJ_LOG(3,(THIS_FILE, "  srv_resolve(): cache test"));
    g_server[0].pkt_count = 0;
    g_server[1].pkt_count = 0;

    status = pj_dns_srv_resolve(&domain, &res_name, PORT2, pool, resolver, PJ_TRUE,
                                &cb_err, &srv_cb_2, NULL);
    if (status != PJ_SUCCESS) {
        app_perror("   srv_resolve error", status);
        return -30;
    }

    pj_sem_wait(sem);

    if (cb_err != 0) {
        PJ_LOG(3,("test", "   srv_resolve cb error, code=%d", cb_err));
        return -40;
    }

    if (g_server[0].pkt_count != 0 || g_server[1].pkt_count != 0) {
        PJ_LOG(3,("test", "   srv_resolve() not from cache"));
        return -50;
    }

    /* Clear cache */
    pj_thread_sleep(1000);

    /* Fallback with PJ_DNS_SRV_FALLBACK_A and PJ_DNS_SRV_FALLBACK_AAAA */
    PJ_LOG(3,(THIS_FILE, "  srv_resolve(): fallback to DNS A and AAAA"));

    g_server[0].action = ACTION_CB;
    g_server[0].action_cb = &action2_1;
    g_server[1].action = ACTION_CB;
    g_server[1].action_cb = &action2_1;

    status = pj_dns_srv_resolve(&domain, &res_name, PORT2, pool, resolver,
                                PJ_DNS_SRV_FALLBACK_A | PJ_DNS_SRV_FALLBACK_AAAA,
                                &cb_err, &srv_cb_2a, NULL);
    if (status != PJ_SUCCESS) {
        app_perror("   srv_resolve error", status);
        return -60;
    }

    pj_sem_wait(sem);

    if (cb_err != 0) {
        PJ_LOG(3,("test", "   srv_resolve cb error, code=%d", cb_err));
        return -70;
    }

    /* Clear cache */
    pj_thread_sleep(1000);

    /* Fallback with PJ_DNS_SRV_FALLBACK_AAAA only */
    PJ_LOG(3,(THIS_FILE, "  srv_resolve(): fallback to DNS AAAA only"));

    g_server[0].action = ACTION_CB;
    g_server[0].action_cb = &action2_1;
    g_server[1].action = ACTION_CB;
    g_server[1].action_cb = &action2_1;

    status = pj_dns_srv_resolve(&domain, &res_name, PORT2, pool, resolver,
                                PJ_DNS_SRV_FALLBACK_AAAA,
                                &cb_err, &srv_cb_2b, NULL);
    if (status != PJ_SUCCESS) {
        app_perror("   srv_resolve error", status);
        return -80;
    }

    pj_sem_wait(sem);

    if (cb_err != 0) {
        PJ_LOG(3,("test", "   srv_resolve cb error, code=%d", cb_err));
        return -90;
    }

    /* Clear cache */
    pj_thread_sleep(1000);

    return 0;
}


////////////////////////////////////////////////////////////////////////////
/* Too many SRV or A entries */
#define DOMAIN3     "d3"
#define SRV_COUNT3  (PJ_DNS_SRV_MAX_ADDR+1)
#define A_COUNT3    (PJ_DNS_MAX_IP_IN_A_REC+1)
#define PORT3       50063
#define IP_ADDR3    0x03030303

static void action3_1(const pj_dns_parsed_packet *pkt,
                      pj_dns_parsed_packet **p_res)
{
    pj_dns_parsed_packet *res;
    unsigned i;

    res = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_packet);

    if (res->q == NULL) {
        res->q = PJ_POOL_ZALLOC_T(pool, pj_dns_parsed_query);
    }

    res->hdr.qdcount = 1;
    res->q[0].type = pkt->q[0].type;
    res->q[0].dnsclass = pkt->q[0].dnsclass;
    res->q[0].name = pkt->q[0].name;

    if (pkt->q[0].type == PJ_DNS_TYPE_SRV) {

        pj_assert(pj_strcmp2(&pkt->q[0].name, "_sip._udp." DOMAIN3)==0);

        res->hdr.anscount = SRV_COUNT3;
        res->ans = (pj_dns_parsed_rr*) 
                   pj_pool_calloc(pool, SRV_COUNT3, sizeof(pj_dns_parsed_rr));

        for (i=0; i<SRV_COUNT3; ++i) {
            char *target;

            res->ans[i].type = PJ_DNS_TYPE_SRV;
            res->ans[i].dnsclass = 1;
            res->ans[i].name = res->q[0].name;
            res->ans[i].ttl = 1;
            res->ans[i].rdata.srv.prio = (pj_uint16_t)i;
            res->ans[i].rdata.srv.weight = 2;
            res->ans[i].rdata.srv.port = (pj_uint16_t)(PORT3+i);

            target = (char*)pj_pool_alloc(pool, 16);
            pj_ansi_snprintf(target, 16, "sip%02d." DOMAIN3, i);
            res->ans[i].rdata.srv.target = pj_str(target);
        }

    } else if (pkt->q[0].type == PJ_DNS_TYPE_A) {

        //pj_assert(pj_strcmp2(&res->q[0].name, "sip." DOMAIN3)==0);

        res->hdr.anscount = A_COUNT3;
        res->ans = (pj_dns_parsed_rr*) 
                   pj_pool_calloc(pool, A_COUNT3, sizeof(pj_dns_parsed_rr));

        for (i=0; i<A_COUNT3; ++i) {
            res->ans[i].type = PJ_DNS_TYPE_A;
            res->ans[i].dnsclass = 1;
            res->ans[i].ttl = 1;
            res->ans[i].name = res->q[0].name;
            res->ans[i].rdata.a.ip_addr.s_addr = IP_ADDR3+i;
        }
    }

    *p_res = res;
}

static void srv_cb_3(void *user_data,
                     pj_status_t status,
                     const pj_dns_srv_record *rec)
{
    unsigned i;
    int *cb_err = (int*)user_data;

    PJ_UNUSED_ARG(status);
    PJ_UNUSED_ARG(rec);

    SRV_CB_CHECK(status == PJ_SUCCESS, -10);
    SRV_CB_CHECK(rec->count == PJ_DNS_SRV_MAX_ADDR, -20);

    for (i=0; i<PJ_DNS_SRV_MAX_ADDR; ++i) {
        unsigned j;

        SRV_CB_CHECK(rec->entry[i].priority == i, -30);
        SRV_CB_CHECK(rec->entry[i].weight == 2, -40);
        //pj_assert(pj_strcmp2(&rec->entry[i].server.name, "sip." DOMAIN3)==0);
        SRV_CB_CHECK(rec->entry[i].server.alias.slen == 0, -50);
        SRV_CB_CHECK(rec->entry[i].port == PORT3+i, -60);

        SRV_CB_CHECK(rec->entry[i].server.addr_count == PJ_DNS_MAX_IP_IN_A_REC, -70);

        for (j=0; j<PJ_DNS_MAX_IP_IN_A_REC; ++j) {
            SRV_CB_CHECK(rec->entry[i].server.addr[j].ip.v4.s_addr == IP_ADDR3+j, -80);
        }
    }

on_return:
    pj_sem_post(sem);
}

static int srv_resolver_many_test(void)
{
    pj_status_t status;
    pj_str_t domain = pj_str(DOMAIN3);
    pj_str_t res_name = pj_str("_sip._udp.");
    int cb_err = 0;

    /* Successful scenario */
    PJ_LOG(3,(THIS_FILE, "  srv_resolve(): too many entries test"));

    g_server[0].action = ACTION_CB;
    g_server[0].action_cb = &action3_1;
    g_server[1].action = ACTION_CB;
    g_server[1].action_cb = &action3_1;

    g_server[0].pkt_count = 0;
    g_server[1].pkt_count = 0;

    status = pj_dns_srv_resolve(&domain, &res_name, 1, pool, resolver, PJ_TRUE,
                                &cb_err, &srv_cb_3, NULL);
    if (status != PJ_SUCCESS) {
        app_perror("   srv_resolve error", status);
        return -10;
    }

    pj_sem_wait(sem);

    if (cb_err != 0) {
        PJ_LOG(3,("test", "   srv_resolve cb error, code=%d", cb_err));
        return -20;
    }

    return 0;
}


////////////////////////////////////////////////////////////////////////////


int resolver_test(void)
{
    int rc;
    
    rc = init(PJ_FALSE);
    if (rc != 0)
        goto on_error;

    rc = a_parser_test();
    if (rc != 0)
        goto on_error;

    rc = addr_parser_test();
    if (rc != 0)
        goto on_error;

    rc = simple_test();
    if (rc != 0)
        goto on_error;

    rc = dns_test();
    if (rc != 0)
        goto on_error;

    rc = srv_resolver_test();
    if (rc != 0)
        goto on_error;

    rc = srv_resolver_fallback_test();
    if (rc != 0)
        goto on_error;

    rc = srv_resolver_many_test();
    if (rc != 0)
        goto on_error;

    destroy();


#if PJ_HAS_IPV6
    /* Similar tests using IPv6 socket and without parser tests */
    PJ_LOG(3,(THIS_FILE, "Re-run DNS resolution tests using IPv6 socket"));

    rc = init(PJ_TRUE);
    if (rc != 0)
        goto on_error;

    rc = simple_test();
    if (rc != 0)
        goto on_error;

    rc = dns_test();
    if (rc != 0)
        goto on_error;

    rc = srv_resolver_test();
    if (rc != 0)
        goto on_error;

    rc = srv_resolver_fallback_test();
    if (rc != 0)
        goto on_error;

    rc = srv_resolver_many_test();
    if (rc != 0)
        goto on_error;

    destroy();
#endif

    return 0;

on_error:
    destroy();
    return rc;
}

