/* 
 * 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"

#define THIS_FILE   "stun_sock_test.c"

enum {
    RESPOND_STUN    = 1,
    WITH_MAPPED     = 2,
    WITH_XOR_MAPPED = 4,
    ECHO            = 8
};

/*
 * Simple STUN server
 */
struct stun_srv
{
    pj_activesock_t     *asock;
    unsigned             flag;
    pj_sockaddr          addr;
    unsigned             rx_cnt;
    pj_ioqueue_op_key_t  send_key;
    pj_str_t             ip_to_send;
    pj_uint16_t          port_to_send;
};

static pj_bool_t srv_on_data_recvfrom(pj_activesock_t *asock,
                                      void *data,
                                      pj_size_t size,
                                      const pj_sockaddr_t *src_addr,
                                      int addr_len,
                                      pj_status_t status)
{
    struct stun_srv *srv;
    pj_ssize_t sent;

    srv = (struct stun_srv*) pj_activesock_get_user_data(asock);

    /* Ignore error */
    if (status != PJ_SUCCESS)
        return PJ_TRUE;

    ++srv->rx_cnt;

    /* Ignore if we're not responding */
    if (srv->flag & RESPOND_STUN) {
        pj_pool_t *pool;
        pj_stun_msg *req_msg, *res_msg;

        pool = pj_pool_create(mem, "stunsrv", 512, 512, NULL);
    
        /* Parse request */
        status = pj_stun_msg_decode(pool, (pj_uint8_t*)data, size, 
                                    PJ_STUN_IS_DATAGRAM | PJ_STUN_CHECK_PACKET,
                                    &req_msg, NULL, NULL);
        if (status != PJ_SUCCESS) {
            app_perror("   pj_stun_msg_decode()", status);
            pj_pool_release(pool);
            return PJ_TRUE;
        }

        /* Create response */
        status = pj_stun_msg_create(pool, PJ_STUN_BINDING_RESPONSE, PJ_STUN_MAGIC,
                                    req_msg->hdr.tsx_id, &res_msg);
        if (status != PJ_SUCCESS) {
            app_perror("   pj_stun_msg_create()", status);
            pj_pool_release(pool);
            return PJ_TRUE;
        }

        /* Add MAPPED-ADDRESS or XOR-MAPPED-ADDRESS (or don't add) */
        if (srv->flag & WITH_MAPPED) {
            pj_sockaddr addr;
            pj_bool_t use_ipv6 = (srv->addr.addr.sa_family == pj_AF_INET6());
            
            pj_sockaddr_init(GET_AF(use_ipv6), &addr, &srv->ip_to_send, 
                             srv->port_to_send);

            pj_stun_msg_add_sockaddr_attr(pool, res_msg, PJ_STUN_ATTR_MAPPED_ADDR,
                                          PJ_FALSE, &addr, sizeof(addr));
        } else if (srv->flag & WITH_XOR_MAPPED) {
            pj_sockaddr addr;
            pj_bool_t use_ipv6 = (srv->addr.addr.sa_family == pj_AF_INET6());
            
            pj_sockaddr_init(GET_AF(use_ipv6), &addr, &srv->ip_to_send, 
                             srv->port_to_send);

            pj_stun_msg_add_sockaddr_attr(pool, res_msg, 
                                          PJ_STUN_ATTR_XOR_MAPPED_ADDR,
                                          PJ_TRUE, &addr, sizeof(addr));
        }

        /* Encode */
        status = pj_stun_msg_encode(res_msg, (pj_uint8_t*)data, 100, 0, 
                                    NULL, &size);
        if (status != PJ_SUCCESS) {
            app_perror("   pj_stun_msg_encode()", status);
            pj_pool_release(pool);
            return PJ_TRUE;
        }

        /* Send back */
        sent = size;
        pj_activesock_sendto(asock, &srv->send_key, data, &sent, 0, 
                             src_addr, addr_len);

        pj_pool_release(pool);

    } else if (srv->flag & ECHO) {
        /* Send back */
        sent = size;
        pj_activesock_sendto(asock, &srv->send_key, data, &sent, 0, 
                             src_addr, addr_len);

    }

    return PJ_TRUE;
}

