Update tgcalls

This commit is contained in:
Ali 2022-03-23 22:54:22 +04:00
parent a7061c651a
commit 84f9f6f24e
4 changed files with 762 additions and 1 deletions

View File

@ -728,6 +728,18 @@ public final class OngoingCallContext {
filteredConnections.append(contentsOf: callConnectionDescriptionsWebrtc(connection))
}
/*#if DEBUG
filteredConnections.removeAll()
filteredConnections.append(OngoingCallConnectionDescriptionWebrtc(
connectionId: 1,
hasStun: true,
hasTurn: true, ip: "178.62.7.192",
port: 1400,
username: "user",
password: "user")
)
#endif*/
let context = OngoingCallThreadLocalContextWebrtc(version: version, queue: OngoingCallThreadLocalContextQueueImpl(queue: queue), proxy: voipProxyServer, networkType: ongoingNetworkTypeForTypeWebrtc(initialNetworkType), dataSaving: ongoingDataSavingForTypeWebrtc(dataSaving), derivedState: derivedState.data, key: key, isOutgoing: isOutgoing, connections: filteredConnections, maxLayer: maxLayer, allowP2P: allowP2P, allowTCP: enableTCP, enableStunMarking: enableStunMarking, logPath: tempLogPath, statsLogPath: tempStatsLogPath, sendSignalingData: { [weak callSessionManager] data in
callSessionManager?.sendSignalingData(internalId: internalId, data: data)
}, videoCapturer: video?.impl, preferredVideoCodec: preferredVideoCodec, audioInputDeviceId: "")

View File

