Sipper 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/sipper/README.rb +26 -0
- data/sipper/b2bua_controller.rb +163 -0
- data/sipper/b2bua_session_mixin.rb +24 -0
- data/sipper/base_controller.rb +425 -0
- data/sipper/bin/common.rb +42 -0
- data/sipper/bin/generate.rb +70 -0
- data/sipper/bin/project.rb +44 -0
- data/sipper/bin/run.rb +85 -0
- data/sipper/bin/run_smoke.rb +8 -0
- data/sipper/config/log4r.xml +71 -0
- data/sipper/controller_class_loader.rb +29 -0
- data/sipper/controller_selector.rb +119 -0
- data/sipper/controllers/invite_controller.rb +30 -0
- data/sipper/controllers/order.yaml +3 -0
- data/sipper/custom_message.rb +4 -0
- data/sipper/detached_session.rb +11 -0
- data/sipper/docs/manual.txt +1621 -0
- data/sipper/generators/README +12 -0
- data/sipper/generators/gen_controller.rb +228 -0
- data/sipper/generators/gen_project.rb +45 -0
- data/sipper/generators/gen_test.rb +72 -0
- data/sipper/generators/project_template_dir/Rakefile +56 -0
- data/sipper/generators/project_template_dir/config/sipper.cfg +31 -0
- data/sipper/generators/project_template_dir/controllers/README.txt +2 -0
- data/sipper/generators/project_template_dir/dot_sipper.proj +2 -0
- data/sipper/generators/project_template_dir/logs/README.txt +2 -0
- data/sipper/generators/project_template_dir/tests/README.txt +2 -0
- data/sipper/lib/smc/statemap.rb +194 -0
- data/sipper/logs/dialog_info_store +0 -0
- data/sipper/logs/r.cmd +6 -0
- data/sipper/logs/r.sh +6 -0
- data/sipper/media/sipper_media_client.rb +268 -0
- data/sipper/media/sipper_media_event.rb +43 -0
- data/sipper/media/sipper_media_manager.rb +145 -0
- data/sipper/media/sipper_media_proxy.rb +60 -0
- data/sipper/media/sipper_offer_answer.rb +285 -0
- data/sipper/message.rb +512 -0
- data/sipper/modified_pattern_formatter.rb +119 -0
- data/sipper/proxy_controller.rb +143 -0
- data/sipper/registration.rb +52 -0
- data/sipper/request.rb +109 -0
- data/sipper/response.rb +123 -0
- data/sipper/ruby_ext/module.rb +27 -0
- data/sipper/ruby_ext/mutable_class.rb +17 -0
- data/sipper/ruby_ext/object.rb +38 -0
- data/sipper/ruby_ext/pqueue.rb +190 -0
- data/sipper/ruby_ext/snapshot.rb +201 -0
- data/sipper/ruby_ext/string.rb +18 -0
- data/sipper/ruby_ext/time.rb +9 -0
- data/sipper/run/run_sipper1.rb +28 -0
- data/sipper/run/run_sipper2.rb +56 -0
- data/sipper/sdp/sdp.rb +257 -0
- data/sipper/sdp/sdp_generator.rb +131 -0
- data/sipper/sdp/sdp_parser.rb +136 -0
- data/sipper/session.rb +1952 -0
- data/sipper/session_manager.rb +170 -0
- data/sipper/session_recorder.rb +190 -0
- data/sipper/session_state/DialogState.sm +54 -0
- data/sipper/session_state/DialogState_sm.rb +337 -0
- data/sipper/session_state/dialog_routes.rb +141 -0
- data/sipper/sip_headers/header.rb +632 -0
- data/sipper/sip_headers/sipuri.rb +352 -0
- data/sipper/sip_logger.rb +65 -0
- data/sipper/sip_message_router.rb +231 -0
- data/sipper/sip_test_driver_controller.rb +10 -0
- data/sipper/sipper.rb +329 -0
- data/sipper/sipper_assertions.rb +21 -0
- data/sipper/sipper_configurator.rb +376 -0
- data/sipper/sipper_http/sipper_http_request_dispatcher.rb +71 -0
- data/sipper/sipper_http/sipper_http_response.rb +25 -0
- data/sipper/stray_message_manager.rb +40 -0
- data/sipper/test_completion_signaling_helper.rb +77 -0
- data/sipper/transaction/Ict.sm +59 -0
- data/sipper/transaction/Ict_sm.rb +430 -0
- data/sipper/transaction/Ist.sm +74 -0
- data/sipper/transaction/Ist_sm.rb +460 -0
- data/sipper/transaction/Nict.sm +51 -0
- data/sipper/transaction/Nict_sm.rb +325 -0
- data/sipper/transaction/Nist.sm +59 -0
- data/sipper/transaction/Nist_sm.rb +356 -0
- data/sipper/transaction/invite_client_transaction.rb +274 -0
- data/sipper/transaction/invite_server_transaction.rb +319 -0
- data/sipper/transaction/non_invite_client_transaction.rb +230 -0
- data/sipper/transaction/non_invite_server_transaction.rb +263 -0
- data/sipper/transaction/state_machine_wrapper.rb +58 -0
- data/sipper/transaction/transaction.rb +212 -0
- data/sipper/transport/base_transport.rb +84 -0
- data/sipper/transport/rel_unrel.rb +19 -0
- data/sipper/transport/transport_and_route_resolver.rb +67 -0
- data/sipper/transport/udp_transport.rb +156 -0
- data/sipper/transport_manager.rb +33 -0
- data/sipper/udp_session.rb +17 -0
- data/sipper/util/command_element.rb +62 -0
- data/sipper/util/compact_converter.rb +50 -0
- data/sipper/util/counter.rb +26 -0
- data/sipper/util/digest/digest_authorizer.rb +204 -0
- data/sipper/util/expectation_parser.rb +164 -0
- data/sipper/util/locator.rb +31 -0
- data/sipper/util/message_fill.rb +58 -0
- data/sipper/util/persistence/ps_sipper_map.rb +63 -0
- data/sipper/util/persistence/sipper_map.rb +41 -0
- data/sipper/util/sipper_util.rb +305 -0
- data/sipper/util/timer/sip_timer_helper.rb +26 -0
- data/sipper/util/timer/timer_manager.rb +80 -0
- data/sipper/util/timer/timer_task.rb +56 -0
- data/sipper/util/validations.rb +44 -0
- data/sipper/version.rb +10 -0
- data/sipper_test/_test_media_uas.rb +79 -0
- data/sipper_test/base_test_case.rb +31 -0
- data/sipper_test/c_134.txt +7 -0
- data/sipper_test/driven_sip_test_case.rb +96 -0
- data/sipper_test/gold.txt +10 -0
- data/sipper_test/gold_res.txt +8 -0
- data/sipper_test/gold_sub.txt +9 -0
- data/sipper_test/hello_sipper.au +0 -0
- data/sipper_test/in_sipper.au +0 -0
- data/sipper_test/nonrr_proxy.rb +17 -0
- data/sipper_test/order_tests.yaml +4 -0
- data/sipper_test/rake_res.txt +399 -0
- data/sipper_test/rr_proxy.rb +17 -0
- data/sipper_test/run_test.cmd +94 -0
- data/sipper_test/sip_test_case.rb +104 -0
- data/sipper_test/test2xx_retransmission.rb +80 -0
- data/sipper_test/test2xx_retransmission_with_limit.rb +81 -0
- data/sipper_test/test2xx_retransmission_with_nist.rb +91 -0
- data/sipper_test/test2xx_retransmission_with_txns.rb +94 -0
- data/sipper_test/test_address_header.rb +66 -0
- data/sipper_test/test_b2bua1.rb +130 -0
- data/sipper_test/test_b2bua2.rb +120 -0
- data/sipper_test/test_b2bua3.rb +160 -0
- data/sipper_test/test_b2bua4.rb +130 -0
- data/sipper_test/test_base_controller.rb +110 -0
- data/sipper_test/test_base_transport.rb +37 -0
- data/sipper_test/test_cancel.rb +21 -0
- data/sipper_test/test_cancel_after2xx.rb +81 -0
- data/sipper_test/test_cancel_retransmission.rb +105 -0
- data/sipper_test/test_cancel_with481.rb +83 -0
- data/sipper_test/test_cancel_with487.rb +70 -0
- data/sipper_test/test_cancel_with_ist_without_nist.rb +99 -0
- data/sipper_test/test_cancel_with_nist.rb +84 -0
- data/sipper_test/test_cancel_without487.rb +77 -0
- data/sipper_test/test_command_element.rb +38 -0
- data/sipper_test/test_compact_converter.rb +33 -0
- data/sipper_test/test_controller_class_loader.rb +37 -0
- data/sipper_test/test_controller_selector.rb +53 -0
- data/sipper_test/test_controller_using_compact_headers.rb +74 -0
- data/sipper_test/test_controller_using_header_order.rb +85 -0
- data/sipper_test/test_controller_using_ict.rb +64 -0
- data/sipper_test/test_controller_using_ict_with_non_success.rb +72 -0
- data/sipper_test/test_controller_using_ict_with_tcbh.rb +24 -0
- data/sipper_test/test_controller_using_ict_with_tcbh_no_action.rb +24 -0
- data/sipper_test/test_controller_using_ist.rb +70 -0
- data/sipper_test/test_controller_using_ist_with_tcbh.rb +24 -0
- data/sipper_test/test_controller_using_nict.rb +115 -0
- data/sipper_test/test_controller_using_nict_with_tcbh.rb +24 -0
- data/sipper_test/test_controller_using_nist.rb +63 -0
- data/sipper_test/test_controller_using_pre_existing_rs0.rb +73 -0
- data/sipper_test/test_controller_using_pre_existing_rs1.rb +75 -0
- data/sipper_test/test_controller_using_pre_existing_rs2.rb +71 -0
- data/sipper_test/test_controller_using_route_set.rb +86 -0
- data/sipper_test/test_controller_with_sdp.rb +80 -0
- data/sipper_test/test_controllers/cancel/order.yaml +2 -0
- data/sipper_test/test_controllers/cancel/uac_cancel_controller.rb +35 -0
- data/sipper_test/test_controllers/cancel/uas_cancel_controller.rb +25 -0
- data/sipper_test/test_controllers/class_loading/ordered/first_ordered_controller.rb +5 -0
- data/sipper_test/test_controllers/class_loading/ordered/order.yaml +3 -0
- data/sipper_test/test_controllers/class_loading/ordered/recond_ordered_controller.rb +6 -0
- data/sipper_test/test_controllers/class_loading/ordered/second_ordered_controller.rb +6 -0
- data/sipper_test/test_controllers/class_loading/unordered/first_unordered_controller.rb +7 -0
- data/sipper_test/test_controllers/class_loading/unordered/second_unordered_controller.rb +6 -0
- data/sipper_test/test_controllers/ctrl_trhandler/lib/transport_filters/my_transport_handler.rb +22 -0
- data/sipper_test/test_controllers/ctrl_trhandler/uac_tr_handler_controller.rb +27 -0
- data/sipper_test/test_controllers/ctrl_trhandler/uas_tr_handler_controller.rb +21 -0
- data/sipper_test/test_controllers/ete/order.yaml +1 -0
- data/sipper_test/test_controllers/ete/uac_controller.rb +39 -0
- data/sipper_test/test_controllers/ete/uas_controller.rb +34 -0
- data/sipper_test/test_controllers/extensions/extension_uac_controller.rb +24 -0
- data/sipper_test/test_controllers/extensions/extension_uas_controller.rb +35 -0
- data/sipper_test/test_controllers/extensions/lib/sipper_extensions/my_from_extension.rb +16 -0
- data/sipper_test/test_controllers/ict_tcbh/lib/transaction_handlers/app_ict_handler.rb +23 -0
- data/sipper_test/test_controllers/ict_tcbh/uac_ict_tcbh_controller.rb +27 -0
- data/sipper_test/test_controllers/ict_tcbh/uac_ict_tcbh_no_action_controller.rb +28 -0
- data/sipper_test/test_controllers/ict_tcbh/uas_ict_tcbh_controller.rb +29 -0
- data/sipper_test/test_controllers/ict_tcbh/uas_ict_tcbh_no_action_controller.rb +31 -0
- data/sipper_test/test_controllers/ist_tcbh/lib/app_ist_handler.rb +13 -0
- data/sipper_test/test_controllers/ist_tcbh/uac_ist_tcbh_controller.rb +22 -0
- data/sipper_test/test_controllers/ist_tcbh/uas_ist_tcbh_controller.rb +31 -0
- data/sipper_test/test_controllers/multi_trhandlers/lib/transport_filters/in_order.yaml +2 -0
- data/sipper_test/test_controllers/multi_trhandlers/lib/transport_filters/my_transport_handler1.rb +21 -0
- data/sipper_test/test_controllers/multi_trhandlers/lib/transport_filters/my_transport_handler2.rb +21 -0
- data/sipper_test/test_controllers/multi_trhandlers/lib/transport_filters/out_order.yaml +2 -0
- data/sipper_test/test_controllers/multi_trhandlers/uac_multi_tr_handler_controller.rb +27 -0
- data/sipper_test/test_controllers/multi_trhandlers/uas_multi_tr_handler_controller.rb +29 -0
- data/sipper_test/test_controllers/multiple/lib/blank_test.rb +2 -0
- data/sipper_test/test_controllers/multiple/uac_info_controller.rb +21 -0
- data/sipper_test/test_controllers/multiple/uac_msg_controller.rb +20 -0
- data/sipper_test/test_controllers/multiple/uas_info_controller.rb +15 -0
- data/sipper_test/test_controllers/multiple/uas_msg_controller.rb +14 -0
- data/sipper_test/test_controllers/nict_tcbh/lib/transaction_handlers/app_nict_handler.rb +13 -0
- data/sipper_test/test_controllers/nict_tcbh/uac_nict_tcbh_controller.rb +26 -0
- data/sipper_test/test_controllers/nict_tcbh/uas_nict_tcbh_controller.rb +20 -0
- data/sipper_test/test_controllers/state_machine_based/lib/CreditControl.sm +43 -0
- data/sipper_test/test_controllers/state_machine_based/lib/CreditControl_sm.rb +194 -0
- data/sipper_test/test_controllers/state_machine_based/order.yaml +1 -0
- data/sipper_test/test_controllers/state_machine_based/uac_message_controller.rb +34 -0
- data/sipper_test/test_controllers/state_machine_based/uas_message_controller.rb +44 -0
- data/sipper_test/test_controllers/stray_message/lib/sipper_extensions/my_stray_handler.rb +9 -0
- data/sipper_test/test_controllers/stray_message/stray_uac_controller.rb +24 -0
- data/sipper_test/test_controllers/stray_message/stray_uas_controller.rb +31 -0
- data/sipper_test/test_controllers/string/order.yaml +1 -0
- data/sipper_test/test_controllers/string/uac_controller.rb +29 -0
- data/sipper_test/test_controllers/string/uas_controller.rb +22 -0
- data/sipper_test/test_controllers/test_controller.rb +24 -0
- data/sipper_test/test_detached_session1.rb +78 -0
- data/sipper_test/test_dialog_routes.rb +139 -0
- data/sipper_test/test_digest_challenge1.rb +89 -0
- data/sipper_test/test_digest_challenge2.rb +90 -0
- data/sipper_test/test_dynamic_parse.rb +249 -0
- data/sipper_test/test_empty_sdp.rb +89 -0
- data/sipper_test/test_ete.rb +37 -0
- data/sipper_test/test_expectation_parser.rb +76 -0
- data/sipper_test/test_extensions.rb +28 -0
- data/sipper_test/test_freeze.rb +81 -0
- data/sipper_test/test_generated.rb +90 -0
- data/sipper_test/test_header_parameters.rb +75 -0
- data/sipper_test/test_header_parse.rb +141 -0
- data/sipper_test/test_http_client1.rb +84 -0
- data/sipper_test/test_http_client2.rb +90 -0
- data/sipper_test/test_ict_with_timeout.rb +62 -0
- data/sipper_test/test_in_dialog_request.rb +61 -0
- data/sipper_test/test_inline_controller.rb +67 -0
- data/sipper_test/test_invite_client_transaction.rb +272 -0
- data/sipper_test/test_invite_replace.rb +147 -0
- data/sipper_test/test_invite_retransmission.rb +90 -0
- data/sipper_test/test_invite_server_transaction.rb +208 -0
- data/sipper_test/test_lower_cseq.rb +79 -0
- data/sipper_test/test_media.rb +52 -0
- data/sipper_test/test_message.rb +392 -0
- data/sipper_test/test_method_specific_response_handling.rb +81 -0
- data/sipper_test/test_multi_homed1.rb +127 -0
- data/sipper_test/test_multi_homed2.rb +109 -0
- data/sipper_test/test_multi_homed_duplicate.rb +87 -0
- data/sipper_test/test_multi_homed_with_detached.rb +88 -0
- data/sipper_test/test_multiple.rb +49 -0
- data/sipper_test/test_multiple_contacts.rb +89 -0
- data/sipper_test/test_non2xx_retransmission_with_ist.rb +87 -0
- data/sipper_test/test_non_invite_client_transaction.rb +210 -0
- data/sipper_test/test_non_invite_retransmission.rb +91 -0
- data/sipper_test/test_non_invite_server_transaction.rb +202 -0
- data/sipper_test/test_offer_answer_prack.rb +103 -0
- data/sipper_test/test_pickup.rb +258 -0
- data/sipper_test/test_prack.rb +105 -0
- data/sipper_test/test_prack_store_and_dispatch.rb +101 -0
- data/sipper_test/test_prack_timer.rb +105 -0
- data/sipper_test/test_ps_sipper_map.rb +56 -0
- data/sipper_test/test_re_invite_uas_ongoing_ict.rb +81 -0
- data/sipper_test/test_re_invite_uas_ongoing_ist.rb +84 -0
- data/sipper_test/test_re_invite_with_ongoing_ict.rb +101 -0
- data/sipper_test/test_re_invite_with_ongoing_ist.rb +89 -0
- data/sipper_test/test_recorder_swap.rb +157 -0
- data/sipper_test/test_refer.rb +121 -0
- data/sipper_test/test_registeration_clearing.rb +97 -0
- data/sipper_test/test_registrar.rb +109 -0
- data/sipper_test/test_registration_controller.rb +73 -0
- data/sipper_test/test_registration_timeout.rb +90 -0
- data/sipper_test/test_remote_controller.rb +39 -0
- data/sipper_test/test_remote_target_update_on2xx.rb +140 -0
- data/sipper_test/test_request.rb +106 -0
- data/sipper_test/test_response.rb +40 -0
- data/sipper_test/test_response_without_to_tag.rb +92 -0
- data/sipper_test/test_rport.rb +88 -0
- data/sipper_test/test_sdp.rb +91 -0
- data/sipper_test/test_sdp_parser.rb +145 -0
- data/sipper_test/test_session.rb +276 -0
- data/sipper_test/test_session_callback_handler.rb +68 -0
- data/sipper_test/test_session_lifetime.rb +66 -0
- data/sipper_test/test_session_manager.rb +141 -0
- data/sipper_test/test_session_recorder.rb +145 -0
- data/sipper_test/test_session_state_user_defined.rb +84 -0
- data/sipper_test/test_session_states.rb +91 -0
- data/sipper_test/test_simple_dialog_state.rb +86 -0
- data/sipper_test/test_simple_re_invite.rb +86 -0
- data/sipper_test/test_sip_uri.rb +234 -0
- data/sipper_test/test_sipper_util.rb +45 -0
- data/sipper_test/test_smc_controller.rb +17 -0
- data/sipper_test/test_smoke.rb +80 -0
- data/sipper_test/test_stray.rb +28 -0
- data/sipper_test/test_stray_inline.rb +89 -0
- data/sipper_test/test_stray_res_acked.rb +103 -0
- data/sipper_test/test_stray_respond.rb +104 -0
- data/sipper_test/test_stray_retry.rb +92 -0
- data/sipper_test/test_stray_retry_failure.rb +93 -0
- data/sipper_test/test_stray_retry_initial.rb +100 -0
- data/sipper_test/test_strict_router_with_angled.rb +84 -0
- data/sipper_test/test_string_record.rb +31 -0
- data/sipper_test/test_subscribe_notify.rb +141 -0
- data/sipper_test/test_subscribe_notify_client_timeout.rb +158 -0
- data/sipper_test/test_subscribe_notify_dialog.rb +146 -0
- data/sipper_test/test_subscribe_notify_expires.rb +155 -0
- data/sipper_test/test_subscribe_notify_multiple_subscription.rb +157 -0
- data/sipper_test/test_subscribe_notify_server_timeout.rb +144 -0
- data/sipper_test/test_subsequent_unsent.rb +88 -0
- data/sipper_test/test_target_refresh_proxy_detached.rb +128 -0
- data/sipper_test/test_target_refresh_proxy_udp.rb +130 -0
- data/sipper_test/test_target_refresh_rr_proxy_udp.rb +133 -0
- data/sipper_test/test_target_refresh_with_new_port.rb +158 -0
- data/sipper_test/test_target_refresh_with_proxy1.rb +113 -0
- data/sipper_test/test_target_refresh_with_rr_update_proxy.rb +145 -0
- data/sipper_test/test_target_refresh_with_update_proxy.rb +144 -0
- data/sipper_test/test_target_refresh_with_update_proxy2.rb +139 -0
- data/sipper_test/test_timer_manager.rb +71 -0
- data/sipper_test/test_timer_task.rb +60 -0
- data/sipper_test/test_transport_handler.rb +19 -0
- data/sipper_test/test_transport_multi_handler.rb +18 -0
- data/sipper_test/test_udp_transport.rb +119 -0
- data/sipper_test/test_unparsed.rb +70 -0
- data/sipper_test/test_validations.rb +69 -0
- data/sipper_test/test_with_rr_proxy.rb +111 -0
- data/sipper_test/testmediacontroller.rb +71 -0
- data/sipper_test/tracing.rb +23 -0
- data/sipper_test/transaction_test_helper.rb +176 -0
- data/sipper_test/transport_filters.rb +26 -0
- data/sipper_test/ts_sipper.rb +91 -0
- data/sipper_test/ts_smoke.rb +3 -0
- data/sipper_test/tt.cmd +20 -0
- metadata +455 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
|
|
2
|
+
=begin
|
|
3
|
+
|
|
4
|
+
RFC 4566
|
|
5
|
+
|
|
6
|
+
Session description
|
|
7
|
+
v= (protocol version)
|
|
8
|
+
o= (originator and session identifier)
|
|
9
|
+
s= (session name)
|
|
10
|
+
i=* (session information)
|
|
11
|
+
u=* (URI of description)
|
|
12
|
+
e=* (email address)
|
|
13
|
+
p=* (phone number)
|
|
14
|
+
c=* (connection information -- not required if included in
|
|
15
|
+
all media)
|
|
16
|
+
b=* (zero or more bandwidth information lines)
|
|
17
|
+
One or more time descriptions ("t=" and "r=" lines; see below)
|
|
18
|
+
z=* (time zone adjustments)
|
|
19
|
+
k=* (encryption key)
|
|
20
|
+
a=* (zero or more session attribute lines)
|
|
21
|
+
Zero or more media descriptions
|
|
22
|
+
|
|
23
|
+
Time description
|
|
24
|
+
t= (time the session is active)
|
|
25
|
+
r=* (zero or more repeat times)
|
|
26
|
+
|
|
27
|
+
Media description, if present
|
|
28
|
+
m= (media name and transport address)
|
|
29
|
+
i=* (media title)
|
|
30
|
+
c=* (connection information -- optional if included at
|
|
31
|
+
session level)
|
|
32
|
+
b=* (zero or more bandwidth information lines)
|
|
33
|
+
k=* (encryption key)
|
|
34
|
+
a=* (zero or more media attribute lines)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
Example -
|
|
39
|
+
v=0
|
|
40
|
+
o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5
|
|
41
|
+
s=SDP Seminar
|
|
42
|
+
i=A Seminar on the session description protocol
|
|
43
|
+
u=http://www.example.com/seminars/sdp.pdf
|
|
44
|
+
e=j.doe@example.com (Jane Doe)
|
|
45
|
+
c=IN IP4 224.2.17.12/127
|
|
46
|
+
t=2873397496 2873404696
|
|
47
|
+
a=recvonly
|
|
48
|
+
m=audio 49170 RTP/AVP 0
|
|
49
|
+
m=video 51372 RTP/AVP 99
|
|
50
|
+
a=rtpmap:99 h263-1998/90000
|
|
51
|
+
|
|
52
|
+
=end
|
|
53
|
+
|
|
54
|
+
require 'sdp/sdp'
|
|
55
|
+
|
|
56
|
+
module SDP
|
|
57
|
+
|
|
58
|
+
class SdpParser
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def self._copy_default_from_session(session, h)
|
|
62
|
+
if session[:c]
|
|
63
|
+
h[:c] = session[:c] unless h[:c]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
sessionStatus = nil
|
|
67
|
+
|
|
68
|
+
if session[:a]
|
|
69
|
+
session[:a].split("||").each do |val|
|
|
70
|
+
sessionStatus = "inactive" if val == "inactive"
|
|
71
|
+
sessionStatus = "sendrecv" if val == "sendrecv"
|
|
72
|
+
sessionStatus = "recvonly" if val == "recvonly"
|
|
73
|
+
sessionStatus = "sendonly" if val == "sendonly"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
mediaStatus = nil
|
|
78
|
+
|
|
79
|
+
if h[:a]
|
|
80
|
+
h[:a].split("||").each do |val|
|
|
81
|
+
mediaStatus = "inactive" if val == "inactive"
|
|
82
|
+
mediaStatus = "sendrecv" if val == "sendrecv"
|
|
83
|
+
mediaStatus = "recvonly" if val == "recvonly"
|
|
84
|
+
mediaStatus = "sendonly" if val == "sendonly"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
if (sessionStatus != nil) && (mediaStatus == nil)
|
|
89
|
+
unless h[:a]
|
|
90
|
+
h[:a] = sessionStatus
|
|
91
|
+
else
|
|
92
|
+
h[:a] << "||" << sessionStatus
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def self.parse(arr, addDefault = false)
|
|
98
|
+
parsing_session = true
|
|
99
|
+
h = {}
|
|
100
|
+
sdp = SDP::Sdp.new
|
|
101
|
+
arr.each do |line|
|
|
102
|
+
line.strip!
|
|
103
|
+
next unless line =~ /=/
|
|
104
|
+
n, v = line.split("=", 2)
|
|
105
|
+
if n == "m"
|
|
106
|
+
if parsing_session
|
|
107
|
+
sdp.session_lines = h
|
|
108
|
+
h = {}
|
|
109
|
+
parsing_session = false
|
|
110
|
+
else
|
|
111
|
+
if addDefault
|
|
112
|
+
_copy_default_from_session(sdp.session_lines, h)
|
|
113
|
+
end
|
|
114
|
+
sdp.add_media_lines(h)
|
|
115
|
+
h = {}
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
if h[n.to_sym].nil?
|
|
119
|
+
h[n.to_sym] = v
|
|
120
|
+
else
|
|
121
|
+
h[n.to_sym] << "||" << v
|
|
122
|
+
end
|
|
123
|
+
end # each
|
|
124
|
+
if parsing_session
|
|
125
|
+
sdp.session_lines = h
|
|
126
|
+
else
|
|
127
|
+
if addDefault
|
|
128
|
+
_copy_default_from_session(sdp.session_lines, h)
|
|
129
|
+
end
|
|
130
|
+
sdp.add_media_lines(h)
|
|
131
|
+
end
|
|
132
|
+
return sdp
|
|
133
|
+
end
|
|
134
|
+
end # class
|
|
135
|
+
|
|
136
|
+
end
|
data/sipper/session.rb
ADDED
|
@@ -0,0 +1,1952 @@
|
|
|
1
|
+
require 'transport/udp_transport'
|
|
2
|
+
require 'transport/transport_and_route_resolver'
|
|
3
|
+
require 'util/message_fill'
|
|
4
|
+
require 'sip_logger'
|
|
5
|
+
require 'util/sipper_util'
|
|
6
|
+
require 'util/locator'
|
|
7
|
+
require 'util/validations'
|
|
8
|
+
require 'util/digest/digest_authorizer'
|
|
9
|
+
require 'sipper_configurator'
|
|
10
|
+
require 'response'
|
|
11
|
+
require 'request'
|
|
12
|
+
require 'session_manager'
|
|
13
|
+
require 'monitor'
|
|
14
|
+
require 'session_recorder'
|
|
15
|
+
require 'transaction/transaction'
|
|
16
|
+
require 'transaction/invite_client_transaction'
|
|
17
|
+
require 'transaction/invite_server_transaction'
|
|
18
|
+
require 'transaction/non_invite_client_transaction'
|
|
19
|
+
require 'transaction/non_invite_server_transaction'
|
|
20
|
+
require 'test_completion_signaling_helper'
|
|
21
|
+
require 'ostruct'
|
|
22
|
+
require 'rubygems'
|
|
23
|
+
gem 'facets', '= 1.8.54'
|
|
24
|
+
require 'facets/more/synchash'
|
|
25
|
+
require 'ruby_ext/snapshot'
|
|
26
|
+
require 'session_state/dialog_routes'
|
|
27
|
+
require 'b2bua_session_mixin'
|
|
28
|
+
require 'media/sipper_media_event'
|
|
29
|
+
require 'media/sipper_media_client'
|
|
30
|
+
require 'media/sipper_offer_answer'
|
|
31
|
+
require 'sdp/sdp'
|
|
32
|
+
require 'sdp/sdp_parser'
|
|
33
|
+
require 'sdp/sdp_generator'
|
|
34
|
+
require 'custom_message'
|
|
35
|
+
require 'registration'
|
|
36
|
+
|
|
37
|
+
class Session
|
|
38
|
+
include SipLogger
|
|
39
|
+
include SipperUtil
|
|
40
|
+
include SIP::Validations
|
|
41
|
+
include B2buaSessionMixin
|
|
42
|
+
|
|
43
|
+
attr_accessor :transport, :local_tag, :remote_tag, :prack_seq, :local_cseq, :remote_cseq,
|
|
44
|
+
:call_id, :our_contact, :max_fwd, :local_uri,
|
|
45
|
+
:remote_uri, :rip, :rp, :tp_flags, :imessage, :irequest, :iresponse, :imedia_event,
|
|
46
|
+
:session_map, :session_key, :half_dialog_key, :controller, :use_transactions, :use_ict,
|
|
47
|
+
:use_nict, :use_ist, :use_nist, :session_timer, :session_limit, :tmr_hash,
|
|
48
|
+
:use_2xx_retrans, :use_1xx_retrans, :t2xx_retrans_timers, :t1xx_retrans_timers,
|
|
49
|
+
:dialog_routes, :force_update_session_map, :reliable_1xx_status, :ihttp_response,
|
|
50
|
+
:subscriptionMap, :name, :offer_answer, :registrations
|
|
51
|
+
|
|
52
|
+
class SubscriptionData
|
|
53
|
+
attr_accessor :key, :timer, :source, :event, :event_id, :state, :method
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
snap_fields :@dialog_routes_snap, :@remote_tag, :@remote_uri, :@state
|
|
57
|
+
|
|
58
|
+
# todo check if there ever would be a case of receiving messages from
|
|
59
|
+
# different transports for the same session.
|
|
60
|
+
# Yes it is possible. UDP to TCP or TLS or back to UDP etc.
|
|
61
|
+
def initialize(*tipportrsl)
|
|
62
|
+
@local_tag = nil
|
|
63
|
+
@remote_tag = nil
|
|
64
|
+
#todo fix the local_tag and local_cseq story for R/R
|
|
65
|
+
@local_cseq = 0
|
|
66
|
+
@prack_seq = 10
|
|
67
|
+
@local_cseq_before_send = nil # this is set once when a request is created and reset on send
|
|
68
|
+
@remote_cseq = 0
|
|
69
|
+
@call_id = nil
|
|
70
|
+
@our_contact = nil
|
|
71
|
+
@max_fwd = 70
|
|
72
|
+
@local_uri = nil #"From" of out requests and To of out responses
|
|
73
|
+
@remote_uri = nil #"To" of out requests and From for out responses
|
|
74
|
+
@transport = tipportrsl[0]
|
|
75
|
+
@rip = tipportrsl[1]
|
|
76
|
+
@rp = tipportrsl[2]
|
|
77
|
+
@tp_flags = 0
|
|
78
|
+
@attr_hash = {}
|
|
79
|
+
@session_recorder = nil
|
|
80
|
+
@session_record = nil
|
|
81
|
+
@last_sent_request = nil
|
|
82
|
+
@last_sent_invite = nil
|
|
83
|
+
@pending_cancels = {} # keyed on txn key and value is cancel message
|
|
84
|
+
@cancellable_txns = []
|
|
85
|
+
@session_queue = []
|
|
86
|
+
@sq_lock = ["free"] #inuse or free, array because we want to keep the same object as we are mixing in a monitor
|
|
87
|
+
@sq_lock.extend(MonitorMixin)
|
|
88
|
+
# set the config for transaction usage
|
|
89
|
+
set_transaction_usage SipperConfigurator[:SessionTxnUsage]
|
|
90
|
+
@tmr_hash = {} # a hash of hash of transaction timer values for each type of Txns
|
|
91
|
+
SIP::Transaction::Transactions.each do |tname|
|
|
92
|
+
@tmr_hash[tname] = {}
|
|
93
|
+
end
|
|
94
|
+
@session_timer = SipperConfigurator[:SessionTimer]
|
|
95
|
+
@session_limit = SipperConfigurator[:SessionLimit]
|
|
96
|
+
@session_life_so_far = 0
|
|
97
|
+
@transactions = SyncHash.new # the total transcations of this session
|
|
98
|
+
@transaction_handlers = {} # the name of transaction handler classes, keyed with types.
|
|
99
|
+
@use_2xx_retrans = SipperConfigurator[:T2xxUsage] # boolean
|
|
100
|
+
@use_1xx_retrans = SipperConfigurator[:T1xxUsage] # boolean
|
|
101
|
+
@reliable_1xx_status = false
|
|
102
|
+
@t2xx_retrans_timers = {:Start=>SIP::Transaction::T1,
|
|
103
|
+
:Cap=>SIP::Transaction::T2,
|
|
104
|
+
:Limit=>(64*SIP::Transaction::T1)}
|
|
105
|
+
@t2xx_retrans_timers = @t2xx_retrans_timers.merge SipperConfigurator[:T2xxTimers] if SipperConfigurator[:T2xxTimers] # 3 timer settings for 2xx retransmission
|
|
106
|
+
|
|
107
|
+
@t1xx_retrans_timers = {:Start=>SIP::Transaction::T1,
|
|
108
|
+
:Limit=>(64*SIP::Transaction::T1)}
|
|
109
|
+
@t1xx_retrans_timers = @t1xx_retrans_timers.merge SipperConfigurator[:T1xxTimers] if SipperConfigurator[:T1xxTimers] # 2 timer settings for 1xx retransmission
|
|
110
|
+
|
|
111
|
+
@dialog_routes = DialogRoutes.new(tipportrsl[3])
|
|
112
|
+
@session_limit = tipportrsl[4] if tipportrsl[4]
|
|
113
|
+
@pending_response_queue = Queue.new
|
|
114
|
+
@subscriptionMap = {}
|
|
115
|
+
@state = ["initial"]
|
|
116
|
+
@user_defined_state = false
|
|
117
|
+
_schedule_timer_for_session(:session_limit, @session_limit)
|
|
118
|
+
@offer_answer = Media::SipperOfferAnswer.new(self)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def get_state_array
|
|
122
|
+
@state
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def last_state
|
|
126
|
+
@state[-1]
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def set_state(state)
|
|
130
|
+
@state = [state]
|
|
131
|
+
@user_defined_state = true
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def remote_target
|
|
135
|
+
@dialog_routes.get_ruri_and_routes[0]
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def remote_target=(rt)
|
|
139
|
+
@dialog_routes.remote_target = rt
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def [](k)
|
|
143
|
+
@attr_hash[k]
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def []=(k,v)
|
|
147
|
+
@attr_hash[k] = v
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _fixed_local_tag(ftag_from_msg=nil)
|
|
152
|
+
unless @local_tag
|
|
153
|
+
if ftag_from_msg && ftag_from_msg != "_PH_LCTG_"
|
|
154
|
+
@local_tag = ftag_from_msg
|
|
155
|
+
return @local_tag
|
|
156
|
+
end
|
|
157
|
+
unless @remote_tag
|
|
158
|
+
@local_tag = "2"
|
|
159
|
+
else
|
|
160
|
+
@local_tag = @remote_tag+"1"
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
@local_tag
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def send(msg)
|
|
167
|
+
# todo: as performance improvement set only when needed.
|
|
168
|
+
unless msg.class < Message
|
|
169
|
+
err_msg = "Message is not a SIP message #{msg}"
|
|
170
|
+
log_and_raise err_msg, ArgumentError
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# A final check to see if any route headers were pushed by the controller on the initial
|
|
174
|
+
# request and here we
|
|
175
|
+
# adjust the request uri and routes one last time only for initial requests.
|
|
176
|
+
if msg.class == Request && msg.initial
|
|
177
|
+
rrt = @dialog_routes.get_ruri_and_routes_for_pushed_routes(msg)
|
|
178
|
+
msg.uri = rrt[0] if rrt[0]
|
|
179
|
+
msg = _add_route_headers_if_present(msg, rrt)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
_check_transport_and_destination(msg)
|
|
183
|
+
unless @transport && @rip && @rp
|
|
184
|
+
err_msg = "Cannot send message, as transport is not properly set #{msg}"
|
|
185
|
+
log_and_raise err_msg, RuntimeError
|
|
186
|
+
end
|
|
187
|
+
case msg
|
|
188
|
+
when Request
|
|
189
|
+
send_request(msg)
|
|
190
|
+
when Response
|
|
191
|
+
send_response(msg)
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def send_request(msg)
|
|
197
|
+
if SipperConfigurator[:ProtocolCompliance] == 'strict'
|
|
198
|
+
msg.from.tag = '_PH_LCTG_' unless msg.from.tag
|
|
199
|
+
end
|
|
200
|
+
@local_uri, @remote_uri = begin
|
|
201
|
+
log_and_raise "Cannot send CANCEL as no response received so far" unless _check_cancel_state(msg) if (msg.method == "CANCEL" && SipperConfigurator[:ProtocolCompliance]=='strict')
|
|
202
|
+
logd("This is a request and is initial "+msg.initial.to_s)
|
|
203
|
+
if msg.initial
|
|
204
|
+
_increment_local_cseq
|
|
205
|
+
SipperUtil::MessageFill.fill(msg, :lcnts=>@local_cseq.to_s, :lctg=>_fixed_local_tag(msg.from.tag).to_s,
|
|
206
|
+
:lcntr=>@remote_cseq.to_s, :gcnt=>SipperUtil::Counter.instance.next.to_s, :rnd=>SipperUtil.trand,
|
|
207
|
+
:lip=>@transport.ip, :lp=>@transport.port.to_s,
|
|
208
|
+
:rip=>@rip, :rp=>@rp.to_s, :trans=>@transport.tid)
|
|
209
|
+
|
|
210
|
+
self.remote_target = msg.uri
|
|
211
|
+
end
|
|
212
|
+
if ((SipperConfigurator[:ProtocolCompliance]=='strict') &&
|
|
213
|
+
(@local_cseq_before_send) &&
|
|
214
|
+
(@local_cseq_before_send+1 != @local_cseq))
|
|
215
|
+
@local_cseq -= 1 # one step back
|
|
216
|
+
log_and_raise "The CSeq has increased by more than one, you may have created but not sent a request"
|
|
217
|
+
end
|
|
218
|
+
@max_fwd = msg.max_forwards.to_s.to_i
|
|
219
|
+
@last_sent_request = msg
|
|
220
|
+
if (msg.method == "INVITE")
|
|
221
|
+
@last_sent_invite = msg
|
|
222
|
+
end
|
|
223
|
+
[msg.from.to_s, msg.to.to_s]
|
|
224
|
+
end
|
|
225
|
+
@local_tag = msg.from.tag if msg.from.tag
|
|
226
|
+
@remote_tag = msg.to.tag if msg.to.tag
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
# todo this is where we will look for 1300 size and flip over the transports and modify Vias
|
|
230
|
+
# we will have to re-write our Via, so do a pop_via and a push_via and continue to send message
|
|
231
|
+
# in the transport layer we will once again look for filling up the values. Also keep the
|
|
232
|
+
# popped via such that if sending of message fails then we revert back to popped via.
|
|
233
|
+
# Same thing needs to be done for Contact and also Record-Route if proxy
|
|
234
|
+
#
|
|
235
|
+
if msg.method == "INVITE"
|
|
236
|
+
if self.use_ict
|
|
237
|
+
# check for existing transactions before sending re-invite
|
|
238
|
+
# 3261 14.1
|
|
239
|
+
# If there is an ongoing INVITE client transaction, the TU MUST wait until the
|
|
240
|
+
# transaction reaches the completed or terminated state before initiating the new INVITE.
|
|
241
|
+
# And
|
|
242
|
+
# If there is an ongoing INVITE server transaction, the TU MUST wait until the
|
|
243
|
+
# transaction reaches the confirmed or terminated state before initiating the new INVITE.
|
|
244
|
+
if !msg.initial && SipperConfigurator[:ProtocolCompliance]=='strict'
|
|
245
|
+
@transactions.values.each do |txn|
|
|
246
|
+
if txn.transaction_name == :Ict
|
|
247
|
+
unless ["IctMap.Completed", "IctMap.Terminated"].include? txn.state
|
|
248
|
+
rollback_to_unsent_state
|
|
249
|
+
log_and_raise "Another ICT #{txn} in progress, cannot send re-INVITE"
|
|
250
|
+
end
|
|
251
|
+
elsif txn.transaction_name == :Ist
|
|
252
|
+
unless ["IstMap.Confirmed", "IstMap.Terminated", "IstMap.Finished"].include? txn.state
|
|
253
|
+
rollback_to_unsent_state
|
|
254
|
+
log_and_raise "Another IST #{txn} in progress, cannot send re-INVITE"
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
branch = msg.via.branch
|
|
260
|
+
klass = @transaction_handlers[:Ict] || @transaction_handlers[:Base]
|
|
261
|
+
txn_handler = klass.new if klass
|
|
262
|
+
ict = SIP::Transaction::InviteClientTransaction.new(self, branch, txn_handler, transport, @tp_flags)
|
|
263
|
+
@tmr_hash[:Ict].each_pair {|k,v| ict.send("#{k}=".to_sym, v) } #check if sym required
|
|
264
|
+
msg.transaction = ict
|
|
265
|
+
@transactions[branch] = ict
|
|
266
|
+
end
|
|
267
|
+
elsif msg.method == "ACK"
|
|
268
|
+
# nothing special because ict txn is already terminated
|
|
269
|
+
else
|
|
270
|
+
if self.use_nict
|
|
271
|
+
branch = msg.via.branch
|
|
272
|
+
klass = @transaction_handlers[:Nict] || @transaction_handlers[:Base]
|
|
273
|
+
txn_handler = klass.new if klass
|
|
274
|
+
nict = SIP::Transaction::NonInviteClientTransaction.new(self, branch, txn_handler, transport, @tp_flags)
|
|
275
|
+
@tmr_hash[:Nict].each_pair {|k,v| nict.send("#{k}=".to_sym, v) } #check if sym required
|
|
276
|
+
msg.transaction = nict
|
|
277
|
+
if msg.method == "CANCEL"
|
|
278
|
+
ctx = @transactions[branch]
|
|
279
|
+
logd("Sending CANCEL found a Ctx #{ctx} for branch #{branch}")
|
|
280
|
+
if ctx
|
|
281
|
+
ctx.cancel_ctxn = nict # ict or even nict
|
|
282
|
+
else
|
|
283
|
+
@transactions[branch] = nict # in the unlikely event of no ctx
|
|
284
|
+
end
|
|
285
|
+
else
|
|
286
|
+
@transactions[branch] = nict
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
@local_cseq_before_send = nil unless msg.method == "ACK" || msg.method == "CANCEL"
|
|
291
|
+
@offer_answer.handle_outgoing_request(msg) if @offer_answer
|
|
292
|
+
_send_common(msg)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
# 13.3.1.4
|
|
297
|
+
# Once the response has been constructed, it is passed to the INVITE server transaction.
|
|
298
|
+
# Note, however, that the INVITE server transaction will be destroyed as soon as it
|
|
299
|
+
# receives this final response and passes it to the transport. Therefore, it is
|
|
300
|
+
# necessary to periodically pass the response directly to the transport until the ACK arrives.
|
|
301
|
+
# The 2xx response is passed to the transport with an interval that starts at T1 seconds
|
|
302
|
+
# and doubles for each retransmission until it reaches T2 seconds (T1 and T2 are defined
|
|
303
|
+
# in Section 17). Response retransmissions cease when an ACK request for the response
|
|
304
|
+
# is received. This is independent of whatever transport protocols are used to send
|
|
305
|
+
# the response .
|
|
306
|
+
# Since 2xx is retransmitted end-to-end, there may be hops between UAS and UAC that are UDP.
|
|
307
|
+
# To ensure reliable delivery across these hops , the response is retransmitted
|
|
308
|
+
# periodically even if the transport at the UAS is reliable.
|
|
309
|
+
# If the server retransmits the 2xx response for 64*T1 seconds without receiving an ACK,
|
|
310
|
+
# the dialog is confirmed, but the session SHOULD be terminated. This is accomplished
|
|
311
|
+
# with a BYE, as described in Section 15.
|
|
312
|
+
|
|
313
|
+
def send_response(msg, check_txn=true)
|
|
314
|
+
if (@reliable_1xx_status == true && msg.get_request_method == "INVITE")
|
|
315
|
+
logd("Adding response message to pending queue as Reliable provision is in progress.")
|
|
316
|
+
@pending_response_queue << msg
|
|
317
|
+
@pending_response_queue << check_txn
|
|
318
|
+
return
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
@offer_answer.handle_outgoing_response(msg) if @offer_answer
|
|
322
|
+
|
|
323
|
+
@local_uri, @remote_uri = begin
|
|
324
|
+
[msg.to.to_s, msg.from.to_s]
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
@local_tag = msg.to.tag if msg.to.tag
|
|
328
|
+
@remote_tag = msg.from.tag if msg.from.tag
|
|
329
|
+
|
|
330
|
+
if check_txn
|
|
331
|
+
txn = @transactions[msg.via.branch] if msg.via
|
|
332
|
+
if txn
|
|
333
|
+
if msg.get_request_method != "CANCEL"
|
|
334
|
+
logd("Found transaction for branch #{msg.via.branch} giving it the response")
|
|
335
|
+
msg.transaction = txn
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
_send_common(msg)
|
|
342
|
+
|
|
343
|
+
if ((SipperUtil::SUCC_RANGE.include?msg.code) && (msg.get_request_method=="INVITE") && @use_2xx_retrans)
|
|
344
|
+
@ok_to_retrans_2xx = true
|
|
345
|
+
@two_xx = OpenStruct.new
|
|
346
|
+
@two_xx.response = msg
|
|
347
|
+
@two_xx.tp_flags = @tp_flags
|
|
348
|
+
rd = get_response_destination(msg)
|
|
349
|
+
@two_xx.rip = rd[1]
|
|
350
|
+
@two_xx.rp = rd[2]
|
|
351
|
+
@current_t2xx = @t2xx_retrans_timers[:Start]
|
|
352
|
+
_schedule_timer_for_session(:t2xx_timer, @current_t2xx)
|
|
353
|
+
_schedule_timer_for_session(:t2xx_limit_timer, @t2xx_retrans_timers[:Limit])
|
|
354
|
+
logd("Started the 2xx retransmission timer for #{self}")
|
|
355
|
+
end
|
|
356
|
+
if ((SipperUtil::RPROV_RANGE.include?msg.code) && (msg.get_request_method=="INVITE") && msg.rseq)
|
|
357
|
+
@reliable_1xx_status = true
|
|
358
|
+
@ok_to_retrans_1xx = true
|
|
359
|
+
@one_xx = OpenStruct.new
|
|
360
|
+
@one_xx.response = msg
|
|
361
|
+
@one_xx.tp_flags = @tp_flags
|
|
362
|
+
rd = get_response_destination(msg)
|
|
363
|
+
@one_xx.rip = rd[1]
|
|
364
|
+
@one_xx.rp = rd[2]
|
|
365
|
+
@current_t1xx = @t1xx_retrans_timers[:Start]
|
|
366
|
+
@active_1xx_timer.cancel if @active_1xx_timer
|
|
367
|
+
|
|
368
|
+
if @use_1xx_retrans
|
|
369
|
+
@active_1xx_timer = _schedule_timer_for_session(:t1xx_timer, @current_t1xx)
|
|
370
|
+
end
|
|
371
|
+
_schedule_timer_for_session(:t1xx_limit_timer, @t1xx_retrans_timers[:Limit])
|
|
372
|
+
logd("Started the 1xx retransmission timer for #{self}")
|
|
373
|
+
@last_sent_reliable_response = msg
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
if (@reliable_1xx_status == true && msg.get_request_method == "PRACK")
|
|
377
|
+
unless @active_1xx_timer && !@active_1xx_timer.canceled?
|
|
378
|
+
@reliable_1xx_status = false
|
|
379
|
+
|
|
380
|
+
if(@pending_response_queue.length >= 2)
|
|
381
|
+
msgval = @pending_response_queue.pop
|
|
382
|
+
chkval = @pending_response_queue.pop
|
|
383
|
+
send_response(msgval, chkval)
|
|
384
|
+
end
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def _send_common(msg)
|
|
390
|
+
@call_id = msg.call_id.to_s unless @call_id
|
|
391
|
+
@our_contact = msg.contact.to_s if msg.contact
|
|
392
|
+
logd("In send_common @local_uri=#{@local_uri.to_s}, @remote_uri=#{@remote_uri.to_s}, @call_id=#{@call_id.to_s}")
|
|
393
|
+
raise StandardError if transport.nil?
|
|
394
|
+
# Now set the content length
|
|
395
|
+
msg.content_length = msg.content_len.to_s unless msg.respond_to?(:content_length) && msg.content_length && msg.content_length.respond_to?(:frozen_str) && msg.content_length.frozen_str
|
|
396
|
+
#logd("Sending msg #{msg}")
|
|
397
|
+
SessionManager.add_session self, ((msg.class == Response) && (SipperUtil::SUCC_RANGE.include?msg.code))
|
|
398
|
+
logd("Session map is #{@session_map}")
|
|
399
|
+
if @header_order_arr
|
|
400
|
+
msg.header_order = @header_order_arr
|
|
401
|
+
end
|
|
402
|
+
if @compact_header_arr
|
|
403
|
+
msg.compact_headers = @compact_header_arr
|
|
404
|
+
end
|
|
405
|
+
# now add a custom header to trace back message to session
|
|
406
|
+
|
|
407
|
+
msg.p_sipper_session = (self.name || self.to_s) if SipperConfigurator[:ShowSessionIdInMessages]
|
|
408
|
+
if msg.transaction
|
|
409
|
+
msg.transaction.txn_send msg
|
|
410
|
+
m_s = msg.transaction.msg_sent
|
|
411
|
+
else
|
|
412
|
+
if msg.is_request?
|
|
413
|
+
rd = get_request_destination()
|
|
414
|
+
elsif msg.is_response?
|
|
415
|
+
rd = get_response_destination(msg)
|
|
416
|
+
end
|
|
417
|
+
m_s = transport.send(msg, @tp_flags, rd[1], rd[2])
|
|
418
|
+
logd("Now record the outgoing message from session")
|
|
419
|
+
_do_record_sip("out", msg, m_s)
|
|
420
|
+
end
|
|
421
|
+
msg
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
#-- callbacks from Ict into TU (TU callbacks)
|
|
425
|
+
def transaction_transport_err(txn)
|
|
426
|
+
# todo simulate a 503 response
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
#++
|
|
430
|
+
|
|
431
|
+
# The timeout happens for IST on TimerH(completed state) and TimerI(confirmed state) expiry
|
|
432
|
+
# For ICT it happens for TimerB expiry when 408 is to be simulated
|
|
433
|
+
#
|
|
434
|
+
# NICT does not send 408 as per RFC 4320
|
|
435
|
+
# 4.2. Action 2
|
|
436
|
+
# A transaction-stateful SIP element MUST NOT send a response with
|
|
437
|
+
# Status-Code of 408 to a non-INVITE request. As a consequence, an
|
|
438
|
+
# element that cannot respond before the transaction expires will not
|
|
439
|
+
# send a final response at all.
|
|
440
|
+
# But this is a notification from the transaction to the TU and onwards to
|
|
441
|
+
# the controller.
|
|
442
|
+
def transaction_timeout(txn)
|
|
443
|
+
if (txn.transaction_name == :Ict || txn.transaction_name == :Nict)
|
|
444
|
+
req = txn.message
|
|
445
|
+
r = Response.create(408, "Transaction Timeout")
|
|
446
|
+
r.local = true
|
|
447
|
+
logd("In timeout, response now is #{r} and calling copy with request #{req}")
|
|
448
|
+
r.copy_from(req, :call_id, :cseq, :via, :to, :from) unless req.nil?
|
|
449
|
+
# note this thread is actually worker thread and not timer thread as
|
|
450
|
+
# we had queued this timer on the transport queue.
|
|
451
|
+
logd("Now sending 408 response locally for consumption to #{self}")
|
|
452
|
+
self.on_message(r)
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
def transaction_cleanup(txn)
|
|
458
|
+
@transactions.delete txn.branch_id
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
def transaction_wrong_state(txn)
|
|
462
|
+
# todo simulate a 503 response
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
# The rip and rp are set by looking at the dialog state, remote target
|
|
466
|
+
# route headers etc. However, if it cannot be ascertained from the
|
|
467
|
+
# message (like name instead of IP/port in RURI or Route) then we default
|
|
468
|
+
# to the rp and rip provided at the time of session creation.
|
|
469
|
+
def get_request_destination
|
|
470
|
+
[@transport, @rip, @rp]
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
# From RFC 3581
|
|
475
|
+
# When a server attempts to send a response, it examines the topmost
|
|
476
|
+
# Via header field value of that response. If the "sent-protocol"
|
|
477
|
+
# component indicates an unreliable unicast transport protocol, such as
|
|
478
|
+
# UDP, and there is no "maddr" parameter, but there is both a
|
|
479
|
+
# "received" parameter and an "rport" parameter, the response MUST be
|
|
480
|
+
# sent to the IP address listed in the "received" parameter, and the
|
|
481
|
+
# port in the "rport" parameter. The response MUST be sent from the
|
|
482
|
+
# same address and port that the corresponding request was received on.
|
|
483
|
+
# This effectively adds a new processing step between bullets two and
|
|
484
|
+
# three in Section 18.2.2 of SIP 3261
|
|
485
|
+
def get_response_destination(res)
|
|
486
|
+
if res.via
|
|
487
|
+
rip = res.via.received
|
|
488
|
+
if res.via.has_param?(:rport) && res.via.transport.downcase == "udp"
|
|
489
|
+
rp = res.via.rport
|
|
490
|
+
else
|
|
491
|
+
if x=res.via.sent_by_port
|
|
492
|
+
rp = x
|
|
493
|
+
else
|
|
494
|
+
rp = "5060" # not @rp here
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
else
|
|
498
|
+
rip = @rip
|
|
499
|
+
rp = @rp
|
|
500
|
+
end
|
|
501
|
+
[@transport, rip, rp]
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
#-- TU callbacks
|
|
505
|
+
#++
|
|
506
|
+
|
|
507
|
+
# Create an initial request, initial request is one which does not have any existing dialog
|
|
508
|
+
# state in sipper. Specifically the new initial request will not have a to tag.
|
|
509
|
+
def create_initial_request(method, uri, *rest)
|
|
510
|
+
log_and_raise "Cannot send initial request as some signaling has happened" unless initial_state?
|
|
511
|
+
self.remote_target = uri
|
|
512
|
+
rrt = @dialog_routes.get_ruri_and_routes
|
|
513
|
+
r = Request.create_initial(method, rrt[0], *rest)
|
|
514
|
+
r = _add_route_headers_if_present(r, rrt)
|
|
515
|
+
return r
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
def create_prack(response = @iresponse)
|
|
519
|
+
logd("Creating a prack request ")
|
|
520
|
+
_increment_local_cseq
|
|
521
|
+
h = {
|
|
522
|
+
:call_id => @call_id,
|
|
523
|
+
:from => @local_uri,
|
|
524
|
+
:to => @remote_uri,
|
|
525
|
+
:cseq => sprintf("%s PRACK", @local_cseq),
|
|
526
|
+
:via => sprintf("SIP/2.0/%s %s:%s;branch=z9hG4bK-%s-%s-%s",
|
|
527
|
+
@transport.tid, @transport.ip, @transport.port.to_s,
|
|
528
|
+
@local_cseq, @remote_cseq, SipperUtil::Counter.instance.next.to_s),
|
|
529
|
+
:contact => @our_contact,
|
|
530
|
+
:max_forwards => @max_fwd.to_s,
|
|
531
|
+
:rack => sprintf("%s %s", response.rseq, response.cseq)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
rrt = @dialog_routes.get_ruri_and_routes
|
|
535
|
+
r = Request.create_subsequent("PRACK", rrt[0], h)
|
|
536
|
+
r = _add_route_headers_if_present(r, rrt)
|
|
537
|
+
return r
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
def create_and_send_prack(response = @iresponse)
|
|
541
|
+
req = create_prack(response)
|
|
542
|
+
send(req)
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
# Create a new subsequent request. A subsequent request is created either within a dialog or for
|
|
546
|
+
# cases when a request is to be sent after some request was originally sent as an example a
|
|
547
|
+
# CANCEL request. However for generating a CANCEL use helper methods for it like create_cancel or
|
|
548
|
+
# create_and_send_cancel_when_ready
|
|
549
|
+
def create_subsequent_request(method, increment_cseq=true)
|
|
550
|
+
logd("Creating a subsequent request for #{method} and increment_cseq flag is #{increment_cseq}")
|
|
551
|
+
log_and_raise "As call_id is not set, it is likely that no initial req was sent or recvd" unless @call_id
|
|
552
|
+
if increment_cseq
|
|
553
|
+
_increment_local_cseq
|
|
554
|
+
end
|
|
555
|
+
if (method == "ACK")
|
|
556
|
+
h = {
|
|
557
|
+
:call_id => @call_id,
|
|
558
|
+
:from => @local_uri,
|
|
559
|
+
:to => @remote_uri,
|
|
560
|
+
:cseq => sprintf("%s %s", SipperUtil.cseq_number(@last_sent_invite.cseq), "ACK"),
|
|
561
|
+
:via => sprintf("SIP/2.0/%s %s:%s;branch=z9hG4bK-%s-%s-%s-%s",
|
|
562
|
+
@transport.tid, @transport.ip, @transport.port.to_s,
|
|
563
|
+
@local_cseq, @remote_cseq, SipperUtil::Counter.instance.next.to_s,
|
|
564
|
+
SipperUtil.trand),
|
|
565
|
+
:contact => @our_contact,
|
|
566
|
+
:max_forwards => @max_fwd.to_s
|
|
567
|
+
}
|
|
568
|
+
else
|
|
569
|
+
h = {
|
|
570
|
+
:call_id => @call_id,
|
|
571
|
+
:from => @local_uri,
|
|
572
|
+
:to => @remote_uri,
|
|
573
|
+
:cseq => sprintf("%s %s", @local_cseq, method.upcase),
|
|
574
|
+
:via => sprintf("SIP/2.0/%s %s:%s;branch=z9hG4bK-%s-%s-%s-%s",
|
|
575
|
+
@transport.tid, @transport.ip, @transport.port.to_s,
|
|
576
|
+
@local_cseq, @remote_cseq, SipperUtil::Counter.instance.next.to_s,
|
|
577
|
+
SipperUtil.trand),
|
|
578
|
+
:contact => @our_contact,
|
|
579
|
+
:max_forwards => @max_fwd.to_s
|
|
580
|
+
}
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
rrt = @dialog_routes.get_ruri_and_routes
|
|
584
|
+
r = Request.create_subsequent(method, rrt[0], h)
|
|
585
|
+
r = _add_route_headers_if_present(r, rrt)
|
|
586
|
+
return r
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
# Challenge here is the header object of WWW-Authenticate or Proxy-Authenticate header.
|
|
590
|
+
def create_request_with_response_to_challenge(challenge, proxy_challenge, user, passwd, lsr = @last_sent_request)
|
|
591
|
+
new_req = lsr.dup
|
|
592
|
+
new_req.initial = false
|
|
593
|
+
_increment_local_cseq
|
|
594
|
+
new_req.cseq = sprintf("%s %s", @local_cseq, lsr.method.upcase)
|
|
595
|
+
new_req.via = sprintf("SIP/2.0/%s %s:%s;branch=z9hG4bK-%s-%s-%s",
|
|
596
|
+
@transport.tid, @transport.ip, @transport.port.to_s,
|
|
597
|
+
@local_cseq, @remote_cseq, SipperUtil::Counter.instance.next.to_s)
|
|
598
|
+
if proxy_challenge
|
|
599
|
+
new_req.proxy_authorization = (@da.nil? ? @da = SipperUtil::DigestAuthorizer.new : @da).create_authorization_header(challenge, proxy_challenge, user, passwd, lsr)
|
|
600
|
+
else
|
|
601
|
+
new_req.authorization = (@da.nil? ? @da = SipperUtil::DigestAuthorizer.new : @da).create_authorization_header(challenge, proxy_challenge, user, passwd, lsr)
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
new_req
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
def create_response(code, phrase="SELECT", req=irequest, reliability=false)
|
|
608
|
+
log_and_raise "There is no request, cannot create response" unless req
|
|
609
|
+
code = Integer(code) unless code.is_a? Numeric
|
|
610
|
+
if @local_uri
|
|
611
|
+
if code > 100
|
|
612
|
+
@local_uri << ";tag=" << _fixed_local_tag.to_s unless @local_uri =~ /;tag=/
|
|
613
|
+
end
|
|
614
|
+
end
|
|
615
|
+
logd("@local_uri is now #{@local_uri.to_s}")
|
|
616
|
+
r = Response.create(code, phrase)
|
|
617
|
+
logd("Response now is #{r} and calling copy with request #{req}")
|
|
618
|
+
r.copy_from(req, :call_id, :cseq, :via, :to, :from, :record_route) unless req.nil?
|
|
619
|
+
r.to = @local_uri if @local_uri
|
|
620
|
+
r.contact = (@our_contact ||= sprintf("<sip:%s:%s;transport=%s>",
|
|
621
|
+
@transport.ip, @transport.port.to_s, @transport.tid)) unless code == 100
|
|
622
|
+
|
|
623
|
+
if ((req.method == "SUBSCRIBE") && (code >= 200) && (code < 300))
|
|
624
|
+
r.copy_from(req, :expires) unless req.nil?
|
|
625
|
+
end
|
|
626
|
+
|
|
627
|
+
if (reliability && code > 100 && code < 200)
|
|
628
|
+
r.require = "100rel"
|
|
629
|
+
r.rseq = sprintf("%d", @prack_seq)
|
|
630
|
+
@prack_seq = @prack_seq + 1
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
if req.method == "REGISTER" && @registrations
|
|
634
|
+
r.contact = nil
|
|
635
|
+
@registrations.each do |data|
|
|
636
|
+
r.add_contact(data.contact_uri)
|
|
637
|
+
r.contacts[-1].expires = data.expires
|
|
638
|
+
end
|
|
639
|
+
r.format_as_separate_headers_for_mv(:contact)
|
|
640
|
+
end
|
|
641
|
+
#logd("Response now is #{r}")
|
|
642
|
+
return r
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
# A simple operation to create and send the response to the request received
|
|
646
|
+
# todo automatically look up reason phrases
|
|
647
|
+
def respond_with(code, req=irequest, phrase=nil)
|
|
648
|
+
send(create_response(code, phrase, req))
|
|
649
|
+
end
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
def respond_reliably_with(code, req=irequest, phrase=nil)
|
|
653
|
+
raise ArgumentError, "Can only be used for provisional responses" if code < 101 || code > 200
|
|
654
|
+
send_response(create_response(code, phrase, req, true))
|
|
655
|
+
end
|
|
656
|
+
|
|
657
|
+
# A simple operation to create and send a request, initial or subsequent as the case
|
|
658
|
+
# may be.
|
|
659
|
+
def request_with(*args)
|
|
660
|
+
method = args[0].downcase
|
|
661
|
+
if(method == "cancel")
|
|
662
|
+
return create_and_send_cancel_when_ready
|
|
663
|
+
end
|
|
664
|
+
|
|
665
|
+
if(method == "ack")
|
|
666
|
+
return create_and_send_ack
|
|
667
|
+
end
|
|
668
|
+
|
|
669
|
+
if (initial_state?)
|
|
670
|
+
send(create_initial_request(*args))
|
|
671
|
+
else
|
|
672
|
+
send(create_subsequent_request(*args))
|
|
673
|
+
end
|
|
674
|
+
end
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
def initial_state?
|
|
678
|
+
@local_cseq+@remote_cseq == 0
|
|
679
|
+
end
|
|
680
|
+
|
|
681
|
+
# todo check for strict / lax operation waiting for prov. creating only for the INVITE
|
|
682
|
+
# request etc.
|
|
683
|
+
def create_cancel(lsr = @last_sent_invite)
|
|
684
|
+
log_and_raise "Cannot create CANCEL as no request was sent so far" unless lsr
|
|
685
|
+
rrt = @dialog_routes.get_ruri_and_routes
|
|
686
|
+
r = Request.create_subsequent("CANCEL", rrt[0], :cseq => sprintf("%s %s", SipperUtil.cseq_number(lsr.cseq), "CANCEL") )
|
|
687
|
+
r.copy_from(lsr, :call_id, :from, :to, :via, :contact, :max_forwards, :route)
|
|
688
|
+
r = _add_route_headers_if_present(r, rrt)
|
|
689
|
+
end
|
|
690
|
+
|
|
691
|
+
def create_and_send_cancel_when_ready(lsr = @last_sent_invite)
|
|
692
|
+
lsr = @last_sent_request unless lsr
|
|
693
|
+
c = create_cancel(lsr)
|
|
694
|
+
if _check_cancel_state(c)
|
|
695
|
+
send(c)
|
|
696
|
+
else
|
|
697
|
+
@pending_cancels[lsr.txn_id] = c
|
|
698
|
+
end
|
|
699
|
+
return c
|
|
700
|
+
end
|
|
701
|
+
|
|
702
|
+
# To be called by controller to generate an ACK for 2xx response.
|
|
703
|
+
#13.2.2.4
|
|
704
|
+
#The UAC core MUST generate an ACK request for each 2xx received from the transaction layer.
|
|
705
|
+
#The header fields of the ACK are constructed in the same way as for any request sent within
|
|
706
|
+
#a dialog (see Section 12) with the exception of the CSeq and the header fields related to authentication.
|
|
707
|
+
#The sequence number of the CSeq header field MUST be the same as the INVITE being acknowledged, but the CSeq
|
|
708
|
+
#method MUST be ACK. The ACK MUST contain the same credentials as the INVITE.
|
|
709
|
+
#If the 2xx contains an offer (based on the rules above), the ACK MUST
|
|
710
|
+
#carry an answer in its body. If the offer in the 2xx response is not acceptable, the
|
|
711
|
+
#UAC core MUST generate a valid answer in the ACK and then send a BYE immediately .
|
|
712
|
+
# Once the ACK has been constructed, the procedures of [4] are used to determine the destination address,
|
|
713
|
+
# port and transport. However, the request is passed to the transport layer directly for transmission,
|
|
714
|
+
# rather than a client transaction. This is because the UAC core handles retransmissions of the ACK,
|
|
715
|
+
# not the transaction layer. The ACK MUST be passed to the client transport every time a
|
|
716
|
+
# retransmission of the 2xx final response that triggered the ACK arrives.
|
|
717
|
+
|
|
718
|
+
def create_2xx_ack
|
|
719
|
+
#todo ACK must have the same credentials as INVITE.
|
|
720
|
+
ack = create_subsequent_request("ACK", false)
|
|
721
|
+
end
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
# Provided such that it can be used by controllers those are not using the transactions, otherwise this
|
|
725
|
+
# method is invoked from the ICT.
|
|
726
|
+
# 17.1.1.3
|
|
727
|
+
# The ACK request constructed by the client transaction MUST contain values for the Call-ID, From,
|
|
728
|
+
# and Request-URI that are equal to the values of those header fields in the request passed to the
|
|
729
|
+
# transport by the client transaction (call this the "original request" ). The To header field in the
|
|
730
|
+
# ACK MUST equal the To header field in the response being acknowledged, and therefore will usually
|
|
731
|
+
# differ from the To header field in the original request by the addition of the tag parameter.
|
|
732
|
+
# The ACK MUST contain a single Via header field, and this MUST be equal to the top Via header field
|
|
733
|
+
# of the original request. The CSeq header field in the ACK MUST contain the same value for the sequence
|
|
734
|
+
# number as was present in the original request, but the method parameter MUST be equal to "ACK".
|
|
735
|
+
# If the INVITE request whose response is being acknowledged had Route header fields, those header
|
|
736
|
+
# fields MUST appear in the ACK. This is to ensure that the ACK can be routed properly through
|
|
737
|
+
# any downstream stateless proxies.
|
|
738
|
+
|
|
739
|
+
def create_non_2xx_ack(invite=@last_sent_invite, response=@iresponse)
|
|
740
|
+
h = {
|
|
741
|
+
:to => response.to.to_s,
|
|
742
|
+
:cseq => sprintf("%s %s", SipperUtil.cseq_number(invite.cseq),"ACK")
|
|
743
|
+
}
|
|
744
|
+
ack = Request.create_subsequent("ACK", invite.uri, h)
|
|
745
|
+
ack.copy_from(invite, :call_id, :from, :via, :route, :contact, :max_forwards)
|
|
746
|
+
ack
|
|
747
|
+
end
|
|
748
|
+
|
|
749
|
+
# To be used only by the controllers on receipt of a response which will be used to figure out if
|
|
750
|
+
# the ACK is for 2xx or non-2xx. If it is required to generate an ACK to a response received previously
|
|
751
|
+
# (not the latest response)
|
|
752
|
+
# then create the ACK using the appropriate method (create_2xx_ack or create_non_2xx_ack) and then send
|
|
753
|
+
# it using session.send
|
|
754
|
+
#
|
|
755
|
+
def create_and_send_ack
|
|
756
|
+
send(create_ack)
|
|
757
|
+
end
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
def create_ack
|
|
761
|
+
if SipperUtil::SUCC_RANGE.include?@iresponse.code
|
|
762
|
+
a = create_2xx_ack
|
|
763
|
+
else
|
|
764
|
+
if use_ict
|
|
765
|
+
if SipperConfigurator[:ProtocolCompliance]=='strict'
|
|
766
|
+
log_and_raise "As InviteClientTransaction is in use, you MUST not send non-2xx ACK from here"
|
|
767
|
+
else
|
|
768
|
+
logw("As InviteClientTransaction is in use, you should not send non-2xx ACK from here")
|
|
769
|
+
end
|
|
770
|
+
end
|
|
771
|
+
a = create_non_2xx_ack
|
|
772
|
+
end
|
|
773
|
+
return a
|
|
774
|
+
end
|
|
775
|
+
|
|
776
|
+
|
|
777
|
+
# The media attributes set here shall be used in the next
|
|
778
|
+
# request or response sent from the controller.
|
|
779
|
+
# The argument is an attribute hash and can take the form
|
|
780
|
+
# set_media_attributes(:codec=>["G711U", "DTMF"] :type=>"SENDONLY", :play=>{:file=><file_name>})
|
|
781
|
+
# set_media_attributes can be called any number of times, as soon as
|
|
782
|
+
# ":codec" can be from of G711U, G711A or DTMF and will be in the form of an array and
|
|
783
|
+
# there may be more that one value for it. This can be added or removed at a later
|
|
784
|
+
# time with a new set_media_attributes() invocation. If a new codec is added
|
|
785
|
+
# then the earlier codec should still be listed in the array. Absense of codec
|
|
786
|
+
# from the list would mean that it is being withdrawn from the offers.
|
|
787
|
+
# ":type" can be one of SENDONLY or RECVONLY or SENDRECV
|
|
788
|
+
# ":play" can be hash of {file=> <file_to_play>, repeat=> true|false, duration=> <seconds>}
|
|
789
|
+
# ":play_spec" can be string "PLAY [file] [duration] | PLAY_REPEAT [file] [duration] | SLEEP [duration] | file"
|
|
790
|
+
# ":play_spec" can also be used to send out DTMF like "5,SLEEP 3,6,SLEEP 2,9"
|
|
791
|
+
# note if play_spec is present the play attribute will be ignored
|
|
792
|
+
# ":record_file" is the name of the file to record the incoming stream
|
|
793
|
+
# ":remote_m_line" the selected "m" line from the remote media stream. It can also be selected as
|
|
794
|
+
# the value "any" in which case the system selects the best option from the offer.
|
|
795
|
+
# ":remote_a_line" "a" line corresponding to the m line. This is an array, corresponding to
|
|
796
|
+
# the payloads.
|
|
797
|
+
# "remote_session_a_line" the "a" line (if any) from the session part of SDP.
|
|
798
|
+
# ":remote_c_line" the selected "c" line from session or media for the remote media stream
|
|
799
|
+
# This function can be called repeatedly, when you send out your offer and when to received
|
|
800
|
+
# an answer.
|
|
801
|
+
# As soon as media object has enough information the media is established. As an example you may have
|
|
802
|
+
# set attributes to set your prefered codec, type and what you want to play but do not have
|
|
803
|
+
# the remote information as you may have just sent out the offer. So initially you will set
|
|
804
|
+
# attributes with :codec, :type, :play. Then when you receive the 200 OK you may select a media line
|
|
805
|
+
# of your choice and call set_attributes with :remote_m_line and :remote_c_line.
|
|
806
|
+
# Alternatively you could have selected :remote_m_line as "any" upfront and on getting answer sipper
|
|
807
|
+
# could have selected the best answer from the given options.
|
|
808
|
+
def set_media_attributes(mattr)
|
|
809
|
+
play_spec = mattr[:play_spec]
|
|
810
|
+
dtmf_spec = mattr[:dtmf_spec]
|
|
811
|
+
rec_spec = mattr[:rec_spec]
|
|
812
|
+
|
|
813
|
+
play = mattr[:play]
|
|
814
|
+
unless play_spec
|
|
815
|
+
if play
|
|
816
|
+
play_spec = play[:repeat] ? "PLAY_REPEAT " : "PLAY "
|
|
817
|
+
play_spec << play[:file] if play[:file]
|
|
818
|
+
play_spec << " " << play[:duration] if play[:duration]
|
|
819
|
+
end
|
|
820
|
+
end
|
|
821
|
+
@offer_answer.setup_media_spec(play_spec, rec_spec, dtmf_spec) if @offer_answer
|
|
822
|
+
end
|
|
823
|
+
|
|
824
|
+
def update_dtmf_spec(mattr)
|
|
825
|
+
set_media_attributes(mattr)
|
|
826
|
+
end
|
|
827
|
+
|
|
828
|
+
def update_audio_spec(mattr)
|
|
829
|
+
set_media_attributes(mattr)
|
|
830
|
+
@offer_answer.refresh_sipper_media if @offer_answer
|
|
831
|
+
end
|
|
832
|
+
|
|
833
|
+
|
|
834
|
+
# todo write tests for on_message, on_request, on_response when doing IT
|
|
835
|
+
def on_message(r)
|
|
836
|
+
logd("session.on_message called for session #{self} and message #{r.short_to_s}")
|
|
837
|
+
@sq_lock.synchronize do
|
|
838
|
+
@session_queue << r
|
|
839
|
+
if @sq_lock[0] == "free"
|
|
840
|
+
@sq_lock[0] = "inuse"
|
|
841
|
+
else
|
|
842
|
+
return
|
|
843
|
+
end
|
|
844
|
+
end
|
|
845
|
+
msg = nil
|
|
846
|
+
loop do
|
|
847
|
+
logd("Looking for a new message from session level queue")
|
|
848
|
+
@sq_lock.synchronize do
|
|
849
|
+
msg = @session_queue.shift
|
|
850
|
+
unless msg
|
|
851
|
+
@sq_lock[0] = "free"
|
|
852
|
+
return
|
|
853
|
+
end
|
|
854
|
+
end
|
|
855
|
+
logd("In session.on_message now processing message #{msg}")
|
|
856
|
+
case msg
|
|
857
|
+
when Request
|
|
858
|
+
_on_request(msg)
|
|
859
|
+
when Response
|
|
860
|
+
_on_response(msg)
|
|
861
|
+
when SIP::TimerTask
|
|
862
|
+
_on_timer(msg) unless msg.canceled?
|
|
863
|
+
when Media::SipperMediaEvent
|
|
864
|
+
_on_media_event(msg)
|
|
865
|
+
when SipperHttp::SipperHttpResponse
|
|
866
|
+
_on_http_response(msg)
|
|
867
|
+
when CustomMessage
|
|
868
|
+
_on_custom_message(msg)
|
|
869
|
+
end
|
|
870
|
+
end
|
|
871
|
+
end
|
|
872
|
+
|
|
873
|
+
# invoked on incoming request
|
|
874
|
+
def _on_request(request)
|
|
875
|
+
_update_and_check_dialog_state_on_request(request)
|
|
876
|
+
@offer_answer.handle_incoming_request(request) if @offer_answer
|
|
877
|
+
|
|
878
|
+
# We forward the CANCEL to the transaction being canceled only after the
|
|
879
|
+
# NIST processing of the CANCEL is over. So typically a 200/CANCEL shall precede
|
|
880
|
+
# the 487/INVITE.
|
|
881
|
+
forward_cancel_to_stxn = false
|
|
882
|
+
nist = nil
|
|
883
|
+
stxn = nil
|
|
884
|
+
|
|
885
|
+
if request.method == "PRACK"
|
|
886
|
+
if request[:rack] && @last_sent_reliable_response && @last_sent_reliable_response[:rseq]
|
|
887
|
+
if request.rack.header_value.strip.split(' ')[0] == @last_sent_reliable_response.rseq.header_value.strip
|
|
888
|
+
@ok_to_retrans_1xx = false
|
|
889
|
+
@active_1xx_timer.cancel if @active_1xx_timer
|
|
890
|
+
else
|
|
891
|
+
logd("PRACK doesnt match the last sent reliable response.")
|
|
892
|
+
end
|
|
893
|
+
end
|
|
894
|
+
end
|
|
895
|
+
|
|
896
|
+
if request.method == "INVITE"
|
|
897
|
+
if self.use_ist
|
|
898
|
+
branch = request.via.branch
|
|
899
|
+
ist = @transactions[branch]
|
|
900
|
+
if ist
|
|
901
|
+
logd("Found transaction #{ist} for branch #{request.via.branch} for INVITE retransmission")
|
|
902
|
+
else
|
|
903
|
+
# one final check for equal CSeq since this is not a retransmission now and therefore
|
|
904
|
+
# must be a bad request.
|
|
905
|
+
_equal_cseq_check(request) unless request.attributes[:_sipper_rejection_response]
|
|
906
|
+
# check for existing invite transactions
|
|
907
|
+
_check_pending_invite_txns_on_invite_in(request)unless request.attributes[:_sipper_rejection_response]
|
|
908
|
+
logd("Not found transaction for branch #{request.via.branch} for INVITE, creating a new IST")
|
|
909
|
+
klass = @transaction_handlers[:Ist] || @transaction_handlers[:Base]
|
|
910
|
+
txn_handler = klass.new if klass
|
|
911
|
+
ist = SIP::Transaction::InviteServerTransaction.new(self, branch, txn_handler, transport, @tp_flags)
|
|
912
|
+
@tmr_hash[:Ist].each_pair {|k,v| ist.send("#{k}=".to_sym, v) }
|
|
913
|
+
@transactions[branch] = ist
|
|
914
|
+
end
|
|
915
|
+
request.transaction = ist
|
|
916
|
+
ist.txn_received(request)
|
|
917
|
+
unless ist.consume?
|
|
918
|
+
logd("Not consuming this request #{request.method} as txn has taken care of it.")
|
|
919
|
+
return
|
|
920
|
+
end
|
|
921
|
+
end # ist being used
|
|
922
|
+
elsif request.method == "ACK"
|
|
923
|
+
txn = @transactions[request.via.branch]
|
|
924
|
+
if txn
|
|
925
|
+
logd("Found transaction for branch #{request.via.branch} for ACK")
|
|
926
|
+
request.transaction = txn
|
|
927
|
+
txn.txn_received(request)
|
|
928
|
+
unless txn.consume?
|
|
929
|
+
logd("Not consuming this request #{request.method} as txn has taken care of it.")
|
|
930
|
+
return
|
|
931
|
+
end
|
|
932
|
+
else # no transaction found, this must be a ACK for 2xx as new branch
|
|
933
|
+
@ok_to_retrans_2xx = false
|
|
934
|
+
end
|
|
935
|
+
else # method other than INV/ACK
|
|
936
|
+
if self.use_nist
|
|
937
|
+
branch = request.via.branch
|
|
938
|
+
stxn = @transactions[branch]
|
|
939
|
+
if request.method == "CANCEL"
|
|
940
|
+
if stxn
|
|
941
|
+
nist = stxn.cancel_stxn
|
|
942
|
+
logd("Found CANCEL transaction #{nist} for branch #{request.via.branch} for CANCEL retransmission") if nist
|
|
943
|
+
end
|
|
944
|
+
else
|
|
945
|
+
nist = stxn
|
|
946
|
+
logd("Found NIST transaction #{nist} for branch #{request.via.branch} for non-INVITE retransmission") if nist
|
|
947
|
+
end
|
|
948
|
+
unless nist
|
|
949
|
+
# one final check for equal CSeq since this is not a retransmission now and therefore
|
|
950
|
+
# must be a bad request, unless it is a CANCEL.
|
|
951
|
+
unless request.method == "CANCEL"
|
|
952
|
+
_equal_cseq_check(request) unless request.attributes[:_sipper_rejection_response]
|
|
953
|
+
end
|
|
954
|
+
logd("Not found transaction for branch #{request.via.branch} for non-INVITE, creating a new NIST")
|
|
955
|
+
klass = @transaction_handlers[:Nist] || @transaction_handlers[:Base]
|
|
956
|
+
txn_handler = klass.new if klass
|
|
957
|
+
nist = SIP::Transaction::NonInviteServerTransaction.new(self, branch, txn_handler, transport, @tp_flags)
|
|
958
|
+
@tmr_hash[:Nist].each_pair {|k,v| nist.send("#{k}=".to_sym, v) }
|
|
959
|
+
end
|
|
960
|
+
request.transaction = nist
|
|
961
|
+
if request.method == "CANCEL"
|
|
962
|
+
logd("Received CANCEL found a Stx #{stxn} for branch #{branch}")
|
|
963
|
+
if stxn
|
|
964
|
+
nist.transaction_being_canceled = stxn if nist
|
|
965
|
+
forward_cancel_to_stxn = true
|
|
966
|
+
else
|
|
967
|
+
nist.ok_to_send_481_to_cancel = true if self.use_ist && self.use_nist
|
|
968
|
+
logd("Processing CANCEL, no stx found, check if 481 is to be sent")
|
|
969
|
+
end
|
|
970
|
+
else
|
|
971
|
+
@transactions[branch] = nist unless stxn
|
|
972
|
+
end
|
|
973
|
+
nist.txn_received(request)
|
|
974
|
+
unless nist.consume?
|
|
975
|
+
logd("Not consuming this request #{request.method} as txn has taken care of it.")
|
|
976
|
+
return
|
|
977
|
+
end
|
|
978
|
+
else # nist not being used, but still look for IST for CANCEL
|
|
979
|
+
if request.method == "CANCEL"
|
|
980
|
+
branch = request.via.branch
|
|
981
|
+
stxn = @transactions[branch] # should be IST
|
|
982
|
+
logd("Received CANCEL found a Stx #{stxn} for branch #{branch}")
|
|
983
|
+
if stxn
|
|
984
|
+
forward_cancel_to_stxn = true
|
|
985
|
+
else
|
|
986
|
+
logd("Processing CANCEL, no stx found, not using nist either")
|
|
987
|
+
end
|
|
988
|
+
end
|
|
989
|
+
end
|
|
990
|
+
end
|
|
991
|
+
|
|
992
|
+
# Check for the rejection response here
|
|
993
|
+
if request.attributes[:_sipper_rejection_response]
|
|
994
|
+
logi("Found the rejection response, sending it and not invoking controller")
|
|
995
|
+
send_response(request.attributes[:_sipper_rejection_response])
|
|
996
|
+
return
|
|
997
|
+
end
|
|
998
|
+
|
|
999
|
+
if request.method == "REGISTER"
|
|
1000
|
+
@registrations = _create_registration_object(request)
|
|
1001
|
+
end
|
|
1002
|
+
|
|
1003
|
+
if @controller
|
|
1004
|
+
logd("Dispatching request to controller #{@controller.name}")
|
|
1005
|
+
begin
|
|
1006
|
+
result = @controller.on_request(self)
|
|
1007
|
+
rescue Exception => e
|
|
1008
|
+
loge("Exception #{e} occured while request processing by controller")
|
|
1009
|
+
loge(e.backtrace.join("\n"))
|
|
1010
|
+
end
|
|
1011
|
+
logw("#{@controller} could not process the request") if result == false
|
|
1012
|
+
else
|
|
1013
|
+
loge("No controller associated with this session")
|
|
1014
|
+
end
|
|
1015
|
+
stxn.cancel_received(request, nist) if forward_cancel_to_stxn && stxn
|
|
1016
|
+
end
|
|
1017
|
+
|
|
1018
|
+
|
|
1019
|
+
def pre_set_dialog_id(r)
|
|
1020
|
+
@remote_tag = r.from_tag
|
|
1021
|
+
@call_id = r.call_id.to_s
|
|
1022
|
+
end
|
|
1023
|
+
|
|
1024
|
+
# invoked on incoming response
|
|
1025
|
+
def _on_response(response)
|
|
1026
|
+
if ((SipperConfigurator[:ProtocolCompliance]=='strict') && !(response.locally_generated?))
|
|
1027
|
+
# check 18.1.2
|
|
1028
|
+
# When a response is received, the client transport examines the top Via header field value.
|
|
1029
|
+
# If the value of the "sent-by" parameter in that header field value does not
|
|
1030
|
+
# correspond to a value that the client transport is configured to insert
|
|
1031
|
+
# into requests, the response MUST be silently discarded.
|
|
1032
|
+
if (transport.ip != response.via.sent_by_ip) || (transport.port.to_s != response.via.sent_by_port) && SipperConfigurator[:ProtocolCompliance] == 'strict'
|
|
1033
|
+
logw("Response via #{response.via} sent_by does not match our transport, dropping message")
|
|
1034
|
+
return
|
|
1035
|
+
end
|
|
1036
|
+
end
|
|
1037
|
+
_update_dialog_state_on_response(response)
|
|
1038
|
+
@offer_answer.handle_incoming_response(response) if @offer_answer
|
|
1039
|
+
txn = @transactions[response.via.branch]
|
|
1040
|
+
if txn && !(response.locally_generated?) && response.get_request_method == "CANCEL"
|
|
1041
|
+
txn = txn.cancel_ctxn || txn # for the case when NICT but not ICT in use
|
|
1042
|
+
end
|
|
1043
|
+
if (txn && !(response.locally_generated?))
|
|
1044
|
+
logd("Found transaction #{txn} for branch #{response.via.branch} giving it the response")
|
|
1045
|
+
response.transaction = txn
|
|
1046
|
+
txn.txn_received(response)
|
|
1047
|
+
unless txn.consume?
|
|
1048
|
+
logd("Not consuming this response #{response.code} as txn has taken care of it.")
|
|
1049
|
+
return
|
|
1050
|
+
end
|
|
1051
|
+
end
|
|
1052
|
+
|
|
1053
|
+
if response.get_request_method == "REGISTER"
|
|
1054
|
+
@registrations = _create_registration_object(response, false)
|
|
1055
|
+
end
|
|
1056
|
+
|
|
1057
|
+
if @controller
|
|
1058
|
+
begin
|
|
1059
|
+
@controller.on_response(self)
|
|
1060
|
+
rescue Exception => e
|
|
1061
|
+
loge("Exception #{e} occured while response processing by controller")
|
|
1062
|
+
loge(e.backtrace.join("\n"))
|
|
1063
|
+
end
|
|
1064
|
+
else
|
|
1065
|
+
loge("No controller associated with this session")
|
|
1066
|
+
end
|
|
1067
|
+
end
|
|
1068
|
+
|
|
1069
|
+
# On receipt of request or response
|
|
1070
|
+
def _on_common_sip(msg)
|
|
1071
|
+
if msg[:content_type] && msg.content_type.to_s =~ /sdp/
|
|
1072
|
+
msg.sdp = SDP::SdpParser.parse(msg.contents, true) if msg[:content]
|
|
1073
|
+
end
|
|
1074
|
+
@call_id = msg.call_id.to_s
|
|
1075
|
+
logd("Now record the incoming message")
|
|
1076
|
+
_do_record_sip("in", msg)
|
|
1077
|
+
end
|
|
1078
|
+
|
|
1079
|
+
#-------------------------------
|
|
1080
|
+
# Transcation handling setting
|
|
1081
|
+
|
|
1082
|
+
def use_ict
|
|
1083
|
+
return @use_ict unless @use_ict.nil?
|
|
1084
|
+
@use_transactions
|
|
1085
|
+
end
|
|
1086
|
+
|
|
1087
|
+
def use_nict
|
|
1088
|
+
return @use_nict unless @use_nict.nil?
|
|
1089
|
+
@use_transactions
|
|
1090
|
+
end
|
|
1091
|
+
|
|
1092
|
+
def use_ist
|
|
1093
|
+
return @use_ist unless @use_ist.nil?
|
|
1094
|
+
@use_transactions
|
|
1095
|
+
end
|
|
1096
|
+
|
|
1097
|
+
def use_nist
|
|
1098
|
+
return @use_nist unless @use_nist.nil?
|
|
1099
|
+
@use_transactions
|
|
1100
|
+
end
|
|
1101
|
+
|
|
1102
|
+
def set_transaction_usage(tr_hash)
|
|
1103
|
+
tr_hash.each_pair { |k,v| self.__send__((k.to_s+'=').to_sym, v) } if tr_hash
|
|
1104
|
+
end
|
|
1105
|
+
|
|
1106
|
+
# returns the consolidated hash of timers which can be set to the
|
|
1107
|
+
# transaction in question by calling SipperUtil.hash_to_iv(@timers, txn) if @timers
|
|
1108
|
+
# the type of transactions can be either the names of transactions as @transation_name
|
|
1109
|
+
# defined in the transaction or it can be :Base which is the override for all types of
|
|
1110
|
+
# transactions.
|
|
1111
|
+
def set_transaction_timers(type, tmr_hash)
|
|
1112
|
+
if tmr_hash
|
|
1113
|
+
if type == :Base
|
|
1114
|
+
@tmr_hash.each_key {|k| @tmr_hash[k]= tmr_hash }
|
|
1115
|
+
else
|
|
1116
|
+
@tmr_hash[type] = @tmr_hash[type].merge(tmr_hash)
|
|
1117
|
+
end
|
|
1118
|
+
end
|
|
1119
|
+
#@tmr_hash.each_key {|k| puts "#{k} = #{@tmr_hash[k]}" }
|
|
1120
|
+
end
|
|
1121
|
+
|
|
1122
|
+
# e.g. :Ict=>MyIctHandler, :Nict=>MyNictHandler, :Base=>CatchAllHandler
|
|
1123
|
+
def set_transaction_handlers(tx_hnd_hash)
|
|
1124
|
+
@transaction_handlers = tx_hnd_hash if tx_hnd_hash
|
|
1125
|
+
end
|
|
1126
|
+
|
|
1127
|
+
def set_session_timer(val)
|
|
1128
|
+
@session_timer = val if val
|
|
1129
|
+
end
|
|
1130
|
+
|
|
1131
|
+
def set_session_limit(val)
|
|
1132
|
+
@session_limit = val if val
|
|
1133
|
+
end
|
|
1134
|
+
|
|
1135
|
+
|
|
1136
|
+
def set_session_record(val)
|
|
1137
|
+
@session_record = val
|
|
1138
|
+
end
|
|
1139
|
+
|
|
1140
|
+
# boolean, whether the 2xx retransmission timer is to be used or not for
|
|
1141
|
+
# the UAS
|
|
1142
|
+
def set_t2xx_retrans_usage(val)
|
|
1143
|
+
@use_2xx_retrans = val
|
|
1144
|
+
end
|
|
1145
|
+
|
|
1146
|
+
def set_t1xx_retrans_usage(val)
|
|
1147
|
+
@use_1xx_retrans = val
|
|
1148
|
+
end
|
|
1149
|
+
|
|
1150
|
+
# the hash of 3 timer values that affect 2xx retransmissions for UAS
|
|
1151
|
+
# i.e {:Start=>100, :Cap=>400, :Limit=>1600} that roughly
|
|
1152
|
+
# correspond with T1, T2 and 64*T1 respectively, which are also the
|
|
1153
|
+
# defaults.
|
|
1154
|
+
def set_t2xx_retrans_timers(t_hash)
|
|
1155
|
+
if t_hash
|
|
1156
|
+
@t2xx_retrans_timers = @t2xx_retrans_timers.merge t_hash
|
|
1157
|
+
end
|
|
1158
|
+
end
|
|
1159
|
+
|
|
1160
|
+
def set_t1xx_retrans_timers(t_hash)
|
|
1161
|
+
if t_hash
|
|
1162
|
+
@t1xx_retrans_timers = @t1xx_retrans_timers.merge t_hash
|
|
1163
|
+
end
|
|
1164
|
+
end
|
|
1165
|
+
|
|
1166
|
+
# The partial or full list of headers in the order in which the message
|
|
1167
|
+
# is to be formatted. In the absense of this the header ordering is
|
|
1168
|
+
# arbitrary. e.g.
|
|
1169
|
+
# set_header_order([:from, :to, :call_id, :via])
|
|
1170
|
+
# shall format the headers in the order
|
|
1171
|
+
# ....
|
|
1172
|
+
# From: sut <sip:service@127.0.0.1:5060>;tag=azxs21
|
|
1173
|
+
# To: sipp <sip:sipp@127.0.0.1:6061>;tag=1
|
|
1174
|
+
# Call-Id: 6766_112@127.0.0.1
|
|
1175
|
+
# Via: SIP/2.0/UDP 127.0.0.1:6061;branch=z9hG4bK-2352-1-0
|
|
1176
|
+
# ....
|
|
1177
|
+
# once set this setting affects all the messages created by this session.
|
|
1178
|
+
def set_header_order(arr)
|
|
1179
|
+
@header_order_arr = arr
|
|
1180
|
+
end
|
|
1181
|
+
#------------------------------
|
|
1182
|
+
|
|
1183
|
+
# The headers which are required to be expressed in their compact form.
|
|
1184
|
+
# e.g.
|
|
1185
|
+
# set_compact_headers [:from, :via]
|
|
1186
|
+
# shall use the compact form for headers From and Via and the message shall
|
|
1187
|
+
# look like -
|
|
1188
|
+
# f: sut <sip:service@127.0.0.1:5060>;tag=azxs21
|
|
1189
|
+
# To: sipp <sip:sipp@127.0.0.1:6061>;tag=1
|
|
1190
|
+
# Call-Id: 6766_112@127.0.0.1
|
|
1191
|
+
# v: SIP/2.0/UDP 127.0.0.1:6061;branch=z9hG4bK-2352-1-0
|
|
1192
|
+
#
|
|
1193
|
+
# In case all possible headers are required in compact form then use
|
|
1194
|
+
# [:all_headers] as the argument value.
|
|
1195
|
+
# A complete list of headers with known compact form can be obtained
|
|
1196
|
+
# from - http://www.iana.org/assignments/sip-parameters
|
|
1197
|
+
def set_compact_headers(arr)
|
|
1198
|
+
@compact_header_arr = arr
|
|
1199
|
+
end
|
|
1200
|
+
|
|
1201
|
+
# public method called when the timer of this session target is fired
|
|
1202
|
+
def on_timer_expiration(task)
|
|
1203
|
+
# we treat the incoming timer task as a message to leverage
|
|
1204
|
+
# the session level queueing for synchronization.
|
|
1205
|
+
on_message(task)
|
|
1206
|
+
end
|
|
1207
|
+
|
|
1208
|
+
# this is internally called when the timer is acted upon
|
|
1209
|
+
def _on_timer(task)
|
|
1210
|
+
if @invalidated
|
|
1211
|
+
logi("This session #{self.session_key} is invalidated, not firing timer #{task}")
|
|
1212
|
+
return
|
|
1213
|
+
end
|
|
1214
|
+
logd("Timer task #{task} invoked")
|
|
1215
|
+
if task.type == :app
|
|
1216
|
+
begin
|
|
1217
|
+
@controller.on_timer(self, task) if @controller
|
|
1218
|
+
rescue Exception => e
|
|
1219
|
+
loge("Exception #{e} occured while timer processing by controller")
|
|
1220
|
+
loge(e.backtrace.join("\n"))
|
|
1221
|
+
end
|
|
1222
|
+
elsif task.type == :subscription
|
|
1223
|
+
begin
|
|
1224
|
+
subscription = task.tid
|
|
1225
|
+
if subscription.state != 'active'
|
|
1226
|
+
return
|
|
1227
|
+
end
|
|
1228
|
+
if task.equal?subscription.timer
|
|
1229
|
+
if subscription.source == 'uac'
|
|
1230
|
+
@controller.on_subscription_refresh_timeout(self, subscription)
|
|
1231
|
+
else
|
|
1232
|
+
@controller.on_subscription_timeout(self, subscription)
|
|
1233
|
+
end
|
|
1234
|
+
end
|
|
1235
|
+
end
|
|
1236
|
+
elsif task.type == :registration
|
|
1237
|
+
registration = task.tid
|
|
1238
|
+
begin
|
|
1239
|
+
@controller.on_registration_expiry(self, registration)
|
|
1240
|
+
end
|
|
1241
|
+
elsif task.type == :session
|
|
1242
|
+
if task.tid == :session_timer
|
|
1243
|
+
if @controller
|
|
1244
|
+
logd("Invoking session listener for #{@controller.name}")
|
|
1245
|
+
begin
|
|
1246
|
+
result = @controller.session_being_invalidated_ok_to_proceed?(self)
|
|
1247
|
+
rescue Exception => e
|
|
1248
|
+
loge("Exception #{e} occured while callback processing by controller")
|
|
1249
|
+
loge(e.backtrace.join("\n"))
|
|
1250
|
+
end
|
|
1251
|
+
if result
|
|
1252
|
+
logd("#{@controller} not interested in session invalidation")
|
|
1253
|
+
else
|
|
1254
|
+
logd("#{@controller} decided to increase the lifetime of session")
|
|
1255
|
+
if (@session_life_so_far+@session_timer < @session_limit)
|
|
1256
|
+
@invalidating = false # to force the timer to start
|
|
1257
|
+
self.invalidate
|
|
1258
|
+
return
|
|
1259
|
+
else
|
|
1260
|
+
logw("Not increasing the lifetime of session, as upper session limit reached")
|
|
1261
|
+
end
|
|
1262
|
+
end
|
|
1263
|
+
else
|
|
1264
|
+
logd("No controller interested in session invalidation")
|
|
1265
|
+
end
|
|
1266
|
+
self.invalidate(true)
|
|
1267
|
+
elsif task.tid == :t2xx_timer
|
|
1268
|
+
logd("2xx retransmission timer fired for #{self}")
|
|
1269
|
+
if @ok_to_retrans_2xx
|
|
1270
|
+
_do_record_sip("out", @two_xx.response, @two_xx.response.to_s)
|
|
1271
|
+
transport.send(@two_xx.response, @two_xx.tp_flags, @two_xx.rip, @two_xx.rp)
|
|
1272
|
+
logd("Retransmitted the 2xx response from #{self}")
|
|
1273
|
+
@current_t2xx = [@t2xx_retrans_timers[:Start]*2, @t2xx_retrans_timers[:Cap]].min
|
|
1274
|
+
_schedule_timer_for_session(:t2xx_timer, @current_t2xx)
|
|
1275
|
+
end # still ok to retrans
|
|
1276
|
+
elsif task.tid == :t2xx_limit_timer
|
|
1277
|
+
@ok_to_retrans_2xx = false
|
|
1278
|
+
if @controller
|
|
1279
|
+
logd("Invoking no_ack_received session listener for #{@controller.name}")
|
|
1280
|
+
begin
|
|
1281
|
+
result = @controller.no_ack_received(self)
|
|
1282
|
+
rescue Exception => e
|
|
1283
|
+
loge("Exception #{e} occured while callback processing by controller")
|
|
1284
|
+
loge(e.backtrace.join("\n"))
|
|
1285
|
+
end
|
|
1286
|
+
end # if controller
|
|
1287
|
+
elsif task.tid == :t1xx_timer
|
|
1288
|
+
if task.equal?@active_1xx_timer
|
|
1289
|
+
logd("1xx retransmission timer fired for #{self}")
|
|
1290
|
+
if @ok_to_retrans_1xx
|
|
1291
|
+
_do_record_sip("out", @one_xx.response, @one_xx.response.to_s)
|
|
1292
|
+
transport.send(@one_xx.response, @one_xx.tp_flags, @one_xx.rip, @one_xx.rp)
|
|
1293
|
+
logd("Retransmitted the 1xx response from #{self}")
|
|
1294
|
+
@current_t1xx = @current_t1xx*2
|
|
1295
|
+
@active_1xx_timer.cancel if @active_1xx_timer
|
|
1296
|
+
@active_1xx_timer = _schedule_timer_for_session(:t1xx_timer, @current_t1xx)
|
|
1297
|
+
end # still ok to retrans
|
|
1298
|
+
else
|
|
1299
|
+
logd("Ignoring invalid 1xx timer #{task}")
|
|
1300
|
+
end
|
|
1301
|
+
elsif task.tid == :t1xx_limit_timer
|
|
1302
|
+
@ok_to_retrans_1xx = false
|
|
1303
|
+
if @controller
|
|
1304
|
+
logd("Invoking no_prack_received session listener for #{@controller.name}")
|
|
1305
|
+
begin
|
|
1306
|
+
result = @controller.no_prack_received(self)
|
|
1307
|
+
rescue Exception => e
|
|
1308
|
+
loge("Exception #{e} occured while callback processing by controller")
|
|
1309
|
+
loge(e.backtrace.join("\n"))
|
|
1310
|
+
end
|
|
1311
|
+
end # if controller
|
|
1312
|
+
elsif task.tid == :session_limit
|
|
1313
|
+
logi("Upper limit of session time limit reached, now invalidating #{self}")
|
|
1314
|
+
self.invalidate(true)
|
|
1315
|
+
end #type of session timer
|
|
1316
|
+
end #session or app
|
|
1317
|
+
end
|
|
1318
|
+
|
|
1319
|
+
|
|
1320
|
+
def schedule_timer_for(tid, duration, &block)
|
|
1321
|
+
logd("Scheduling an app timer #{tid} for #{duration}")
|
|
1322
|
+
SIP::Locator[:Sth].schedule_for(self, tid, block, :app, duration)
|
|
1323
|
+
end
|
|
1324
|
+
|
|
1325
|
+
def _schedule_timer_for_session(tid, duration, &block)
|
|
1326
|
+
logd("Scheduling a session level timer #{tid} for #{duration}")
|
|
1327
|
+
SIP::Locator[:Sth].schedule_for(self, tid, block, :session, duration)
|
|
1328
|
+
end
|
|
1329
|
+
|
|
1330
|
+
def send_http_post_to(url, params)
|
|
1331
|
+
SIP::Locator[:HttpRequestDispatcher].request_post(url, self, params)
|
|
1332
|
+
end
|
|
1333
|
+
|
|
1334
|
+
def send_http_get_to(url)
|
|
1335
|
+
SIP::Locator[:HttpRequestDispatcher].request_get(url, self)
|
|
1336
|
+
end
|
|
1337
|
+
|
|
1338
|
+
# Invoked when the HTTP response is ready to be consumed
|
|
1339
|
+
def on_http_response(http_res)
|
|
1340
|
+
on_message(http_res)
|
|
1341
|
+
end
|
|
1342
|
+
|
|
1343
|
+
# Invoked when either the Sipper media response or an actual media event
|
|
1344
|
+
# is received.
|
|
1345
|
+
def on_media_event(media_evt)
|
|
1346
|
+
# we treat the incoming media event as a message to leverage
|
|
1347
|
+
# the session level queueing for synchronization.
|
|
1348
|
+
on_message(media_evt)
|
|
1349
|
+
end
|
|
1350
|
+
|
|
1351
|
+
def _on_media_event(media_event)
|
|
1352
|
+
@imedia_event = media_event
|
|
1353
|
+
|
|
1354
|
+
if @controller
|
|
1355
|
+
logd("Dispatching media event to controller #{@controller.name}")
|
|
1356
|
+
begin
|
|
1357
|
+
result = @controller.on_media_event(self)
|
|
1358
|
+
rescue Exception => e
|
|
1359
|
+
loge("Exception #{e} occured while media processing by controller")
|
|
1360
|
+
loge(e.backtrace.join("\n"))
|
|
1361
|
+
end
|
|
1362
|
+
logw("#{@controller} could not process the media event") if result == false
|
|
1363
|
+
else
|
|
1364
|
+
loge("No controller associated with this session")
|
|
1365
|
+
end
|
|
1366
|
+
end
|
|
1367
|
+
|
|
1368
|
+
def _on_http_response(http_res)
|
|
1369
|
+
@ihttp_response = http_res
|
|
1370
|
+
if @controller
|
|
1371
|
+
logd("Dispatching http response to controller #{@controller.name}")
|
|
1372
|
+
begin
|
|
1373
|
+
result = @controller.on_http_res(self)
|
|
1374
|
+
rescue Exception => e
|
|
1375
|
+
loge("Exception #{e} occured while http response processing by controller")
|
|
1376
|
+
loge(e.backtrace.join("\n"))
|
|
1377
|
+
end
|
|
1378
|
+
logw("#{@controller} could not process the http response") if result == false
|
|
1379
|
+
else
|
|
1380
|
+
loge("No controller associated with this session")
|
|
1381
|
+
end
|
|
1382
|
+
end
|
|
1383
|
+
|
|
1384
|
+
def _on_custom_message(custom_msg)
|
|
1385
|
+
if @controller
|
|
1386
|
+
logd("Dispatching custom message to controller #{@controller.name}")
|
|
1387
|
+
begin
|
|
1388
|
+
result = @controller.on_custom_msg(self, custom_msg)
|
|
1389
|
+
rescue Exception => e
|
|
1390
|
+
loge("Exception #{e} occured while processing custom message by controller")
|
|
1391
|
+
loge(e.backtrace.join("\n"))
|
|
1392
|
+
end
|
|
1393
|
+
logw("#{@controller} could not process the custom message") if result == false
|
|
1394
|
+
else
|
|
1395
|
+
loge("No controller associated with this session")
|
|
1396
|
+
end
|
|
1397
|
+
end
|
|
1398
|
+
|
|
1399
|
+
def post_custom_message(custom_msg)
|
|
1400
|
+
on_message(custom_msg)
|
|
1401
|
+
end
|
|
1402
|
+
|
|
1403
|
+
|
|
1404
|
+
def invalidate(force=false)
|
|
1405
|
+
if @invalidated
|
|
1406
|
+
logw("This session with key #{self.session_key} is already invalidated")
|
|
1407
|
+
return
|
|
1408
|
+
end
|
|
1409
|
+
|
|
1410
|
+
if force
|
|
1411
|
+
logd("Now invalidating the session #{self} with key #{self.session_key}")
|
|
1412
|
+
@offer_answer.close if @offer_answer
|
|
1413
|
+
@transactions.each_value do |t|
|
|
1414
|
+
logd("Invalidating the txn #{t} in session #{self}")
|
|
1415
|
+
t.invalidate
|
|
1416
|
+
end
|
|
1417
|
+
@session_recorder.save if @session_recorder
|
|
1418
|
+
SessionManager.remove_session self
|
|
1419
|
+
@invalidated = true
|
|
1420
|
+
SIP::TestCompletionSignalingHelper.signal_waiting_test(@signal_test_complete_when_invalidated) if @signal_test_complete_when_invalidated
|
|
1421
|
+
else
|
|
1422
|
+
if @invalidating
|
|
1423
|
+
logi("This session with key #{self.session_key} is already scheduled for invalidation")
|
|
1424
|
+
return
|
|
1425
|
+
end
|
|
1426
|
+
@invalidating = true
|
|
1427
|
+
tmr = _schedule_timer_for_session(:session_timer, @session_timer)
|
|
1428
|
+
@session_life_so_far += @session_timer
|
|
1429
|
+
logd("Now scheduling the session #{self.session_key} for invalidation after #{@session_timer} #{tmr}")
|
|
1430
|
+
end
|
|
1431
|
+
end
|
|
1432
|
+
|
|
1433
|
+
|
|
1434
|
+
def record_io=(rio)
|
|
1435
|
+
@rio = rio
|
|
1436
|
+
end
|
|
1437
|
+
|
|
1438
|
+
def do_record(msg)
|
|
1439
|
+
msg = SipperUtil.recordify(msg)
|
|
1440
|
+
_do_record_sip("neutral", msg)
|
|
1441
|
+
end
|
|
1442
|
+
|
|
1443
|
+
def transaction_record(direction, msg)
|
|
1444
|
+
_do_record_sip(direction, msg)
|
|
1445
|
+
end
|
|
1446
|
+
|
|
1447
|
+
def flow_completed_for(test_name)
|
|
1448
|
+
unless @invalidated
|
|
1449
|
+
logd("Session not invalidated yet, setting flag signaling completion")
|
|
1450
|
+
@signal_test_complete_when_invalidated = test_name.to_s
|
|
1451
|
+
return true
|
|
1452
|
+
end
|
|
1453
|
+
logd("In flow_completed_for() now siganling the waiting test")
|
|
1454
|
+
SIP::TestCompletionSignalingHelper.signal_waiting_test(test_name.to_s)
|
|
1455
|
+
end
|
|
1456
|
+
|
|
1457
|
+
# The first recording will create the recording file name using in or out
|
|
1458
|
+
# todo will it be enough for uniqueness?
|
|
1459
|
+
def _do_record_sip(direction, msg, msg_s=nil)
|
|
1460
|
+
logd("record() invoked for #{direction} and #{msg.call_id.to_s}")
|
|
1461
|
+
# maintain simple state in the session
|
|
1462
|
+
unless @user_defined_state
|
|
1463
|
+
if direction == "out"
|
|
1464
|
+
if msg.is_request?
|
|
1465
|
+
@state << ("sent_" + msg.method.downcase)
|
|
1466
|
+
elsif msg.is_response?
|
|
1467
|
+
@state << ("sent_" + msg.code.to_s)
|
|
1468
|
+
end
|
|
1469
|
+
elsif direction == "in"
|
|
1470
|
+
if msg.is_request?
|
|
1471
|
+
@state << ("received_" + msg.method.downcase)
|
|
1472
|
+
elsif msg.is_response?
|
|
1473
|
+
@state << ("received_" + msg.code.to_s)
|
|
1474
|
+
end
|
|
1475
|
+
end
|
|
1476
|
+
end
|
|
1477
|
+
unless @session_recorder
|
|
1478
|
+
@session_recorder = SessionRecorder.create_and_record(@rio, msg, msg_s, direction, @session_record)
|
|
1479
|
+
else
|
|
1480
|
+
@session_recorder.record(direction, msg, msg_s)
|
|
1481
|
+
end
|
|
1482
|
+
rescue RuntimeError => e
|
|
1483
|
+
loge("Unable to record the #{msg} as recorder is closed")
|
|
1484
|
+
end
|
|
1485
|
+
|
|
1486
|
+
|
|
1487
|
+
def _check_cancel_state(msg)
|
|
1488
|
+
@cancellable_txns.include? msg.txn_id
|
|
1489
|
+
end
|
|
1490
|
+
|
|
1491
|
+
def _check_for_pending_cancel
|
|
1492
|
+
txn_id = @iresponse.txn_id
|
|
1493
|
+
@cancellable_txns << txn_id
|
|
1494
|
+
if pc = @pending_cancels[txn_id]
|
|
1495
|
+
@pending_cancels[txn_id] = nil
|
|
1496
|
+
if SipperConfigurator[:ProtocolCompliance] == 'strict'
|
|
1497
|
+
return if @iresponse.code >= 200
|
|
1498
|
+
end
|
|
1499
|
+
send pc
|
|
1500
|
+
end
|
|
1501
|
+
end
|
|
1502
|
+
|
|
1503
|
+
def _increment_local_cseq
|
|
1504
|
+
@local_cseq_before_send ||= @local_cseq
|
|
1505
|
+
@local_cseq += 1
|
|
1506
|
+
logd "Local cseq before send set to #{@local_cseq_before_send} and @local_cseq is #{@local_cseq}"
|
|
1507
|
+
end
|
|
1508
|
+
|
|
1509
|
+
# Useful when you create a subsequent request but do not send it.
|
|
1510
|
+
# This resets the state to the time when the request was not created.
|
|
1511
|
+
def rollback_to_unsent_state
|
|
1512
|
+
@local_cseq = @local_cseq_before_send if @local_cseq_before_send
|
|
1513
|
+
@local_cseq_before_send = nil
|
|
1514
|
+
end
|
|
1515
|
+
|
|
1516
|
+
# 3261 Section 8.2
|
|
1517
|
+
# Note that request processing is atomic.
|
|
1518
|
+
# If a request is accepted, all state changes associated with it MUST be performed.
|
|
1519
|
+
# If it is rejected, all state changes MUST NOT be performed.
|
|
1520
|
+
#
|
|
1521
|
+
# This rollback is invoked automatically when Sipper rejects the request before it invokes
|
|
1522
|
+
# the controller. The controller can invoke it to rollback any dialog state if it is
|
|
1523
|
+
# going to reject the request or alternatively if the compliance flag is set to strict
|
|
1524
|
+
# this will automatically be called on request rejection.
|
|
1525
|
+
# Note that the snapshot is single-depth state capture, does not save any contained
|
|
1526
|
+
# object's state.
|
|
1527
|
+
def rollback_to_before_request_received_state(request)
|
|
1528
|
+
if request.session_state_snapshot
|
|
1529
|
+
self.restore_snapshot(request.session_state_snapshot)
|
|
1530
|
+
@dialog_routes.restore_snapshot(@dialog_routes_snap)
|
|
1531
|
+
@dialog_routes_snap = nil
|
|
1532
|
+
request.session_state_snapshot = nil
|
|
1533
|
+
logd("Restored to previous session state for request #{request.method}")
|
|
1534
|
+
else
|
|
1535
|
+
logd("No state to rollback.")
|
|
1536
|
+
end
|
|
1537
|
+
end
|
|
1538
|
+
|
|
1539
|
+
# creates a failure response and also rolls back the session state, this is called
|
|
1540
|
+
# when the request is rejected by Sipper itself.
|
|
1541
|
+
def rejection_response_with(code, request)
|
|
1542
|
+
logi("Rejecting the request #{request.method} with a #{code}")
|
|
1543
|
+
r = create_response(code, "SELECT", request)
|
|
1544
|
+
rollback_to_before_request_received_state(request)
|
|
1545
|
+
return r
|
|
1546
|
+
end
|
|
1547
|
+
|
|
1548
|
+
# Sometimes you create a new session but would like to continue the
|
|
1549
|
+
# recording process from previous session for validation purposes.
|
|
1550
|
+
# This would typically be used when you drop a leg and create a new
|
|
1551
|
+
# one.
|
|
1552
|
+
def continue_recording_from(old_session)
|
|
1553
|
+
@session_recorder = old_session._get_recorder
|
|
1554
|
+
old_session._remove_recorder
|
|
1555
|
+
end
|
|
1556
|
+
|
|
1557
|
+
|
|
1558
|
+
def _update_and_check_dialog_state_on_request(request)
|
|
1559
|
+
new_remote_cseq = SipperUtil.cseq_number(request.cseq)
|
|
1560
|
+
@dialog_routes_snap = @dialog_routes.take_snapshot
|
|
1561
|
+
request.session_state_snapshot = self.take_snapshot
|
|
1562
|
+
@dialog_routes.request_received(request)
|
|
1563
|
+
_on_common_sip(request)
|
|
1564
|
+
@imessage = @irequest = request
|
|
1565
|
+
@remote_tag = @irequest.from_tag unless @remote_tag
|
|
1566
|
+
@local_uri = @irequest.to.to_s
|
|
1567
|
+
@remote_uri = @irequest.from.to_s
|
|
1568
|
+
|
|
1569
|
+
# 3261 12.2.2 testing for < because a retransmission will have same sequence, as will be
|
|
1570
|
+
# ACK and CANCEL
|
|
1571
|
+
if SipperConfigurator[:ProtocolCompliance]=='strict' &&
|
|
1572
|
+
new_remote_cseq < @remote_cseq
|
|
1573
|
+
if @irequest.method == "ACK"
|
|
1574
|
+
logd("Dropping an ACK for an out of CSeq rejected request")
|
|
1575
|
+
else
|
|
1576
|
+
request.attributes[:_sipper_rejection_response] = rejection_response_with(500, @irequest)
|
|
1577
|
+
logd("Rejected the request #{@irequest.method} for out of CSeq")
|
|
1578
|
+
end
|
|
1579
|
+
return false
|
|
1580
|
+
end
|
|
1581
|
+
request.attributes[:_sipper_old_cseq] = @remote_cseq # save old for later check
|
|
1582
|
+
@remote_cseq = new_remote_cseq
|
|
1583
|
+
|
|
1584
|
+
if request.method == 'NOTIFY'
|
|
1585
|
+
if @session_map == :half
|
|
1586
|
+
if get_subscription(request) != nil
|
|
1587
|
+
logd("Moving the dialog to full on Subscription notify.")
|
|
1588
|
+
SessionManager.find_session(request.call_id, request.to_tag, request.from_tag, true)
|
|
1589
|
+
end
|
|
1590
|
+
end
|
|
1591
|
+
end
|
|
1592
|
+
return true
|
|
1593
|
+
end
|
|
1594
|
+
|
|
1595
|
+
def _update_dialog_state_on_response(response)
|
|
1596
|
+
@dialog_routes.response_received(response)
|
|
1597
|
+
_on_common_sip(response)
|
|
1598
|
+
@imessage = @iresponse = response
|
|
1599
|
+
_check_for_pending_cancel
|
|
1600
|
+
@remote_tag = response.to_tag
|
|
1601
|
+
@remote_uri = response.to.to_s
|
|
1602
|
+
end
|
|
1603
|
+
|
|
1604
|
+
# Reject the request if we have seen thsi CSeq before and there is no transaction
|
|
1605
|
+
# found for this request which rules out a retransmission.
|
|
1606
|
+
def _equal_cseq_check(request)
|
|
1607
|
+
if SipperConfigurator[:ProtocolCompliance] == 'strict' &&
|
|
1608
|
+
SipperUtil.cseq_number(request.cseq) == request.attributes[:_sipper_old_cseq]
|
|
1609
|
+
request.attributes[:_sipper_rejection_response] = rejection_response_with(500, @irequest)
|
|
1610
|
+
logd("Rejected the request #{request.method} for out of (equal) CSeq")
|
|
1611
|
+
false
|
|
1612
|
+
else
|
|
1613
|
+
true
|
|
1614
|
+
end
|
|
1615
|
+
end
|
|
1616
|
+
|
|
1617
|
+
# A UAS that receives a second INVITE before it sends the final response to
|
|
1618
|
+
# a first INVITE with a lower CSeq sequence number on the same dialog MUST
|
|
1619
|
+
# return a 500 (Server Internal Error) response to the second INVITE and
|
|
1620
|
+
# MUST include a Retry-After header field with a randomly chosen value
|
|
1621
|
+
# of between 0 and 10 seconds.
|
|
1622
|
+
#
|
|
1623
|
+
# A UAS that receives an INVITE on a dialog
|
|
1624
|
+
# while an INVITE it had sent on that dialog is in progress MUST return a
|
|
1625
|
+
# 491 (Request Pending) response to the received INVITE.
|
|
1626
|
+
def _check_pending_invite_txns_on_invite_in(request)
|
|
1627
|
+
@transactions.values.each do |txn|
|
|
1628
|
+
if txn.transaction_name == :Ict
|
|
1629
|
+
unless ["IctMap.Completed", "IctMap.Terminated"].include? txn.state
|
|
1630
|
+
request.attributes[:_sipper_rejection_response] = rejection_response_with(491, request)
|
|
1631
|
+
return false
|
|
1632
|
+
end
|
|
1633
|
+
elsif txn.transaction_name == :Ist
|
|
1634
|
+
unless ["IstMap.Confirmed", "IstMap.Terminated", "IstMap.Finished"].include? txn.state
|
|
1635
|
+
r = rejection_response_with(500, request)
|
|
1636
|
+
r.retry_after = rand(10).to_s
|
|
1637
|
+
request.attributes[:_sipper_rejection_response] = r
|
|
1638
|
+
return false
|
|
1639
|
+
end
|
|
1640
|
+
end
|
|
1641
|
+
end
|
|
1642
|
+
return true
|
|
1643
|
+
end
|
|
1644
|
+
|
|
1645
|
+
|
|
1646
|
+
def _add_route_headers_if_present(msg, rrt)
|
|
1647
|
+
unless rrt[1].empty?
|
|
1648
|
+
msg.route = rrt[1]
|
|
1649
|
+
msg.format_as_separate_headers_for_mv(:route)
|
|
1650
|
+
end
|
|
1651
|
+
msg.attributes[:_sipper_use_ruri_to_send] = rrt[2]
|
|
1652
|
+
msg
|
|
1653
|
+
end
|
|
1654
|
+
|
|
1655
|
+
|
|
1656
|
+
# populates the transport, rip and rp attributes in the session if they are not
|
|
1657
|
+
# set. This can happen if an unbound session is created.
|
|
1658
|
+
def _check_transport_and_destination(msg)
|
|
1659
|
+
if !(@transport && @rip && @rp) || @dialog_routes.target_refreshed?
|
|
1660
|
+
if (@controller && (cstp=@controller.specified_transport))
|
|
1661
|
+
msg.attributes[:_sipper_controller_specified_transport] = cstp
|
|
1662
|
+
end
|
|
1663
|
+
rv = Transport::TransportAndRouteResolver.ascertain_transport_and_destination(msg)
|
|
1664
|
+
@transport = rv[0]||@transport
|
|
1665
|
+
@rip = rv[1] || @rip
|
|
1666
|
+
@rp = rv[2] || @rp
|
|
1667
|
+
end
|
|
1668
|
+
end
|
|
1669
|
+
|
|
1670
|
+
def _get_sq_lock
|
|
1671
|
+
@sq_lock
|
|
1672
|
+
end
|
|
1673
|
+
|
|
1674
|
+
def _get_recorder
|
|
1675
|
+
@session_recorder
|
|
1676
|
+
end
|
|
1677
|
+
|
|
1678
|
+
def _remove_recorder
|
|
1679
|
+
@session_recorder = nil
|
|
1680
|
+
end
|
|
1681
|
+
|
|
1682
|
+
def create_subscription_from_request(request)
|
|
1683
|
+
# Creating subscription based on Subscribe/Refer request received.
|
|
1684
|
+
event = request.event.header_value if request["event".to_sym]
|
|
1685
|
+
|
|
1686
|
+
unless event
|
|
1687
|
+
if request.method == "REFER"
|
|
1688
|
+
event = "refer"
|
|
1689
|
+
else
|
|
1690
|
+
log_and_raise "Invalid subscription", ArgumentError
|
|
1691
|
+
end
|
|
1692
|
+
end
|
|
1693
|
+
|
|
1694
|
+
if event == "refer" && request.method != "REFER"
|
|
1695
|
+
log_and_raise "Only Refer method can create refer subscription", ArgumentError
|
|
1696
|
+
end
|
|
1697
|
+
|
|
1698
|
+
id_val = request.event['id'] if request["event".to_sym]
|
|
1699
|
+
unless id_val
|
|
1700
|
+
id_val = 0
|
|
1701
|
+
end
|
|
1702
|
+
|
|
1703
|
+
key = sprintf("|%s|%d", event, id_val)
|
|
1704
|
+
subscription = @subscriptionMap[key]
|
|
1705
|
+
unless subscription
|
|
1706
|
+
logd("Creating new uas subscription object.")
|
|
1707
|
+
subscription = SubscriptionData.new
|
|
1708
|
+
subscription.key = key
|
|
1709
|
+
subscription.timer = nil
|
|
1710
|
+
subscription.source = 'uas'
|
|
1711
|
+
subscription.event = event
|
|
1712
|
+
subscription.event_id = id_val
|
|
1713
|
+
subscription.state = 'active'
|
|
1714
|
+
subscription.method = request.method
|
|
1715
|
+
|
|
1716
|
+
if request.method == "SUBSCRIBE"
|
|
1717
|
+
if request.expires.header_value == '0'
|
|
1718
|
+
subscription.state = "terminated"
|
|
1719
|
+
end
|
|
1720
|
+
end
|
|
1721
|
+
@subscriptionMap[key] = subscription
|
|
1722
|
+
end
|
|
1723
|
+
|
|
1724
|
+
return @subscriptionMap[key]
|
|
1725
|
+
end
|
|
1726
|
+
|
|
1727
|
+
def create_subscription(event, id_val=0, method="SUBSCRIBE")
|
|
1728
|
+
# Creating new subscription.
|
|
1729
|
+
key = sprintf("|%s|%d", event, id_val)
|
|
1730
|
+
subscription = @subscriptionMap[key]
|
|
1731
|
+
unless subscription
|
|
1732
|
+
logd("Creating new uac subscription object.")
|
|
1733
|
+
subscription = SubscriptionData.new
|
|
1734
|
+
subscription.key = key
|
|
1735
|
+
subscription.timer = nil
|
|
1736
|
+
subscription.source = 'uac'
|
|
1737
|
+
subscription.event = event
|
|
1738
|
+
subscription.event_id = id_val
|
|
1739
|
+
subscription.state = 'active'
|
|
1740
|
+
subscription.method = method
|
|
1741
|
+
@subscriptionMap[key] = subscription
|
|
1742
|
+
end
|
|
1743
|
+
|
|
1744
|
+
return subscription
|
|
1745
|
+
end
|
|
1746
|
+
|
|
1747
|
+
def get_subscription(request)
|
|
1748
|
+
event = request.event.header_value if request["event".to_sym]
|
|
1749
|
+
|
|
1750
|
+
unless event
|
|
1751
|
+
if request.method == "REFER"
|
|
1752
|
+
event = "refer"
|
|
1753
|
+
else
|
|
1754
|
+
log_and_raise "Invalid subscription", ArgumentError
|
|
1755
|
+
end
|
|
1756
|
+
end
|
|
1757
|
+
|
|
1758
|
+
event_id = request.event['id'] if request["event".to_sym]
|
|
1759
|
+
unless event_id
|
|
1760
|
+
event_id = 0
|
|
1761
|
+
end
|
|
1762
|
+
|
|
1763
|
+
key = sprintf("|%s|%d", event, event_id)
|
|
1764
|
+
subscription = @subscriptionMap[key]
|
|
1765
|
+
|
|
1766
|
+
return subscription
|
|
1767
|
+
end
|
|
1768
|
+
|
|
1769
|
+
def update_subscription(request)
|
|
1770
|
+
subscription = get_subscription(request)
|
|
1771
|
+
|
|
1772
|
+
if request.method == "NOTIFY"
|
|
1773
|
+
subscription.state = request.subscription_state.header_value
|
|
1774
|
+
|
|
1775
|
+
if request.subscription_state["expires"] != nil
|
|
1776
|
+
if request.subscription_state["expires"] == '0'
|
|
1777
|
+
subscription.state = "terminated"
|
|
1778
|
+
end
|
|
1779
|
+
end
|
|
1780
|
+
end
|
|
1781
|
+
|
|
1782
|
+
if request.method == "SUBSCRIBE"
|
|
1783
|
+
if request.expires.header_value == '0'
|
|
1784
|
+
subscription.state = "terminated"
|
|
1785
|
+
end
|
|
1786
|
+
end
|
|
1787
|
+
|
|
1788
|
+
return subscription
|
|
1789
|
+
end
|
|
1790
|
+
|
|
1791
|
+
def remove_subscription(request)
|
|
1792
|
+
event = request.event.header_value if request["event".to_sym]
|
|
1793
|
+
|
|
1794
|
+
unless event
|
|
1795
|
+
if request.method == "REFER"
|
|
1796
|
+
event = "refer"
|
|
1797
|
+
else
|
|
1798
|
+
log_and_raise "Invalid subscription", ArgumentError
|
|
1799
|
+
end
|
|
1800
|
+
end
|
|
1801
|
+
|
|
1802
|
+
event_id = request.event.id if request["event".to_sym]
|
|
1803
|
+
unless event_id
|
|
1804
|
+
event_id = 0
|
|
1805
|
+
end
|
|
1806
|
+
|
|
1807
|
+
key = sprintf("|%s|%d", event, event_id)
|
|
1808
|
+
@subscriptionMap[key] = nil
|
|
1809
|
+
end
|
|
1810
|
+
|
|
1811
|
+
def remove_subscription(subscription)
|
|
1812
|
+
@subscriptionMap[subscription.key] = nil
|
|
1813
|
+
end
|
|
1814
|
+
|
|
1815
|
+
def add_subscription_to_request(request, subscription)
|
|
1816
|
+
logd(subscription.to_s)
|
|
1817
|
+
|
|
1818
|
+
if subscription.event != "refer" || request.method != "REFER" || subscription.event_id != 0
|
|
1819
|
+
request.event = subscription.event
|
|
1820
|
+
end
|
|
1821
|
+
|
|
1822
|
+
if subscription.event_id != 0
|
|
1823
|
+
request.event.id = subscription.event_id.to_s
|
|
1824
|
+
end
|
|
1825
|
+
|
|
1826
|
+
if request.method == "NOTIFY"
|
|
1827
|
+
request['subscription-state'] = subscription.state
|
|
1828
|
+
end
|
|
1829
|
+
end
|
|
1830
|
+
|
|
1831
|
+
def _schedule_timer_for_subscription(duration, subscription)
|
|
1832
|
+
logd("Scheduling a subscription timer for #{duration}")
|
|
1833
|
+
subscription.timer = SIP::Locator[:Sth].schedule_for(self, subscription, nil, :subscription, duration)
|
|
1834
|
+
end
|
|
1835
|
+
|
|
1836
|
+
def start_subscription_expiry_timer(subscription, response)
|
|
1837
|
+
logd('starting subscription expiry timer')
|
|
1838
|
+
if subscription.state != "active"
|
|
1839
|
+
logd("Not scheduling timer as state is not active")
|
|
1840
|
+
return
|
|
1841
|
+
end
|
|
1842
|
+
|
|
1843
|
+
expires = response.expires.header_value
|
|
1844
|
+
if expires == nil
|
|
1845
|
+
logd("Not scheduling timer as expires is empty")
|
|
1846
|
+
return
|
|
1847
|
+
end
|
|
1848
|
+
|
|
1849
|
+
expiresDuration = expires.to_i * 1000
|
|
1850
|
+
|
|
1851
|
+
logd("Scheduling timer for subscription")
|
|
1852
|
+
_schedule_timer_for_subscription(expiresDuration, subscription)
|
|
1853
|
+
end
|
|
1854
|
+
|
|
1855
|
+
def start_subscription_refresh_timer(subscription, response)
|
|
1856
|
+
if subscription.state != "active"
|
|
1857
|
+
logd("Not starting refresh Timer as state is #{subscription.state}");
|
|
1858
|
+
return
|
|
1859
|
+
end
|
|
1860
|
+
|
|
1861
|
+
expires = response.expires.header_value
|
|
1862
|
+
if expires == nil
|
|
1863
|
+
logd("Not starting refresh Timer as expires is nil")
|
|
1864
|
+
return
|
|
1865
|
+
end
|
|
1866
|
+
|
|
1867
|
+
expiresDuration = (expires.to_i - 10) * 1000
|
|
1868
|
+
|
|
1869
|
+
if (expiresDuration < 0)
|
|
1870
|
+
expiresDuration = expires.to_i * 500
|
|
1871
|
+
end
|
|
1872
|
+
|
|
1873
|
+
logd("Starting refresh Timer for #{expiresDuration}");
|
|
1874
|
+
_schedule_timer_for_subscription(expiresDuration, subscription)
|
|
1875
|
+
end
|
|
1876
|
+
|
|
1877
|
+
# Persists for REGISTRAR not REGISTRANT
|
|
1878
|
+
def _create_registration_object(message, persist=true)
|
|
1879
|
+
if message[:to]
|
|
1880
|
+
aor = message.to.header_value
|
|
1881
|
+
else
|
|
1882
|
+
log_and_raise "Invalid registration", ArgumentError
|
|
1883
|
+
end
|
|
1884
|
+
|
|
1885
|
+
key = sprintf("|%s", aor)
|
|
1886
|
+
reg_list = SIP::Locator[:RegistrationStore].get(key) if persist
|
|
1887
|
+
unless reg_list
|
|
1888
|
+
logd("Creating new registration object.")
|
|
1889
|
+
reg_list =[]
|
|
1890
|
+
if message[:contact]
|
|
1891
|
+
message.contacts.each do|cn|
|
|
1892
|
+
registration = Registration.add_registration_data(cn,message)
|
|
1893
|
+
reg_list.push(registration) if registration.expires.to_i != 0
|
|
1894
|
+
end
|
|
1895
|
+
end
|
|
1896
|
+
else
|
|
1897
|
+
logd("Updating registration object.")
|
|
1898
|
+
message.contacts.each do|cn|
|
|
1899
|
+
updated = Registration.update_registration_data(cn, reg_list, message)
|
|
1900
|
+
if not updated
|
|
1901
|
+
registration = Registration.add_registration_data(cn,message)
|
|
1902
|
+
reg_list.push(registration) if registration.expires.to_i != 0
|
|
1903
|
+
end
|
|
1904
|
+
end
|
|
1905
|
+
end
|
|
1906
|
+
|
|
1907
|
+
if message[:expires]
|
|
1908
|
+
if message.expires.header_value == '0' and message.contact.header_value == '*'
|
|
1909
|
+
reg_list.clear
|
|
1910
|
+
end
|
|
1911
|
+
end
|
|
1912
|
+
SIP::Locator[:RegistrationStore].put(key, reg_list) if persist
|
|
1913
|
+
return reg_list
|
|
1914
|
+
end
|
|
1915
|
+
|
|
1916
|
+
def _schedule_timer_for_registration(duration, registration)
|
|
1917
|
+
logd("Scheduling a registration refresh timer for #{duration}")
|
|
1918
|
+
SIP::Locator[:Sth].schedule_for(self, registration, nil, :registration, duration)
|
|
1919
|
+
end
|
|
1920
|
+
|
|
1921
|
+
def start_registration_expiry_timer
|
|
1922
|
+
expires = '99999'
|
|
1923
|
+
@registrations.each do | reg_data |
|
|
1924
|
+
expires = reg_data.expires if expires.to_i > reg_data.expires.to_i
|
|
1925
|
+
end
|
|
1926
|
+
if expires.to_i == 0
|
|
1927
|
+
logd("Not starting registration refresh Timer as expires is 0")
|
|
1928
|
+
return
|
|
1929
|
+
end
|
|
1930
|
+
|
|
1931
|
+
expiresDuration = (expires.to_i - 10)*1000
|
|
1932
|
+
|
|
1933
|
+
if (expiresDuration < 0)
|
|
1934
|
+
expiresDuration = expires.to_i * 500
|
|
1935
|
+
end
|
|
1936
|
+
logd("Starting registration expiry Timer for #{expiresDuration}");
|
|
1937
|
+
_schedule_timer_for_registration(expiresDuration, @registrations)
|
|
1938
|
+
end
|
|
1939
|
+
|
|
1940
|
+
def offer_answer=(val)
|
|
1941
|
+
@offer_answer.close if @offer_answer && val.nil?
|
|
1942
|
+
@offer_answer = val
|
|
1943
|
+
end
|
|
1944
|
+
|
|
1945
|
+
|
|
1946
|
+
protected :_get_sq_lock, :_get_recorder, :_remove_recorder
|
|
1947
|
+
private :_on_request, :_on_response, :_on_common_sip, :_do_record_sip, :_check_cancel_state,
|
|
1948
|
+
:_check_for_pending_cancel, :_fixed_local_tag, :_send_common,
|
|
1949
|
+
:_schedule_timer_for_session, :_increment_local_cseq, :_update_and_check_dialog_state_on_request,
|
|
1950
|
+
:_add_route_headers_if_present, :_check_transport_and_destination, :_create_registration_object
|
|
1951
|
+
|
|
1952
|
+
end
|