Line data Source code
1 : /*
2 : * Copyright (C) 2021-2024 Savoir-faire Linux Inc.
3 : *
4 : * Author: Mohamed Chibani <mohamed.chibani@savoirfairelinux.com>
5 : *
6 : * This program is free software; you can redistribute it and/or modify
7 : * it under the terms of the GNU General Public License as published by
8 : * the Free Software Foundation; either version 3 of the License, or
9 : * (at your option) any later version.
10 : *
11 : * This program is distributed in the hope that it will be useful,
12 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : * GNU General Public License for more details.
15 : *
16 : * You should have received a copy of the GNU General Public License
17 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 : */
19 :
20 : #include <cppunit/TestAssert.h>
21 : #include <cppunit/TestFixture.h>
22 : #include <cppunit/extensions/HelperMacros.h>
23 :
24 : #include <condition_variable>
25 : #include <string>
26 :
27 : #include "callmanager_interface.h"
28 : #include "manager.h"
29 : #include "sip/sipaccount.h"
30 : #include "../../test_runner.h"
31 : #include "jami.h"
32 : #include "jami/media_const.h"
33 : #include "call_const.h"
34 : #include "account_const.h"
35 : #include "sip/sipcall.h"
36 : #include "sip/sdp.h"
37 :
38 : using namespace libjami::Account;
39 : using namespace libjami::Call;
40 :
41 : namespace jami {
42 : namespace test {
43 :
44 : struct CallData
45 : {
46 : struct Signal
47 : {
48 83 : Signal(const std::string& name, const std::string& event = {})
49 83 : : name_(std::move(name))
50 83 : , event_(std::move(event)) {};
51 :
52 : std::string name_ {};
53 : std::string event_ {};
54 : };
55 :
56 15 : CallData() = default;
57 : CallData(CallData&& other) = delete;
58 15 : CallData(const CallData& other)
59 15 : {
60 15 : accountId_ = std::move(other.accountId_);
61 15 : listeningPort_ = other.listeningPort_;
62 15 : userName_ = std::move(other.userName_);
63 15 : alias_ = std::move(other.alias_);
64 15 : callId_ = std::move(other.callId_);
65 15 : signals_ = std::move(other.signals_);
66 15 : };
67 :
68 : std::string accountId_ {};
69 : uint16_t listeningPort_ {0};
70 : std::string userName_ {};
71 : std::string alias_ {};
72 : std::string callId_ {};
73 : std::vector<Signal> signals_;
74 : std::condition_variable cv_ {};
75 : std::mutex mtx_;
76 : };
77 :
78 : /**
79 : * Call tests for SIP accounts.
80 : */
81 : class SipBasicCallTest : public CppUnit::TestFixture
82 : {
83 : public:
84 5 : SipBasicCallTest()
85 5 : {
86 : // Init daemon
87 5 : libjami::init(libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG));
88 5 : if (not Manager::instance().initialized)
89 1 : CPPUNIT_ASSERT(libjami::start("dring-sample.yml"));
90 5 : }
91 10 : ~SipBasicCallTest() { libjami::fini(); }
92 :
93 2 : static std::string name() { return "SipBasicCallTest"; }
94 : void setUp();
95 : void tearDown();
96 :
97 : private:
98 : // Test cases.
99 : void audio_only_test();
100 : void audio_video_test();
101 : void peer_answer_with_all_media_disabled();
102 : void hold_resume_test();
103 : void blind_transfer_test();
104 :
105 2 : CPPUNIT_TEST_SUITE(SipBasicCallTest);
106 1 : CPPUNIT_TEST(audio_only_test);
107 1 : CPPUNIT_TEST(audio_video_test);
108 : // Test when the peer answers with all the media disabled (RTP port = 0)
109 1 : CPPUNIT_TEST(peer_answer_with_all_media_disabled);
110 1 : CPPUNIT_TEST(hold_resume_test);
111 1 : CPPUNIT_TEST(blind_transfer_test);
112 4 : CPPUNIT_TEST_SUITE_END();
113 :
114 : // Event/Signal handlers
115 : static void onCallStateChange(const std::string& accountId,
116 : const std::string& callId,
117 : const std::string& state,
118 : CallData& callData);
119 : static void onIncomingCallWithMedia(const std::string& accountId,
120 : const std::string& callId,
121 : const std::vector<libjami::MediaMap> mediaList,
122 : CallData& callData);
123 : static void onMediaNegotiationStatus(const std::string& callId,
124 : const std::string& event,
125 : CallData& callData);
126 :
127 : // Helpers
128 : bool addTestAccount(const std::string& alias, uint16_t port);
129 : void audio_video_call(std::vector<MediaAttribute> offer,
130 : std::vector<MediaAttribute> answer,
131 : bool expectedToSucceed = true,
132 : bool validateMedia = true);
133 : void configureTest();
134 : std::string getAccountId(const std::string& callId);
135 : std::string getUserAlias(const std::string& accountId);
136 : CallData& getCallData(const std::string& account);
137 : // Wait for a signal from the callbacks. Some signals also report the event that
138 : // triggered the signal a like the StateChange signal.
139 : static bool waitForSignal(CallData& callData,
140 : const std::string& signal,
141 : const std::string& expectedEvent = {});
142 :
143 : std::map<std::string, CallData> callDataMap_;
144 : std::set<std::string> testAccounts_;
145 : };
146 :
147 : CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(SipBasicCallTest, SipBasicCallTest::name());
148 :
149 : bool
150 15 : SipBasicCallTest::addTestAccount(const std::string& alias, uint16_t port)
151 : {
152 15 : CallData callData;
153 15 : callData.alias_ = alias;
154 15 : callData.userName_ = alias;
155 15 : callData.listeningPort_ = port;
156 30 : std::map<std::string, std::string> details = libjami::getAccountTemplate("SIP");
157 15 : details[ConfProperties::TYPE] = "SIP";
158 15 : details[ConfProperties::USERNAME] = alias;
159 15 : details[ConfProperties::DISPLAYNAME] = alias;
160 15 : details[ConfProperties::ALIAS] = alias;
161 15 : details[ConfProperties::LOCAL_PORT] = std::to_string(port);
162 15 : details[ConfProperties::UPNP_ENABLED] = "false";
163 15 : callData.accountId_ = Manager::instance().addAccount(details);
164 15 : testAccounts_.insert(callData.accountId_);
165 15 : callDataMap_.emplace(alias, std::move(callData));
166 30 : return (not callDataMap_[alias].accountId_.empty());
167 15 : }
168 :
169 : void
170 5 : SipBasicCallTest::setUp()
171 : {
172 5 : JAMI_INFO("Initialize accounts ...");
173 :
174 5 : CPPUNIT_ASSERT(addTestAccount("ALICE", 5080));
175 5 : CPPUNIT_ASSERT(addTestAccount("BOB", 5082));
176 5 : CPPUNIT_ASSERT(addTestAccount("CARLA", 5084));
177 5 : }
178 :
179 : void
180 5 : SipBasicCallTest::tearDown()
181 : {
182 5 : JAMI_INFO("Remove created accounts...");
183 :
184 5 : std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
185 5 : std::mutex mtx;
186 5 : std::unique_lock lk {mtx};
187 5 : std::condition_variable cv;
188 5 : std::atomic_bool accountsRemoved {false};
189 5 : confHandlers.insert(
190 10 : libjami::exportable_callback<libjami::ConfigurationSignal::AccountsChanged>([&]() {
191 15 : auto currAccounts = Manager::instance().getAccountList();
192 45 : for (auto iter = testAccounts_.begin(); iter != testAccounts_.end();) {
193 30 : auto item = std::find(currAccounts.begin(), currAccounts.end(), *iter);
194 30 : if (item == currAccounts.end()) {
195 15 : JAMI_INFO("Removing account %s", (*iter).c_str());
196 15 : iter = testAccounts_.erase(iter);
197 : } else {
198 15 : iter++;
199 : }
200 : }
201 :
202 15 : if (testAccounts_.empty()) {
203 5 : accountsRemoved = true;
204 5 : JAMI_INFO("All accounts removed...");
205 5 : cv.notify_one();
206 : }
207 15 : }));
208 :
209 5 : libjami::registerSignalHandlers(confHandlers);
210 :
211 5 : Manager::instance().removeAccount(callDataMap_["ALICE"].accountId_, true);
212 5 : Manager::instance().removeAccount(callDataMap_["BOB"].accountId_, true);
213 5 : Manager::instance().removeAccount(callDataMap_["CARLA"].accountId_, true);
214 :
215 10 : CPPUNIT_ASSERT(
216 : cv.wait_for(lk, std::chrono::seconds(30), [&] { return accountsRemoved.load(); }));
217 :
218 5 : libjami::unregisterSignalHandlers();
219 5 : }
220 :
221 : std::string
222 14 : SipBasicCallTest::getAccountId(const std::string& callId)
223 : {
224 14 : auto call = Manager::instance().getCallFromCallID(callId);
225 :
226 14 : if (not call) {
227 0 : JAMI_WARN("Call [%s] does not exist anymore!", callId.c_str());
228 0 : return {};
229 : }
230 :
231 14 : auto const& account = call->getAccount().lock();
232 :
233 14 : if (account) {
234 14 : return account->getAccountID();
235 : }
236 :
237 0 : JAMI_WARN("Account owning the call [%s] does not exist anymore!", callId.c_str());
238 0 : return {};
239 14 : }
240 :
241 : std::string
242 87 : SipBasicCallTest::getUserAlias(const std::string& accountId)
243 : {
244 87 : if (accountId.empty()) {
245 4 : JAMI_WARN("No account ID is empty");
246 4 : return {};
247 : }
248 :
249 83 : auto ret = std::find_if(callDataMap_.begin(), callDataMap_.end(), [accountId](auto const& item) {
250 144 : return item.second.accountId_ == accountId;
251 : });
252 :
253 83 : if (ret != callDataMap_.end())
254 83 : return ret->first;
255 :
256 0 : JAMI_WARN("No matching test account %s", accountId.c_str());
257 0 : return {};
258 : }
259 :
260 : void
261 6 : SipBasicCallTest::onIncomingCallWithMedia(const std::string& accountId,
262 : const std::string& callId,
263 : const std::vector<libjami::MediaMap> mediaList,
264 : CallData& callData)
265 : {
266 6 : CPPUNIT_ASSERT_EQUAL(callData.accountId_, accountId);
267 :
268 6 : JAMI_INFO("Signal [%s] - user [%s] - call [%s] - media count [%lu]",
269 : libjami::CallSignal::IncomingCallWithMedia::name,
270 : callData.alias_.c_str(),
271 : callId.c_str(),
272 : mediaList.size());
273 :
274 6 : if (not Manager::instance().getCallFromCallID(callId)) {
275 0 : JAMI_WARN("Call with ID [%s] does not exist!", callId.c_str());
276 0 : callData.callId_ = {};
277 0 : return;
278 : }
279 :
280 6 : std::unique_lock lock {callData.mtx_};
281 6 : callData.callId_ = callId;
282 6 : callData.signals_.emplace_back(CallData::Signal(libjami::CallSignal::IncomingCallWithMedia::name));
283 :
284 6 : callData.cv_.notify_one();
285 6 : }
286 :
287 : void
288 63 : SipBasicCallTest::onCallStateChange(const std::string& accountId,
289 : const std::string& callId,
290 : const std::string& state,
291 : CallData& callData)
292 : {
293 63 : JAMI_INFO("Signal [%s] - user [%s] - call [%s] - state [%s]",
294 : libjami::CallSignal::StateChange::name,
295 : callData.alias_.c_str(),
296 : callId.c_str(),
297 : state.c_str());
298 :
299 63 : CPPUNIT_ASSERT(accountId == callData.accountId_);
300 :
301 : {
302 63 : std::unique_lock lock {callData.mtx_};
303 63 : callData.signals_.emplace_back(
304 126 : CallData::Signal(libjami::CallSignal::StateChange::name, state));
305 63 : }
306 : // NOTE. Only states that we are interested on will notify the CV. If this
307 : // unit test is modified to process other states, they must be added here.
308 63 : if (state == "CURRENT" or state == "OVER" or state == "HUNGUP" or state == "RINGING") {
309 37 : callData.cv_.notify_one();
310 : }
311 63 : }
312 :
313 : void
314 14 : SipBasicCallTest::onMediaNegotiationStatus(const std::string& callId,
315 : const std::string& event,
316 : CallData& callData)
317 : {
318 14 : auto call = Manager::instance().getCallFromCallID(callId);
319 14 : if (not call) {
320 0 : JAMI_WARN("Call with ID [%s] does not exist!", callId.c_str());
321 0 : return;
322 : }
323 :
324 14 : auto account = call->getAccount().lock();
325 14 : if (not account) {
326 0 : JAMI_WARN("Account owning the call [%s] does not exist!", callId.c_str());
327 0 : return;
328 : }
329 :
330 14 : JAMI_INFO("Signal [%s] - user [%s] - call [%s] - state [%s]",
331 : libjami::CallSignal::MediaNegotiationStatus::name,
332 : account->getAccountDetails()[ConfProperties::ALIAS].c_str(),
333 : call->getCallId().c_str(),
334 : event.c_str());
335 :
336 14 : if (account->getAccountID() != callData.accountId_)
337 0 : return;
338 :
339 : {
340 14 : std::unique_lock lock {callData.mtx_};
341 14 : callData.signals_.emplace_back(
342 28 : CallData::Signal(libjami::CallSignal::MediaNegotiationStatus::name, event));
343 14 : }
344 :
345 14 : callData.cv_.notify_one();
346 14 : }
347 :
348 : bool
349 39 : SipBasicCallTest::waitForSignal(CallData& callData,
350 : const std::string& expectedSignal,
351 : const std::string& expectedEvent)
352 : {
353 39 : const std::chrono::seconds TIME_OUT {10};
354 39 : std::unique_lock lock {callData.mtx_};
355 :
356 : // Combined signal + event (if any).
357 39 : std::string sigEvent(expectedSignal);
358 39 : if (not expectedEvent.empty())
359 33 : sigEvent += "::" + expectedEvent;
360 :
361 39 : JAMI_INFO("[%s] is waiting for [%s] signal/event", callData.alias_.c_str(), sigEvent.c_str());
362 :
363 39 : auto res = callData.cv_.wait_for(lock, TIME_OUT, [&] {
364 : // Search for the expected signal in list of received signals.
365 51 : bool pred = false;
366 194 : for (auto it = callData.signals_.begin(); it != callData.signals_.end(); it++) {
367 : // The predicate is true if the signal names match, and if the
368 : // expectedEvent is not empty, the events must also match.
369 182 : if (it->name_ == expectedSignal
370 182 : and (expectedEvent.empty() or it->event_ == expectedEvent)) {
371 39 : pred = true;
372 : // Done with this signal.
373 39 : callData.signals_.erase(it);
374 39 : break;
375 : }
376 : }
377 :
378 51 : return pred;
379 : });
380 :
381 39 : if (not res) {
382 0 : JAMI_ERR("[%s] waiting for signal/event [%s] timed-out!",
383 : callData.alias_.c_str(),
384 : sigEvent.c_str());
385 :
386 0 : JAMI_INFO("[%s] currently has the following signals:", callData.alias_.c_str());
387 :
388 0 : for (auto const& sig : callData.signals_) {
389 0 : JAMI_INFO() << "\tSignal [" << sig.name_
390 0 : << (sig.event_.empty() ? "" : ("::" + sig.event_)) << "]";
391 : }
392 : }
393 :
394 39 : return res;
395 39 : }
396 :
397 : void
398 5 : SipBasicCallTest::configureTest()
399 : {
400 : {
401 5 : CPPUNIT_ASSERT(not callDataMap_["ALICE"].accountId_.empty());
402 5 : auto const& account = Manager::instance().getAccount<SIPAccount>(
403 5 : callDataMap_["ALICE"].accountId_);
404 5 : account->setLocalPort(callDataMap_["ALICE"].listeningPort_);
405 5 : }
406 :
407 : {
408 5 : CPPUNIT_ASSERT(not callDataMap_["BOB"].accountId_.empty());
409 5 : auto const& account = Manager::instance().getAccount<SIPAccount>(
410 5 : callDataMap_["BOB"].accountId_);
411 5 : account->setLocalPort(callDataMap_["BOB"].listeningPort_);
412 5 : }
413 :
414 : {
415 5 : CPPUNIT_ASSERT(not callDataMap_["CARLA"].accountId_.empty());
416 5 : auto const& account = Manager::instance().getAccount<SIPAccount>(
417 5 : callDataMap_["CARLA"].accountId_);
418 5 : account->setLocalPort(callDataMap_["CARLA"].listeningPort_);
419 5 : }
420 :
421 5 : std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> signalHandlers;
422 :
423 : // Insert needed signal handlers.
424 5 : signalHandlers.insert(libjami::exportable_callback<libjami::CallSignal::IncomingCallWithMedia>(
425 6 : [&](const std::string& accountId,
426 : const std::string& callId,
427 : const std::string&,
428 : const std::vector<libjami::MediaMap> mediaList) {
429 6 : auto alias = getUserAlias(accountId);
430 6 : if (not alias.empty()) {
431 6 : onIncomingCallWithMedia(accountId, callId, mediaList, callDataMap_[alias]);
432 : }
433 6 : }));
434 :
435 5 : signalHandlers.insert(
436 10 : libjami::exportable_callback<libjami::CallSignal::StateChange>([&](const std::string& accountId,
437 : const std::string& callId,
438 : const std::string& state,
439 : signed) {
440 67 : auto alias = getUserAlias(accountId);
441 67 : if (not alias.empty())
442 63 : onCallStateChange(accountId, callId, state, callDataMap_[alias]);
443 67 : }));
444 :
445 5 : signalHandlers.insert(libjami::exportable_callback<libjami::CallSignal::MediaNegotiationStatus>(
446 14 : [&](const std::string& callId,
447 : const std::string& event,
448 : const std::vector<std::map<std::string, std::string>>& /* mediaList */) {
449 14 : auto alias = getUserAlias(getAccountId(callId));
450 14 : if (not alias.empty())
451 14 : onMediaNegotiationStatus(callId, event, callDataMap_[alias]);
452 14 : }));
453 :
454 5 : libjami::registerSignalHandlers(signalHandlers);
455 5 : }
456 :
457 : void
458 3 : SipBasicCallTest::audio_video_call(std::vector<MediaAttribute> offer,
459 : std::vector<MediaAttribute> answer,
460 : bool expectedToSucceed,
461 : bool validateMedia)
462 : {
463 3 : configureTest();
464 :
465 3 : JAMI_INFO("=== Start a call and validate ===");
466 :
467 6 : std::string bobUri = callDataMap_["BOB"].userName_
468 12 : + "@127.0.0.1:" + std::to_string(callDataMap_["BOB"].listeningPort_);
469 :
470 6 : callDataMap_["ALICE"].callId_
471 6 : = libjami::placeCallWithMedia(callDataMap_["ALICE"].accountId_,
472 : bobUri,
473 9 : MediaAttribute::mediaAttributesToMediaMaps(offer));
474 :
475 3 : CPPUNIT_ASSERT(not callDataMap_["ALICE"].callId_.empty());
476 :
477 3 : JAMI_INFO("ALICE [%s] started a call with BOB [%s] and wait for answer",
478 : callDataMap_["ALICE"].accountId_.c_str(),
479 : callDataMap_["BOB"].accountId_.c_str());
480 :
481 : // Give it some time to ring
482 3 : std::this_thread::sleep_for(std::chrono::seconds(2));
483 :
484 : // Wait for call to be processed.
485 3 : CPPUNIT_ASSERT(waitForSignal(callDataMap_["ALICE"],
486 : libjami::CallSignal::StateChange::name,
487 : StateEvent::RINGING));
488 :
489 : // Wait for incoming call signal.
490 3 : CPPUNIT_ASSERT(
491 : waitForSignal(callDataMap_["BOB"], libjami::CallSignal::IncomingCallWithMedia::name));
492 :
493 : // Answer the call.
494 3 : libjami::acceptWithMedia(callDataMap_["BOB"].accountId_,
495 6 : callDataMap_["BOB"].callId_,
496 6 : MediaAttribute::mediaAttributesToMediaMaps(answer));
497 :
498 3 : if (expectedToSucceed) {
499 : // Wait for media negotiation complete signal.
500 2 : CPPUNIT_ASSERT(
501 : waitForSignal(callDataMap_["BOB"],
502 : libjami::CallSignal::MediaNegotiationStatus::name,
503 : libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS));
504 :
505 : // Wait for the StateChange signal.
506 2 : CPPUNIT_ASSERT(waitForSignal(callDataMap_["BOB"],
507 : libjami::CallSignal::StateChange::name,
508 : StateEvent::CURRENT));
509 :
510 2 : JAMI_INFO("BOB answered the call [%s]", callDataMap_["BOB"].callId_.c_str());
511 :
512 : // Wait for media negotiation complete signal.
513 2 : CPPUNIT_ASSERT(
514 : waitForSignal(callDataMap_["ALICE"],
515 : libjami::CallSignal::MediaNegotiationStatus::name,
516 : libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS));
517 : // Validate Alice's media
518 2 : if (validateMedia) {
519 4 : auto call = Manager::instance().getCallFromCallID(callDataMap_["ALICE"].callId_);
520 2 : auto activeMediaList = call->getMediaAttributeList();
521 2 : CPPUNIT_ASSERT_EQUAL(offer.size(), activeMediaList.size());
522 : // Audio
523 2 : CPPUNIT_ASSERT_EQUAL(MediaType::MEDIA_AUDIO, activeMediaList[0].type_);
524 2 : CPPUNIT_ASSERT_EQUAL(offer[0].enabled_, activeMediaList[0].enabled_);
525 :
526 : // Video
527 2 : if (offer.size() > 1) {
528 2 : CPPUNIT_ASSERT_EQUAL(MediaType::MEDIA_VIDEO, activeMediaList[1].type_);
529 2 : CPPUNIT_ASSERT_EQUAL(offer[1].enabled_, activeMediaList[1].enabled_);
530 : }
531 2 : }
532 :
533 : // Validate Bob's media
534 2 : if (validateMedia) {
535 4 : auto call = Manager::instance().getCallFromCallID(callDataMap_["BOB"].callId_);
536 2 : auto activeMediaList = call->getMediaAttributeList();
537 2 : CPPUNIT_ASSERT_EQUAL(answer.size(), activeMediaList.size());
538 : // Audio
539 2 : CPPUNIT_ASSERT_EQUAL(MediaType::MEDIA_AUDIO, activeMediaList[0].type_);
540 2 : CPPUNIT_ASSERT_EQUAL(answer[0].enabled_, activeMediaList[0].enabled_);
541 :
542 : // Video
543 2 : if (offer.size() > 1) {
544 2 : CPPUNIT_ASSERT_EQUAL(MediaType::MEDIA_VIDEO, activeMediaList[1].type_);
545 2 : CPPUNIT_ASSERT_EQUAL(answer[1].enabled_, activeMediaList[1].enabled_);
546 : }
547 2 : }
548 :
549 : // Give some time to media to start and flow
550 2 : std::this_thread::sleep_for(std::chrono::seconds(3));
551 :
552 : // Bob hang-up.
553 2 : JAMI_INFO("Hang up BOB's call and wait for ALICE to hang up");
554 2 : libjami::hangUp(callDataMap_["BOB"].accountId_, callDataMap_["BOB"].callId_);
555 : } else {
556 : // The media negotiation for the call is expected to fail, so we
557 : // should receive the signal.
558 1 : CPPUNIT_ASSERT(waitForSignal(callDataMap_["BOB"],
559 : libjami::CallSignal::StateChange::name,
560 : StateEvent::FAILURE));
561 : }
562 :
563 : // The hang-up signal will be emitted on caller's side (Alice) in both
564 : // success failure scenarios.
565 :
566 3 : CPPUNIT_ASSERT(waitForSignal(callDataMap_["ALICE"],
567 : libjami::CallSignal::StateChange::name,
568 : StateEvent::HUNGUP));
569 :
570 3 : JAMI_INFO("Call terminated on both sides");
571 3 : }
572 :
573 : void
574 1 : SipBasicCallTest::audio_only_test()
575 : {
576 : // Test with video enabled on Alice's side and disabled
577 : // on Bob's side.
578 :
579 1 : JAMI_INFO("=== Begin test %s ===", __FUNCTION__);
580 :
581 1 : auto const aliceAcc = Manager::instance().getAccount<SIPAccount>(
582 2 : callDataMap_["ALICE"].accountId_);
583 2 : auto const bobAcc = Manager::instance().getAccount<SIPAccount>(callDataMap_["BOB"].accountId_);
584 :
585 1 : std::vector<MediaAttribute> offer;
586 1 : std::vector<MediaAttribute> answer;
587 :
588 1 : MediaAttribute audio(MediaType::MEDIA_AUDIO);
589 1 : MediaAttribute video(MediaType::MEDIA_VIDEO);
590 :
591 : // Configure Alice
592 1 : audio.enabled_ = true;
593 1 : audio.label_ = "audio_0";
594 1 : offer.emplace_back(audio);
595 :
596 1 : video.enabled_ = true;
597 1 : video.label_ = "video_0";
598 1 : aliceAcc->enableVideo(true);
599 1 : offer.emplace_back(video);
600 :
601 : // Configure Bob
602 1 : audio.enabled_ = true;
603 1 : audio.label_ = "audio_0";
604 1 : answer.emplace_back(audio);
605 :
606 1 : video.enabled_ = false;
607 1 : video.label_ = "video_0";
608 1 : bobAcc->enableVideo(false);
609 1 : answer.emplace_back(video);
610 :
611 : // Run the scenario
612 1 : audio_video_call(offer, answer);
613 1 : }
614 :
615 : void
616 1 : SipBasicCallTest::audio_video_test()
617 : {
618 1 : JAMI_INFO("=== Begin test %s ===", __FUNCTION__);
619 :
620 1 : auto const aliceAcc = Manager::instance().getAccount<SIPAccount>(
621 2 : callDataMap_["ALICE"].accountId_);
622 2 : auto const bobAcc = Manager::instance().getAccount<SIPAccount>(callDataMap_["BOB"].accountId_);
623 :
624 1 : std::vector<MediaAttribute> offer;
625 1 : std::vector<MediaAttribute> answer;
626 :
627 1 : MediaAttribute audio(MediaType::MEDIA_AUDIO);
628 1 : MediaAttribute video(MediaType::MEDIA_VIDEO);
629 :
630 : // Configure Alice
631 1 : audio.enabled_ = true;
632 1 : audio.label_ = "audio_0";
633 1 : offer.emplace_back(audio);
634 :
635 1 : video.enabled_ = true;
636 1 : video.label_ = "video_0";
637 1 : aliceAcc->enableVideo(true);
638 1 : offer.emplace_back(video);
639 :
640 : // Configure Bob
641 1 : audio.enabled_ = true;
642 1 : audio.label_ = "audio_0";
643 1 : answer.emplace_back(audio);
644 :
645 1 : video.enabled_ = true;
646 1 : video.label_ = "video_0";
647 1 : bobAcc->enableVideo(true);
648 1 : answer.emplace_back(video);
649 :
650 : // Run the scenario
651 1 : audio_video_call(offer, answer);
652 1 : }
653 :
654 : void
655 1 : SipBasicCallTest::peer_answer_with_all_media_disabled()
656 : {
657 1 : JAMI_INFO("=== Begin test %s ===", __FUNCTION__);
658 :
659 2 : auto const bobAcc = Manager::instance().getAccount<SIPAccount>(callDataMap_["BOB"].accountId_);
660 :
661 1 : std::vector<MediaAttribute> offer;
662 1 : std::vector<MediaAttribute> answer;
663 :
664 1 : MediaAttribute video(MediaType::MEDIA_VIDEO);
665 :
666 : // Configure Alice
667 1 : video.enabled_ = true;
668 1 : video.label_ = "video_0";
669 1 : video.secure_ = false;
670 1 : offer.emplace_back(video);
671 :
672 : // Configure Bob
673 1 : video.enabled_ = false;
674 1 : video.label_ = "video_0";
675 1 : video.secure_ = false;
676 1 : bobAcc->enableVideo(false);
677 1 : answer.emplace_back(video);
678 :
679 : // Run the scenario
680 1 : audio_video_call(offer, answer, false, false);
681 1 : }
682 :
683 : void
684 1 : SipBasicCallTest::hold_resume_test()
685 : {
686 1 : JAMI_INFO("=== Begin test %s ===", __FUNCTION__);
687 :
688 1 : auto const aliceAcc = Manager::instance().getAccount<SIPAccount>(
689 2 : callDataMap_["ALICE"].accountId_);
690 2 : auto const bobAcc = Manager::instance().getAccount<SIPAccount>(callDataMap_["BOB"].accountId_);
691 :
692 1 : std::vector<MediaAttribute> offer;
693 1 : std::vector<MediaAttribute> answer;
694 :
695 1 : MediaAttribute audio(MediaType::MEDIA_AUDIO);
696 1 : MediaAttribute video(MediaType::MEDIA_VIDEO);
697 :
698 1 : audio.enabled_ = true;
699 1 : audio.label_ = "audio_0";
700 1 : video.enabled_ = true;
701 1 : video.label_ = "video_0";
702 :
703 : // Alice's media
704 1 : offer.emplace_back(audio);
705 1 : offer.emplace_back(video);
706 :
707 : // Bob's media
708 1 : answer.emplace_back(audio);
709 1 : answer.emplace_back(video);
710 :
711 : {
712 1 : configureTest();
713 :
714 1 : JAMI_INFO("=== Start a call and validate ===");
715 :
716 2 : std::string bobUri = callDataMap_["BOB"].userName_
717 4 : + "@127.0.0.1:" + std::to_string(callDataMap_["BOB"].listeningPort_);
718 :
719 2 : callDataMap_["ALICE"].callId_
720 2 : = libjami::placeCallWithMedia(callDataMap_["ALICE"].accountId_,
721 : bobUri,
722 3 : MediaAttribute::mediaAttributesToMediaMaps(offer));
723 :
724 1 : CPPUNIT_ASSERT(not callDataMap_["ALICE"].callId_.empty());
725 :
726 1 : JAMI_INFO("ALICE [%s] started a call with BOB [%s] and wait for answer",
727 : callDataMap_["ALICE"].accountId_.c_str(),
728 : callDataMap_["BOB"].accountId_.c_str());
729 :
730 : // Give it some time to ring
731 1 : std::this_thread::sleep_for(std::chrono::seconds(2));
732 :
733 : // Wait for call to be processed.
734 1 : CPPUNIT_ASSERT(waitForSignal(callDataMap_["ALICE"],
735 : libjami::CallSignal::StateChange::name,
736 : StateEvent::RINGING));
737 :
738 : // Wait for incoming call signal.
739 1 : CPPUNIT_ASSERT(
740 : waitForSignal(callDataMap_["BOB"], libjami::CallSignal::IncomingCallWithMedia::name));
741 :
742 : // Answer the call.
743 1 : libjami::acceptWithMedia(callDataMap_["BOB"].accountId_,
744 2 : callDataMap_["BOB"].callId_,
745 2 : MediaAttribute::mediaAttributesToMediaMaps(answer));
746 :
747 : // Wait for media negotiation complete signal.
748 1 : CPPUNIT_ASSERT(
749 : waitForSignal(callDataMap_["BOB"],
750 : libjami::CallSignal::MediaNegotiationStatus::name,
751 : libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS));
752 :
753 : // Wait for the StateChange signal.
754 1 : CPPUNIT_ASSERT(waitForSignal(callDataMap_["BOB"],
755 : libjami::CallSignal::StateChange::name,
756 : StateEvent::CURRENT));
757 :
758 1 : JAMI_INFO("BOB answered the call [%s]", callDataMap_["BOB"].callId_.c_str());
759 :
760 : // Wait for media negotiation complete signal.
761 1 : CPPUNIT_ASSERT(
762 : waitForSignal(callDataMap_["ALICE"],
763 : libjami::CallSignal::MediaNegotiationStatus::name,
764 : libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS));
765 :
766 : // Validate Alice's side of media direction
767 : {
768 : auto call = std::static_pointer_cast<SIPCall>(
769 2 : Manager::instance().getCallFromCallID(callDataMap_["ALICE"].callId_));
770 1 : auto& sdp = call->getSDP();
771 1 : auto mediaStreams = sdp.getMediaSlots();
772 3 : for (auto const& media : mediaStreams) {
773 2 : CPPUNIT_ASSERT_EQUAL(MediaDirection::SENDRECV, media.first.direction_);
774 2 : CPPUNIT_ASSERT_EQUAL(MediaDirection::SENDRECV, media.second.direction_);
775 : }
776 1 : }
777 :
778 : // Validate Bob's side of media direction
779 : {
780 : auto call = std::static_pointer_cast<SIPCall>(
781 2 : Manager::instance().getCallFromCallID(callDataMap_["BOB"].callId_));
782 1 : auto& sdp = call->getSDP();
783 1 : auto mediaStreams = sdp.getMediaSlots();
784 3 : for (auto const& media : mediaStreams) {
785 2 : CPPUNIT_ASSERT_EQUAL(MediaDirection::SENDRECV, media.first.direction_);
786 2 : CPPUNIT_ASSERT_EQUAL(MediaDirection::SENDRECV, media.second.direction_);
787 : }
788 1 : }
789 :
790 : // Give some time to media to start and flow
791 1 : std::this_thread::sleep_for(std::chrono::seconds(2));
792 :
793 : // Hold/Resume the call
794 1 : JAMI_INFO("Hold Alice's call");
795 1 : libjami::hold(callDataMap_["ALICE"].accountId_, callDataMap_["ALICE"].callId_);
796 : // Wait for the StateChange signal.
797 1 : CPPUNIT_ASSERT(waitForSignal(callDataMap_["ALICE"],
798 : libjami::CallSignal::StateChange::name,
799 : StateEvent::HOLD));
800 :
801 : // Wait for media negotiation complete signal.
802 1 : CPPUNIT_ASSERT(
803 : waitForSignal(callDataMap_["ALICE"],
804 : libjami::CallSignal::MediaNegotiationStatus::name,
805 : libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS));
806 : {
807 : // Validate hold state.
808 2 : auto call = Manager::instance().getCallFromCallID(callDataMap_["ALICE"].callId_);
809 1 : auto activeMediaList = call->getMediaAttributeList();
810 3 : for (const auto& mediaAttr : activeMediaList) {
811 2 : CPPUNIT_ASSERT(mediaAttr.onHold_);
812 : }
813 1 : }
814 :
815 : // Validate Alice's side of media direction
816 : {
817 : auto call = std::static_pointer_cast<SIPCall>(
818 2 : Manager::instance().getCallFromCallID(callDataMap_["ALICE"].callId_));
819 1 : auto& sdp = call->getSDP();
820 1 : auto mediaStreams = sdp.getMediaSlots();
821 3 : for (auto const& media : mediaStreams) {
822 2 : if (media.first.type == MediaType::MEDIA_AUDIO) {
823 1 : CPPUNIT_ASSERT_EQUAL(MediaDirection::SENDRECV, media.first.direction_);
824 1 : CPPUNIT_ASSERT_EQUAL(MediaDirection::SENDRECV, media.second.direction_);
825 : } else {
826 1 : CPPUNIT_ASSERT_EQUAL(MediaDirection::SENDONLY, media.first.direction_);
827 1 : CPPUNIT_ASSERT_EQUAL(MediaDirection::RECVONLY, media.second.direction_);
828 : }
829 : }
830 1 : }
831 :
832 : // Validate Bob's side of media direction
833 : {
834 : auto call = std::static_pointer_cast<SIPCall>(
835 2 : Manager::instance().getCallFromCallID(callDataMap_["BOB"].callId_));
836 1 : auto& sdp = call->getSDP();
837 1 : auto mediaStreams = sdp.getMediaSlots();
838 3 : for (auto const& media : mediaStreams) {
839 2 : if (media.first.type == MediaType::MEDIA_AUDIO) {
840 1 : CPPUNIT_ASSERT_EQUAL(MediaDirection::SENDRECV, media.first.direction_);
841 1 : CPPUNIT_ASSERT_EQUAL(MediaDirection::SENDRECV, media.second.direction_);
842 : } else {
843 1 : CPPUNIT_ASSERT_EQUAL(MediaDirection::RECVONLY, media.first.direction_);
844 1 : CPPUNIT_ASSERT_EQUAL(MediaDirection::SENDONLY, media.second.direction_);
845 : }
846 : }
847 1 : }
848 :
849 1 : std::this_thread::sleep_for(std::chrono::seconds(2));
850 :
851 1 : JAMI_INFO("Resume Alice's call");
852 1 : libjami::unhold(callDataMap_["ALICE"].accountId_, callDataMap_["ALICE"].callId_);
853 :
854 : // Wait for the StateChange signal.
855 1 : CPPUNIT_ASSERT(waitForSignal(callDataMap_["ALICE"],
856 : libjami::CallSignal::StateChange::name,
857 : StateEvent::CURRENT));
858 :
859 : // Wait for media negotiation complete signal.
860 1 : CPPUNIT_ASSERT(
861 : waitForSignal(callDataMap_["ALICE"],
862 : libjami::CallSignal::MediaNegotiationStatus::name,
863 : libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS));
864 1 : std::this_thread::sleep_for(std::chrono::seconds(2));
865 :
866 : {
867 : // Validate hold state.
868 2 : auto call = Manager::instance().getCallFromCallID(callDataMap_["ALICE"].callId_);
869 1 : auto activeMediaList = call->getMediaAttributeList();
870 3 : for (const auto& mediaAttr : activeMediaList) {
871 2 : CPPUNIT_ASSERT(not mediaAttr.onHold_);
872 : }
873 1 : }
874 :
875 : // Bob hang-up.
876 1 : JAMI_INFO("Hang up BOB's call and wait for ALICE to hang up");
877 1 : libjami::hangUp(callDataMap_["BOB"].accountId_, callDataMap_["BOB"].callId_);
878 :
879 : // The hang-up signal will be emitted on caller's side (Alice) in both
880 : // success and failure scenarios.
881 :
882 1 : CPPUNIT_ASSERT(waitForSignal(callDataMap_["ALICE"],
883 : libjami::CallSignal::StateChange::name,
884 : StateEvent::HUNGUP));
885 :
886 1 : JAMI_INFO("Call terminated on both sides");
887 1 : }
888 1 : }
889 :
890 : void
891 1 : SipBasicCallTest::blind_transfer_test()
892 : {
893 : // Test a "blind" (a.k.a. unattended) transfer as described in
894 : // https://datatracker.ietf.org/doc/html/rfc5589
895 :
896 : /** Call transfer scenario:
897 : *
898 : * Alice and Bob are in an active call
899 : * Alice performs a call transfer (SIP REFER method) to Carla
900 : * Bob automatically accepts the transfer request
901 : * Alice ends the call with Bob
902 : * Bob send a new call invite to Carla
903 : * Carla accepts the call
904 : * Carl ends the call
905 : *
906 : * Here is a simplified version of a call flow from
907 : * rfc5589
908 : *
909 : *
910 : Alice Bob Carla
911 : | REFER | |
912 : |----------------------->| |
913 : | 202 Accepted | |
914 : |<-----------------------| |
915 : | BYE | |
916 : |<-----------------------| |
917 : | 200 OK | |
918 : |----------------------->| |
919 : | | INVITE |
920 : | |----------------------->|
921 : | | 200 OK |
922 : | |<-----------------------|
923 : */
924 :
925 1 : JAMI_INFO("=== Begin test %s ===", __FUNCTION__);
926 :
927 1 : auto const aliceAcc = Manager::instance().getAccount<SIPAccount>(
928 2 : callDataMap_["ALICE"].accountId_);
929 2 : auto const bobAcc = Manager::instance().getAccount<SIPAccount>(callDataMap_["BOB"].accountId_);
930 1 : auto const carlaAcc = Manager::instance().getAccount<SIPAccount>(
931 2 : callDataMap_["CARLA"].accountId_);
932 :
933 1 : aliceAcc->enableIceForMedia(false);
934 1 : bobAcc->enableIceForMedia(false);
935 1 : carlaAcc->enableIceForMedia(false);
936 :
937 1 : std::vector<MediaAttribute> offer;
938 1 : std::vector<MediaAttribute> answer;
939 :
940 1 : MediaAttribute audio(MediaType::MEDIA_AUDIO);
941 1 : MediaAttribute video(MediaType::MEDIA_VIDEO);
942 :
943 1 : audio.enabled_ = true;
944 1 : audio.label_ = "audio_0";
945 :
946 : // Alice's media
947 1 : offer.emplace_back(audio);
948 :
949 : // Bob's media
950 1 : answer.emplace_back(audio);
951 :
952 1 : configureTest();
953 :
954 1 : JAMI_INFO("=== Start a call and validate ===");
955 :
956 2 : std::string bobUri = callDataMap_["BOB"].userName_
957 4 : + "@127.0.0.1:" + std::to_string(callDataMap_["BOB"].listeningPort_);
958 :
959 2 : callDataMap_["ALICE"].callId_
960 2 : = libjami::placeCallWithMedia(callDataMap_["ALICE"].accountId_,
961 : bobUri,
962 3 : MediaAttribute::mediaAttributesToMediaMaps(offer));
963 :
964 1 : CPPUNIT_ASSERT(not callDataMap_["ALICE"].callId_.empty());
965 :
966 1 : JAMI_INFO("ALICE [%s] started a call with BOB [%s] and wait for answer",
967 : callDataMap_["ALICE"].accountId_.c_str(),
968 : callDataMap_["BOB"].accountId_.c_str());
969 :
970 : // Give it some time to ring
971 1 : std::this_thread::sleep_for(std::chrono::seconds(2));
972 :
973 : // Wait for call to be processed.
974 1 : CPPUNIT_ASSERT(waitForSignal(callDataMap_["ALICE"],
975 : libjami::CallSignal::StateChange::name,
976 : StateEvent::RINGING));
977 :
978 : // Wait for incoming call signal.
979 1 : CPPUNIT_ASSERT(
980 : waitForSignal(callDataMap_["BOB"], libjami::CallSignal::IncomingCallWithMedia::name));
981 :
982 : // Answer the call.
983 1 : libjami::acceptWithMedia(callDataMap_["BOB"].accountId_,
984 2 : callDataMap_["BOB"].callId_,
985 2 : MediaAttribute::mediaAttributesToMediaMaps(answer));
986 :
987 : // Wait for media negotiation complete signal.
988 1 : CPPUNIT_ASSERT(waitForSignal(callDataMap_["BOB"],
989 : libjami::CallSignal::MediaNegotiationStatus::name,
990 : libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS));
991 :
992 : // Wait for the StateChange signal.
993 1 : CPPUNIT_ASSERT(waitForSignal(callDataMap_["BOB"],
994 : libjami::CallSignal::StateChange::name,
995 : StateEvent::CURRENT));
996 :
997 1 : JAMI_INFO("BOB answered the call [%s]", callDataMap_["BOB"].callId_.c_str());
998 :
999 : // Wait for media negotiation complete signal.
1000 1 : CPPUNIT_ASSERT(waitForSignal(callDataMap_["ALICE"],
1001 : libjami::CallSignal::MediaNegotiationStatus::name,
1002 : libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS));
1003 :
1004 : // Give some time to media to start and flow
1005 1 : std::this_thread::sleep_for(std::chrono::seconds(2));
1006 :
1007 : // Transfer the call to Carla
1008 1 : std::string carlaUri = carlaAcc->getUsername()
1009 3 : + "@127.0.0.1:" + std::to_string(callDataMap_["CARLA"].listeningPort_);
1010 :
1011 1 : libjami::transfer(callDataMap_["ALICE"].accountId_,
1012 2 : callDataMap_["ALICE"].callId_,
1013 : carlaUri); // TODO. Check trim
1014 :
1015 : // Expect Alice's call to end.
1016 1 : CPPUNIT_ASSERT(waitForSignal(callDataMap_["ALICE"],
1017 : libjami::CallSignal::StateChange::name,
1018 : StateEvent::HUNGUP));
1019 :
1020 : // Wait for the new call to be processed.
1021 1 : CPPUNIT_ASSERT(waitForSignal(callDataMap_["BOB"],
1022 : libjami::CallSignal::StateChange::name,
1023 : StateEvent::RINGING));
1024 :
1025 : // Wait for incoming call signal.
1026 1 : CPPUNIT_ASSERT(
1027 : waitForSignal(callDataMap_["CARLA"], libjami::CallSignal::IncomingCallWithMedia::name));
1028 :
1029 : // Let it ring
1030 1 : std::this_thread::sleep_for(std::chrono::seconds(2));
1031 :
1032 : // Carla answers the call.
1033 1 : libjami::acceptWithMedia(callDataMap_["CARLA"].accountId_,
1034 2 : callDataMap_["CARLA"].callId_,
1035 2 : MediaAttribute::mediaAttributesToMediaMaps(answer));
1036 :
1037 : // Wait for media negotiation complete signal.
1038 1 : CPPUNIT_ASSERT(waitForSignal(callDataMap_["CARLA"],
1039 : libjami::CallSignal::MediaNegotiationStatus::name,
1040 : libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS));
1041 :
1042 : // Wait for the StateChange signal.
1043 1 : CPPUNIT_ASSERT(waitForSignal(callDataMap_["CARLA"],
1044 : libjami::CallSignal::StateChange::name,
1045 : StateEvent::CURRENT));
1046 :
1047 1 : JAMI_INFO("CARLA answered the call [%s]", callDataMap_["BOB"].callId_.c_str());
1048 :
1049 : // Wait for media negotiation complete signal.
1050 1 : CPPUNIT_ASSERT(waitForSignal(callDataMap_["BOB"],
1051 : libjami::CallSignal::MediaNegotiationStatus::name,
1052 : libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS));
1053 : // Wait for the StateChange signal.
1054 1 : CPPUNIT_ASSERT(waitForSignal(callDataMap_["BOB"],
1055 : libjami::CallSignal::StateChange::name,
1056 : StateEvent::CURRENT));
1057 :
1058 : // Validate Carla's side of media direction
1059 : {
1060 : auto call = std::static_pointer_cast<SIPCall>(
1061 2 : Manager::instance().getCallFromCallID(callDataMap_["CARLA"].callId_));
1062 1 : auto& sdp = call->getSDP();
1063 1 : auto mediaStreams = sdp.getMediaSlots();
1064 2 : for (auto const& media : mediaStreams) {
1065 1 : CPPUNIT_ASSERT_EQUAL(MediaDirection::SENDRECV, media.first.direction_);
1066 1 : CPPUNIT_ASSERT_EQUAL(MediaDirection::SENDRECV, media.second.direction_);
1067 : }
1068 1 : }
1069 :
1070 : // NOTE:
1071 : // For now, we dont validate Bob's media because currently
1072 : // test does not update BOB's call ID (callDataMap_["BOB"].callId_
1073 : // still point to the first call).
1074 : // It seems there is no easy way to get the ID of the new call
1075 : // made by Bob to Carla.
1076 :
1077 : // Give some time to media to start and flow
1078 1 : std::this_thread::sleep_for(std::chrono::seconds(2));
1079 :
1080 : // Bob hang-up.
1081 1 : JAMI_INFO("Hang up CARLA's call and wait for CARLA to hang up");
1082 1 : libjami::hangUp(callDataMap_["CARLA"].accountId_, callDataMap_["CARLA"].callId_);
1083 :
1084 : // Expect end call on Carla's side.
1085 1 : CPPUNIT_ASSERT(waitForSignal(callDataMap_["CARLA"],
1086 : libjami::CallSignal::StateChange::name,
1087 : StateEvent::HUNGUP));
1088 :
1089 1 : JAMI_INFO("Calls normally ended on both sides");
1090 1 : }
1091 :
1092 : } // namespace test
1093 : } // namespace jami
1094 :
1095 1 : RING_TEST_RUNNER(jami::test::SipBasicCallTest::name())
|