Index: pjproject/trunk/pjlib-util/build/pjlib_util.dsp
===================================================================
--- pjproject/trunk/pjlib-util/build/pjlib_util.dsp	(revision 992)
+++ pjproject/trunk/pjlib-util/build/pjlib_util.dsp	(revision 993)
@@ -138,4 +138,8 @@
 # Begin Source File
 
+SOURCE="..\src\pjlib-util\stun_msg_dump.c"
+# End Source File
+# Begin Source File
+
 SOURCE="..\src\pjlib-util\stun_server.c"
 # End Source File
Index: pjproject/trunk/pjlib-util/build/pjlib_util.dsw
===================================================================
--- pjproject/trunk/pjlib-util/build/pjlib_util.dsw	(revision 992)
+++ pjproject/trunk/pjlib-util/build/pjlib_util.dsw	(revision 993)
@@ -58,4 +58,22 @@
 ###############################################################################
 
+Project: "pjstun_client"=".\pjstun_client.dsp" - Package Owner=<4>
+
+Package=<5>
+{{{
+}}}
+
+Package=<4>
+{{{
+    Begin Project Dependency
+    Project_Dep_Name pjlib
+    End Project Dependency
+    Begin Project Dependency
+    Project_Dep_Name pjlib_util
+    End Project Dependency
+}}}
+
+###############################################################################
+
 Project: "pjstun_srv"=".\pjstun_srv.dsp" - Package Owner=<4>
 
Index: pjproject/trunk/pjlib-util/build/pjstun_client.dsp
===================================================================
--- pjproject/trunk/pjlib-util/build/pjstun_client.dsp	(revision 993)
+++ pjproject/trunk/pjlib-util/build/pjstun_client.dsp	(revision 993)
@@ -0,0 +1,110 @@
+# Microsoft Developer Studio Project File - Name="pjstun_client" - Package Owner=<4>
+# Microsoft Developer Studio Generated Build File, Format Version 6.00
+# ** DO NOT EDIT **
+
+# TARGTYPE "Win32 (x86) Console Application" 0x0103
+
+CFG=pjstun_client - Win32 Debug
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,
+!MESSAGE use the Export Makefile command and run
+!MESSAGE 
+!MESSAGE NMAKE /f "pjstun_client.mak".
+!MESSAGE 
+!MESSAGE You can specify a configuration when running NMAKE
+!MESSAGE by defining the macro CFG on the command line. For example:
+!MESSAGE 
+!MESSAGE NMAKE /f "pjstun_client.mak" CFG="pjstun_client - Win32 Debug"
+!MESSAGE 
+!MESSAGE Possible choices for configuration are:
+!MESSAGE 
+!MESSAGE "pjstun_client - Win32 Release" (based on "Win32 (x86) Console Application")
+!MESSAGE "pjstun_client - Win32 Debug" (based on "Win32 (x86) Console Application")
+!MESSAGE 
+
+# Begin Project
+# PROP AllowPerConfigDependencies 0
+# PROP Scc_ProjName ""
+# PROP Scc_LocalPath ""
+CPP=cl.exe
+RSC=rc.exe
+
+!IF  "$(CFG)" == "pjstun_client - Win32 Release"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 0
+# PROP BASE Output_Dir "./output/pjstun-client-i386-win32-vc6-release"
+# PROP BASE Intermediate_Dir "./output/pjstun-client-i386-win32-vc6-release"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 0
+# PROP Output_Dir "./output/pjstun-client-i386-win32-vc6-release"
+# PROP Intermediate_Dir "./output/pjstun-client-i386-win32-vc6-release"
+# PROP Ignore_Export_Lib 0
+# 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 BASE RSC /l 0x409 /d "NDEBUG"
+# ADD RSC /l 0x409 /d "NDEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386
+# ADD LINK32 netapi32.lib mswsock.lib ws2_32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /out:"../bin/pjstun-client-i386-win32-vc6-release.exe"
+
+!ELSEIF  "$(CFG)" == "pjstun_client - Win32 Debug"
+
+# PROP BASE Use_MFC 0
+# PROP BASE Use_Debug_Libraries 1
+# PROP BASE Output_Dir "./output/pjstun-client-i386-win32-vc6-debug"
+# PROP BASE Intermediate_Dir "./output/pjstun-client-i386-win32-vc6-debug"
+# PROP BASE Target_Dir ""
+# PROP Use_MFC 0
+# PROP Use_Debug_Libraries 1
+# PROP Output_Dir "./output/pjstun-client-i386-win32-vc6-debug"
+# PROP Intermediate_Dir "./output/pjstun-client-i386-win32-vc6-debug"
+# PROP Ignore_Export_Lib 0
+# 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 BASE RSC /l 0x409 /d "_DEBUG"
+# ADD RSC /l 0x409 /d "_DEBUG"
+BSC32=bscmake.exe
+# ADD BASE BSC32 /nologo
+# ADD BSC32 /nologo
+LINK32=link.exe
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
+# ADD LINK32 netapi32.lib ws2_32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /out:"../bin/pjstun-client-i386-win32-vc6-debug.exe" /pdbtype:sept
+
+!ENDIF 
+
+# Begin Target
+
+# Name "pjstun_client - Win32 Release"
+# Name "pjstun_client - Win32 Debug"
+# Begin Group "Source Files"
+
+# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
+# Begin Source File
+
+SOURCE="..\src\pjstun-client\client_main.c"
+# End Source File
+# Begin Source File
+
+SOURCE="..\src\pjstun-client\stun_session.c"
+# End Source File
+# End Group
+# Begin Group "Header Files"
+
+# PROP Default_Filter "h;hpp;hxx;hm;inl"
+# Begin Source File
+
+SOURCE="..\src\pjstun-client\stun_session.h"
+# End Source File
+# End Group
+# Begin Group "Resource Files"
+
+# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
+# End Group
+# End Target
+# End Project
Index: pjproject/trunk/pjlib-util/include/pjlib-util/stun_msg.h
===================================================================
--- pjproject/trunk/pjlib-util/include/pjlib-util/stun_msg.h	(revision 992)
+++ pjproject/trunk/pjlib-util/include/pjlib-util/stun_msg.h	(revision 993)
@@ -1191,9 +1191,26 @@
 
 /**
+ * Dump STUN message to a printable string output.
+ *
+ * @param msg		The STUN message
+ * @param buffer	Buffer where the printable string output will
+ *			be printed on.
+ * @param length	On input, specify the maximum length of the buffer.
+ *			On output, it will be filled up with the actual
+ *			length of the output string.
+ *
+ * @return		The message string output.
+ */
+PJ_DECL(char*) pj_stun_msg_dump(const pj_stun_msg *msg,
+			        char *buffer,
+			        unsigned *length);
+
+
+/**
  * Find STUN attribute in the STUN message, starting from the specified
  * index.
  *
  * @param msg		The STUN message.
- * @param attr_type	The attribute type to be found.
+ * @param attr_type	The attribute type to be found, from pj_stun_attr_type.
  * @param start_index	The start index of the attribute in the message.
  *			Specify zero to start searching from the first
@@ -1213,5 +1230,5 @@
  *
  * @param pool		The pool to allocate memory from.
- * @param attr_type	Attribute type.
+ * @param attr_type	Attribute type, from #pj_stun_attr_type.
  * @param xor_ed	If non-zero, the port and address will be XOR-ed
  *			with magic, to make the XOR-MAPPED-ADDRESS attribute.
@@ -1235,4 +1252,5 @@
  *
  * @param pool		The pool to allocate memory from.
+ * @param attr_type	Attribute type, from #pj_stun_attr_type.
  * @param value		The string value to be assigned to the attribute.
  * @param p_attr	Pointer to receive the attribute.
@@ -1316,5 +1334,5 @@
  *
  * @param pool		The pool to allocate memory from.
- * @param attr_type	The attribute type.
+ * @param attr_type	The attribute type, from #pj_stun_attr_type.
  * @param p_attr	Pointer to receive the attribute.
  *
Index: pjproject/trunk/pjlib-util/include/pjlib-util/stun_transaction.h
===================================================================
--- pjproject/trunk/pjlib-util/include/pjlib-util/stun_transaction.h	(revision 992)
+++ pjproject/trunk/pjlib-util/include/pjlib-util/stun_transaction.h	(revision 993)
@@ -74,5 +74,5 @@
     void	(*on_complete)(pj_stun_client_tsx *tsx,
 			       pj_status_t status, 
-			       pj_stun_msg *response);
+			       const pj_stun_msg *response);
 
     /**
@@ -125,4 +125,14 @@
 
 /**
+ * Check if transaction has completed.
+ *
+ * @param tsx		The STUN transaction.
+ *
+ * @return		Non-zero if transaction has completed.
+ */
+PJ_DECL(pj_bool_t) pj_stun_client_tsx_is_complete(pj_stun_client_tsx *tsx);
+
+
+/**
  * Associate an arbitrary data with the STUN transaction. This data
  * can be then retrieved later from the transaction, by using
@@ -187,5 +197,5 @@
  * @return		PJ_SUCCESS on success or the appropriate error code.
  */
