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