Index: pjproject/trunk/pjnath/include/pjnath/ice_strans.h
===================================================================
--- pjproject/trunk/pjnath/include/pjnath/ice_strans.h	(revision 3027)
+++ pjproject/trunk/pjnath/include/pjnath/ice_strans.h	(revision 3028)
@@ -623,4 +623,15 @@
 
 /**
+ * Get the number of local candidates for the specified component ID.
+ *
+ * @param ice_st	The ICE stream transport.
+ * @param comp_id	Component ID.
+ *
+ * @return		The number of candidates.
+ */
+PJ_DECL(unsigned) pj_ice_strans_get_cands_count(pj_ice_strans *ice_st,
+					        unsigned comp_id);
+
+/**
  * Enumerate the local candidates for the specified component.
  *
Index: pjproject/trunk/pjnath/include/pjnath/turn_session.h
===================================================================
--- pjproject/trunk/pjnath/include/pjnath/turn_session.h	(revision 3027)
+++ pjproject/trunk/pjnath/include/pjnath/turn_session.h	(revision 3028)
@@ -456,9 +456,17 @@
  *
  * @param sess		The TURN client session.
+ * @param last_err	Optional error code to be set to the session,
+ *			which would be returned back in the \a info
+ *			parameter of #pj_turn_session_get_info(). If
+ *			this argument value is PJ_SUCCESS, the error
+ *			code will not be set. If the session already
+ *			has an error code set, this function will not
+ *			overwrite that error code either.
  *
  * @return		PJ_SUCCESS if the operation has been successful,
  *			or the appropriate error code on failure.
  */
-PJ_DECL(pj_status_t) pj_turn_session_destroy(pj_turn_session *sess);
+PJ_DECL(pj_status_t) pj_turn_session_destroy(pj_turn_session *sess,
+					     pj_status_t last_err);
 
 
Index: pjproject/trunk/pjnath/src/pjnath/ice_strans.c
===================================================================
--- pjproject/trunk/pjnath/src/pjnath/ice_strans.c	(revision 3027)
+++ pjproject/trunk/pjnath/src/pjnath/ice_strans.c	(revision 3028)
@@ -913,4 +913,25 @@
 
 /*
+ * Get number of candidates
+ */
+PJ_DEF(unsigned) pj_ice_strans_get_cands_count(pj_ice_strans *ice_st,
+					       unsigned comp_id)
+{
+    unsigned i, cnt;
+
+    PJ_ASSERT_RETURN(ice_st && ice_st->ice && comp_id && 
+		     comp_id <= ice_st->comp_cnt, 0);
+
+    cnt = 0;
+    for (i=0; i<ice_st->ice->lcand_cnt; ++i) {
+	if (ice_st->ice->lcand[i].comp_id != comp_id)
+	    continue;
+	++cnt;
+    }
+
+    return cnt;
+}
+
+/*
  * Enum candidates
  */
Index: pjproject/trunk/pjnath/src/pjnath/turn_session.c
===================================================================
--- pjproject/trunk/pjnath/src/pjnath/turn_session.c	(revision 3027)
+++ pjproject/trunk/pjnath/src/pjnath/turn_session.c	(revision 3028)
@@ -462,8 +462,11 @@
  * Forcefully destroy the TURN session.
  */
