/* $Id: stun_msg.c 2928 2009-10-07 08:38:02Z bennylp $ */
/* 
 * Copyright (C) 2008-2009 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 <pjnath/stun_msg.h>
#include <pjnath/errno.h>
#include <pjlib-util/crc32.h>
#include <pjlib-util/hmac_sha1.h>
#include <pj/assert.h>
#include <pj/log.h>
#include <pj/os.h>
#include <pj/pool.h>
#include <pj/rand.h>
#include <pj/string.h>

#define THIS_FILE		"stun_msg.c"
#define STUN_XOR_FINGERPRINT	0x5354554eL

static int padding_char;

static const char *stun_method_names[PJ_STUN_METHOD_MAX] = 
{
    "Unknown",			/* 0 */
    "Binding",			/* 1 */
    "SharedSecret",		/* 2 */
    "Allocate",			/* 3 */
    "Refresh",			/* 4 */
    "???",			/* 5 */
    "Send",			/* 6 */
    "Data",			/* 7 */
    "CreatePermission",		/* 8 */
    "ChannelBind",		/* 9 */
};

static struct
{
    int err_code;
    const char *err_msg;
} stun_err_msg_map[] = 
{
    { PJ_STUN_SC_TRY_ALTERNATE,		    "Try Alternate"}, 
    { PJ_STUN_SC_BAD_REQUEST,		    "Bad Request"},
    { PJ_STUN_SC_UNAUTHORIZED,		    "Unauthorized"},
    { PJ_STUN_SC_FORBIDDEN,		    "Forbidden"},
    { PJ_STUN_SC_UNKNOWN_ATTRIBUTE,	    "Unknown Attribute"},
    //{ PJ_STUN_SC_STALE_CREDENTIALS,	    "Stale Credentials"},
    //{ PJ_STUN_SC_INTEGRITY_CHECK_FAILURE, "Integrity Check Failure"},
    //{ PJ_STUN_SC_MISSING_USERNAME,	    "Missing Username"},
    //{ PJ_STUN_SC_USE_TLS,		    "Use TLS"},
    //{ PJ_STUN_SC_MISSING_REALM,	    "Missing Realm"},
    //{ PJ_STUN_SC_MISSING_NONCE,	    "Missing Nonce"},
    //{ PJ_STUN_SC_UNKNOWN_USERNAME,	    "Unknown Username"},
    { PJ_STUN_SC_ALLOCATION_MISMATCH,	    "Allocation Mismatch"},
    { PJ_STUN_SC_STALE_NONCE,		    "Stale Nonce"},
    { PJ_STUN_SC_TRANSITIONING,		    "Active Destination Already Set"},
    { PJ_STUN_SC_WRONG_CREDENTIALS,	    "Wrong Credentials"},
    { PJ_STUN_SC_UNSUPP_TRANSPORT_PROTO,    "Unsupported Transport Protocol"},
    { PJ_STUN_SC_OPER_TCP_ONLY,		    "Operation for TCP Only"},
    { PJ_STUN_SC_CONNECTION_FAILURE,	    "Connection Failure"},
    { PJ_STUN_SC_CONNECTION_TIMEOUT,	    "Connection Timeout"},
    { PJ_STUN_SC_ALLOCATION_QUOTA_REACHED,  "Allocation Quota Reached"},
    { PJ_STUN_SC_ROLE_CONFLICT,		    "Role Conflict"},
    { PJ_STUN_SC_SERVER_ERROR,		    "Server Error"},
    { PJ_STUN_SC_INSUFFICIENT_CAPACITY,	    "Insufficient Capacity"},
    { PJ_STUN_SC_GLOBAL_FAILURE,	    "Global Failure"}
};



struct attr_desc
{
    const char   *name;
    pj_status_t	(*decode_attr)(pj_pool_t *pool, const pj_uint8_t *buf, 
			       const pj_stun_msg_hdr *msghdr, void **p_attr);
    pj_status_t (*encode_attr)(const void *a, pj_uint8_t *buf, 
			       unsigned len, const pj_stun_msg_hdr *msghdr,
			       unsigned *printed);
    void*       (*clone_attr)(pj_pool_t *pool, const void *src);
};

static pj_status_t decode_sockaddr_attr(pj_pool_t *pool, 
				        const pj_uint8_t *buf, 
				        const pj_stun_msg_hdr *msghdr,
				        void **p_attr);
static pj_status_t decode_xored_sockaddr_attr(pj_pool_t *pool, 
					      const pj_uint8_t *buf, 
					      const pj_stun_msg_hdr *msghdr, 
					      void **p_attr);
static pj_status_t encode_sockaddr_attr(const void *a, pj_uint8_t *buf, 
				        unsigned len, 
					const pj_stun_msg_hdr *msghdr,
				        unsigned *printed);
static void*       clone_sockaddr_attr(pj_pool_t *pool, const void *src);
static pj_status_t decode_string_attr(pj_pool_t *pool, 
				      const pj_uint8_t *buf, 
				      const pj_stun_msg_hdr *msghdr, 
				      void **p_attr);
static pj_status_t encode_string_attr(const void *a, pj_uint8_t *buf, 
				      unsigned len, 
				      const pj_stun_msg_hdr *msghdr,
				      unsigned *printed);
static void*       clone_string_attr(pj_pool_t *pool, const void *src);
static pj_status_t decode_msgint_attr(pj_pool_t *pool, 
				      const pj_uint8_t *buf,
				      const pj_stun_msg_hdr *msghdr, 
				      void **p_attr);
static pj_status_t encode_msgint_attr(const void *a, pj_uint8_t *buf, 
				      unsigned len, 
				      const pj_stun_msg_hdr *msghdr,
				      unsigned *printed);
static void*       clone_msgint_attr(pj_pool_t *pool, const void *src);
static pj_status_t decode_errcode_attr(pj_pool_t *pool, 
				       const pj_uint8_t *buf,
				       const pj_stun_msg_hdr *msghdr, 
				       void **p_attr);
static pj_status_t encode_errcode_attr(const void *a, pj_uint8_t *buf, 
				       unsigned len, 
				       const pj_stun_msg_hdr *msghdr,
				       unsigned *printed);
static void*       clone_errcode_attr(pj_pool_t *pool, const void *src);
static pj_status_t decode_unknown_attr(pj_pool_t *pool, 
				       const pj_uint8_t *buf, 
				       const pj_stun_msg_hdr *msghdr, 
				       void **p_attr);
static pj_status_t encode_unknown_attr(const void *a, pj_uint8_t *buf, 
				       unsigned len, 
				       const pj_stun_msg_hdr *msghdr,
				       unsigned *printed);
static void*       clone_unknown_attr(pj_pool_t *pool, const void *src);
static pj_status_t decode_uint_attr(pj_pool_t *pool, 
				    const pj_uint8_t *buf, 
				    const pj_stun_msg_hdr *msghdr, 
				    void **p_attr);
static pj_status_t encode_uint_attr(const void *a, pj_uint8_t *buf, 
				    unsigned len, 
				    const pj_stun_msg_hdr *msghdr,
				    unsigned *printed);
static void*       clone_uint_attr(pj_pool_t *pool, const void *src);
static pj_status_t decode_uint64_attr(pj_pool_t *pool, 
				      const pj_uint8_t *buf, 
				      const pj_stun_msg_hdr *msghdr, 
				      void **p_attr);
static pj_status_t encode_uint64_attr(const void *a, pj_uint8_t *buf, 
				      unsigned len, 
				      const pj_stun_msg_hdr *msghdr,
				      unsigned *printed);
static void*       clone_uint64_attr(pj_pool_t *pool, const void *src);
static pj_status_t decode_binary_attr(pj_pool_t *pool, 
				      const pj_uint8_t *buf,
				      const pj_stun_msg_hdr *msghdr, 
				      void **p_attr);
static pj_status_t encode_binary_attr(const void *a, pj_uint8_t *buf, 
				      unsigned len, 
				      const pj_stun_msg_hdr *msghdr,
				      unsigned *printed);
static void*       clone_binary_attr(pj_pool_t *pool, const void *src);
static pj_status_t decode_empty_attr(pj_pool_t *pool, 
				     const pj_uint8_t *buf, 
				     const pj_stun_msg_hdr *msghdr, 
				     void **p_attr);
static pj_status_t encode_empty_attr(const void *a, pj_uint8_t *buf, 
				     unsigned len, 
				     const pj_stun_msg_hdr *msghdr,
				     unsigned *printed);
static void*       clone_empty_attr(pj_pool_t *pool, const void *src);