static pj_status_t create_server(pj_pool_t *pool,
                                 pj_ioqueue_t *ioqueue,
                                 unsigned flag,
                                 pj_bool_t use_ipv6,
                                 struct stun_srv **p_srv)
{
    struct stun_srv *srv;
    pj_activesock_cb activesock_cb;
    pj_status_t status;

    srv = PJ_POOL_ZALLOC_T(pool, struct stun_srv);
    srv->flag = flag;
    srv->ip_to_send = (use_ipv6)?pj_str("2002:101:101::"):pj_str("1.1.1.1");
    srv->port_to_send = 1000;

    status = pj_sockaddr_init(GET_AF(use_ipv6), &srv->addr, NULL, 0);
    if (status != PJ_SUCCESS)
        return status;

    pj_bzero(&activesock_cb, sizeof(activesock_cb));
    activesock_cb.on_data_recvfrom = &srv_on_data_recvfrom;
    status = pj_activesock_create_udp(pool, &srv->addr, NULL, ioqueue,
                                      &activesock_cb, srv, &srv->asock, 
                                      &srv->addr);
    if (status != PJ_SUCCESS)
        return status;

    pj_ioqueue_op_key_init(&srv->send_key, sizeof(srv->send_key));

    status = pj_activesock_start_recvfrom(srv->asock, pool, 512, 0);
    if (status != PJ_SUCCESS) {
        pj_activesock_close(srv->asock);
        return status;
    }

    *p_srv = srv;
    return PJ_SUCCESS;
}

static void destroy_server(struct stun_srv *srv)
{
    pj_activesock_close(srv->asock);
}


struct stun_client
{
    pj_pool_t           *pool;
    pj_stun_sock        *sock;

    pj_ioqueue_op_key_t  send_key;
    pj_bool_t            destroy_on_err;

    unsigned             on_status_cnt;
    pj_stun_sock_op      last_op;
    pj_status_t          last_status;

    unsigned             on_rx_data_cnt;
};

static pj_bool_t stun_sock_on_status(pj_stun_sock *stun_sock, 
                                     pj_stun_sock_op op,
                                     pj_status_t status)
{
    struct stun_client *client;

    client = (struct stun_client*) pj_stun_sock_get_user_data(stun_sock);
    client->on_status_cnt++;
    client->last_op = op;
    client->last_status = status;

    if (status != PJ_SUCCESS && client->destroy_on_err) {
        pj_stun_sock_destroy(client->sock);
        client->sock = NULL;
        return PJ_FALSE;
    }

    return PJ_TRUE;
}

static pj_bool_t stun_sock_on_rx_data(pj_stun_sock *stun_sock,
                                      void *pkt,
                                      unsigned pkt_len,
                                      const pj_sockaddr_t *src_addr,
                                      unsigned addr_len)
{
    struct stun_client *client;

    PJ_UNUSED_ARG(pkt);
    PJ_UNUSED_ARG(pkt_len);
    PJ_UNUSED_ARG(src_addr);
    PJ_UNUSED_ARG(addr_len);

    client = (struct stun_client*) pj_stun_sock_get_user_data(stun_sock);
    client->on_rx_data_cnt++;

    return PJ_TRUE;
}

static pj_status_t create_client(pj_stun_config *cfg,
                                 struct stun_client **p_client,
                                 pj_bool_t destroy_on_err,
                                 pj_bool_t use_ipv6)
{
    pj_pool_t *pool;
    struct stun_client *client;
    pj_stun_sock_cfg sock_cfg;
    pj_stun_sock_cb cb;
    pj_status_t status;

    pool = pj_pool_create(mem, "test", 512, 512, NULL);
    client = PJ_POOL_ZALLOC_T(pool, struct stun_client);
    client->pool = pool;

    pj_stun_sock_cfg_default(&sock_cfg);

    pj_bzero(&cb, sizeof(cb));
    cb.on_status = &stun_sock_on_status;
    cb.on_rx_data = &stun_sock_on_rx_data;
    status = pj_stun_sock_create(cfg, NULL, GET_AF(use_ipv6), &cb, &sock_cfg, 
                                 client, &client->sock);
    if (status != PJ_SUCCESS) {
        app_perror("   pj_stun_sock_create()", status);
        pj_pool_release(pool);
        return status;
    }

    pj_stun_sock_set_user_data(client->sock, client);

    pj_ioqueue_op_key_init(&client->send_key, sizeof(client->send_key));

    client->destroy_on_err = destroy_on_err;

    *p_client = client;

    return PJ_SUCCESS;
}


