Index: /pjproject/trunk/pjlib-util/build/pjlib_util_test.dsp
===================================================================
--- /pjproject/trunk/pjlib-util/build/pjlib_util_test.dsp	(revision 1002)
+++ /pjproject/trunk/pjlib-util/build/pjlib_util_test.dsp	(revision 1003)
@@ -43,5 +43,5 @@
 # PROP Target_Dir ""
 # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
-# ADD CPP /nologo /MD /W3 /GX /O2 /I "../include" /I "../../pjlib/include" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D PJ_WIN32=1 /D PJ_M_I386=1 /FR /YX /FD /c
+# ADD CPP /nologo /MD /W4 /GX /O2 /I "../include" /I "../../pjlib/include" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D PJ_WIN32=1 /D PJ_M_I386=1 /FR /YX /FD /c
 # ADD BASE RSC /l 0x409 /d "NDEBUG"
 # ADD RSC /l 0x409 /d "NDEBUG"
@@ -67,5 +67,5 @@
 # PROP Target_Dir ""
 # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c
-# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I "../include" /I "../../pjlib/include" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D PJ_WIN32=1 /D PJ_M_I386=1 /FR /YX /FD /GZ /c
+# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "../include" /I "../../pjlib/include" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D PJ_WIN32=1 /D PJ_M_I386=1 /FR /YX /FD /GZ /c
 # ADD BASE RSC /l 0x409 /d "_DEBUG"
 # ADD RSC /l 0x409 /d "_DEBUG"
@@ -96,4 +96,8 @@
 # Begin Source File
 
+SOURCE="..\src\pjlib-util-test\stun.c"
+# End Source File
+# Begin Source File
+
 SOURCE="..\src\pjlib-util-test\test.c"
 # End Source File
Index: /pjproject/trunk/pjlib-util/build/pjlib_util_test.vcproj
===================================================================
--- /pjproject/trunk/pjlib-util/build/pjlib_util_test.vcproj	(revision 1002)
+++ /pjproject/trunk/pjlib-util/build/pjlib_util_test.vcproj	(revision 1003)
@@ -234,4 +234,8 @@
 			</File>
 			<File
+				RelativePath="..\src\pjlib-util-test\stun.c"
+				>
+			</File>
+			<File
 				RelativePath="..\src\pjlib-util-test\test.c"
 				>
Index: /pjproject/trunk/pjlib-util/include/pjlib-util/config.h
===================================================================
--- /pjproject/trunk/pjlib-util/include/pjlib-util/config.h	(revision 1002)
+++ /pjproject/trunk/pjlib-util/include/pjlib-util/config.h	(revision 1003)
@@ -212,4 +212,22 @@
 #endif
 
+
+/* **************************************************************************
+ * ENCRYPTION
+ */
+
+/**
+ * Specifies whether CRC32 algorithm should use the table based lookup table
+ * for faster calculation, at the expense of about 1KB table size on the
+ * executable. If zero, the CRC32 will use non-table based which is more than
+ * an order of magnitude slower.
+ *
+ * Default: 1
+ */
+#ifndef PJ_CRC32_HAS_TABLES
+#   define PJ_CRC32_HAS_TABLES			    1
+#endif
+
+
 /**
  * @}
Index: /pjproject/trunk/pjlib-util/include/pjlib-util/crc32.h
===================================================================
--- /pjproject/trunk/pjlib-util/include/pjlib-util/crc32.h	(revision 1002)
+++ /pjproject/trunk/pjlib-util/include/pjlib-util/crc32.h	(revision 1003)
@@ -25,5 +25,5 @@
  */
 
-#include <pj/types.h>
+#include <pjlib-util/types.h>
 
 PJ_BEGIN_DECL
Index: /pjproject/trunk/pjlib-util/include/pjlib-util/stun_msg.h
===================================================================
--- /pjproject/trunk/pjlib-util/include/pjlib-util/stun_msg.h	(revision 1002)
+++ /pjproject/trunk/pjlib-util/include/pjlib-util/stun_msg.h	(revision 1003)
@@ -1246,4 +1246,104 @@
 				        pj_stun_msg **p_response);
 
