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 "manager.h"
21 : #include "jamidht/jamiaccount.h"
22 : #include "sip/sipaccount.h"
23 : #include "../../test_runner.h"
24 : #include "jami.h"
25 : #include "jami/media_const.h"
26 : #include "call_const.h"
27 : #include "account_const.h"
28 : #include "sip/sipcall.h"
29 : #include "sip/sdp.h"
30 :
31 : #include "common.h"
32 :
33 : #include <dhtnet/connectionmanager.h>
34 :
35 : #include <cppunit/TestAssert.h>
36 : #include <cppunit/TestFixture.h>
37 : #include <cppunit/extensions/HelperMacros.h>
38 :
39 : #include <condition_variable>
40 : #include <string>
41 :
42 : using namespace libjami::Account;
43 : using namespace libjami::Call;
44 :
45 : namespace jami {
46 : namespace test {
47 :
48 : struct TestScenario
49 : {
50 : TestScenario(const std::vector<MediaAttribute>& offer,
51 : const std::vector<MediaAttribute>& answer,
52 : const std::vector<MediaAttribute>& offerUpdate,
53 : const std::vector<MediaAttribute>& answerUpdate)
54 : : offer_(std::move(offer))
55 : , answer_(std::move(answer))
56 : , offerUpdate_(std::move(offerUpdate))
57 : , answerUpdate_(std::move(answerUpdate))
58 : {}
59 :
60 6 : TestScenario() {};
61 :
62 : std::vector<MediaAttribute> offer_;
63 : std::vector<MediaAttribute> answer_;
64 : std::vector<MediaAttribute> offerUpdate_;
65 : std::vector<MediaAttribute> answerUpdate_;
66 : // Determine if we should expect the MediaNegotiationStatus signal.
67 : bool expectMediaRenegotiation_ {false};
68 : // Determine if we should expect the MediaChangeRequested signal.
69 : bool expectMediaChangeRequest_ {false};
70 : };
71 :
72 : struct CallData
73 : {
74 : struct Signal
75 : {
76 95 : Signal(const std::string& name, const std::string& event = {})
77 95 : : name_(std::move(name))
78 95 : , event_(std::move(event)) {};
79 :
80 : std::string name_ {};
81 : std::string event_ {};
82 : };
83 :
84 12 : CallData() = default;
85 : CallData(CallData&& other) = delete;
86 0 : CallData(const CallData& other)
87 0 : {
88 0 : accountId_ = std::move(other.accountId_);
89 0 : listeningPort_ = other.listeningPort_;
90 0 : userName_ = std::move(other.userName_);
91 0 : alias_ = std::move(other.alias_);
92 0 : callId_ = std::move(other.callId_);
93 0 : signals_ = std::move(other.signals_);
94 0 : };
95 :
96 : std::string accountId_ {};
97 : std::string userName_ {};
98 : std::string alias_ {};
99 : uint16_t listeningPort_ {0};
100 : std::string toUri_ {};
101 : std::string callId_ {};
102 : std::vector<Signal> signals_;
103 : std::condition_variable cv_ {};
104 : std::mutex mtx_;
105 : };
106 :
107 : /**
108 : * Basic tests for media negotiation.
109 : */
110 : class MediaNegotiationTest
111 : {
112 : public:
113 6 : MediaNegotiationTest()
114 6 : {
115 : // Init daemon
116 6 : libjami::init(libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG));
117 6 : if (not Manager::instance().initialized)
118 1 : CPPUNIT_ASSERT(libjami::start("jami-sample.yml"));
119 6 : }
120 6 : ~MediaNegotiationTest() { libjami::fini(); }
121 :
122 : static std::string name() { return "MediaNegotiationTest"; }
123 :
124 : protected:
125 : // Test cases.
126 : void audio_and_video_then_caller_mute_video();
127 : void audio_only_then_caller_add_video();
128 : void audio_and_video_then_caller_mute_audio();
129 : void audio_and_video_answer_muted_video_then_mute_video();
130 : void audio_and_video_then_change_video_source();
131 : void negotiate_2_videos_1_audio();
132 :
133 : // Event/Signal handlers
134 : static void onCallStateChange(const std::string& accountId,
135 : const std::string& callId,
136 : const std::string& state,
137 : CallData& callData);
138 : static void onIncomingCallWithMedia(const std::string& accountId,
139 : const std::string& callId,
140 : const std::vector<libjami::MediaMap> mediaList,
141 : CallData& callData);
142 : // For backward compatibility test cases.
143 : // TODO. Do we still need this?
144 : static void onIncomingCall(const std::string& accountId,
145 : const std::string& callId,
146 : CallData& callData);
147 : static void onMediaChangeRequested(const std::string& accountId,
148 : const std::string& callId,
149 : const std::vector<libjami::MediaMap> mediaList,
150 : CallData& callData);
151 : static void onVideoMuted(const std::string& callId, bool muted, CallData& callData);
152 : static void onMediaNegotiationStatus(const std::string& callId,
153 : const std::string& event,
154 : CallData& callData);
155 :
156 : // Helpers
157 : void configureScenario();
158 : void testWithScenario(CallData& aliceData, CallData& bobData, const TestScenario& scenario);
159 : std::string getAccountId(const std::string& callId);
160 : std::string getUserAlias(const std::string& callId);
161 : // Infer media direction of an offer.
162 : static uint8_t directionToBitset(MediaDirection direction, bool isLocal);
163 : static MediaDirection bitsetToDirection(uint8_t val);
164 : static MediaDirection inferInitialDirection(const MediaAttribute& offer);
165 : // Infer media direction of an answer.
166 : static MediaDirection inferNegotiatedDirection(MediaDirection local, MediaDirection answer);
167 : // Wait for a signal from the callbacks. Some signals also report the event that
168 : // triggered the signal a like the StateChange signal.
169 : static bool validateMuteState(std::vector<MediaAttribute> expected,
170 : std::vector<MediaAttribute> actual);
171 : static bool validateMediaDirection(std::vector<MediaDescription> descrList,
172 : std::vector<MediaAttribute> listInOffer,
173 : std::vector<MediaAttribute> listInAnswer);
174 : static bool waitForSignal(CallData& callData,
175 : const std::string& signal,
176 : const std::string& expectedEvent = {});
177 :
178 : bool isSipAccount_ {false};
179 : std::map<std::string, CallData> callDataMap_;
180 : std::set<std::string> testAccounts_;
181 : };
182 :
183 : // Specialized test case for Jami accounts
184 : class MediaNegotiationTestJami : public MediaNegotiationTest, public CppUnit::TestFixture
185 : {
186 : public:
187 6 : MediaNegotiationTestJami() { isSipAccount_ = false; }
188 :
189 2 : static std::string name() { return "MediaNegotiationTestJami"; }
190 :
191 6 : void setUp() override
192 : {
193 6 : auto actors = load_actors("actors/alice-bob-no-upnp.yml");
194 6 : callDataMap_["ALICE"].accountId_ = actors["alice"];
195 6 : callDataMap_["BOB"].accountId_ = actors["bob"];
196 :
197 6 : JAMI_INFO("Initialize account...");
198 6 : auto aliceAccount = Manager::instance().getAccount<JamiAccount>(
199 12 : callDataMap_["ALICE"].accountId_);
200 6 : auto bobAccount = Manager::instance().getAccount<JamiAccount>(
201 12 : callDataMap_["BOB"].accountId_);
202 :
203 18 : wait_for_announcement_of({aliceAccount->getAccountID(), bobAccount->getAccountID()});
204 6 : }
205 :
206 6 : void tearDown() override
207 : {
208 18 : wait_for_removal_of({callDataMap_["ALICE"].accountId_, callDataMap_["BOB"].accountId_});
209 6 : }
210 :
211 : private:
212 2 : CPPUNIT_TEST_SUITE(MediaNegotiationTestJami);
213 1 : CPPUNIT_TEST(audio_and_video_then_caller_mute_video);
214 1 : CPPUNIT_TEST(audio_only_then_caller_add_video);
215 1 : CPPUNIT_TEST(audio_and_video_then_caller_mute_audio);
216 1 : CPPUNIT_TEST(audio_and_video_answer_muted_video_then_mute_video);
217 1 : CPPUNIT_TEST(audio_and_video_then_change_video_source);
218 1 : CPPUNIT_TEST(negotiate_2_videos_1_audio);
219 4 : CPPUNIT_TEST_SUITE_END();
220 : };
221 :
222 : // Specialized test case for SIP accounts
223 : class MediaNegotiationTestSip : public MediaNegotiationTest, public CppUnit::TestFixture
224 : {
225 : public:
226 0 : MediaNegotiationTestSip() { isSipAccount_ = true; }
227 :
228 1 : static std::string name() { return "MediaNegotiationTestSip"; }
229 :
230 0 : bool addTestAccount(const std::string& alias, uint16_t port)
231 : {
232 0 : CallData callData;
233 0 : callData.alias_ = alias;
234 0 : callData.userName_ = alias;
235 0 : callData.listeningPort_ = port;
236 0 : std::map<std::string, std::string> details = libjami::getAccountTemplate("SIP");
237 0 : details[ConfProperties::TYPE] = "SIP";
238 0 : details[ConfProperties::USERNAME] = alias;
239 0 : details[ConfProperties::DISPLAYNAME] = alias;
240 0 : details[ConfProperties::ALIAS] = alias;
241 0 : details[ConfProperties::LOCAL_PORT] = std::to_string(port);
242 0 : details[ConfProperties::UPNP_ENABLED] = "false";
243 0 : callData.accountId_ = Manager::instance().addAccount(details);
244 0 : testAccounts_.insert(callData.accountId_);
245 0 : callDataMap_.emplace(alias, std::move(callData));
246 0 : return (not callDataMap_[alias].accountId_.empty());
247 0 : }
248 :
249 0 : void setUp() override
250 : {
251 0 : CPPUNIT_ASSERT(addTestAccount("ALICE", 5080));
252 0 : CPPUNIT_ASSERT(addTestAccount("BOB", 5082));
253 0 : }
254 :
255 0 : void tearDown() override
256 : {
257 0 : std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
258 0 : std::mutex mtx;
259 0 : std::unique_lock lk {mtx};
260 0 : std::condition_variable cv;
261 0 : std::atomic_bool accountsRemoved {false};
262 0 : confHandlers.insert(
263 0 : libjami::exportable_callback<libjami::ConfigurationSignal::AccountsChanged>([&]() {
264 0 : auto currAccounts = Manager::instance().getAccountList();
265 0 : for (auto iter = testAccounts_.begin(); iter != testAccounts_.end();) {
266 0 : auto item = std::find(currAccounts.begin(), currAccounts.end(), *iter);
267 0 : if (item == currAccounts.end()) {
268 0 : JAMI_INFO("Removing account %s", (*iter).c_str());
269 0 : iter = testAccounts_.erase(iter);
270 : } else {
271 0 : iter++;
272 : }
273 : }
274 :
275 0 : if (testAccounts_.empty()) {
276 0 : accountsRemoved = true;
277 0 : JAMI_INFO("All accounts removed...");
278 0 : cv.notify_one();
279 : }
280 0 : }));
281 :
282 0 : libjami::registerSignalHandlers(confHandlers);
283 :
284 0 : Manager::instance().removeAccount(callDataMap_["ALICE"].accountId_, true);
285 0 : Manager::instance().removeAccount(callDataMap_["BOB"].accountId_, true);
286 0 : CPPUNIT_ASSERT(
287 : cv.wait_for(lk, std::chrono::seconds(30), [&] { return accountsRemoved.load(); }));
288 :
289 0 : libjami::unregisterSignalHandlers();
290 0 : }
291 :
292 : private:
293 0 : CPPUNIT_TEST_SUITE(MediaNegotiationTestSip);
294 0 : CPPUNIT_TEST(audio_and_video_then_caller_mute_video);
295 0 : CPPUNIT_TEST(audio_only_then_caller_add_video);
296 0 : CPPUNIT_TEST(audio_and_video_then_caller_mute_audio);
297 0 : CPPUNIT_TEST(audio_and_video_answer_muted_video_then_mute_video);
298 0 : CPPUNIT_TEST(audio_and_video_then_change_video_source);
299 0 : CPPUNIT_TEST(negotiate_2_videos_1_audio);
300 0 : CPPUNIT_TEST_SUITE_END();
301 : };
302 :
303 : std::string
304 26 : MediaNegotiationTest::getAccountId(const std::string& callId)
305 : {
306 26 : auto call = Manager::instance().getCallFromCallID(callId);
307 :
308 26 : if (not call) {
309 0 : JAMI_WARN("Call [%s] does not exist anymore!", callId.c_str());
310 0 : return {};
311 : }
312 :
313 26 : auto const& account = call->getAccount().lock();
314 :
315 26 : if (account) {
316 26 : return account->getAccountID();
317 : }
318 :
319 0 : JAMI_WARN("Account owning the call [%s] does not exist anymore!", callId.c_str());
320 0 : return {};
321 26 : }
322 :
323 : std::string
324 95 : MediaNegotiationTest::getUserAlias(const std::string& accountId)
325 : {
326 95 : if (accountId.empty()) {
327 0 : JAMI_WARN("No account ID is empty");
328 0 : return {};
329 : }
330 :
331 95 : auto ret = std::find_if(callDataMap_.begin(), callDataMap_.end(), [accountId](auto const& item) {
332 149 : return item.second.accountId_ == accountId;
333 : });
334 :
335 95 : if (ret != callDataMap_.end())
336 95 : return ret->first;
337 :
338 0 : JAMI_WARN("No matching test account %s", accountId.c_str());
339 0 : return {};
340 : }
341 :
342 : MediaDirection
343 88 : MediaNegotiationTest::inferInitialDirection(const MediaAttribute& mediaAttr)
344 : {
345 88 : if (not mediaAttr.enabled_)
346 0 : return MediaDirection::INACTIVE;
347 :
348 88 : if (mediaAttr.muted_) {
349 10 : if (mediaAttr.onHold_)
350 0 : return MediaDirection::INACTIVE;
351 10 : return MediaDirection::RECVONLY;
352 : }
353 :
354 78 : if (mediaAttr.onHold_)
355 0 : return MediaDirection::SENDONLY;
356 :
357 78 : return MediaDirection::SENDRECV;
358 : }
359 :
360 : uint8_t
361 88 : MediaNegotiationTest::directionToBitset(MediaDirection direction, bool isLocal)
362 : {
363 88 : if (direction == MediaDirection::SENDRECV)
364 78 : return 3;
365 10 : if (direction == MediaDirection::RECVONLY)
366 10 : return isLocal ? 2 : 1;
367 0 : if (direction == MediaDirection::SENDONLY)
368 0 : return isLocal ? 1 : 2;
369 0 : return 0;
370 : }
371 :
372 : MediaDirection
373 44 : MediaNegotiationTest::bitsetToDirection(uint8_t val)
374 : {
375 44 : if (val == 3)
376 36 : return MediaDirection::SENDRECV;
377 8 : if (val == 2)
378 3 : return MediaDirection::RECVONLY;
379 5 : if (val == 1)
380 3 : return MediaDirection::SENDONLY;
381 2 : return MediaDirection::INACTIVE;
382 : }
383 :
384 : MediaDirection
385 44 : MediaNegotiationTest::inferNegotiatedDirection(MediaDirection local, MediaDirection remote)
386 : {
387 44 : uint8_t val = directionToBitset(local, true) & directionToBitset(remote, false);
388 44 : return bitsetToDirection(val);
389 : }
390 :
391 : bool
392 22 : MediaNegotiationTest::validateMuteState(std::vector<MediaAttribute> expected,
393 : std::vector<MediaAttribute> actual)
394 : {
395 22 : CPPUNIT_ASSERT_EQUAL(expected.size(), actual.size());
396 :
397 22 : return std::equal(expected.begin(),
398 : expected.end(),
399 : actual.begin(),
400 : actual.end(),
401 44 : [](auto const& expAttr, auto const& actAttr) {
402 44 : return expAttr.muted_ == actAttr.muted_;
403 22 : });
404 : }
405 :
406 : bool
407 22 : MediaNegotiationTest::validateMediaDirection(std::vector<MediaDescription> descrList,
408 : std::vector<MediaAttribute> localMediaList,
409 : std::vector<MediaAttribute> remoteMediaList)
410 : {
411 22 : CPPUNIT_ASSERT_EQUAL(descrList.size(), localMediaList.size());
412 22 : CPPUNIT_ASSERT_EQUAL(descrList.size(), remoteMediaList.size());
413 :
414 66 : for (size_t idx = 0; idx < descrList.size(); idx++) {
415 44 : auto local = inferInitialDirection(localMediaList[idx]);
416 44 : auto remote = inferInitialDirection(remoteMediaList[idx]);
417 44 : auto negotiated = inferNegotiatedDirection(local, remote);
418 :
419 44 : if (descrList[idx].direction_ != negotiated) {
420 0 : JAMI_WARN("Media [%lu] direction mismatch: expected %i - found %i",
421 : idx,
422 : static_cast<int>(negotiated),
423 : static_cast<int>(descrList[idx].direction_));
424 0 : return false;
425 : }
426 : }
427 :
428 22 : return true;
429 : }
430 :
431 : void
432 6 : MediaNegotiationTest::onIncomingCallWithMedia(const std::string& accountId,
433 : const std::string& callId,
434 : const std::vector<libjami::MediaMap> mediaList,
435 : CallData& callData)
436 : {
437 6 : CPPUNIT_ASSERT_EQUAL(callData.accountId_, accountId);
438 :
439 6 : JAMI_INFO("Signal [%s] - user [%s] - call [%s] - media count [%lu]",
440 : libjami::CallSignal::IncomingCallWithMedia::name,
441 : callData.alias_.c_str(),
442 : callId.c_str(),
443 : mediaList.size());
444 :
445 : // NOTE.
446 : // We shouldn't access shared_ptr<Call> as this event is supposed to mimic
447 : // the client, and the client have no access to this type. But here, we only
448 : // needed to check if the call exists. This is the most straightforward and
449 : // reliable way to do it until we add a new API (like hasCall(id)).
450 6 : if (not Manager::instance().getCallFromCallID(callId)) {
451 0 : JAMI_WARN("Call with ID [%s] does not exist!", callId.c_str());
452 0 : callData.callId_ = {};
453 0 : return;
454 : }
455 :
456 6 : std::unique_lock lock {callData.mtx_};
457 6 : callData.callId_ = callId;
458 6 : callData.signals_.emplace_back(CallData::Signal(libjami::CallSignal::IncomingCallWithMedia::name));
459 :
460 6 : callData.cv_.notify_one();
461 6 : }
462 :
463 : void
464 0 : MediaNegotiationTest::onIncomingCall(const std::string& accountId,
465 : const std::string& callId,
466 : CallData& callData)
467 : {
468 0 : CPPUNIT_ASSERT_EQUAL(callData.accountId_, accountId);
469 :
470 0 : JAMI_INFO("Signal [%s] - user [%s] - call [%s]",
471 : libjami::CallSignal::IncomingCall::name,
472 : callData.alias_.c_str(),
473 : callId.c_str());
474 :
475 : // NOTE.
476 : // We shouldn't access shared_ptr<Call> as this event is supposed to mimic
477 : // the client, and the client have no access to this type. But here, we only
478 : // needed to check if the call exists. This is the most straightforward and
479 : // reliable way to do it until we add a new API (like hasCall(id)).
480 0 : if (not Manager::instance().getCallFromCallID(callId)) {
481 0 : JAMI_WARN("Call with ID [%s] does not exist!", callId.c_str());
482 0 : callData.callId_ = {};
483 0 : return;
484 : }
485 :
486 0 : std::unique_lock lock {callData.mtx_};
487 0 : callData.callId_ = callId;
488 0 : callData.signals_.emplace_back(CallData::Signal(libjami::CallSignal::IncomingCall::name));
489 :
490 0 : callData.cv_.notify_one();
491 0 : }
492 :
493 : void
494 2 : MediaNegotiationTest::onMediaChangeRequested(const std::string& accountId,
495 : const std::string& callId,
496 : const std::vector<libjami::MediaMap> mediaList,
497 : CallData& callData)
498 : {
499 2 : CPPUNIT_ASSERT_EQUAL(callData.accountId_, accountId);
500 :
501 2 : JAMI_INFO("Signal [%s] - user [%s] - call [%s] - media count [%lu]",
502 : libjami::CallSignal::MediaChangeRequested::name,
503 : callData.alias_.c_str(),
504 : callId.c_str(),
505 : mediaList.size());
506 :
507 : // TODO
508 : // We shouldn't access shared_ptr<Call> as this event is supposed to mimic
509 : // the client, and the client have no access to this type. But here, we only
510 : // needed to check if the call exists. This is the most straightforward and
511 : // reliable way to do it until we add a new API (like hasCall(id)).
512 2 : if (not Manager::instance().getCallFromCallID(callId)) {
513 0 : JAMI_WARN("Call with ID [%s] does not exist!", callId.c_str());
514 0 : callData.callId_ = {};
515 0 : return;
516 : }
517 :
518 2 : std::unique_lock lock {callData.mtx_};
519 2 : callData.callId_ = callId;
520 2 : callData.signals_.emplace_back(CallData::Signal(libjami::CallSignal::MediaChangeRequested::name));
521 :
522 2 : callData.cv_.notify_one();
523 2 : }
524 :
525 : void
526 61 : MediaNegotiationTest::onCallStateChange(const std::string& accountId,
527 : const std::string& callId,
528 : const std::string& state,
529 : CallData& callData)
530 : {
531 61 : JAMI_INFO("Signal [%s] - user [%s] - call [%s] - state [%s]",
532 : libjami::CallSignal::StateChange::name,
533 : callData.alias_.c_str(),
534 : callId.c_str(),
535 : state.c_str());
536 :
537 61 : CPPUNIT_ASSERT(accountId == callData.accountId_);
538 :
539 : {
540 61 : std::unique_lock lock {callData.mtx_};
541 61 : callData.signals_.emplace_back(
542 122 : CallData::Signal(libjami::CallSignal::StateChange::name, state));
543 61 : }
544 : // NOTE. Only states that we are interested in will notify the CV.
545 : // If this unit test is modified to process other states, they must
546 : // be added here.
547 61 : if (state == "CURRENT" or state == "OVER" or state == "HUNGUP" or state == "RINGING") {
548 37 : callData.cv_.notify_one();
549 : }
550 61 : }
551 :
552 : void
553 3 : MediaNegotiationTest::onVideoMuted(const std::string& callId, bool muted, CallData& callData)
554 : {
555 3 : auto call = Manager::instance().getCallFromCallID(callId);
556 :
557 3 : if (not call) {
558 0 : JAMI_WARN("Call with ID [%s] does not exist anymore!", callId.c_str());
559 0 : return;
560 : }
561 :
562 3 : auto account = call->getAccount().lock();
563 3 : if (not account) {
564 0 : JAMI_WARN("Account owning the call [%s] does not exist!", callId.c_str());
565 0 : return;
566 : }
567 :
568 3 : JAMI_INFO("Signal [%s] - user [%s] - call [%s] - state [%s]",
569 : libjami::CallSignal::VideoMuted::name,
570 : account->getAccountDetails()[ConfProperties::ALIAS].c_str(),
571 : call->getCallId().c_str(),
572 : muted ? "Mute" : "Un-mute");
573 :
574 3 : if (account->getAccountID() != callData.accountId_)
575 0 : return;
576 :
577 : {
578 3 : std::unique_lock lock {callData.mtx_};
579 3 : callData.signals_.emplace_back(
580 6 : CallData::Signal(libjami::CallSignal::VideoMuted::name, muted ? "muted" : "un-muted"));
581 3 : }
582 :
583 3 : callData.cv_.notify_one();
584 3 : }
585 :
586 : void
587 23 : MediaNegotiationTest::onMediaNegotiationStatus(const std::string& callId,
588 : const std::string& event,
589 : CallData& callData)
590 : {
591 23 : auto call = Manager::instance().getCallFromCallID(callId);
592 23 : if (not call) {
593 0 : JAMI_WARN("Call with ID [%s] does not exist!", callId.c_str());
594 0 : return;
595 : }
596 :
597 23 : auto account = call->getAccount().lock();
598 23 : if (not account) {
599 0 : JAMI_WARN("Account owning the call [%s] does not exist!", callId.c_str());
600 0 : return;
601 : }
602 :
603 23 : JAMI_INFO("Signal [%s] - user [%s] - call [%s] - state [%s]",
604 : libjami::CallSignal::MediaNegotiationStatus::name,
605 : account->getAccountDetails()[ConfProperties::ALIAS].c_str(),
606 : call->getCallId().c_str(),
607 : event.c_str());
608 :
609 23 : if (account->getAccountID() != callData.accountId_)
610 0 : return;
611 :
612 : {
613 23 : std::unique_lock lock {callData.mtx_};
614 23 : callData.signals_.emplace_back(
615 46 : CallData::Signal(libjami::CallSignal::MediaNegotiationStatus::name, event));
616 23 : }
617 :
618 23 : callData.cv_.notify_one();
619 23 : }
620 :
621 : bool
622 37 : MediaNegotiationTest::waitForSignal(CallData& callData,
623 : const std::string& expectedSignal,
624 : const std::string& expectedEvent)
625 : {
626 37 : const std::chrono::seconds TIME_OUT {30};
627 37 : std::unique_lock lock {callData.mtx_};
628 :
629 : // Combined signal + event (if any).
630 37 : std::string sigEvent(expectedSignal);
631 37 : if (not expectedEvent.empty())
632 29 : sigEvent += "::" + expectedEvent;
633 :
634 37 : JAMI_INFO("[%s] is waiting for [%s] signal/event", callData.alias_.c_str(), sigEvent.c_str());
635 :
636 37 : auto res = callData.cv_.wait_for(lock, TIME_OUT, [&] {
637 : // Search for the expected signal in list of received signals.
638 63 : bool pred = false;
639 257 : for (auto it = callData.signals_.begin(); it != callData.signals_.end(); it++) {
640 : // The predicate is true if the signal names match, and if the
641 : // expectedEvent is not empty, the events must also match.
642 231 : if (it->name_ == expectedSignal
643 231 : and (expectedEvent.empty() or it->event_ == expectedEvent)) {
644 37 : pred = true;
645 : // Done with this signal.
646 37 : callData.signals_.erase(it);
647 37 : break;
648 : }
649 : }
650 :
651 63 : return pred;
652 : });
653 :
654 37 : if (not res) {
655 0 : JAMI_ERR("[%s] waiting for signal/event [%s] timed-out!",
656 : callData.alias_.c_str(),
657 : sigEvent.c_str());
658 :
659 0 : JAMI_INFO("[%s] currently has the following signals:", callData.alias_.c_str());
660 :
661 0 : for (auto const& sig : callData.signals_) {
662 0 : JAMI_INFO() << "\tSignal [" << sig.name_
663 0 : << (sig.event_.empty() ? "" : ("::" + sig.event_)) << "]";
664 : }
665 : }
666 :
667 37 : return res;
668 37 : }
669 :
670 : void
671 6 : MediaNegotiationTest::configureScenario()
672 : {
673 : // Configure Alice
674 : {
675 6 : CPPUNIT_ASSERT(not callDataMap_["ALICE"].accountId_.empty());
676 6 : auto const& account = Manager::instance().getAccount<Account>(
677 6 : callDataMap_["ALICE"].accountId_);
678 6 : callDataMap_["ALICE"].userName_ = account->getAccountDetails()[ConfProperties::USERNAME];
679 6 : callDataMap_["ALICE"].alias_ = account->getAccountDetails()[ConfProperties::ALIAS];
680 6 : if (isSipAccount_) {
681 0 : auto sipAccount = std::dynamic_pointer_cast<SIPAccount>(account);
682 0 : CPPUNIT_ASSERT(sipAccount);
683 0 : sipAccount->setLocalPort(callDataMap_["ALICE"].listeningPort_);
684 0 : }
685 6 : }
686 :
687 : // Configure Bob
688 : {
689 6 : CPPUNIT_ASSERT(not callDataMap_["BOB"].accountId_.empty());
690 6 : auto const& account = Manager::instance().getAccount<Account>(
691 6 : callDataMap_["BOB"].accountId_);
692 6 : callDataMap_["BOB"].userName_ = account->getAccountDetails()[ConfProperties::USERNAME];
693 6 : callDataMap_["BOB"].alias_ = account->getAccountDetails()[ConfProperties::ALIAS];
694 :
695 6 : if (isSipAccount_) {
696 0 : auto sipAccount = std::dynamic_pointer_cast<SIPAccount>(account);
697 0 : CPPUNIT_ASSERT(sipAccount);
698 0 : sipAccount->setLocalPort(callDataMap_["BOB"].listeningPort_);
699 0 : callDataMap_["BOB"].toUri_ = fmt::format("127.0.0.1:{}",
700 0 : callDataMap_["BOB"].listeningPort_);
701 0 : }
702 6 : }
703 :
704 6 : std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> signalHandlers;
705 :
706 : // Insert needed signal handlers.
707 6 : signalHandlers.insert(libjami::exportable_callback<libjami::CallSignal::IncomingCallWithMedia>(
708 6 : [&](const std::string& accountId,
709 : const std::string& callId,
710 : const std::string&,
711 : const std::vector<libjami::MediaMap> mediaList) {
712 6 : auto user = getUserAlias(accountId);
713 6 : if (not user.empty())
714 6 : onIncomingCallWithMedia(accountId, callId, mediaList, callDataMap_[user]);
715 6 : }));
716 :
717 6 : signalHandlers.insert(libjami::exportable_callback<libjami::CallSignal::MediaChangeRequested>(
718 2 : [&](const std::string& accountId,
719 : const std::string& callId,
720 : const std::vector<libjami::MediaMap> mediaList) {
721 2 : auto user = getUserAlias(accountId);
722 2 : if (not user.empty())
723 2 : onMediaChangeRequested(accountId, callId, mediaList, callDataMap_[user]);
724 2 : }));
725 :
726 6 : signalHandlers.insert(
727 12 : libjami::exportable_callback<libjami::CallSignal::StateChange>([&](const std::string& accountId,
728 : const std::string& callId,
729 : const std::string& state,
730 : signed) {
731 61 : auto user = getUserAlias(accountId);
732 61 : if (not user.empty())
733 61 : onCallStateChange(accountId, callId, state, callDataMap_[user]);
734 61 : }));
735 :
736 6 : signalHandlers.insert(libjami::exportable_callback<libjami::CallSignal::VideoMuted>(
737 3 : [&](const std::string& callId, bool muted) {
738 3 : auto user = getUserAlias(getAccountId(callId));
739 3 : if (not user.empty())
740 3 : onVideoMuted(callId, muted, callDataMap_[user]);
741 3 : }));
742 :
743 6 : signalHandlers.insert(libjami::exportable_callback<libjami::CallSignal::MediaNegotiationStatus>(
744 23 : [&](const std::string& callId,
745 : const std::string& event,
746 : const std::vector<std::map<std::string, std::string>>&) {
747 23 : auto user = getUserAlias(getAccountId(callId));
748 23 : if (not user.empty())
749 23 : onMediaNegotiationStatus(callId, event, callDataMap_[user]);
750 23 : }));
751 :
752 6 : libjami::registerSignalHandlers(signalHandlers);
753 6 : }
754 :
755 : void
756 6 : MediaNegotiationTest::testWithScenario(CallData& aliceData,
757 : CallData& bobData,
758 : const TestScenario& scenario)
759 : {
760 6 : JAMI_INFO("=== Start a call and validate ===");
761 :
762 : // The media count of the offer and answer must match (RFC-3264).
763 6 : auto mediaCount = scenario.offer_.size();
764 6 : CPPUNIT_ASSERT_EQUAL(mediaCount, scenario.answer_.size());
765 :
766 12 : aliceData.callId_ = libjami::placeCallWithMedia(aliceData.accountId_,
767 6 : isSipAccount_ ? bobData.toUri_
768 12 : : callDataMap_["BOB"].userName_,
769 12 : MediaAttribute::mediaAttributesToMediaMaps(
770 12 : scenario.offer_));
771 6 : CPPUNIT_ASSERT(not aliceData.callId_.empty());
772 :
773 : auto aliceCall = std::static_pointer_cast<SIPCall>(
774 6 : Manager::instance().getCallFromCallID(aliceData.callId_));
775 6 : CPPUNIT_ASSERT(aliceCall);
776 :
777 6 : aliceData.callId_ = aliceCall->getCallId();
778 :
779 6 : JAMI_INFO("ALICE [%s] started a call with BOB [%s] and wait for answer",
780 : aliceData.accountId_.c_str(),
781 : bobData.accountId_.c_str());
782 :
783 : // Wait for incoming call signal.
784 6 : CPPUNIT_ASSERT(waitForSignal(bobData, libjami::CallSignal::IncomingCallWithMedia::name));
785 :
786 : // Answer the call.
787 : {
788 6 : auto const& mediaList = MediaAttribute::mediaAttributesToMediaMaps(scenario.answer_);
789 6 : libjami::acceptWithMedia(bobData.accountId_, bobData.callId_, mediaList);
790 6 : }
791 :
792 : // Wait for media negotiation complete signal.
793 6 : CPPUNIT_ASSERT_EQUAL(
794 : true,
795 : waitForSignal(bobData,
796 : libjami::CallSignal::MediaNegotiationStatus::name,
797 : libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS));
798 : // Wait for the StateChange signal.
799 6 : CPPUNIT_ASSERT_EQUAL(true,
800 : waitForSignal(bobData,
801 : libjami::CallSignal::StateChange::name,
802 : StateEvent::CURRENT));
803 :
804 6 : JAMI_INFO("BOB answered the call [%s]", bobData.callId_.c_str());
805 :
806 : // Wait for media negotiation complete signal.
807 6 : CPPUNIT_ASSERT_EQUAL(
808 : true,
809 : waitForSignal(aliceData,
810 : libjami::CallSignal::MediaNegotiationStatus::name,
811 : libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS));
812 :
813 : // Validate Alice's media
814 : {
815 6 : auto mediaList = aliceCall->getMediaAttributeList();
816 6 : CPPUNIT_ASSERT_EQUAL(mediaCount, mediaList.size());
817 :
818 : // Validate mute state
819 6 : CPPUNIT_ASSERT(validateMuteState(scenario.offer_, mediaList));
820 :
821 6 : auto& sdp = aliceCall->getSDP();
822 :
823 : // Validate local media direction
824 6 : auto descrList = sdp.getActiveMediaDescription(false);
825 6 : CPPUNIT_ASSERT_EQUAL(mediaCount, descrList.size());
826 : // For Alice, local is the offer and remote is the answer.
827 6 : CPPUNIT_ASSERT(validateMediaDirection(descrList, scenario.offer_, scenario.answer_));
828 6 : }
829 :
830 : // Validate Bob's media
831 : {
832 : auto const& bobCall = std::dynamic_pointer_cast<SIPCall>(
833 6 : Manager::instance().getCallFromCallID(bobData.callId_));
834 6 : auto mediaList = bobCall->getMediaAttributeList();
835 6 : CPPUNIT_ASSERT_EQUAL(mediaCount, mediaList.size());
836 :
837 : // Validate mute state
838 6 : CPPUNIT_ASSERT(validateMuteState(scenario.answer_, mediaList));
839 :
840 6 : auto& sdp = bobCall->getSDP();
841 :
842 : // Validate local media direction
843 6 : auto descrList = sdp.getActiveMediaDescription(false);
844 6 : CPPUNIT_ASSERT_EQUAL(mediaCount, descrList.size());
845 : // For Bob, local is the answer and remote is the offer.
846 6 : CPPUNIT_ASSERT(validateMediaDirection(descrList, scenario.answer_, scenario.offer_));
847 6 : }
848 :
849 6 : std::this_thread::sleep_for(std::chrono::seconds(3));
850 :
851 6 : JAMI_INFO("=== Request Media Change and validate ===");
852 : {
853 6 : auto const& mediaList = MediaAttribute::mediaAttributesToMediaMaps(scenario.offerUpdate_);
854 6 : libjami::requestMediaChange(aliceData.accountId_, aliceData.callId_, mediaList);
855 6 : }
856 :
857 : // Update and validate media count.
858 6 : mediaCount = scenario.offerUpdate_.size();
859 6 : CPPUNIT_ASSERT_EQUAL(mediaCount, scenario.answerUpdate_.size());
860 :
861 : // Not all media change requests requires validation from client.
862 6 : if (scenario.expectMediaChangeRequest_) {
863 : // Wait for media change request signal.
864 2 : CPPUNIT_ASSERT_EQUAL(true,
865 : waitForSignal(bobData, libjami::CallSignal::MediaChangeRequested::name));
866 :
867 : // Answer the change request.
868 2 : auto const& mediaList = MediaAttribute::mediaAttributesToMediaMaps(scenario.answerUpdate_);
869 2 : libjami::answerMediaChangeRequest(bobData.accountId_, bobData.callId_, mediaList);
870 2 : }
871 :
872 6 : if (scenario.expectMediaRenegotiation_) {
873 : // Wait for media negotiation complete signal.
874 5 : CPPUNIT_ASSERT_EQUAL(
875 : true,
876 : waitForSignal(aliceData,
877 : libjami::CallSignal::MediaNegotiationStatus::name,
878 : libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS));
879 :
880 : // Validate Alice's media
881 : {
882 5 : auto mediaList = aliceCall->getMediaAttributeList();
883 5 : CPPUNIT_ASSERT_EQUAL(mediaCount, mediaList.size());
884 :
885 : // Validate mute state
886 5 : CPPUNIT_ASSERT(validateMuteState(scenario.offerUpdate_, mediaList));
887 :
888 5 : auto& sdp = aliceCall->getSDP();
889 :
890 : // Validate local media direction
891 5 : auto descrList = sdp.getActiveMediaDescription(false);
892 5 : CPPUNIT_ASSERT_EQUAL(mediaCount, descrList.size());
893 5 : CPPUNIT_ASSERT(
894 : validateMediaDirection(descrList, scenario.offerUpdate_, scenario.answerUpdate_));
895 : // Validate remote media direction
896 5 : descrList = sdp.getActiveMediaDescription(true);
897 5 : CPPUNIT_ASSERT_EQUAL(mediaCount, descrList.size());
898 5 : CPPUNIT_ASSERT(
899 : validateMediaDirection(descrList, scenario.answerUpdate_, scenario.offerUpdate_));
900 5 : }
901 :
902 : // Validate Bob's media
903 : {
904 : auto const& bobCall = std::dynamic_pointer_cast<SIPCall>(
905 5 : Manager::instance().getCallFromCallID(bobData.callId_));
906 5 : auto mediaList = bobCall->getMediaAttributeList();
907 5 : CPPUNIT_ASSERT_EQUAL(mediaCount, mediaList.size());
908 :
909 : // Validate mute state
910 5 : CPPUNIT_ASSERT(validateMuteState(scenario.answerUpdate_, mediaList));
911 :
912 : // NOTE:
913 : // It should be enough to validate media direction on Alice's side
914 5 : }
915 : }
916 :
917 6 : std::this_thread::sleep_for(std::chrono::seconds(3));
918 :
919 : // Bob hang-up.
920 6 : JAMI_INFO("Hang up BOB's call and wait for ALICE to hang up");
921 6 : libjami::hangUp(bobData.accountId_, bobData.callId_);
922 :
923 6 : CPPUNIT_ASSERT_EQUAL(true,
924 : waitForSignal(aliceData,
925 : libjami::CallSignal::StateChange::name,
926 : StateEvent::HUNGUP));
927 :
928 6 : JAMI_INFO("Call terminated on both sides");
929 6 : }
930 :
931 : void
932 1 : MediaNegotiationTest::audio_and_video_then_caller_mute_video()
933 : {
934 1 : JAMI_INFO("=== Begin test %s ===", __FUNCTION__);
935 :
936 1 : configureScenario();
937 :
938 1 : MediaAttribute defaultAudio(MediaType::MEDIA_AUDIO);
939 1 : defaultAudio.label_ = "audio_0";
940 1 : defaultAudio.enabled_ = true;
941 :
942 1 : MediaAttribute defaultVideo(MediaType::MEDIA_VIDEO);
943 1 : defaultVideo.label_ = "video_0";
944 1 : defaultVideo.enabled_ = true;
945 :
946 1 : MediaAttribute audio(defaultAudio);
947 1 : MediaAttribute video(defaultVideo);
948 :
949 1 : TestScenario scenario;
950 : // First offer/answer
951 1 : scenario.offer_.emplace_back(audio);
952 1 : scenario.offer_.emplace_back(video);
953 1 : scenario.answer_.emplace_back(audio);
954 1 : scenario.answer_.emplace_back(video);
955 :
956 : // Updated offer/answer
957 1 : scenario.offerUpdate_.emplace_back(audio);
958 1 : video.muted_ = true;
959 1 : scenario.offerUpdate_.emplace_back(video);
960 :
961 1 : scenario.answerUpdate_.emplace_back(audio);
962 1 : video.muted_ = false;
963 1 : scenario.answerUpdate_.emplace_back(video);
964 1 : scenario.expectMediaRenegotiation_ = true;
965 1 : scenario.expectMediaChangeRequest_ = false;
966 :
967 1 : testWithScenario(callDataMap_["ALICE"], callDataMap_["BOB"], scenario);
968 :
969 1 : libjami::unregisterSignalHandlers();
970 :
971 1 : JAMI_INFO("=== End test %s ===", __FUNCTION__);
972 1 : }
973 :
974 : void
975 1 : MediaNegotiationTest::audio_only_then_caller_add_video()
976 : {
977 1 : JAMI_INFO("=== Begin test %s ===", __FUNCTION__);
978 :
979 1 : configureScenario();
980 :
981 1 : MediaAttribute defaultAudio(MediaType::MEDIA_AUDIO);
982 1 : defaultAudio.label_ = "audio_0";
983 1 : defaultAudio.enabled_ = true;
984 :
985 1 : MediaAttribute defaultVideo(MediaType::MEDIA_VIDEO);
986 1 : defaultVideo.label_ = "video_0";
987 1 : defaultVideo.enabled_ = true;
988 :
989 1 : MediaAttribute audio(defaultAudio);
990 1 : MediaAttribute video(defaultVideo);
991 :
992 1 : TestScenario scenario;
993 : // First offer/answer
994 1 : scenario.offer_.emplace_back(audio);
995 1 : scenario.answer_.emplace_back(audio);
996 :
997 : // Updated offer/answer
998 1 : scenario.offerUpdate_.emplace_back(audio);
999 1 : scenario.offerUpdate_.emplace_back(video);
1000 1 : scenario.answerUpdate_.emplace_back(audio);
1001 1 : video.muted_ = true;
1002 1 : scenario.answerUpdate_.emplace_back(video);
1003 1 : scenario.expectMediaRenegotiation_ = true;
1004 1 : scenario.expectMediaChangeRequest_ = true;
1005 :
1006 1 : testWithScenario(callDataMap_["ALICE"], callDataMap_["BOB"], scenario);
1007 :
1008 1 : libjami::unregisterSignalHandlers();
1009 :
1010 1 : JAMI_INFO("=== End test %s ===", __FUNCTION__);
1011 1 : }
1012 :
1013 : void
1014 1 : MediaNegotiationTest::audio_and_video_then_caller_mute_audio()
1015 : {
1016 1 : JAMI_INFO("=== Begin test %s ===", __FUNCTION__);
1017 :
1018 1 : configureScenario();
1019 :
1020 1 : MediaAttribute defaultAudio(MediaType::MEDIA_AUDIO);
1021 1 : defaultAudio.label_ = "audio_0";
1022 1 : defaultAudio.enabled_ = true;
1023 :
1024 1 : MediaAttribute defaultVideo(MediaType::MEDIA_VIDEO);
1025 1 : defaultVideo.label_ = "video_0";
1026 1 : defaultVideo.enabled_ = true;
1027 :
1028 1 : MediaAttribute audio(defaultAudio);
1029 1 : MediaAttribute video(defaultVideo);
1030 :
1031 1 : TestScenario scenario;
1032 : // First offer/answer
1033 1 : scenario.offer_.emplace_back(audio);
1034 1 : scenario.offer_.emplace_back(video);
1035 1 : scenario.answer_.emplace_back(audio);
1036 1 : scenario.answer_.emplace_back(video);
1037 :
1038 : // Updated offer/answer
1039 1 : audio.muted_ = true;
1040 1 : scenario.offerUpdate_.emplace_back(audio);
1041 1 : scenario.offerUpdate_.emplace_back(video);
1042 :
1043 1 : audio.muted_ = false;
1044 1 : scenario.answerUpdate_.emplace_back(audio);
1045 1 : scenario.answerUpdate_.emplace_back(video);
1046 :
1047 1 : scenario.expectMediaRenegotiation_ = false;
1048 1 : scenario.expectMediaChangeRequest_ = false;
1049 :
1050 1 : testWithScenario(callDataMap_["ALICE"], callDataMap_["BOB"], scenario);
1051 :
1052 1 : libjami::unregisterSignalHandlers();
1053 :
1054 1 : JAMI_INFO("=== End test %s ===", __FUNCTION__);
1055 1 : }
1056 :
1057 : void
1058 1 : MediaNegotiationTest::audio_and_video_answer_muted_video_then_mute_video()
1059 : {
1060 1 : JAMI_INFO("=== Begin test %s ===", __FUNCTION__);
1061 :
1062 1 : configureScenario();
1063 :
1064 1 : MediaAttribute defaultAudio(MediaType::MEDIA_AUDIO);
1065 1 : defaultAudio.label_ = "audio_0";
1066 1 : defaultAudio.enabled_ = true;
1067 :
1068 1 : MediaAttribute defaultVideo(MediaType::MEDIA_VIDEO);
1069 1 : defaultVideo.label_ = "video_0";
1070 1 : defaultVideo.enabled_ = true;
1071 :
1072 1 : MediaAttribute audio(defaultAudio);
1073 1 : MediaAttribute video(defaultVideo);
1074 :
1075 1 : TestScenario scenario;
1076 : // First offer/answer
1077 1 : scenario.offer_.emplace_back(audio);
1078 1 : scenario.offer_.emplace_back(video);
1079 1 : video.muted_ = true;
1080 1 : scenario.answer_.emplace_back(audio);
1081 1 : scenario.answer_.emplace_back(video);
1082 :
1083 : // Updated offer/answer
1084 1 : video.muted_ = true;
1085 1 : scenario.offerUpdate_.emplace_back(audio);
1086 1 : scenario.offerUpdate_.emplace_back(video);
1087 :
1088 1 : video.muted_ = true;
1089 1 : scenario.answerUpdate_.emplace_back(audio);
1090 1 : scenario.answerUpdate_.emplace_back(video);
1091 :
1092 1 : scenario.expectMediaChangeRequest_ = false;
1093 1 : scenario.expectMediaRenegotiation_ = true;
1094 :
1095 1 : testWithScenario(callDataMap_["ALICE"], callDataMap_["BOB"], scenario);
1096 :
1097 1 : libjami::unregisterSignalHandlers();
1098 :
1099 1 : JAMI_INFO("=== End test %s ===", __FUNCTION__);
1100 1 : }
1101 :
1102 : void
1103 1 : MediaNegotiationTest::audio_and_video_then_change_video_source()
1104 : {
1105 1 : JAMI_INFO("=== Begin test %s ===", __FUNCTION__);
1106 :
1107 1 : configureScenario();
1108 :
1109 1 : MediaAttribute defaultAudio(MediaType::MEDIA_AUDIO);
1110 1 : defaultAudio.label_ = "audio_0";
1111 1 : defaultAudio.enabled_ = true;
1112 :
1113 1 : MediaAttribute defaultVideo(MediaType::MEDIA_VIDEO);
1114 1 : defaultVideo.label_ = "video_0";
1115 1 : defaultVideo.enabled_ = true;
1116 :
1117 1 : MediaAttribute audio(defaultAudio);
1118 1 : MediaAttribute video(defaultVideo);
1119 :
1120 1 : TestScenario scenario;
1121 : // First offer/answer
1122 1 : scenario.offer_.emplace_back(audio);
1123 1 : scenario.offer_.emplace_back(video);
1124 1 : scenario.answer_.emplace_back(audio);
1125 1 : scenario.answer_.emplace_back(video);
1126 :
1127 : // Updated offer/answer
1128 1 : scenario.offerUpdate_.emplace_back(audio);
1129 : // Just change the media source to validate that a new
1130 : // media negotiation (re-invite) will be triggered.
1131 1 : video.sourceUri_ = "Fake source";
1132 1 : scenario.offerUpdate_.emplace_back(video);
1133 :
1134 1 : scenario.answerUpdate_.emplace_back(audio);
1135 1 : scenario.answerUpdate_.emplace_back(video);
1136 :
1137 1 : scenario.expectMediaRenegotiation_ = true;
1138 1 : scenario.expectMediaChangeRequest_ = false;
1139 :
1140 1 : testWithScenario(callDataMap_["ALICE"], callDataMap_["BOB"], scenario);
1141 :
1142 1 : libjami::unregisterSignalHandlers();
1143 :
1144 1 : JAMI_INFO("=== End test %s ===", __FUNCTION__);
1145 1 : }
1146 :
1147 : void
1148 1 : MediaNegotiationTest::negotiate_2_videos_1_audio()
1149 : {
1150 1 : JAMI_INFO("=== Begin test %s ===", __FUNCTION__);
1151 :
1152 1 : configureScenario();
1153 :
1154 1 : MediaAttribute defaultAudio(MediaType::MEDIA_AUDIO);
1155 1 : defaultAudio.label_ = "audio_0";
1156 1 : defaultAudio.enabled_ = true;
1157 :
1158 1 : MediaAttribute defaultVideo(MediaType::MEDIA_VIDEO);
1159 1 : defaultVideo.label_ = "video_0";
1160 1 : defaultVideo.sourceUri_ = "foo";
1161 1 : defaultVideo.enabled_ = true;
1162 :
1163 1 : MediaAttribute defaultVideo2(MediaType::MEDIA_VIDEO);
1164 1 : defaultVideo2.label_ = "video_1";
1165 1 : defaultVideo2.sourceUri_ = "bar";
1166 1 : defaultVideo2.enabled_ = true;
1167 :
1168 1 : MediaAttribute audio(defaultAudio);
1169 1 : MediaAttribute video(defaultVideo);
1170 1 : MediaAttribute video2(defaultVideo2);
1171 :
1172 1 : TestScenario scenario;
1173 : // First offer/answer
1174 1 : scenario.offer_.emplace_back(audio);
1175 1 : scenario.offer_.emplace_back(video);
1176 1 : scenario.answer_.emplace_back(audio);
1177 1 : scenario.answer_.emplace_back(video);
1178 :
1179 : // Update offer/answer with 2 videos
1180 1 : scenario.offerUpdate_.emplace_back(audio);
1181 1 : scenario.offerUpdate_.emplace_back(video);
1182 1 : scenario.offerUpdate_.emplace_back(video2);
1183 1 : scenario.answerUpdate_.emplace_back(audio);
1184 1 : scenario.answerUpdate_.emplace_back(video);
1185 1 : scenario.answerUpdate_.emplace_back(video2);
1186 :
1187 1 : scenario.expectMediaRenegotiation_ = true;
1188 1 : scenario.expectMediaChangeRequest_ = true;
1189 :
1190 1 : testWithScenario(callDataMap_["ALICE"], callDataMap_["BOB"], scenario);
1191 :
1192 1 : libjami::unregisterSignalHandlers();
1193 :
1194 1 : JAMI_INFO("=== End test %s ===", __FUNCTION__);
1195 1 : }
1196 :
1197 : CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(MediaNegotiationTestJami, MediaNegotiationTestJami::name());
1198 : CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(MediaNegotiationTestSip, MediaNegotiationTestSip::name());
1199 :
1200 : } // namespace test
1201 : } // namespace jami
1202 :
1203 5 : JAMI_TEST_RUNNER(jami::test::MediaNegotiationTestJami::name())
|