static void destroy_client(struct stun_client *client)
{
    if (client->sock) {
        pj_stun_sock_destroy(client->sock);
        client->sock = NULL;
    }
    pj_pool_release(client->pool);
}

static void handle_events(pj_stun_config *cfg, unsigned msec_delay)
{
    pj_time_val delay;

    pj_timer_heap_poll(cfg->timer_heap, NULL);

    delay.sec = 0;
    delay.msec = msec_delay;
    pj_time_val_normalize(&delay);

    pj_ioqueue_poll(cfg->ioqueue, &delay);
}

/*
 * Timeout test: scenario when no response is received from server
 */
static int timeout_test(pj_stun_config *cfg, pj_bool_t destroy_on_err, 
                        pj_bool_t use_ipv6)
{
    struct stun_srv *srv;
    struct stun_client *client;
    pj_str_t srv_addr;
    pj_time_val timeout, t;
    int i, ret = 0;
    pj_status_t status;

    PJ_LOG(3,(THIS_FILE, "  timeout test [%d] - (%s)", destroy_on_err, 
           (use_ipv6)?"IPv6":"IPv4"));

    status =  create_client(cfg, &client, destroy_on_err, use_ipv6);
    if (status != PJ_SUCCESS)
        return -10;

    status = create_server(client->pool, cfg->ioqueue, 0, use_ipv6, &srv);
    if (status != PJ_SUCCESS) {
        destroy_client(client);
        return -20;
    }

    srv_addr = (use_ipv6)?pj_str("::1"):pj_str("127.0.0.1");

    status = pj_stun_sock_start(client->sock, &srv_addr, 
                                pj_sockaddr_get_port(&srv->addr), NULL);
    if (status != PJ_SUCCESS) {
        destroy_server(srv);
        destroy_client(client);
        return -30;
    }

    /* Wait until on_status() callback is called with the failure */
    pj_gettimeofday(&timeout);
    timeout.sec += 60;
    do {
        handle_events(cfg, 100);
        pj_gettimeofday(&t);
    } while (client->on_status_cnt==0 && PJ_TIME_VAL_LT(t, timeout));

    /* Check that callback with correct operation is called */
    if (client->last_op != PJ_STUN_SOCK_BINDING_OP) {
        PJ_LOG(3,(THIS_FILE, "    error: expecting Binding operation status"));
        ret = -40;
        goto on_return;
    }
    /* .. and with the correct status */
    if (client->last_status != PJNATH_ESTUNTIMEDOUT) {
        PJ_LOG(3,(THIS_FILE, "    error: expecting PJNATH_ESTUNTIMEDOUT"));
        ret = -50;
        goto on_return;
    }
    /* Check that server received correct retransmissions */
    if (srv->rx_cnt != PJ_STUN_MAX_TRANSMIT_COUNT) {
        PJ_LOG(3,(THIS_FILE, "    error: expecting %d retransmissions, got %d",
                   PJ_STUN_MAX_TRANSMIT_COUNT, srv->rx_cnt));
        ret = -60;
        goto on_return;
    }
    /* Check that client doesn't receive anything */
    if (client->on_rx_data_cnt != 0) {
        PJ_LOG(3,(THIS_FILE, "    error: client shouldn't have received anything"));
        ret = -70;
        goto on_return;
    }

on_return:
    destroy_server(srv);
    destroy_client(client);
    for (i=0; i<7; ++i)
        handle_events(cfg, 100);

    if ((ret == 0) && use_ipv6)
        ret = timeout_test(cfg, destroy_on_err, 0);

    return ret;
}


/*
 * Invalid response scenario: when server returns no MAPPED-ADDRESS or
 * XOR-MAPPED-ADDRESS attribute.
 */
