Final ICE stream transport

git-svn-id: https://svn.pjsip.org/repos/pjproject/trunk@1096 74dad513-b988-da41-8d7b-12977e46ad98
diff --git a/pjnath/build/pjnath.dsp b/pjnath/build/pjnath.dsp
index 081f89e..83bc6a2 100644
--- a/pjnath/build/pjnath.dsp
+++ b/pjnath/build/pjnath.dsp
@@ -95,7 +95,7 @@
 # End Source File

 # Begin Source File

 

-SOURCE=..\src\pjnath\ice_mt.c

+SOURCE=..\src\pjnath\ice_stream_transport.c

 # End Source File

 # Begin Source File

 

@@ -135,7 +135,7 @@
 # End Source File

 # Begin Source File

 

-SOURCE=..\include\pjnath\ice_mt.h

+SOURCE=..\include\pjnath\ice_stream_transport.h

 # End Source File

 # Begin Source File

 

diff --git a/pjnath/include/pjnath.h b/pjnath/include/pjnath.h
index c3f85fc..8574631 100644
--- a/pjnath/include/pjnath.h
+++ b/pjnath/include/pjnath.h
@@ -20,7 +20,7 @@
 #include <pjnath/config.h>
 #include <pjnath/errno.h>
 #include <pjnath/ice.h>
-#include <pjnath/ice_mt.h>
+#include <pjnath/ice_stream_transport.h>
 #include <pjnath/stun_auth.h>
 #include <pjnath/stun_config.h>
 #include <pjnath/stun_msg.h>
diff --git a/pjnath/include/pjnath/errno.h b/pjnath/include/pjnath/errno.h
index 0a4d588..3a7c591 100644
--- a/pjnath/include/pjnath/errno.h
+++ b/pjnath/include/pjnath/errno.h
@@ -155,6 +155,17 @@
 #define PJ_EICEINCOMPID		    -1
 /**
  * @hideinitializer
+ * Invalid ICE candidate ID
+ */
+#define PJ_EICEINCANDID		    -1
+/**
+ * @hideinitializer
+ * ICE session not available
+ */
+#define PJ_ENOICE		    -1
+
+ /**
+ * @hideinitializer
  * ICE check is in progress
  */
 #define PJ_EICEINPROGRESS	    -1
diff --git a/pjnath/include/pjnath/ice.h b/pjnath/include/pjnath/ice.h
index 94cd7a2..8aa77cb 100644
--- a/pjnath/include/pjnath/ice.h
+++ b/pjnath/include/pjnath/ice.h
@@ -81,8 +81,6 @@
 typedef struct pj_ice_comp
 {
     unsigned	     comp_id;
-    pj_stun_session *stun_sess;
-    pj_sockaddr	     local_addr;
     int		     nominated_check_id;
 } pj_ice_comp;
 
@@ -99,6 +97,7 @@
     pj_sockaddr		 addr;
     pj_sockaddr		 base_addr;
     pj_sockaddr		 srv_addr;
+    pj_stun_session	*stun_sess;
 } pj_ice_cand;
 
 typedef enum pj_ice_check_state