+typedef enum pj_stun_auth_policy_type
+{
+    PJ_STUN_POLICY_NONE,
+    PJ_STUN_POLICY_STATIC_SHORT_TERM,
+    PJ_STUN_POLICY_STATIC_LONG_TERM,
+    PJ_STUN_POLICY_DYNAMIC
+} pj_stun_auth_policy_type;
+
+typedef struct pj_stun_auth_policy
+{
+    pj_stun_auth_policy_type	type;
+    void		       *user_data;
+
+    union 
+    {
+	struct
+	{
+	    pj_str_t	  username;
+	    pj_str_t	  password;
+	    pj_str_t	  nonce;
+	} static_short_term;
+
+	struct
+	{
+	    pj_str_t	  realm;
+	    pj_str_t	  username;
+	    pj_str_t	  password;
+	    pj_str_t	  nonce;
+	} static_long_term;
+
+	struct
+	{
+	    /**
+	     * This callback is called by pj_stun_verify_credential() when
+	     * server needs to challenge the request with 401 response.
+	     *
+	     * @param user_data	The user data as specified in the policy.
+	     * @param pool	Pool to allocate memory.
+	     * @param realm	On return, the function should fill in with
+	     *			realm if application wants to use long term
+	     *			credential. Otherwise application should set
+	     *			empty string for the realm.
+	     * @param nonce	On return, if application wants to use long
+	     *			term credential, it MUST fill in the nonce
+	     *			with some value. Otherwise  if short term 
+	     *			credential is wanted, it MAY set this value.
+	     *			If short term credential is wanted and the
+	     *			application doesn't want to include NONCE,
+	     *			then it must set this to empty string.
+	     *
+	     * @return		The callback should return PJ_SUCCESS, or
+	     *			otherwise response message will not be 
+	     *			created.
+	     */
+	    pj_status_t (*get_auth)(void *user_data,
+				    pj_pool_t *pool,
+				    pj_str_t *realm,
+				    pj_str_t *nonce);
+
+	    /**
+	     * Get the password for the specified username. This function 
+	     * is also used to check whether the username is valid.
+	     *
+	     * @param user_data	The user data as specified in the policy.
+	     * @param realm	The realm as specified in the message.
+	     * @param username	The username as specified in the message.
+	     * @param pool	Pool to allocate memory when necessary.
+	     * @param password	On return, application should fill up this
+	     *			argument with the password.
+	     *
+	     * @return		The callback should return PJ_SUCCESS if
+	     *			username has been successfully verified
+	     *			and password was obtained. If non-PJ_SUCCESS
+	     *			is returned, it is assumed that the
+	     *			username is not valid.
+	     */
+	    pj_status_t	(*get_password)(void *user_data, 
+				        const pj_str_t *realm,
+				        const pj_str_t *username,
+					pj_pool_t *pool,
+					pj_str_t *password);
+	    pj_bool_t	(*require_nonce)(void *user_data,
+					 const pj_str_t *realm,
+					 const pj_str_t *username);
+	    pj_bool_t	(*verify_nonce)(void *data,
+					const pj_str_t *realm,
+					const pj_str_t *username,
+					const pj_str_t *nonce);
+	    pj_status_t	(*make_nonce)(void *user_data,
+				      const pj_str_t *realm,
+				      const pj_str_t *username,
+				      pj_pool_t *pool,
+				      pj_str_t *nonce);
+	} dynamic;
+
+    } data;
+
+} pj_stun_auth_policy;
+
+
 /**
  * Verify credential in the STUN message. Note that before calling this
@@ -1253,14 +1353,10 @@
  * if it doesn't contain PJ_STUN_ATTR_MESSAGE_INTEGRITY attribute.
  *
- * @param msg		The message to be verified.
- * @param realm		Realm, if long term credential is required. If
- *			short term credential is required, this argument
- *			must be set to NULL.
- * @param username	If this attribute is specified, then the USERNAME
- *			attribute in the message will be compared against
- *			this value. If NULL is specified, then this function
- *			will accept any usernames.
- * @param password	The password.
- * @param options	Options, must be zero for now.
+ * @param pkt		The original packet which has been parsed into
+ *			the message. This packet MUST NOT have been modified
+ *			after the parsing.
+ * @param pkt_len	The length of the packet.
+ * @param msg		The parsed message to be verified.
+ * @param policy	Pointer to authentication policy.
  * @param pool		If response is to be created, then memory will
  *			be allocated from this pool.
@@ -1274,9 +1370,8 @@
  *			\a p_response.
  */
-PJ_DECL(pj_status_t) pj_stun_verify_credential(const pj_stun_msg *msg,
-					       const pj_str_t *realm,
-					       const pj_str_t *username,
-					       const pj_str_t *password,
-					       unsigned options,
+PJ_DECL(pj_status_t) pj_stun_verify_credential(const pj_uint8_t *pkt,
+					       unsigned pkt_len,
+					       const pj_stun_msg *msg,
+					       pj_stun_auth_policy *policy,
 					       pj_pool_t *pool,
 					       pj_stun_msg **p_response);
Index: /pjproject/trunk/pjlib-util/src/pjlib-util-test/encryption.c
===================================================================
--- /pjproject/trunk/pjlib-util/src/pjlib-util-test/encryption.c	(revision 1002)
+++ /pjproject/trunk/pjlib-util/src/pjlib-util-test/encryption.c	(revision 1003)
@@ -423,4 +423,5 @@
     PJ_LOG(3, (THIS_FILE, "  crc32 test.."));
 
+    /* testing pj_crc32_calc */
     for (i=0; i<PJ_ARRAY_SIZE(crc32_test_data); ++i) {
 	pj_uint32_t crc;
@@ -433,4 +434,32 @@
 	}
     }
+
+    /* testing incremental CRC32 calculation */
+    for (i=0; i<PJ_ARRAY_SIZE(crc32_test_data); ++i) {
+	pj_crc32_context ctx;
+	pj_uint32_t crc0, crc1;
+	unsigned len;
+
+	len = pj_ansi_strlen(crc32_test_data[i].input);
+	crc0 = pj_crc32_calc((pj_uint8_t*)crc32_test_data[i].input, len);
+
+	pj_crc32_init(&ctx);
+	pj_crc32_update(&ctx, (pj_uint8_t*)crc32_test_data[i].input,
+			len / 2);
+
+	if (len/2 > 0) {
+	    pj_crc32_update(&ctx, (pj_uint8_t*)crc32_test_data[i].input + len/2,
+			    len - len/2);
+	}
+
+	crc1 = pj_crc32_final(&ctx);
+
+	if (crc0 != crc1) {
+	    PJ_LOG(3,(THIS_FILE, 
+		      "    error: crc algorithm error on test %d", i));
+	    return -85;
+	}
+
+    }
     return 0;
 }