static int missing_attr_test(pj_stun_config *cfg, pj_bool_t destroy_on_err, 
                             pj_bool_t use_ipv6)
{
    struct stun_srv *srv;
    struct stun_client *client;
    pj_str_t srv_addr;
    pj_time_val timeout, t;
    int i, ret = 0;
    pj_status_t status;

    PJ_LOG(3,(THIS_FILE, "  missing attribute test [%d] - (%s)", 
           destroy_on_err, (use_ipv6)?"IPv6":"IPv4"));

    status =  create_client(cfg, &client, destroy_on_err, use_ipv6);
    if (status != PJ_SUCCESS)
        return -110;

    status = create_server(client->pool, cfg->ioqueue, RESPOND_STUN, use_ipv6, 
                           &srv);
    if (status != PJ_SUCCESS) {
        destroy_client(client);
        return -120;
    }
    srv_addr = (use_ipv6)?pj_str("::1"):pj_str("127.0.0.1");

    status = pj_stun_sock_start(client->sock, &srv_addr, 
                                pj_sockaddr_get_port(&srv->addr), NULL);                                
    if (status != PJ_SUCCESS) {
        destroy_server(srv);
        destroy_client(client);
        return -130;
    }

    /* Wait until on_status() callback is called with the failure */
    pj_gettimeofday(&timeout);
    timeout.sec += 60;
    do {
        handle_events(cfg, 100);
        pj_gettimeofday(&t);
    } while (client->on_status_cnt==0 && PJ_TIME_VAL_LT(t, timeout));

    /* Check that callback with correct operation is called */
    if (client->last_op != PJ_STUN_SOCK_BINDING_OP) {
        PJ_LOG(3,(THIS_FILE, "    error: expecting Binding operation status"));
        ret = -140;
        goto on_return;
    }
    if (client->last_status != PJNATH_ESTUNNOMAPPEDADDR) {
        PJ_LOG(3,(THIS_FILE, "    error: expecting PJNATH_ESTUNNOMAPPEDADDR"));
        ret = -150;
        goto on_return;
    }
    /* Check that client doesn't receive anything */
    if (client->on_rx_data_cnt != 0) {
        PJ_LOG(3,(THIS_FILE, "    error: client shouldn't have received anything"));
        ret = -170;
        goto on_return;
    }

on_return:
    destroy_server(srv);
    destroy_client(client);
    for (i=0; i<7; ++i)
        handle_events(cfg, 100);

    if ((ret == 0) && use_ipv6)
        ret = missing_attr_test(cfg, destroy_on_err, 0);

    return ret;
}

/*
 * Keep-alive test.
 */
