[Freeswitch-svn] [commit] r5692 - in freeswitch/trunk/libs/sofia-sip: . libsofia-sip-ua libsofia-sip-ua/docs libsofia-sip-ua/nta libsofia-sip-ua/nta/sofia-sip libsofia-sip-ua/nua libsofia-sip-ua/soa libsofia-sip-ua/tport libsofia-sip-ua/tport/sofia-sip libsofia-sip-ua/url

Freeswitch SVN mikej at freeswitch.org
Mon Sep 10 16:45:25 EDT 2007


Author: mikej
Date: Mon Sep 10 16:45:25 2007
New Revision: 5692

Modified:
   freeswitch/trunk/libs/sofia-sip/Makefile.am
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/Makefile.am
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/docs/mainpage.docs
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nta/nta.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nta/sofia-sip/nta.h
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nta/sofia-sip/nta_tag.h
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nta/test_nta_api.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua.docs
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_common.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_dialog.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_dialog.h
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_notifier.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_params.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_register.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_session.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_stack.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_subnotref.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_100rel.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_basic_call.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_call_reject.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_cancel_bye.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_init.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_nua.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_nua.h
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_ops.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_proxy.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_proxy.h
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_register.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_simple.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_sip_events.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/soa/test_soa.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/sofia-sip/tport_tag.h
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/test_tport.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport_internal.h
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport_tag.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport_tls.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_connect.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_sctp.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_tcp.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_tls.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_udp.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/url/torture_url.c
   freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/url/url.c

Log:
Merge up to the most recent sofia-sip darcs tree.  Includes the following patches from darcs:

Tue Aug 21 09:38:59 EDT 2007  Pekka.Pessi at nokia.com
  * tport_type_udp.c: checking error while checking that MSG_TRUNC works.
Shall I pull this patch? (1/43)  [ynWvpxqadjk], or ? for help: y

Tue Aug 21 10:49:33 EDT 2007  Pekka.Pessi at nokia.com
  * nua_params.c: NUTAG_SIPS_URL() now sets the handle target, too.

  Problem reported by Jari Tenhunen.
Shall I pull this patch? (2/43)  [ynWvpxqadjk], or ? for help: y

Thu Aug 23 11:22:42 EDT 2007  Pekka.Pessi at nokia.com
  * nta.c: do not destroy INVITE transaction if it has been CANCELed

  Handle gracefully cases where the INVITE transaction is destroyed
  immediately after canceling it. The old behaviour was to left it up to the
  application to ACK the final response returned to INVITE.

  Thanks for Fabio Margarido for reporting this problem.
Shall I pull this patch? (3/43)  [ynWvpxqadjk], or ? for help: y

Thu Aug 23 13:02:01 EDT 2007  Pekka.Pessi at nokia.com
  * test_soa.c: added test with user SDP containing already rejected media
Shall I pull this patch? (4/43)  [ynWvpxqadjk], or ? for help: y

Fri Aug 24 09:41:20 EDT 2007  Pekka.Pessi at nokia.com
  * nta: added option for processing orphan responses matching with a dialog

  The orphan responses matching with the dialog can now be processed by the
  response callback.The dialog leg can be created with
  NTATAG_RESPONSE_CALLBACK() or a response callback can be later bound to the
  leg with nta_leg_bind_response().

  This is practically useful only with 200 OK responses to the INVITE that are
  retransmitted by the UAS. By default, the retransmission are catched by the
  ACK transaction (which then retransmits the ACK request message). However,
  after ACK transaction times out, the retransmitted 200 OK indicates most
  probably that the ACK request messages do not reach UAS.

  Partially fixes the sf.net bug #1750691 reported by Mikhail Zabaluev.
Shall I pull this patch? (5/43)  [ynWvpxqadjk], or ? for help: y

Fri Aug 24 09:41:20 EDT 2007  Pekka.Pessi at nokia.com
  UNDO: nta: added option for processing orphan responses matching with a dialog

  The orphan responses matching with the dialog can now be processed by the
  response callback.The dialog leg can be created with
  NTATAG_RESPONSE_CALLBACK() or a response callback can be later bound to the
  leg with nta_leg_bind_response().

  This is practically useful only with 200 OK responses to the INVITE that are
  retransmitted by the UAS. By default, the retransmission are catched by the
  ACK transaction (which then retransmits the ACK request message). However,
  after ACK transaction times out, the retransmitted 200 OK indicates most
  probably that the ACK request messages do not reach UAS.

  Partially fixes the sf.net bug #1750691 reported by Mikhail Zabaluev.
Shall I pull this patch? (6/43)  [ynWvpxqadjk], or ? for help: y

Thu Aug 30 07:00:10 EDT 2007  Pekka.Pessi at nokia.com
  * nta.c: disabled nta_msg_ackbye(). Fix for sf.net bug #1750691

  Thanks for Mikhail Zabaluev for reporting this bug.
Shall I pull this patch? (7/43)  [ynWvpxqadjk], or ? for help: y

Thu Aug 30 06:54:38 EDT 2007  Pekka.Pessi at nokia.com
  * test_nua: added test for sf.net bug #1750691
Shall I pull this patch? (8/43)  [ynWvpxqadjk], or ? for help: y

Thu Aug 30 07:03:45 EDT 2007  Pekka.Pessi at nokia.com
  * test_nua: added test for nua_bye() sending CANCEL
Shall I pull this patch? (9/43)  [ynWvpxqadjk], or ? for help: y

Fri Aug 31 12:08:09 EDT 2007  Pekka.Pessi at nokia.com
  * url.c: fixed escaping of '/' %2F, ';' %3B and '=' %3D in URL path/params

  Thanks for Fabio Margarido for reporting this bug.
Shall I pull this patch? (10/43)  [ynWvpxqadjk], or ? for help: y

Mon Sep  3 10:14:55 EDT 2007  Pekka.Pessi at nokia.com
  * url.c: do not un-escape %40 in URI parameters.

  Do not unescape %2C, %3B, %3D, or %40 in URI parameters, nor
  %2C, %2F, %3B, %3D, or %40 in URI path.

  The @ sign can be ambiguous in the SIP URL, e.g.,

  <sip:test.info;p=value at test.com>

  can be parsed in two ways:
  1) username contains test.info;param=value and host part has test.com
  2) empty username, host part test.info, URI parameter p=value at test.com

  Previously Sofia URL parser converted escaped '@' at signs (%40) in the URI
  parameters to the unescaped form. The resulting URI could be ambiguous and
  sometimes fail the syntax check if there was no '@' sign before the
  unescaped one.

  Thanks for Jan van den Bosch and Mikhail Zabaluev for reporting this bug.
Shall I pull this patch? (11/43)  [ynWvpxqadjk], or ? for help: y

Wed Jul 25 04:59:57 EDT 2007  Pekka.Pessi at nokia.com
  * tport.c: fixed indenting, logging
Shall I pull this patch? (12/43)  [ynWvpxqadjk], or ? for help: y

Fri Jul 13 12:47:33 EDT 2007  Pekka.Pessi at nokia.com
  * nua/test_proxy.h, nua/test_proxy.c: added support for multiple domains

  Each domain has its own registrar and authentication module.
Shall I pull this patch? (13/43)  [ynWvpxqadjk], or ? for help: y

Mon Jul 23 11:19:33 EDT 2007  Pekka.Pessi at nokia.com
  * test_ops.c: added timestamp to event logging
Shall I pull this patch? (14/43)  [ynWvpxqadjk], or ? for help: y

Mon Jul 23 11:20:12 EDT 2007  Pekka.Pessi at nokia.com
  * test_nua: fixed timing problems in testing.
Shall I pull this patch? (15/43)  [ynWvpxqadjk], or ? for help: y

Mon Jul 23 11:31:04 EDT 2007  Pekka.Pessi at nokia.com
  * test_ops.c: reduce su_root_step() delay to 0.1 seconds
Shall I pull this patch? (16/43)  [ynWvpxqadjk], or ? for help: y

Mon Jul 23 11:31:22 EDT 2007  Pekka.Pessi at nokia.com
  * test_register.c: fixed timing problem
Shall I pull this patch? (17/43)  [ynWvpxqadjk], or ? for help: y

Mon Jul 23 17:03:46 EDT 2007  Pekka.Pessi at nokia.com
  * test_100rel.c: fixed timing problems resulting in events being reordered
Shall I pull this patch? (18/43)  [ynWvpxqadjk], or ? for help: y

Wed Jul 25 12:40:53 EDT 2007  Pekka.Pessi at nokia.com
  * nua (test_init.c, test_register.c): using test_proxy domains
Shall I pull this patch? (19/43)  [ynWvpxqadjk], or ? for help: y

Thu Aug 23 12:12:32 EDT 2007  Pekka.Pessi at nokia.com
  * test_soa.c: added cleanup code
Shall I pull this patch? (20/43)  [ynWvpxqadjk], or ? for help: y

Fri Aug 24 09:35:35 EDT 2007  Pekka.Pessi at nokia.com
  * nta.c: increase lifetime of ACK transaction from T4 to T1 x 64

  nta.c creates a ACK transaction in order to restransmit ACK requests when
  ever a retransmitted 2XX response to INVITE is received. The UAS retransmits
  the 2XX responses for 64 x T1 (32 second by default).

  Partially fixes the sf.net bug #1750691 reported by Mikhail Zabaluev.
Shall I pull this patch? (21/43)  [ynWvpxqadjk], or ? for help: y

Thu Sep  6 10:21:04 EDT 2007  Pekka.Pessi at nokia.com
  * Makefile.am: generating libsofia-sip-ua/docs/Doxyfile.rfc before making manpages
Shall I pull this patch? (22/43)  [ynWvpxqadjk], or ? for help: y

Wed Jul 25 12:05:33 EDT 2007  Pekka.Pessi at nokia.com
  * sofia-sip/tport_tag.h: added TPTAG_KEEPALIVE(), TPTAG_PINGPONG(), TPTAG_PONG2PING()
Shall I pull this patch? (23/43)  [ynWvpxqadjk], or ? for help: y

Wed Jul 25 12:09:06 EDT 2007  Pekka.Pessi at nokia.com
  * tport: added ping-pong keepalive on TCP. replaced single tick with connection-specific timer

  Now detecting closed connections on TLS, too.

  Added tests for idle timeout, receive timeout, ping-pong timeout.
Shall I pull this patch? (24/43)  [ynWvpxqadjk], or ? for help: y

Fri Jul  6 10:19:32 EDT 2007  Pekka.Pessi at nokia.com
  * nta.c: added nta_incoming_received()
Shall I pull this patch? (25/43)  [ynWvpxqadjk], or ? for help: y

Mon Jul 23 11:29:56 EDT 2007  Pekka.Pessi at nokia.com
  * nua_session.c: delay transition to ready when O/A is incomplete

  Delay sending ACK and subsequent transition of call to the ready state when
  the 200 OK response to the INVITE is received if the SDP Offer/Answer
  exchange using UPDATE/PRACK was still incomplete.

  Previously, if the O/A using UPDATE or PRACK was incomplete and an 200 OK
  was received, the call setup logic regarded this as a fatal error and
  terminated the call.

  Thanks for Mike Jerris for detecting and reporting this bug.
Shall I pull this patch? (26/43)  [ynWvpxqadjk], or ? for help: y

Wed Jul 25 12:22:46 EDT 2007  Pekka.Pessi at nokia.com
  * test_call_reject.c: testing Retry-After
Shall I pull this patch? (27/43)  [ynWvpxqadjk], or ? for help: y

Wed Jul 25 12:42:51 EDT 2007  Pekka.Pessi at nokia.com
  * test_nua: using rudimentary outbound support in B's proxy.
Shall I pull this patch? (28/43)  [ynWvpxqadjk], or ? for help: y

Wed Jul 25 12:48:33 EDT 2007  Pekka.Pessi at nokia.com
  * nua_register.c: added some logging to nua_register_connection_closed()
Shall I pull this patch? (29/43)  [ynWvpxqadjk], or ? for help: y

Wed Jul 25 12:43:57 EDT 2007  Pekka.Pessi at nokia.com
  * test_nua: using AUTHTAG_MAX_NCOUNT(1) for Mr. C

  C is now challenged every time.
Shall I pull this patch? (30/43)  [ynWvpxqadjk], or ? for help: y

Thu Sep  6 11:05:19 EDT 2007  Pekka.Pessi at nokia.com
  * nua/test_100rel.c: fixed timing problem re response to PRACK and ACK
Shall I pull this patch? (31/43)  [ynWvpxqadjk], or ? for help: y

Thu Sep  6 06:02:50 EDT 2007  Mikhail Zabaluev <mikhail.zabaluev at nokia.com>
  * DIST_SUBDIRS must include everything unconditionally
Shall I pull this patch? (32/43)  [ynWvpxqadjk], or ? for help: y

Thu Sep  6 13:53:04 EDT 2007  Pekka.Pessi at nokia.com
  * test_soa.c: silenced warnings
Shall I pull this patch? (33/43)  [ynWvpxqadjk], or ? for help: y

Mon Jul 23 16:59:48 EDT 2007  Pekka.Pessi at nokia.com
  * nua: refactored dialog refresh code
Shall I pull this patch? (34/43)  [ynWvpxqadjk], or ? for help: y

Mon Jul 23 16:59:48 EDT 2007  Pekka.Pessi at nokia.com
  UNDO: nua: refactored dialog refresh code
Shall I pull this patch? (35/43)  [ynWvpxqadjk], or ? for help: y

Thu Sep  6 12:01:25 EDT 2007  Pekka.Pessi at nokia.com
  * nua_dialog.[hc]: renamed functions setting refresh interval

Shall I pull this patch? (36/43)  [ynWvpxqadjk], or ? for help: y

Thu Sep  6 12:15:03 EDT 2007  Pekka.Pessi at nokia.com
  * nua_dialog.[hc], nua_stack.c: added nua_dialog_repeat_shutdown()
Shall I pull this patch? (37/43)  [ynWvpxqadjk], or ? for help: y

Thu Sep  6 12:19:20 EDT 2007  Pekka.Pessi at nokia.com
  * nua_dialog.h: renamed nua_remote_t as nua_dialog_peer_info_t
Shall I pull this patch? (38/43)  [ynWvpxqadjk], or ? for help: y

Thu Sep  6 12:23:04 EDT 2007  Pekka.Pessi at nokia.com
  * nua_stack.c: added timer to client request in order to implement Retry-After
Shall I pull this patch? (39/43)  [ynWvpxqadjk], or ? for help: y

Thu Sep  6 12:33:53 EDT 2007  Pekka.Pessi at nokia.com
  * nua: added backpointers to nua_dialog_usage_t and nua_dialog_state_t
Shall I pull this patch? (40/43)  [ynWvpxqadjk], or ? for help: y

Thu Sep  6 13:56:48 EDT 2007  Pekka.Pessi at nokia.com
  * test_nua.c: abort() in timeout alarm function if -a is given
Shall I pull this patch? (41/43)  [ynWvpxqadjk], or ? for help: y

Thu Sep  6 17:13:18 EDT 2007  Pekka.Pessi at nokia.com
  * nua_subnotref.c: include SIPTAG_EVENT() in the nua_i_notify tag list
Shall I pull this patch? (42/43)  [ynWvpxqadjk], or ? for help: y

Mon Sep 10 12:27:53 EDT 2007  Pekka.Pessi at nokia.com
  * nua: save Contact from target refresh request or response.

  Save the Contact header which the application has added to the target
  refresh requests or responses and use the saved contact in subsequent target
  refresh requests or responses.

  Previously the application had no way of specifying the Contact included in
  the automatic responses to target refresh requests.

  Thanks for Anthony Minessale for reporting this problem.
Shall I pull this patch? (43/43)  [ynWvpxqadjk], or ? for help: y




Modified: freeswitch/trunk/libs/sofia-sip/Makefile.am
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/Makefile.am	(original)
+++ freeswitch/trunk/libs/sofia-sip/Makefile.am	Mon Sep 10 16:45:25 2007
@@ -42,6 +42,7 @@
 manpages:
 	-mkdir -p man man/man1 2> /dev/null
 if HAVE_DOXYGEN
+	$(MAKE) $(AM_MAKEFLAGS) -C libsofia-sip-ua/docs built-sources
 	@echo 'cd utils && $(DOXYGEN)'
 	@cd utils && \
 	{ exec 3>&1 1>&2; { $(DOXYGEN) 2>&1; echo $$? >& 3 ;} | \

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/Makefile.am
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/Makefile.am	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/Makefile.am	Mon Sep 10 16:45:25 2007
@@ -26,7 +26,8 @@
 # note: order does matter in the subdir list
 SUBDIRS = su features bnf sresolv sdp url msg sip $(OPT_SUBDIRS_STUN) ipt soa \
 	  tport nta nea iptsec $(OPT_SUBDIRS_NTH) nua
-DIST_SUBDIRS = $(SUBDIRS) docs
+DIST_SUBDIRS = su features bnf sresolv sdp url msg sip stun ipt soa \
+	  tport nta nea iptsec nth http nua docs
 
 DOXYGEN = doxygen
 

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/docs/mainpage.docs
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/docs/mainpage.docs	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/docs/mainpage.docs	Mon Sep 10 16:45:25 2007
@@ -55,9 +55,10 @@
 
 @section subdirs Directory Structure
 
-In libsofia-sip-ua, there are subdirectories
+In libsofia-sip-ua, there are subdirectories for different modules listed
+below.
 
-Terminal and high-level libraries utilizing for both signaling and media:
+Terminal and high-level libraries used for both signaling and media:
 
 Common runtime library:
  - <a href="su/index.html">"su" - sockets, memory management, threads</a>

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nta/nta.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nta/nta.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nta/nta.c	Mon Sep 10 16:45:25 2007
@@ -2690,12 +2690,14 @@
       && sip->sip_via && !sip->sip_via->v_next 
       && agent_has_via(agent, sip->sip_via)) {
     agent->sa_stats->as_trless_200++;
+#if nomore /* sf.net bug #1750691. Let UAS to cope with it. */
     if (agent->sa_is_a_uas) {
       /* Orphan 200 Ok to INVITE. ACK and BYE it */
       SU_DEBUG_5(("nta: %03d %s %s\n", status, phrase, "is ACK&BYE"));
       if (nta_msg_ackbye(agent, msg) != -1)
 	return;
     }
+#endif
   }
 
   SU_DEBUG_5(("nta: %03d %s %s\n", status, phrase, "was discarded"));
@@ -3067,15 +3069,19 @@
 
 /** ACK and BYE an unknown 200 OK response to INVITE.
  *
- * A UAS may still return a 2XX series response to an INVITE request after
- * the client transaction has been terminated. In that case, the UAC can not
- * really accept the call, but it may send a ACK request to UAS followed
- * immediately by BYE using nta_msg_ackbye(). The function does not create a
- * transaction objects, but just sends the ACK and BYE request messages
- * according to the @RecordRoute and @Contact headers in the @a msg.
+ * A UAS may still return a 2XX series response to client request after the
+ * client transactions has been terminated. In that case, the UAC can not
+ * really accept the call. This function was used to accept and immediately
+ * terminate such a call.
+ *
+ * @deprecated This was a bad idea: see sf.net bug #1750691. It can be used
+ * to amplify DoS attacks. Let UAS take care of retransmission timeout and
+ * let it terminate the session. As of @VERSION_1_12_7, this function just
+ * returns -1.
  */
 int nta_msg_ackbye(nta_agent_t *agent, msg_t *msg)
 {
+#if nomore
   sip_t *sip = sip_object(msg);
   msg_t *amsg = nta_msg_create(agent, 0);
   sip_t *asip = sip_object(amsg);
@@ -3164,6 +3170,8 @@
  err:
   msg_destroy(amsg);
   msg_destroy(bmsg);
+#endif
+  (void)agent; (void)msg;
   return -1;
 }
 
@@ -5121,6 +5129,21 @@
   return irq && irq->irq_callback == callback ? irq->irq_magic : NULL;
 }
 
+/** When received */
+sip_time_t nta_incoming_received(nta_incoming_t *irq,
+				 su_nanotime_t *return_nano)
+{
+  su_time_t tv = { 0, 0 };
+
+  if (irq)
+    tv = irq->irq_received;
+
+  if (return_nano)
+    *return_nano = (su_nanotime_t)tv.tv_sec * 1000000000 + tv.tv_usec * 1000;
+
+  return tv.tv_sec;
+}
+
 /** Find incoming transaction. */
 nta_incoming_t *nta_incoming_find(nta_agent_t const *agent,
 				  sip_t const *sip,
@@ -7007,6 +7030,7 @@
   orq->orq_via_branch = branch;
 
   if (orq->orq_method == sip_method_ack) {
+    /* Find the original INVITE which we are ACKing */
     if (ack_branch != NULL && ack_branch != NONE) {
       if (strncasecmp(ack_branch, "branch=", 7) == 0)
 	orq->orq_branch = su_strdup(home, ack_branch);
@@ -7313,16 +7337,11 @@
   if (retransmit)
     return;
 
-  /* Set timers */
-  if (orq->orq_method == sip_method_ack) {
-    /* ACK */
-    outgoing_complete(orq); /* Timer K */
-    return;
-  }
-
   outgoing_trying(orq);		/* Timer B / F */
 
-  if (!orq->orq_reliable)
+  if (orq->orq_method == sip_method_ack)
+    ;
+  else if (!orq->orq_reliable)
     outgoing_set_timer(orq, agent->sa_t1); /* Timer A/E */
   else if (orq->orq_try_tcp_instead && !tport_is_connected(tp))
     outgoing_set_timer(orq, agent->sa_t4); /* Timer N3 */
@@ -7771,11 +7790,11 @@
   if (orq->orq_terminated || orq->orq_default) {
     outgoing_free(orq);
   }
-  /* We have to handle 200 OK statelessly => 
-     kill transaction immediately */
+  /* Application is expected to handle 200 OK statelessly
+     => kill transaction immediately */
   else if (orq->orq_method == sip_method_invite && !orq->orq_completed
-	   /* (unless we have to wait to send CANCEL) */
-	   && !orq->orq_cancel) {
+	   /* (unless we the transaction has been canceled) */
+	   && !orq->orq_canceled) {
     orq->orq_destroyed = 1;
     outgoing_terminate(orq);
   }
@@ -7927,10 +7946,14 @@
     timeout++;
     
     SU_DEBUG_5(("nta: timer %s fired, %s %s (%u)\n",
-		timer, "timeout", 
+		timer, 
+		orq->orq_method != sip_method_ack ? "timeout" : "terminating",
 		orq->orq_method_name, orq->orq_cseq->cs_seq));
 
-    outgoing_timeout(orq, now);
+    if (orq->orq_method != sip_method_ack)
+      outgoing_timeout(orq, now);
+    else
+      outgoing_terminate(orq);
 
     assert(q->q_head != orq || orq->orq_timeout - now > 0);
   }
@@ -8002,10 +8025,7 @@
 
   outgoing_reset_timer(orq); /* Timer A/E */
 
-  if (orq->orq_stateless)
-    return outgoing_terminate(orq);
-
-  if (orq->orq_reliable && orq->orq_method != sip_method_ack)
+  if (orq->orq_stateless || orq->orq_reliable)
     return outgoing_terminate(orq);
 
   if (orq->orq_method == sip_method_invite) {
@@ -8607,7 +8627,7 @@
 		  (void *)orq, status, phrase));
     orq->orq_status = status;
     if (orq->orq_queue == NULL)
-      outgoing_complete(orq);	/* Timer D/K */
+      outgoing_trying(orq);	/* Timer F */
     return 0;
   }
     

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nta/sofia-sip/nta.h
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nta/sofia-sip/nta.h	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nta/sofia-sip/nta.h	Mon Sep 10 16:45:25 2007
@@ -284,6 +284,7 @@
 SOFIAPUBFUN char const *nta_incoming_method_name(nta_incoming_t const *irq);
 SOFIAPUBFUN url_t const *nta_incoming_url(nta_incoming_t const *irq);
 SOFIAPUBFUN uint32_t nta_incoming_cseq(nta_incoming_t const *irq);
+SOFIAPUBFUN sip_time_t nta_incoming_received(nta_incoming_t *irq, su_nanotime_t *nano);
 
 SOFIAPUBFUN int nta_incoming_set_params(nta_incoming_t *irq,
 					tag_type_t tag, tag_value_t value, ...);

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nta/sofia-sip/nta_tag.h
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nta/sofia-sip/nta_tag.h	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nta/sofia-sip/nta_tag.h	Mon Sep 10 16:45:25 2007
@@ -1725,15 +1725,15 @@
 /** Get number of responses without matching request.
  *
  * Return number of received responses for which no matching client
- * transaction was found. Such responses are processed either by the default
+ * transaction was found. Such responses are processed either by the
  * client transaction created with nta_outgoing_default(), the
  * #nta_message_f message callback given to nta_agent_create(), or, missing
  * both the default client transaction and message callback, they are
  * silently discarded.
  *
- * When stack is in UA mode, the successful 2XX responses to the INVITE
- * transaction are an exception: when such a response is received the stack
- * tries to send an ACK and BYE requests to the originator of 2XX response.
+ * The NTATAG_S_TRLESS_200_REF() counter counts those successful 2XX
+ * responses to the INVITE without client transaction which are silently
+ * discarded.
  *
  * @sa nta_agent_get_stats(), nta_outgoing_default(), nta_agent_create(),
  * <sofia-sip/nta_stateless.h>, #nta_message_f, nta_msg_ackbye(),
@@ -1753,10 +1753,6 @@
  * default client transaction created with nta_outgoing_default() or
  * #nta_message_f message callback given to nta_agent_create().
  *
- * When such a successful 2XX responses to the INVITE is received but it is
- * not processed, the stack tries to send an ACK and BYE requests to the
- * originator of 2XX response if stack is in UA mode (set by NTATAG_UA(1). 
- *
  * @sa nta_agent_get_stats(), nta_outgoing_default(), nta_agent_create(),
  * <sofia-sip/nta_stateless.h>, #nta_message_f, nta_msg_ackbye(),
  * NTATAG_S_TRLESS_RESPONSE_REF().

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nta/test_nta_api.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nta/test_nta_api.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nta/test_nta_api.c	Mon Sep 10 16:45:25 2007
@@ -453,7 +453,7 @@
 
   sip_contact_t const *aliases = (void *)-1;
   msg_mclass_t *mclass = (void *)-1;
-  sip_contact_t *contact = (void *)-1;
+  sip_contact_t const *contact = (void *)-1;
   url_string_t const *default_proxy = (void *)-1;
   void *smime = (void *)-1;
 
@@ -859,6 +859,7 @@
   nta_incoming_t *irq;
   nta_outgoing_t *orq;
   sip_via_t via[1];
+  su_nanotime_t nano;
 
   TEST_1(nta = ag->ag_agent);
 
@@ -876,7 +877,9 @@
   TEST_S(nta_incoming_method_name(irq), "*");
   TEST_P(nta_incoming_url(irq), NULL);
   TEST(nta_incoming_cseq(irq), 0);
-  
+
+  TEST(nta_incoming_received(irq, &nano), nano / 1000000000);
+
   TEST(nta_incoming_set_params(irq, TAG_END()), 0);
   
   TEST_P(nta_incoming_getrequest(irq), NULL);
@@ -944,6 +947,7 @@
   nta_agent_t *nta;
   su_root_t *root;
   su_home_t home[1];
+  su_nanotime_t nano;
 
   BEGIN();
 
@@ -1044,6 +1048,8 @@
   TEST_P(nta_incoming_method_name(NULL), NULL);
   TEST_P(nta_incoming_url(NULL), NULL);
   TEST(nta_incoming_cseq(NULL), 0);
+  TEST(nta_incoming_received(NULL, &nano), 0);
+  TEST64(nano, 0);
 
   TEST(nta_incoming_set_params(NULL, TAG_END()), -1);
 

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua.docs
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua.docs	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua.docs	Mon Sep 10 16:45:25 2007
@@ -1174,9 +1174,9 @@
     <td rowspan=2>terminating*</td>
     <td rowspan=2>-</td>
     <td>
-   The cannot be terminated with BYE before the dialog is established with a
-   non-100 preliminary response. So, instead of @b BYE, stack sends a @b
-   CANCEL request, and enters terminating state.
+   The call cannot be terminated with BYE before the dialog is established
+   with a non-100 preliminary response. So, instead of a @b BYE, stack sends
+   a @b CANCEL request, and enters terminating state.
 
    However, there is a race condition and the server can respond with a
    succesful 2XX response before receiving CANCEL. If the server responds with

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_common.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_common.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_common.c	Mon Sep 10 16:45:25 2007
@@ -114,6 +114,7 @@
     nh->nh_nua = nua;
     nh->nh_magic = hmagic;
     nh->nh_prefs = nua->nua_dhandle->nh_prefs;
+    nh->nh_ds->ds_owner = nh;
 
     if (nua_handle_save_tags(nh, tags) < 0) {
       SU_DEBUG_5(("nua(%p): creating handle %p failed\n",

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_dialog.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_dialog.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_dialog.c	Mon Sep 10 16:45:25 2007
@@ -43,6 +43,7 @@
 #include <sofia-sip/su_uniqueid.h>
 
 #include <sofia-sip/sip_protos.h>
+#include <sofia-sip/sip_status.h>
 
 #define NUA_OWNER_T su_home_t
 
@@ -127,9 +128,9 @@
 				nua_dialog_state_t *ds,
 				sip_t const *sip)
 {
-  nua_remote_t *nr = ds->ds_remote_ua;
+  nua_dialog_peer_info_t *nr = ds->ds_remote_ua;
   nua_dialog_usage_t *du;
-  nua_remote_t old[1];
+  nua_dialog_peer_info_t old[1];
 
   *old = *nr;
 
@@ -189,6 +190,7 @@
 {
   if (ds->ds_usage == usage && (usage == NULL || usage->du_next == NULL)) {
     nua_dialog_store_peer_info(own, ds, NULL); /* zap peer info */
+    msg_header_free(own, (msg_header_t *)ds->ds_ltarget), ds->ds_ltarget = NULL;
     nta_leg_destroy(ds->ds_leg), ds->ds_leg = NULL;
     su_free(own, (void *)ds->ds_remote_tag), ds->ds_remote_tag = NULL;
     ds->ds_route = 0;
@@ -287,6 +289,8 @@
       du = su_zalloc(own, sizeof *du + uclass->usage_size);
 
     if (du) {
+      su_home_ref(own);
+      du->du_dialog = ds; 
       du->du_class = uclass;
       du->du_event = o;
 
@@ -300,7 +304,6 @@
 		  (void *)own, nua_dialog_usage_name(du), 
 		  o ? " with event " : "", o ? o->o_type :""));
 
-      su_home_ref(own);
       du->du_next = ds->ds_usage, ds->ds_usage = du;
 
       return du;
@@ -450,29 +453,28 @@
  * if @a delta is less than 5 minutes but longer than 90 seconds, 30..60
  * seconds before end of interval.
  *
- * If @a delta is 0, the refresh time is set at the end of the world
- * (maximum time, for 32-bit systems sometimes during 2036).
+ * If @a delta is 0, the dialog usage is never refreshed.
  */
 void nua_dialog_usage_set_refresh(nua_dialog_usage_t *du, unsigned delta)
 {
   if (delta == 0)
-    du->du_refresh = 0;
+    nua_dialog_usage_reset_refresh(du);
   else if (delta > 90 && delta < 5 * 60)
     /* refresh 30..60 seconds before deadline */
-    nua_dialog_usage_refresh_range(du, delta - 60, delta - 30);
+    nua_dialog_usage_set_refresh_range(du, delta - 60, delta - 30);
   else {
     /* By default, refresh around half time before deadline */
     unsigned min = (delta + 2) / 4;
     unsigned max = (delta + 2) / 4 + (delta + 1) / 2;
     if (min == 0)
       min = 1;
-    nua_dialog_usage_refresh_range(du, min, max);
+    nua_dialog_usage_set_refresh_range(du, min, max);
   }
 }
 
 /**@internal Set refresh in range min..max seconds in the future. */
-void nua_dialog_usage_refresh_range(nua_dialog_usage_t *du, 
-				    unsigned min, unsigned max)
+void nua_dialog_usage_set_refresh_range(nua_dialog_usage_t *du, 
+					unsigned min, unsigned max)
 {
   sip_time_t now = sip_now(), target;
   unsigned delta;
@@ -493,12 +495,12 @@
   SU_DEBUG_7(("nua(): refresh %s after %lu seconds (in [%u..%u])\n",
 	      nua_dialog_usage_name(du), target - now, min, max));
 
-  du->du_refresh = target;
+  nua_dialog_usage_set_refresh_at(du, target);
 }
 
 /** Set absolute refresh time */
-void nua_dialog_usage_refresh_at(nua_dialog_usage_t *du,
-				 sip_time_t target)
+void nua_dialog_usage_set_refresh_at(nua_dialog_usage_t *du,
+				     sip_time_t target)
 {
   SU_DEBUG_7(("nua(): refresh %s after %lu seconds\n",
 	      nua_dialog_usage_name(du), target - sip_now()));
@@ -512,25 +514,14 @@
     du->du_refresh = 0;
 }
 
-/** @internal Refresh usage or shutdown usage if @a now is 0. */
+/** @internal Refresh usage. */
 void nua_dialog_usage_refresh(nua_owner_t *owner,
 			      nua_dialog_state_t *ds,
 			      nua_dialog_usage_t *du, 
 			      sip_time_t now)
 {
-  if (du) {
-    du->du_refresh = 0;
-
-    if (now > 0) {
-      assert(du->du_class->usage_refresh);
-      du->du_class->usage_refresh(owner, ds, du, now);
-    }
-    else {
-      du->du_shutdown = 1;
-      assert(du->du_class->usage_shutdown);
-      du->du_class->usage_shutdown(owner, ds, du);
-    }
-  }
+  assert(du && du->du_class->usage_refresh);
+  du->du_class->usage_refresh(owner, ds, du, now);
 }
 
 /** Terminate all dialog usages gracefully. */
@@ -552,18 +543,18 @@
   return 1;
 }
 