@@ -460,4 +489,110 @@
 }
 
+static void crc32_update(pj_crc32_context *c, const pj_uint8_t *data,
+			 pj_size_t nbytes)
+{
+    pj_crc32_update(c, data, nbytes);
+}
+
+static void crc32_final(pj_crc32_context *ctx, pj_uint32_t *digest)
+{
+    *digest = pj_crc32_final(ctx);
+}
+
+int encryption_benchmark()
+{
+    pj_pool_t *pool;
+    pj_uint8_t *input;
+    union {
+	pj_md5_context md5_context;
+	pj_sha1_context sha1_context;
+    } context;
+    pj_uint8_t digest[32];
+    pj_size_t input_len;
+    struct algorithm
+    {
+	const char *name;
+	void (*init_context)(void*);
+	void (*update)(void*, const pj_uint8_t*, unsigned);
+	void (*final)(void*, void*);
+	pj_uint32_t t;
+    } algorithms[] = 
+    {
+	{
+	    "MD5  ",
+	    &pj_md5_init,
+	    &pj_md5_update,
+	    &pj_md5_final
+	},
+	{
+	    "SHA1 ",
+	    &pj_sha1_init,
+	    &pj_sha1_update,
+	    &pj_sha1_final
+	},
+	{
+	    "CRC32",
+	    &pj_crc32_init,
+	    &crc32_update,
+	    &crc32_final
+	}
+    };
+#if defined(PJ_DEBUG) && PJ_DEBUG!=0
+    enum { LOOP = 1000 };
+#else
+    enum { LOOP = 10000 };
+#endif
+    unsigned i;
+    double total_len;
+
+    input_len = 2048;
+    total_len = input_len * LOOP;
+    pool = pj_pool_create(mem, "enc", input_len+256, 0, NULL);
+    if (!pool)
+	return PJ_ENOMEM;
+
+    input = pj_pool_alloc(pool, input_len);
+    pj_memset(input, '\xaa', input_len);
+    
+    PJ_LOG(3, (THIS_FILE, "  feeding %d Mbytes of data",
+	       (unsigned)(total_len/1024/1024)));
+
+    /* Dry run */
+    for (i=0; i<PJ_ARRAY_SIZE(algorithms); ++i) {
+	algorithms[i].init_context(&context);
+	algorithms[i].update(&context, input, input_len);
+	algorithms[i].final(&context, digest);
+    }
+
+    /* Run */
+    for (i=0; i<PJ_ARRAY_SIZE(algorithms); ++i) {
+	int j;
+	pj_timestamp t1, t2;
+
+	pj_get_timestamp(&t1);
+	algorithms[i].init_context(&context);
+	for (j=0; j<LOOP; ++j) {
+	    algorithms[i].update(&context, input, input_len);
+	}
+	algorithms[i].final(&context, digest);
+	pj_get_timestamp(&t2);
+
+	algorithms[i].t = pj_elapsed_usec(&t1, &t2);
+    }
+
+    /* Results */
+    for (i=0; i<PJ_ARRAY_SIZE(algorithms); ++i) {
+	double bytes;
+
+	bytes = (total_len * 1000000 / algorithms[i].t);
+	PJ_LOG(3, (THIS_FILE, "    %s:%8d usec (%3d.%03d Mbytes/sec)",
+		   algorithms[i].name, algorithms[i].t,
+		   (unsigned)(bytes / 1024 / 1024),
+		   ((unsigned)(bytes) % (1024 * 1024)) / 1024));
+    }
+
+    return 0;
+}
+
 
 
Index: /pjproject/trunk/pjlib-util/src/pjlib-util-test/stun.c
===================================================================
--- /pjproject/trunk/pjlib-util/src/pjlib-util-test/stun.c	(revision 1003)
+++ /pjproject/trunk/pjlib-util/src/pjlib-util-test/stun.c	(revision 1003)
@@ -0,0 +1,118 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2007 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 
+ */
+
+static int decode_test(void)
+{
+    /* Invalid message type */
+
+    /* Short message */
+
+    /* Long, random message */
+
+    /* Message length in header is shorter */
+
+    /* Message length in header is longer */
+
+    /* Invalid magic */
+
+    /* Attribute length is not valid */
+
+    /* Unknown mandatory attribute type should generate error */
+
+    /* Unknown but non-mandatory should be okay */
+
+    /* String/binary attribute length is larger than the message */
+
+    /* Valid message with MESSAGE-INTEGRITY */
+
+    /* Valid message with FINGERPRINT */
+
+    /* Valid message with MESSAGE-INTEGRITY and FINGERPRINT */
+
+    /* Another attribute not FINGERPRINT exists after MESSAGE-INTEGRITY */
+
+    /* Another attribute exists after FINGERPRINT */
+
+    return 0;
+}
+
+static int decode_verify(void)
+{
+    /* Decode all attribute types */
+    return 0;
+}
+
+static int auth_test(void)
+{
+    /* REALM and USERNAME is present, but MESSAGE-INTEGRITY is not present.
+     * For short term, must with reply 401 without REALM.
+     * For long term, must reply with 401 with REALM.
+     */
+
+    /* USERNAME is not present, server must respond with 432 (Missing
+     * Username).
+     */
+
+    /* If long term credential is wanted and REALM is not present, server 
+     * must respond with 434 (Missing Realm) 
+     */
+
+    /* If REALM doesn't match, server must respond with 434 (Missing Realm)
+     * too, containing REALM and NONCE attribute.
+     */
+
+    /* When long term authentication is wanted and NONCE is NOT present,
+     * server must respond with 435 (Missing Nonce), containing REALM and
+     * NONCE attribute.
+     */
+
+    /* Simulate 438 (Stale Nonce) */
+    
+    /* Simulate 436 (Unknown Username) */
+
+    /* When server wants to use short term credential, but request has
+     * REALM, reject with .... ???
+     */
+
+    /* Invalid HMAC */
+
+    /* Valid static short term, without NONCE */
+
+    /* Valid static short term, WITH NONCE */
+
+    /* Valid static long term (with NONCE */
+
+    /* Valid dynamic short term (without NONCE) */
+
+    /* Valid dynamic short term (with NONCE) */
+
+    /* Valid dynamic long term (with NONCE) */
+
+    return 0;
+}
+
+
+int stun_test(void)
+{
+    decode_verify();
+    decode_test();
+    auth_test();
+    return 0;
+}
+
Index: /pjproject/trunk/pjlib-util/src/pjlib-util-test/test.c
===================================================================
--- /pjproject/trunk/pjlib-util/src/pjlib-util-test/test.c	(revision 1002)
+++ /pjproject/trunk/pjlib-util/src/pjlib-util-test/test.c	(revision 1003)
@@ -69,6 +69,10 @@
 #if INCLUDE_ENCRYPTION_TEST
     DO_TEST(encryption_test());