static int keep_alive_test(pj_stun_config *cfg, pj_bool_t use_ipv6)
{
    struct stun_srv *srv;
    struct stun_client *client;
    pj_sockaddr mapped_addr;
    pj_stun_sock_info info;
    pj_str_t srv_addr;
    pj_time_val timeout, t;
    int i, ret = 0;
    pj_status_t status;

    PJ_LOG(3,(THIS_FILE, "  normal operation - (%s)", 
           (use_ipv6)?"IPv6":"IPv4"));

    status =  create_client(cfg, &client, PJ_TRUE, use_ipv6);
    if (status != PJ_SUCCESS)
        return -310;

    status = create_server(client->pool, cfg->ioqueue, RESPOND_STUN|WITH_XOR_MAPPED, 
                           use_ipv6, &srv);
    if (status != PJ_SUCCESS) {
        destroy_client(client);
        return -320;
    }

    /*
     * Part 1: initial Binding resolution.
     */
    PJ_LOG(3,(THIS_FILE, "    initial Binding request"));

    srv_addr = (use_ipv6)?pj_str("::1"):pj_str("127.0.0.1");

    status = pj_stun_sock_start(client->sock, &srv_addr, 
                                pj_sockaddr_get_port(&srv->addr), NULL);
    if (status != PJ_SUCCESS) {
        destroy_server(srv);
        destroy_client(client);
        return -330;
    }

    /* Wait until on_status() callback is called with success status */
    pj_gettimeofday(&timeout);
    timeout.sec += 60;
    do {
        handle_events(cfg, 100);
        pj_gettimeofday(&t);
    } while (client->on_status_cnt==0 && PJ_TIME_VAL_LT(t, timeout));

    /* Check that callback with correct operation is called */
    if (client->last_op != PJ_STUN_SOCK_BINDING_OP) {
        PJ_LOG(3,(THIS_FILE, "    error: expecting Binding operation status"));
        ret = -340;
        goto on_return;
    }
    if (client->last_status != PJ_SUCCESS) {
        PJ_LOG(3,(THIS_FILE, "    error: expecting PJ_SUCCESS status"));
        ret = -350;
        goto on_return;
    }
    /* Check that client doesn't receive anything */
    if (client->on_rx_data_cnt != 0) {
        PJ_LOG(3,(THIS_FILE, "    error: client shouldn't have received anything"));
        ret = -370;
        goto on_return;
    }

    /* Get info */
    pj_bzero(&info, sizeof(info));
    pj_stun_sock_get_info(client->sock, &info);

    /* Check that we have server address */
    if (!pj_sockaddr_has_addr(&info.srv_addr)) {
        PJ_LOG(3,(THIS_FILE, "    error: missing server address"));
        ret = -380;
        goto on_return;
    }
    /* .. and bound address port must not be zero */
    if (pj_sockaddr_get_port(&info.bound_addr)==0) {
        PJ_LOG(3,(THIS_FILE, "    error: bound address is zero"));
        ret = -381;
        goto on_return;
    }
    /* .. and mapped address */
    if (!pj_sockaddr_has_addr(&info.mapped_addr)) {
        PJ_LOG(3,(THIS_FILE, "    error: missing mapped address"));
        ret = -382;
        goto on_return;
    }
    /* verify the mapped address */
    pj_sockaddr_init(GET_AF(use_ipv6), &mapped_addr, 
                     &srv->ip_to_send, srv->port_to_send);
    if (pj_sockaddr_cmp(&info.mapped_addr, &mapped_addr) != 0) {
        PJ_LOG(3,(THIS_FILE, "    error: mapped address mismatched"));
        ret = -383;
        goto on_return;
    }

    /* .. and at least one alias */
    if (info.alias_cnt == 0) {
        PJ_LOG(3,(THIS_FILE, "    error: must have at least one alias"));
        ret = -384;
        goto on_return;
    }
    if (!pj_sockaddr_has_addr(&info.aliases[0])) {
        PJ_LOG(3,(THIS_FILE, "    error: missing alias"));
        ret = -386;
        goto on_return;
    }


    /*
     * Part 2: sending and receiving data
     */
    PJ_LOG(3,(THIS_FILE, "    sending/receiving data"));

    /* Change server operation mode to echo back data */
    srv->flag = ECHO;

    /* Reset server */
    srv->rx_cnt = 0;

    /* Client sending data to echo server */
    {
        char txt[100];
        PJ_LOG(3,(THIS_FILE, "     sending to %s", pj_sockaddr_print(&info.srv_addr, txt, sizeof(txt), 3)));
    }
    status = pj_stun_sock_sendto(client->sock, NULL, &ret, sizeof(ret),
                                 0, &info.srv_addr, 
                                 pj_sockaddr_get_len(&info.srv_addr));
    if (status != PJ_SUCCESS && status != PJ_EPENDING) {
        app_perror("    error: server sending data", status);
        ret = -390;
        goto on_return;
    }

    /* Wait for a short period until client receives data. We can't wait for
     * too long otherwise the keep-alive will kick in.
     */
    pj_gettimeofday(&timeout);
    timeout.sec += 1;
    do {
        handle_events(cfg, 100);
        pj_gettimeofday(&t);
    } while (client->on_rx_data_cnt==0 && PJ_TIME_VAL_LT(t, timeout));

    /* Check that data is received in server */
    if (srv->rx_cnt == 0) {
        PJ_LOG(3,(THIS_FILE, "    error: server didn't receive data"));
        ret = -395;
        goto on_return;
    }

    /* Check that status is still OK */
    if (client->last_status != PJ_SUCCESS) {
        app_perror("    error: client has failed", client->last_status);
        ret = -400;
        goto on_return;
    }
    /* Check that data has been received */
    if (client->on_rx_data_cnt == 0) {
        PJ_LOG(3,(THIS_FILE, "    error: client doesn't receive data"));
        ret = -410;
        goto on_return;
    }

    /*
     * Part 3: Successful keep-alive,
     */
    PJ_LOG(3,(THIS_FILE, "    successful keep-alive scenario"));

    /* Change server operation mode to normal mode */
    srv->flag = RESPOND_STUN | WITH_XOR_MAPPED;

    /* Reset server */
    srv->rx_cnt = 0;

    /* Reset client */
    client->on_status_cnt = 0;
    client->last_status = PJ_SUCCESS;
    client->on_rx_data_cnt = 0;

    /* Wait for keep-alive duration to see if client actually sends the
     * keep-alive.
     */
    pj_gettimeofday(&timeout);
    timeout.sec += (PJ_STUN_KEEP_ALIVE_SEC + 1);
    do {
        handle_events(cfg, 100);
        pj_gettimeofday(&t);
    } while (PJ_TIME_VAL_LT(t, timeout));

    /* Check that server receives some packets */
    if (srv->rx_cnt == 0) {
        PJ_LOG(3, (THIS_FILE, "    error: no keep-alive was received"));
        ret = -420;
        goto on_return;
    }
    /* Check that client status is still okay and on_status() callback is NOT
     * called
     */
    /* No longer valid due to this ticket:
     *  https://github.com/pjsip/pjproject/issues/742

    if (client->on_status_cnt != 0) {
        PJ_LOG(3, (THIS_FILE, "    error: on_status() must not be called on successful"
                              "keep-alive when mapped-address does not change"));
        ret = -430;
        goto on_return;
    }
    */
    /* Check that client doesn't receive anything */
    if (client->on_rx_data_cnt != 0) {
        PJ_LOG(3,(THIS_FILE, "    error: client shouldn't have received anything"));
        ret = -440;
        goto on_return;
    }


    /*
     * Part 4: Successful keep-alive with IP address change
     */
    PJ_LOG(3,(THIS_FILE, "    mapped IP address change"));

    /* Change server operation mode to normal mode */
    srv->flag = RESPOND_STUN | WITH_XOR_MAPPED;

    /* Change mapped address in the response */
    srv->ip_to_send = (use_ipv6)?pj_str("2002:202:202::"):pj_str("2.2.2.2");    
    srv->port_to_send++;

    /* Reset server */
    srv->rx_cnt = 0;

    /* Reset client */
    client->on_status_cnt = 0;
    client->last_status = PJ_SUCCESS;
    client->on_rx_data_cnt = 0;

    /* Wait for keep-alive duration to see if client actually sends the
     * keep-alive.
     */
    pj_gettimeofday(&timeout);
    timeout.sec += (PJ_STUN_KEEP_ALIVE_SEC + 1);
    do {
        handle_events(cfg, 100);
        pj_gettimeofday(&t);
    } while (PJ_TIME_VAL_LT(t, timeout));

    /* Check that server receives some packets */
    if (srv->rx_cnt == 0) {
        PJ_LOG(3, (THIS_FILE, "    error: no keep-alive was received"));
        ret = -450;
        goto on_return;
    }
    /* Check that on_status() callback is called (because mapped address
     * has changed)
     */
    if (client->on_status_cnt != 1) {
        PJ_LOG(3, (THIS_FILE, "    error: on_status() was not called"));
        ret = -460;
        goto on_return;
    }
    /* Check that callback was called with correct operation */
    if (client->last_op != PJ_STUN_SOCK_MAPPED_ADDR_CHANGE) {
        PJ_LOG(3,(THIS_FILE, "    error: expecting keep-alive operation status"));
        ret = -470;
        goto on_return;
    }
    /* Check that last status is still success */
    if (client->last_status != PJ_SUCCESS) {
        PJ_LOG(3, (THIS_FILE, "    error: expecting successful status"));
        ret = -480;
        goto on_return;
    }
    /* Check that client doesn't receive anything */
    if (client->on_rx_data_cnt != 0) {
        PJ_LOG(3,(THIS_FILE, "    error: client shouldn't have received anything"));
        ret = -490;
        goto on_return;
    }

    /* Get info */
    pj_bzero(&info, sizeof(info));
    pj_stun_sock_get_info(client->sock, &info);

    /* Check that we have server address */
    if (!pj_sockaddr_has_addr(&info.srv_addr)) {
        PJ_LOG(3,(THIS_FILE, "    error: missing server address"));
        ret = -500;
        goto on_return;
    }
    /* .. and mapped address */
    if (!pj_sockaddr_has_addr(&info.mapped_addr)) {
        PJ_LOG(3,(THIS_FILE, "    error: missing mapped address"));
        ret = -510;
        goto on_return;
    }
    /* verify the mapped address */
    pj_sockaddr_init(GET_AF(use_ipv6), &mapped_addr, 
                     &srv->ip_to_send, srv->port_to_send);
    if (pj_sockaddr_cmp(&info.mapped_addr, &mapped_addr) != 0) {
        PJ_LOG(3,(THIS_FILE, "    error: mapped address mismatched"));
        ret = -520;
        goto on_return;
    }

    /* .. and at least one alias */
    if (info.alias_cnt == 0) {
        PJ_LOG(3,(THIS_FILE, "    error: must have at least one alias"));
        ret = -530;
        goto on_return;
    }
    if (!pj_sockaddr_has_addr(&info.aliases[0])) {
        PJ_LOG(3,(THIS_FILE, "    error: missing alias"));
        ret = -540;
        goto on_return;
    }


    /*
     * Part 5: Failed keep-alive
     */
    PJ_LOG(3,(THIS_FILE, "    failed keep-alive scenario"));
    
    /* Change server operation mode to respond without attribute */
    srv->flag = RESPOND_STUN;

    /* Reset server */
    srv->rx_cnt = 0;

    /* Reset client */
    client->on_status_cnt = 0;
    client->last_status = PJ_SUCCESS;
    client->on_rx_data_cnt = 0;

    /* Wait until on_status() is called with failure. */
    pj_gettimeofday(&timeout);
    timeout.sec += (PJ_STUN_KEEP_ALIVE_SEC + PJ_STUN_TIMEOUT_VALUE + 5);
    do {
        handle_events(cfg, 100);
        pj_gettimeofday(&t);
    } while (client->on_status_cnt==0 && PJ_TIME_VAL_LT(t, timeout));

    /* Check that callback with correct operation is called */
    if (client->last_op != PJ_STUN_SOCK_KEEP_ALIVE_OP) {
        PJ_LOG(3,(THIS_FILE, "    error: expecting keep-alive operation status"));
        ret = -600;
        goto on_return;
    }
    if (client->last_status == PJ_SUCCESS) {
        PJ_LOG(3,(THIS_FILE, "    error: expecting failed keep-alive"));
        ret = -610;
        goto on_return;
    }
    /* Check that client doesn't receive anything */
    if (client->on_rx_data_cnt != 0) {
        PJ_LOG(3,(THIS_FILE, "    error: client shouldn't have received anything"));
        ret = -620;
        goto on_return;
    }


on_return:
    destroy_server(srv);
    destroy_client(client);
    for (i=0; i<7; ++i)
        handle_events(cfg, 100);

    if ((ret == 0) && use_ipv6)
        ret = keep_alive_test(cfg, 0);

    return ret;
}


