Line data Source code
1 : /*
2 : * Copyright (C) 2021-2024 Savoir-faire Linux Inc.
3 : *
4 : * Author: Mohamed Chibani <mohamed.chibani@savoirfairelinux.com>
5 : *
6 : * This program is free software; you can redistribute it and/or modify
7 : * it under the terms of the GNU General Public License as published by
8 : * the Free Software Foundation; either version 3 of the License, or
9 : * (at your option) any later version.
10 : *
11 : * This program is distributed in the hope that it will be useful,
12 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : * GNU General Public License for more details.
15 : *
16 : * You should have received a copy of the GNU General Public License
17 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 : */
19 :
20 : #include <cppunit/TestAssert.h>
21 : #include <cppunit/TestFixture.h>
22 : #include <cppunit/extensions/HelperMacros.h>
23 :
24 : #include <condition_variable>
25 : #include <string>
26 :
27 : #include "manager.h"
28 : #include "sip/sipaccount.h"
29 : #include "../../test_runner.h"
30 :
31 : #include "jami.h"
32 : #include "media_const.h"
33 : #include "call_const.h"
34 : #include "account_const.h"
35 : #include "sip/sipcall.h"
36 : #include "media/audio/audio_rtp_session.h"
37 : #include "media/audio/audio_receive_thread.h"
38 : #include "media/video/video_rtp_session.h"
39 : #include "media/video/video_receive_thread.h"
40 :
41 : #include "common.h"
42 :
43 : using namespace libjami::Account;
44 : using namespace libjami::Call;
45 :
46 : namespace jami {
47 : namespace test {
48 :
49 : struct CallData
50 : {
51 : struct Signal
52 : {
53 24 : Signal(const std::string& name, const std::string& event = {})
54 24 : : name_(std::move(name))
55 24 : , event_(std::move(event)) {};
56 :
57 : std::string name_ {};
58 : std::string event_ {};
59 : };
60 :
61 : std::string accountId_ {};
62 : std::string userName_ {};
63 : uint16_t listeningPort_ {0};
64 : std::string alias_ {};
65 : std::string callId_ {};
66 : std::vector<Signal> signals_;
67 : std::condition_variable cv_ {};
68 : std::mutex mtx_;
69 : bool compliancyEnabled_ {false};
70 : };
71 :
72 : // Used to register a MediaFrame observer to RTP session in order
73 : // to validate the media stream.
74 : class MediaReceiver : public Observer<std::shared_ptr<MediaFrame>>
75 : {
76 : public:
77 4 : MediaReceiver(MediaType type)
78 8 : : mediaType_(type)
79 8 : , mediaTypeStr_(type == MediaType::MEDIA_AUDIO ? "AUDIO" : "VIDEO") {};
80 :
81 4 : virtual ~MediaReceiver() {};
82 : void update(Observable<std::shared_ptr<jami::MediaFrame>>* observer,
83 : const std::shared_ptr<jami::MediaFrame>& mediaframe) override;
84 :
85 : bool waitForMediaFlow();
86 : const MediaType mediaType_ {MediaType::MEDIA_NONE};
87 : const std::string mediaTypeStr_ {};
88 4 : const std::chrono::seconds TIME_OUT {10};
89 : const unsigned REQUIRED_FRAME_COUNT {100};
90 :
91 : private:
92 : unsigned long frameCounter_ {0};
93 : std::condition_variable cv_ {};
94 : std::mutex mtx_;
95 : };
96 :
97 : void
98 0 : MediaReceiver::update(Observable<std::shared_ptr<jami::MediaFrame>>*,
99 : const std::shared_ptr<jami::MediaFrame>& frame)
100 : {
101 0 : std::unique_lock lock {mtx_};
102 0 : if (frame and frame->getFrame())
103 0 : frameCounter_++;
104 :
105 0 : if (frameCounter_ % 10 == 1) {
106 0 : JAMI_INFO("[%s] Frame counter %lu", mediaTypeStr_.c_str(), frameCounter_);
107 : }
108 :
109 0 : if (frameCounter_ >= REQUIRED_FRAME_COUNT)
110 0 : cv_.notify_one();
111 0 : }
112 :
113 : bool
114 0 : MediaReceiver::waitForMediaFlow()
115 : {
116 0 : std::unique_lock lock {mtx_};
117 :
118 0 : return cv_.wait_for(lock, TIME_OUT, [this] { return frameCounter_ > 100; });
119 0 : }
120 :
121 : class IceSdpParsingTest : public CppUnit::TestFixture
122 : {
123 : public:
124 2 : IceSdpParsingTest()
125 2 : {
126 : // Init daemon
127 2 : libjami::init(libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG));
128 2 : if (not Manager::instance().initialized)
129 1 : CPPUNIT_ASSERT(libjami::start("jami-sample.yml"));
130 :
131 6 : for (size_t idx = 0; idx < MEDIA_COUNT; idx++) {
132 4 : mediaReceivers_.emplace_back(std::make_shared<MediaReceiver>(MediaType::MEDIA_AUDIO));
133 : }
134 2 : }
135 4 : ~IceSdpParsingTest() { libjami::fini(); }
136 :
137 2 : static std::string name() { return "IceSdpParsingTest"; }
138 : void setUp();
139 : void tearDown();
140 :
141 : private:
142 : // Test cases.
143 : void call_with_rfc5245_compliancy_disabled();
144 : void call_with_rfc5245_compliancy_enabled();
145 :
146 2 : CPPUNIT_TEST_SUITE(IceSdpParsingTest);
147 1 : CPPUNIT_TEST(call_with_rfc5245_compliancy_disabled);
148 1 : CPPUNIT_TEST(call_with_rfc5245_compliancy_enabled);
149 4 : CPPUNIT_TEST_SUITE_END();
150 :
151 : // Event/Signal handlers
152 : static void onCallStateChange(const std::string& accountId,
153 : const std::string& callId,
154 : const std::string& state,
155 : CallData& callData);
156 : static void onIncomingCallWithMedia(const std::string& accountId,
157 : const std::string& callId,
158 : const std::vector<libjami::MediaMap> mediaList,
159 : CallData& callData);
160 : static void onMediaNegotiationStatus(const std::string& callId,
161 : const std::string& event,
162 : CallData& callData);
163 :
164 : // Helpers
165 : void test_call();
166 : static void configureTest(CallData& bob, CallData& alice);
167 : static std::string getUserAlias(const std::string& callId);
168 : // Wait for a signal from the callbacks. Some signals also report the event that
169 : // triggered the signal a like the StateChange signal.
170 : static bool waitForSignal(CallData& callData,
171 : const std::string& signal,
172 : const std::string& expectedEvent = {});
173 : static bool attachReceiver(std::shared_ptr<MediaReceiver> receiver,
174 : std::shared_ptr<RtpSession> rtpStream);
175 : static bool detachReceiver(std::shared_ptr<MediaReceiver> receiver,
176 : std::shared_ptr<RtpSession> rtpStream);
177 :
178 : private:
179 : CallData aliceData_;
180 : CallData bobData_;
181 : const size_t MEDIA_COUNT {2};
182 : std::vector<std::shared_ptr<MediaReceiver>> mediaReceivers_;
183 : };
184 :
185 : CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(IceSdpParsingTest, IceSdpParsingTest::name());
186 :
187 : void
188 2 : IceSdpParsingTest::setUp()
189 : {
190 2 : aliceData_.listeningPort_ = 5080;
191 4 : std::map<std::string, std::string> details = libjami::getAccountTemplate("SIP");
192 2 : details[ConfProperties::TYPE] = "SIP";
193 2 : details[ConfProperties::DISPLAYNAME] = "ALICE";
194 2 : details[ConfProperties::ALIAS] = "ALICE";
195 2 : details[ConfProperties::LOCAL_PORT] = std::to_string(aliceData_.listeningPort_);
196 2 : details[ConfProperties::UPNP_ENABLED] = "false";
197 2 : aliceData_.accountId_ = Manager::instance().addAccount(details);
198 :
199 2 : bobData_.listeningPort_ = 5082;
200 2 : details = libjami::getAccountTemplate("SIP");
201 2 : details[ConfProperties::TYPE] = "SIP";
202 2 : details[ConfProperties::DISPLAYNAME] = "BOB";
203 2 : details[ConfProperties::ALIAS] = "BOB";
204 2 : details[ConfProperties::LOCAL_PORT] = std::to_string(bobData_.listeningPort_);
205 2 : details[ConfProperties::UPNP_ENABLED] = "false";
206 2 : bobData_.accountId_ = Manager::instance().addAccount(details);
207 :
208 2 : JAMI_INFO("Initialize accounts ...");
209 2 : auto aliceAccount = Manager::instance().getAccount<SIPAccount>(aliceData_.accountId_);
210 2 : auto bobAccount = Manager::instance().getAccount<SIPAccount>(bobData_.accountId_);
211 2 : }
212 :
213 : void
214 2 : IceSdpParsingTest::tearDown()
215 : {
216 2 : JAMI_INFO("Remove created accounts...");
217 6 : wait_for_removal_of({aliceData_.accountId_, bobData_.accountId_});
218 2 : }
219 :
220 : std::string
221 27 : IceSdpParsingTest::getUserAlias(const std::string& callId)
222 : {
223 27 : auto call = Manager::instance().getCallFromCallID(callId);
224 :
225 27 : if (not call) {
226 3 : JAMI_WARN("Call [%s] does not exist!", callId.c_str());
227 3 : return {};
228 : }
229 :
230 24 : auto const& account = call->getAccount().lock();
231 24 : if (not account) {
232 0 : return {};
233 : }
234 :
235 24 : return account->getAccountDetails()[ConfProperties::ALIAS];
236 27 : }
237 :
238 : void
239 2 : IceSdpParsingTest::onIncomingCallWithMedia(const std::string& accountId,
240 : const std::string& callId,
241 : const std::vector<libjami::MediaMap> mediaList,
242 : CallData& callData)
243 : {
244 2 : CPPUNIT_ASSERT_EQUAL(callData.accountId_, accountId);
245 :
246 2 : JAMI_INFO("Signal [%s] - user [%s] - call [%s] - media count [%lu]",
247 : libjami::CallSignal::IncomingCallWithMedia::name,
248 : callData.alias_.c_str(),
249 : callId.c_str(),
250 : mediaList.size());
251 :
252 : // NOTE.
253 : // We shouldn't access shared_ptr<Call> as this event is supposed to mimic
254 : // the client, and the client have no access to this type. But here, we only
255 : // needed to check if the call exists. This is the most straightforward and
256 : // reliable way to do it until we add a new API (like hasCall(id)).
257 2 : if (not Manager::instance().getCallFromCallID(callId)) {
258 0 : JAMI_WARN("Call [%s] does not exist!", callId.c_str());
259 0 : callData.callId_ = {};
260 0 : return;
261 : }
262 :
263 2 : std::unique_lock lock {callData.mtx_};
264 2 : callData.callId_ = callId;
265 2 : callData.signals_.emplace_back(CallData::Signal(libjami::CallSignal::IncomingCallWithMedia::name));
266 :
267 2 : callData.cv_.notify_one();
268 2 : }
269 :
270 : void
271 18 : IceSdpParsingTest::onCallStateChange(const std::string&,
272 : const std::string& callId,
273 : const std::string& state,
274 : CallData& callData)
275 : {
276 18 : auto call = Manager::instance().getCallFromCallID(callId);
277 18 : if (not call) {
278 0 : JAMI_WARN("Call [%s] does not exist!", callId.c_str());
279 0 : return;
280 : }
281 :
282 18 : auto account = call->getAccount().lock();
283 18 : if (not account) {
284 0 : JAMI_WARN("Account owning the call [%s] does not exist!", callId.c_str());
285 0 : return;
286 : }
287 :
288 18 : JAMI_INFO("Signal [%s] - user [%s] - call [%s] - state [%s]",
289 : libjami::CallSignal::StateChange::name,
290 : callData.alias_.c_str(),
291 : callId.c_str(),
292 : state.c_str());
293 :
294 18 : if (account->getAccountID() != callData.accountId_)
295 0 : return;
296 :
297 : {
298 18 : std::unique_lock lock {callData.mtx_};
299 18 : callData.signals_.emplace_back(
300 36 : CallData::Signal(libjami::CallSignal::StateChange::name, state));
301 18 : }
302 :
303 18 : if (state == "CURRENT" or state == "OVER" or state == "HUNGUP") {
304 8 : callData.cv_.notify_one();
305 : }
306 18 : }
307 :
308 : void
309 4 : IceSdpParsingTest::onMediaNegotiationStatus(const std::string& callId,
310 : const std::string& event,
311 : CallData& callData)
312 : {
313 4 : auto call = Manager::instance().getCallFromCallID(callId);
314 4 : if (not call) {
315 0 : JAMI_WARN("Call [%s] does not exist!", callId.c_str());
316 0 : return;
317 : }
318 :
319 4 : auto account = call->getAccount().lock();
320 4 : if (not account) {
321 0 : JAMI_WARN("Account owning the call [%s] does not exist!", callId.c_str());
322 0 : return;
323 : }
324 :
325 4 : JAMI_INFO("Signal [%s] - user [%s] - call [%s] - state [%s]",
326 : libjami::CallSignal::MediaNegotiationStatus::name,
327 : account->getAccountDetails()[ConfProperties::ALIAS].c_str(),
328 : call->getCallId().c_str(),
329 : event.c_str());
330 :
331 4 : if (account->getAccountID() != callData.accountId_)
332 0 : return;
333 :
334 : {
335 4 : std::unique_lock lock {callData.mtx_};
336 4 : callData.signals_.emplace_back(
337 8 : CallData::Signal(libjami::CallSignal::MediaNegotiationStatus::name, event));
338 4 : }
339 :
340 4 : callData.cv_.notify_one();
341 4 : }
342 :
343 : bool
344 14 : IceSdpParsingTest::waitForSignal(CallData& callData,
345 : const std::string& expectedSignal,
346 : const std::string& expectedEvent)
347 : {
348 14 : const std::chrono::seconds TIME_OUT {30};
349 14 : std::unique_lock lock {callData.mtx_};
350 :
351 : // Combined signal + event (if any).
352 14 : std::string sigEvent(expectedSignal);
353 14 : if (not expectedEvent.empty())
354 12 : sigEvent += "::" + expectedEvent;
355 :
356 14 : JAMI_INFO("[%s] is waiting for [%s] signal/event", callData.alias_.c_str(), sigEvent.c_str());
357 :
358 14 : auto res = callData.cv_.wait_for(lock, TIME_OUT, [&] {
359 : // Search for the expected signal in list of received signals.
360 72 : for (auto it = callData.signals_.begin(); it != callData.signals_.end(); it++) {
361 : // The predicate is true if the signal names match, and if the
362 : // expectedEvent is not empty, the events must also match.
363 66 : if (it->name_ == expectedSignal
364 66 : and (expectedEvent.empty() or it->event_ == expectedEvent)) {
365 : // Done with this signal.
366 14 : callData.signals_.erase(it);
367 14 : return true;
368 : }
369 : }
370 : // Signal/event not found.
371 6 : return false;
372 : });
373 :
374 14 : if (not res) {
375 0 : JAMI_ERR("[%s] waiting for signal/event [%s] timed-out!",
376 : callData.alias_.c_str(),
377 : sigEvent.c_str());
378 :
379 0 : JAMI_INFO("[%s] currently has the following signals:", callData.alias_.c_str());
380 :
381 0 : for (auto const& sig : callData.signals_) {
382 0 : JAMI_INFO() << "Signal [" << sig.name_
383 0 : << (sig.event_.empty() ? "" : ("::" + sig.event_)) << "]";
384 : }
385 : }
386 :
387 14 : return res;
388 14 : }
389 :
390 : bool
391 4 : IceSdpParsingTest::attachReceiver(std::shared_ptr<MediaReceiver> mediaReceiver,
392 : std::shared_ptr<RtpSession> rtpSession)
393 : {
394 4 : CPPUNIT_ASSERT(mediaReceiver);
395 4 : CPPUNIT_ASSERT(mediaReceiver->mediaType_ == MediaType::MEDIA_AUDIO
396 : or mediaReceiver->mediaType_ == MediaType::MEDIA_VIDEO);
397 :
398 4 : if (mediaReceiver->mediaType_ == MediaType::MEDIA_AUDIO) {
399 4 : auto audioRtp = std::dynamic_pointer_cast<AudioRtpSession>(rtpSession);
400 4 : auto receiver = audioRtp->getAudioReceive().get();
401 4 : CPPUNIT_ASSERT(receiver != nullptr);
402 4 : if (receiver == nullptr)
403 0 : return false;
404 4 : return receiver->attach(mediaReceiver.get());
405 4 : }
406 :
407 0 : auto videoRtp = std::dynamic_pointer_cast<video::VideoRtpSession>(rtpSession);
408 0 : auto receiver = videoRtp->getVideoReceive().get();
409 0 : CPPUNIT_ASSERT(receiver != nullptr);
410 0 : return receiver->attach(mediaReceiver.get());
411 0 : }
412 :
413 : bool
414 4 : IceSdpParsingTest::detachReceiver(std::shared_ptr<MediaReceiver> mediaReceiver,
415 : std::shared_ptr<RtpSession> rtpSession)
416 : {
417 4 : CPPUNIT_ASSERT(mediaReceiver);
418 4 : CPPUNIT_ASSERT(mediaReceiver->mediaType_ == MediaType::MEDIA_AUDIO
419 : or mediaReceiver->mediaType_ == MediaType::MEDIA_VIDEO);
420 :
421 4 : if (mediaReceiver->mediaType_ == MediaType::MEDIA_AUDIO) {
422 4 : auto audioRtp = std::dynamic_pointer_cast<AudioRtpSession>(rtpSession);
423 4 : auto receiver = audioRtp->getAudioReceive().get();
424 4 : CPPUNIT_ASSERT(receiver != nullptr);
425 4 : return receiver->detach(mediaReceiver.get());
426 4 : }
427 :
428 0 : auto videoRtp = std::dynamic_pointer_cast<video::VideoRtpSession>(rtpSession);
429 0 : auto receiver = videoRtp->getVideoReceive().get();
430 0 : CPPUNIT_ASSERT(receiver != nullptr);
431 0 : return receiver->detach(mediaReceiver.get());
432 0 : }
433 :
434 : void
435 2 : IceSdpParsingTest::configureTest(CallData& aliceData, CallData& bobData)
436 : {
437 : {
438 2 : CPPUNIT_ASSERT(not aliceData.accountId_.empty());
439 2 : auto const& account = Manager::instance().getAccount<SIPAccount>(aliceData.accountId_);
440 2 : aliceData.userName_ = account->getAccountDetails()[ConfProperties::USERNAME];
441 2 : aliceData.alias_ = account->getAccountDetails()[ConfProperties::ALIAS];
442 2 : account->setLocalPort(aliceData.listeningPort_);
443 2 : account->enableIceCompIdRfc5245Compliance(aliceData.compliancyEnabled_);
444 2 : }
445 :
446 : {
447 2 : CPPUNIT_ASSERT(not bobData.accountId_.empty());
448 2 : auto const& account = Manager::instance().getAccount<SIPAccount>(bobData.accountId_);
449 2 : bobData.userName_ = account->getAccountDetails()[ConfProperties::USERNAME];
450 2 : bobData.alias_ = account->getAccountDetails()[ConfProperties::ALIAS];
451 2 : account->setLocalPort(bobData.listeningPort_);
452 2 : account->enableIceCompIdRfc5245Compliance(bobData.compliancyEnabled_);
453 2 : }
454 :
455 2 : std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> signalHandlers;
456 :
457 : // Insert needed signal handlers.
458 2 : signalHandlers.insert(libjami::exportable_callback<libjami::CallSignal::IncomingCallWithMedia>(
459 2 : [&](const std::string& accountId,
460 : const std::string& callId,
461 : const std::string&,
462 : const std::vector<libjami::MediaMap> mediaList) {
463 2 : auto user = getUserAlias(callId);
464 2 : if (not user.empty())
465 2 : onIncomingCallWithMedia(accountId,
466 : callId,
467 : mediaList,
468 2 : user == aliceData.alias_ ? aliceData : bobData);
469 2 : }));
470 :
471 2 : signalHandlers.insert(
472 4 : libjami::exportable_callback<libjami::CallSignal::StateChange>([&](const std::string& accountId,
473 : const std::string& callId,
474 : const std::string& state,
475 : signed) {
476 21 : auto user = getUserAlias(callId);
477 21 : if (not user.empty())
478 18 : onCallStateChange(accountId,
479 : callId,
480 : state,
481 18 : user == aliceData.alias_ ? aliceData : bobData);
482 21 : }));
483 :
484 2 : signalHandlers.insert(libjami::exportable_callback<libjami::CallSignal::MediaNegotiationStatus>(
485 4 : [&](const std::string& callId,
486 : const std::string& event,
487 : const std::vector<std::map<std::string, std::string>>& /* mediaList */) {
488 4 : auto user = getUserAlias(callId);
489 4 : if (not user.empty())
490 4 : onMediaNegotiationStatus(callId,
491 : event,
492 4 : user == aliceData.alias_ ? aliceData : bobData);
493 4 : }));
494 :
495 2 : libjami::registerSignalHandlers(signalHandlers);
496 2 : }
497 :
498 : void
499 2 : IceSdpParsingTest::test_call()
500 : {
501 2 : configureTest(aliceData_, bobData_);
502 :
503 2 : JAMI_INFO("=== Start a call and validate ===");
504 :
505 : // NOTE:
506 : // We use two audio media instead of one audio and one video media
507 : // to be able to run the test on machines that do not have access to
508 : // camera.
509 : // For this specific UT, testing with two audio media is valid, because
510 : // the main goal is to validate that the media sockets negotiated
511 : // through ICE can correctly exchange media (RTP packets).
512 :
513 2 : MediaAttribute media_0(MediaType::MEDIA_AUDIO);
514 2 : media_0.label_ = "audio_0";
515 2 : media_0.enabled_ = true;
516 2 : MediaAttribute media_1(MediaType::MEDIA_AUDIO);
517 2 : media_1.label_ = "audio_1";
518 2 : media_1.enabled_ = true;
519 :
520 2 : std::vector<MediaAttribute> offer;
521 2 : offer.emplace_back(media_0);
522 2 : offer.emplace_back(media_1);
523 :
524 2 : std::vector<MediaAttribute> answer;
525 2 : answer.emplace_back(media_0);
526 2 : answer.emplace_back(media_1);
527 :
528 2 : CPPUNIT_ASSERT_EQUAL(MEDIA_COUNT, offer.size());
529 2 : CPPUNIT_ASSERT_EQUAL(MEDIA_COUNT, answer.size());
530 2 : auto bobAddr = dhtnet::ip_utils::getLocalAddr(AF_INET);
531 2 : bobAddr.setPort(bobData_.listeningPort_);
532 :
533 4 : aliceData_.callId_ = libjami::placeCallWithMedia(aliceData_.accountId_,
534 4 : bobAddr.toString(true),
535 4 : MediaAttribute::mediaAttributesToMediaMaps(
536 2 : offer));
537 2 : CPPUNIT_ASSERT(not aliceData_.callId_.empty());
538 :
539 2 : JAMI_INFO("ALICE [%s] started a call with BOB [%s] and wait for answer",
540 : aliceData_.accountId_.c_str(),
541 : bobData_.accountId_.c_str());
542 :
543 : // Give it some time to ring
544 2 : std::this_thread::sleep_for(std::chrono::seconds(2));
545 :
546 : // Wait for call to be processed.
547 2 : CPPUNIT_ASSERT(
548 : waitForSignal(aliceData_, libjami::CallSignal::StateChange::name, StateEvent::RINGING));
549 :
550 : // Wait for incoming call signal.
551 2 : CPPUNIT_ASSERT(waitForSignal(bobData_, libjami::CallSignal::IncomingCallWithMedia::name));
552 :
553 : // Answer the call.
554 2 : libjami::acceptWithMedia(bobData_.accountId_,
555 2 : bobData_.callId_,
556 4 : MediaAttribute::mediaAttributesToMediaMaps(answer));
557 :
558 : // Wait for media negotiation complete signal.
559 2 : CPPUNIT_ASSERT(waitForSignal(bobData_,
560 : libjami::CallSignal::MediaNegotiationStatus::name,
561 : libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS));
562 :
563 : // Wait for the StateChange signal.
564 2 : CPPUNIT_ASSERT(
565 : waitForSignal(bobData_, libjami::CallSignal::StateChange::name, StateEvent::CURRENT));
566 :
567 2 : JAMI_INFO("BOB answered the call [%s]", bobData_.callId_.c_str());
568 :
569 : // Wait for media negotiation complete signal.
570 2 : CPPUNIT_ASSERT(waitForSignal(aliceData_,
571 : libjami::CallSignal::MediaNegotiationStatus::name,
572 : libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS));
573 :
574 : // Give some time to media to start.
575 2 : std::this_thread::sleep_for(std::chrono::seconds(2));
576 :
577 : // Register the media observer to validate media flow.
578 2 : CPPUNIT_ASSERT_EQUAL(MEDIA_COUNT, mediaReceivers_.size());
579 : auto call = std::dynamic_pointer_cast<SIPCall>(
580 2 : Manager::instance().getCallFromCallID(aliceData_.callId_));
581 2 : CPPUNIT_ASSERT(call);
582 :
583 2 : auto rtpList = call->getRtpSessionList();
584 2 : CPPUNIT_ASSERT(rtpList.size() == offer.size());
585 :
586 6 : for (size_t i = 0; i < MEDIA_COUNT; i++) {
587 4 : CPPUNIT_ASSERT(rtpList[i]);
588 4 : CPPUNIT_ASSERT(rtpList[i]->getMediaType() == offer[i].type_);
589 4 : CPPUNIT_ASSERT(attachReceiver(mediaReceivers_[i], rtpList[i]));
590 : }
591 :
592 : // NOTE:
593 : // This validation step works on hosts/containers that have correctly
594 : // configured sound system.
595 : // Currenty hosts/containers used for testing are not setup to capture
596 : // and playback audio, so this validation will be disabled for now.
597 : #if 0
598 : JAMI_INFO("Waiting for media to flow ...");
599 : for (size_t i = 0; i < MEDIA_COUNT; i++) {
600 : CPPUNIT_ASSERT(mediaReceivers_[i]->waitForMediaFlow());
601 : }
602 : #endif
603 : // Detach the observers.
604 6 : for (size_t i = 0; i < MEDIA_COUNT; i++) {
605 4 : CPPUNIT_ASSERT(detachReceiver(mediaReceivers_[i], rtpList[i]));
606 : }
607 :
608 : // Bob hang-up.
609 2 : JAMI_INFO("Hang up BOB's call and wait for ALICE to hang up");
610 2 : Manager::instance().hangupCall(bobData_.accountId_, bobData_.callId_);
611 :
612 2 : CPPUNIT_ASSERT_EQUAL(true,
613 : waitForSignal(aliceData_,
614 : libjami::CallSignal::StateChange::name,
615 : StateEvent::HUNGUP));
616 :
617 2 : CPPUNIT_ASSERT_EQUAL(true,
618 : waitForSignal(bobData_,
619 : libjami::CallSignal::StateChange::name,
620 : StateEvent::HUNGUP));
621 :
622 2 : JAMI_INFO("Call terminated on both sides");
623 2 : }
624 :
625 : void
626 1 : IceSdpParsingTest::call_with_rfc5245_compliancy_disabled()
627 : {
628 1 : JAMI_INFO("=== Begin test %s ===", __FUNCTION__);
629 :
630 1 : aliceData_.compliancyEnabled_ = bobData_.compliancyEnabled_ = false;
631 1 : test_call();
632 1 : }
633 :
634 : void
635 1 : IceSdpParsingTest::call_with_rfc5245_compliancy_enabled()
636 : {
637 1 : JAMI_INFO("=== Begin test %s ===", __FUNCTION__);
638 :
639 1 : aliceData_.compliancyEnabled_ = bobData_.compliancyEnabled_ = true;
640 1 : test_call();
641 1 : }
642 :
643 : } // namespace test
644 : } // namespace jami
645 :
646 1 : RING_TEST_RUNNER(jami::test::IceSdpParsingTest::name())
|