""" Test basic outgoing and incoming call handling """ import dbus from dbus.exceptions import DBusException from twisted.words.xish import xpath from gabbletest import exec_test, sync_stream from servicetest import ( EventPattern, call_async, assertEquals, assertContains, assertLength ) import constants as cs from jingletest2 import JingleTest2, test_all_dialects, JingleProtocol031 import ns from gabbletest import make_muc_presence from mucutil import * from callutils import * from config import VOIP_ENABLED if not VOIP_ENABLED: print "NOTE: built with --disable-voip" raise SystemExit(77) muc = "muji@test" def run_incoming_test(q, bus, conn, stream, bob_leaves_room = False): jp = JingleProtocol031 () jt = JingleTest2(jp, conn, q, stream, 'test@localhost', muc + "/bob") jt.prepare() forbidden = [ no_muji_presences (muc) ] self_handle = conn.Properties.Get(cs.CONN, "SelfHandle") _, _, test_handle, bob_handle = \ join_muc_and_check(q, bus, conn, stream, muc) presence = make_muc_presence('owner', 'moderator', muc, 'bob') muji = ('muji', ns.MUJI, {}, [('content', ns.MUJI, { "name": "Voice" }, [( 'description', ns.JINGLE_RTP, {"media": "audio"}, jt.generate_payloads(jt.audio_codecs))])]) presence.addChild(jp._simple_xml(muji)) stream.send(presence) e = q.expect ('dbus-signal', signal='NewChannel', predicate=lambda e: \ e.args[1][cs.CHANNEL_TYPE] == cs.CHANNEL_TYPE_CALL ) (path, props) = e.args[0][0] assertContains((cs.CHANNEL_TYPE_CALL + '.InitialAudio', True), props.items()) assertContains((cs.CHANNEL_TYPE_CALL + '.InitialVideo', False), props.items()) general_tests (jp, q, bus, conn, stream, path, props) channel = bus.get_object (conn.bus_name, path) props = channel.GetAll (cs.CHANNEL_TYPE_CALL, dbus_interface = dbus.PROPERTIES_IFACE) content = bus.get_object (conn.bus_name, props['Contents'][0]) check_state (q, channel, cs.CALL_STATE_PENDING_RECEIVER) md = jt.get_call_audio_md_dbus() check_and_accept_offer (q, bus, conn, self_handle, content, md) channel.Accept (dbus_interface=cs.CHANNEL_TYPE_CALL) # Preparing stanza e = q.expect('stream-presence', to = muc + "/test") echo_muc_presence (q, stream, e.stanza, 'none', 'participant') # Codecs stanza e = q.expect('stream-presence', to = muc + "/test") echo_muc_presence (q, stream, e.stanza, 'none', 'participant') # Gabble shouldn't send new presences for a while q.forbid_events(forbidden) e = q.expect ('dbus-signal', signal = 'StreamsAdded') cstream = bus.get_object (conn.bus_name, e.args[0][0]) cstream.SetCredentials(jt.ufrag, jt.pwd, dbus_interface=cs.CALL_STREAM_IFACE_MEDIA) candidates = jt.get_call_remote_transports_dbus () cstream.AddCandidates (candidates, dbus_interface=cs.CALL_STREAM_IFACE_MEDIA) e = q.expect('stream-iq', predicate=jp.action_predicate('session-initiate')) jt.parse_session_initiate (e.query) jt.accept() # Bob adds a Video content presence = make_muc_presence('owner', 'moderator', muc, 'bob') presence.addElement ((ns.MUJI, 'muji')).addElement('preparing') stream.send(presence) presence = make_muc_presence('owner', 'moderator', muc, 'bob') muji = ('muji', ns.MUJI, {}, [('content', ns.MUJI, { "name": "Voice" }, [( 'description', ns.JINGLE_RTP, {"media": "audio"}, jt.generate_payloads(jt.audio_codecs))]), ('content', ns.MUJI, { "name": "Camera" }, [( 'description', ns.JINGLE_RTP, {"media": "video"}, jt.generate_payloads(jt.video_codecs))]), ]) presence.addChild(jp._simple_xml(muji)) stream.send(presence) # Gabble noticed bob added a content e = q.expect('dbus-signal', signal = 'ContentAdded') q.unforbid_events (forbidden) content = bus.get_object (conn.bus_name, e.args[0]) check_and_accept_offer (q, bus, conn, self_handle, content, jt.get_call_video_codecs_dbus(), check_codecs_changed = False) # Gabble sends a presence to prepare e = q.expect('stream-presence', to = muc + "/test") echo_muc_presence (q, stream, e.stanza, 'none', 'participant') # Gabble sends a presence with the video codecs e = q.expect('stream-presence', to = muc + "/test") echo_muc_presence (q, stream, e.stanza, 'none', 'participant') # Gabble adds a content to the jingle session and thus a stream is added e = q.expect ('dbus-signal', signal = 'StreamsAdded') cstream = bus.get_object (conn.bus_name, e.args[0][0]) candidates = jt.get_call_remote_transports_dbus () cstream.AddCandidates (candidates, dbus_interface=cs.CALL_STREAM_IFACE_MEDIA) # And now the content-add on the jingle streams e = q.expect('stream-iq', to = muc + "/bob", predicate = lambda x: \ xpath.queryForNodes("/iq/jingle[@action='content-add']", x.stanza)) # Bob leaves the call, bye bob if bob_leaves_room: presence = make_muc_presence('owner', 'moderator', muc, 'bob') presence['type'] = 'unavailable' else: presence = make_muc_presence('owner', 'moderator', muc, 'bob') stream.send(presence) (cmembers, _, _) = q.expect_many( EventPattern ('dbus-signal', signal = 'CallMembersChanged'), # Audio and video stream EventPattern ('dbus-signal', signal = 'StreamsRemoved'), EventPattern ('dbus-signal', signal = 'StreamsRemoved')) # Just bob left assertLength (1, cmembers.args[1]) def run_outgoing_test(q, bus, conn, stream, close_channel=False): jp = JingleProtocol031 () jt = JingleTest2(jp, conn, q, stream, 'test@localhost', muc + '/bob') jt.prepare() self_handle = conn.Properties.Get(cs.CONN, "SelfHandle") # Not allowed to have muji related presences before we accept the channel forbidden = [ no_muji_presences (muc) ] (path, props) = create_muji_channel (q, conn, stream, muc) q.forbid_events(forbidden) general_tests (jp, q, bus, conn, stream, path, props) channel = bus.get_object (conn.bus_name, path) props = channel.GetAll (cs.CHANNEL_TYPE_CALL, dbus_interface = dbus.PROPERTIES_IFACE) content = bus.get_object (conn.bus_name, props['Contents'][0]) md = jt.get_call_audio_md_dbus() check_and_accept_offer (q, bus, conn, content, md) # Accept the channel, which means we can get muji presences q.unforbid_events (forbidden) channel.Accept() e = q.expect('stream-presence', to = muc + "/test") echo_muc_presence (q, stream, e.stanza, 'none', 'participant') mujinode = xpath.queryForNodes("/presence/muji", e.stanza) assertLength (1, mujinode) # The one with the codecs e = q.expect('stream-presence', to = muc + "/test") echo_muc_presence (q, stream, e.stanza, 'none', 'participant') # Gabble shouldn't send new presences for a while q.forbid_events(forbidden) presence = make_muc_presence('owner', 'moderator', muc, 'bob') presence.addElement ((ns.MUJI, 'muji')).addElement('preparing') stream.send(presence) presence = make_muc_presence('owner', 'moderator', muc, 'bob') muji = ('muji', ns.MUJI, {}, [('content', ns.MUJI, { "name": "Audio" }, [( 'description', ns.JINGLE_RTP, {"media": "audio"}, jt.generate_payloads(jt.audio_codecs))])]) presence.addChild(jp._simple_xml(muji)) stream.send(presence) q.expect('dbus-signal', signal = 'CallStateChanged') # Bob appears and starts a session right afterwards q.expect('dbus-signal', signal = 'CallMembersChanged') q.unforbid_events(forbidden) e = q.expect('dbus-signal', signal = 'NewMediaDescriptionOffer') offer = bus.get_object (conn.bus_name, e.args[0]) offer.Accept(md, dbus_interface=cs.CALL_CONTENT_MEDIA_DESCRIPTION) jt.incoming_call(audio = "Audio") e = q.expect ('dbus-signal', signal = 'StreamsAdded') cstream = bus.get_object (conn.bus_name, e.args[0][0]) candidates = jt.get_call_remote_transports_dbus () cstream.AddCandidates (candidates, dbus_interface=cs.CALL_STREAM_IFACE_MEDIA) # Fake our endpoint being connected endpoints = cstream.Get(cs.CALL_STREAM_IFACE_MEDIA, "Endpoints", dbus_interface=dbus.PROPERTIES_IFACE) assertLength (1, endpoints) endpoint = bus.get_object (conn.bus_name, endpoints[0]) endpoint.SetEndpointState (1, cs.MEDIA_STREAM_STATE_CONNECTED, dbus_interface=cs.CALL_STREAM_ENDPOINT) endpoint.SetEndpointState (2, cs.MEDIA_STREAM_STATE_CONNECTED, dbus_interface=cs.CALL_STREAM_ENDPOINT) e = q.expect ('stream-iq', predicate = jp.action_predicate ('session-accept')) stream.send(jp.xml(jp.ResultIq(jt.peer, e.stanza, []))) # But we want video as well ! c = channel.AddContent ("Camera!", cs.MEDIA_STREAM_TYPE_VIDEO, dbus_interface=cs.CHANNEL_TYPE_CALL) q.expect('dbus-signal', signal = 'ContentAdded') content = bus.get_object (conn.bus_name, c) # wait for the CodecOffer and Accept it q.expect('dbus-signal', signal = 'NewMediaDescriptionOffer') md = jt.get_call_video_md_dbus() check_and_accept_offer (q, bus, conn, content, md) # preparing e = q.expect('stream-presence', to = muc + "/test") echo_muc_presence (q, stream, e.stanza, 'none', 'participant') #codecs e = q.expect('stream-presence', to = muc + "/test") echo_muc_presence (q, stream, e.stanza, 'none', 'participant') # Bob would like to join our video party presence = make_muc_presence('owner', 'moderator', muc, 'bob') muji = ('muji', ns.MUJI, {}, [('content', ns.MUJI, { "name": "Audio" }, [( 'description', ns.JINGLE_RTP, {"media": "audio"}, jt.generate_payloads(jt.audio_codecs))]), ('content', ns.MUJI, { "name": "Camera!" }, [( 'description', ns.JINGLE_RTP, {"media": "video"}, jt.generate_payloads(jt.video_codecs))]), ]) presence.addChild(jp._simple_xml(muji)) stream.send(presence) # new codec offer as bob threw in some codecs q.expect('dbus-signal', signal='NewMediaDescriptionOffer') check_and_accept_offer (q, bus, conn, self_handle, content, codecs, check_codecs_changed = False) # Bob sends a content node = jp.SetIq(jt.peer, jt.jid, [ jp.Jingle(jt.sid, jt.peer, 'content-add', [ jp.Content('videostream', 'initiator', 'both', jp.Description('video', [ jp.PayloadType(name, str(rate), str(id), parameters) for (name, id, rate, parameters) in jt.video_codecs ]), jp.TransportGoogleP2P()) ]) ]) stream.send(jp.xml(node)) # We get a new stream q.expect('dbus-signal', signal = 'StreamsAdded') # Sync up the stream to ensure we sent out all the xmpp traffic that was # the result of a stream being added sync_stream (q, stream) # happiness.. Now let's hang up if close_channel: channel.Close() hangup_event = EventPattern ('dbus-signal', signal = "Closed", path = path) else: channel.Hangup (0, "", "", dbus_interface=cs.CHANNEL_TYPE_CALL) hangup_event = EventPattern ('dbus-signal', signal='CallStateChanged') # Should change the call state to ended, send a session-terminate to our # only peer and send a muc presence without any mention of muji q.forbid_events(forbidden) q.expect_many (EventPattern ('stream-presence', to = muc + "/test"), EventPattern ('stream-iq', predicate=jp.action_predicate ('session-terminate')), hangup_event) if not close_channel: channel.Close() q.expect ('dbus-signal', signal="Closed", path = path) try: channel.Close() raise AssertionError ("Channel didn't actually close") except DBusException: pass def general_tests (jp, q, bus, conn, stream, path, props): assertEquals (cs.HT_ROOM, props[cs.TARGET_HANDLE_TYPE]) channel = bus.get_object (conn.bus_name, path) chan_props = channel.GetAll (cs.CHANNEL_TYPE_CALL, dbus_interface = dbus.PROPERTIES_IFACE) contents = chan_props['Contents'] assertLength(1, contents) if __name__ == '__main__': print "FIXME: needs to be ported to Call1" raise SystemExit(77) exec_test (run_outgoing_test) exec_test (lambda q,b, c, s: run_outgoing_test (q, b, c, s, True)) exec_test (run_incoming_test) exec_test (lambda q,b, c, s: run_incoming_test (q, b, c, s, True))