1"""
2Test StreamError events when new content is rejected in-call.
3"""
4
5import dbus
6
7from gabbletest import make_result_iq, sync_stream, exec_test
8from servicetest import (
9    make_channel_proxy, unwrap, EventPattern, assertEquals, assertLength)
10from jingletest2 import JingleTest2, JingleProtocol031
11import constants as cs
12
13from twisted.words.xish import xpath
14
15from config import VOIP_ENABLED
16
17if not VOIP_ENABLED:
18    print "NOTE: built with --disable-voip"
19    raise SystemExit(77)
20
21def _content_reject_predicate(event):
22    reason = xpath.queryForNodes("/iq"
23                               "/jingle[@action='content-reject']"
24                               "/reason/failed-application",
25                               event.stanza)
26
27    return bool(reason)
28
29def _start_audio_session(jp, q, bus, conn, stream, incoming):
30    jt = JingleTest2(jp, conn, q, stream, 'test@localhost', 'foo@bar.com/Foo')
31    jt.prepare()
32
33    self_handle = conn.GetSelfHandle()
34    remote_handle = conn.RequestHandles(cs.HT_CONTACT, [jt.peer])[0]
35
36    if incoming:
37        jt.incoming_call()
38    else:
39        ret = conn.Requests.CreateChannel(
40            { cs.CHANNEL_TYPE: cs.CHANNEL_TYPE_STREAMED_MEDIA,
41              cs.TARGET_HANDLE_TYPE: cs.HT_CONTACT,
42              cs.TARGET_HANDLE: remote_handle,
43              cs.INITIAL_AUDIO: True
44              })
45
46    nc, e = q.expect_many(
47        EventPattern('dbus-signal', signal='NewChannel',
48            predicate=lambda e: cs.CHANNEL_TYPE_CONTACT_LIST not in e.args),
49        EventPattern('dbus-signal', signal='NewSessionHandler'))
50
51    path = nc.args[0]
52
53    media_chan = make_channel_proxy(conn, path, 'Channel.Interface.Group')
54    media_iface = make_channel_proxy(conn, path, 'Channel.Type.StreamedMedia')
55
56    # S-E was notified about new session handler, and calls Ready on it
57    session_handler = make_channel_proxy(conn, e.args[0],
58                                         'Media.SessionHandler')
59    session_handler.Ready()
60
61    nsh_event = q.expect('dbus-signal', signal='NewStreamHandler')
62
63    # S-E gets notified about a newly-created stream
64    stream_handler = make_channel_proxy(conn, nsh_event.args[0],
65        'Media.StreamHandler')
66
67    group_props = media_chan.GetAll(
68        cs.CHANNEL_IFACE_GROUP, dbus_interface=dbus.PROPERTIES_IFACE)
69
70    if incoming:
71        assertEquals([remote_handle], group_props['Members'])
72        assertEquals(unwrap(group_props['LocalPendingMembers']),
73                     [(self_handle, remote_handle, cs.GC_REASON_INVITED, '')])
74    else:
75        assertEquals([self_handle], group_props['Members'])
76
77    streams = media_chan.ListStreams(
78            dbus_interface=cs.CHANNEL_TYPE_STREAMED_MEDIA)
79
80    stream_id = streams[0][0]
81
82    stream_handler.NewNativeCandidate("fake", jt.get_remote_transports_dbus())
83    stream_handler.Ready(jt.dbusify_codecs([("FOO", 5, 8000, {})]))
84
85    msg = u"None of the codecs are good for us, damn!"
86
87    expected_events = []
88
89    if incoming:
90        stream_handler.StreamState(cs.MEDIA_STREAM_STATE_CONNECTED)
91        stream_handler.SupportedCodecs(jt.get_audio_codecs_dbus())
92
93        e = q.expect('stream-iq', predicate=jp.action_predicate('transport-info'))
94        assertEquals(jt.peer, e.query['initiator'])
95        content = xpath.queryForNodes('/iq/jingle/content', e.stanza)[0]
96        assertEquals('initiator', content['creator'])
97
98        stream.send(make_result_iq(stream, e.stanza))
99
100        media_chan.AddMembers([self_handle], 'accepted')
101
102        memb, acc, _, _, _ = q.expect_many(
103            EventPattern('dbus-signal', signal='MembersChanged',
104                         args=[u'', [self_handle], [], [], [], self_handle,
105                               cs.GC_REASON_NONE]),
106            EventPattern('stream-iq',
107                         predicate=jp.action_predicate('session-accept')),
108            EventPattern('dbus-signal', signal='SetStreamSending',
109                         args=[True]),
110            EventPattern('dbus-signal', signal='SetStreamPlaying',
111                         args=[True]),
112            EventPattern('dbus-signal', signal='StreamDirectionChanged',
113                         args=[stream_id,
114                               cs.MEDIA_STREAM_DIRECTION_BIDIRECTIONAL, 0]))
115
116        stream.send(make_result_iq(stream, acc.stanza))
117
118        active_event = jp.rtp_info_event("active")
119        if active_event is not None:
120            q.expect_many(active_event)
121
122        members = media_chan.GetMembers()
123        assert set(members) == set([self_handle, remote_handle]), members
124    else:
125        stream_handler.StreamState(cs.MEDIA_STREAM_STATE_CONNECTED)
126        session_initiate = q.expect(
127            'stream-iq',
128            predicate=jp.action_predicate('session-initiate'))
129
130        q.expect('dbus-signal', signal='MembersChanged', path=path,
131                 args=['', [], [], [], [remote_handle], self_handle,
132                       cs.GC_REASON_INVITED])
133
134        jt.parse_session_initiate(session_initiate.query)
135        stream.send(jp.xml(jp.ResultIq('test@localhost',
136                                       session_initiate.stanza, [])))
137
138        jt.accept()
139
140        q.expect_many(
141            EventPattern('stream-iq', iq_type='result'),
142            # Call accepted
143            EventPattern('dbus-signal', signal='MembersChanged',
144                         args=['', [remote_handle], [], [], [], remote_handle,
145                               cs.GC_REASON_NONE]),
146            )
147    return jt, media_iface
148
149def _start_audio_session_outgoing(jp, q, bus, conn, stream):
150    return _start_audio_session(jp, q, bus, conn, stream, False)
151
152def _start_audio_session_incoming(jp, q, bus, conn, stream):
153    return _start_audio_session(jp, q, bus, conn, stream, True)
154
155def _remote_content_add(jp, q, bus, conn, stream, initiate_call_func):
156    jt, chan = initiate_call_func(jp, q, bus, conn, stream)
157
158    video_codecs = [
159        jp.PayloadType(name, str(rate), str(id), parameters) \
160            for (name, id, rate, parameters) in jt.video_codecs]
161
162    node = jp.SetIq(jt.peer, jt.jid, [
163            jp.Jingle(jt.sid, jt.peer, 'content-add', [
164                    jp.Content(
165                        'videostream', 'initiator', 'both',
166                        jp.Description('video', video_codecs),
167                        jp.TransportGoogleP2P()) ]) ])
168    stream.send(jp.xml(node))
169
170    _, nsh = q.expect_many(
171        EventPattern('dbus-signal', signal='StreamAdded'),
172        EventPattern('dbus-signal', signal='NewStreamHandler'))
173
174    stream_handler_path, stream_id, media_type, direction = nsh.args
175
176    video_handler = make_channel_proxy(conn, stream_handler_path,
177                                       'Media.StreamHandler')
178
179    video_handler.NewNativeCandidate("fake",
180                                     jt.get_remote_transports_dbus())
181    video_handler.Ready(jt.dbusify_codecs([("FOO", 5, 8000, {})]))
182
183    msg = u"None of the codecs are good for us, damn!"
184
185    video_handler.Error(cs.MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED, msg)
186
187    q.expect_many(
188        EventPattern('dbus-signal', signal='StreamError',
189                     args=[stream_id,
190                           cs.MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED,
191                           msg]),
192        EventPattern('stream-iq', predicate=_content_reject_predicate))
193
194def _local_content_add(jp, q, bus, conn, stream, initiate_call_func):
195    jt, chan = initiate_call_func(jp, q, bus, conn, stream)
196
197    remote_handle = conn.RequestHandles(cs.HT_CONTACT, [jt.peer])[0]
198
199    chan.RequestStreams(remote_handle, [cs.MEDIA_STREAM_TYPE_VIDEO])
200
201    nsh = q.expect('dbus-signal', signal='NewStreamHandler')
202    stream_handler_path, stream_id, media_type, direction = nsh.args
203    video_handler = make_channel_proxy(conn, stream_handler_path,
204                                       'Media.StreamHandler')
205
206    video_handler.NewNativeCandidate("fake", jt.get_remote_transports_dbus())
207    video_handler.Ready(jt.get_audio_codecs_dbus())
208    video_handler.StreamState(cs.MEDIA_STREAM_STATE_CONNECTED)
209
210    e = q.expect('stream-iq', predicate=jp.action_predicate('content-add'))
211    c = e.query.firstChildElement()
212    stream.send(make_result_iq(stream, e.stanza))
213
214    node = jp.SetIq(jt.peer, jt.jid, [
215            jp.Jingle(jt.sid, jt.peer, 'content-reject', [
216                    ('reason', None, {}, [
217                            ('failed-application', None, {}, [])]),
218                    jp.Content(c['name'], c['creator'], c['senders']) ]) ])
219    stream.send(jp.xml(node))
220
221    q.expect('dbus-signal', signal='StreamError',
222             args=[stream_id,
223                   cs.MEDIA_STREAM_ERROR_CODEC_NEGOTIATION_FAILED,
224                   ""]),
225
226def test_remote_content_add_incoming(jp, q, bus, conn, stream):
227    _remote_content_add(jp, q, bus, conn, stream,
228                        _start_audio_session_incoming)
229
230def test_remote_content_add_outgoing(jp, q, bus, conn, stream):
231    _remote_content_add(jp, q, bus, conn, stream,
232                        _start_audio_session_outgoing)
233
234def test_local_content_add_incoming(jp, q, bus, conn, stream):
235    _local_content_add(jp, q, bus, conn, stream, _start_audio_session_incoming)
236
237def test_local_content_add_outgoing(jp, q, bus, conn, stream):
238    _local_content_add(jp, q, bus, conn, stream, _start_audio_session_outgoing)
239
240if __name__ == '__main__':
241    for f in (test_local_content_add_incoming,
242              test_local_content_add_outgoing,
243              test_remote_content_add_incoming,
244              test_remote_content_add_outgoing):
245        exec_test(
246            lambda q, b, c, s: f(JingleProtocol031(), q, b, c, s))
247