+    DO_TEST(encryption_benchmark());
 #endif
 
+#if INCLUDE_STUN_TEST
+    DO_TEST(stun_test());
+#endif
 
 on_return:
Index: /pjproject/trunk/pjlib-util/src/pjlib-util-test/test.h
===================================================================
--- /pjproject/trunk/pjlib-util/src/pjlib-util-test/test.h	(revision 1002)
+++ /pjproject/trunk/pjlib-util/src/pjlib-util-test/test.h	(revision 1003)
@@ -21,7 +21,10 @@
 #define INCLUDE_XML_TEST	    1
 #define INCLUDE_ENCRYPTION_TEST	    1
+#define INCLUDE_STUN_TEST	    1
 
 extern int xml_test(void);
 extern int encryption_test();
+extern int encryption_benchmark();
+extern int stun_test();
 extern int test_main(void);
 
Index: /pjproject/trunk/pjlib-util/src/pjlib-util/crc32.c
===================================================================
--- /pjproject/trunk/pjlib-util/src/pjlib-util/crc32.c	(revision 1002)
+++ /pjproject/trunk/pjlib-util/src/pjlib-util/crc32.c	(revision 1003)
@@ -4,5 +4,6 @@
  * for a formal specification
  *
- * This file is partly taken from Crypto++ library (http://www.cryptopp.com).
+ * This file is partly taken from Crypto++ library (http://www.cryptopp.com)
+ * and http://www.di-mgt.com.au/crypto.html#CRC.
  *
  * Since the original version of the code is put in public domain,
@@ -11,4 +12,8 @@
 #include <pjlib-util/crc32.h>
 
+
+#define CRC32_NEGL  0xffffffffL
+
+#if defined(PJ_CRC32_HAS_TABLES) && PJ_CRC32_HAS_TABLES!=0
 // crc.cpp - written and placed in the public domain by Wei Dai
 
@@ -139,7 +144,4 @@
 
 
-#define CRC32_NEGL  0xffffffffL
-
-
 PJ_DEF(void) pj_crc32_init(pj_crc32_context *ctx)
 {
@@ -181,4 +183,48 @@
 }
 
+
+#else
+
+PJ_DEF(void) pj_crc32_init(pj_crc32_context *ctx)
+{
+    ctx->crc_state = CRC32_NEGL;
+}
+
+
+PJ_DEF(pj_uint32_t) pj_crc32_update(pj_crc32_context *ctx, 
+				    const pj_uint8_t *octets,
+				    pj_size_t len)
+
+{
+    pj_uint32_t crc = ctx->crc_state;
+    
+    while (len--) {
+	pj_uint32_t temp;
+	int j;
+
+	temp = (pj_uint32_t)((crc & 0xFF) ^ *octets++);
+	for (j = 0; j < 8; j++)
+	{
+	    if (temp & 0x1)
+		temp = (temp >> 1) ^ 0xEDB88320;
+	    else
+		temp >>= 1;
+	}
+	crc = (crc >> 8) ^ temp;
+    }
+    ctx->crc_state = crc;
+
+    return crc ^ CRC32_NEGL;
+}
+
+PJ_DEF(pj_uint32_t) pj_crc32_final(pj_crc32_context *ctx)
+{
+    ctx->crc_state ^= CRC32_NEGL;
+    return ctx->crc_state;
+}
+
+#endif
+
+
 PJ_DEF(pj_uint32_t) pj_crc32_calc( const pj_uint8_t *data,
 				   pj_size_t nbytes)
Index: /pjproject/trunk/pjlib-util/src/pjlib-util/stun_msg.c
===================================================================
--- /pjproject/trunk/pjlib-util/src/pjlib-util/stun_msg.c	(revision 1002)
+++ /pjproject/trunk/pjlib-util/src/pjlib-util/stun_msg.c	(revision 1003)
@@ -22,4 +22,5 @@
 #include <pjlib-util/hmac_sha1.h>
 #include <pjlib-util/md5.h>
+#include <pjlib-util/sha1.h>
 #include <pj/assert.h>
 #include <pj/log.h>
@@ -1560,4 +1561,6 @@
     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;
 
@@ -1587,5 +1590,6 @@
 
     pdu += sizeof(pj_stun_msg_hdr);
-    pdu_len -= 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 */
@@ -1688,5 +1692,49 @@
 		return status;
 	    }
-	    
+
+	    if (attr_type == PJ_STUN_ATTR_MESSAGE_INTEGRITY && 
+		!has_fingerprint) 
+	    {
+		if (has_msg_int) {
+		    /* Already has MESSAGE-INTEGRITY */
+		    if (p_response) {
+			pj_str_t e;
+			e = pj_str("MESSAGE-INTEGRITY already present");
+			pj_stun_msg_create_response(pool, msg,
+						    PJ_STUN_STATUS_BAD_REQUEST,
+						    NULL, p_response);
+		    }
+		    return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_STATUS_BAD_REQUEST);
+		}
+		has_msg_int = PJ_TRUE;
+
+	    } else if (attr_type == PJ_STUN_ATTR_FINGERPRINT) {
+		if (has_fingerprint) {
+		    /* Already has FINGERPRINT */
+		    if (p_response) {
+			pj_str_t e;
+			e = pj_str("FINGERPRINT already present");
+			pj_stun_msg_create_response(pool, msg,
+						    PJ_STUN_STATUS_BAD_REQUEST,
+						    NULL, p_response);
+		    }
+		    return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_STATUS_BAD_REQUEST);
+		}
+		has_fingerprint = PJ_TRUE;
+	    } else {
+		if (has_msg_int || has_fingerprint) {
+		    /* Another attribute is found which is not FINGERPRINT
+		     * after FINGERPRINT or MESSAGE-INTEGRITY */
+		    if (p_response) {
+			pj_str_t e;
+			e = pj_str("Invalid attribute order");
+			pj_stun_msg_create_response(pool, msg,
+						    PJ_STUN_STATUS_BAD_REQUEST,
+						    NULL, p_response);
+		    }
+		    return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_STATUS_BAD_REQUEST);
+		}
+	    }
+
 	    /* Make sure we have rooms for the new attribute */
 	    if (msg->attr_count >= PJ_STUN_MAX_ATTR) {
@@ -1716,4 +1764,54 @@
 
     return PJ_SUCCESS;
+}
+
+/* Calculate HMAC-SHA1 key for long term credential, by getting
+ * MD5 digest of username, realm, and password. 
+ */
+static void calc_md5_key(pj_uint8_t digest[16],
+			 const pj_str_t *realm,
+			 const pj_str_t *username,
+			 const pj_str_t *passwd)
+{
+    /* The 16-byte key for MESSAGE-INTEGRITY HMAC is formed by taking
+     * the MD5 hash of the result of concatenating the following five
+     * fields: (1) The username, with any quotes and trailing nulls
+     * removed, (2) A single colon, (3) The realm, with any quotes and
+     * trailing nulls removed, (4) A single colon, and (5) The 
+     * password, with any trailing nulls removed.
+     */
+    pj_md5_context ctx;
+    pj_str_t s;
+
+    pj_md5_init(&ctx);
+
+#define REMOVE_QUOTE(s)	if (s.slen && *s.ptr=='"') \
+		    s.ptr++, s.slen--; \
+		if (s.slen && s.ptr[s.slen-1]=='"') \
+		    s.slen--;
+
+    /* Add username */
+    s = *username;
+    REMOVE_QUOTE(s);
+    pj_md5_update(&ctx, (pj_uint8_t*)s.ptr, s.slen);
+
+    /* Add single colon */
+    pj_md5_update(&ctx, (pj_uint8_t*)":", 1);
+
+    /* Add realm */
+    s = *realm;
+    REMOVE_QUOTE(s);
+    pj_md5_update(&ctx, (pj_uint8_t*)s.ptr, s.slen);
+
+#undef REMOVE_QUOTE
+
+    /* Another colon */
+    pj_md5_update(&ctx, (pj_uint8_t*)":", 1);
+
+    /* Add password */
+    pj_md5_update(&ctx, (pj_uint8_t*)passwd->ptr, passwd->slen);
+
+    /* Done */
+    pj_md5_final(&ctx, digest);
 }
 