-PJ_DEF(pj_status_t) pj_turn_session_destroy( pj_turn_session *sess)
+PJ_DEF(pj_status_t) pj_turn_session_destroy( pj_turn_session *sess,
+					     pj_status_t last_err)
 {
     PJ_ASSERT_RETURN(sess, PJ_EINVAL);
 
+    if (last_err != PJ_SUCCESS && sess->last_status == PJ_SUCCESS)
+	sess->last_status = last_err;
     set_state(sess, PJ_TURN_STATE_DEALLOCATED);
     sess_shutdown(sess, PJ_SUCCESS);
@@ -960,4 +963,6 @@
 			   PJ_FALSE, PJ_FALSE);
     if (ch && ch->num != PJ_TURN_INVALID_CHANNEL && ch->bound) {
+	unsigned total_len;
+
 	/* Peer is assigned a channel number, we can use ChannelData */
 	pj_turn_channel_data *cd = (pj_turn_channel_data*)sess->tx_pkt;
@@ -965,5 +970,7 @@
 	pj_assert(sizeof(*cd)==4);
 
-	if (pkt_len > sizeof(sess->tx_pkt)-sizeof(*cd)) {
+	/* Calculate total length, including paddings */
+	total_len = (pkt_len + sizeof(*cd) + 3) & (~3);
+	if (total_len > sizeof(sess->tx_pkt)) {
 	    status = PJ_ETOOBIG;
 	    goto on_return;
@@ -976,5 +983,5 @@
 	pj_assert(sess->srv_addr != NULL);
 
-	status = sess->cb.on_send_pkt(sess, sess->tx_pkt, pkt_len+sizeof(*cd),
+	status = sess->cb.on_send_pkt(sess, sess->tx_pkt, total_len,
 				      sess->srv_addr,
 				      pj_sockaddr_get_len(sess->srv_addr));
@@ -1157,5 +1164,6 @@
 	} else {
 	    if (parsed_len) {
-		*parsed_len = cd.length + sizeof(cd);
+		/* Apply padding too */
+		*parsed_len = ((cd.length + 3) & (~3)) + sizeof(cd);
 	    }
 	}
Index: pjproject/trunk/pjnath/src/pjnath/turn_sock.c
===================================================================
--- pjproject/trunk/pjnath/src/pjnath/turn_sock.c	(revision 3027)
+++ pjproject/trunk/pjnath/src/pjnath/turn_sock.c	(revision 3028)
@@ -274,12 +274,5 @@
 		     pj_status_t status)
 {
-    char errmsg[PJ_ERR_MSG_SIZE];
-
-    if (status != PJ_SUCCESS) {
-	pj_strerror(status, errmsg, sizeof(errmsg));
-	PJ_LOG(4,(turn_sock->obj_name, "%s: %s", title, errmsg));
-    } else {
-	PJ_LOG(4,(turn_sock->obj_name, "%s", title, errmsg));
-    }
+    PJ_PERROR(4,(turn_sock->obj_name, status, title));
 }
 
@@ -289,6 +282,7 @@
 {
     show_err(turn_sock, title, status);
-    if (turn_sock->sess)
-	pj_turn_session_destroy(turn_sock->sess);
+    if (turn_sock->sess) {
+	pj_turn_session_destroy(turn_sock->sess, status);
+    }
 }
 
@@ -495,4 +489,47 @@
 }
 
+static pj_uint16_t GETVAL16H(const pj_uint8_t *buf, unsigned pos)
+{
+    return (pj_uint16_t) ((buf[pos + 0] << 8) | \
+			  (buf[pos + 1] << 0));
+}
+
+/* Quick check to determine if there is enough packet to process in the
+ * incoming buffer. Return the packet length, or zero if there's no packet.
+ */
+static unsigned has_packet(pj_turn_sock *turn_sock, const void *buf, pj_size_t bufsize)
+{
+    pj_bool_t is_stun;
+
+    if (turn_sock->conn_type == PJ_TURN_TP_UDP)
+	return bufsize;
+
+    /* Quickly check if this is STUN message, by checking the first two bits and
+     * size field which must be multiple of 4 bytes
+     */
+    is_stun = ((((pj_uint8_t*)buf)[0] & 0xC0) == 0) &&
+	      ((GETVAL16H((const pj_uint8_t*)buf, 2) & 0x03)==0);
+
+    if (is_stun) {
+	pj_size_t msg_len = GETVAL16H((const pj_uint8_t*)buf, 2);
+	return (msg_len+20 <= bufsize) ? msg_len+20 : 0;
+    } else {
+	/* This must be ChannelData. */
+	pj_turn_channel_data cd;
+
+	if (bufsize < 4)
+	    return 0;
+
+	/* Decode ChannelData packet */
+	pj_memcpy(&cd, buf, sizeof(pj_turn_channel_data));
+	cd.length = pj_ntohs(cd.length);
+
+	if (bufsize >= cd.length+sizeof(cd)) 
+	    return (cd.length+sizeof(cd)+3) & (~3);
+	else
+	    return 0;
+    }
+}
+
 /*
  * Notification from ioqueue when incoming UDP packet is received.
@@ -505,5 +542,4 @@
 {
     pj_turn_sock *turn_sock;
-    pj_size_t parsed_len;
     pj_bool_t ret = PJ_TRUE;
 
@@ -512,12 +548,43 @@
 
     if (status == PJ_SUCCESS && turn_sock->sess) {
-	/* Report incoming packet to TURN session */
-	parsed_len = (unsigned)size;
-	pj_turn_session_on_rx_pkt(turn_sock->sess, data,  size, &parsed_len);
-	if (parsed_len < (unsigned)size) {
-	    *remainder = size - parsed_len;
-	    pj_memmove(data, ((char*)data)+parsed_len, *remainder);
-	} else {
-	    *remainder = 0;
+	/* Report incoming packet to TURN session, repeat while we have
+	 * "packet" in the buffer (required for stream-oriented transports)
+	 */
+	unsigned pkt_len;
+
+	//PJ_LOG(5,(turn_sock->pool->obj_name, 
+	//	  "Incoming data, %lu bytes total buffer", size));
+
+	while ((pkt_len=has_packet(turn_sock, data, size)) != 0) {
+	    pj_size_t parsed_len;
+	    //const pj_uint8_t *pkt = (const pj_uint8_t*)data;
+
+	    //PJ_LOG(5,(turn_sock->pool->obj_name, 
+	    //	      "Packet start: %02X %02X %02X %02X", 
+	    //	      pkt[0], pkt[1], pkt[2], pkt[3]));
+
+	    //PJ_LOG(5,(turn_sock->pool->obj_name, 
+	    //	      "Processing %lu bytes packet of %lu bytes total buffer",
+	    //	      pkt_len, size));
+
+	    parsed_len = (unsigned)size;
+	    pj_turn_session_on_rx_pkt(turn_sock->sess, data,  size, &parsed_len);
+
+	    /* parsed_len may be zero if we have parsing error, so use our
+	     * previous calculation to exhaust the bad packet.
+	     */
+	    if (parsed_len == 0)
+		parsed_len = pkt_len;
+
+	    if (parsed_len < (unsigned)size) {
+		*remainder = size - parsed_len;
+		pj_memmove(data, ((char*)data)+parsed_len, *remainder);
+	    } else {
+		*remainder = 0;
+	    }
+	    size = *remainder;
+
+	    //PJ_LOG(5,(turn_sock->pool->obj_name, 
+	    //	      "Buffer size now %lu bytes", size));
 	}
     } else if (status != PJ_SUCCESS && 
Index: pjproject/trunk/pjsip-apps/src/samples/debug.c
===================================================================
--- pjproject/trunk/pjsip-apps/src/samples/debug.c	(revision 3027)
+++ pjproject/trunk/pjsip-apps/src/samples/debug.c	(revision 3028)
@@ -29,4 +29,4 @@
  *  #include "playfile.c"
  */
-#include "jbsim.c"
+#include "icedemo.c"
 
Index: pjproject/trunk/pjsip-apps/src/samples/icedemo.c
===================================================================
--- pjproject/trunk/pjsip-apps/src/samples/icedemo.c	(revision 3027)
+++ pjproject/trunk/pjsip-apps/src/samples/icedemo.c	(revision 3028)
@@ -48,4 +48,5 @@
 	pj_str_t    turn_password;
 	pj_bool_t   turn_fingerprint;
+	const char *log_file;
     } opt;
 
@@ -57,4 +58,5 @@
     pj_ice_strans_cfg	 ice_cfg;
     pj_ice_strans	*icest;
+    FILE		*log_fhnd;
 
     /* Variables to store parsed remote ICE info */
@@ -110,4 +112,10 @@
 
     pj_shutdown();
+
+    if (icedemo.log_fhnd) {
+	fclose(icedemo.log_fhnd);
+	icedemo.log_fhnd = NULL;
+    }
+
     exit(status != PJ_SUCCESS);
 }