-/** (Gracefully) terminate usage.
+/** Shutdown (gracefully terminate) usage.
  *
  * @retval >0  shutdown done
  * @retval 0   shutdown in progress
  * @retval <0  try again later
  */
 int nua_dialog_usage_shutdown(nua_owner_t *owner,
-			       nua_dialog_state_t *ds,
-			       nua_dialog_usage_t *du)
+			      nua_dialog_state_t *ds,
+			      nua_dialog_usage_t *du)
 {
   if (du) {
-    du->du_refresh = 0;
+    nua_dialog_usage_reset_refresh(du);
     du->du_shutdown = 1;
     assert(du->du_class->usage_shutdown);
     return du->du_class->usage_shutdown(owner, ds, du);
@@ -571,3 +562,41 @@
   else
     return 200;
 }
+
+/** Repeat shutdown of all usages.
+ *
+ * @note Caller must have a reference to nh
+ */
+int nua_dialog_repeat_shutdown(nua_owner_t *owner, nua_dialog_state_t *ds)
+{
+  nua_dialog_usage_t *du;
+  nua_server_request_t *sr, *sr_next;
+
+  for (sr = ds->ds_sr; sr; sr = sr_next) {
+    sr_next = sr->sr_next;
+
+    if (nua_server_request_is_pending(sr)) {
+      SR_STATUS1(sr, SIP_410_GONE); /* 410 terminates dialog */
+      nua_server_respond(sr, NULL);
+      nua_server_report(sr);
+    }
+  }
+
+  for (du = ds->ds_usage; du ;) {
+    nua_dialog_usage_t *du_next = du->du_next;
+
+    nua_dialog_usage_shutdown(owner, ds, du);
+
+    if (du_next == NULL)
+      break;
+
+    for (du = ds->ds_usage; du; du = du->du_next) {
+      if (du == du_next)
+	break;
+      else if (!du->du_shutdown)
+	break;
+    }
+  }
+
+  return ds->ds_usage != NULL;
+}

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_dialog.h
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_dialog.h	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_dialog.h	Mon Sep 10 16:45:25 2007
@@ -35,10 +35,6 @@
  * @date Created: Wed Mar  8 11:38:18 EET 2006  ppessi
  */
 
-typedef struct nua_dialog_state nua_dialog_state_t;
-typedef struct nua_dialog_usage nua_dialog_usage_t;
-typedef struct nua_remote_s nua_remote_t;
-
 #ifndef NUA_OWNER_T
 #define NUA_OWNER_T struct nua_owner_s
 #endif
@@ -48,10 +44,13 @@
 #include <sofia-sip/nta.h>
 #endif
 
-typedef su_msg_r nua_saved_signal_t;
-
+typedef struct nua_dialog_state nua_dialog_state_t;
+typedef struct nua_dialog_usage nua_dialog_usage_t;
 typedef struct nua_server_request nua_server_request_t; 
 typedef struct nua_client_request nua_client_request_t; 
+typedef struct nua_dialog_peer_info nua_dialog_peer_info_t;
+
+typedef su_msg_r nua_saved_signal_t;
 
 typedef struct {
   sip_method_t sm_method; 
@@ -272,6 +271,8 @@
 
   nta_outgoing_t     *cr_orq;
 
+  su_timer_t         *cr_timer;	        /**< Expires or retry timer */
+
   /*nua_event_t*/ int cr_event;		/**< Request event */
   sip_method_t        cr_method;
   char const         *cr_method_name;
@@ -303,8 +304,10 @@
   unsigned cr_dialog:1;		/**< Request can initiate dialog */
 
   /* Current state */
+  unsigned cr_waiting:1;	/**< Request is waiting */
   unsigned cr_challenged:1;	/**< Request was challenged */
   unsigned cr_wait_for_cred:1;	/**< Request is pending authentication */
+  unsigned cr_wait_for_timer:1;	/**< Request is waiting for a timer to expire  */
   unsigned cr_restarting:1;	/**< Request is being restarted */
   unsigned cr_reporting:1;	/**< Reporting in progress */
   unsigned cr_terminating:1;	/**< Request terminates the usage */
@@ -316,6 +319,9 @@
 
 struct nua_dialog_state
 {
+  /** Dialog owner */
+  nua_owner_t            *ds_owner;
+
   /** Dialog usages. */
   nua_dialog_usage_t     *ds_usage;
 
@@ -346,12 +352,13 @@
   sip_from_t const *ds_local;		/**< Local address */
   sip_to_t const *ds_remote;		/**< Remote address */
   nta_leg_t      *ds_leg;
+  sip_contact_t  *ds_ltarget;	        /**< Local target */
   char const     *ds_remote_tag;	/**< Remote tag (if any). 
 					 * Should be non-NULL 
 					 * if dialog is established.
 					 */
-
-  struct nua_remote_s {
+  
+  struct nua_dialog_peer_info {
     sip_allow_t      *nr_allow;
     sip_accept_t     *nr_accept;
     sip_require_t    *nr_require;
@@ -360,10 +367,6 @@
   } ds_remote_ua[1];
 };
 
-typedef void nh_pending_f(nua_owner_t *, 
-			  nua_dialog_usage_t *du,
-			  sip_time_t now);
-
 /** Virtual function pointer table for dialog usage. */
 typedef struct {
   unsigned usage_size, usage_class_size;
@@ -388,6 +391,7 @@
 struct nua_dialog_usage {
   nua_dialog_usage_t *du_next;
   nua_usage_class const *du_class;
+  nua_dialog_state_t *du_dialog;
   nua_client_request_t *du_cr;	        /**< Client request bound with usage */
 
   unsigned     du_ready:1;	        /**< Established usage */
@@ -440,13 +444,16 @@
 
 int nua_dialog_shutdown(nua_owner_t *owner, nua_dialog_state_t *ds);
 
+int nua_dialog_repeat_shutdown(nua_owner_t *owner,
+			       nua_dialog_state_t *ds);
+
 void nua_dialog_usage_set_refresh(nua_dialog_usage_t *du, unsigned delta);
 
-void nua_dialog_usage_refresh_range(nua_dialog_usage_t *du, 
-				    unsigned min, unsigned max);
+void nua_dialog_usage_set_refresh_range(nua_dialog_usage_t *du, 
+					unsigned min, unsigned max);
 
-void nua_dialog_usage_refresh_at(nua_dialog_usage_t *du, 
-				 sip_time_t target);
+void nua_dialog_usage_set_refresh_at(nua_dialog_usage_t *du, 
+				     sip_time_t target);
 
 void nua_dialog_usage_reset_refresh(nua_dialog_usage_t *du);
 

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_notifier.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_notifier.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_notifier.c	Mon Sep 10 16:45:25 2007
@@ -696,7 +696,7 @@
       nua_client_resend_request(cr, 0);
     }
     else if (nu->nu_expires) {
-      nua_dialog_usage_refresh_at(du, nu->nu_expires);
+      nua_dialog_usage_set_refresh_at(du, nu->nu_expires);
     }
   }
 

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_params.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_params.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_params.c	Mon Sep 10 16:45:25 2007
@@ -1158,7 +1158,7 @@
       url = (url_string_t *)t->t_value;
     }
     /* NUTAG_SIPS_URL_REF(url) */
-    else if (t->t_tag == nutag_url) {
+    else if (t->t_tag == nutag_sips_url) {
       url = (url_string_t *)t->t_value;
     }
   }

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_register.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_register.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_register.c	Mon Sep 10 16:45:25 2007
@@ -973,7 +973,7 @@
     nua_registration_set_ready(nr, 1);
   }
   else if (du) {
-    nua_dialog_usage_set_refresh(du, 0);
+    nua_dialog_usage_reset_refresh(du);
 
     su_free(nh->nh_home, nr->nr_route);
     nr->nr_route = NULL;
@@ -1004,14 +1004,31 @@
 				    msg_t *msg,
 				    int error)
 {
-  if (tport_release(nr->nr_tport, nr->nr_error_report_id, NULL, NULL, nr, 0) < 0)
-    SU_DEBUG_1(("nua_register: tport_release() failed\n"));
+  tp_name_t const *tpn;
+  int pending = nr->nr_error_report_id;
+
+  assert(tport == nr->nr_tport);
 
+  if (!nr->nr_tport)
+    return;
+
+  if (tport_release(nr->nr_tport, pending, NULL, NULL, nr, 0) < 0)
+    SU_DEBUG_1(("nua_register: tport_release() failed\n"));
   nr->nr_error_report_id = 0;
+
+  tpn = tport_name(nr->nr_tport);
+
+  SU_DEBUG_5(("nua_register(%p): tport to %s/%s:%s%s%s closed %s\n", 
+	      nua_dialog_usage_public(nr)->du_dialog->ds_owner,
+	      tpn->tpn_proto, tpn->tpn_host, tpn->tpn_port,
+	      tpn->tpn_comp ? ";comp=" : "",
+	      tpn->tpn_comp ? tpn->tpn_comp : "",
+	      error != 0 ? su_strerror(error) : ""));
+
   tport_unref(nr->nr_tport), nr->nr_tport = NULL;
 
   /* Schedule re-REGISTER immediately */
-  nua_dialog_usage_refresh_at(nua_dialog_usage_public(nr), sip_now());
+  nua_dialog_usage_set_refresh_range(nua_dialog_usage_public(nr), 0, 0);
 }
 
 

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_session.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_session.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_session.c	Mon Sep 10 16:45:25 2007
@@ -173,6 +173,8 @@
   char const     *ss_oa_recv, *ss_oa_sent;
 } nua_session_usage_t;
 
+static char const Offer[] = "offer", Answer[] = "answer";
+
 static char const *nua_session_usage_name(nua_dialog_usage_t const *du);
 static int nua_session_usage_add(nua_handle_t *nh,
 				 nua_dialog_state_t *ds,
@@ -579,7 +581,7 @@
 
   cr->cr_usage = du = nua_dialog_usage_for_session(nh->nh_ds);
   /* Errors returned by nua_invite_client_init() 
-     are neutral to session state */
+     do not change the session state */
   cr->cr_neutral = 1;	
   
   if (nh_is_special(nh) || 
@@ -640,7 +642,7 @@
     invite_timeout = UINT_MAX;
   /* Send CANCEL if we don't get response within timeout*/
   /* nua_dialog_usage_set_expires(du, invite_timeout); Xyzzy */
-  nua_dialog_usage_set_refresh(du, 0);
+  nua_dialog_usage_reset_refresh(du);
 
   /* Add session timer headers */
   if (session_timer_is_supported(ss->ss_timer))
@@ -689,10 +691,10 @@
 				    NTATAG_REL100(ss->ss_100rel),
 				    TAG_NEXT(tags));
   if (retval == 0) {
-    cr->cr_offer_sent = offer_sent;
-    ss->ss_oa_sent = offer_sent ? "offer" : NULL;
+    if ((cr->cr_offer_sent = offer_sent))
+      ss->ss_oa_sent = Offer;
 
-    if (!cr->cr_restarting)
+    if (!cr->cr_restarting) /* Restart logic calls nua_invite_client_report */
       signal_call_state_change(nh, ss, 0, "INVITE sent", 
 			       nua_callstate_calling);
   }
@@ -807,9 +809,9 @@
     sdp = NULL;
   }
   else if (cr->cr_offer_sent) {
-    /* case 1: incoming answer */
+    /* case 1: answer to our offer */
     cr->cr_answer_recv = status;
-    received = "answer";
+    received = Answer;
 
     if (nh->nh_soa == NULL)
       LOG5("got SDP");
@@ -836,9 +838,9 @@
     sdp = NULL;
   }
   else {
-    /* case 2: answer to our offer */
+    /* case 2: new offer */
     cr->cr_offer_recv = 1, cr->cr_answer_sent = 0;
-    received = "offer";
+    received = Offer;
 
     if (nh->nh_soa && soa_set_remote_sdp(nh->nh_soa, NULL, sdp, len) < 0) {
       LOG3("error parsing SDP");
@@ -868,12 +870,13 @@
 				    tagi_t const *tags)
 {
   nua_handle_t *nh = cr->cr_owner;
+  nua_dialog_state_t *ds = nh->nh_ds;
   nua_dialog_usage_t *du = cr->cr_usage;
   nua_session_usage_t *ss = nua_dialog_usage_private(du);
   unsigned next_state;
   int error;
 
-  nh_referral_respond(nh, status, phrase);
+  nh_referral_respond(nh, status, phrase); /* XXX - restarting after 401/407 */
 
   nua_stack_event(nh->nh_nua, nh, 
 		  nta_outgoing_getresponse(orq),
@@ -881,7 +884,8 @@
 		  status, phrase,
 		  tags);
 
-  if (orq != cr->cr_orq && status != 100)
+  if (cr->cr_waiting)
+    /* Do not report call state change if waiting for restart */
     return 1;
 
   if (ss == NULL) {
@@ -897,7 +901,10 @@
     return 1;
   }
 
-  if (status == 100) {
+  if (orq != cr->cr_orq && cr->cr_orq) {	/* Being restarted */
+    next_state = nua_callstate_calling;
+  }
+  else if (status == 100) {
     next_state = nua_callstate_calling;
   }
   else if (status < 300 && cr->cr_graceful) {
@@ -953,8 +960,16 @@
 	/* Auto-ACK response to re-INVITE unless auto_ack is set to 0 */
 	(ss->ss_state == nua_callstate_ready &&
 	 !NH_PISSET(nh, auto_ack))) {
+      nua_client_request_t *cru;
 
-      if (nua_invite_client_ack(cr, NULL) > 0)
+      for (cru = ds->ds_cr; cru; cru = cru->cr_next) {
+	if (cr != cru && cru->cr_offer_sent && !cru->cr_answer_recv)
+	  break;
+      }
+
+      if (cru)
+	/* A final response to UPDATE or PRACK with answer on its way? */;
+      else if (nua_invite_client_ack(cr, NULL) > 0)
 	next_state = nua_callstate_ready;
       else
 	next_state = nua_callstate_terminating;
@@ -1129,7 +1144,7 @@
     else if (cr->cr_offer_recv && !cr->cr_answer_sent) {
       if (nh->nh_soa == NULL) {
 	if (session_get_description(sip, NULL, NULL))
-	  cr->cr_answer_sent = 1, ss->ss_oa_sent = "answer";
+	  cr->cr_answer_sent = 1, ss->ss_oa_sent = Answer;
       }
       else if (soa_generate_answer(nh->nh_soa, NULL) < 0 ||
 	  session_include_description(nh->nh_soa, 1, msg, sip) < 0) {
@@ -1138,23 +1153,33 @@
 	/* reason = soa_error_as_sip_reason(nh->nh_soa); */
       }
       else {
-	cr->cr_answer_sent = 1, ss->ss_oa_sent = "answer";
+	cr->cr_answer_sent = 1, ss->ss_oa_sent = Answer;
       }
     }
 
     if (ss == NULL || ss->ss_state >= nua_callstate_ready || reason)
       ;
-    else if (nh->nh_soa
-	     ? soa_is_complete(nh->nh_soa)
-	     : !(cr->cr_offer_sent && !cr->cr_answer_recv)) {
-      /* signal that O/A round(s) is (are) complete */
-      if (nh->nh_soa) 
+    else if (nh->nh_soa && soa_is_complete(nh->nh_soa)) {
+      /* signal SOA that O/A round(s) is (are) complete */
 	soa_activate(nh->nh_soa, NULL);
     }
+    else if (nh->nh_soa == NULL && !(cr->cr_offer_sent && !cr->cr_answer_recv)) {
+      ;
+    }
     else {
-      /* No SDP answer -> terminate call */
-      status = 988, phrase = "Incomplete offer/answer";
-      reason = "SIP;cause=488;text=\"Incomplete offer/answer\"";
+      nua_client_request_t *cru;
+
+      /* Final response to UPDATE or PRACK may be on its way ... */
+      for (cru = ds->ds_cr; cru; cru = cru->cr_next) {
+	if (cr != cru && cru->cr_offer_sent && !cru->cr_answer_recv)
+	  break;
+      }
+
+      if (cru == NULL) {
+	/* No SDP answer -> terminate call */
+	status = 988, phrase = "Incomplete offer/answer";
+	reason = "SIP;cause=488;text=\"Incomplete offer/answer\"";
+      }
     }
     
     if ((ack = nta_outgoing_mcreate(nh->nh_nua->nua_nta, NULL, NULL, NULL,
@@ -1561,15 +1586,13 @@
     cr->cr_offer_sent = offer_sent;
     cr->cr_answer_sent = answer_sent;
 
-    if (!cr->cr_restarting) {
-      if (offer_sent) 
-	ss->ss_oa_sent = "offer";
-      else if (answer_sent)
-	ss->ss_oa_sent = "answer";
+    if (offer_sent) 
+      ss->ss_oa_sent = Offer;
+    else if (answer_sent)
+      ss->ss_oa_sent = Answer;
 
-      if (!ss->ss_reporting)
-	signal_call_state_change(nh, ss, status, phrase, ss->ss_state);
-    }
+    if (!cr->cr_restarting) /* Restart logic calls nua_prack_client_report */
+      signal_call_state_change(nh, ss, status, phrase, ss->ss_state);
   }
 
   return retval;
@@ -1591,7 +1614,8 @@
 				   tagi_t const *tags)
 {
   nua_handle_t *nh = cr->cr_owner;
-  nua_session_usage_t *ss = nua_dialog_usage_private(cr->cr_usage);
+  nua_dialog_usage_t *du = cr->cr_usage;
+  nua_session_usage_t *ss = nua_dialog_usage_private(du);
 
   nua_stack_event(nh->nh_nua, nh, 
 		  nta_outgoing_getresponse(orq),
@@ -1599,11 +1623,34 @@
 		  status, phrase,
 		  tags);
 
-  if (!ss || orq != cr->cr_orq || cr->cr_terminated || cr->cr_graceful)
+  if (!ss || cr->cr_terminated || cr->cr_graceful)
     return 1;
 
-  if (cr->cr_offer_sent)
-    signal_call_state_change(nh, ss, status, phrase, ss->ss_state);
+  if (cr->cr_waiting)
+    /* Do not report call state change if restarting later */
+    return 1;
+
+  if (cr->cr_offer_sent || cr->cr_answer_sent) {
+    unsigned next_state = ss->ss_state;
+
+    if (status < 200)
+      ;
+    else if (du->du_cr && du->du_cr->cr_orq && du->du_cr->cr_status >= 200) {
+      /* There is an un-ACK-ed INVITE there */
+      assert(du->du_cr->cr_method == sip_method_invite);
+      if (NH_PGET(nh, auto_ack) || 
+	  /* Auto-ACK response to re-INVITE unless auto_ack is set to 0 */
+	  (ss->ss_state == nua_callstate_ready && !NH_PISSET(nh, auto_ack))) {
+	/* No UPDATE with offer/answer if PRACK with offer/answer was ongoing! */
+	if (nua_invite_client_ack(du->du_cr, NULL) > 0)
+	  next_state = nua_callstate_ready;
+	else
+	  next_state = nua_callstate_terminating;
+      }
+    }
+
+    signal_call_state_change(nh, ss, status, phrase, next_state);
+  }
 
   if (ss->ss_update_needed && 200 <= status && status < 300 &&
       !SIP_IS_ALLOWED(NH_PGET(nh, appl_method), sip_method_update))
@@ -1904,7 +1951,7 @@
   ss = nua_dialog_usage_private(sr->sr_usage);
 
   if (sr->sr_offer_recv)
-    ss->ss_oa_recv = "offer";
+    ss->ss_oa_recv = Offer;
 
   ss->ss_100rel = NH_PGET(nh, early_media);
   ss->ss_precondition = sip_has_feature(request->sip_require, "precondition");
@@ -2049,9 +2096,9 @@
     if (nh->nh_soa && session_include_description(nh->nh_soa, 1, msg, sip) < 0)
       SR_STATUS1(sr, SIP_500_INTERNAL_SERVER_ERROR);
     else if (offer)
-      sr->sr_offer_sent = 1 + reliable, ss->ss_oa_sent = "offer";
+      sr->sr_offer_sent = 1 + reliable, ss->ss_oa_sent = Offer;
     else if (answer)
-      sr->sr_answer_sent = 1 + reliable, ss->ss_oa_sent = "answer";
+      sr->sr_answer_sent = 1 + reliable, ss->ss_oa_sent = Answer;
   }
 
   if (reliable && sr->sr_status < 200) {
@@ -2236,7 +2283,7 @@
     int error;
 
     if (session_get_description(sip, &sdp, &len))
-      recv = "answer";
+      recv = Answer;
 
     if (recv) {
       assert(ss->ss_oa_recv == NULL);
@@ -2474,14 +2521,14 @@
     /* XXX - check for overlap? */
     
     if (sri->sr_offer_sent)
-      sr->sr_answer_recv = 1, ss->ss_oa_recv = "answer";
+      sr->sr_answer_recv = 1, ss->ss_oa_recv = Answer;
     else 
-      sr->sr_offer_recv = 1, ss->ss_oa_recv = "offer";
+      sr->sr_offer_recv = 1, ss->ss_oa_recv = Offer;
 
     if (nh->nh_soa &&
 	soa_set_remote_sdp(nh->nh_soa, NULL, sr->sr_sdp, sr->sr_sdp_len) < 0) {
       SU_DEBUG_5(("nua(%p): %s server: error parsing %s\n", (void *)nh,
-		  "PRACK", "offer"));
+		  "PRACK", Offer));
       return 
 	sr->sr_status = soa_error_as_sip_response(nh->nh_soa, &sr->sr_phrase);
     }
@@ -2504,21 +2551,21 @@
 
     if (nh->nh_soa == NULL) {
       if (sr->sr_offer_recv && session_get_description(sip, NULL, NULL))
-	sr->sr_answer_sent = 1, ss->ss_oa_sent = "answer";
+	sr->sr_answer_sent = 1, ss->ss_oa_sent = Answer;
     }
     else if ((sr->sr_offer_recv && soa_generate_answer(nh->nh_soa, NULL) < 0) ||
 	     (sr->sr_answer_recv && soa_process_answer(nh->nh_soa, NULL) < 0)) {
       SU_DEBUG_5(("nua(%p): %s server: %s %s\n", 
 		  (void *)nh, "PRACK", 
 		  "error processing",
-		  sr->sr_offer_recv ? "offer" : "answer"));
+		  sr->sr_offer_recv ? Offer : Answer));
       sr->sr_status = soa_error_as_sip_response(nh->nh_soa, &sr->sr_phrase);
     }
     else if (sr->sr_offer_recv) {
       if (session_include_description(nh->nh_soa, 1, msg, sip) < 0)
 	sr_status(sr, SIP_500_INTERNAL_SERVER_ERROR);
       else
-      sr->sr_answer_sent = 1, ss->ss_oa_sent = "answer";
+      sr->sr_answer_sent = 1, ss->ss_oa_sent = Answer;
     }
   }
 
@@ -2976,20 +3023,18 @@
   retval = nua_base_client_request(cr, msg, sip, NULL);
 
   if (retval == 0) {
+    enum nua_callstate state = ss->ss_state;
     cr->cr_offer_sent = offer_sent;
     ss->ss_update_needed = 0;
 
-    if (!cr->cr_restarting) {
-      enum nua_callstate state = ss->ss_state;
-
-      if (state == nua_callstate_ready)
-	state = nua_callstate_calling;
+    if (state == nua_callstate_ready)
+      state = nua_callstate_calling; /* XXX */
 
-      if (offer_sent)
-	ss->ss_oa_sent = "offer";
+    if (offer_sent)
+      ss->ss_oa_sent = Offer;
 
+    if (!cr->cr_restarting)	/* Restart logic calls nua_update_client_report */
       signal_call_state_change(nh, ss, 0, "UPDATE sent", state);
-    }
   }
 
   return retval;
@@ -3057,6 +3102,7 @@
   nua_handle_t *nh = cr->cr_owner;
   nua_dialog_usage_t *du = cr->cr_usage;
   nua_session_usage_t *ss = nua_dialog_usage_private(du);
+  unsigned next_state = ss->ss_state;
 
   nua_stack_event(nh->nh_nua, nh, 
 		  nta_outgoing_getresponse(orq),
@@ -3064,11 +3110,32 @@
 		  status, phrase,
 		  tags);
 
-  if (!ss || orq != cr->cr_orq || 
-      cr->cr_terminated || cr->cr_graceful || !cr->cr_offer_sent)
+  if (!ss || cr->cr_terminated || cr->cr_graceful)
     return 1;
 
-  signal_call_state_change(nh, ss, status, phrase, ss->ss_state);
+  if (cr->cr_waiting)
+    /* Do not report call state change if restarting later */
+    return 1;
+
+  if (cr->cr_offer_sent) {
+    if (status < 200)
+      ;
+    else if (du->du_cr && du->du_cr->cr_orq && du->du_cr->cr_status >= 200) {
+      /* There is an un-ACK-ed INVITE there */
+      assert(du->du_cr->cr_method == sip_method_invite);
+
+      if (NH_PGET(nh, auto_ack) || 
+	  /* Auto-ACK response to re-INVITE unless auto_ack is set to 0 */
+	  (ss->ss_state == nua_callstate_ready && !NH_PISSET(nh, auto_ack))) {
+	if (nua_invite_client_ack(du->du_cr, NULL) > 0)
+	  next_state = nua_callstate_ready;
+	else
+	  next_state = nua_callstate_terminating;
+      }
+    }
+
+    signal_call_state_change(nh, ss, status, phrase, next_state);
+  }
 
   return 1;
 }
@@ -3148,13 +3215,13 @@
     if (nh->nh_soa &&
 	soa_set_remote_sdp(nh->nh_soa, NULL, sr->sr_sdp, sr->sr_sdp_len) < 0) {
       SU_DEBUG_5(("nua(%p): %s server: error parsing %s\n", (void *)nh,
-		  "UPDATE", "offer"));
+		  "UPDATE", Offer));
       return 
 	sr->sr_status = soa_error_as_sip_response(nh->nh_soa, &sr->sr_phrase);
     }
 
     sr->sr_offer_recv = 1;
-    ss->ss_oa_recv = "offer";
+    ss->ss_oa_recv = Offer;
   }
 
   return 0;
@@ -3172,11 +3239,11 @@
 
   if (200 <= sr->sr_status && sr->sr_status < 300 && sr->sr_sdp) {
     if (nh->nh_soa == NULL) {
-      sr->sr_answer_sent = 1, ss->ss_oa_sent = "answer";
+      sr->sr_answer_sent = 1, ss->ss_oa_sent = Answer;
     }
     else if (soa_generate_answer(nh->nh_soa, NULL) < 0) {
       SU_DEBUG_5(("nua(%p): %s server: %s %s\n", 
-		  (void *)nh, "UPDATE", "error processing", "offer"));
+		  (void *)nh, "UPDATE", "error processing", Offer));
       sr->sr_status = soa_error_as_sip_response(nh->nh_soa, &sr->sr_phrase);
     }
     else if (soa_activate(nh->nh_soa, NULL) < 0) {
@@ -3188,7 +3255,7 @@
       sr_status(sr, SIP_500_INTERNAL_SERVER_ERROR);
     }
     else {
-      sr->sr_answer_sent = 1, ss->ss_oa_sent = "answer";
+      sr->sr_answer_sent = 1, ss->ss_oa_sent = Answer;
     }
   }
 
@@ -3639,14 +3706,17 @@
     oa_recv = ss->ss_oa_recv, ss->ss_oa_recv = NULL;
     oa_sent = ss->ss_oa_sent, ss->ss_oa_sent = NULL;
 
+    assert(oa_sent == Offer || oa_sent == Answer || oa_sent == NULL);
+    assert(oa_recv == Offer || oa_recv == Answer || oa_recv == NULL);
+
     if (oa_recv) {
-      offer_recv = strcasecmp(oa_recv, "offer") == 0;
-      answer_recv = strcasecmp(oa_recv, "answer") == 0;
+      offer_recv = oa_recv == Offer;
+      answer_recv = oa_recv == Answer;
     }
 
     if (oa_sent) {
-      offer_sent = strcasecmp(oa_sent, "offer") == 0;
-      answer_sent = strcasecmp(oa_sent, "answer") == 0;
+      offer_sent = oa_sent == Offer;
+      answer_sent = oa_sent == Answer;
     }
   }
 
@@ -4061,7 +4131,7 @@
     if (t->interval >= 90)
       low -=5, high += 5;
 