@@ -1796,4 +1894,22 @@
     }
 
+    /* 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 (amsg_integrity && afingerprint) {
+	length = (pj_uint16_t)((buf - start) - 20 + 24 + 8);
+    } else if (amsg_integrity) {
+	length = (pj_uint16_t)((buf - start) - 20 + 24);
+    } else if (afingerprint) {
+	length = (pj_uint16_t)((buf - start) - 20 + 8);
+    } else {
+	length = (pj_uint16_t)((buf - start) - 20);
+    }
+
+    /* hdr->length = pj_htons(length); */
+    *(buf+2) = (pj_uint8_t)((length >> 8) & 0x00FF);
+    *(buf+3) = (pj_uint8_t)(length & 0x00FF);
+
     /* Calculate message integrity, if present */
     if (amsg_integrity != NULL) {
@@ -1836,44 +1952,6 @@
 
 	} else {
-	    /* The 16-byte key for MESSAGE-INTEGRITY HMAC is formed by taking
-	     * the MD5 hash of the result of concatenating the following five
-	     * fields: (1) The username, with any quotes and trailing nulls
-	     * removed, (2) A single colon, (3) The realm, with any quotes and
-	     * trailing nulls removed, (4) A single colon, and (5) The 
-	     * password, with any trailing nulls removed.
-	     */
-	    pj_md5_context ctx;
-	    pj_str_t s;
-
-	    pj_md5_init(&ctx);
-
-#define REMOVE_QUOTE(s)	if (s.slen && *s.ptr=='"') \
-			    s.ptr++, s.slen--; \
-			if (s.slen && s.ptr[s.slen-1]=='"') \
-			    s.slen--;
-
-	    /* Add username */
-	    s = auname->value;
-	    REMOVE_QUOTE(s);
-	    pj_md5_update(&ctx, (pj_uint8_t*)s.ptr, s.slen);
-
-	    /* Add single colon */
-	    pj_md5_update(&ctx, (pj_uint8_t*)":", 1);
-
-	    /* Add realm */
-	    s = arealm->value;
-	    REMOVE_QUOTE(s);
-	    pj_md5_update(&ctx, (pj_uint8_t*)s.ptr, s.slen);
-
-#undef REMOVE_QUOTE
-
-	    /* Another colon */
-	    pj_md5_update(&ctx, (pj_uint8_t*)":", 1);
-
-	    /* Add password */
-	    pj_md5_update(&ctx, (pj_uint8_t*)password->ptr, password->slen);
-
-	    /* Done */
-	    pj_md5_final(&ctx, md5_key_buf);
+	    calc_md5_key(md5_key_buf, &arealm->value, &auname->value, 
+			 password);
 	    key.ptr = (char*) md5_key_buf;
 	    key.slen = 16;
@@ -1910,13 +1988,4 @@
     }
 