static struct attr_desc mandatory_attr_desc[] = 
{
    {
	/* type zero */
	NULL,
	NULL,
	NULL,
	NULL
    },
    {
	/* PJ_STUN_ATTR_MAPPED_ADDR, */
	"MAPPED-ADDRESS",
	&decode_sockaddr_attr,
	&encode_sockaddr_attr,
	&clone_sockaddr_attr
    },
    {
	/* PJ_STUN_ATTR_RESPONSE_ADDR, */
	"RESPONSE-ADDRESS",
	&decode_sockaddr_attr,
	&encode_sockaddr_attr,
	&clone_sockaddr_attr
    },
    {
	/* PJ_STUN_ATTR_CHANGE_REQUEST, */
	"CHANGE-REQUEST",
	&decode_uint_attr,
	&encode_uint_attr,
	&clone_uint_attr
    },
    {
	/* PJ_STUN_ATTR_SOURCE_ADDR, */
	"SOURCE-ADDRESS",
	&decode_sockaddr_attr,
	&encode_sockaddr_attr,
	&clone_sockaddr_attr
    },
    {
	/* PJ_STUN_ATTR_CHANGED_ADDR, */
	"CHANGED-ADDRESS",
	&decode_sockaddr_attr,
	&encode_sockaddr_attr,
	&clone_sockaddr_attr
    },
    {
	/* PJ_STUN_ATTR_USERNAME, */
	"USERNAME",
	&decode_string_attr,
	&encode_string_attr,
	&clone_string_attr
    },
    {
	/* PJ_STUN_ATTR_PASSWORD, */
	"PASSWORD",
	&decode_string_attr,
	&encode_string_attr,
	&clone_string_attr
    },
    {
	/* PJ_STUN_ATTR_MESSAGE_INTEGRITY, */
	"MESSAGE-INTEGRITY",
	&decode_msgint_attr,
	&encode_msgint_attr,
	&clone_msgint_attr
    },
    {
	/* PJ_STUN_ATTR_ERROR_CODE, */
	"ERROR-CODE",
	&decode_errcode_attr,
	&encode_errcode_attr,
	&clone_errcode_attr
    },
    {
	/* PJ_STUN_ATTR_UNKNOWN_ATTRIBUTES, */
	"UNKNOWN-ATTRIBUTES",
	&decode_unknown_attr,
	&encode_unknown_attr,
	&clone_unknown_attr
    },
    {
	/* PJ_STUN_ATTR_REFLECTED_FROM, */
	"REFLECTED-FROM",
	&decode_sockaddr_attr,
	&encode_sockaddr_attr,
	&clone_sockaddr_attr
    },
    {
	/* PJ_STUN_ATTR_CHANNEL_NUMBER (0x000C) */
	"CHANNEL-NUMBER",
	&decode_uint_attr,
	&encode_uint_attr,
	&clone_uint_attr
    },
    {
	/* PJ_STUN_ATTR_LIFETIME, */
	"LIFETIME",
	&decode_uint_attr,
	&encode_uint_attr,
	&clone_uint_attr
    },
    {
	/* ID 0x000E is not assigned */
	NULL,
	NULL,
	NULL,
	NULL
    },
    {
	/* PJ_STUN_ATTR_MAGIC_COOKIE */
	"MAGIC-COOKIE",
	&decode_uint_attr,
	&encode_uint_attr,
	&clone_uint_attr
    },
    {
	/* PJ_STUN_ATTR_BANDWIDTH, */
	"BANDWIDTH",
	&decode_uint_attr,
	&encode_uint_attr,
	&clone_uint_attr
    },
    {
	/* ID 0x0011 is not assigned */
	NULL,
	NULL,
	NULL,
	NULL
    },
    {
	/* PJ_STUN_ATTR_XOR_PEER_ADDRESS, */
	"XOR-PEER-ADDRESS",
	&decode_xored_sockaddr_attr,
	&encode_sockaddr_attr,
	&clone_sockaddr_attr
    },
    {
	/* PJ_STUN_ATTR_DATA, */
	"DATA",
	&decode_binary_attr,
	&encode_binary_attr,
	&clone_binary_attr
    },
    {
	/* PJ_STUN_ATTR_REALM, */
	"REALM",
	&decode_string_attr,
	&encode_string_attr,
	&clone_string_attr
    },
    {
	/* PJ_STUN_ATTR_NONCE, */
	"NONCE",
	&decode_string_attr,
	&encode_string_attr,
	&clone_string_attr
    },
    {
	/* PJ_STUN_ATTR_XOR_RELAYED_ADDR, */
	"XOR-RELAYED-ADDRESS",
	&decode_xored_sockaddr_attr,
	&encode_sockaddr_attr,
	&clone_sockaddr_attr
    },
    {
	/* PJ_STUN_ATTR_REQUESTED_ADDR_TYPE, */
	"REQUESTED-ADDRESS-TYPE",
	&decode_uint_attr,
	&encode_uint_attr,
	&clone_uint_attr
    },
    {
	/* PJ_STUN_ATTR_EVEN_PORT, */
	"EVEN-PORT",
	&decode_uint_attr,
	&encode_uint_attr,
	&clone_uint_attr
    },
    {
	/* PJ_STUN_ATTR_REQUESTED_TRANSPORT, */
	"REQUESTED-TRANSPORT",
	&decode_uint_attr,
	&encode_uint_attr,
	&clone_uint_attr
    },
    {
	/* PJ_STUN_ATTR_DONT_FRAGMENT */
	"DONT-FRAGMENT",
	&decode_empty_attr,
	&encode_empty_attr,
	&clone_empty_attr
    },
    {
	/* ID 0x001B is not assigned */
	NULL,
	NULL,
	NULL,
	NULL
    },
    {
	/* ID 0x001C is not assigned */
	NULL,
	NULL,
	NULL,
	NULL
    },
    {
	/* ID 0x001D is not assigned */
	NULL,
	NULL,
	NULL,
	NULL
    },
    {
	/* ID 0x001E is not assigned */
	NULL,
	NULL,
	NULL,
	NULL
    },
    {
	/* ID 0x001F is not assigned */
	NULL,
	NULL,
	NULL,
	NULL
    },
    {
	/* PJ_STUN_ATTR_XOR_MAPPED_ADDRESS, */
	"XOR-MAPPED-ADDRESS",
	&decode_xored_sockaddr_attr,
	&encode_sockaddr_attr,
	&clone_sockaddr_attr
    },
    {
	/* PJ_STUN_ATTR_TIMER_VAL, */
	"TIMER-VAL",
	&decode_uint_attr,
	&encode_uint_attr,
	&clone_uint_attr
    },
    {
	/* PJ_STUN_ATTR_RESERVATION_TOKEN, */
	"RESERVATION-TOKEN",
	&decode_uint64_attr,
	&encode_uint64_attr,
	&clone_uint64_attr
    },
    {
	/* PJ_STUN_ATTR_XOR_REFLECTED_FROM, */
	"XOR-REFLECTED-FROM",
	&decode_xored_sockaddr_attr,
	&encode_sockaddr_attr,
	&clone_sockaddr_attr
    },
    {
	/* PJ_STUN_ATTR_PRIORITY, */
	"PRIORITY",
	&decode_uint_attr,
	&encode_uint_attr,
	&clone_uint_attr
    },
    {
	/* PJ_STUN_ATTR_USE_CANDIDATE, */
	"USE-CANDIDATE",
	&decode_empty_attr,
	&encode_empty_attr,
	&clone_empty_attr
    },
    {
	/* ID 0x0026 is not assigned */
	NULL,
	NULL,
	NULL,
	NULL
    },
    {
	/* ID 0x0027 is not assigned */
	NULL,
	NULL,
	NULL,
	NULL
    },
    {
	/* ID 0x0028 is not assigned */
	NULL,
	NULL,
	NULL,
	NULL
    },
    {
	/* ID 0x0029 is not assigned */
	NULL,
	NULL,
	NULL,
	NULL
    },
    {
	/* ID 0x002a is not assigned */
	NULL,
	NULL,
	NULL,
	NULL
    },
    {
	/* ID 0x002b is not assigned */
	NULL,
	NULL,
	NULL,
	NULL
    },
    {
	/* ID 0x002c is not assigned */
	NULL,
	NULL,
	NULL,
	NULL
    },
    {
	/* ID 0x002d is not assigned */
	NULL,
	NULL,
	NULL,
	NULL
    },
    {
	/* ID 0x002e is not assigned */
	NULL,
	NULL,
	NULL,
	NULL
    },
    {
	/* ID 0x002f is not assigned */
	NULL,
	NULL,
	NULL,
	NULL
    },
    {
	/* PJ_STUN_ATTR_ICMP, */
	"ICMP",
	&decode_uint_attr,
	&encode_uint_attr,
	&clone_uint_attr
    },

    /* Sentinel */
    {
	/* PJ_STUN_ATTR_END_MANDATORY_ATTR */
	NULL,
	NULL,
	NULL,
	NULL
    }
};

static struct attr_desc extended_attr_desc[] =
{
    {
	/* ID 0x8021 is not assigned */
	NULL,
	NULL,
	NULL,
	NULL
    },
    {
	/* PJ_STUN_ATTR_SOFTWARE, */
	"SOFTWARE",
	&decode_string_attr,
	&encode_string_attr,
	&clone_string_attr
    },
    {
	/* PJ_STUN_ATTR_ALTERNATE_SERVER, */
	"ALTERNATE-SERVER",
	&decode_sockaddr_attr,
	&encode_sockaddr_attr,
	&clone_sockaddr_attr
    },
    {
	/* PJ_STUN_ATTR_REFRESH_INTERVAL, */
	"REFRESH-INTERVAL",
	&decode_uint_attr,
	&encode_uint_attr,
	&clone_uint_attr
    },
    {
	/* ID 0x8025 is not assigned*/
	NULL,
	NULL,
	NULL,
	NULL
    },
    {
	/* PADDING, 0x8026 */
	NULL,
	NULL,
	NULL,
	NULL
    },
    {
	/* CACHE-TIMEOUT, 0x8027 */
	NULL,
	NULL,
	NULL,
	NULL
    },
    {
	/* PJ_STUN_ATTR_FINGERPRINT, */
	"FINGERPRINT",
	&decode_uint_attr,
	&encode_uint_attr,
	&clone_uint_attr
    },
    {
	/* PJ_STUN_ATTR_ICE_CONTROLLED, */
	"ICE-CONTROLLED",
	&decode_uint64_attr,
	&encode_uint64_attr,
	&clone_uint64_attr
    },
    {
	/* PJ_STUN_ATTR_ICE_CONTROLLING, */
	"ICE-CONTROLLING",
	&decode_uint64_attr,
	&encode_uint64_attr,
	&clone_uint64_attr
    }
};



/*
 * Get STUN message type name.
 */
PJ_DEF(const char*) pj_stun_get_method_name(unsigned msg_type)
{
    unsigned method = PJ_STUN_GET_METHOD(msg_type);

    if (method >= PJ_ARRAY_SIZE(stun_method_names))
	return "???";

    return stun_method_names[method];
}


/*
 * Get STUN message class name.
 */
PJ_DEF(const char*) pj_stun_get_class_name(unsigned msg_type)
{
    if (PJ_STUN_IS_REQUEST(msg_type))
	return "request";
    else if (PJ_STUN_IS_SUCCESS_RESPONSE(msg_type))
	return "success response";
    else if (PJ_STUN_IS_ERROR_RESPONSE(msg_type))
	return "error response";
    else if (PJ_STUN_IS_INDICATION(msg_type))
	return "indication";
    else
	return "???";
}


static const struct attr_desc *find_attr_desc(unsigned attr_type)
{
    struct attr_desc *desc;

    /* Check that attr_desc array is valid */
    pj_assert(PJ_ARRAY_SIZE(mandatory_attr_desc)==
	      PJ_STUN_ATTR_END_MANDATORY_ATTR+1);
    pj_assert(mandatory_attr_desc[PJ_STUN_ATTR_END_MANDATORY_ATTR].decode_attr
	      == NULL);
    pj_assert(mandatory_attr_desc[PJ_STUN_ATTR_USE_CANDIDATE].decode_attr 
	      == &decode_empty_attr);
    pj_assert(PJ_ARRAY_SIZE(extended_attr_desc) ==
	      PJ_STUN_ATTR_END_EXTENDED_ATTR-PJ_STUN_ATTR_START_EXTENDED_ATTR);

    if (attr_type < PJ_STUN_ATTR_END_MANDATORY_ATTR)
	desc = &mandatory_attr_desc[attr_type];
    else if (attr_type >= PJ_STUN_ATTR_START_EXTENDED_ATTR &&
	     attr_type < PJ_STUN_ATTR_END_EXTENDED_ATTR)
	desc = &extended_attr_desc[attr_type-PJ_STUN_ATTR_START_EXTENDED_ATTR];
    else
	return NULL;

    return desc->decode_attr == NULL ? NULL : desc;
}


/*
 * Get STUN attribute name.
 */
PJ_DEF(const char*) pj_stun_get_attr_name(unsigned attr_type)
{
    const struct attr_desc *attr_desc;

    attr_desc = find_attr_desc(attr_type);
    if (!attr_desc || attr_desc->name==NULL)
	return "???";

    return attr_desc->name;
}


/**
 * Get STUN standard reason phrase for the specified error code.
 */
PJ_DEF(pj_str_t) pj_stun_get_err_reason(int err_code)
{
#if 0
    /* Find error using linear search */
    unsigned i;

    for (i=0; i<PJ_ARRAY_SIZE(stun_err_msg_map); ++i) {
	if (stun_err_msg_map[i].err_code == err_code)
	    return pj_str((char*)stun_err_msg_map[i].err_msg);
    }
    return pj_str(NULL);
#else
    /* Find error message using binary search */
    int first = 0;
    int n = PJ_ARRAY_SIZE(stun_err_msg_map);

    while (n > 0) {
	int half = n/2;
	int mid = first + half;

	if (stun_err_msg_map[mid].err_code < err_code) {
	    first = mid+1;
	    n -= (half+1);
	} else if (stun_err_msg_map[mid].err_code > err_code) {
	    n = half;
	} else {
	    first = mid;
	    break;
	}
    }


    if (stun_err_msg_map[first].err_code == err_code) {
	return pj_str((char*)stun_err_msg_map[first].err_msg);
    } else {
	return pj_str(NULL);
    }
#endif
}


