1"""
2Base classes for Call tests
3"""
4
5import config
6
7if not config.CHANNEL_TYPE_CALL_ENABLED:
8    print "NOTE: built with --disable-channel-type-call"
9    raise SystemExit(77)
10
11import dbus
12from dbus.exceptions import DBusException
13
14from functools import partial
15from servicetest import (
16    EventPattern, call_async, wrap_channel, wrap_content,
17    assertEquals, assertDoesNotContain, assertContains, assertLength,
18    assertNotEquals, DictionarySupersetOf)
19from gabbletest import sync_stream, make_result_iq
20from jingletest2 import JingleTest2, test_all_dialects
21import constants as cs
22import ns
23
24from config import VOIP_ENABLED
25
26if not VOIP_ENABLED:
27    print "NOTE: built with --disable-voip"
28    raise SystemExit(77)
29
30class CallTest(object):
31
32    SELF_JID = 'test@localhost'
33    PEER_JID = 'foo@bar.com/Foo'
34
35    # These can be changed as needed by base class
36    initial_audio = True
37    initial_video = False
38
39    # The following will be set after initiate_call()
40    chan = None
41
42    audio_content = None
43    audio_content_name = None
44    audio_stream = None
45
46    video_content = None
47    video_content_name = None
48    video_stream = None
49
50
51    def __init__(self, jp, q, bus, conn, stream, incoming, params):
52        self.jp = jp
53        self.q = q
54        self.bus = bus
55        self.conn = conn
56        self.stream = stream
57        self.incoming = incoming
58        self.params = params
59        self.jt2 = JingleTest2(jp, conn, q, stream, self.SELF_JID,
60                self.PEER_JID)
61        self.can_change_direction = (jp.dialect not in ['gtalk-v0.3',
62                'gtalk-v0.4'])
63        self.self_handle = conn.GetSelfHandle()
64        self.peer_handle = conn.RequestHandles(1, ["foo@bar.com/Foo"])[0]
65
66
67    def check_channel_state(self, state, wait = False):
68        """Optionnally wait for channel state to be reached and check that the
69           property has the right value"""
70        if wait:
71            self.q.expect('dbus-signal', signal='CallStateChanged',
72                    interface = cs.CHANNEL_TYPE_CALL,
73                    predicate = lambda e: e.args[0] == state)
74
75        assertEquals(state,
76                self.chan.Get(cs.CHANNEL_TYPE_CALL, 'CallState',
77                    dbus_interface=dbus.PROPERTIES_IFACE))
78
79
80    def check_stream_recv_state(self, stream, state):
81        assertEquals(state,
82                stream.Get(cs.CALL_STREAM_IFACE_MEDIA, 'ReceivingState',
83                    dbus_interface=dbus.PROPERTIES_IFACE))
84
85
86    def check_stream_send_state(self, stream, state):
87        assertEquals(state,
88                stream.Get(cs.CALL_STREAM_IFACE_MEDIA, 'SendingState',
89                    dbus_interface=dbus.PROPERTIES_IFACE))
90
91
92    def complete_receiving_state(self, stream):
93        if stream is None:
94            return
95        self.check_stream_recv_state(stream,
96                cs.CALL_STREAM_FLOW_STATE_PENDING_START)
97        stream.CompleteReceivingStateChange(cs.CALL_STREAM_FLOW_STATE_STARTED,
98                dbus_interface = cs.CALL_STREAM_IFACE_MEDIA)
99        self.q.expect('dbus-signal', signal='ReceivingStateChanged',
100                args = [cs.CALL_STREAM_FLOW_STATE_STARTED],
101                interface = cs.CALL_STREAM_IFACE_MEDIA)
102
103
104    def check_and_accept_offer(self, content, md, md_changed = True,
105            offer_path = None):
106        [path, remote_md] = content.Get(cs.CALL_CONTENT_IFACE_MEDIA,
107                "MediaDescriptionOffer", dbus_interface=dbus.PROPERTIES_IFACE)
108
109        if offer_path != None:
110            assertEquals(offer_path, path)
111
112        assertNotEquals("/", path)
113
114        offer = self.bus.get_object(self.conn.bus_name, path)
115        codecmap_property = offer.Get(cs.CALL_CONTENT_MEDIADESCRIPTION,
116                "Codecs", dbus_interface=dbus.PROPERTIES_IFACE)
117
118        assertEquals(remote_md[cs.CALL_CONTENT_MEDIADESCRIPTION + '.Codecs'],
119                codecmap_property)
120
121        offer.Accept(md, dbus_interface=cs.CALL_CONTENT_MEDIADESCRIPTION)
122
123        current_md = content.Get(cs.CALL_CONTENT_IFACE_MEDIA,
124                "LocalMediaDescriptions", dbus_interface=dbus.PROPERTIES_IFACE)
125        assertEquals(md,  current_md[self.peer_handle])
126
127        if md_changed:
128            o = self.q.expect('dbus-signal',
129                    signal='LocalMediaDescriptionChanged')
130            assertEquals([md], o.args)
131
132
133    def store_content(self, content_path, initial = True, incoming = None):
134        if incoming is None:
135            incoming = self.incoming
136
137        content = wrap_content(self.bus.get_object(self.conn.bus_name,
138                    content_path), ['DTMF', 'Media'])
139        content_props = content.GetAll(cs.CALL_CONTENT,
140                dbus_interface=dbus.PROPERTIES_IFACE)
141
142        # Has one stream
143        assertLength(1, content_props["Streams"])
144        if initial:
145            assertEquals(cs.CALL_DISPOSITION_INITIAL,
146                    content_props["Disposition"])
147        else:
148            assertEquals(cs.CALL_DISPOSITION_NONE, content_props["Disposition"])
149
150
151        # Implements Content.Interface.Media
152        assertContains(cs.CALL_CONTENT_IFACE_MEDIA, content_props["Interfaces"])
153
154        if content_props['Type'] == cs.CALL_MEDIA_TYPE_AUDIO:
155            # Implements Content.Interface.DTMF
156            assertContains(cs.CALL_CONTENT_IFACE_DTMF,
157                    content_props["Interfaces"])
158
159        assertContains("Name", content_props.keys())
160        content_name = content_props["Name"]
161
162        stream = self.bus.get_object(self.conn.bus_name,
163                content_props["Streams"][0])
164
165        stream_props = stream.GetAll(cs.CALL_STREAM,
166                dbus_interface = dbus.PROPERTIES_IFACE)
167
168        assertDoesNotContain(self.self_handle,
169                stream_props["RemoteMembers"].keys())
170        assertContains(self.peer_handle, stream_props["RemoteMembers"].keys())
171        assertEquals([cs.CALL_STREAM_IFACE_MEDIA], stream_props["Interfaces"])
172        assertEquals(self.can_change_direction,
173                stream_props["CanRequestReceiving"])
174
175        if incoming:
176            assertEquals(cs.CALL_SENDING_STATE_PENDING_SEND,
177                         stream_props["LocalSendingState"])
178            assertEquals(cs.CALL_SENDING_STATE_SENDING,
179                         stream_props["RemoteMembers"][self.peer_handle])
180        else:
181            if initial:
182                assertEquals(cs.CALL_SENDING_STATE_PENDING_SEND,
183                             stream_props["RemoteMembers"][self.peer_handle])
184            else:
185                assertEquals(cs.CALL_SENDING_STATE_SENDING,
186                             stream_props["RemoteMembers"][self.peer_handle])
187
188            assertEquals(cs.CALL_SENDING_STATE_SENDING,
189                         stream_props["LocalSendingState"])
190
191        # Packetization should be RTP
192        content_media_props = content.GetAll(cs.CALL_CONTENT_IFACE_MEDIA,
193                dbus_interface=dbus.PROPERTIES_IFACE)
194        assertEquals(cs.CALL_CONTENT_PACKETIZATION_RTP,
195                content_media_props["Packetization"])
196
197        # Check the directions
198        stream_media_props = stream.GetAll(cs.CALL_STREAM_IFACE_MEDIA,
199                dbus_interface=dbus.PROPERTIES_IFACE)
200        if initial or incoming:
201            assertEquals(cs.CALL_STREAM_FLOW_STATE_STOPPED,
202                         stream_media_props["SendingState"])
203        else:
204            assertEquals(cs.CALL_STREAM_FLOW_STATE_PENDING_START,
205                         stream_media_props["SendingState"])
206        if initial:
207            assertEquals(cs.CALL_STREAM_FLOW_STATE_STOPPED,
208                         stream_media_props["ReceivingState"])
209        else:
210            assertEquals(cs.CALL_STREAM_FLOW_STATE_PENDING_START,
211                         stream_media_props["ReceivingState"])
212        assertEquals(False,  stream_media_props["ICERestartPending"])
213
214        # Store the content and stream
215        if content_props['Type'] == cs.CALL_MEDIA_TYPE_AUDIO:
216            assert self.initial_audio == initial
217            assert self.audio_content == None
218            assert self.audio_stream == None
219            self.audio_content = content
220            self.audio_content_name = content_name
221            self.audio_stream = stream
222        elif content_props['Type'] == cs.CALL_MEDIA_TYPE_VIDEO:
223            assert self.initial_video == initial
224            assert self.video_content == None
225            assert self.video_stream == None
226            self.video_content = content
227            self.video_content_name = content_name
228            self.video_stream = stream
229        else:
230            assert not 'Bad content type value'
231
232
233    def enable_endpoint(self, endpoint):
234        endpoint.SetEndpointState(cs.CALL_STREAM_COMPONENT_DATA,
235                cs.CALL_STREAM_ENDPOINT_STATE_FULLY_CONNECTED,
236                dbus_interface=cs.CALL_STREAM_ENDPOINT)
237        self.q.expect('dbus-signal', signal='EndpointStateChanged',
238                interface=cs.CALL_STREAM_ENDPOINT)
239
240        endpoint.SetEndpointState(cs.CALL_STREAM_COMPONENT_CONTROL,
241                cs.CALL_STREAM_ENDPOINT_STATE_FULLY_CONNECTED,
242                dbus_interface=cs.CALL_STREAM_ENDPOINT)
243        self.q.expect('dbus-signal', signal='EndpointStateChanged',
244                interface=cs.CALL_STREAM_ENDPOINT)
245
246        state = endpoint.Get(cs.CALL_STREAM_ENDPOINT,
247                "EndpointState",  dbus_interface=dbus.PROPERTIES_IFACE)
248        assertEquals(cs.CALL_STREAM_ENDPOINT_STATE_FULLY_CONNECTED, state[1])
249        assertEquals(cs.CALL_STREAM_ENDPOINT_STATE_FULLY_CONNECTED, state[2])
250
251
252    def advertise(self, initial_audio = True, initial_video = True):
253        """Advertise that Call is supported"""
254        self.conn.ContactCapabilities.UpdateCapabilities([
255        (cs.CLIENT + ".CallHandler", [
256            { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CALL,
257                cs.CALL_INITIAL_AUDIO: initial_audio},
258            { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CALL,
259                cs.CALL_INITIAL_VIDEO: initial_video},
260            ], [
261                cs.CHANNEL_TYPE_CALL + '/gtalk-p2p',
262                cs.CHANNEL_TYPE_CALL + '/ice',
263                cs.CHANNEL_TYPE_CALL + '/video/h264',
264            ]),
265        ])
266
267
268    def prepare(self, events=None):
269        """Prepare the JingleTest2 object. This method can be override to trap
270           special event linke jingleinfo."""
271        self.jt2.prepare(events=events)
272
273
274    def initiate(self):
275        """Brind the call to INITIALISING state. This method will fill the
276            channel, contents and streams members."""
277        # Ensure a channel that doesn't exist yet.
278        if self.incoming:
279            if self.initial_audio and self.initial_video:
280                self.jt2.incoming_call(audio='audio1', video='video1')
281            elif self.initial_audio:
282                self.jt2.incoming_call(audio='audio1', video=None)
283            else:
284                self.jt2.incoming_call(audio=None, video='video1')
285        else:
286            ret = self.conn.Requests.CreateChannel(
287                { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_CALL,
288                  cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT,
289                  cs.TARGET_HANDLE: self.peer_handle,
290                  cs.CALL_INITIAL_AUDIO: self.initial_audio,
291                  cs.CALL_INITIAL_VIDEO: self.initial_video,
292                })
293
294        signal = self.q.expect('dbus-signal', signal='NewChannels',
295            predicate=lambda e:
296                cs.CHANNEL_TYPE_CONTACT_LIST not in e.args[0][0][1].values())
297
298        assertLength(1, signal.args)
299        assertLength(1, signal.args[0])       # one channel
300        assertLength(2, signal.args[0][0])    # two struct members
301        emitted_props = signal.args[0][0][1]
302
303        assertEquals(
304            cs.CHANNEL_TYPE_CALL, emitted_props[cs.CHANNEL_TYPE])
305
306        peer_bare_jid = self.PEER_JID.split('/')[0]
307        assertEquals(self.peer_handle, emitted_props[cs.TARGET_HANDLE])
308        assertEquals(cs.HT_CONTACT, emitted_props[cs.TARGET_HANDLE_TYPE])
309        assertEquals(peer_bare_jid, emitted_props[cs.TARGET_ID])
310
311        assertEquals(not self.incoming, emitted_props[cs.REQUESTED])
312        if self.incoming:
313            assertEquals(self.peer_handle, emitted_props[cs.INITIATOR_HANDLE])
314            assertEquals(peer_bare_jid, emitted_props[cs.INITIATOR_ID])
315        else:
316            assertEquals(self.self_handle, emitted_props[cs.INITIATOR_HANDLE])
317            assertEquals(self.SELF_JID, emitted_props[cs.INITIATOR_ID])
318
319        assertEquals(self.initial_audio, emitted_props[cs.CALL_INITIAL_AUDIO])
320        assertEquals(self.initial_video, emitted_props[cs.CALL_INITIAL_VIDEO])
321
322        chan_path = signal.args[0][0][0]
323        self.chan = wrap_channel(
324                self.bus.get_object(self.conn.bus_name, chan_path),
325                'Call', ['Hold'])
326
327        properties = self.chan.GetAll(cs.CHANNEL_TYPE_CALL,
328            dbus_interface=dbus.PROPERTIES_IFACE)
329
330        # Check if all the properties are there
331        assertEquals(sorted([ "Contents", "CallMembers",
332            "CallState", "CallFlags", "CallStateReason", "CallStateDetails",
333            "HardwareStreaming", "InitialAudio", "InitialAudioName",
334            "InitialVideo", "InitialVideoName", "MutableContents",
335            "InitialTransport", "MemberIdentifiers" ]),
336            sorted(properties.keys()))
337
338        # Remote member is the target
339        assertEquals([self.peer_handle], properties["CallMembers"].keys())
340        assertEquals(0, properties["CallMembers"][self.peer_handle])
341
342        # No Hardware Streaming for you
343        assertEquals(False, properties["HardwareStreaming"])
344
345        # Store content and stream
346        nb_contents = self.initial_audio + self.initial_video
347        assertLength(nb_contents, properties["Contents"])
348
349        for content_path in  properties["Contents"]:
350            self.store_content(content_path)
351
352        if self.initial_audio:
353            assert self.audio_content
354        if self.initial_video:
355            assert self.video_content
356
357
358    def accept_outgoing(self):
359        """If call is incoming, accept the channel and complete the receiving
360           state change. Then do state check. This method shall be called even
361           if receiving a call to execute the state sanity checks."""
362        # Check if the channel is in the right pending state
363        if not self.incoming:
364            self.check_channel_state(cs.CALL_STATE_PENDING_INITIATOR)
365            self.chan.Accept(dbus_interface=cs.CHANNEL_TYPE_CALL)
366
367            if self.initial_audio:
368                self.complete_receiving_state(self.audio_stream)
369                # Don't start sending before the call is accepted locally or
370                # remotely
371                self.check_stream_send_state(self.audio_stream,
372                        cs.CALL_STREAM_FLOW_STATE_STOPPED)
373
374            if self.initial_video:
375                self.complete_receiving_state(self.video_stream)
376                self.check_stream_send_state(self.video_stream,
377                        cs.CALL_STREAM_FLOW_STATE_STOPPED)
378
379        # All Direction should be both now for outgoing
380        if self.initial_audio:
381            stream_props = self.audio_stream.GetAll(cs.CALL_STREAM,
382                    dbus_interface = dbus.PROPERTIES_IFACE)
383
384            if self.incoming:
385                assertEquals({self.peer_handle: cs.CALL_SENDING_STATE_SENDING},
386                        stream_props["RemoteMembers"])
387                assertEquals(cs.CALL_SENDING_STATE_PENDING_SEND,
388                        stream_props["LocalSendingState"])
389            else:
390                assertEquals(
391                        {self.peer_handle: cs.CALL_SENDING_STATE_PENDING_SEND},
392                        stream_props["RemoteMembers"])
393                assertEquals(cs.CALL_SENDING_STATE_SENDING,
394                        stream_props["LocalSendingState"])
395
396        if self.initial_video:
397            stream_props = self.video_stream.GetAll(cs.CALL_STREAM,
398                    dbus_interface = dbus.PROPERTIES_IFACE)
399
400            if self.incoming:
401                assertEquals({self.peer_handle: cs.CALL_SENDING_STATE_SENDING},
402                        stream_props["RemoteMembers"])
403                assertEquals(cs.CALL_SENDING_STATE_PENDING_SEND,
404                        stream_props["LocalSendingState"])
405            else:
406                assertEquals(
407                        {self.peer_handle: cs.CALL_SENDING_STATE_PENDING_SEND},
408                        stream_props["RemoteMembers"])
409                assertEquals(cs.CALL_SENDING_STATE_SENDING,
410                        stream_props["LocalSendingState"])
411
412        self.check_channel_state(cs.CALL_STATE_INITIALISING)
413
414    def connect_streams(self, contents, streams, mds, expect_after_si=None):
415        # Expected to fail since we did not said we are controlling side
416        try:
417            contents[0].UpdateLocalMediaDescription(mds[0],
418                    dbus_interface=cs.CALL_CONTENT_IFACE_MEDIA)
419        except DBusException, e:
420            if e.get_dbus_name() != cs.NOT_AVAILABLE:
421                raise e
422        else:
423            assert False
424
425        expected = []
426        candidates = self.jt2.get_call_remote_transports_dbus()
427
428        for i in range(len(contents)):
429            self.check_and_accept_offer(contents[i], mds[i], md_changed=False)
430            expected.append(EventPattern('dbus-signal',
431                        signal='LocalMediaDescriptionChanged', args=[mds[i]]))
432
433            current_md = contents[i].Get(cs.CALL_CONTENT_IFACE_MEDIA,
434                    "LocalMediaDescriptions",
435                    dbus_interface=dbus.PROPERTIES_IFACE)
436            assertEquals(mds[i],  current_md[self.peer_handle])
437
438            streams[i].SetCredentials(self.jt2.ufrag, self.jt2.pwd,
439                    dbus_interface=cs.CALL_STREAM_IFACE_MEDIA)
440
441            expected.append(EventPattern('dbus-signal',
442                        signal='LocalCredentialsChanged',
443                        args=[self.jt2.ufrag, self.jt2.pwd]))
444
445            credentials = streams[i].GetAll(cs.CALL_STREAM_IFACE_MEDIA,
446                    dbus_interface=dbus.PROPERTIES_IFACE)["LocalCredentials"]
447            assertEquals((self.jt2.ufrag, self.jt2.pwd), credentials)
448
449            # Add candidates
450            streams[i].AddCandidates(candidates,
451                dbus_interface=cs.CALL_STREAM_IFACE_MEDIA)
452
453            expected.append(EventPattern('dbus-signal',
454                        signal='LocalCandidatesAdded'))
455
456        if not self.incoming:
457            expected.append(EventPattern('stream-iq',
458                        predicate=self.jp.action_predicate('session-initiate')))
459
460        ret = self.q.expect_many(*expected)
461        # Check the first LocalCandidatesAdded signal (third in the array)
462        assertEquals(candidates, ret[2].args[0])
463
464        if not self.incoming:
465            if expect_after_si is not None:
466                sync_stream(self.q, self.stream)
467                self.q.unforbid_events(expect_after_si)
468
469            self.stream.send(make_result_iq(self.stream, ret[-1].stanza))
470
471            if expect_after_si is not None:
472                self.q.expect_many(*expect_after_si)
473
474            self.jt2.parse_session_initiate(ret[-1].query)
475
476        endpoints = []
477
478        for stream in streams:
479            stream.FinishInitialCandidates(
480                    dbus_interface=cs.CALL_STREAM_IFACE_MEDIA)
481
482            local_candidates = stream.Get(cs.CALL_STREAM_IFACE_MEDIA,
483                    "LocalCandidates", dbus_interface=dbus.PROPERTIES_IFACE)
484            assertEquals(candidates,  local_candidates)
485
486            endpoint_paths = stream.Get(cs.CALL_STREAM_IFACE_MEDIA,
487                    "Endpoints", dbus_interface=dbus.PROPERTIES_IFACE)
488            assertLength(1, endpoint_paths)
489
490            # There doesn't seem to be a good way to get the transport type from
491            # the JP used, for now assume we prefer gtalk p2p and always pick
492            # that..
493            transport = stream.Get(cs.CALL_STREAM_IFACE_MEDIA,
494                    "Transport", dbus_interface=dbus.PROPERTIES_IFACE)
495            assertEquals(cs.CALL_STREAM_TRANSPORT_GTALK_P2P, transport)
496
497            endpoint = self.bus.get_object(self.conn.bus_name,
498                    endpoint_paths[0])
499            endpoints.append(endpoint)
500
501            endpoint_props = endpoint.GetAll(cs.CALL_STREAM_ENDPOINT,
502                    dbus_interface=dbus.PROPERTIES_IFACE)
503            transport = endpoint_props["Transport"]
504            assertEquals(cs.CALL_STREAM_TRANSPORT_GTALK_P2P, transport)
505
506            remote_candidates = endpoint.Get(cs.CALL_STREAM_ENDPOINT,
507                    "RemoteCandidates",  dbus_interface=dbus.PROPERTIES_IFACE)
508
509            assertEquals([], remote_candidates)
510
511            selected_candidate = endpoint.Get(cs.CALL_STREAM_ENDPOINT,
512                    "SelectedCandidatePairs",
513                    dbus_interface=dbus.PROPERTIES_IFACE)
514            assertEquals([], selected_candidate)
515
516            state = endpoint.Get(cs.CALL_STREAM_ENDPOINT,
517                    "EndpointState",  dbus_interface=dbus.PROPERTIES_IFACE)
518            assertEquals({}, state)
519
520        names = []
521        for content in contents:
522            if content is self.audio_content:
523                names.append(self.jt2.audio_names[0])
524            else:
525                names.append(self.jt2.video_names[0])
526
527        for name in names:
528            if self.jp.dialect == 'gtalk-v0.3':
529                # Candidates must be sent one at a time.
530                for candidate in self.jt2.get_call_remote_transports_dbus():
531                    component, addr, port, props = candidate
532                    self.jt2.send_remote_candidates_call_xmpp(
533                            name, "initiator", [candidate])
534                    self.q.expect('dbus-signal',
535                            signal='RemoteCandidatesAdded',
536                            interface=cs.CALL_STREAM_ENDPOINT,
537                            args=[[(component, addr, port,
538                                DictionarySupersetOf(props))]])
539            elif self.jp.dialect == 'gtalk-v0.4' and not self.incoming:
540                # Don't test this case at all.
541                pass
542            else:
543                self.jt2.send_remote_candidates_call_xmpp(name, "initiator")
544
545                candidates = []
546                for component, addr, port, props in \
547                        self.jt2.get_call_remote_transports_dbus():
548                    candidates.append((component, addr, port,
549                                DictionarySupersetOf(props)))
550
551                self.q.expect('dbus-signal',
552                        signal='RemoteCandidatesAdded',
553                        interface=cs.CALL_STREAM_ENDPOINT,
554                        args=[candidates])
555
556        # FIXME: makes sense to have same local and remote candidate?
557        candidate1 = self.jt2.get_call_remote_transports_dbus()[0]
558        candidate2 = self.jt2.get_call_remote_transports_dbus()[1]
559
560        for endpoint in endpoints:
561            # Expected to fail since we did not said we are controlling side
562            try:
563                endpoint.SetSelectedCandidatePair(candidate1, candidate1,
564                    dbus_interface=cs.CALL_STREAM_ENDPOINT)
565            except DBusException, e:
566                if e.get_dbus_name() != cs.INVALID_ARGUMENT:
567                    raise e
568            else:
569                assert false
570
571            endpoint.SetControlling(True,
572                    dbus_interface=cs.CALL_STREAM_ENDPOINT)
573            endpoint.SetSelectedCandidatePair(candidate1, candidate1,
574                    dbus_interface=cs.CALL_STREAM_ENDPOINT)
575
576            pair = self.q.expect('dbus-signal',
577                    signal='CandidatePairSelected',
578                    interface=cs.CALL_STREAM_ENDPOINT)
579            assertEquals(candidate1, pair.args[0])
580            assertEquals(candidate1, pair.args[1])
581
582            endpoint.SetSelectedCandidatePair(candidate2, candidate2,
583                    dbus_interface=cs.CALL_STREAM_ENDPOINT)
584
585            # We have an RTCP candidate as well, so we should set this as
586            # selected too.
587            pair = self.q.expect('dbus-signal', signal='CandidatePairSelected',
588                    interface=cs.CALL_STREAM_ENDPOINT)
589            assertEquals(candidate2, pair.args[0])
590            assertEquals(candidate2, pair.args[1])
591
592            pairs = endpoint.Get(cs.CALL_STREAM_ENDPOINT,
593                    "SelectedCandidatePairs",
594                    dbus_interface=dbus.PROPERTIES_IFACE)
595            assertEquals(len(pairs), 2)
596            assertEquals(pairs[0][0], pairs[0][1])
597            assertEquals(pairs[1][0], pairs[1][1])
598            if pairs[0][0] == candidate1:
599                assertEquals(pairs[1][0], candidate2)
600            else:
601                assertEquals(pairs[0][0], candidate2)
602                assertEquals(pairs[1][0], candidate1)
603
604            # setting endpoints to CONNECTED should make the call state move
605            # from INITIALISING to INITIALISED
606            self.enable_endpoint(endpoint)
607
608        self.check_channel_state(cs.CALL_STATE_INITIALISED)
609
610    def connect(self, expect_after_si=None):
611        """Negotiate all the codecs, bringing the channel to INITIALISED
612           state"""
613
614        contents = []
615        streams = []
616        mds = []
617
618        if self.initial_audio:
619            # Setup media description
620            contents.append(self.audio_content)
621            streams.append(self.audio_stream)
622            mds.append(self.jt2.get_call_audio_md_dbus(self.peer_handle))
623
624        if self.initial_video:
625            contents.append(self.video_content)
626            streams.append(self.video_stream)
627            mds.append(self.jt2.get_call_video_md_dbus(self.peer_handle))
628
629        self.connect_streams(contents, streams, mds,
630                expect_after_si=expect_after_si)
631
632
633    def pickup(self, held=False):
634        if self.initial_audio:
635            self.check_stream_send_state(self.audio_stream,
636                    cs.CALL_STREAM_FLOW_STATE_STOPPED)
637        if self.initial_video:
638            self.check_stream_send_state(self.video_stream,
639                    cs.CALL_STREAM_FLOW_STATE_STOPPED)
640
641        if self.incoming:
642            # Act as if we're ringing
643            self.chan.SetRinging(dbus_interface=cs.CHANNEL_TYPE_CALL)
644            signal = self.q.expect('dbus-signal', signal='CallStateChanged')
645            assertEquals(cs.CALL_FLAG_LOCALLY_RINGING,
646                    signal.args[1] & cs.CALL_FLAG_LOCALLY_RINGING)
647
648            # And now pickup the call
649            self.chan.Accept(dbus_interface=cs.CHANNEL_TYPE_CALL)
650
651            expected = [
652                EventPattern('dbus-signal', signal='CallStateChanged'),
653                EventPattern('stream-iq',
654                        predicate=self.jp.action_predicate('session-accept'))]
655            if self.initial_audio:
656                # SendingStateChanged is caused by chan.Accept
657                expected.append(EventPattern('dbus-signal',
658                            signal='SendingStateChanged'))
659                recv_state = self.audio_stream.GetAll(
660                        cs.CALL_STREAM_IFACE_MEDIA,
661                        dbus_interface=dbus.PROPERTIES_IFACE)["ReceivingState"]
662                assertEquals(cs.CALL_STREAM_FLOW_STATE_PENDING_START,
663                        recv_state)
664                self.audio_stream.CompleteReceivingStateChange(
665                        cs.CALL_STREAM_FLOW_STATE_STARTED,
666                        dbus_interface = cs.CALL_STREAM_IFACE_MEDIA)
667                expected.append(EventPattern('dbus-signal',
668                            signal='ReceivingStateChanged'))
669
670            if self.initial_video:
671                # SendingStateChanged is caused by chan.Accept
672                expected.append(EventPattern('dbus-signal',
673                            signal='SendingStateChanged'))
674                recv_state = self.video_stream.GetAll(
675                        cs.CALL_STREAM_IFACE_MEDIA,
676                        dbus_interface=dbus.PROPERTIES_IFACE)["ReceivingState"]
677                assertEquals(cs.CALL_STREAM_FLOW_STATE_PENDING_START,
678                        recv_state)
679                self.video_stream.CompleteReceivingStateChange(
680                        cs.CALL_STREAM_FLOW_STATE_STARTED,
681                        dbus_interface = cs.CALL_STREAM_IFACE_MEDIA)
682                expected.append(EventPattern('dbus-signal',
683                            signal='ReceivingStateChanged'))
684
685            ret = self.q.expect_many(*expected)
686
687            assertEquals(0, ret[0].args[1] & cs.CALL_FLAG_LOCALLY_RINGING)
688            if self.initial_audio and self.initial_video:
689                assertEquals(cs.CALL_STREAM_FLOW_STATE_PENDING_START,
690                        ret[2].args[0])
691                assertEquals(cs.CALL_STREAM_FLOW_STATE_PENDING_START,
692                        ret[3].args[0])
693            else:
694                assertEquals(cs.CALL_STREAM_FLOW_STATE_PENDING_START,
695                        ret[2].args[0])
696
697            self.jt2.result_iq(ret[1])
698        else:
699            if self.jp.is_modern_jingle():
700                # The other person's client starts ringing, and tells us so!
701                node = self.jp.SetIq(self.jt2.peer, self.jt2.jid, [
702                    self.jp.Jingle(self.jt2.sid, self.jt2.jid, 'session-info', [
703                        ('ringing', ns.JINGLE_RTP_INFO_1, {}, []) ]) ])
704                self.stream.send(self.jp.xml(node))
705
706                o = self.q.expect('dbus-signal', signal="CallMembersChanged")
707                assertEquals(cs.CALL_MEMBER_FLAG_RINGING,
708                        o.args[0][self.peer_handle])
709
710            self.jt2.accept()
711
712            expected = [EventPattern('dbus-signal',
713                    signal='NewMediaDescriptionOffer')]
714
715            if not held:
716                if self.initial_audio:
717                    expected.append(EventPattern('dbus-signal',
718                                signal='SendingStateChanged'))
719                if self.initial_video:
720                    expected.append(EventPattern('dbus-signal',
721                                signal='SendingStateChanged'))
722
723            ret = self.q.expect_many(*expected)
724
725            if not held:
726                # Checking one of sending states
727                assertEquals(cs.CALL_STREAM_FLOW_STATE_PENDING_START,
728                        ret[1].args[0])
729
730            if self.initial_audio:
731                md = self.jt2.get_call_audio_md_dbus(self.peer_handle)
732                self.check_and_accept_offer(self.audio_content, md,
733                        md_changed = False)
734            if self.initial_video:
735                md = self.jt2.get_call_video_md_dbus(self.peer_handle)
736                self.check_and_accept_offer(self.video_content, md,
737                        md_changed = False)
738
739        self.check_channel_state(cs.CALL_STATE_ACTIVE)
740
741        # All Direction should be both sending now
742
743        if self.initial_audio and not held:
744            stream_props = self.audio_stream.GetAll(cs.CALL_STREAM,
745                    dbus_interface = dbus.PROPERTIES_IFACE)
746            assertEquals({self.peer_handle: cs.CALL_SENDING_STATE_SENDING},
747                    stream_props["RemoteMembers"])
748            assertEquals(cs.CALL_SENDING_STATE_SENDING,
749                    stream_props["LocalSendingState"])
750            assertEquals(cs.CALL_STREAM_FLOW_STATE_PENDING_START,
751                    self.audio_stream.Get(cs.CALL_STREAM_IFACE_MEDIA,
752                        "SendingState",
753                        dbus_interface = dbus.PROPERTIES_IFACE))
754
755            self.audio_stream.CompleteSendingStateChange(
756                    cs.CALL_STREAM_FLOW_STATE_STARTED,
757                    dbus_interface = cs.CALL_STREAM_IFACE_MEDIA)
758            o = self.q.expect('dbus-signal', signal='SendingStateChanged',
759                    interface = cs.CALL_STREAM_IFACE_MEDIA)
760            assertEquals(cs.CALL_STREAM_FLOW_STATE_STARTED, o.args[0])
761
762        if self.initial_video and not held:
763            stream_props = self.video_stream.GetAll(cs.CALL_STREAM,
764                    dbus_interface = dbus.PROPERTIES_IFACE)
765            assertEquals({self.peer_handle: cs.CALL_SENDING_STATE_SENDING},
766                    stream_props["RemoteMembers"])
767            assertEquals(cs.CALL_SENDING_STATE_SENDING,
768                    stream_props["LocalSendingState"])
769            assertEquals(cs.CALL_STREAM_FLOW_STATE_PENDING_START,
770                    self.video_stream.Get(cs.CALL_STREAM_IFACE_MEDIA,
771                        "SendingState",
772                        dbus_interface = dbus.PROPERTIES_IFACE))
773
774            self.video_stream.CompleteSendingStateChange(
775                    cs.CALL_STREAM_FLOW_STATE_STARTED,
776                    dbus_interface = cs.CALL_STREAM_IFACE_MEDIA)
777            o = self.q.expect('dbus-signal', signal='SendingStateChanged',
778                    interface = cs.CALL_STREAM_IFACE_MEDIA)
779            assertEquals(cs.CALL_STREAM_FLOW_STATE_STARTED, o.args[0])
780
781    def hangup(self):
782        if self.incoming:
783            self.jt2.terminate()
784        else:
785            self.chan.Hangup(0, "", "",
786                dbus_interface=cs.CHANNEL_TYPE_CALL)
787
788        self.check_channel_state(cs.CALL_STATE_ENDED, wait = True)
789
790
791    def run(self):
792        if self.initial_video:
793            if not self.initial_audio and not self.jp.can_do_video_only():
794                return
795            elif not self.jp.can_do_video():
796                return
797        self.advertise()
798        self.prepare()
799        self.initiate()
800        self.accept_outgoing()
801        self.connect()
802        self.pickup()
803        self.hangup()
804
805
806def run_call_test(jp, q, bus, conn, stream, klass=CallTest, incoming=False,
807        params={}):
808    test = klass(jp, q, bus, conn, stream, incoming, params)
809    test.run()
810
811if __name__ == '__main__':
812    test_all_dialects(partial(run_call_test, incoming=False))
813    test_all_dialects(partial(run_call_test, incoming=True))
814
815