Sipper 1.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (326) hide show
  1. data/sipper/README.rb +26 -0
  2. data/sipper/b2bua_controller.rb +163 -0
  3. data/sipper/b2bua_session_mixin.rb +24 -0
  4. data/sipper/base_controller.rb +425 -0
  5. data/sipper/bin/common.rb +42 -0
  6. data/sipper/bin/generate.rb +70 -0
  7. data/sipper/bin/project.rb +44 -0
  8. data/sipper/bin/run.rb +85 -0
  9. data/sipper/bin/run_smoke.rb +8 -0
  10. data/sipper/config/log4r.xml +71 -0
  11. data/sipper/controller_class_loader.rb +29 -0
  12. data/sipper/controller_selector.rb +119 -0
  13. data/sipper/controllers/invite_controller.rb +30 -0
  14. data/sipper/controllers/order.yaml +3 -0
  15. data/sipper/custom_message.rb +4 -0
  16. data/sipper/detached_session.rb +11 -0
  17. data/sipper/docs/manual.txt +1621 -0
  18. data/sipper/generators/README +12 -0
  19. data/sipper/generators/gen_controller.rb +228 -0
  20. data/sipper/generators/gen_project.rb +45 -0
  21. data/sipper/generators/gen_test.rb +72 -0
  22. data/sipper/generators/project_template_dir/Rakefile +56 -0
  23. data/sipper/generators/project_template_dir/config/sipper.cfg +31 -0
  24. data/sipper/generators/project_template_dir/controllers/README.txt +2 -0
  25. data/sipper/generators/project_template_dir/dot_sipper.proj +2 -0
  26. data/sipper/generators/project_template_dir/logs/README.txt +2 -0
  27. data/sipper/generators/project_template_dir/tests/README.txt +2 -0
  28. data/sipper/lib/smc/statemap.rb +194 -0
  29. data/sipper/logs/dialog_info_store +0 -0
  30. data/sipper/logs/r.cmd +6 -0
  31. data/sipper/logs/r.sh +6 -0
  32. data/sipper/media/sipper_media_client.rb +268 -0
  33. data/sipper/media/sipper_media_event.rb +43 -0
  34. data/sipper/media/sipper_media_manager.rb +145 -0
  35. data/sipper/media/sipper_media_proxy.rb +60 -0
  36. data/sipper/media/sipper_offer_answer.rb +285 -0
  37. data/sipper/message.rb +512 -0
  38. data/sipper/modified_pattern_formatter.rb +119 -0
  39. data/sipper/proxy_controller.rb +143 -0
  40. data/sipper/registration.rb +52 -0
  41. data/sipper/request.rb +109 -0
  42. data/sipper/response.rb +123 -0
  43. data/sipper/ruby_ext/module.rb +27 -0
  44. data/sipper/ruby_ext/mutable_class.rb +17 -0
  45. data/sipper/ruby_ext/object.rb +38 -0
  46. data/sipper/ruby_ext/pqueue.rb +190 -0
  47. data/sipper/ruby_ext/snapshot.rb +201 -0
  48. data/sipper/ruby_ext/string.rb +18 -0
  49. data/sipper/ruby_ext/time.rb +9 -0
  50. data/sipper/run/run_sipper1.rb +28 -0
  51. data/sipper/run/run_sipper2.rb +56 -0
  52. data/sipper/sdp/sdp.rb +257 -0
  53. data/sipper/sdp/sdp_generator.rb +131 -0
  54. data/sipper/sdp/sdp_parser.rb +136 -0
  55. data/sipper/session.rb +1952 -0
  56. data/sipper/session_manager.rb +170 -0
  57. data/sipper/session_recorder.rb +190 -0
  58. data/sipper/session_state/DialogState.sm +54 -0
  59. data/sipper/session_state/DialogState_sm.rb +337 -0
  60. data/sipper/session_state/dialog_routes.rb +141 -0
  61. data/sipper/sip_headers/header.rb +632 -0
  62. data/sipper/sip_headers/sipuri.rb +352 -0
  63. data/sipper/sip_logger.rb +65 -0
  64. data/sipper/sip_message_router.rb +231 -0
  65. data/sipper/sip_test_driver_controller.rb +10 -0
  66. data/sipper/sipper.rb +329 -0
  67. data/sipper/sipper_assertions.rb +21 -0
  68. data/sipper/sipper_configurator.rb +376 -0
  69. data/sipper/sipper_http/sipper_http_request_dispatcher.rb +71 -0
  70. data/sipper/sipper_http/sipper_http_response.rb +25 -0
  71. data/sipper/stray_message_manager.rb +40 -0
  72. data/sipper/test_completion_signaling_helper.rb +77 -0
  73. data/sipper/transaction/Ict.sm +59 -0
  74. data/sipper/transaction/Ict_sm.rb +430 -0
  75. data/sipper/transaction/Ist.sm +74 -0
  76. data/sipper/transaction/Ist_sm.rb +460 -0
  77. data/sipper/transaction/Nict.sm +51 -0
  78. data/sipper/transaction/Nict_sm.rb +325 -0
  79. data/sipper/transaction/Nist.sm +59 -0
  80. data/sipper/transaction/Nist_sm.rb +356 -0
  81. data/sipper/transaction/invite_client_transaction.rb +274 -0
  82. data/sipper/transaction/invite_server_transaction.rb +319 -0
  83. data/sipper/transaction/non_invite_client_transaction.rb +230 -0
  84. data/sipper/transaction/non_invite_server_transaction.rb +263 -0
  85. data/sipper/transaction/state_machine_wrapper.rb +58 -0
  86. data/sipper/transaction/transaction.rb +212 -0
  87. data/sipper/transport/base_transport.rb +84 -0
  88. data/sipper/transport/rel_unrel.rb +19 -0
  89. data/sipper/transport/transport_and_route_resolver.rb +67 -0
  90. data/sipper/transport/udp_transport.rb +156 -0
  91. data/sipper/transport_manager.rb +33 -0
  92. data/sipper/udp_session.rb +17 -0
  93. data/sipper/util/command_element.rb +62 -0
  94. data/sipper/util/compact_converter.rb +50 -0
  95. data/sipper/util/counter.rb +26 -0
  96. data/sipper/util/digest/digest_authorizer.rb +204 -0
  97. data/sipper/util/expectation_parser.rb +164 -0
  98. data/sipper/util/locator.rb +31 -0
  99. data/sipper/util/message_fill.rb +58 -0
  100. data/sipper/util/persistence/ps_sipper_map.rb +63 -0
  101. data/sipper/util/persistence/sipper_map.rb +41 -0
  102. data/sipper/util/sipper_util.rb +305 -0
  103. data/sipper/util/timer/sip_timer_helper.rb +26 -0
  104. data/sipper/util/timer/timer_manager.rb +80 -0
  105. data/sipper/util/timer/timer_task.rb +56 -0
  106. data/sipper/util/validations.rb +44 -0
  107. data/sipper/version.rb +10 -0
  108. data/sipper_test/_test_media_uas.rb +79 -0
  109. data/sipper_test/base_test_case.rb +31 -0
  110. data/sipper_test/c_134.txt +7 -0
  111. data/sipper_test/driven_sip_test_case.rb +96 -0
  112. data/sipper_test/gold.txt +10 -0
  113. data/sipper_test/gold_res.txt +8 -0
  114. data/sipper_test/gold_sub.txt +9 -0
  115. data/sipper_test/hello_sipper.au +0 -0
  116. data/sipper_test/in_sipper.au +0 -0
  117. data/sipper_test/nonrr_proxy.rb +17 -0
  118. data/sipper_test/order_tests.yaml +4 -0
  119. data/sipper_test/rake_res.txt +399 -0
  120. data/sipper_test/rr_proxy.rb +17 -0
  121. data/sipper_test/run_test.cmd +94 -0
  122. data/sipper_test/sip_test_case.rb +104 -0
  123. data/sipper_test/test2xx_retransmission.rb +80 -0
  124. data/sipper_test/test2xx_retransmission_with_limit.rb +81 -0
  125. data/sipper_test/test2xx_retransmission_with_nist.rb +91 -0
  126. data/sipper_test/test2xx_retransmission_with_txns.rb +94 -0
  127. data/sipper_test/test_address_header.rb +66 -0
  128. data/sipper_test/test_b2bua1.rb +130 -0
  129. data/sipper_test/test_b2bua2.rb +120 -0
  130. data/sipper_test/test_b2bua3.rb +160 -0
  131. data/sipper_test/test_b2bua4.rb +130 -0
  132. data/sipper_test/test_base_controller.rb +110 -0
  133. data/sipper_test/test_base_transport.rb +37 -0
  134. data/sipper_test/test_cancel.rb +21 -0
  135. data/sipper_test/test_cancel_after2xx.rb +81 -0
  136. data/sipper_test/test_cancel_retransmission.rb +105 -0
  137. data/sipper_test/test_cancel_with481.rb +83 -0
  138. data/sipper_test/test_cancel_with487.rb +70 -0
  139. data/sipper_test/test_cancel_with_ist_without_nist.rb +99 -0
  140. data/sipper_test/test_cancel_with_nist.rb +84 -0
  141. data/sipper_test/test_cancel_without487.rb +77 -0
  142. data/sipper_test/test_command_element.rb +38 -0
  143. data/sipper_test/test_compact_converter.rb +33 -0
  144. data/sipper_test/test_controller_class_loader.rb +37 -0
  145. data/sipper_test/test_controller_selector.rb +53 -0
  146. data/sipper_test/test_controller_using_compact_headers.rb +74 -0
  147. data/sipper_test/test_controller_using_header_order.rb +85 -0
  148. data/sipper_test/test_controller_using_ict.rb +64 -0
  149. data/sipper_test/test_controller_using_ict_with_non_success.rb +72 -0
  150. data/sipper_test/test_controller_using_ict_with_tcbh.rb +24 -0
  151. data/sipper_test/test_controller_using_ict_with_tcbh_no_action.rb +24 -0
  152. data/sipper_test/test_controller_using_ist.rb +70 -0
  153. data/sipper_test/test_controller_using_ist_with_tcbh.rb +24 -0
  154. data/sipper_test/test_controller_using_nict.rb +115 -0
  155. data/sipper_test/test_controller_using_nict_with_tcbh.rb +24 -0
  156. data/sipper_test/test_controller_using_nist.rb +63 -0
  157. data/sipper_test/test_controller_using_pre_existing_rs0.rb +73 -0
  158. data/sipper_test/test_controller_using_pre_existing_rs1.rb +75 -0
  159. data/sipper_test/test_controller_using_pre_existing_rs2.rb +71 -0
  160. data/sipper_test/test_controller_using_route_set.rb +86 -0
  161. data/sipper_test/test_controller_with_sdp.rb +80 -0
  162. data/sipper_test/test_controllers/cancel/order.yaml +2 -0
  163. data/sipper_test/test_controllers/cancel/uac_cancel_controller.rb +35 -0
  164. data/sipper_test/test_controllers/cancel/uas_cancel_controller.rb +25 -0
  165. data/sipper_test/test_controllers/class_loading/ordered/first_ordered_controller.rb +5 -0
  166. data/sipper_test/test_controllers/class_loading/ordered/order.yaml +3 -0
  167. data/sipper_test/test_controllers/class_loading/ordered/recond_ordered_controller.rb +6 -0
  168. data/sipper_test/test_controllers/class_loading/ordered/second_ordered_controller.rb +6 -0
  169. data/sipper_test/test_controllers/class_loading/unordered/first_unordered_controller.rb +7 -0
  170. data/sipper_test/test_controllers/class_loading/unordered/second_unordered_controller.rb +6 -0
  171. data/sipper_test/test_controllers/ctrl_trhandler/lib/transport_filters/my_transport_handler.rb +22 -0
  172. data/sipper_test/test_controllers/ctrl_trhandler/uac_tr_handler_controller.rb +27 -0
  173. data/sipper_test/test_controllers/ctrl_trhandler/uas_tr_handler_controller.rb +21 -0
  174. data/sipper_test/test_controllers/ete/order.yaml +1 -0
  175. data/sipper_test/test_controllers/ete/uac_controller.rb +39 -0
  176. data/sipper_test/test_controllers/ete/uas_controller.rb +34 -0
  177. data/sipper_test/test_controllers/extensions/extension_uac_controller.rb +24 -0
  178. data/sipper_test/test_controllers/extensions/extension_uas_controller.rb +35 -0
  179. data/sipper_test/test_controllers/extensions/lib/sipper_extensions/my_from_extension.rb +16 -0
  180. data/sipper_test/test_controllers/ict_tcbh/lib/transaction_handlers/app_ict_handler.rb +23 -0
  181. data/sipper_test/test_controllers/ict_tcbh/uac_ict_tcbh_controller.rb +27 -0
  182. data/sipper_test/test_controllers/ict_tcbh/uac_ict_tcbh_no_action_controller.rb +28 -0
  183. data/sipper_test/test_controllers/ict_tcbh/uas_ict_tcbh_controller.rb +29 -0
  184. data/sipper_test/test_controllers/ict_tcbh/uas_ict_tcbh_no_action_controller.rb +31 -0
  185. data/sipper_test/test_controllers/ist_tcbh/lib/app_ist_handler.rb +13 -0
  186. data/sipper_test/test_controllers/ist_tcbh/uac_ist_tcbh_controller.rb +22 -0
  187. data/sipper_test/test_controllers/ist_tcbh/uas_ist_tcbh_controller.rb +31 -0
  188. data/sipper_test/test_controllers/multi_trhandlers/lib/transport_filters/in_order.yaml +2 -0
  189. data/sipper_test/test_controllers/multi_trhandlers/lib/transport_filters/my_transport_handler1.rb +21 -0
  190. data/sipper_test/test_controllers/multi_trhandlers/lib/transport_filters/my_transport_handler2.rb +21 -0
  191. data/sipper_test/test_controllers/multi_trhandlers/lib/transport_filters/out_order.yaml +2 -0
  192. data/sipper_test/test_controllers/multi_trhandlers/uac_multi_tr_handler_controller.rb +27 -0
  193. data/sipper_test/test_controllers/multi_trhandlers/uas_multi_tr_handler_controller.rb +29 -0
  194. data/sipper_test/test_controllers/multiple/lib/blank_test.rb +2 -0
  195. data/sipper_test/test_controllers/multiple/uac_info_controller.rb +21 -0
  196. data/sipper_test/test_controllers/multiple/uac_msg_controller.rb +20 -0
  197. data/sipper_test/test_controllers/multiple/uas_info_controller.rb +15 -0
  198. data/sipper_test/test_controllers/multiple/uas_msg_controller.rb +14 -0
  199. data/sipper_test/test_controllers/nict_tcbh/lib/transaction_handlers/app_nict_handler.rb +13 -0
  200. data/sipper_test/test_controllers/nict_tcbh/uac_nict_tcbh_controller.rb +26 -0
  201. data/sipper_test/test_controllers/nict_tcbh/uas_nict_tcbh_controller.rb +20 -0
  202. data/sipper_test/test_controllers/state_machine_based/lib/CreditControl.sm +43 -0
  203. data/sipper_test/test_controllers/state_machine_based/lib/CreditControl_sm.rb +194 -0
  204. data/sipper_test/test_controllers/state_machine_based/order.yaml +1 -0
  205. data/sipper_test/test_controllers/state_machine_based/uac_message_controller.rb +34 -0
  206. data/sipper_test/test_controllers/state_machine_based/uas_message_controller.rb +44 -0
  207. data/sipper_test/test_controllers/stray_message/lib/sipper_extensions/my_stray_handler.rb +9 -0
  208. data/sipper_test/test_controllers/stray_message/stray_uac_controller.rb +24 -0
  209. data/sipper_test/test_controllers/stray_message/stray_uas_controller.rb +31 -0
  210. data/sipper_test/test_controllers/string/order.yaml +1 -0
  211. data/sipper_test/test_controllers/string/uac_controller.rb +29 -0
  212. data/sipper_test/test_controllers/string/uas_controller.rb +22 -0
  213. data/sipper_test/test_controllers/test_controller.rb +24 -0
  214. data/sipper_test/test_detached_session1.rb +78 -0
  215. data/sipper_test/test_dialog_routes.rb +139 -0
  216. data/sipper_test/test_digest_challenge1.rb +89 -0
  217. data/sipper_test/test_digest_challenge2.rb +90 -0
  218. data/sipper_test/test_dynamic_parse.rb +249 -0
  219. data/sipper_test/test_empty_sdp.rb +89 -0
  220. data/sipper_test/test_ete.rb +37 -0
  221. data/sipper_test/test_expectation_parser.rb +76 -0
  222. data/sipper_test/test_extensions.rb +28 -0
  223. data/sipper_test/test_freeze.rb +81 -0
  224. data/sipper_test/test_generated.rb +90 -0
  225. data/sipper_test/test_header_parameters.rb +75 -0
  226. data/sipper_test/test_header_parse.rb +141 -0
  227. data/sipper_test/test_http_client1.rb +84 -0
  228. data/sipper_test/test_http_client2.rb +90 -0
  229. data/sipper_test/test_ict_with_timeout.rb +62 -0
  230. data/sipper_test/test_in_dialog_request.rb +61 -0
  231. data/sipper_test/test_inline_controller.rb +67 -0
  232. data/sipper_test/test_invite_client_transaction.rb +272 -0
  233. data/sipper_test/test_invite_replace.rb +147 -0
  234. data/sipper_test/test_invite_retransmission.rb +90 -0
  235. data/sipper_test/test_invite_server_transaction.rb +208 -0
  236. data/sipper_test/test_lower_cseq.rb +79 -0
  237. data/sipper_test/test_media.rb +52 -0
  238. data/sipper_test/test_message.rb +392 -0
  239. data/sipper_test/test_method_specific_response_handling.rb +81 -0
  240. data/sipper_test/test_multi_homed1.rb +127 -0
  241. data/sipper_test/test_multi_homed2.rb +109 -0
  242. data/sipper_test/test_multi_homed_duplicate.rb +87 -0
  243. data/sipper_test/test_multi_homed_with_detached.rb +88 -0
  244. data/sipper_test/test_multiple.rb +49 -0
  245. data/sipper_test/test_multiple_contacts.rb +89 -0
  246. data/sipper_test/test_non2xx_retransmission_with_ist.rb +87 -0
  247. data/sipper_test/test_non_invite_client_transaction.rb +210 -0
  248. data/sipper_test/test_non_invite_retransmission.rb +91 -0
  249. data/sipper_test/test_non_invite_server_transaction.rb +202 -0
  250. data/sipper_test/test_offer_answer_prack.rb +103 -0
  251. data/sipper_test/test_pickup.rb +258 -0
  252. data/sipper_test/test_prack.rb +105 -0
  253. data/sipper_test/test_prack_store_and_dispatch.rb +101 -0
  254. data/sipper_test/test_prack_timer.rb +105 -0
  255. data/sipper_test/test_ps_sipper_map.rb +56 -0
  256. data/sipper_test/test_re_invite_uas_ongoing_ict.rb +81 -0
  257. data/sipper_test/test_re_invite_uas_ongoing_ist.rb +84 -0
  258. data/sipper_test/test_re_invite_with_ongoing_ict.rb +101 -0
  259. data/sipper_test/test_re_invite_with_ongoing_ist.rb +89 -0
  260. data/sipper_test/test_recorder_swap.rb +157 -0
  261. data/sipper_test/test_refer.rb +121 -0
  262. data/sipper_test/test_registeration_clearing.rb +97 -0
  263. data/sipper_test/test_registrar.rb +109 -0
  264. data/sipper_test/test_registration_controller.rb +73 -0
  265. data/sipper_test/test_registration_timeout.rb +90 -0
  266. data/sipper_test/test_remote_controller.rb +39 -0
  267. data/sipper_test/test_remote_target_update_on2xx.rb +140 -0
  268. data/sipper_test/test_request.rb +106 -0
  269. data/sipper_test/test_response.rb +40 -0
  270. data/sipper_test/test_response_without_to_tag.rb +92 -0
  271. data/sipper_test/test_rport.rb +88 -0
  272. data/sipper_test/test_sdp.rb +91 -0
  273. data/sipper_test/test_sdp_parser.rb +145 -0
  274. data/sipper_test/test_session.rb +276 -0
  275. data/sipper_test/test_session_callback_handler.rb +68 -0
  276. data/sipper_test/test_session_lifetime.rb +66 -0
  277. data/sipper_test/test_session_manager.rb +141 -0
  278. data/sipper_test/test_session_recorder.rb +145 -0
  279. data/sipper_test/test_session_state_user_defined.rb +84 -0
  280. data/sipper_test/test_session_states.rb +91 -0
  281. data/sipper_test/test_simple_dialog_state.rb +86 -0
  282. data/sipper_test/test_simple_re_invite.rb +86 -0
  283. data/sipper_test/test_sip_uri.rb +234 -0
  284. data/sipper_test/test_sipper_util.rb +45 -0
  285. data/sipper_test/test_smc_controller.rb +17 -0
  286. data/sipper_test/test_smoke.rb +80 -0
  287. data/sipper_test/test_stray.rb +28 -0
  288. data/sipper_test/test_stray_inline.rb +89 -0
  289. data/sipper_test/test_stray_res_acked.rb +103 -0
  290. data/sipper_test/test_stray_respond.rb +104 -0
  291. data/sipper_test/test_stray_retry.rb +92 -0
  292. data/sipper_test/test_stray_retry_failure.rb +93 -0
  293. data/sipper_test/test_stray_retry_initial.rb +100 -0
  294. data/sipper_test/test_strict_router_with_angled.rb +84 -0
  295. data/sipper_test/test_string_record.rb +31 -0
  296. data/sipper_test/test_subscribe_notify.rb +141 -0
  297. data/sipper_test/test_subscribe_notify_client_timeout.rb +158 -0
  298. data/sipper_test/test_subscribe_notify_dialog.rb +146 -0
  299. data/sipper_test/test_subscribe_notify_expires.rb +155 -0
  300. data/sipper_test/test_subscribe_notify_multiple_subscription.rb +157 -0
  301. data/sipper_test/test_subscribe_notify_server_timeout.rb +144 -0
  302. data/sipper_test/test_subsequent_unsent.rb +88 -0
  303. data/sipper_test/test_target_refresh_proxy_detached.rb +128 -0
  304. data/sipper_test/test_target_refresh_proxy_udp.rb +130 -0
  305. data/sipper_test/test_target_refresh_rr_proxy_udp.rb +133 -0
  306. data/sipper_test/test_target_refresh_with_new_port.rb +158 -0
  307. data/sipper_test/test_target_refresh_with_proxy1.rb +113 -0
  308. data/sipper_test/test_target_refresh_with_rr_update_proxy.rb +145 -0
  309. data/sipper_test/test_target_refresh_with_update_proxy.rb +144 -0
  310. data/sipper_test/test_target_refresh_with_update_proxy2.rb +139 -0
  311. data/sipper_test/test_timer_manager.rb +71 -0
  312. data/sipper_test/test_timer_task.rb +60 -0
  313. data/sipper_test/test_transport_handler.rb +19 -0
  314. data/sipper_test/test_transport_multi_handler.rb +18 -0
  315. data/sipper_test/test_udp_transport.rb +119 -0
  316. data/sipper_test/test_unparsed.rb +70 -0
  317. data/sipper_test/test_validations.rb +69 -0
  318. data/sipper_test/test_with_rr_proxy.rb +111 -0
  319. data/sipper_test/testmediacontroller.rb +71 -0
  320. data/sipper_test/tracing.rb +23 -0
  321. data/sipper_test/transaction_test_helper.rb +176 -0
  322. data/sipper_test/transport_filters.rb +26 -0
  323. data/sipper_test/ts_sipper.rb +91 -0
  324. data/sipper_test/ts_smoke.rb +3 -0
  325. data/sipper_test/tt.cmd +20 -0
  326. 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,3 @@
1
+ - invite_controller.rb
2
+ - second_hello_controller.rb
3
+ - hello_controller.rb
@@ -0,0 +1,4 @@
1
+ require 'ostruct'
2
+
3
+ class CustomMessage < OpenStruct
4
+ end
@@ -0,0 +1,11 @@
1
+ require 'session'
2
+ require 'sip_logger'
3
+
4
+ class DetachedSession < Session
5
+ include SipLogger
6
+
7
+ def initialize(rip, rp, rs, session_limit=nil, sepecified_transport=nil)
8
+ super(nil, nil, nil, rs, session_limit)
9
+ end
10
+
11
+ 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
+