/*
 * Set padding character.
 */
PJ_DEF(int) pj_stun_set_padding_char(int chr)
{
    int old_pad = padding_char;
    padding_char = chr;
    return old_pad;
}


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


#define INIT_ATTR(a,t,l)    (a)->hdr.type=(pj_uint16_t)(t), \
			    (a)->hdr.length=(pj_uint16_t)(l)
#define ATTR_HDR_LEN	    4

static pj_uint16_t GETVAL16H(const pj_uint8_t *buf, unsigned pos)
{
    return (pj_uint16_t) ((buf[pos + 0] << 8) | \
			  (buf[pos + 1] << 0));
}

PJ_INLINE(pj_uint16_t) GETVAL16N(const pj_uint8_t *buf, unsigned pos)
{
    return pj_htons(GETVAL16H(buf,pos));
}

static void PUTVAL16H(pj_uint8_t *buf, unsigned pos, pj_uint16_t hval)
{
    buf[pos+0] = (pj_uint8_t) ((hval & 0xFF00) >> 8);
    buf[pos+1] = (pj_uint8_t) ((hval & 0x00FF) >> 0);
}

PJ_INLINE(pj_uint32_t) GETVAL32H(const pj_uint8_t *buf, unsigned pos)
{
    return (pj_uint32_t) ((buf[pos + 0] << 24UL) | \
	                  (buf[pos + 1] << 16UL) | \
	                  (buf[pos + 2] <<  8UL) | \
			  (buf[pos + 3] <<  0UL));
}

PJ_INLINE(pj_uint32_t) GETVAL32N(const pj_uint8_t *buf, unsigned pos)
{
    return pj_htonl(GETVAL32H(buf,pos));
}

static void PUTVAL32H(pj_uint8_t *buf, unsigned pos, pj_uint32_t hval)
{
    buf[pos+0] = (pj_uint8_t) ((hval & 0xFF000000UL) >> 24);
    buf[pos+1] = (pj_uint8_t) ((hval & 0x00FF0000UL) >> 16);
    buf[pos+2] = (pj_uint8_t) ((hval & 0x0000FF00UL) >>  8);
    buf[pos+3] = (pj_uint8_t) ((hval & 0x000000FFUL) >>  0);
}

static void GETVAL64H(const pj_uint8_t *buf, unsigned pos, pj_timestamp *ts)
{
    ts->u32.hi = GETVAL32H(buf, pos);
    ts->u32.lo = GETVAL32H(buf, pos+4);
}

static void PUTVAL64H(pj_uint8_t *buf, unsigned pos, const pj_timestamp *ts)
{
    PUTVAL32H(buf, pos, ts->u32.hi);
    PUTVAL32H(buf, pos+4, ts->u32.lo);
}


static void GETATTRHDR(const pj_uint8_t *buf, pj_stun_attr_hdr *hdr)
{
    hdr->type = GETVAL16H(buf, 0);
    hdr->length = GETVAL16H(buf, 2);
}

//////////////////////////////////////////////////////////////////////////////
/*
 * STUN generic IP address container
 */
#define STUN_GENERIC_IPV4_ADDR_LEN	8
#define STUN_GENERIC_IPV6_ADDR_LEN	20

/*
 * Init sockaddr attr
 */
PJ_DEF(pj_status_t) pj_stun_sockaddr_attr_init( pj_stun_sockaddr_attr *attr,
						int attr_type, 
						pj_bool_t xor_ed,
						const pj_sockaddr_t *addr,
						unsigned addr_len)
{
    unsigned attr_len;

    PJ_ASSERT_RETURN(attr && addr_len && addr, PJ_EINVAL);
    PJ_ASSERT_RETURN(addr_len == sizeof(pj_sockaddr_in) ||
		     addr_len == sizeof(pj_sockaddr_in6), PJ_EINVAL);

    attr_len = pj_sockaddr_get_addr_len(addr) + 4;
    INIT_ATTR(attr, attr_type, attr_len);

    pj_memcpy(&attr->sockaddr, addr, addr_len);
    attr->xor_ed = xor_ed;

    return PJ_SUCCESS;
}


/*
 * Create a generic STUN IP address attribute for IPv4 address.
 */
PJ_DEF(pj_status_t) pj_stun_sockaddr_attr_create(pj_pool_t *pool,
						 int attr_type,
						 pj_bool_t xor_ed,
						 const pj_sockaddr_t *addr,
						 unsigned addr_len,
						 pj_stun_sockaddr_attr **p_attr)
{
    pj_stun_sockaddr_attr *attr;

    PJ_ASSERT_RETURN(pool && p_attr, PJ_EINVAL);
    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_sockaddr_attr);
    *p_attr = attr;
    return pj_stun_sockaddr_attr_init(attr, attr_type, xor_ed, 
				      addr, addr_len);
}


/*
 * Create and add generic STUN IP address attribute to a STUN message.
 */
PJ_DEF(pj_status_t) pj_stun_msg_add_sockaddr_attr(pj_pool_t *pool,
						  pj_stun_msg *msg,
						  int attr_type, 
						  pj_bool_t xor_ed,
						  const pj_sockaddr_t *addr,
						  unsigned addr_len)
{
    pj_stun_sockaddr_attr *attr;
    pj_status_t status;

    status = pj_stun_sockaddr_attr_create(pool, attr_type, xor_ed,
					         addr, addr_len, &attr);
    if (status != PJ_SUCCESS)
	return status;

    return pj_stun_msg_add_attr(msg, &attr->hdr);
}

static pj_status_t decode_sockaddr_attr(pj_pool_t *pool, 
				        const pj_uint8_t *buf, 
					const pj_stun_msg_hdr *msghdr, 
				        void **p_attr)
{
    pj_stun_sockaddr_attr *attr;
    int af;
    unsigned addr_len;
    pj_uint32_t val;

    PJ_CHECK_STACK();
    
    PJ_UNUSED_ARG(msghdr);

    /* Create the attribute */
    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_sockaddr_attr);
    GETATTRHDR(buf, &attr->hdr);

    /* Check that the attribute length is valid */
    if (attr->hdr.length != STUN_GENERIC_IPV4_ADDR_LEN &&
	attr->hdr.length != STUN_GENERIC_IPV6_ADDR_LEN)
    {
	return PJNATH_ESTUNINATTRLEN;
    }

    /* Check address family */
    val = *(pj_uint8_t*)(buf + ATTR_HDR_LEN + 1);

    /* Check address family is valid */
    if (val == 1) {
	if (attr->hdr.length != STUN_GENERIC_IPV4_ADDR_LEN)
	    return PJNATH_ESTUNINATTRLEN;
	af = pj_AF_INET();
	addr_len = 4;
    } else if (val == 2) {
	if (attr->hdr.length != STUN_GENERIC_IPV6_ADDR_LEN)
	    return PJNATH_ESTUNINATTRLEN;
	af = pj_AF_INET6();
	addr_len = 16;
    } else {
	/* Invalid address family */
	return PJNATH_EINVAF;
    }

    /* Get port and address */
    pj_sockaddr_init(af, &attr->sockaddr, NULL, 0);
    pj_sockaddr_set_port(&attr->sockaddr, 
			 GETVAL16H(buf, ATTR_HDR_LEN+2));
    pj_memcpy(pj_sockaddr_get_addr(&attr->sockaddr),
	      buf+ATTR_HDR_LEN+4,
	      addr_len);

    /* Done */
    *p_attr = (void*)attr;

    return PJ_SUCCESS;
}


static pj_status_t decode_xored_sockaddr_attr(pj_pool_t *pool, 
					      const pj_uint8_t *buf, 
					      const pj_stun_msg_hdr *msghdr, 
					      void **p_attr)
{
    pj_stun_sockaddr_attr *attr;
    pj_status_t status;

    status = decode_sockaddr_attr(pool, buf, msghdr, p_attr);
    if (status != PJ_SUCCESS)
	return status;

    attr = *(pj_stun_sockaddr_attr**)p_attr;

    attr->xor_ed = PJ_TRUE;

    if (attr->sockaddr.addr.sa_family == pj_AF_INET()) {
	attr->sockaddr.ipv4.sin_port ^= pj_htons(PJ_STUN_MAGIC >> 16);
	attr->sockaddr.ipv4.sin_addr.s_addr ^= pj_htonl(PJ_STUN_MAGIC);
    } else if (attr->sockaddr.addr.sa_family == pj_AF_INET6()) {
	unsigned i;
	pj_uint8_t *dst = (pj_uint8_t*) &attr->sockaddr.ipv6.sin6_addr;
	pj_uint32_t magic = pj_htonl(PJ_STUN_MAGIC);

	attr->sockaddr.ipv6.sin6_port ^= pj_htons(PJ_STUN_MAGIC >> 16);

	/* If the IP address family is IPv6, X-Address is computed by
	 * taking the mapped IP address in host byte order, XOR'ing it
	 * with the concatenation of the magic cookie and the 96-bit 
	 * transaction ID, and converting the result to network byte 
	 * order.
	 */
	for (i=0; i<4; ++i) {
	    dst[i] ^= ((const pj_uint8_t*)&magic)[i];
	}
	pj_assert(sizeof(msghdr->tsx_id[0]) == 1);
	for (i=0; i<12; ++i) {
	    dst[i+4] ^= msghdr->tsx_id[i];
	}

    } else {
	return PJNATH_EINVAF;
    }

    /* Done */
    *p_attr = attr;

    return PJ_SUCCESS;
}