-    /* Update the message length in the header. 
-     * Note that length is not including the 20 bytes header.
-     */
-    length = (pj_uint16_t)((buf - start) - 20);
-    /* hdr->length = pj_htons(length); */
-    *(buf+2) = (pj_uint8_t)((length >> 8) & 0x00FF);
-    *(buf+3) = (pj_uint8_t)(length & 0x00FF);
-
-
     /* Done */
     if (p_msg_len)
@@ -1946,23 +2015,98 @@
 
 
+/**************************************************************************/
+/*
+ * Authentication
+ */
+
+
+/* Send 401 response */
+static pj_status_t create_challenge(pj_pool_t *pool,
+				    const pj_stun_msg *msg,
+				    int err_code,
+				    const pj_str_t *err_msg,
+				    const pj_str_t *realm,
+				    const pj_str_t *nonce,
+				    pj_stun_msg **p_response)
+{
+    pj_stun_msg *response;
+    pj_status_t rc;
+
+    rc = pj_stun_msg_create_response(pool, msg, 
+				     err_code,  err_msg, &response);
+    if (rc != PJ_SUCCESS)
+	return rc;
+
+
+    if (realm && realm->slen) {
+	rc = pj_stun_msg_add_generic_string_attr(pool, response,
+						 PJ_STUN_ATTR_REALM, 
+						 realm);
+	if (rc != PJ_SUCCESS)
+	    return rc;
+    }
+
+    if (nonce && nonce->slen) {
+	rc = pj_stun_msg_add_generic_string_attr(pool, response,
+						 PJ_STUN_ATTR_NONCE, 
+						 nonce);
+	if (rc != PJ_SUCCESS)
+	    return rc;
+    }
+
+    *p_response = response;
+
+    return PJ_SUCCESS;
+}
+
 /* Verify credential */