@@ -217,9 +225,11 @@
     PJ_UNUSED_ARG(pkt);
 
-    ((char*)pkt)[size] = '\0';
-
-    PJ_LOG(3,(THIS_FILE, "Component %d: received %d bytes data from %s: \"%s\"",
+    // Don't do this! It will ruin the packet buffer in case TCP is used!
+    //((char*)pkt)[size] = '\0';
+
+    PJ_LOG(3,(THIS_FILE, "Component %d: received %d bytes data from %s: \"%.*s\"",
 	      comp_id, size,
 	      pj_sockaddr_print(src_addr, ipstr, sizeof(ipstr), 3),
+	      (unsigned)size,
 	      (char*)pkt));
 }
@@ -236,6 +246,4 @@
 	(op==PJ_ICE_STRANS_OP_INIT? "initialization" :
 	    (op==PJ_ICE_STRANS_OP_NEGOTIATION ? "negotiation" : "unknown_op"));
-
-    PJ_UNUSED_ARG(ice_st);
 
     if (status == PJ_SUCCESS) {
@@ -246,7 +254,16 @@
 	pj_strerror(status, errmsg, sizeof(errmsg));
 	PJ_LOG(1,(THIS_FILE, "ICE %s failed: %s", opname, errmsg));
-    }
-}
-
+	pj_ice_strans_destroy(ice_st);
+	icedemo.icest = NULL;
+    }
+}
+
+/* log callback to write to file */
+static void log_func(int level, const char *data, int len)
+{
+    if (icedemo.log_fhnd)
+	fwrite(data, len, 1, icedemo.log_fhnd);
+    pj_log_write(level, data, len);
+}
 
 /*
@@ -258,4 +275,9 @@
 {
     pj_status_t status;
+
+    if (icedemo.opt.log_file) {
+	icedemo.log_fhnd = fopen(icedemo.opt.log_file, "a");
+	pj_log_set_log_func(&log_func);
+    }
 
     /* Initialize the libraries before anything else */