static pj_status_t encode_sockaddr_attr(const void *a, pj_uint8_t *buf, 
				        unsigned len, 
					const pj_stun_msg_hdr *msghdr,
					unsigned *printed)
{
    pj_uint8_t *start_buf = buf;
    const pj_stun_sockaddr_attr *ca = 
	(const pj_stun_sockaddr_attr *)a;

    PJ_CHECK_STACK();
    
    /* Common: attribute type */
    PUTVAL16H(buf, 0, ca->hdr.type);

    if (ca->sockaddr.addr.sa_family == pj_AF_INET()) {
	enum {
	    ATTR_LEN = ATTR_HDR_LEN + STUN_GENERIC_IPV4_ADDR_LEN
	};

	if (len < ATTR_LEN) 
	    return PJ_ETOOSMALL;

	/* attribute len */
	PUTVAL16H(buf, 2, STUN_GENERIC_IPV4_ADDR_LEN);
	buf += ATTR_HDR_LEN;
    
	/* Ignored */
	*buf++ = '\0';

	/* Address family, 1 for IPv4 */
	*buf++ = 1;

	/* IPv4 address */
	if (ca->xor_ed) {
	    pj_uint32_t addr;
	    pj_uint16_t port;

	    addr = ca->sockaddr.ipv4.sin_addr.s_addr;
	    port = ca->sockaddr.ipv4.sin_port;

	    port ^= pj_htons(PJ_STUN_MAGIC >> 16);
	    addr ^= pj_htonl(PJ_STUN_MAGIC);

	    /* Port */
	    pj_memcpy(buf, &port, 2);
	    buf += 2;

	    /* Address */
	    pj_memcpy(buf, &addr, 4);
	    buf += 4;

	} else {
	    /* Port */
	    pj_memcpy(buf, &ca->sockaddr.ipv4.sin_port, 2);
	    buf += 2;

	    /* Address */
	    pj_memcpy(buf, &ca->sockaddr.ipv4.sin_addr, 4);
	    buf += 4;
	}

	pj_assert(buf - start_buf == ATTR_LEN);

    } else if (ca->sockaddr.addr.sa_family == pj_AF_INET6()) {
	/* IPv6 address */
	enum {
	    ATTR_LEN = ATTR_HDR_LEN + STUN_GENERIC_IPV6_ADDR_LEN
	};

	if (len < ATTR_LEN) 
	    return PJ_ETOOSMALL;

	/* attribute len */
	PUTVAL16H(buf, 2, STUN_GENERIC_IPV6_ADDR_LEN);
	buf += ATTR_HDR_LEN;
    
	/* Ignored */
	*buf++ = '\0';

	/* Address family, 2 for IPv6 */
	*buf++ = 2;

	/* IPv6 address */
	if (ca->xor_ed) {
	    unsigned i;
	    pj_uint8_t *dst;
	    const pj_uint8_t *src;
	    pj_uint32_t magic = pj_htonl(PJ_STUN_MAGIC);
	    pj_uint16_t port = ca->sockaddr.ipv6.sin6_port;

	    /* Port */
	    port ^= pj_htons(PJ_STUN_MAGIC >> 16);
	    pj_memcpy(buf, &port, 2);
	    buf += 2;

	    /* Address */
	    dst = buf;
	    src = (const pj_uint8_t*) &ca->sockaddr.ipv6.sin6_addr;
	    for (i=0; i<4; ++i) {
		dst[i] = (pj_uint8_t)(src[i] ^ ((const pj_uint8_t*)&magic)[i]);
	    }
	    pj_assert(sizeof(msghdr->tsx_id[0]) == 1);
	    for (i=0; i<12; ++i) {
		dst[i+4] = (pj_uint8_t)(src[i+4] ^ msghdr->tsx_id[i]);
	    }

	    buf += 16;

	} else {
	    /* Port */
	    pj_memcpy(buf, &ca->sockaddr.ipv6.sin6_port, 2);
	    buf += 2;

	    /* Address */
	    pj_memcpy(buf, &ca->sockaddr.ipv6.sin6_addr, 16);
	    buf += 16;
	}

	pj_assert(buf - start_buf == ATTR_LEN);

    } else {
	return PJNATH_EINVAF;
    }

    /* Done */
    *printed = buf - start_buf;

    return PJ_SUCCESS;
}


static void* clone_sockaddr_attr(pj_pool_t *pool, const void *src)
{
    pj_stun_sockaddr_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_sockaddr_attr);
    pj_memcpy(dst, src, sizeof(pj_stun_sockaddr_attr));
    return (void*)dst;
}

//////////////////////////////////////////////////////////////////////////////
/*
 * STUN generic string attribute
 */

/*
 * Initialize a STUN generic string attribute.
 */
PJ_DEF(pj_status_t) pj_stun_string_attr_init( pj_stun_string_attr *attr,
					      pj_pool_t *pool,
					      int attr_type,
					      const pj_str_t *value)
{
    INIT_ATTR(attr, attr_type, value->slen);
    if (value && value->slen)
	pj_strdup(pool, &attr->value, value);
    else
	attr->value.slen = 0;
    return PJ_SUCCESS;
}


/*
 * Create a STUN generic string attribute.
 */
PJ_DEF(pj_status_t) pj_stun_string_attr_create(pj_pool_t *pool,
					       int attr_type,
					       const pj_str_t *value,
					       pj_stun_string_attr **p_attr)
{
    pj_stun_string_attr *attr;

    PJ_ASSERT_RETURN(pool && value && p_attr, PJ_EINVAL);

    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_string_attr);
    *p_attr = attr;

    return pj_stun_string_attr_init(attr, pool, attr_type, value);
}


/*
 * Create and add STUN generic string attribute to the message.
 */
PJ_DEF(pj_status_t) pj_stun_msg_add_string_attr(pj_pool_t *pool,
						pj_stun_msg *msg,
						int attr_type,
						const pj_str_t *value)
{
    pj_stun_string_attr *attr = NULL;
    pj_status_t status;

    status = pj_stun_string_attr_create(pool, attr_type, value, 
						&attr);
    if (status != PJ_SUCCESS)
	return status;

    return pj_stun_msg_add_attr(msg, &attr->hdr);
}


static pj_status_t decode_string_attr(pj_pool_t *pool, 
				      const pj_uint8_t *buf, 
				      const pj_stun_msg_hdr *msghdr, 
				      void **p_attr)
{
    pj_stun_string_attr *attr;
    pj_str_t value;

    PJ_UNUSED_ARG(msghdr);

    /* Create the attribute */
    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_string_attr);
    GETATTRHDR(buf, &attr->hdr);

    /* Get pointer to the string in the message */
    value.ptr = ((char*)buf + ATTR_HDR_LEN);
    value.slen = attr->hdr.length;

    /* Copy the string to the attribute */
    pj_strdup(pool, &attr->value, &value);

    /* Done */
    *p_attr = attr;

    return PJ_SUCCESS;

}


static pj_status_t encode_string_attr(const void *a, pj_uint8_t *buf, 
				      unsigned len, 
				      const pj_stun_msg_hdr *msghdr,
				      unsigned *printed)
{
    const pj_stun_string_attr *ca = 
	(const pj_stun_string_attr*)a;

    PJ_CHECK_STACK();
    
    PJ_UNUSED_ARG(msghdr);

    /* Calculated total attr_len (add padding if necessary) */
    *printed = (ca->value.slen + ATTR_HDR_LEN + 3) & (~3);
    if (len < *printed) {
	*printed = 0;
	return PJ_ETOOSMALL;
    }

    PUTVAL16H(buf, 0, ca->hdr.type);

    /* Special treatment for SOFTWARE attribute:
     * This attribute had caused interop problem when talking to 
     * legacy RFC 3489 STUN servers, due to different "length"
     * rules with RFC 5389.
     */
    if (msghdr->magic != PJ_STUN_MAGIC ||
	ca->hdr.type == PJ_STUN_ATTR_SOFTWARE)
    {
	/* Set the length to be 4-bytes aligned so that we can
	 * communicate with RFC 3489 endpoints
	 */
	PUTVAL16H(buf, 2, (pj_uint16_t)((ca->value.slen + 3) & (~3)));
    } else {
	/* Use RFC 5389 rule */
	PUTVAL16H(buf, 2, (pj_uint16_t)ca->value.slen);
    }

    /* Copy the string */
    pj_memcpy(buf+ATTR_HDR_LEN, ca->value.ptr, ca->value.slen);

    /* Add padding character, if string is not 4-bytes aligned. */
    if (ca->value.slen & 0x03) {
	pj_uint8_t pad[3];
	pj_memset(pad, padding_char, sizeof(pad));
	pj_memcpy(buf+ATTR_HDR_LEN+ca->value.slen, pad,
		  4-(ca->value.slen & 0x03));
    }

    /* Done */
    return PJ_SUCCESS;
}


static void* clone_string_attr(pj_pool_t *pool, const void *src)
{
    const pj_stun_string_attr *asrc = (const pj_stun_string_attr*)src;
    pj_stun_string_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_string_attr);

    pj_memcpy(dst, src, sizeof(pj_stun_attr_hdr));
    pj_strdup(pool, &dst->value, &asrc->value);

    return (void*)dst;
}

//////////////////////////////////////////////////////////////////////////////
/*
 * STUN empty attribute (used by USE-CANDIDATE).
 */

/*
 * Create a STUN empty attribute.
 */
PJ_DEF(pj_status_t) pj_stun_empty_attr_create(pj_pool_t *pool,
					      int attr_type,
					      pj_stun_empty_attr **p_attr)
{
    pj_stun_empty_attr *attr;

    PJ_ASSERT_RETURN(pool && p_attr, PJ_EINVAL);

    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_empty_attr);
    INIT_ATTR(attr, attr_type, 0);

    *p_attr = attr;

    return PJ_SUCCESS;
}


/*
 * Create STUN empty attribute and add the attribute to the message.
 */
PJ_DEF(pj_status_t) pj_stun_msg_add_empty_attr( pj_pool_t *pool,
						pj_stun_msg *msg,
						int attr_type)
{
    pj_stun_empty_attr *attr = NULL;
    pj_status_t status;

    status = pj_stun_empty_attr_create(pool, attr_type, &attr);
    if (status != PJ_SUCCESS)
	return status;

    return pj_stun_msg_add_attr(msg, &attr->hdr);
}

static pj_status_t decode_empty_attr(pj_pool_t *pool, 
				     const pj_uint8_t *buf, 
				     const pj_stun_msg_hdr *msghdr,
				     void **p_attr)
{
    pj_stun_empty_attr *attr;

    PJ_UNUSED_ARG(msghdr);

    /* Check that the struct address is valid */
    pj_assert(sizeof(pj_stun_empty_attr) == ATTR_HDR_LEN);

    /* Create the attribute */
    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_empty_attr);
    GETATTRHDR(buf, &attr->hdr);

    /* Check that the attribute length is valid */
    if (attr->hdr.length != 0)
	return PJNATH_ESTUNINATTRLEN;

    /* Done */
    *p_attr = attr;

    return PJ_SUCCESS;
}


static pj_status_t encode_empty_attr(const void *a, pj_uint8_t *buf, 
				     unsigned len, 
				     const pj_stun_msg_hdr *msghdr,
				     unsigned *printed)
{
    const pj_stun_empty_attr *ca = (pj_stun_empty_attr*)a;

    PJ_UNUSED_ARG(msghdr);

    if (len < ATTR_HDR_LEN) 
	return PJ_ETOOSMALL;

    PUTVAL16H(buf, 0, ca->hdr.type);
    PUTVAL16H(buf, 2, 0);

    /* Done */
    *printed = ATTR_HDR_LEN;

    return PJ_SUCCESS;
}


static void* clone_empty_attr(pj_pool_t *pool, const void *src)
{
    pj_stun_empty_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_empty_attr);

    pj_memcpy(dst, src, sizeof(pj_stun_empty_attr));

    return (void*) dst;
}

//////////////////////////////////////////////////////////////////////////////
/*
 * STUN generic 32bit integer attribute.
 */

/*
 * Create a STUN generic 32bit value attribute.
 */
PJ_DEF(pj_status_t) pj_stun_uint_attr_create(pj_pool_t *pool,
					     int attr_type,
					     pj_uint32_t value,
					     pj_stun_uint_attr **p_attr)
{
    pj_stun_uint_attr *attr;

    PJ_ASSERT_RETURN(pool && p_attr, PJ_EINVAL);

    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_uint_attr);
    INIT_ATTR(attr, attr_type, 4);
    attr->value = value;

    *p_attr = attr;

    return PJ_SUCCESS;
}

/* Create and add STUN generic 32bit value attribute to the message. */
PJ_DEF(pj_status_t) pj_stun_msg_add_uint_attr(pj_pool_t *pool,
					      pj_stun_msg *msg,
					      int attr_type,
					      pj_uint32_t value)
{
    pj_stun_uint_attr *attr = NULL;
    pj_status_t status;

    status = pj_stun_uint_attr_create(pool, attr_type, value, &attr);
    if (status != PJ_SUCCESS)
	return status;

    return pj_stun_msg_add_attr(msg, &attr->hdr);
}