@@ -145,11 +144,13 @@
 typedef struct pj_ice_cb
 {
     void	(*on_ice_complete)(pj_ice *ice, pj_status_t status);
-    pj_status_t (*on_tx_pkt)(pj_ice *ice, unsigned comp_id,
+    pj_status_t (*on_tx_pkt)(pj_ice *ice, unsigned comp_id, 
+			     unsigned cand_id,
 			     const void *pkt, pj_size_t size,
 			     const pj_sockaddr_t *dst_addr,
 			     unsigned dst_addr_len);
-    pj_status_t	(*on_rx_data)(pj_ice *ice, unsigned comp_id,
+    void	(*on_rx_data)(pj_ice *ice, unsigned comp_id,
+			      unsigned cand_id,
 			      void *pkt, pj_size_t size,
 			      const pj_sockaddr_t *src_addr,
 			      unsigned src_addr_len);
@@ -212,17 +213,11 @@
 				   const char *name,
 				   pj_ice_role role,
 				   const pj_ice_cb *cb,
+				   const pj_str_t *local_ufrag,
+				   const pj_str_t *local_passwd,
 				   pj_ice **p_ice);
 PJ_DECL(pj_status_t) pj_ice_destroy(pj_ice *ice);
-PJ_DECL(pj_status_t) pj_ice_add_comp(pj_ice *ice,
-				     unsigned comp_id,
-				     const pj_sockaddr_t *local_addr,
-				     unsigned addr_len);
-PJ_DECL(pj_status_t) pj_ice_set_credentials(pj_ice *ice,
-					    const pj_str_t *local_ufrag,
-					    const pj_str_t *local_pass,
-					    const pj_str_t *remote_ufrag,
-					    const pj_str_t *remote_pass);
+PJ_DECL(pj_status_t) pj_ice_add_comp(pj_ice *ice, unsigned comp_id);
 PJ_DECL(pj_status_t) pj_ice_add_cand(pj_ice *ice,
 				     unsigned comp_id,
 				     pj_ice_cand_type type,
@@ -246,6 +241,8 @@
 				     pj_ice_cand **p_cand);
 
 PJ_DECL(pj_status_t) pj_ice_create_check_list(pj_ice *ice,
+					      const pj_str_t *rem_ufrag,
+					      const pj_str_t *rem_passwd,
 					      unsigned rem_cand_cnt,
 					      const pj_ice_cand rem_cand[]);
 PJ_DECL(pj_status_t) pj_ice_start_check(pj_ice *ice);
@@ -256,6 +253,7 @@
 				      pj_size_t data_len);
 PJ_DECL(pj_status_t) pj_ice_on_rx_pkt(pj_ice *ice,
 				      unsigned comp_id,
+				      unsigned cand_id,
 				      void *pkt,
 				      pj_size_t pkt_size,
 				      const pj_sockaddr_t *src_addr,
diff --git a/pjnath/include/pjnath/ice_mt.h b/pjnath/include/pjnath/ice_mt.h
deleted file mode 100644
index d96f39b..0000000
--- a/pjnath/include/pjnath/ice_mt.h
+++ /dev/null
@@ -1,114 +0,0 @@
-/* $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 
- */
-#ifndef __PJNATH_ICE_MT_H__
-#define __PJNATH_ICE_MT_H__
-
-
-/**
- * @file ice_mt.h
- * @brief ICE Media Transport.
- */
-#include <pjnath/ice.h>
-#include <pj/ioqueue.h>
-
-
-PJ_BEGIN_DECL
-
-
-/**
- * @defgroup PJNATH_ICE_MEDIA_TRANSPORT ICE Media Transport
- * @brief ICE Media Transport
- * @ingroup PJNATH_ICE
- * @{
- */
-
-typedef struct pj_icemt pj_icemt;
-
-typedef struct pj_icemt_cb
-{
-    void	(*on_ice_complete)(pj_icemt *icemt, 
-				   pj_status_t status);
-    void	(*on_rx_rtp)(pj_icemt *icemt,
-			     void *pkt, pj_size_t size,
-			     const pj_sockaddr_t *src_addr,
-			     unsigned src_addr_len);
-    void	(*on_rx_rtcp)(pj_icemt *icemt,
-			      void *pkt, pj_size_t size,
-			      const pj_sockaddr_t *src_addr,
-			      unsigned src_addr_len);
-
-} pj_icemt_cb;
-
-
-typedef struct pj_icemt_sock
-{
-    pj_icemt		*icemt;
-    unsigned		 comp_id;
-    pj_sock_t		 sock;
-    pj_sockaddr		 addr;
-    pj_sockaddr		 base_addr;
-    pj_ioqueue_key_t	*key;
-    pj_uint8_t		 pkt[1500];
-    pj_ioqueue_op_key_t	 read_op;
-    pj_ioqueue_op_key_t	 write_op;
-    pj_sockaddr		 src_addr;
-    int			 src_addr_len;
-} pj_icemt_sock;
-
-
-struct pj_icemt
-{
-    pj_pool_t	    *pool;
-    pj_ice	    *ice;
-    void	    *user_data;
-
-    pj_icemt_cb	     cb;
-
-    pj_icemt_sock    rtp;
-    pj_icemt_sock    rtcp;
-
-    pj_bool_t	     has_turn;
-    pj_sockaddr	     stun_srv;
-};
-
-
-PJ_DECL(pj_status_t) pj_icemt_create(pj_stun_config *stun_cfg,
-				     const char *name,
-				     pj_ice_role role,
-				     const pj_icemt_cb *cb,
-				     unsigned rtp_port,
-				     pj_bool_t has_rtcp,
-				     pj_bool_t has_turn,
-				     const pj_sockaddr *srv,
-				     pj_icemt **p_icemt);
-PJ_DECL(pj_status_t) pj_icemt_destroy(pj_icemt *icemt);
-
-
-
-/**
- * @}
- */
-
-
-PJ_END_DECL
-
-
-
-#endif	/* __PJNATH_ICE_MT_H__ */
-
diff --git a/pjnath/include/pjnath/ice_stream_transport.h b/pjnath/include/pjnath/ice_stream_transport.h
new file mode 100644
index 0000000..c85aff9
--- /dev/null
+++ b/pjnath/include/pjnath/ice_stream_transport.h
@@ -0,0 +1,185 @@
+/* $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 
+ */
+#ifndef __PJNATH_ICE_STREAM_TRANSPORT_H__
+#define __PJNATH_ICE_STREAM_TRANSPORT_H__
+
+
+/**
+ * @file ice_mt.h
+ * @brief ICE Media Transport.
+ */
+#include <pjnath/ice.h>
+#include <pjlib-util/resolver.h>
+#include <pj/ioqueue.h>
+
+
+PJ_BEGIN_DECL
+
+
+/**
+ * @defgroup PJNATH_ICE_STREAM_TRANSPORT ICE Stream Transport
+ * @brief Transport for media stream using ICE
+ * @ingroup PJNATH_ICE
+ * @{
+ */
+
+typedef struct pj_ice_st pj_ice_st;
+
+typedef struct pj_ice_st_cb
+{
+    void    (*on_rx_data)(pj_ice_st *ice_st,
+			  unsigned comp_id, unsigned cand_id,
+			  void *pkt, pj_size_t size,
+			  const pj_sockaddr_t *src_addr,
+			  unsigned src_addr_len);
+
+    void    (*on_stun_srv_resolved)(pj_ice_st *ice_st,
+				    pj_status_t status);
+    void    (*on_interface_status)(pj_ice_st *ice_st,
+				   void *notify_data,
+				   pj_status_t status,
+				   int itf_id);
+    void    (*on_ice_complete)(pj_ice_st *ice_st, 
+			       pj_status_t status);
+
+} pj_ice_st_cb;
+
+
+typedef struct pj_ice_st_comp
+{
+    unsigned		 comp_id;
+} pj_ice_st_comp;
+
+
+typedef struct pj_ice_st_interface
+{
+    pj_ice_st		*ice_st;
+    pj_ice_cand_type	 type;
+    unsigned		 comp_id;
+    int			 cand_id;
+    pj_str_t		 foundation;
+    pj_uint16_t		 local_pref;
+    pj_sock_t		 sock;
+    pj_sockaddr		 addr;
+    pj_sockaddr		 base_addr;
+    pj_ioqueue_key_t	*key;
+    pj_uint8_t		 pkt[1500];
+    pj_ioqueue_op_key_t	 read_op;
+    pj_ioqueue_op_key_t	 write_op;
+    pj_sockaddr		 src_addr;
+    int			 src_addr_len;
+} pj_ice_st_interface;
+
+
+struct pj_ice_st
+{
+    char		     obj_name[PJ_MAX_OBJ_NAME];
+    pj_pool_t		    *pool;
+    void		    *user_data;
+    pj_stun_config	     stun_cfg;
+    pj_ice_st_cb	     cb;
+
+    pj_ice		    *ice;
+
+    unsigned		     comp_cnt;
+    unsigned		     comps[PJ_ICE_MAX_COMP];
+
+    unsigned		     itf_cnt;
+    pj_ice_st_interface	    *itfs[PJ_ICE_MAX_CAND];
+
+    pj_dns_resolver	    *resolver;
+    pj_bool_t		     relay_enabled;
+    pj_str_t		     stun_domain;
+    pj_sockaddr_in	     stun_srv;
+};
+
+
+PJ_DECL(pj_status_t) pj_ice_st_create(pj_stun_config *stun_cfg,
+				      const char *name,
+				      void *user_data,
+				      const pj_ice_st_cb *cb,
+				      pj_ice_st **p_ice_st);
+PJ_DECL(pj_status_t) pj_ice_st_destroy(pj_ice_st *ice_st);
+
+PJ_DECL(pj_status_t) pj_ice_st_set_stun(pj_ice_st *ice_st,
+					pj_dns_resolver *resolver,
+					pj_bool_t enable_relay,
+					const pj_str_t *domain);
+PJ_DECL(pj_status_t) pj_ice_st_set_stun_addr(pj_ice_st *ice_st,
+					     pj_bool_t enable_relay,
+					     const pj_sockaddr_in *srv_addr);
+
+PJ_DECL(pj_status_t) pj_ice_st_add_comp(pj_ice_st *ice_st,
+					unsigned comp_id);
+
+PJ_DECL(pj_status_t) pj_ice_st_add_host_interface(pj_ice_st *ice_st,
+						  unsigned comp_id,
+						  pj_uint16_t local_pref,
+					          const pj_sockaddr_in *addr,
+				    		  unsigned *p_itf_id,
+						  pj_bool_t notify,
+						  void *notify_data);
+PJ_DECL(pj_status_t) pj_ice_st_add_all_host_interfaces(pj_ice_st *ice_st,
+						       unsigned comp_id,
+						       unsigned port,
+						       pj_bool_t notify,
+						       void *notify_data);
+PJ_DECL(pj_status_t) pj_ice_st_add_stun_interface(pj_ice_st *ice_st,
+						  unsigned comp_id,
+						  unsigned local_port,
+						  pj_bool_t notify,
+						  void *notify_data);
+PJ_DECL(pj_status_t) pj_ice_st_add_relay_interface(pj_ice_st *ice_st,
+						   unsigned comp_id,
+						   unsigned local_port,
+						   pj_bool_t notify,
+						   void *notify_data);
+
+
+PJ_DECL(pj_status_t) pj_ice_st_init_ice(pj_ice_st *ice_st,
+					pj_ice_role role,
+					const pj_str_t *local_ufrag,
+					const pj_str_t *local_passwd);
+PJ_DECL(pj_status_t) pj_ice_st_enum_cands(pj_ice_st *ice_st,
+					  unsigned *count,
+					  pj_ice_cand cand[]);
+PJ_DECL(pj_status_t) pj_ice_st_start_ice(pj_ice_st *ice_st,
+					 const pj_str_t *rem_ufrag,
+					 const pj_str_t *rem_passwd,
+					 unsigned rem_cand_cnt,
+					 const pj_ice_cand rem_cand[]);
+PJ_DECL(pj_status_t) pj_ice_st_stop_ice(pj_ice_st *ice_st);
+
+PJ_DECL(pj_status_t) pj_ice_st_send_data(pj_ice_st *ice_st,
+					 unsigned comp_id,
+					 const void *data,
+					 pj_size_t data_len);
+
+
+/**
+ * @}
+ */
+
+
+PJ_END_DECL
+
+
+
+#endif	/* __PJNATH_ICE_STREAM_TRANSPORT_H__ */
+
diff --git a/pjnath/src/pjnath-test/ice_test.c b/pjnath/src/pjnath-test/ice_test.c
index efa2311..d097a19 100644
--- a/pjnath/src/pjnath-test/ice_test.c
+++ b/pjnath/src/pjnath-test/ice_test.c
@@ -37,10 +37,10 @@
 
 static pj_stun_config stun_cfg;
 
-static void on_ice_complete(pj_icemt *icemt, 
+static void on_ice_complete(pj_ice_st *icest, 
 			    pj_status_t status)
 {
-    struct ice_data *id = (struct ice_data*) icemt->user_data;
+    struct ice_data *id = (struct ice_data*) icest->user_data;
     id->complete = PJ_TRUE;
     id->err_code = status;
     PJ_LOG(3,(THIS_FILE, "    ICE %s complete %s", id->obj_name,
@@ -48,33 +48,27 @@
 }
 
 
-static void on_rx_rtp(pj_icemt *icemt,
-		      void *pkt, pj_size_t size,
-		      const pj_sockaddr_t *src_addr,
-		      unsigned src_addr_len)
-{
-    struct ice_data *id = (struct ice_data*) icemt->user_data;
-
-    id->rx_rtp_cnt++;
-    pj_memcpy(id->last_rx_rtp_data, pkt, size);
-    id->last_rx_rtp_data[size] = '\0';
-
-    PJ_UNUSED_ARG(src_addr);
-    PJ_UNUSED_ARG(src_addr_len);
-}
-
-
-static void on_rx_rtcp(pj_icemt *icemt,
+static void on_rx_data(pj_ice_st *icest,
+		       unsigned comp_id, unsigned cand_id,
 		       void *pkt, pj_size_t size,
 		       const pj_sockaddr_t *src_addr,
 		       unsigned src_addr_len)
 {
-    struct ice_data *id = (struct ice_data*) icemt->user_data;
+    struct ice_data *id = (struct ice_data*) icest->user_data;
 
-    id->rx_rtcp_cnt++;
-    pj_memcpy(id->last_rx_rtcp_data, pkt, size);
-    id->last_rx_rtcp_data[size] = '\0';
+    if (comp_id == 1) {
+	id->rx_rtp_cnt++;
+	pj_memcpy(id->last_rx_rtp_data, pkt, size);
+	id->last_rx_rtp_data[size] = '\0';
+    } else if (comp_id == 2) {
+	id->rx_rtcp_cnt++;
+	pj_memcpy(id->last_rx_rtcp_data, pkt, size);
+	id->last_rx_rtcp_data[size] = '\0';
+    } else {
+	pj_assert(!"Invalid component ID");
+    }
 
+    PJ_UNUSED_ARG(cand_id);
     PJ_UNUSED_ARG(src_addr);
     PJ_UNUSED_ARG(src_addr_len);
 }
@@ -97,57 +91,43 @@
 /* Basic create and destroy test */
 static int ice_basic_create_destroy_test()
 {
-    pj_icemt *im;
-    pj_ice *ice;
-    pj_icemt_cb icemt_cb;
+    pj_ice_st *im;
+    pj_ice_st_cb icest_cb;
     pj_status_t status;
 
     PJ_LOG(3,(THIS_FILE, "...basic create/destroy"));
 
-    pj_bzero(&icemt_cb, sizeof(icemt_cb));
-    icemt_cb.on_ice_complete = &on_ice_complete;
-    icemt_cb.on_rx_rtp = &on_rx_rtp;
-    icemt_cb.on_rx_rtcp = &on_rx_rtcp;
+    pj_bzero(&icest_cb, sizeof(icest_cb));
+    icest_cb.on_ice_complete = &on_ice_complete;
+    icest_cb.on_rx_data = &on_rx_data;
 
-    status = pj_icemt_create(&stun_cfg, NULL, PJ_ICE_ROLE_CONTROLLING,
-			     &icemt_cb, 0, PJ_FALSE, PJ_FALSE, NULL, &im);
+    status = pj_ice_st_create(&stun_cfg, NULL, NULL, &icest_cb, &im);
     if (status != PJ_SUCCESS)
 	return -10;
 
-    ice = im->ice;
-
-    pj_icemt_destroy(im);
+    pj_ice_st_destroy(im);
 
     return 0;
 }
 
 
-static pj_status_t set_remote_list(pj_icemt *src, pj_icemt *dst)
+static pj_status_t start_ice(pj_ice_st *ist, pj_ice_st *remote)
 {
-    unsigned i, count;
-    unsigned cand_id[PJ_ICE_MAX_CAND];
+    unsigned count;
     pj_ice_cand cand[PJ_ICE_MAX_CAND];
     pj_status_t status;
 
-    count = PJ_ARRAY_SIZE(cand_id);
-    status = pj_ice_enum_cands(src->ice, &count, cand_id);
+    count = PJ_ARRAY_SIZE(cand);
+    status = pj_ice_st_enum_cands(remote, &count, cand);
     if (status != PJ_SUCCESS)
 	return status;
 
-    for (i=0; i<count; ++i) {
-	pj_ice_cand *p_cand;
-	status = pj_ice_get_cand(src->ice, cand_id[i], &p_cand);
-	if (status != PJ_SUCCESS)
-	    return status;
-
-	pj_memcpy(&cand[i], p_cand, sizeof(pj_ice_cand));
-    }
-
-    status = pj_ice_create_check_list(dst->ice, count, cand);
-    return status;
+    return pj_ice_st_start_ice(ist, &remote->ice->rx_ufrag, &remote->ice->rx_pass,
+			       count, cand);
 }
 
 
+
 /* Perform ICE test with the following parameters:
  *
  * - title:	The title of the test
@@ -162,31 +142,24 @@
  */
 static int perform_ice_test(const char *title,
 			    unsigned wait_before_send,
-			    unsigned max_total_time,
-			    unsigned ocand_cnt,
-			    const pj_ice_cand ocand[],
-			    unsigned acand_cnt,
-			    const pj_ice_cand acand[])
+			    unsigned max_total_time)
 {
-    pj_icemt *im1, *im2;
-    pj_icemt_cb icemt_cb;
+    pj_ice_st *im1, *im2;
+    pj_ice_st_cb icest_cb;
     struct ice_data *id1, *id2;
     pj_timestamp t_start, t_end;
     pj_ice_cand *rcand;
     pj_str_t data_from_offerer, data_from_answerer;
-    unsigned i;
     pj_status_t status;
 
     PJ_LOG(3,(THIS_FILE, "...%s", title));
 
-    pj_bzero(&icemt_cb, sizeof(icemt_cb));
-    icemt_cb.on_ice_complete = &on_ice_complete;
-    icemt_cb.on_rx_rtp = &on_rx_rtp;
-    icemt_cb.on_rx_rtcp = &on_rx_rtcp;
+    pj_bzero(&icest_cb, sizeof(icest_cb));
+    icest_cb.on_ice_complete = &on_ice_complete;
+    icest_cb.on_rx_data = &on_rx_data;
 
     /* Create first ICE */
-    status = pj_icemt_create(&stun_cfg, "offerer", PJ_ICE_ROLE_CONTROLLING,
-			     &icemt_cb, 0, PJ_FALSE, PJ_FALSE, NULL, &im1);
+    status = pj_ice_st_create(&stun_cfg, "offerer", NULL, &icest_cb, &im1);
     if (status != PJ_SUCCESS)
 	return -20;
 
@@ -194,19 +167,18 @@
     id1->obj_name = "offerer";
     im1->user_data = id1;
 
-    /* Add additional candidates */
-    for (i=0; i<ocand_cnt; ++i) {
-	status = pj_ice_add_cand(im1->ice, 1, ocand[i].type, 65535,
-				 &ocand[i].foundation, &ocand[i].addr,
-				 &ocand[i].base_addr, &ocand[i].srv_addr,
-				 sizeof(pj_sockaddr_in), NULL);
-	if (status != PJ_SUCCESS)
-	    return -22;
-    }
+    /* Add first component */
+    status = pj_ice_st_add_comp(im1, 1);
+    if (status != PJ_SUCCESS)
+	return -21;
+
+    /* Add host candidate */
+    status = pj_ice_st_add_host_interface(im1, 1, 65535, NULL, NULL, PJ_FALSE, NULL);
+    if (status != PJ_SUCCESS)
+	return -21;
 
     /* Create second ICE */
-    status = pj_icemt_create(&stun_cfg, "answerer", PJ_ICE_ROLE_CONTROLLED,
-			     &icemt_cb, 0, PJ_FALSE, PJ_FALSE, NULL, &im2);
+    status = pj_ice_st_create(&stun_cfg, "answerer", NULL, &icest_cb, &im2);
     if (status != PJ_SUCCESS)
 	return -25;
 
@@ -214,51 +186,39 @@
     id2->obj_name = "answerer";
     im2->user_data = id2;
 
-    /* Add additional candidates */
-    for (i=0; i<acand_cnt; ++i) {
-	status = pj_ice_add_cand(im1->ice, 1, acand[i].type, 65535,
-				 &acand[i].foundation, &acand[i].addr,
-				 &acand[i].base_addr, &acand[i].srv_addr,
-				 sizeof(pj_sockaddr_in), NULL);
-	if (status != PJ_SUCCESS)
-	    return -22;
-    }
+    /* Add first component */
+    status = pj_ice_st_add_comp(im2, 1);
+    if (status != PJ_SUCCESS)
+	return -26;
 
-    /* Set credentials */
-    {
-	pj_str_t u1 = pj_str("offerer");
-	pj_str_t p1 = pj_str("pass1");
-	pj_str_t u2 = pj_str("answerer");
-	pj_str_t p2 = pj_str("pass2");
+    /* Add host candidate */
+    status = pj_ice_st_add_host_interface(im2, 1, 65535, NULL, NULL, PJ_FALSE, NULL);
+    if (status != PJ_SUCCESS)
+	return -27;
 
-	pj_ice_set_credentials(im1->ice, &u1, &p1, &u2, &p2);
-	pj_ice_set_credentials(im2->ice, &u2, &p2, &u1, &p1);
-    }
+    /* Init ICE on im1 */
+    status = pj_ice_st_init_ice(im1, PJ_ICE_ROLE_CONTROLLING, NULL, NULL);
+    if (status != PJ_SUCCESS)
+	return -29;
 
-    /* Send offer to im2 */
-    status = set_remote_list(im1, im2);
+    /* Init ICE on im2 */
+    status = pj_ice_st_init_ice(im2, PJ_ICE_ROLE_CONTROLLED, NULL, NULL);
+    if (status != PJ_SUCCESS)
+	return -29;
+
+    /* Start ICE on im2 */
+    status = start_ice(im2, im1);
     if (status != PJ_SUCCESS)
 	return -30;
 
-    /* Send answer to im1 */
-    status = set_remote_list(im2, im1);
+    /* Start ICE on im1 */
+    status = start_ice(im1, im2);
     if (status != PJ_SUCCESS)
 	return -35;
 
     /* Mark start time */
     pj_get_timestamp(&t_start);
 
-    /* Both can start now */
-    status = pj_ice_start_check(im1->ice);
-    if (status != PJ_SUCCESS)
-	return -40;
-
-#if 1
-    status = pj_ice_start_check(im2->ice);
-    if (status != PJ_SUCCESS)
-	return -45;
-#endif
-
     /* Poll for wait_before_send msecs before we send the first data */
     for (;;) {
 	pj_timestamp t_now;
@@ -365,8 +325,8 @@
     }
 
 
-    pj_icemt_destroy(im1);
-    pj_icemt_destroy(im2);
+    pj_ice_st_destroy(im1);
+    pj_ice_st_destroy(im2);
     return 0;
 }
 
@@ -377,9 +337,6 @@
     pj_pool_t *pool;
     pj_ioqueue_t *ioqueue;
     pj_timer_heap_t *timer_heap;
-    pj_ice_cand ocand[PJ_ICE_MAX_CAND];
-    pj_ice_cand acand[PJ_ICE_MAX_CAND];
-    pj_str_t s;
 
     pool = pj_pool_create(mem, NULL, 4000, 4000, NULL);
     pj_ioqueue_create(pool, 12, &ioqueue);
@@ -395,24 +352,17 @@
 	goto on_return;
 
     /* Direct communication */
-    rc = perform_ice_test("Direct connection", 500, 1000, 0, NULL, 0, NULL);
+    rc = perform_ice_test("Direct connection", 500, 1000);
     if (rc != 0)
 	goto on_return;
 
     /* Direct communication with invalid address */
-    pj_bzero(ocand, sizeof(ocand));
-    pj_sockaddr_in_init(&ocand[0].addr.ipv4, pj_cstr(&s, "127.0.0.127"), 1234);
-    pj_sockaddr_in_init(&ocand[0].base_addr.ipv4, pj_cstr(&s, "127.0.0.128"), 1234);
-    ocand[0].comp_id = 1;
-    ocand[0].foundation = pj_str("H2");
-    ocand[0].type = PJ_ICE_CAND_TYPE_HOST;
-
-    rc = perform_ice_test("Direct connection with 1 invalid address", 500, 1000, 1, ocand, 0, NULL);
+    rc = perform_ice_test("Direct connection with 1 invalid address", 500, 1000);
     if (rc != 0)
 	goto on_return;
 
     /* Direct communication with two components */
-    rc = perform_ice_test("Direct connection with two components", 500, 1000, 0, NULL, 0, NULL);
+    rc = perform_ice_test("Direct connection with two components", 500, 1000);
     if (rc != 0)
 	goto on_return;
 
diff --git a/pjnath/src/pjnath/ice.c b/pjnath/src/pjnath/ice.c
index de78170..9fb5923 100644
--- a/pjnath/src/pjnath/ice.c
+++ b/pjnath/src/pjnath/ice.c
@@ -21,6 +21,7 @@
 #include <pj/addr_resolv.h>
 #include <pj/array.h>
 #include <pj/assert.h>
+#include <pj/guid.h>
 #include <pj/log.h>
 #include <pj/os.h>
 #include <pj/pool.h>
@@ -68,8 +69,8 @@
 typedef struct stun_data
 {
     pj_ice	*ice;
-    unsigned	 comp_id;
-    pj_ice_comp	*comp;
+    unsigned	 lcand_id;
+    pj_ice_cand	*lcand;
 } stun_data;
 
 typedef struct timer_data
@@ -132,20 +133,27 @@
 					const pj_str_t *nonce);
 
 
+/*
+ * Create ICE stream session.
+ */
 PJ_DEF(pj_status_t) pj_ice_create(pj_stun_config *stun_cfg,
 				  const char *name,
 				  pj_ice_role role,
 				  const pj_ice_cb *cb,
+				  const pj_str_t *local_ufrag,
+				  const pj_str_t *local_passwd,
 				  pj_ice **p_ice)
 {
     pj_pool_t *pool;
     pj_ice *ice;
+    char tmp[32];
+    pj_str_t s;
     unsigned i;
     pj_status_t status;
 
     PJ_ASSERT_RETURN(stun_cfg && cb && p_ice, PJ_EINVAL);
 
-    if (!name)
+    if (name == NULL)
 	name = "ice%p";
 
     pool = pj_pool_create(stun_cfg->pf, name, 4000, 4000, NULL);
@@ -170,15 +178,34 @@
 	ice->comp[i].nominated_check_id = -1;
     }
 
+    if (local_ufrag == NULL) {
+	pj_ansi_snprintf(tmp, sizeof(tmp), "%x", pj_rand());
+	s = pj_str(tmp);
+	local_ufrag = &s;
+    }
+    pj_strdup(ice->pool, &ice->rx_ufrag, local_ufrag);
+
+    if (local_passwd == NULL) {
+	pj_ansi_snprintf(tmp, sizeof(tmp), "%x", pj_rand());
+	s = pj_str(tmp);
+	local_passwd = &s;
+    }
+    pj_strdup(ice->pool, &ice->rx_pass, local_passwd);
+
+
     /* Done */
     *p_ice = ice;
 
-    LOG((ice->obj_name, "ICE media stream created"));
+    LOG((ice->obj_name, "ICE stream session created, role is %s agent",
+	(ice->role==PJ_ICE_ROLE_CONTROLLING ? "controlling" : "controlled")));
 
     return PJ_SUCCESS;
 }
 
 
+/*
+ * Destroy
+ */
 static void destroy_ice(pj_ice *ice,
 			pj_status_t reason)
 {
@@ -189,11 +216,13 @@
     }
 
     for (i=0; i<ice->comp_cnt; ++i) {
-	pj_ice_comp *comp = &ice->comp[i];
+	/* Nothing to do */
+    }
 
-	if (comp->stun_sess) {
-	    pj_stun_session_destroy(comp->stun_sess);
-	    comp->stun_sess = NULL;
+    for (i=0; i<ice->lcand_cnt; ++i) {
+	if (ice->lcand[i].stun_sess) {
+	    pj_stun_session_destroy(ice->lcand[i].stun_sess);
+	    ice->lcand[i].stun_sess = NULL;
 	}
     }
 
@@ -222,31 +251,27 @@
 }
 
 
+/* Find component by ID */
 static pj_ice_comp *find_comp(const pj_ice *ice, unsigned comp_id)
 {
     unsigned i;
     for (i=0; i<ice->comp_cnt; ++i) {
 	if (ice->comp[i].comp_id == comp_id)
-	    return (pj_ice_comp*) &ice->comp[i];
+	    return (pj_ice_comp *) &ice->comp[i];
     }
 
     return NULL;
 }
 
 
-PJ_DEF(pj_status_t) pj_ice_add_comp(pj_ice *ice,
-				    unsigned comp_id,
-				    const pj_sockaddr_t *local_addr,
-				    unsigned addr_len)
+/* Add a new component */
+PJ_DEF(pj_status_t) pj_ice_add_comp(pj_ice *ice, unsigned comp_id)
 {
-    pj_stun_session_cb sess_cb;
     pj_ice_comp *comp;
-    pj_stun_auth_cred auth_cred;
-    stun_data *sd;
-    pj_status_t status;
 
-    PJ_ASSERT_RETURN(ice && local_addr && addr_len, PJ_EINVAL);
+    PJ_ASSERT_RETURN(ice && comp_id, PJ_EINVAL);
     PJ_ASSERT_RETURN(ice->comp_cnt < PJ_ARRAY_SIZE(ice->comp), PJ_ETOOMANY);
+    PJ_ASSERT_RETURN(comp_id==ice->comp_cnt+1, PJ_EICEINCOMPID);
     PJ_ASSERT_RETURN(find_comp(ice, comp_id) == NULL, PJ_EEXISTS);
 
     pj_mutex_lock(ice->mutex);
@@ -254,40 +279,6 @@
     comp = &ice->comp[ice->comp_cnt];
     comp->comp_id = comp_id;
     comp->nominated_check_id = -1;
-    pj_memcpy(&comp->local_addr, local_addr, addr_len);
-
-    /* Init STUN callbacks */
-    pj_bzero(&sess_cb, sizeof(sess_cb));
-    sess_cb.on_request_complete = &on_stun_request_complete;
-    sess_cb.on_rx_indication = &on_stun_rx_indication;
-    sess_cb.on_rx_request = &on_stun_rx_request;
-    sess_cb.on_send_msg = &on_stun_send_msg;
-
-    /* Create STUN session for this component */
-    status = pj_stun_session_create(&ice->stun_cfg, ice->obj_name, 
-			            &sess_cb, PJ_FALSE,
-				    &comp->stun_sess);
-    if (status != PJ_SUCCESS) {
-	pj_mutex_unlock(ice->mutex);
-	return status;
-    }
-
-    /* Associate data with this STUN session */
-    sd = PJ_POOL_ZALLOC_T(ice->pool, struct stun_data);
-    sd->ice = ice;
-    sd->comp_id = comp_id;
-    sd->comp = comp;
-    pj_stun_session_set_user_data(comp->stun_sess, sd);
-
-    /* Init STUN authentication credential */
-    pj_bzero(&auth_cred, sizeof(auth_cred));
-    auth_cred.type = PJ_STUN_AUTH_CRED_DYNAMIC;
-    auth_cred.data.dyn_cred.get_auth = &stun_auth_get_auth;
-    auth_cred.data.dyn_cred.get_cred = &stun_auth_get_cred;
-    auth_cred.data.dyn_cred.get_password = &stun_auth_get_password;
-    auth_cred.data.dyn_cred.verify_nonce = &stun_auth_verify_nonce;
-    auth_cred.data.dyn_cred.user_data = comp->stun_sess;
-    pj_stun_session_set_credential(comp->stun_sess, &auth_cred);
 
     /* Done */
     ice->comp_cnt++;
@@ -408,42 +399,6 @@
 }
 
 
-PJ_DEF(pj_status_t) pj_ice_set_credentials(pj_ice *ice,
-					   const pj_str_t *local_ufrag,
-					   const pj_str_t *local_pass,
-					   const pj_str_t *remote_ufrag,
-					   const pj_str_t *remote_pass)
-{
-    char buf[128];
-    pj_str_t username;
-
-    username.ptr = buf;
-
-    PJ_ASSERT_RETURN(ice && local_ufrag && local_pass &&
-		     remote_ufrag && remote_pass, PJ_EINVAL);
-    PJ_ASSERT_RETURN(local_ufrag->slen + remote_ufrag->slen <
-		     sizeof(buf), PJ_ENAMETOOLONG);
-
-    pj_strcpy(&username, remote_ufrag);
-    pj_strcat2(&username, ":");
-    pj_strcat(&username, local_ufrag);
-
-    pj_strdup(ice->pool, &ice->tx_uname, &username);
-    pj_strdup(ice->pool, &ice->tx_ufrag, remote_ufrag);
-    pj_strdup(ice->pool, &ice->tx_pass, remote_pass);
-
-    pj_strcpy(&username, local_ufrag);
-    pj_strcat2(&username, ":");
-    pj_strcat(&username, remote_ufrag);
-
-    pj_strdup(ice->pool, &ice->rx_uname, &username);
-    pj_strdup(ice->pool, &ice->rx_ufrag, local_ufrag);
-    pj_strdup(ice->pool, &ice->rx_pass, local_pass);
-
-    return PJ_SUCCESS;
-}
-
-
 static pj_uint32_t CALC_CAND_PRIO(pj_ice_cand_type type,
 				  pj_uint32_t local_pref,
 				  pj_uint32_t comp_id)
@@ -462,6 +417,9 @@
 }
 
 
+/*
+ * Add ICE candidate
+ */
 PJ_DEF(pj_status_t) pj_ice_add_cand(pj_ice *ice,
 				    unsigned comp_id,
 				    pj_ice_cand_type type,
@@ -474,6 +432,9 @@
 				    unsigned *p_cand_id)
 {
     pj_ice_cand *lcand;
+    pj_stun_session_cb sess_cb;
+    pj_stun_auth_cred auth_cred;
+    stun_data *sd;
     pj_status_t status = PJ_SUCCESS;
     char tmp[128];
 
@@ -500,8 +461,39 @@
     else
 	pj_bzero(&lcand->srv_addr, sizeof(lcand->srv_addr));
 
-    if (p_cand_id)
-	*p_cand_id = ice->lcand_cnt;
+    /* Init STUN callbacks */
+    pj_bzero(&sess_cb, sizeof(sess_cb));
+    sess_cb.on_request_complete = &on_stun_request_complete;
+    sess_cb.on_rx_indication = &on_stun_rx_indication;
+    sess_cb.on_rx_request = &on_stun_rx_request;
+    sess_cb.on_send_msg = &on_stun_send_msg;
+
+    /* Create STUN session for this candidate */
+    status = pj_stun_session_create(&ice->stun_cfg, ice->obj_name, 
+			            &sess_cb, PJ_FALSE,
+				    &lcand->stun_sess);
+    if (status != PJ_SUCCESS) {
+	pj_mutex_unlock(ice->mutex);
+	return status;
+    }
+
+    /* Associate data with this STUN session */
+    sd = PJ_POOL_ZALLOC_T(ice->pool, struct stun_data);
+    sd->ice = ice;
+    sd->lcand_id = GET_LCAND_ID(lcand);
+    sd->lcand = lcand;
+    pj_stun_session_set_user_data(lcand->stun_sess, sd);
+
+    /* Init STUN authentication credential */
+    pj_bzero(&auth_cred, sizeof(auth_cred));
+    auth_cred.type = PJ_STUN_AUTH_CRED_DYNAMIC;
+    auth_cred.data.dyn_cred.get_auth = &stun_auth_get_auth;
+    auth_cred.data.dyn_cred.get_cred = &stun_auth_get_cred;
+    auth_cred.data.dyn_cred.get_password = &stun_auth_get_password;
+    auth_cred.data.dyn_cred.verify_nonce = &stun_auth_verify_nonce;
+    auth_cred.data.dyn_cred.user_data = lcand->stun_sess;
+    pj_stun_session_set_credential(lcand->stun_sess, &auth_cred);
+
 
     pj_ansi_strcpy(tmp, pj_inet_ntoa(lcand->addr.ipv4.sin_addr));
     LOG((ice->obj_name, 
@@ -518,6 +510,9 @@
 	 (int)pj_htons(lcand->base_addr.ipv4.sin_port),
 	 lcand->prio, lcand->prio));
 
+    if (p_cand_id)
+	*p_cand_id = ice->lcand_cnt;
+
     ++ice->lcand_cnt;
 
 on_error:
@@ -1041,19 +1036,42 @@
 
 
 PJ_DEF(pj_status_t) pj_ice_create_check_list(pj_ice *ice,
+					     const pj_str_t *rem_ufrag,
+					     const pj_str_t *rem_passwd,
 					     unsigned rcand_cnt,
 					     const pj_ice_cand rcand[])
 {
     pj_ice_checklist *clist;
+    char buf[128];
+    pj_str_t username;
     timer_data *td;
     unsigned i, j;
 
-    PJ_ASSERT_RETURN(ice && rcand_cnt && rcand, PJ_EINVAL);
+    PJ_ASSERT_RETURN(ice && rem_ufrag && rem_passwd && rcand_cnt && rcand,
+		     PJ_EINVAL);
     PJ_ASSERT_RETURN(rcand_cnt + ice->rcand_cnt <= PJ_ICE_MAX_CAND, 
 		     PJ_ETOOMANY);
 
     pj_mutex_lock(ice->mutex);
 
+    /* Save credentials */
+    username.ptr = buf;
+
+    pj_strcpy(&username, rem_ufrag);
+    pj_strcat2(&username, ":");
+    pj_strcat(&username, &ice->rx_ufrag);
+
+    pj_strdup(ice->pool, &ice->tx_uname, &username);
+    pj_strdup(ice->pool, &ice->tx_ufrag, rem_ufrag);
+    pj_strdup(ice->pool, &ice->tx_pass, rem_passwd);
+
+    pj_strcpy(&username, &ice->rx_ufrag);
+    pj_strcat2(&username, ":");
+    pj_strcat(&username, rem_ufrag);
+
+    pj_strdup(ice->pool, &ice->rx_uname, &username);
+
+
     /* Save remote candidates */
     ice->rcand_cnt = 0;
     for (i=0; i<rcand_cnt; ++i) {
@@ -1154,7 +1172,7 @@
 	 dump_check(buffer, sizeof(buffer), ice, check)));
 
     /* Create request */
-    status = pj_stun_session_create_req(comp->stun_sess, 
+    status = pj_stun_session_create_req(lcand->stun_sess, 
 					PJ_STUN_BINDING_REQUEST, &tdata);
     if (status != PJ_SUCCESS)
 	return status;
@@ -1186,7 +1204,7 @@
      */
 
     /* Initiate STUN transaction to send the request */
-    status = pj_stun_session_send_msg(comp->stun_sess, PJ_FALSE, 
+    status = pj_stun_session_send_msg(lcand->stun_sess, PJ_FALSE, 
 				      &rcand->addr, 
 				      sizeof(pj_sockaddr_in), tdata);
     if (status != PJ_SUCCESS)
@@ -1331,7 +1349,7 @@
 				    unsigned addr_len)
 {
     stun_data *sd = (stun_data*) pj_stun_session_get_user_data(sess);
-    return (*sd->ice->cb.on_tx_pkt)(sd->ice, sd->comp_id, 
+    return (*sd->ice->cb.on_tx_pkt)(sd->ice, sd->lcand->comp_id, sd->lcand_id,
 				    pkt, pkt_size, 
 				    dst_addr, addr_len);
 }
@@ -1534,7 +1552,8 @@
 
     sd = (stun_data*) pj_stun_session_get_user_data(sess);
     ice = sd->ice;
-    comp = sd->comp;
+    lcand = sd->lcand;
+    comp = find_comp(ice, lcand->comp_id);
 
     pj_mutex_lock(ice->mutex);
 
@@ -1621,26 +1640,6 @@
     PJ_TODO(DETERMINE_IF_REQUEST_COMES_FROM_RELAYED_CANDIDATE);
     is_relayed = PJ_FALSE;
 
-    /* Next find local candidate, by first finding a check in the checklist
-     * which base address is equal to the local address.
-     */
-    for (i=0; i<ice->clist.count; ++i) {
-	pj_ice_check *c = &ice->clist.checks[i];
-	if (sockaddr_cmp(&c->lcand->base_addr, &comp->local_addr)==0)
-	    break;
-    }
-
-    /* MUST find a local candidate! */
-    pj_assert(i != ice->clist.count);
-    if (i == ice->clist.count) {
-	pj_mutex_unlock(ice->mutex);
-	LOG((ice->obj_name, "Error: unable to find local candidate for "
-	     "incoming request"));
-	return PJ_SUCCESS;
-    }
-
-    lcand = ice->clist.checks[i].lcand;
-
     /* Now that we have local and remote candidate, check if we already
      * have this pair in our checklist.
      */
@@ -1758,6 +1757,7 @@
 {
     pj_status_t status = PJ_SUCCESS;
     pj_ice_comp *comp;
+    unsigned cand_id;
     pj_ice_check *check;
 
     PJ_ASSERT_RETURN(ice, PJ_EINVAL);
@@ -1776,8 +1776,9 @@
     }
 
     check = &ice->clist.checks[comp->nominated_check_id];
+    cand_id = GET_LCAND_ID(check->lcand);
 
-    status = (*ice->cb.on_tx_pkt)(ice, comp_id, data, data_len, 
+    status = (*ice->cb.on_tx_pkt)(ice, comp_id, cand_id, data, data_len, 
 				  &check->rcand->addr, 
 				  sizeof(pj_sockaddr_in));
 
@@ -1789,6 +1790,7 @@
 
 PJ_DEF(pj_status_t) pj_ice_on_rx_pkt( pj_ice *ice,
 				      unsigned comp_id,
+				      unsigned cand_id,
 				      void *pkt,
 				      pj_size_t pkt_size,
 				      const pj_sockaddr_t *src_addr,
@@ -1796,6 +1798,7 @@
 {
     pj_status_t status = PJ_SUCCESS;
     pj_ice_comp *comp;
+    pj_ice_cand *lcand;
     pj_status_t stun_status;
 
     PJ_ASSERT_RETURN(ice, PJ_EINVAL);
@@ -1808,14 +1811,16 @@
 	goto on_return;
     }
 
+    lcand = &ice->lcand[cand_id];
+
     stun_status = pj_stun_msg_check(pkt, pkt_size, PJ_STUN_IS_DATAGRAM);
     if (stun_status == PJ_SUCCESS) {
-	status = pj_stun_session_on_rx_pkt(comp->stun_sess, pkt, pkt_size,
+	status = pj_stun_session_on_rx_pkt(lcand->stun_sess, pkt, pkt_size,
 					   PJ_STUN_IS_DATAGRAM,
 					   NULL, src_addr, src_addr_len);
     } else {
-	status = (*ice->cb.on_rx_data)(ice, comp_id, pkt, pkt_size, 
-				       src_addr, src_addr_len);
+	(*ice->cb.on_rx_data)(ice, comp_id, cand_id, pkt, pkt_size, 
+			      src_addr, src_addr_len);
     }
     
 
diff --git a/pjnath/src/pjnath/ice_mt.c b/pjnath/src/pjnath/ice_mt.c
deleted file mode 100644
index 187a1ba..0000000
--- a/pjnath/src/pjnath/ice_mt.c
+++ /dev/null
@@ -1,281 +0,0 @@
-/* $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 
- */
-#include <pjnath/ice_mt.h>
-#include <pjnath/errno.h>
-#include <pj/addr_resolv.h>
-#include <pj/assert.h>
-#include <pj/pool.h>
-#include <pj/string.h>
-
-
-#define RTP_COMP_ID	1
-#define RTCP_COMP_ID	2
-
-
-
-/* ICE callbacks */
-static void	   on_ice_complete(pj_ice *ice, pj_status_t status);
-static pj_status_t on_tx_pkt(pj_ice *ice, unsigned comp_id,
-			     const void *pkt, pj_size_t size,
-			     const pj_sockaddr_t *dst_addr,
-			     unsigned dst_addr_len);
-static pj_status_t on_rx_data(pj_ice *ice, unsigned comp_id,
-			      void *pkt, pj_size_t size,
-			      const pj_sockaddr_t *src_addr,
-			      unsigned src_addr_len);
-
-/* Ioqueue callback */
-static void on_read_complete(pj_ioqueue_key_t *key, 
-                             pj_ioqueue_op_key_t *op_key, 
-                             pj_ssize_t bytes_read);
-
-static void destroy_ice_sock(pj_icemt_sock *is);
-
-static pj_status_t create_ice_sock(pj_icemt *icemt,
-				   pj_ioqueue_t *ioqueue,
-				   unsigned comp_id,
-				   unsigned port,
-				   pj_icemt_sock *is)
-{
-    pj_ioqueue_callback ioqueue_cb;
-    const pj_str_t H1 = { "H1", 2 };
-    int addr_len;
-    pj_status_t status;
-
-    pj_bzero(is, sizeof(*is));
-    is->sock = PJ_INVALID_SOCKET;
-    is->comp_id = comp_id;
-    is->icemt = icemt;
-
-    status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &is->sock);
-    if (status != PJ_SUCCESS)
-	return status;
-
-    /* Bind and get the local IP address */
-    pj_sockaddr_in_init(&is->base_addr.ipv4, NULL, (pj_uint16_t)port);
-    status = pj_sock_bind(is->sock, &is->base_addr, sizeof(pj_sockaddr_in));
-    if (status != PJ_SUCCESS)
-	goto on_error;
-
-    addr_len = sizeof(is->base_addr);
-    status = pj_sock_getsockname(is->sock, &is->base_addr, &addr_len);
-    if (status != PJ_SUCCESS)
-	goto on_error;
-
-    if (is->base_addr.ipv4.sin_addr.s_addr == 0) {
-	status = pj_gethostip(&is->base_addr.ipv4.sin_addr);
-	if (status != PJ_SUCCESS) 
-	    goto on_error;
-    }
-
-    /* Register to ioqueue */
-    pj_bzero(&ioqueue_cb, sizeof(ioqueue_cb));
-    ioqueue_cb.on_read_complete = &on_read_complete;
-    status = pj_ioqueue_register_sock(icemt->pool, ioqueue, is->sock, is,
-				      &ioqueue_cb, &is->key);
-    if (status != PJ_SUCCESS)
-	goto on_error;
-
-    pj_ioqueue_op_key_init(&is->read_op, sizeof(is->read_op));
-    pj_ioqueue_op_key_init(&is->write_op, sizeof(is->write_op));
-
-    on_read_complete(is->key, &is->read_op, 0);
-
-    /* Add new ICE component */
-    status = pj_ice_add_comp(icemt->ice, comp_id, &is->base_addr,
-			     sizeof(pj_sockaddr_in));
-    if (status != PJ_SUCCESS)
-	goto on_error;
-
-    /* Add host candidate */
-    status = pj_ice_add_cand(icemt->ice, comp_id, PJ_ICE_CAND_TYPE_HOST,
-			     65535, &H1, &is->base_addr, &is->base_addr,
-			     NULL, sizeof(pj_sockaddr_in), NULL);
-    if (status != PJ_SUCCESS)
-	goto on_error;
-    
-    return PJ_SUCCESS;
-
-on_error:
-    destroy_ice_sock(is);
-    return status;
-}
-
-
-static void on_read_complete(pj_ioqueue_key_t *key, 
-                             pj_ioqueue_op_key_t *op_key, 
-                             pj_ssize_t bytes_read)
-{
-    pj_icemt_sock *is = (pj_icemt_sock*) pj_ioqueue_get_user_data(key);
-    pj_ssize_t pkt_size;
-    pj_status_t status;
-
-    if (bytes_read > 0) {
-	status = pj_ice_on_rx_pkt(is->icemt->ice, is->comp_id, 
-				  is->pkt, bytes_read,
-				  &is->src_addr, is->src_addr_len);
-    }
-
-    pkt_size = sizeof(is->pkt);
-    is->src_addr_len = sizeof(is->src_addr);
-    status = pj_ioqueue_recvfrom(key, op_key, is->pkt, &pkt_size, 
-				 PJ_IOQUEUE_ALWAYS_ASYNC,
-				 &is->src_addr, &is->src_addr_len);
-    pj_assert(status == PJ_SUCCESS || status == PJ_EPENDING);
-}
-
-
-static void destroy_ice_sock(pj_icemt_sock *is)
-{
-    if (is->key) {
-	pj_ioqueue_unregister(is->key);
-	is->key = NULL;
-	is->sock = PJ_INVALID_SOCKET;
-    } else if (is->sock != PJ_INVALID_SOCKET && is->sock != 0) {
-	pj_sock_close(is->sock);
-	is->sock = PJ_INVALID_SOCKET;
-    }
-}
-
-
-PJ_DEF(pj_status_t) pj_icemt_create( pj_stun_config *stun_cfg,
-				     const char *name,
-				     pj_ice_role role,
-				     const pj_icemt_cb *cb,
-				     unsigned rtp_port,
-				     pj_bool_t has_rtcp,
-				     pj_bool_t has_turn,
-				     const pj_sockaddr *srv,
-				     pj_icemt **p_icemt)
-{
-    pj_pool_t *pool;
-    pj_icemt *icemt;
-    pj_ice_cb ice_cb;
-    pj_status_t status;
-
-    pool = pj_pool_create(stun_cfg->pf, name, 512, 512, NULL);
-    icemt = PJ_POOL_ZALLOC_T(pool, struct pj_icemt);
-    icemt->pool = pool;
-
-
-    pj_bzero(&ice_cb, sizeof(ice_cb));
-    ice_cb.on_ice_complete = &on_ice_complete;
-    ice_cb.on_tx_pkt = &on_tx_pkt;
-    ice_cb.on_rx_data = &on_rx_data;
-
-    pj_memcpy(&icemt->cb, cb, sizeof(*cb));
-
-    status = pj_ice_create(stun_cfg, name, role, &ice_cb, &icemt->ice);
-    if (status != PJ_SUCCESS)
-	goto on_error;
-
-    icemt->ice->user_data = (void*)icemt;
-
-    icemt->has_turn = has_turn;
-    if (srv)
-	pj_memcpy(&icemt->stun_srv, srv, sizeof(pj_sockaddr));
-
-    status = create_ice_sock(icemt, stun_cfg->ioqueue, RTP_COMP_ID,
-			     rtp_port, &icemt->rtp);
-    if (status != PJ_SUCCESS)
-	goto on_error;
-
-    if (has_rtcp) {
-	if (rtp_port) ++rtp_port;
-
-	status = create_ice_sock(icemt, stun_cfg->ioqueue, RTCP_COMP_ID,
-				 rtp_port, &icemt->rtcp);
-	if (status != PJ_SUCCESS)
-	    goto on_error;
-    }
-
-    *p_icemt = icemt;
-    return PJ_SUCCESS;
-
-on_error:
-    if (icemt->ice)
-	pj_ice_destroy(icemt->ice);
-    pj_pool_release(pool);
-    return status;
-}
-
-
-PJ_DEF(pj_status_t) pj_icemt_destroy(pj_icemt *icemt)
-{
-    destroy_ice_sock(&icemt->rtp);
-    destroy_ice_sock(&icemt->rtcp);
-
-    pj_ice_destroy(icemt->ice);
-    pj_pool_release(icemt->pool);
-
-    return PJ_SUCCESS;
-}
-
-
-static void on_ice_complete(pj_ice *ice, pj_status_t status)
-{
-    pj_icemt *icemt = (pj_icemt*)ice->user_data;
-    (*icemt->cb.on_ice_complete)(icemt, status);
-}
-
-
-static pj_status_t on_tx_pkt(pj_ice *ice, unsigned comp_id,
-			     const void *pkt, pj_size_t size,
-			     const pj_sockaddr_t *dst_addr,
-			     unsigned dst_addr_len)
-{
-    pj_icemt *icemt = (pj_icemt*)ice->user_data;
-    pj_icemt_sock *is;
-    pj_ssize_t pkt_size;
-    pj_status_t status;
-
-    if (comp_id == RTP_COMP_ID)
-	is = &icemt->rtp;
-    else if (comp_id == RTCP_COMP_ID)
-	is = &icemt->rtcp;
-    else {
-	pj_assert(!"Invalid comp_id");
-	return -1;
-    }
-
-    pkt_size = size;
-    status = pj_ioqueue_sendto(is->key, &is->write_op, 
-			       pkt, &pkt_size, 0,
-			       dst_addr, dst_addr_len);
-    
-    return (status==PJ_SUCCESS||status==PJ_EPENDING) ? PJ_SUCCESS : status;
-}
-
-
-static pj_status_t on_rx_data(pj_ice *ice, unsigned comp_id,
-			      void *pkt, pj_size_t size,
-			      const pj_sockaddr_t *src_addr,
-			      unsigned src_addr_len)
-{
-    pj_icemt *icemt = (pj_icemt*)ice->user_data;
-
-    if (comp_id == RTP_COMP_ID && icemt->cb.on_rx_rtp) {
-	(*icemt->cb.on_rx_rtp)(icemt, pkt, size, src_addr, src_addr_len);
-    } else if (comp_id == RTCP_COMP_ID && icemt->cb.on_rx_rtcp) {
-	(*icemt->cb.on_rx_rtcp)(icemt, pkt, size, src_addr, src_addr_len);
-    }
-    return PJ_SUCCESS;
-}
-
-
diff --git a/pjnath/src/pjnath/ice_stream_transport.c b/pjnath/src/pjnath/ice_stream_transport.c
new file mode 100644
index 0000000..475aa35
--- /dev/null
+++ b/pjnath/src/pjnath/ice_stream_transport.c
@@ -0,0 +1,688 @@
+/* $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 
+ */
+#include <pjnath/ice_stream_transport.h>
+#include <pjnath/errno.h>
+#include <pj/addr_resolv.h>
+#include <pj/assert.h>
+#include <pj/log.h>
+#include <pj/pool.h>
+#include <pj/string.h>
+
+
+
+/* ICE callbacks */
+static void	   on_ice_complete(pj_ice *ice, pj_status_t status);
+static pj_status_t on_tx_pkt(pj_ice *ice, 
+			     unsigned comp_id, unsigned cand_id,
+			     const void *pkt, pj_size_t size,
+			     const pj_sockaddr_t *dst_addr,
+			     unsigned dst_addr_len);
+static void	   on_rx_data(pj_ice *ice, 
+			      unsigned comp_id, unsigned cand_id,
+			      void *pkt, pj_size_t size,
+			      const pj_sockaddr_t *src_addr,
+			      unsigned src_addr_len);
+
+/* Ioqueue callback */
+static void on_read_complete(pj_ioqueue_key_t *key, 
+                             pj_ioqueue_op_key_t *op_key, 
+                             pj_ssize_t bytes_read);
+
+static void destroy_ice_interface(pj_ice_st_interface *is);
+static void destroy_ice_st(pj_ice_st *ice_st, pj_status_t reason);
+
+static void ice_st_perror(pj_ice_st *ice_st, const char *title, 
+			  pj_status_t status)
+{
+    char errmsg[PJ_ERR_MSG_SIZE];
+
+    pj_strerror(status, errmsg, sizeof(errmsg));
+    PJ_LOG(1,(ice_st->obj_name, "%s: %s", title, errmsg));
+}
+
+
+/* Get the prefix for the foundation */
+static int get_type_prefix(pj_ice_cand_type type)
+{
+    switch (type) {
+    case PJ_ICE_CAND_TYPE_HOST:	    return 'H';
+    case PJ_ICE_CAND_TYPE_SRFLX:    return 'S';
+    case PJ_ICE_CAND_TYPE_PRFLX:    return 'P';
+    case PJ_ICE_CAND_TYPE_RELAYED:  return 'R';
+    default:
+	pj_assert(!"Invalid type");
+	return 'U';
+    }
+}
+
+
+/* 
+ * Create new interface (i.e. socket) 
+ */
+static pj_status_t create_ice_interface(pj_ice_st *ice_st,
+					pj_ice_cand_type type,
+					unsigned comp_id,
+					pj_uint16_t local_pref,
+					const pj_sockaddr_in *addr,
+					pj_ice_st_interface **p_is)
+{
+    pj_ioqueue_callback ioqueue_cb;
+    pj_ice_st_interface *is;
+    char foundation[32];
+    int addr_len;
+    pj_status_t status;
+
+    is = PJ_POOL_ZALLOC_T(ice_st->pool, pj_ice_st_interface);
+    is->type = type;
+    is->comp_id = comp_id;
+    is->cand_id = -1;
+    is->sock = PJ_INVALID_SOCKET;
+    is->ice_st = ice_st;
+    is->local_pref = local_pref;
+
+    status = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, &is->sock);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Bind and get the local IP address */
+    if (addr) 
+	pj_memcpy(&is->base_addr, addr, sizeof(pj_sockaddr_in));
+    else 
+	pj_sockaddr_in_init(&is->base_addr.ipv4, NULL, 0);
+
+    status = pj_sock_bind(is->sock, &is->base_addr, sizeof(pj_sockaddr_in));
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    addr_len = sizeof(is->base_addr);
+    status = pj_sock_getsockname(is->sock, &is->base_addr, &addr_len);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    if (is->base_addr.ipv4.sin_addr.s_addr == 0) {
+	status = pj_gethostip(&is->base_addr.ipv4.sin_addr);
+	if (status != PJ_SUCCESS) 
+	    goto on_error;
+    }
+
+    /* Assign foundation */
+    pj_ansi_snprintf(foundation, sizeof(foundation), "%c%x", 
+		     get_type_prefix(type),
+		     (int)pj_ntohl(is->base_addr.ipv4.sin_addr.s_addr));
+    pj_strdup2(ice_st->pool, &is->foundation, foundation);
+
+
+    /* Register to ioqueue */
+    pj_bzero(&ioqueue_cb, sizeof(ioqueue_cb));
+    ioqueue_cb.on_read_complete = &on_read_complete;
+    status = pj_ioqueue_register_sock(ice_st->pool, ice_st->stun_cfg.ioqueue, 
+				      is->sock, is, &ioqueue_cb, &is->key);
+    if (status != PJ_SUCCESS)
+	goto on_error;
+
+    pj_ioqueue_op_key_init(&is->read_op, sizeof(is->read_op));
+    pj_ioqueue_op_key_init(&is->write_op, sizeof(is->write_op));
+
+    /* Kick start reading the socket */
+    on_read_complete(is->key, &is->read_op, 0);
+
+    /* Done */
+    *p_is = is;
+    return PJ_SUCCESS;
+
+on_error:
+    destroy_ice_interface(is);
+    return status;
+}
+
+
+/* 
+ * This is callback called by ioqueue on incoming packet 
+ */
+static void on_read_complete(pj_ioqueue_key_t *key, 
+                             pj_ioqueue_op_key_t *op_key, 
+                             pj_ssize_t bytes_read)
+{
+    pj_ice_st_interface *is = (pj_ice_st_interface*) 
+			      pj_ioqueue_get_user_data(key);
+    pj_ssize_t pkt_size;
+    pj_status_t status;
+
+    if (bytes_read > 0) {
+
+	/* If we have an active ICE session, hand over all incoming
+	 * packets to the ICE session. Otherwise just drop the packet.
+	 */
+	if (is->ice_st->ice) {
+	    status = pj_ice_on_rx_pkt(is->ice_st->ice, 
+				      is->comp_id, is->cand_id,
+				      is->pkt, bytes_read,
+				      &is->src_addr, is->src_addr_len);
+	}
+
+    } else if (bytes_read < 0) {
+	ice_st_perror(is->ice_st, "ioqueue read callback error", -bytes_read);
+    }
+
+    /* Read next packet */
+    pkt_size = sizeof(is->pkt);
+    is->src_addr_len = sizeof(is->src_addr);
+    status = pj_ioqueue_recvfrom(key, op_key, is->pkt, &pkt_size, 
+				 PJ_IOQUEUE_ALWAYS_ASYNC,
+				 &is->src_addr, &is->src_addr_len);
+    if (status != PJ_SUCCESS && status != PJ_EPENDING) {
+	ice_st_perror(is->ice_st, "ioqueue recvfrom() error", status);
+    }
+}
+
+
+/* 
+ * Destroy an interface 
+ */
+static void destroy_ice_interface(pj_ice_st_interface *is)
+{
+    if (is->key) {
+	pj_ioqueue_unregister(is->key);
+	is->key = NULL;
+	is->sock = PJ_INVALID_SOCKET;
+    } else if (is->sock != PJ_INVALID_SOCKET && is->sock != 0) {
+	pj_sock_close(is->sock);
+	is->sock = PJ_INVALID_SOCKET;
+    }
+}
+
+
+/* 
+ * Create ICE stream transport 
+ */
+PJ_DECL(pj_status_t) pj_ice_st_create(pj_stun_config *stun_cfg,
+				      const char *name,
+				      void *user_data,
+				      const pj_ice_st_cb *cb,
+				      pj_ice_st **p_ice_st)
+{
+    pj_pool_t *pool;
+    pj_ice_st *ice_st;
+
+    PJ_ASSERT_RETURN(stun_cfg && cb && p_ice_st, PJ_EINVAL);
+    PJ_ASSERT_RETURN(stun_cfg->ioqueue && stun_cfg->timer_heap, PJ_EINVAL);
+
+    if (name == NULL)
+	name = "icest%p";
+
+    pool = pj_pool_create(stun_cfg->pf, name, 4000, 4000, NULL);
+    ice_st = PJ_POOL_ZALLOC_T(pool, pj_ice_st);
+    ice_st->pool = pool;
+    pj_memcpy(ice_st->obj_name, pool->obj_name, PJ_MAX_OBJ_NAME);
+    ice_st->user_data = user_data;
+    
+    pj_memcpy(&ice_st->cb, cb, sizeof(*cb));
+    pj_memcpy(&ice_st->stun_cfg, stun_cfg, sizeof(*stun_cfg));
+
+    PJ_LOG(4,(ice_st->obj_name, "ICE stream transport created"));
+
+    *p_ice_st = ice_st;
+    return PJ_SUCCESS;
+}
+
+
+static void destroy_ice_st(pj_ice_st *ice_st, pj_status_t reason)
+{
+    unsigned i;
+    char obj_name[PJ_MAX_OBJ_NAME];
+
+    if (reason == PJ_SUCCESS) {
+	pj_memcpy(obj_name, ice_st->obj_name, PJ_MAX_OBJ_NAME);
+	PJ_LOG(4,(obj_name, "ICE stream transport shutting down"));
+    }
+
+    /* Destroy ICE if we have ICE */
+    if (ice_st->ice) {
+	pj_ice_destroy(ice_st->ice);
+	ice_st->ice = NULL;
+    }
+
+    /* Destroy all interfaces */
+    for (i=0; i<ice_st->itf_cnt; ++i) {
+	destroy_ice_interface(ice_st->itfs[i]);
+	ice_st->itfs[i] = NULL;
+    }
+    ice_st->itf_cnt = 0;
+
+    /* Done */
+    pj_pool_release(ice_st->pool);
+
+    if (reason == PJ_SUCCESS) {
+	PJ_LOG(4,(obj_name, "ICE stream transport destroyed"));
+    }
+}
+
+
+/*
+ * Destroy ICE stream transport.
+ */
+PJ_DEF(pj_status_t) pj_ice_st_destroy(pj_ice_st *ice_st)
+{
+    destroy_ice_st(ice_st, PJ_SUCCESS);
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Resolve STUN server
+ */
+PJ_DEF(pj_status_t) pj_ice_st_set_stun( pj_ice_st *ice_st,
+				        pj_dns_resolver *resolver,
+					pj_bool_t enable_relay,
+					const pj_str_t *domain)
+{
+    /* Yeah, TODO */
+    PJ_UNUSED_ARG(ice_st);
+    PJ_UNUSED_ARG(resolver);
+    PJ_UNUSED_ARG(enable_relay);
+    PJ_UNUSED_ARG(domain);
+    return -1;
+}
+
+
+/*
+ * Set STUN server address.
+ */
+PJ_DEF(pj_status_t) pj_ice_st_set_stun_addr( pj_ice_st *ice_st,
+					     pj_bool_t enable_relay,
+					     const pj_sockaddr_in *srv_addr)
+{
+
+    PJ_ASSERT_RETURN(ice_st && srv_addr, PJ_EINVAL);
+    
+    ice_st->relay_enabled = enable_relay;
+    pj_strdup2(ice_st->pool, &ice_st->stun_domain,
+	       pj_inet_ntoa(srv_addr->sin_addr));
+    pj_memcpy(&ice_st->stun_srv, srv_addr, sizeof(pj_sockaddr_in));
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Add new component.
+ */
+PJ_DEF(pj_status_t) pj_ice_st_add_comp(pj_ice_st *ice_st,
+				       unsigned comp_id)
+{
+    /* Verify arguments */
+    PJ_ASSERT_RETURN(ice_st && comp_id, PJ_EINVAL);
+
+    /* Can only add component when we don't have active ICE session */
+    PJ_ASSERT_RETURN(ice_st->ice == NULL, PJ_EBUSY);
+
+    /* Check that we don't have too many components */
+    PJ_ASSERT_RETURN(ice_st->comp_cnt < PJ_ICE_MAX_COMP, PJ_ETOOMANY);
+
+    /* Component ID must be valid */
+    PJ_ASSERT_RETURN(comp_id <= PJ_ICE_MAX_COMP, PJ_EICEINCOMPID);
+
+    /* First component ID must be 1, second must be 2, etc., and 
+     * they must be registered in order.
+     */
+    PJ_ASSERT_RETURN(ice_st->comps[comp_id-1] == ice_st->comp_cnt, 
+		     PJ_EICEINCOMPID);
+
+    /* All in order, add the component. */
+    ice_st->comps[ice_st->comp_cnt++] = comp_id;
+
+    return PJ_SUCCESS;
+}
+
+
+/* Add interface */
+static void add_interface(pj_ice_st *ice_st, pj_ice_st_interface *is,
+			  unsigned *p_itf_id, pj_bool_t notify,
+			  void *notify_data)
+{
+    unsigned itf_id;
+
+    itf_id = ice_st->itf_cnt++;
+    ice_st->itfs[itf_id] = is;
+
+    if (p_itf_id)
+	*p_itf_id = itf_id;
+
+    if (notify && ice_st->cb.on_interface_status) {
+	(*ice_st->cb.on_interface_status)(ice_st, notify_data, 
+					  PJ_SUCCESS, itf_id);
+    }
+}
+
+/*
+ * Add new host interface.
+ */
+PJ_DEF(pj_status_t) pj_ice_st_add_host_interface(pj_ice_st *ice_st,
+						 unsigned comp_id,
+						 pj_uint16_t local_pref,
+					         const pj_sockaddr_in *addr,
+				    		 unsigned *p_itf_id,
+						 pj_bool_t notify,
+						 void *notify_data)
+{
+    pj_ice_st_interface *is;
+    pj_status_t status;
+
+    /* Verify arguments */
+    PJ_ASSERT_RETURN(ice_st && comp_id, PJ_EINVAL);
+
+    /* Check that component ID present */
+    PJ_ASSERT_RETURN(comp_id <= ice_st->comp_cnt, PJ_EICEINCOMPID);
+
+    /* Can't add new interface while ICE is running */
+    PJ_ASSERT_RETURN(ice_st->ice == NULL, PJ_EBUSY);
+
+    /* Create interface */
+    status = create_ice_interface(ice_st, PJ_ICE_CAND_TYPE_HOST, comp_id,
+				  local_pref, addr, &is);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* For host interface, the address is the base address */
+    pj_memcpy(&is->addr, &is->base_addr, sizeof(is->addr));
+
+    /* Store this interface */
+    add_interface(ice_st, is, p_itf_id, notify, notify_data);
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Enumerate and add all host interfaces.
+ */
+PJ_DEF(pj_status_t) pj_ice_st_add_all_host_interfaces(pj_ice_st *ice_st,
+						      unsigned comp_id,
+						      unsigned port,
+						      pj_bool_t notify,
+						      void *notify_data)
+{
+    pj_sockaddr_in addr;
+    pj_status_t status;
+
+    /* Yeah, TODO.
+     * For now just add the default interface.
+     */
+    pj_sockaddr_in_init(&addr, NULL, (pj_uint16_t)port);
+    
+    status = pj_gethostip(&addr.sin_addr);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    return pj_ice_st_add_host_interface(ice_st, comp_id, 65535, &addr, 
+					NULL, notify, notify_data);
+}
+
+
+/*
+ * Add STUN mapping interface.
+ */
+PJ_DEF(pj_status_t) pj_ice_st_add_stun_interface(pj_ice_st *ice_st,
+						 unsigned comp_id,
+						 unsigned local_port,
+						 pj_bool_t notify,
+						 void *notify_data)
+{
+    /* Yeah, TODO */
+    PJ_UNUSED_ARG(ice_st);
+    PJ_UNUSED_ARG(comp_id);
+    PJ_UNUSED_ARG(local_port);
+    PJ_UNUSED_ARG(notify);
+    PJ_UNUSED_ARG(notify_data);
+    return -1;
+}
+
+
+/*
+ * Add TURN mapping interface.
+ */
+PJ_DEF(pj_status_t) pj_ice_st_add_relay_interface(pj_ice_st *ice_st,
+						  unsigned comp_id,
+						  unsigned local_port,
+						  pj_bool_t notify,
+						  void *notify_data)
+{
+    /* Yeah, TODO */
+    PJ_UNUSED_ARG(ice_st);
+    PJ_UNUSED_ARG(comp_id);
+    PJ_UNUSED_ARG(local_port);
+    PJ_UNUSED_ARG(notify);
+    PJ_UNUSED_ARG(notify_data);
+    return -1;
+}
+
+
+/*
+ * Create ICE!
+ */
+PJ_DEF(pj_status_t) pj_ice_st_init_ice(pj_ice_st *ice_st,
+				       pj_ice_role role,
+				       const pj_str_t *local_ufrag,
+				       const pj_str_t *local_passwd)
+{
+    pj_status_t status;
+    unsigned i;
+    pj_ice_cb ice_cb;
+
+    /* Check arguments */
+    PJ_ASSERT_RETURN(ice_st, PJ_EINVAL);
+    /* Must not have ICE */
+    PJ_ASSERT_RETURN(ice_st->ice == NULL, PJ_EINVALIDOP);
+
+    /* Init callback */
+    pj_bzero(&ice_cb, sizeof(ice_cb));
+    ice_cb.on_ice_complete = &on_ice_complete;
+    ice_cb.on_rx_data = &on_rx_data;
+    ice_cb.on_tx_pkt = &on_tx_pkt;
+
+    /* Create! */
+    status = pj_ice_create(&ice_st->stun_cfg, ice_st->obj_name, role, 
+			   &ice_cb, local_ufrag, local_passwd, &ice_st->ice);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    /* Associate user data */
+    ice_st->ice->user_data = (void*)ice_st;
+
+    /* Add components */
+    for (i=0; i<ice_st->comp_cnt; ++i) {
+	status = pj_ice_add_comp(ice_st->ice, ice_st->comps[i]);
+	if (status != PJ_SUCCESS)
+	    goto on_error;
+    }
+
+    /* Add candidates */
+    for (i=0; i<ice_st->itf_cnt; ++i) {
+	pj_ice_st_interface *is= ice_st->itfs[i];
+	status = pj_ice_add_cand(ice_st->ice, is->comp_id, is->type, 
+				 is->local_pref, &is->foundation,
+				 &is->addr, &is->base_addr, NULL, 
+				 sizeof(pj_sockaddr_in), 
+				 (unsigned*)&is->cand_id);
+	if (status != PJ_SUCCESS)
+	    goto on_error;
+    }
+
+    return PJ_SUCCESS;
+
+on_error:
+    for (i=0; i<ice_st->itf_cnt; ++i) {
+	ice_st->itfs[i]->cand_id = -1;
+    }
+    if (ice_st->ice) {
+	pj_ice_destroy(ice_st->ice);
+	ice_st->ice = NULL;
+    }
+    return status;
+}
+
+
+/*
+ * Enum candidates
+ */
+PJ_DEF(pj_status_t) pj_ice_st_enum_cands(pj_ice_st *ice_st,
+					 unsigned *count,
+					 pj_ice_cand cand[])
+{
+    unsigned i, cnt;
+    pj_ice_cand *pcand;
+    pj_status_t status;
+
+    PJ_ASSERT_RETURN(ice_st && count && cand, PJ_EINVAL);
+    PJ_ASSERT_RETURN(ice_st->ice, PJ_EINVALIDOP);
+
+    cnt = pj_ice_get_cand_cnt(ice_st->ice);
+    cnt = (cnt > *count) ? *count : cnt;
+    *count = 0;
+
+    for (i=0; i<cnt; ++i) {
+	status = pj_ice_get_cand(ice_st->ice, i, &pcand);
+	if (status != PJ_SUCCESS)
+	    return status;
+
+	pj_memcpy(&cand[i], pcand, sizeof(pj_ice_cand));
+    }
+
+    *count = cnt;
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Start ICE processing !
+ */
+PJ_DEF(pj_status_t) pj_ice_st_start_ice( pj_ice_st *ice_st,
+					 const pj_str_t *rem_ufrag,
+					 const pj_str_t *rem_passwd,
+					 unsigned rem_cand_cnt,
+					 const pj_ice_cand rem_cand[])
+{
+    pj_status_t status;
+
+    status = pj_ice_create_check_list(ice_st->ice, rem_ufrag, rem_passwd,
+				      rem_cand_cnt, rem_cand);
+    if (status != PJ_SUCCESS)
+	return status;
+
+    return pj_ice_start_check(ice_st->ice);
+}
+
+
+/*
+ * Stop ICE!
+ */
+PJ_DECL(pj_status_t) pj_ice_st_stop_ice(pj_ice_st *ice_st)
+{
+    if (ice_st->ice) {
+	pj_ice_destroy(ice_st->ice);
+	ice_st->ice = NULL;
+    }
+
+    return PJ_SUCCESS;
+}
+
+
+/*
+ * Send data to peer agent.
+ */
+PJ_DEF(pj_status_t) pj_ice_st_send_data( pj_ice_st *ice_st,
+					 unsigned comp_id,
+					 const void *data,
+					 pj_size_t data_len)
+{
+    if (!ice_st->ice)
+	return PJ_ENOICE;
+
+    return pj_ice_send_data(ice_st->ice, comp_id, data, data_len);
+}
+
+
+
+/*
+ * Callback called by ICE session when ICE processing is complete, either
+ * successfully or with failure.
+ */
+static void on_ice_complete(pj_ice *ice, pj_status_t status)
+{
+    pj_ice_st *ice_st = (pj_ice_st*)ice->user_data;
+    if (ice_st->cb.on_ice_complete) {
+	(*ice_st->cb.on_ice_complete)(ice_st, status);
+    }
+}
+
+
+/*
+ * Callback called by ICE session when it wants to send outgoing packet.
+ */
+static pj_status_t on_tx_pkt(pj_ice *ice, 
+			     unsigned comp_id, unsigned cand_id,
+			     const void *pkt, pj_size_t size,
+			     const pj_sockaddr_t *dst_addr,
+			     unsigned dst_addr_len)
+{
+    pj_ice_st *ice_st = (pj_ice_st*)ice->user_data;
+    pj_ice_st_interface *is = NULL;
+    unsigned i;
+    pj_ssize_t pkt_size;
+    pj_status_t status;
+
+    PJ_UNUSED_ARG(comp_id);
+
+    for (i=0; i<ice_st->itf_cnt; ++i) {
+	if (ice_st->itfs[i]->cand_id == (int)cand_id) {
+	    is = ice_st->itfs[i];
+	    break;
+	}
+    }
+    if (is == NULL) {
+	return PJ_EICEINCANDID;
+    }
+
+    pkt_size = size;
+    status = pj_ioqueue_sendto(is->key, &is->write_op, 
+			       pkt, &pkt_size, 0,
+			       dst_addr, dst_addr_len);
+    
+    return (status==PJ_SUCCESS||status==PJ_EPENDING) ? PJ_SUCCESS : status;
+}
+
+
+/*
+ * Callback called by ICE session when it receives application data.
+ */
+static void on_rx_data(pj_ice *ice, 
+		       unsigned comp_id, unsigned cand_id,
+		       void *pkt, pj_size_t size,
+		       const pj_sockaddr_t *src_addr,
+		       unsigned src_addr_len)
+{
+    pj_ice_st *ice_st = (pj_ice_st*)ice->user_data;
+
+    if (ice_st->cb.on_rx_data) {
+	(*ice_st->cb.on_rx_data)(ice_st, comp_id, cand_id, 
+				 pkt, size, src_addr, src_addr_len);
+    }
+}
+
+