1: -module(jingle_SUITE). 2: -compile([export_all, nowarn_export_all]). 3: 4: -include_lib("common_test/include/ct.hrl"). 5: -include_lib("exml/include/exml.hrl"). 6: -include_lib("eunit/include/eunit.hrl"). 7: 8: -import(distributed_helper, [mim/0, mim2/0, 9: require_rpc_nodes/1, 10: rpc/4]). 11: 12: -import(jingle_helper, [content/1, 13: content_group/1]). 14: 15: -import(domain_helper, [domain/0]). 16: 17: %%-------------------------------------------------------------------- 18: %% Suite configuration 19: %%-------------------------------------------------------------------- 20: 21: all() -> 22: [{group, all}]. 23: 24: groups() -> 25: G = [{all, [parallel], test_cases()}], 26: ct_helper:repeat_all_until_all_ok(G). 27: 28: test_cases() -> 29: [jingle_session_is_established_for_full_jids, 30: jingle_session_is_established_for_full_jids_on_different_nodes, 31: resp_4xx_from_sip_proxy_results_in_session_terminate, 32: jingle_session_is_established_when_calling_a_number, 33: jingle_session_is_established_and_terminated_by_initiator, 34: jingle_session_is_established_and_terminated_by_receiver, 35: jingle_session_is_established_and_terminated_by_receiver_on_different_node, 36: jingle_session_is_intiated_and_canceled_by_initiator, 37: jingle_session_is_intiated_and_canceled_by_receiver, 38: jingle_session_is_intiated_and_canceled_by_receiver_on_different_node, 39: jingle_session_is_established_with_a_conference_room, 40: jingle_session_is_terminated_on_other_receivers_devices, 41: jingle_session_initiate_is_resent_on_demand, 42: mongoose_replies_with_480_when_invitee_is_offline, 43: mongoose_returns_404_when_not_authorized_user_tires_to_accept_a_session, 44: mongoose_returns_404_when_nto_authorized_user_tries_to_cancel_a_session, 45: mongoose_sends_reINVITE_on_source_remove_action, 46: mongoose_sends_reINVITE_on_source_add_action, 47: mongoose_sends_reINVITE_on_source_update_action 48: 49: ]. 50: 51: suite() -> 52: require_rpc_nodes([mim]) ++ escalus:suite(). 53: 54: %%-------------------------------------------------------------------- 55: %% Init & teardown 56: %%-------------------------------------------------------------------- 57: 58: init_per_suite(Config) -> 59: case rpc(mim(), application, get_application, [nksip]) of 60: {ok, nksip} -> 61: distributed_helper:add_node_to_cluster(mim2(), Config), 62: start_nksip_in_mim_nodes(), 63: application:ensure_all_started(esip), 64: spawn(fun() -> ets:new(jingle_sip_translator, [public, named_table]), 65: ets:new(jingle_sip_translator_bindings, [public, named_table]), 66: receive stop -> ok end end), 67: esip:add_listener(12345, tcp, []), 68: esip:set_config_value(module, jingle_sip_translator), 69: escalus:init_per_suite(Config); 70: undefined -> 71: {skip, build_was_not_configured_with_jingle_sip} 72: end. 73: 74: start_nksip_in_mim_nodes() -> 75: Pid1 = start_nskip_in_parallel(mim(), #{}), 76: Pid2 = start_nskip_in_parallel(mim2(), #{listen_port => 12346}), 77: wait_for_process_to_stop(Pid1), 78: wait_for_process_to_stop(Pid2). 79: 80: wait_for_process_to_stop(Pid) -> 81: erlang:monitor(process, Pid), 82: receive 83: {'DOWN', _, process, Pid, _} -> ok 84: after timer:seconds(60) -> 85: ct:fail(wait_for_process_to_stop_timeout) 86: end. 87: 88: start_nskip_in_parallel(NodeSpec, ExtraOpts) -> 89: Domain = domain(), 90: Opts = #{proxy_host => <<"localhost">>, 91: proxy_port => 12345, 92: backend => ct_helper:get_internal_database()}, 93: OptsWithExtra = maps:merge(Opts, ExtraOpts), 94: AllOpts = config_parser_helper:mod_config(mod_jingle_sip, OptsWithExtra), 95: RPCSpec = NodeSpec#{timeout => timer:seconds(60)}, 96: proc_lib:spawn_link(dynamic_modules, start, [RPCSpec, Domain, mod_jingle_sip, AllOpts]). 97: 98: end_per_suite(Config) -> 99: escalus_fresh:clean(), 100: Domain = domain(), 101: dynamic_modules:stop(mim(), Domain, mod_jingle_sip), 102: dynamic_modules:stop(mim2(), Domain, mod_jingle_sip), 103: distributed_helper:remove_node_from_cluster(mim2(), Config), 104: escalus:end_per_suite(Config). 105: 106: init_per_group(_GroupName, Config) -> 107: Config. 108: 109: end_per_group(_GroupName, Config) -> 110: Config. 111: 112: init_per_testcase(CaseName, Config) -> 113: escalus:init_per_testcase(CaseName, Config). 114: 115: end_per_testcase(CaseName, Config) -> 116: escalus:end_per_testcase(CaseName, Config). 117: 118: %%-------------------------------------------------------------------- 119: %% Cases 120: %%-------------------------------------------------------------------- 121: 122: jingle_session_is_established_for_full_jids(Config) -> 123: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 124: {InviteStanza, InviteRequest} = initiate_jingle_session(Alice, Bob), 125: 126: accept_jingle_session(Alice, Bob, InviteStanza, InviteRequest) 127: end). 128: 129: jingle_session_is_established_for_full_jids_on_different_nodes(Config) -> 130: escalus:fresh_story(Config, [{alice, 1}, {clusterguy, 1}], fun(Alice, Bob) -> 131: {InviteStanza, InviteRequest} = initiate_jingle_session(Alice, Bob), 132: 133: accept_jingle_session(Alice, Bob, InviteStanza, InviteRequest) 134: end). 135: 136: resp_4xx_from_sip_proxy_results_in_session_terminate(Config) -> 137: escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> 138: {_InviteStanza, FirstIQSet} = send_initiate_and_wait_for_first_iq_set(Alice, <<"error.480@localhost">>), 139: assert_is_session_terminate(FirstIQSet, <<"general-error">>) 140: 141: end). 142: 143: jingle_session_is_established_when_calling_a_number(Config) -> 144: escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> 145: {InviteStanza, FirstIQSet} = send_initiate_and_wait_for_first_iq_set(Alice, <<"+488790@numbers.localhost">>), 146: assert_ringing(InviteStanza, FirstIQSet), 147: AcceptInfo = escalus:wait_for_stanza(Alice, timer:seconds(5)), 148: assert_accept_response(InviteStanza, AcceptInfo), 149: 150: ok 151: 152: end). 153: 154: jingle_session_is_established_with_a_conference_room(Config) -> 155: escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> 156: {InviteStanza, FirstIQSet} = send_initiate_and_wait_for_first_iq_set(Alice, <<"*901@numbers.localhost">>), 157: assert_ringing(InviteStanza, FirstIQSet), 158: AcceptInfo = escalus:wait_for_stanza(Alice, timer:seconds(5)), 159: assert_accept_response(InviteStanza, AcceptInfo), 160: 161: TransportInfo = escalus:wait_for_stanza(Alice), 162: assert_transport_info(InviteStanza, TransportInfo), 163: 164: ok 165: end). 166: 167: jingle_session_initiate_is_resent_on_demand(Config) -> 168: escalus:fresh_story(Config, [{alice, 1}, {bob, 2}], fun(Alice, Bob, Bob2) -> 169: %% Bob2 becomes unavailalbe 170: push_helper:become_unavailable(Bob2), 171: escalus:wait_for_stanza(Bob), %%Bob first device gets unavailable form the other 172: {InviteStanza, InviteRequest} = initiate_jingle_session(Alice, Bob), 173: 174: escalus_assert:has_no_stanzas(Bob2), 175: 176: SID = exml_query:path(InviteRequest, path_to_jingle_sid()), 177: 178: ResendSessionInitiateIQ = iq_get(jingle_element(SID, <<"existing-session-initiate">>, [])), 179: 180: %% Bob2 becomes available 181: escalus:send(Bob2, escalus_stanza:presence(<<"available">>)), 182: escalus:wait_for_stanzas(Bob2, 2), %% 2 presence stansa from Bob2 and Bob 183: %% and asks for the session-initiate received by the other device 184: %% this is to get the invite in new session (new browser window) 185: escalus:send(Bob2, ResendSessionInitiateIQ), 186: ResendResult = escalus:wait_for_stanza(Bob2), 187: escalus:assert(is_iq_result, [ResendSessionInitiateIQ], ResendResult), 188: InviteRequest2 = escalus:wait_for_stanza(Bob2), 189: assert_same_sid(InviteRequest, InviteRequest2), 190: assert_invite_request(InviteStanza, InviteRequest2), 191: AliceShortJID = escalus_client:short_jid(Alice), 192: escalus:assert(is_stanza_from, [AliceShortJID], InviteRequest2) 193: 194: 195: end). 196: 197: jingle_session_is_terminated_on_other_receivers_devices(Config) -> 198: escalus:fresh_story(Config, [{alice, 1}, {bob, 2}], fun(Alice, Bob, Bob2) -> 199: {InviteStanza, InviteRequest} = initiate_jingle_session(Alice, Bob), 200: %% The other Bob's device also gets the invite 201: InviteRequest2 = escalus:wait_for_stanza(Bob2), 202: 203: %% then bob accepts the call on one of the devices 204: assert_same_sid(InviteRequest, InviteRequest2), 205: accept_jingle_session(Alice, Bob2, InviteStanza, InviteRequest2), 206: 207: %% then Bob's first device gets cancel request 208: Terminate = escalus:wait_for_stanza(Bob), 209: assert_is_session_terminate(Terminate, <<"cancel">>) 210: 211: end). 212: 213: 214: mongoose_replies_with_480_when_invitee_is_offline(Config) -> 215: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 216: %% Bob becomes unavailalbe 217: push_helper:become_unavailable(Bob), 218: jingle_sip_translator:send_invite(Alice, Bob, self()), 219: 220: receive 221: {sip_resp, 480} -> 222: ok; 223: {sip_resp, Other} -> 224: ct:fail("Received SIP resp: ~p", [Other]) 225: after timer:seconds(5) -> 226: ct:fail(timeout_waiting_for_sip_resp) 227: end 228: 229: end). 230: 231: mongoose_returns_404_when_not_authorized_user_tires_to_accept_a_session(Config) -> 232: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) -> 233: {InviteStanza, _InviteRequest} = initiate_jingle_session(Alice, Bob), 234: %% Then Kate tries to accept Alice's session 235: AcceptStanza = escalus_stanza:to(jingle_accept(InviteStanza), Bob), 236: escalus:send(Kate, AcceptStanza), 237: AcceptResult = escalus:wait_for_stanza(Kate, timer:seconds(5)), 238: escalus:assert(is_iq_error, AcceptResult), 239: escalus:assert(is_error, [<<"cancel">>, <<"item-not-found">>], AcceptResult) 240: 241: 242: end). 243: 244: mongoose_returns_404_when_nto_authorized_user_tries_to_cancel_a_session(Config) -> 245: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}, {kate, 1}], fun(Alice, Bob, Kate) -> 246: {_InviteStanza, InviteRequest} = initiate_jingle_session(Alice, Bob), 247: %% Then Kate tries to cancel Bob's session 248: 249: Result = send_session_terminate_request(Kate, Alice, InviteRequest, <<"decline">>), 250: 251: escalus:assert(is_iq_error, Result), 252: escalus:assert(is_error, [<<"cancel">>, <<"item-not-found">>], Result) 253: 254: end). 255: 256: jingle_session_is_established_and_terminated_by_initiator(Config) -> 257: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 258: {InviteStanza, InviteRequest} = initiate_jingle_session(Alice, Bob), 259: 260: accept_jingle_session(Alice, Bob, InviteStanza, InviteRequest), 261: timer:sleep(1000), 262: 263: %% Then Alice (who initiated) terminates the call 264: terminate_jingle_session(Alice, Bob, InviteStanza) 265: 266: end). 267: 268: jingle_session_is_established_and_terminated_by_receiver(Config) -> 269: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 270: {InviteStanza, InviteRequest} = initiate_jingle_session(Alice, Bob), 271: 272: accept_jingle_session(Alice, Bob, InviteStanza, InviteRequest), 273: 274: timer:sleep(timer:seconds(5)), 275: %% then Bob (who was invited to the call) terminates the call 276: %% it's important that bob terminates the call from the invite he got 277: terminate_jingle_session(Bob, Alice, InviteRequest) 278: 279: end). 280: 281: jingle_session_is_established_and_terminated_by_receiver_on_different_node(Config) -> 282: escalus:fresh_story(Config, [{alice, 1}, {clusterguy, 1}], fun(Alice, Bob) -> 283: {InviteStanza, InviteRequest} = initiate_jingle_session(Alice, Bob), 284: 285: accept_jingle_session(Alice, Bob, InviteStanza, InviteRequest), 286: 287: timer:sleep(timer:seconds(5)), 288: %% then Bob (who was invited to the call) terminates the call 289: %% it's important that bob terminates the call from the invite he got 290: terminate_jingle_session(Bob, Alice, InviteRequest) 291: 292: end). 293: 294: 295: jingle_session_is_intiated_and_canceled_by_initiator(Config) -> 296: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 297: {InviteStanza, _InviteRequest} = initiate_jingle_session(Alice, Bob), 298: timer:sleep(1000), 299: %% then Bob (who was invited to the call) terminates the call 300: terminate_jingle_session(Alice, Bob, InviteStanza, <<"decline">>) 301: 302: end). 303: 304: jingle_session_is_intiated_and_canceled_by_receiver(Config) -> 305: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 306: {_InviteStanza, InviteRequest} = initiate_jingle_session(Alice, Bob), 307: 308: timer:sleep(1000), 309: %% then Bob (who was invited to the call) terminates the call 310: terminate_jingle_session(Bob, Alice, InviteRequest, <<"decline">>) 311: end). 312: 313: jingle_session_is_intiated_and_canceled_by_receiver_on_different_node(Config) -> 314: escalus:fresh_story(Config, [{alice, 1}, {clusterguy, 1}], fun(Alice, Bob) -> 315: {_InviteStanza, InviteRequest} = initiate_jingle_session(Alice, Bob), 316: 317: timer:sleep(1000), 318: %% then Bob (who was invited to the call) terminates the call 319: terminate_jingle_session(Bob, Alice, InviteRequest, <<"decline">>) 320: end). 321: 322: mongoose_sends_reINVITE_on_source_remove_action(Config) -> 323: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 324: {InviteStanza, InviteRequest} = initiate_jingle_session(Alice, Bob), 325: accept_jingle_session(Alice, Bob, InviteStanza, InviteRequest), 326: 327: %% then Alice sends source-remove 328: SourceRemoveStanza = escalus_stanza:to(jingle_source_remove(InviteStanza), Bob), 329: escalus:send(Alice, SourceRemoveStanza), 330: SourceRemoveResult = escalus:wait_for_stanza(Alice), 331: escalus:assert(is_iq_result, [SourceRemoveStanza], SourceRemoveResult), 332: SourceRemoveToBob = escalus:wait_for_stanza(Bob), 333: assert_source_remove_action(SourceRemoveToBob, InviteRequest) 334: end). 335: 336: mongoose_sends_reINVITE_on_source_add_action(Config) -> 337: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 338: {InviteStanza, InviteRequest} = initiate_jingle_session(Alice, Bob), 339: accept_jingle_session(Alice, Bob, InviteStanza, InviteRequest), 340: 341: %% then Alice sends source-remove 342: SourceRemoveStanza = escalus_stanza:to(jingle_source_add(InviteStanza), Bob), 343: escalus:send(Alice, SourceRemoveStanza), 344: SourceRemoveResult = escalus:wait_for_stanza(Alice), 345: escalus:assert(is_iq_result, [SourceRemoveStanza], SourceRemoveResult), 346: SourceRemoveToBob = escalus:wait_for_stanza(Bob), 347: assert_source_add_action(SourceRemoveToBob, InviteRequest) 348: end). 349: 350: mongoose_sends_reINVITE_on_source_update_action(Config) -> 351: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 352: {InviteStanza, InviteRequest} = initiate_jingle_session(Alice, Bob), 353: accept_jingle_session(Alice, Bob, InviteStanza, InviteRequest), 354: 355: %% then Alice sends source-remove 356: SourceRemoveStanza = escalus_stanza:to(jingle_source_update(InviteStanza), Bob), 357: escalus:send(Alice, SourceRemoveStanza), 358: SourceRemoveResult = escalus:wait_for_stanza(Alice), 359: escalus:assert(is_iq_result, [SourceRemoveStanza], SourceRemoveResult), 360: SourceRemoveToBob = escalus:wait_for_stanza(Bob), 361: assert_source_update_action(SourceRemoveToBob, InviteRequest) 362: end). 363: %%-------------------------------------------------------------------- 364: %% Helpers 365: %%-------------------------------------------------------------------- 366: 367: accept_jingle_session(Alice, Bob, InviteStanza, InviteRequest) -> 368: AcceptStanza = escalus_stanza:to(jingle_accept(InviteRequest), Alice), 369: escalus:send(Bob, AcceptStanza), 370: AcceptResult = escalus:wait_for_stanza(Bob, timer:seconds(5)), 371: escalus:assert(is_iq_result, AcceptResult), 372: 373: AcceptInfo = escalus:wait_for_stanza(Alice, timer:seconds(5)), 374: assert_accept_response(InviteStanza, AcceptInfo). 375: 376: initiate_jingle_session(Alice, Bob) -> 377: {InviteStanza, _FirstIQSet} = send_initiate_and_wait_for_first_iq_set(Alice, Bob), 378: 379: %jingle_sip_translator:send_invite(Alice, Bob), 380: 381: InviteRequest = escalus:wait_for_stanza(Bob, timer:seconds(5)), 382: escalus:assert(is_iq_set, InviteRequest), 383: assert_invite_request(InviteStanza, InviteRequest), 384: {InviteStanza, InviteRequest}. 385: 386: send_initiate_and_wait_for_first_iq_set(Alice, Bob) -> 387: InviteStanza = escalus_stanza:to(jingle_initiate(), Bob), 388: escalus:send(Alice, InviteStanza), 389: SessionInitiateResult = escalus:wait_for_stanza(Alice, timer:seconds(5)), 390: escalus:assert(is_iq_result, SessionInitiateResult), 391: RingingStanza = escalus:wait_for_stanza(Alice, timer:seconds(5)), 392: escalus:assert(is_iq_set, RingingStanza), 393: {InviteStanza, RingingStanza}. 394: 395: terminate_jingle_session(Terminator, Other, InviteStanza) -> 396: terminate_jingle_session(Terminator, Other, InviteStanza, <<"success">>). 397: 398: terminate_jingle_session(Terminator, Other, InviteStanza, Reason) -> 399: TerminateResult = send_session_terminate_request(Terminator, Other, 400: InviteStanza, Reason), 401: escalus:assert(is_iq_result, TerminateResult), 402: 403: TerminateInfo = escalus:wait_for_stanza(Other, timer:seconds(5)), 404: assert_is_session_terminate(TerminateInfo, Reason). 405: 406: send_session_terminate_request(Terminator, Other, InviteStanza, Reason) -> 407: TerminateStanza = escalus_stanza:to(jingle_terminate(InviteStanza, Reason), Other), 408: escalus:send(Terminator, TerminateStanza), 409: escalus:wait_for_stanza(Terminator, timer:seconds(5)). 410: 411: assert_invite_request(InviteStanza, InviteRequest) -> 412: JingleEl = exml_query:subelement(InviteRequest, <<"jingle">>), 413: ?assertEqual(<<"session-initiate">>, 414: (exml_query:attr(JingleEl, <<"action">>))), 415: 416: assert_different_sid(InviteStanza, InviteRequest), 417: assert_session_description(JingleEl). 418: 419: assert_ringing(InviteStanza, RingingStanza) -> 420: JingleEl = exml_query:subelement(RingingStanza, <<"jingle">>), 421: ?assertEqual(<<"session-info">>, 422: (exml_query:attr(JingleEl, <<"action">>))), 423: Ringing = exml_query:subelement(JingleEl, <<"ringing">>), 424: ?assertMatch(#xmlel{}, Ringing), 425: ?assertEqual(<<"urn:xmpp:jingle:apps:rtp:info:1">>, exml_query:attr(Ringing, <<"xmlns">>)), 426: 427: assert_same_sid(InviteStanza, RingingStanza). 428: 429: 430: assert_session_description(JingleEl) -> 431: Contents = exml_query:subelements(JingleEl, <<"content">>), 432: ?assertMatch([#xmlel{} | _], Contents), 433: 434: [assert_transport_with_candidate(Content) || Content <- Contents], 435: 436: case Contents of 437: [_, _ | _ ] -> %% For at least 2 contents 438: ?assertMatch((#xmlel{}), (exml_query:subelement(JingleEl, <<"group">>))); 439: _ -> 440: ok 441: end. 442: 443: assert_transport_with_candidate(Content) -> 444: TransportEl = exml_query:subelement(Content, <<"transport">>), 445: ?assertMatch((#xmlel{}), TransportEl), 446: ?assertMatch([#xmlel{} | _], (exml_query:subelements(TransportEl, <<"candidate">>))). 447: 448: assert_different_sid(Sent, Received) -> 449: SIDPath = path_to_jingle_sid(), 450: ?assertNotEqual((exml_query:path(Sent, SIDPath)), 451: (exml_query:path(Received, SIDPath))). 452: assert_same_sid(Sent, Received) -> 453: SIDPath = path_to_jingle_sid(), 454: ?assertEqual((exml_query:path(Sent, SIDPath)), 455: (exml_query:path(Received, SIDPath))). 456: 457: path_to_jingle_sid() -> [{element, <<"jingle">>}, {attr, <<"sid">>}]. 458: 459: assert_accept_response(InviteStanza, AcceptResponse) -> 460: JingleEl = exml_query:subelement(AcceptResponse, <<"jingle">>), 461: ?assertEqual(<<"session-accept">>, 462: (exml_query:attr(JingleEl, <<"action">>))), 463: 464: assert_same_sid(InviteStanza, AcceptResponse), 465: assert_session_description(JingleEl). 466: 467: assert_is_session_terminate(FirstIQSet, ReasonName) -> 468: JingleEl = exml_query:subelement(FirstIQSet, <<"jingle">>), 469: ?assertEqual(<<"session-terminate">>, 470: (exml_query:attr(JingleEl, <<"action">>))), 471: ?assertMatch((#xmlel{}), 472: exml_query:path(JingleEl, 473: [{element, <<"reason">>}, {element, ReasonName}])). 474: 475: assert_transport_info(InviteStanza, TransportInfo) -> 476: JingleEl = exml_query:subelement(TransportInfo, <<"jingle">>), 477: ?assertEqual(<<"transport-info">>, 478: (exml_query:attr(JingleEl, <<"action">>))), 479: assert_same_sid(InviteStanza, TransportInfo). 480: 481: assert_source_remove_action(SourceRemoveRequest, InviteRequest) -> 482: assert_same_sid(InviteRequest, SourceRemoveRequest), 483: JingleEl = exml_query:subelement(SourceRemoveRequest, <<"jingle">>), 484: ?assertEqual(<<"source-remove">>, 485: (exml_query:attr(JingleEl, <<"action">>))). 486: 487: assert_source_add_action(SourceRemoveRequest, InviteRequest) -> 488: assert_same_sid(InviteRequest, SourceRemoveRequest), 489: JingleEl = exml_query:subelement(SourceRemoveRequest, <<"jingle">>), 490: ?assertEqual(<<"source-add">>, 491: (exml_query:attr(JingleEl, <<"action">>))). 492: 493: assert_source_update_action(SourceRemoveRequest, InviteRequest) -> 494: assert_same_sid(InviteRequest, SourceRemoveRequest), 495: JingleEl = exml_query:subelement(SourceRemoveRequest, <<"jingle">>), 496: ?assertEqual(<<"source-update">>, 497: (exml_query:attr(JingleEl, <<"action">>))). 498: 499: jingle_stanza_addressed_to_bare_jid_is_delivered(Config) -> 500: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 501: BobBareJID = escalus_client:short_jid(Bob), 502: Stanza = escalus_stanza:to(jingle_initiate(), BobBareJID), 503: escalus:send(Alice, Stanza), 504: R = escalus:wait_for_stanza(Bob), 505: escalus:assert(is_iq_set, R), 506: ?assertEqual(exml_query:attr(Stanza, <<"id">>), exml_query:attr(R, <<"id">>)) 507: end). 508: 509: jingle_stanza_addressed_to_own_bare_jid_is_rejected(Config) -> 510: escalus:fresh_story(Config, [{alice, 1}], fun(Alice) -> 511: AliceBareJID = escalus_client:short_jid(Alice), 512: Stanza = escalus_stanza:to(jingle_initiate(), AliceBareJID), 513: escalus:send(Alice, Stanza), 514: R = escalus:wait_for_stanza(Alice), 515: escalus:assert(is_error, [<<"modify">>, <<"bad-request">>], R) 516: end). 517: 518: 519: other_iq_stanza_addressed_to_bare_jid_are_not_routed(Config) -> 520: escalus:fresh_story(Config, [{alice, 1}, {bob, 1}], fun(Alice, Bob) -> 521: BobBareJID = escalus_client:short_jid(Bob), 522: Stanza = escalus_stanza:to(escalus_stanza:iq_set(<<"urn:unknown:iq:ns">>, []), BobBareJID), 523: escalus:send(Alice, Stanza), 524: Reply = escalus:wait_for_stanza(Alice), 525: escalus:assert(is_error, [<<"cancel">>, <<"service-unavailable">>], Reply) 526: 527: end). 528: 529: 530: jingle_initiate() -> 531: Audio = content(audio), 532: Video = content(video), 533: I = jingle_element(<<"session-initiate">>, [Audio, Video, content_group([Audio, Video])]), 534: iq_set(I). 535: 536: iq_set(I) -> 537: Stanza = escalus_stanza:iq_set_nonquery(<<"jabber:client">>, [I]), 538: iq_with_id(Stanza). 539: 540: iq_with_id(#xmlel{attrs = Attrs} = Stanza) -> 541: NewAttrs = lists:keystore(<<"id">>, 1, Attrs, {<<"id">>, uuid:uuid_to_string(uuid:get_v4(), binary_standard)}), 542: Stanza#xmlel{attrs = NewAttrs}. 543: 544: iq_get(I) -> 545: Stanza = #xmlel{name = <<"iq">>, 546: attrs = [{<<"xmlns">>, <<"jabber:client">>}, 547: {<<"type">>, <<"get">>}], 548: children = [I]}, 549: iq_with_id(Stanza). 550: 551: jingle_element(Action, Children) -> 552: SID = uuid:uuid_to_string(uuid:get_v4(), binary_standard), 553: jingle_element(SID, Action, Children). 554: 555: jingle_element(SID, Action, Children) -> 556: #xmlel{name = <<"jingle">>, 557: attrs = [{<<"action">>, Action}, 558: {<<"sid">>, SID}, 559: {<<"xmlns">>, <<"urn:xmpp:jingle:1">>}], 560: children = Children}. 561: 562: jingle_accept(InviteRequest) -> 563: SID = exml_query:path(InviteRequest, path_to_jingle_sid()), 564: Audio = content(audio), 565: Video = content(video_disabled), 566: I = jingle_element(SID, <<"session-accept">>, [Audio, Video, content_group([Audio])]), 567: iq_set(I). 568: 569: 570: jingle_source_remove(InviteRequest) -> 571: SID = exml_query:path(InviteRequest, path_to_jingle_sid()), 572: Audio = content(audio_source_remove), 573: Video = content(video_source_remove), 574: I = jingle_element(SID, <<"source-remove">>, [Audio, Video, 575: content_group([Audio, Video])]), 576: iq_set(I). 577: 578: jingle_source_add(InviteRequest) -> 579: SID = exml_query:path(InviteRequest, path_to_jingle_sid()), 580: Audio = content(audio_source_remove), 581: Video = content(video_source_remove), 582: I = jingle_element(SID, <<"source-add">>, [Audio, Video, 583: content_group([Audio, Video])]), 584: iq_set(I). 585: 586: jingle_source_update(InviteRequest) -> 587: SID = exml_query:path(InviteRequest, path_to_jingle_sid()), 588: Audio = content(audio_source_remove), 589: Video = content(video_source_remove), 590: I = jingle_element(SID, <<"source-update">>, [Audio, Video, 591: content_group([Audio, Video])]), 592: iq_set(I). 593: 594: jingle_transport_info(InviteRequest, Creator, Media, TransportAttrs) -> 595: SID = exml_query:path(InviteRequest, path_to_jingle_sid()), 596: iq_set(jingle_element(SID, <<"transport-info">>, [trickle_ice_candidate(Creator, Media, TransportAttrs)])). 597: 598: jingle_terminate(InviteRequest, Reason) -> 599: SID = exml_query:path(InviteRequest, path_to_jingle_sid()), 600: ReasonEl = #xmlel{name = <<"reason">>, 601: children = [#xmlel{name = Reason}]}, 602: iq_set(jingle_element(SID, <<"session-terminate">>, [ReasonEl])). 603: 604: get_ice_candidates() -> 605: [ 606: [{<<"foundation">>, <<"1293499931">>}, {<<"component">>, <<"1">>}, {<<"protocol">>, <<"udp">>}, {<<"priority">>, <<"2122260223">>}, {<<"ip">>, <<"172.86.160.16">>}, {<<"port">>, <<"46515">>}, {<<"type">>, <<"host">>}, {<<"generation">>, <<"0">>}, {<<"network">>, <<"1">>}, {<<"id">>, <<"1.1947885zlx">>}] 607: ]. 608: 609: trickle_ice_candidate(Creator, Content, TransportAttrs) -> 610: Candidate = #xmlel{name = <<"candidate">>, 611: attrs = TransportAttrs}, 612: Transport = #xmlel{name = <<"transport">>, 613: attrs = [{<<"xmlns">>, <<"urn:xmpp:jingle:transports:ice-udp:1">>}, 614: {<<"ufrag">>, <<"7Gpn">>}, 615: {<<"pwd">>, <<"MUOzzatqL2qP7n1uRC7msD+c">>}], 616: children = [Candidate]}, 617: #xmlel{name = <<"content">>, 618: attrs = [{<<"name">>, Content}, 619: {<<"creator">>, Creator}], 620: children = [Transport]}.