static pj_status_t decode_uint_attr(pj_pool_t *pool, 
				    const pj_uint8_t *buf, 
				    const pj_stun_msg_hdr *msghdr, 
				    void **p_attr)
{
    pj_stun_uint_attr *attr;

    PJ_UNUSED_ARG(msghdr);

    /* Create the attribute */
    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_uint_attr);
    GETATTRHDR(buf, &attr->hdr);

    attr->value = GETVAL32H(buf, 4);

    /* Check that the attribute length is valid */
    if (attr->hdr.length != 4)
	return PJNATH_ESTUNINATTRLEN;

    /* Done */
    *p_attr = attr;

    return PJ_SUCCESS;
}


static pj_status_t encode_uint_attr(const void *a, pj_uint8_t *buf, 
				    unsigned len, 
				    const pj_stun_msg_hdr *msghdr,
				    unsigned *printed)
{
    const pj_stun_uint_attr *ca = (const pj_stun_uint_attr*)a;

    PJ_CHECK_STACK();

    PJ_UNUSED_ARG(msghdr);
    
    if (len < 8) 
	return PJ_ETOOSMALL;

    PUTVAL16H(buf, 0, ca->hdr.type);
    PUTVAL16H(buf, 2, (pj_uint16_t)4);
    PUTVAL32H(buf, 4, ca->value);
    
    /* Done */
    *printed = 8;

    return PJ_SUCCESS;
}


static void* clone_uint_attr(pj_pool_t *pool, const void *src)
{
    pj_stun_uint_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_uint_attr);

    pj_memcpy(dst, src, sizeof(pj_stun_uint_attr));

    return (void*)dst;
}

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

/*
 * Create a STUN generic 64bit value attribute.
 */
PJ_DEF(pj_status_t) pj_stun_uint64_attr_create(pj_pool_t *pool,
					       int attr_type,
					       const pj_timestamp *value,
					       pj_stun_uint64_attr **p_attr)
{
    pj_stun_uint64_attr *attr;

    PJ_ASSERT_RETURN(pool && p_attr, PJ_EINVAL);

    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_uint64_attr);
    INIT_ATTR(attr, attr_type, 8);

    if (value) {
	attr->value.u32.hi = value->u32.hi;
	attr->value.u32.lo = value->u32.lo;
    }

    *p_attr = attr;

    return PJ_SUCCESS;
}

/* Create and add STUN generic 64bit value attribute to the message. */
PJ_DEF(pj_status_t)  pj_stun_msg_add_uint64_attr(pj_pool_t *pool,
					         pj_stun_msg *msg,
					         int attr_type,
					         const pj_timestamp *value)
{
    pj_stun_uint64_attr *attr = NULL;
    pj_status_t status;

    status = pj_stun_uint64_attr_create(pool, attr_type, value, &attr);
    if (status != PJ_SUCCESS)
	return status;

    return pj_stun_msg_add_attr(msg, &attr->hdr);
}

static pj_status_t decode_uint64_attr(pj_pool_t *pool, 
				      const pj_uint8_t *buf, 
				      const pj_stun_msg_hdr *msghdr, 
				      void **p_attr)
{
    pj_stun_uint64_attr *attr;

    PJ_UNUSED_ARG(msghdr);

    /* Create the attribute */
    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_uint64_attr);
    GETATTRHDR(buf, &attr->hdr);

    if (attr->hdr.length != 8)
	return PJNATH_ESTUNINATTRLEN;

    GETVAL64H(buf, 4, &attr->value);	

    /* Done */
    *p_attr = attr;

    return PJ_SUCCESS;
}


static pj_status_t encode_uint64_attr(const void *a, pj_uint8_t *buf, 
				      unsigned len, 
				      const pj_stun_msg_hdr *msghdr,
				      unsigned *printed)
{
    const pj_stun_uint64_attr *ca = (const pj_stun_uint64_attr*)a;

    PJ_CHECK_STACK();

    PJ_UNUSED_ARG(msghdr);
    
    if (len < 12) 
	return PJ_ETOOSMALL;

    PUTVAL16H(buf, 0, ca->hdr.type);
    PUTVAL16H(buf, 2, (pj_uint16_t)8);
    PUTVAL64H(buf, 4, &ca->value);

    /* Done */
    *printed = 12;

    return PJ_SUCCESS;
}


static void* clone_uint64_attr(pj_pool_t *pool, const void *src)
{
    pj_stun_uint64_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_uint64_attr);

    pj_memcpy(dst, src, sizeof(pj_stun_uint64_attr));

    return (void*)dst;
}


//////////////////////////////////////////////////////////////////////////////
/*
 * STUN MESSAGE-INTEGRITY attribute.
 */

/*
 * Create a STUN MESSAGE-INTEGRITY attribute.
 */
PJ_DEF(pj_status_t) pj_stun_msgint_attr_create(pj_pool_t *pool,
					       pj_stun_msgint_attr **p_attr)
{
    pj_stun_msgint_attr *attr;

    PJ_ASSERT_RETURN(pool && p_attr, PJ_EINVAL);

    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_msgint_attr);
    INIT_ATTR(attr, PJ_STUN_ATTR_MESSAGE_INTEGRITY, 20);

    *p_attr = attr;

    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pj_stun_msg_add_msgint_attr(pj_pool_t *pool,
						pj_stun_msg *msg)
{
    pj_stun_msgint_attr *attr = NULL;
    pj_status_t status;

    status = pj_stun_msgint_attr_create(pool, &attr);
    if (status != PJ_SUCCESS)
	return status;

    return pj_stun_msg_add_attr(msg, &attr->hdr);
}

static pj_status_t decode_msgint_attr(pj_pool_t *pool, 
				      const pj_uint8_t *buf,
				      const pj_stun_msg_hdr *msghdr, 
				      void **p_attr)
{
    pj_stun_msgint_attr *attr;

    PJ_UNUSED_ARG(msghdr);

    /* Create attribute */
    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_msgint_attr);
    GETATTRHDR(buf, &attr->hdr);

    /* Check that the attribute length is valid */
    if (attr->hdr.length != 20)
	return PJNATH_ESTUNINATTRLEN;

    /* Copy hmac */
    pj_memcpy(attr->hmac, buf+4, 20);

    /* Done */
    *p_attr = attr;
    return PJ_SUCCESS;
}


static pj_status_t encode_msgint_attr(const void *a, pj_uint8_t *buf, 
				      unsigned len, 
				      const pj_stun_msg_hdr *msghdr,
				      unsigned *printed)
{
    const pj_stun_msgint_attr *ca = (const pj_stun_msgint_attr*)a;

    PJ_CHECK_STACK();
    
    PJ_UNUSED_ARG(msghdr);

    if (len < 24) 
	return PJ_ETOOSMALL;

    /* Copy and convert attribute to network byte order */
    PUTVAL16H(buf, 0, ca->hdr.type);
    PUTVAL16H(buf, 2, ca->hdr.length);

    pj_memcpy(buf+4, ca->hmac, 20);

    /* Done */
    *printed = 24;

    return PJ_SUCCESS;
}


static void* clone_msgint_attr(pj_pool_t *pool, const void *src)
{
    pj_stun_msgint_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_msgint_attr);

    pj_memcpy(dst, src, sizeof(pj_stun_msgint_attr));

    return (void*) dst;
}

//////////////////////////////////////////////////////////////////////////////
/*
 * STUN ERROR-CODE
 */

/*
 * Create a STUN ERROR-CODE attribute.
 */
PJ_DEF(pj_status_t) pj_stun_errcode_attr_create(pj_pool_t *pool,
						int err_code,
						const pj_str_t *err_reason,
						pj_stun_errcode_attr **p_attr)
{
    pj_stun_errcode_attr *attr;
    char err_buf[80];
    pj_str_t str;

    PJ_ASSERT_RETURN(pool && err_code && p_attr, PJ_EINVAL);

    if (err_reason == NULL) {
	str = pj_stun_get_err_reason(err_code);
	if (str.slen == 0) {
	    str.slen = pj_ansi_snprintf(err_buf, sizeof(err_buf),
				        "Unknown error %d", err_code);
	    str.ptr = err_buf;
	}
	err_reason = &str;
    }

    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_errcode_attr);
    INIT_ATTR(attr, PJ_STUN_ATTR_ERROR_CODE, 4+err_reason->slen);
    attr->err_code = err_code;
    pj_strdup(pool, &attr->reason, err_reason);

    *p_attr = attr;

    return PJ_SUCCESS;
}


PJ_DEF(pj_status_t) pj_stun_msg_add_errcode_attr(pj_pool_t *pool,
						 pj_stun_msg *msg,
						 int err_code,
						 const pj_str_t *err_reason)
{
    pj_stun_errcode_attr *err_attr = NULL;
    pj_status_t status;

    status = pj_stun_errcode_attr_create(pool, err_code, err_reason,
					 &err_attr);
    if (status != PJ_SUCCESS)
	return status;

    return pj_stun_msg_add_attr(msg, &err_attr->hdr);
}

static pj_status_t decode_errcode_attr(pj_pool_t *pool, 
				       const pj_uint8_t *buf,
				       const pj_stun_msg_hdr *msghdr, 
				       void **p_attr)
{
    pj_stun_errcode_attr *attr;
    pj_str_t value;

    PJ_UNUSED_ARG(msghdr);

    /* Create the attribute */
    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_errcode_attr);
    GETATTRHDR(buf, &attr->hdr);

    attr->err_code = buf[6] * 100 + buf[7];

    /* Get pointer to the string in the message */
    value.ptr = ((char*)buf + ATTR_HDR_LEN + 4);
    value.slen = attr->hdr.length - 4;

    /* Copy the string to the attribute */
    pj_strdup(pool, &attr->reason, &value);

    /* Done */
    *p_attr = attr;

    return PJ_SUCCESS;
}


static pj_status_t encode_errcode_attr(const void *a, pj_uint8_t *buf, 
				       unsigned len, 
				       const pj_stun_msg_hdr *msghdr,
				       unsigned *printed)
{
    const pj_stun_errcode_attr *ca = 
	(const pj_stun_errcode_attr*)a;

    PJ_CHECK_STACK();
    
    PJ_UNUSED_ARG(msghdr);

    if (len < ATTR_HDR_LEN + 4 + (unsigned)ca->reason.slen) 
	return PJ_ETOOSMALL;

    /* Copy and convert attribute to network byte order */
    PUTVAL16H(buf, 0, ca->hdr.type);
    PUTVAL16H(buf, 2, (pj_uint16_t)(4 + ca->reason.slen));
    PUTVAL16H(buf, 4, 0);
    buf[6] = (pj_uint8_t)(ca->err_code / 100);
    buf[7] = (pj_uint8_t)(ca->err_code % 100);

    /* Copy error string */
    pj_memcpy(buf + ATTR_HDR_LEN + 4, ca->reason.ptr, ca->reason.slen);

    /* Done */
    *printed = (ATTR_HDR_LEN + 4 + ca->reason.slen + 3) & (~3);

    return PJ_SUCCESS;
}


static void* clone_errcode_attr(pj_pool_t *pool, const void *src)
{
    const pj_stun_errcode_attr *asrc = (const pj_stun_errcode_attr*)src;
    pj_stun_errcode_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_errcode_attr);

    pj_memcpy(dst, src, sizeof(pj_stun_errcode_attr));
    pj_strdup(pool, &dst->reason, &asrc->reason);

    return (void*)dst;
}

//////////////////////////////////////////////////////////////////////////////
/*
 * STUN UNKNOWN-ATTRIBUTES attribute
 */

/*
 * Create an empty instance of STUN UNKNOWN-ATTRIBUTES attribute.
 *
 * @param pool		The pool to allocate memory from.
 * @param p_attr	Pointer to receive the attribute.
 *
 * @return		PJ_SUCCESS on success or the appropriate error code.
 */
