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