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