@@ -1148,4 +1170,5 @@
     puts(" --max-host, -H N          Set max number of host candidates to N");
     puts(" --regular, -R             Use regular nomination (default aggressive)");
+    puts(" --log-file, -L FILE       Save output to log FILE");
     puts(" --help, -h                Display this screen.");
     puts("");
@@ -1183,5 +1206,6 @@
 	{ "turn-password",	1, 0, 'p'},
 	{ "turn-fingerprint",	0, 0, 'F'},
-	{ "regular",		0, 0, 'R'}
+	{ "regular",		0, 0, 'R'},
+	{ "log-file",		1, 0, 'L'},
     };
     int c, opt_id;
@@ -1191,5 +1215,5 @@
     icedemo.opt.max_host = -1;
 
-    while((c=pj_getopt_long(argc,argv, "c:n:s:t:u:p:H:hTFR", long_options, &opt_id))!=-1) {
+    while((c=pj_getopt_long(argc,argv, "c:n:s:t:u:p:H:L:hTFR", long_options, &opt_id))!=-1) {
 	switch (c) {
 	case 'c':
@@ -1230,4 +1254,7 @@
 	    icedemo.opt.regular = PJ_TRUE;
 	    break;
+	case 'L':
+	    icedemo.opt.log_file = pj_optarg;
+	    break;
 	default:
 	    printf("Argument \"%s\" is not valid. Use -h to see help",
