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