PJ_DEF(pj_status_t) pj_stun_unknown_attr_create(pj_pool_t *pool,
						unsigned attr_cnt,
						const pj_uint16_t attr_array[],
						pj_stun_unknown_attr **p_attr)
{
    pj_stun_unknown_attr *attr;
    unsigned i;

    PJ_ASSERT_RETURN(pool && attr_cnt < PJ_STUN_MAX_ATTR && p_attr, PJ_EINVAL);

    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_unknown_attr);
    INIT_ATTR(attr, PJ_STUN_ATTR_UNKNOWN_ATTRIBUTES, attr_cnt * 2);

    attr->attr_count = attr_cnt;
    for (i=0; i<attr_cnt; ++i) {
	attr->attrs[i] = attr_array[i];
    }

    /* If the number of unknown attributes is an odd number, one of the
     * attributes MUST be repeated in the list.
     */
    /* No longer necessary
    if ((attr_cnt & 0x01)) {
	attr->attrs[attr_cnt] = attr_array[attr_cnt-1];
    }
    */

    *p_attr = attr;

    return PJ_SUCCESS;
}


/* Create and add STUN UNKNOWN-ATTRIBUTES attribute to the message. */
PJ_DEF(pj_status_t) pj_stun_msg_add_unknown_attr(pj_pool_t *pool,
						 pj_stun_msg *msg,
						 unsigned attr_cnt,
						 const pj_uint16_t attr_type[])
{
    pj_stun_unknown_attr *attr = NULL;
    pj_status_t status;

    status = pj_stun_unknown_attr_create(pool, attr_cnt, attr_type, &attr);
    if (status != PJ_SUCCESS)
	return status;

    return pj_stun_msg_add_attr(msg, &attr->hdr);
}

static pj_status_t decode_unknown_attr(pj_pool_t *pool, 
				       const pj_uint8_t *buf, 
				       const pj_stun_msg_hdr *msghdr, 
				       void **p_attr)
{
    pj_stun_unknown_attr *attr;
    const pj_uint16_t *punk_attr;
    unsigned i;

    PJ_UNUSED_ARG(msghdr);

    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_unknown_attr);
    GETATTRHDR(buf, &attr->hdr);
 
    attr->attr_count = (attr->hdr.length >> 1);
    if (attr->attr_count > PJ_STUN_MAX_ATTR)
	return PJ_ETOOMANY;

    punk_attr = (const pj_uint16_t*)(buf + ATTR_HDR_LEN);
    for (i=0; i<attr->attr_count; ++i) {
	attr->attrs[i] = pj_ntohs(punk_attr[i]);
    }

    /* Done */
    *p_attr = attr;

    return PJ_SUCCESS;
}


static pj_status_t encode_unknown_attr(const void *a, pj_uint8_t *buf, 
				       unsigned len, 
				       const pj_stun_msg_hdr *msghdr,
				       unsigned *printed)
{
    const pj_stun_unknown_attr *ca = (const pj_stun_unknown_attr*) a;
    pj_uint16_t *dst_unk_attr;
    unsigned i;

    PJ_CHECK_STACK();
    
    PJ_UNUSED_ARG(msghdr);

    /* Check that buffer is enough */
    if (len < ATTR_HDR_LEN + (ca->attr_count << 1))
	return PJ_ETOOSMALL;

    PUTVAL16H(buf, 0, ca->hdr.type);
    PUTVAL16H(buf, 2, (pj_uint16_t)(ca->attr_count << 1));

    /* Copy individual attribute */
    dst_unk_attr = (pj_uint16_t*)(buf + ATTR_HDR_LEN);
    for (i=0; i < ca->attr_count; ++i, ++dst_unk_attr) {
	*dst_unk_attr = pj_htons(ca->attrs[i]);
    }

    /* Done */
    *printed = (ATTR_HDR_LEN + (ca->attr_count << 1) + 3) & (~3);

    return PJ_SUCCESS;
}


static void* clone_unknown_attr(pj_pool_t *pool, const void *src)
{
    pj_stun_unknown_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_unknown_attr);

    pj_memcpy(dst, src, sizeof(pj_stun_unknown_attr));
    
    return (void*)dst;
}

//////////////////////////////////////////////////////////////////////////////
/*
 * STUN generic binary attribute
 */

/*
 * Initialize STUN binary attribute.
 */
PJ_DEF(pj_status_t) pj_stun_binary_attr_init( pj_stun_binary_attr *attr,
					      pj_pool_t *pool,
					      int attr_type,
					      const pj_uint8_t *data,
					      unsigned length)
{
    PJ_ASSERT_RETURN(attr_type, PJ_EINVAL);

    INIT_ATTR(attr, attr_type, length);

    attr->magic = PJ_STUN_MAGIC;

    if (data && length) {
	attr->length = length;
	attr->data = (pj_uint8_t*) pj_pool_alloc(pool, length);
	pj_memcpy(attr->data, data, length);
    } else {
	attr->data = NULL;
	attr->length = 0;
    }

    return PJ_SUCCESS;
}


/*
 * Create a blank binary attribute.
 */
PJ_DEF(pj_status_t) pj_stun_binary_attr_create(pj_pool_t *pool,
					       int attr_type,
					       const pj_uint8_t *data,
					       unsigned length,
					       pj_stun_binary_attr **p_attr)
{
    pj_stun_binary_attr *attr;

    PJ_ASSERT_RETURN(pool && attr_type && p_attr, PJ_EINVAL);
    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_binary_attr);
    *p_attr = attr;
    return pj_stun_binary_attr_init(attr, pool, attr_type, data, length);
}


/* Create and add binary attr. */
PJ_DEF(pj_status_t) pj_stun_msg_add_binary_attr(pj_pool_t *pool,
						pj_stun_msg *msg,
						int attr_type,
						const pj_uint8_t *data,
						unsigned length)
{
    pj_stun_binary_attr *attr = NULL;
    pj_status_t status;

    status = pj_stun_binary_attr_create(pool, attr_type,
					data, length, &attr);
    if (status != PJ_SUCCESS)
	return status;

    return pj_stun_msg_add_attr(msg, &attr->hdr);
}


static pj_status_t decode_binary_attr(pj_pool_t *pool, 
				      const pj_uint8_t *buf,
				      const pj_stun_msg_hdr *msghdr,
				      void **p_attr)
{
    pj_stun_binary_attr *attr;

    PJ_UNUSED_ARG(msghdr);

    /* Create the attribute */
    attr = PJ_POOL_ZALLOC_T(pool, pj_stun_binary_attr);
    GETATTRHDR(buf, &attr->hdr);

    /* Copy the data to the attribute */
    attr->length = attr->hdr.length;
    attr->data = (pj_uint8_t*) pj_pool_alloc(pool, attr->length);
    pj_memcpy(attr->data, buf+ATTR_HDR_LEN, attr->length);

    /* Done */
    *p_attr = attr;

    return PJ_SUCCESS;

}


static pj_status_t encode_binary_attr(const void *a, pj_uint8_t *buf, 
				      unsigned len, 
				      const pj_stun_msg_hdr *msghdr,
				      unsigned *printed)
{
    const pj_stun_binary_attr *ca = (const pj_stun_binary_attr*)a;

    PJ_CHECK_STACK();
    
    PJ_UNUSED_ARG(msghdr);

    /* Calculated total attr_len (add padding if necessary) */
    *printed = (ca->length + ATTR_HDR_LEN + 3) & (~3);
    if (len < *printed)
	return PJ_ETOOSMALL;

    PUTVAL16H(buf, 0, ca->hdr.type);
    PUTVAL16H(buf, 2, (pj_uint16_t) ca->length);

    /* Copy the data */
    pj_memcpy(buf+ATTR_HDR_LEN, ca->data, ca->length);

    /* Done */
    return PJ_SUCCESS;
}


static void* clone_binary_attr(pj_pool_t *pool, const void *src)
{
    const pj_stun_binary_attr *asrc = (const pj_stun_binary_attr*)src;
    pj_stun_binary_attr *dst = PJ_POOL_ALLOC_T(pool, pj_stun_binary_attr);

    pj_memcpy(dst, src, sizeof(pj_stun_binary_attr));

    if (asrc->length) {
	dst->data = (pj_uint8_t*) pj_pool_alloc(pool, asrc->length);
	pj_memcpy(dst->data, asrc->data, asrc->length);
    }

    return (void*)dst;
}

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

/*
 * Initialize a generic STUN message.
 */
PJ_DEF(pj_status_t) pj_stun_msg_init( pj_stun_msg *msg,
				      unsigned msg_type,
				      pj_uint32_t magic,
				      const pj_uint8_t tsx_id[12])
{
    PJ_ASSERT_RETURN(msg && msg_type, PJ_EINVAL);

    msg->hdr.type = (pj_uint16_t) msg_type;
    msg->hdr.length = 0;
    msg->hdr.magic = magic;
    msg->attr_count = 0;

    if (tsx_id) {
	pj_memcpy(&msg->hdr.tsx_id, tsx_id, sizeof(msg->hdr.tsx_id));
    } else {
	struct transaction_id
	{
	    pj_uint32_t	    proc_id;
	    pj_uint32_t	    random;
	    pj_uint32_t	    counter;
	} id;
	static pj_uint32_t pj_stun_tsx_id_counter;

	if (!pj_stun_tsx_id_counter)
	    pj_stun_tsx_id_counter = pj_rand();

	id.proc_id = pj_getpid();
	id.random = pj_rand();
	id.counter = pj_stun_tsx_id_counter++;

	pj_memcpy(&msg->hdr.tsx_id, &id, sizeof(msg->hdr.tsx_id));
    }

    return PJ_SUCCESS;
}


/*
 * Create a blank STUN message.
 */
PJ_DEF(pj_status_t) pj_stun_msg_create( pj_pool_t *pool,
					unsigned msg_type,
					pj_uint32_t magic,
					const pj_uint8_t tsx_id[12],
					pj_stun_msg **p_msg)
{
    pj_stun_msg *msg;

    PJ_ASSERT_RETURN(pool && msg_type && p_msg, PJ_EINVAL);

    msg = PJ_POOL_ZALLOC_T(pool, pj_stun_msg);
    *p_msg = msg;
    return pj_stun_msg_init(msg, msg_type, magic, tsx_id);
}


/*
 * Clone a STUN message with all of its attributes.
 */
PJ_DEF(pj_stun_msg*) pj_stun_msg_clone( pj_pool_t *pool,
					const pj_stun_msg *src)
{
    pj_stun_msg *dst;
    unsigned i;

    PJ_ASSERT_RETURN(pool && src, NULL);

    dst = PJ_POOL_ZALLOC_T(pool, pj_stun_msg);
    pj_memcpy(dst, src, sizeof(pj_stun_msg));

    /* Duplicate the attributes */
    for (i=0, dst->attr_count=0; i<src->attr_count; ++i) {
	dst->attr[dst->attr_count] = pj_stun_attr_clone(pool, src->attr[i]);
	if (dst->attr[dst->attr_count])
	    ++dst->attr_count;
    }

    return dst;
}


/*
 * Add STUN attribute to STUN message.
 */
PJ_DEF(pj_status_t) pj_stun_msg_add_attr(pj_stun_msg *msg,
					 pj_stun_attr_hdr *attr)
{
    PJ_ASSERT_RETURN(msg && attr, PJ_EINVAL);
    PJ_ASSERT_RETURN(msg->attr_count < PJ_STUN_MAX_ATTR, PJ_ETOOMANY);

    msg->attr[msg->attr_count++] = attr;
    return PJ_SUCCESS;
}