@ -1,3 +1,4 @@
load("@build_bazel_rules_apple//apple:ios.bzl", "ios_unit_test")
config_setting(
name = "debug_build",
@ -92,9 +93,52 @@ objc_library(
"VideoToolbox",
"CoreTelephony",
"CoreMedia",
"GLKit",
"AVFoundation",
],
visibility = [
"//visibility:public",
],
)
objc_library(
name = "TgCallsTestsLib",
copts = [
"-I{}/tgcalls/tgcalls".format(package_name()),
"-Ithird-party/webrtc/webrtc",
"-Ithird-party/webrtc/dependencies",
"-Ithird-party/webrtc/dependencies/third_party/abseil-cpp",
"-Ithird-party/webrtc/webrtc/sdk/objc",
"-Ithird-party/webrtc/webrtc/sdk/objc/base",
"-Ithird-party/webrtc/webrtc/sdk/objc/components/renderer/metal",
"-Ithird-party/webrtc/webrtc/sdk/objc/components/renderer/opengl",
"-Ithird-party/webrtc/webrtc/sdk/objc/components/video_codec",
"-Ithird-party/libyuv/third_party/libyuv/include",
"-Ithird-party/libyuv",
"-Ithird-party/webrtc/webrtc/sdk/objc/api/video_codec",
"-DWEBRTC_IOS",
"-DWEBRTC_MAC",
"-DWEBRTC_POSIX",
"-DRTC_ENABLE_VP9",
"-DTGVOIP_NAMESPACE=tgvoip_webrtc",
"-std=c++14",
],
srcs = glob([
"tests/*.m",
"tests/*.mm",
]),
deps = [
":TgVoipWebrtc"
],
)
ios_unit_test(
name = "TgCallsTests",
minimum_os_version = "9.0",
deps = [
":TgCallsTestsLib",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,705 @@
#import <XCTest/XCTest.h>
#include "api/task_queue/default_task_queue_factory.h"
#include "media/engine/webrtc_media_engine.h"
#include "api/audio_codecs/audio_encoder_factory_template.h"
#include "api/audio_codecs/audio_decoder_factory_template.h"
#include "api/audio_codecs/opus/audio_decoder_opus.h"
#include "api/audio_codecs/opus/audio_decoder_multi_channel_opus.h"
#include "api/audio_codecs/opus/audio_encoder_opus.h"
#include "api/audio_codecs/L16/audio_decoder_L16.h"
#include "api/audio_codecs/L16/audio_encoder_L16.h"
#include "StaticThreads.h"
#include "FakeAudioDeviceModule.h"
#include "platform/PlatformInterface.h"
#include "v2/ContentNegotiation.h"
namespace {
class Context {
public:
Context(bool isOutgoing) :
_isOutgoing(isOutgoing),
_threads(tgcalls::StaticThreads::getThreads()),
_taskQueueFactory(webrtc::CreateDefaultTaskQueueFactory()),
_uniqueRandomIdGenerator(std::make_unique<rtc::UniqueRandomIdGenerator>()) {
_threads->getWorkerThread()->Invoke<void>(RTC_FROM_HERE, [&]() {
cricket::MediaEngineDependencies mediaDeps;
mediaDeps.task_queue_factory = _taskQueueFactory.get();
mediaDeps.audio_encoder_factory = webrtc::CreateAudioEncoderFactory<webrtc::AudioEncoderOpus, webrtc::AudioEncoderL16>();
mediaDeps.audio_decoder_factory = webrtc::CreateAudioDecoderFactory<webrtc::AudioDecoderOpus, webrtc::AudioDecoderL16>();
mediaDeps.video_encoder_factory = tgcalls::PlatformInterface::SharedInstance()->makeVideoEncoderFactory(true);
mediaDeps.video_decoder_factory = tgcalls::PlatformInterface::SharedInstance()->makeVideoDecoderFactory();
tgcalls::FakeAudioDeviceModule::Options options;
options.num_channels = 1;
_audioDeviceModule = tgcalls::FakeAudioDeviceModule::Creator(nullptr, nullptr, options)(_taskQueueFactory.get());
mediaDeps.adm = _audioDeviceModule;
_mediaEngine = cricket::CreateMediaEngine(std::move(mediaDeps));
_channelManager = cricket::ChannelManager::Create(
std::move(_mediaEngine),
true,
_threads->getWorkerThread(),
_threads->getNetworkThread()
);
});
_contentNegotiationContext = std::make_unique<tgcalls::ContentNegotiationContext>(isOutgoing, _uniqueRandomIdGenerator.get());
_contentNegotiationContext->copyCodecsFromChannelManager(_channelManager.get(), isOutgoing);
}
~Context() {
_contentNegotiationContext.reset();
_threads->getWorkerThread()->Invoke<void>(RTC_FROM_HERE, [&]() {
_channelManager.reset();
_mediaEngine.reset();
_audioDeviceModule = nullptr;
});
}
public:
tgcalls::ContentNegotiationContext *contentNegotiationContext() const {
return _contentNegotiationContext.get();
}
void assertSsrcs(std::vector<uint32_t> const &outgoingSsrcs, std::vector<uint32_t> const &incomingSsrcs) {
std::set<uint32_t> incomingSsrcsSet;
for (auto ssrc : incomingSsrcs) {
incomingSsrcsSet.insert(ssrc);
}
std::set<uint32_t> outgoingSsrcsSet;
for (auto ssrc : outgoingSsrcs) {
outgoingSsrcsSet.insert(ssrc);
}
std::set<uint32_t> actualIncomingSsrcs;
std::set<uint32_t> actualOutgoingSsrcs;
auto coordinatedState = _contentNegotiationContext->coordinatedState();
XCTAssert(coordinatedState != nullptr);
for (const auto &content : coordinatedState->incomingContents) {
actualIncomingSsrcs.insert(content.ssrc);
}
for (const auto &content : coordinatedState->outgoingContents) {
actualOutgoingSsrcs.insert(content.ssrc);
}
XCTAssert(incomingSsrcsSet == actualIncomingSsrcs);
XCTAssert(outgoingSsrcsSet == actualOutgoingSsrcs);
}
bool isContentsEqualToRemote(Context &remoteContext) {
auto localCoordinatedState = _contentNegotiationContext->coordinatedState();
auto remoteCoordinatedState = remoteContext.contentNegotiationContext()->coordinatedState();
auto mediaContentComparator = [](tgcalls::signaling::MediaContent const &lhs, tgcalls::signaling::MediaContent const &rhs) -> bool {
return lhs.ssrc < rhs.ssrc;
};
auto localIncomingContents = localCoordinatedState->incomingContents;
std::sort(localIncomingContents.begin(), localIncomingContents.end(), mediaContentComparator);
auto localOutgoingContents = localCoordinatedState->outgoingContents;
std::sort(localOutgoingContents.begin(), localOutgoingContents.end(), mediaContentComparator);
auto remoteIncomingContents = remoteCoordinatedState->incomingContents;
std::sort(remoteIncomingContents.begin(), remoteIncomingContents.end(), mediaContentComparator);
auto remoteOutgoingContents = remoteCoordinatedState->outgoingContents;
std::sort(remoteOutgoingContents.begin(), remoteOutgoingContents.end(), mediaContentComparator);
if (localIncomingContents != remoteOutgoingContents) {
return false;
}
if (localOutgoingContents != remoteIncomingContents) {
return false;
}
return true;
}
private:
__unused bool _isOutgoing = false;
std::shared_ptr<tgcalls::Threads> _threads;
std::unique_ptr<webrtc::TaskQueueFactory> _taskQueueFactory;
std::unique_ptr<rtc::UniqueRandomIdGenerator> _uniqueRandomIdGenerator;
rtc::scoped_refptr<webrtc::AudioDeviceModule> _audioDeviceModule;
std::unique_ptr<cricket::MediaEngineInterface> _mediaEngine;
std::unique_ptr<cricket::ChannelManager> _channelManager;
std::unique_ptr<tgcalls::ContentNegotiationContext> _contentNegotiationContext;
};
std::unique_ptr<tgcalls::ContentNegotiationContext::NegotiationContents> copyNegotiationContents(tgcalls::ContentNegotiationContext::NegotiationContents *value) {
if (!value) {
return nullptr;
}
auto result = std::make_unique<tgcalls::ContentNegotiationContext::NegotiationContents>();
result->exchangeId = value->exchangeId;
result->contents = value->contents;
return result;
}
void runUntilStableSequential(Context &localContext, Context &remoteContext) {
for (int i = 0; i < 6; i++) {
auto localOffer = localContext.contentNegotiationContext()->getPendingOffer();
if (localOffer) {
auto remoteAnswer = remoteContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(localOffer.get()));
XCTAssert(remoteAnswer != nullptr);
auto localResponse = localContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(remoteAnswer.get()));
XCTAssert(localResponse == nullptr);
} else {
auto remoteOffer = remoteContext.contentNegotiationContext()->getPendingOffer();
if (remoteOffer) {
auto localAnswer = localContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(remoteOffer.get()));
XCTAssert(localAnswer != nullptr);
auto remoteResponse = remoteContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(localAnswer.get()));
XCTAssert(remoteResponse == nullptr);
} else {
return;
}
}
}
XCTFail(@"Did not complete");
}
void runUntilStableConcurrent(Context &localContext, Context &remoteContext) {
std::vector<std::unique_ptr<tgcalls::ContentNegotiationContext::NegotiationContents>> localNegotiationContent;
std::vector<std::unique_ptr<tgcalls::ContentNegotiationContext::NegotiationContents>> remoteNegotiationContent;
for (int i = 0; i < 6; i++) {
std::unique_ptr<tgcalls::ContentNegotiationContext::NegotiationContents> nextLocalNegotiationContent;
std::unique_ptr<tgcalls::ContentNegotiationContext::NegotiationContents> nextRemoteNegotiationContent;
while (!localNegotiationContent.empty()) {
auto content = std::move(localNegotiationContent[0]);
localNegotiationContent.erase(localNegotiationContent.begin());
nextRemoteNegotiationContent = remoteContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(content.get()));
}
while (!remoteNegotiationContent.empty()) {
auto content = std::move(remoteNegotiationContent[0]);
remoteNegotiationContent.erase(remoteNegotiationContent.begin());
nextLocalNegotiationContent = localContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(content.get()));
}
if (nextLocalNegotiationContent) {
localNegotiationContent.push_back(std::move(nextLocalNegotiationContent));
}
if (nextRemoteNegotiationContent) {
remoteNegotiationContent.push_back(std::move(nextRemoteNegotiationContent));
}
auto localOffer = localContext.contentNegotiationContext()->getPendingOffer();
if (localOffer) {
localNegotiationContent.push_back(std::move(localOffer));
}
auto remoteOffer = remoteContext.contentNegotiationContext()->getPendingOffer();
if (remoteOffer) {
remoteNegotiationContent.push_back(std::move(remoteOffer));
}
if (localNegotiationContent.empty() && remoteNegotiationContent.empty()) {
return;
}
}
XCTFail(@"Did not complete");
}
}
@interface NegotiationTests : XCTestCase
@end
@implementation NegotiationTests
- (void)setUp {
[super setUp];
self.continueAfterFailure = false;
}
- (void)tearDown {
[super tearDown];
}
- (void)testNegotiateEmpty {
Context localContext(true);
Context remoteContext(false);
XCTAssert(localContext.contentNegotiationContext()->getPendingOffer() != nullptr);
XCTAssert(remoteContext.contentNegotiationContext()->getPendingOffer() != nullptr);
}
- (void)testNegotiateAudioOnewayOutgoing {
Context localContext(true);
Context remoteContext(false);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
localContext.contentNegotiationContext()->addOutgoingChannel(tgcalls::signaling::MediaContent::Type::Audio);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
auto offer = localContext.contentNegotiationContext()->getPendingOffer();
XCTAssert(offer != nullptr);
XCTAssert(offer->contents.size() == 1);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->getPendingOffer() == nullptr);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
auto response = remoteContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(offer.get()));
XCTAssert(response != nullptr);
XCTAssert(response->contents.size() == offer->contents.size());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.size() == 1);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents[0].ssrc == offer->contents[0].ssrc);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
auto backOffer = remoteContext.contentNegotiationContext()->getPendingOffer();
XCTAssert(backOffer != nullptr);
XCTAssert(backOffer->contents.size() == 0);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.size() == 1);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents[0].ssrc == offer->contents[0].ssrc);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
auto responseToAnswer = localContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(response.get()));
XCTAssert(responseToAnswer == nullptr);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.size() == 1);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents[0].ssrc == offer->contents[0].ssrc);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.size() == 1);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents[0].ssrc == offer->contents[0].ssrc);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
auto nextOffer = localContext.contentNegotiationContext()->getPendingOffer();
XCTAssert(nextOffer == nullptr);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.size() == 1);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents[0].ssrc == offer->contents[0].ssrc);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.size() == 1);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents[0].ssrc == offer->contents[0].ssrc);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
}
- (void)testNegotiateAudioOnewayIncoming {
Context localContext(true);
Context remoteContext(false);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
remoteContext.contentNegotiationContext()->addOutgoingChannel(tgcalls::signaling::MediaContent::Type::Audio);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
auto offer = localContext.contentNegotiationContext()->getPendingOffer();
XCTAssert(offer != nullptr);
XCTAssert(offer->contents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->getPendingOffer() == nullptr);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
auto response = remoteContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(offer.get()));
XCTAssert(response != nullptr);
XCTAssert(response->contents.size() == offer->contents.size());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
auto backOffer = remoteContext.contentNegotiationContext()->getPendingOffer();
XCTAssert(backOffer != nullptr);
XCTAssert(backOffer->contents.size() == 1);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
auto responseToAnswer = localContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(response.get()));
XCTAssert(responseToAnswer == nullptr);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
auto responseToBackOffer = localContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(backOffer.get()));
XCTAssert(responseToBackOffer != nullptr);
XCTAssert(responseToBackOffer->contents.size() == 1);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.size() == 1);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents[0].ssrc == backOffer->contents[0].ssrc);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
auto responseToBackOfferAnswer = remoteContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(responseToBackOffer.get()));
XCTAssert(responseToBackOfferAnswer == nullptr);
XCTAssert(localContext.contentNegotiationContext()->getPendingOffer() == nullptr);
XCTAssert(remoteContext.contentNegotiationContext()->getPendingOffer() == nullptr);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.size() == 1);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents[0].ssrc == backOffer->contents[0].ssrc);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.size() == 1);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents[0].ssrc == backOffer->contents[0].ssrc);
}
- (void)testNegotiateAudioTwoway {
Context localContext(true);
Context remoteContext(false);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
localContext.contentNegotiationContext()->addOutgoingChannel(tgcalls::signaling::MediaContent::Type::Audio);
remoteContext.contentNegotiationContext()->addOutgoingChannel(tgcalls::signaling::MediaContent::Type::Audio);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
auto offer = localContext.contentNegotiationContext()->getPendingOffer();
XCTAssert(offer != nullptr);
XCTAssert(offer->contents.size() == 1);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->getPendingOffer() == nullptr);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
auto response = remoteContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(offer.get()));
XCTAssert(response != nullptr);
XCTAssert(response->contents.size() == offer->contents.size());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.size() == 1);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents[0].ssrc == offer->contents[0].ssrc);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
auto backOffer = remoteContext.contentNegotiationContext()->getPendingOffer();
XCTAssert(backOffer != nullptr);
XCTAssert(backOffer->contents.size() == 1);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.size() == 1);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents[0].ssrc == offer->contents[0].ssrc);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
auto responseToAnswer = localContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(response.get()));
XCTAssert(responseToAnswer == nullptr);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.size() == 1);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents[0].ssrc == offer->contents[0].ssrc);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.size() == 1);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents[0].ssrc == offer->contents[0].ssrc);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
XCTAssert(localContext.contentNegotiationContext()->getPendingOffer() == nullptr);
auto responseToBackOffer = localContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(backOffer.get()));
XCTAssert(responseToBackOffer != nullptr);
XCTAssert(responseToBackOffer->contents.size() == 1);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.size() == 1);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents[0].ssrc == backOffer->contents[0].ssrc);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.size() == 1);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents[0].ssrc == offer->contents[0].ssrc);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.size() == 1);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents[0].ssrc == offer->contents[0].ssrc);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.empty());
auto responseToBackOfferAnswer = remoteContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(responseToBackOffer.get()));
XCTAssert(responseToBackOfferAnswer == nullptr);
XCTAssert(localContext.contentNegotiationContext()->getPendingOffer() == nullptr);
XCTAssert(remoteContext.contentNegotiationContext()->getPendingOffer() == nullptr);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents.size() == 1);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->incomingContents[0].ssrc == backOffer->contents[0].ssrc);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents.size() == 1);
XCTAssert(localContext.contentNegotiationContext()->coordinatedState()->outgoingContents[0].ssrc == offer->contents[0].ssrc);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents.size() == 1);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->incomingContents[0].ssrc == offer->contents[0].ssrc);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents.size() == 1);
XCTAssert(remoteContext.contentNegotiationContext()->coordinatedState()->outgoingContents[0].ssrc == backOffer->contents[0].ssrc);
}
- (void)testConcurrentOffers {
Context localContext(true);
Context remoteContext(false);
auto localOffer = localContext.contentNegotiationContext()->getPendingOffer();
XCTAssert(localOffer != nullptr);
auto remoteOffer = remoteContext.contentNegotiationContext()->getPendingOffer();
XCTAssert(remoteOffer != nullptr);
auto localAnswer = remoteContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(localOffer.get()));
XCTAssert(localAnswer != nullptr);
XCTAssert(localAnswer->exchangeId == localOffer->exchangeId);
auto remoteAnswer = localContext.contentNegotiationContext()->setRemoteNegotiationContent(copyNegotiationContents(remoteOffer.get()));
XCTAssert(remoteAnswer == nullptr);
}
- (void)service_runUntilStable1Using:(std::function<void(Context &localContext, Context &remoteContext)>)runUntilStable {
Context localContext(true);
Context remoteContext(false);
runUntilStable(localContext, remoteContext);
localContext.assertSsrcs({}, {});
remoteContext.assertSsrcs({}, {});
localContext.isContentsEqualToRemote(remoteContext);
auto localAudioId = localContext.contentNegotiationContext()->addOutgoingChannel(tgcalls::signaling::MediaContent::Type::Audio);
runUntilStable(localContext, remoteContext);
auto localAudioSsrc = localContext.contentNegotiationContext()->outgoingChannelSsrc(localAudioId);
XCTAssert(localAudioSsrc);
localContext.assertSsrcs({ localAudioSsrc.value() }, {});
remoteContext.assertSsrcs({}, { localAudioSsrc.value() });
localContext.isContentsEqualToRemote(remoteContext);
auto remoteAudioId = remoteContext.contentNegotiationContext()->addOutgoingChannel(tgcalls::signaling::MediaContent::Type::Audio);
runUntilStable(localContext, remoteContext);
auto remoteAudioSsrc = remoteContext.contentNegotiationContext()->outgoingChannelSsrc(remoteAudioId);
XCTAssert(remoteAudioSsrc);
localContext.assertSsrcs({ localAudioSsrc.value() }, { remoteAudioSsrc.value() });
remoteContext.assertSsrcs({ remoteAudioSsrc.value() }, { localAudioSsrc.value() });
localContext.isContentsEqualToRemote(remoteContext);
auto remoteVideoId = remoteContext.contentNegotiationContext()->addOutgoingChannel(tgcalls::signaling::MediaContent::Type::Video);
runUntilStable(localContext, remoteContext);
auto remoteVideoSsrc = remoteContext.contentNegotiationContext()->outgoingChannelSsrc(remoteVideoId);
XCTAssert(remoteVideoSsrc);
localContext.assertSsrcs({ localAudioSsrc.value() }, { remoteAudioSsrc.value(), remoteVideoSsrc.value() });
remoteContext.assertSsrcs({ remoteAudioSsrc.value(), remoteVideoSsrc.value() }, { localAudioSsrc.value() });
localContext.isContentsEqualToRemote(remoteContext);
auto localVideoId = localContext.contentNegotiationContext()->addOutgoingChannel(tgcalls::signaling::MediaContent::Type::Video);
runUntilStable(localContext, remoteContext);
auto localVideoSsrc = localContext.contentNegotiationContext()->outgoingChannelSsrc(localVideoId);
XCTAssert(localVideoSsrc);
localContext.assertSsrcs({ localAudioSsrc.value(), localVideoSsrc.value() }, { remoteAudioSsrc.value(), remoteVideoSsrc.value() });
remoteContext.assertSsrcs({ remoteAudioSsrc.value(), remoteVideoSsrc.value() }, { localAudioSsrc.value(), localVideoSsrc.value() });
localContext.isContentsEqualToRemote(remoteContext);
auto remoteScreencastId = remoteContext.contentNegotiationContext()->addOutgoingChannel(tgcalls::signaling::MediaContent::Type::Video);
runUntilStable(localContext, remoteContext);
auto remoteScreencastSsrc = remoteContext.contentNegotiationContext()->outgoingChannelSsrc(remoteScreencastId);
XCTAssert(remoteScreencastSsrc);
localContext.assertSsrcs({ localAudioSsrc.value(), localVideoSsrc.value() }, { remoteAudioSsrc.value(), remoteVideoSsrc.value(), remoteScreencastSsrc.value() });
remoteContext.assertSsrcs({ remoteAudioSsrc.value(), remoteVideoSsrc.value(), remoteScreencastSsrc.value() }, { localAudioSsrc.value(), localVideoSsrc.value() });
localContext.isContentsEqualToRemote(remoteContext);
localContext.contentNegotiationContext()->removeOutgoingChannel(localVideoId);
// local removal is reflected right away
localContext.assertSsrcs({ localAudioSsrc.value() }, { remoteAudioSsrc.value(), remoteVideoSsrc.value(), remoteScreencastSsrc.value() });
remoteContext.assertSsrcs({ remoteAudioSsrc.value(), remoteVideoSsrc.value(), remoteScreencastSsrc.value() }, { localAudioSsrc.value(), localVideoSsrc.value() });
localVideoSsrc = localContext.contentNegotiationContext()->outgoingChannelSsrc(localVideoId);
XCTAssert(!localVideoSsrc);
runUntilStable(localContext, remoteContext);
localVideoSsrc = localContext.contentNegotiationContext()->outgoingChannelSsrc(localVideoId);
XCTAssert(!localVideoSsrc);
localContext.assertSsrcs({ localAudioSsrc.value() }, { remoteAudioSsrc.value(), remoteVideoSsrc.value(), remoteScreencastSsrc.value() });
remoteContext.assertSsrcs({ remoteAudioSsrc.value(), remoteVideoSsrc.value(), remoteScreencastSsrc.value() }, { localAudioSsrc.value() });
localContext.isContentsEqualToRemote(remoteContext);
localVideoId = localContext.contentNegotiationContext()->addOutgoingChannel(tgcalls::signaling::MediaContent::Type::Video);
runUntilStable(localContext, remoteContext);
localVideoSsrc = localContext.contentNegotiationContext()->outgoingChannelSsrc(localVideoId);
XCTAssert(localVideoSsrc);
localContext.assertSsrcs({ localAudioSsrc.value(), localVideoSsrc.value() }, { remoteAudioSsrc.value(), remoteVideoSsrc.value(), remoteScreencastSsrc.value() });
remoteContext.assertSsrcs({ remoteAudioSsrc.value(), remoteVideoSsrc.value(), remoteScreencastSsrc.value() }, { localAudioSsrc.value(), localVideoSsrc.value() });
localContext.isContentsEqualToRemote(remoteContext);
}
- (void)service_runUntilStable2Using:(std::function<void(Context &localContext, Context &remoteContext)>)runUntilStable {
Context localContext(true);
Context remoteContext(false);
runUntilStable(localContext, remoteContext);
localContext.assertSsrcs({}, {});
remoteContext.assertSsrcs({}, {});
localContext.isContentsEqualToRemote(remoteContext);
auto localAudioId = localContext.contentNegotiationContext()->addOutgoingChannel(tgcalls::signaling::MediaContent::Type::Audio);
auto remoteAudioId = remoteContext.contentNegotiationContext()->addOutgoingChannel(tgcalls::signaling::MediaContent::Type::Audio);
runUntilStable(localContext, remoteContext);
auto localAudioSsrc = localContext.contentNegotiationContext()->outgoingChannelSsrc(localAudioId);
XCTAssert(localAudioSsrc);
auto remoteAudioSsrc = remoteContext.contentNegotiationContext()->outgoingChannelSsrc(remoteAudioId);
XCTAssert(remoteAudioSsrc);
localContext.assertSsrcs({ localAudioSsrc.value() }, { remoteAudioSsrc.value() });
remoteContext.assertSsrcs({ remoteAudioSsrc.value() }, { localAudioSsrc.value() });
localContext.isContentsEqualToRemote(remoteContext);
auto remoteVideoId = remoteContext.contentNegotiationContext()->addOutgoingChannel(tgcalls::signaling::MediaContent::Type::Video);
auto localVideoId = localContext.contentNegotiationContext()->addOutgoingChannel(tgcalls::signaling::MediaContent::Type::Video);
runUntilStable(localContext, remoteContext);
auto remoteVideoSsrc = remoteContext.contentNegotiationContext()->outgoingChannelSsrc(remoteVideoId);
XCTAssert(remoteVideoSsrc);
auto localVideoSsrc = localContext.contentNegotiationContext()->outgoingChannelSsrc(localVideoId);
XCTAssert(localVideoSsrc);
localContext.assertSsrcs({ localAudioSsrc.value(), localVideoSsrc.value() }, { remoteAudioSsrc.value(), remoteVideoSsrc.value() });
remoteContext.assertSsrcs({ remoteAudioSsrc.value(), remoteVideoSsrc.value() }, { localAudioSsrc.value(), localVideoSsrc.value() });
localContext.isContentsEqualToRemote(remoteContext);
auto remoteScreencastId = remoteContext.contentNegotiationContext()->addOutgoingChannel(tgcalls::signaling::MediaContent::Type::Video);
runUntilStable(localContext, remoteContext);
auto remoteScreencastSsrc = remoteContext.contentNegotiationContext()->outgoingChannelSsrc(remoteScreencastId);
XCTAssert(remoteScreencastSsrc);
localContext.assertSsrcs({ localAudioSsrc.value(), localVideoSsrc.value() }, { remoteAudioSsrc.value(), remoteVideoSsrc.value(), remoteScreencastSsrc.value() });
remoteContext.assertSsrcs({ remoteAudioSsrc.value(), remoteVideoSsrc.value(), remoteScreencastSsrc.value() }, { localAudioSsrc.value(), localVideoSsrc.value() });
localContext.isContentsEqualToRemote(remoteContext);
localContext.contentNegotiationContext()->removeOutgoingChannel(localVideoId);
// local removal is reflected right away
localContext.assertSsrcs({ localAudioSsrc.value() }, { remoteAudioSsrc.value(), remoteVideoSsrc.value(), remoteScreencastSsrc.value() });
remoteContext.assertSsrcs({ remoteAudioSsrc.value(), remoteVideoSsrc.value(), remoteScreencastSsrc.value() }, { localAudioSsrc.value(), localVideoSsrc.value() });
localVideoSsrc = localContext.contentNegotiationContext()->outgoingChannelSsrc(localVideoId);
XCTAssert(!localVideoSsrc);
runUntilStable(localContext, remoteContext);
localVideoSsrc = localContext.contentNegotiationContext()->outgoingChannelSsrc(localVideoId);
XCTAssert(!localVideoSsrc);
localContext.assertSsrcs({ localAudioSsrc.value() }, { remoteAudioSsrc.value(), remoteVideoSsrc.value(), remoteScreencastSsrc.value() });
remoteContext.assertSsrcs({ remoteAudioSsrc.value(), remoteVideoSsrc.value(), remoteScreencastSsrc.value() }, { localAudioSsrc.value() });
localContext.isContentsEqualToRemote(remoteContext);
localVideoId = localContext.contentNegotiationContext()->addOutgoingChannel(tgcalls::signaling::MediaContent::Type::Video);
runUntilStable(localContext, remoteContext);
localVideoSsrc = localContext.contentNegotiationContext()->outgoingChannelSsrc(localVideoId);
XCTAssert(localVideoSsrc);
localContext.assertSsrcs({ localAudioSsrc.value(), localVideoSsrc.value() }, { remoteAudioSsrc.value(), remoteVideoSsrc.value(), remoteScreencastSsrc.value() });
remoteContext.assertSsrcs({ remoteAudioSsrc.value(), remoteVideoSsrc.value(), remoteScreencastSsrc.value() }, { localAudioSsrc.value(), localVideoSsrc.value() });
localContext.isContentsEqualToRemote(remoteContext);
}
- (void)testConvergenceSequential1 {
[self service_runUntilStable1Using:&runUntilStableSequential];
}
- (void)testConvergenceSequential2 {
[self service_runUntilStable2Using:&runUntilStableSequential];
}
- (void)testConvergenceConcurrent1 {
[self service_runUntilStable1Using:&runUntilStableConcurrent];
}
- (void)testConvergenceConcurrent2 {
[self service_runUntilStable2Using:&runUntilStableConcurrent];
}
@end

@ -1 +1 @@
Subproject commit 33af0208111e5994e33c7fbb8872cd08bf38edd7
Subproject commit 95369c01dfc8639b89dd91c4bc6e9da2ae2ed223