Sipper 1.1.3
Sign up to get free protection for your applications and to get access to all the features.
- 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,30 @@
|
|
1
|
+
require 'base_controller'
|
2
|
+
|
3
|
+
class InviteController < SIP::BaseController
|
4
|
+
|
5
|
+
transaction_usage :use_transactions=>true
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
logd("#{name} controller created")
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
def on_invite(session)
|
13
|
+
logd("on_invite called for #{name}")
|
14
|
+
r = session.create_response(200, "OK")
|
15
|
+
r.server = "Sipper"
|
16
|
+
session.send(r)
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
def on_ack(session)
|
21
|
+
logd("on_ack called for #{name}")
|
22
|
+
end
|
23
|
+
|
24
|
+
def on_bye(session)
|
25
|
+
logd("on_bye called for #{name}, now invalidating")
|
26
|
+
r = session.create_response(200, "OK")
|
27
|
+
session.send(r)
|
28
|
+
session.invalidate(true)
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,1621 @@
|
|
1
|
+
- todo make it rdoc compatibile
|
2
|
+
- todo have several complete examples in the doc to clarify usage.
|
3
|
+
- todo Have two part manual. (a) Test/App Developer's manual (b) Sipper Developer's manual
|
4
|
+
- todo Rather than large paras have small headings
|
5
|
+
- todo with each section have a SIP box or SIP section that describes or refreshes the SIP concepts
|
6
|
+
or quote from the RFCs.
|
7
|
+
|
8
|
+
Third Party Libraries
|
9
|
+
---------------------
|
10
|
+
0. Ruby 1.8.5 or higher
|
11
|
+
1. Log4r : http://log4r.sourceforge.net/
|
12
|
+
2. Flex Mock : http://onestepback.org/software/flexmock/ gem install flexmock
|
13
|
+
3. Facets 1.8.54 [Note previous version shall not work]
|
14
|
+
4. Rake
|
15
|
+
5. SMC http://smc.sourceforge.net/
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
Manual
|
20
|
+
------
|
21
|
+
|
22
|
+
To get started set the environment variable SIPPER_HOME to where the sipper lib is installed, upto and
|
23
|
+
including sipper_src. IMPORTANT Even on Windows platform for this ENV var, use the forward slashes.
|
24
|
+
Also set the configuration defaults in the SipperConfigurator of which the most important is the
|
25
|
+
:LocalSipperIP which should be set to each of the nodes IP (or name), this is the only node
|
26
|
+
specific configuration. In case of multihomed host you can start the Sipper instance on any of the
|
27
|
+
IP addresses using the Sipper start options. The :LocalSipperIP is primarily used as a default for
|
28
|
+
tests.
|
29
|
+
[todo clarify the above]
|
30
|
+
|
31
|
+
Starting Sipper
|
32
|
+
---------------
|
33
|
+
Config and default config.
|
34
|
+
|
35
|
+
Usage
|
36
|
+
-----
|
37
|
+
|
38
|
+
Session Object
|
39
|
+
--------------
|
40
|
+
It is important to first of all understand what this session object is and how does it relate to
|
41
|
+
various SIP constructs.
|
42
|
+
First of all this Session object in the Sipper API has nothing to do with the concept of "session"
|
43
|
+
as talked about in the SIP RFC 3261, well almost nothing. But please read this small writeup to
|
44
|
+
understand not only the Sipper session object but also the overall context it is used.
|
45
|
+
|
46
|
+
The Session in S(ession)IP is the multimedia session established between two end points using the
|
47
|
+
SIP offer answer model. So the (SIP) session is a term used for an end-to-end communication session.
|
48
|
+
Within the SIP parlance there is another term called "dialog" that is related but not the same as
|
49
|
+
session. While (SIP) session is the overall communication session, the SIP dialog is a signaling
|
50
|
+
only concept that enables the (SIP) session setup to take place.
|
51
|
+
SIP is a signaling protocol and it enables the establishment of these (SIP) sessions. A SIP Dialog
|
52
|
+
(early or confirmed) is created when a provisional response with To tag or a 2xx response is
|
53
|
+
received for a dialog creating request (INVITE, SUBSCRIBE, NOTIFY, REFER).
|
54
|
+
A dialog is a context within which further SIP message exchange can take place and a dialog lasts
|
55
|
+
until is teared down by a BYE request in most cases. The only purpose of dialog is to establish
|
56
|
+
some state at the end point UAs such that once the dialog is established the subsequent SIP messages
|
57
|
+
are delivered and are sequenced properly.
|
58
|
+
(SIP) session may be setup as a result of this dialog creation for the INVITE request. RFC 3261
|
59
|
+
and 3264 define mechanisms of how the offer answer exchange happens using SDP (Session Description
|
60
|
+
protocol) that is the payload of SIP messages. 3261 also established some rules as to what SIP
|
61
|
+
messages can carry offers and answers. The effect of these rules is that there is a correspondence
|
62
|
+
between (SIP) sessions and dialogs. It not necessarily one to one but there is a relationship.
|
63
|
+
Please refer to sections 12-14 of RFC 3261 to better understand the correspondence and distinction.
|
64
|
+
|
65
|
+
Now the Session object that you see in Sipper API is closely associated with a Dialog. In fact
|
66
|
+
it a superset of a SIP dialog. A Sipper Session can be created even before any signaling takes
|
67
|
+
place on the UAC. In fact this creation would the first step for starting any SIP communication as
|
68
|
+
the Session API is the primary API for controllers.
|
69
|
+
|
70
|
+
|
71
|
+
Session creation
|
72
|
+
----------------
|
73
|
+
A session is automatically created when the request is first received by Sipper before the request is handed over to the right controller.
|
74
|
+
A session can also be created explicitly by invoking one of the several
|
75
|
+
create_xxx_session methods defined on the BaseController.
|
76
|
+
Controller usually will know what type of session namely UDP, TCP, TLS or SCTP is it going to require, in such a case you should use the protocol specific method passing the remote ip and port. e.g for UDP the method would be create_udp_session(ip, port). This session is like
|
77
|
+
a point to point association between this Sipper end point and the
|
78
|
+
remote SIP entity at the given IP and port.
|
79
|
+
Sometimes the remote IP and port and even the transport to which the
|
80
|
+
request is to be sent is not known. In such a case procedures defined
|
81
|
+
in RFC 3263 are followed to determine the right transport, ip and port
|
82
|
+
based upon the appropriate DNS records.
|
83
|
+
|
84
|
+
Detached Session
|
85
|
+
----------------
|
86
|
+
In such a case a "detached" session is used which is a session that
|
87
|
+
does not have a transport or a remote IP/port known at creation time.
|
88
|
+
Such a session can be created by calling create_session() method.
|
89
|
+
At run time when the request is being sent, the right session protocol
|
90
|
+
is looked up as are the IP and port and are set in the session.
|
91
|
+
From this point onwards for all SIP signaling that transport, IP and port are used.
|
92
|
+
The detached session can also be used for cases when remote SIP end point is not yet known and some signaling in some other protocol is
|
93
|
+
to be done, like HTTP.
|
94
|
+
[Until the actual DNS lookup code is in place the only limitation
|
95
|
+
of using create_session is that a real IP needs to be provided in
|
96
|
+
the request uri and currently udp transport is assumed which should
|
97
|
+
be fixed when we have tcp and/or 3263 support]
|
98
|
+
|
99
|
+
|
100
|
+
Dialog State Management
|
101
|
+
-----------------------
|
102
|
+
|
103
|
+
|
104
|
+
Route Set
|
105
|
+
---------
|
106
|
+
In the case of UAs the Route Set is learned as a result of Record-Route header exchange during setup of the
|
107
|
+
dialog. This learned route set (even if empty) overrides any pre-existing route set.
|
108
|
+
The following are relevant quotes from RFC 3261 with respect for route set management all in one place
|
109
|
+
to ease in understanding of the implementation -
|
110
|
+
|
111
|
+
"In some special circumstances, the presence of a pre-existing route set can affect the Request-URI
|
112
|
+
of the message. A pre-existing route set is an ordered set of URIs that identify a chain of servers,
|
113
|
+
to which a UAC will send outgoing requests that are outside of a dialog.
|
114
|
+
Commonly, they are configured on the UA by a user or service provider manually, or through some
|
115
|
+
other non-SIP mechanism. When a provider wishes to configure a UA with an outbound proxy,
|
116
|
+
it is RECOMMENDED that this be done by providing it with a pre-existing route set with a single URI,
|
117
|
+
that of the outbound proxy.
|
118
|
+
|
119
|
+
If the first element in the route set indicated a strict router (resulting in forming the request as
|
120
|
+
described in Section 12.2.1.1), the procedures MUST be applied to the Request-URI of the request .
|
121
|
+
A dialog contains certain pieces of state needed for further message transmissions within the dialog.
|
122
|
+
This state consists of the dialog ID, a local sequence number (used to order requests from the UA
|
123
|
+
to its peer), a remote sequence number (used to order requests from its peer to the UA), a local URI,
|
124
|
+
a remote URI, remote target, a Boolean flag called "secure", and a route set, which is an ordered
|
125
|
+
list of URIs. The route set is the list of servers that need to be traversed to send a request to
|
126
|
+
the peer.
|
127
|
+
|
128
|
+
The route set MUST be set to the list of URIs in the Record-Route header field from the request, taken
|
129
|
+
in order and preserving all URI parameters. If no Record-Route header field is present in the request,
|
130
|
+
the route set MUST be set to the empty set. This route set, even if empty, overrides any pre-existing
|
131
|
+
route set for future requests in this dialog. The remote target MUST be set to the URI from the
|
132
|
+
Contact header field of the request.
|
133
|
+
|
134
|
+
The UAC uses the remote target and route set to build the Request-URI and Route header field of the request.
|
135
|
+
|
136
|
+
If the route set is empty, the UAC MUST place the remote target URI into the Request-URI.
|
137
|
+
The UAC MUST NOT add a Route header field to the request.
|
138
|
+
|
139
|
+
If the route set is not empty, and the first URI in the route set contains the lr parameter (see Section 19.1.1),
|
140
|
+
the UAC MUST place the remote target URI into the Request-URI and MUST include a Route header field
|
141
|
+
containing the route set values in order, including all parameters .
|
142
|
+
|
143
|
+
If the route set is not empty, and its first URI does not contain the lr parameter, the UAC MUST
|
144
|
+
place the first URI from the route set into the Request-URI, stripping any parameters that are not
|
145
|
+
allowed in a Request-URI. The UAC MUST add a Route header field containing the remainder of the
|
146
|
+
route set values in order, including all parameters. The UAC MUST then place the remote target URI
|
147
|
+
into the Route header field as the last value. "
|
148
|
+
|
149
|
+
As usual Sipper allows multiple ways to set the pre-existing route set.
|
150
|
+
1. It can be configured system wide using the configuration parameter SipperConfigurator[:PreExistingRouteSet]
|
151
|
+
2. This can be overridden by a controller directive pre_existing_route_set <array> which affects all
|
152
|
+
initial requests created by this controller. [see e.g. test_controller_using_pre_existing_rs1.rb]
|
153
|
+
3. This can be overridden while creating session and passing arguments to it.
|
154
|
+
[see e.g. test_controller_using_pre_existing_rs2.rb]
|
155
|
+
|
156
|
+
Note:
|
157
|
+
The route set handling, target selection and strict router handling is all done automatically by Sipper,
|
158
|
+
however for testing purposes if it is ever required to modify the request-uri or change the Route headers
|
159
|
+
(differently from the normal protocol behavior), the message can be changed prior to sending in any way.
|
160
|
+
|
161
|
+
|
162
|
+
|
163
|
+
Session Invalidation
|
164
|
+
--------------------
|
165
|
+
When session.invalidate is called then the session does not immediately terminate. This is so because there
|
166
|
+
may be some messages in the network owing to retransmissions that this session should deal.
|
167
|
+
Therefore the session is scheduled for invalidation at a time configured in as SipperConfigurator option
|
168
|
+
:SessionTimer. This is global setting for every session which can be overridden at the session level by
|
169
|
+
either calling session.session_timer = xxx or by setting the controller directive "session_timer".
|
170
|
+
The value of this parameter is in milliseconds.
|
171
|
+
|
172
|
+
Further the invalidate() method takes a boolean argument (force) which if set invalidates the session
|
173
|
+
immediately.
|
174
|
+
|
175
|
+
When the timed session invalidation is going to happen the controller can optionally implement
|
176
|
+
session_being_invalidated_ok_to_proceed?(session) and return false if they want to increase the life
|
177
|
+
of session. They can also set a new session timer value by calling session.set_session_timer(val) which
|
178
|
+
then becomes the new invalidation timer increment.
|
179
|
+
This extension cannot however go on indefinitely, there is an upper limit to the session lifetime
|
180
|
+
which by default comes from the configuration parameter :SessionLimit. This can also be set by the
|
181
|
+
controller directive "session_limit" which roughly becomes the overall maximum lifetime of the session.
|
182
|
+
"Roughly" because if the session has lived for time x, the session timer is x' and the
|
183
|
+
session limit is set for y where y > x but x + x' > y then the session will not re-schedule but
|
184
|
+
invalidate. In other words if the increment is such that it will increase the lifetime beyond
|
185
|
+
session limit then it is not re-scheduled. See the test case "test_session_lifetime" to see the
|
186
|
+
example of usage of these timers.
|
187
|
+
|
188
|
+
Session Limit Cleanup Timer
|
189
|
+
---------------------
|
190
|
+
The session timer kicks in only when the invalidate call is made. For situations where the controller
|
191
|
+
never calls invalidate on session there is a overarching cleanup timer whose value is set to the
|
192
|
+
:SessionLimit that cleans up these session resources regardless.
|
193
|
+
|
194
|
+
|
195
|
+
|
196
|
+
Assigning Headers
|
197
|
+
-----------------
|
198
|
+
You can do simple assignments as msg.from = "myfrom" which will add the header.
|
199
|
+
While assigning headers the argument must be a string.
|
200
|
+
In case you have a multivalued header you can do either of
|
201
|
+
1. msg.route = "route1, route2, route3"
|
202
|
+
2. msg.route = ["route1", "route2", "route3"]
|
203
|
+
3. msg.add_route("route1").add_route("route2").add_route("route3")
|
204
|
+
4. msg.push_route("route3").push_route("route2").push_route("route1")
|
205
|
+
|
206
|
+
Header can be accessed by either the accessor eg. msg.header_name or by the hash access
|
207
|
+
msg[:header_name] note: it is better to use hash access for custom headers because if they are not
|
208
|
+
created you might get a method missing exception.
|
209
|
+
|
210
|
+
todo - elaborate above.
|
211
|
+
|
212
|
+
|
213
|
+
Accessing Headers and Content
|
214
|
+
-----------------------------
|
215
|
+
Headers can be accessed simply by using a method name same as that of the header name. As an example
|
216
|
+
you can access To header by calling request.to or response.to, or for that matter any custom
|
217
|
+
header like request.my_header.
|
218
|
+
Another way to access header value is use the hash access on the message using the header name as the symbol key. As an example you could access the via header using request[:via]. These two methods are almost identical though the latter should be preferred when you are not sure of the existence of an header and want to check it. So you should do something like -
|
219
|
+
if request[:my_header]
|
220
|
+
puts request.my_header
|
221
|
+
end
|
222
|
+
This is particularly relevant for custom headers as Sipper creates the dynamic method access on messages for headers that are known to Sipper, you could of course do things like request.my_header = "foo" in which case Sipper will create the header and also dynamically generate the method definition for such access. However if you are accessing header which has not been assigned so far with method access like request.unknown_header_so_far() then Sipper shall throw an UnknownMethod Exception, the hash access request[:unknown_header_so_far] would correctly return nil.
|
223
|
+
|
224
|
+
Message content can also be accessed in a similar way, but make sure you use the plural form of content method. So request.content() returns only the first line of content while request.contents (note the s in the end) shall return the entire content as an array. (See the section on multivalued headers to see how Sipper uses the same mechanism for multivalued headers as well).
|
225
|
+
However if you want the message content as it is in raw form then you should use the method body() defined on the message. So request.body() shall return the message body as it appeared as a single string.
|
226
|
+
[todo add proper protocol handlers for handling sdp and other content types for proper handling].
|
227
|
+
|
228
|
+
|
229
|
+
Header Params
|
230
|
+
-------------
|
231
|
+
The easiest way to add a param to a header is to just add it by a method invocation. The header should
|
232
|
+
be defined before that. So to add a parameter foo to the header via you could do msg.via.foo = "bar".
|
233
|
+
Note that this way of adding parameter implicitly parses the headers. If you all you want to do is to
|
234
|
+
set a header with some parameter then you can directly add the header as string
|
235
|
+
e.g msg.test_header = "header;foo=bar"
|
236
|
+
|
237
|
+
Multivalued headers
|
238
|
+
-------------------
|
239
|
+
In a SIP message the headers can be accessed by convenient method like access like
|
240
|
+
msg.via. If the header is a multivalued header then access like this gets the top most header of
|
241
|
+
this kind.
|
242
|
+
In order to get the entire set add an "s" to the method. So msg.vias will give you all headers in an
|
243
|
+
ordered array.
|
244
|
+
|
245
|
+
When you want to format multi valued header as separate headers in the message just invoke
|
246
|
+
format_as_separate_headers_for_mv() on the request/response. As an example the following code adds the
|
247
|
+
Allow header using both add() (to the end) and push() (to the top) variations and then sets them to be
|
248
|
+
used headers on separate lines.
|
249
|
+
|
250
|
+
r = Request.create_initial("info", "sip:nasir@codepresso.com", :allow=>"INVITE")
|
251
|
+
r.add_allow("BYE").push_allow("OPTIONS")
|
252
|
+
r.format_as_separate_headers_for_mv(:allow)
|
253
|
+
|
254
|
+
The resultant message looks like -
|
255
|
+
|
256
|
+
INFO sip:nasir@codepresso.com SIP/2.0
|
257
|
+
Contact: <sip:127.0.0.1:5060;transport=UDP>
|
258
|
+
From: Sipper <sip:sipper@127.0.0.1:5060>;tag=1
|
259
|
+
Via: SIP/2.0/UDP 127.0.0.1:5060;branch=z9hG4bK-1-0-1
|
260
|
+
Call-Id: 1-2292@127.0.0.1
|
261
|
+
Max-Forwards: 70
|
262
|
+
To: Sut <sip:sut@127.0.0.1:5066>
|
263
|
+
Cseq: 1 INFO
|
264
|
+
Content-Length: 0
|
265
|
+
Allow: OPTIONS
|
266
|
+
Allow: INVITE
|
267
|
+
Allow: BYE
|
268
|
+
|
269
|
+
Whereas if you do not use format_as_separate_headers_for_mv() for multivalued headers the default is
|
270
|
+
single headers with comma separation.
|
271
|
+
|
272
|
+
So the message would look like -
|
273
|
+
INFO sip:nasir@codepresso.com SIP/2.0
|
274
|
+
Contact: <sip:127.0.0.1:5060;transport=UDP>
|
275
|
+
From: Sipper <sip:sipper@127.0.0.1:5060>;tag=1
|
276
|
+
Via: SIP/2.0/UDP 127.0.0.1:5060;branch=z9hG4bK-1-0-1
|
277
|
+
Call-Id: 1-1804@127.0.0.1
|
278
|
+
Max-Forwards: 70
|
279
|
+
To: Sut <sip:sut@127.0.0.1:5066>
|
280
|
+
Cseq: 1 INFO
|
281
|
+
Content-Length: 0
|
282
|
+
Allow: OPTIONS, INVITE, BYE
|
283
|
+
|
284
|
+
|
285
|
+
Header Parsing
|
286
|
+
--------------
|
287
|
+
|
288
|
+
There is a default parser that is applied to every unknown header which basically parses the header into
|
289
|
+
values and parameters. default_parse? method on such a header returns true. If your new header is such
|
290
|
+
that you need to work on the actual header constituents (for example like Sipper provided Via header) then
|
291
|
+
you may need to provide your own parser for that header.
|
292
|
+
|
293
|
+
Any header in the message is always parsed either by using the named parser or if the parser is not present
|
294
|
+
for a header then the default parser. The string version of header value is always accesible through
|
295
|
+
to_s method on the header. For example for a request say r you would use -
|
296
|
+
r.from.to_s to get the string representation of the From header.
|
297
|
+
|
298
|
+
Assigning anything to the header results in either setting or creation of the named parameter. As an
|
299
|
+
example -
|
300
|
+
request.to.tag = "xyz" results in setting of a tag parameter on the To header of the request.
|
301
|
+
If the parameter is not a known parameter, it is created on the header and can be accessed later by name
|
302
|
+
|
303
|
+
response.via.foo = "bar"
|
304
|
+
Would result in creation of a new parameter foo with value bar on the Via header, something so
|
305
|
+
response.via.to_s woudl then return something like -
|
306
|
+
SIP/2.0/UDP 127.0.0.1:6061;branch=z9hG4bK-2352-1-0;received=127.0.0.2;foo=bar
|
307
|
+
All the Message assignment methods like message.header_name = "xyz", message[:header_name] = "xyz" or
|
308
|
+
message.push or message.add result in parsing of the header value as header object.
|
309
|
+
Any subsequent access of the header returns the appropriate header object. Caling a to_s on the header
|
310
|
+
object returns a properly formatted header string. Also on the header object can be called header_value
|
311
|
+
which returns the header value without the parameters and header_params which returns the parameters
|
312
|
+
in a hash.
|
313
|
+
Depending upon the headers there are various convenience methods provided to access header constituents
|
314
|
+
which are not necessarily header parameters eg. via.protocol, via.transport, from.diaply_name etc.
|
315
|
+
Besides this all the parameters defined on the header are also available as attributes e.g from.tag,
|
316
|
+
via.maddr etc. These attributes can also be changed and they will then affect the header value e.g
|
317
|
+
e.g if via.to_s is "SIP/2.0/TLS 175.19.37.207:6062;branch=z9hG4bK-1-0" and you change the sent_by_port
|
318
|
+
like via.sent_by_port = "6063" then via.to_s now returns "SIP/2.0/TLS 175.19.37.207:6063;branch=z9hG4bK-1-0"
|
319
|
+
|
320
|
+
Force assign a header
|
321
|
+
---------------------
|
322
|
+
In some cases you want to assign a header and bypass the parsing altogether. In that case use
|
323
|
+
Message#assign_unparsed instead of usual assignment. The header thus assigned is a string value and
|
324
|
+
no parser is invoked. Also if you already have a header object and want to assign but not parse a value
|
325
|
+
then Header#assign(value, parse_option) can be used with parse_option as false.
|
326
|
+
e.g. request.from.assign("Nasir Khan <sip:nasir@sipper.com>;tag=1", false) assigns the string to the
|
327
|
+
already defined From header but assigns it without parsing.
|
328
|
+
However, a subsequent call to assign with parse_option as true now parses and assigns the header.
|
329
|
+
|
330
|
+
|
331
|
+
Freezing a header
|
332
|
+
-----------------
|
333
|
+
Header#freeze freezes the header and fixes the value of the header. Any further assignment will fail and
|
334
|
+
the value of the header as of now if also frozen. There is no way to unfreeze a header.
|
335
|
+
|
336
|
+
Providing a custom pluggable parser is extremely
|
337
|
+
easy to do.
|
338
|
+
|
339
|
+
Write your own parser for a new header
|
340
|
+
---------------------------------------------
|
341
|
+
Your parser need to be in the lib/sipper_extensions directory under your controller source tree.
|
342
|
+
It should be a class in the module SipHeaders and the class itself
|
343
|
+
should be a subclass of Header. So for a header called Foo your class will look like
|
344
|
+
|
345
|
+
module SipHeaders
|
346
|
+
class Foo < Header
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
The next thing that you may want to do is to provide an assign method that would parse and populate
|
351
|
+
various header constituents. You may also provide header_value and optionally a private _format and
|
352
|
+
header_params
|
353
|
+
|
354
|
+
|
355
|
+
Extensions Mechanism
|
356
|
+
-------------------
|
357
|
+
With your controller you can also provide an optional extension that can be used to extend the Sipper
|
358
|
+
behavior. Under your lib directory under controller source tree you may provide a sipper_extensions
|
359
|
+
directory. In that you can provide a ruby source file where you may re-open any Sipper class.
|
360
|
+
Using the extensions mechanism you can for example modify an existing parser provided by Sipper.
|
361
|
+
As an example see test_extensions.rb for a sample of how From header formatting can be changed.
|
362
|
+
Note however that this extension is a Sipper level extension and once provided shall be applicable to
|
363
|
+
all controllers henceforth.
|
364
|
+
It is a good practice to "require" the file that is being extended in the extension before it is reopened
|
365
|
+
and changed.
|
366
|
+
|
367
|
+
Stray Messages and Stray Message Handler
|
368
|
+
----------------------------------------
|
369
|
+
A stray message is a subsequent request or response for which no session is found or is an initial
|
370
|
+
request for which no suitable controller could be found.
|
371
|
+
The default behavior to handle a stray message is to silently drop it.
|
372
|
+
An optional stray message handler can be provided that can deal with the stray message in many ways.
|
373
|
+
The stray message handler needs to be a subclass of SIP::StrayMessageHandler class (to be required from
|
374
|
+
stray_message_manager.rb). The stray message handler can be anywhere with the controller, even inline but
|
375
|
+
the preferred place for the handler is the sipper_extensions directory. This is the place from where the
|
376
|
+
extensions are loaded on startup.
|
377
|
+
The only method that needs be implemented by the stray message handler is the handle(message) method
|
378
|
+
that takes the stray message as an argument and is expected to return and array the first element of
|
379
|
+
which is an enum value the values of which are -
|
380
|
+
|
381
|
+
1. SIP::StrayMessageHandler::SMH_DROP
|
382
|
+
2. SIP::StrayMessageHandler::SMH_HANDLED
|
383
|
+
3. SIP::StrayMessageHandler::SMH_RETRY
|
384
|
+
4. SIP::StrayMessageHandler::SMH_TREAT_INITIAL
|
385
|
+
|
386
|
+
Both SIP::StrayMessageHandler::SMH_DROP and SIP::StrayMessageHandler::SMH_HANDLED indicate that Sipper
|
387
|
+
does not need to deal with the message any more.
|
388
|
+
SIP::StrayMessageHandler::SMH_RETRY indicates that the message is to be retried. The message to be
|
389
|
+
retried is taken from the second element of the array returned from the stray message handler if present.
|
390
|
+
The idea is that SMH may modify the request such that it may find a session now. However, the retry
|
391
|
+
happens only once.
|
392
|
+
The return value SIP::StrayMessageHandler::SMH_TREAT_INITIAL indicates that the subsequent request
|
393
|
+
that could not find a session should now be treated as an initial request and retried, again the retry
|
394
|
+
shall happen only once.
|
395
|
+
Of course for the stray message that is was already an initial request or is a response the TREAT_INITIAL
|
396
|
+
option is not applicable.
|
397
|
+
|
398
|
+
To illustrate if a stray request is received then following can be done in the stray message handler -
|
399
|
+
|
400
|
+
1. Drop the request without doing any processing (this is also the default in absence of a handler), this
|
401
|
+
can be accomplished by returning [SIP::StrayMessageHandler::SMH_DROP, nil] from the handler. For an
|
402
|
+
example of dropping the message through the handler see test_stray.rb and test_stray_inline.rb
|
403
|
+
|
404
|
+
2. Create and send a response to the request. For example send a 500 class response. In order to send the
|
405
|
+
response to a stray request the following needs to be done -
|
406
|
+
|
407
|
+
class ResponsiveStrayHandler < SIP::StrayMessageHandler
|
408
|
+
def handle(m)
|
409
|
+
## ["AF_INET", 33302, "localhost.localdomain", "127.0.0.1"]
|
410
|
+
if m.is_request? && m.rcvd_from_info[0] == "AF_INET"
|
411
|
+
remote_ip = m.via.received || m.via.sent_by_ip
|
412
|
+
remote_port = m.via.sent_by_port
|
413
|
+
s = UdpSession.new(remote_ip, remote_port, nil)
|
414
|
+
r = s.create_response(200, "OK", m)
|
415
|
+
s.send(r)
|
416
|
+
s.invalidate(true)
|
417
|
+
end
|
418
|
+
[SIP::StrayMessageHandler::SMH_HANDLED, nil]
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
Of course the first step is definition of a stray message handler class. The message (including stray)
|
423
|
+
has an rcvd_from_info attribute which is an array containing network information on the received message.
|
424
|
+
In this example above, given that the request was received on UDP (AF_INET) we create a UdpSession for
|
425
|
+
sending response. Session is the abstraction for all the communication in Sipper and so we create a
|
426
|
+
session using the remote ip and port from the via header.
|
427
|
+
Usually when you create a response from the session you do not pass the request to create the session
|
428
|
+
from because the session already knows about the incoming request, however in this case it is required
|
429
|
+
to pass in the stray request to session. There is also a minor kink of calling the create_response with
|
430
|
+
not just the response code but also the response phrase "OK". You could alternatively have also used
|
431
|
+
r = s.create_response(200, "SELECT", m) which selects the appropriate phrase from the response phrase
|
432
|
+
dictionary.
|
433
|
+
Always remember to invalidate the session after you create such temporary sessions, if you do not do it
|
434
|
+
the session will eventually get timed out but needlessly hog memory till such time.
|
435
|
+
Finally we must return from the handle() method the result of our actions which in this case is
|
436
|
+
[SIP::StrayMessageHandler::SMH_HANDLED, nil]
|
437
|
+
|
438
|
+
3. Retry the subsequent request after changing something in the request (for example the tags)
|
439
|
+
See the test_stray_retry.rb for an example of retrying the request, in this, arguably contrived example
|
440
|
+
the handler knows about the actual tags from another message parameter, but broadly this illustrates
|
441
|
+
how returning SIP::StrayMessageHandler::SMH_RETRY and the message as the second array element retries the
|
442
|
+
new request for dispatch.
|
443
|
+
Keep in mind that this retry happens only once.
|
444
|
+
class RetryStrayHandler < SIP::StrayMessageHandler
|
445
|
+
def handle(m)
|
446
|
+
m.from.tag = m.from.orig_tg
|
447
|
+
[SIP::StrayMessageHandler::SMH_RETRY, m]
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
4. Retry the request as if it is an initial request. This will allow the Sipper to create a new controller
|
452
|
+
session to deal with the request. An example could be a BYE request that was delivered to a system after
|
453
|
+
a failure and we instead of dropping it want to invoke a controller to handle it for billing purposes.
|
454
|
+
See test_stray_retry_initial.rb for an example. Here the subequent BYE did not find a session as it was
|
455
|
+
invalidated on ACK. Now the handler returns the same stray message (i.e BYE) with a TREAT_INITIAL flag.
|
456
|
+
This results in Sipper creating a new session and finding an appropriate controller, which happens to be
|
457
|
+
the same UAS controller. Another interesting aspect illustrated in this example is the usage of
|
458
|
+
session.force_update_session_map = true in the UAC controller before sending the BYE. Normally the session
|
459
|
+
is added to the session map automatically, in this case however the session is already added as it has
|
460
|
+
both local and remote tags. "session.force_update_session_map = true" forces the entry in the map with
|
461
|
+
the new tags such that when the 200 OK is received for this BYE it reaches the session.
|
462
|
+
|
463
|
+
|
464
|
+
Similarly the stray responses can also be handled, not much is done with the responses except for
|
465
|
+
perhaps sending ACK in some extreme test cases. Usually you would just record the receipt of a response
|
466
|
+
in the stray handler.
|
467
|
+
|
468
|
+
The following example illustrates sending of an ACK from the stray message handler for the response.
|
469
|
+
Please see the test case test_stray_res_acked.rb for a complete flow.
|
470
|
+
|
471
|
+
class ResponseStrayHandler < SIP::StrayMessageHandler
|
472
|
+
def handle(m)
|
473
|
+
## ["AF_INET", 33302, "localhost.localdomain", "127.0.0.1"]
|
474
|
+
if m.is_response? && m.rcvd_from_info[0] == "AF_INET"
|
475
|
+
remote_ip = m.rcvd_from_info[3]
|
476
|
+
remote_port = SipperConfigurator[:LocalTestPort]
|
477
|
+
rts = if m[:record_route]
|
478
|
+
m.record_routes.reverse
|
479
|
+
else
|
480
|
+
nil
|
481
|
+
end
|
482
|
+
s = UdpSession.new(remote_ip, remote_port, rts)
|
483
|
+
r = s.create_initial_request("ACK", m.contact)
|
484
|
+
r.copy_from(m, :from, :to, :call_id)
|
485
|
+
s.send(r)
|
486
|
+
s.invalidate(true)
|
487
|
+
end
|
488
|
+
[SIP::StrayMessageHandler::SMH_HANDLED, nil]
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
492
|
+
Since sending an ACK literally from thin air is not a normal scenario, we may have to write a few
|
493
|
+
extra lines to get it right.
|
494
|
+
In this handler we first extract the remote IP from the rcvd_from_info attribute from the message and
|
495
|
+
since there is no way to find the remote port to which to send the ACK we use the :LocalTestPort
|
496
|
+
from our configuration. To be strictly correct we take the Record-Route header values from the response
|
497
|
+
and create the Route headers by reverting them. Next we create an initial request from a newly created
|
498
|
+
session and copy headers from response and send it. It is important to copy From/To and CallId to be
|
499
|
+
able to match the remote dialog on the UAS.
|
500
|
+
|
501
|
+
|
502
|
+
Message Attributes
|
503
|
+
------------------
|
504
|
+
You can set and get attributes on the Message (both request and response) at any time and
|
505
|
+
retrieve the value later. The attributes are stored in a hash with the message.
|
506
|
+
e.g
|
507
|
+
request.attributes['one'] = 1
|
508
|
+
my_attr = request.attributes['one']
|
509
|
+
|
510
|
+
|
511
|
+
|
512
|
+
Controller Invocation
|
513
|
+
---------------------
|
514
|
+
todo clarify:
|
515
|
+
A controller is a class that is defined to handle requests or responses using the defined API.
|
516
|
+
A controller class must be derived from either the BaseController or from any of the BaseController's
|
517
|
+
children.
|
518
|
+
The controller class name must have the string "Controller" in it. Valid names of controllers are
|
519
|
+
"UacInviteController", "MyTestController1" etc. while "MyTestcontroller" and "MyRandomClass" are not
|
520
|
+
valid controller names.
|
521
|
+
The controllers are read from a controller directory and if there is an order.yaml present then
|
522
|
+
they are tried in that order of invocation. If order.yaml is not present then the order
|
523
|
+
defaults to alphabetical ordering. Even if present order.yaml does not need to list every
|
524
|
+
single controller, only the ones listed will be tried in that order and the remaining default
|
525
|
+
to alphabetical ordering.
|
526
|
+
Of the ordered list of controllers thus present, on a request interested? method is invoked
|
527
|
+
on the controllers, only if they return true is that controller invoked.
|
528
|
+
The default interested? implementation just looks for the presence of the appropriate handling
|
529
|
+
method, like for INVITE it looks for the implementation of "on_invite" method to see the
|
530
|
+
interest.
|
531
|
+
If the controller is defined inline then there is an option to have a method "order" on the
|
532
|
+
controller itself that places this controller on the defined index in the ordered list.
|
533
|
+
Controller interface for upcall of messages are -
|
534
|
+
1. on_request(session) [on_invite, on_bye.....extended to any SIP method including custom methods]
|
535
|
+
2. on_response(session) [on_trying_res, on_provisional_res, on_success_res, on_redirect_res, on_failure_res]
|
536
|
+
3. on_timer(session) for invocation of timers
|
537
|
+
|
538
|
+
Besides this controllers is consulted for order() if implemented and interested?(),
|
539
|
+
again if implemented.
|
540
|
+
|
541
|
+
Apart from this the controller can also implement the callback listener methods for session events
|
542
|
+
|
543
|
+
1. no_ack_received(session) - if controller acted as a UAS and 2xx retrasnmission timer (64*T1) expires.
|
544
|
+
2. no_prack_received(session)
|
545
|
+
3. session_being_invalidated_ok_to_proceed?(session) - when the session is going to be invalidated
|
546
|
+
if the return value from this is false then the session_timer is reset and session is not
|
547
|
+
invalidated. The default value is true if not implemented. If the controllers want to change the
|
548
|
+
session lifetime in this callback they can do so by calling set_session_timer before returning
|
549
|
+
false from this callback method.
|
550
|
+
See test_session_callback_handler.rb to see an example.
|
551
|
+
|
552
|
+
|
553
|
+
Controller Lib
|
554
|
+
--------------
|
555
|
+
The default controller lib dir is controller_directory/lib. This can be changed by using
|
556
|
+
:ControllerLibPath configuration. This is automatically added to load path and so inside the
|
557
|
+
controller you can require any of your lib files. Usually you will put your state machine files
|
558
|
+
or transaction handler files there but it can be any external library too.
|
559
|
+
Important to note that even though the default for :ControllerLibPath is controller_directory/lib but
|
560
|
+
once set it does not change even if you change :ControllerPath subsequently.
|
561
|
+
|
562
|
+
Further there is a mechanism to load arbitrary libraries at the global level which you can do by
|
563
|
+
putting them (nested or otherwise) under the sipper/lib directory. These will be automatically loaded
|
564
|
+
at sipper startup time. In case you want to reload libraries at the top level, for example you have
|
565
|
+
dropped in a plugin module and want it to be reloaded then simply use the method
|
566
|
+
Sipper#reload_libs
|
567
|
+
to reload everything under the sipper/lib directory dynamically.
|
568
|
+
|
569
|
+
You should keep different types of libs in separate directories under
|
570
|
+
the controller lib and top level lib. So the transaction handlers should be placed in a directory
|
571
|
+
separate from state machine drivers, the transport filters should always be placed under the directory
|
572
|
+
transport_filters .
|
573
|
+
See sipper_test/test_controllers/ict_tcbh directory and sipper_test/test_controllers/ctrl_trhandler
|
574
|
+
for an example of the layout.
|
575
|
+
|
576
|
+
|
577
|
+
Controller Directives
|
578
|
+
---------------------
|
579
|
+
|
580
|
+
1. start_on_load <boolean> e.g start_on_load false
|
581
|
+
2. transaction_usage <hash> e.g transaction_usage :use_transactions=>false, :use_ict=>true
|
582
|
+
3. transaction_timers <hash> e.g transaction_timers :t1=>100, :tb=>7000
|
583
|
+
4. transaction_handlers <hash> e.g transaction_handlers :Ict=>MyIctHandler, :Nict=>MyNictHandler, :Base=>CatchAllHandler
|
584
|
+
5. session_timer <num> e.g session_timer 500
|
585
|
+
6. session_limit <num> e.g session_limit 2000
|
586
|
+
7. t2xx_usage <boolean> e.g t2xx_usage true
|
587
|
+
8. t2xx_timers <hash> e.g t2xx_timers :Start=>200, :Cap=>400, :Limit=>1500
|
588
|
+
9. header_order <array> e.g. header_order [:to, :from, :call_id, :via, :cseq]
|
589
|
+
10. pre_existing_route_set <array> e.g. pre_existing_route_set ["sip:nasir@sipper.com;lr", "sip:nasir@goblet.com;lr"]
|
590
|
+
11. use_compact_headers <array> e.g use_compact_headers [:via, :from] or use_compact_headers [:all_headers]
|
591
|
+
|
592
|
+
Ordering of headers
|
593
|
+
-------------------
|
594
|
+
There are many ways to order headers in messages generated by Sipper.
|
595
|
+
(a) Using controller level directive - header_order [:to, :from, :call_id, :via, :cseq] orders the
|
596
|
+
request and responses generated as a result of this controller activity in the given order.
|
597
|
+
|
598
|
+
(b) Set at the session level. Session#set_header_order(order_arr). Sets the header order array to the
|
599
|
+
session and that applies to all the messages after this invocation. This overrides any setting
|
600
|
+
that the controller set using header_order directive.
|
601
|
+
e.g session.set_header_order([:to, :from, :call_id, :via, :cseq])
|
602
|
+
|
603
|
+
(c) Setting directly on the message. This is the local scope limited to the message is question and
|
604
|
+
does not affect other messages from the session. This overrides both the directive setting and
|
605
|
+
also the session level setting.
|
606
|
+
e.g. request.header_order([:to, :from, :call_id, :via, :cseq])
|
607
|
+
|
608
|
+
|
609
|
+
Using Compact headers
|
610
|
+
---------------------
|
611
|
+
In coming message is transparently parsed for any known compact form. As for outgoing messages there
|
612
|
+
are three options.
|
613
|
+
|
614
|
+
(a) A controller level directive - e.g use_compact_headers [:via, :from] can be used to use cpmpact
|
615
|
+
headers in all messages generated by this controller for all sessions.
|
616
|
+
|
617
|
+
(b) Set at the session level. Session#set_compact_headers(array). Sets for the session and all messages
|
618
|
+
that are sent from this session after the setting.
|
619
|
+
|
620
|
+
(c) Directly at the message. This has the scope limited to just this message in question.
|
621
|
+
e.g request.compact_headers = [:to, :from]
|
622
|
+
|
623
|
+
In all the above cases a special symbol :all_headers can be used to enable setting of all headers
|
624
|
+
as compact which have a compact notation.
|
625
|
+
e.g use_compact_headers :all_headers
|
626
|
+
|
627
|
+
|
628
|
+
|
629
|
+
Session Recording and Call Flow validation
|
630
|
+
------------------------------------------
|
631
|
+
Session recording can be enabled by creating a request with header P-Session-Record with value
|
632
|
+
either msg-info or msg-debug.
|
633
|
+
With msg-info only the message type and their direction is recorded while with msg-debug the whole
|
634
|
+
message is recorded.
|
635
|
+
|
636
|
+
e.g Request.create_initial("invite", "sip:nasir@sipper.com", :p_session_record=>"msg-info")
|
637
|
+
|
638
|
+
The session is recorded in a yaml file whereever the configured value for :SessionRecordPath is.
|
639
|
+
This recording is conveniently made available through helper methods in the SipTestCase class.
|
640
|
+
To make it even simpler for the test writer the DrivenSipTestCase class provides a mechanism for
|
641
|
+
automatic validation based on expectation provided as regular expressions.
|
642
|
+
Please see as an example the test test_cancel.rb to see a simple automatic validation of expectation
|
643
|
+
based.
|
644
|
+
In that you will also see the usage of message recorded by the controller which is logged as a "neutral"
|
645
|
+
message. This message does not have any direction so the direction elemnet in the message is recorded
|
646
|
+
as "!". Note the direction can be "<" for incoming messages, ">" for outgoing messages and "!" for
|
647
|
+
neutral messages.
|
648
|
+
The expectation can be provided in a simple regular expression language provided for Sipper.
|
649
|
+
|
650
|
+
The following is the description of the regular expression support for expectation validation.
|
651
|
+
|
652
|
+
1. Wildcard is denoted by character "x" and is available for responses only e.g
|
653
|
+
2xx, 1xx, 18x, 4x0. For wildcard responses the first character in the message MUST be a number.
|
654
|
+
For example 1xx and 41x are valid as is 3xx but xxx or x00 are not valid as they do not
|
655
|
+
start with an integer.
|
656
|
+
|
657
|
+
2. Alteration is denoted by the pipe symbol "|". e.g. 1xx|2xx|302 (optional) responses.
|
658
|
+
|
659
|
+
3. Similarly alteration can be used for optional requests "< INVITE|SUBSCRIBE"
|
660
|
+
|
661
|
+
4. Request and response MUST not be mixed in an alteration.
|
662
|
+
|
663
|
+
5. Optional repetition can be provided like "> INVITE {min,max}" where min is the minimum times the
|
664
|
+
incoming INVITE is expected and max is the maximum times the incoming INVITE is expected.
|
665
|
+
|
666
|
+
examples.
|
667
|
+
"> INVITE {2,2}" have exactly 2 outgoing INVITEs.
|
668
|
+
"< 18x {2,3}" have 2 or 3 incoming 18x.
|
669
|
+
"> 1xx {,3}" at most 3 out going 1xx responses.
|
670
|
+
"> INVITE|SUBSCRIBE" exactly one INVITE or SUBSCRIBE.
|
671
|
+
"< 2xx {1,}" at least 1 incoming 2xx responses.
|
672
|
+
|
673
|
+
6. The neutral expectation, the one recorded by the controller is recorded with the direction element
|
674
|
+
"!" and the message string MUST not contain any spaces.
|
675
|
+
|
676
|
+
BNF for an element of expectation is -
|
677
|
+
element = direction 1*SPACE message*(1*PIPE message) 1*SPACE [repetition]
|
678
|
+
direction = ( "<" / ">" / "!" )
|
679
|
+
message = string_with_no_spaces
|
680
|
+
PIPE = "|"
|
681
|
+
SPACE = " "
|
682
|
+
repetition = {[DIGIT] COMMA [DIGIT]}
|
683
|
+
|
684
|
+
To see an example of complex regular expression see the test_expectation_parser or test2xx_retransmission.
|
685
|
+
Valid expressions.
|
686
|
+
|
687
|
+
Invalid Expresssions
|
688
|
+
"> INVITE, < 100 {0,}, > INVITE" -
|
689
|
+
because we are stating that exactly one INVITE should come
|
690
|
+
first and an optional 100 followed by another INVITE. So if 100 does not come then the actual
|
691
|
+
message flow would be two INVITEs in succesion. This would violate the first requirement of
|
692
|
+
having exactly one INVITE as first element. If you want this behavior then you may use
|
693
|
+
"> INVITE {1,}, < 100 {0,}"
|
694
|
+
|
695
|
+
"< 100, < 100" - Here you are are expecting to have two 100 responses. If that is the case then '
|
696
|
+
the way to specify is to use the repetition options. So "< 100 {2,2}" should be used instead.
|
697
|
+
|
698
|
+
|
699
|
+
Complex examples
|
700
|
+
|
701
|
+
|
702
|
+
|
703
|
+
|
704
|
+
Simple Controller Generator
|
705
|
+
---------------------------
|
706
|
+
Simple usage is
|
707
|
+
|
708
|
+
gen_controller.rb <NameOfTheControllerClass> <flow in quotes>
|
709
|
+
e.g. gen_controller.rb TestController "> INVITE, < 180, > UPDATE, < 200, ! Hello, < 200"
|
710
|
+
|
711
|
+
Note that a space is mandatory between direction and message and messages need to be comma separated.
|
712
|
+
Since it does'nt make sense to have a optional outgoing messages for controller generation, if there
|
713
|
+
is found a usage like "> INVITE|SUBSCRIBE" then the controller will only be generated to send the
|
714
|
+
first message. INVITE in this case.
|
715
|
+
|
716
|
+
It is possible to use the neutral direction "!" and a message following it. The only constraint is the
|
717
|
+
message should be a single word with no spaces. So "message received afer 200" is not valid but
|
718
|
+
"message_received_afer_200" is valid.
|
719
|
+
|
720
|
+
If you are using optional incoming message and if one of the message from the optional list is going to
|
721
|
+
be received again then you must use the same optional string in its entirety.
|
722
|
+
|
723
|
+
e.g. > INVITE, < 100, < 180, < 200|202, > ACK, > BYE, < 200
|
724
|
+
|
725
|
+
is not valid as we are expecting a 200 or a 202 to INVITE and generating an ACK, while for the BYE
|
726
|
+
we are expecting just 200. While this would be a valid call flow scenario, it may not work correctly
|
727
|
+
so the right flow should be -
|
728
|
+
|
729
|
+
> INVITE, < 100, < 180, < 200|202, > ACK, > BYE, < 200|202
|
730
|
+
|
731
|
+
Notice the 200|202 even for BYE. This usually would'nt affect the call flow.
|
732
|
+
The generated controller can be run using the run/run_sipper1 script passing it this
|
733
|
+
generated controller as in run_sipper1.rb -c <proper_path_to my_controller.rb>
|
734
|
+
|
735
|
+
Besides the option for messages "<", ">" and log "!" there is also an option of adding a command to the
|
736
|
+
flow string. The command start with an @ sign immediately followed by the command. e.g. "@sleep_ms 300" shall
|
737
|
+
make the flow sleep for 300 milliseconds.
|
738
|
+
Another useful command is @set_timer <time_in_ms>, which schedules a timer and executes the next command or
|
739
|
+
sends out the next message when the timer expires.
|
740
|
+
For a complete list of all commands please refer to documentation for
|
741
|
+
CommandElement class.
|
742
|
+
|
743
|
+
Similarly there is a script to generate the automated test and the accompanying inlined driver
|
744
|
+
controller.
|
745
|
+
|
746
|
+
|
747
|
+
Simple Test Generator
|
748
|
+
---------------------
|
749
|
+
|
750
|
+
Usage for gen_test is -
|
751
|
+
|
752
|
+
gen_test.rb <name_of_test_class> "String of call flow"
|
753
|
+
|
754
|
+
e.g gen_test.rb MyTest "> INVITE, < 200, > ACK"
|
755
|
+
|
756
|
+
The generated test class can be run standalone as
|
757
|
+
|
758
|
+
ruby -I <path to sipper_test> my_test.rb
|
759
|
+
|
760
|
+
If it is a UAS then it will wait for 180 seconds for getting a request before failing the test.
|
761
|
+
This config is part of generated code, which can be modified later.
|
762
|
+
|
763
|
+
|
764
|
+
Generator Scripts
|
765
|
+
-----------------
|
766
|
+
In order to further facilitate the controller and test generation there is a sgen script provided.
|
767
|
+
|
768
|
+
sgen -h
|
769
|
+
|
770
|
+
Options: --version | -v => print version information
|
771
|
+
--help | -h => print this message
|
772
|
+
-c|-t [-r] <class_name> <flow_str>
|
773
|
+
=> -c for controller and -t for test generation
|
774
|
+
=> -r to optionally generate the reverse or opposite
|
775
|
+
flow from flow_str.
|
776
|
+
i.e if flow indicates UAS then generate a UAC etc.
|
777
|
+
=> <class_name> is the name of class to be generated,
|
778
|
+
controller or test.
|
779
|
+
=> <flow_str> is the actual call flow.
|
780
|
+
e.g. '< INVITE, > 100, > 200 {2,7}, < ACK'
|
781
|
+
|
782
|
+
|
783
|
+
|
784
|
+
Run Script
|
785
|
+
----------
|
786
|
+
In order to easily run the controller and/or the test there is a srun script -
|
787
|
+
|
788
|
+
srun -h
|
789
|
+
|
790
|
+
--version | -v => print version information
|
791
|
+
--help | -h => print this message
|
792
|
+
[-i <local_ip>] [-p <local_port>] [-r <remote_ip>] [-o remote_port>] [-c|-t <file_name>]
|
793
|
+
=> -c for controller and -t for test generation
|
794
|
+
=> <file_name> is the name of test or controller class to be run.
|
795
|
+
in its simples usage, you can just start sipper by running it
|
796
|
+
without arguments. The controllers in this case are loaded from
|
797
|
+
the default controller path location.
|
798
|
+
|
799
|
+
The defaults for the controller case are -
|
800
|
+
local_ip = :LocalSipperIP
|
801
|
+
local_port = 5060
|
802
|
+
remote_ip = :DefaultRIP
|
803
|
+
remote_port = :DefaultRP
|
804
|
+
|
805
|
+
And the defaults for test are -
|
806
|
+
local_ip = :LocalSipperIP
|
807
|
+
local_port = :LocalTestPort
|
808
|
+
remote_ip = :DefaultRIP
|
809
|
+
remote_port = :DefaultRP
|
810
|
+
|
811
|
+
If you are running both controller and the test on the same server then make sure than the run options
|
812
|
+
take this into consideration.
|
813
|
+
|
814
|
+
|
815
|
+
String Recording
|
816
|
+
----------------
|
817
|
+
- In order to record only through a stringio object, use the start_controller with boolean true
|
818
|
+
this will create a single stringio object and pass it through the controller to the session.
|
819
|
+
In your controller you need to set the stringio object in your session.
|
820
|
+
So you should do something like "u.record_io = yield if block_given? " in your start() method.
|
821
|
+
Hopefully this manual step would go away.
|
822
|
+
You would anyway need to have the P-Session_Record header set properly or a system wide configuration.
|
823
|
+
:SessionRecord.
|
824
|
+
|
825
|
+
|
826
|
+
- The recording is automatically read and verified using a helper for that in SipTestCase class.
|
827
|
+
|
828
|
+
|
829
|
+
Automatic validations
|
830
|
+
---------------------
|
831
|
+
Simple validation methods are provided on session to check the presence/value of header or header
|
832
|
+
parameters. [todo automatically generate the code for such validations]. The presence of a header
|
833
|
+
can be checked by adding just one line in the controller. Session#validate_presence_of_headers e.g
|
834
|
+
|
835
|
+
def on_success_res(s)
|
836
|
+
s.validate_presence_of_headers :cseq, :test_header
|
837
|
+
end
|
838
|
+
|
839
|
+
to test the presence of CSeq and Test-Header in the message (in this case response). If found then
|
840
|
+
an expectation element "! true" is added to the recording automatically. So if you are not using
|
841
|
+
the generator tool to create the test case you shall need to add the expectaion element "! true" at
|
842
|
+
the right place. e.g
|
843
|
+
"> INVITE, < 200, ! true" etc. in your test.
|
844
|
+
In case the header is not present then the recording will be anything other than "! true". It actually
|
845
|
+
prints what header was not found in the message.
|
846
|
+
|
847
|
+
Similar to validating the headers you can also validate the presence of header parameters using
|
848
|
+
Session#validate_presence_of_header_params(hdr, *params)
|
849
|
+
e.g
|
850
|
+
|
851
|
+
def on_invite(session)
|
852
|
+
session.validate_presence_of_header_params :test_header, :mytag
|
853
|
+
end
|
854
|
+
validates that the header Test-Header exists and also contains a parameter called "mytag". Similar to
|
855
|
+
the header validation this also records a "! true" if the parameter is found.
|
856
|
+
|
857
|
+
Note that if there are many parameters or headers that you are expecting then you should use the
|
858
|
+
repetition option in the expectations like "! true {3,3}". Using "! true, !true, ! true" would be
|
859
|
+
an invalid expression and would fail with a message like -
|
860
|
+
"Expected= ! true between 1 and 1 Actual= ! true"
|
861
|
+
This could never happen of course if you are using generators to write your tests.
|
862
|
+
|
863
|
+
You can also validate the header values by using the validation - validate_header_values it takes an argument
|
864
|
+
hash with key as the header name and value as the header value..
|
865
|
+
|
866
|
+
e.g session.validate_header_values :test_header_response=>"foo"
|
867
|
+
validates that the header Test-Header-Response has value "foo". The validation needs a "! true" at the
|
868
|
+
appropriate place in the expectation string.
|
869
|
+
|
870
|
+
|
871
|
+
For examples on all the validations please refer to test_validations.rb
|
872
|
+
|
873
|
+
|
874
|
+
Writing Tests
|
875
|
+
-------------
|
876
|
+
|
877
|
+
todo BaseTestCase
|
878
|
+
todo SipTestCase
|
879
|
+
todo DrivenSipTestCase
|
880
|
+
|
881
|
+
In either of the type of test cases if you are using setup() then remember that setup() is called before
|
882
|
+
every test method. You must always call super() before doing anything in your setup().
|
883
|
+
e.g
|
884
|
+
def setup
|
885
|
+
super
|
886
|
+
my_setup_step1
|
887
|
+
my_setup_step2
|
888
|
+
........
|
889
|
+
end
|
890
|
+
Similarly if you are having a teardown() method then you must call super() after you have
|
891
|
+
done your cleanup.
|
892
|
+
|
893
|
+
e.g
|
894
|
+
def teardown
|
895
|
+
my_cleanup_step1
|
896
|
+
my_cleanup_step2
|
897
|
+
..........
|
898
|
+
end
|
899
|
+
|
900
|
+
|
901
|
+
setup_once
|
902
|
+
----------
|
903
|
+
Further in sipper we have another type of setup step called setup_once(). You can override this
|
904
|
+
method in your tests too. As the name implies this will invoked just once when the test class is run
|
905
|
+
for the first time. You may place one time setup stuff in this method like definition of inline
|
906
|
+
controllers. [See test_controller_using_nict.rb for an example]. As with setup super() needs to be
|
907
|
+
called as a first step in setup_once too.
|
908
|
+
|
909
|
+
|
910
|
+
|
911
|
+
|
912
|
+
Signaling completion of a test
|
913
|
+
------------------------------
|
914
|
+
|
915
|
+
SIP is a asynchronous protocol and furthermore as the call flow may vary on a case by case basis.
|
916
|
+
No one but the client can say for sure when the call flow has actually ended. Instead of sleeping in
|
917
|
+
the test case waiting for all signaling to end there is a better way to signal completion.
|
918
|
+
|
919
|
+
Your controller should be a subclass of SipTestDriverController and your test case should be a subclass of
|
920
|
+
DrivenSipTestCase.
|
921
|
+
The only thing different that you need to do now is to invoke "session.flow_completed_for(test_class_name)"
|
922
|
+
in your controller when your flow actually completes.
|
923
|
+
If you do so then the test can proceed to capture results or do something
|
924
|
+
else without any sleep or wait as the test will be signaled under the covers, however if you do not call
|
925
|
+
"flow_completed_for" then the test will block a default 5 seconds before proceeding (config :WaitSecondsForTestCompletion).
|
926
|
+
If you are writing both UAC and UAS in the same system then in that case it is likely that the flow completes
|
927
|
+
last in a UAS, so it is better to invoke "flow_completed_for" in the UAS.
|
928
|
+
It is possible to run the test and controller(s) on different nodes and still make use of this
|
929
|
+
completion signaling feature. The only thing that you would need to do is to make sure that :TestServerName
|
930
|
+
and :TestServerPort configuration is set properly. The value for this should be same for all the nodes
|
931
|
+
in the setup regardless of their role.
|
932
|
+
[todo elaborate on this with examples]
|
933
|
+
There is one limitation with using Driven test cases and that is the controller will need to be started from
|
934
|
+
the test case itself. e.g.
|
935
|
+
start_named_controller("SipIct::UacIctTcbhController")
|
936
|
+
You may not use the "start_on_load :true" directive with Driven test cases.
|
937
|
+
|
938
|
+
|
939
|
+
The session recording is written out when the session is invalidated, as the session is invalidated based on
|
940
|
+
a timer by default the signaling actually happens after session invalidation.
|
941
|
+
|
942
|
+
|
943
|
+
In case you are not using Driven test cases then you just want to make sure that you wait enough to just get
|
944
|
+
the recording out to the file. To do that you can enable a configuration option :EnableRecordingLock which
|
945
|
+
uses file locking mechanism to ensure that recording files are written and read under proper locks.
|
946
|
+
|
947
|
+
Inline Controllers
|
948
|
+
-------------------
|
949
|
+
Besides defining the controllers in a FS location and providing the controller path at startup time
|
950
|
+
you can also define the controller inline in a string and load and execute it from where
|
951
|
+
you create sipper instance. For examples see sipper.rb and specialized form in test cases
|
952
|
+
in test_inline_controller.rb
|
953
|
+
|
954
|
+
It is oftentimes advisable to use the timer granularity value to base your tests on, rather than hard
|
955
|
+
coded values of times. If you are using the granularity in the inline tests then you shall have to use
|
956
|
+
inline interpreted variables [todo right name?] like #{var} eg.
|
957
|
+
|
958
|
+
def setup_once
|
959
|
+
super
|
960
|
+
@grty = SIP::Locator[:Sth].granularity
|
961
|
+
str = <<-EOF
|
962
|
+
require 'sip_test_driver_controller'
|
963
|
+
class UacNictController1 < SIP::SipTestDriverController
|
964
|
+
transaction_timers :t1=>#{@grty*2}
|
965
|
+
....
|
966
|
+
EOF
|
967
|
+
end
|
968
|
+
|
969
|
+
One important consideration while writing Inline controllers is the usage of variables while
|
970
|
+
inlining them in a string.
|
971
|
+
In Ruby while constructing a string you can inline local variable or instance variables e.g.
|
972
|
+
x = 1
|
973
|
+
puts "The value of x is #{x}"
|
974
|
+
prints "The value of x is 1"
|
975
|
+
|
976
|
+
or with instance variables as
|
977
|
+
|
978
|
+
@iv = "hello"
|
979
|
+
puts "The instance variable is #@iv"
|
980
|
+
prints "The stance variable is hello"
|
981
|
+
|
982
|
+
Now while you are writing the inline controller, the entire inline controller definition is itself
|
983
|
+
a string inside the test case. If you are trying to use strings like
|
984
|
+
|
985
|
+
tid = session.irequest.transaction.object_id # ask for Demeter's forgiveness
|
986
|
+
logd("Received INFO with transcation #{tid}")
|
987
|
+
|
988
|
+
it will not work in inline controller case, while it shall work just fine with the external controller.
|
989
|
+
This is a limitation because Ruby tries to evaluate the string in the context of the test case
|
990
|
+
and does not find the varaible "tid" in that scope.
|
991
|
+
The way out for inline controllers for such uses is to explicitly concatenate the string like
|
992
|
+
|
993
|
+
tid = session.irequest.transaction.object_id # ask for Demeter's forgiveness again!
|
994
|
+
logd("Received INFO with transaction " + tid.to_s)
|
995
|
+
|
996
|
+
|
997
|
+
|
998
|
+
Controllers Using State Machine Compiler
|
999
|
+
----------------------------------------
|
1000
|
+
SIP is a stateful protocol and so are your controllers. For any relatively complex controller you
|
1001
|
+
will end up writing non-trivial flows.
|
1002
|
+
While Sipper makes it a extremely simple to write out a controller in minutes, you still will have to
|
1003
|
+
somehow maintain state for your business logic. As an example your controller may listen for 200 OK
|
1004
|
+
responses, so you simple implement the "on_success_res()" method. Now what if you issue different
|
1005
|
+
requests and receive 200 for all of them but your behavior is different for say a 200 for INVITE and
|
1006
|
+
200 for INFO. Also you could treat the same type of request differently based on CSeq or something
|
1007
|
+
else, like your business code.
|
1008
|
+
What will happen is that you will save the state of controller in session parameter session["state"]
|
1009
|
+
and end up writing a series of if/else or case/when statements. This is fine even preferable for a
|
1010
|
+
controller with say 6-7 message exchanges, as it is simple and readable, but for any exchange more than
|
1011
|
+
10 it becomes unreadable fast.
|
1012
|
+
State machine patterns are a well known pattern described in several books and reference material on
|
1013
|
+
patterns, but perhaps one the best books it is dealt with is Robert Martin's
|
1014
|
+
"Agile Software Development, Principles, Patterns, and Practices".
|
1015
|
+
There is an open source implementation of the FSM pattern at http://smc.sourceforge.net which we are
|
1016
|
+
using extensively in Sipper.
|
1017
|
+
In order to use this state machine compiler you will have to be conversant with the state machine
|
1018
|
+
description language that Uncle Bob invented. It is fairly simple and there is a tutorial at
|
1019
|
+
http://smc.sourceforge.net. For the purposes of writing controllers you should be up on your feet in
|
1020
|
+
a matter of few hours. There is a simple test case "test_smc_controller.rb" in this distro to help you.
|
1021
|
+
The steps to use this pattern are -
|
1022
|
+
|
1023
|
+
1. Create the .sm file of your interactions. The context class will be your controller object.
|
1024
|
+
As controller objects are themselves stateless you should pass Session object to state machine
|
1025
|
+
if you want a callback to be invoked in the context of a session.
|
1026
|
+
2. Compile the state machine file into Ruby source using the smc compiler.
|
1027
|
+
3. Keep the Ruby source in the lib directory of controller which defaults to controller_dir/lib, you
|
1028
|
+
could override it by using c.
|
1029
|
+
4. Next thing is to write controllers and instantiate the state machine object from within the
|
1030
|
+
controller from which you want to use this pattern. You should save the instance as a session
|
1031
|
+
attribute.
|
1032
|
+
|
1033
|
+
Note: When you are using smc and when the state machine calls back into the context call back function it
|
1034
|
+
first clears the state, Only getting the control back it proceeds to change/advance the state.
|
1035
|
+
So if you try to check the state in call back functions (basically in transit) you will get an exception.
|
1036
|
+
< elaborate on the example >
|
1037
|
+
|
1038
|
+
|
1039
|
+
Simple Usage
|
1040
|
+
------------
|
1041
|
+
request_with and respond_with has been added to simplify the usage. Also you do the same thing for both
|
1042
|
+
initial and subsequent requests.
|
1043
|
+
Both request_with and respond_with methods return the request or response sent as their return values.
|
1044
|
+
|
1045
|
+
Logging
|
1046
|
+
-------
|
1047
|
+
|
1048
|
+
|
1049
|
+
Usage of CANCEL and Transactions
|
1050
|
+
--------------------------------
|
1051
|
+
|
1052
|
+
Sending CANCEL
|
1053
|
+
--------------
|
1054
|
+
CANCEL can be sent either by creating one using session#create_cancel and then a subsequent session#send or
|
1055
|
+
directly using session#create_and_send_cancel_when_ready. The latter shall send CANCEL
|
1056
|
+
if a provisional response is received and if not received so far then it shall buffer the CANCEL request
|
1057
|
+
and send automatically when a provisional response is received.
|
1058
|
+
Also if strict protocol compliance is in use then a CANCEL can only be sent only if a provisional response
|
1059
|
+
is received, trying to send it using session#send shall raise an exception. However if the :ProtocolCompliance
|
1060
|
+
check is lax then not only can a CANCEL be sent anytime but also if the CANCEL is buffered in anticipation
|
1061
|
+
of a 1xx response and actually a final response comes instead, the CANCEL is sent if compliance is lax and
|
1062
|
+
not otherwise.
|
1063
|
+
|
1064
|
+
If ICT is in use and a CANCEL is sent then a timer (called Timer Y in Sipper) is started which would fire
|
1065
|
+
if a 487 is not received for the ICT. The default is 64xT1.
|
1066
|
+
In case a 487/INVITE is not received and Timer Y fires then a locally generated 408 response is sent up the
|
1067
|
+
stack to the controller. Since the 408 is generated locally an ACK by the transaction is not sent as
|
1068
|
+
would be the case if a 487 response were received.
|
1069
|
+
Of course NICT for CANCEL is a separate but associated transaction and completes independently of the ICT.
|
1070
|
+
|
1071
|
+
|
1072
|
+
Receiving CANCEL
|
1073
|
+
----------------
|
1074
|
+
As for sending 481/CANCEL when no server transaction was found the setting should be such that both
|
1075
|
+
IST and NIST are in use. Sipper looks for the server transaction that is going to be cancelled and as the
|
1076
|
+
server transaction, though usually INVITE can well be non-INVITE both types of transactions should be in
|
1077
|
+
use in configuration. Also the 481/CANCEL is generated by the CANCEL NIST.
|
1078
|
+
|
1079
|
+
|
1080
|
+
|
1081
|
+
Request Rejection
|
1082
|
+
-----------------
|
1083
|
+
Though controller can generate any responses, rejection of a request with an error (> 399) responses are
|
1084
|
+
special in that the request is rejected. todo add behavior abour Session#reject_request_with
|
1085
|
+
todo describe Sipper rejection, controller rejection and strict/lax behavior.
|
1086
|
+
|
1087
|
+
|
1088
|
+
Timers
|
1089
|
+
------
|
1090
|
+
Timer facility added, for controllers the primary interface is session.schedule_timer_for() and the
|
1091
|
+
controllers should implement the on_timer(session, task) method.
|
1092
|
+
There are 3 types of timers.
|
1093
|
+
- app - set by controllers and handled as defined above.
|
1094
|
+
- session - set at the session level for things like retransmission of 200 OK to INVITE or cleanup
|
1095
|
+
- transaction - set from a certain transaction.
|
1096
|
+
|
1097
|
+
Each timer has a target of which the on_timer_expiration method is called, for both :app and :session
|
1098
|
+
types of timers the target is the concerned session object. Though any infrastructure class that implements a
|
1099
|
+
on_timer_expiration(SIP::TimerTask) can be a timer target and can set a timer using SipTimerHelper, but
|
1100
|
+
for Sipper so far there are just two types of targets - Session and Transcations. Session is the target for
|
1101
|
+
both Session level timers and also for app level timers, whereas Transaction is the target for transcation
|
1102
|
+
timers. (Note there is no constraint of any kind to not having additional timer types, no change is required
|
1103
|
+
if you create a new timer type :foo and schedule it using the timer manager.)
|
1104
|
+
|
1105
|
+
TimerManager started from Sipper main is the class that controls the timer thread and has only
|
1106
|
+
one configuration paramater i.e Timer granularity. The default granularity is 50ms.
|
1107
|
+
|
1108
|
+
TimerTask is the actual timer task which is passed around on schedule or invocation and can optionally
|
1109
|
+
carry a closure such that the entity that scheduled the task can access the context. For controllers
|
1110
|
+
this is just a block passed to the schedule_timer_for() method.
|
1111
|
+
The timer id (tid) argument is meant to disambiguate if several timers are set from the same entity.
|
1112
|
+
|
1113
|
+
[As an example test_inline_controller uses the timer facility]
|
1114
|
+
|
1115
|
+
SipTimerHelper is an infrastructure class that helps in setting of various types of timers and shall
|
1116
|
+
have configuration for different timer values. ( todo this shall allow context level setting of
|
1117
|
+
timers)
|
1118
|
+
|
1119
|
+
The timers are at first placed in the transport queue so that they do not usurp protocol messages and
|
1120
|
+
if the timer task has the session target then they are queued at the session level queue.
|
1121
|
+
|
1122
|
+
A TimerTask with duration equal to zero is a special task which is allowed to fire instantaneously. This is
|
1123
|
+
required to meet the RFC reqiutrements where we still go the timer route but with 0 value for consistency for
|
1124
|
+
reliable transports. Like Timer D in Invite Client Transaction.
|
1125
|
+
|
1126
|
+
Canceling
|
1127
|
+
---------
|
1128
|
+
A TimerTask after it is created can be canceled by calling cancel() on it. If a timer is canceled then
|
1129
|
+
it is not invoked. If a timer is canceled before it is scheduled then it is not scheduled.
|
1130
|
+
|
1131
|
+
|
1132
|
+
Transport
|
1133
|
+
---------
|
1134
|
+
1. In Request.parse the received= is added to Via if the sent-by and actual IP is different.
|
1135
|
+
2. In Session.on_response the response is dropped if the top via has sent-by that does not belong to
|
1136
|
+
the transport.
|
1137
|
+
3. todo connection oriented protocols.
|
1138
|
+
4. todo in Session.send_request we will flip the transport based on size
|
1139
|
+
5. todo multicast handling of messages
|
1140
|
+
6. For framing if strict protocol check is on we truncate the message on UDP upto the
|
1141
|
+
content length in Message.parse_content. In case the message is truncated and the Content-Length
|
1142
|
+
header is larger than the actual content, Sipper does not automatically send 400 Bad Request
|
1143
|
+
instead it returns the actual content length with content_len method and alleged content length
|
1144
|
+
by content_length. The controller can if they want generate a 400 Bad Request. See test_message.rb
|
1145
|
+
test case "test_truncated_content".
|
1146
|
+
|
1147
|
+
|
1148
|
+
Transport Filters
|
1149
|
+
-----------------
|
1150
|
+
A simple filter mechanism is provided at the lower most transport layer. There can be two types of filters
|
1151
|
+
Ingress and Outgress filters. These filters can be optionally provided under the controller lib directory
|
1152
|
+
under transport_filters subdirectory.
|
1153
|
+
The purpose of these optional filters is to work on the raw message as received or being delivered to the
|
1154
|
+
socket underneath. The simple use could be to transform the message before sending or just after receiving
|
1155
|
+
on the wire for -
|
1156
|
+
|
1157
|
+
1. Performing compression (SigComp) on the message or change some message attributes.
|
1158
|
+
2. Logging and auditing-Tracking users using a regex match.
|
1159
|
+
3. Content conversion-Scaling maps, and so on.
|
1160
|
+
4. Localization-Targeting the request and response to a particular locale.
|
1161
|
+
5. XSL/T transformations of XML content-Targeting responses to more that one type of client.
|
1162
|
+
6. Message, Header, Content manipulation.
|
1163
|
+
7. Drop incoming or outgoing packet
|
1164
|
+
|
1165
|
+
etc.
|
1166
|
+
|
1167
|
+
In order to use the filters you should create a class that is a subclass of Transport::TransportIngressFilter
|
1168
|
+
if the filter is to transform incoming messages or a subclass of Transport::TransportOutgressFilter if the
|
1169
|
+
filter is required for outgoing messages. The class thus created should just have one method "do_filter(str)"
|
1170
|
+
which takes in a string argument which is the raw message on/from the wire. This method "do_filter" should
|
1171
|
+
return the string after any transformation that it has applied to the message.
|
1172
|
+
In case do_filter returns a nil then the filter chain is broken there and all further message processing
|
1173
|
+
stops, i.e an incoming message is not consumed and an outgoing message is not sent.
|
1174
|
+
See test2xx_retransmission_with_txns for an example of a transport filter that drops a message.
|
1175
|
+
|
1176
|
+
You can also have more than one filters of each type, each of these filters are then invoked sequentially
|
1177
|
+
in a chain. The order of filter invocation by default is arbitrary, if you want them to be processed in a
|
1178
|
+
certain order then you can provide the ordering in a yaml file each for ingress and outgress filters.
|
1179
|
+
The yaml files must be named "in_order.yaml" and "out_order.yaml" respectively. The files should contain
|
1180
|
+
the names of the filter classes in order. e.g
|
1181
|
+
- InFilter1
|
1182
|
+
- InFilter2
|
1183
|
+
- InFilter3
|
1184
|
+
|
1185
|
+
As an example of filter usage see the example under sipper_test/test_controllers/ctrl_trhandler and the test
|
1186
|
+
case test_transport_handler.rb. In this example the filter converts the outgoing INFO message to MESSAGE
|
1187
|
+
request and when the UAS on getting MESSAGE sends the 200/OK it is converted back to 200/OK for INFO before
|
1188
|
+
being delivered to the UAC. i.e.
|
1189
|
+
|
1190
|
+
UAC UAS
|
1191
|
+
>--INFO--(conv MESSAGE) -->----------MESSAGE
|
1192
|
+
<--200/INFO--(conv INFO)--<-------200/MESSAGE
|
1193
|
+
|
1194
|
+
For an example of multiple filters and their ordering mechanism also look at
|
1195
|
+
sipper_test/test_controllers/multi_trhandlers and the associated test case at test_transport_multi_handler.rb
|
1196
|
+
|
1197
|
+
Controller Level Filter for Testing
|
1198
|
+
-----------------------------------
|
1199
|
+
The transport filter chains are at a global level. They apply to all the incoming messages and
|
1200
|
+
outgoing messages. Sometimes you may want them at the individual controller level when
|
1201
|
+
you are writing a test case with inlined controller. In that case the trick is to define
|
1202
|
+
the filter not in the transport_filter directory but inline with the controller.
|
1203
|
+
Also before defining the filter inline it is important to clear all filters. It is
|
1204
|
+
advisable to define such filters in their own namespace i.e wrapped in a module to
|
1205
|
+
protect namespace collisions.
|
1206
|
+
To see an example of filters with inline controllers see test_non2xx_retransmission_with_ist.
|
1207
|
+
|
1208
|
+
|
1209
|
+
|
1210
|
+
|
1211
|
+
Transactions
|
1212
|
+
------------
|
1213
|
+
todo elaborate on TU and usage of transaction with examples.
|
1214
|
+
todo also describe "transcation actions", "transitions", "ctxt callback actions" etc and callbacks.
|
1215
|
+
|
1216
|
+
Transaction Usage - todo describe all. [break the description for controller usage / developer usage]
|
1217
|
+
-----------------
|
1218
|
+
- Config at the main config level, - SessionConfigurator[:SessionTxnUsage] (global setting), all controllers all sessions
|
1219
|
+
- Settable by controller - todo describe "transaction_usage" directive (see test_base_controller.rb &
|
1220
|
+
test_controller.rb). setting for that controller. All sessions of this controller
|
1221
|
+
- Session level - session.set_transaction_usage (hash_of_options, same as config) or
|
1222
|
+
session.use_ict=true, session.use_transactions=false etc.
|
1223
|
+
- Request level. - the above session setting can be changed on a request by request basis (just before
|
1224
|
+
sending the request).
|
1225
|
+
|
1226
|
+
Transaction Timers from controller - todo describe in detail.
|
1227
|
+
----------------------------------
|
1228
|
+
- Config at the main config level - SipperConfigurator[:TransactionTimers] (todo take info from below)
|
1229
|
+
- Settable by controller - todo describe "transaction_timers" directive in detail.
|
1230
|
+
this is the setting for *all* the transactions under *all* sessions for the given
|
1231
|
+
controller.
|
1232
|
+
|
1233
|
+
- Session level - session.set_transaction_timers(type, tmr_hash) on Session can be used to set timers for a given session
|
1234
|
+
this will affect all the transactions under the given session of the given type. The type can be any one
|
1235
|
+
of the transcation types :Ict, :Nict, :Ist, :Nist or it can be :Base where it sets those values for
|
1236
|
+
all the types of transactions.
|
1237
|
+
|
1238
|
+
- Transaction level - the above session setting can be changed on a request by request basis (just before
|
1239
|
+
sending the request). The setting will affect the transcations after the setting
|
1240
|
+
was changed and any previous txns will remain bound by the timers they were started
|
1241
|
+
with. The setting will override the configured setting for the timers given in the
|
1242
|
+
hash, the rest of the timer values will be taken from the configuration or RFC default
|
1243
|
+
for the ones for which no value is configured. As with Session level this setting is
|
1244
|
+
also available for all transactions or transaction of a particular type.
|
1245
|
+
|
1246
|
+
- On the transaction object - The transaction is associated with the message that is sent or received, the
|
1247
|
+
transaction object can be obtained from the message msg.transaction and on it
|
1248
|
+
the timers can be changed again even after the creation of transactions. However the
|
1249
|
+
timers that are already scheduled shall remain unaffected by this change.
|
1250
|
+
Note however the transaction is created by the TU (Session) just before sending the request
|
1251
|
+
out and so msg.transaction may not be available when say the request is first created.
|
1252
|
+
|
1253
|
+
|
1254
|
+
|
1255
|
+
Transaction Callback Handler
|
1256
|
+
----------------------------
|
1257
|
+
Optionally you can supply a transcation callback handler at the time the transcation is created. All the
|
1258
|
+
transaction transitions have a before_ and after_ method (around the actions) defined which the
|
1259
|
+
handler can use.
|
1260
|
+
todo define transition for each type of transaction in a table.
|
1261
|
+
So for example if the transcation SM defines a transition "provisional" there will be before_provisional and
|
1262
|
+
after_provisional callbacks created. If a transaction callback handler is provided and that handler
|
1263
|
+
implements any of the methods "before_provisional" or "after_provisional" then they will be called before
|
1264
|
+
and after the transition takes place.
|
1265
|
+
The transaction is passed as the argument to the handler. The "before" handler can return some values
|
1266
|
+
affecting the behavior of the state machine.
|
1267
|
+
In the before transition, if the return value "SIP::Transaction::SM_PROCEED_NO_ACTION" then all the
|
1268
|
+
methods starting with "__" (action methods) are masked (no action performed) when the state machine
|
1269
|
+
is actually invoked.
|
1270
|
+
todo define transaction action methods.
|
1271
|
+
The idea is that callback handler will return this only if it
|
1272
|
+
is willing to perform the action itself but still wants the state machine to proceed.
|
1273
|
+
It could return "SIP::Transaction::SM_PROCEED" (default) where the transaction callback is basially a
|
1274
|
+
notification and does not affect the behavior of transaction or associated action methods.
|
1275
|
+
Or it could return "SIP::Transaction::SM_DO_NOT_PROCEED",
|
1276
|
+
in which case the SM ignores this transition altogether.
|
1277
|
+
The "after" transition is just for informational purposes as far as that transition is concerned and any
|
1278
|
+
return value does not have any effect on transition. However the handler can still invoke any method
|
1279
|
+
on the transcation or the associated message of the transaction. As an example the handler
|
1280
|
+
can change the consumption status of message such that it shall or shall not be delivered to the
|
1281
|
+
controller. See test_invite_client_transaction.rb and test_non_invite_client_transaction.rb for callback
|
1282
|
+
examples.
|
1283
|
+
Further the transaction callback handler can also define additional callback methods for transactions like
|
1284
|
+
transport_err(txn), timeout(txn), cleanup(txn) and wrong_state(txn).
|
1285
|
+
if they are interested in these events. These events are all "after" in nature and no return value affect the
|
1286
|
+
transaction behavior. wrong_state() callback indicates that the state change that was attempted is not allowed
|
1287
|
+
by the state machine. No state change or messaging associated with the transition happens.
|
1288
|
+
|
1289
|
+
todo make a table for each transaction with before and after transitions.
|
1290
|
+
|
1291
|
+
The transaction callback handler can be provided from the controller. From the controller you specify the
|
1292
|
+
name of the class for each type of transaction callback handler. like -
|
1293
|
+
|
1294
|
+
transaction_handlers :Ict=>MyIctHandler, :Nict=>MyNictHandler, :Base=>CatchAllHandler
|
1295
|
+
|
1296
|
+
Above is a controller directive where you can specify the class name which will handle the transitions for
|
1297
|
+
each type of transactions. For each transaction a new instance of this class will be created. The :Base
|
1298
|
+
handler can be used for any transaction which does not have a specific handler defined.
|
1299
|
+
If a specific handler class is defined then that is used instead of the Base. The callback handler is invoked
|
1300
|
+
only when it implements the callback methods ("before", "after" or any of the 4 event callbacks i.e transport_err,
|
1301
|
+
timeout, cleanup and wrong_state)
|
1302
|
+
The transaction handler files must be located in the controller/lib directory.
|
1303
|
+
|
1304
|
+
Two important things to note-
|
1305
|
+
1. If you are returning SM_PROCEED_NO_ACTION then you should do all what the state machine was
|
1306
|
+
supposed to do to have the right behavior (unless you do not want it), including setting the
|
1307
|
+
consume flag on the transcation. See "test_controller_using_ict_with_tcbh_no_action"
|
1308
|
+
The default consume status is false unless set explicitly by the transaction or in this case
|
1309
|
+
by the handler.
|
1310
|
+
2. In order for you to control the transaction using the callback handler you MUST set the
|
1311
|
+
SipperConfigurator[:ProtocolCompliance] to lax. This is so because you are taking matters in your
|
1312
|
+
hands and will be doing things that the RFC requires the transaction state machine to do.
|
1313
|
+
|
1314
|
+
|
1315
|
+
Alternatively the transaction handler can be directly set on the session using -
|
1316
|
+
|
1317
|
+
set_transaction_handlers(txn_handler_hash)
|
1318
|
+
e.g. session.set_transaction_handlers(:Ict=>MyIctHandler, :Base=>CatchAllHandler)
|
1319
|
+
|
1320
|
+
Here the class MyIctHandler should be on the Ruby load path, so either it can be part of the controller
|
1321
|
+
lib or it can be placed in the top level lib at sipper/lib.
|
1322
|
+
|
1323
|
+
Transaction handler is a very powerful feature and can be used to do interesting stuff.
|
1324
|
+
As an example in the test case test_invite_retransmission the transaction handler is used
|
1325
|
+
to supress the generation of a 100 Trying response from the transaction, however the transaction
|
1326
|
+
is allowed to proceed in state.
|
1327
|
+
|
1328
|
+
class IstTxnHandler
|
1329
|
+
def before_invite(txn)
|
1330
|
+
def before_invite(txn)
|
1331
|
+
SIP::Transaction::SM_PROCEED
|
1332
|
+
end
|
1333
|
+
txn.__consume_msg(true)
|
1334
|
+
SIP::Transaction::SM_PROCEED_NO_ACTION
|
1335
|
+
end
|
1336
|
+
end
|
1337
|
+
|
1338
|
+
As you can see in this example we are actually re-defining the before_invite() method which is a
|
1339
|
+
trick that can be used to make the handler change its behavior after say a first invocation as
|
1340
|
+
in this case.
|
1341
|
+
Here we do not want 100 Trying to go for the first INVITE (forcing retransmissions) but then we
|
1342
|
+
do want the transaction to proceed and also behave as usual (namely filter retransmissions) for
|
1343
|
+
retransmissions.
|
1344
|
+
Also in the same example you can see that the transaction handler is defined inline with the
|
1345
|
+
controllers.
|
1346
|
+
For another interesting use of transaction handler see test_cancel_ith_ist_without_nist.
|
1347
|
+
|
1348
|
+
Warning about the return value : See the code below
|
1349
|
+
|
1350
|
+
def before_success_final(txn)
|
1351
|
+
txn.__consume_msg(true)
|
1352
|
+
SIP::Transaction::SM_DO_NOT_PROCEED
|
1353
|
+
def before_success_final(txn)
|
1354
|
+
SIP::Transaction::SM_PROCEED
|
1355
|
+
end
|
1356
|
+
end
|
1357
|
+
|
1358
|
+
this is a snippet from ICT transaction handler. This handler basically bypasses the transaction
|
1359
|
+
on getting the first 2xx response but re-defines the method to normaly handle any 2xx
|
1360
|
+
retransmission. However there is a small problem with the code which is not apparent on the
|
1361
|
+
first glance. The return value SIP::Transaction::SM_DO_NOT_PROCEED is not the last statement
|
1362
|
+
in the first execution of the callback. The simple fix is to move the return to the end of the
|
1363
|
+
method.
|
1364
|
+
|
1365
|
+
def before_success_final(txn)
|
1366
|
+
txn.__consume_msg(true)
|
1367
|
+
def before_success_final(txn)
|
1368
|
+
SIP::Transaction::SM_PROCEED
|
1369
|
+
end
|
1370
|
+
SIP::Transaction::SM_DO_NOT_PROCEED # Moved to end to return this value.
|
1371
|
+
end
|
1372
|
+
|
1373
|
+
|
1374
|
+
|
1375
|
+
Separation of transcation and TU
|
1376
|
+
--------------------------------
|
1377
|
+
Even though the callbacks on transaction states look similar to upcall to controller they are quite
|
1378
|
+
different things. It is for this reason there is no API provided to say initiate requests or create
|
1379
|
+
responses from the transaction callback handlers. As these are low level actions the transaction
|
1380
|
+
state may not be amenable to carry out a high level action like messgaing at this time.
|
1381
|
+
The handler however, can access the transaction and the associated message and can invoke some
|
1382
|
+
methods on them, like changing the consumption status [e.g change the status such that a 2xx
|
1383
|
+
retransmission is sent to TU even in completed state of the NICT] or add or remove a header from
|
1384
|
+
the request/response.
|
1385
|
+
|
1386
|
+
|
1387
|
+
Transaction User
|
1388
|
+
---------------
|
1389
|
+
TU creates transaction and starts invoking "Invocation Action" methods. todo describe them
|
1390
|
+
Also for responses received, checks whether to consume them or not based on consume? method return value.
|
1391
|
+
There is only one call back into TU from ICT and that is notification on transport error.
|
1392
|
+
There are 4 TU callbacks that TU should implement.
|
1393
|
+
(a) transaction_transport_err() gets called should CTs get a transport failure
|
1394
|
+
(b) transaction_timeout() gets called on timeout of a transaction
|
1395
|
+
(c) transaction_cleanup() gets called on transaction termination
|
1396
|
+
(d) transaction_wrong_state() gets called when a message is either received or being sent that is not
|
1397
|
+
valid for this state. The state transition and any messaging associated with such action does not
|
1398
|
+
happen.
|
1399
|
+
(e) transaction_record() record the message from the transaction. Only outgoing messages ever get
|
1400
|
+
recorded from this method because the incoming messages always come through the session, which is
|
1401
|
+
where they are recorded. For messages like 4xx/ACK or request retransmissions the session is not
|
1402
|
+
involved and would not be able to record them, so transactions record them.
|
1403
|
+
Besides this Session (which is also TU) is used to create responses or requests like ACKs that transaction
|
1404
|
+
sends.
|
1405
|
+
For the purposes of Sipper, Session is the TU and creates and maintains the transactions. The controller
|
1406
|
+
can optionally provide a transaction handler.
|
1407
|
+
|
1408
|
+
Transaction Timeout
|
1409
|
+
-------------------
|
1410
|
+
According to RFC 3261 8.1.3.1 "Transaction Layer Errors" in case of an Invite Client Transaction
|
1411
|
+
timeout a 408 response is locally generated and sent up to TU or a Transaction handler if a
|
1412
|
+
handler is present. This response will be like any other except that it will return true for
|
1413
|
+
locally_generated?() method defined on the response. As this repsonse is not delivered to the
|
1414
|
+
transaction no ACK is generated for this. (It is a response generated by the transaction itself).
|
1415
|
+
The controllers MUST NOT try to generate any ACK for the locally generated responses.
|
1416
|
+
|
1417
|
+
The 408 response is locally generated on transaction timeout even in case of NICT. The locally
|
1418
|
+
generated response can be tested to be local by invoking response.locally_generated?().
|
1419
|
+
If the response is locally generated then the B2BUA or Proxy MUST not forward the response
|
1420
|
+
according to the provisions of RFC 4320 -
|
1421
|
+
|
1422
|
+
"4.2. Action 2
|
1423
|
+
A transaction-stateful SIP element MUST NOT send a response with
|
1424
|
+
Status-Code of 408 to a non-INVITE request. As a consequence, an
|
1425
|
+
element that cannot respond before the transaction expires will not
|
1426
|
+
send a final response at all."
|
1427
|
+
|
1428
|
+
todo : For Proxy or B2bua implementations have a strict/lax behavior to forward or not the
|
1429
|
+
408 response.
|
1430
|
+
|
1431
|
+
Invite Client Transaction
|
1432
|
+
-------------------------
|
1433
|
+
Timers: The transcation base timers like T1, T2 and also derived timers TimerA, B etc. have 3 levels of
|
1434
|
+
configuration.
|
1435
|
+
1. The bare default is what is recommended in RFC and will be taken if no configuration
|
1436
|
+
override is provided.
|
1437
|
+
2. These defaults can be overridden by a system wide setting for any and all of these timer values
|
1438
|
+
by setting the configuration in SipperConfigurator[:TransactionTimers] hash. eg.
|
1439
|
+
SipperConfigurator[:TransactionTimers] = { :t1=>200 }. Usually derived timers like Timer A, B .. etc
|
1440
|
+
are dependent of T1, T2 etc. This can be overridden in the configuration but be aware that these
|
1441
|
+
timers are made dependent for a reason and if you override them you should carefully consider the
|
1442
|
+
behavior. e.g config SipperConfigurator[:TransactionTimers] = { :t1=>200, :ta=>150, :tb=10500 }
|
1443
|
+
3. Finally this configuration can be overriden on a transaction by transaction basis when the Txn of
|
1444
|
+
any kind is constructed by optionally passing it a block of these timer values with a self qualifier.
|
1445
|
+
e.g ict = Ict.new(transport, tp_flags, rip, rp) { self.t1 = 200; self.ta=100 }. Note the ";" separating
|
1446
|
+
the timers as this is a block not a hash.
|
1447
|
+
4. For reliable transports the TimerA does not start and TimerD starts for instantaneous firing. In case
|
1448
|
+
(say for testing) it is required to run these timers for any transport then all that is required is to
|
1449
|
+
make the transport behave as unreliable for which you just need to extend the transport object with
|
1450
|
+
the SIP::Transport::ReliableTransport module after making a dup object.
|
1451
|
+
See "test_no_timerA_reliable" test case in test_invite_client_transaction.rb.
|
1452
|
+
|
1453
|
+
Strict/Lax behavior of ICT
|
1454
|
+
--------------------------
|
1455
|
+
If 'strict' protocol compliance check is in place and TU is using ICT then you will not be able to send ACK/non-2xx
|
1456
|
+
yourself from the controller (you will obviously be able to send ACK/2xx), instead the ACK
|
1457
|
+
to non-2xx response will be automatically sent from
|
1458
|
+
the ICT. If the compliance check is 'lax' then you can send (if you want for some reason)
|
1459
|
+
the ACK/non-2xx from controller but
|
1460
|
+
note that the ICT will also send its ACK when the response reaches it. If you do not want "two"
|
1461
|
+
ACKs then you should implement an transaction callback handler and on "before_non_success_final()"
|
1462
|
+
callback return the SIP::Transaction::SM_PROCEED_NO_ACTION to stop ICT from performing any action
|
1463
|
+
while still advancing in state. See the test case - TestControllerUsingIctWithTcbhNoAction.
|
1464
|
+
|
1465
|
+
|
1466
|
+
UAS 2xx retransmission behavior
|
1467
|
+
-------------------------------
|
1468
|
+
Independent of transactions the 2xx response from the UAS is to be retransmitted, regardless of the
|
1469
|
+
transport. [section 13.3.1.4 of 3261]. There are 3 levels of configuration that affect this
|
1470
|
+
behavior.
|
1471
|
+
1. The default is to retransmit the 2xx response until the ACK is received or a timeout governed by
|
1472
|
+
times T1, T2 and 64*T1.
|
1473
|
+
2. The usage itself and the timers can be overridden in the configuration using the config options
|
1474
|
+
SipperConfigurator[:T2xxUsage] and SipperConfigurator[:T2xxTimers]. This setting applies to
|
1475
|
+
every session created.
|
1476
|
+
3. The abose setting can be overridden by controller directives "t2xx_usage <boolean>" and
|
1477
|
+
"t2xx_timers <hash>", the hash argument to the directive can contain upto 3 timer values with
|
1478
|
+
names :Start, :Cap and :Limit that correspond to T1, T2 and 64*T1 timers as described in the RFC.
|
1479
|
+
as an example the directive could be -
|
1480
|
+
t2xx_timers :Start=>100
|
1481
|
+
which would set the starting time (equivalent to T1) to 100 msec while others would still be
|
1482
|
+
coming from configuration.
|
1483
|
+
t2xx_timers :Start=>100, :Cap=>400, :Limit=>16000
|
1484
|
+
Sets the 2xx retransmission timer to start at 100 msec, doubles until it reaches :Cap and the
|
1485
|
+
retransmissions continue until :Limit or until ACK is received.
|
1486
|
+
These settings apply to all the sessions created by the controller that uses these directives.
|
1487
|
+
4. These settings can also be set on the Session object itself by using the methods
|
1488
|
+
session.set_t2xx_retrans_usage(boolean) and session.set_t2xx_retrans_timers(hash) which can be
|
1489
|
+
changed on a session by session basis.
|
1490
|
+
|
1491
|
+
See test2xx_retransmission.rb for example code.
|
1492
|
+
|
1493
|
+
|
1494
|
+
|
1495
|
+
Writing a B2BUA
|
1496
|
+
---------------
|
1497
|
+
[todo describe B2BUA entity]. A base B2buaController class is provided to easily create back to back user agents.
|
1498
|
+
A B2BUA controller of yours should be a subclass of SIP::B2buaController e.g
|
1499
|
+
|
1500
|
+
require 'b2bua_controller'
|
1501
|
+
class MyB2buaController < SIP::B2buaController
|
1502
|
+
end
|
1503
|
+
|
1504
|
+
A B2BUA gets a request acting as a UAS and sends it out acting as a UAC with appliction specific
|
1505
|
+
transformations. The first thing that a B2BUA might like to do is to create the UAC session.
|
1506
|
+
The base SIP::B2buaController has methods -
|
1507
|
+
create_peer_session(session, rip=SipperConfigurator[:DefaultRIP],
|
1508
|
+
rp=SipperConfigurator[:DefaultRP] )
|
1509
|
+
to create a new peer session.
|
1510
|
+
|
1511
|
+
get_peer_session(session)
|
1512
|
+
to return an existing peer session, nil otherwise and
|
1513
|
+
|
1514
|
+
get_or_create_peer_session(session, rip=SipperConfigurator[:DefaultRIP],
|
1515
|
+
rp=SipperConfigurator[:DefaultRP] )
|
1516
|
+
|
1517
|
+
that creates or returns a peer session for the given remote IP and remote port if associated with the session
|
1518
|
+
argument, if not specified the default remote IP and port are taken from the configuration
|
1519
|
+
- SipperConfigurator[:DefaultRIP] and SipperConfigurator[:DefaultRP].
|
1520
|
+
The peer session once created is associated with the session passed in argument and vice versa.
|
1521
|
+
This allows for a subsequent call to get_peer_session to get the peer session from either of the
|
1522
|
+
sessions.
|
1523
|
+
create_b2bua_request and create_b2bua_response are methods provided to create the request or response for
|
1524
|
+
the peer leg. This is just for convenience, you can of course create a new request or response, populate
|
1525
|
+
the headers appropriately and send it on the peer session.
|
1526
|
+
|
1527
|
+
As a further convenience relay_request and relay_response methods are provided which simply relay the
|
1528
|
+
request or response on the peer leg without any controller involvement, they do however use the session
|
1529
|
+
specific information like From/To tags, Call-Id, CSeq etc.
|
1530
|
+
At any point the B2BUA can optionally call go_transparent method which does not even require a B2BUA
|
1531
|
+
controller to implement any of the on_xxx methods. If a peer session is defined then the request and
|
1532
|
+
response is relayed without any controller involvement.
|
1533
|
+
|
1534
|
+
Rather than invalidating peer session manually one by one, a convenience method is provided
|
1535
|
+
called invalidate_sessions which invalidates both the B2BUA sessions from one invocation.
|
1536
|
+
|
1537
|
+
It is important to note that if the B2BUA is made to go transparent then it will not be invoked for
|
1538
|
+
incoming requests or responses and therefore will not have an oppprtunity to invalidate sessions.
|
1539
|
+
In such a case the session_limit directive which sets the overall session lifetime is used to clean up
|
1540
|
+
the session resources.
|
1541
|
+
|
1542
|
+
The linked B2BUA sessions are a two way association. One session can be linked to only one other peer
|
1543
|
+
session. Specifically a session can only link one and only one peer, but one session may have linkages
|
1544
|
+
in more than one sessions.
|
1545
|
+
Explicit linking and unlinking is possible using link_sessions and unlink_sessions. So if you want to
|
1546
|
+
create a new session from a b2bua session which already has a linked session you shall have to unlink
|
1547
|
+
the sessions first and then use get_or_create_peer_session to create a new session and its linkage.
|
1548
|
+
|
1549
|
+
To see a very simple B2BUA example see the test_b2bua1.rb. Note that in all the b2bua tests, all the UAC,
|
1550
|
+
B2BUA and UAS are defined in the same test code file, correct routing is made possible by using a
|
1551
|
+
custom header and implementation of the interested? method.
|
1552
|
+
|
1553
|
+
test_b2bua2.rb is an example of a transparent b2bua using the session_limit directive to invalidate
|
1554
|
+
sessions. [Explain with diagrams]
|
1555
|
+
Finally for a complex b2bua example have a loook at test_b2bua3.rb. [Explain with diagrams]
|
1556
|
+
Strict/Lax behavior
|
1557
|
+
-------------------
|
1558
|
+
todo describe in detail what is the strict and lax behavior, also lax is when you are allowed to do
|
1559
|
+
something which are usually a SHOULD NOT. (todo validate this). You can otherwise override a lot of
|
1560
|
+
behavior by transaction machine hooks and other such plugin behavior.
|
1561
|
+
|
1562
|
+
|
1563
|
+
|
1564
|
+
Testing
|
1565
|
+
-------
|
1566
|
+
- If in your tests you need to run Sipper then extend the built in class SipTestCase but remember to
|
1567
|
+
call super as the first thing in your "setup" method if you have one.
|
1568
|
+
- todo indicate difference between BaseTestCase and SipTestCase.
|
1569
|
+
|
1570
|
+
Troubleshooting
|
1571
|
+
---------------
|
1572
|
+
1. If you are seeing errors like -
|
1573
|
+
|
1574
|
+
[ERROR]:[1187887677.97]:[WorkerThread-4]:siplog::udpsession :: Exception can't p
|
1575
|
+
arse uri:druby://: occured while response processing by controller
|
1576
|
+
[ERROR]:[1187887677.97]:[WorkerThread-4]:siplog::udpsession :: d:/ruby/lib/ruby/
|
1577
|
+
1.8/drb/drb.rb:815:in `parse_uri'
|
1578
|
+
|
1579
|
+
Then most likely your test is not derived from DrivenSipTestCase and you are still trying to signal the test using
|
1580
|
+
session.flow_completed_for("MyTest") where MyTest is your test class name. This signaling facility is available only to driven test cases. Note however this might just add some delay in processing the outcome of test case is unaffected by this error.
|
1581
|
+
|
1582
|
+
---------
|
1583
|
+
|
1584
|
+
Done
|
1585
|
+
1. Add the continue recording stuff
|
1586
|
+
2. DA
|
1587
|
+
3. Registeration
|
1588
|
+
4. Media (sdp available from Message)
|
1589
|
+
5. Whole set_attribute story being called repeatedly, explain offer answer model etc
|
1590
|
+
6. Running tests in order, define "order_tests.yaml" and define test file names, make sure
|
1591
|
+
that the name of Test class inside of .rb file is derivable from the file name.
|
1592
|
+
7. on_success_res_for_invite etc document it.
|
1593
|
+
8.
|
1594
|
+
|
1595
|
+
New
|
1596
|
+
1.
|
1597
|
+
2. Goblet release overlay
|
1598
|
+
3. Config "GET xyz", "SET xyz pqr", "SET xyz pqr data_type"
|
1599
|
+
irb(main):082:0> t = TCPSocket.new('127.0.0.1', 4681)
|
1600
|
+
=> #<TCPSocket:0x33ae4ec>
|
1601
|
+
irb(main):083:0> t.puts "SET WaitSecondsForTestCompletion 10"
|
1602
|
+
=> nil
|
1603
|
+
irb(main):084:0> t.gets
|
1604
|
+
=> "10\n"
|
1605
|
+
irb(main):085:0> t.puts "SET LocalSipperIP 192.168.1.2"
|
1606
|
+
=> nil
|
1607
|
+
irb(main):086:0> t.gets
|
1608
|
+
=> "192.168.1.2\n"
|
1609
|
+
irb(main):087:0>
|
1610
|
+
irb(main):088:0* t.puts "SET Abracadabra hello string"
|
1611
|
+
=> nil
|
1612
|
+
irb(main):089:0> t.puts "GET Abracadabra"
|
1613
|
+
=> nil
|
1614
|
+
irb(main):090:0> t.gets
|
1615
|
+
=> "hello\n"
|
1616
|
+
|
1617
|
+
Also io.puts "KEY_NAMES"
|
1618
|
+
io.puts "KEY_DESCRIPTIONS"
|
1619
|
+
|
1620
|
+
4.
|
1621
|
+
|