/*
 * Check that the PDU is potentially a valid STUN message.
 */
PJ_DEF(pj_status_t) pj_stun_msg_check(const pj_uint8_t *pdu, pj_size_t pdu_len,
				      unsigned options)
{
    pj_size_t msg_len;

    PJ_ASSERT_RETURN(pdu, PJ_EINVAL);

    if (pdu_len < sizeof(pj_stun_msg_hdr))
	return PJNATH_EINSTUNMSGLEN;

    /* First byte of STUN message is always 0x00 or 0x01. */
    if (*pdu != 0x00 && *pdu != 0x01)
	return PJNATH_EINSTUNMSGTYPE;

    /* Check the PDU length */
    msg_len = GETVAL16H(pdu, 2);
    if ((msg_len + 20 > pdu_len) || 
	((options & PJ_STUN_IS_DATAGRAM) && msg_len + 20 != pdu_len))
    {
	return PJNATH_EINSTUNMSGLEN;
    }

    /* STUN message is always padded to the nearest 4 bytes, thus
     * the last two bits of the length field are always zero.
     */
    if ((msg_len & 0x03) != 0) {
	return PJNATH_EINSTUNMSGLEN;
    }

    /* If magic is set, then there is great possibility that this is
     * a STUN message.
     */
    if (GETVAL32H(pdu, 4) == PJ_STUN_MAGIC) {

	/* Check if FINGERPRINT attribute is present */
	if ((options & PJ_STUN_NO_FINGERPRINT_CHECK )==0 && 
	    GETVAL16H(pdu, msg_len + 20 - 8) == PJ_STUN_ATTR_FINGERPRINT) 
	{
	    pj_uint16_t attr_len = GETVAL16H(pdu, msg_len + 20 - 8 + 2);
	    pj_uint32_t fingerprint = GETVAL32H(pdu, msg_len + 20 - 8 + 4);
	    pj_uint32_t crc;

	    if (attr_len != 4)
		return PJNATH_ESTUNINATTRLEN;

	    crc = pj_crc32_calc(pdu, msg_len + 20 - 8);
	    crc ^= STUN_XOR_FINGERPRINT;

	    if (crc != fingerprint)
		return PJNATH_ESTUNFINGERPRINT;
	}
    }

    /* Could be a STUN message */
    return PJ_SUCCESS;
}


/* Create error response */
PJ_DEF(pj_status_t) pj_stun_msg_create_response(pj_pool_t *pool,
						const pj_stun_msg *req_msg,
						unsigned err_code,
						const pj_str_t *err_msg,
						pj_stun_msg **p_response)
{
    unsigned msg_type = req_msg->hdr.type;
    pj_stun_msg *response = NULL;
    pj_status_t status;

    PJ_ASSERT_RETURN(pool && p_response, PJ_EINVAL);

    PJ_ASSERT_RETURN(PJ_STUN_IS_REQUEST(msg_type), 
		     PJNATH_EINSTUNMSGTYPE);

    /* Create response or error response */
    if (err_code)
	msg_type |= PJ_STUN_ERROR_RESPONSE_BIT;
    else
	msg_type |= PJ_STUN_SUCCESS_RESPONSE_BIT;

    status = pj_stun_msg_create(pool, msg_type, req_msg->hdr.magic, 
				req_msg->hdr.tsx_id, &response);
    if (status != PJ_SUCCESS) {
	return status;
    }

    /* Add error code attribute */
    if (err_code) {
	status = pj_stun_msg_add_errcode_attr(pool, response, 
					      err_code, err_msg);
	if (status != PJ_SUCCESS) {
	    return status;
	}
    }

    *p_response = response;
    return PJ_SUCCESS;
}


/*
 * Parse incoming packet into STUN message.
 */
PJ_DEF(pj_status_t) pj_stun_msg_decode(pj_pool_t *pool,
				       const pj_uint8_t *pdu,
				       pj_size_t pdu_len,
				       unsigned options,
				       pj_stun_msg **p_msg,
				       pj_size_t *p_parsed_len,
				       pj_stun_msg **p_response)
{
    
    pj_stun_msg *msg;
    unsigned uattr_cnt;
    const pj_uint8_t *start_pdu = pdu;
    pj_bool_t has_msg_int = PJ_FALSE;
    pj_bool_t has_fingerprint = PJ_FALSE;
    pj_status_t status;

    PJ_UNUSED_ARG(options);

    PJ_ASSERT_RETURN(pool && pdu && pdu_len && p_msg, PJ_EINVAL);
    PJ_ASSERT_RETURN(sizeof(pj_stun_msg_hdr) == 20, PJ_EBUG);

    if (p_parsed_len)
	*p_parsed_len = 0;
    if (p_response)
	*p_response = NULL;

    /* Check if this is a STUN message, if necessary */
    if (options & PJ_STUN_CHECK_PACKET) {
	status = pj_stun_msg_check(pdu, pdu_len, options);
	if (status != PJ_SUCCESS)
	    return status;
    }

    /* Create the message, copy the header, and convert to host byte order */
    msg = PJ_POOL_ZALLOC_T(pool, pj_stun_msg);
    pj_memcpy(&msg->hdr, pdu, sizeof(pj_stun_msg_hdr));
    msg->hdr.type = pj_ntohs(msg->hdr.type);
    msg->hdr.length = pj_ntohs(msg->hdr.length);
    msg->hdr.magic = pj_ntohl(msg->hdr.magic);

    pdu += sizeof(pj_stun_msg_hdr);
    /* pdu_len -= sizeof(pj_stun_msg_hdr); */
    pdu_len = msg->hdr.length;

    /* No need to create response if this is not a request */
    if (!PJ_STUN_IS_REQUEST(msg->hdr.type))
	p_response = NULL;

    /* Parse attributes */
    uattr_cnt = 0;
    while (pdu_len >= 4) {
	unsigned attr_type, attr_val_len;
	const struct attr_desc *adesc;

	/* Get attribute type and length. If length is not aligned
	 * to 4 bytes boundary, add padding.
	 */
	attr_type = GETVAL16H(pdu, 0);
	attr_val_len = GETVAL16H(pdu, 2);
	attr_val_len = (attr_val_len + 3) & (~3);

	/* Check length */
	if (pdu_len < attr_val_len) {
	    pj_str_t err_msg;
	    char err_msg_buf[80];

	    err_msg.ptr = err_msg_buf;
	    err_msg.slen = pj_ansi_snprintf(err_msg_buf, sizeof(err_msg_buf),
					    "Attribute %s has invalid length",
					    pj_stun_get_attr_name(attr_type));

	    PJ_LOG(4,(THIS_FILE, "Error decoding message: %.*s",
		      (int)err_msg.slen, err_msg.ptr));

	    if (p_response) {
		pj_stun_msg_create_response(pool, msg, 
					    PJ_STUN_SC_BAD_REQUEST, 
					    &err_msg, p_response);
	    }
	    return PJNATH_ESTUNINATTRLEN;
	}

	/* Get the attribute descriptor */
	adesc = find_attr_desc(attr_type);

	if (adesc == NULL) {
	    /* Unrecognized attribute */
	    pj_stun_binary_attr *attr = NULL;

	    PJ_LOG(5,(THIS_FILE, "Unrecognized attribute type 0x%x", 
		      attr_type));

	    /* Is this a fatal condition? */
	    if (attr_type <= 0x7FFF) {
		/* This is a mandatory attribute, we must return error
		 * if we don't understand the attribute.
		 */
		if (p_response) {
		    unsigned err_code = PJ_STUN_SC_UNKNOWN_ATTRIBUTE;

		    status = pj_stun_msg_create_response(pool, msg,
							 err_code, NULL, 
							 p_response);
		    if (status==PJ_SUCCESS) {
			pj_uint16_t d = (pj_uint16_t)attr_type;
			pj_stun_msg_add_unknown_attr(pool, *p_response, 1, &d);
		    }
		}

		return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_SC_UNKNOWN_ATTRIBUTE);
	    }

	    /* Make sure we have rooms for the new attribute */
	    if (msg->attr_count >= PJ_STUN_MAX_ATTR) {
		if (p_response) {
		    pj_stun_msg_create_response(pool, msg,
						PJ_STUN_SC_SERVER_ERROR,
						NULL, p_response);
		}
		return PJNATH_ESTUNTOOMANYATTR;
	    }

	    /* Create binary attribute to represent this */
	    status = pj_stun_binary_attr_create(pool, attr_type, pdu+4, 
						GETVAL16H(pdu, 2), &attr);
	    if (status != PJ_SUCCESS) {
		if (p_response) {
		    pj_stun_msg_create_response(pool, msg,
						PJ_STUN_SC_SERVER_ERROR,
						NULL, p_response);
		}

		PJ_LOG(4,(THIS_FILE, 
			  "Error parsing unknown STUN attribute type %d",
			  attr_type));

		return status;
	    }

	    /* Add the attribute */
	    msg->attr[msg->attr_count++] = &attr->hdr;

	} else {
	    void *attr;
	    char err_msg1[PJ_ERR_MSG_SIZE],
		 err_msg2[PJ_ERR_MSG_SIZE];

	    /* Parse the attribute */
	    status = (adesc->decode_attr)(pool, pdu, &msg->hdr, &attr);

	    if (status != PJ_SUCCESS) {
		pj_strerror(status, err_msg1, sizeof(err_msg1));

		if (p_response) {
		    pj_str_t e;

		    e.ptr = err_msg2;
		    e.slen= pj_ansi_snprintf(err_msg2, sizeof(err_msg2),
					     "%s in %s",
					     err_msg1,
					     pj_stun_get_attr_name(attr_type));

		    pj_stun_msg_create_response(pool, msg,
						PJ_STUN_SC_BAD_REQUEST,
						&e, p_response);
		}

		PJ_LOG(4,(THIS_FILE, 
			  "Error parsing STUN attribute %s: %s",
			  pj_stun_get_attr_name(attr_type), 
			  err_msg1));

		return status;
	    }

	    if (attr_type == PJ_STUN_ATTR_MESSAGE_INTEGRITY && 
		!has_fingerprint) 
	    {
		if (has_msg_int) {
		    /* Already has MESSAGE-INTEGRITY */
		    if (p_response) {
			pj_stun_msg_create_response(pool, msg,
						    PJ_STUN_SC_BAD_REQUEST,
						    NULL, p_response);
		    }
		    return PJNATH_ESTUNDUPATTR;
		}
		has_msg_int = PJ_TRUE;

	    } else if (attr_type == PJ_STUN_ATTR_FINGERPRINT) {
		if (has_fingerprint) {
		    /* Already has FINGERPRINT */
		    if (p_response) {
			pj_stun_msg_create_response(pool, msg,
						    PJ_STUN_SC_BAD_REQUEST,
						    NULL, p_response);
		    }
		    return PJNATH_ESTUNDUPATTR;
		}
		has_fingerprint = PJ_TRUE;
	    } else {
		if (has_fingerprint) {
		    /* Another attribute is found which is not FINGERPRINT
		     * after FINGERPRINT. Note that non-FINGERPRINT is
		     * allowed to appear after M-I
		     */
		    if (p_response) {
			pj_stun_msg_create_response(pool, msg,
						    PJ_STUN_SC_BAD_REQUEST,
						    NULL, p_response);
		    }
		    return PJNATH_ESTUNFINGERPOS;
		}
	    }

	    /* Make sure we have rooms for the new attribute */
	    if (msg->attr_count >= PJ_STUN_MAX_ATTR) {
		if (p_response) {
		    pj_stun_msg_create_response(pool, msg,
						PJ_STUN_SC_SERVER_ERROR,
						NULL, p_response);
		}
		return PJNATH_ESTUNTOOMANYATTR;
	    }

	    /* Add the attribute */
	    msg->attr[msg->attr_count++] = (pj_stun_attr_hdr*)attr;
	}

	/* Next attribute */
	if (attr_val_len + 4 >= pdu_len) {
	    pdu += pdu_len;
	    pdu_len = 0;
	} else {
	    pdu += (attr_val_len + 4);
	    pdu_len -= (attr_val_len + 4);
	}
    }

    if (pdu_len > 0) {
	/* Stray trailing bytes */
	PJ_LOG(4,(THIS_FILE, 
		  "Error decoding STUN message: unparsed trailing %d bytes",
		  pdu_len));
	return PJNATH_EINSTUNMSGLEN;
    }

    *p_msg = msg;

    if (p_parsed_len)
	*p_parsed_len = (pdu - start_pdu);

    return PJ_SUCCESS;
}