-    nua_dialog_usage_refresh_range(du, low, high);
+    nua_dialog_usage_set_refresh_range(du, low, high);
     t->timer_set = 1;
   }
   else if (t->refresher == nua_remote_refresher) {
@@ -4074,7 +4144,7 @@
 
     interval -= 32 > interval / 6 ? interval / 3 : 32 + interval / 3;
 
-    nua_dialog_usage_refresh_range(du, interval, interval);
+    nua_dialog_usage_set_refresh_range(du, interval, interval);
     t->timer_set = 1;
   }
   else {

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_stack.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_stack.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_stack.c	Mon Sep 10 16:45:25 2007
@@ -46,6 +46,7 @@
 
 #define SU_ROOT_MAGIC_T   struct nua_s
 #define SU_MSG_ARG_T      struct event_s
+#define SU_TIMER_ARG_T    struct nua_client_request
 
 #define NUA_SAVED_EVENT_T su_msg_t *
 
@@ -271,9 +272,7 @@
   if ((event > nua_r_authenticate && event <= nua_r_ack)
       || event < nua_i_error
       || (nh && !nh->nh_valid)
-	  /* disable hiding all events after shutdown so that we can get state callbacks to properly tear down our calls */
-      /* || (nua->nua_shutdown && event != nua_r_shutdown) */
-	  ) {
+      || (nua->nua_shutdown && event != nua_r_shutdown)) {
     if (msg)
       msg_destroy(msg);
     return event;
@@ -486,7 +485,8 @@
   su_timer_set(t, nua_stack_timer, a);
 
   if (nua->nua_shutdown) {
-	  nua_stack_shutdown(nua);
+    nua_stack_shutdown(nua);
+    return;
   }
 
   for (nh = nua->nua_handles; nh; nh = nh_next) {
@@ -605,21 +605,10 @@
 
   for (nh = nua->nua_handles; nh; nh = nh_next) {
     nua_dialog_state_t *ds = nh->nh_ds;
-    nua_server_request_t *sr, *sr_next;
 
     nh_next = nh->nh_next;
 
-    for (sr = ds->ds_sr; sr; sr = sr_next) {
-      sr_next = sr->sr_next;
-
-      if (nua_server_request_is_pending(sr)) {
-	SR_STATUS1(sr, SIP_410_GONE); /* 410 terminates dialog */
-	nua_server_respond(sr, NULL);
-	nua_server_report(sr);
-      }
-    }
-
-    busy += nh_call_pending(nh, 0);
+    busy += nua_dialog_repeat_shutdown(nh, ds);
 
     if (nh->nh_soa) {
       soa_destroy(nh->nh_soa), nh->nh_soa = NULL;
@@ -992,6 +981,7 @@
 
   if (status > 0) {
     if (cr && cr->cr_wait_for_cred) {
+      cr->cr_waiting = cr->cr_wait_for_cred = 0;
       nua_client_restart_request(cr, cr->cr_terminating, tags);
     }
     else {
@@ -1001,8 +991,8 @@
     }
   }
   else if (cr && cr->cr_wait_for_cred) {
-    cr->cr_wait_for_cred = 0;
-
+    cr->cr_waiting = cr->cr_wait_for_cred = 0;
+    
     if (status < 0)
       nua_client_response(cr, 900, "Cannot add credentials", NULL);
     else
@@ -1439,9 +1429,10 @@
 int nua_server_respond(nua_server_request_t *sr, tagi_t const *tags)
 {
   nua_handle_t *nh = sr->sr_owner;
+  nua_dialog_state_t *ds = nh->nh_ds;
   sip_method_t method = sr->sr_method;
   struct { msg_t *msg; sip_t *sip; } next = { NULL, NULL };
-  int retval;
+  int retval, user_contact = 1;
 #if HAVE_OPEN_C
   /* Nice. And old arm symbian compiler; see below. */
   tagi_t next_tags[2];
@@ -1496,15 +1487,26 @@
 	   sip_add_dup(msg, sip, (void *)NH_PGET(nh, allow_events)) < 0)
     ;
   else if (!sip->sip_contact && sr->sr_status < 300 && sr->sr_add_contact &&
-	   nua_registration_add_contact_to_response(nh, msg, sip, NULL, m) < 0)
+	   (user_contact = 0,
+	    ds->ds_ltarget 
+	    ? sip_add_dup(msg, sip, (sip_header_t *)ds->ds_ltarget) 
+	    : nua_registration_add_contact_to_response(nh, msg, sip, NULL, m))
+	   < 0)
     ;
   else {
     int term;
-    
+    sip_contact_t *ltarget = NULL;
+
     term = sip_response_terminates_dialog(sr->sr_status, sr->sr_method, NULL);
 
     sr->sr_terminating = (term < 0) ? -1 : (term > 0 || sr->sr_terminating);
 
+    if (sr->sr_target_refresh && sr->sr_status < 300 && !sr->sr_terminating && 
+	user_contact && sip->sip_contact) {
+      /* Save Contact given by application */
+      ltarget = sip_contact_dup(nh->nh_home, sip->sip_contact);
+    }
+
     retval = sr->sr_methods->sm_respond(sr, next_tags);
 
     if (sr->sr_status < 200) 
@@ -1514,6 +1516,16 @@
 
     assert(sr->sr_status >= 200 || sr->sr_response.msg);
 
+    if (ltarget) {
+      if (sr->sr_status < 300) {
+	nua_dialog_state_t *ds = nh->nh_ds;
+	msg_header_free(nh->nh_home, (msg_header_t *)ds->ds_ltarget);
+	ds->ds_ltarget = ltarget;	
+      }
+      else 
+	msg_header_free(nh->nh_home, (msg_header_t *)ltarget);
+    }
+
     return retval;
   }
 
@@ -1714,6 +1726,9 @@
 static int nua_client_request_try(nua_client_request_t *cr);
 static int nua_client_request_sendmsg(nua_client_request_t *cr,
   				      msg_t *msg, sip_t *sip);
+static void nua_client_restart_after(su_root_magic_t *magic,
+				     su_timer_t *timer,
+				     nua_client_request_t *cr);
 
 /**Create a client request.
  *
@@ -1860,6 +1875,9 @@
 
   cr->cr_orq = NULL;
 
+  if (cr->cr_timer)
+    su_timer_destroy(cr->cr_timer), cr->cr_timer = NULL;
+
   if (cr->cr_target)
     su_free(nh->nh_home, cr->cr_target);
 
@@ -2255,9 +2273,20 @@
    * registrar is also added to the request message.
    */
   if (cr->cr_method != sip_method_register) {
+    if (cr->cr_contactize && cr->cr_has_contact) {
+      sip_contact_t *ltarget = sip_contact_dup(nh->nh_home, sip->sip_contact);
+      if (ds->ds_ltarget) 
+	msg_header_free(nh->nh_home, (msg_header_t *)ds->ds_ltarget);
+      ds->ds_ltarget = ltarget;
+    }
+
+    if (ds->ds_ltarget && !cr->cr_has_contact)
+      sip_add_dup(msg, sip, (sip_header_t *)ds->ds_ltarget);
+
     if (nua_registration_add_contact_to_request(nh, msg, sip,
 						cr->cr_contactize &&
-						!cr->cr_has_contact,
+						!cr->cr_has_contact &&
+						!ds->ds_ltarget,
 						!ds->ds_route) < 0)
       return -1;
   }
@@ -2443,7 +2472,7 @@
 
 /** Check if request should be restarted.
  *
- * @retval 1 if restarted or waring for restart
+ * @retval 1 if restarted or waiting for restart
  * @retval 0 otherwise
  */
 int nua_client_check_restart(nua_client_request_t *cr,
@@ -2470,8 +2499,7 @@
 				  sip_t const *sip)
 {
   nua_handle_t *nh = cr->cr_owner; 
-
-  /* XXX - handle Retry-After */
+  nta_outgoing_t *orq;
 
   if (status == 302 || status == 305) {
     sip_route_t r[1];
@@ -2520,7 +2548,6 @@
   if ((status == 401 && sip->sip_www_authenticate) ||
       (status == 407 && sip->sip_proxy_authenticate)) {
     int server = 0, proxy = 0;
-    nta_outgoing_t *orq;
 
     if (sip->sip_www_authenticate)
       server = auc_challenge(&nh->nh_auth, nh->nh_home,
@@ -2544,7 +2571,8 @@
 	return nua_client_restart(cr, 100, "Request Authorized by Cache");
 
       orq = cr->cr_orq, cr->cr_orq = NULL;
-      cr->cr_wait_for_cred = 1;
+
+      cr->cr_waiting = cr->cr_wait_for_cred = 1;
       nua_client_report(cr, status, phrase, NULL, orq, NULL);
       nta_outgoing_destroy(orq);
 
@@ -2552,9 +2580,44 @@
     }
   }
 
+  if (500 <= status && status < 600 && 
+      sip->sip_retry_after && 
+      sip->sip_retry_after->af_delta < 32) {
+    char phrase[18];		/* Retry-After: XXXX\0 */
+
+    if (cr->cr_timer == NULL)
+      cr->cr_timer = su_timer_create(su_root_task(nh->nh_nua->nua_root), 0);
+
+    if (su_timer_set_interval(cr->cr_timer, nua_client_restart_after, cr,
+			      sip->sip_retry_after->af_delta * 1000) < 0)
+      return 0; /* Too bad */
+
+    snprintf(phrase, sizeof phrase, "Retry After %u", 
+	     (unsigned)sip->sip_retry_after->af_delta);
+
+    orq = cr->cr_orq, cr->cr_orq = NULL;
+    cr->cr_waiting = cr->cr_wait_for_timer = 1;
+    nua_client_report(cr, 100, phrase, NULL, orq, NULL);
+    nta_outgoing_destroy(orq);
+    return 1;
+  }
+
   return 0;  /* This was a final response that cannot be restarted. */
 }
 
+/** Request restarted by timer */
+static
+void nua_client_restart_after(su_root_magic_t *magic,
+			      su_timer_t *timer,
+			      nua_client_request_t *cr) 
+{
+  if (!cr->cr_wait_for_timer)
+    return;
+
+  cr->cr_waiting = cr->cr_wait_for_timer = 0;
+  nua_client_restart_request(cr, cr->cr_terminating, NULL);
+}
+
 /** Restart request.
  *
  * @retval 1 if restarted
@@ -2587,7 +2650,6 @@
       msg_destroy(msg);
   }
 
-
   if (error) {
     cr->cr_graceful = graceful;
     cr->cr_terminated = terminated;

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_subnotref.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_subnotref.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/nua_subnotref.c	Mon Sep 10 16:45:25 2007
@@ -389,7 +389,7 @@
       if (eu->eu_substate == nua_substate_terminated)
 	eu->eu_substate = nua_substate_embryonic;
 
-      nua_dialog_usage_refresh_range(du, delta, delta);
+      nua_dialog_usage_set_refresh_range(du, delta, delta);
     }
     else {
       eu->eu_substate = nua_substate_terminated;
@@ -635,6 +635,7 @@
   sip_t const *sip = sr->sr_request.sip;
   enum nua_substate substate = nua_substate_terminated;
   sip_time_t delta = SIP_TIME_MAX;
+  sip_event_t const *o = sip->sip_event;
   int retry = -1;
   int retval;
 
@@ -670,6 +671,7 @@
   
   retval = nua_base_server_treport(sr, /* can destroy sr */
 				   NUTAG_SUBSTATE(substate),
+				   SIPTAG_EVENT(o),
 				   TAG_NEXT(tags)); 
 
   if (retval != 1 || du == NULL)
@@ -681,7 +683,7 @@
   else if (retry >= 0) {		/* Try to subscribe again */
     /* XXX - this needs through testing */
     nua_dialog_remove(nh, nh->nh_ds, du); /* tear down */
-    nua_dialog_usage_refresh_range(du, retry, retry + 5);
+    nua_dialog_usage_set_refresh_range(du, retry, retry + 5);
   }
   else {
     nua_dialog_usage_set_refresh(du, delta);

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_100rel.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_100rel.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_100rel.c	Mon Sep 10 16:45:25 2007
@@ -196,7 +196,7 @@
   ei = event_by_type(e->next, nua_r_invite);
   ep = event_by_type(e->next, nua_r_prack);
   if (!ep) {
-    run_a_until(ctx, -1, until_final_response);
+    run_a_until(ctx, -1, save_until_final_response);
     ep = event_by_type(e->next, nua_r_prack);
   }
 
@@ -332,10 +332,9 @@
   struct event *e, *ep, *ei;
   sip_t *sip;
   sip_proxy_authenticate_t *au;
-  char const *md5 = NULL, *md5sess = NULL;
 
   if (print_headings)
-    printf("TEST NUA-10.1.3: Call with 100rel and 180\n");
+    printf("TEST NUA-10.1.3: Call with 100rel, PRACK is challenged\n");
 
 /* Test for authentication during 100rel
 
@@ -396,10 +395,6 @@
   TEST(e->data->e_status, 407);
   TEST_1(sip = sip_object(e->data->e_msg));
   TEST_1(au = sip->sip_proxy_authenticate); 
-  TEST_1(auth_get_params(NULL, au->au_params, 
-			 "algorithm=md5", &md5,
-			 "algorithm=md5-sess", &md5sess,
-			 NULL) > 0);
 
   TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
   TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
@@ -415,24 +410,15 @@
 
   ei = event_by_type(e->next, nua_r_invite);
   ep = event_by_type(e->next, nua_r_prack);
-  if (!ep) {
-    run_a_until(ctx, -1, until_final_response);
-    ep = event_by_type(e->next, nua_r_prack);
-  }
 
   TEST_1(e = ep); TEST_E(e->data->e_event, nua_r_prack);
-  if (e->data->e_status != 200 && md5 && !md5sess) {
-    if (e->data->e_status != 100) {
-      TEST(e->data->e_status, 407);
-    }
-
-    TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
-    TEST(callstate(e->data->e_tags), nua_callstate_proceeding);
-    TEST_1(!is_answer_recv(e->data->e_tags));
-    TEST_1(!is_offer_sent(e->data->e_tags));
-
-    TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_prack);
+  if (e->data->e_status == 100 || e->data->e_status == 407) {
+    /* The final response to PRACK may be received after ACK is sent */
+    if (!event_by_type(e->next, nua_r_prack))
+      run_bc_until(ctx, -1, save_events, -1, save_until_final_response);
+    TEST_1(e = ep = event_by_type(e->next, nua_r_prack));
   }
+  TEST_E(e->data->e_event, nua_r_prack);
   TEST(e->data->e_status, 200);
 
   TEST_1(e = ei); TEST_E(e->data->e_event, nua_r_invite);
@@ -441,7 +427,7 @@
   TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
   TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
   TEST_1(!is_offer_answer_done(e->data->e_tags));
-  TEST_1(!e->next || !ep->next || (ep->data->e_status != 200 && !e->next->next->next));
+  TEST_1(!e->next || !ep->next);
   free_events_in_list(ctx, c->events);
 
   /*
@@ -626,7 +612,7 @@
   ei = event_by_type(e->next, nua_r_invite);
   ep = event_by_type(e->next, nua_r_prack);
   if (!ep) {
-    run_a_until(ctx, -1, until_final_response);
+    run_a_until(ctx, -1, save_until_final_response);
     ep = event_by_type(e->next, nua_r_prack);
   }
 
@@ -1024,7 +1010,7 @@
   ei = event_by_type(e->next, nua_r_invite);
   ep = event_by_type(e->next, nua_r_prack);
   if (!ep) {
-    run_a_until(ctx, -1, until_final_response);
+    run_a_until(ctx, -1, save_until_final_response);
     ep = event_by_type(e->next, nua_r_prack);
   }
 
@@ -1213,6 +1199,7 @@
   struct endpoint *a = &ctx->a,  *b = &ctx->b;
   struct call *a_call = a->call, *b_call = b->call;
   struct event *e, *eu, *ei;
+  enum nua_callstate ustate, istate;
 
   if (print_headings)
     printf("TEST NUA-10.4.1: Call with preconditions and non-100rel 180\n");
@@ -1227,15 +1214,23 @@
    |-------PRACK------->|
    |<-------200---------|
    |			|
-   |<-------180---------|
-   |			|
-   |<------200 OK-------|
+   |-------UPDATE------>|
+ +------------------------+
+ | |<-------200---------| |
+ | |			| |
+ | |<-------180---------| |
+ | |			| |
+ | |<------200 OK-------| |
+ +------------------------+
    |--------ACK-------->|
    |			|
    |<-------BYE---------|
    |-------200 OK-------|
    |			|
 
+   Note that the boxed responses above can be re-ordered 
+   (180 or 200 OK to INVITE is received before 200 OK to UPDATE).
+   ACK, however, is sent only after 200 OK to both UPDATE and INVITE. 
 */
 
   a_call->sdp = "m=audio 5008 RTP/AVP 8";
@@ -1280,6 +1275,7 @@
   TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
   TEST(callstate(e->data->e_tags), nua_callstate_proceeding);
   TEST_1(is_answer_recv(e->data->e_tags));
+  /* Offer is sent in PRACK */
   TEST_1(is_offer_sent(e->data->e_tags));
 
   TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_prack);
@@ -1296,13 +1292,14 @@
   TEST_1(!is_answer_recv(e->data->e_tags));
   TEST_1(is_offer_sent(e->data->e_tags));
 
+  /* The final response to the UPDATE and INVITE can be received in any order */
   eu = event_by_type(e->next, nua_r_update);
   ei = event_by_type(e->next, nua_r_invite);
 
   TEST_1(e = eu); TEST_E(e->data->e_event, nua_r_update);
   TEST(e->data->e_status, 200);
   TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
-  TEST(callstate(e->data->e_tags), nua_callstate_proceeding);
+  ustate = callstate(e->data->e_tags);
   TEST_1(is_answer_recv(e->data->e_tags));
   TEST_1(!is_offer_sent(e->data->e_tags));
 
@@ -1312,14 +1309,25 @@
   TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
   TEST_1(!is_offer_answer_done(e->data->e_tags));
 
-  TEST_1(e = eu->next->next == ei ? ei->next->next : eu->next->next);
+  /* Final response to INVITE  */
+  TEST_1(ei = event_by_type(ei->next, nua_r_invite));
 
-  TEST_E(e->data->e_event, nua_r_invite); TEST(e->data->e_status, 200);
-
-  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
-  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_E(ei->data->e_event, nua_r_invite); TEST(ei->data->e_status, 200);
+  TEST_1(e = ei->next); TEST_E(e->data->e_event, nua_i_state);
+  istate = callstate(e->data->e_tags);
   TEST_1(!is_offer_answer_done(e->data->e_tags));
-  TEST_1(!e->next);
+
+  if (eu == e->next) {
+    /* 200 OK to UPDATE is received after 200 OK to INVITE */
+    TEST(ustate, nua_callstate_ready);
+    TEST(istate, nua_callstate_completing);
+  }
+  else {
+    /* 200 OK to UPDATE is received before 200 OK to INVITE */
+    TEST(ustate, nua_callstate_proceeding);
+    TEST(istate, nua_callstate_ready);
+  }
+
   free_events_in_list(ctx, a->events);
 
   /*
@@ -1499,7 +1507,7 @@
   if (print_headings)
     printf("TEST NUA-10.5.1: Call with dual UPDATE\n");
 
-/* Test for precondition:
+/* Test for update by UAS.
 
    A			B
    |-------INVITE------>|
@@ -1510,9 +1518,11 @@
    |<-------200---------|
    |			|
    |-------UPDATE------>|
-   |<-------200---------|
-   |			|
-   |<------UPDATE-------|
+ +------------------------+
+ | |<-------200---------| |
+ | |			| |
+ | |<------UPDATE-------| |
+ +------------------------+
    |--------200-------->|
    |			|
    |<-------180---------|
@@ -1526,6 +1536,9 @@
    |-------200 OK-------|
    |			|
 
+ Note that the 200 OK to UPDATE from A and UPDATE from B may be re-ordered
+ In that case, A will respond with 500/Retry-After and B will retry UPDATE.
+ See do {} while () loop below.
 */
 
   a_call->sdp = "m=audio 5008 RTP/AVP 8";
@@ -1612,7 +1625,7 @@
   ei = event_by_type(e->next, nua_r_invite);
   ep = event_by_type(e->next, nua_r_prack);
   if (!ep) {
-    run_a_until(ctx, -1, until_final_response);
+    run_a_until(ctx, -1, save_until_final_response);
     ep = event_by_type(e->next, nua_r_prack);
   }
 
@@ -1672,17 +1685,24 @@
   TEST_1(is_answer_sent(e->data->e_tags));
 
   /* sent UPDATE */
-  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
-  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
-  TEST_1(is_offer_sent(e->data->e_tags)); 
-  TEST_1(!is_offer_recv(e->data->e_tags));
-  TEST_1(!is_answer_sent(e->data->e_tags));
-  TEST_1(!is_answer_recv(e->data->e_tags));
+  do {
+    TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+    TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+    TEST_1(is_offer_sent(e->data->e_tags)); 
+    TEST_1(!is_offer_recv(e->data->e_tags));
+    TEST_1(!is_answer_sent(e->data->e_tags));
+    TEST_1(!is_answer_recv(e->data->e_tags));
 
-  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_update);
+    TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_update);
+    if (e->data->e_status == 100) {
+      TEST_1(sip = sip_object(e->data->e_msg));
+      TEST(sip->sip_status->st_status, 500); TEST_1(sip->sip_retry_after);
+    }
+  } while (e->data->e_status == 100);
+  TEST(e->data->e_status, 200);
   TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
   TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
-  TEST_1(!is_offer_sent(e->data->e_tags)); 
+  TEST_1(!is_offer_sent(e->data->e_tags)); /* XXX */
   TEST_1(!is_offer_recv(e->data->e_tags));
   TEST_1(!is_answer_sent(e->data->e_tags));
   TEST_1(is_answer_recv(e->data->e_tags));
@@ -2060,7 +2080,7 @@
 
 int test_100rel(struct context *ctx)
 {
-  int retval;
+  int retval = 0;
   
   retval = test_180rel(ctx); RETURN_ON_SINGLE_FAILURE(retval);
   retval = test_prack_auth(ctx); RETURN_ON_SINGLE_FAILURE(retval);

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_basic_call.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_basic_call.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_basic_call.c	Mon Sep 10 16:45:25 2007
@@ -1356,14 +1356,262 @@
   }
 }
 
+/* Basic call and re-INVITE with user-specified Contact:
+
+   A			B
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |			|
+   |<----180 Ringing----|
+   |			|
+   |<------200 OK-------|
+   |--------ACK-------->|
+   |			|
+   |-----re-INVITE----->|
+   |<------200 OK-------|
+   |--------ACK-------->|
+   |			|
+   |<-------BYE---------|
+   |-------200 OK------>|
+   |			|
+
+   Client transitions:
+   INIT -(C1)-> CALLING -(C2a)-> PROCEEDING -(C3+C4)-> READY
+   Server transitions:
+   INIT -(S1)-> RECEIVED -(S2a)-> EARLY -(S3b)-> COMPLETED -(S4)-> READY
+
+   Both client and server save Contact from nua_invite() and nua_respond(),
+   respectively.
+
+   INIT -(C1)-> CALLING -(C3a+C4)-> READY
+   INIT -(S3c)-> COMPLETED -(S4)-> READY
+
+   Both client and server use saved Contact.
+
+   B sends BYE:
+   READY -(T2)-> TERMINATING -(T3)-> TERMINATED
+   A receives BYE:
+   READY -(T1)-> TERMINATED
+
+   See @page nua_call_model in nua.docs for more information
+*/
+
+static sip_contact_t *contact_for_b;
+
+int accept_call_with_contact(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_received:
+    RESPOND(ep, call, nh, SIP_180_RINGING, 
+	    TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)),
+	    SIPTAG_CONTACT(contact_for_b),
+	    TAG_END());
+    return 0;
+  case nua_callstate_early:
+    RESPOND(ep, call, nh, SIP_200_OK,
+	    TAG_IF(call->sdp, SOATAG_USER_SDP_STR(call->sdp)),
+	    SIPTAG_CONTACT(contact_for_b),
+	    TAG_END());
+    return 0;
+  case nua_callstate_ready:
+    return 1;
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+int test_basic_call_6(struct context *ctx)
+{
+  BEGIN();
+
+  struct endpoint *a = &ctx->a,  *b = &ctx->b;
+  struct call *a_call = a->call, *b_call = b->call;
+  struct event *e;
+  sip_t *sip;
+
+  sip_contact_t ma[1], mb[1];
+
+  if (print_headings)
+    printf("TEST NUA-3.1: Basic call\n");
+
+  a_call->sdp = "m=audio 5008 RTP/AVP 8";
+  b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  TEST_1(!nua_handle_has_active_call(a_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(a_call->nh));
+
+  *ma = *a->contact;
+  ma->m_display = "Alice B.";
+  ma->m_url->url_user = "a++a";
+
+  *mb = *b->contact;
+  mb->m_display = "Bob A.";
+  mb->m_url->url_user = "b++b";
+
+  contact_for_b = mb;
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 SIPTAG_CONTACT(ma),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, until_ready, -1, accept_call_with_contact);
+
+  TEST_1(nua_handle_has_active_call(a_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(a_call->nh));
+
+  TEST_1(nua_handle_has_active_call(b_call->nh));
+  TEST_1(!nua_handle_has_call_on_hold(b_call->nh));
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state
+     CALLING -(C2)-> PROCEEDING: nua_r_invite, nua_i_state
+     PROCEEDING -(C3+C4)-> READY: nua_r_invite, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_payload);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+  TEST_1(is_answer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_payload);
+  TEST_1(sip->sip_contact);
+  TEST_S(sip->sip_contact->m_display, "Bob A.");
+  TEST_S(sip->sip_contact->m_url->url_user, "b++b");
+  /* Test that B uses application-specific contact */
+  TEST_1(sip->sip_contact->m_url->url_user);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S2a)-> EARLY: nua_respond(), nua_i_state
+   EARLY -(S3b)-> COMPLETED: nua_respond(), nua_i_state
+   COMPLETED -(S4)-> READY: nua_i_ack, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_contact);
+  TEST_S(sip->sip_contact->m_display, "Alice B.");
+  TEST_S(sip->sip_contact->m_url->url_user, "a++a");
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  /* re-INVITE */
+  INVITE(a, a_call, a_call->nh, TAG_END());
+  run_ab_until(ctx, -1, until_ready, -1, until_ready);
+
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_contact);
+  TEST_S(sip->sip_contact->m_display, "Bob A.");
+  TEST_S(sip->sip_contact->m_url->url_user, "b++b");
+  /* Test that B uses application-specific contact */
+  TEST_1(sip->sip_contact->m_url->url_user);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+  
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_contact);
+  TEST_S(sip->sip_contact->m_display, "Alice B.");
+  TEST_S(sip->sip_contact->m_url->url_user, "a++a");
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_ack);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  BYE(b, b_call, b_call->nh, TAG_END());
+  run_ab_until(ctx, -1, until_terminated, -1, until_terminated);
+
+  /* B transitions:
+   READY --(T2)--> TERMINATING: nua_bye()
+   TERMINATING --(T3)--> TERMINATED: nua_r_bye, nua_i_state
+  */
+  TEST_1(e = b->events->head);  TEST_E(e->data->e_event, nua_r_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, b->events);
+
+  TEST_1(!nua_handle_has_active_call(b_call->nh));
+
+  /* A transitions:
+     READY -(T1)-> TERMINATED: nua_i_bye, nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_bye);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+  free_events_in_list(ctx, a->events);
+
+  TEST_1(!nua_handle_has_active_call(a_call->nh));
+
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-3.1: PASSED\n");
+
+  END();
+}
+
 int test_basic_call(struct context *ctx)
 {
-  return
-    test_basic_call_1(ctx)
+  return 0
+    || test_basic_call_1(ctx)
     || test_basic_call_2(ctx)
     || test_basic_call_3(ctx)
     || test_basic_call_4(ctx)
     || test_basic_call_5(ctx)
+    || test_basic_call_6(ctx)
     || test_video_call_1(ctx)
     ;
 }

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_call_reject.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_call_reject.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_call_reject.c	Mon Sep 10 16:45:25 2007
@@ -262,6 +262,7 @@
 /* ------------------------------------------------------------------------ */
 
 int reject_302(CONDITION_PARAMS), reject_305(CONDITION_PARAMS);
+int reject_500_retry_after(CONDITION_PARAMS);
 int redirect_always(CONDITION_PARAMS);
 int reject_604(CONDITION_PARAMS);
 
@@ -270,12 +271,21 @@
  |                    |
  |-------INVITE------>|
  |<----100 Trying-----|
- |                    |
  |<-----302 Other-----|
  |--------ACK-------->|
  |                    |
  |-------INVITE------>|
  |<----100 Trying-----|
+ |<--305 Use Proxy----|
+ |--------ACK-------->|
+ |                    |
+ |-------INVITE------>|
+ |<----100 Trying-----|
+ |<----500 Retry------|
+ |--------ACK-------->|
+ |                    |
+ |-------INVITE------>|
+ |<----100 Trying-----|
  |                    |
  |<----180 Ringing----|
  |                    |
@@ -330,29 +340,33 @@
   case nua_callstate_terminated:
     if (call)
       nua_handle_destroy(call->nh), call->nh = NULL;
-    ep->next_condition = reject_604;
+    ep->next_condition = reject_500_retry_after;
     return 0;
   default:
     return 0;
   }
 }
 
-int redirect_always(CONDITION_PARAMS)
+int reject_500_retry_after(CONDITION_PARAMS)
 {
   if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
     return 0;
 
+  save_event_in_list(ctx, event, ep, call);
+
   if (event == nua_i_invite) {
-    char user[30];
-    sip_contact_t m[1];
-    *m = *ep->contact;
-    snprintf(user, sizeof user, "user-%u", ep->flags.n++);
-    m->m_url->url_user = user;
-    RESPOND(ep, call, nh, SIP_302_MOVED_TEMPORARILY,
-	    SIPTAG_CONTACT(m), TAG_END());
-    nua_handle_destroy(nh);
-    call->nh = NULL;
-    return 1;
+    sip_retry_after_t af[1];
+    sip_retry_after_init(af)->af_delta = 1;
+    RESPOND(ep, call, nh, 500, "Retry After", SIPTAG_RETRY_AFTER(af), TAG_END());
+  }
+  else if (event == nua_i_state) switch (callstate(tags)) {
+  case nua_callstate_terminated:
+    if (call)
+      nua_handle_destroy(call->nh), call->nh = NULL;
+    ep->next_condition = reject_604;
+    break;
+  default:
+    break;
   }
 
   return 0;
@@ -381,6 +395,28 @@
   }
 }
 
+int redirect_always(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  if (event == nua_i_invite) {
+    char user[30];
+    sip_contact_t m[1];
+    *m = *ep->contact;
+    snprintf(user, sizeof user, "user-%u", ep->flags.n++);
+    m->m_url->url_user = user;
+    RESPOND(ep, call, nh, SIP_302_MOVED_TEMPORARILY,
+	    SIPTAG_CONTACT(m), TAG_END());
+    nua_handle_destroy(nh);
+    call->nh = NULL;
+    return 1;
+  }
+
+  return 0;
+}
+
+
 int test_reject_302(struct context *ctx)
 {
   BEGIN();
@@ -407,18 +443,29 @@
 
   /*
    A      reject-3      B
-   |			|
+   |                    |
    |-------INVITE------>|
    |<----100 Trying-----|
-   |			|
+   |                    |
    |<-----302 Other-----|
    |--------ACK-------->|
-   |			|
+   |                    |
    |-------INVITE------>|
    |<----100 Trying-----|
-   |			|
+   |<---305 Use Proxy---|
+   |--------ACK-------->|
+   |                    |
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |<-----500 Retry-----|
+   |--------ACK-------->|
+   |                    |
+   |                    |
+   |-------INVITE------>|
+   |<----100 Trying-----|
+   |                    |
    |<----180 Ringing----|
-   |			|
+   |                    |
    |<---604 Nowhere-----|
    |--------ACK-------->|
   */
@@ -445,6 +492,12 @@
   TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
   TEST_1(is_offer_sent(e->data->e_tags));
   TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 100);
+  TEST(sip_object(e->data->e_msg)->sip_status->st_status, 500);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
   TEST(e->data->e_status, 180);
   TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
   TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
@@ -475,6 +528,13 @@
   TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
   TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_invite);
   TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
   TEST_1(sip = sip_object(e->data->e_msg));
   TEST_1(sip->sip_request);
   TEST_S(sip->sip_request->rq_url->url_user, "302");
@@ -1430,8 +1490,8 @@
  |<--------200--------|
  |--ACK-X             |
  |                    |
- |---------BYE------->|
- |<-------200 OK------|
+ |<--------BYE--------|
+ |--------200 OK----->|
 
   */
 
@@ -1484,6 +1544,109 @@
   if (print_headings)
     printf("TEST NUA-4.7.3: PASSED\n");
 
+  if (!ctx->nat)
+    goto completed_4_7_4;
+
+  if (print_headings)
+    printf("TEST NUA-4.7.4: 200 OK timeout after client has timed out\n");
+
+  if (ctx->expensive)
+    nua_set_params(b->nua, NTATAG_SIP_T1X64(34000), TAG_END());
+  else
+    nua_set_params(b->nua, NTATAG_SIP_T1X64(4000), TAG_END());
+  run_b_until(ctx, nua_r_set_params, until_final_response);
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  TEST_1(f = test_nat_add_filter(ctx->nat, filter_ACK, NULL, nat_outbound));
+  
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SIPTAG_SUBJECT_STR("NUA-4.7.4"),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 TAG_END());
+  run_ab_until(ctx, -1, until_terminated, -1, accept_call);
+
+  /*
+ A     accept_call    B
+ |                    |
+ |-------INVITE------>|
+ |<----100 Trying-----|
+ |                    |
+ |<----180 Ringing----|
+ |                    |
+ |<--------200--------| Timer H'
+ |--------ACK-----X   X--+
+ |                    |  |
+ |<--------200--------|  |
+ |--------ACK-----X   |  |
+ |                    |  |
+ |<--------200--------|  |
+ |                    |  |
+ |<--------200--------|  |
+ |                    |  |
+ |                    |<-+
+ |<--------BYE--------|
+ |--------200 OK----->|
+
+  */
+
+  /*
+  */
+
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 180);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); 
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_ready); /* READY */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); 
+  TEST_1(!e->next);
+
+  /*
+   Server transitions:
+   -(S1)-> RECEIVED -(S2a)-> EARLY -(S3b)-> COMPLETED -(S5)-> TERMINATING
+   -(S10)-> TERMINATED -X
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_early); /* EARLY */
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_completed); /* COMPLETED */
+  TEST_1(is_answer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_error);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminating); 
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_bye);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  TEST_1(test_nat_remove_filter(ctx->nat, f) == 0);
+
+  if (print_headings)
+    printf("TEST NUA-4.7.4: PASSED\n");
+
+  nua_set_params(b->nua, NTATAG_SIP_T1X64(2000), TAG_END());
+  run_b_until(ctx, nua_r_set_params, until_final_response);
+
+ completed_4_7_4:
+
   /* XXX - PRACK timeout, PRACK failing, media failing, re-INVITEs */
 
   if (print_headings)

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_cancel_bye.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_cancel_bye.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_cancel_bye.c	Mon Sep 10 16:45:25 2007
@@ -109,6 +109,24 @@
   }
 }
 
+int bye_when_calling(CONDITION_PARAMS)
+{
+  if (!(check_handle(ep, call, nh, SIP_500_INTERNAL_SERVER_ERROR)))
+    return 0;
+
+  save_event_in_list(ctx, event, ep, call);
+
+  switch (callstate(tags)) {
+  case nua_callstate_calling:
+    BYE(ep, call, nh, TAG_END());
+    return 0;
+  case nua_callstate_terminated:
+    return 1;
+  default:
+    return 0;
+  }
+}
+
 
 int cancel_when_ringing(CONDITION_PARAMS)
 {
@@ -163,7 +181,7 @@
   b_call->sdp = "m=audio 5010 RTP/AVP 0 8";
 
   if (print_headings)
-    printf("TEST NUA-5.1: cancel call\n");
+    printf("TEST NUA-5.1.1: cancel call\n");
 
   TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
 
@@ -212,11 +230,66 @@
   nua_handle_destroy(b_call->nh), b_call->nh = NULL;
 
   if (print_headings)
-    printf("TEST NUA-5.1: PASSED\n");
+    printf("TEST NUA-5.1.1: PASSED\n");
 
   /* ------------------------------------------------------------------------ */
 
   if (print_headings)
+    printf("TEST NUA-5.1.2: cancel call (with nua_bye())\n");
+
+  TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
+
+  INVITE(a, a_call, a_call->nh,
+	 TAG_IF(!ctx->proxy_tests, NUTAG_URL(b->contact->m_url)),
+	 SIPTAG_SUBJECT_STR("TEST NUA-5.1.2"),
+	 SOATAG_USER_SDP_STR(a_call->sdp),
+	 TAG_END());
+
+  run_ab_until(ctx, -1, bye_when_calling, -1, until_terminated);
+
+  /* Client transitions:
+     INIT -(C1)-> CALLING: nua_invite(), nua_i_state, nua_cancel()
+     CALLING -(C6a)-> TERMINATED: nua_r_invite(487), nua_i_state
+  */
+  TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
+  TEST_1(is_offer_sent(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_bye);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
+  TEST(e->data->e_status, 487);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  /*
+   Server transitions:
+   INIT -(S1)-> RECEIVED: nua_i_invite, nua_i_state
+   RECEIVED -(S6a)--> TERMINATED: nua_i_cancel, nua_i_state
+  */
+  TEST_1(e = b->events->head); TEST_E(e->data->e_event, nua_i_invite);
+  TEST(e->data->e_status, 100);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_received); /* RECEIVED */
+  TEST_1(is_offer_recv(e->data->e_tags));
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_cancel);
+  TEST(e->data->e_status, 200);
+  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+  TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, a->events);
+  nua_handle_destroy(a_call->nh), a_call->nh = NULL;
+
+  free_events_in_list(ctx, b->events);
+  nua_handle_destroy(b_call->nh), b_call->nh = NULL;
+
+  if (print_headings)
+    printf("TEST NUA-5.1.2: PASSED\n");
+
+ /* ----------------------------------------------------------------------- */
+ 
+  if (print_headings)
     printf("TEST NUA-5.2.1: cancel call when ringing\n");
 
   TEST_1(a_call->nh = nua_handle(a->nua, a_call, SIPTAG_TO(b->to), TAG_END()));
@@ -679,20 +752,24 @@
   TEST_1(e = a->events->head); TEST_E(e->data->e_event, nua_i_state);
   TEST(callstate(e->data->e_tags), nua_callstate_calling); /* CALLING */
   TEST_1(is_offer_sent(e->data->e_tags));
-  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
-  TEST(e->data->e_status, 180);
-  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
-  TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
-  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_r_invite);
-  TEST(e->data->e_status, 200);
-  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
-  TEST(callstate(e->data->e_tags), nua_callstate_completing); /* COMPLETING */
-  TEST_1(is_answer_recv(e->data->e_tags));
-  TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_bye);
+  TEST_1(e = e->next); if (e->data->e_event == nua_r_invite) {
+    TEST_E(e->data->e_event, nua_r_invite);
+    TEST(e->data->e_status, 180);
+    TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+    TEST(callstate(e->data->e_tags), nua_callstate_proceeding); /* PROCEEDING */
+    TEST_1(e = e->next); 
+  }
+  if (e->data->e_event == nua_r_invite) {
+    TEST_E(e->data->e_event, nua_r_invite);
+    TEST(e->data->e_status, 200);
+    TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
+    TEST(callstate(e->data->e_tags), nua_callstate_completing); /* COMPLETING */
+    TEST_1(is_answer_recv(e->data->e_tags));
+    TEST_1(e = e->next); 
+  }
+  TEST_E(e->data->e_event, nua_i_bye);
   TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
   TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
-  TEST_1(!e->next);
-  TEST_1(!e->next);
 
   free_events_in_list(ctx, a->events);
   nua_handle_destroy(a_call->nh), a_call->nh = NULL;
@@ -1445,8 +1522,6 @@
   TEST_1(e = b->events->head);  TEST_E(e->data->e_event, nua_r_bye);
   TEST_1(e = e->next); TEST_E(e->data->e_event, nua_i_state);
   TEST(callstate(e->data->e_tags), nua_callstate_terminated); /* TERMINATED */
-  TEST_1(!e->next);
-  free_events_in_list(ctx, b->events);
 
   TEST_1(!nua_handle_has_active_call(b_call->nh));
   TEST_1(nua_handle_has_active_call(a_call->nh));
@@ -1457,19 +1532,21 @@
   if (print_headings)
     printf("TEST NUA-6.4.3: PASSED\n");
 
-  if (!ctx->p)
+  if (!ctx->p) {
+    free_events_in_list(ctx, b->events);
     return 0;
+  }
 
   if (print_headings)
     printf("TEST NUA-6.4.4: Wait for re-REGISTER after connection has been closed\n");
 
-  /* B is supposed to re-register pretty soon, wait for re-registration */
-
-  run_b_until(ctx, -1, save_until_final_response);
+  if (!e->next || (!e->next->next || !e->next->data->e_status != 200))
+    /* B is supposed to re-register pretty soon, wait for re-registration */
+    run_b_until(ctx, -1, save_until_final_response);
 
   seen_401 = 0;
 
