From 274a956f00f43106e37b6408f4be85a36483279c Mon Sep 17 00:00:00 2001 From: Michael Jerris Date: Mon, 10 Sep 2007 20:45:25 +0000 Subject: [PATCH] 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@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@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@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@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@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@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@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@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@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@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@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., 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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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@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 * 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@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@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@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@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@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@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@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@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@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@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@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 git-svn-id: http://svn.freeswitch.org/svn/freeswitch/trunk@5692 d0543943-73ff-0310-b7d9-9358b9ac24b2 --- libs/sofia-sip/Makefile.am | 1 + libs/sofia-sip/libsofia-sip-ua/Makefile.am | 3 +- .../libsofia-sip-ua/docs/mainpage.docs | 5 +- libs/sofia-sip/libsofia-sip-ua/nta/nta.c | 70 +- .../libsofia-sip-ua/nta/sofia-sip/nta.h | 1 + .../libsofia-sip-ua/nta/sofia-sip/nta_tag.h | 12 +- .../libsofia-sip-ua/nta/test_nta_api.c | 10 +- libs/sofia-sip/libsofia-sip-ua/nua/nua.docs | 6 +- .../libsofia-sip-ua/nua/nua_common.c | 1 + .../libsofia-sip-ua/nua/nua_dialog.c | 91 +- .../libsofia-sip-ua/nua/nua_dialog.h | 39 +- .../libsofia-sip-ua/nua/nua_notifier.c | 2 +- .../libsofia-sip-ua/nua/nua_params.c | 2 +- .../libsofia-sip-ua/nua/nua_register.c | 25 +- .../libsofia-sip-ua/nua/nua_session.c | 204 ++- .../sofia-sip/libsofia-sip-ua/nua/nua_stack.c | 118 +- .../libsofia-sip-ua/nua/nua_subnotref.c | 6 +- .../libsofia-sip-ua/nua/test_100rel.c | 118 +- .../libsofia-sip-ua/nua/test_basic_call.c | 252 +++- .../libsofia-sip-ua/nua/test_call_reject.c | 203 ++- .../libsofia-sip-ua/nua/test_cancel_bye.c | 119 +- .../sofia-sip/libsofia-sip-ua/nua/test_init.c | 53 +- libs/sofia-sip/libsofia-sip-ua/nua/test_nua.c | 2 + libs/sofia-sip/libsofia-sip-ua/nua/test_nua.h | 1 + libs/sofia-sip/libsofia-sip-ua/nua/test_ops.c | 30 +- .../libsofia-sip-ua/nua/test_proxy.c | 1260 ++++++++++------- .../libsofia-sip-ua/nua/test_proxy.h | 29 +- .../libsofia-sip-ua/nua/test_register.c | 51 +- .../libsofia-sip-ua/nua/test_simple.c | 3 +- .../libsofia-sip-ua/nua/test_sip_events.c | 8 +- libs/sofia-sip/libsofia-sip-ua/soa/test_soa.c | 67 + .../tport/sofia-sip/tport_tag.h | 60 + .../libsofia-sip-ua/tport/test_tport.c | 305 +++- libs/sofia-sip/libsofia-sip-ua/tport/tport.c | 559 +++++--- .../libsofia-sip-ua/tport/tport_internal.h | 65 +- .../libsofia-sip-ua/tport/tport_tag.c | 3 + .../libsofia-sip-ua/tport/tport_tls.c | 10 +- .../tport/tport_type_connect.c | 2 + .../libsofia-sip-ua/tport/tport_type_sctp.c | 80 +- .../libsofia-sip-ua/tport/tport_type_tcp.c | 265 +++- .../libsofia-sip-ua/tport/tport_type_tls.c | 2 + .../libsofia-sip-ua/tport/tport_type_udp.c | 3 + .../libsofia-sip-ua/url/torture_url.c | 10 +- libs/sofia-sip/libsofia-sip-ua/url/url.c | 58 +- 44 files changed, 3094 insertions(+), 1120 deletions(-) diff --git a/libs/sofia-sip/Makefile.am b/libs/sofia-sip/Makefile.am index a8a7e9f189..98e7707893 100644 --- a/libs/sofia-sip/Makefile.am +++ b/libs/sofia-sip/Makefile.am @@ -42,6 +42,7 @@ $(dist_man_MANS): manpages 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 ;} | \ diff --git a/libs/sofia-sip/libsofia-sip-ua/Makefile.am b/libs/sofia-sip/libsofia-sip-ua/Makefile.am index e3359f7d98..40acd84b06 100644 --- a/libs/sofia-sip/libsofia-sip-ua/Makefile.am +++ b/libs/sofia-sip/libsofia-sip-ua/Makefile.am @@ -26,7 +26,8 @@ endif # 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 diff --git a/libs/sofia-sip/libsofia-sip-ua/docs/mainpage.docs b/libs/sofia-sip/libsofia-sip-ua/docs/mainpage.docs index 3986062ce6..f40ebce673 100644 --- a/libs/sofia-sip/libsofia-sip-ua/docs/mainpage.docs +++ b/libs/sofia-sip/libsofia-sip-ua/docs/mainpage.docs @@ -55,9 +55,10 @@ Or post to the Sofia-SIP mailing list: @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: - "su" - sockets, memory management, threads diff --git a/libs/sofia-sip/libsofia-sip-ua/nta/nta.c b/libs/sofia-sip/libsofia-sip-ua/nta/nta.c index 944510aa09..e6f03e994f 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nta/nta.c +++ b/libs/sofia-sip/libsofia-sip-ua/nta/nta.c @@ -2690,12 +2690,14 @@ void agent_recv_response(nta_agent_t *agent, && 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 @@ int complete_response(msg_t *response, /** 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 @@ int nta_msg_ackbye(nta_agent_t *agent, msg_t *msg) err: msg_destroy(amsg); msg_destroy(bmsg); +#endif + (void)agent; (void)msg; return -1; } @@ -5121,6 +5129,21 @@ nta_incoming_magic_t *nta_incoming_magic(nta_incoming_t *irq, 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 @@ nta_outgoing_t *outgoing_create(nta_agent_t *agent, 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 @@ outgoing_send(nta_outgoing_t *orq, int retransmit) 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 @@ void outgoing_destroy(nta_outgoing_t *orq) 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 @@ size_t outgoing_timer_bf(outgoing_queue_t *q, 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 @@ int outgoing_complete(nta_outgoing_t *orq) 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 @@ int outgoing_reply(nta_outgoing_t *orq, int status, char const *phrase, (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; } diff --git a/libs/sofia-sip/libsofia-sip-ua/nta/sofia-sip/nta.h b/libs/sofia-sip/libsofia-sip-ua/nta/sofia-sip/nta.h index f61e3e0379..8778f2f982 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nta/sofia-sip/nta.h +++ b/libs/sofia-sip/libsofia-sip-ua/nta/sofia-sip/nta.h @@ -284,6 +284,7 @@ SOFIAPUBFUN sip_method_t nta_incoming_method(nta_incoming_t const *irq); 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, ...); diff --git a/libs/sofia-sip/libsofia-sip-ua/nta/sofia-sip/nta_tag.h b/libs/sofia-sip/libsofia-sip-ua/nta/sofia-sip/nta_tag.h index 246e4f290d..e635027a0b 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nta/sofia-sip/nta_tag.h +++ b/libs/sofia-sip/libsofia-sip-ua/nta/sofia-sip/nta_tag.h @@ -1725,15 +1725,15 @@ NTA_DLL extern tag_typedef_t ntatag_s_trless_response_ref; /** 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(), * , #nta_message_f, nta_msg_ackbye(), @@ -1753,10 +1753,6 @@ NTA_DLL extern tag_typedef_t ntatag_s_trless_200_ref; * 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(), * , #nta_message_f, nta_msg_ackbye(), * NTATAG_S_TRLESS_RESPONSE_REF(). diff --git a/libs/sofia-sip/libsofia-sip-ua/nta/test_nta_api.c b/libs/sofia-sip/libsofia-sip-ua/nta/test_nta_api.c index 30c71244a7..429d425ca0 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nta/test_nta_api.c +++ b/libs/sofia-sip/libsofia-sip-ua/nta/test_nta_api.c @@ -453,7 +453,7 @@ int api_test_params(agent_t *ag) 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 @@ static int api_test_default(agent_t *ag) 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 @@ static int api_test_default(agent_t *ag) 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 @@ static int api_test_errors(agent_t *ag) nta_agent_t *nta; su_root_t *root; su_home_t home[1]; + su_nanotime_t nano; BEGIN(); @@ -1044,6 +1048,8 @@ static int api_test_errors(agent_t *ag) 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); diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/nua.docs b/libs/sofia-sip/libsofia-sip-ua/nua/nua.docs index fb241b7fe2..f27c793cb7 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/nua.docs +++ b/libs/sofia-sip/libsofia-sip-ua/nua/nua.docs @@ -1174,9 +1174,9 @@ follows: terminating* - - 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 diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/nua_common.c b/libs/sofia-sip/libsofia-sip-ua/nua/nua_common.c index 6e7499636e..a37d57c6cf 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/nua_common.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/nua_common.c @@ -114,6 +114,7 @@ nua_handle_t *nh_create_handle(nua_t *nua, 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", diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/nua_dialog.c b/libs/sofia-sip/libsofia-sip-ua/nua/nua_dialog.c index 185c35c5f9..d4cbd08128 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/nua_dialog.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/nua_dialog.c @@ -43,6 +43,7 @@ #include #include +#include #define NUA_OWNER_T su_home_t @@ -127,9 +128,9 @@ void nua_dialog_store_peer_info(nua_owner_t *own, 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 @@ int nua_dialog_remove(nua_owner_t *own, { 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 @@ nua_dialog_usage_t *nua_dialog_usage_add(nua_owner_t *own, 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 @@ nua_dialog_usage_t *nua_dialog_usage_add(nua_owner_t *own, (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 @@ void nua_dialog_deinit(nua_owner_t *own, * 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 @@ void nua_dialog_usage_refresh_range(nua_dialog_usage_t *du, 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 @@ void nua_dialog_usage_reset_refresh(nua_dialog_usage_t *du) 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 @@ int nua_dialog_shutdown(nua_owner_t *owner, nua_dialog_state_t *ds) 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 @@ int nua_dialog_usage_shutdown(nua_owner_t *owner, 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; +} diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/nua_dialog.h b/libs/sofia-sip/libsofia-sip-ua/nua/nua_dialog.h index fc667f864b..77d264d896 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/nua_dialog.h +++ b/libs/sofia-sip/libsofia-sip-ua/nua/nua_dialog.h @@ -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 @@ typedef NUA_OWNER_T nua_owner_t; #include #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 @@ struct nua_client_request 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 @@ struct nua_client_request 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_client_request struct nua_dialog_state { + /** Dialog owner */ + nua_owner_t *ds_owner; + /** Dialog usages. */ nua_dialog_usage_t *ds_usage; @@ -346,12 +352,13 @@ struct nua_dialog_state 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 @@ struct nua_dialog_state } 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 @@ typedef struct { 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 @@ void nua_dialog_deinit(nua_owner_t *own, 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); diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/nua_notifier.c b/libs/sofia-sip/libsofia-sip-ua/nua/nua_notifier.c index fcb37f8c68..be3376ebb4 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/nua_notifier.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/nua_notifier.c @@ -696,7 +696,7 @@ static int nua_notify_client_report(nua_client_request_t *cr, 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); } } diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/nua_params.c b/libs/sofia-sip/libsofia-sip-ua/nua/nua_params.c index 92f1d00915..25458c799b 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/nua_params.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/nua_params.c @@ -1158,7 +1158,7 @@ int nua_handle_save_tags(nua_handle_t *nh, tagi_t *tags) 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; } } diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/nua_register.c b/libs/sofia-sip/libsofia-sip-ua/nua/nua_register.c index 9c31ba6ec6..d16b4e378c 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/nua_register.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/nua_register.c @@ -973,7 +973,7 @@ static int nua_register_client_response(nua_client_request_t *cr, 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 @@ void nua_register_connection_closed(tp_stack_t *sip_stack, 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); } diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/nua_session.c b/libs/sofia-sip/libsofia-sip-ua/nua/nua_session.c index 6508e72d1f..a68d35a3e8 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/nua_session.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/nua_session.c @@ -173,6 +173,8 @@ typedef struct nua_session_usage 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 @@ static int nua_invite_client_init(nua_client_request_t *cr, 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 @@ static int nua_invite_client_request(nua_client_request_t *cr, 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 @@ static int nua_invite_client_request(nua_client_request_t *cr, 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 @@ static int nua_session_client_response(nua_client_request_t *cr, 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 @@ static int nua_session_client_response(nua_client_request_t *cr, 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 @@ static int nua_invite_client_report(nua_client_request_t *cr, 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 @@ static int nua_invite_client_report(nua_client_request_t *cr, 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 @@ static int nua_invite_client_report(nua_client_request_t *cr, 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 @@ static int nua_invite_client_report(nua_client_request_t *cr, /* 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 @@ int nua_invite_client_ack(nua_client_request_t *cr, tagi_t const *tags) 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 @@ int nua_invite_client_ack(nua_client_request_t *cr, tagi_t const *tags) /* 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 @@ static int nua_prack_client_request(nua_client_request_t *cr, 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 @@ static int nua_prack_client_report(nua_client_request_t *cr, 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 @@ static int nua_prack_client_report(nua_client_request_t *cr, 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 @@ int nua_invite_server_preprocess(nua_server_request_t *sr) 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 @@ int nua_invite_server_respond(nua_server_request_t *sr, tagi_t const *tags) 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 process_ack(nua_server_request_t *sr, 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 @@ int nua_prack_server_init(nua_server_request_t *sr) /* 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 @@ int nua_prack_server_respond(nua_server_request_t *sr, tagi_t const *tags) 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 @@ static int nua_update_client_request(nua_client_request_t *cr, 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; /* XXX */ - if (state == nua_callstate_ready) - state = nua_callstate_calling; - - 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 @@ static int nua_update_client_report(nua_client_request_t *cr, 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 @@ static int nua_update_client_report(nua_client_request_t *cr, 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 @@ int nua_update_server_init(nua_server_request_t *sr) 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 @@ int nua_update_server_respond(nua_server_request_t *sr, tagi_t const *tags) 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 @@ int nua_update_server_respond(nua_server_request_t *sr, tagi_t const *tags) 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 @@ static void signal_call_state_change(nua_handle_t *nh, 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 @@ session_timer_set(nua_session_usage_t *ss) 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 @@ session_timer_set(nua_session_usage_t *ss) 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 { diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/nua_stack.c b/libs/sofia-sip/libsofia-sip-ua/nua/nua_stack.c index 123187bf2d..498b5e011b 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/nua_stack.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/nua_stack.c @@ -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 @@ int nua_stack_event(nua_t *nua, nua_handle_t *nh, msg_t *msg, 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 @@ void nua_stack_timer(nua_t *nua, su_timer_t *t, su_timer_arg_t *a) 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 @@ void nua_stack_shutdown(nua_t *nua) 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 @@ nua_stack_authenticate(nua_t *nua, nua_handle_t *nh, nua_event_t e, 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 @@ nua_stack_authenticate(nua_t *nua, nua_handle_t *nh, nua_event_t e, } } 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_trespond(nua_server_request_t *sr, 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 @@ int nua_server_respond(nua_server_request_t *sr, tagi_t const *tags) 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 @@ int nua_server_respond(nua_server_request_t *sr, tagi_t const *tags) 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 @@ int nua_base_server_report(nua_server_request_t *sr, tagi_t const *tags) 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 @@ void nua_client_request_destroy(nua_client_request_t *cr) 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 @@ int nua_client_request_sendmsg(nua_client_request_t *cr, msg_t *msg, sip_t *sip) * 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 @@ int nua_client_response(nua_client_request_t *cr, /** 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 @@ int nua_base_client_check_restart(nua_client_request_t *cr, 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 @@ int nua_base_client_check_restart(nua_client_request_t *cr, 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 @@ int nua_base_client_check_restart(nua_client_request_t *cr, 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 @@ int nua_base_client_check_restart(nua_client_request_t *cr, } } + 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 @@ int nua_client_restart(nua_client_request_t *cr, msg_destroy(msg); } - if (error) { cr->cr_graceful = graceful; cr->cr_terminated = terminated; diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/nua_subnotref.c b/libs/sofia-sip/libsofia-sip-ua/nua/nua_subnotref.c index 9855fb7913..024df1cb6d 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/nua_subnotref.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/nua_subnotref.c @@ -389,7 +389,7 @@ static int nua_subscribe_client_response(nua_client_request_t *cr, 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 @@ int nua_notify_server_report(nua_server_request_t *sr, tagi_t const *tags) 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 @@ int nua_notify_server_report(nua_server_request_t *sr, tagi_t const *tags) 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 @@ int nua_notify_server_report(nua_server_request_t *sr, tagi_t const *tags) 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); diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_100rel.c b/libs/sofia-sip/libsofia-sip-ua/nua/test_100rel.c index 592124b09b..019a600c4b 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/test_100rel.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/test_100rel.c @@ -196,7 +196,7 @@ int test_180rel(struct context *ctx) 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 @@ int test_prack_auth(struct context *ctx) 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 @@ int test_prack_auth(struct context *ctx) 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 @@ int test_prack_auth(struct context *ctx) 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 @@ int test_prack_auth(struct context *ctx) 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 @@ int test_183rel(struct context *ctx) 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 @@ int test_preconditions(struct context *ctx) 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 @@ int test_preconditions2(struct context *ctx) 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 @@ int test_preconditions2(struct context *ctx) |-------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 @@ int test_preconditions2(struct context *ctx) 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 @@ int test_preconditions2(struct context *ctx) 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 @@ int test_preconditions2(struct context *ctx) 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 @@ int test_update_by_uas(struct context *ctx) 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 @@ int test_update_by_uas(struct context *ctx) |<-------200---------| | | |-------UPDATE------>| - |<-------200---------| - | | - |<------UPDATE-------| + +------------------------+ + | |<-------200---------| | + | | | | + | |<------UPDATE-------| | + +------------------------+ |--------200-------->| | | |<-------180---------| @@ -1526,6 +1536,9 @@ int test_update_by_uas(struct context *ctx) |-------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 @@ int test_update_by_uas(struct context *ctx) 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 @@ int test_update_by_uas(struct context *ctx) 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_180rel_cancel2(struct context *ctx) 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); diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_basic_call.c b/libs/sofia-sip/libsofia-sip-ua/nua/test_basic_call.c index e5b9eb18e3..836e35e92f 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/test_basic_call.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/test_basic_call.c @@ -1356,14 +1356,262 @@ int accept_upgrade(CONDITION_PARAMS) } } +/* 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) ; } diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_call_reject.c b/libs/sofia-sip/libsofia-sip-ua/nua/test_call_reject.c index a3bf75e179..c3be7ad0dd 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/test_call_reject.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/test_call_reject.c @@ -262,6 +262,7 @@ int test_reject_b(struct context *ctx) /* ------------------------------------------------------------------------ */ 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 @@ int reject_604(CONDITION_PARAMS); | | |-------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 @@ int reject_305(CONDITION_PARAMS) 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 reject_604(CONDITION_PARAMS) } } +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 @@ int test_reject_302(struct context *ctx) /* 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 @@ int test_reject_302(struct context *ctx) 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 @@ int test_reject_302(struct context *ctx) 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 @@ int test_call_timeouts(struct context *ctx) |<--------200--------| |--ACK-X | | | - |---------BYE------->| - |<-------200 OK------| + |<--------BYE--------| + |--------200 OK----->| */ @@ -1484,6 +1544,109 @@ int test_call_timeouts(struct context *ctx) 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) diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_cancel_bye.c b/libs/sofia-sip/libsofia-sip-ua/nua/test_cancel_bye.c index 6e69fa465c..107247c8cc 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/test_cancel_bye.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/test_cancel_bye.c @@ -109,6 +109,24 @@ int cancel_when_calling(CONDITION_PARAMS) } } +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 @@ int test_call_cancel(struct context *ctx) 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,10 +230,65 @@ int test_call_cancel(struct context *ctx) 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"); @@ -679,20 +752,24 @@ int test_call_destroy_4(struct context *ctx) 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 @@ int test_bye_to_invalid_contact(struct context *ctx) 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 @@ int test_bye_to_invalid_contact(struct context *ctx) 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)); diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_init.c b/libs/sofia-sip/libsofia-sip-ua/nua/test_init.c index 269275d9f1..e8f67d94c3 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/test_init.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/test_init.c @@ -36,6 +36,7 @@ #include "test_nua.h" #include +#include #if HAVE_FUNC #elif HAVE_FUNCTION @@ -107,17 +108,52 @@ int test_nua_init(struct context *ctx, 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 @@ int test_nua_init(struct context *ctx, 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); diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_nua.c b/libs/sofia-sip/libsofia-sip-ua/nua/test_nua.c index 5798748dab..cc41308888 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/test_nua.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/test_nua.c @@ -65,6 +65,8 @@ int tstflags = 0; static RETSIGTYPE sig_alarm(int s) { fprintf(stderr, "%s: FAIL! test timeout!\n", name); + if (tstflags & tst_abort) + abort(); exit(1); } #endif diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_nua.h b/libs/sofia-sip/libsofia-sip-ua/nua/test_nua.h index 5947f04f72..a77c7f54ee 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/test_nua.h +++ b/libs/sofia-sip/libsofia-sip-ua/nua/test_nua.h @@ -139,6 +139,7 @@ struct context int running; + struct domain *domain; condition_function *next_condition; nua_event_t next_event, last_event; nua_t *nua; diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_ops.c b/libs/sofia-sip/libsofia-sip-ua/nua/test_ops.c index 1e89252126..355d289adb 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/test_ops.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/test_ops.c @@ -146,47 +146,59 @@ void print_event(nua_event_t event, 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 @@ void run_abc_until(struct context *ctx, 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); } } diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_proxy.c b/libs/sofia-sip/libsofia-sip-ua/nua/test_proxy.c index bcdb160bb3..e2b169a66b 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/test_proxy.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/test_proxy.c @@ -35,14 +35,17 @@ #include 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 #include @@ -83,50 +86,74 @@ STORAGE void PREFIX ##_remove(T *node) \ } \ extern int LIST_DUMMY_VARIABLE -#include "test_proxy.h" +#include +#include 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 proxy_tr *stateless; + struct proxy_tr *transactions; + + struct domain *domains; + + struct { + sip_time_t session_expires, min_se; + } 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; - - sip_time_t session_expires, min_se; - int outbound_tcp; /**< Use inbound TCP connection as outbound */ - } prefs; -}; + 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 @@ struct binding 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 @@ static int binding_is_active(struct binding const *b) (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 auth_challenger_t proxy_challenger[1]; 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 @@ test_proxy_init(su_root_t *root, struct proxy *proxy) 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 @@ test_proxy_init(su_root_t *root, struct proxy *proxy) 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 @@ test_proxy_init(su_root_t *root, struct proxy *proxy) 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 @@ struct proxy *test_proxy_create(su_root_t *root, if (p) { ta_list ta; + p->magic = test_proxy_create; + p->parent = root; ta_start(ta, tag, value); @@ -362,27 +391,27 @@ url_t const *test_proxy_uri(struct proxy const *p) 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) +void test_proxy_domain_set_expiration(struct domain *d, + 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; + 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) +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 (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; + 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_get_session_timer(struct proxy *p, } } -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 @@ int test_proxy_close_tports(struct proxy *p) /* ---------------------------------------------------------------------- */ +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 sip_contact_t *create_transport_contacts(struct proxy *p) /* ---------------------------------------------------------------------- */ -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) +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) { - url_t const *request_uri, *target; - struct proxy_transaction *t = NULL; - sip_request_t *rq = NULL; - sip_max_forwards_t *mf; + 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); - mf = sip->sip_max_forwards; + 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 (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 (process == NULL) + return 501; /* Not implemented */ - 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; - } + return proxy_tr_with(pod->domain->proxy, pod->domain, irq, sip, process); +} - if (method == sip_method_invite) { - if (proxy->prefs.min_se) { - if (!sip->sip_min_se || - sip->sip_min_se->min_delta < proxy->prefs.min_se) { - min_se = sip_min_se_init(min_se0); - min_se->min_delta = proxy->prefs.min_se; - } +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; - if (sip->sip_session_expires - && sip->sip_session_expires->x_delta < 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; - } - } + assert(proxy->magic = test_proxy_init); - if (proxy->prefs.session_expires) { - if (!sip->sip_session_expires || - sip->sip_session_expires->x_delta > proxy->prefs.session_expires) { - x = sip_session_expires_init(x0); - x->x_delta = proxy->prefs.session_expires; - if (!sip_has_supported(sip->sip_supported, "timer")) - require = "timer"; - } - } - } + 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); - /* We don't do any route processing */ - request_uri = sip->sip_request->rq_url; + t->method = sip->sip_request->rq_method; + t->target = sip->sip_request->rq_url; + t->now = nta_incoming_received(irq, NULL); - 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 (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 { - struct registration_entry *e; - struct binding *b; - - if (sip->sip_request->rq_method == sip_method_register) - return process_register(proxy, irq, sip); - - e = registration_entry_find(proxy, request_uri); - if (e == NULL) { - nta_incoming_treply(irq, SIP_404_NOT_FOUND, TAG_END()); - return 404; - } - - for (b = e->bindings; b; b = b->next) - if (binding_is_active(b)) - break; - - if (b == NULL) { - nta_incoming_treply(irq, SIP_480_TEMPORARILY_UNAVAILABLE, TAG_END()); - return 480; - } - - target = b->contact->m_url; - tport = 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); + + 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); +} - 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; - } - t->rq = rq; +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; - /* 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()); - return 500; + 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; } - else if (sip->sip_request->rq_method == sip_method_ack) - proxy_transaction_destroy(t); return 0; } -static -int challenge_request(struct proxy *p, - nta_incoming_t *irq, - sip_t const *sip) +static int validate_transaction(struct proxy_tr *t) { - int status; - auth_status_t *as; - msg_t *msg; + sip_max_forwards_t *mf; - as = auth_status_new(p->home); - if (!as) + mf = t->sip->sip_max_forwards; + + if (mf && mf->mf_count <= 1) { + if (t->method == sip_method_options) + return process_options(t); + + return respond_transaction(t, SIP_483_TOO_MANY_HOPS, TAG_END()); + } + + /* 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 (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 < t->proxy->prefs.min_se) { + min_se = sip_min_se_init(min_se0); + min_se->min_delta = t->proxy->prefs.min_se; + } + + if (sip->sip_session_expires + && 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); + return respond_transaction(t, SIP_422_SESSION_TIMER_TOO_SMALL, + SIPTAG_MIN_SE(min_se), + TAG_END()); + } + } + + if (t->proxy->prefs.session_expires) { + if (!sip->sip_session_expires || + sip->sip_session_expires->x_delta > t->proxy->prefs.session_expires) { + x = sip_session_expires_init(x0); + x->x_delta = t->proxy->prefs.session_expires; + if (!sip_has_supported(sip->sip_supported, "timer")) + require = "timer"; + } + } + + 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()); + } + + return 0; +} + +static int incoming_transaction(struct proxy_tr *t) +{ + struct registration_entry *e; + struct binding *b; + +#if 0 + if (sip->sip_request->rq_method == sip_method_register) + return process_register(proxy, irq, sip); +#endif + + t->entry = e = registration_entry_find(t->domain, t->target); + if (e == NULL) + return respond_transaction(t, SIP_404_NOT_FOUND, TAG_END()); + + for (b = e->bindings; b; b = b->next) { + if (binding_is_active(b)) + target_transaction(t, b->contact->m_url, b->tport); + + if (t->clients) /* XXX - enable forking */ + break; + } + + 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); + } + + 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_transaction(struct proxy_tr *t) +{ + auth_status_t *as; + sip_t *sip = t->sip; + + assert(t->am); + + t->as = as = auth_status_new(t->proxy->home); + if (!as) + 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 @@ int challenge_request(struct proxy *p, 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; + /* This is before authentication because we want to be bug-compatible */ + if (check_received_contact(t)) + return t->status; - as = auth_status_new(proxy->home); - if (!as) - return 500; + if (t->domain->auth) { + t->am = t->domain->auth, t->use_auth = 401; + if (challenge_transaction(t)) + return t->status; + } - as->as_method = sip->sip_request->rq_method_name; - msg = nta_incoming_getrequest(irq); - as->as_source = msg_addrinfo(msg); - msg_destroy(msg); + if (validate_contacts(t)) + return t->status; - as->as_user_uri = sip->sip_from->a_url; - as->as_display = sip->sip_from->a_display; + t->entry = registration_entry_find(t->domain, t->sip->sip_to->a_url); - if (sip->sip_payload) - as->as_body = sip->sip_payload->pl_data, - as->as_bodylen = sip->sip_payload->pl_len; - - 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()); - - status = as->as_status; - - su_home_unref(as->as_home); - - 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); + return 0; +} - if (validate_contacts(p, as, sip)) - return as->as_status; +/* Validate expiration times */ +static int validate_contacts(struct proxy_tr *t) +{ + 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; - 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 (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; } - 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; -} - -static int validate_contacts(struct proxy *p, - auth_status_t *as, - sip_t const *sip) -{ - sip_contact_t const *m; - 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; - } - - 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 @@ static int validate_contacts(struct proxy *p, } /** 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 @@ int check_out_of_order(struct proxy *p, 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 @@ registration_entry_destroy(struct registration_entry *e) { 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 @@ 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) @@ -1046,40 +1286,48 @@ void binding_destroy(su_home_t *home, struct binding *b) 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 (t->sip->sip_contact == NULL) { + if (t->entry) + contacts = t->entry->contacts; + goto ok200; + } - 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->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 @@ int binding_update(struct proxy *p, 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 @@ int binding_update(struct proxy *p, 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 @@ int binding_update(struct proxy *p, } 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 @@ int binding_update(struct proxy *p, 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 @@ sip_contact_t *binding_contacts(su_home_t *home, struct binding *bindings) 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; + } } } } diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_proxy.h b/libs/sofia-sip/libsofia-sip-ua/nua/test_proxy.h index 9f74a533a8..fa433eca2a 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/test_proxy.h +++ b/libs/sofia-sip/libsofia-sip-ua/nua/test_proxy.h @@ -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 @@ void test_proxy_destroy(struct proxy *); 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); +struct domain *test_proxy_add_domain(struct proxy *, + url_t const *domain, + tag_type_t, tag_value_t, ...); -void test_proxy_get_expiration(struct proxy *, - sip_time_t *return_min_expires, - sip_time_t *return_expires, - sip_time_t *return_max_expires); +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 @@ void test_proxy_get_session_timer(struct proxy *p, 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 diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_register.c b/libs/sofia-sip/libsofia-sip-ua/nua/test_register.c index ed1ab1520d..aafdb961db 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/test_register.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/test_register.c @@ -57,9 +57,6 @@ int test_register_to_proxy(struct context *ctx) 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 @@ int test_register_to_proxy(struct context *ctx) if (print_headings) printf("TEST NUA-2.3.0.3: PASSED\n"); - /* REGISTER test A B @@ -123,6 +119,8 @@ int test_register_to_proxy(struct context *ctx) 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 @@ int test_register_to_proxy(struct context *ctx) 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 @@ int test_register_to_proxy(struct context *ctx) 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 @@ int test_register_to_proxy(struct context *ctx) 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 @@ int test_register_to_proxy(struct context *ctx) 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 @@ int test_register_to_proxy(struct context *ctx) 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(); } diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_simple.c b/libs/sofia-sip/libsofia-sip-ua/nua/test_simple.c index ead1658f96..020b1f2c4e 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/test_simple.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/test_simple.c @@ -814,6 +814,7 @@ static 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 @@ int test_subscribe_notify_graceful(struct context *ctx) 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: diff --git a/libs/sofia-sip/libsofia-sip-ua/nua/test_sip_events.c b/libs/sofia-sip/libsofia-sip-ua/nua/test_sip_events.c index 6c005c1fcc..e441ccb5fb 100644 --- a/libs/sofia-sip/libsofia-sip-ua/nua/test_sip_events.c +++ b/libs/sofia-sip/libsofia-sip-ua/nua/test_sip_events.c @@ -64,9 +64,15 @@ int save_until_notified(CONDITION_PARAMS) 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; diff --git a/libs/sofia-sip/libsofia-sip-ua/soa/test_soa.c b/libs/sofia-sip/libsofia-sip-ua/soa/test_soa.c index c3d539b25b..4054f18920 100644 --- a/libs/sofia-sip/libsofia-sip-ua/soa/test_soa.c +++ b/libs/sofia-sip/libsofia-sip-ua/soa/test_soa.c @@ -1264,6 +1264,67 @@ int test_media_replace(struct context *ctx) } +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_asynch_offer_answer(struct context *ctx) 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 @@ int main(int argc, char *argv[]) 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(); } diff --git a/libs/sofia-sip/libsofia-sip-ua/tport/sofia-sip/tport_tag.h b/libs/sofia-sip/libsofia-sip-ua/tport/sofia-sip/tport_tag.h index 500a898319..64ee4ee916 100644 --- a/libs/sofia-sip/libsofia-sip-ua/tport/sofia-sip/tport_tag.h +++ b/libs/sofia-sip/libsofia-sip-ua/tport/sofia-sip/tport_tag.h @@ -225,6 +225,66 @@ TPORT_DLL extern tag_typedef_t tptag_timeout; 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. * diff --git a/libs/sofia-sip/libsofia-sip-ua/tport/test_tport.c b/libs/sofia-sip/libsofia-sip-ua/tport/test_tport.c index e7355051a2..a6cf2a3ec3 100644 --- a/libs/sofia-sip/libsofia-sip-ua/tport/test_tport.c +++ b/libs/sofia-sip/libsofia-sip-ua/tport/test_tport.c @@ -33,6 +33,9 @@ * @date Created: Wed Apr 3 11:25:13 2002 ppessi */ +/* always assert()s */ +#undef NDEBUG + #include "config.h" #include @@ -49,6 +52,8 @@ typedef struct tp_test_s tp_test_t; #include #include +#include "tport_internal.h" /* Get SU_DEBUG_*() */ + #include "test_class.h" #include "test_protos.h" #include "sofia-sip/msg.h" @@ -101,6 +106,9 @@ struct tp_test_s { 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 @@ static void tp_test_recv(tp_test_t *tt, 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 @@ tp_stack_class_t const tp_test_class[1] = 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 @@ static int init_test(tp_test_t *tt) 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 @@ static int init_test(tp_test_t *tt) 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 @@ static int udp_test(tp_test_t *tt) 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 @@ static int udp_test(tp_test_t *tt) 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 @@ static int tcp_test(tp_test_t *tt) 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 @@ static int tcp_test(tp_test_t *tt) /* 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 @@ static int tcp_test(tp_test_t *tt) 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 @@ static int sctp_test(tp_test_t *tt) 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 @@ static int sctp_test(tp_test_t *tt) 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 @@ static int sctp_test(tp_test_t *tt) 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 @@ static int sctp_test(tp_test_t *tt) } /* 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 @@ static int sctp_test(tp_test_t *tt) 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 @@ static int tls_test(tp_test_t *tt) 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 @@ static int tls_test(tp_test_t *tt) 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 @@ static int tls_test(tp_test_t *tt) 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 @@ static int filter_test(tp_test_t *tt) END(); } +#if HAVE_ALARM +#include + +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 main(int argc, char *argv[]) { 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 @@ int main(int argc, char *argv[]) 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 @@ int main(int argc, char *argv[]) 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 @@ int main(int argc, char *argv[]) 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... */ diff --git a/libs/sofia-sip/libsofia-sip-ua/tport/tport.c b/libs/sofia-sip/libsofia-sip-ua/tport/tport.c index 66d31569d0..846ae10210 100644 --- a/libs/sofia-sip/libsofia-sip-ua/tport/tport.c +++ b/libs/sofia-sip/libsofia-sip-ua/tport/tport.c @@ -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 @@ -127,6 +127,33 @@ RBTREE_BODIES(su_inline, tprb, tport_t, 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 @@ int tport_is_clear_to_send(tport_t const *self) !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 @@ tport_t *tport_by_addrinfo(tport_primary_t const *pri, 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 void tport_zap_primary(tport_primary_t *); 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_t *tport_tcreate(tp_stack_t *stack, 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 @@ tport_t *tport_tcreate(tp_stack_t *stack, 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_t *tport_tcreate(tp_stack_t *stack, 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 @@ void tport_zap_primary(tport_primary_t *pri) 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 @@ void tport_zap_primary(tport_primary_t *pri) *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 @@ tport_primary_t *tport_listen(tport_master_t *mr, 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 @@ int tport_set_events(tport_t *self, int set, int clear) /**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 @@ tport_t *tport_alloc_secondary(tport_primary_t *pri, 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 @@ tport_t *tport_alloc_secondary(tport_primary_t *pri, 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 @@ tport_t *tport_connect(tport_primary_t *pri, 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 @@ tport_t *tport_base_connect(tport_primary_t *pri, /* 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 @@ tport_t *tport_base_connect(tport_primary_t *pri, 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 @@ void tport_zap_secondary(tport_t *self) 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 @@ void tport_zap_secondary(tport_t *self) 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 @@ tport_t *tport_ref(tport_t *tp) /** 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 @@ void tport_decref(tport_t **ttp) * * @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 @@ int tport_get_params(tport_t const *self, 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 @@ int tport_get_params(tport_t const *self, * * @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 @@ int tport_set_params(tport_t *self, 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 @@ int tport_set_params(tport_t *self, 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 @@ int tport_set_params(tport_t *self, 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 @@ int tport_set_params(tport_t *self, 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 @@ int tport_set_params(tport_t *self, 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 @@ int tport_set_params(tport_t *self, memcpy(tpp0, tpp, sizeof tpp); + if (tport_is_secondary(self)) + tport_set_secondary_timer(self); + return n; } @@ -1441,7 +1511,8 @@ int tport_bind_client(tport_master_t *mr, 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 @@ int tport_bind_server(tport_master_t *mr, (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 @@ int tport_bind_server(tport_master_t *mr, 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 tport_server_addrinfo(tport_master_t *mr, 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 @@ tport_get_local_addrinfo(tport_master_t *mr, 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 @@ int tport_addrinfo_copy(su_addrinfo_t *dst, void *addr, socklen_t addrlen, /** 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 @@ void tport_close(tport_t *self) msg_ref_destroy(self->tp_queue[i]), self->tp_queue[i] = NULL; } } - + self->tp_index = 0; self->tp_events = 0; } @@ -2024,16 +2103,23 @@ void tport_close(tport_t *self) */ 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 @@ int tport_shutdown(tport_t *self, int how) 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 @@ int tport_shutdown(tport_t *self, int how) } } + return 0; +} + +static void tport_secondary_timer(su_root_magic_t *magic, + su_timer_t *t, + tport_t *self) +{ + 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); +} + +/** 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) +{ + 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; + } + } + + tport_set_secondary_timer(self); +} + +/** 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 (!tport_is_secondary(self)) + return 0; + + 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); + + return 0; + } + + 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"; + } + } + } + + 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; } -su_inline -unsigned long tport_now(void) -{ - return su_now().tv_sec; -} - -/** Transport timer function. */ -static -void tport_tick(su_root_magic_t *magic, su_timer_t *t, tport_master_t *mr) -{ - 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); - } - } - } - - /* 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; - } - - tp_next = tprb_succ(tp); - - if (tp->tp_refs) - continue; - - closed = tport_is_closed(tp); - - if (!closed && - !(tp->tp_params->tpp_idle > 0 - && (int)tp->tp_params->tpp_idle < ts - (int)tp->tp_time)) { - continue; - } - - 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); - } - - tport_zap_secondary(tp); - } - } - - su_timer_set(t, tport_tick, mr); -} /** 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 @@ void tport_error_report(tport_t *self, int errcode, } 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 @@ int tport_accept(tport_primary_t *pri, int events) 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 @@ static int tport_connected(su_root_magic_t *magic, su_wait_t *w, tport_t *self) 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 @@ static int tport_wakeup_pri(su_root_magic_t *m, su_wait_t *w, tport_t *self) #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 @@ static int tport_wakeup(su_root_magic_t *magic, su_wait_t *w, tport_t *self) #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 @@ static int tport_base_wakeup(tport_t *self, int events) 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 @@ int tport_continue(tport_t *self) */ 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 @@ void tport_hup_event(tport_t *self) 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 @@ int tport_recv_data(tport_t *self) */ 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 @@ void tport_recv_event(tport_t *self) 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 @@ void tport_recv_event(tport_t *self) } 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 @@ static void tport_parse(tport_t *self, int complete, su_time_t now) 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 @@ ssize_t tport_recv_iovec(tport_t const *self, 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 @@ ssize_t tport_recv_iovec(tport_t const *self, 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 @@ ssize_t tport_recv_iovec(tport_t const *self, 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 @@ tport_t *tport_tsend(tport_t *self, 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 @@ int tport_prepare_and_send(tport_t *self, msg_t *msg, 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 @@ int tport_prepare_and_send(tport_t *self, msg_t *msg, 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 @@ int tport_send_msg(tport_t *self, msg_t *msg, 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 @@ int tport_send_msg(tport_t *self, msg_t *msg, 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 @@ int tport_send_msg(tport_t *self, msg_t *msg, 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 @@ ssize_t tport_vsend(tport_t *self, 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 @@ int tport_send_error(tport_t *self, msg_t *msg, 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 @@ int tport_send_error(tport_t *self, msg_t *msg, 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 @@ int tport_send_error(tport_t *self, msg_t *msg, 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 @@ int tport_queue(tport_t *self, msg_t *msg) 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 @@ int tport_tqsend(tport_t *self, msg_t *msg, msg_t *next, 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 @@ int tport_tqsend(tport_t *self, msg_t *msg, msg_t *next, 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 @@ void tport_send_event(tport_t *self) 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 @@ void tport_send_queue(tport_t *self) 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 @@ void tport_send_queue(tport_t *self) 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 @@ void tport_send_queue(tport_t *self) 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 @@ int tport_pend(tport_t *self, { 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 @@ int tport_release(tport_t *self, 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_next(tport_t const *self) 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 @@ tport_t *tport_by_name(tport_t const *self, tp_name_t const *tpn) 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 @@ tport_t *tport_by_name(tport_t const *self, tp_name_t const *tpn) } 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 @@ tport_t *tport_by_name(tport_t const *self, tp_name_t const *tpn) 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 @@ tport_t *tport_by_addrinfo(tport_primary_t const *pri, 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 @@ tport_t *tport_by_addrinfo(tport_primary_t const *pri, 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; } diff --git a/libs/sofia-sip/libsofia-sip-ua/tport/tport_internal.h b/libs/sofia-sip/libsofia-sip-ua/tport/tport_internal.h index db95fdd751..b330b143b2 100644 --- a/libs/sofia-sip/libsofia-sip-ua/tport/tport_internal.h +++ b/libs/sofia-sip/libsofia-sip-ua/tport/tport_internal.h @@ -94,6 +94,9 @@ typedef struct { 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 @@ typedef struct { 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 @@ typedef struct { 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 @@ struct tport_s { 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 @@ struct tport_s { 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 @@ struct tport_s { /* ==== 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 @@ struct tport_s { 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 @@ struct tport_primary { * 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 @@ struct tport_primary { void *pri_stun_handle; tport_params_t pri_params[1]; /**< Transport parameters */ - }; /** Master structure */ @@ -307,7 +320,7 @@ struct tport_master { #endif }; -/** Virtual funtion table for transports */ +/** Virtual function table for transports */ struct tport_vtable { char const *vtp_name; @@ -344,6 +357,9 @@ struct tport_vtable 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 @@ tport_t *tport_alloc_secondary(tport_primary_t *pri, 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 @@ int tport_send_msg(tport_t *self, msg_t *msg, 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 @@ void tport_dump_iovec(tport_t const *self, msg_t *msg, 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 @@ int tport_recv_stream(tport_t *self); 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; diff --git a/libs/sofia-sip/libsofia-sip-ua/tport/tport_tag.c b/libs/sofia-sip/libsofia-sip-ua/tport/tport_tag.c index e775869395..b577d93c09 100644 --- a/libs/sofia-sip/libsofia-sip-ua/tport/tport_tag.c +++ b/libs/sofia-sip/libsofia-sip-ua/tport/tport_tag.c @@ -64,6 +64,9 @@ tag_typedef_t tptag_sdwn_after = BOOLTAG_TYPEDEF(sdwn_after); 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); diff --git a/libs/sofia-sip/libsofia-sip-ua/tport/tport_tls.c b/libs/sofia-sip/libsofia-sip-ua/tport/tport_tls.c index 0a242cc47e..73ab7324c7 100644 --- a/libs/sofia-sip/libsofia-sip-ua/tport/tport_tls.c +++ b/libs/sofia-sip/libsofia-sip-ua/tport/tport_tls.c @@ -587,6 +587,10 @@ int tls_error(tls_t *tls, int ret, char const *who, char const *operation, 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 @@ int tls_want_read(tls_t *tls, int events) 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; } diff --git a/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_connect.c b/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_connect.c index dc57ec21f6..93affa56a6 100644 --- a/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_connect.c +++ b/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_connect.c @@ -201,6 +201,8 @@ static tport_t *tport_http_connect(tport_primary_t *pri, su_addrinfo_t *ai, return NULL; } + tport_set_secondary_timer(tport); + return tport; } diff --git a/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_sctp.c b/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_sctp.c index df97398c84..8c06dee251 100644 --- a/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_sctp.c +++ b/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_sctp.c @@ -37,12 +37,12 @@ #include "config.h" +#if HAVE_SCTP + #include "tport_internal.h" #if HAVE_NETINET_SCTP_H #include -#undef HAVE_SCTP -#define HAVE_SCTP 1 #endif #include @@ -63,7 +63,6 @@ #define SOL_SCTP IPPROTO_SCTP #endif - enum { MAX_STREAMS = 1 }; typedef struct tport_sctp_t { @@ -81,8 +80,6 @@ typedef struct tport_sctp_t #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 int tport_recv_sctp(tport_t *self); 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 @@ tport_vtable_t const tport_sctp_client_vtable = 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 @@ tport_vtable_t const tport_sctp_vtable = 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 @@ static ssize_t tport_send_sctp(tport_t const *self, msg_t *msg, 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 diff --git a/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_tcp.c b/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_tcp.c index 2916076841..f2a98a237f 100644 --- a/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_tcp.c +++ b/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_tcp.c @@ -45,6 +45,7 @@ #endif #include +#include #include #include #include @@ -76,6 +77,12 @@ tport_vtable_t const tport_tcp_vtable = 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 @@ tport_vtable_t const tport_tcp_client_vtable = sizeof (tport_primary_t), tport_tcp_init_client, NULL, - tport_accept, + NULL, NULL, sizeof (tport_t), tport_tcp_init_secondary, @@ -94,6 +101,12 @@ tport_vtable_t const tport_tcp_client_vtable = 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 @@ static int tport_tcp_setsndbuf(int socket, int atleast) #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 @@ int tport_recv_stream(tport_t *self) { 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 @@ int tport_recv_stream(tport_t *self) 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 @@ int tport_recv_stream(tport_t *self) 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 @@ int tport_recv_stream(tport_t *self) /* 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 @@ ssize_t tport_send_stream(tport_t const *self, msg_t *msg, #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); +} diff --git a/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_tls.c b/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_tls.c index eec8279b10..d25bfe34f0 100644 --- a/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_tls.c +++ b/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_tls.c @@ -307,6 +307,8 @@ int tport_tls_events(tport_t *self, int events) 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); } diff --git a/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_udp.c b/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_udp.c index b7c1ba51c3..db5288369f 100644 --- a/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_udp.c +++ b/libs/sofia-sip/libsofia-sip-ua/tport/tport_type_udp.c @@ -198,6 +198,9 @@ static void tport_check_trunc(tport_t *tp, su_addrinfo_t *ai) "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); diff --git a/libs/sofia-sip/libsofia-sip-ua/url/torture_url.c b/libs/sofia-sip/libsofia-sip-ua/url/torture_url.c index b26f751bdc..9a05516127 100644 --- a/libs/sofia-sip/libsofia-sip-ua/url/torture_url.c +++ b/libs/sofia-sip/libsofia-sip-ua/url/torture_url.c @@ -120,11 +120,12 @@ int test_quote(void) 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 @@ int test_quote(void) 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 @@ int test_sip(void) "sip:user:pass@host:32;param=1" "?From=foo@bar&To=bar@baz#unf"; char sip2url[] = - "sip:user/path;tel-param:pass@host:32;param=1" + "sip:user/path;tel-param:pass@host:32;param=1%3d%3d1" "?From=foo@bar&To=bar@baz#unf"; char sip2[sizeof(sipurl) + 32]; char sipsurl[] = @@ -305,6 +308,7 @@ int test_sip(void) 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@bar\nTo:bar@baz"); diff --git a/libs/sofia-sip/libsofia-sip-ua/url/url.c b/libs/sofia-sip/libsofia-sip-ua/url/url.c index 051f81d214..312e5340fe 100644 --- a/libs/sofia-sip/libsofia-sip-ua/url/url.c +++ b/libs/sofia-sip/libsofia-sip-ua/url/url.c @@ -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 @@ char *url_unescape(char *d, char const *s) /** 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 @@ char *url_canonize2(char *d, char const * const s, size_t n, 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 @@ char *url_canonize2(char *d, char const * const s, size_t n, * 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 @@ int _url_d(url_t *url, char *s) 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 @@ int _url_d(url_t *url, char *s) 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 @@ int url_d(url_t *url, char *s) # 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 @@ int url_d(url_t *url, char *s) # 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 @@ int url_d(url_t *url, char *s) /* 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 @@ void url_string_update(su_md5_t *md5, char const *s) 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);