/*
static char *print_binary(const pj_uint8_t *data, unsigned data_len)
{
    static char static_buffer[1024];
    char *buffer = static_buffer;
    unsigned length=sizeof(static_buffer), i;

    if (length < data_len * 2 + 8)
	return "";

    pj_ansi_sprintf(buffer, ", data=");
    buffer += 7;

    for (i=0; i<data_len; ++i) {
	pj_ansi_sprintf(buffer, "%02x", (*data) & 0xFF);
	buffer += 2;
	data++;
    }

    pj_ansi_sprintf(buffer, "\n");
    buffer++;

    return static_buffer;
}
*/

/*
 * Print the message structure to a buffer.
 */
PJ_DEF(pj_status_t) pj_stun_msg_encode(pj_stun_msg *msg,
				       pj_uint8_t *buf, pj_size_t buf_size,
				       unsigned options,
				       const pj_str_t *key,
				       pj_size_t *p_msg_len)
{
    pj_uint8_t *start = buf;
    pj_stun_msgint_attr *amsgint = NULL;
    pj_stun_fingerprint_attr *afingerprint = NULL;
    unsigned printed = 0, body_len;
    pj_status_t status;
    unsigned i;


    PJ_ASSERT_RETURN(msg && buf && buf_size, PJ_EINVAL);

    PJ_UNUSED_ARG(options);
    PJ_ASSERT_RETURN(options == 0, PJ_EINVAL);

    /* Copy the message header part and convert the header fields to
     * network byte order
     */
    if (buf_size < sizeof(pj_stun_msg_hdr))
	return PJ_ETOOSMALL;
    
    PUTVAL16H(buf, 0, msg->hdr.type);
    PUTVAL16H(buf, 2, 0);   /* length will be calculated later */
    PUTVAL32H(buf, 4, msg->hdr.magic);
    pj_memcpy(buf+8, msg->hdr.tsx_id, sizeof(msg->hdr.tsx_id));

    buf += sizeof(pj_stun_msg_hdr);
    buf_size -= sizeof(pj_stun_msg_hdr);

    /* Encode each attribute to the message */
    for (i=0; i<msg->attr_count; ++i) {
	const struct attr_desc *adesc;
	const pj_stun_attr_hdr *attr_hdr = msg->attr[i];

	if (attr_hdr->type == PJ_STUN_ATTR_MESSAGE_INTEGRITY) {
	    pj_assert(amsgint == NULL);
	    amsgint = (pj_stun_msgint_attr*) attr_hdr;

	    /* Stop when encountering MESSAGE-INTEGRITY */
	    break;

	} else if (attr_hdr->type == PJ_STUN_ATTR_FINGERPRINT) {
	    afingerprint = (pj_stun_fingerprint_attr*) attr_hdr;
	    break;
	}

	adesc = find_attr_desc(attr_hdr->type);
	if (adesc) {
	    status = adesc->encode_attr(attr_hdr, buf, buf_size, &msg->hdr, 
					&printed);
	} else {
	    /* This may be a generic attribute */
	    const pj_stun_binary_attr *bin_attr = (const pj_stun_binary_attr*) 
						   attr_hdr;
	    PJ_ASSERT_RETURN(bin_attr->magic == PJ_STUN_MAGIC, PJ_EBUG);
	    status = encode_binary_attr(bin_attr, buf, buf_size, &msg->hdr,
					&printed);
	}

	if (status != PJ_SUCCESS)
	    return status;

	buf += printed;
	buf_size -= printed;
    }

    /* We may have stopped printing attribute because we found
     * MESSAGE-INTEGRITY or FINGERPRINT. Scan the rest of the
     * attributes.
     */
    for ( ++i; i<msg->attr_count; ++i) {
	const pj_stun_attr_hdr *attr_hdr = msg->attr[i];

	/* There mustn't any attribute after FINGERPRINT */
	PJ_ASSERT_RETURN(afingerprint == NULL, PJNATH_ESTUNFINGERPOS);

	if (attr_hdr->type == PJ_STUN_ATTR_MESSAGE_INTEGRITY) {
	    /* There mustn't be MESSAGE-INTEGRITY before */
	    PJ_ASSERT_RETURN(amsgint == NULL, 
			     PJNATH_ESTUNMSGINTPOS);
	    amsgint = (pj_stun_msgint_attr*) attr_hdr;

	} else if (attr_hdr->type == PJ_STUN_ATTR_FINGERPRINT) {
	    afingerprint = (pj_stun_fingerprint_attr*) attr_hdr;
	}
    }

#if PJ_STUN_OLD_STYLE_MI_FINGERPRINT
    /*
     * This is the old style MESSAGE-INTEGRITY and FINGERPRINT
     * calculation, used in rfc3489bis-06 and older.
     */
    /* We MUST update the message length in the header NOW before
     * calculating MESSAGE-INTEGRITY and FINGERPRINT. 
     * Note that length is not including the 20 bytes header.
      */
    if (amsgint && afingerprint) {
	body_len = (pj_uint16_t)((buf - start) - 20 + 24 + 8);
    } else if (amsgint) {
	body_len = (pj_uint16_t)((buf - start) - 20 + 24);
    } else if (afingerprint) {
	body_len = (pj_uint16_t)((buf - start) - 20 + 8);
    } else {
	body_len = (pj_uint16_t)((buf - start) - 20);
    }
#else
    /* If MESSAGE-INTEGRITY is present, include the M-I attribute
     * in message length before calculating M-I
     */
    if (amsgint) {
	body_len = (pj_uint16_t)((buf - start) - 20 + 24);
    } else {
	body_len = (pj_uint16_t)((buf - start) - 20);
    }
#endif	/* PJ_STUN_OLD_STYLE_MI_FINGERPRINT */

    /* hdr->length = pj_htons(length); */
    PUTVAL16H(start, 2, (pj_uint16_t)body_len);

    /* Calculate message integrity, if present */
    if (amsgint != NULL) {
	pj_hmac_sha1_context ctx;

	/* Key MUST be specified */
	PJ_ASSERT_RETURN(key, PJ_EINVALIDOP);

	/* MESSAGE-INTEGRITY must be the last attribute in the message, or
	 * the last attribute before FINGERPRINT.
	 */
	if (msg->attr_count>1 && i < msg->attr_count-2) {
	    /* Should not happen for message generated by us */
	    pj_assert(PJ_FALSE);
	    return PJNATH_ESTUNMSGINTPOS;

	} else if (i == msg->attr_count-2)  {
	    if (msg->attr[i+1]->type != PJ_STUN_ATTR_FINGERPRINT) {
		/* Should not happen for message generated by us */
		pj_assert(PJ_FALSE);
		return PJNATH_ESTUNMSGINTPOS;
	    } else {
		afingerprint = (pj_stun_fingerprint_attr*) msg->attr[i+1];
	    }
	}

	/* Calculate HMAC-SHA1 digest, add zero padding to input
	 * if necessary to make the input 64 bytes aligned.
	 */
	pj_hmac_sha1_init(&ctx, (const pj_uint8_t*)key->ptr, key->slen);
	pj_hmac_sha1_update(&ctx, (const pj_uint8_t*)start, buf-start);
#if PJ_STUN_OLD_STYLE_MI_FINGERPRINT
	// These are obsoleted in rfc3489bis-08
	if ((buf-start) & 0x3F) {
	    pj_uint8_t zeroes[64];
	    pj_bzero(zeroes, sizeof(zeroes));
	    pj_hmac_sha1_update(&ctx, zeroes, 64-((buf-start) & 0x3F));
	}
#endif	/* PJ_STUN_OLD_STYLE_MI_FINGERPRINT */
	pj_hmac_sha1_final(&ctx, amsgint->hmac);

	/* Put this attribute in the message */
	status = encode_msgint_attr(amsgint, buf, buf_size, 
			            &msg->hdr, &printed);
	if (status != PJ_SUCCESS)
	    return status;

	buf += printed;
	buf_size -= printed;
    }

    /* Calculate FINGERPRINT if present */
    if (afingerprint != NULL) {

#if !PJ_STUN_OLD_STYLE_MI_FINGERPRINT
	/* Update message length */
	PUTVAL16H(start, 2, 
		 (pj_uint16_t)(GETVAL16H(start, 2)+8));
#endif

	afingerprint->value = pj_crc32_calc(start, buf-start);
	afingerprint->value ^= STUN_XOR_FINGERPRINT;

	/* Put this attribute in the message */
	status = encode_uint_attr(afingerprint, buf, buf_size, 
				  &msg->hdr, &printed);
	if (status != PJ_SUCCESS)
	    return status;

	buf += printed;
	buf_size -= printed;
    }

    /* Update message length. */
    msg->hdr.length = (pj_uint16_t) ((buf - start) - 20);

    /* Return the length */
    if (p_msg_len)
	*p_msg_len = (buf - start);

    return PJ_SUCCESS;
}


/*
 * Find STUN attribute in the STUN message, starting from the specified
 * index.
 */
PJ_DEF(pj_stun_attr_hdr*) pj_stun_msg_find_attr( const pj_stun_msg *msg,
						 int attr_type,
						 unsigned index)
{
    PJ_ASSERT_RETURN(msg, NULL);

    for (; index < msg->attr_count; ++index) {
	if (msg->attr[index]->type == attr_type)
	    return (pj_stun_attr_hdr*) msg->attr[index];
    }

    return NULL;
}


/*
 * Clone a STUN attribute.
 */
PJ_DEF(pj_stun_attr_hdr*) pj_stun_attr_clone( pj_pool_t *pool,
					      const pj_stun_attr_hdr *attr)
{
    const struct attr_desc *adesc;

    /* Get the attribute descriptor */
    adesc = find_attr_desc(attr->type);
    if (adesc) {
	return (pj_stun_attr_hdr*) (*adesc->clone_attr)(pool, attr);
    } else {
	/* Clone generic attribute */
	const pj_stun_binary_attr *bin_attr = (const pj_stun_binary_attr*)
					       attr;
	PJ_ASSERT_RETURN(bin_attr->magic == PJ_STUN_MAGIC, NULL);
	if (bin_attr->magic == PJ_STUN_MAGIC) {
	    return (pj_stun_attr_hdr*) clone_binary_attr(pool, attr);
	} else {
	    return NULL;
	}
    }
}