-  for (e = b->events->head; e; e = e->next) {
+  for (e = e->next; e; e = e->next) {
     TEST_E(e->data->e_event, nua_r_register);
     TEST_1(sip = sip_object(e->data->e_msg));
 

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_init.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_init.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_init.c	Mon Sep 10 16:45:25 2007
@@ -36,6 +36,7 @@
 #include "test_nua.h"
 
 #include <sofia-sip/tport_tag.h>
+#include <sofia-sip/auth_module.h>
 
 #if HAVE_FUNC
 #elif HAVE_FUNCTION
@@ -107,17 +108,52 @@
     TEST_1(close(temp) == 0);
 
     ctx->p = test_proxy_create(ctx->root,
-			       AUTHTAG_METHOD("Digest"),
-			       AUTHTAG_REALM("test-proxy"),
-			       AUTHTAG_OPAQUE("kuik"),
-			       AUTHTAG_DB(passwd_name),
-			       AUTHTAG_QOP("auth-int"),
-			       AUTHTAG_ALGORITHM("md5"),
-			       AUTHTAG_NEXT_EXPIRES(60),
 			       TAG_IF(ctx->proxy_logging, TPTAG_LOG(1)),
 			       TAG_END());
 
-    ctx->proxy_tests = ctx->p != NULL;
+    if (ctx->p) {
+      ctx->a.domain = 
+	test_proxy_add_domain(ctx->p,
+			      URL_STRING_MAKE("sip:example.com")->us_url,
+			      AUTHTAG_METHOD("Digest"),
+			      AUTHTAG_REALM("test-proxy"),
+			      AUTHTAG_OPAQUE("kuik"),
+			      AUTHTAG_DB(passwd_name),
+			      AUTHTAG_QOP("auth-int"),
+			      AUTHTAG_ALGORITHM("md5"),
+			      AUTHTAG_NEXT_EXPIRES(60),
+			      TAG_END());
+
+      ctx->b.domain = 
+	test_proxy_add_domain(ctx->p,
+			      URL_STRING_MAKE("sip:example.org")->us_url,
+			      AUTHTAG_METHOD("Digest"),
+			      AUTHTAG_REALM("test-proxy"),
+			      AUTHTAG_OPAQUE("kuik"),
+			      AUTHTAG_DB(passwd_name),
+			      AUTHTAG_QOP("auth-int"),
+			      AUTHTAG_ALGORITHM("md5"),
+			      AUTHTAG_NEXT_EXPIRES(60),
+			      TAG_END());
+
+      test_proxy_domain_set_outbound(ctx->b.domain, 1);
+
+      ctx->c.domain = 
+	test_proxy_add_domain(ctx->p,
+			      URL_STRING_MAKE("sip:example.net")->us_url,
+			      AUTHTAG_METHOD("Digest"),
+			      AUTHTAG_REALM("test-proxy"),
+			      AUTHTAG_OPAQUE("kuik"),
+			      AUTHTAG_DB(passwd_name),
+			      AUTHTAG_QOP("auth-int"),
+			      AUTHTAG_ALGORITHM("md5"),
+			      AUTHTAG_NEXT_EXPIRES(60),
+			      AUTHTAG_MAX_NCOUNT(1),
+			      TAG_END());
+
+      ctx->proxy_tests = 1;
+    }
+
 
     if (print_headings)
       printf("TEST NUA-2.1.1: PASSED\n");
@@ -276,6 +312,7 @@
 			  NUTAG_INSTANCE(ctx->b.instance),
 			  /* Quicker timeout */
 			  NTATAG_SIP_T1X64(2000),
+			  TPTAG_KEEPALIVE(100),
 			  TAG_IF(ctx->b.logging, TPTAG_LOG(1)),
 			  TAG_END());
   TEST_1(ctx->b.nua);

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_nua.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_nua.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_nua.c	Mon Sep 10 16:45:25 2007
@@ -65,6 +65,8 @@
 static RETSIGTYPE sig_alarm(int s)
 {
   fprintf(stderr, "%s: FAIL! test timeout!\n", name);
+  if (tstflags & tst_abort)
+    abort();
   exit(1);
 }
 #endif

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_nua.h
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_nua.h	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_nua.h	Mon Sep 10 16:45:25 2007
@@ -139,6 +139,7 @@
 
     int running;
 
+    struct domain *domain;
     condition_function *next_condition;
     nua_event_t next_event, last_event;
     nua_t *nua;

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_ops.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_ops.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_ops.c	Mon Sep 10 16:45:25 2007
@@ -146,47 +146,59 @@
 		 tagi_t tags[])
 {
   tagi_t const *t;
+  static su_nanotime_t started = 0;
+  su_nanotime_t now;
+  char timestamp[32];
+
+  su_nanotime(&now);
+
+  if (started == 0) started = now;
+
+  now -= started; now /= 1000000;
+
+  snprintf(timestamp, sizeof timestamp, "%03u.%03u",
+	   (unsigned)(now / 1000), (unsigned)(now % 1000));
 
   if (event == nua_i_state) {
-    fprintf(stderr, "%s.nua(%p): event %s %s\n",
+    fprintf(stderr, "%s %s.nua(%p): event %s %s\n", timestamp,
 	    ep->name, (void *)nh, nua_event_name(event),
 	    nua_callstate_name(callstate(tags)));
   }
   else if ((int)event >= nua_r_set_params) {
     t = tl_find(tags, nutag_substate);
     if (t) {
-      fprintf(stderr, "%s.nua(%p): event %s status %u %s (%s)\n",
+      fprintf(stderr, "%s %s.nua(%p): event %s status %u %s (%s)\n", timestamp,
 	      ep->name, (void*)nh, nua_event_name(event), status, phrase,
 	      nua_substate_name(t->t_value));
     }
     else {
-      fprintf(stderr, "%s.nua(%p): event %s status %u %s\n",
+      fprintf(stderr, "%s %s.nua(%p): event %s status %u %s\n", timestamp,
 	      ep->name, (void *)nh, nua_event_name(event), status, phrase);
     }
   }
   else if (event == nua_i_notify) {
     t = tl_find(tags, nutag_substate);
-    fprintf(stderr, "%s.nua(%p): event %s %s (%s)\n",
+    fprintf(stderr, "%s %s.nua(%p): event %s %s (%s)\n", timestamp,
 	    ep->name, (void *)nh, nua_event_name(event), phrase,
 	    nua_substate_name(t ? t->t_value : 0));
   }
   else if ((int)event >= 0) {
-    fprintf(stderr, "%s.nua(%p): event %s %s\n",
+    fprintf(stderr, "%s %s.nua(%p): event %s %s\n", timestamp,
 	    ep->name, (void *)nh, nua_event_name(event), phrase);
   }
   else if (status > 0) {
-    fprintf(stderr, "%s.nua(%p): call %s() with status %u %s\n",
+    fprintf(stderr, "%s %s.nua(%p): call %s() with status %u %s\n", timestamp,
 	    ep->name, (void *)nh, operation, status, phrase);
   }
   else {
     t = tl_find(tags, siptag_subject_str);
     if (t && t->t_value) {
       char const *subject = (char const *)t->t_value;
-      fprintf(stderr, "%s.nua(%p): call %s() \"%s\"\n",
+      fprintf(stderr, "%s %s.nua(%p): call %s() \"%s\"\n", timestamp,
 	      ep->name, (void *)nh, operation, subject);
     }
     else
-      fprintf(stderr, "%s.nua(%p): call %s()\n",
+      fprintf(stderr, "%s %s.nua(%p): call %s()\n", timestamp,
 	      ep->name, (void *)nh, operation);
   }
 
@@ -292,7 +304,7 @@
   memset(&c->flags, 0, sizeof c->flags);
 
   for (; a->running || b->running || c->running;) {
-    su_root_step(ctx->root, 1000);
+    su_root_step(ctx->root, 100);
   }
 }
 

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_proxy.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_proxy.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_proxy.c	Mon Sep 10 16:45:25 2007
@@ -35,14 +35,17 @@
 #include <string.h>
 
 struct proxy;
-struct proxy_transaction;
+struct domain;
+union proxy_or_domain;
+struct proxy_tr;
+struct client_tr;
 struct registration_entry;
 struct binding;
 
 #define SU_ROOT_MAGIC_T struct proxy
-#define NTA_LEG_MAGIC_T struct proxy
-#define NTA_OUTGOING_MAGIC_T struct proxy_transaction
-#define NTA_INCOMING_MAGIC_T struct proxy_transaction
+#define NTA_LEG_MAGIC_T union proxy_or_domain
+#define NTA_OUTGOING_MAGIC_T struct client_tr
+#define NTA_INCOMING_MAGIC_T struct proxy_tr
 
 #include <sofia-sip/su_wait.h>
 #include <sofia-sip/nta.h>
@@ -83,50 +86,74 @@
 }							 \
 extern int LIST_DUMMY_VARIABLE
 
-#include "test_proxy.h"
+#include <test_proxy.h>
+#include <sofia-sip/auth_module.h>
 
 struct proxy {
   su_home_t    home[1];
+  void        *magic;
   su_root_t   *parent;
   su_clone_r   clone;
   tagi_t      *tags;
 
   su_root_t   *root;
-  auth_mod_t  *auth;
  
   nta_agent_t *agent;
   url_t const *uri;
-  
-  nta_leg_t *defleg;
+  url_t const *rr_uri;
 
-  nta_leg_t *example_net;
-  nta_leg_t *example_org;
-  nta_leg_t *example_com;
+  nta_leg_t *defleg;
 
   sip_contact_t *transport_contacts;
 
-  struct proxy_transaction *stateless;
-  struct proxy_transaction *transactions;
-  struct registration_entry *entries;
+  struct proxy_tr *stateless;
+  struct proxy_tr *transactions;
+
+  struct domain *domains;
 
   struct {
-    sip_time_t min_expires, expires, max_expires;
-    
     sip_time_t session_expires, min_se;
-
-    int outbound_tcp;		/**< Use inbound TCP connection as outbound */
   } prefs;
 }; 
 
+struct domain {
+  su_home_t home[1];
+  void *magic;
+  struct proxy *proxy;
+  struct domain *next, **prev;
+
+  url_t *uri;
+
+  nta_leg_t *rleg, *uleg;
+
+  auth_mod_t *auth;
+  struct registration_entry *entries;
+
+  struct {
+    sip_time_t min_expires, expires, max_expires;
+    int outbound_tcp;		/**< Use inbound TCP connection as outbound */
+    int authorize;
+  } prefs;  
+
+  tagi_t *tags;
+};
+
+LIST_PROTOS(static, domain, struct domain);
+static int _domain_init(void *_d);
+static int  domain_init(struct domain *domain);
+static void domain_destroy(struct domain *domain);
+
+LIST_BODIES(static, domain, struct domain, next, prev);
+
 LIST_PROTOS(static, registration_entry, struct registration_entry);
-static struct registration_entry *registration_entry_new(struct proxy *,
+static struct registration_entry *registration_entry_new(struct domain *,
 							 url_t const *);
 static void registration_entry_destroy(struct registration_entry *e);
 
 struct registration_entry
 {
   struct registration_entry *next, **prev;
-  struct proxy *proxy;		/* backpointer */
+  struct domain *domain;	/* backpointer */
   url_t *aor;			/* address-of-record */
   struct binding *bindings;	/* list of bindings */
   sip_contact_t *contacts;
@@ -145,7 +172,7 @@
 static struct binding *binding_new(su_home_t *home, 
 				   sip_contact_t *contact,
 				   tport_t *tport,
-				   sip_call_id_t *call_id,
+				   sip_call_id_t const *call_id,
 				   uint32_t cseq,
 				   sip_time_t registered, 
 				   sip_time_t expires);
@@ -157,51 +184,75 @@
     (b->tport == NULL || tport_is_clear_to_send(b->tport));
 }
 
-LIST_PROTOS(static, proxy_transaction, struct proxy_transaction);
-struct proxy_transaction *proxy_transaction_new(struct proxy *);
-static void proxy_transaction_destroy(struct proxy_transaction *t);
+LIST_PROTOS(static, proxy_tr, struct proxy_tr);
+struct proxy_tr *proxy_tr_new(struct proxy *);
+static void proxy_tr_destroy(struct proxy_tr *t);
 
-struct proxy_transaction
+struct proxy_tr
 {
-  struct proxy_transaction *next, **prev;
+  struct proxy_tr *next, **prev;
 
   struct proxy *proxy;		/* backpointer */
-  sip_request_t *rq;		/* request line */
+
+  struct domain *origin;	/* originating domain */
+  struct domain *domain;	/* destination domain */
+
+  sip_time_t now;		/* when received */
+
   nta_incoming_t *server;	/* server transaction */
-  nta_outgoing_t *client;	/* client transaction */
+  msg_t *msg;			/* request message */
+  sip_t *sip;			/* request headers */
+
+  sip_method_t method;		/* request method */
+  int status;			/* best status */
+  url_t *target;		/* request-URI */
+
+  struct client_tr *clients;	/* client transactions */
+
+  struct registration_entry *entry;
+				/* Registration entry */
+
+  auth_mod_t *am;		/* Authentication module */
+  auth_status_t *as;		/* Authentication status */
+  unsigned use_auth;		/* Authentication method (401/407) to use */
+
+  unsigned rr:1;
 };
 
+LIST_PROTOS(static, client_tr, struct client_tr);
+
+struct client_tr
+{
+  struct client_tr *next, **prev;
+  struct proxy_tr *t;
+
+  int status;			/* response status */
+  sip_request_t *rq;		/* request line */
+  msg_t *msg;			/* request message */
+  sip_t *sip;			/* request headers */
+  nta_outgoing_t *client;	/* transaction */
+};
+
+LIST_BODIES(static, client_tr, struct client_tr, next, prev);
+
 static sip_contact_t *create_transport_contacts(struct proxy *p);
 
-static int proxy_request(struct proxy *proxy,
+union proxy_or_domain { struct proxy proxy[1]; struct domain domain[1]; };
+
+static int proxy_request(union proxy_or_domain *proxy,
 			 nta_leg_t *leg,
 			 nta_incoming_t *irq,
 			 sip_t const *sip);
 
-static int proxy_ack_cancel(struct proxy_transaction *t,
-			    nta_incoming_t *irq,
-			    sip_t const *sip);
-
-static int proxy_response(struct proxy_transaction *t,
-			  nta_outgoing_t *client,
-			  sip_t const *sip);
-
-static int process_register(struct proxy *proxy,
-			    nta_incoming_t *irq,
-			    sip_t const *sip);
-
-static int domain_request(struct proxy *proxy,
+static int domain_request(union proxy_or_domain *domain,
 			  nta_leg_t *leg,
 			  nta_incoming_t *irq,
 			  sip_t const *sip);
 
-static int process_options(struct proxy *proxy,
-			   nta_incoming_t *irq,
+static int proxy_response(struct client_tr *client,
+			   nta_outgoing_t *orq,
 			   sip_t const *sip);
 
-static struct registration_entry *
-registration_entry_find(struct proxy const *proxy, url_t const *uri);
-
 static int close_tports(void *proxy);
 
 static auth_challenger_t registrar_challenger[1];
@@ -211,7 +262,8 @@
 static int 
 test_proxy_init(su_root_t *root, struct proxy *proxy)
 {
-  struct proxy_transaction *t;
+  struct proxy_tr *t;
+  struct client_tr *c;
 
   auth_challenger_t _proxy_challenger[1] = 
   {{ 
@@ -232,8 +284,6 @@
 
   proxy->root = root;
 
-  proxy->auth = auth_mod_create(root, TAG_NEXT(proxy->tags));
-
   proxy->agent = nta_agent_create(root,
 				  URL_STRING_MAKE("sip:0.0.0.0:*"),
 				  NULL, NULL,
@@ -247,53 +297,32 @@
 
   proxy->defleg = nta_leg_tcreate(proxy->agent,
 				  proxy_request,
-				  proxy,
+				  (union proxy_or_domain *)proxy,
 				  NTATAG_NO_DIALOG(1),
 				  TAG_END());
 
-  proxy->example_net = nta_leg_tcreate(proxy->agent,
-				       domain_request,
-				       proxy,
-				       NTATAG_NO_DIALOG(1),
-				       URLTAG_URL("sip:example.net"),
-				       TAG_END());
-  proxy->example_org = nta_leg_tcreate(proxy->agent,
-				       domain_request,
-				       proxy,
-				       NTATAG_NO_DIALOG(1),
-				       URLTAG_URL("sip:example.org"),
-				       TAG_END());
-  proxy->example_com = nta_leg_tcreate(proxy->agent,
-				       domain_request,
-				       proxy,
-				       NTATAG_NO_DIALOG(1),
-				       URLTAG_URL("sip:example.com"),
-				       TAG_END());
-
-  proxy->prefs.min_expires = 30;
-  proxy->prefs.expires = 3600;
-  proxy->prefs.max_expires = 3600;
-
   proxy->prefs.session_expires = 180;
   proxy->prefs.min_se = 90;
 
-  proxy->prefs.outbound_tcp = 1;
-
-  if (!proxy->defleg || 
-      !proxy->example_net || !proxy->example_org || !proxy->example_com)
+  if (!proxy->defleg) 
     return -1;
+  /* if (!proxy->example_net || !proxy->example_org || !proxy->example_com)
+     return -1; */
 
-  t = su_zalloc(proxy->home, sizeof *t); 
+  /* Create stateless client */
+  t = su_zalloc(proxy->home, sizeof *t);
+  c = su_zalloc(proxy->home, sizeof *c); 
 
-  if (!t)
+  if (!t || !c)
     return -1;
 
   proxy->stateless = t;
   t->proxy = proxy;
+  c->t = t, client_tr_insert(&t->clients, c);
   t->server = nta_incoming_default(proxy->agent);
-  t->client = nta_outgoing_default(proxy->agent, proxy_response, t);
+  c->client = nta_outgoing_default(proxy->agent, proxy_response, c);
 
-  if (!t->client || !t->server)
+  if (!c->client || !t->server)
     return -1;
 
   proxy->uri = nta_agent_contact(proxy->agent)->m_url;
@@ -304,17 +333,15 @@
 static void
 test_proxy_deinit(su_root_t *root, struct proxy *proxy)
 {
-  struct proxy_transaction *t;
-  
-  auth_mod_destroy(proxy->auth);
+  struct proxy_tr *t;
 
   if ((t = proxy->stateless)) {
-    nta_incoming_destroy(t->server), t->server = NULL;
-    nta_outgoing_destroy(t->client), t->client = NULL;
+    proxy->stateless = NULL;
+    proxy_tr_destroy(t);
   }
 
-  while (proxy->entries)
-    registration_entry_destroy(proxy->entries);
+  while (proxy->domains)
+    domain_destroy(proxy->domains);
 
   nta_agent_destroy(proxy->agent);
 
@@ -330,6 +357,8 @@
   if (p) {
     ta_list ta;
 
+    p->magic = test_proxy_create;
+
     p->parent = root;
 
     ta_start(ta, tag, value);
@@ -362,27 +391,27 @@
   return p ? p->uri : NULL;
 }
 
-void test_proxy_set_expiration(struct proxy *p,
-			       sip_time_t min_expires, 
-			       sip_time_t expires, 
-			       sip_time_t max_expires)
-{
-  if (p) {
-    p->prefs.min_expires = min_expires;
-    p->prefs.expires = expires;
-    p->prefs.max_expires = max_expires;
+void test_proxy_domain_set_expiration(struct domain *d,
+				      sip_time_t min_expires, 
+				      sip_time_t expires, 
+				      sip_time_t max_expires)
+{
+  if (d) {
+    d->prefs.min_expires = min_expires;
+    d->prefs.expires = expires;
+    d->prefs.max_expires = max_expires;
   }
 }
 
-void test_proxy_get_expiration(struct proxy *p,
-			       sip_time_t *return_min_expires,
-			       sip_time_t *return_expires,
-			       sip_time_t *return_max_expires)
-{
-  if (p) {
-    if (return_min_expires) *return_min_expires = p->prefs.min_expires;
-    if (return_expires) *return_expires = p->prefs.expires;
-    if (return_max_expires) *return_max_expires = p->prefs.max_expires;
+void test_proxy_domain_get_expiration(struct domain *d,
+				      sip_time_t *return_min_expires,
+				      sip_time_t *return_expires,
+				      sip_time_t *return_max_expires)
+{
+  if (d) {
+    if (return_min_expires) *return_min_expires = d->prefs.min_expires;
+    if (return_expires) *return_expires = d->prefs.expires;
+    if (return_max_expires) *return_max_expires = d->prefs.max_expires;
   }
 }
 
@@ -407,20 +436,36 @@
   }
 }
 
-void test_proxy_set_outbound(struct proxy *p,
-			     int use_outbound)
+void test_proxy_domain_set_outbound(struct domain *d,
+				    int use_outbound)
 {
-  if (p) {
-    p->prefs.outbound_tcp = use_outbound;
+  if (d) {
+    d->prefs.outbound_tcp = use_outbound;
   }
 }
 
-void test_proxy_get_outbound(struct proxy *p,
-			     int *return_use_outbound)
+void test_proxy_domain_get_outbound(struct domain *d,
+				    int *return_use_outbound)
 {
-  if (p) {
+  if (d) {
     if (return_use_outbound)
-      *return_use_outbound = p->prefs.outbound_tcp;
+      *return_use_outbound = d->prefs.outbound_tcp;
+  }
+}
+
+void test_proxy_domain_set_authorize(struct domain *d, int authorize)
+{
+  if (d) {
+    d->prefs.authorize = authorize;
+  }
+}
+
+void test_proxy_domain_get_authorize(struct domain *d,
+				     int *return_authorize)
+{
+  if (d) {
+    if (return_authorize)
+      *return_authorize = d->prefs.authorize;
   }
 }
 
@@ -441,6 +486,106 @@
 
 /* ---------------------------------------------------------------------- */
 
+struct domain *test_proxy_add_domain(struct proxy *p,
+				     url_t const *uri,
+				     tag_type_t tag, tag_value_t value, ...)
+{
+  struct domain *d;
+
+  if (p == NULL || uri == NULL)
+    return NULL;
+
+  d = su_home_clone(p->home, sizeof *d);
+
+  if (d) {
+    ta_list ta;
+    int init = 0;
+
+    ta_start(ta, tag, value);
+
+    d->magic = domain_init;
+
+    d->proxy = p;
+    d->uri = url_hdup(d->home, uri);
+    d->tags = tl_adup(d->home, ta_args(ta));
+
+    d->prefs.min_expires = 300;
+    d->prefs.expires = 3600;
+    d->prefs.max_expires = 36000;
+    d->prefs.outbound_tcp = 0;
+    d->prefs.authorize = 0;
+
+    if (d->uri && d->tags && 
+	!su_task_execute(su_clone_task(p->clone), _domain_init, d, &init)) {
+      if (init == 0)
+	/* OK */;
+      else
+	d = NULL;
+    }
+    else 
+      su_home_unref(d->home);
+  }
+
+  return d;
+}
+
+static int _domain_init(void *_d)
+{
+  return domain_init(_d);
+}
+
+static int domain_init(struct domain *d)
+{
+  struct proxy *p = d->proxy;
+  url_t uri[1];
+
+  *uri = *d->uri;
+
+  d->auth = auth_mod_create(p->root, TAG_NEXT(d->tags));
+
+  /* Leg for URIs without userpart */
+  d->rleg = nta_leg_tcreate(d->proxy->agent,
+			    domain_request,
+			    (union proxy_or_domain *)d,
+			    NTATAG_NO_DIALOG(1),
+			    URLTAG_URL(uri),
+			    TAG_END());
+
+  /* Leg for URIs with wildcard userpart */
+  uri->url_user = "%";
+  d->uleg = nta_leg_tcreate(d->proxy->agent,
+			    domain_request,
+			    (union proxy_or_domain *)d,
+			    NTATAG_NO_DIALOG(1),
+			    URLTAG_URL(uri),
+			    TAG_END());
+
+  if (d->auth && d->rleg && d->uleg) {
+    domain_insert(&p->domains, d);
+    return 0;
+  }
+
+  domain_destroy(d);
+
+  return -1;
+}
+
+static void domain_destroy(struct domain *d)
+{
+  while (d->entries)
+    registration_entry_destroy(d->entries);
+
+  nta_leg_destroy(d->rleg), d->rleg = NULL;
+  nta_leg_destroy(d->uleg), d->uleg = NULL;
+  auth_mod_destroy(d->auth), d->auth = NULL;
+
+  domain_remove(d);
+
+  su_home_unref(d->home);
+}
+
+/* ---------------------------------------------------------------------- */
+
 static sip_contact_t *create_transport_contacts(struct proxy *p)
 {
   su_home_t *home = p->home;
@@ -474,166 +619,332 @@
 
 /* ---------------------------------------------------------------------- */
 
-static int challenge_request(struct proxy *, nta_incoming_t *, sip_t const *);
+static int proxy_tr_with(struct proxy *proxy,
+			 struct domain *domain,
+			 nta_incoming_t *irq,
+			 sip_t const *sip,
+			 int (*process)(struct proxy_tr *));
+static int proxy_transaction(struct proxy_tr *t);
+static int respond_transaction(struct proxy_tr *t,
+			       int status, char const *phrase,
+			       tag_type_t tag, tag_value_t value,
+			       ...);
+static int validate_transaction(struct proxy_tr *t);
+static int originating_transaction(struct proxy_tr *t);
+static int challenge_transaction(struct proxy_tr *t);
+static int session_timers(struct proxy_tr *t);
+static int incoming_transaction(struct proxy_tr *t);
+static int target_transaction(struct proxy_tr *t,
+			      url_t const *target,
+			      tport_t *tport);
+static int process_register(struct proxy_tr *t);
+static int process_options(struct proxy_tr *t);
 
-/** Forward request */
-static
-int proxy_request(struct proxy *proxy,
-		  nta_leg_t *leg,
-		  nta_incoming_t *irq,
-		  sip_t const *sip)
-{
-  url_t const *request_uri, *target;
-  struct proxy_transaction *t = NULL;
-  sip_request_t *rq = NULL;
-  sip_max_forwards_t *mf;
+static int proxy_ack_cancel(struct proxy_tr *t,
+			    nta_incoming_t *irq,
+			    sip_t const *sip);
+
+static struct registration_entry *
+registration_entry_find(struct domain const *domain, url_t const *uri);
+
+static int proxy_request(union proxy_or_domain *pod,
+			 nta_leg_t *leg,
+			 nta_incoming_t *irq,
+			 sip_t const *sip)
+{
+  assert(pod->proxy->magic = test_proxy_init);
+
+  return proxy_tr_with(pod->proxy, NULL, irq, sip, proxy_transaction);
+}
+
+static int domain_request(union proxy_or_domain *pod,
+			  nta_leg_t *leg,
+			  nta_incoming_t *irq,
+			  sip_t const *sip)
+{
+  int (*process)(struct proxy_tr *) = NULL;
   sip_method_t method = sip->sip_request->rq_method;
 
-  sip_session_expires_t *x = NULL, x0[1];
-  sip_min_se_t *min_se = NULL, min_se0[1];
-  char const *require = NULL;
-  tport_t *tport = NULL;
+  assert(pod->domain->magic = domain_init);
+
+  if (leg == pod->domain->uleg)
+    process = proxy_transaction;
+  else if (method == sip_method_register)
+    process = process_register;
+  else if (method == sip_method_options) 
+    process = process_options;
+
+  if (process == NULL)
+    return 501;			/* Not implemented */
+
+  return proxy_tr_with(pod->domain->proxy, pod->domain, irq, sip, process);
+}
+
+static int proxy_tr_with(struct proxy *proxy,
+			 struct domain *domain,
+			 nta_incoming_t *irq,
+			 sip_t const *sip,
+			 int (*process)(struct proxy_tr *))
+{
+  struct proxy_tr *t = NULL;
+  int status = 500;
+
+  assert(proxy->magic = test_proxy_init);
+
+  t = proxy_tr_new(proxy);
+  if (t) {
+    t->proxy = proxy, t->domain = domain, t->server = irq;
+    t->msg = nta_incoming_getrequest(irq);
+    t->sip = sip_object(t->msg);
+
+    t->method = sip->sip_request->rq_method;
+    t->target = sip->sip_request->rq_url;
+    t->now = nta_incoming_received(irq, NULL);
+
+    if (t->method != sip_method_ack && t->method != sip_method_cancel)
+      nta_incoming_bind(irq, proxy_ack_cancel, t);
+
+    if (process(t) < 200)
+      return 0;
+
+    proxy_tr_destroy(t);
+  }
+  else {
+    nta_incoming_treply(irq, SIP_500_INTERNAL_SERVER_ERROR, TAG_END());
+  }
+
+  return status;
+}
+
+/** Forward request */
+static int proxy_transaction(struct proxy_tr *t)
+{
+  if (originating_transaction(t))
+    return t->status;
+
+  if (validate_transaction(t))
+    return t->status;
+
+  if (session_timers(t))
+    return t->status;
+
+  if (t->domain)
+    return incoming_transaction(t);
+
+  return target_transaction(t, t->target, NULL);
+}
+  
+static int respond_transaction(struct proxy_tr *t,
+			       int status, char const *phrase,
+			       tag_type_t tag, tag_value_t value,
+			       ...)
+{
+  ta_list ta;
+  void *info = NULL, *response = NULL;
+
+  ta_start(ta, tag, value);
+
+  if (t->as)
+    info = t->as->as_info, response = t->as->as_response;
+  
+  if (nta_incoming_treply(t->server, t->status = status, phrase, 
+			  SIPTAG_HEADER(info),
+			  SIPTAG_HEADER(response),
+			  ta_tags(ta)) < 0)
+    t->status = status = 500;
+
+  ta_end(ta);
+  
+  return status;
+}
+
+static int originating_transaction(struct proxy_tr *t)
+{
+  struct domain *o;
+  char const *host;
+
+  host = t->sip->sip_from->a_url->url_host;
+  if (!host)
+    return 0;
+
+  for (o = t->proxy->domains; o; o = o->next)
+    if (strcasecmp(host, o->uri->url_host) == 0)
+      break;
+
+  t->origin = o;
+
+  if (o && o->auth && o->prefs.authorize) {
+    t->am = o->auth;
+    t->use_auth = 407;
+  }
+
+  return 0;
+}
+
+static int validate_transaction(struct proxy_tr *t)
+{
+  sip_max_forwards_t *mf;
 
-  mf = sip->sip_max_forwards;
+  mf = t->sip->sip_max_forwards;
 
   if (mf && mf->mf_count <= 1) {
-    if (sip->sip_request->rq_method == sip_method_options) {
-      return process_options(proxy, irq, sip);
-    }
-    nta_incoming_treply(irq, SIP_483_TOO_MANY_HOPS, TAG_END());
-    return 483;
+    if (t->method == sip_method_options)
+      return process_options(t);
+
+    return respond_transaction(t, SIP_483_TOO_MANY_HOPS, TAG_END());
   }
 
-  if (method != sip_method_ack && method != sip_method_cancel && 
-      str0casecmp(sip->sip_from->a_url->url_host, "example.net") == 0) {
-    /* Challenge everything but CANCEL and ACK coming from Mr. C */
-    int status = challenge_request(proxy, irq, sip);
-    if (status)
-      return status;
+  /* Remove our routes */
+  while (t->sip->sip_route && 
+	 url_has_param(t->sip->sip_route->r_url, "lr") &&
+	 url_cmp(t->proxy->rr_uri, t->sip->sip_route->r_url) == 0) {
+    sip_route_remove(t->msg, t->sip);
+    /* add record-route also to the forwarded request  */
+    t->rr = 1;			
   }
 
-  if (method == sip_method_invite) {
-    if (proxy->prefs.min_se) {
+  if (t->use_auth)
+    return challenge_transaction(t);
+  
+  return 0;
+}
+
+static int session_timers(struct proxy_tr *t)
+{
+  sip_t *sip = t->sip;
+  sip_session_expires_t *x = NULL, x0[1];
+  sip_min_se_t *min_se = NULL, min_se0[1];
+  char const *require = NULL;
+
+  if (t->method == sip_method_invite) {
+    if (t->proxy->prefs.min_se) {
       if (!sip->sip_min_se || 
-	  sip->sip_min_se->min_delta < proxy->prefs.min_se) {
+	  sip->sip_min_se->min_delta < t->proxy->prefs.min_se) {
 	min_se = sip_min_se_init(min_se0);
-	min_se->min_delta = proxy->prefs.min_se;
+	min_se->min_delta = t->proxy->prefs.min_se;
       }
 
       if (sip->sip_session_expires
-	  && sip->sip_session_expires->x_delta < proxy->prefs.min_se
+	  && sip->sip_session_expires->x_delta < t->proxy->prefs.min_se
 	  && sip_has_supported(sip->sip_supported, "timer")) {
 	if (min_se == NULL)
 	  min_se = sip->sip_min_se; assert(min_se);
-	nta_incoming_treply(irq, SIP_422_SESSION_TIMER_TOO_SMALL,
-			    SIPTAG_MIN_SE(min_se),
-			    TAG_END());
-	return 422;
+	return respond_transaction(t, SIP_422_SESSION_TIMER_TOO_SMALL,
+				   SIPTAG_MIN_SE(min_se),
+				   TAG_END());
       }
     }
 
-    if (proxy->prefs.session_expires) {
+    if (t->proxy->prefs.session_expires) {
       if (!sip->sip_session_expires ||
-	  sip->sip_session_expires->x_delta > proxy->prefs.session_expires) {
+	  sip->sip_session_expires->x_delta > t->proxy->prefs.session_expires) {
 	x = sip_session_expires_init(x0);
-	x->x_delta = proxy->prefs.session_expires;
+	x->x_delta = t->proxy->prefs.session_expires;
 	if (!sip_has_supported(sip->sip_supported, "timer"))
 	  require = "timer";
       }
     }
-  }
-
-  /* We don't do any route processing */
-  request_uri = sip->sip_request->rq_url;
 
-  if (!request_uri->url_host || 
-      (strcasecmp(request_uri->url_host, "example.org") &&
-       strcasecmp(request_uri->url_host, "example.net") &&
-       strcasecmp(request_uri->url_host, "example.com"))) {
-    target = request_uri;
+    if (x || min_se || require)
+      sip_add_tl(t->msg, t->sip,
+		 SIPTAG_REQUIRE_STR(require),
+		 SIPTAG_MIN_SE(min_se),
+		 SIPTAG_SESSION_EXPIRES(x),
+		 TAG_END());
   }
-  else {
-    struct registration_entry *e;
-    struct binding *b;
 
-    if (sip->sip_request->rq_method == sip_method_register) 
-      return process_register(proxy, irq, sip);
+  return 0;
+}
 
-    e = registration_entry_find(proxy, request_uri);
-    if (e == NULL) {
-      nta_incoming_treply(irq, SIP_404_NOT_FOUND, TAG_END());
-      return 404;
-    }
+static int incoming_transaction(struct proxy_tr *t)
+{
+  struct registration_entry *e;
+  struct binding *b;
 
-    for (b = e->bindings; b; b = b->next)
-      if (binding_is_active(b))
-	break;
+#if 0
+  if (sip->sip_request->rq_method == sip_method_register) 
+    return process_register(proxy, irq, sip);
+#endif
 
-    if (b == NULL) {
-      nta_incoming_treply(irq, SIP_480_TEMPORARILY_UNAVAILABLE, TAG_END());
-      return 480;
-    }
+  t->entry = e = registration_entry_find(t->domain, t->target);
+  if (e == NULL)
+    return respond_transaction(t, SIP_404_NOT_FOUND, TAG_END());
 
-    target = b->contact->m_url;
-    tport = b->tport;
-  }
+  for (b = e->bindings; b; b = b->next) {
+    if (binding_is_active(b)) 
+      target_transaction(t, b->contact->m_url, b->tport);
 
-  t = proxy_transaction_new(proxy);
-  if (t == NULL) {
-    nta_incoming_treply(irq, SIP_500_INTERNAL_SERVER_ERROR, TAG_END());
-    return 500;
-  }
-  nta_incoming_bind(t->server = irq, proxy_ack_cancel, t);
-  
-  rq = sip_request_create(proxy->home,
-			  sip->sip_request->rq_method,
-			  sip->sip_request->rq_method_name,
-			  (url_string_t *)target,
-			  NULL);
-  if (rq == NULL) {
-    nta_incoming_treply(irq, SIP_500_INTERNAL_SERVER_ERROR, TAG_END());
-    proxy_transaction_destroy(t);
-    return 500;
+    if (t->clients)		/* XXX - enable forking */
+      break;
   }
-  t->rq = rq;
 
-  /* Forward request */
-  t->client = nta_outgoing_mcreate(proxy->agent, proxy_response, t, NULL,
-				   nta_incoming_getrequest(irq),
-				   /* rewrite request */
-				   SIPTAG_REQUEST(rq),
-				   SIPTAG_SESSION_EXPIRES(x),
-				   SIPTAG_MIN_SE(min_se),
-				   SIPTAG_REQUIRE_STR(require),
-				   NTATAG_TPORT(tport),
-				   TAG_END());
-  if (t->client == NULL) {
-    proxy_transaction_destroy(t);
-    nta_incoming_treply(irq, SIP_500_INTERNAL_SERVER_ERROR, TAG_END());
+  if (t->clients != NULL)
+    return 0;
+
+  return respond_transaction(t, SIP_480_TEMPORARILY_UNAVAILABLE, TAG_END());
+}
+
+static int target_transaction(struct proxy_tr *t,
+			      url_t const *target,
+			      tport_t *tport)
+{
+  struct client_tr *c = su_zalloc(t->proxy->home, sizeof *c);
+
+  if (c == NULL)
     return 500;
+
+  c->t = t;
+  c->msg = msg_copy(t->msg);
+  c->sip = sip_object(c->msg);
+
+  if (c->msg)
+    c->rq = sip_request_create(msg_home(c->msg),
+			       c->sip->sip_request->rq_method,
+			       c->sip->sip_request->rq_method_name,
+			       (url_string_t *)target,
+			       NULL);
+
+  msg_header_insert(c->msg, (msg_pub_t *)c->sip, (msg_header_t *)c->rq);
+
+  if (t->rr && 0) {
+    sip_record_route_t rr[1];
+
+    *sip_record_route_init(rr)->r_url = *t->proxy->rr_uri;
+
+    msg_header_add_dup(c->msg, (msg_pub_t *)c->sip, (msg_header_t *)rr);    
   }
-  else if (sip->sip_request->rq_method == sip_method_ack)
-    proxy_transaction_destroy(t);
 
-  return 0;
+  if (c->rq)
+    /* Forward request */
+    c->client = nta_outgoing_mcreate(t->proxy->agent,
+				     proxy_response, c,
+				     NULL,
+				     msg_ref_create(c->msg),
+				     NTATAG_TPORT(tport),
+				     TAG_END());
+
+  if (c->client)
+    return client_tr_insert(&t->clients, c), 0;
+
+  msg_destroy(c->msg);
+  su_free(t->proxy->home, c);
+
+  return 500;
 }
 
-static
-int challenge_request(struct proxy *p,
-		     nta_incoming_t *irq,
-		     sip_t const *sip)
+static int challenge_transaction(struct proxy_tr *t)
 {
-  int status;
   auth_status_t *as;
-  msg_t *msg;
+  sip_t *sip = t->sip;
+
+  assert(t->am);
 
-  as = auth_status_new(p->home);
+  t->as = as = auth_status_new(t->proxy->home);
   if (!as)
-    return 500;
+    return respond_transaction(t, SIP_500_INTERNAL_SERVER_ERROR, TAG_END());
 
   as->as_method = sip->sip_request->rq_method_name;
-  msg = nta_incoming_getrequest(irq);
-  as->as_source = msg_addrinfo(msg);
+  as->as_source = msg_addrinfo(t->msg);
 
   as->as_user_uri = sip->sip_from->a_url;
   as->as_display = sip->sip_from->a_display;
@@ -642,271 +953,196 @@
     as->as_body = sip->sip_payload->pl_data,
       as->as_bodylen = sip->sip_payload->pl_len;
 
-  auth_mod_check_client(p->auth, as, sip->sip_proxy_authorization,
-			proxy_challenger);
+  if (t->use_auth == 401)
+    auth_mod_check_client(t->am, as, sip->sip_authorization,
+			  registrar_challenger);
+  else
+    auth_mod_check_client(t->am, as, sip->sip_proxy_authorization,
+			  proxy_challenger);
 
-  if ((status = as->as_status)) {
-    nta_incoming_treply(irq,
-			as->as_status, as->as_phrase,
-			SIPTAG_HEADER((void *)as->as_info),
-			SIPTAG_HEADER((void *)as->as_response),
-			TAG_END());
-  }
-  else if (as->as_match) {
-    msg_header_remove(msg, NULL, as->as_match);
-  }
+  if (as->as_status)
+    return respond_transaction(t, as->as_status, as->as_phrase, TAG_END());
 
-  msg_destroy(msg);
-  su_home_unref(as->as_home);
+  if (as->as_match)
+    msg_header_remove(t->msg, (msg_pub_t *)sip, as->as_match);
 
-  return status;
+  return 0;
 }		      
 
-int proxy_ack_cancel(struct proxy_transaction *t,
+int proxy_ack_cancel(struct proxy_tr *t,
 		     nta_incoming_t *irq,
 		     sip_t const *sip)
 {
-  if (sip == NULL) {
-    proxy_transaction_destroy(t);
+  struct client_tr *c;
+  int status;
+
+  if (sip == NULL) {		/* timeout */
+    proxy_tr_destroy(t);	
     return 0;
   }
 
-  if (sip->sip_request->rq_method == sip_method_cancel) {
-    /* We don't care about response to CANCEL (or ACK)
-     * so we give NULL as callback pointer (and nta immediately 
-     * destroys transaction object or marks it disposable)
-     */
-    if (nta_outgoing_tcancel(t->client, NULL, NULL, TAG_END()))
-      return 200;
-    else
-      return 500;
-  }
-  else {
+  if (sip->sip_request->rq_method != sip_method_cancel)
     return 500;
+
+  status = 200;
+
+  for (c = t->clients; c; c = c->next) {
+    if (c->client && c->status < 200)
+      /*
+       * We don't care about response to CANCEL (or ACK)
+       * so we give NULL as callback pointer (and nta immediately 
+       * destroys transaction object or marks it disposable)
+       */
+      if (nta_outgoing_tcancel(c->client, NULL, NULL, TAG_END()) == NULL)
+	status = 500;
   }
+
+  return status;
 }
 
-int proxy_response(struct proxy_transaction *t,
+int proxy_response(struct client_tr *c,
 		   nta_outgoing_t *client,
 		   sip_t const *sip)
 {
   int final;
 
+  assert(c->t);
+
   if (sip) {
     msg_t *response = nta_outgoing_getresponse(client);
     final = sip->sip_status->st_status >= 200;
     sip_via_remove(response, sip_object(response));
-    nta_incoming_mreply(t->server, response);
+    nta_incoming_mreply(c->t->server, response);
   }
   else {
     final = 1;
-    nta_incoming_treply(t->server, SIP_408_REQUEST_TIMEOUT, TAG_END());
+    respond_transaction(c->t, SIP_408_REQUEST_TIMEOUT, TAG_END());
   }
 
   if (final)
-    proxy_transaction_destroy(t);
+    proxy_tr_destroy(c->t);
 
   return 0;
 }
 
-struct proxy_transaction *
-proxy_transaction_new(struct proxy *proxy)
+struct proxy_tr *
+proxy_tr_new(struct proxy *proxy)
 {
-  struct proxy_transaction *t;
+  struct proxy_tr *t;
 
   t = su_zalloc(proxy->home, sizeof *t);
   if (t) {
     t->proxy = proxy;
-    proxy_transaction_insert(&proxy->transactions, t);
+    proxy_tr_insert(&proxy->transactions, t);
   }
   return t;
 }
 
 static
-void proxy_transaction_destroy(struct proxy_transaction *t)
+void proxy_tr_destroy(struct proxy_tr *t)
 {
+  struct client_tr *c;
+
   if (t == t->proxy->stateless)
     return;
-  proxy_transaction_remove(t);
+
+  proxy_tr_remove(t);
+
+  if (t->as)
+    su_home_unref(t->as->as_home), t->as = NULL;
+  
+  while (t->clients) {
+    client_tr_remove(c = t->clients);
+    nta_outgoing_destroy(c->client), c->client = NULL;
+    msg_destroy(c->msg), c->msg = NULL;
+    su_free(t->proxy->home, c);
+  }
+
   nta_incoming_destroy(t->server);
-  nta_outgoing_destroy(t->client);
-  su_free(t->proxy->home, t->rq);
+
   su_free(t->proxy->home, t);
 }
 
-LIST_BODIES(static, proxy_transaction, struct proxy_transaction, next, prev);
+LIST_BODIES(static, proxy_tr, struct proxy_tr, next, prev);
 
 /* ---------------------------------------------------------------------- */
 
-
-static
-int domain_request(struct proxy *proxy,
-		   nta_leg_t *leg,
-		   nta_incoming_t *irq,
-		   sip_t const *sip)
+static int process_options(struct proxy_tr *t)
 {
-  sip_method_t method = sip->sip_request->rq_method;
-
-  if (method == sip_method_register)
-    return process_register(proxy, irq, sip);
-
-  if (method == sip_method_options) 
-    return process_options(proxy, irq, sip);
-
-  return 501;
-}
-
-static
-int process_options(struct proxy *proxy,
-		    nta_incoming_t *irq,
-		    sip_t const *sip)
-{
-  nta_incoming_treply(irq, SIP_200_OK,
-		      SIPTAG_CONTACT(proxy->transport_contacts),
-		      TAG_END());
-  return 200;
+  return respond_transaction(t, SIP_200_OK,
+			     SIPTAG_CONTACT(t->proxy->transport_contacts),
+			     TAG_END());
 }
 
 /* ---------------------------------------------------------------------- */
 
+static int check_received_contact(struct proxy_tr *t);
+static int validate_contacts(struct proxy_tr *t);
+static int check_out_of_order(struct proxy_tr *t);
+static int update_bindings(struct proxy_tr *t);
 
-static int process_register2(struct proxy *p, auth_status_t *as,
-			      nta_incoming_t *irq, sip_t const *sip);
-
-static int set_status(auth_status_t *as, int status, char const *phrase);
-
-static int validate_contacts(struct proxy *p, auth_status_t *as,
-			     sip_t const *sip);
-static int check_out_of_order(struct proxy *p, auth_status_t *as,
-			      struct registration_entry *e, sip_t const *);
-static int binding_update(struct proxy *p,
-			  auth_status_t *as,
-			  struct registration_entry *e,
-			  nta_incoming_t *irq,
-			  sip_t const *sip);
-
-sip_contact_t *binding_contacts(su_home_t *home, struct binding *bindings);
-
-int process_register(struct proxy *proxy,
-		     nta_incoming_t *irq,
-		     sip_t const *sip)
+int process_register(struct proxy_tr *t)
 {
-  auth_status_t *as;
-  msg_t *msg;
-  int status;
-
-  as = auth_status_new(proxy->home);
-  if (!as)
-    return 500;
-
-  as->as_method = sip->sip_request->rq_method_name;
-  msg = nta_incoming_getrequest(irq);
-  as->as_source = msg_addrinfo(msg);
-  msg_destroy(msg);
-
-  as->as_user_uri = sip->sip_from->a_url;
-  as->as_display = sip->sip_from->a_display;
-
-  if (sip->sip_payload)
-    as->as_body = sip->sip_payload->pl_data,
-      as->as_bodylen = sip->sip_payload->pl_len;
+  /* This is before authentication because we want to be bug-compatible */
+  if (check_received_contact(t))
+    return t->status;
 
-  process_register2(proxy, as, irq, sip);
-  assert(as->as_status >= 200);
-
-  nta_incoming_treply(irq,
-		      as->as_status, as->as_phrase,
-		      SIPTAG_HEADER((void *)as->as_info),
-		      SIPTAG_HEADER((void *)as->as_response),
-		      TAG_END());
+  if (t->domain->auth) {
+    t->am = t->domain->auth, t->use_auth = 401;
+    if (challenge_transaction(t))
+      return t->status;
+  }
 
-  status = as->as_status;
+  if (validate_contacts(t))
+    return t->status;
 
-  su_home_unref(as->as_home);
+  t->entry = registration_entry_find(t->domain, t->sip->sip_to->a_url);
 
-  return status;
+  if (check_out_of_order(t))
+    return t->status;
+  
+  return update_bindings(t);
 }
 
-static int process_register2(struct proxy *p,
-			     auth_status_t *as,
-			     nta_incoming_t *irq,
-			     sip_t const *sip)
+static int check_received_contact(struct proxy_tr *t)
 {
-  struct registration_entry *e = NULL;
+  sip_t *sip = t->sip;
   sip_contact_t *m = sip->sip_contact;
   sip_via_t *v = sip->sip_via;
 
   if (m && v && v->v_received && m->m_url->url_host
       && strcasecmp(v->v_received, m->m_url->url_host) 
       && host_is_ip_address(m->m_url->url_host))
-    return set_status(as, 406, "Unacceptable Contact");
+    return respond_transaction(t, 406, "Unacceptable Contact", TAG_END());
 
-  auth_mod_check_client(p->auth, as, sip->sip_authorization,
-			registrar_challenger);
-  if (as->as_status)
-    return as->as_status;
-  assert(as->as_response == NULL);
-
-  if (validate_contacts(p, as, sip))
-    return as->as_status;
-
-  e = registration_entry_find(p, sip->sip_to->a_url);
-  if (!sip->sip_contact) {
-    as->as_response = (msg_header_t *)e->contacts;
-    return set_status(as, SIP_200_OK);
-  }
-
-  if (e && check_out_of_order(p, as, e, sip))
-    return as->as_status;
-  
-  if (!e) 
-    e = registration_entry_new(p, sip->sip_to->a_url);
-  if (!e)
-    return set_status(as, SIP_500_INTERNAL_SERVER_ERROR);
-
-  if (binding_update(p, as, e, irq, sip))
-    return as->as_status;
-
-  msg_header_free(p->home, (void *)e->contacts);
-  e->contacts = binding_contacts(p->home, e->bindings);
-
-  as->as_response = (msg_header_t *)e->contacts;
-
-  return set_status(as, SIP_200_OK);
-}
-
-static int set_status(auth_status_t *as, int status, char const *phrase)
-{
-  return as->as_phrase = phrase, as->as_status = status;
+  return 0;
 }
 
-static int validate_contacts(struct proxy *p,
-			     auth_status_t *as,
-			     sip_t const *sip)
+/* Validate expiration times */
+static int validate_contacts(struct proxy_tr *t)
 {
-  sip_contact_t const *m;
+  sip_contact_t const *m = t->sip->sip_contact;
+  sip_expires_t const *ex = t->sip->sip_expires;
+  sip_date_t const *date = t->sip->sip_date;
   sip_time_t expires;
-  sip_time_t now = sip_now();
 
-  for (m = sip->sip_contact; m; m = m->m_next) {
-    if (m->m_url->url_type == url_any) {
-      if (!sip->sip_expires ||
-	  sip->sip_expires->ex_delta || 
-	  sip->sip_expires->ex_time ||
-	  sip->sip_contact->m_next)
-	return set_status(as, SIP_400_BAD_REQUEST);
-      else
-	return 0;
-    }
+  if (m && m->m_url->url_type == url_any) {
+    if (!ex || ex->ex_delta || ex->ex_time || m->m_next)
+      return respond_transaction(t, SIP_400_BAD_REQUEST, TAG_END());
+    return 0;
+  }
 
-    expires = sip_contact_expires(m, sip->sip_expires, sip->sip_date,
-				  p->prefs.expires, now);
+  for (; m; m = m->m_next) {
+    expires = sip_contact_expires(m, ex, date, t->domain->prefs.expires, t->now);
     
-    if (expires > 0 && expires < p->prefs.min_expires) {
-      as->as_response = (msg_header_t *)
-	sip_min_expires_format(as->as_home, "%u", 
-			       (unsigned)p->prefs.min_expires);
-      return set_status(as, SIP_423_INTERVAL_TOO_BRIEF);
+    if (expires > 0 && expires < t->domain->prefs.min_expires) {
+      sip_min_expires_t me[1];
+
+      sip_min_expires_init(me)->me_delta = t->domain->prefs.min_expires;
+
+      return respond_transaction(t, SIP_423_INTERVAL_TOO_BRIEF,
+				 SIPTAG_MIN_EXPIRES(me),
+				 TAG_END());
     }
   }
 
@@ -914,31 +1150,27 @@
 }
 
 /** Check for out-of-order register request */
-static
-int check_out_of_order(struct proxy *p,
-		       auth_status_t *as,
-		       struct registration_entry *e,
-		       sip_t const *sip)
+static int check_out_of_order(struct proxy_tr *t)
 {
   struct binding const *b;
-  sip_call_id_t const *id;
+  sip_call_id_t const *id = t->sip->sip_call_id;
+  uint32_t cseq = t->sip->sip_cseq->cs_seq;
   sip_contact_t *m;
 
-  if (e == NULL || !sip->sip_contact)
+  if (t->entry == NULL || !t->sip->sip_contact)
     return 0;
 
-  id = sip->sip_call_id;
-  
   /* RFC 3261 subsection 10.3 step 6 and step 7 (p. 66): */
   /* Check for reordered register requests */
-  for (b = e->bindings; b; b = b->next) {
+  for (b = t->entry->bindings; b; b = b->next) {
     if (binding_is_active(b) &&
-	strcmp(sip->sip_call_id->i_id, b->call_id->i_id) == 0 &&
-	sip->sip_cseq->cs_seq <= b->cseq) {
-      for (m = sip->sip_contact; m; m = m->m_next) {
+	strcmp(id->i_id, b->call_id->i_id) == 0 &&
+	cseq <= b->cseq) {
+      for (m = t->sip->sip_contact; m; m = m->m_next) {
 	if (m->m_url->url_type == url_any ||
 	    url_cmp_all(m->m_url, b->contact->m_url) == 0)
-	  return set_status(as, SIP_500_INTERNAL_SERVER_ERROR);
+	  return respond_transaction(t, SIP_500_INTERNAL_SERVER_ERROR,
+				     TAG_END());
       }
     }
   }
@@ -946,37 +1178,40 @@
   return 0;
 }
 
-
 static struct registration_entry *
-registration_entry_find(struct proxy const *proxy, url_t const *uri)
+registration_entry_find(struct domain const *d, url_t const *uri)
 {
   struct registration_entry *e;
 
   /* Our routing table */
-  for (e = proxy->entries; e; e = e->next) {
+  for (e = d->entries; e; e = e->next) {
     if (url_cmp(uri, e->aor) == 0)
       return e;
   }
+
   return NULL;
 }
 
 static struct registration_entry *
-registration_entry_new(struct proxy *proxy, url_t const *aor)
+registration_entry_new(struct domain *d, url_t const *aor)
 {
   struct registration_entry *e;
 
-  e = su_zalloc(proxy->home, sizeof *e); 
+  if (d == NULL)
+    return NULL;
+
+  e = su_zalloc(d->home, sizeof *e); 
   if (!e) 
     return NULL;
 
-  e->proxy = proxy;
-  e->aor = url_hdup(proxy->home, aor);
+  e->domain = d;
+  e->aor = url_hdup(d->home, aor);
   if (!e->aor) {
-    su_free(proxy->home, e);
+    su_free(d->home, e);
     return NULL;
   }
 
-  registration_entry_insert(&proxy->entries, e);
+  registration_entry_insert(&d->entries, e);
 
   return e;
 }
@@ -986,14 +1221,19 @@
 {
   if (e) {
     registration_entry_remove(e);
-    su_free(e->proxy->home, e->aor);
+    su_free(e->domain->home, e->aor);
     while (e->bindings)
-      binding_destroy(e->proxy->home, e->bindings);
-    msg_header_free(e->proxy->home, (void *)e->contacts);
-    su_free(e->proxy->home, e);
+      binding_destroy(e->domain->home, e->bindings);
+    msg_header_free(e->domain->home, (void *)e->contacts);
+    su_free(e->domain->home, e);
   }
 }
 
+sip_contact_t *entry_contacts(struct registration_entry *entry)
+{
+  return entry ? entry->contacts : NULL;
+}
+
 LIST_BODIES(static, registration_entry, struct registration_entry, next, prev);
 
 /* ---------------------------------------------------------------------- */
@@ -1003,7 +1243,7 @@
 struct binding *binding_new(su_home_t *home, 
 			    sip_contact_t *contact,
 			    tport_t *tport,
-			    sip_call_id_t *call_id,
+			    sip_call_id_t const *call_id,
 			    uint32_t cseq,
 			    sip_time_t registered, 
 			    sip_time_t expires)
@@ -1046,40 +1286,48 @@
   su_free(home, b);
 }
 
-static
-int binding_update(struct proxy *p,
-		   auth_status_t *as,
-		   struct registration_entry *e,
-		   nta_incoming_t *irq,
-		   sip_t const *sip)
+static int update_bindings(struct proxy_tr *t)
 {
+  struct domain *d = t->domain;
   struct binding *b, *old, *next, *last, *bindings = NULL, **bb = &bindings;
   sip_contact_t *m;
+  sip_call_id_t const *id = t->sip->sip_call_id;
+  uint32_t cseq = t->sip->sip_cseq->cs_seq;
+  sip_expires_t *ex = t->sip->sip_expires;
+  sip_date_t *date = t->sip->sip_date;
   sip_time_t expires;
-  sip_time_t now = sip_now();
   tport_t *tport = NULL;
+  sip_contact_t *contacts = NULL, **mm = &contacts; 
+  void *tbf;
 
-  assert(sip->sip_contact);
-
-  if (p->prefs.outbound_tcp && 
-      str0casecmp(sip->sip_via->v_protocol, sip_transport_tcp) == 0)
-    tport = nta_incoming_transport(p->agent, irq, NULL);
+  if (t->sip->sip_contact == NULL) {
+    if (t->entry)
+      contacts = t->entry->contacts;
+    goto ok200;
+  }
+
+  if (t->entry == NULL)
+    t->entry = registration_entry_new(d, t->sip->sip_to->a_url);
+  if (t->entry == NULL)
+    return respond_transaction(t, SIP_500_INTERNAL_SERVER_ERROR, TAG_END());
+
+  if (d->prefs.outbound_tcp && 
+      str0casecmp(t->sip->sip_via->v_protocol, sip_transport_tcp) == 0)
+    tport = nta_incoming_transport(t->proxy->agent, t->server, NULL);
 
   /* Create new bindings */
-  for (m = sip->sip_contact; m; m = m->m_next) {
+  for (m = t->sip->sip_contact; m; m = m->m_next) {
     if (m->m_url->url_type == url_any)
       break;
     
-    expires = sip_contact_expires(m, sip->sip_expires, sip->sip_date,
-				  p->prefs.expires, now);
+    expires = sip_contact_expires(m, ex, date, d->prefs.expires, t->now);
 
-    if (expires > p->prefs.max_expires)
-      expires = p->prefs.max_expires;
+    if (expires > d->prefs.max_expires)
+      expires = d->prefs.max_expires;
 
     msg_header_remove_param(m->m_common, "expires");
 
-    b = binding_new(p->home, m, tport, sip->sip_call_id, sip->sip_cseq->cs_seq, 
-		    now, now + expires);
+    b = binding_new(d->home, m, tport, id, cseq, t->now, t->now + expires);
     if (!b)
       break;
 
@@ -1092,7 +1340,7 @@
 
   if (m == NULL) {
     /* Merge new bindings with old ones */
-    for (old = e->bindings; old; old = next) {
+    for (old = t->entry->bindings; old; old = next) {
       next = old->next;
 
       for (b = bindings; b != last; b = b->next) {
@@ -1102,12 +1350,12 @@
 	if (strcmp(old->call_id->i_id, b->call_id->i_id) == 0) {
 	  b->registered = old->registered;
 	}
-	binding_destroy(p->home, old);
+	binding_destroy(d->home, old);
 	break;
       }
     }
 
-    for (bb = &e->bindings; *bb; bb = &(*bb)->next)
+    for (bb = &t->entry->bindings; *bb; bb = &(*bb)->next)
       ;
 
     if ((*bb = bindings))
@@ -1115,8 +1363,8 @@
   }
   else if (m->m_url->url_type == url_any) {
     /* Unregister all */
-    for (b = e->bindings; b; b = b->next) {
-      b->expires = now;
+    for (b = t->entry->bindings; b; b = b->next) {
+      b->expires = t->now;
     }
   }
   else {
@@ -1124,34 +1372,35 @@
 
     for (old = bindings; old; old = next) {
       next = old->next;
-      binding_destroy(p->home, old);
+      binding_destroy(d->home, old);
     }
 
-    return set_status(as, SIP_500_INTERNAL_SERVER_ERROR);
+    return respond_transaction(t, SIP_500_INTERNAL_SERVER_ERROR, TAG_END());
   }
 
-  return 0;
-}
-
-sip_contact_t *binding_contacts(su_home_t *home, struct binding *bindings)
-{
-  sip_contact_t *retval = NULL, **mm = &retval; 
-  struct binding *b;
-  sip_time_t now = sip_now();
-
-  for (b = bindings; b; b = b->next) {
+  for (b = t->entry->bindings; b; b = b->next) {
     char const *expires;
-    if (b->expires <= now)
+
+    if (b->expires <= t->now)
       continue;
-    *mm = sip_contact_copy(home, b->contact);
+
+    *mm = sip_contact_copy(d->home, b->contact);
     if (*mm) {
-      expires = su_sprintf(home, "expires=%u", (unsigned)(b->expires - now));
-      msg_header_add_param(home, (*mm)->m_common, expires);
+      expires = su_sprintf(d->home, "expires=%u", 
+			   (unsigned)(b->expires - t->now));
+      msg_header_add_param(d->home, (*mm)->m_common, expires);
       mm = &(*mm)->m_next;
     }
   }
 
-  return retval;
+  tbf = t->entry->contacts;
+  t->entry->contacts = contacts;
+  msg_header_free(d->home, tbf);
+
+ ok200:
+  return respond_transaction(t, SIP_200_OK,
+			     SIPTAG_CONTACT(contacts),
+			     TAG_END());
 }
 
 /* ---------------------------------------------------------------------- */
@@ -1159,16 +1408,19 @@
 static int close_tports(void *_proxy)
 {
   struct proxy *p = _proxy;
+  struct domain *d;
   struct registration_entry *e;
   struct binding *b;
   
   /* Close all outbound transports */
-  for (e = p->entries; e; e = e->next) {
-    for (b = e->bindings; b; b = b->next) {
-      if (b->tport) {
-	tport_shutdown(b->tport, 2);
-	tport_unref(b->tport);
-	b->tport = NULL;
+  for (d = p->domains; d; d = d->next) {
+    for (e = d->entries; e; e = e->next) {
+      for (b = e->bindings; b; b = b->next) {
+	if (b->tport) {
+	  tport_shutdown(b->tport, 1);
+	  tport_unref(b->tport);
+	  b->tport = NULL;
+	}
       }
     }
   }

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_proxy.h
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_proxy.h	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_proxy.h	Mon Sep 10 16:45:25 2007
@@ -31,6 +31,7 @@
 SOFIA_BEGIN_DECLS
 
 struct proxy;
+struct domain;
 
 struct proxy *test_proxy_create(su_root_t *, tag_type_t, tag_value_t, ...);
 
@@ -38,15 +39,19 @@
 
 url_t const *test_proxy_uri(struct proxy const *);
 
-void test_proxy_set_expiration(struct proxy *,
-			       sip_time_t min_expires, 
-			       sip_time_t expires, 
-			       sip_time_t max_expires);
-
-void test_proxy_get_expiration(struct proxy *,
-			       sip_time_t *return_min_expires, 
-			       sip_time_t *return_expires, 
-			       sip_time_t *return_max_expires);
+struct domain *test_proxy_add_domain(struct proxy *,
+				     url_t const *domain,
+				     tag_type_t, tag_value_t, ...);
+
+void test_proxy_domain_set_expiration(struct domain *,
+				      sip_time_t min_expires, 
+				      sip_time_t expires, 
+				      sip_time_t max_expires);
+
+void test_proxy_domain_get_expiration(struct domain *,
+				      sip_time_t *return_min_expires, 
+				      sip_time_t *return_expires, 
+				      sip_time_t *return_max_expires);
 
 void test_proxy_set_session_timer(struct proxy *p,
 				  sip_time_t session_expires, 
@@ -56,6 +61,14 @@
 				  sip_time_t *return_session_expires,
 				  sip_time_t *return_min_se);
 
+void test_proxy_domain_set_authorize(struct domain *d, int authorize);
+void test_proxy_domain_get_authorize(struct domain *d, int *return_authorize);
+
+void test_proxy_domain_set_outbound(struct domain *d,
+				    int use_outbound);
+void test_proxy_domain_get_outbound(struct domain *d,
+				    int *return_use_outbound);
+
 int test_proxy_close_tports(struct proxy *p);
 
 SOFIA_END_DECLS

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_register.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_register.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_register.c	Mon Sep 10 16:45:25 2007
@@ -57,9 +57,6 @@
   sip_cseq_t cseq[1];
   int seen_401;
 
-  if (ctx->p)
-    test_proxy_set_expiration(ctx->p, 5, 5, 10);
-
   if (print_headings)
     printf("TEST NUA-2.3.0.1: un-REGISTER a\n");
 
@@ -108,7 +105,6 @@
   if (print_headings)
     printf("TEST NUA-2.3.0.3: PASSED\n");
 
-
 /* REGISTER test
 
    A			B
@@ -123,6 +119,8 @@
   if (print_headings)
     printf("TEST NUA-2.3.1: REGISTER a\n");
 
+  test_proxy_domain_set_expiration(ctx->a.domain, 5, 5, 10);
+
   TEST_1(a_reg->nh = nua_handle(a->nua, a_reg, TAG_END()));
 
   sip_cseq_init(cseq)->cs_seq = 12;
@@ -142,7 +140,7 @@
 
   TEST_1(e = a->events->head);
   TEST_1(sip = sip_object(e->data->e_msg));
-  if (ctx->nat) {
+  if (ctx->nat && e->data->e_status == 100) {
     TEST_E(e->data->e_event, nua_r_register);
     TEST(e->data->e_status, 100);
     TEST(sip->sip_status->st_status, 406);
@@ -184,12 +182,16 @@
     TEST_1(e = a->specials->head);
   }
 
+  test_proxy_domain_set_expiration(ctx->a.domain, 600, 3600, 36000);
+
   if (print_headings)
     printf("TEST NUA-2.3.1: PASSED\n");
 
   if (print_headings)
     printf("TEST NUA-2.3.2: REGISTER b\n");
 
+  test_proxy_domain_set_expiration(ctx->b.domain, 5, 5, 10);
+
   TEST_1(b_reg->nh = nua_handle(b->nua, b_reg, TAG_END()));
 
   /* Test application-supplied contact */
@@ -237,14 +239,14 @@
   if (print_headings)
     printf("TEST NUA-2.3.2: PASSED\n");
 
-  if (ctx->p) {
-    test_proxy_close_tports(ctx->p);
-    test_proxy_set_expiration(ctx->p, 600, 3600, 36000);
-  }
+  test_proxy_domain_set_expiration(ctx->b.domain, 600, 3600, 36000);
 
   if (print_headings)
     printf("TEST NUA-2.3.3: REGISTER c\n");
 
+  test_proxy_domain_set_expiration(ctx->c.domain, 600, 3600, 36000);
+  test_proxy_domain_set_authorize(ctx->c.domain, 2);
+
   TEST_1(c_reg->nh = nua_handle(c->nua, c_reg, TAG_END()));
 
   REGISTER(c, c_reg, c_reg->nh, SIPTAG_TO(c->to), 
@@ -279,7 +281,12 @@
   TEST_1(sip = sip_object(e->data->e_msg));
   TEST(sip->sip_status->st_status, 423);
   TEST_1(e = e->next);
-  TEST(e->data->e_status, 200);
+  if (e->data->e_status == 100 && e->data->e_event == nua_r_register) {
+    TEST_1(sip = sip_object(e->data->e_msg));
+    TEST(sip->sip_status->st_status, 401);
+    TEST_1(e = e->next);
+  }
+  TEST(e->data->e_status, 200); TEST_E(e->data->e_event, nua_r_register);
   TEST_1(sip = sip_object(e->data->e_msg));
   TEST_1(sip->sip_contact);
   TEST_S(sip->sip_contact->m_display, "C");
@@ -366,6 +373,30 @@
   if (print_headings)
     printf("TEST NUA-2.3.4: PASSED\n");
 
+  if (!ctx->p)
+    return 0;
+
+  if (print_headings)
+    printf("TEST NUA-2.3.5: re-REGISTER when TCP connection is closed\n");
+
+  test_proxy_close_tports(ctx->p);
+
+  run_b_until(ctx, -1, save_until_final_response);
+
+  TEST_1(e = b->events->head);
+  TEST_E(e->data->e_event, nua_r_register);
+  if (e->data->e_status == 100)
+    TEST_1(e = e->next);
+  TEST_1(sip = sip_object(e->data->e_msg));
+  TEST_1(sip->sip_contact);
+  TEST_S(sip->sip_contact->m_expires, "3600");
+  TEST_1(!e->next);
+
+  free_events_in_list(ctx, b->events);
+
+  if (print_headings)
+    printf("TEST NUA-2.3.5: PASSED\n");
+
   END();
 }
 

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_simple.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_simple.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_simple.c	Mon Sep 10 16:45:25 2007
@@ -814,6 +814,7 @@
 size_t change_status_to_483(void *a, void *message, size_t len);
 int save_until_notified_and_responded_twice(CONDITION_PARAMS);
 int save_until_notify_responded_twice(CONDITION_PARAMS);
+int accept_subscription_until_terminated(CONDITION_PARAMS);
 
 int test_subscribe_notify_graceful(struct context *ctx)
 {
@@ -902,7 +903,7 @@
   SUBSCRIBE(a, a_call, a_call->nh, TAG_END());
 
   run_ab_until(ctx, -1, save_until_notified_and_responded_twice, 
-	       -1, save_until_notify_responded_twice);
+	       -1, accept_subscription_until_terminated);
 
 #if 0
   /* Client events:

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_sip_events.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_sip_events.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/nua/test_sip_events.c	Mon Sep 10 16:45:25 2007
@@ -64,9 +64,15 @@
 int save_until_notified_and_responded(CONDITION_PARAMS)
 {
   save_event_in_list(ctx, event, ep, call);
+
   if (event == nua_i_notify) ep->flags.bit0 = 1;
   if (event == nua_r_subscribe || event == nua_r_unsubscribe) {
-    if (status >= 300)
+    if (status == 407) {
+      AUTHENTICATE(ep, call, nh,
+		   NUTAG_AUTH("Digest:\"test-proxy\":charlie:secret"),
+		   TAG_END());
+    }
+    else if (status >= 300) 
       return 1;
     else if (status >= 200)
       ep->flags.bit1 = 1;

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/soa/test_soa.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/soa/test_soa.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/soa/test_soa.c	Mon Sep 10 16:45:25 2007
@@ -1264,6 +1264,67 @@
 }
 
 
+int test_media_reject(struct context *ctx)
+{
+  BEGIN();
+  int n;
+  
+  soa_session_t *a, *b;
+
+  char const *offer = NONE, *answer = NONE;
+  isize_t offerlen = (isize_t)-1, answerlen = (isize_t)-1;
+
+  sdp_session_t const *b_sdp;
+
+  char const a_caps[] = 
+    "v=0\r\n"
+    "o=left 219498671 2 IN IP4 127.0.0.2\r\n"
+    "c=IN IP4 127.0.0.2\r\n"
+    "m=audio 5008 RTP/AVP 0 8 97\r\n"
+    "a=rtpmap:97 GSM/8000\n"
+    ;
+
+  char const b_caps[] = 
+    "m=audio 0 RTP/AVP 96 97\n"
+    "a=rtpmap:96 G7231/8000\n"
+    "a=rtpmap:97 G729/8000\n";
+
+  TEST_1(a = soa_create("static", ctx->root, ctx));
+  TEST_1(b = soa_create("static", ctx->root, ctx));
+
+  TEST(soa_set_user_sdp(a, 0, a_caps, strlen(a_caps)), 1);
+  TEST(soa_set_user_sdp(b, 0, b_caps, strlen(b_caps)), 1);
+
+  n = soa_generate_offer(a, 1, test_completed); TEST(n, 0);
+  n = soa_get_local_sdp(a, NULL, &offer, &offerlen); TEST(n, 1);
+  TEST_1(offer != NULL && offer != NONE);
+  n = soa_set_remote_sdp(b, 0, offer, offerlen); TEST(n, 1);
+  n = soa_get_local_sdp(b, NULL, &answer, &answerlen); TEST(n, 0);
+  n = soa_generate_answer(b, test_completed); TEST(n, 0);
+  n = soa_get_local_sdp(b, &b_sdp, &answer, &answerlen); TEST(n, 1);
+  TEST_1(answer != NULL && answer != NONE);
+  n = soa_set_remote_sdp(a, 0, answer, -1); TEST(n, 1);
+  n = soa_process_answer(a, test_completed); TEST(n, 0);
+
+  TEST_1(soa_is_complete(b));
+  TEST(soa_activate(b, NULL), 0);
+
+  TEST_1(soa_is_complete(a));
+  TEST(soa_activate(a, NULL), 0);
+
+  TEST(soa_is_audio_active(a), SOA_ACTIVE_REJECTED);
+  TEST(soa_is_remote_audio_active(a), SOA_ACTIVE_REJECTED);
+
+  TEST_VOID(soa_terminate(a, NULL));
+  TEST_VOID(soa_terminate(b, NULL));
+  
+  TEST_VOID(soa_destroy(a));
+  TEST_VOID(soa_destroy(b));
+
+  END();
+}
+
+
 int test_asynch_offer_answer(struct context *ctx)
 {
   BEGIN();
@@ -1356,6 +1417,11 @@
 int test_deinit(struct context *ctx)
 {
   BEGIN();
+
+  su_root_destroy(ctx->root), ctx->root = NULL;
+  soa_destroy(ctx->a);
+  soa_destroy(ctx->b);
+  
   END();
 }
 
@@ -1459,6 +1525,7 @@
     retval |= test_static_offer_answer(ctx); SINGLE_FAILURE_CHECK();
     retval |= test_codec_selection(ctx); SINGLE_FAILURE_CHECK();
     retval |= test_media_replace(ctx); SINGLE_FAILURE_CHECK();
+    retval |= test_media_reject(ctx); SINGLE_FAILURE_CHECK();
 
     retval |= test_asynch_offer_answer(ctx); SINGLE_FAILURE_CHECK();
   }

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/sofia-sip/tport_tag.h
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/sofia-sip/tport_tag.h	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/sofia-sip/tport_tag.h	Mon Sep 10 16:45:25 2007
@@ -225,6 +225,66 @@
 TPORT_DLL extern tag_typedef_t tptag_timeout_ref;
 #define TPTAG_TIMEOUT_REF(x) tptag_timeout_ref, tag_uint_vr(&(x))
 
+TPORT_DLL extern tag_typedef_t tptag_keepalive;
+/**Keepalive interval in milliseconds.
+ *
+ * If 0 or UINT_MAX, do not use keepalives. Default value is 0.
+ *
+ * On TCP, the keepalive if a CR-LF-CR-LF sequence.
+ *
+ * Use with tport_tcreate(), tport_tbind(), tport_set_params(), nua_create(),
+ * nta_agent_create(), nta_agent_add_tport(), nth_engine_create(), or
+ * initial nth_site_create().
+ *
+ * @sa TPTAG_PINGPONG(), TPTAG_PONG2PING(), TPTAG_TIMEOUT(), TPTAG_IDLE()
+ */
+#define TPTAG_KEEPALIVE(x) tptag_keepalive, tag_uint_v((x))
+
+TPORT_DLL extern tag_typedef_t tptag_keepalive_ref;
+#define TPTAG_KEEPALIVE_REF(x) tptag_keepalive_ref, tag_uint_vr(&(x))
+
+TPORT_DLL extern tag_typedef_t tptag_pingpong;
+/**Ping-pong interval in milliseconds.
+ *
+ * If 0 or UINT_MAX, do not check for PONGs. Default value is 0. 
+ *
+ * If set, the ping-pong protocol is used on TCP connections. If pinger
+ * sends a ping and receives no data in the specified ping-pong interval, it
+ * considers the connection failed and closes it. The value recommended in
+ * draft-ietf-sip-outbound-10 is 10 seconds (10000 milliseconds).
+ *
+ * Use with tport_tcreate(), tport_tbind(), tport_set_params(), nua_create(),
+ * nta_agent_create(), nta_agent_add_tport(), nth_engine_create(), or
+ * initial nth_site_create().
+ *
+ * @sa TPTAG_PONG2PING(), TPTAG_KEEPALIVE(), TPTAG_TIMEOUT(), TPTAG_IDLE(),
+ * draft-ietf-sip-outbound-10.txt
+ */
+#define TPTAG_PINGPONG(x) tptag_pingpong, tag_uint_v((x))
+
+TPORT_DLL extern tag_typedef_t tptag_pingpong_ref;
+#define TPTAG_PINGPONG_REF(x) tptag_pingpong_ref, tag_uint_vr(&(x))
+
+TPORT_DLL extern tag_typedef_t tptag_pong2ping;
+/**Respond PING with PONG.
+ *
+ * If true, respond with PONG to PING. Default value is 0 (false).
+ *
+ * If set, the ping-pong protocol is used on TCP connections. If a ping (at
+ * least 4 whitespace characters) is received within messages, a pong
+ * (CR-LF) is sent in response.
+ *
+ * Use with tport_tcreate(), tport_tbind(), tport_set_params(), nua_create(),
+ * nta_agent_create(), nta_agent_add_tport(), nth_engine_create(), or
+ * initial nth_site_create().
+ *
+ * @sa TPTAG_PINGPONG(), TPTAG_KEEPALIVE(), TPTAG_TIMEOUT(), TPTAG_IDLE()
+ */
+#define TPTAG_PONG2PING(x) tptag_pong2ping, tag_bool_v((x))
+
+TPORT_DLL extern tag_typedef_t tptag_pong2ping_ref;
+#define TPTAG_PONG2PING_REF(x) tptag_pong2ping_ref, tag_bool_vr(&(x))
+
 TPORT_DLL extern tag_typedef_t tptag_sigcomp_lifetime;
 /**Default SigComp lifetime in seconds.
  *

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/test_tport.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/test_tport.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/test_tport.c	Mon Sep 10 16:45:25 2007
@@ -33,6 +33,9 @@
  * @date Created: Wed Apr  3 11:25:13 2002 ppessi
  */
 
+/* always assert()s */
+#undef NDEBUG
+
 #include "config.h"
 
 #include <stddef.h>
@@ -49,6 +52,8 @@
 #include <sofia-sip/su_wait.h>
 #include <sofia-sip/su_md5.h>
 
+#include "tport_internal.h"	/* Get SU_DEBUG_*() */
+
 #include "test_class.h"
 #include "test_protos.h"
 #include "sofia-sip/msg.h"
@@ -101,6 +106,9 @@
   int        tt_received;
   msg_t     *tt_rmsg;
   uint8_t    tt_digest[SU_MD5_DIGEST_SIZE];
+
+  su_addrinfo_t const *tt_tcp_addr;
+  tport_t   *tt_tcp;
 };
 
 int tstflags;
@@ -359,7 +367,11 @@
   tt->tt_status = 1;
   tt->tt_received++;
 
-  if (test_msg_md5(tt, msg))
+  if (msg_has_error(msg)) {
+    tt->tt_status = -1;
+    tt->tt_rtport = tp;
+  }
+  else if (test_msg_md5(tt, msg))
     msg_destroy(msg);
   else if (tt->tt_rmsg) 
     msg_destroy(msg);
@@ -451,7 +463,7 @@
 static int init_test(tp_test_t *tt)
 {
   tp_name_t myname[1] = {{ "*", "*", "*", "*", "sigcomp" }};
-#if HAVE_NETINET_SCTP_H
+#if HAVE_SCTP
   char const * transports[] = { "udp", "tcp", "sctp", NULL };
 #else
   char const * transports[] = { "udp", "tcp", NULL };
@@ -635,7 +647,7 @@
   for (tp = tport_primaries(tt->tt_srv_tports); tp; tp = tport_next(tp)) {
     TEST_1(tpn = tport_name(tp));
 
-    if (1 || tt->tt_flags & tst_verbatim) {
+    if (tt->tt_flags & tst_verbatim) {
       char const *host = tpn->tpn_host != tpn->tpn_canon ? tpn->tpn_host : "";
       printf("bound transport to %s/%s:%s%s%s%s%s\n",
 	     tpn->tpn_proto, tpn->tpn_canon, tpn->tpn_port, 
@@ -661,6 +673,11 @@
       tt->tt_tcp_name->tpn_ident = NULL;
       *tt->tt_tcp_comp = *tpn;
       tt->tt_tcp_comp->tpn_ident = NULL;
+
+      if (tt->tt_tcp_addr == NULL) {
+	tt->tt_tcp_addr = tport_get_address(tp); 
+	tt->tt_tcp = tp;
+      }
     } 
     else if (strcmp(tpn->tpn_proto, "sctp") == 0) {
       *tt->tt_sctp_name = *tpn;
@@ -738,7 +755,7 @@
 
   TEST_1(md5 = msg_content_md5_make(home, "R6nitdrtJFpxYzrPaSXfrA=="));
   TEST(msg_header_insert(msg, (void *)tst, (msg_header_t *)md5), 0);
-  
+
   TEST_1(sep = msg_separator_create(home));
   TEST(msg_header_insert(msg, (void *)tst, (msg_header_t *)sep), 0);
 
@@ -778,23 +795,72 @@
   END();
 }
 
+int pending_server_close, pending_client_close;
+
+void server_closed_callback(tp_stack_t *tt, tp_client_t *client,
+       		     tport_t *tp, msg_t *msg, int error)
+{
+  assert(msg == NULL);
+  assert(client == NULL);
+  if (msg == NULL) {
+    tport_release(tp, pending_server_close, NULL, NULL, client, 0);
+    pending_server_close = 0;
+  }
+}
+
+void client_closed_callback(tp_stack_t *tt, tp_client_t *client,
+       		     tport_t *tp, msg_t *msg, int error)
+{
+  assert(msg == NULL);
+  assert(client == NULL);
+  if (msg == NULL) {
+    tport_release(tp, pending_client_close, NULL, NULL, client, 0);
+    pending_client_close = 0;
+  }
+}
+
 static int tcp_test(tp_test_t *tt)
 {
   BEGIN();
 
-#ifndef WIN32			/* Windows seems to be buffering too much */
-
   msg_t *msg = NULL;
   int i;
   tport_t *tp, *tp0;
   char ident[16];
+  su_time_t started;
+
+  /* Send a single message */
+  TEST_1(!new_test_msg(tt, &msg, "tcp-first", 1, 1024));
+  TEST_1(tp = tport_tsend(tt->tt_tports, msg, tt->tt_tcp_name, TAG_END()));
+  TEST_S(tport_name(tp)->tpn_ident, "client");
+  tp0 = tport_incref(tp);
+  msg_destroy(msg);
+
+  tport_set_params(tp,
+       	    TPTAG_KEEPALIVE(100), 
+       	    TPTAG_PINGPONG(500),
+       	    TPTAG_IDLE(500),
+       	    TAG_END());
+
+  TEST(tport_test_run(tt, 5), 1);
+  TEST_1(!check_msg(tt, tt->tt_rmsg, "tcp-first"));
+  msg_destroy(tt->tt_rmsg), tt->tt_rmsg = NULL;
+
+  /* Ask for notification upon close */
+  pending_client_close = tport_pend(tp0, NULL, client_closed_callback, NULL);
+  TEST_1(pending_client_close > 0);
+  tp = tt->tt_rtport;
+  pending_server_close = tport_pend(tp, NULL, server_closed_callback, NULL);
+  TEST_1(pending_server_close > 0);
+
+#ifndef WIN32			/* Windows seems to be buffering too much */
 
   /* Create a large message, just to force queueing in sending end */
   TEST(new_test_msg(tt, &msg, "tcp-0", 1, 16 * 64 * 1024), 0);
   test_create_md5(tt, msg);
   TEST_1(tp = tport_tsend(tt->tt_tports, msg, tt->tt_tcp_name, TAG_END()));
   TEST_S(tport_name(tp)->tpn_ident, "client");
-  tp0 = tport_incref(tp);
+  TEST_P(tport_incref(tp), tp0); tport_decref(&tp);
   msg_destroy(msg);
 
   /* Fill up the queue */
@@ -827,10 +893,12 @@
     msg_destroy(tt->tt_rmsg), tt->tt_rmsg = NULL;
   }
 
+#endif
+
   /* This uses a new connection */
   TEST_1(!new_test_msg(tt, &msg, "tcp-no-reuse", 1, 1024));
   TEST_1(tp = tport_tsend(tt->tt_tports, msg, tt->tt_tcp_name, 
-			  TPTAG_REUSE(0), TAG_END()));
+       		   TPTAG_REUSE(0), TAG_END()));
   TEST_S(tport_name(tp)->tpn_ident, "client");
   TEST_1(tport_incref(tp) != tp0); tport_decref(&tp);
   msg_destroy(msg);
@@ -838,7 +906,7 @@
   /* This uses the old connection */
   TEST_1(!new_test_msg(tt, &msg, "tcp-reuse", 1, 1024));
   TEST_1(tp = tport_tsend(tt->tt_tports, msg, tt->tt_tcp_name, 
-			  TPTAG_REUSE(1), TAG_END()));
+       		   TPTAG_REUSE(1), TAG_END()));
   TEST_S(tport_name(tp)->tpn_ident, "client");
   TEST_1(tport_incref(tp) == tp0); tport_decref(&tp);
   msg_destroy(msg);
@@ -863,9 +931,99 @@
   TEST_1(!check_msg(tt, tt->tt_rmsg, "tcp-last"));
   msg_destroy(tt->tt_rmsg), tt->tt_rmsg = NULL;
 
+  TEST_1(pending_server_close && pending_client_close);
+  SU_DEBUG_3(("tport_test(%p): waiting for PONG timeout\n", (void *)tp0));
+
+  /* Wait until notifications - 
+     client closes when no pong is received and notifys pending, 
+     then server closes and notifys pending */
+  while (pending_server_close || pending_client_close)
+    su_root_step(tt->tt_root, 50);
+
   tport_decref(&tp0);
 
-#endif
+  /* Again a single message */
+  TEST_1(!new_test_msg(tt, &msg, "tcp-pingpong", 1, 512));
+  TEST_1(tp = tport_tsend(tt->tt_tports, msg, tt->tt_tcp_name, TAG_END()));
+  TEST_S(tport_name(tp)->tpn_ident, "client");
+  tp0 = tport_incref(tp);
+  msg_destroy(msg);
+
+  tport_set_params(tp0,
+       	    TPTAG_KEEPALIVE(250), 
+       	    TPTAG_PINGPONG(200),
+       	    TAG_END());
+
+  TEST(tport_test_run(tt, 5), 1);
+  TEST_1(!check_msg(tt, tt->tt_rmsg, "tcp-pingpong"));
+  msg_destroy(tt->tt_rmsg), tt->tt_rmsg = NULL;
+
+  /* Ask for notifications upon close */
+  pending_client_close = tport_pend(tp0, NULL, client_closed_callback, NULL);
+  TEST_1(pending_client_close > 0);
+
+  tp = tt->tt_rtport;
+  pending_server_close = tport_pend(tp, NULL, server_closed_callback, NULL);
+  TEST_1(pending_server_close > 0);
+
+  /* Now server responds with pong ... */
+  TEST(tport_set_params(tp, TPTAG_PONG2PING(1), TAG_END()), 1);
+
+  started = su_now();
+
+  while (pending_server_close && pending_client_close) {
+    su_root_step(tt->tt_root, 50);
+    if (su_duration(su_now(), started) > 1000)
+      break;
+  }
+
+  /* ... and we are still pending after a second */
+  TEST_1(pending_client_close && pending_server_close);
+  TEST_1(su_duration(su_now(), started) > 1000);
+
+  tport_shutdown(tp0, 2);
+  tport_unref(tp0);
+
+  while (pending_server_close || pending_client_close)
+    su_root_step(tt->tt_root, 50);
+
+  END();
+}
+
+static int test_incomplete(tp_test_t *tt)
+{
+  BEGIN();
+  
+  su_addrinfo_t const *ai = tt->tt_tcp_addr;
+  su_socket_t s;
+  int connected;
+
+  TEST_1(ai != NULL);
+
+  TEST(tport_set_params(tt->tt_tcp, TPTAG_TIMEOUT(500), TAG_END()), 1);
+
+  s = su_socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+  TEST_1(s != SOCKET_ERROR);
+
+  connected = connect(s, ai->ai_addr, ai->ai_addrlen);
+
+  su_root_step(tt->tt_root, 50);
+  
+  TEST(send(s, "F", 1, 0), 1);
+  su_root_step(tt->tt_root, 50);
+  TEST(send(s, "O", 1, 0), 1);
+  su_root_step(tt->tt_root, 50);
+  TEST(send(s, "O", 1, 0), 1);
+  su_root_step(tt->tt_root, 50);
+  TEST(send(s, " ", 1, 0), 1);
+  su_root_step(tt->tt_root, 50);
+  
+  tt->tt_received = 0;
+  TEST(tport_test_run(tt, 5), -1);
+  TEST(tt->tt_received, 1);
+  TEST_P(tt->tt_rmsg, NULL);
+  
+  su_close(s);
 
   END();
 }
@@ -967,34 +1125,48 @@
 
   msg_t *msg = NULL;
   int i, n;
-  tport_t *tp;
+  tport_t *tp, *tp0;
   char buffer[32];
 
   if (!tt->tt_sctp_name->tpn_proto) 
     return 0;
 
   /* Just a small and nice message first */
-  TEST_1(!new_test_msg(tt, &msg, "sctp-small", 1, 1024));
+  TEST_1(!new_test_msg(tt, &msg, "cid:sctp-first", 1, 1024));
   test_create_md5(tt, msg);
   TEST_1(tp = tport_tsend(tt->tt_tports, msg, tt->tt_sctp_name, TAG_END()));
   TEST_S(tport_name(tp)->tpn_ident, "client");
   msg_destroy(msg);
-  
+
+  tport_set_params(tp, TPTAG_KEEPALIVE(100), TPTAG_IDLE(500), TAG_END());
+
   TEST(tport_test_run(tt, 5), 1);
     
   TEST_1(!check_msg(tt, tt->tt_rmsg, NULL));
   test_check_md5(tt, tt->tt_rmsg);
   msg_destroy(tt->tt_rmsg), tt->tt_rmsg = NULL;
 
-  if (1)
-    return 0;			/* SCTP does not work reliably. Really. */
+  tp0 = tport_ref(tp);
+
+  pending_server_close = pending_client_close = 0;
+
+  /* Ask for notification upon close */
+  pending_client_close = tport_pend(tp, NULL, client_closed_callback, NULL);
+  TEST_1(pending_client_close > 0);
+  tp = tt->tt_rtport;
+  pending_server_close = tport_pend(tp, NULL, server_closed_callback, NULL);
+  TEST_1(pending_server_close > 0);
+
+  if (0) { /* SCTP does not work reliably. Really. */
+
+  tt->tt_received = 0;
 
   /* Create large messages, just to force queueing in sending end */
   for (n = 0; !tport_queuelen(tp); n++) {
     snprintf(buffer, sizeof buffer, "cid:sctp-%u", n);
     TEST_1(!new_test_msg(tt, &msg, buffer, 1, 32000));
     test_create_md5(tt, msg);
-    TEST_1(tport_tsend(tp, msg, tt->tt_sctp_name, TAG_END()));
+    TEST_1(tp = tport_tsend(tp0, msg, tt->tt_sctp_name, TAG_END()));
     TEST_S(tport_name(tp)->tpn_ident, "client");
     msg_destroy(msg);
   }
@@ -1003,11 +1175,11 @@
   for (i = 1; i < TPORT_QUEUESIZE; i++) {
     snprintf(buffer, sizeof buffer, "cid:sctp-%u", n + i);
     TEST_1(!new_test_msg(tt, &msg, buffer, 1, 1024));
-    TEST_1(tport_tsend(tp, msg, tt->tt_sctp_name, TAG_END()));
+    TEST_1(tp = tport_tsend(tp0, msg, tt->tt_sctp_name, TAG_END()));
     msg_destroy(msg);
   }
 
-  /* This overflows the queue */
+  /* Try to overflow the queue */
   snprintf(buffer, sizeof buffer, "cid:sctp-%u", n + i);
   TEST_1(!new_test_msg(tt, &msg, buffer, 1, 1024));
   TEST_1(!tport_tsend(tt->tt_tports, msg, tt->tt_sctp_name, TAG_END()));
@@ -1020,13 +1192,13 @@
   msg_destroy(tt->tt_rmsg), tt->tt_rmsg = NULL;
 
   /* This uses a new connection */
-  TEST_1(!new_test_msg(tt, &msg, "cid-sctp-new", 1, 1024));
+  TEST_1(!new_test_msg(tt, &msg, "cid:sctp-new", 1, 1024));
   TEST_1(tport_tsend(tt->tt_tports, msg, tt->tt_sctp_name, 
 		     TPTAG_REUSE(0), TAG_END()));
   msg_destroy(msg);
 
   /* Receive every message from queue */
-  for (tt->tt_received = 0; tt->tt_received < TPORT_QUEUESIZE + n;) {
+  for (; tt->tt_received < n + TPORT_QUEUESIZE - 1;) {
     TEST(tport_test_run(tt, 10), 1);
     /* Validate message */
     TEST_1(!check_msg(tt, tt->tt_rmsg, NULL));
@@ -1034,7 +1206,7 @@
   }
   
   /* Try to send a single message */
-  TEST_1(!new_test_msg(tt, &msg, "cid:sctp-final", 1, 1024));
+  TEST_1(!new_test_msg(tt, &msg, "cid:sctp-final", 1, 512));
   TEST_1(tport_tsend(tt->tt_tports, msg, tt->tt_sctp_name, TAG_END()));
   msg_destroy(msg);
   
@@ -1042,6 +1214,15 @@
   
   TEST_1(!check_msg(tt, tt->tt_rmsg, NULL));
   msg_destroy(tt->tt_rmsg), tt->tt_rmsg = NULL;
+  }
+
+  tport_unref(tp0);
+
+  /* Wait until notifications - 
+     client closes when idle and notifys pending, 
+     then server closes and notifys pending */
+  while (pending_server_close || pending_client_close)
+    su_root_step(tt->tt_root, 50);
 
   END();
 }
@@ -1055,15 +1236,35 @@
   msg_t *msg = NULL;
   int i;
   char ident[16];
+  tport_t *tp, *tp0;
 
   TEST_S(dst->tpn_proto, "tls");
 
-  tt->tt_received = 0;
+  /* Send a single message */
+  TEST_1(!new_test_msg(tt, &msg, "tls-first", 1, 1024));
+  TEST_1(tp = tport_tsend(tt->tt_tports, msg, dst, TAG_END()));
+  TEST_1(tp0 = tport_ref(tp));
+  msg_destroy(msg);
 
-  /* Create a large message, just to force queueing in sending end */
-  TEST_1(!new_test_msg(tt, &msg, "tls-0", 16, 64 * 1024));
+  TEST(tport_test_run(tt, 5), 1);
+
+  TEST_1(!check_msg(tt, tt->tt_rmsg, "tls-first"));
+  msg_destroy(tt->tt_rmsg), tt->tt_rmsg = NULL;
+
+  tport_set_params(tp, TPTAG_KEEPALIVE(100), TPTAG_IDLE(500), TAG_END());
+
+  /* Ask for notification upon close */
+  pending_client_close = tport_pend(tp0, NULL, client_closed_callback, NULL);
+  TEST_1(pending_client_close > 0);
+  tp = tt->tt_rtport;
+  pending_server_close = tport_pend(tp, NULL, server_closed_callback, NULL);
+  TEST_1(pending_server_close > 0);
+
+  /* Send a largish message */
+  TEST_1(!new_test_msg(tt, &msg, "tls-0", 16, 16 * 1024));
   test_create_md5(tt, msg);
-  TEST_1(tport_tsend(tt->tt_tports, msg, dst, TAG_END()));
+  TEST_1(tp = tport_tsend(tt->tt_tports, msg, dst, TAG_END()));
+  TEST_1(tp == tp0);
   msg_destroy(msg);
 
   /* Fill up the queue */
@@ -1071,25 +1272,20 @@
     snprintf(ident, sizeof ident, "tls-%u", i);
 
     TEST_1(!new_test_msg(tt, &msg, ident, 2, 512));
-    TEST_1(tport_tsend(tt->tt_tports, msg, dst, TAG_END()));
+    TEST_1(tp = tport_tsend(tt->tt_tports, msg, dst, TAG_END()));
+    TEST_1(tp == tp0);
     msg_destroy(msg);
   }
 
-  /* This overflows the queue */
-  TEST_1(!new_test_msg(tt, &msg, "tls-overflow", 1, 1024));
-  TEST_1(!tport_tsend(tt->tt_tports, msg, dst, TAG_END()));
-  msg_destroy(msg);
-
-  TEST(tport_test_run(tt, 60), 1);
-
-  msg_destroy(tt->tt_rmsg), tt->tt_rmsg = NULL;
-
   /* This uses a new connection */
   TEST_1(!new_test_msg(tt, &msg, "tls-no-reuse", 1, 1024));
-  TEST_1(tport_tsend(tt->tt_tports, msg, dst, 
+  TEST_1(tp = tport_tsend(tt->tt_tports, msg, dst, 
 		     TPTAG_REUSE(0), TAG_END()));
+  TEST_1(tp != tp0);
   msg_destroy(msg);
 
+  tt->tt_received = 0;
+
   /* Receive every message from queue */
   while (tt->tt_received < TPORT_QUEUESIZE + 1) {
     TEST(tport_test_run(tt, 5), 1);
@@ -1107,6 +1303,14 @@
   TEST_1(!check_msg(tt, tt->tt_rmsg, "tls-last"));
   msg_destroy(tt->tt_rmsg), tt->tt_rmsg = NULL;
 
+  tport_decref(&tp0);
+
+  /* Wait until notifications - 
+     client closes when idle and notifys pending, 
+     then server closes and notifys pending */
+  while (pending_server_close || pending_client_close)
+    su_root_step(tt->tt_root, 50);
+  
 #endif
 
   END();
@@ -1363,9 +1567,24 @@
   END();
 }
 
+#if HAVE_ALARM
+#include <signal.h>
+
+static RETSIGTYPE sig_alarm(int s)
+{
+  fprintf(stderr, "%s: FAIL! test timeout!\n", name);
+  exit(1);
+}
+
+char const alarm_option[] = " [--no-alarm]";
+
+#else
+char const alarm_option[] = "";
+#endif
+
 void usage(int exitcode)
 {
-  fprintf(stderr, "usage: %s [-v] [-a]\n", name);
+  fprintf(stderr, "usage: %s [-v] [-a]%s\n", name, alarm_option);
   exit(exitcode);
 }
 
@@ -1373,7 +1592,9 @@
 {
   int flags = 0;	/* XXX */
   int retval = 0;
+  int no_alarm = 0;
   int i;
+
   tp_test_t tt[1] = {{{ SU_HOME_INIT(tt) }}};
 
   for (i = 1; argv[i]; i++) {
@@ -1381,6 +1602,8 @@
       tstflags |= tst_verbatim;
     else if (strcmp(argv[i], "-a") == 0 || strcmp(argv[i], "--abort") == 0)
       tstflags |= tst_abort;
+    else if (strcmp(argv[i], "--no-alarm") == 0)
+      no_alarm = 1;
     else
       usage(1);
   }
@@ -1389,6 +1612,13 @@
   tstflags |= tst_verbatim;
 #endif
 
+#if HAVE_ALARM
+  if (!no_alarm) {
+    signal(SIGALRM, sig_alarm);
+    alarm(120);
+  }
+#endif
+
   /* Use log */
   if (flags & tst_verbatim)
     tport_log->log_default = 9;
@@ -1406,6 +1636,7 @@
     retval |= sctp_test(tt); fflush(stdout);
     retval |= udp_test(tt); fflush(stdout);
     retval |= tcp_test(tt); fflush(stdout);
+    retval |= test_incomplete(tt); fflush(stdout);
     retval |= reuse_test(tt); fflush(stdout);
     retval |= tls_test(tt); fflush(stdout);
     if (0)			/* Not yet working... */

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport.c	Mon Sep 10 16:45:25 2007
@@ -47,7 +47,7 @@
 typedef struct tport_nat_s tport_nat_t;
 
 #define SU_WAKEUP_ARG_T         struct tport_s
-#define SU_TIMER_ARG_T          struct tport_master
+#define SU_TIMER_ARG_T          struct tport_s
 #define SU_MSG_ARG_T            union tport_su_msg_arg
 
 #include <sofia-sip/su_wait.h>
@@ -127,6 +127,33 @@
 	      TP_IS_RED, TP_SET_RED, TP_IS_BLACK, TP_SET_BLACK, TP_COPY_COLOR,
 	      tp_cmp, TP_INSERT, TP_REMOVE);
 
+static void tplist_insert(tport_t **list, tport_t *tp)
+{
+  if (*list == NULL)
+    *list = tp;
+  else
+    tp->tp_right = *list, (*list)->tp_left = tp, *list = tp;
+
+  for (tp = *list; tp; tp = tp->tp_right) {
+    assert(tp->tp_left == NULL || tp == tp->tp_left->tp_right);
+    assert(tp->tp_right == NULL || tp == tp->tp_right->tp_left);
+  }
+}
+
+static void tplist_remove(tport_t **list, tport_t *tp)
+{
+  if (*list == tp) {
+    *list = tp->tp_right; assert(tp->tp_left == NULL);
+  }
+  else if (tp->tp_left) {
+    tp->tp_left->tp_right = tp->tp_right;
+  }
+  if (tp->tp_right) {
+    tp->tp_right->tp_left = tp->tp_left;
+  }
+  TP_REMOVE(tp);
+}
+
 enum {
   /** Default per-thread read queue length */
   THRP_PENDING = 8
@@ -304,6 +331,12 @@
      !self->tp_send_close);
 }
 
+/** Return true if transport has message in send queue. @NEW_1_12_7  */
+int tport_has_queued(tport_t const *self)
+{
+  return self && self->tp_queue && self->tp_queue[self->tp_qhead];
+}
+
 /** MTU for transport  */
 su_inline unsigned tport_mtu(tport_t const *self)
 {
@@ -387,9 +420,6 @@
 			   tp_name_t const *tpn);
 
 void tport_peer_address(tport_t *self, msg_t *msg);
-static unsigned long tport_now(void);
-
-static void tport_tick(su_root_magic_t *, su_timer_t *, tport_master_t *mr);
 
 static void tport_parse(tport_t *self, int complete, su_time_t now);
 
@@ -410,7 +440,6 @@
 static char *localipname(int pf, char *buf, size_t bufsiz);
 static int getprotohints(su_addrinfo_t *hints,
 			 char const *proto, int flags);
-static void tport_send_queue(tport_t *self);
 
 
 /* Stack class used when transports are being destroyed */
@@ -454,7 +483,6 @@
   tport_master_t *mr;
   tp_name_t *tpn;
   tport_params_t *tpp;
-  unsigned tick;
   ta_list ta;
 
   if (!stack || !tpac || !root) {
@@ -483,6 +511,9 @@
   tpp->tpp_idle = UINT_MAX;
   tpp->tpp_timeout = UINT_MAX;
   tpp->tpp_sigcomp_lifetime = UINT_MAX;
+  tpp->tpp_keepalive = 0;
+  tpp->tpp_pingpong = 0;
+  tpp->tpp_pong2ping = 0;
   tpp->tpp_stun_server = 1;
   tpp->tpp_tos = -1;                  /* set invalid, valid values are 0-255 */
 
@@ -497,21 +528,10 @@
   tport_set_params(mr->mr_master, ta_tags(ta));
   tport_open_log(mr, ta_args(ta));
 
-  tick = 5000; /* For testing, usually 30000 is enough */  
-  if (tpp->tpp_idle < 4 * tick)
-    tick = tpp->tpp_idle / 4;
-  if (tpp->tpp_timeout < 4 * tick)
-    tick = tpp->tpp_timeout / 4;
-  if (tick < 200)
-    tick = 200;
-
 #if HAVE_SOFIA_STUN
   tport_init_stun_server(mr, ta_args(ta));
 #endif
 
-  mr->mr_timer = su_timer_create(su_root_task(root), tick);
-  su_timer_set(mr->mr_timer, tport_tick, mr);
-
   ta_end(ta);
 
   return mr->mr_master;
@@ -640,8 +660,10 @@
   if (pri->pri_vtable->vtp_deinit_primary)
     pri->pri_vtable->vtp_deinit_primary(pri);
 
-  while (pri->pri_secondary)
-    tport_zap_secondary(pri->pri_secondary);
+  while (pri->pri_open)
+    tport_zap_secondary(pri->pri_open);
+  while (pri->pri_closed)
+    tport_zap_secondary(pri->pri_closed);
 
   /* We have just a single-linked list for primary transports */
   for (prip = &pri->pri_master->mr_primaries;
@@ -651,7 +673,7 @@
 
   *prip = pri->pri_next;
 
-  tport_zap_secondary(pri->pri_primary);
+  tport_zap_secondary((tport_t *)pri);
 }
 
 /**Create a primary transport object with socket.
@@ -724,7 +746,7 @@
   pri->pri_primary->tp_has_connection = 0;
 
   SU_DEBUG_5(("%s(%p): %s " TPN_FORMAT "\n", 
-			  __func__, (void *)pri, "listening at",
+	      __func__, (void *)pri, "listening at",
 	      TPN_ARGS(pri->pri_primary->tp_name)));
 
   return pri;
@@ -812,9 +834,8 @@
 
 /**Allocate a secondary transport. @internal
  *
- * The function tport_alloc_secondary() creates a secondary transport
- * object. The new transport initally shares parameters structure with the
- * original transport.
+ * Create a secondary transport object. The new transport initally shares
+ * parameters structure with the original transport.
  *
  * @param pri    primary transport
  * @param socket socket for transport
@@ -836,7 +857,8 @@
   self = su_home_clone(mr->mr_home, pri->pri_vtable->vtp_secondary_size);
 
   if (self) {
-	  SU_DEBUG_7(("%s(%p): new secondary tport %p\n", __func__, (void *)pri, (void *)self));
+    SU_DEBUG_7(("%s(%p): new secondary tport %p\n", 
+		__func__, (void *)pri, (void *)self));
 
     self->tp_refs = -1;			/* Freshly allocated  */
     self->tp_master = mr;
@@ -850,7 +872,10 @@
     self->tp_addrinfo->ai_addr = (void *)self->tp_addr;
 
     self->tp_socket = socket;
-    
+
+    self->tp_timer = su_timer_create(su_root_task(mr->mr_root), 0);
+    self->tp_stime = self->tp_ktime = self->tp_rtime = su_now();
+
     if (pri->pri_vtable->vtp_init_secondary &&
 	pri->pri_vtable->vtp_init_secondary(self, socket, accepted,
 					    return_reason) < 0) {
@@ -889,13 +914,18 @@
 		       su_addrinfo_t *ai,
 		       tp_name_t const *tpn)
 {
+  tport_t *tp;
+
   if (ai == NULL || ai->ai_addrlen > sizeof (pri->pri_primary->tp_addr))
     return NULL;
 
   if (pri->pri_vtable->vtp_connect)
     return pri->pri_vtable->vtp_connect(pri, ai, tpn);
-  else
-    return tport_base_connect(pri, ai, ai, tpn);
+
+  tp = tport_base_connect(pri, ai, ai, tpn);
+  if (tp)
+    tport_set_secondary_timer(tp);
+  return tp;
 }
 
 /**Create a connected transport object with socket.
@@ -961,14 +991,14 @@
 
     /* Bind this socket to same IP address as the primary server socket */
     if (getsockname(server_socket, &susa.su_sa, &susalen) < 0) {
-      SU_DEBUG_3(("tport_connect: getsockname(): %s\n", 
-		  su_strerror(su_errno())));
+      SU_DEBUG_3(("%s(%p): getsockname(): %s\n", 
+		  __func__, (void *)self, su_strerror(su_errno())));
     }
     else {
       susa.su_port = 0;
       if (bind(s, &susa.su_sa, susalen) < 0) {
-	SU_DEBUG_3(("tport_connect: bind(local-ip): %s\n", 
-		    su_strerror(su_errno())));
+	SU_DEBUG_3(("%s(%p): bind(local-ip): %s\n", 
+		    __func__, (void *)self, su_strerror(su_errno())));
       }
     }
   }
@@ -1005,16 +1035,16 @@
 
   if (ai == real_ai) {
     SU_DEBUG_5(("%s(%p): %s to " TPN_FORMAT "\n", 
-				__func__, (void *)self, what, TPN_ARGS(self->tp_name)));
+		__func__, (void *)self, what, TPN_ARGS(self->tp_name)));
   }
   else {
     SU_DEBUG_5(("%s(%p): %s via %s to " TPN_FORMAT "\n",
-				__func__, (void *)self, what,
+		__func__, (void *)self, what,
 		tport_hostport(buf, sizeof(buf), (void *)ai->ai_addr, 2),
 		TPN_ARGS(self->tp_name)));
   }
-  
-  tprb_append(&pri->pri_secondary, self);
+
+  tprb_append(&pri->pri_open, self);
 
   return self;
 }
@@ -1028,7 +1058,13 @@
     return;
 
   /* Remove from rbtree */
-  tprb_remove(&self->tp_pri->pri_secondary, self);
+  if (!tport_is_closed(self))
+    tprb_remove(&self->tp_pri->pri_open, self);
+  else
+    tplist_remove(&self->tp_pri->pri_closed, self);
+
+  if (self->tp_timer)
+    su_timer_destroy(self->tp_timer), self->tp_timer = NULL;
 
   /* Do not deinit primary as secondary! */
   if (tport_is_secondary(self) &&
@@ -1038,21 +1074,22 @@
   if (self->tp_msg) {
     msg_destroy(self->tp_msg), self->tp_msg = NULL;
     SU_DEBUG_3(("%s(%p): zapped partially received message\n", 
-				__func__, (void *)self));
+		__func__, (void *)self));
   }
 
-  if (self->tp_queue && self->tp_queue[self->tp_qhead]) {
+  if (tport_has_queued(self)) {
     size_t n = 0, i, N = self->tp_params->tpp_qsize;
     for (i = self->tp_qhead; self->tp_queue[i]; i = (i + 1) % N) {
       msg_destroy(self->tp_queue[i]), self->tp_queue[i] = NULL;
       n++;
     }
     SU_DEBUG_3(("%s(%p): zapped %lu queued messages\n", 
-				__func__, (void *)self, (LU)n));
+		__func__, (void *)self, (LU)n));
   }
 
   if (self->tp_pused) {
-	  SU_DEBUG_3(("%s(%p): zapped with pending requests\n", __func__, (void *)self));
+    SU_DEBUG_3(("%s(%p): zapped while pending\n",
+		__func__, (void *)self));
   }
 
   mr = self->tp_master;
@@ -1086,10 +1123,18 @@
 /** Destroy reference to a transport object. */
 void tport_unref(tport_t *tp)
 {
-  if (tp && tp->tp_refs > 0)
-    if (--tp->tp_refs == 0 && tp->tp_params->tpp_idle == 0)
-      if (!tport_is_closed(tp))
-	tport_close(tp);
+  if (tp == NULL || tp->tp_refs <= 0)
+    return;
+  if (--tp->tp_refs > 0)
+    return;
+  
+  if (!tport_is_secondary(tp))
+    return;
+
+  if (tp->tp_params->tpp_idle == 0)
+    tport_close(tp);
+
+  tport_set_secondary_timer(tp);
 }
 
 /** Create a new reference to transport object. */
@@ -1113,6 +1158,14 @@
  *
  * @param self          pointer to a transport object
  * @param tag,value,... list of tags
+ *
+ * @TAGS
+ * TPTAG_MTU_REF(), TPTAG_QUEUESIZE_REF(), TPTAG_IDLE_REF(),
+ * TPTAG_TIMEOUT_REF(), TPTAG_KEEPALIVE_REF(), TPTAG_PINGPONG_REF(),
+ * TPTAG_PONG2PING_REF(), TPTAG_DEBUG_DROP_REF(), TPTAG_THRPSIZE_REF(),
+ * TPTAG_THRPRQSIZE_REF(), TPTAG_SIGCOMP_LIFETIME_REF(),
+ * TPTAG_CONNECT_REF(), TPTAG_SDWN_ERROR_REF(), TPTAG_REUSE_REF(),
+ * TPTAG_STUN_SERVER_REF(), TPTAG_PUBLIC_REF() and TPTAG_TOS_REF().
  */
 int tport_get_params(tport_t const *self,
 		     tag_type_t tag, tag_value_t value, ...)
@@ -1140,10 +1193,15 @@
 	       TPTAG_QUEUESIZE(tpp->tpp_qsize),
 	       TPTAG_IDLE(tpp->tpp_idle),
 	       TPTAG_TIMEOUT(tpp->tpp_timeout),
+	       TPTAG_KEEPALIVE(tpp->tpp_keepalive),
+	       TPTAG_PINGPONG(tpp->tpp_pingpong),
+	       TPTAG_PONG2PING(tpp->tpp_pong2ping),
 	       TPTAG_SDWN_ERROR(tpp->tpp_sdwn_error),
 	       TPTAG_DEBUG_DROP(tpp->tpp_drop),
 	       TPTAG_THRPSIZE(tpp->tpp_thrpsize),
 	       TPTAG_THRPRQSIZE(tpp->tpp_thrprqsize),
+	       TPTAG_SIGCOMP_LIFETIME(tpp->tpp_sigcomp_lifetime),
+	       TPTAG_STUN_SERVER(tpp->tpp_stun_server),
 	       TAG_IF(pri, TPTAG_PUBLIC(pri ? pri->pri_public : 0)),
 	       TPTAG_TOS(tpp->tpp_tos),
 	       TAG_END());
@@ -1160,6 +1218,7 @@
  *
  * @TAGS
  * TPTAG_MTU(), TPTAG_QUEUESIZE(), TPTAG_IDLE(), TPTAG_TIMEOUT(),
+ * TPTAG_KEEPALIVE(), TPTAG_PINGPONG(), TPTAG_PONG2PING(),
  * TPTAG_DEBUG_DROP(), TPTAG_THRPSIZE(), TPTAG_THRPRQSIZE(),
  * TPTAG_SIGCOMP_LIFETIME(), TPTAG_CONNECT(), TPTAG_SDWN_ERROR(),
  * TPTAG_REUSE(), TPTAG_STUN_SERVER(), and TPTAG_TOS().
@@ -1172,7 +1231,7 @@
   tport_params_t tpp[1], *tpp0;
   
   usize_t mtu;
-  int connect, sdwn_error, reusable, stun_server;
+  int connect, sdwn_error, reusable, stun_server, pong2ping;
   
   if (self == NULL)
     return su_seterrno(EINVAL);
@@ -1184,6 +1243,7 @@
   sdwn_error = tpp->tpp_sdwn_error;
   reusable = self->tp_reusable;
   stun_server = tpp->tpp_stun_server;
+  pong2ping = tpp->tpp_pong2ping;
 
   ta_start(ta, tag, value);
 
@@ -1192,6 +1252,9 @@
 	      TAG_IF(!self->tp_queue, TPTAG_QUEUESIZE_REF(tpp->tpp_qsize)),
 	      TPTAG_IDLE_REF(tpp->tpp_idle),
 	      TPTAG_TIMEOUT_REF(tpp->tpp_timeout),
+	      TPTAG_KEEPALIVE_REF(tpp->tpp_keepalive),
+	      TPTAG_PINGPONG_REF(tpp->tpp_pingpong),
+	      TPTAG_PONG2PING_REF(pong2ping),
 	      TPTAG_DEBUG_DROP_REF(tpp->tpp_drop),
 	      TPTAG_THRPSIZE_REF(tpp->tpp_thrpsize),
 	      TPTAG_THRPRQSIZE_REF(tpp->tpp_thrprqsize),
@@ -1208,10 +1271,10 @@
   if (n == 0)
     return 0;
 
-  if (tpp->tpp_idle > 0 && tpp->tpp_idle < 2000)
-    tpp->tpp_idle = 2000;
-  if (tpp->tpp_timeout < 1000)
-    tpp->tpp_timeout = 1000;
+  if (tpp->tpp_idle > 0 && tpp->tpp_idle < 100)
+    tpp->tpp_idle = 100;
+  if (tpp->tpp_timeout < 100)
+    tpp->tpp_timeout = 100;
   if (tpp->tpp_drop > 1000)
     tpp->tpp_drop = 1000;
   if (tpp->tpp_thrprqsize > 0)
@@ -1229,6 +1292,10 @@
   tpp->tpp_sdwn_error = sdwn_error;
   self->tp_reusable = reusable;
   tpp->tpp_stun_server = stun_server;
+  tpp->tpp_pong2ping = pong2ping;
+
+  if (memcmp(tpp0, tpp, sizeof tpp) == 0)
+    return n;
 
   if (tport_is_secondary(self) && 
       self->tp_params == self->tp_pri->pri_primary->tp_params) {
@@ -1238,6 +1305,9 @@
 
   memcpy(tpp0, tpp, sizeof tpp);
 
+  if (tport_is_secondary(self))
+    tport_set_secondary_timer(self);
+
   return n;
 }
 
@@ -1441,7 +1511,8 @@
   if (public == tport_type_local)
     public = tport_type_client;
   
-  SU_DEBUG_5(("%s(%p) to " TPN_FORMAT "\n", __func__, (void *)mr, TPN_ARGS(tpn)));
+  SU_DEBUG_5(("%s(%p) to " TPN_FORMAT "\n",
+	      __func__, (void *)mr, TPN_ARGS(tpn)));
 
   memset(tpn0, 0, sizeof(tpn0));
 
@@ -1503,7 +1574,8 @@
 
   (void)hostname;
   
-  SU_DEBUG_5(("%s(%p) to " TPN_FORMAT "\n", __func__, (void *)mr, TPN_ARGS(tpn)));
+  SU_DEBUG_5(("%s(%p) to " TPN_FORMAT "\n",
+	      __func__, (void *)mr, TPN_ARGS(tpn)));
 
   if (tpn->tpn_host == NULL || strcmp(tpn->tpn_host, tpn_any) == 0) {
     /* Use a local IP address */
@@ -1618,7 +1690,7 @@
       break;
 
     SU_DEBUG_3(("%s(%p): cannot bind all transports to port %u, trying %u\n", 
-				__func__, (void *)mr, old, port));
+		__func__, (void *)mr, old, port));
   }
 
   tport_freeaddrinfo(res);
@@ -1723,7 +1795,8 @@
     int error = tport_getaddrinfo(host, service, hints, return_addrinfo);
     if (error || !*return_addrinfo) {
       SU_DEBUG_3(("%s(%p): su_getaddrinfo(%s, %s) for %s: %s\n", 
-				  __func__, (void *)mr, host ? host : "\"\"", service, protocol,
+		  __func__, (void *)mr,
+		  host ? host : "\"\"", service, protocol,
 		  su_gai_strerror(error)));
       return su_seterrno(error != EAI_MEMORY ? ENOENT : ENOMEM);
     }
@@ -1774,13 +1847,13 @@
   if (error) {
 #if SU_HAVE_IN6
     SU_DEBUG_3(("%s(%p): su_getlocalinfo() for %s address: %s\n", 
-				__func__, (void *)mr, 
+		__func__, (void *)mr, 
 		family == AF_INET6 ? "ip6" 
 		: family == AF_INET ? "ip4" : "ip",
 		su_gli_strerror(error)));
 #else
     SU_DEBUG_3(("%s(%p): su_getlocalinfo() for %s address: %s\n", 
-				__func__, (void *)mr, 
+		__func__, (void *)mr, 
 		family == AF_INET ? "ip4" : "ip",
 		su_gli_strerror(error)));
 #endif
@@ -1972,13 +2045,19 @@
 
 /** Close a transport. 
  * 
- * The function tport_close() closes a socket associated with a transport
- * object.
+ * Close the socket associated with a transport object. Report an error to
+ * all pending clients, if required. Set/reset timer, too.
  */
 void tport_close(tport_t *self)
 {
-  SU_DEBUG_5(("%s(%p): " TPN_FORMAT "\n", "tport_close", (void *)self,
-	      TPN_ARGS(self->tp_name)));
+  SU_DEBUG_5(("%s(%p): " TPN_FORMAT "\n",
+	      __func__, (void *)self, TPN_ARGS(self->tp_name)));
+
+  if (self->tp_closed || !tport_is_secondary(self))
+    return;
+
+  tprb_remove(&self->tp_pri->pri_open, self);
+  tplist_insert(&self->tp_pri->pri_closed, self);
 
   self->tp_closed = 1;
   self->tp_send_close = 3;
@@ -2009,7 +2088,7 @@
 	msg_ref_destroy(self->tp_queue[i]), self->tp_queue[i] = NULL;
     }
   }
-
+  
   self->tp_index = 0;
   self->tp_events = 0;
 }
@@ -2024,16 +2103,23 @@
  */
 int tport_shutdown(tport_t *self, int how)
 {
+  int retval;
   if (!tport_is_secondary(self))
     return -1;
-  
-  SU_DEBUG_7(("%s(%p, %d)\n", "tport_shutdown", (void *)self, how));
+  retval = tport_shutdown0(self, how);
+  tport_set_secondary_timer(self);
+  return retval;
+}
+
+/** Internal shutdown function */
+int tport_shutdown0(tport_t *self, int how)
+{
+  SU_DEBUG_7(("%s(%p, %d)\n", __func__, (void *)self, how));
 
   if (!tport_is_tcp(self) ||
-      how < 0 || 
+      how < 0 || how >= 2 ||
       (how == 0 && self->tp_send_close) ||
-      (how == 1 && self->tp_recv_close > 1) || 
-      how >= 2) {
+      (how == 1 && self->tp_recv_close > 1)) {
     tport_close(self);
     return 1;
   }
@@ -2052,7 +2138,7 @@
   else if (how == 1) {
     self->tp_send_close = 2;
     tport_set_events(self, 0, SU_WAIT_OUT);
-    if (self->tp_queue && self->tp_queue[self->tp_qhead]) {
+    if (tport_has_queued(self)) {
       unsigned short i, N = self->tp_params->tpp_qsize;
       for (i = 0; i < N; i++) {
 	if (self->tp_queue[i]) {
@@ -2063,107 +2149,143 @@
     }
   }
 
-
   return 0;
 }
 
-su_inline
-unsigned long tport_now(void)
+static void tport_secondary_timer(su_root_magic_t *magic,
+				  su_timer_t *t,
+				  tport_t *self)
 {
-  return su_now().tv_sec;
+  su_time_t now;
+
+  if (tport_is_closed(self)) {
+    if (self->tp_refs == 0)
+      tport_zap_secondary(self);
+    return;
+  }
+
+  now = /* su_timer_expired(t); */ su_now();
+
+  if (self->tp_pri->pri_vtable->vtp_secondary_timer)
+    self->tp_pri->pri_vtable->vtp_secondary_timer(self, now);
+  else
+    tport_base_timer(self, now);
 }
 
-/** Transport timer function. */
-static
-void tport_tick(su_root_magic_t *magic, su_timer_t *t, tport_master_t *mr)
+/** Base timer for secondary transports.  
+ *
+ * Closes and zaps unused transports.  Sets the timer again.
+ */
+void tport_base_timer(tport_t *self, su_time_t now)
 {
-  tport_primary_t *dad;
-  tport_t *tp, *tp_next;
-  su_time_t now = su_now();
-  int ts = (int)su_time_ms(now);
-
-  /* Go through all primary transports */
-  for (dad = mr->mr_primaries; dad; dad = dad->pri_next) {
-    if (dad->pri_primary->tp_addrinfo->ai_protocol == IPPROTO_SCTP) {
-      /* Go through all SCTP connections */
-
-      tp = dad->pri_secondary;
-
-      for (tp = tprb_first(tp); tp; tp = tp_next) {
-	tp_next = tprb_succ(tp);
-	if (tp->tp_queue && tp->tp_queue[tp->tp_qhead]) {
-	  SU_DEBUG_9(("tport_tick(%p) - trying to send to %s/%s:%s\n", 
-				  (void *)tp, tp->tp_protoname, tp->tp_host, tp->tp_port));
-	  tport_send_queue(tp);
-	}
-      }      
+  unsigned timeout = self->tp_params->tpp_idle;
+
+  if (timeout != UINT_MAX) {
+    if (self->tp_refs == 0 && 
+	self->tp_msg == NULL && 
+	!tport_has_queued(self) &&
+	su_time_cmp(su_time_add(self->tp_rtime, timeout), now) < 0 &&
+	su_time_cmp(su_time_add(self->tp_stime, timeout), now) < 0) {
+      SU_DEBUG_7(("%s(%p): unused for %d ms,%s zapping\n",
+		  __func__, (void *)self,
+		  timeout, tport_is_closed(self) ? "" : " closing and"));
+      if (!tport_is_closed(self))
+	tport_close(self);
+      tport_zap_secondary(self);
+      return;
     }
+  }
 
-    /* Go through all secondary transports with incomplete messages */
-    for (tp = tprb_first(dad->pri_secondary); tp; tp = tp_next) {
-      msg_t *msg = tp->tp_msg;
-      int closed;
-
-      if (msg &&
-	  tp->tp_params->tpp_timeout < INT_MAX && 
-	  (int)tp->tp_params->tpp_timeout < ts - (int)tp->tp_time &&
-	  !msg_is_streaming(msg)) {
-	SU_DEBUG_5(("tport_tick(%p): incomplete message idle for %d ms\n",
-				(void *)tp, ts - (int)tp->tp_time));
-	msg_set_streaming(msg, 0);
-	msg_set_flags(msg, MSG_FLG_ERROR | MSG_FLG_TRUNC | MSG_FLG_TIMEOUT);
-	tport_deliver(tp, msg, NULL, NULL, now);
-	tp->tp_msg = NULL;
-      }
+  tport_set_secondary_timer(self);
+}
 
-      tp_next = tprb_succ(tp);
+/** Set timer for a secondary transport. 
+ *
+ * This function should be called after any network activity:
+ * tport_base_connect(), tport_send_msg(), tport_send_queue(),
+ * tport_recv_data(), tport_shutdown0(), tport_close(),
+ *
+ * @retval 0 always
+ */
+int tport_set_secondary_timer(tport_t *self)
+{
+  su_time_t const infinity = { ULONG_MAX, 999999 };
+  su_time_t target = infinity;
+  char const *why = "not specified";
+  su_timer_f timer = tport_secondary_timer;
 
-      if (tp->tp_refs)
-	continue;
+  if (!tport_is_secondary(self))
+    return 0;
 
-      closed = tport_is_closed(tp);
+  if (tport_is_closed(self)) {
+    if (self->tp_refs == 0) {
+      SU_DEBUG_7(("tport(%p): set timer at %u ms because %s\n",
+		  self, 0, "zap"));
+      su_timer_set_interval(self->tp_timer, timer, self, 0);
+    }
+    else
+      su_timer_reset(self->tp_timer);
 
-      if (!closed &&
-	  !(tp->tp_params->tpp_idle > 0 
-	    && (int)tp->tp_params->tpp_idle < ts - (int)tp->tp_time)) {
-	continue;
-      }
+    return 0;
+  }
 
-      if (closed) {
-		  SU_DEBUG_5(("tport_tick(%p): closed, zapping\n", (void *)tp));
-      } else {
-	SU_DEBUG_5(("tport_tick(%p): unused for %d ms, closing and zapping\n",
-				(void *)tp, ts - (int)tp->tp_time));
-	if (!tport_is_closed(tp))
-	  tport_close(tp);
+  if (self->tp_params->tpp_idle != UINT_MAX) {
+    if (self->tp_refs == 0 && 
+	self->tp_msg == NULL && !tport_has_queued(self)) {
+      if (su_time_cmp(self->tp_stime, self->tp_rtime) < 0) {
+	target = su_time_add(self->tp_rtime, self->tp_params->tpp_idle);
+	why = "idle since recv";
+      }
+      else {
+	target = su_time_add(self->tp_stime, self->tp_params->tpp_idle);
+	why = "idle since send";
       }
-
-      tport_zap_secondary(tp);
     }
   }
 
-  su_timer_set(t, tport_tick, mr);
+  if (self->tp_pri->pri_vtable->vtp_next_secondary_timer)
+    self->tp_pri->pri_vtable->
+      vtp_next_secondary_timer(self, &target, &why);
+
+  if (su_time_cmp(target, infinity)) {
+    SU_DEBUG_7(("tport(%p): set timer at %ld ms because %s\n",
+		(void *)self, su_duration(target, su_now()), why));
+    su_timer_set_at(self->tp_timer, timer, self, target);
+  }
+  else {
+    SU_DEBUG_9(("tport(%p): reset timer\n", (void *)self));
+    su_timer_reset(self->tp_timer);
+  }
+
+  return 0;
 }
 
+
 /** Flush idle connections. */
 int tport_flush(tport_t *tp)
 {
   tport_t *tp_next;
+  tport_primary_t *pri;
 
   if (tp == NULL)
     return -1;
 
+  pri = tp->tp_pri;
+
+  while (pri->pri_closed)
+    tport_zap_secondary(pri->pri_closed);
+
   /* Go through all secondary transports, zap idle ones */
-  for (tp = tprb_first(tp->tp_pri->pri_secondary); tp; tp = tp_next) {
+  for (tp = tprb_first(tp->tp_pri->pri_open); tp; tp = tp_next) {
     tp_next = tprb_succ(tp);
 
     if (tp->tp_refs != 0)
       continue;
 
     SU_DEBUG_1(("tport_flush(%p): %szapping\n",
-				(void *)tp, tport_is_closed(tp) ? "" : "closing and "));
-    if (!tport_is_closed(tp))
-      tport_close(tp);
+		(void *)tp, tport_is_closed(tp) ? "" : "closing and "));
+
+    tport_close(tp);
     tport_zap_secondary(tp);
   }
 
@@ -2401,10 +2523,10 @@
   }
   else {
     if (tport_is_primary(self))
-		SU_DEBUG_3(("%s(%p): %s (with %s)\n", __func__, (void *)self, 
+      SU_DEBUG_3(("%s(%p): %s (with %s)\n", __func__, (void *)self, 
 		  errmsg, self->tp_protoname));
     else
-		SU_DEBUG_3(("%s(%p): %s (with %s/%s:%s)\n", __func__, (void *)self, 
+      SU_DEBUG_3(("%s(%p): %s (with %s/%s:%s)\n", __func__, (void *)self, 
 		  errmsg, self->tp_protoname, self->tp_host, self->tp_port));
   }
 
@@ -2470,9 +2592,9 @@
 
       if (tport_setname(self, pri->pri_protoname, ai, NULL) != -1) {
 	SU_DEBUG_5(("%s(%p): new connection from " TPN_FORMAT "\n", 
-				__func__,  (void *)self, TPN_ARGS(self->tp_name)));
+		    __func__,  (void *)self, TPN_ARGS(self->tp_name)));
 
-	tprb_append(&pri->pri_secondary, self);
+	tprb_append(&pri->pri_open, self);
 
 	/* Return succesfully */
 	return 0;
@@ -2550,15 +2672,20 @@
   su_root_deregister(mr->mr_root, self->tp_index);
   self->tp_index = -1;
   self->tp_events = SU_WAIT_IN | SU_WAIT_ERR | SU_WAIT_HUP;
+
   if (su_wait_create(wait, self->tp_socket, self->tp_events) == -1 ||
       (self->tp_index = su_root_register(mr->mr_root, 
 					 wait, tport_wakeup, self, 0))
       == -1) {
     tport_close(self);
+    tport_set_secondary_timer(self);
+    return 0;
   }
-  else if (self->tp_queue && self->tp_queue[self->tp_qhead]) {
+
+  if (tport_has_queued(self))
     tport_send_event(self);
-  }
+  else
+    tport_set_secondary_timer(self);
 
   return 0;
 }
@@ -2574,7 +2701,7 @@
 #endif
 
   SU_DEBUG_7(("%s(%p): events%s%s%s%s%s%s\n", 
-			  "tport_wakeup_pri", (void *)self,
+	      "tport_wakeup_pri", (void *)self,
 	      events & SU_WAIT_IN ? " IN" : "",
 	      SU_WAIT_ACCEPT != SU_WAIT_IN && 
 	      (events & SU_WAIT_ACCEPT) ? " ACCEPT" : "",
@@ -2600,7 +2727,7 @@
 #endif
 
   SU_DEBUG_7(("%s(%p): events%s%s%s%s%s\n", 
-			  "tport_wakeup", (void *)self,
+	      "tport_wakeup", (void *)self,
 	      events & SU_WAIT_IN ? " IN" : "",
 	      events & SU_WAIT_OUT ? " OUT" : "",
 	      events & SU_WAIT_HUP ? " HUP" : "",
@@ -2629,8 +2756,12 @@
   if ((events & SU_WAIT_HUP) && !self->tp_closed)
     tport_hup_event(self);
 
-  if (error)
+  if (error) {
+    if (self->tp_closed && error == EPIPE)
+      return 0;
+
     tport_error_report(self, error, NULL);
+  }
 
   return 0;
 }
@@ -2654,7 +2785,7 @@
  */
 void tport_hup_event(tport_t *self)
 {
-	SU_DEBUG_7(("%s(%p)\n", __func__, (void *)self));
+  SU_DEBUG_7(("%s(%p)\n", __func__, (void *)self));
 
   if (self->tp_msg) {
     su_time_t now = su_now();
@@ -2662,8 +2793,12 @@
     tport_parse(self, 1, now);
   }
 
+  if (!tport_is_secondary(self))
+    return;
+
   /* End of stream */
-  tport_shutdown(self, 0);
+  tport_shutdown0(self, 0);
+  tport_set_secondary_timer(self);
 }
 
 /** Receive data available on the socket.
@@ -2685,18 +2820,15 @@
  */
 void tport_recv_event(tport_t *self)
 {
-  su_time_t now;
   int again;
   
   SU_DEBUG_7(("%s(%p)\n", "tport_recv_event", (void *)self));
 
   do {
-    now = su_now(); 
-
     /* Receive data from socket */
     again = tport_recv_data(self);
 
-    self->tp_time = su_time_ms(now);
+    su_time(&self->tp_rtime);
 
 #if HAVE_SOFIA_STUN
     if (again == 3) /* STUN keepalive */
@@ -2708,9 +2840,6 @@
 
       if (!su_is_blocking(error)) {
 	tport_error_report(self, error, NULL);
-	/* Failure: shutdown socket */
-	if (tport_has_connection(self))
-	  tport_close(self);
 	return;
       } 
       else {
@@ -2720,17 +2849,22 @@
     }
 
     if (again >= 0)
-      tport_parse(self, !again, now);
+      tport_parse(self, !again, self->tp_rtime);
   } 
   while (again > 1);
 
+  if (!tport_is_secondary(self))
+    return;
+
   if (again == 0 && !tport_is_dgram(self)) {
     /* End of stream */
     if (!self->tp_closed) {
       /* Don't shutdown completely if there are queued messages */
-      tport_shutdown(self, self->tp_queue && self->tp_queue[self->tp_qhead] ? 0 : 2);
+      tport_shutdown0(self, tport_has_queued(self) ? 0 : 2);
     }
   }
+
+  tport_set_secondary_timer(self);
 }
 
 /* 
@@ -2757,7 +2891,7 @@
 
     if (msg_get_flags(msg, MSG_FLG_TOOLARGE))
       SU_DEBUG_3(("%s(%p): too large message from " TPN_FORMAT "\n",
-				  __func__, (void *)self, TPN_ARGS(self->tp_name)));
+		  __func__, (void *)self, TPN_ARGS(self->tp_name)));
 
     /* Do not try to read anymore from this connection? */
     if (tport_is_stream(self) && 
@@ -2934,7 +3068,7 @@
     if (!(*in_out_msg = msg = tport_msg_alloc(self, N))) {
       SU_DEBUG_7(("%s(%p): cannot allocate msg for "MOD_ZU" bytes "
 		  "from (%s/%s:%s)\n", 
-				  __func__, (void *)self, N, 
+		  __func__, (void *)self, N, 
 		  self->tp_protoname, self->tp_host, self->tp_port));
       return -1;
     }
@@ -2953,7 +3087,7 @@
     int err = su_errno();
     SU_DEBUG_7(("%s(%p): cannot get msg %p buffer for "MOD_ZU" bytes "
 		"from (%s/%s:%s): %s\n", 
-				__func__, (void *)self, (void *)msg, N, 
+		__func__, (void *)self, (void *)msg, N, 
 		self->tp_protoname, self->tp_host, self->tp_port,
 		su_strerror(err)));
     su_seterrno(err);
@@ -2965,7 +3099,7 @@
   SU_DEBUG_7(("%s(%p) msg %p from (%s/%s:%s) has "MOD_ZU" bytes, "
 	      "veclen = "MOD_ZD"\n",
               __func__, (void *)self, 
-			  (void *)msg, self->tp_protoname, self->tp_host, self->tp_port, 
+	      (void *)msg, self->tp_protoname, self->tp_host, self->tp_port, 
 	      N, veclen));
 
   for (i = 0; veclen > 1 && i < veclen; i++) {
@@ -3070,7 +3204,7 @@
   if (tpn->tpn_comp) {
     ai->ai_flags |= TP_AI_COMPRESSED;
     SU_DEBUG_9(("%s: compressed msg(%p) with %s\n", 
-				__func__, (void *)msg, tpn->tpn_comp));
+		__func__, (void *)msg, tpn->tpn_comp));
   } 
 
   if (!tpn->tpn_comp || cc == NONE)
@@ -3168,6 +3302,8 @@
 			   struct sigcomp_compartment *cc,
 			   unsigned mtu)
 {
+  int retval;
+
   /* Prepare message for sending - i.e., encode it */
   if (msg_prepare(msg) < 0) {
     msg_set_errno(msg, errno);
@@ -3193,7 +3329,11 @@
     return 0;
   }
   
-  return tport_send_msg(self, msg, tpn, cc);
+  retval = tport_send_msg(self, msg, tpn, cc);
+
+  tport_set_secondary_timer(self);
+
+  return retval;
 }
 
 
@@ -3243,7 +3383,7 @@
 
   assert(iovused > 0);
 
-  self->tp_time = su_time_ms(now = su_now());
+  self->tp_stime = self->tp_ktime = now = su_now();
 
   nerror = tport_vsend(self, msg, tpn, iov, iovused, cc);
   SU_DEBUG_9(("tport_vsend returned "MOD_ZD"\n", nerror));
@@ -3272,7 +3412,7 @@
 	char const *comp = tpn->tpn_comp;
 	
 	SU_DEBUG_1(("tport(%p): send truncated for %s/%s:%s%s%s\n", 
-				(void *)self, tpn->tpn_proto, tpn->tpn_host, tpn->tpn_port,
+		    (void *)self, tpn->tpn_proto, tpn->tpn_host, tpn->tpn_port,
 		    comp ? ";comp=" : "", comp ? comp : ""));
 
 	su_seterrno(EIO);
@@ -3288,14 +3428,17 @@
   self->tp_slogged = NULL;
   self->tp_stats.sent_msgs ++;
 
+  if (!tport_is_secondary(self))
+    return 0;
+
   ai = msg_addrinfo(msg); assert(ai);
   close_after = (ai->ai_flags & TP_AI_CLOSE) == TP_AI_CLOSE;
   sdwn_after = (ai->ai_flags & TP_AI_SHUTDOWN) == TP_AI_SHUTDOWN ||
     self->tp_send_close;
 
   if (close_after || sdwn_after)
-    tport_shutdown(self, close_after ? 2 : 1);
-
+    tport_shutdown0(self, close_after ? 2 : 1);
+  
   return 0;
 }
 
@@ -3339,7 +3482,7 @@
       tpn = self->tp_name;
     
     SU_DEBUG_7(("tport_vsend(%p): "MOD_ZU" bytes of "MOD_ZU" to %s/%s:%s%s\n", 
-				(void *)self, n, m, tpn->tpn_proto, tpn->tpn_host, 
+		(void *)self, n, m, tpn->tpn_proto, tpn->tpn_host, 
 		tpn->tpn_port, 
 		(ai->ai_flags & TP_AI_COMPRESSED) ? ";comp=sigcomp" : ""));
   }
@@ -3370,7 +3513,7 @@
 
   if (self->tp_addrinfo->ai_family == AF_INET) {
     SU_DEBUG_3(("tport_vsend(%p): %s with (s=%d %s/%s:%s%s)\n", 
-				(void *)self, su_strerror(error), (int)self->tp_socket, 
+		(void *)self, su_strerror(error), (int)self->tp_socket, 
 		tpn->tpn_proto, tpn->tpn_host, tpn->tpn_port, comp));
   }
 #if SU_HAVE_IN6
@@ -3378,7 +3521,7 @@
     su_sockaddr_t const *su = (su_sockaddr_t const *)ai->ai_addr;
     SU_DEBUG_3(("tport_vsend(%p): %s with "
 		"(s=%d, IP6=%s/%s:%s%s (scope=%i) addrlen=%u)\n", 
-				(void *)self, su_strerror(error), (int)self->tp_socket, 
+		(void *)self, su_strerror(error), (int)self->tp_socket, 
 		tpn->tpn_proto, tpn->tpn_host, tpn->tpn_port, comp,
 		su->su_scope_id, (unsigned)ai->ai_addrlen));
   }
@@ -3386,7 +3529,7 @@
   else {
     SU_DEBUG_3(("\ttport_vsend(%p): %s with "
 		"(s=%d, AF=%u addrlen=%u)%s\n", 
-				(void *)self, su_strerror(error), 
+		(void *)self, su_strerror(error), 
 		(int)self->tp_socket, ai->ai_family, (unsigned)ai->ai_addrlen, comp));
   }
 
@@ -3517,7 +3660,8 @@
   unsigned short N = self->tp_params->tpp_qsize;
   
   SU_DEBUG_7(("tport_queue(%p): queueing %p for %s/%s:%s\n", 
-			  (void *)self, (void *)msg, self->tp_protoname, self->tp_host, self->tp_port));
+	      (void *)self, (void *)msg,
+	      self->tp_protoname, self->tp_host, self->tp_port));
 
   if (self->tp_queue == NULL) {
     assert(N > 0);
@@ -3600,8 +3744,10 @@
     if (close_after)
       ai->ai_flags |= TP_AI_CLOSE;
 
-    if (self->tp_queue[qhead] == msg)
+    if (self->tp_queue[qhead] == msg) {
       tport_send_queue(self);
+      tport_set_secondary_timer(self);
+    }
     return 0;
   }
 
@@ -3615,6 +3761,7 @@
   if (self->tp_queue[qhead] == msg) {
     /* XXX - what about errors? */
     tport_send_msg(self, msg, self->tp_name, NULL);
+    tport_set_secondary_timer(self);
     if (!self->tp_unsent) {
       msg_destroy(self->tp_queue[qhead]);
       if ((self->tp_queue[qhead] = msg_ref_create(next)))
@@ -3655,24 +3802,21 @@
   assert(tport_is_connection_oriented(self));
   
   SU_DEBUG_7(("tport_send_event(%p) - ready to send to (%s/%s:%s)\n", 
-			  (void *)self, self->tp_protoname, self->tp_host, self->tp_port));
+	      (void *)self, self->tp_protoname, self->tp_host, self->tp_port));
   tport_send_queue(self);
+  tport_set_secondary_timer(self);
 }
 
 /** Send queued messages */
-static
 void tport_send_queue(tport_t *self)
 {
   msg_t *msg;
   msg_iovec_t *iov;
   size_t i, iovused, n, total;
   unsigned short qhead = self->tp_qhead, N = self->tp_params->tpp_qsize;
-  su_time_t now;
 
   assert(self->tp_queue && self->tp_queue[qhead]);
 
-  self->tp_time = su_time_ms(now = su_now());
-
   msg = self->tp_queue[qhead];
 
   iov = self->tp_unsent, self->tp_unsent = NULL;
@@ -3680,6 +3824,9 @@
 
   if (iov && iovused) {
     ssize_t e;
+
+    self->tp_stime = self->tp_ktime = su_now();
+
     e = tport_vsend(self, msg, self->tp_name, iov, iovused, NULL);
 
     if (e == -1)				/* XXX */
@@ -3688,7 +3835,7 @@
     n = (size_t)e;
 
     if (n > 0 && self->tp_master->mr_log && self->tp_slogged != msg) {
-      tport_log_msg(self, msg, "send", "to", now);
+      tport_log_msg(self, msg, "send", "to", self->tp_stime);
       self->tp_slogged = msg;
     }
     
@@ -3719,7 +3866,7 @@
   while (msg_is_prepared(msg = self->tp_queue[self->tp_qhead = qhead])) {
     /* XXX - what about errors? */
     tport_send_msg(self, msg, self->tp_name, NULL); 
-    if (self->tp_unsent) 
+    if (self->tp_unsent)
       return;
 
     msg = self->tp_queue[qhead]; /* tport_send_msg() may flush queue! */
@@ -3892,14 +4039,14 @@
 {
   tport_pending_t *pending;
 
-  if (self == NULL || callback == NULL || client == NULL)
+  if (self == NULL || callback == NULL)
     return -1;
 
   if (msg == NULL && tport_is_primary(self))
     return -1;
 
   SU_DEBUG_7(("tport_pend(%p): pending %p for %s/%s:%s (already %u)\n", 
-			  (void *)self, (void *)msg, 
+	      (void *)self, (void *)msg, 
 	      self->tp_protoname, self->tp_host, self->tp_port,
 	      self->tp_pused));
 
@@ -3954,13 +4101,15 @@
 
   if (pending->p_client != client || 
       pending->p_msg != msg) {
-	  SU_DEBUG_1(("tport_release(%p): %u %p by %p not pending\n", (void *)self, 
-				  pendd, (void *)msg, (void *)client));
+	  SU_DEBUG_1(("%s(%p): %u %p by %p not pending\n",
+		      __func__, (void *)self, 
+		      pendd, (void *)msg, (void *)client));
     return su_seterrno(EINVAL), -1;
   }
   
-  SU_DEBUG_7(("tport_release(%p): %p by %p with %p%s\n", 
-			  (void *)self, (void *)msg, (void *)client, (void *)reply,
+  SU_DEBUG_7(("%s(%p): %p by %p with %p%s\n",
+	      __func__, (void *)self,
+	      (void *)msg, (void *)client, (void *)reply,
 	      still_pending ? " (preliminary)" : ""));
 
   /* sigcomp can here associate request (msg) with response (reply) */
@@ -4104,7 +4253,7 @@
 tport_t *tport_secondary(tport_t const *self)
 {
   if (tport_is_primary(self))
-    return self->tp_pri->pri_secondary;
+    return self->tp_pri->pri_open;
   else
     return NULL;
 }
@@ -4240,7 +4389,7 @@
     socklen_t sulen;
     su_sockaddr_t su[1];
 
-    sub = self->tp_pri->pri_secondary;
+    sub = self->tp_pri->pri_open;
 
     memset(su, 0, sizeof su);
 
@@ -4302,7 +4451,7 @@
     }
     else {
       SU_DEBUG_7(("tport(%p): EXPENSIVE unresolved " TPN_FORMAT "\n",
-				  (void *)self, TPN_ARGS(tpn)));
+		  (void *)self, TPN_ARGS(tpn)));
 
       sub = tprb_first(sub);
     }
@@ -4322,11 +4471,11 @@
 	if ((socklen_t)sub->tp_addrlen != sulen ||
 	    memcmp(sub->tp_addr, su, sulen)) {
 	  SU_DEBUG_7(("tport(%p): not found by name " TPN_FORMAT "\n",
-				  (void *)self, TPN_ARGS(tpn)));
+		      (void *)self, TPN_ARGS(tpn)));
 	  break;
 	}
 	SU_DEBUG_7(("tport(%p): found %p by name " TPN_FORMAT "\n",
-				(void *)self, (void *)sub, TPN_ARGS(tpn)));
+		    (void *)self, (void *)sub, TPN_ARGS(tpn)));
       }
       else if ((strcasecmp(canon, sub->tp_canon) &&
 		strcasecmp(host, sub->tp_host)) ||
@@ -4354,7 +4503,7 @@
 
   sa = ai->ai_addr;
 
-  sub = pri->pri_secondary, maybe = NULL;
+  sub = pri->pri_open, maybe = NULL;
 
   comp = tport_canonize_comp(tpn->tpn_comp);
 
@@ -4407,10 +4556,10 @@
 
   if (sub)
     SU_DEBUG_7(("%s(%p): found %p by name " TPN_FORMAT "\n",
-				__func__, (void *)pri, (void *)sub, TPN_ARGS(tpn)));
+		__func__, (void *)pri, (void *)sub, TPN_ARGS(tpn)));
   else
     SU_DEBUG_7(("%s(%p): not found by name " TPN_FORMAT "\n",
-				__func__, (void *)pri, TPN_ARGS(tpn)));
+		__func__, (void *)pri, TPN_ARGS(tpn)));
 
   return (tport_t *)sub;
 }

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport_internal.h
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport_internal.h	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport_internal.h	Mon Sep 10 16:45:25 2007
@@ -94,6 +94,9 @@
   unsigned tpp_mtu;		/**< Maximum packet size */
   unsigned tpp_idle;		/**< Allowed connection idle time. */
   unsigned tpp_timeout;		/**< Allowed idle time for message. */
+  unsigned tpp_keepalive;	/**< Keepalive PING interval */
+  unsigned tpp_pingpong;	/**< PONG-to-PING interval */
+
   unsigned tpp_sigcomp_lifetime;  /**< SigComp compartment lifetime  */
   unsigned tpp_thrpsize;	/**< Size of thread pool */
 
@@ -106,6 +109,7 @@
   unsigned tpp_conn_orient:1;   /**< Connection-orienteded */
   unsigned tpp_sdwn_error:1;	/**< If true, shutdown is error. */
   unsigned tpp_stun_server:1;	/**< If true, use stun server */
+  unsigned tpp_pong2ping:1;	/**< If true, respond with pong to ping */
 
   unsigned :0;
 
@@ -122,7 +126,7 @@
 struct tport_s {
   su_home_t           tp_home[1];       /**< Memory home */
 
-  int                 tp_refs;		/**< Number of references to tport */
+  ssize_t             tp_refs;		/**< Number of references to tport */
 
   unsigned            tp_black:1;       /**< Used by red-black-tree */
   
@@ -130,8 +134,13 @@
   unsigned            tp_conn_orient:1;	/**< Is connection-oriented */
   unsigned            tp_has_connection:1; /**< Has real connection */
   unsigned            tp_reusable:1;    /**< Can this connection be reused */
-  unsigned            tp_closed : 1;    /**< This transport is closed */
-  /**< Remote end has sent FIN (2) or we should not just read */
+  unsigned            tp_closed : 1;
+  /**< This transport is closed.
+   * 
+   * A closed transport is inserted into pri_closed list.
+   */
+
+  /** Remote end has sent FIN (2) or we should not just read */
   unsigned            tp_recv_close:2;
   /** We will send FIN (1) or have sent FIN (2) */
   unsigned            tp_send_close:2; 
@@ -150,10 +159,10 @@
 
   tp_magic_t         *tp_magic; 	/**< Context provided by consumer */
 
-  msg_t const        *tp_rlogged;       /**< Last logged when receiving */
-  msg_t const        *tp_slogged;       /**< Last logged when sending */
+  su_timer_t         *tp_timer;	        /**< Timer object */
 
-  unsigned            tp_time;	        /**< When this transport was last used */
+  su_time_t           tp_ktime;	        /**< Keepalive timer updated */
+  su_time_t           tp_ptime;	        /**< Ping sent */
 
   tp_name_t           tp_name[1];	/**< Transport name.
 					 * 
@@ -178,16 +187,18 @@
   /* ==== Receive queue ================================================== */
 
   msg_t   	     *tp_msg;		/**< Message being received */
+  msg_t const        *tp_rlogged;       /**< Last logged when receiving */
+  su_time_t           tp_rtime;	        /**< Last time received data */
+  unsigned short      tp_ping;	        /**< Whitespace ping being received */
 
   /* ==== Pending messages =============================================== */
 
-  tport_pending_t    *tp_pending;       /**< Pending requests */
-  tport_pending_t    *tp_released;      /**< Released pends */
+  unsigned short      tp_reported;      /**< Report counter */
   unsigned            tp_plen;          /**< Size of tp_pending */
   unsigned            tp_pused;         /**< Used pends */
-  unsigned short      tp_reported;      /**< Report counter */
-  unsigned short      tp_pad;
-
+  tport_pending_t    *tp_pending;       /**< Pending requests */
+  tport_pending_t    *tp_released;      /**< Released pends */
+  
   /* ==== Send queue ===================================================== */
 
   msg_t             **tp_queue;		/**< Messages being sent */
@@ -199,6 +210,9 @@
   msg_iovec_t        *tp_iov;		/**< Iovecs allocated for sending */
   size_t              tp_iovlen;	/**< Number of allocated iovecs */
 
+  msg_t const        *tp_slogged;       /**< Last logged when sending */
+  su_time_t           tp_stime;	        /**< Last time sent message */
+
   /* ==== Extensions  ===================================================== */
 
   tport_compressor_t *tp_comp;
@@ -228,10 +242,10 @@
 					 * tport_type_stun, etc. 
 					 */
 
-  char                pri_ident[16];
   tport_primary_t    *pri_next;	        /**< Next primary tport */
 
-  tport_t            *pri_secondary;	/**< Secondary tports */
+  tport_t            *pri_open;		/**< Open secondary tports */
+  tport_t            *pri_closed;       /**< Closed secondary tports */
 
   unsigned            pri_updating:1;   /**< Currently updating address */
   unsigned            pri_natted:1;	/**< Using natted address  */
@@ -241,7 +255,6 @@
   void               *pri_stun_handle;
 
   tport_params_t      pri_params[1];      /**< Transport parameters */
-
 };
 
 /** Master structure */
@@ -307,7 +320,7 @@
 #endif
 };
 
-/** Virtual funtion table for transports */
+/** Virtual function table for transports */
 struct tport_vtable
 {
   char const *vtp_name;
@@ -344,6 +357,9 @@
   int (*vtp_stun_response)(tport_t const *self,
 			   void *msg, size_t msglen,
 			   void *addr, socklen_t addrlen);
+  int (*vtp_next_secondary_timer)(tport_t *self, su_time_t *, 
+				  char const **return_why);
+  void (*vtp_secondary_timer)(tport_t *self, su_time_t);
 };
 
 int tport_register_type(tport_vtable_t const *vtp);
@@ -388,11 +404,16 @@
 int tport_accept(tport_primary_t *pri, int events);
 void tport_zap_secondary(tport_t *self);
 
+int tport_set_secondary_timer(tport_t *self);
+void tport_base_timer(tport_t *self, su_time_t now);
+
 int tport_bind_socket(int socket,
 		      su_addrinfo_t *ai,
 		      char const **return_culprit);
 void tport_close(tport_t *self);
+int tport_shutdown0(tport_t *self, int how);
 
+int tport_has_queued(tport_t const *self);
 
 int tport_error_event(tport_t *self);
 void tport_recv_event(tport_t *self);
@@ -414,6 +435,8 @@
 		   tp_name_t const *tpn, 
 		   struct sigcomp_compartment *cc);
 
+void tport_send_queue(tport_t *self);
+
 void tport_deliver(tport_t *self, msg_t *msg, msg_t *next, 
 		   tport_compressor_t *comp,
 		   su_time_t now);
@@ -430,6 +453,9 @@
 		      size_t n, su_iovec_t const iov[], size_t iovused,
 		      char const *what, char const *how);
 
+int tport_tcp_ping(tport_t *self, su_time_t now);
+int tport_tcp_pong(tport_t *self);
+
 extern tport_vtable_t const tport_udp_vtable;
 extern tport_vtable_t const tport_udp_client_vtable;
 
@@ -461,6 +487,15 @@
 ssize_t tport_send_stream(tport_t const *self, msg_t *msg,
 			  msg_iovec_t iov[], size_t iovused);
 
+int tport_tcp_next_timer(tport_t *self, su_time_t *, char const **);
+void tport_tcp_timer(tport_t *self, su_time_t);
+
+int tport_next_recv_timeout(tport_t *, su_time_t *, char const **);
+void tport_recv_timeout_timer(tport_t *self, su_time_t now);
+
+int tport_next_keepalive(tport_t *self, su_time_t *, char const **);
+void tport_keepalive_timer(tport_t *self, su_time_t now);
+
 extern tport_vtable_t const tport_sctp_vtable;
 extern tport_vtable_t const tport_sctp_client_vtable;
 extern tport_vtable_t const tport_tls_vtable;

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport_tag.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport_tag.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport_tag.c	Mon Sep 10 16:45:25 2007
@@ -64,6 +64,9 @@
 tag_typedef_t tptag_close_after = BOOLTAG_TYPEDEF(sdwn_after);
 tag_typedef_t tptag_idle = UINTTAG_TYPEDEF(idle);
 tag_typedef_t tptag_timeout = UINTTAG_TYPEDEF(timeout);
+tag_typedef_t tptag_keepalive = UINTTAG_TYPEDEF(keepalive);
+tag_typedef_t tptag_pingpong = UINTTAG_TYPEDEF(pingpong);
+tag_typedef_t tptag_pong2ping = BOOLTAG_TYPEDEF(pong2ping);
 tag_typedef_t tptag_sigcomp_lifetime = UINTTAG_TYPEDEF(sigcomp_lifetime);
 tag_typedef_t tptag_certificate = STRTAG_TYPEDEF(certificate);
 tag_typedef_t tptag_compartment = PTRTAG_TYPEDEF(compartment);

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport_tls.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport_tls.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport_tls.c	Mon Sep 10 16:45:25 2007
@@ -587,6 +587,10 @@
     return 0;
 
   case SSL_ERROR_SYSCALL:
+    if (SSL_get_shutdown(tls->con) & SSL_RECEIVED_SHUTDOWN)
+      return 0;			/* EOS */
+    if (errno == 0)
+      return 0;			/* EOS */
     return -1;
 
   default:
@@ -665,10 +669,12 @@
   if (tls && (events & tls->read_events)) {
     int ret = tls_read(tls);
 
-    if (ret >= 0)
+    if (ret > 0)
       return 1;
-    else if (errno == EAGAIN)
+    else if (ret == 0)
       return 0;
+    else if (errno == EAGAIN)
+      return 2;
     else
       return -1;
   }

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_connect.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_connect.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_connect.c	Mon Sep 10 16:45:25 2007
@@ -201,6 +201,8 @@
     return NULL;
   }  
 
+  tport_set_secondary_timer(tport);
+
   return tport;
 }
 

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_sctp.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_sctp.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_sctp.c	Mon Sep 10 16:45:25 2007
@@ -37,12 +37,12 @@
 
 #include "config.h"
 
+#if HAVE_SCTP
+
 #include "tport_internal.h"
 
 #if HAVE_NETINET_SCTP_H
 #include <netinet/sctp.h>
-#undef HAVE_SCTP
-#define HAVE_SCTP 1
 #endif
 
 #include <stdlib.h>
@@ -63,7 +63,6 @@
 #define SOL_SCTP IPPROTO_SCTP
 #endif
 
-
 enum { MAX_STREAMS = 1 };
 typedef struct tport_sctp_t
 {
@@ -81,8 +80,6 @@
 
 #define TP_SCTP_MSG_MAX (65536)
 
-#if HAVE_SCTP
-
 static int tport_sctp_init_primary(tport_primary_t *, 
 				   tp_name_t tpn[1], 
 				   su_addrinfo_t *, tagi_t const *,
@@ -100,6 +97,9 @@
 static ssize_t tport_send_sctp(tport_t const *self, msg_t *msg,
 			       msg_iovec_t iov[], size_t iovused);
 
+static int tport_sctp_next_timer(tport_t *self, su_time_t *, char const **);
+static void tport_sctp_timer(tport_t *self, su_time_t);
+
 tport_vtable_t const tport_sctp_client_vtable =
 {
   "sctp", tport_type_client,
@@ -116,11 +116,14 @@
   NULL,
   tport_recv_sctp,
   tport_send_sctp,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  tport_sctp_next_timer,
+  tport_sctp_timer,
 };
 
-#undef NEXT_VTABLE
-#define NEXT_VTABLE &tport_sctp_client_vtable
-
 tport_vtable_t const tport_sctp_vtable =
 {
   "sctp", tport_type_local,
@@ -137,11 +140,14 @@
   NULL,
   tport_recv_sctp,
   tport_send_sctp,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  tport_sctp_next_timer,
+  tport_sctp_timer,
 };
 
-#undef NEXT_VTABLE
-#define NEXT_VTABLE &tport_sctp_vtable
-
 static int tport_sctp_init_primary(tport_primary_t *pri, 
 				   tp_name_t tpn[1],
 				   su_addrinfo_t *ai,
@@ -263,4 +269,56 @@
   return su_vsend(self->tp_socket, iov, iovused, MSG_NOSIGNAL, NULL, 0);
 }
 
+/** Calculate tick timer if send is pending. */
+int tport_next_sctp_send_tick(tport_t *self,
+			    su_time_t *return_target, 
+			    char const **return_why)
+{
+  unsigned timeout = 100;  /* Retry 10 times a second... */
+
+  if (tport_has_queued(self)) {
+    su_time_t ntime = su_time_add(self->tp_ktime, timeout);
+    if (su_time_cmp(ntime, *return_target) < 0)
+      *return_target = ntime, *return_why = "send tick";
+  }
+
+  return 0;
+}
+
+/** Tick timer if send is pending */
+void tport_sctp_send_tick_timer(tport_t *self, su_time_t now)
+{
+  unsigned timeout = 100;
+
+  /* Send timeout */
+  if (tport_has_queued(self) && 
+      su_time_cmp(su_time_add(self->tp_ktime, timeout), now) < 0) {
+    uint64_t bytes = self->tp_stats.sent_bytes;
+    su_time_t stime = self->tp_stime;
+
+    tport_send_queue(self);
+
+    if (self->tp_stats.sent_bytes == bytes)
+      self->tp_stime = stime;	/* Restore send timestamp */
+  }
+}
+
+/** Calculate next timer for SCTP. */
+int tport_sctp_next_timer(tport_t *self,
+			 su_time_t *return_target, 
+			 char const **return_why)
+{
+  return 
+    tport_next_recv_timeout(self, return_target, return_why) |
+    tport_next_sctp_send_tick(self, return_target, return_why);
+}
+
+/** SCTP timer. */
+void tport_sctp_timer(tport_t *self, su_time_t now)
+{
+  tport_sctp_send_tick_timer(self, now);
+  tport_recv_timeout_timer(self, now);
+  tport_base_timer(self, now);
+}
+
 #endif

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_tcp.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_tcp.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_tcp.c	Mon Sep 10 16:45:25 2007
@@ -45,6 +45,7 @@
 #endif
 
 #include <stdlib.h>
+#include <string.h>
 #include <time.h>
 #include <assert.h>
 #include <errno.h>
@@ -76,6 +77,12 @@
   NULL,
   tport_recv_stream,
   tport_send_stream,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  tport_tcp_next_timer,
+  tport_tcp_timer,
 };
 
 tport_vtable_t const tport_tcp_client_vtable =
@@ -84,7 +91,7 @@
   sizeof (tport_primary_t),
   tport_tcp_init_client,
   NULL,
-  tport_accept,
+  NULL,
   NULL,
   sizeof (tport_t),
   tport_tcp_init_secondary,
@@ -94,6 +101,12 @@
   NULL,
   tport_recv_stream,
   tport_send_stream,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  tport_tcp_next_timer,
+  tport_tcp_timer,
 };
 
 static int tport_tcp_setsndbuf(int socket, int atleast);
@@ -205,6 +218,20 @@
 #endif
 }
 
+/** Return span of whitespace from buffer */
+static inline size_t ws_span(void *buffer, size_t len)
+{
+  size_t i;
+  char const *b = buffer;
+  
+  for (i = 0; i < len; i++) {
+    if (b[i] != '\r' && b[i] != '\n' && b[i] != ' ' && b[i] != '\t')
+      break;
+  }
+
+  return i;
+}
+
 /** Receive from stream.
  *
  * @retval -1 error
@@ -217,7 +244,7 @@
 {
   msg_t *msg;
   ssize_t n, N, veclen;
-  int err;
+  int err, initial;
   msg_iovec_t iovec[msg_n_fragments] = {{ 0 }};
 
   N = su_getmsgsize(self->tp_socket);
@@ -233,6 +260,36 @@
     return -1;
   }
 
+  initial = self->tp_msg == NULL;
+  memset(&self->tp_ptime, 0, sizeof self->tp_ptime);
+
+  while (initial && N <= 8) {	/* Check for whitespace */
+    char crlf[9];
+    size_t i;
+
+    n = su_recv(self->tp_socket, crlf, N, MSG_PEEK);
+
+    i = ws_span(crlf, n);
+    if (i == 0)
+      break;
+
+    n = su_recv(self->tp_socket, crlf, i, 0);
+    if (n <= 0)
+      return (int)n;
+
+    SU_DEBUG_7(("%s(%p): received keepalive\n", __func__, (void *)self));
+
+    N -= n, self->tp_ping += n;
+
+    if (N == 0) {
+      /* outbound-10 section 3.5.1  - send pong */
+      if (self->tp_ping >= 4)
+	tport_tcp_pong(self);
+
+      return 1;
+    }
+  }
+
   veclen = tport_recv_iovec(self, &self->tp_msg, iovec, N, 0);
   if (veclen == -1)
     return -1;
@@ -242,11 +299,30 @@
   msg_set_address(msg, self->tp_addr, (socklen_t)(self->tp_addrlen));
 
   n = su_vrecv(self->tp_socket, iovec, veclen, 0, NULL, NULL);
+
   if (n == SOCKET_ERROR)
     return tport_recv_error_report(self);
 
   assert(n <= N);
 
+  /* Check if message contains only whitespace */
+  /* This can happen if multiple PINGs are received at once */
+  if (initial) {
+    size_t i = ws_span(iovec->siv_base, iovec->siv_len);
+
+    if (i + self->tp_ping >= 4)
+      tport_tcp_pong(self);
+    else
+      self->tp_ping += i;
+
+    if (i == iovec->siv_len && veclen == 1) {
+      SU_DEBUG_7(("%s(%p): received %u bytes of keepalive\n",
+		  __func__, (void *)self, (unsigned)i));
+      msg_destroy(self->tp_msg), self->tp_msg = NULL;
+      return 1;
+    }
+  }
+
   /* Write the received data to the message dump file */
   if (self->tp_master->mr_dump_file)
     tport_dump_iovec(self, msg, n, iovec, veclen, "recv", "from");
@@ -254,9 +330,13 @@
   /* Mark buffer as used */
   msg_recv_commit(msg, n, n == 0);
 
+  if (n > 0)
+    self->tp_ping = 0;
+
   return n != 0;
 }
 
+/** Send to stream */
 ssize_t tport_send_stream(tport_t const *self, msg_t *msg, 
 			  msg_iovec_t iov[], 
 			  size_t iovused)
@@ -267,3 +347,184 @@
 #endif
   return su_vsend(self->tp_socket, iov, iovused, MSG_NOSIGNAL, NULL, 0);
 }
+
+/** Calculate timeout if receive is incomplete. */
+int tport_next_recv_timeout(tport_t *self,
+			    su_time_t *return_target, 
+			    char const **return_why)
+{
+  unsigned timeout = self->tp_params->tpp_timeout;
+
+  if (timeout < INT_MAX) {
+    /* Recv timeout */
+    if (self->tp_msg) {
+      su_time_t ntime = su_time_add(self->tp_rtime, timeout);
+      if (su_time_cmp(ntime, *return_target) < 0)
+	*return_target = ntime, *return_why = "recv timeout";
+    }
+
+#if 0
+    /* Send timeout */
+    if (tport_has_queued(self)) {
+      su_time_t ntime = su_time_add(self->tp_stime, timeout);
+      if (su_time_cmp(ntime, *return_target) < 0)
+	*return_target = ntime, *return_why = "send timeout";
+    }
+#endif
+  }
+
+  return 0;
+}
+
+/** Timeout timer if receive is incomplete */
+void tport_recv_timeout_timer(tport_t *self, su_time_t now)
+{
+  unsigned timeout = self->tp_params->tpp_timeout;
+
+  if (timeout < INT_MAX) {
+    if (self->tp_msg && 
+	su_time_cmp(su_time_add(self->tp_rtime, timeout), now) < 0) {
+      msg_t *msg = self->tp_msg;
+      msg_set_streaming(msg, 0);
+      msg_set_flags(msg, MSG_FLG_ERROR | MSG_FLG_TRUNC | MSG_FLG_TIMEOUT);
+      tport_deliver(self, msg, NULL, NULL, now);
+      self->tp_msg = NULL;
+    }
+
+#if 0
+    /* Send timeout */
+    if (tport_has_queued(self) && 
+	su_time_cmp(su_time_add(self->tp_stime, timeout), now) < 0) {
+      stime = su_time_add(self->tp_stime, self->tp_params->tpp_timeout);
+      if (su_time_cmp(stime, target) < 0)
+	target = stime;
+    }
+#endif
+  }
+}
+
+/** Calculate next timeout for keepalive */
+int tport_next_keepalive(tport_t *self,
+			 su_time_t *return_target, 
+			 char const **return_why)
+{
+  /* Keepalive timer */
+  unsigned timeout = self->tp_params->tpp_keepalive;
+
+  if (timeout != 0 && timeout != UINT_MAX) {
+    if (!tport_has_queued(self)) {
+      su_time_t ntime = su_time_add(self->tp_ktime, timeout);
+      if (su_time_cmp(ntime, *return_target) < 0)
+	*return_target = ntime, *return_why = "keepalive";
+    }
+  }
+
+  timeout = self->tp_params->tpp_pingpong;
+  if (timeout != 0) {
+    if (self->tp_ptime.tv_sec && !self->tp_recv_close) {
+      su_time_t ntime = su_time_add(self->tp_ptime, timeout);
+      if (su_time_cmp(ntime, *return_target) < 0)
+	*return_target = ntime, *return_why = "waiting for pong";
+    }
+  }
+
+  return 0;
+}
+
+
+/** Keepalive timer. */
+void tport_keepalive_timer(tport_t *self, su_time_t now)
+{
+  unsigned timeout = self->tp_params->tpp_pingpong;
+
+  if (timeout != 0) {
+    if (self->tp_ptime.tv_sec && !self->tp_recv_close &&
+	su_time_cmp(su_time_add(self->tp_ptime, timeout), now) < 0) {
+      SU_DEBUG_3(("%s(%p): %s to " TPN_FORMAT "%s\n", 
+		  __func__, (void *)self,
+		  "closing connection", TPN_ARGS(self->tp_name), 
+		  " because of PONG timeout"));
+      tport_close(self);
+      return;
+    }
+  }
+
+  timeout = self->tp_params->tpp_keepalive;
+
+  if (timeout != 0 && timeout != UINT_MAX) {
+    if (su_time_cmp(su_time_add(self->tp_ktime, timeout), now) < 0) {
+      tport_tcp_ping(self, now);
+    }
+  }
+}
+
+/** Send PING */
+int tport_tcp_ping(tport_t *self, su_time_t now)
+{
+  ssize_t n;
+  char *why = "";
+
+  if (tport_has_queued(self))
+    return 0;
+
+  n = send(self->tp_socket, "\r\n\r\n", 4, 0);
+
+  if (n > 0)
+    self->tp_ktime = now;
+
+  if (n == 4) {
+    if (self->tp_ptime.tv_sec == 0)
+      self->tp_ptime = now;
+  }
+  else if (n == -1) {
+    int error = su_errno();
+
+    why = " failed";
+    
+    if (!su_is_blocking(error)) 
+      tport_error_report(self, error, NULL);
+    else
+      why = " blocking";
+
+    return -1;
+  }
+
+  SU_DEBUG_7(("%s(%p): %s to " TPN_FORMAT "%s\n", 
+	      __func__, (void *)self,
+	      "sending PING", TPN_ARGS(self->tp_name), why));
+
+  return n == -1 ? -1 : 0;
+}
+
+/** Send pong */
+int tport_tcp_pong(tport_t *self)
+{
+  self->tp_ping = 0;
+
+  if (tport_has_queued(self) || !self->tp_params->tpp_pong2ping)
+    return 0;
+
+  SU_DEBUG_7(("%s(%p): %s to " TPN_FORMAT "%s\n", 
+	      __func__, (void *)self,
+	      "sending PONG", TPN_ARGS(self->tp_name), ""));
+
+  return send(self->tp_socket, "\r\n", 2, 0);
+}
+
+/** Calculate next timer for TCP. */
+int tport_tcp_next_timer(tport_t *self,
+			 su_time_t *return_target, 
+			 char const **return_why)
+{
+  return 
+    tport_next_recv_timeout(self, return_target, return_why) |
+    tport_next_keepalive(self, return_target, return_why);
+}
+
+/** TCP timer. */
+void tport_tcp_timer(tport_t *self, su_time_t now)
+{
+  tport_recv_timeout_timer(self, now);
+  tport_keepalive_timer(self, now);
+  tport_base_timer(self, now);
+}

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_tls.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_tls.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_tls.c	Mon Sep 10 16:45:25 2007
@@ -307,6 +307,8 @@
     ret = tls_want_read(tlstp->tlstp_context, events);
     if (ret > 0)
       tport_recv_event(self);
+    else if (ret == 0)		/* End-of-stream */
+      tport_shutdown0(self, 2);
     else if (ret < 0)
       tport_error_report(self, errno, NULL);
   }

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_udp.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_udp.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_udp.c	Mon Sep 10 16:45:25 2007
@@ -198,6 +198,9 @@
 		"TEST", 4, 0,
 		(void *)ai->ai_addr, ai->ai_addrlen);
 
+  if (n != 4)
+    return;
+
   for (;;) {
     n = su_recvfrom(tp->tp_socket, buffer, sizeof buffer, MSG_TRUNC, 
 		    (void *)&su, &sulen);

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/url/torture_url.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/url/torture_url.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/url/torture_url.c	Mon Sep 10 16:45:25 2007
@@ -120,11 +120,12 @@
   d = url_as_string(home, u); TEST_1(d);
   TEST_S(d, c);
 
-  d = "sip:&=+$,;?/:&=+$,@[::1]:56001;param=+$,/:@&;another=@"
+  d = "sip:&=+$,;?/:&=+$,@[::1]:56001;param=+$,/:@&;another=@%40%2F"
     "?header=" RESERVED "&%3b%2f%3f%3a%40%26%3d%2b%24%2c";
   u = url_hdup(home, (url_t *)d); TEST_1(u);
   TEST_S(u->url_user, "&=+$,;?/");
   TEST_S(u->url_host, "[::1]");
+  TEST_S(u->url_params, "param=+$,/:@&;another=@%40/");
   TEST_S(u->url_headers, "header=" RESERVED "&%3B%2F%3F%3A%40%26%3D%2B%24%2C");
   url_digest(hash1, sizeof(hash1), u, NULL);
   url_digest(hash2, sizeof(hash2), (url_t const *)d, NULL);
@@ -134,10 +135,12 @@
   d = url_as_string(home, u); TEST_1(d);
   TEST_S(d, c);
 
-  d = "http://&=+$,;:&=+$,;@host:8080/foo;param=+$,/:@&;another=@"
+  d = "http://&=+$,;:&=+$,;@host:8080/foo%2F%3B%3D"
+    ";param=+$,%2f%3b%3d/bar;param=:@&;another=@"
     "?query=" RESERVED;
   u = url_hdup(home, (url_t *)d); TEST_1(u);
   TEST_S(u->url_user, "&=+$,;"); TEST_S(u->url_password, "&=+$,;");
+  TEST_S(u->url_path, "foo%2F%3B%3D;param=+$,%2F%3B%3D/bar;param=:@&;another=@");
   url_digest(hash1, sizeof(hash1), u, NULL);
   url_digest(hash2, sizeof(hash2), (url_t const *)d, NULL);
   TEST(memcmp(hash1, hash2, sizeof(hash1)), 0);
@@ -242,7 +245,7 @@
     "sip:user:pass at host:32;param=1"
     "?From=foo at bar&To=bar at baz#unf";
   char sip2url[] = 
-    "sip:user/path;tel-param:pass at host:32;param=1"
+    "sip:user/path;tel-param:pass at host:32;param=1%3d%3d1"
     "?From=foo at bar&To=bar at baz#unf";
   char sip2[sizeof(sipurl) + 32];
   char sipsurl[] = 
@@ -305,6 +308,7 @@
   TEST_1(tst = su_strdup(home, sip2url));
   TEST_1(url_d(url, tst) == 0);
   TEST_S(url->url_user, "user/path;tel-param");
+  TEST_S(url->url_params, "param=1%3D%3D1");
 
   TEST_S(url_query_as_header_string(home, url->url_headers),
 	 "From:foo at bar\nTo:bar at baz");

Modified: freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/url/url.c
==============================================================================
--- freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/url/url.c	(original)
+++ freeswitch/trunk/libs/sofia-sip/libsofia-sip-ua/url/url.c	Mon Sep 10 16:45:25 2007
@@ -129,6 +129,8 @@
   else if (a < 128)				\
     mask96 &= ~(1U << (127 - a))
 
+#define NUL '\0'
+#define NULNULNUL '\0', '\0', '\0'
 
 #define RMASK1 0xbe19003f
 #define RMASK2 0x8000001e
@@ -141,8 +143,10 @@
 
 /* Internal prototypes */
 static char *url_canonize(char *d, char const *s, size_t n,
+			  unsigned syn33,
 			  char const allowed[]);
 static char *url_canonize2(char *d, char const *s, size_t n, 
+			   unsigned syn33,
 			   unsigned m32, unsigned m64, unsigned m96);
 static int url_tel_cmp_numbers(char const *A, char const *B);
 
@@ -322,18 +326,24 @@
 
 /** Canonize a URL component */
 static
-char *url_canonize(char *d, char const *s, size_t n, char const allowed[])
+char *url_canonize(char *d, char const *s, size_t n,
+		   unsigned syn33,
+		   char const allowed[])
 {
   unsigned mask32 = 0xbe19003f, mask64 = 0x8000001e, mask96 = 0x8000001d;
 
   MASKS_WITH_ALLOWED(allowed, mask32, mask64, mask96);
 
-  return url_canonize2(d, s, n, mask32, mask64, mask96);
+  return url_canonize2(d, s, n, syn33, mask32, mask64, mask96);
 }
 
+#define SYN33(c) (1 << (c - 33))
+#define IS_SYN33(syn33, c) ((syn33 & (1 << (c - 33))) != 0)
+
 /** Canonize a URL component (with precomputed mask) */
 static
-char *url_canonize2(char *d, char const * const s, size_t n, 
+char *url_canonize2(char *d, char const * const s, size_t n,
+		    unsigned syn33,
 		    unsigned m32, unsigned m64, unsigned m96)
 {
   size_t i = 0;
@@ -347,7 +357,7 @@
     unsigned char c = s[i], h1, h2;
 
     if (c != '%') {
-      if (IS_EXCLUDED(c, m32, m64, m96))
+      if (!IS_SYN33(syn33, c) && IS_EXCLUDED(c, m32, m64, m96))
 	return NULL;
       *d = c;
       continue;
@@ -393,7 +403,7 @@
  * be escaped.
  */
 static
-char *url_canonize3(char *d, char const * const s, size_t n, 
+char *url_canonize3(char *d, char const * const s, size_t n,
 		    unsigned m32, unsigned m64, unsigned m96)
 {
   size_t i = 0;
@@ -575,7 +585,7 @@
     char *scheme;
     url->url_scheme = scheme = s; s[n] = '\0'; s = s + n + 1;
 
-    if (!(scheme = url_canonize(scheme, scheme, SIZE_MAX, "+")))
+    if (!(scheme = url_canonize(scheme, scheme, SIZE_MAX, 0, "+")))
       return -1;
 
     n = scheme - url->url_scheme;
@@ -694,7 +704,7 @@
       case url_file:
       case url_rtsp:
       case url_rtspu:
-	if (!url_canonize2(port, port, SIZE_MAX, RESERVED_MASK))
+	if (!url_canonize2(port, port, SIZE_MAX, 0, RESERVED_MASK))
 	  return -1;
 
 	/* Check that port is really numeric or wildcard */
@@ -758,14 +768,14 @@
 
 #   define SIP_USER_UNRESERVED "&=+$,;?/"
     s = (char *)url->url_user;
-    if (s && !url_canonize(s, s, SIZE_MAX, SIP_USER_UNRESERVED))
+    if (s && !url_canonize(s, s, SIZE_MAX, 0, SIP_USER_UNRESERVED))
       return -1;
 
     /* Having different charset in user and password does not make sense */
     /* but that is how it is defined in RFC 3261 */
 #   define SIP_PASS_UNRESERVED "&=+$,"
     s = (char *)url->url_password;
-    if (s && !url_canonize(s, s, SIZE_MAX, SIP_PASS_UNRESERVED))
+    if (s && !url_canonize(s, s, SIZE_MAX, 0, SIP_PASS_UNRESERVED))
       return -1;
 
   }
@@ -773,31 +783,37 @@
 
 #   define USER_UNRESERVED "&=+$,;"
     s = (char *)url->url_user;
-    if (s && !url_canonize(s, s, SIZE_MAX, USER_UNRESERVED))
+    if (s && !url_canonize(s, s, SIZE_MAX, 0, USER_UNRESERVED))
       return -1;
 
 #   define PASS_UNRESERVED "&=+$,;:"
     s = (char *)url->url_password;
-    if (s && !url_canonize(s, s, SIZE_MAX, PASS_UNRESERVED))
+    if (s && !url_canonize(s, s, SIZE_MAX, 0, PASS_UNRESERVED))
       return -1;
   }
 
   s = (char *)url->url_host;
-  if (s && !url_canonize2(s, s, SIZE_MAX, RESERVED_MASK))
+  if (s && !url_canonize2(s, s, SIZE_MAX, 0, RESERVED_MASK))
     return -1;
 
   /* port is canonized by _url_d() */
-
-  /* Allow all URI characters but ? and ; */
-# define PATH_UNRESERVED "/:@&=+$,"
   s = (char *)url->url_path;
-  if (s && !url_canonize(s, s, SIZE_MAX, PATH_UNRESERVED))
+  if (s && !url_canonize(s, s, SIZE_MAX, 
+			 /* Allow all URI characters but ? */
+			 /* Allow unescaped /;?@, - but do not convert */
+			 SYN33('/') | SYN33(';') | SYN33('=') | SYN33('@') |
+			 SYN33(','),
+			 /* Convert escaped :&+$ to unescaped */
+			 ":&+$"))
     return -1;
 
-  /* Allow all URI characters but ? */
-# define PARAMS_UNRESERVED ";" PATH_UNRESERVED
   s = (char *)url->url_params;
-  if (s && !url_canonize(s, s, SIZE_MAX, PARAMS_UNRESERVED))
+  if (s && !url_canonize(s, s, SIZE_MAX,
+			 /* Allow all URI characters but ? */
+			 /* Allow unescaped ;=@, - but do not convert */
+			 SYN33(';') | SYN33('=') | SYN33('@') | SYN33(','),
+			 /* Convert escaped /:&+$ to unescaped */
+			 "/:&+$"))
     return -1;
   
   /* Unhex alphanumeric and unreserved URI characters */
@@ -807,7 +823,7 @@
 
   /* Allow all URI characters (including reserved ones) */
   s = (char *)url->url_fragment;
-  if (s && !url_canonize2(s, s, SIZE_MAX, URIC_MASK))
+  if (s && !url_canonize2(s, s, SIZE_MAX, 0, URIC_MASK))
     return -1;
 
   return 0;
@@ -1927,7 +1943,7 @@
     su_md5_update(md5, ":", 1);
   }
   else if (n && s[n] == ':' ) {
-    at = url_canonize(schema, s, n, "+");
+    at = url_canonize(schema, s, n, 0, "+");
 
     type = url_get_type(schema, at - schema);
     su_md5_iupdate(md5, schema, at - schema);



More information about the Freeswitch-svn mailing list