1""" 2Test everything related to contents 3""" 4 5from gabbletest import sync_stream 6from servicetest import ( 7 make_channel_proxy, assertEquals, EventPattern) 8import constants as cs 9from jingletest2 import ( 10 JingleTest2, JingleProtocol015, JingleProtocol031, test_dialects) 11 12from twisted.words.xish import xpath 13 14from config import VOIP_ENABLED 15 16if not VOIP_ENABLED: 17 print "NOTE: built with --disable-voip" 18 raise SystemExit(77) 19 20def worker(jp, q, bus, conn, stream): 21 22 def make_stream_request(stream_type): 23 media_iface.RequestStreams(remote_handle, [stream_type]) 24 25 e = q.expect('dbus-signal', signal='NewStreamHandler') 26 stream_id = e.args[1] 27 28 stream_handler = make_channel_proxy(conn, e.args[0], 'Media.StreamHandler') 29 30 stream_handler.NewNativeCandidate("fake", jt2.get_remote_transports_dbus()) 31 stream_handler.Ready(jt2.get_audio_codecs_dbus()) 32 stream_handler.StreamState(cs.MEDIA_STREAM_STATE_CONNECTED) 33 return (stream_handler, stream_id) 34 35 36 jt2 = JingleTest2(jp, conn, q, stream, 'test@localhost', 'foo@bar.com/Foo') 37 jt2.prepare() 38 39 self_handle = conn.GetSelfHandle() 40 remote_handle = conn.RequestHandles(cs.HT_CONTACT, ["foo@bar.com/Foo"])[0] 41 42 # Remote end calls us 43 jt2.incoming_call() 44 45 # FIXME: these signals are not observable by real clients, since they 46 # happen before NewChannels. 47 # The caller is in members 48 e = q.expect('dbus-signal', signal='MembersChanged', 49 args=[u'', [remote_handle], [], [], [], 0, 0]) 50 51 # We're pending because of remote_handle 52 e = q.expect('dbus-signal', signal='MembersChanged', 53 args=[u'', [], [], [self_handle], [], remote_handle, 54 cs.GC_REASON_INVITED]) 55 56 media_chan = make_channel_proxy(conn, e.path, 'Channel.Interface.Group') 57 signalling_iface = make_channel_proxy(conn, e.path, 'Channel.Interface.MediaSignalling') 58 media_iface = make_channel_proxy(conn, e.path, 'Channel.Type.StreamedMedia') 59 60 # S-E gets notified about new session handler, and calls Ready on it 61 e = q.expect('dbus-signal', signal='NewSessionHandler') 62 assert e.args[1] == 'rtp' 63 64 session_handler = make_channel_proxy(conn, e.args[0], 'Media.SessionHandler') 65 session_handler.Ready() 66 67 media_chan.AddMembers([self_handle], 'accepted') 68 69 # S-E gets notified about a newly-created stream 70 e = q.expect('dbus-signal', signal='NewStreamHandler') 71 id1 = e.args[1] 72 73 stream_handler = make_channel_proxy(conn, e.args[0], 'Media.StreamHandler') 74 75 # We are now in members too 76 e = q.expect('dbus-signal', signal='MembersChanged', 77 args=[u'', [self_handle], [], [], [], self_handle, 78 cs.GC_REASON_NONE]) 79 80 # we are now both in members 81 members = media_chan.GetMembers() 82 assert set(members) == set([self_handle, remote_handle]), members 83 84 stream_handler.NewNativeCandidate("fake", jt2.get_remote_transports_dbus()) 85 stream_handler.Ready(jt2.get_audio_codecs_dbus()) 86 stream_handler.StreamState(cs.MEDIA_STREAM_STATE_CONNECTED) 87 88 # First one is transport-info 89 e = q.expect('stream-iq', predicate=jp.action_predicate('transport-info')) 90 assertEquals('foo@bar.com/Foo', e.query['initiator']) 91 92 # stream.send(gabbletest.make_result_iq(stream, e.stanza)) 93 stream.send(jp.xml(jp.ResultIq('test@localhost', e.stanza, []))) 94 95 # S-E reports codec intersection, after which gabble can send acceptance 96 stream_handler.SupportedCodecs(jt2.get_audio_codecs_dbus()) 97 98 # Second one is session-accept 99 e = q.expect('stream-iq', predicate=jp.action_predicate('session-accept')) 100 101 # stream.send(gabbletest.make_result_iq(stream, e.stanza)) 102 stream.send(jp.xml(jp.ResultIq('test@localhost', e.stanza, []))) 103 104 # Here starts the interesting part of this test 105 # Remote end tries to create a content we can't handle 106 node = jp.SetIq(jt2.peer, jt2.jid, [ 107 jp.Jingle(jt2.sid, jt2.peer, 'content-add', [ 108 jp.Content('bogus', 'initiator', 'both', 109 jp.Description('hologram', [ 110 jp.PayloadType(name, str(rate), str(id), parameters) for 111 (name, id, rate, parameters) in jt2.audio_codecs ]), 112 jp.TransportGoogleP2P()) ]) ]) 113 stream.send(jp.xml(node)) 114 115 # In older Jingle, this is a separate namespace, which isn't 116 # recognized, but it's a valid request, so it gets ackd and rejected 117 if jp.dialect == 'jingle-v0.15': 118 # Gabble should acknowledge content-add 119 q.expect('stream-iq', iq_type='result') 120 121 # .. and then send content-reject for the bogus content 122 e = q.expect('stream-iq', iq_type='set', predicate=lambda x: 123 xpath.queryForNodes("/iq/jingle[@action='content-reject']/content[@name='bogus']", 124 x.stanza)) 125 126 # In new Jingle, this is a bogus subtype of recognized namespace, 127 # so Gabble returns a bad request error 128 else: 129 q.expect('stream-iq', iq_type='error') 130 131 # Remote end then tries to create a content with a name it's already used 132 node = jp.SetIq(jt2.peer, jt2.jid, [ 133 jp.Jingle(jt2.sid, jt2.peer, 'content-add', [ 134 jp.Content(jt2.audio_names[0], 'initiator', 'both', 135 jp.Description('audio', [ 136 jp.PayloadType(name, str(rate), str(id), parameters) for 137 (name, id, rate, parameters) in jt2.audio_codecs ]), 138 jp.TransportGoogleP2P()) ]) ]) 139 stream.send(jp.xml(node)) 140 141 # Gabble should return error (content already exists) 142 q.expect('stream-iq', iq_type='error') 143 144 # We try to add a stream 145 (stream_handler2, id2) = make_stream_request(cs.MEDIA_STREAM_TYPE_VIDEO) 146 147 # Gabble should now send content-add 148 e = q.expect('stream-iq', iq_type='set', predicate=lambda x: 149 xpath.queryForNodes("/iq/jingle[@action='content-add']", 150 x.stanza)) 151 152 c = e.query.firstChildElement() 153 assert c['creator'] == 'responder', c['creator'] 154 155 stream.send(jp.xml(jp.ResultIq('test@localhost', e.stanza, []))) 156 157 # We try to add yet another stream 158 (stream_handler3, id3) = make_stream_request(cs.MEDIA_STREAM_TYPE_VIDEO) 159 160 # Gabble should send another content-add 161 e = q.expect('stream-iq', iq_type='set', predicate=lambda x: 162 xpath.queryForNodes("/iq/jingle[@action='content-add']", 163 x.stanza)) 164 165 d = e.query.firstChildElement() 166 assertEquals('responder', d['creator']) 167 168 stream.send(jp.xml(jp.ResultIq('test@localhost', e.stanza, []))) 169 170 # Remote end rejects the first stream we tried to add. 171 node = jp.SetIq(jt2.peer, jt2.jid, [ 172 jp.Jingle(jt2.sid, jt2.peer, 'content-reject', [ 173 jp.Content(c['name'], c['creator'], c['senders']) ]) ]) 174 stream.send(jp.xml(node)) 175 176 # Gabble removes the stream 177 q.expect('dbus-signal', signal='StreamRemoved', 178 interface=cs.CHANNEL_TYPE_STREAMED_MEDIA) 179 180 # Remote end tries to add a content with the same name as the second one we 181 # just added 182 node = jp.SetIq(jt2.peer, jt2.jid, [ 183 jp.Jingle(jt2.sid, jt2.peer, 'content-add', [ 184 jp.Content(d['name'], 'initiator', 'both', 185 jp.Description('audio', [ 186 jp.PayloadType(name, str(rate), str(id), parameters) for 187 (name, id, rate, parameters) in jt2.audio_codecs ]), 188 jp.TransportGoogleP2P()) ]) ]) 189 stream.send(jp.xml(node)) 190 191 # Because stream names are namespaced by creator, Gabble should be okay 192 # with that. 193 q.expect_many( 194 EventPattern('stream-iq', iq_type='result', iq_id=node[2]['id']), 195 EventPattern('dbus-signal', signal='StreamAdded'), 196 ) 197 198 # Remote end thinks better of that, and removes the similarly-named stream 199 # it tried to add. 200 node = jp.SetIq(jt2.peer, jt2.jid, [ 201 jp.Jingle(jt2.sid, jt2.peer, 'content-remove', [ 202 jp.Content(d['name'], 'initiator', d['senders']) ]) ]) 203 stream.send(jp.xml(node)) 204 205 q.expect_many( 206 EventPattern('stream-iq', iq_type='result', iq_id=node[2]['id']), 207 EventPattern('dbus-signal', signal='StreamRemoved'), 208 ) 209 210 # Remote end finally accepts. When Gabble did not namespace contents by 211 # their creator, it would NAK this IQ: 212 # - Gabble (responder) created a stream called 'foo'; 213 # - test suite (initiator) created a stream called 'foo', which Gabble 214 # decided would replace its own stream called 'foo'; 215 # - test suite removed its 'foo'; 216 # - test suite accepted Gabble's 'foo', but Gabble didn't believe a stream 217 # called 'foo' existed any more. 218 node = jp.SetIq(jt2.peer, jt2.jid, [ 219 jp.Jingle(jt2.sid, jt2.peer, 'content-accept', [ 220 jp.Content(d['name'], d['creator'], d['senders'], 221 jp.Description('video', [ 222 jp.PayloadType(name, str(rate), str(id), parameters) for 223 (name, id, rate, parameters ) in jt2.audio_codecs ]), 224 jp.TransportGoogleP2P()) ]) ]) 225 stream.send(jp.xml(node)) 226 227 # We get remote codecs 228 e = q.expect('dbus-signal', signal='SetRemoteCodecs') 229 230 # Now, both we and remote peer try to remove the content simultaneously: 231 # Telepathy client calls RemoveStreams... 232 media_iface.RemoveStreams([id3]) 233 234 # ...so Gabble sends a content-remove... 235 e = q.expect('stream-iq', iq_type='set', predicate=lambda x: 236 xpath.queryForNodes("/iq/jingle[@action='content-remove']", 237 x.stanza)) 238 239 # ...but before it's acked the peer sends its own content-remove... 240 node = jp.SetIq(jt2.peer, jt2.jid, [ 241 jp.Jingle(jt2.sid, jt2.peer, 'content-remove', [ 242 jp.Content(c['name'], c['creator'], c['senders']) ]) ]) 243 stream.send(jp.xml(node)) 244 245 # ...and we don't want Gabble to break when that happens. 246 sync_stream(q, stream) 247 248 # Now we want to remove the first stream 249 media_iface.RemoveStreams([id1]) 250 251 # Since this is the last stream, Gabble will just terminate the session. 252 e = q.expect('stream-iq', iq_type='set', predicate=lambda x: 253 xpath.queryForNodes("/iq/jingle[@action='session-terminate']", 254 x.stanza)) 255 256if __name__ == '__main__': 257 test_dialects(worker, [JingleProtocol015, JingleProtocol031]) 258