mirror of
https://git.jami.net/savoirfairelinux/jami-daemon.git
synced 2025-08-12 22:09:25 +08:00
test: remove ice_media_cand_exchange
There were no useful test as it duplicates test for ICE connectivity and doesn't test media Change-Id: Ifde558bf44bbd60911385760c209eb4ccab9dc12
This commit is contained in:
@ -216,18 +216,6 @@ test('ice', ut_ice,
|
||||
workdir: ut_workdir, is_parallel: false, timeout: 1800
|
||||
)
|
||||
|
||||
|
||||
ut_ice_media_candidates_exchange = executable('ut_ice_media_candidates_exchange',
|
||||
sources: files('unitTest/ice/ice_media_cand_exchange.cpp'),
|
||||
include_directories: ut_includedirs,
|
||||
dependencies: ut_dependencies,
|
||||
link_with: ut_library
|
||||
)
|
||||
test('ice_media_candidates_exchange', ut_ice_media_candidates_exchange,
|
||||
workdir: ut_workdir, is_parallel: false, timeout: 1800
|
||||
)
|
||||
|
||||
|
||||
ut_ice_sdp_parser = executable('ut_ice_sdp_parser',
|
||||
sources: files('unitTest/ice/ice_sdp_parser.cpp'),
|
||||
include_directories: ut_includedirs,
|
||||
|
@ -225,9 +225,6 @@ ut_revoke_SOURCES = revoke/revoke.cpp common.cpp
|
||||
check_PROGRAMS += ut_ice_sdp_parser
|
||||
ut_ice_sdp_parser_SOURCES = ice/ice_sdp_parser.cpp common.cpp
|
||||
|
||||
check_PROGRAMS += ut_ice_media_cand_exchange
|
||||
ut_ice_media_cand_exchange_SOURCES = ice/ice_media_cand_exchange.cpp common.cpp
|
||||
|
||||
#
|
||||
# Calls using SIP accounts
|
||||
#
|
||||
|
@ -1,835 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021-2024 Savoir-faire Linux Inc.
|
||||
*
|
||||
* Author: Mohamed Chibani <mohamed.chibani@savoirfairelinux.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "manager.h"
|
||||
#include "jamidht/jamiaccount.h"
|
||||
#include "sip/sipaccount.h"
|
||||
#include "../../test_runner.h"
|
||||
|
||||
#include "jami.h"
|
||||
#include "media_const.h"
|
||||
#include "call_const.h"
|
||||
#include "account_const.h"
|
||||
#include "sip/sipcall.h"
|
||||
#include "common.h"
|
||||
|
||||
#include <dhtnet/upnp/upnp_control.h>
|
||||
|
||||
#include <cppunit/TestAssert.h>
|
||||
#include <cppunit/TestFixture.h>
|
||||
#include <cppunit/extensions/HelperMacros.h>
|
||||
|
||||
#include <condition_variable>
|
||||
#include <string>
|
||||
|
||||
using namespace libjami::Account;
|
||||
using namespace libjami::Call;
|
||||
|
||||
namespace jami {
|
||||
namespace test {
|
||||
|
||||
struct CallData
|
||||
{
|
||||
struct Signal
|
||||
{
|
||||
Signal(const std::string& callId, const std::string& name, const std::string& event = {})
|
||||
: callId_(std::move(callId))
|
||||
, name_(std::move(name))
|
||||
, event_(std::move(event)) {};
|
||||
|
||||
std::string callId_ {};
|
||||
std::string name_ {};
|
||||
std::string event_ {};
|
||||
};
|
||||
|
||||
std::string accountId_ {};
|
||||
std::string displayName_ {};
|
||||
std::string userName_ {};
|
||||
std::string alias_ {};
|
||||
std::string callId_ {};
|
||||
std::vector<Signal> signals_;
|
||||
std::condition_variable cv_ {};
|
||||
std::mutex mtx_;
|
||||
bool deviceAnnounced_ {false};
|
||||
bool upnpEnabled_ {false};
|
||||
bool turnEnabled_ {false};
|
||||
dhtnet::IpAddr dest_ {};
|
||||
// SIP accounts only
|
||||
uint16_t listeningPort_ {0};
|
||||
};
|
||||
|
||||
class IceMediaCandExchangeTest : public CppUnit::TestFixture
|
||||
{
|
||||
public:
|
||||
IceMediaCandExchangeTest()
|
||||
{
|
||||
// Init daemon
|
||||
libjami::init(libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG));
|
||||
if (not Manager::instance().initialized)
|
||||
CPPUNIT_ASSERT(libjami::start("jami-sample.yml"));
|
||||
}
|
||||
~IceMediaCandExchangeTest() { libjami::fini(); }
|
||||
|
||||
static std::string name() { return "IceMediaCandExchangeTest"; }
|
||||
void setUp();
|
||||
void tearDown();
|
||||
|
||||
private:
|
||||
// Test cases.
|
||||
// This is not actual test, it only checks if upnp is
|
||||
// functional. This information is needed to validate
|
||||
// generated ICE srflx candidates.
|
||||
void check_upnp();
|
||||
void jami_account_no_turn();
|
||||
void jami_account_with_turn();
|
||||
void sip_account_no_turn();
|
||||
void sip_account_with_turn();
|
||||
|
||||
CPPUNIT_TEST_SUITE(IceMediaCandExchangeTest);
|
||||
CPPUNIT_TEST(check_upnp);
|
||||
CPPUNIT_TEST(jami_account_no_turn);
|
||||
CPPUNIT_TEST(jami_account_with_turn);
|
||||
CPPUNIT_TEST_SUITE_END();
|
||||
|
||||
// Event/Signal handlers
|
||||
static void onCallStateChange(const std::string& accountId,
|
||||
const std::string& callId,
|
||||
const std::string& state,
|
||||
CallData& callData);
|
||||
static void onIncomingCallWithMedia(const std::string& accountId,
|
||||
const std::string& callId,
|
||||
const std::vector<libjami::MediaMap> mediaList,
|
||||
CallData& callData);
|
||||
static void onMediaNegotiationStatus(const std::string& callId,
|
||||
const std::string& event,
|
||||
CallData& callData);
|
||||
|
||||
// Helpers
|
||||
void test_call(const char* accountType);
|
||||
static void validate_ice_candidates(CallData& user,
|
||||
const char* accountType,
|
||||
bool hasUpnp,
|
||||
bool upnpSameAsPublished);
|
||||
static void setupJamiAccount(CallData& user);
|
||||
static void setupSipAccount(CallData& user);
|
||||
static void setupAccounts(CallData& bob, CallData& alice, const char* accountType);
|
||||
static void configureAccount(CallData& user, const char* accountType);
|
||||
static std::string getUserAlias(const std::string& callId);
|
||||
// Wait for a signal from the callbacks. Some signals also report the event that
|
||||
// triggered the signal a like the StateChange signal.
|
||||
static bool waitForSignal(CallData& callData,
|
||||
const std::string& signal,
|
||||
const std::string& expectedEvent = {});
|
||||
|
||||
private:
|
||||
CallData aliceData_;
|
||||
CallData bobData_;
|
||||
const size_t MEDIA_COUNT {2};
|
||||
std::condition_variable upnpCv_ {};
|
||||
std::mutex upnpMtx_;
|
||||
bool hasUpnp_ {false};
|
||||
bool upnpAddrSameAsPublished_ {false};
|
||||
};
|
||||
|
||||
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(IceMediaCandExchangeTest, IceMediaCandExchangeTest::name());
|
||||
|
||||
void
|
||||
IceMediaCandExchangeTest::setUp()
|
||||
{
|
||||
// Everything is done in ConfigureTest()
|
||||
}
|
||||
|
||||
void
|
||||
IceMediaCandExchangeTest::tearDown()
|
||||
{
|
||||
JAMI_INFO("Remove created accounts...");
|
||||
wait_for_removal_of({aliceData_.accountId_, bobData_.accountId_});
|
||||
}
|
||||
|
||||
std::string
|
||||
IceMediaCandExchangeTest::getUserAlias(const std::string& callId)
|
||||
{
|
||||
auto call = Manager::instance().getCallFromCallID(callId);
|
||||
|
||||
if (call) {
|
||||
auto const& account = call->getAccount().lock();
|
||||
if (not account) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return account->getAccountDetails()[ConfProperties::ALIAS];
|
||||
}
|
||||
|
||||
JAMI_WARN("Call [%s] does not exist!", callId.c_str());
|
||||
return {};
|
||||
}
|
||||
|
||||
void
|
||||
IceMediaCandExchangeTest::onIncomingCallWithMedia(const std::string& accountId,
|
||||
const std::string& callId,
|
||||
const std::vector<libjami::MediaMap> mediaList,
|
||||
CallData& callData)
|
||||
{
|
||||
CPPUNIT_ASSERT_EQUAL(callData.accountId_, accountId);
|
||||
|
||||
JAMI_INFO("Signal [%s] - user [%s] - call [%s] - media count [%lu]",
|
||||
libjami::CallSignal::IncomingCallWithMedia::name,
|
||||
callData.alias_.c_str(),
|
||||
callId.c_str(),
|
||||
mediaList.size());
|
||||
|
||||
if (not Manager::instance().getCallFromCallID(callId)) {
|
||||
JAMI_WARN("Call [%s] does not exist!", callId.c_str());
|
||||
callData.callId_ = {};
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> lock {callData.mtx_};
|
||||
callData.callId_ = callId;
|
||||
callData.signals_.emplace_back(
|
||||
CallData::Signal(callId, libjami::CallSignal::IncomingCallWithMedia::name));
|
||||
|
||||
callData.cv_.notify_one();
|
||||
}
|
||||
|
||||
void
|
||||
IceMediaCandExchangeTest::onCallStateChange(const std::string&,
|
||||
const std::string& callId,
|
||||
const std::string& state,
|
||||
CallData& callData)
|
||||
{
|
||||
JAMI_INFO("Signal [%s] - user [%s] - call [%s] - state [%s]",
|
||||
libjami::CallSignal::StateChange::name,
|
||||
callData.alias_.c_str(),
|
||||
callId.c_str(),
|
||||
state.c_str());
|
||||
{
|
||||
std::unique_lock<std::mutex> lock {callData.mtx_};
|
||||
callData.signals_.emplace_back(
|
||||
CallData::Signal(callId, libjami::CallSignal::StateChange::name, state));
|
||||
}
|
||||
|
||||
if (state == "RINGING" or state == "CURRENT" or state == "HUNGUP" or state == "OVER") {
|
||||
callData.cv_.notify_one();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
IceMediaCandExchangeTest::onMediaNegotiationStatus(const std::string& callId,
|
||||
const std::string& event,
|
||||
CallData& callData)
|
||||
{
|
||||
auto call = Manager::instance().getCallFromCallID(callId);
|
||||
if (not call) {
|
||||
JAMI_WARN("Call [%s] does not exist!", callId.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
auto account = call->getAccount().lock();
|
||||
if (not account) {
|
||||
JAMI_WARN("Account owning the call [%s] does not exist!", callId.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
JAMI_INFO("Signal [%s] - user [%s] - call [%s] - state [%s]",
|
||||
libjami::CallSignal::MediaNegotiationStatus::name,
|
||||
account->getAccountDetails()[ConfProperties::ALIAS].c_str(),
|
||||
call->getCallId().c_str(),
|
||||
event.c_str());
|
||||
|
||||
if (account->getAccountID() != callData.accountId_)
|
||||
return;
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock {callData.mtx_};
|
||||
callData.signals_.emplace_back(
|
||||
CallData::Signal(callId, libjami::CallSignal::MediaNegotiationStatus::name, event));
|
||||
}
|
||||
|
||||
callData.cv_.notify_one();
|
||||
}
|
||||
|
||||
bool
|
||||
IceMediaCandExchangeTest::waitForSignal(CallData& callData,
|
||||
const std::string& expectedSignal,
|
||||
const std::string& expectedEvent)
|
||||
{
|
||||
const std::chrono::seconds TIME_OUT {45};
|
||||
std::unique_lock<std::mutex> lock {callData.mtx_};
|
||||
|
||||
// Combined signal + event (if any).
|
||||
std::string sigEvent(expectedSignal);
|
||||
if (not expectedEvent.empty())
|
||||
sigEvent += "::" + expectedEvent;
|
||||
|
||||
JAMI_INFO("[%s] is waiting for [%s] signal/event", callData.alias_.c_str(), sigEvent.c_str());
|
||||
|
||||
auto res = callData.cv_.wait_for(lock, TIME_OUT, [&] {
|
||||
// Search for the expected signal in list of received signals.
|
||||
for (auto it = callData.signals_.begin(); it != callData.signals_.end(); it++) {
|
||||
// The predicate is true if the signal names match, and if the
|
||||
// expectedEvent is not empty, the events must also match.
|
||||
if (it->name_ == expectedSignal
|
||||
and (expectedEvent.empty() or it->event_ == expectedEvent)) {
|
||||
// Done with this signal.
|
||||
callData.signals_.erase(it);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Signal/event not found.
|
||||
return false;
|
||||
});
|
||||
|
||||
if (not res) {
|
||||
JAMI_ERR("[%s] waiting for signal/event [%s] [call:%s] timed-out!",
|
||||
callData.alias_.c_str(),
|
||||
sigEvent.c_str(),
|
||||
callData.callId_.c_str());
|
||||
|
||||
JAMI_INFO("[%s] currently has the following signals:", callData.alias_.c_str());
|
||||
|
||||
for (auto const& sig : callData.signals_) {
|
||||
JAMI_INFO() << "Signal [" << sig.name_
|
||||
<< (sig.event_.empty() ? "" : ("::" + sig.event_)) << "] "
|
||||
<< "call [" << sig.callId_ << "]";
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void
|
||||
IceMediaCandExchangeTest::setupJamiAccount(CallData& user)
|
||||
{
|
||||
auto account = Manager::instance().getAccount<JamiAccount>(user.accountId_);
|
||||
auto details = account->getAccountDetails();
|
||||
|
||||
user.userName_ = details[ConfProperties::USERNAME];
|
||||
user.alias_ = details[ConfProperties::ALIAS];
|
||||
|
||||
// Apply the settings according to the test case
|
||||
details[ConfProperties::UPNP_ENABLED] = user.upnpEnabled_ ? "true" : "false";
|
||||
details[ConfProperties::TURN::ENABLED] = user.turnEnabled_ ? "true" : "false";
|
||||
libjami::setAccountDetails(user.accountId_, details);
|
||||
}
|
||||
|
||||
void
|
||||
IceMediaCandExchangeTest::setupSipAccount(CallData& user)
|
||||
{
|
||||
CPPUNIT_ASSERT_GREATER(0, static_cast<int>(user.listeningPort_));
|
||||
|
||||
auto details = libjami::getAccountTemplate(libjami::Account::ProtocolNames::SIP);
|
||||
details[ConfProperties::TYPE] = libjami::Account::ProtocolNames::SIP;
|
||||
details[ConfProperties::DISPLAYNAME] = user.displayName_.c_str();
|
||||
details[ConfProperties::ALIAS] = user.displayName_.c_str();
|
||||
details[ConfProperties::LOCAL_PORT] = std::to_string(user.listeningPort_);
|
||||
details[ConfProperties::PUBLISHED_PORT] = std::to_string(user.listeningPort_);
|
||||
details[ConfProperties::PUBLISHED_SAMEAS_LOCAL] = "true";
|
||||
details[ConfProperties::SRTP::ENABLED] = "false";
|
||||
details[ConfProperties::SRTP::KEY_EXCHANGE] = "NONE";
|
||||
|
||||
// Apply the settings according to the test case
|
||||
details[ConfProperties::UPNP_ENABLED] = user.upnpEnabled_ ? "true" : "false";
|
||||
details[ConfProperties::TURN::ENABLED] = user.turnEnabled_ ? "true" : "false";
|
||||
|
||||
user.accountId_ = Manager::instance().addAccount(details);
|
||||
CPPUNIT_ASSERT(not user.accountId_.empty());
|
||||
auto const& account = Manager::instance().getAccount<Account>(user.accountId_);
|
||||
details = account->getAccountDetails();
|
||||
user.userName_ = details[ConfProperties::USERNAME];
|
||||
user.alias_ = details[ConfProperties::ALIAS];
|
||||
user.dest_ = dhtnet::ip_utils::getLocalAddr(AF_INET);
|
||||
user.dest_.setPort(user.listeningPort_);
|
||||
}
|
||||
|
||||
void
|
||||
IceMediaCandExchangeTest::setupAccounts(CallData& aliceData,
|
||||
CallData& bobData,
|
||||
const char* accountType)
|
||||
{
|
||||
if (strcmp(accountType, libjami::Account::ProtocolNames::SIP) == 0) {
|
||||
JAMI_INFO("Setup SIP accounts and configure test case ...");
|
||||
|
||||
aliceData.displayName_ = "ALICE";
|
||||
aliceData.listeningPort_ = 5080;
|
||||
setupSipAccount(aliceData);
|
||||
|
||||
bobData.displayName_ = "BOB";
|
||||
bobData.listeningPort_ = 5082;
|
||||
setupSipAccount(bobData);
|
||||
} else {
|
||||
JAMI_INFO("Setup JAMI accounts and configure test case ...");
|
||||
auto actors = load_actors("actors/alice-bob-no-upnp.yml");
|
||||
|
||||
aliceData.accountId_ = actors["alice"];
|
||||
auto const& aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceData.accountId_);
|
||||
|
||||
bobData.accountId_ = actors["bob"];
|
||||
auto const& bobAccount = Manager::instance().getAccount<JamiAccount>(bobData.accountId_);
|
||||
|
||||
wait_for_announcement_of({aliceAccount->getAccountID(), bobAccount->getAccountID()});
|
||||
|
||||
setupJamiAccount(bobData);
|
||||
setupJamiAccount(aliceData);
|
||||
}
|
||||
|
||||
// Setup signal handlers.
|
||||
std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> signalHandlers;
|
||||
|
||||
signalHandlers.insert(libjami::exportable_callback<libjami::CallSignal::IncomingCallWithMedia>(
|
||||
[&](const std::string& accountId,
|
||||
const std::string& callId,
|
||||
const std::string&,
|
||||
const std::vector<libjami::MediaMap> mediaList) {
|
||||
auto user = getUserAlias(callId);
|
||||
if (user.empty()) {
|
||||
// The call was probably already removed, in this case, just
|
||||
// check callId against current calls.
|
||||
if (callId == aliceData.callId_)
|
||||
user = aliceData.alias_;
|
||||
if (callId == bobData.callId_)
|
||||
user = bobData.alias_;
|
||||
}
|
||||
|
||||
if (not user.empty()) {
|
||||
onIncomingCallWithMedia(accountId,
|
||||
callId,
|
||||
mediaList,
|
||||
user == aliceData.alias_ ? aliceData : bobData);
|
||||
} else {
|
||||
JAMI_WARN("Received [CallSignal::IncomingCallWithMedia] for a removed call [%s]",
|
||||
callId.c_str());
|
||||
}
|
||||
}));
|
||||
|
||||
signalHandlers.insert(
|
||||
libjami::exportable_callback<libjami::CallSignal::StateChange>([&](const std::string& accountId,
|
||||
const std::string& callId,
|
||||
const std::string& state,
|
||||
signed) {
|
||||
auto user = getUserAlias(callId);
|
||||
if (user.empty()) {
|
||||
// The call was probably already removed, in this case, just
|
||||
// check callId against current calls.
|
||||
if (callId == aliceData.callId_)
|
||||
user = aliceData.alias_;
|
||||
if (callId == bobData.callId_)
|
||||
user = bobData.alias_;
|
||||
}
|
||||
if (not user.empty()) {
|
||||
onCallStateChange(accountId,
|
||||
callId,
|
||||
state,
|
||||
user == aliceData.alias_ ? aliceData : bobData);
|
||||
} else {
|
||||
JAMI_WARN("Received [StateChange::%s] for a removed call [%s]",
|
||||
state.c_str(),
|
||||
callId.c_str());
|
||||
}
|
||||
}));
|
||||
|
||||
signalHandlers.insert(libjami::exportable_callback<libjami::CallSignal::MediaNegotiationStatus>(
|
||||
[&](const std::string& callId,
|
||||
const std::string& event,
|
||||
const std::vector<std::map<std::string, std::string>>& /* mediaList */) {
|
||||
auto user = getUserAlias(callId);
|
||||
if (user.empty()) {
|
||||
// The call was probably already removed, in this case, just
|
||||
// check callId against current calls.
|
||||
if (callId == aliceData.callId_)
|
||||
user = aliceData.alias_;
|
||||
if (callId == bobData.callId_)
|
||||
user = bobData.alias_;
|
||||
}
|
||||
if (not user.empty()) {
|
||||
onMediaNegotiationStatus(callId,
|
||||
event,
|
||||
user == aliceData.alias_ ? aliceData : bobData);
|
||||
} else {
|
||||
JAMI_WARN("Received [CallSignal::MediaNegotiationStatus] for a removed call [%s]",
|
||||
callId.c_str());
|
||||
}
|
||||
}));
|
||||
|
||||
signalHandlers.insert(
|
||||
libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
|
||||
[&](const std::string& accountId, const std::map<std::string, std::string>&) {
|
||||
auto account = Manager::instance().getAccount(accountId);
|
||||
if (not account) {
|
||||
JAMI_ERR("No account with ID [%s]", accountId.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
auto details = account->getVolatileAccountDetails();
|
||||
auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
|
||||
|
||||
if (accountId == aliceData.accountId_) {
|
||||
aliceData.deviceAnnounced_ = deviceAnnounced == "true";
|
||||
aliceData.cv_.notify_one();
|
||||
} else if (accountId == bobData.accountId_) {
|
||||
bobData.deviceAnnounced_ = deviceAnnounced == "true";
|
||||
bobData.cv_.notify_one();
|
||||
} else {
|
||||
JAMI_ERR("Account with ID [%s] is unknown", accountId.c_str());
|
||||
}
|
||||
}));
|
||||
|
||||
libjami::registerSignalHandlers(signalHandlers);
|
||||
}
|
||||
|
||||
void
|
||||
IceMediaCandExchangeTest::configureAccount(CallData& user, const char* accountType)
|
||||
{
|
||||
auto details
|
||||
= strcmp(accountType, libjami::Account::ProtocolNames::RING) == 0
|
||||
? Manager::instance().getAccount<JamiAccount>(user.accountId_)->getAccountDetails()
|
||||
: Manager::instance().getAccount<SIPAccount>(user.accountId_)->getAccountDetails();
|
||||
|
||||
// Apply the settings according to the test case
|
||||
details[ConfProperties::UPNP_ENABLED] = user.upnpEnabled_ ? "true" : "false";
|
||||
details[ConfProperties::TURN::ENABLED] = user.turnEnabled_ ? "true" : "false";
|
||||
|
||||
libjami::setAccountDetails(user.accountId_, details);
|
||||
|
||||
// Note: setAccountDetails will trigger a re-register of the account, so
|
||||
// we need to wait for the registration to proceed.
|
||||
// Only done for JAMI accounts.
|
||||
|
||||
if (strcmp(accountType, libjami::Account::ProtocolNames::RING) != 0)
|
||||
return;
|
||||
|
||||
auto account = Manager::instance().getAccount<JamiAccount>(user.accountId_);
|
||||
CPPUNIT_ASSERT(account);
|
||||
|
||||
JAMI_INFO("Waiting for [%s] account [%s] to register ...",
|
||||
user.alias_.c_str(),
|
||||
user.accountId_.c_str());
|
||||
std::unique_lock<std::mutex> lock {user.mtx_};
|
||||
CPPUNIT_ASSERT(
|
||||
user.cv_.wait_for(lock, std::chrono::seconds(60), [&] { return user.deviceAnnounced_; }));
|
||||
}
|
||||
|
||||
void
|
||||
IceMediaCandExchangeTest::validate_ice_candidates(CallData& user,
|
||||
const char* accountType,
|
||||
bool hasUpnp,
|
||||
bool upnpSameAsPublished)
|
||||
{
|
||||
auto sipCall = std::dynamic_pointer_cast<SIPCall>(
|
||||
Manager::instance().getCallFromCallID(user.callId_));
|
||||
CPPUNIT_ASSERT(sipCall);
|
||||
|
||||
// Only check the first component for now.
|
||||
auto localCand = sipCall->getLocalIceCandidates(1);
|
||||
// Should not be empty.
|
||||
CPPUNIT_ASSERT(not localCand.empty());
|
||||
|
||||
int srflxCandCount = 0;
|
||||
int relayCandCount = 0;
|
||||
for (auto const& cand : localCand) {
|
||||
JAMI_INFO("[%s] ICE cand: [%s]", user.alias_.c_str(), cand.c_str());
|
||||
if (cand.find("srflx") != std::string::npos) {
|
||||
srflxCandCount++;
|
||||
} else if (cand.find("relay") != std::string::npos) {
|
||||
relayCandCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Note:
|
||||
// Enabling UPNP does not guarantee that we have valid UPNP srflx
|
||||
// candidate. However, for RING accounts, we must have at least one
|
||||
// srflx candidate generated using the public address discovered
|
||||
// after connecting to the DHT.
|
||||
// For SIP accounts, we use un-registered (P2P) mode), so srflx
|
||||
// candidates are only added if UPNP is enabled and the mapping is
|
||||
// successful.
|
||||
int expectedSrflxCandCount = 0;
|
||||
if (strcmp(accountType, libjami::Account::ProtocolNames::RING) == 0) {
|
||||
if (hasUpnp) {
|
||||
if (upnpSameAsPublished) {
|
||||
// UPNP and published are the same, published wont be added.
|
||||
expectedSrflxCandCount = 1;
|
||||
} else {
|
||||
// Both UPNP and published will be added.
|
||||
expectedSrflxCandCount = 2;
|
||||
}
|
||||
} else {
|
||||
// No UPNP, only published address.
|
||||
expectedSrflxCandCount = 1;
|
||||
}
|
||||
} else {
|
||||
// SIP accounts in un-registered mode will have only UPNP srflx
|
||||
// candidates if available
|
||||
if (hasUpnp) {
|
||||
expectedSrflxCandCount = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate srflx
|
||||
CPPUNIT_ASSERT_EQUAL(expectedSrflxCandCount, srflxCandCount);
|
||||
|
||||
// Validate relay
|
||||
CPPUNIT_ASSERT_EQUAL(user.turnEnabled_ ? 1 : 0, relayCandCount);
|
||||
}
|
||||
|
||||
void
|
||||
IceMediaCandExchangeTest::test_call(const char* accountType)
|
||||
{
|
||||
JAMI_INFO("=== Start a call and validate ICE for account type [%s] ===", accountType);
|
||||
|
||||
MediaAttribute media_0(MediaType::MEDIA_AUDIO);
|
||||
media_0.label_ = "audio_0";
|
||||
media_0.enabled_ = true;
|
||||
MediaAttribute media_1(MediaType::MEDIA_VIDEO);
|
||||
media_1.label_ = "video_0";
|
||||
media_1.enabled_ = true;
|
||||
|
||||
std::vector<MediaAttribute> offer;
|
||||
offer.emplace_back(media_0);
|
||||
offer.emplace_back(media_1);
|
||||
|
||||
std::vector<MediaAttribute> answer;
|
||||
answer.emplace_back(media_0);
|
||||
answer.emplace_back(media_1);
|
||||
|
||||
CPPUNIT_ASSERT_EQUAL(MEDIA_COUNT, offer.size());
|
||||
CPPUNIT_ASSERT_EQUAL(MEDIA_COUNT, answer.size());
|
||||
|
||||
// Set the destination according to account type.
|
||||
auto dest = strcmp(accountType, libjami::Account::ProtocolNames::SIP) == 0
|
||||
? bobData_.dest_.toString(true)
|
||||
: bobData_.userName_;
|
||||
|
||||
CPPUNIT_ASSERT(not dest.empty());
|
||||
|
||||
aliceData_.callId_ = libjami::placeCallWithMedia(aliceData_.accountId_,
|
||||
dest,
|
||||
MediaAttribute::mediaAttributesToMediaMaps(
|
||||
offer));
|
||||
CPPUNIT_ASSERT(not aliceData_.callId_.empty());
|
||||
|
||||
JAMI_INFO("ALICE [%s] started a call with BOB [%s:%s] and wait for answer",
|
||||
aliceData_.accountId_.c_str(),
|
||||
bobData_.accountId_.c_str(),
|
||||
dest.c_str());
|
||||
|
||||
// Give it some time to ring
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
|
||||
// Wait for call to be processed.
|
||||
CPPUNIT_ASSERT(
|
||||
waitForSignal(aliceData_, libjami::CallSignal::StateChange::name, StateEvent::RINGING));
|
||||
|
||||
// Wait for incoming call signal.
|
||||
CPPUNIT_ASSERT(waitForSignal(bobData_, libjami::CallSignal::IncomingCallWithMedia::name));
|
||||
|
||||
// Answer the call.
|
||||
libjami::acceptWithMedia(bobData_.accountId_,
|
||||
bobData_.callId_,
|
||||
MediaAttribute::mediaAttributesToMediaMaps(answer));
|
||||
|
||||
// Wait for media negotiation complete signal.
|
||||
CPPUNIT_ASSERT(waitForSignal(bobData_,
|
||||
libjami::CallSignal::MediaNegotiationStatus::name,
|
||||
libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS));
|
||||
|
||||
// Wait for the StateChange signal.
|
||||
CPPUNIT_ASSERT(
|
||||
waitForSignal(bobData_, libjami::CallSignal::StateChange::name, StateEvent::CURRENT));
|
||||
|
||||
JAMI_INFO("BOB answered the call [%s]", bobData_.callId_.c_str());
|
||||
|
||||
// Wait for media negotiation complete signal.
|
||||
CPPUNIT_ASSERT(waitForSignal(aliceData_,
|
||||
libjami::CallSignal::MediaNegotiationStatus::name,
|
||||
libjami::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS));
|
||||
|
||||
// Validate ICE candidates
|
||||
validate_ice_candidates(aliceData_, accountType, hasUpnp_, upnpAddrSameAsPublished_);
|
||||
validate_ice_candidates(bobData_, accountType, hasUpnp_, upnpAddrSameAsPublished_);
|
||||
|
||||
// Give some time to media to start.
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
|
||||
// Bob hang-up.
|
||||
JAMI_INFO("Hang up BOB's call and wait for ALICE to hang up");
|
||||
Manager::instance().hangupCall(bobData_.accountId_, bobData_.callId_);
|
||||
|
||||
CPPUNIT_ASSERT(
|
||||
waitForSignal(bobData_, libjami::CallSignal::StateChange::name, StateEvent::HUNGUP));
|
||||
|
||||
CPPUNIT_ASSERT(
|
||||
waitForSignal(aliceData_, libjami::CallSignal::StateChange::name, StateEvent::HUNGUP));
|
||||
|
||||
CPPUNIT_ASSERT(waitForSignal(bobData_, libjami::CallSignal::StateChange::name, StateEvent::OVER));
|
||||
|
||||
CPPUNIT_ASSERT(
|
||||
waitForSignal(aliceData_, libjami::CallSignal::StateChange::name, StateEvent::OVER));
|
||||
|
||||
JAMI_INFO("Call terminated on both sides");
|
||||
|
||||
// Reset signals
|
||||
aliceData_.signals_.clear();
|
||||
aliceData_.callId_.clear();
|
||||
bobData_.signals_.clear();
|
||||
bobData_.callId_.clear();
|
||||
}
|
||||
|
||||
void
|
||||
IceMediaCandExchangeTest::check_upnp()
|
||||
{
|
||||
auto accountType = libjami::Account::ProtocolNames::RING;
|
||||
setupAccounts(aliceData_, bobData_, accountType);
|
||||
auto const& account = Manager::instance().getAccount<JamiAccount>(aliceData_.accountId_);
|
||||
auto publishedAddr = account->getPublishedIpAddress(AF_INET);
|
||||
|
||||
const std::chrono::seconds TIME_OUT {15};
|
||||
auto upnpCtrl = std::make_shared<dhtnet::upnp::Controller>(Manager::instance().upnpContext());
|
||||
dhtnet::upnp::Mapping map {dhtnet::upnp::PortType::UDP};
|
||||
std::string upnpAddr {};
|
||||
|
||||
map.setNotifyCallback([this_ = this, &publishedAddr, &upnpAddr](
|
||||
dhtnet::upnp::Mapping::sharedPtr_t mapRes) {
|
||||
if (mapRes->getState() == dhtnet::upnp::MappingState::OPEN) {
|
||||
upnpAddr = mapRes->getExternalAddress();
|
||||
this_->upnpAddrSameAsPublished_ = publishedAddr.toString(false).compare(upnpAddr) == 0;
|
||||
this_->hasUpnp_ = true;
|
||||
this_->upnpCv_.notify_one();
|
||||
} else {
|
||||
this_->upnpAddrSameAsPublished_ = false;
|
||||
this_->hasUpnp_ = false;
|
||||
}
|
||||
});
|
||||
|
||||
upnpCtrl->reserveMapping(map);
|
||||
|
||||
JAMI_INFO("Waiting for upnp request response ...");
|
||||
|
||||
std::unique_lock<std::mutex> lock(upnpMtx_);
|
||||
auto res = upnpCv_.wait_for(lock, TIME_OUT, [this_ = this] { return this_->hasUpnp_; });
|
||||
|
||||
if (res) {
|
||||
JAMI_INFO("UPNP is available");
|
||||
if (upnpAddrSameAsPublished_) {
|
||||
JAMI_INFO("UPNP address [%s] same as published address: expect only one srflx cand per "
|
||||
"component",
|
||||
upnpAddr.c_str());
|
||||
} else {
|
||||
JAMI_INFO("UPNP [%s] and published [%s] addresses differ: expect two srflx cand per "
|
||||
"component",
|
||||
upnpAddr.c_str(),
|
||||
publishedAddr.toString().c_str());
|
||||
}
|
||||
} else {
|
||||
JAMI_WARN("UPNP is not available (timeout!)");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
IceMediaCandExchangeTest::jami_account_no_turn()
|
||||
{
|
||||
JAMI_INFO("=== Begin test %s ===", __FUNCTION__);
|
||||
|
||||
auto accountType = libjami::Account::ProtocolNames::RING;
|
||||
setupAccounts(aliceData_, bobData_, accountType);
|
||||
|
||||
bobData_.turnEnabled_ = false;
|
||||
aliceData_.turnEnabled_ = false;
|
||||
|
||||
{
|
||||
aliceData_.upnpEnabled_ = false;
|
||||
bobData_.upnpEnabled_ = false;
|
||||
configureAccount(aliceData_, accountType);
|
||||
configureAccount(bobData_, accountType);
|
||||
test_call(accountType);
|
||||
}
|
||||
|
||||
{
|
||||
aliceData_.upnpEnabled_ = true;
|
||||
bobData_.upnpEnabled_ = false;
|
||||
configureAccount(aliceData_, accountType);
|
||||
configureAccount(bobData_, accountType);
|
||||
test_call(accountType);
|
||||
}
|
||||
|
||||
{
|
||||
aliceData_.upnpEnabled_ = false;
|
||||
bobData_.upnpEnabled_ = true;
|
||||
configureAccount(aliceData_, accountType);
|
||||
configureAccount(bobData_, accountType);
|
||||
test_call(accountType);
|
||||
}
|
||||
|
||||
{
|
||||
aliceData_.upnpEnabled_ = true;
|
||||
bobData_.upnpEnabled_ = true;
|
||||
configureAccount(aliceData_, accountType);
|
||||
configureAccount(bobData_, accountType);
|
||||
test_call(accountType);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
IceMediaCandExchangeTest::jami_account_with_turn()
|
||||
{
|
||||
JAMI_INFO("=== Begin test %s ===", __FUNCTION__);
|
||||
|
||||
auto accountType = libjami::Account::ProtocolNames::RING;
|
||||
|
||||
setupAccounts(aliceData_, bobData_, accountType);
|
||||
bobData_.turnEnabled_ = true;
|
||||
aliceData_.turnEnabled_ = true;
|
||||
|
||||
{
|
||||
aliceData_.upnpEnabled_ = false;
|
||||
bobData_.upnpEnabled_ = false;
|
||||
configureAccount(aliceData_, accountType);
|
||||
configureAccount(bobData_, accountType);
|
||||
test_call(accountType);
|
||||
}
|
||||
|
||||
{
|
||||
aliceData_.upnpEnabled_ = true;
|
||||
bobData_.upnpEnabled_ = false;
|
||||
configureAccount(aliceData_, accountType);
|
||||
configureAccount(bobData_, accountType);
|
||||
test_call(accountType);
|
||||
}
|
||||
|
||||
{
|
||||
aliceData_.upnpEnabled_ = false;
|
||||
bobData_.upnpEnabled_ = true;
|
||||
configureAccount(aliceData_, accountType);
|
||||
configureAccount(bobData_, accountType);
|
||||
test_call(accountType);
|
||||
}
|
||||
|
||||
{
|
||||
aliceData_.upnpEnabled_ = true;
|
||||
bobData_.upnpEnabled_ = true;
|
||||
configureAccount(aliceData_, accountType);
|
||||
configureAccount(bobData_, accountType);
|
||||
test_call(accountType);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace jami
|
||||
|
||||
RING_TEST_RUNNER(jami::test::IceMediaCandExchangeTest::name())
|
Reference in New Issue
Block a user