-PJ_DECL(pj_status_t) pj_stun_client_tsx_on_rx_msg(pj_stun_client_tsx *tsx,
+PJ_DECL(pj_status_t) pj_stun_client_tsx_on_rx_pkt(pj_stun_client_tsx *tsx,
 						  const void *packet,
 						  pj_size_t pkt_size,
@@ -193,4 +203,26 @@
 
 
+/**
+ * Notify the STUN transaction about the arrival of STUN response.
+ * If the STUN response contains a final error (300 and greater), the
+ * transaction will be terminated and callback will be called. If the
+ * STUN response contains response code 100-299, retransmission
+ * will  cease, but application must still call this function again
+ * with a final response later to allow the transaction to complete.
+ *
+ * @param tsx		The STUN client transaction instance.
+ * @param packet	The incoming packet.
+ * @param pkt_size	Size of the incoming packet.
+ * @param parsed_len	Optional pointer to receive the number of bytes
+ *			that have been parsed from the incoming packet
+ *			for the STUN message. This is useful if the
+ *			STUN transaction is running over stream oriented
+ *			socket such as TCP or TLS.
+ *
+ * @return		PJ_SUCCESS on success or the appropriate error code.
+ */
+PJ_DECL(pj_status_t) pj_stun_client_tsx_on_rx_msg(pj_stun_client_tsx *tsx,
+						  const pj_stun_msg *msg);
+
 
 /**
Index: pjproject/trunk/pjlib-util/src/pjlib-util/stun_msg_dump.c
===================================================================
--- pjproject/trunk/pjlib-util/src/pjlib-util/stun_msg_dump.c	(revision 993)
+++ pjproject/trunk/pjlib-util/src/pjlib-util/stun_msg_dump.c	(revision 993)
@@ -0,0 +1,208 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2005 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+#include <pjlib-util/stun_msg.h>
+#include <pjlib-util/errno.h>
+#include <pj/assert.h>
+#include <pj/string.h>
+
+
+#define APPLY()		if (len < 1 || len >= (end-p)) \
+			    goto on_return; \
+			p += len
+
+static int print_attr(char *buffer, unsigned length,
+		      const pj_stun_attr_hdr *ahdr)
+{
+    char *p = buffer, *end = buffer + length;
+    int len;
+
+    len = pj_ansi_snprintf(buffer, end-p,
+			   "  %s: length=%d",
+			   pj_stun_get_attr_name(ahdr->type),
+			   (int)ahdr->length);
+    APPLY();
+
+
+    switch (ahdr->type) {
+    case PJ_STUN_ATTR_MAPPED_ADDR:
+    case PJ_STUN_ATTR_RESPONSE_ADDR:
+    case PJ_STUN_ATTR_SOURCE_ADDR:
+    case PJ_STUN_ATTR_CHANGED_ADDR:
+    case PJ_STUN_ATTR_REFLECTED_FROM:
+    case PJ_STUN_ATTR_REMOTE_ADDRESS:
+    case PJ_STUN_ATTR_RELAY_ADDRESS:
+    case PJ_STUN_ATTR_XOR_MAPPED_ADDRESS:
+    case PJ_STUN_ATTR_REQUESTED_IP:
+    case PJ_STUN_ATTR_XOR_REFLECTED_FROM:
+    case PJ_STUN_ATTR_XOR_INTERNAL_ADDR:
+    case PJ_STUN_ATTR_ALTERNATE_SERVER:
+	{
+	    const pj_stun_generic_ip_addr_attr *attr;
+
+	    attr = (const pj_stun_generic_ip_addr_attr*)ahdr;
+
+	    if (attr->addr.addr.sa_family == PJ_AF_INET) {
+		len = pj_ansi_snprintf(buffer, end-p,
+				       ", IPv4 addr=%s:%d\n",
+				       pj_inet_ntoa(attr->addr.ipv4.sin_addr),
+				       pj_ntohs(attr->addr.ipv4.sin_port));
+
+	    } else if (attr->addr.addr.sa_family == PJ_AF_INET6) {
+		len = pj_ansi_snprintf(buffer, end-p,
+				       ", IPv6 addr present\n");
+	    } else {
+		len = pj_ansi_snprintf(buffer, end-p,
+				       ", INVALID ADDRESS FAMILY!\n");
+	    }
+	}
+	break;
+
+    case PJ_STUN_ATTR_CHANGE_REQUEST:
+    case PJ_STUN_ATTR_LIFETIME:
+    case PJ_STUN_ATTR_BANDWIDTH:
+    case PJ_STUN_ATTR_REQUESTED_ADDR_TYPE:
+    case PJ_STUN_ATTR_REQUESTED_PORT_PROPS:
+    case PJ_STUN_ATTR_REQUESTED_TRANSPORT:
+    case PJ_STUN_ATTR_TIMER_VAL:
+    case PJ_STUN_ATTR_PRIORITY:
+    case PJ_STUN_ATTR_FINGERPRINT:
+    case PJ_STUN_ATTR_REFRESH_INTERVAL:
+	{
+	    const pj_stun_generic_uint_attr *attr;
+
+	    attr = (const pj_stun_generic_uint_attr*)ahdr;
+	    len = pj_ansi_snprintf(buffer, end-p,
+				   ", value=%d (%x)\n",
+				   (pj_uint32_t)attr->value,
+				   (pj_uint32_t)attr->value);
+	}
+	break;
+
+    case PJ_STUN_ATTR_USERNAME:
+    case PJ_STUN_ATTR_PASSWORD:
+    case PJ_STUN_ATTR_REALM:
+    case PJ_STUN_ATTR_NONCE:
+    case PJ_STUN_ATTR_SERVER:
+	{
+	    const pj_stun_generic_string_attr *attr;
+
+	    attr = (pj_stun_generic_string_attr*)ahdr;
+	    len = pj_ansi_snprintf(buffer, end-p,
+				   ", value=\"%.*s\"\n",
+				   (int)attr->value.slen,
+				   attr->value.ptr);
+	}
+	break;
+
+    case PJ_STUN_ATTR_ERROR_CODE:
+	{
+	    const pj_stun_error_code_attr *attr;
+
+	    attr = (const pj_stun_error_code_attr*) ahdr;
+	    len = pj_ansi_snprintf(buffer, end-p,
+				   ", err_code=%d, reason=\"%.*s\"\n",
+				   attr->err_class*100 + attr->number,
+				   (int)attr->reason.slen,
+				   attr->reason.ptr);
+	}
+	break;
+
+    case PJ_STUN_ATTR_UNKNOWN_ATTRIBUTES:
+	{
+	    const pj_stun_unknown_attr *attr;
+	    unsigned j;
+
+	    attr = (const pj_stun_unknown_attr*) ahdr;
+
+	    len = pj_ansi_snprintf(buffer, end-p,
+				   ", unknown list:");
+	    APPLY();
+
+	    for (j=0; j<attr->attr_count; ++j) {
+		len = pj_ansi_snprintf(buffer, end-p,
+				       " %d",
+				       (int)attr->attrs[j]);
+		APPLY();
+	    }
+	}
+	break;
+
+    case PJ_STUN_ATTR_MESSAGE_INTEGRITY:
+    case PJ_STUN_ATTR_DATA:
+    case PJ_STUN_ATTR_USE_CANDIDATE:
+    default:
+	len = pj_ansi_snprintf(buffer, end-p, "\n");
+
+	break;
+    }
+
+    APPLY();
+
+    return (p-buffer);
+
+on_return:
+    return len;
+}
+
+
+/*
+ * Dump STUN message to a printable string output.
+ */
+PJ_DEF(char*) pj_stun_msg_dump(const pj_stun_msg *msg,
+			       char *buffer,
+			       unsigned *length)
+{
+    char *p, *end;
+    int len;
+    unsigned i;
+
+    PJ_ASSERT_RETURN(msg && buffer && length, NULL);
+
+    p = buffer;
+    end = buffer + (*length);
+
+    len = pj_ansi_snprintf(p, end-p, "STUN %s %s\n",
+			   pj_stun_get_method_name(msg->hdr.type),
+			   pj_stun_get_class_name(msg->hdr.type));
+    APPLY();
+
+    len = pj_ansi_snprintf(p, end-p, 
+			    " Hdr: length=%d, magic=%x, tsx_id=%x %x %x\n"
+			    " Attributes:\n",
+			   msg->hdr.length,
+			   msg->hdr.magic,
+			   *(pj_uint32_t*)&msg->hdr.tsx_id[0],
+			   *(pj_uint32_t*)&msg->hdr.tsx_id[4],
+			   *(pj_uint32_t*)&msg->hdr.tsx_id[8]);
+    APPLY();
+
+    for (i=0; i<msg->attr_count; ++i) {
+	len = print_attr(p, end-p, msg->attr[i]);
+	APPLY();
+    }
+
+on_return:
+    *p = '\0';
+    *length = (p-buffer);
+    return buffer;
+
+}
+
+
+#undef APPLY
Index: pjproject/trunk/pjlib-util/src/pjlib-util/stun_transaction.c
===================================================================
--- pjproject/trunk/pjlib-util/src/pjlib-util/stun_transaction.c	(revision 992)
+++ pjproject/trunk/pjlib-util/src/pjlib-util/stun_transaction.c	(revision 993)
@@ -253,26 +253,13 @@
 
 
+
 /*
  * Notify the STUN transaction about the arrival of STUN response.
  */
 PJ_DEF(pj_status_t) pj_stun_client_tsx_on_rx_msg(pj_stun_client_tsx *tsx,
-						 const void *packet,
-						 pj_size_t pkt_size,
-						 unsigned *parsed_len)
-{
-    pj_stun_msg *msg;
+						 const pj_stun_msg *msg)
+{
     pj_stun_error_code_attr *err_attr;
     pj_status_t status;
-
-    PJ_ASSERT_RETURN(tsx && packet && pkt_size, PJ_EINVAL);
-
-    /* Try to parse the message */
-    status = pj_stun_msg_decode(tsx->pool, (const pj_uint8_t*)packet,
-			        pkt_size, 0, &msg, parsed_len,
-				NULL, NULL, NULL);
-    if (status != PJ_SUCCESS) {
-	stun_perror(tsx, "STUN msg_decode() error", status);
-	return status;
-    }
 
     /* Must be STUN response message */
@@ -330,4 +317,31 @@
 
     return PJ_SUCCESS;
-}
-
+
+}
+
+
+/*
+ * Notify the STUN transaction about the arrival of STUN response.
+ */
+PJ_DEF(pj_status_t) pj_stun_client_tsx_on_rx_pkt(pj_stun_client_tsx *tsx,
+						 const void *packet,
+						 pj_size_t pkt_size,
+						 unsigned *parsed_len)
+{
+    pj_stun_msg *msg;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(tsx && packet && pkt_size, PJ_EINVAL);
+
+    /* Try to parse the message */
+    status = pj_stun_msg_decode(tsx->pool, (const pj_uint8_t*)packet,
+			        pkt_size, 0, &msg, parsed_len,
+				NULL, NULL, NULL);
+    if (status != PJ_SUCCESS) {
+	stun_perror(tsx, "STUN msg_decode() error", status);
+	return status;
+    }
+
+    return pj_stun_client_tsx_on_rx_msg(tsx, msg);
+}
+
Index: pjproject/trunk/pjlib-util/src/pjstun-client/client_main.c
===================================================================
--- pjproject/trunk/pjlib-util/src/pjstun-client/client_main.c	(revision 993)
+++ pjproject/trunk/pjlib-util/src/pjstun-client/client_main.c	(revision 993)
@@ -0,0 +1,25 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2005 Benny Prijono <benny@prijono.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
+ */
+#include <pjlib-util.h>
+#include <pjlib.h>
+
+
+#define THIS_FILE	"client_main.c"
+#define MAX_THREADS	8
+
Index: pjproject/trunk/pjlib-util/src/pjstun-client/stun_session.c
===================================================================
--- pjproject/trunk/pjlib-util/src/pjstun-client/stun_session.c	(revision 993)
+++ pjproject/trunk/pjlib-util/src/pjstun-client/stun_session.c	(revision 993)
@@ -0,0 +1,499 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2005 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 "stun_session.h"
+#include <pjlib.h>
+
+struct pj_stun_session
+{
+    pj_stun_endpoint	*endpt;
+    pj_pool_t		*pool;
+    pj_stun_session_cb	 cb;
+    void		*user_data;
+
+    pj_str_t		 realm;
+    pj_str_t		 username;
+    pj_str_t		 password;
+
+    pj_bool_t		 fingerprint_enabled;
+
+    pj_stun_tx_data	 pending_request_list;
+};
+
+#define SNAME(s_)		    ((s_)->pool->obj_name)
+
+#if PJ_LOG_MAX_LEVEL >= 5
+#   define TRACE_(expr)		    PJ_LOG(5,expr)
+#else
+#   define TRACE_(expr)
+#endif
+
+#if PJ_LOG_MAX_LEVEL >= 4
+#   define LOG_ERR_(sess, title, rc)
+static void stun_perror(pj_stun_session *sess, const char *title, 
+			pj_status_t status)
+{
+    char errmsg[PJ_ERR_MSG_SIZE];
+
+    pj_strerror(status, errmsg, sizeof(errmsg));
+
+    PJ_LOG(4,(SNAME(sess), "%s: %s", title, errmsg));
+}
+
+#else
+#   define ERR_(sess, title, rc)
+#endif
+
+#define TDATA_POOL_SIZE		    1024
+#define TDATA_POOL_INC		    1024
+
+
+static void tsx_on_complete(pj_stun_client_tsx *tsx,
+			       pj_status_t status, 
+			       const pj_stun_msg *response);
+static pj_status_t tsx_on_send_msg(pj_stun_client_tsx *tsx,
+				   const void *stun_pkt,
+				   pj_size_t pkt_size);
+
+static pj_stun_tsx_cb tsx_cb = 
+{
+    &tsx_on_complete,
+    &tsx_on_send_msg
+};
+
+
+static pj_status_t tsx_add(pj_stun_session *sess,
+			   pj_stun_tx_data *tdata)
+{
+    pj_list_push_back(&sess->pending_request_list, tdata);
+    return PJ_SUCCESS;
+}
+
+static pj_status_t tsx_erase(pj_stun_session *sess,
+			     pj_stun_tx_data *tdata)
+{
+    pj_list_erase(tdata);
+    return PJ_SUCCESS;
+}
+
+static pj_stun_tx_data* tsx_lookup(pj_stun_session *sess,
+				   const pj_stun_msg *msg)
+{
+    pj_stun_tx_data *tdata;
+
+    tdata = sess->pending_request_list.next;
+    while (tdata != &sess->pending_request_list) {
+	pj_assert(sizeof(tdata->client_key)==sizeof(msg->hdr.tsx_id));
+	if (pj_memcmp(tdata->client_key, msg->hdr.tsx_id, 
+		      sizeof(msg->hdr.tsx_id))==0)
+	{
+	    return tdata;
+	}
+	tdata = tdata->next;
+    }
+
+    return NULL;
+}
+
+static pj_status_t create_tdata(pj_stun_session *sess,
+				unsigned msg_type,
+				void *user_data,
+			        pj_stun_tx_data **p_tdata)
+{
+    pj_pool_t *pool;
+    pj_status_t status;
+    pj_stun_tx_data *tdata;
+
+    /* Create pool and initialize basic tdata attributes */
+    pool = pj_pool_create(sess->endpt->pf, "tdata%p", 
+			  TDATA_POOL_SIZE, TDATA_POOL_INC, NULL);
+    PJ_ASSERT_RETURN(pool, PJ_ENOMEM);
+
+    tdata = PJ_POOL_ZALLOC_TYPE(pool, pj_stun_tx_data);
+    tdata->pool = pool;
+    tdata->sess = sess;
+    tdata->user_data = tdata;
+
+    /* Create STUN message */
+    status = pj_stun_msg_create(pool, msg_type,  PJ_STUN_MAGIC, 
+				NULL, &tdata->msg);
+    if (status != PJ_SUCCESS) {
+	pj_pool_release(pool);
+	return status;
+    }
+
+    /* If this is a request, then copy the request's transaction ID
+     * as the transaction key.
+     */
+    if (PJ_STUN_IS_REQUEST(msg_type)) {
+	pj_assert(sizeof(tdata->client_key)==sizeof(tdata->msg->hdr.tsx_id));
+	pj_memcpy(tdata->client_key, tdata->msg->hdr.tsx_id,
+		  sizeof(tdata->msg->hdr.tsx_id));
+    }
+
+    *p_tdata = tdata;
+
+    return PJ_SUCCESS;
+}
+
+static void destroy_tdata(pj_stun_tx_data *tdata)
+{
+    if (tdata->client_tsx) {
+	tsx_erase(tdata->sess, tdata);
+	pj_stun_client_tsx_destroy(tdata->client_tsx);
+	tdata->client_tsx = NULL;
+    }
+
+    pj_pool_release(tdata->pool);
+}
+
+static pj_status_t session_apply_req(pj_stun_session *sess,
+				     pj_pool_t *pool,
+				     pj_stun_msg *msg)
+{
+    pj_status_t status;
+
+    /* From draft-ietf-behave-rfc3489bis-05.txt
+     * Section 8.3.1.  Formulating the Request Message
+     */
+    if (sess->realm.slen || sess->username.slen) {
+	pj_stun_generic_string_attr *auname;
+	pj_stun_msg_integrity_attr *amsgi;
+
+	/* Create and add USERNAME attribute */
+	status = pj_stun_generic_string_attr_create(sess->pool, 
+						    PJ_STUN_ATTR_USERNAME,
+						    &sess->username,
+						    &auname);
+	PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+
+	status = pj_stun_msg_add_attr(msg, &auname->hdr);
+	PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+
+	if (sess->realm.slen) {
+	    /* Add REALM only when long term credential is used */
+	    pj_stun_generic_string_attr *arealm;
+	    status = pj_stun_generic_string_attr_create(sess->pool, 
+							PJ_STUN_ATTR_REALM,
+							&sess->realm,
+							&arealm);
+	    PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+
+	    status = pj_stun_msg_add_attr(msg, &arealm->hdr);
+	    PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+	}
+
+	/* Add MESSAGE-INTEGRITY attribute */
+	status = pj_stun_msg_integrity_attr_create(sess->pool, &amsgi);
+	PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+
+	status = pj_stun_msg_add_attr(msg, &amsgi->hdr);
+	PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+
+	PJ_TODO(COMPUTE_MESSAGE_INTEGRITY);
+
+    }
+
+    /* Add FINGERPRINT attribute if necessary */
+    if (sess->fingerprint_enabled) {
+	pj_stun_fingerprint_attr *af;
+
+	status = pj_stun_generic_uint_attr_create(sess->pool, 
+						  PJ_STUN_ATTR_FINGERPRINT,
+						  0, &af);
+	PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+
+	status = pj_stun_msg_add_attr(msg, &af->hdr);
+	PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+
+
+
+
+PJ_DEF(pj_status_t) pj_stun_session_create( pj_stun_endpoint *endpt,
+					    const char *name,
+					    const pj_stun_session_cb *cb,
+					    pj_stun_session **p_sess)
+{
+    pj_pool_t	*pool;
+    pj_stun_session *sess;
+
+    PJ_ASSERT_RETURN(endpt && cb && p_sess, PJ_EINVAL);
+
+    pool = pj_pool_create(endpt->pf, name, 4000, 4000, NULL);
+    PJ_ASSERT_RETURN(pool, PJ_ENOMEM);
+
+    sess = PJ_POOL_ZALLOC_TYPE(pool, pj_stun_session);
+    sess->endpt = endpt;
+    sess->pool = pool;
+    pj_memcpy(&sess->cb, cb, sizeof(*cb));
+
+    pj_list_init(&sess->pending_request_list);
+
+    *p_sess = sess;
+
+    PJ_TODO(MUTEX_PROTECTION);
+
+    return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pj_stun_session_destroy(pj_stun_session *sess)
+{
+    PJ_ASSERT_RETURN(sess, PJ_EINVAL);
+    pj_pool_release(sess->pool);
+
+    return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pj_stun_session_set_user_data( pj_stun_session *sess,
+						   void *user_data)
+{
+    PJ_ASSERT_RETURN(sess, PJ_EINVAL);
+    sess->user_data = user_data;
+    return PJ_SUCCESS;
+}
+
+PJ_DEF(void*) pj_stun_session_get_user_data(pj_stun_session *sess)
+{
+    PJ_ASSERT_RETURN(sess, NULL);
+    return sess->user_data;
+}
+
+PJ_DEF(pj_status_t) pj_stun_session_set_credential( pj_stun_session *sess,
+						    const pj_str_t *realm,
+						    const pj_str_t *user,
+						    const pj_str_t *passwd)
+{
+    pj_str_t empty = { NULL, 0 };
+
+    PJ_ASSERT_RETURN(sess, PJ_EINVAL);
+    pj_strdup_with_null(sess->pool, &sess->realm, realm ? realm : &empty);
+    pj_strdup_with_null(sess->pool, &sess->username, user ? user : &empty);
+    pj_strdup_with_null(sess->pool, &sess->password, passwd ? passwd : &empty);
+
+    return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pj_stun_session_enable_fingerprint(pj_stun_session *sess,
+						       pj_bool_t enabled)
+{
+    PJ_ASSERT_RETURN(sess, PJ_EINVAL);
+    sess->fingerprint_enabled = enabled;
+    return PJ_SUCCESS;
+}
+
+
+PJ_DEF(pj_status_t) pj_stun_session_create_bind_req(pj_stun_session *sess,
+						    pj_stun_tx_data **p_tdata)
+{
+    pj_pool_t *pool;
+    pj_stun_tx_data *tdata;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(sess && p_tdata, PJ_EINVAL);
+
+    status = create_tdata(sess, PJ_STUN_BINDING_REQUEST, NULL, &tdata);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    status = session_apply_req(sess, pool, tdata->msg);
+    if (status != PJ_SUCCESS) {
+	destroy_tdata(tdata);
+	return status;
+    }
+
+    *p_tdata = tdata;
+    return PJ_SUCCESS;
+}
+
+PJ_DEF(pj_status_t) pj_stun_session_create_allocate_req(pj_stun_session *sess,
+							pj_stun_tx_data **p_tdata)
+{
+    PJ_ASSERT_RETURN(PJ_FALSE, PJ_ENOTSUP);
+}
+
+
+PJ_DEF(pj_status_t) 
+pj_stun_session_create_set_active_destination_req(pj_stun_session *sess,
+						  pj_stun_tx_data **p_tdata)
+{
+    PJ_ASSERT_RETURN(PJ_FALSE, PJ_ENOTSUP);
+}
+
+PJ_DEF(pj_status_t) pj_stun_session_create_connect_req(	pj_stun_session *sess,
+						        pj_stun_tx_data **p_tdata)
+{
+    PJ_ASSERT_RETURN(PJ_FALSE, PJ_ENOTSUP);
+}
+
+PJ_DEF(pj_status_t) 
+pj_stun_session_create_connection_status_ind(pj_stun_session *sess,
+					     pj_stun_tx_data **p_tdata)
+{
+    PJ_ASSERT_RETURN(PJ_FALSE, PJ_ENOTSUP);
+}
+
+PJ_DEF(pj_status_t) pj_stun_session_create_send_ind( pj_stun_session *sess,
+						     pj_stun_tx_data **p_tdata)
+{
+    PJ_ASSERT_RETURN(PJ_FALSE, PJ_ENOTSUP);
+}
+
+PJ_DEF(pj_status_t) pj_stun_session_create_data_ind( pj_stun_session *sess,
+						     pj_stun_tx_data **p_tdata)
+{
+    PJ_ASSERT_RETURN(PJ_FALSE, PJ_ENOTSUP);
+}
+
+PJ_DEF(pj_status_t) pj_stun_session_send_msg( pj_stun_session *sess,
+					      unsigned addr_len,
+					      const pj_sockaddr_t *server,
+					      pj_stun_tx_data *tdata)
+{
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(sess && addr_len && server && tdata, PJ_EINVAL);
+
+    if (PJ_LOG_MAX_LEVEL >= 5) {
+	char buf[512];
+	unsigned buflen = sizeof(buf);
+	const char *dst_name;
+	int dst_port;
+	const pj_sockaddr *dst = (const pj_sockaddr*)server;
+	
+	if (dst->sa_family == PJ_AF_INET) {
+	    const pj_sockaddr_in *dst4 = (const pj_sockaddr_in*)dst;
+	    dst_name = pj_inet_ntoa(dst4->sin_addr);
+	    dst_port = pj_ntohs(dst4->sin_port);
+	} else if (dst->sa_family == PJ_AF_INET6) {
+	    const pj_sockaddr_in6 *dst6 = (const pj_sockaddr_in6*)dst;
+	    dst_name = "IPv6";
+	    dst_port = pj_ntohs(dst6->sin6_port);
+	} else {
+	    LOG_ERR_(sess, "Invalid address family", PJ_EINVAL);
+	    return PJ_EINVAL;
+	}
+
+	PJ_LOG(5,(SNAME(sess), 
+		  "Sending STUN message to %s:%d:\n"
+		  "%s\n",
+		  dst_name, dst_port,
+		  pj_stun_msg_dump(tdata->msg, buf, &buflen)));
+    }
+
+
+    /* If this is a STUN request message, then send the request with
+     * a new STUN client transaction.
+     */
+    if (PJ_STUN_IS_REQUEST(tdata->msg->hdr.type)) {
+
+	/* Create STUN client transaction */
+	status = pj_stun_client_tsx_create(sess->endpt, &tsx_cb, 
+					   &tdata->client_tsx);
+	PJ_ASSERT_RETURN(status==PJ_SUCCESS, status);
+	pj_stun_client_tsx_set_data(tdata->client_tsx, (void*)tdata);
+
+	/* Send the request! */
+	status = pj_stun_client_tsx_send_msg(tdata->client_tsx, PJ_TRUE,
+					     tdata->msg);
+	if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+	    LOG_ERR_(sess, "Error sending STUN request", status);
+	    return status;
+	}
+
+	/* Add to pending request list */
+	tsx_add(sess, tdata);
+
+    } else {
+	/* Otherwise for non-request message, send directly to transport. */
+	status = sess->cb.on_send_msg(tdata, addr_len, server);
+    }
+
+
+    return status;
+}
+
+
+PJ_DEF(pj_status_t) pj_stun_session_on_rx_pkt(pj_stun_session *sess,
+					      const void *packet,
+					      pj_size_t pkt_size,
+					      unsigned *parsed_len)
+{
+    pj_stun_msg *msg;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(sess && packet && pkt_size, PJ_EINVAL);
+
+    /* Try to parse the message */
+    status = pj_stun_msg_decode(tsx->pool, (const pj_uint8_t*)packet,
+			        pkt_size, 0, &msg, parsed_len,
+				NULL, NULL, NULL);
+    if (status != PJ_SUCCESS) {
+	LOG_ERR_(sess, "STUN msg_decode() error", status);
+	return status;
+    }
+
+    if (PJ_STUN_IS_RESPONSE(msg->hdr.type) ||
+	PJ_STUN_IS_ERROR_RESPONSE(msg->hdr.type))
+    {
+	pj_stun_tx_data *tdata;
+
+	/* Lookup pending client transaction */
+	tdata = tsx_lookup(sess, msg);
+	if (tdata == NULL) {
+	    LOG_ERR_(sess, "STUN error finding transaction", PJ_ENOTFOUND);
+	    return PJ_ENOTFOUND;
+	}
+
+	/* Pass the response to the transaction. 
+	 * If the message is accepted, transaction callback will be called,
+	 * and this will call the session callback too.
+	 */
+	status = pj_stun_client_tsx_on_rx_msg(tdata->client_tsx, msg);
+	if (status != PJ_SUCCESS)
+	    return status;
+
+	/* If transaction has completed, destroy the transmit data.
+	 * This will remove the transaction from the pending list too.
+	 */
+	if (pj_stun_client_tsx_is_complete(tdata->client_tsx)) {
+	    destroy_tdata(tdata);
+	    tdata = NULL;
+	}
+
+	return PJ_SUCCESS;
+
+    } else if (PJ_STUN_IS_REQUEST(msg->hdr.type)) {
+
+
+    } else if (PJ_STUN_IS_INDICATION(msg->hdr.type)) {
+
+
+    } else {
+	pj_assert(!"Unexpected!");
+	return PJ_EBUG;
+    }
+
+}
+
Index: pjproject/trunk/pjlib-util/src/pjstun-client/stun_session.h
===================================================================
--- pjproject/trunk/pjlib-util/src/pjstun-client/stun_session.h	(revision 993)
+++ pjproject/trunk/pjlib-util/src/pjstun-client/stun_session.h	(revision 993)
@@ -0,0 +1,114 @@
+/* $Id$ */
+/* 
+ * Copyright (C) 2003-2005 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 
+ */
+#ifndef __PJLIB_UTIL_STUN_SESSION_H__
+#define __PJLIB_UTIL_STUN_SESSION_H__
+
+#include <pjlib-util/stun_msg.h>
+#include <pjlib-util/stun_endpoint.h>
+#include <pjlib-util/stun_transaction.h>
+#include <pj/list.h>
+
+
+typedef struct pj_stun_tx_data pj_stun_tx_data;
+typedef struct pj_stun_session pj_stun_session;
+
+typedef struct pj_stun_session_cb
+{
+    pj_status_t (*on_send_msg)(pj_stun_tx_data *tdata,
+			       unsigned addr_len, 
+			       const pj_sockaddr_t *dst_addr);
+
+    void (*on_bind_response)(void *user_data, pj_status_t status, pj_stun_msg *response);
+    void (*on_allocate_response)(void *user_data, pj_status_t status, pj_stun_msg *response);
+    void (*on_set_active_destination_response)(void *user_data, pj_status_t status, pj_stun_msg *response);
+    void (*on_connect_response)(void *user_data, pj_status_t status, pj_stun_msg *response);
+} pj_stun_session_cb;
+
+
+struct pj_stun_tx_data
+{
+    PJ_DECL_LIST_MEMBER(struct pj_stun_tx_data);
+
+    pj_pool_t		*pool;
+    pj_stun_session	*sess;
+    pj_stun_msg		*msg;
+    void		*user_data;
+
+    pj_stun_client_tsx	*client_tsx;
+    pj_uint8_t		 client_key[12];
+};
+
+
+PJ_DECL(pj_status_t) pj_stun_session_create(pj_stun_endpoint *endpt,
+					    const char *name,
+					    const pj_stun_session_cb *cb,
+					    pj_stun_session **p_sess);
+
+PJ_DECL(pj_status_t) pj_stun_session_destroy(pj_stun_session *sess);
+
+PJ_DECL(pj_status_t) pj_stun_session_set_user_data(pj_stun_session *sess,
+						   void *user_data);
+
+PJ_DECL(void*) pj_stun_session_get_user_data(pj_stun_session *sess);
+
+PJ_DECL(pj_status_t) pj_stun_session_set_credential(pj_stun_session *sess,
+						    const pj_str_t *realm,
+						    const pj_str_t *user,
+						    const pj_str_t *passwd);
+
+PJ_DECL(pj_status_t) pj_stun_session_enable_fingerprint(pj_stun_session *sess,
+							pj_bool_t enabled);
+
+PJ_DECL(pj_status_t) pj_stun_session_create_bind_req(pj_stun_session *sess,
+						     pj_stun_tx_data **p_tdata);
+
+PJ_DECL(pj_status_t) pj_stun_session_create_allocate_req(pj_stun_session *sess,
+							 pj_stun_tx_data **p_tdata);
+
+PJ_DECL(pj_status_t) 
+pj_stun_session_create_set_active_destination_req(pj_stun_session *sess,
+						  pj_stun_tx_data **p_tdata);
+
+PJ_DECL(pj_status_t) pj_stun_session_create_connect_req(pj_stun_session *sess,
+							pj_stun_tx_data **p_tdata);
+
+PJ_DECL(pj_status_t) 
+pj_stun_session_create_connection_status_ind(pj_stun_session *sess,
+					     pj_stun_tx_data **p_tdata);
+
+PJ_DECL(pj_status_t) pj_stun_session_create_send_ind(pj_stun_session *sess,
+						     pj_stun_tx_data **p_tdata);
+
+PJ_DECL(pj_status_t) pj_stun_session_create_data_ind(pj_stun_session *sess,
+						     pj_stun_tx_data **p_tdata);
+
+PJ_DECL(pj_status_t) pj_stun_session_send_msg(pj_stun_session *sess,
+					      unsigned addr_len,
+					      const pj_sockaddr_t *server,
+					      pj_stun_tx_data *tdata);
+
+PJ_DECL(pj_status_t) pj_stun_session_on_rx_pkt(pj_stun_session *sess,
+					       const void *packet,
+					       pj_size_t pkt_size,
+					       unsigned *parsed_len);
+
+
+
+#endif	/* __PJLIB_UTIL_STUN_SESSION_H__ */
+