#define DO_TEST(expr)       \
            capture_pjlib_state(&stun_cfg, &pjlib_state); \
            ret = expr; \
            if (ret != 0) goto on_return; \
            ret = check_pjlib_state(&stun_cfg, &pjlib_state); \
            if (ret != 0) goto on_return;


int stun_sock_test(void)
{
    struct pjlib_state pjlib_state;
    pj_stun_config stun_cfg;
    pj_ioqueue_t *ioqueue = NULL;
    pj_timer_heap_t *timer_heap = NULL;
    pj_pool_t *pool = NULL;
    pj_status_t status;
    int ret = 0;

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

    status = pj_ioqueue_create(pool, 12, &ioqueue);
    if (status != PJ_SUCCESS) {
        app_perror("   pj_ioqueue_create()", status);
        ret = -4;
        goto on_return;
    }

    status = pj_timer_heap_create(pool, 100, &timer_heap);
    if (status != PJ_SUCCESS) {
        app_perror("   pj_timer_heap_create()", status);
        ret = -8;
        goto on_return;
    }
    
    pj_stun_config_init(&stun_cfg, mem, 0, ioqueue, timer_heap);

    DO_TEST(timeout_test(&stun_cfg, PJ_FALSE, USE_IPV6));
    DO_TEST(timeout_test(&stun_cfg, PJ_TRUE, USE_IPV6));

    DO_TEST(missing_attr_test(&stun_cfg, PJ_FALSE, USE_IPV6));
    DO_TEST(missing_attr_test(&stun_cfg, PJ_TRUE, USE_IPV6));

    DO_TEST(keep_alive_test(&stun_cfg, USE_IPV6));

on_return:
    if (timer_heap) pj_timer_heap_destroy(timer_heap);
    if (ioqueue) pj_ioqueue_destroy(ioqueue);
    if (pool) pj_pool_release(pool);
    return ret;
}