-PJ_DEF(pj_status_t) pj_stun_verify_credential( const pj_stun_msg *msg,
-					       const pj_str_t *realm,
-					       const pj_str_t *username,
-					       const pj_str_t *password,
-					       unsigned options,
+PJ_DEF(pj_status_t) pj_stun_verify_credential( const pj_uint8_t *pkt,
+					       unsigned pkt_len,
+					       const pj_stun_msg *msg,
+					       pj_stun_auth_policy *pol,
 					       pj_pool_t *pool,
 					       pj_stun_msg **p_response)
 {
+    pj_str_t realm, nonce, password;
     const pj_stun_msg_integrity_attr *amsgi;
+    unsigned amsgi_pos;
     const pj_stun_username_attr *auser;
+    pj_bool_t username_ok;
     const pj_stun_realm_attr *arealm;
-
-    PJ_ASSERT_RETURN(msg && password, PJ_EINVAL);
-    PJ_ASSERT_RETURN(options==0, PJ_EINVAL);
-    PJ_UNUSED_ARG(options);
+    const pj_stun_realm_attr *anonce;
+    pj_uint8_t digest[PJ_SHA1_DIGEST_SIZE];
+    pj_uint8_t md5_digest[16];
+    pj_str_t key;
+    pj_status_t status;
+
+    /* msg and policy MUST be specified */
+    PJ_ASSERT_RETURN(pkt && pkt_len && msg && pol, PJ_EINVAL);
+
+    /* If p_response is specified, pool MUST be specified. */
+    PJ_ASSERT_RETURN(!p_response || pool, PJ_EINVAL);
 
     if (p_response)
 	*p_response = NULL;
+
+    if (PJ_STUN_IS_REQUEST(msg->hdr.type))
+	p_response = NULL;
+
+    /* Get realm and nonce */
+    realm.slen = nonce.slen = 0;
+    if (pol->type == PJ_STUN_POLICY_STATIC_SHORT_TERM) {
+	realm.slen = 0;
+	nonce = pol->data.static_short_term.nonce;
+    } else if (pol->type == PJ_STUN_POLICY_STATIC_LONG_TERM) {
+	realm = pol->data.static_long_term.realm;
+	nonce = pol->data.static_long_term.nonce;
+    } else if (pol->type == PJ_STUN_POLICY_DYNAMIC) {
+	status = pol->data.dynamic.get_auth(pol->user_data, pool, 
+					    &realm, &nonce);
+	if (status != PJ_SUCCESS)
+	    return status;
+    } else {
+	pj_assert(!"Unexpected");
+	return PJ_EBUG;
+    }
 
     /* First check that MESSAGE-INTEGRITY is present */
@@ -1970,15 +2114,7 @@
 	    pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_MESSAGE_INTEGRITY, 0);
     if (amsgi == NULL) {
-	if (pool && p_response) {
-	    pj_status_t rc;
-
-	    rc = pj_stun_msg_create_response(pool, msg, 
-					     PJ_STUN_STATUS_UNAUTHORIZED, 
-					     NULL, p_response);
-	    if (rc==PJ_SUCCESS && realm) {
-		pj_stun_msg_add_generic_string_attr(pool, *p_response,
-						    PJ_STUN_ATTR_REALM, 
-						    realm);
-	    }
+	if (p_response) {
+	    create_challenge(pool, msg, PJ_STUN_STATUS_UNAUTHORIZED, NULL,
+			     &realm, &nonce, p_response);
 	}
 	return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_STATUS_UNAUTHORIZED);
@@ -1989,71 +2125,178 @@
 	    pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_USERNAME, 0);
     if (auser == NULL) {
-	if (pool && p_response) {
-	    pj_status_t rc;
-
-	    rc = pj_stun_msg_create_response(pool, msg, 
-					     PJ_STUN_STATUS_MISSING_USERNAME, 
-					     NULL, p_response);
-	    if (rc==PJ_SUCCESS && realm) {
-		pj_stun_msg_add_generic_string_attr(pool, *p_response,
-						    PJ_STUN_ATTR_REALM, 
-						    realm);
-	    }
+	if (p_response) {
+	    create_challenge(pool, msg, PJ_STUN_STATUS_MISSING_USERNAME, NULL,
+			     &realm, &nonce, p_response);
 	}
 	return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_STATUS_MISSING_USERNAME);
     }
 
-    /* Check if username match */
-    if (username && pj_stricmp(&auser->value, username) != 0) {
-	/* Username mismatch */
-	if (pool && p_response) {
-	    pj_status_t rc;
-
-	    rc = pj_stun_msg_create_response(pool, msg, 
-					     PJ_STUN_STATUS_WRONG_USERNAME, 
-					     NULL, p_response);
-	    if (rc==PJ_SUCCESS && realm) {
-		pj_stun_msg_add_generic_string_attr(pool, *p_response,
-						    PJ_STUN_ATTR_REALM, 
-						    realm);
-	    }
-	}
-	return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_STATUS_WRONG_USERNAME);
-    }
-
-    /* Next check that REALM is present */
+    /* Get REALM, if any */
     arealm = (const pj_stun_realm_attr*)
 	     pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_REALM, 0);
