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 "manager.h"
19 : #include "jamidht/jamiaccount.h"
20 : #include "../../test_runner.h"
21 : #include "jami.h"
22 : #include "jami/media_const.h"
23 : #include "call_const.h"
24 : #include "account_const.h"
25 : #include "sip/sipcall.h"
26 : #include "sip/sdp.h"
27 : #include "common.h"
28 :
29 : #include <dhtnet/connectionmanager.h>
30 :
31 : #include <cppunit/TestAssert.h>
32 : #include <cppunit/TestFixture.h>
33 : #include <cppunit/extensions/HelperMacros.h>
34 :
35 : #include <condition_variable>
36 : #include <string>
37 :
38 : using namespace libjami::Account;
39 : using namespace libjami::Call;
40 :
41 : namespace jami {
42 : namespace test {
43 :
44 : struct TestScenario
45 : {
46 : TestScenario(const std::vector<MediaAttribute>& offer,
47 : const std::vector<MediaAttribute>& answer,
48 : const std::vector<MediaAttribute>& offerUpdate,
49 : const std::vector<MediaAttribute>& answerUpdate)
50 : : offer_(std::move(offer))
51 : , answer_(std::move(answer))
52 : , offerUpdate_(std::move(offerUpdate))
53 : , answerUpdate_(std::move(answerUpdate))
54 : {}
55 :
56 2 : TestScenario() {};
57 :
58 : std::vector<MediaAttribute> offer_;
59 : std::vector<MediaAttribute> answer_;
60 : std::vector<MediaAttribute> offerUpdate_;
61 : std::vector<MediaAttribute> answerUpdate_;
62 : // Determine if we should expect the MediaNegotiationStatus signal.
63 : bool expectMediaRenegotiation_ {false};
64 : };
65 :
66 : struct CallData
67 : {
68 : struct Signal
69 : {
70 36 : Signal(const std::string& name, const std::string& event = {})
71 36 : : name_(std::move(name))
72 36 : , event_(std::move(event)) {};
73 :
74 : std::string name_ {};
75 : std::string event_ {};
76 : };
77 :
78 : std::string accountId_ {};
79 : std::string userName_ {};
80 : std::string alias_ {};
81 : std::string callId_ {};
82 : std::vector<Signal> signals_;
83 : std::condition_variable cv_ {};
84 : std::mutex mtx_;
85 : };
86 :
87 : /**
88 : * Basic tests for media negotiation.
89 : */
90 : class HoldResumeTest : public CppUnit::TestFixture
91 : {
92 : public:
93 2 : HoldResumeTest()
94 2 : {
95 : // Init daemon
96 2 : libjami::init(libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG));
97 2 : if (not Manager::instance().initialized)
98 1 : CPPUNIT_ASSERT(libjami::start("jami-sample.yml"));
99 2 : }
100 4 : ~HoldResumeTest() { libjami::fini(); }
101 :
102 2 : static std::string name() { return "HoldResumeTest"; }
103 : void setUp();
104 : void tearDown();
105 :
106 : private:
107 : // Test cases.
108 : void audio_and_video_then_hold_resume();
109 : void audio_only_then_hold_resume();
110 :
111 2 : CPPUNIT_TEST_SUITE(HoldResumeTest);
112 1 : CPPUNIT_TEST(audio_and_video_then_hold_resume);
113 1 : CPPUNIT_TEST(audio_only_then_hold_resume);
114 4 : CPPUNIT_TEST_SUITE_END();
115 :
116 : // Event/Signal handlers
117 : static void onCallStateChange(const std::string& callId,
118 : const std::string& state,
119 : CallData& callData);
120 : static void onIncomingCallWithMedia(const std::string& accountId,
121 : const std::string& callId,
122 : const std::vector<libjami::MediaMap> mediaList,
123 : CallData& callData);
124 : // For backward compatibility test cases
125 : static void onIncomingCall(const std::string& accountId,
126 : const std::string& callId,
127 : CallData& callData);
128 : static void onMediaChangeRequested(const std::string& accountId,
129 : const std::string& callId,
130 : const std::vector<libjami::MediaMap> mediaList,
131 : CallData& callData);
132 : static void onMediaNegotiationStatus(const std::string& callId,
133 : const std::string& event,
134 : CallData& callData);
135 :
136 : // Helpers
137 : static void configureScenario(CallData& bob, CallData& alice);
138 : void testWithScenario(CallData& aliceData, CallData& bobData, const TestScenario& scenario);
139 : static std::string getUserAlias(const std::string& callId);
140 : // Wait for a signal from the callbacks. Some signals also report the event that
141 : // triggered the signal a like the StateChange signal.
142 : static bool waitForSignal(CallData& callData,
143 : const std::string& signal,
144 : const std::string& expectedEvent = {});
145 :
146 : private:
147 : CallData aliceData_;
148 : CallData bobData_;
149 : };
150 :
151 : CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(HoldResumeTest, HoldResumeTest::name());
152 :
153 : void
154 2 : HoldResumeTest::setUp()
155 : {
156 2 : auto actors = load_actors("actors/alice-bob-no-upnp.yml");
157 :
158 2 : aliceData_.accountId_ = actors["alice"];
159 2 : bobData_.accountId_ = actors["bob"];
160 :
161 2 : JAMI_INFO("Initialize account...");
162 2 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceData_.accountId_);
163 2 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobData_.accountId_);
164 :
165 6 : wait_for_announcement_of({aliceAccount->getAccountID(), bobAccount->getAccountID()});
166 2 : }
167 :
168 : void
169 2 : HoldResumeTest::tearDown()
170 : {
171 6 : wait_for_removal_of({aliceData_.accountId_, bobData_.accountId_});
172 2 : }
173 :
174 : std::string
175 38 : HoldResumeTest::getUserAlias(const std::string& callId)
176 : {
177 38 : auto call = Manager::instance().getCallFromCallID(callId);
178 :
179 38 : if (not call) {
180 2 : JAMI_WARN("Call with ID [%s] does not exist anymore!", callId.c_str());
181 2 : return {};
182 : }
183 :
184 36 : auto const& account = call->getAccount().lock();
185 36 : if (not account) {
186 0 : return {};
187 : }
188 :
189 36 : return account->getAccountDetails()[ConfProperties::ALIAS];
190 38 : }
191 :
192 : void
193 2 : HoldResumeTest::onIncomingCallWithMedia(const std::string& accountId,
194 : const std::string& callId,
195 : const std::vector<libjami::MediaMap> mediaList,
196 : CallData& callData)
197 : {
198 2 : CPPUNIT_ASSERT_EQUAL(callData.accountId_, accountId);
199 :
200 2 : JAMI_INFO("Signal [%s] - user [%s] - call [%s] - media count [%lu]",
201 : libjami::CallSignal::IncomingCallWithMedia::name,
202 : callData.alias_.c_str(),
203 : callId.c_str(),
204 : mediaList.size());
205 :
206 2 : if (not Manager::instance().getCallFromCallID(callId)) {
207 0 : JAMI_WARN("Call with ID [%s] does not exist!", callId.c_str());
208 0 : callData.callId_ = {};
209 0 : return;
210 : }
211 :
212 2 : std::unique_lock lock {callData.mtx_};
213 2 : callData.callId_ = callId;
214 2 : callData.signals_.emplace_back(CallData::Signal(libjami::CallSignal::IncomingCallWithMedia::name));
215 :
216 2 : callData.cv_.notify_one();
217 2 : }
218 :
219 : void
220 0 : HoldResumeTest::onIncomingCall(const std::string& accountId,
221 : const std::string& callId,
222 : CallData& callData)
223 : {
224 0 : CPPUNIT_ASSERT_EQUAL(callData.accountId_, accountId);
225 :
226 0 : JAMI_INFO("Signal [%s] - user [%s] - call [%s]",
227 : libjami::CallSignal::IncomingCall::name,
228 : callData.alias_.c_str(),
229 : callId.c_str());
230 :
231 0 : if (not Manager::instance().getCallFromCallID(callId)) {
232 0 : JAMI_WARN("Call with ID [%s] does not exist!", callId.c_str());
233 0 : callData.callId_ = {};
234 0 : return;
235 : }
236 :
237 0 : std::unique_lock lock {callData.mtx_};
238 0 : callData.callId_ = callId;
239 0 : callData.signals_.emplace_back(CallData::Signal(libjami::CallSignal::IncomingCall::name));
240 :
241 0 : callData.cv_.notify_one();
242 0 : }
243 :
244 : void
245 0 : HoldResumeTest::onMediaChangeRequested(const std::string& accountId,
246 : const std::string& callId,
247 : const std::vector<libjami::MediaMap> mediaList,
248 : CallData& callData)
249 : {
250 0 : CPPUNIT_ASSERT_EQUAL(callData.accountId_, accountId);
251 :
252 0 : JAMI_INFO("Signal [%s] - user [%s] - call [%s] - media count [%lu]",
253 : libjami::CallSignal::MediaChangeRequested::name,
254 : callData.alias_.c_str(),
255 : callId.c_str(),
256 : mediaList.size());
257 :
258 0 : if (not Manager::instance().getCallFromCallID(callId)) {
259 0 : JAMI_WARN("Call with ID [%s] does not exist!", callId.c_str());
260 0 : callData.callId_ = {};
261 0 : return;
262 : }
263 :
264 0 : std::unique_lock lock {callData.mtx_};
265 0 : callData.callId_ = callId;
266 0 : callData.signals_.emplace_back(CallData::Signal(libjami::CallSignal::MediaChangeRequested::name));
267 :
268 0 : callData.cv_.notify_one();
269 0 : }
270 :
271 : void
272 22 : HoldResumeTest::onCallStateChange(const std::string& callId,
273 : const std::string& state,
274 : CallData& callData)
275 : {
276 22 : auto call = Manager::instance().getCallFromCallID(callId);
277 22 : if (not call) {
278 0 : JAMI_WARN("Call with ID [%s] does not exist anymore!", callId.c_str());
279 0 : return;
280 : }
281 :
282 22 : auto account = call->getAccount().lock();
283 22 : if (not account) {
284 0 : JAMI_WARN("Account owning the call [%s] does not exist!", callId.c_str());
285 0 : return;
286 : }
287 :
288 22 : 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 22 : if (account->getAccountID() != callData.accountId_)
295 0 : return;
296 :
297 : {
298 22 : std::unique_lock lock {callData.mtx_};
299 22 : callData.signals_.emplace_back(
300 44 : CallData::Signal(libjami::CallSignal::StateChange::name, state));
301 22 : }
302 :
303 22 : if (state == "CURRENT" or state == "OVER" or state == "HUNGUP") {
304 10 : callData.cv_.notify_one();
305 : }
306 22 : }
307 :
308 : void
309 12 : HoldResumeTest::onMediaNegotiationStatus(const std::string& callId,
310 : const std::string& event,
311 : CallData& callData)
312 : {
313 12 : auto call = Manager::instance().getCallFromCallID(callId);
314 12 : if (not call) {
315 0 : JAMI_WARN("Call with ID [%s] does not exist!", callId.c_str());
316 0 : return;
317 : }
318 :
319 12 : auto account = call->getAccount().lock();
320 12 : if (not account) {
321 0 : JAMI_WARN("Account owning the call [%s] does not exist!", callId.c_str());
322 0 : return;
323 : }
324 :
325 12 : 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 12 : if (account->getAccountID() != callData.accountId_)
332 0 : return;
333 :
334 : {
335 12 : std::unique_lock lock {callData.mtx_};
336 12 : callData.signals_.emplace_back(
337 24 : CallData::Signal(libjami::CallSignal::MediaNegotiationStatus::name, event));
338 12 : }
339 :
340 12 : callData.cv_.notify_one();
341 12 : }
342 :
343 : bool
344 14 : HoldResumeTest::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 26 : bool pred = false;
361 122 : for (auto it = callData.signals_.begin(); it != callData.signals_.end(); it++) {
362 : // The predicate is true if the signal names match, and if the
363 : // expectedEvent is not empty, the events must also match.
364 110 : if (it->name_ == expectedSignal
365 110 : and (expectedEvent.empty() or it->event_ == expectedEvent)) {
366 14 : pred = true;
367 : // Done with this signal.
368 14 : callData.signals_.erase(it);
369 14 : break;
370 : }
371 : }
372 :
373 26 : return pred;
374 : });
375 :
376 14 : if (not res) {
377 0 : JAMI_ERR("[%s] waiting for signal/event [%s] timed-out!",
378 : callData.alias_.c_str(),
379 : sigEvent.c_str());
380 :
381 0 : JAMI_INFO("[%s] currently has the following signals:", callData.alias_.c_str());
382 :
383 0 : for (auto const& sig : callData.signals_) {
384 0 : JAMI_INFO() << "Signal [" << sig.name_
385 0 : << (sig.event_.empty() ? "" : ("::" + sig.event_)) << "]";
386 : }
387 : }
388 :
389 14 : return res;
390 14 : }
391 :
392 : void
393 2 : HoldResumeTest::configureScenario(CallData& aliceData, CallData& bobData)
394 : {
395 : {
396 2 : CPPUNIT_ASSERT(not aliceData.accountId_.empty());
397 2 : auto const& account = Manager::instance().getAccount<JamiAccount>(aliceData.accountId_);
398 2 : aliceData.userName_ = account->getAccountDetails()[ConfProperties::USERNAME];
399 2 : aliceData.alias_ = account->getAccountDetails()[ConfProperties::ALIAS];
400 2 : }
401 :
402 : {
403 2 : CPPUNIT_ASSERT(not bobData.accountId_.empty());
404 2 : auto const& account = Manager::instance().getAccount<JamiAccount>(bobData.accountId_);
405 2 : bobData.userName_ = account->getAccountDetails()[ConfProperties::USERNAME];
406 2 : bobData.alias_ = account->getAccountDetails()[ConfProperties::ALIAS];
407 2 : }
408 :
409 2 : std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> signalHandlers;
410 :
411 : // Insert needed signal handlers.
412 2 : signalHandlers.insert(libjami::exportable_callback<libjami::CallSignal::IncomingCallWithMedia>(
413 2 : [&](const std::string& accountId,
414 : const std::string& callId,
415 : const std::string&,
416 : const std::vector<libjami::MediaMap> mediaList) {
417 2 : auto user = getUserAlias(callId);
418 2 : if (not user.empty())
419 2 : onIncomingCallWithMedia(accountId,
420 : callId,
421 : mediaList,
422 2 : user == aliceData.alias_ ? aliceData : bobData);
423 2 : }));
424 :
425 2 : signalHandlers.insert(libjami::exportable_callback<libjami::CallSignal::IncomingCall>(
426 0 : [&](const std::string& accountId, const std::string& callId, const std::string&) {
427 0 : auto user = getUserAlias(callId);
428 0 : if (not user.empty()) {
429 0 : onIncomingCall(accountId, callId, user == aliceData.alias_ ? aliceData : bobData);
430 : }
431 0 : }));
432 :
433 2 : signalHandlers.insert(libjami::exportable_callback<libjami::CallSignal::MediaChangeRequested>(
434 0 : [&](const std::string& accountId,
435 : const std::string& callId,
436 : const std::vector<libjami::MediaMap> mediaList) {
437 0 : auto user = getUserAlias(callId);
438 0 : if (not user.empty())
439 0 : onMediaChangeRequested(accountId,
440 : callId,
441 : mediaList,
442 0 : user == aliceData.alias_ ? aliceData : bobData);
443 0 : }));
444 :
445 2 : signalHandlers.insert(libjami::exportable_callback<libjami::CallSignal::StateChange>(
446 24 : [&](const std::string&, const std::string& callId, const std::string& state, signed) {
447 24 : auto user = getUserAlias(callId);
448 24 : if (not user.empty())
449 22 : onCallStateChange(callId, state, user == aliceData.alias_ ? aliceData : bobData);
450 24 : }));
451 :
452 2 : signalHandlers.insert(libjami::exportable_callback<libjami::CallSignal::MediaNegotiationStatus>(
453 12 : [&](const std::string& callId,
454 : const std::string& event,
455 : const std::vector<std::map<std::string, std::string>>&) {
456 12 : auto user = getUserAlias(callId);
457 12 : if (not user.empty())
458 12 : onMediaNegotiationStatus(callId,
459 : event,
460 12 : user == aliceData.alias_ ? aliceData : bobData);
461 12 : }));
462 :
463 2 : libjami::registerSignalHandlers(signalHandlers);
464 2 : }
465 :
466 : void
467 2 : HoldResumeTest::testWithScenario(CallData& aliceData,
468 : CallData& bobData,
469 : const TestScenario& scenario)
470 : {
471 2 : JAMI_INFO("=== Start a call and validate ===");
472 :
473 : // The media count of the offer and answer must match (RFC-3264).
474 2 : auto mediaCount = scenario.offer_.size();
475 2 : CPPUNIT_ASSERT_EQUAL(mediaCount, scenario.answer_.size());
476 :
477 : auto const& aliceCall = std::dynamic_pointer_cast<SIPCall>(
478 2 : (Manager::instance().getAccount<JamiAccount>(aliceData.accountId_))
479 4 : ->newOutgoingCall(bobData.userName_,
480 4 : MediaAttribute::mediaAttributesToMediaMaps(scenario.offer_)));
481 2 : CPPUNIT_ASSERT(aliceCall);
482 2 : aliceData.callId_ = aliceCall->getCallId();
483 :
484 2 : JAMI_INFO("ALICE [%s] started a call with BOB [%s] and wait for answer",
485 : aliceData.accountId_.c_str(),
486 : bobData.accountId_.c_str());
487 :
488 : // Wait for incoming call signal.
489 2 : CPPUNIT_ASSERT(waitForSignal(bobData, libjami::CallSignal::IncomingCallWithMedia::name));
490 :
491 : // Answer the call.
492 : {
493 2 : auto const& mediaList = MediaAttribute::mediaAttributesToMediaMaps(scenario.answer_);
494 2 : Manager::instance().answerCall(bobData.accountId_, bobData.callId_, mediaList);
495 2 : }
496 :
497 : // Wait for media negotiation complete signal.
498 2 : CPPUNIT_ASSERT_EQUAL(
499 : true,
500 : waitForSignal(bobData,
501 : libjami::CallSignal::MediaNegotiationStatus::name,
502 : libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS));
503 : // Wait for the StateChange signal.
504 2 : CPPUNIT_ASSERT_EQUAL(true,
505 : waitForSignal(bobData,
506 : libjami::CallSignal::StateChange::name,
507 : StateEvent::CURRENT));
508 :
509 2 : JAMI_INFO("BOB answered the call [%s]", bobData.callId_.c_str());
510 :
511 : // Wait for media negotiation complete signal.
512 2 : CPPUNIT_ASSERT_EQUAL(
513 : true,
514 : waitForSignal(aliceData,
515 : libjami::CallSignal::MediaNegotiationStatus::name,
516 : libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS));
517 :
518 : // Validate Alice's media and SDP
519 : {
520 2 : auto mediaAttr = aliceCall->getMediaAttributeList();
521 2 : CPPUNIT_ASSERT_EQUAL(mediaCount, mediaAttr.size());
522 5 : for (size_t idx = 0; idx < mediaCount; idx++) {
523 3 : CPPUNIT_ASSERT_EQUAL(scenario.offer_[idx].muted_, mediaAttr[idx].muted_);
524 : }
525 : // Check media direction
526 2 : auto& sdp = aliceCall->getSDP();
527 2 : auto mediaStreams = sdp.getMediaSlots();
528 5 : for (auto const& media : mediaStreams) {
529 3 : CPPUNIT_ASSERT_EQUAL(media.first.direction_, MediaDirection::SENDRECV);
530 3 : CPPUNIT_ASSERT_EQUAL(media.second.direction_, MediaDirection::SENDRECV);
531 : }
532 2 : }
533 :
534 : // Validate Bob's media
535 : {
536 : auto const& bobCall = std::dynamic_pointer_cast<SIPCall>(
537 2 : Manager::instance().getCallFromCallID(bobData.callId_));
538 2 : auto mediaAttr = bobCall->getMediaAttributeList();
539 2 : CPPUNIT_ASSERT_EQUAL(mediaCount, mediaAttr.size());
540 5 : for (size_t idx = 0; idx < mediaCount; idx++) {
541 3 : CPPUNIT_ASSERT_EQUAL(scenario.answer_[idx].muted_, mediaAttr[idx].muted_);
542 : }
543 : // Check media direction
544 2 : auto& sdp = bobCall->getSDP();
545 2 : auto mediaStreams = sdp.getMediaSlots();
546 5 : for (auto const& media : mediaStreams) {
547 3 : CPPUNIT_ASSERT_EQUAL(media.first.direction_, MediaDirection::SENDRECV);
548 3 : CPPUNIT_ASSERT_EQUAL(media.second.direction_, MediaDirection::SENDRECV);
549 : }
550 2 : }
551 :
552 2 : std::this_thread::sleep_for(std::chrono::seconds(3));
553 :
554 2 : JAMI_INFO("=== Hold the call and validate ===");
555 : {
556 2 : auto const& mediaList = MediaAttribute::mediaAttributesToMediaMaps(scenario.offerUpdate_);
557 2 : libjami::hold(aliceData.accountId_, aliceData.callId_);
558 2 : }
559 :
560 : // Update and validate media count.
561 2 : mediaCount = scenario.offerUpdate_.size();
562 2 : CPPUNIT_ASSERT_EQUAL(mediaCount, scenario.answerUpdate_.size());
563 :
564 2 : if (scenario.expectMediaRenegotiation_) {
565 : // Wait for media negotiation complete signal.
566 2 : CPPUNIT_ASSERT_EQUAL(
567 : true,
568 : waitForSignal(aliceData,
569 : libjami::CallSignal::MediaNegotiationStatus::name,
570 : libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS));
571 :
572 : // Validate Alice's media
573 : {
574 2 : auto mediaAttr = aliceCall->getMediaAttributeList();
575 2 : CPPUNIT_ASSERT_EQUAL(mediaCount, mediaAttr.size());
576 5 : for (size_t idx = 0; idx < mediaCount; idx++) {
577 3 : CPPUNIT_ASSERT(mediaAttr[idx].onHold_);
578 3 : CPPUNIT_ASSERT_EQUAL(scenario.offerUpdate_[idx].muted_, mediaAttr[idx].muted_);
579 : // Check isCaptureDeviceMuted API
580 3 : CPPUNIT_ASSERT_EQUAL(mediaAttr[idx].muted_,
581 : aliceCall->isCaptureDeviceMuted(mediaAttr[idx].type_));
582 : }
583 2 : }
584 :
585 : // Validate Bob's media
586 : {
587 : auto const& bobCall = std::dynamic_pointer_cast<SIPCall>(
588 2 : Manager::instance().getCallFromCallID(bobData.callId_));
589 2 : auto mediaAttr = bobCall->getMediaAttributeList();
590 2 : CPPUNIT_ASSERT_EQUAL(mediaCount, mediaAttr.size());
591 5 : for (size_t idx = 0; idx < mediaCount; idx++) {
592 3 : CPPUNIT_ASSERT(not mediaAttr[idx].onHold_);
593 3 : CPPUNIT_ASSERT_EQUAL(scenario.answerUpdate_[idx].muted_, mediaAttr[idx].muted_);
594 : // Check isCaptureDeviceMuted API
595 3 : CPPUNIT_ASSERT_EQUAL(mediaAttr[idx].muted_,
596 : bobCall->isCaptureDeviceMuted(mediaAttr[idx].type_));
597 : }
598 2 : }
599 : }
600 :
601 2 : std::this_thread::sleep_for(std::chrono::seconds(2));
602 :
603 2 : JAMI_INFO("=== Resume the call and validate ===");
604 : {
605 2 : auto const& mediaList = MediaAttribute::mediaAttributesToMediaMaps(scenario.offerUpdate_);
606 2 : libjami::unhold(aliceData.accountId_, aliceData.callId_);
607 2 : }
608 :
609 : // Update and validate media count.
610 2 : mediaCount = scenario.offerUpdate_.size();
611 2 : CPPUNIT_ASSERT_EQUAL(mediaCount, scenario.answerUpdate_.size());
612 :
613 2 : if (scenario.expectMediaRenegotiation_) {
614 : // Wait for media negotiation complete signal.
615 2 : CPPUNIT_ASSERT_EQUAL(
616 : true,
617 : waitForSignal(aliceData,
618 : libjami::CallSignal::MediaNegotiationStatus::name,
619 : libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS));
620 : // Validate Alice's media
621 : {
622 2 : auto mediaAttr = aliceCall->getMediaAttributeList();
623 2 : CPPUNIT_ASSERT_EQUAL(mediaCount, mediaAttr.size());
624 5 : for (size_t idx = 0; idx < mediaCount; idx++) {
625 3 : CPPUNIT_ASSERT(not mediaAttr[idx].onHold_);
626 3 : CPPUNIT_ASSERT_EQUAL(scenario.offerUpdate_[idx].muted_, mediaAttr[idx].muted_);
627 : // Check isCaptureDeviceMuted API
628 3 : CPPUNIT_ASSERT_EQUAL(mediaAttr[idx].muted_,
629 : aliceCall->isCaptureDeviceMuted(mediaAttr[idx].type_));
630 : }
631 2 : }
632 :
633 : // Validate Bob's media
634 : {
635 : auto const& bobCall = std::dynamic_pointer_cast<SIPCall>(
636 2 : Manager::instance().getCallFromCallID(bobData.callId_));
637 2 : auto mediaAttr = bobCall->getMediaAttributeList();
638 2 : CPPUNIT_ASSERT_EQUAL(mediaCount, mediaAttr.size());
639 5 : for (size_t idx = 0; idx < mediaCount; idx++) {
640 3 : CPPUNIT_ASSERT(not mediaAttr[idx].onHold_);
641 3 : CPPUNIT_ASSERT_EQUAL(scenario.answerUpdate_[idx].muted_, mediaAttr[idx].muted_);
642 : // Check isCaptureDeviceMuted API
643 3 : CPPUNIT_ASSERT_EQUAL(mediaAttr[idx].muted_,
644 : bobCall->isCaptureDeviceMuted(mediaAttr[idx].type_));
645 : }
646 2 : }
647 : }
648 :
649 2 : std::this_thread::sleep_for(std::chrono::seconds(2));
650 :
651 : // Bob hang-up.
652 2 : JAMI_INFO("Hang up BOB's call and wait for ALICE to hang up");
653 2 : Manager::instance().hangupCall(bobData.accountId_, bobData.callId_);
654 :
655 2 : CPPUNIT_ASSERT_EQUAL(true,
656 : waitForSignal(aliceData,
657 : libjami::CallSignal::StateChange::name,
658 : StateEvent::HUNGUP));
659 :
660 2 : JAMI_INFO("Call terminated on both sides");
661 2 : }
662 :
663 : void
664 1 : HoldResumeTest::audio_and_video_then_hold_resume()
665 : {
666 1 : JAMI_INFO("=== Begin test %s ===", __FUNCTION__);
667 :
668 1 : configureScenario(aliceData_, bobData_);
669 :
670 1 : MediaAttribute defaultAudio(MediaType::MEDIA_AUDIO);
671 1 : defaultAudio.label_ = "audio_0";
672 1 : defaultAudio.enabled_ = true;
673 :
674 1 : MediaAttribute defaultVideo(MediaType::MEDIA_VIDEO);
675 1 : defaultVideo.label_ = "video_0";
676 1 : defaultVideo.enabled_ = true;
677 :
678 1 : MediaAttribute audio(defaultAudio);
679 1 : MediaAttribute video(defaultVideo);
680 :
681 1 : TestScenario scenario;
682 : // First offer/answer
683 1 : scenario.offer_.emplace_back(audio);
684 1 : scenario.offer_.emplace_back(video);
685 1 : scenario.answer_.emplace_back(audio);
686 1 : scenario.answer_.emplace_back(video);
687 :
688 : // Updated offer/answer
689 1 : scenario.offerUpdate_.emplace_back(audio);
690 1 : scenario.offerUpdate_.emplace_back(video);
691 :
692 1 : scenario.answerUpdate_.emplace_back(audio);
693 1 : scenario.answerUpdate_.emplace_back(video);
694 1 : scenario.expectMediaRenegotiation_ = true;
695 :
696 1 : testWithScenario(aliceData_, bobData_, scenario);
697 :
698 1 : libjami::unregisterSignalHandlers();
699 :
700 1 : JAMI_INFO("=== End test %s ===", __FUNCTION__);
701 1 : }
702 :
703 : void
704 1 : HoldResumeTest::audio_only_then_hold_resume()
705 : {
706 1 : JAMI_INFO("=== Begin test %s ===", __FUNCTION__);
707 :
708 1 : configureScenario(aliceData_, bobData_);
709 :
710 1 : MediaAttribute defaultAudio(MediaType::MEDIA_AUDIO);
711 1 : defaultAudio.label_ = "audio_0";
712 1 : defaultAudio.enabled_ = true;
713 :
714 1 : MediaAttribute audio(defaultAudio);
715 :
716 1 : TestScenario scenario;
717 : // First offer/answer
718 1 : scenario.offer_.emplace_back(audio);
719 1 : scenario.answer_.emplace_back(audio);
720 :
721 : // Updated offer/answer
722 1 : scenario.offerUpdate_.emplace_back(audio);
723 1 : scenario.answerUpdate_.emplace_back(audio);
724 1 : scenario.expectMediaRenegotiation_ = true;
725 :
726 1 : testWithScenario(aliceData_, bobData_, scenario);
727 :
728 1 : libjami::unregisterSignalHandlers();
729 :
730 1 : JAMI_INFO("=== End test %s ===", __FUNCTION__);
731 1 : }
732 :
733 : } // namespace test
734 : } // namespace jami
735 :
736 1 : RING_TEST_RUNNER(jami::test::HoldResumeTest::name())
|