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