-    if (realm != NULL && arealm == NULL) {
-	/* Long term credential is required */
-	if (pool && p_response) {
-	    pj_status_t rc;
-
-	    rc = pj_stun_msg_create_response(pool, msg, 
-					     PJ_STUN_STATUS_MISSING_REALM, 
-					     NULL, p_response);
-	    if (rc==PJ_SUCCESS) {
-		pj_stun_msg_add_generic_string_attr(pool, *p_response,
-						    PJ_STUN_ATTR_REALM, 
-						    realm);
-	    }
+
+    /* Check if username match */
+    if (pol->type == PJ_STUN_POLICY_STATIC_SHORT_TERM) {
+	username_ok = !pj_strcmp(&auser->value, 
+				 &pol->data.static_short_term.username);
+	password = pol->data.static_short_term.password;
+    } else if (pol->type == PJ_STUN_POLICY_STATIC_LONG_TERM) {
+	username_ok = !pj_strcmp(&auser->value, 
+				 &pol->data.static_long_term.username);
+	password = pol->data.static_long_term.password;
+    } else if (pol->type == PJ_STUN_POLICY_DYNAMIC) {
+	pj_status_t rc;
+	rc = pol->data.dynamic.get_password(pol->user_data, 
+					    (arealm?&arealm->value:NULL),
+					    &auser->value, pool,
+					    &password);
+	username_ok = (rc == PJ_SUCCESS);
+    } else {
+	username_ok = PJ_TRUE;
+	password.slen = 0;
+    }
+
+    if (!username_ok) {
+	/* Username mismatch */
+	if (p_response) {
+	    create_challenge(pool, msg, PJ_STUN_STATUS_UNKNOWN_USERNAME, NULL,
+			     &realm, &nonce, p_response);
+	}
+	return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_STATUS_UNKNOWN_USERNAME);
+    }
+
+
+    /* Get NONCE attribute */
+    anonce = (pj_stun_nonce_attr*)
+	     pj_stun_msg_find_attr(msg, PJ_STUN_ATTR_NONCE, 0);
+
+    /* Check for long term/short term requirements. */
+    if (realm.slen != 0 && arealm == NULL) {
+	/* Long term credential is required and REALM is not present */
+	if (p_response) {
+	    create_challenge(pool, msg, PJ_STUN_STATUS_MISSING_REALM, NULL,
+			     &realm, &nonce, p_response);
 	}
 	return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_STATUS_MISSING_REALM);
 
-    } else if (realm != NULL && arealm != NULL) {
-
-
-    } else if (realm == NULL && arealm != NULL) {
+    } else if (realm.slen != 0 && arealm != NULL) {
+	/* We want long term, and REALM is present */
+
+	/* NONCE must be present. */
+	if (anonce == NULL) {
+	    if (p_response) {
+		create_challenge(pool, msg, PJ_STUN_STATUS_MISSING_NONCE, 
+				 NULL, &realm, &nonce, p_response);
+	    }
+	    return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_STATUS_MISSING_NONCE);
+	}
+
+	/* Verify REALM matches */
+	if (pj_stricmp(&arealm->value, &realm)) {
+	    /* REALM doesn't match */
+	    if (p_response) {
+		create_challenge(pool, msg, PJ_STUN_STATUS_MISSING_REALM, 
+				 NULL, &realm, &nonce, p_response);
+	    }
+	    return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_STATUS_MISSING_REALM);
+	}
+
+	/* Valid case, will validate the message integrity later */
+
+    } else if (realm.slen == 0 && arealm != NULL) {
 	/* We want to use short term credential, but client uses long
 	 * term credential. The draft doesn't mention anything about
 	 * switching between long term and short term.
 	 */
-	PJ_TODO(SWITCHING_BETWEEN_SHORT_TERM_AND_LONG_TERM);
-    }
-
-    PJ_TODO(CONTINUE_IMPLEMENTATION);
-
-    return PJ_SUCCESS;
-}
-
-
+	
+	/* For now just accept the credential, anyway it will probably
+	 * cause wrong message integrity value later.
+	 */
+    } else if (realm.slen==0 && arealm == NULL) {
+	/* Short term authentication is wanted, and one is supplied */
+
+	/* Application MAY request NONCE to be supplied */
+	if (nonce.slen != 0) {
+	    if (p_response) {
+		create_challenge(pool, msg, PJ_STUN_STATUS_MISSING_NONCE, 
+				 NULL, &realm, &nonce, p_response);
+	    }
+	    return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_STATUS_MISSING_NONCE);
+	}
+    }
+
+    /* If NONCE is present, validate it */
+    if (anonce) {
+	pj_bool_t ok;
+
+	if (pol->type == PJ_STUN_POLICY_DYNAMIC) {
+	    ok = pol->data.dynamic.verify_nonce(pol->user_data,
+						(arealm?&arealm->value:NULL),
+						&auser->value,
+						&anonce->value);
+	} else {
+	    if (nonce.slen) {
+		ok = !pj_strcmp(&anonce->value, &nonce);
+	    } else {
+		ok = PJ_TRUE;
+	    }
+	}
+
+	if (!ok) {
+	    if (p_response) {
+		create_challenge(pool, msg, PJ_STUN_STATUS_STALE_NONCE, 
+				 NULL, &realm, &nonce, p_response);
+	    }
+	    return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_STATUS_STALE_NONCE);
+	}
+    }
+
+    /* Get the position of MESSAGE-INTEGRITY in the packet */
+    amsgi_pos = 20+msg->hdr.length-22;
+    if (GET_VAL16(pkt, amsgi_pos) == PJ_STUN_ATTR_MESSAGE_INTEGRITY) {
+	/* Found MESSAGE-INTEGRITY as the last attribute */
+    } else {
+	amsgi_pos = 0;
+    }
+    
+    if (amsgi_pos==0) {
+	amsgi_pos = 20+msg->hdr.length-8-22;
+	if (GET_VAL16(pkt, amsgi_pos) == PJ_STUN_ATTR_MESSAGE_INTEGRITY) {
+	    /* Found MESSAGE-INTEGRITY before FINGERPRINT */
+	} else {
+	    amsgi_pos = 0;
+	}
+    }
+
+    if (amsgi_pos==0) {
+	pj_assert(!"Unable to find MESSAGE-INTEGRITY in the message!");
+	return PJ_EBUG;
+    }
+
+    /* Determine which key to use */
+    if (realm.slen) {
+	calc_md5_key(md5_digest, &realm, &auser->value, &password);
+	key.ptr = (char*)md5_digest;
+	key.slen = 16;
+    } else {
+	key = password;
+    }
+
+    /* Now calculate HMAC of the message */
+    pj_hmac_sha1(pkt, amsgi_pos, (pj_uint8_t*)key.ptr, key.slen, digest);
+
+    /* Compare HMACs */
+    if (pj_memcmp(amsgi->hmac, digest, 20)) {
+	/* HMAC value mismatch */
+	if (p_response) {
+	    create_challenge(pool, msg, PJ_STUN_STATUS_INTEGRITY_CHECK_FAILURE,
+			     NULL, &realm, &nonce, p_response);
+	}
+	return PJ_STATUS_FROM_STUN_CODE(PJ_STUN_STATUS_INTEGRITY_CHECK_FAILURE);
+    }
+
+    /* Everything looks okay! */
+    return PJ_SUCCESS;
+}
+
+
