Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 14:04:28 +01:00
parent 81b91f4139
commit f8c34fa5ee
22732 changed files with 4815320 additions and 2 deletions

View file

@ -0,0 +1,802 @@
#include "v2/ContentNegotiation.h"
#include "rtc_base/rtc_certificate_generator.h"
#include "media/base/media_engine.h"
#include <sstream>
namespace tgcalls {
namespace {
signaling::MediaContent convertContentInfoToSingalingContent(cricket::ContentInfo const &content) {
signaling::MediaContent mappedContent;
switch (content.media_description()->type()) {
case cricket::MediaType::MEDIA_TYPE_AUDIO: {
mappedContent.type = signaling::MediaContent::Type::Audio;
for (const auto &codec : content.media_description()->as_audio()->codecs()) {
signaling::PayloadType mappedPayloadType;
mappedPayloadType.id = codec.id;
mappedPayloadType.name = codec.name;
mappedPayloadType.clockrate = codec.clockrate;
mappedPayloadType.channels = (uint32_t)codec.channels;
for (const auto &feedbackType : codec.feedback_params.params()) {
signaling::FeedbackType mappedFeedbackType;
mappedFeedbackType.type = feedbackType.id();
mappedFeedbackType.subtype = feedbackType.param();
mappedPayloadType.feedbackTypes.push_back(std::move(mappedFeedbackType));
}
for (const auto &parameter : codec.params) {
mappedPayloadType.parameters.push_back(std::make_pair(parameter.first, parameter.second));
}
std::sort(mappedPayloadType.parameters.begin(), mappedPayloadType.parameters.end(), [](std::pair<std::string, std::string> const &lhs, std::pair<std::string, std::string> const &rhs) -> bool {
return lhs.first < rhs.first;
});
mappedContent.payloadTypes.push_back(std::move(mappedPayloadType));
}
break;
}
case cricket::MediaType::MEDIA_TYPE_VIDEO: {
mappedContent.type = signaling::MediaContent::Type::Video;
for (const auto &codec : content.media_description()->as_video()->codecs()) {
signaling::PayloadType mappedPayloadType;
mappedPayloadType.id = codec.id;
mappedPayloadType.name = codec.name;
mappedPayloadType.clockrate = codec.clockrate;
mappedPayloadType.channels = 0;
for (const auto &feedbackType : codec.feedback_params.params()) {
signaling::FeedbackType mappedFeedbackType;
mappedFeedbackType.type = feedbackType.id();
mappedFeedbackType.subtype = feedbackType.param();
mappedPayloadType.feedbackTypes.push_back(std::move(mappedFeedbackType));
}
for (const auto &parameter : codec.params) {
mappedPayloadType.parameters.push_back(std::make_pair(parameter.first, parameter.second));
}
std::sort(mappedPayloadType.parameters.begin(), mappedPayloadType.parameters.end(), [](std::pair<std::string, std::string> const &lhs, std::pair<std::string, std::string> const &rhs) -> bool {
return lhs.first < rhs.first;
});
mappedContent.payloadTypes.push_back(std::move(mappedPayloadType));
}
break;
}
default: {
RTC_FATAL() << "Unknown media type";
break;
}
}
if (!content.media_description()->streams().empty()) {
mappedContent.ssrc = content.media_description()->streams()[0].first_ssrc();
for (const auto &ssrcGroup : content.media_description()->streams()[0].ssrc_groups) {
signaling::SsrcGroup mappedSsrcGroup;
mappedSsrcGroup.semantics = ssrcGroup.semantics;
mappedSsrcGroup.ssrcs = ssrcGroup.ssrcs;
mappedContent.ssrcGroups.push_back(std::move(mappedSsrcGroup));
}
}
for (const auto &extension : content.media_description()->rtp_header_extensions()) {
mappedContent.rtpExtensions.push_back(extension);
}
return mappedContent;
}
cricket::ContentInfo convertSingalingContentToContentInfo(std::string const &contentId, signaling::MediaContent const &content, webrtc::RtpTransceiverDirection direction) {
std::unique_ptr<cricket::MediaContentDescription> contentDescription;
switch (content.type) {
case signaling::MediaContent::Type::Audio: {
auto audioDescription = std::make_unique<cricket::AudioContentDescription>();
for (const auto &payloadType : content.payloadTypes) {
cricket::AudioCodec mappedCodec = cricket::CreateAudioCodec((int)payloadType.id, payloadType.name, (int)payloadType.clockrate, payloadType.channels);
for (const auto &parameter : payloadType.parameters) {
mappedCodec.params.insert(parameter);
}
for (const auto &feedbackParam : payloadType.feedbackTypes) {
mappedCodec.AddFeedbackParam(cricket::FeedbackParam(feedbackParam.type, feedbackParam.subtype));
}
audioDescription->AddCodec(mappedCodec);
}
contentDescription = std::move(audioDescription);
break;
}
case signaling::MediaContent::Type::Video: {
auto videoDescription = std::make_unique<cricket::VideoContentDescription>();
for (const auto &payloadType : content.payloadTypes) {
webrtc::SdpVideoFormat videoFormat(payloadType.name);
for (const auto &parameter : payloadType.parameters) {
videoFormat.parameters.insert(parameter);
}
cricket::VideoCodec mappedCodec = cricket::CreateVideoCodec(videoFormat);
mappedCodec.id = (int)payloadType.id;
for (const auto &feedbackParam : payloadType.feedbackTypes) {
mappedCodec.AddFeedbackParam(cricket::FeedbackParam(feedbackParam.type, feedbackParam.subtype));
}
videoDescription->AddCodec(mappedCodec);
}
contentDescription = std::move(videoDescription);
break;
}
default: {
RTC_FATAL() << "Unknown media type";
break;
}
}
cricket::StreamParams streamParams;
streamParams.id = contentId;
streamParams.set_stream_ids({ contentId });
streamParams.add_ssrc(content.ssrc);
for (const auto &ssrcGroup : content.ssrcGroups) {
streamParams.ssrc_groups.push_back(cricket::SsrcGroup(ssrcGroup.semantics, ssrcGroup.ssrcs));
for (const auto &ssrc : ssrcGroup.ssrcs) {
if (!streamParams.has_ssrc(ssrc)) {
streamParams.add_ssrc(ssrc);
}
}
}
contentDescription->AddStream(streamParams);
for (const auto &extension : content.rtpExtensions) {
contentDescription->AddRtpHeaderExtension(extension);
}
contentDescription->set_direction(direction);
contentDescription->set_rtcp_mux(true);
cricket::ContentInfo mappedContentInfo(cricket::MediaProtocolType::kRtp);
mappedContentInfo.name = contentId;
mappedContentInfo.rejected = false;
mappedContentInfo.bundle_only = false;
mappedContentInfo.set_media_description(std::move(contentDescription));
return mappedContentInfo;
}
cricket::ContentInfo createInactiveContentInfo(std::string const &contentId) {
std::unique_ptr<cricket::MediaContentDescription> contentDescription;
auto audioDescription = std::make_unique<cricket::AudioContentDescription>();
contentDescription = std::move(audioDescription);
contentDescription->set_direction(webrtc::RtpTransceiverDirection::kInactive);
contentDescription->set_rtcp_mux(true);
cricket::ContentInfo mappedContentInfo(cricket::MediaProtocolType::kRtp);
mappedContentInfo.name = contentId;
mappedContentInfo.rejected = false;
mappedContentInfo.bundle_only = false;
mappedContentInfo.set_media_description(std::move(contentDescription));
return mappedContentInfo;
}
std::string contentIdBySsrc(uint32_t ssrc) {
return std::to_string(ssrc);
}
}
ContentNegotiationContext::ContentNegotiationContext(const webrtc::FieldTrialsView& fieldTrials, bool isOutgoing, cricket::MediaEngineInterface *mediaEngine, rtc::UniqueRandomIdGenerator *uniqueRandomIdGenerator) :
_isOutgoing(isOutgoing),
_uniqueRandomIdGenerator(uniqueRandomIdGenerator) {
_transportDescriptionFactory = std::make_unique<cricket::TransportDescriptionFactory>(fieldTrials);
// tempCertificate is only used to fill in the local SDP
auto tempCertificate = rtc::RTCCertificateGenerator::GenerateCertificate(rtc::KeyParams(rtc::KT_ECDSA), absl::nullopt);
_transportDescriptionFactory->set_certificate(tempCertificate);
_sessionDescriptionFactory = std::make_unique<cricket::MediaSessionDescriptionFactory>(mediaEngine, true, uniqueRandomIdGenerator, _transportDescriptionFactory.get());
_needNegotiation = true;
}
ContentNegotiationContext::~ContentNegotiationContext() {
}
void ContentNegotiationContext::copyCodecsFromChannelManager(cricket::MediaEngineInterface *mediaEngine, bool randomize) {
cricket::AudioCodecs audioSendCodecs = mediaEngine->voice().send_codecs();
cricket::AudioCodecs audioRecvCodecs = mediaEngine->voice().recv_codecs();
cricket::VideoCodecs videoSendCodecs = mediaEngine->video().send_codecs();
cricket::VideoCodecs videoRecvCodecs = mediaEngine->video().recv_codecs();
for (const auto &codec : audioSendCodecs) {
if (codec.name == "opus") {
audioSendCodecs = { codec };
audioRecvCodecs = { codec };
break;
}
}
if (randomize) {
for (auto &codec : audioSendCodecs) {
codec.id += 3;
}
for (auto &codec : videoSendCodecs) {
codec.id += 3;
}
for (auto &codec : audioRecvCodecs) {
codec.id += 3;
}
for (auto &codec : videoRecvCodecs) {
codec.id += 3;
}
}
_sessionDescriptionFactory->set_audio_codecs(audioSendCodecs, audioRecvCodecs);
_sessionDescriptionFactory->set_video_codecs(videoSendCodecs, videoRecvCodecs);
int absSendTimeUriId = 2;
int transportSequenceNumberUriId = 3;
int videoRotationUri = 13;
if (randomize) {
absSendTimeUriId = 3;
transportSequenceNumberUriId = 2;
videoRotationUri = 4;
}
_rtpAudioExtensions.emplace_back(webrtc::RtpExtension::kAbsSendTimeUri, absSendTimeUriId);
_rtpAudioExtensions.emplace_back(webrtc::RtpExtension::kTransportSequenceNumberUri, transportSequenceNumberUriId);
_rtpVideoExtensions.emplace_back(webrtc::RtpExtension::kAbsSendTimeUri, absSendTimeUriId);
_rtpVideoExtensions.emplace_back(webrtc::RtpExtension::kTransportSequenceNumberUri, transportSequenceNumberUriId);
_rtpVideoExtensions.emplace_back(webrtc::RtpExtension::kVideoRotationUri, videoRotationUri);
}
std::string ContentNegotiationContext::addOutgoingChannel(signaling::MediaContent::Type mediaType) {
std::string channelId = takeNextOutgoingChannelId();
cricket::MediaType mappedMediaType;
std::vector<webrtc::RtpHeaderExtensionCapability> rtpExtensions;
switch (mediaType) {
case signaling::MediaContent::Type::Audio: {
mappedMediaType = cricket::MediaType::MEDIA_TYPE_AUDIO;
rtpExtensions = _rtpAudioExtensions;
break;
}
case signaling::MediaContent::Type::Video: {
mappedMediaType = cricket::MediaType::MEDIA_TYPE_VIDEO;
rtpExtensions = _rtpVideoExtensions;
break;
}
default: {
RTC_FATAL() << "Unknown media type";
break;
}
}
cricket::MediaDescriptionOptions offerDescription(mappedMediaType, channelId, webrtc::RtpTransceiverDirection::kSendOnly, false);
offerDescription.header_extensions = rtpExtensions;
switch (mediaType) {
case signaling::MediaContent::Type::Audio: {
offerDescription.AddAudioSender(channelId, { channelId });
break;
}
case signaling::MediaContent::Type::Video: {
cricket::SimulcastLayerList simulcastLayers;
offerDescription.AddVideoSender(channelId, { channelId }, {}, simulcastLayers, 1);
break;
}
default: {
RTC_FATAL() << "Unknown media type";
break;
}
}
_outgoingChannelDescriptions.emplace_back(std::move(offerDescription));
_needNegotiation = true;
return channelId;
}
void ContentNegotiationContext::removeOutgoingChannel(std::string const &id) {
for (size_t i = 0; i < _outgoingChannels.size(); i++) {
if (_outgoingChannelDescriptions[i].description.mid == id) {
_outgoingChannelDescriptions.erase(_outgoingChannelDescriptions.begin() + i);
_needNegotiation = true;
break;
}
}
}
std::unique_ptr<cricket::SessionDescription> ContentNegotiationContext::currentSessionDescriptionFromCoordinatedState() {
if (_channelIdOrder.empty()) {
return nullptr;
}
auto sessionDescription = std::make_unique<cricket::SessionDescription>();
for (const auto &id : _channelIdOrder) {
bool found = false;
for (const auto &channel : _incomingChannels) {
if (contentIdBySsrc(channel.ssrc) == id) {
found = true;
auto mappedContent = convertSingalingContentToContentInfo(contentIdBySsrc(channel.ssrc), channel, webrtc::RtpTransceiverDirection::kRecvOnly);
// only required for content negotiation
auto localCertificate = rtc::RTCCertificateGenerator::GenerateCertificate(rtc::KeyParams(rtc::KT_ECDSA), absl::nullopt);
std::unique_ptr<rtc::SSLFingerprint> fingerprint;
if (localCertificate) {
fingerprint = rtc::SSLFingerprint::CreateFromCertificate(*localCertificate.get());
}
std::vector<std::string> transportOptions;
cricket::TransportDescription transportDescription(
transportOptions,
"ufrag",
"pwd",
cricket::IceMode::ICEMODE_FULL,
cricket::ConnectionRole::CONNECTIONROLE_ACTPASS,
fingerprint.get()
);
cricket::TransportInfo transportInfo(contentIdBySsrc(channel.ssrc), transportDescription);
sessionDescription->AddTransportInfo(transportInfo);
sessionDescription->AddContent(std::move(mappedContent));
break;
}
}
for (const auto &channel : _outgoingChannels) {
if (channel.id == id) {
found = true;
auto mappedContent = convertSingalingContentToContentInfo(channel.id, channel.content, webrtc::RtpTransceiverDirection::kSendOnly);
// only required for content negotiation
auto localCertificate = rtc::RTCCertificateGenerator::GenerateCertificate(rtc::KeyParams(rtc::KT_ECDSA), absl::nullopt);
std::unique_ptr<rtc::SSLFingerprint> fingerprint;
if (localCertificate) {
fingerprint = rtc::SSLFingerprint::CreateFromCertificate(*localCertificate.get());
}
std::vector<std::string> transportOptions;
cricket::TransportDescription transportDescription(
transportOptions,
"ufrag",
"pwd",
cricket::IceMode::ICEMODE_FULL,
cricket::ConnectionRole::CONNECTIONROLE_ACTPASS,
fingerprint.get()
);
cricket::TransportInfo transportInfo(mappedContent.name, transportDescription);
sessionDescription->AddTransportInfo(transportInfo);
sessionDescription->AddContent(std::move(mappedContent));
break;
}
}
if (!found) {
auto mappedContent = createInactiveContentInfo("_" + id);
// only required for content negotiation
auto localCertificate = rtc::RTCCertificateGenerator::GenerateCertificate(rtc::KeyParams(rtc::KT_ECDSA), absl::nullopt);
std::unique_ptr<rtc::SSLFingerprint> fingerprint;
if (localCertificate) {
fingerprint = rtc::SSLFingerprint::CreateFromCertificate(*localCertificate.get());
}
std::vector<std::string> transportOptions;
cricket::TransportDescription transportDescription(
transportOptions,
"ufrag",
"pwd",
cricket::IceMode::ICEMODE_FULL,
cricket::ConnectionRole::CONNECTIONROLE_ACTPASS,
fingerprint.get()
);
cricket::TransportInfo transportInfo(mappedContent.name, transportDescription);
sessionDescription->AddTransportInfo(transportInfo);
sessionDescription->AddContent(std::move(mappedContent));
}
}
return sessionDescription;
}
static cricket::MediaDescriptionOptions getIncomingContentDescription(signaling::MediaContent const &content) {
auto mappedContent = convertSingalingContentToContentInfo(contentIdBySsrc(content.ssrc), content, webrtc::RtpTransceiverDirection::kSendOnly);
cricket::MediaDescriptionOptions contentDescription(mappedContent.media_description()->type(), mappedContent.name, webrtc::RtpTransceiverDirection::kRecvOnly, false);
for (const auto &extension : mappedContent.media_description()->rtp_header_extensions()) {
contentDescription.header_extensions.emplace_back(extension.uri, extension.id);
}
return contentDescription;
}
std::unique_ptr<ContentNegotiationContext::NegotiationContents> ContentNegotiationContext::getPendingOffer() {
if (!_needNegotiation) {
return nullptr;
}
if (_pendingOutgoingOffer) {
return nullptr;
}
_needNegotiation = false;
_pendingOutgoingOffer = std::make_unique<PendingOutgoingOffer>();
_pendingOutgoingOffer->exchangeId = _uniqueRandomIdGenerator->GenerateId();
auto currentSessionDescription = currentSessionDescriptionFromCoordinatedState();
cricket::MediaSessionOptions offerOptions;
offerOptions.offer_extmap_allow_mixed = true;
offerOptions.bundle_enabled = true;
for (const auto &id : _channelIdOrder) {
bool found = false;
for (const auto &channel : _outgoingChannelDescriptions) {
if (channel.description.mid == id) {
found = true;
offerOptions.media_description_options.push_back(channel.description);
break;
}
}
for (const auto &content : _incomingChannels) {
if (contentIdBySsrc(content.ssrc) == id) {
found = true;
offerOptions.media_description_options.push_back(getIncomingContentDescription(content));
break;
}
}
if (!found) {
cricket::MediaDescriptionOptions contentDescription(cricket::MediaType::MEDIA_TYPE_AUDIO, "_" + id, webrtc::RtpTransceiverDirection::kInactive, false);
offerOptions.media_description_options.push_back(contentDescription);
}
}
for (const auto &channel : _outgoingChannelDescriptions) {
if (std::find(_channelIdOrder.begin(), _channelIdOrder.end(), channel.description.mid) == _channelIdOrder.end()) {
_channelIdOrder.push_back(channel.description.mid);
offerOptions.media_description_options.push_back(channel.description);
}
for (const auto &content : _incomingChannels) {
if (std::find(_channelIdOrder.begin(), _channelIdOrder.end(), contentIdBySsrc(content.ssrc)) == _channelIdOrder.end()) {
_channelIdOrder.push_back(contentIdBySsrc(content.ssrc));
offerOptions.media_description_options.push_back(getIncomingContentDescription(content));
}
}
}
auto offerOrError = _sessionDescriptionFactory->CreateOfferOrError(offerOptions, currentSessionDescription.get());
if (!offerOrError.ok()) {
return nullptr;
}
auto offer = offerOrError.MoveValue();
auto mappedOffer = std::make_unique<ContentNegotiationContext::NegotiationContents>();
mappedOffer->exchangeId = _pendingOutgoingOffer->exchangeId;
for (const auto &content : offer->contents()) {
auto mappedContent = convertContentInfoToSingalingContent(content);
if (content.media_description()->direction() == webrtc::RtpTransceiverDirection::kSendOnly) {
mappedOffer->contents.push_back(std::move(mappedContent));
for (auto &channel : _outgoingChannelDescriptions) {
if (channel.description.mid == content.mid()) {
channel.ssrc = mappedContent.ssrc;
channel.ssrcGroups = mappedContent.ssrcGroups;
}
}
}
}
return mappedOffer;
}
std::unique_ptr<ContentNegotiationContext::NegotiationContents> ContentNegotiationContext::setRemoteNegotiationContent(std::unique_ptr<NegotiationContents> &&remoteNegotiationContent) {
if (!remoteNegotiationContent) {
return nullptr;
}
if (_pendingOutgoingOffer) {
if (remoteNegotiationContent->exchangeId == _pendingOutgoingOffer->exchangeId) {
setAnswer(std::move(remoteNegotiationContent));
return nullptr;
} else {
// race condition detected — call initiator wins
if (!_isOutgoing) {
_pendingOutgoingOffer.reset();
return getAnswer(std::move(remoteNegotiationContent));
} else {
return nullptr;
}
}
} else {
return getAnswer(std::move(remoteNegotiationContent));
}
}
std::unique_ptr<ContentNegotiationContext::NegotiationContents> ContentNegotiationContext::getAnswer(std::unique_ptr<ContentNegotiationContext::NegotiationContents> &&offer) {
auto currentSessionDescription = currentSessionDescriptionFromCoordinatedState();
auto mappedOffer = std::make_unique<cricket::SessionDescription>();
cricket::MediaSessionOptions answerOptions;
answerOptions.offer_extmap_allow_mixed = true;
answerOptions.bundle_enabled = true;
for (const auto &id : _channelIdOrder) {
bool found = false;
for (const auto &channel : _outgoingChannels) {
if (channel.id == id) {
found = true;
auto mappedContent = convertSingalingContentToContentInfo(channel.id, channel.content, webrtc::RtpTransceiverDirection::kRecvOnly);
cricket::MediaDescriptionOptions contentDescription(mappedContent.media_description()->type(), mappedContent.name, webrtc::RtpTransceiverDirection::kSendOnly, false);
for (const auto &extension : mappedContent.media_description()->rtp_header_extensions()) {
contentDescription.header_extensions.emplace_back(extension.uri, extension.id);
}
answerOptions.media_description_options.push_back(contentDescription);
// only required for content negotiation
auto localCertificate = rtc::RTCCertificateGenerator::GenerateCertificate(rtc::KeyParams(rtc::KT_ECDSA), absl::nullopt);
std::unique_ptr<rtc::SSLFingerprint> fingerprint;
if (localCertificate) {
fingerprint = rtc::SSLFingerprint::CreateFromCertificate(*localCertificate.get());
}
std::vector<std::string> transportOptions;
cricket::TransportDescription transportDescription(
transportOptions,
"ufrag",
"pwd",
cricket::IceMode::ICEMODE_FULL,
cricket::ConnectionRole::CONNECTIONROLE_ACTPASS,
fingerprint.get()
);
cricket::TransportInfo transportInfo(channel.id, transportDescription);
mappedOffer->AddTransportInfo(transportInfo);
mappedOffer->AddContent(std::move(mappedContent));
break;
}
}
for (const auto &content : offer->contents) {
if (contentIdBySsrc(content.ssrc) == id) {
found = true;
auto mappedContent = convertSingalingContentToContentInfo(contentIdBySsrc(content.ssrc), content, webrtc::RtpTransceiverDirection::kSendOnly);
cricket::MediaDescriptionOptions contentDescription(mappedContent.media_description()->type(), mappedContent.name, webrtc::RtpTransceiverDirection::kRecvOnly, false);
for (const auto &extension : mappedContent.media_description()->rtp_header_extensions()) {
contentDescription.header_extensions.emplace_back(extension.uri, extension.id);
}
answerOptions.media_description_options.push_back(contentDescription);
// only required for content negotiation
auto localCertificate = rtc::RTCCertificateGenerator::GenerateCertificate(rtc::KeyParams(rtc::KT_ECDSA), absl::nullopt);
std::unique_ptr<rtc::SSLFingerprint> fingerprint;
if (localCertificate) {
fingerprint = rtc::SSLFingerprint::CreateFromCertificate(*localCertificate.get());
}
std::vector<std::string> transportOptions;
cricket::TransportDescription transportDescription(
transportOptions,
"ufrag",
"pwd",
cricket::IceMode::ICEMODE_FULL,
cricket::ConnectionRole::CONNECTIONROLE_ACTPASS,
fingerprint.get()
);
cricket::TransportInfo transportInfo(mappedContent.mid(), transportDescription);
mappedOffer->AddTransportInfo(transportInfo);
mappedOffer->AddContent(std::move(mappedContent));
break;
}
}
if (!found) {
auto mappedContent = createInactiveContentInfo("_" + id);
cricket::MediaDescriptionOptions contentDescription(cricket::MediaType::MEDIA_TYPE_AUDIO, "_" + id, webrtc::RtpTransceiverDirection::kInactive, false);
answerOptions.media_description_options.push_back(contentDescription);
// only required for content negotiation
auto localCertificate = rtc::RTCCertificateGenerator::GenerateCertificate(rtc::KeyParams(rtc::KT_ECDSA), absl::nullopt);
std::unique_ptr<rtc::SSLFingerprint> fingerprint;
if (localCertificate) {
fingerprint = rtc::SSLFingerprint::CreateFromCertificate(*localCertificate.get());
}
std::vector<std::string> transportOptions;
cricket::TransportDescription transportDescription(
transportOptions,
"ufrag",
"pwd",
cricket::IceMode::ICEMODE_FULL,
cricket::ConnectionRole::CONNECTIONROLE_ACTPASS,
fingerprint.get()
);
cricket::TransportInfo transportInfo(mappedContent.mid(), transportDescription);
mappedOffer->AddTransportInfo(transportInfo);
mappedOffer->AddContent(std::move(mappedContent));
}
}
for (const auto &content : offer->contents) {
if (std::find(_channelIdOrder.begin(), _channelIdOrder.end(), contentIdBySsrc(content.ssrc)) == _channelIdOrder.end()) {
_channelIdOrder.push_back(contentIdBySsrc(content.ssrc));
answerOptions.media_description_options.push_back(getIncomingContentDescription(content));
auto mappedContent = convertSingalingContentToContentInfo(contentIdBySsrc(content.ssrc), content, webrtc::RtpTransceiverDirection::kSendOnly);
// only required for content negotiation
auto localCertificate = rtc::RTCCertificateGenerator::GenerateCertificate(rtc::KeyParams(rtc::KT_ECDSA), absl::nullopt);
std::unique_ptr<rtc::SSLFingerprint> fingerprint;
if (localCertificate) {
fingerprint = rtc::SSLFingerprint::CreateFromCertificate(*localCertificate.get());
}
std::vector<std::string> transportOptions;
cricket::TransportDescription transportDescription(
transportOptions,
"ufrag",
"pwd",
cricket::IceMode::ICEMODE_FULL,
cricket::ConnectionRole::CONNECTIONROLE_ACTPASS,
fingerprint.get()
);
cricket::TransportInfo transportInfo(mappedContent.mid(), transportDescription);
mappedOffer->AddTransportInfo(transportInfo);
mappedOffer->AddContent(std::move(mappedContent));
}
}
auto answerOrError = _sessionDescriptionFactory->CreateAnswerOrError(mappedOffer.get(), answerOptions, currentSessionDescription.get());
if (!answerOrError.ok()) {
return nullptr;
}
auto answer = answerOrError.MoveValue();
auto mappedAnswer = std::make_unique<NegotiationContents>();
mappedAnswer->exchangeId = offer->exchangeId;
std::vector<signaling::MediaContent> incomingChannels;
for (const auto &content : answer->contents()) {
auto mappedContent = convertContentInfoToSingalingContent(content);
if (content.media_description()->direction() == webrtc::RtpTransceiverDirection::kRecvOnly) {
for (const auto &offerContent : offer->contents) {
if (contentIdBySsrc(offerContent.ssrc) == content.mid()) {
mappedContent.ssrc = offerContent.ssrc;
mappedContent.ssrcGroups = offerContent.ssrcGroups;
break;
}
}
incomingChannels.push_back(mappedContent);
mappedAnswer->contents.push_back(std::move(mappedContent));
}
}
_incomingChannels = incomingChannels;
return mappedAnswer;
}
void ContentNegotiationContext::setAnswer(std::unique_ptr<ContentNegotiationContext::NegotiationContents> &&answer) {
if (!_pendingOutgoingOffer) {
return;
}
if (_pendingOutgoingOffer->exchangeId != answer->exchangeId) {
return;
}
_pendingOutgoingOffer.reset();
_outgoingChannels.clear();
for (const auto &content : answer->contents) {
for (const auto &pendingChannel : _outgoingChannelDescriptions) {
if (pendingChannel.ssrc != 0 && content.ssrc == pendingChannel.ssrc) {
_outgoingChannels.emplace_back(pendingChannel.description.mid, content);
break;
}
}
}
}
std::string ContentNegotiationContext::takeNextOutgoingChannelId() {
const auto result = "m" + std::to_string(_nextOutgoingChannelId);
_nextOutgoingChannelId++;
return result;
}
std::unique_ptr<ContentNegotiationContext::CoordinatedState> ContentNegotiationContext::coordinatedState() const {
auto result = std::make_unique<ContentNegotiationContext::CoordinatedState>();
result->incomingContents = _incomingChannels;
for (const auto &channel : _outgoingChannels) {
bool found = false;
for (const auto &channelDescription : _outgoingChannelDescriptions) {
if (channelDescription.description.mid == channel.id) {
found = true;
break;
}
}
if (found) {
result->outgoingContents.push_back(channel.content);
}
}
return result;
}
absl::optional<uint32_t> ContentNegotiationContext::outgoingChannelSsrc(std::string const &id) const {
for (const auto &channel : _outgoingChannels) {
bool found = false;
for (const auto &channelDescription : _outgoingChannelDescriptions) {
if (channelDescription.description.mid == channel.id) {
found = true;
break;
}
}
if (found && channel.id == id) {
if (channel.content.ssrc != 0) {
return channel.content.ssrc;
}
}
}
return absl::nullopt;
}
} // namespace tgcalls

View file

@ -0,0 +1,99 @@
#ifndef TGCALLS_CONTENT_NEGOTIATION_H
#define TGCALLS_CONTENT_NEGOTIATION_H
#include <memory>
#include "media/base/media_engine.h"
#include "pc/media_session.h"
#include "pc/session_description.h"
#include "p2p/base/transport_description_factory.h"
#include "v2/Signaling.h"
namespace tgcalls {
class ContentNegotiationContext {
public:
struct NegotiationContents {
uint32_t exchangeId = 0;
std::vector<signaling::MediaContent> contents;
};
struct PendingOutgoingOffer {
uint32_t exchangeId = 0;
};
struct PendingOutgoingChannel {
cricket::MediaDescriptionOptions description;
uint32_t ssrc = 0;
std::vector<signaling::SsrcGroup> ssrcGroups;
PendingOutgoingChannel(cricket::MediaDescriptionOptions &&description_) :
description(std::move(description_)) {
}
};
struct OutgoingChannel {
std::string id;
signaling::MediaContent content;
OutgoingChannel(std::string id_, signaling::MediaContent content_) :
id(id_), content(content_) {
}
};
struct CoordinatedState {
std::vector<signaling::MediaContent> outgoingContents;
std::vector<signaling::MediaContent> incomingContents;
};
public:
ContentNegotiationContext(const webrtc::FieldTrialsView &fieldTrials, bool isOutgoing, cricket::MediaEngineInterface *mediaEngine, rtc::UniqueRandomIdGenerator *uniqueRandomIdGenerator);
~ContentNegotiationContext();
void copyCodecsFromChannelManager(cricket::MediaEngineInterface *mediaEngine, bool randomize);
std::string addOutgoingChannel(signaling::MediaContent::Type mediaType);
void removeOutgoingChannel(std::string const &id);
std::unique_ptr<NegotiationContents> getPendingOffer();
std::unique_ptr<NegotiationContents> setRemoteNegotiationContent(std::unique_ptr<NegotiationContents> &&remoteNegotiationContent);
std::unique_ptr<CoordinatedState> coordinatedState() const;
absl::optional<uint32_t> outgoingChannelSsrc(std::string const &id) const;
private:
std::string takeNextOutgoingChannelId();
std::unique_ptr<cricket::SessionDescription> currentSessionDescriptionFromCoordinatedState();
std::unique_ptr<NegotiationContents> getAnswer(std::unique_ptr<NegotiationContents> &&offer);
void setAnswer(std::unique_ptr<NegotiationContents> &&answer);
private:
bool _isOutgoing = false;
rtc::UniqueRandomIdGenerator *_uniqueRandomIdGenerator = nullptr;
std::unique_ptr<cricket::TransportDescriptionFactory> _transportDescriptionFactory;
std::unique_ptr<cricket::MediaSessionDescriptionFactory> _sessionDescriptionFactory;
std::vector<std::string> _channelIdOrder;
std::vector<webrtc::RtpHeaderExtensionCapability> _rtpAudioExtensions;
std::vector<webrtc::RtpHeaderExtensionCapability> _rtpVideoExtensions;
std::vector<PendingOutgoingChannel> _outgoingChannelDescriptions;
bool _needNegotiation = false;
std::vector<OutgoingChannel> _outgoingChannels;
std::vector<signaling::MediaContent> _incomingChannels;
std::unique_ptr<PendingOutgoingOffer> _pendingOutgoingOffer;
int _nextOutgoingChannelId = 0;
};
} // namespace tgcalls
#endif

View file

@ -0,0 +1,600 @@
#include "v2/DirectNetworkingImpl.h"
#include "p2p/base/basic_packet_socket_factory.h"
#include "p2p/client/basic_port_allocator.h"
#include "p2p/base/p2p_transport_channel.h"
#include "p2p/base/basic_async_resolver_factory.h"
#include "api/packet_socket_factory.h"
#include "rtc_base/rtc_certificate_generator.h"
#include "p2p/base/ice_credentials_iterator.h"
#include "api/jsep_ice_candidate.h"
#include "p2p/base/dtls_transport.h"
#include "p2p/base/dtls_transport_factory.h"
#include "pc/dtls_srtp_transport.h"
#include "pc/dtls_transport.h"
#include "pc/jsep_transport_controller.h"
#include "api/async_dns_resolver.h"
#include "TurnCustomizerImpl.h"
#include "ReflectorRelayPortFactory.h"
#include "SctpDataChannelProviderInterfaceImpl.h"
#include "StaticThreads.h"
#include "platform/PlatformInterface.h"
#include "p2p/base/turn_port.h"
#include "ReflectorPort.h"
#include "FieldTrialsConfig.h"
namespace tgcalls {
namespace {
}
class DirectPacketTransport : public rtc::PacketTransportInternal, public std::enable_shared_from_this<DirectPacketTransport> {
public:
DirectPacketTransport(
rtc::Thread *thread,
EncryptionKey const &encryptionKey,
std::shared_ptr<DirectConnectionChannel> channel, std::function<void(bool)> &&isConnectedUpdated
) :
_isConnectedUpdated(std::move(isConnectedUpdated)),
_thread(thread),
_encryption(
EncryptedConnection::Type::Transport,
encryptionKey,
[=](int delayMs, int cause) {
assert(false);
}),
_channel(channel) {
assert(_thread->IsCurrent());
}
virtual ~DirectPacketTransport() {
}
void start() {
auto weakSelf = std::weak_ptr<DirectPacketTransport>(shared_from_this());
_onIncomingPacketToken = _channel->addOnIncomingPacket([weakSelf, thread = _thread](std::shared_ptr<std::vector<uint8_t>> packet) {
thread->PostTask([weakSelf, packet] {
auto strongSelf = weakSelf.lock();
if (!strongSelf) {
return;
}
strongSelf->processIncomingPacket(packet);
});
});
updateState();
runStateTimer();
}
void stop() {
if (!_onIncomingPacketToken.empty()) {
_channel->removeOnIncomingPacket(_onIncomingPacketToken);
_onIncomingPacketToken.resize(0);
}
}
public:
virtual const std::string &transport_name() const override {
return name;
}
// The transport has been established.
virtual bool writable() const override {
return _isConnected;
}
// The transport has received a packet in the last X milliseconds, here X is
// configured by each implementation.
virtual bool receiving() const override {
return _isConnected;
}
// Attempts to send the given packet.
// The return value is < 0 on failure. The return value in failure case is not
// descriptive. Depending on failure cause and implementation details
// GetError() returns an descriptive errno.h error value.
// This mimics posix socket send() or sendto() behavior.
// TODO(johan): Reliable, meaningful, consistent error codes for all
// implementations would be nice.
// TODO(johan): Remove the default argument once channel code is updated.
virtual int SendPacket(const char *data,
size_t len,
const rtc::PacketOptions& options,
int flags = 0) override {
if (!_isConnected) {
_lastError = ENOTCONN;
return -1;
}
rtc::CopyOnWriteBuffer buffer;
buffer.AppendData(data, len);
Message message = flags == 0 ? Message { AudioDataMessage { buffer } } : Message { VideoDataMessage { buffer } };
if (const auto prepared = _encryption.prepareForSending(message)) {
rtc::PacketOptions packetOptions;
rtc::ByteBufferWriter bufferWriter;
bufferWriter.WriteUInt32((uint32_t)prepared->bytes.size());
bufferWriter.WriteBytes(reinterpret_cast<const uint8_t *>(prepared->bytes.data()), prepared->bytes.size());
while (bufferWriter.Length() % 4 != 0) {
bufferWriter.WriteUInt8(0);
}
auto packet = std::make_unique<std::vector<uint8_t>>(bufferWriter.Data(), bufferWriter.Data() + bufferWriter.Length());
_channel->sendPacket(std::move(packet));
}
rtc::SentPacket sentPacket;
sentPacket.packet_id = options.packet_id;
sentPacket.send_time_ms = rtc::TimeMillis();
SignalSentPacket(this, sentPacket);
return 0;
}
// Sets a socket option. Note that not all options are
// supported by all transport types.
virtual int SetOption(rtc::Socket::Option opt, int value) override {
return 0;
}
// TODO(pthatcher): Once Chrome's MockPacketTransportInterface implements
// this, remove the default implementation.
virtual bool GetOption(rtc::Socket::Option opt, int *value) override {
return false;
}
// Returns the most recent error that occurred on this channel.
virtual int GetError() override {
return _lastError;
}
// Returns the current network route with transport overhead.
// TODO(zhihuang): Make it pure virtual once the Chrome/remoting is updated.
virtual absl::optional<rtc::NetworkRoute> network_route() const override {
return absl::nullopt;
}
/*sigslot::signal1<PacketTransportInternal*> SignalWritableState;
// Emitted when the PacketTransportInternal is ready to send packets. "Ready
// to send" is more sensitive than the writable state; a transport may be
// writable, but temporarily not able to send packets. For example, the
// underlying transport's socket buffer may be full, as indicated by
// SendPacket's return code and/or GetError.
sigslot::signal1<PacketTransportInternal*> SignalReadyToSend;
// Emitted when receiving state changes to true.
sigslot::signal1<PacketTransportInternal*> SignalReceivingState;
// Signalled each time a packet is received on this channel.
sigslot::signal5<PacketTransportInternal*,
const char*,
size_t,
// TODO(bugs.webrtc.org/9584): Change to passing the int64_t
// timestamp by value.
const int64_t&,
int>
SignalReadPacket;
// Signalled each time a packet is sent on this channel.
sigslot::signal2<PacketTransportInternal*, const rtc::SentPacket&>
SignalSentPacket;
// Signalled when the current network route has changed.
sigslot::signal1<absl::optional<rtc::NetworkRoute>> SignalNetworkRouteChanged;
// Signalled when the transport is closed.
sigslot::signal1<PacketTransportInternal*> SignalClosed;*/
private:
void runStateTimer() {
const auto weakSelf = std::weak_ptr<DirectPacketTransport>(shared_from_this());
_thread->PostDelayedTask([weakSelf]() {
auto strongSelf = weakSelf.lock();
if (!strongSelf) {
return;
}
strongSelf->updateState();
strongSelf->runStateTimer();
}, webrtc::TimeDelta::Millis(100));
}
std::unique_ptr<std::vector<uint8_t>> makeServerHelloPacket() {
rtc::ByteBufferWriter bufferWriter;
for (int i = 0; i < 12; i++) {
bufferWriter.WriteUInt8(0xffu);
}
bufferWriter.WriteUInt8(0xfeu);
for (int i = 0; i < 3; i++) {
bufferWriter.WriteUInt8(0xffu);
}
bufferWriter.WriteUInt64(123);
while (bufferWriter.Length() % 4 != 0) {
bufferWriter.WriteUInt8(0);
}
return std::make_unique<std::vector<uint8_t>>(bufferWriter.Data(), bufferWriter.Data() + bufferWriter.Length());
}
std::unique_ptr<std::vector<uint8_t>> makePingPacket() {
rtc::ByteBufferWriter bufferWriter;
bufferWriter.WriteUInt32(_pingMarker);
return std::make_unique<std::vector<uint8_t>>(bufferWriter.Data(), bufferWriter.Data() + bufferWriter.Length());
}
void updateState() {
auto timestamp = rtc::TimeMillis();
if (_isConnected && _lastDataReceivedTimestamp < timestamp - _keepalivePingInterval * 2) {
_isConnected = false;
SignalWritableState(this);
if (_isConnectedUpdated) {
_isConnectedUpdated(_isConnected);
}
}
if (_isConnected) {
if (_lastPingSentTimestamp < timestamp - _initialPingInterval) {
_lastPingSentTimestamp = timestamp;
auto packet = makePingPacket();
_channel->sendPacket(std::move(packet));
}
} else {
if (_lastPingSentTimestamp < timestamp - _keepalivePingInterval) {
_lastPingSentTimestamp = timestamp;
if (_hasCompletedHello) {
auto packet = makePingPacket();
_channel->sendPacket(std::move(packet));
} else {
auto packet = makeServerHelloPacket();
_channel->sendPacket(std::move(packet));
}
}
}
}
void processIncomingPacket(std::shared_ptr<std::vector<uint8_t>> const &packet) {
rtc::ByteBufferReader reader(rtc::ArrayView<const uint8_t>(reinterpret_cast<const uint8_t *>(packet->data()), packet->size()));
uint32_t header = 0;
if (!reader.ReadUInt32(&header)) {
return;
}
_lastDataReceivedTimestamp = rtc::TimeMillis();
if (!_isConnected) {
_isConnected = true;
SignalWritableState(this);
SignalReadyToSend(this);
SignalReceivingState(this);
if (_isConnectedUpdated) {
_isConnectedUpdated(_isConnected);
}
}
if (header == _pingMarker) {
} else {
bool isSpecialPacket = false;
if (packet->size() >= 12) {
uint8_t specialTag[12];
memcpy(specialTag, packet->data(), 12);
uint8_t expectedSpecialTag[12];
memset(expectedSpecialTag, 0xff, 12);
if (memcmp(specialTag, expectedSpecialTag, 12) == 0) {
isSpecialPacket = true;
}
}
if (!isSpecialPacket) {
rtc::ByteBufferReader dataPacketReader(rtc::ArrayView<const uint8_t>(reinterpret_cast<const uint8_t *>(packet->data()), packet->size()));
uint32_t dataSize = 0;
if (!dataPacketReader.ReadUInt32(&dataSize)) {
return;
}
if (dataSize > packet->size() - 4) {
return;
}
if (auto decrypted = _encryption.handleIncomingPacket(reinterpret_cast<const char *>(packet->data()) + 4, dataSize)) {
handleIncomingMessage(decrypted->main);
for (auto &message : decrypted->additional) {
handleIncomingMessage(message);
}
/*if (_transportMessageReceived) {
_transportMessageReceived(std::move(decrypted->main));
for (auto &message : decrypted->additional) {
_transportMessageReceived(std::move(message));
}
}*/
} else {
RTC_LOG(LS_ERROR) << "DirectPacketTransport: could not decrypt incoming packet";
}
/*uint32_t dataSize = 0;
memcpy(&dataSize, packet->data(), 4);
dataSize = be32toh(dataSize);
if (dataSize > packet->size() - 4) {
RTC_LOG(LS_WARNING) << "DirectPacketTransport: Received data packet with invalid size tag";
} else {
SignalReadPacket(this, reinterpret_cast<const char *>(packet->data() + 4), dataSize, rtc::TimeMicros(), 0);
}*/
}
}
}
void handleIncomingMessage(DecryptedMessage const &message) {
const auto data = &message.message.data;
if (const auto dataMessage = absl::get_if<AudioDataMessage>(data)) {
SignalReadPacket(this, reinterpret_cast<const char *>(dataMessage->data.data()), dataMessage->data.size(), rtc::TimeMicros(), 0);
} else if (const auto dataMessage = absl::get_if<VideoDataMessage>(data)) {
SignalReadPacket(this, reinterpret_cast<const char *>(dataMessage->data.data()), dataMessage->data.size(), rtc::TimeMicros(), 1);
} else {
RTC_LOG(LS_INFO) << "DirectPacketTransport: unknown incoming message";
}
}
private:
std::string name = "DirectPacketTransport";
std::function<void(bool)> _isConnectedUpdated;
rtc::Thread *_thread = nullptr;
EncryptedConnection _encryption;
std::shared_ptr<DirectConnectionChannel> _channel;
std::vector<uint8_t> _onIncomingPacketToken;
int _lastError = 0;
int64_t _lastPingSentTimestamp = 0;
int64_t _lastDataReceivedTimestamp = 0;
bool _isConnected = false;
bool _hasCompletedHello = false;
uint32_t _pingMarker = 0xabcd0102;
int64_t _initialPingInterval = 100;
int64_t _keepalivePingInterval = 1000;
};
class DirectRtpTransport : public webrtc::RtpTransport {
public:
explicit DirectRtpTransport() :
webrtc::RtpTransport(true) {
}
virtual bool IsSrtpActive() const override {
return true;
}
};
DirectNetworkingImpl::DirectNetworkingImpl(Configuration &&configuration) :
_threads(std::move(configuration.threads)),
_isOutgoing(configuration.isOutgoing),
_rtcServers(configuration.rtcServers),
_stateUpdated(std::move(configuration.stateUpdated)),
_transportMessageReceived(std::move(configuration.transportMessageReceived)),
_rtcpPacketReceived(std::move(configuration.rtcpPacketReceived)),
_dataChannelStateUpdated(configuration.dataChannelStateUpdated),
_dataChannelMessageReceived(configuration.dataChannelMessageReceived) {
assert(_threads->getNetworkThread()->IsCurrent());
_localIceParameters = PeerIceParameters(rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH), rtc::CreateRandomString(cricket::ICE_PWD_LENGTH), true);
_localCertificate = rtc::RTCCertificateGenerator::GenerateCertificate(rtc::KeyParams(rtc::KT_ECDSA), absl::nullopt);
_rtpTransport = std::make_unique<DirectRtpTransport>();
_rtpTransport->SubscribeReadyToSend(this, [this](bool value) {
this->DtlsReadyToSend(value);
});
_rtpTransport->SubscribeRtcpPacketReceived(this, [this](rtc::CopyOnWriteBuffer *packet, int64_t timestamp) {
this->OnRtcpPacketReceived_n(packet, timestamp);
});
_directConnectionChannel = configuration.directConnectionChannel;
_packetTransport = std::make_shared<DirectPacketTransport>(
_threads->getNetworkThread(),
configuration.encryptionKey,
_directConnectionChannel,
[this](bool isConnected) {
this->_transportIsConnected = isConnected;
this->UpdateAggregateStates_n();
}
);
resetDtlsSrtpTransport();
}
DirectNetworkingImpl::~DirectNetworkingImpl() {
assert(_threads->getNetworkThread()->IsCurrent());
RTC_LOG(LS_INFO) << "DirectNetworkingImpl::~DirectNetworkingImpl()";
_rtpTransport.reset();
_dataChannelInterface.reset();
_packetTransport.reset();
}
void DirectNetworkingImpl::resetDtlsSrtpTransport() {
_rtpTransport->SetRtpPacketTransport(_packetTransport.get());
}
void DirectNetworkingImpl::start() {
const auto weak = std::weak_ptr<DirectNetworkingImpl>(shared_from_this());
_dataChannelInterface.reset(new SctpDataChannelProviderInterfaceImpl(
_packetTransport.get(),
_isOutgoing,
[weak, threads = _threads](bool state) {
assert(threads->getNetworkThread()->IsCurrent());
const auto strong = weak.lock();
if (!strong) {
return;
}
strong->_dataChannelStateUpdated(state);
},
[weak, threads = _threads]() {
assert(threads->getNetworkThread()->IsCurrent());
const auto strong = weak.lock();
if (!strong) {
return;
}
//strong->restartDataChannel();
},
[weak, threads = _threads](std::string const &message) {
assert(threads->getNetworkThread()->IsCurrent());
const auto strong = weak.lock();
if (!strong) {
return;
}
strong->_dataChannelMessageReceived(message);
},
_threads
));
_lastDisconnectedTimestamp = rtc::TimeMillis();
checkConnectionTimeout();
_packetTransport->start();
}
void DirectNetworkingImpl::stop() {
_rtpTransport->UnsubscribeReadyToSend(this);
_dataChannelInterface.reset();
_rtpTransport.reset();
_packetTransport->stop();
_localIceParameters = PeerIceParameters(rtc::CreateRandomString(cricket::ICE_UFRAG_LENGTH), rtc::CreateRandomString(cricket::ICE_PWD_LENGTH), true);
_localCertificate = rtc::RTCCertificateGenerator::GenerateCertificate(rtc::KeyParams(rtc::KT_ECDSA), absl::nullopt);
}
PeerIceParameters DirectNetworkingImpl::getLocalIceParameters() {
return _localIceParameters;
}
std::unique_ptr<rtc::SSLFingerprint> DirectNetworkingImpl::getLocalFingerprint() {
auto certificate = _localCertificate;
if (!certificate) {
return nullptr;
}
return rtc::SSLFingerprint::CreateFromCertificate(*certificate);
}
void DirectNetworkingImpl::setRemoteParams(PeerIceParameters const &remoteIceParameters, rtc::SSLFingerprint *fingerprint, std::string const &sslSetup) {
}
void DirectNetworkingImpl::addCandidates(std::vector<cricket::Candidate> const &candidates) {
}
void DirectNetworkingImpl::sendDataChannelMessage(std::string const &message) {
if (_dataChannelInterface) {
_dataChannelInterface->sendDataChannelMessage(message);
}
}
webrtc::RtpTransport *DirectNetworkingImpl::getRtpTransport() {
return _rtpTransport.get();
}
void DirectNetworkingImpl::checkConnectionTimeout() {
const auto weak = std::weak_ptr<DirectNetworkingImpl>(shared_from_this());
_threads->getNetworkThread()->PostDelayedTask([weak]() {
auto strong = weak.lock();
if (!strong) {
return;
}
int64_t currentTimestamp = rtc::TimeMillis();
const int64_t maxTimeout = 20000;
if (!strong->_isConnected && strong->_lastDisconnectedTimestamp + maxTimeout < currentTimestamp) {
RTC_LOG(LS_INFO) << "DirectNetworkingImpl timeout " << (currentTimestamp - strong->_lastDisconnectedTimestamp) << " ms";
strong->_isFailed = true;
strong->notifyStateUpdated();
}
strong->checkConnectionTimeout();
}, webrtc::TimeDelta::Millis(1000));
}
void DirectNetworkingImpl::OnTransportWritableState_n(rtc::PacketTransportInternal *transport) {
assert(_threads->getNetworkThread()->IsCurrent());
UpdateAggregateStates_n();
}
void DirectNetworkingImpl::OnTransportReceivingState_n(rtc::PacketTransportInternal *transport) {
assert(_threads->getNetworkThread()->IsCurrent());
UpdateAggregateStates_n();
}
void DirectNetworkingImpl::DtlsReadyToSend(bool isReadyToSend) {
UpdateAggregateStates_n();
if (isReadyToSend) {
const auto weak = std::weak_ptr<DirectNetworkingImpl>(shared_from_this());
_threads->getNetworkThread()->PostTask([weak]() {
const auto strong = weak.lock();
if (!strong) {
return;
}
strong->UpdateAggregateStates_n();
});
}
}
void DirectNetworkingImpl::OnRtcpPacketReceived_n(rtc::CopyOnWriteBuffer *packet, int64_t packet_time_us) {
if (_rtcpPacketReceived) {
_rtcpPacketReceived(*packet, packet_time_us);
}
}
void DirectNetworkingImpl::UpdateAggregateStates_n() {
assert(_threads->getNetworkThread()->IsCurrent());
bool isConnected = _transportIsConnected;
if (_isConnected != isConnected) {
_isConnected = isConnected;
if (!isConnected) {
_lastDisconnectedTimestamp = rtc::TimeMillis();
}
notifyStateUpdated();
if (_dataChannelInterface) {
_dataChannelInterface->updateIsConnected(isConnected);
}
}
}
void DirectNetworkingImpl::notifyStateUpdated() {
DirectNetworkingImpl::State emitState;
emitState.isReadyToSendData = _isConnected;
emitState.route = _currentRouteDescription;
emitState.connection = _currentConnectionDescription;
emitState.isFailed = _isFailed;
_stateUpdated(emitState);
}
} // namespace tgcalls

View file

@ -0,0 +1,119 @@
#ifndef TGCALLS_DIRECT_NETWORKING_IMPL_H
#define TGCALLS_DIRECT_NETWORKING_IMPL_H
#ifdef WEBRTC_WIN
// Compiler errors in conflicting Windows headers if not included here.
#include <winsock2.h>
#endif // WEBRTC_WIN
#include "rtc_base/copy_on_write_buffer.h"
#include "rtc_base/third_party/sigslot/sigslot.h"
#include "api/candidate.h"
#include "media/base/media_channel.h"
#include "rtc_base/ssl_fingerprint.h"
#include "pc/sctp_data_channel.h"
#include "p2p/base/port.h"
#include "api/transport/field_trial_based_config.h"
#include <functional>
#include <memory>
#include "InstanceNetworking.h"
#include "Message.h"
#include "ThreadLocalObject.h"
#include "Instance.h"
#include "EncryptedConnection.h"
namespace rtc {
class BasicPacketSocketFactory;
class BasicNetworkManager;
class PacketTransportInternal;
struct NetworkRoute;
} // namespace rtc
namespace cricket {
class BasicPortAllocator;
class P2PTransportChannel;
class IceTransportInternal;
class DtlsTransport;
class RelayPortFactoryInterface;
} // namespace cricket
namespace webrtc {
class TurnCustomizer;
class DtlsSrtpTransport;
class RtpTransport;
class AsyncDnsResolverFactoryInterface;
} // namespace webrtc
namespace tgcalls {
struct Message;
class SctpDataChannelProviderInterfaceImpl;
class Threads;
class DirectPacketTransport;
class DirectRtpTransport;
class DirectNetworkingImpl : public InstanceNetworking, public sigslot::has_slots<>, public std::enable_shared_from_this<DirectNetworkingImpl> {
public:
static webrtc::CryptoOptions getDefaulCryptoOptions();
DirectNetworkingImpl(Configuration &&configuration);
~DirectNetworkingImpl();
void start();
void stop();
PeerIceParameters getLocalIceParameters();
std::unique_ptr<rtc::SSLFingerprint> getLocalFingerprint();
void setRemoteParams(PeerIceParameters const &remoteIceParameters, rtc::SSLFingerprint *fingerprint, std::string const &sslSetup);
void addCandidates(std::vector<cricket::Candidate> const &candidates);
void sendDataChannelMessage(std::string const &message);
webrtc::RtpTransport *getRtpTransport();
private:
void checkConnectionTimeout();
void notifyStateUpdated();
void resetDtlsSrtpTransport();
void DtlsReadyToSend(bool DtlsReadyToSend);
void UpdateAggregateStates_n();
void OnRtcpPacketReceived_n(rtc::CopyOnWriteBuffer *packet, int64_t packet_time_us);
void OnTransportWritableState_n(rtc::PacketTransportInternal *transport);
void OnTransportReceivingState_n(rtc::PacketTransportInternal *transport);
private:
std::shared_ptr<Threads> _threads;
bool _isOutgoing = false;
webrtc::scoped_refptr<rtc::RTCCertificate> _localCertificate;
std::vector<RtcServer> _rtcServers;
PeerIceParameters _localIceParameters;
std::unique_ptr<DirectConnectionChannel> _channel;
std::shared_ptr<DirectConnectionChannel> _directConnectionChannel;
std::shared_ptr<DirectPacketTransport> _packetTransport;
std::unique_ptr<DirectRtpTransport> _rtpTransport;
std::unique_ptr<SctpDataChannelProviderInterfaceImpl> _dataChannelInterface;
std::function<void(const DirectNetworkingImpl::State &)> _stateUpdated;
std::function<void(rtc::CopyOnWriteBuffer const &, bool)> _transportMessageReceived;
std::function<void(rtc::CopyOnWriteBuffer const &, int64_t)> _rtcpPacketReceived;
std::function<void(bool)> _dataChannelStateUpdated;
std::function<void(std::string const &)> _dataChannelMessageReceived;
bool _transportIsConnected = false;
bool _isConnected = false;
bool _isFailed = false;
int64_t _lastDisconnectedTimestamp = 0;
absl::optional<RouteDescription> _currentRouteDescription;
absl::optional<ConnectionDescription> _currentConnectionDescription;
};
} // namespace tgcalls
#endif

View file

@ -0,0 +1,30 @@
#include "v2/ExternalSignalingConnection.h"
namespace tgcalls {
namespace {
}
ExternalSignalingConnection::ExternalSignalingConnection(std::function<void(const std::vector<uint8_t> &)> onIncomingData, std::function<void(const std::vector<uint8_t> &)> emitData) :
_onIncomingData(onIncomingData),
_emitData(emitData) {
}
ExternalSignalingConnection::~ExternalSignalingConnection() {
}
void ExternalSignalingConnection::start() {
}
void ExternalSignalingConnection::send(const std::vector<uint8_t> &data) {
_emitData(data);
}
void ExternalSignalingConnection::receiveExternal(const std::vector<uint8_t> &data) {
_onIncomingData(data);
}
}

View file

@ -0,0 +1,32 @@
#ifndef TGCALLS_EXTERNAL_SIGNALING_CONNECTION_H_
#define TGCALLS_EXTERNAL_SIGNALING_CONNECTION_H_
#include <functional>
#include <vector>
#include "SignalingConnection.h"
namespace rtc {
class AsyncPacketSocket;
}
namespace tgcalls {
class ExternalSignalingConnection : public SignalingConnection {
public:
ExternalSignalingConnection(std::function<void(const std::vector<uint8_t> &)> onIncomingData, std::function<void(const std::vector<uint8_t> &)> emitData);
virtual ~ExternalSignalingConnection();
virtual void start() override;
virtual void send(const std::vector<uint8_t> &data) override;
virtual void receiveExternal(const std::vector<uint8_t> &data) override;
private:
std::function<void(const std::vector<uint8_t> &)> _onIncomingData;
std::function<void(const std::vector<uint8_t> &)> _emitData;
};
} // namespace tgcalls
#endif // TGCALLS_EXTERNAL_SIGNALING_CONNECTION_H_

View file

@ -0,0 +1,172 @@
#ifndef TGCALLS_INSTANCE_NETWORKING_H
#define TGCALLS_INSTANCE_NETWORKING_H
#ifdef WEBRTC_WIN
// Compiler errors in conflicting Windows headers if not included here.
#include <winsock2.h>
#endif // WEBRTC_WIN
#include "rtc_base/copy_on_write_buffer.h"
#include "rtc_base/third_party/sigslot/sigslot.h"
#include "api/candidate.h"
#include "media/base/media_channel.h"
#include "rtc_base/ssl_fingerprint.h"
#include "pc/sctp_data_channel.h"
#include "p2p/base/port.h"
#include "api/transport/field_trial_based_config.h"
#include <functional>
#include <memory>
#include "Message.h"
#include "ThreadLocalObject.h"
#include "Instance.h"
#include "third-party/json11.hpp"
namespace rtc {
class BasicPacketSocketFactory;
class BasicNetworkManager;
class PacketTransportInternal;
struct NetworkRoute;
} // namespace rtc
namespace cricket {
class BasicPortAllocator;
class P2PTransportChannel;
class IceTransportInternal;
class DtlsTransport;
class RelayPortFactoryInterface;
} // namespace cricket
namespace webrtc {
class TurnCustomizer;
class DtlsSrtpTransport;
class RtpTransport;
class AsyncDnsResolverFactoryInterface;
} // namespace webrtc
namespace tgcalls {
struct Message;
class SctpDataChannelProviderInterfaceImpl;
class Threads;
class InstanceNetworking {
public:
struct RouteDescription {
explicit RouteDescription(std::string const &localDescription_, std::string const &remoteDescription_) :
localDescription(localDescription_),
remoteDescription(remoteDescription_) {
}
std::string localDescription;
std::string remoteDescription;
bool operator==(RouteDescription const &rhs) const {
if (localDescription != rhs.localDescription) {
return false;
}
if (remoteDescription != rhs.remoteDescription) {
return false;
}
return true;
}
bool operator!=(const RouteDescription& rhs) const {
return !(*this == rhs);
}
};
struct ConnectionDescription {
struct CandidateDescription {
std::string protocol;
std::string type;
std::string address;
bool operator==(CandidateDescription const &rhs) const {
if (protocol != rhs.protocol) {
return false;
}
if (type != rhs.type) {
return false;
}
if (address != rhs.address) {
return false;
}
return true;
}
bool operator!=(const CandidateDescription& rhs) const {
return !(*this == rhs);
}
};
CandidateDescription local;
CandidateDescription remote;
bool operator==(ConnectionDescription const &rhs) const {
if (local != rhs.local) {
return false;
}
if (remote != rhs.remote) {
return false;
}
return true;
}
bool operator!=(const ConnectionDescription& rhs) const {
return !(*this == rhs);
}
};
struct State {
bool isReadyToSendData = false;
bool isFailed = false;
absl::optional<RouteDescription> route;
absl::optional<ConnectionDescription> connection;
};
struct Configuration {
EncryptionKey encryptionKey;
bool isOutgoing = false;
bool enableStunMarking = false;
bool enableTCP = false;
bool enableP2P = false;
std::vector<RtcServer> rtcServers;
absl::optional<Proxy> proxy;
std::function<void(const InstanceNetworking::State &)> stateUpdated;
std::function<void(const cricket::Candidate &)> candidateGathered;
std::function<void(rtc::CopyOnWriteBuffer const &, bool)> transportMessageReceived;
std::function<void(rtc::CopyOnWriteBuffer const &, int64_t)> rtcpPacketReceived;
std::function<void(bool)> dataChannelStateUpdated;
std::function<void(std::string const &)> dataChannelMessageReceived;
std::shared_ptr<Threads> threads;
std::shared_ptr<DirectConnectionChannel> directConnectionChannel;
std::map<std::string, json11::Json> customParameters;
};
static webrtc::CryptoOptions getDefaulCryptoOptions();
static ConnectionDescription::CandidateDescription connectionDescriptionFromCandidate(cricket::Candidate const &candidate);
virtual ~InstanceNetworking() = default;
virtual void start() = 0;
virtual void stop() = 0;
virtual PeerIceParameters getLocalIceParameters() = 0;
virtual std::unique_ptr<rtc::SSLFingerprint> getLocalFingerprint() = 0;
virtual void setRemoteParams(PeerIceParameters const &remoteIceParameters, rtc::SSLFingerprint *fingerprint, std::string const &sslSetup) = 0;
virtual void addCandidates(std::vector<cricket::Candidate> const &candidates) = 0;
virtual void sendDataChannelMessage(std::string const &message) = 0;
virtual webrtc::RtpTransport *getRtpTransport() = 0;
};
} // namespace tgcalls
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,59 @@
#ifndef TGCALLS_INSTANCEV2_IMPL_H
#define TGCALLS_INSTANCEV2_IMPL_H
#include "Instance.h"
#include "StaticThreads.h"
namespace tgcalls {
class LogSinkImpl;
class Manager;
template <typename T>
class ThreadLocalObject;
class InstanceV2ImplInternal;
class InstanceV2Impl final : public Instance {
public:
explicit InstanceV2Impl(Descriptor &&descriptor);
~InstanceV2Impl() override;
void receiveSignalingData(const std::vector<uint8_t> &data) override;
void setVideoCapture(std::shared_ptr<VideoCaptureInterface> videoCapture) override;
void setRequestedVideoAspect(float aspect) override;
void setNetworkType(NetworkType networkType) override;
void setMuteMicrophone(bool muteMicrophone) override;
bool supportsVideo() override {
return true;
}
void setIncomingVideoOutput(std::weak_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink) override;
void setAudioOutputGainControlEnabled(bool enabled) override;
void setEchoCancellationStrength(int strength) override;
void setAudioInputDevice(std::string id) override;
void setAudioOutputDevice(std::string id) override;
void setInputVolume(float level) override;
void setOutputVolume(float level) override;
void setAudioOutputDuckingEnabled(bool enabled) override;
void setIsLowBatteryLevel(bool isLowBatteryLevel) override;
static std::vector<std::string> GetVersions();
static int GetConnectionMaxLayer();
std::string getLastError() override;
std::string getDebugInfo() override;
int64_t getPreferredRelayId() override;
TrafficStats getTrafficStats() override;
PersistentState getPersistentState() override;
void stop(std::function<void(FinalState)> completion) override;
void sendVideoDeviceUpdated() override {
}
private:
std::shared_ptr<Threads> _threads;
std::unique_ptr<ThreadLocalObject<InstanceV2ImplInternal>> _internal;
std::unique_ptr<LogSinkImpl> _logSink;
};
} // namespace tgcalls
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,59 @@
#ifndef TGCALLS_INSTANCEV2_REFERENCE_IMPL_H
#define TGCALLS_INSTANCEV2_REFERENCE_IMPL_H
#include "Instance.h"
#include "StaticThreads.h"
namespace tgcalls {
class LogSinkImpl;
class Manager;
template <typename T>
class ThreadLocalObject;
class InstanceV2ReferenceImplInternal;
class InstanceV2ReferenceImpl final : public Instance {
public:
explicit InstanceV2ReferenceImpl(Descriptor &&descriptor);
~InstanceV2ReferenceImpl() override;
void receiveSignalingData(const std::vector<uint8_t> &data) override;
void setVideoCapture(std::shared_ptr<VideoCaptureInterface> videoCapture) override;
void setRequestedVideoAspect(float aspect) override;
void setNetworkType(NetworkType networkType) override;
void setMuteMicrophone(bool muteMicrophone) override;
bool supportsVideo() override {
return true;
}
void setIncomingVideoOutput(std::weak_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>> sink) override;
void setAudioOutputGainControlEnabled(bool enabled) override;
void setEchoCancellationStrength(int strength) override;
void setAudioInputDevice(std::string id) override;
void setAudioOutputDevice(std::string id) override;
void setInputVolume(float level) override;
void setOutputVolume(float level) override;
void setAudioOutputDuckingEnabled(bool enabled) override;
void setIsLowBatteryLevel(bool isLowBatteryLevel) override;
static std::vector<std::string> GetVersions();
static int GetConnectionMaxLayer();
std::string getLastError() override;
std::string getDebugInfo() override;
int64_t getPreferredRelayId() override;
TrafficStats getTrafficStats() override;
PersistentState getPersistentState() override;
void stop(std::function<void(FinalState)> completion) override;
void sendVideoDeviceUpdated() override {
}
private:
std::shared_ptr<Threads> _threads;
std::unique_ptr<ThreadLocalObject<InstanceV2ReferenceImplInternal>> _internal;
std::unique_ptr<LogSinkImpl> _logSink;
};
} // namespace tgcalls
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,142 @@
#ifndef TGCALLS_NATIVE_NETWORKING_IMPL_H
#define TGCALLS_NATIVE_NETWORKING_IMPL_H
#ifdef WEBRTC_WIN
// Compiler errors in conflicting Windows headers if not included here.
#include <winsock2.h>
#endif // WEBRTC_WIN
#include "rtc_base/copy_on_write_buffer.h"
#include "rtc_base/third_party/sigslot/sigslot.h"
#include "api/candidate.h"
#include "media/base/media_channel.h"
#include "rtc_base/ssl_fingerprint.h"
#include "pc/sctp_data_channel.h"
#include "p2p/base/port.h"
#include "api/transport/field_trial_based_config.h"
#include <functional>
#include <memory>
#include "InstanceNetworking.h"
#include "Message.h"
#include "ThreadLocalObject.h"
#include "Instance.h"
namespace rtc {
class BasicPacketSocketFactory;
class BasicNetworkManager;
class PacketTransportInternal;
struct NetworkRoute;
} // namespace rtc
namespace cricket {
class BasicPortAllocator;
class P2PTransportChannel;
class IceTransportInternal;
class DtlsTransport;
class RelayPortFactoryInterface;
} // namespace cricket
namespace webrtc {
class TurnCustomizer;
class DtlsSrtpTransport;
class RtpTransport;
class AsyncDnsResolverFactoryInterface;
} // namespace webrtc
namespace tgcalls {
struct Message;
class SctpDataChannelProviderInterfaceImpl;
class Threads;
class NativeNetworkingImpl : public InstanceNetworking, public sigslot::has_slots<>, public std::enable_shared_from_this<NativeNetworkingImpl> {
public:
static webrtc::CryptoOptions getDefaulCryptoOptions();
NativeNetworkingImpl(Configuration &&configuration);
virtual ~NativeNetworkingImpl();
virtual void start() override;
virtual void stop() override;
virtual PeerIceParameters getLocalIceParameters() override;
virtual std::unique_ptr<rtc::SSLFingerprint> getLocalFingerprint() override;
virtual void setRemoteParams(PeerIceParameters const &remoteIceParameters, rtc::SSLFingerprint *fingerprint, std::string const &sslSetup) override;
virtual void addCandidates(std::vector<cricket::Candidate> const &candidates) override;
virtual void sendDataChannelMessage(std::string const &message) override;
virtual webrtc::RtpTransport *getRtpTransport() override;
private:
void resetDtlsSrtpTransport();
void checkConnectionTimeout();
void candidateGathered(cricket::IceTransportInternal *transport, const cricket::Candidate &candidate);
void candidateGatheringState(cricket::IceTransportInternal *transport);
void OnTransportWritableState_n(rtc::PacketTransportInternal *transport);
void OnTransportReceivingState_n(rtc::PacketTransportInternal *transport);
void transportStateChanged(cricket::IceTransportInternal *transport);
void transportReadyToSend(cricket::IceTransportInternal *transport);
void transportRouteChanged(absl::optional<rtc::NetworkRoute> route);
void candidatePairChanged(cricket::CandidatePairChangeEvent const &event);
void DtlsReadyToSend(bool DtlsReadyToSend);
void UpdateAggregateStates_n();
void RtpPacketReceived_n(rtc::CopyOnWriteBuffer *packet, int64_t packet_time_us, bool isUnresolved);
void OnRtcpPacketReceived_n(rtc::CopyOnWriteBuffer *packet, int64_t packet_time_us);
void sctpReadyToSendData();
void notifyStateUpdated();
void processPendingLocalStandaloneReflectorCandidates();
std::shared_ptr<Threads> _threads;
bool _isOutgoing = false;
EncryptionKey _encryptionKey;
bool _enableStunMarking = false;
bool _enableTCP = false;
bool _enableP2P = false;
std::vector<RtcServer> _rtcServers;
absl::optional<Proxy> _proxy;
std::map<std::string, json11::Json> _customParameters;
std::function<void(const InstanceNetworking::State &)> _stateUpdated;
std::function<void(const cricket::Candidate &)> _candidateGathered;
std::function<void(rtc::CopyOnWriteBuffer const &, bool)> _transportMessageReceived;
std::function<void(rtc::CopyOnWriteBuffer const &, int64_t)> _rtcpPacketReceived;
std::function<void(bool)> _dataChannelStateUpdated;
std::function<void(std::string const &)> _dataChannelMessageReceived;
std::unique_ptr<rtc::NetworkMonitorFactory> _networkMonitorFactory;
rtc::SocketFactory *_underlyingSocketFactory = nullptr;
std::unique_ptr<rtc::PacketSocketFactory> _socketFactory;
std::unique_ptr<rtc::NetworkManager> _networkManager;
std::unique_ptr<webrtc::TurnCustomizer> _turnCustomizer;
std::unique_ptr<cricket::RelayPortFactoryInterface> _relayPortFactory;
std::unique_ptr<cricket::BasicPortAllocator> _portAllocator;
std::unique_ptr<webrtc::AsyncDnsResolverFactoryInterface> _asyncResolverFactory;
std::unique_ptr<cricket::P2PTransportChannel> _transportChannel;
std::unique_ptr<webrtc::RtpTransport> _mtProtoRtpTransport;
std::unique_ptr<cricket::DtlsTransport> _dtlsTransport;
std::unique_ptr<webrtc::DtlsSrtpTransport> _dtlsSrtpTransport;
std::unique_ptr<SctpDataChannelProviderInterfaceImpl> _dataChannelInterface;
webrtc::scoped_refptr<rtc::RTCCertificate> _localCertificate;
PeerIceParameters _localIceParameters;
absl::optional<PeerIceParameters> _remoteIceParameters;
bool _isConnected = false;
bool _isFailed = false;
int64_t _lastDisconnectedTimestamp = 0;
absl::optional<RouteDescription> _currentRouteDescription;
absl::optional<ConnectionDescription> _currentConnectionDescription;
std::vector<cricket::Candidate> _pendingLocalStandaloneReflectorCandidates;
};
} // namespace tgcalls
#endif

View file

@ -0,0 +1,111 @@
/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "RawTcpSocket.h"
#include <stdint.h>
#include <string.h>
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <memory>
#include "api/array_view.h"
#include "rtc_base/byte_order.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/network/sent_packet.h"
#include "rtc_base/time_utils.h" // for TimeMillis
#if defined(WEBRTC_POSIX)
#include <errno.h>
#endif // WEBRTC_POSIX
namespace rtc {
static const size_t kMaxPacketSize = 64 * 1024;
static const size_t kBufSize = kMaxPacketSize + 4;
// RawTcpSocket
// Binds and connects `socket` and creates RawTcpSocket for
// it. Takes ownership of `socket`. Returns null if bind() or
// connect() fail (`socket` is destroyed in that case).
RawTcpSocket* RawTcpSocket::Create(Socket* socket,
const SocketAddress& bind_address,
const SocketAddress& remote_address) {
return new RawTcpSocket(
AsyncTCPSocketBase::ConnectSocket(socket, bind_address, remote_address));
}
RawTcpSocket::RawTcpSocket(Socket* socket)
: AsyncTCPSocketBase(socket, kBufSize) {}
int RawTcpSocket::Send(const void* pv,
size_t cb,
const rtc::PacketOptions& options) {
if (cb > kBufSize) {
SetError(EMSGSIZE);
return -1;
}
// If we are blocking on send, then silently drop this packet
if (!IsOutBufferEmpty())
return static_cast<int>(cb);
if (!did_send_mtproto_prologue_) {
did_send_mtproto_prologue_ = true;
uint32_t prologue = 0xeeeeeeee;
AppendToOutBuffer(&prologue, 4);
}
uint32_t pkt_len = (uint32_t)cb;
AppendToOutBuffer(&pkt_len, 4);
AppendToOutBuffer(pv, cb);
int res = FlushOutBuffer();
if (res <= 0) {
// drop packet if we made no progress
ClearOutBuffer();
return res;
}
rtc::SentPacket sent_packet(options.packet_id, rtc::TimeMillis(),
options.info_signaled_after_sent);
CopySocketInformationToPacketInfo(cb, *this, false, &sent_packet.info);
SignalSentPacket(this, sent_packet);
// We claim to have sent the whole thing, even if we only sent partial
return static_cast<int>(cb);
}
size_t RawTcpSocket::ProcessInput(rtc::ArrayView<const uint8_t> data) {
SocketAddress remote_addr(GetRemoteAddress());
size_t processed_bytes = 0;
while (true) {
size_t bytes_left = data.size() - processed_bytes;
if (bytes_left < 4)
return processed_bytes;
uint32_t pkt_len = rtc::GetLE32(data.data() + processed_bytes);
if (bytes_left < 4 + pkt_len)
return processed_bytes;
rtc::ReceivedPacket received_packet(
data.subview(processed_bytes + 4, pkt_len), remote_addr,
webrtc::Timestamp::Micros(rtc::TimeMicros()));
NotifyPacketReceived(received_packet);
processed_bytes += 4 + pkt_len;
}
}
} // namespace rtc

View file

@ -0,0 +1,53 @@
/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef TG_CALLS_RAW_TCP_SOCKET_H_
#define TG_CALLS_RAW_TCP_SOCKET_H_
#include <stddef.h>
#include <cstdint>
#include <memory>
#include "api/array_view.h"
#include "rtc_base/async_packet_socket.h"
#include "rtc_base/buffer.h"
#include "rtc_base/socket.h"
#include "rtc_base/socket_address.h"
#include "rtc_base/async_tcp_socket.h"
namespace rtc {
class RawTcpSocket : public AsyncTCPSocketBase {
public:
// Binds and connects `socket` and creates RawTcpSocket for
// it. Takes ownership of `socket`. Returns null if bind() or
// connect() fail (`socket` is destroyed in that case).
static RawTcpSocket* Create(Socket* socket,
const SocketAddress& bind_address,
const SocketAddress& remote_address);
explicit RawTcpSocket(Socket* socket);
~RawTcpSocket() override {}
RawTcpSocket(const RawTcpSocket&) = delete;
RawTcpSocket& operator=(const RawTcpSocket&) = delete;
int Send(const void* pv,
size_t cb,
const rtc::PacketOptions& options) override;
size_t ProcessInput(rtc::ArrayView<const uint8_t>) override;
private:
bool did_send_mtproto_prologue_ = false;
};
} // namespace rtc
#endif // TG_CALLS_RAW_TCP_SOCKET_H_

View file

@ -0,0 +1,933 @@
#include "v2/ReflectorPort.h"
#include <functional>
#include <memory>
#include <utility>
#include <vector>
#include <random>
#include <sstream>
#include "absl/algorithm/container.h"
#include "absl/strings/match.h"
#include "absl/types/optional.h"
#include "api/transport/stun.h"
#include "p2p/base/connection.h"
#include "p2p/base/p2p_constants.h"
#include "rtc_base/async_packet_socket.h"
#include "rtc_base/byte_order.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/net_helpers.h"
#include "rtc_base/socket_address.h"
#include "rtc_base/strings/string_builder.h"
#include "system_wrappers/include/field_trial.h"
#include "rtc_base/byte_order.h"
#include "RawTcpSocket.h"
namespace tgcalls {
namespace {
rtc::CopyOnWriteBuffer parseHex(std::string const &string) {
rtc::CopyOnWriteBuffer result;
for (size_t i = 0; i < string.length(); i += 2) {
std::string byteString = string.substr(i, 2);
char byte = (char)strtol(byteString.c_str(), NULL, 16);
result.AppendData(&byte, 1);
}
return result;
}
int BindSocket(rtc::Socket* socket,
const rtc::SocketAddress& local_address,
uint16_t min_port,
uint16_t max_port) {
int ret = -1;
if (min_port == 0 && max_port == 0) {
// If there's no port range, let the OS pick a port for us.
ret = socket->Bind(local_address);
} else {
// Otherwise, try to find a port in the provided range.
for (int port = min_port; ret < 0 && port <= max_port; ++port) {
ret = socket->Bind(rtc::SocketAddress(local_address.ipaddr(), port));
}
}
return ret;
}
rtc::AsyncPacketSocket *CreateClientRawTcpSocket(
rtc::SocketFactory *socket_factory_,
const rtc::SocketAddress& local_address,
const rtc::SocketAddress& remote_address,
const rtc::ProxyInfo& proxy_info,
const std::string& user_agent,
const rtc::PacketSocketTcpOptions& tcp_options) {
rtc::Socket* socket =
socket_factory_->CreateSocket(local_address.family(), SOCK_STREAM);
if (!socket) {
return NULL;
}
if (BindSocket(socket, local_address, 0, 0) < 0) {
// Allow BindSocket to fail if we're binding to the ANY address, since this
// is mostly redundant in the first place. The socket will be bound when we
// call Connect() instead.
if (local_address.IsAnyIP()) {
RTC_LOG(LS_WARNING) << "TCP bind failed with error " << socket->GetError()
<< "; ignoring since socket is using 'any' address.";
} else {
RTC_LOG(LS_ERROR) << "TCP bind failed with error " << socket->GetError();
delete socket;
return NULL;
}
}
// Set TCP_NODELAY (via OPT_NODELAY) for improved performance; this causes
// small media packets to be sent immediately rather than being buffered up,
// reducing latency.
//
// Must be done before calling Connect, otherwise it may fail.
if (socket->SetOption(rtc::Socket::OPT_NODELAY, 1) != 0) {
RTC_LOG(LS_ERROR) << "Setting TCP_NODELAY option failed with error "
<< socket->GetError();
}
if (socket->Connect(remote_address) < 0) {
RTC_LOG(LS_ERROR) << "TCP connect failed with error " << socket->GetError();
delete socket;
return NULL;
}
// Finally, wrap that socket in a TCP or STUN TCP packet socket.
rtc::AsyncPacketSocket* tcp_socket;
tcp_socket = new rtc::RawTcpSocket(socket);
return tcp_socket;
}
}
static int GetRelayPreference(cricket::ProtocolType proto) {
switch (proto) {
case cricket::PROTO_TCP:
return cricket::ICE_TYPE_PREFERENCE_RELAY_TCP;
case cricket::PROTO_TLS:
return cricket::ICE_TYPE_PREFERENCE_RELAY_TLS;
default:
RTC_DCHECK(proto == cricket::PROTO_UDP);
return cricket::ICE_TYPE_PREFERENCE_RELAY_UDP;
}
}
ReflectorPort::ReflectorPort(const cricket::CreateRelayPortArgs& args,
rtc::SocketFactory *underlying_socket_factory,
rtc::AsyncPacketSocket* socket,
uint8_t serverId,
int server_priority,
bool standaloneReflectorMode,
uint32_t standaloneReflectorRoleId)
: Port(args.network_thread,
cricket::RELAY_PORT_TYPE,
args.socket_factory,
args.network,
args.username,
args.password),
server_address_(*args.server_address),
credentials_(args.config->credentials),
socket_(socket),
underlying_socket_factory_(underlying_socket_factory),
error_(0),
stun_dscp_value_(rtc::DSCP_NO_CHANGE),
state_(STATE_CONNECTING),
server_priority_(server_priority),
standaloneReflectorMode_(standaloneReflectorMode),
standaloneReflectorRoleId_(standaloneReflectorRoleId) {
serverId_ = serverId;
if (standaloneReflectorMode_) {
randomTag_ = standaloneReflectorRoleId_;
} else {
auto generator = std::mt19937(std::random_device()());
auto distribution = std::uniform_int_distribution<uint32_t>();
do {
randomTag_ = distribution(generator);
} while (!randomTag_);
}
auto rawPeerTag = parseHex(args.config->credentials.password);
peer_tag_.AppendData(rawPeerTag.data(), rawPeerTag.size() - 4);
peer_tag_.AppendData((uint8_t *)&randomTag_, 4);
}
ReflectorPort::ReflectorPort(const cricket::CreateRelayPortArgs& args,
rtc::SocketFactory *underlying_socket_factory,
uint16_t min_port,
uint16_t max_port,
uint8_t serverId,
int server_priority,
bool standaloneReflectorMode,
uint32_t standaloneReflectorRoleId)
: Port(args.network_thread,
cricket::RELAY_PORT_TYPE,
args.socket_factory,
args.network,
min_port,
max_port,
args.username,
args.password),
server_address_(*args.server_address),
credentials_(args.config->credentials),
socket_(NULL),
underlying_socket_factory_(underlying_socket_factory),
error_(0),
stun_dscp_value_(rtc::DSCP_NO_CHANGE),
state_(STATE_CONNECTING),
server_priority_(server_priority),
standaloneReflectorMode_(standaloneReflectorMode),
standaloneReflectorRoleId_(standaloneReflectorRoleId) {
serverId_ = serverId;
if (standaloneReflectorMode_) {
randomTag_ = standaloneReflectorRoleId_;
} else {
auto generator = std::mt19937(std::random_device()());
auto distribution = std::uniform_int_distribution<uint32_t>();
do {
randomTag_ = distribution(generator);
} while (!randomTag_);
}
auto rawPeerTag = parseHex(args.config->credentials.password);
peer_tag_.AppendData(rawPeerTag.data(), rawPeerTag.size() - 4);
peer_tag_.AppendData((uint8_t *)&randomTag_, 4);
}
ReflectorPort::~ReflectorPort() {
// TODO(juberti): Should this even be necessary?
// release the allocation by sending a refresh with
// lifetime 0.
if (ready()) {
Release();
}
if (!SharedSocket()) {
delete socket_;
}
if (server_address_.proto == cricket::PROTO_TCP) {
socket_->UnsubscribeCloseEvent(this);
}
}
rtc::SocketAddress ReflectorPort::GetLocalAddress() const {
return socket_ ? socket_->GetLocalAddress() : rtc::SocketAddress();
}
cricket::ProtocolType ReflectorPort::GetProtocol() const {
return server_address_.proto;
}
void ReflectorPort::PrepareAddress() {
if (peer_tag_.size() != 16) {
RTC_LOG(LS_ERROR) << "Allocation can't be started without setting the"
" peer tag.";
OnAllocateError(cricket::STUN_ERROR_UNAUTHORIZED,
"Missing REFLECTOR server credentials.");
return;
}
if (serverId_ == 0) {
RTC_LOG(LS_ERROR) << "Allocation can't be started without setting the"
" server id.";
OnAllocateError(cricket::STUN_ERROR_UNAUTHORIZED,
"Missing REFLECTOR server id.");
return;
}
if (!server_address_.address.port()) {
// We will set default REFLECTOR port, if no port is set in the address.
server_address_.address.SetPort(599);
}
if (!AllowedReflectorPort(server_address_.address.port())) {
// This can only happen after a 300 ALTERNATE SERVER, since the port can't
// be created with a disallowed port number.
RTC_LOG(LS_ERROR) << "Attempt to start allocation with disallowed port# "
<< server_address_.address.port();
OnAllocateError(cricket::STUN_ERROR_SERVER_ERROR,
"Attempt to start allocation to a disallowed port");
return;
}
if (server_address_.address.IsUnresolvedIP()) {
ResolveTurnAddress(server_address_.address);
} else {
// If protocol family of server address doesn't match with local, return.
if (!IsCompatibleAddress(server_address_.address)) {
RTC_LOG(LS_ERROR) << "IP address family does not match. server: "
<< server_address_.address.family()
<< " local: " << Network()->GetBestIP().family();
OnAllocateError(cricket::STUN_ERROR_GLOBAL_FAILURE,
"IP address family does not match.");
return;
}
// Insert the current address to prevent redirection pingpong.
attempted_server_addresses_.insert(server_address_.address);
RTC_LOG(LS_INFO) << ToString() << ": Trying to connect to REFLECTOR server via "
<< ProtoToString(server_address_.proto) << " @ "
<< server_address_.address.ToSensitiveString();
if (!CreateReflectorClientSocket()) {
RTC_LOG(LS_ERROR) << "Failed to create REFLECTOR client socket";
OnAllocateError(cricket::SERVER_NOT_REACHABLE_ERROR,
"Failed to create REFLECTOR client socket.");
return;
}
if (server_address_.proto == cricket::PROTO_UDP) {
SendReflectorHello();
}
}
}
void ReflectorPort::SendReflectorHello() {
if (!(state_ == STATE_CONNECTED || state_ == STATE_READY)) {
return;
}
RTC_LOG(LS_WARNING)
<< ToString()
<< ": REFLECTOR sending ping to " << server_address_.address.ToString();
if (server_address_.proto == cricket::PROTO_TCP) {
rtc::ByteBufferWriter bufferWriter;
bufferWriter.WriteBytes((const uint8_t *)peer_tag_.data(), peer_tag_.size());
bufferWriter.WriteUInt32(0);
while (bufferWriter.Length() % 4 != 0) {
bufferWriter.WriteUInt8(0);
}
rtc::PacketOptions options;
Send(bufferWriter.Data(), bufferWriter.Length(), options);
} else {
rtc::ByteBufferWriter bufferWriter;
bufferWriter.WriteBytes((const uint8_t *)peer_tag_.data(), peer_tag_.size());
for (int i = 0; i < 12; i++) {
bufferWriter.WriteUInt8(0xffu);
}
bufferWriter.WriteUInt8(0xfeu);
for (int i = 0; i < 3; i++) {
bufferWriter.WriteUInt8(0xffu);
}
bufferWriter.WriteUInt64(123);
while (bufferWriter.Length() % 4 != 0) {
bufferWriter.WriteUInt8(0);
}
rtc::PacketOptions options;
Send(bufferWriter.Data(), bufferWriter.Length(), options);
}
if (!is_running_ping_task_) {
is_running_ping_task_ = true;
int timeoutMs = 10000;
// Send pings faster until response arrives
if (state_ == STATE_CONNECTED) {
timeoutMs = 500;
}
thread()->PostDelayedTask(SafeTask(task_safety_.flag(), [this] {
is_running_ping_task_ = false;
SendReflectorHello();
}), webrtc::TimeDelta::Millis(timeoutMs));
}
}
bool ReflectorPort::CreateReflectorClientSocket() {
RTC_DCHECK(!socket_ || SharedSocket());
if (server_address_.proto == cricket::PROTO_UDP && !SharedSocket()) {
if (standaloneReflectorMode_ && Network()->name() == "shared-reflector-network") {
const rtc::IPAddress ipv4_any_address(INADDR_ANY);
socket_ = socket_factory()->CreateUdpSocket(rtc::SocketAddress(ipv4_any_address, 12345), min_port(), max_port());
} else {
socket_ = socket_factory()->CreateUdpSocket(rtc::SocketAddress(Network()->GetBestIP(), 0), min_port(), max_port());
}
} else if (server_address_.proto == cricket::PROTO_TCP) {
RTC_DCHECK(!SharedSocket());
int opts = 0;
rtc::PacketSocketTcpOptions tcp_options;
tcp_options.opts = opts;
socket_ = CreateClientRawTcpSocket(
underlying_socket_factory_,
rtc::SocketAddress(Network()->GetBestIP(), 0), server_address_.address,
proxy(), user_agent(), tcp_options);
}
if (!socket_) {
error_ = SOCKET_ERROR;
return false;
}
// Apply options if any.
for (SocketOptionsMap::iterator iter = socket_options_.begin();
iter != socket_options_.end(); ++iter) {
socket_->SetOption(iter->first, iter->second);
}
if (!SharedSocket()) {
// If socket is shared, AllocationSequence will receive the packet.
socket_->RegisterReceivedPacketCallback([this](rtc::AsyncPacketSocket *socket, const rtc::ReceivedPacket &packet) {
this->OnReadPacket(socket, packet);
});
}
socket_->SignalReadyToSend.connect(this, &ReflectorPort::OnReadyToSend);
socket_->SignalSentPacket.connect(this, &ReflectorPort::OnSentPacket);
// TCP port is ready to send stun requests after the socket is connected,
// while UDP port is ready to do so once the socket is created.
if (server_address_.proto == cricket::PROTO_TCP ||
server_address_.proto == cricket::PROTO_TLS) {
socket_->SignalConnect.connect(this, &ReflectorPort::OnSocketConnect);
socket_->SubscribeCloseEvent(this, [this](rtc::AsyncPacketSocket* socket, int error) { OnSocketClose(socket, error); });
} else {
state_ = STATE_CONNECTED;
}
return true;
}
void ReflectorPort::OnSocketConnect(rtc::AsyncPacketSocket* socket) {
// This slot should only be invoked if we're using a connection-oriented
// protocol.
RTC_DCHECK(server_address_.proto == cricket::PROTO_TCP ||
server_address_.proto == cricket::PROTO_TLS);
// Do not use this port if the socket bound to an address not associated with
// the desired network interface. This is seen in Chrome, where TCP sockets
// cannot be given a binding address, and the platform is expected to pick
// the correct local address.
//
// However, there are two situations in which we allow the bound address to
// not be one of the addresses of the requested interface:
// 1. The bound address is the loopback address. This happens when a proxy
// forces TCP to bind to only the localhost address (see issue 3927).
// 2. The bound address is the "any address". This happens when
// multiple_routes is disabled (see issue 4780).
//
// Note that, aside from minor differences in log statements, this logic is
// identical to that in TcpPort.
const rtc::SocketAddress& socket_address = socket->GetLocalAddress();
if (absl::c_none_of(Network()->GetIPs(),
[socket_address](const rtc::InterfaceAddress& addr) {
return socket_address.ipaddr() == addr;
})) {
if (socket->GetLocalAddress().IsLoopbackIP()) {
RTC_LOG(LS_WARNING) << "Socket is bound to the address:"
<< socket_address.ipaddr().ToSensitiveString()
<< ", rather than an address associated with network:"
<< Network()->ToString()
<< ". Still allowing it since it's localhost.";
} else if (IPIsAny(Network()->GetBestIP())) {
RTC_LOG(LS_WARNING)
<< "Socket is bound to the address:"
<< socket_address.ipaddr().ToSensitiveString()
<< ", rather than an address associated with network:"
<< Network()->ToString()
<< ". Still allowing it since it's the 'any' address"
", possibly caused by multiple_routes being disabled.";
} else {
RTC_LOG(LS_WARNING) << "Socket is bound to the address:"
<< socket_address.ipaddr().ToSensitiveString()
<< ", rather than an address associated with network:"
<< Network()->ToString() << ". Discarding REFLECTOR port.";
OnAllocateError(
cricket::STUN_ERROR_GLOBAL_FAILURE,
"Address not associated with the desired network interface.");
return;
}
}
state_ = STATE_CONNECTED; // It is ready to send stun requests.
if (server_address_.address.IsUnresolvedIP()) {
server_address_.address = socket_->GetRemoteAddress();
}
RTC_LOG(LS_INFO) << "ReflectorPort connected to "
<< socket->GetRemoteAddress().ToSensitiveString()
<< " using tcp.";
if (server_address_.proto == cricket::PROTO_TCP && state_ != STATE_READY) {
state_ = STATE_READY;
RTC_LOG(LS_INFO)
<< ToString()
<< ": REFLECTOR " << server_address_.address.ToString() << " is now ready";
const auto ipFormat = "reflector-" + std::to_string((uint32_t)serverId_) + "-" + std::to_string(randomTag_) + ".reflector";
rtc::SocketAddress candidateAddress(ipFormat, server_address_.address.port());
if (standaloneReflectorMode_) {
candidateAddress.SetResolvedIP(server_address_.address.ipaddr());
}
// For relayed candidate, Base is the candidate itself.
AddAddress(candidateAddress, // Candidate address.
server_address_.address, // Base address.
rtc::SocketAddress(), // Related address.
cricket::UDP_PROTOCOL_NAME,
ProtoToString(server_address_.proto), // The first hop protocol.
"", // TCP canddiate type, empty for turn candidates.
cricket::RELAY_PORT_TYPE, GetRelayPreference(server_address_.proto),
server_priority_, ReconstructedServerUrl(false /* use_hostname */),
true);
SendReflectorHello();
}
}
void ReflectorPort::OnSocketClose(rtc::AsyncPacketSocket* socket, int error) {
RTC_LOG(LS_WARNING) << ToString()
<< ": Connection with server failed with error: "
<< error;
RTC_DCHECK(socket == socket_);
//Close();
}
cricket::Connection* ReflectorPort::CreateConnection(const cricket::Candidate& remote_candidate,
CandidateOrigin origin) {
// REFLECTOR-UDP can only connect to UDP candidates.
if (!SupportsProtocol(remote_candidate.protocol())) {
return nullptr;
}
auto remoteHostname = remote_candidate.address().hostname();
if (remoteHostname.empty()) {
return nullptr;
}
const auto ipFormat = "reflector-" + std::to_string((uint32_t)serverId_) + "-";
if (!absl::StartsWith(remoteHostname, ipFormat) || !absl::EndsWith(remoteHostname, ".reflector")) {
return nullptr;
}
if (remote_candidate.address().port() != server_address_.address.port()) {
return nullptr;
}
if (state_ == STATE_DISCONNECTED || state_ == STATE_RECEIVEONLY) {
return nullptr;
}
cricket::Candidate updated_remote_candidate = remote_candidate;
if (server_address_.proto == cricket::PROTO_TCP) {
rtc::SocketAddress updated_address = updated_remote_candidate.address();
updated_address.SetResolvedIP(server_address_.address.ipaddr());
updated_remote_candidate.set_address(updated_address);
}
cricket::ProxyConnection* conn = new cricket::ProxyConnection(NewWeakPtr(), 0, updated_remote_candidate);
AddOrReplaceConnection(conn);
return conn;
}
bool ReflectorPort::FailAndPruneConnection(const rtc::SocketAddress& address) {
cricket::Connection* conn = GetConnection(address);
if (conn != nullptr) {
conn->FailAndPrune();
return true;
}
return false;
}
int ReflectorPort::SetOption(rtc::Socket::Option opt, int value) {
// Remember the last requested DSCP value, for STUN traffic.
if (opt == rtc::Socket::OPT_DSCP)
stun_dscp_value_ = static_cast<rtc::DiffServCodePoint>(value);
if (!socket_) {
// If socket is not created yet, these options will be applied during socket
// creation.
socket_options_[opt] = value;
return 0;
}
return socket_->SetOption(opt, value);
}
int ReflectorPort::GetOption(rtc::Socket::Option opt, int* value) {
if (!socket_) {
SocketOptionsMap::const_iterator it = socket_options_.find(opt);
if (it == socket_options_.end()) {
return -1;
}
*value = it->second;
return 0;
}
return socket_->GetOption(opt, value);
}
int ReflectorPort::GetError() {
return error_;
}
int ReflectorPort::SendTo(const void* data,
size_t size,
const rtc::SocketAddress& addr,
const rtc::PacketOptions& options,
bool payload) {
rtc::CopyOnWriteBuffer targetPeerTag;
auto syntheticHostname = addr.hostname();
uint32_t resolvedPeerTag = 0;
auto resolvedPeerTagIt = resolved_peer_tags_by_hostname_.find(syntheticHostname);
if (resolvedPeerTagIt != resolved_peer_tags_by_hostname_.end()) {
resolvedPeerTag = resolvedPeerTagIt->second;
} else {
const auto prefixFormat = "reflector-" + std::to_string((uint32_t)serverId_) + "-";
std::string suffixFormat = ".reflector";
if (!absl::StartsWith(syntheticHostname, prefixFormat) || !absl::EndsWith(syntheticHostname, suffixFormat)) {
RTC_LOG(LS_ERROR) << ToString()
<< ": Discarding SendTo request with destination "
<< addr.ToString();
return -1;
}
auto startPosition = prefixFormat.size();
auto tagString = syntheticHostname.substr(startPosition, syntheticHostname.size() - suffixFormat.size() - startPosition);
std::stringstream tagStringStream(tagString);
tagStringStream >> resolvedPeerTag;
if (resolvedPeerTag == 0) {
RTC_LOG(LS_ERROR) << ToString()
<< ": Discarding SendTo request with destination "
<< addr.ToString() << " (could not parse peer tag)";
return -1;
}
resolved_peer_tags_by_hostname_.insert(std::make_pair(syntheticHostname, resolvedPeerTag));
}
targetPeerTag.AppendData(peer_tag_.data(), peer_tag_.size() - 4);
targetPeerTag.AppendData((uint8_t *)&resolvedPeerTag, 4);
rtc::ByteBufferWriter bufferWriter;
bufferWriter.WriteBytes((const uint8_t *)targetPeerTag.data(), targetPeerTag.size());
bufferWriter.WriteBytes((const uint8_t *)&randomTag_, 4);
bufferWriter.WriteUInt32((uint32_t)size);
bufferWriter.WriteBytes((const uint8_t *)data, size);
while (bufferWriter.Length() % 4 != 0) {
bufferWriter.WriteUInt8(0);
}
rtc::PacketOptions modified_options(options);
CopyPortInformationToPacketInfo(&modified_options.info_signaled_after_sent);
modified_options.info_signaled_after_sent.turn_overhead_bytes = bufferWriter.Length() - size;
Send(bufferWriter.Data(), bufferWriter.Length(), modified_options);
return static_cast<int>(size);
}
bool ReflectorPort::CanHandleIncomingPacketsFrom(
const rtc::SocketAddress& addr) const {
return server_address_.address == addr;
}
bool ReflectorPort::HandleIncomingPacket(rtc::AsyncPacketSocket* socket, rtc::ReceivedPacket const &packet) {
if (socket != socket_) {
// The packet was received on a shared socket after we've allocated a new
// socket for this REFLECTOR port.
return false;
}
uint8_t const *data = packet.payload().begin();
size_t size = packet.payload().size();
rtc::SocketAddress const &remote_addr = packet.source_address();
auto packet_time_us = packet.arrival_time();
// This is to guard against a STUN response from previous server after
// alternative server redirection. TODO(guoweis): add a unit test for this
// race condition.
if (remote_addr != server_address_.address) {
RTC_LOG(LS_WARNING) << ToString()
<< ": Discarding REFLECTOR message from unknown address: "
<< remote_addr.ToSensitiveString()
<< " server_address_: "
<< server_address_.address.ToSensitiveString();
return false;
}
// The message must be at least 16 bytes (peer tag).
if (size < 16) {
RTC_LOG(LS_WARNING) << ToString()
<< ": Received REFLECTOR message that was too short (" << size << ")";
return false;
}
if (state_ == STATE_DISCONNECTED) {
RTC_LOG(LS_WARNING)
<< ToString()
<< ": Received REFLECTOR message while the REFLECTOR port is disconnected";
return false;
}
uint8_t receivedPeerTag[16];
memcpy(receivedPeerTag, data, 16);
if (memcmp(receivedPeerTag, peer_tag_.data(), 16 - 4) != 0) {
RTC_LOG(LS_WARNING)
<< ToString()
<< ": Received REFLECTOR message with incorrect peer_tag";
return false;
}
if (state_ != STATE_READY) {
state_ = STATE_READY;
RTC_LOG(LS_INFO)
<< ToString()
<< ": REFLECTOR " << server_address_.address.ToString() << " is now ready";
const auto ipFormat = "reflector-" + std::to_string((uint32_t)serverId_) + "-" + std::to_string(randomTag_) + ".reflector";
rtc::SocketAddress candidateAddress(ipFormat, server_address_.address.port());
if (standaloneReflectorMode_) {
candidateAddress.SetResolvedIP(server_address_.address.ipaddr());
}
// For relayed candidate, Base is the candidate itself.
AddAddress(candidateAddress, // Candidate address.
server_address_.address, // Base address.
rtc::SocketAddress(), // Related address.
cricket::UDP_PROTOCOL_NAME,
ProtoToString(server_address_.proto), // The first hop protocol.
"", // TCP canddiate type, empty for turn candidates.
cricket::RELAY_PORT_TYPE, GetRelayPreference(server_address_.proto),
server_priority_, ReconstructedServerUrl(false /* use_hostname */),
true);
}
if (size > 16 + 4 + 4) {
bool isSpecialPacket = false;
if (size >= 16 + 12) {
uint8_t specialTag[12];
memcpy(specialTag, data + 16, 12);
uint8_t expectedSpecialTag[12];
memset(expectedSpecialTag, 0xff, 12);
if (memcmp(specialTag, expectedSpecialTag, 12) == 0) {
isSpecialPacket = true;
}
}
if (!isSpecialPacket) {
uint32_t senderTag = 0;
memcpy(&senderTag, data + 16, 4);
uint32_t dataSize = 0;
memcpy(&dataSize, data + 16 + 4, 4);
dataSize = be32toh(dataSize);
if (dataSize > size - 16 - 4 - 4) {
RTC_LOG(LS_WARNING)
<< ToString()
<< ": Received data packet with invalid size tag";
} else {
const auto ipFormat = "reflector-" + std::to_string((uint32_t)serverId_) + "-" + std::to_string(senderTag) + ".reflector";
rtc::SocketAddress candidateAddress(ipFormat, server_address_.address.port());
candidateAddress.SetResolvedIP(server_address_.address.ipaddr());
int64_t packet_timestamp = -1;
if (packet_time_us.has_value()) {
packet_timestamp = packet_time_us->us_or(-1);
}
DispatchPacket(rtc::ReceivedPacket::CreateFromLegacy(data + 16 + 4 + 4, dataSize, packet_timestamp, candidateAddress), cricket::ProtocolType::PROTO_UDP);
}
}
}
return true;
}
void ReflectorPort::OnReadPacket(rtc::AsyncPacketSocket* socket, rtc::ReceivedPacket const &packet) {
HandleIncomingPacket(socket, packet);
}
void ReflectorPort::OnSentPacket(rtc::AsyncPacketSocket* socket,
const rtc::SentPacket& sent_packet) {
PortInterface::SignalSentPacket(sent_packet);
}
void ReflectorPort::OnReadyToSend(rtc::AsyncPacketSocket* socket) {
if (ready()) {
Port::OnReadyToSend();
}
}
bool ReflectorPort::SupportsProtocol(absl::string_view protocol) const {
// Turn port only connects to UDP candidates.
return protocol == cricket::UDP_PROTOCOL_NAME;
}
void ReflectorPort::ResolveTurnAddress(const rtc::SocketAddress& address) {
if (resolver_)
return;
RTC_LOG(LS_INFO) << ToString() << ": Starting TURN host lookup for "
<< address.ToSensitiveString();
resolver_ = socket_factory()->CreateAsyncDnsResolver();
resolver_->Start(address, [this] {
// If DNS resolve is failed when trying to connect to the server using TCP,
// one of the reason could be due to DNS queries blocked by firewall.
// In such cases we will try to connect to the server with hostname,
// assuming socket layer will resolve the hostname through a HTTP proxy (if
// any).
auto& result = resolver_->result();
if (result.GetError() != 0 && (server_address_.proto == cricket::PROTO_TCP ||
server_address_.proto == cricket::PROTO_TLS)) {
if (!CreateReflectorClientSocket()) {
OnAllocateError(cricket::SERVER_NOT_REACHABLE_ERROR,
"TURN host lookup received error.");
}
return;
}
// Copy the original server address in `resolved_address`. For TLS based
// sockets we need hostname along with resolved address.
rtc::SocketAddress resolved_address = server_address_.address;
if (result.GetError() != 0 ||
!result.GetResolvedAddress(Network()->GetBestIP().family(),
&resolved_address)) {
RTC_LOG(LS_WARNING) << ToString() << ": TURN host lookup received error "
<< result.GetError();
error_ = result.GetError();
OnAllocateError(cricket::SERVER_NOT_REACHABLE_ERROR,
"TURN host lookup received error.");
return;
}
// Signal needs both resolved and unresolved address. After signal is sent
// we can copy resolved address back into `server_address_`.
SignalResolvedServerAddress(this, server_address_.address,
resolved_address);
server_address_.address = resolved_address;
PrepareAddress();
});
}
void ReflectorPort::OnSendStunPacket(const void* data,
size_t size,
cricket::StunRequest* request) {
RTC_DCHECK(connected());
rtc::PacketOptions options(StunDscpValue());
options.info_signaled_after_sent.packet_type = rtc::PacketType::kTurnMessage;
CopyPortInformationToPacketInfo(&options.info_signaled_after_sent);
if (Send(data, size, options) < 0) {
RTC_LOG(LS_ERROR) << ToString() << ": Failed to send TURN message, error: "
<< socket_->GetError();
}
}
void ReflectorPort::OnAllocateError(int error_code, const std::string& reason) {
// We will send SignalPortError asynchronously as this can be sent during
// port initialization. This way it will not be blocking other port
// creation.
thread()->PostTask(
SafeTask(task_safety_.flag(), [this] { SignalPortError(this); }));
std::string address = GetLocalAddress().HostAsSensitiveURIString();
int port = GetLocalAddress().port();
if (server_address_.proto == cricket::PROTO_TCP &&
server_address_.address.IsPrivateIP()) {
address.clear();
port = 0;
}
SignalCandidateError(this, cricket::IceCandidateErrorEvent(address, port, ReconstructedServerUrl(true /* use_hostname */), error_code, reason));
}
void ReflectorPort::Release() {
state_ = STATE_RECEIVEONLY;
}
void ReflectorPort::Close() {
if (!ready()) {
OnAllocateError(cricket::SERVER_NOT_REACHABLE_ERROR, "");
}
// Stop the port from creating new connections.
state_ = STATE_DISCONNECTED;
// Delete all existing connections; stop sending data.
for (auto kv : connections()) {
kv.second->Destroy();
}
SignalReflectorPortClosed(this);
}
rtc::DiffServCodePoint ReflectorPort::StunDscpValue() const {
return stun_dscp_value_;
}
// static
bool ReflectorPort::AllowedReflectorPort(int port) {
return true;
}
void ReflectorPort::DispatchPacket(rtc::ReceivedPacket const &packet, cricket::ProtocolType proto) {
if (cricket::Connection* conn = GetConnection(packet.source_address())) {
conn->OnReadPacket(packet);
} else {
Port::OnReadPacket(packet, proto);
}
}
int ReflectorPort::Send(const void* data,
size_t len,
const rtc::PacketOptions& options) {
return socket_->SendTo(data, len, server_address_.address, options);
}
void ReflectorPort::HandleConnectionDestroyed(cricket::Connection* conn) {
}
std::string ReflectorPort::ReconstructedServerUrl(bool use_hostname) {
// draft-petithuguenin-behave-turn-uris-01
// turnURI = scheme ":" turn-host [ ":" turn-port ]
// [ "?transport=" transport ]
// scheme = "turn" / "turns"
// transport = "udp" / "tcp" / transport-ext
// transport-ext = 1*unreserved
// turn-host = IP-literal / IPv4address / reg-name
// turn-port = *DIGIT
std::string scheme = "turn";
std::string transport = "tcp";
switch (server_address_.proto) {
case cricket::PROTO_SSLTCP:
case cricket::PROTO_TLS:
scheme = "turns";
break;
case cricket::PROTO_UDP:
transport = "udp";
break;
case cricket::PROTO_TCP:
break;
}
rtc::StringBuilder url;
url << scheme << ":"
<< (use_hostname ? server_address_.address.hostname()
: server_address_.address.ipaddr().ToString())
<< ":" << server_address_.address.port() << "?transport=" << transport;
return url.Release();
}
} // namespace cricket

View file

@ -0,0 +1,250 @@
#ifndef TGCALLS_REFLECTOR_PORT_H_
#define TGCALLS_REFLECTOR_PORT_H_
#include <stdio.h>
#include <list>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <vector>
#include "absl/memory/memory.h"
#include "api/async_dns_resolver.h"
#include "p2p/base/port.h"
#include "p2p/client/basic_port_allocator.h"
#include "rtc_base/async_packet_socket.h"
#include "rtc_base/ssl_certificate.h"
namespace webrtc {
class TurnCustomizer;
}
namespace tgcalls {
extern const int STUN_ATTR_TURN_LOGGING_ID;
extern const char TURN_PORT_TYPE[];
class TurnAllocateRequest;
class TurnEntry;
class ReflectorPort : public cricket::Port {
public:
enum PortState {
STATE_CONNECTING, // Initial state, cannot send any packets.
STATE_CONNECTED, // Socket connected, ready to send stun requests.
STATE_READY, // Received allocate success, can send any packets.
STATE_RECEIVEONLY, // Had REFRESH_REQUEST error, cannot send any packets.
STATE_DISCONNECTED, // TCP connection died, cannot send/receive any
// packets.
};
// Create a TURN port using the shared UDP socket, `socket`.
static std::unique_ptr<ReflectorPort> Create(
const cricket::CreateRelayPortArgs& args,
rtc::SocketFactory *underlying_socket_factory,
rtc::AsyncPacketSocket* socket,
uint8_t serverId,
int server_priority,
bool standaloneReflectorMode,
uint32_t standaloneReflectorRoleId
) {
// Do basic parameter validation.
if (args.config->credentials.username.size() > 32) {
RTC_LOG(LS_ERROR) << "Attempt to use REFLECTOR with a too long username "
<< "of length " << args.config->credentials.username.size();
return nullptr;
}
// Do not connect to low-numbered ports. The default STUN port is 3478.
if (!AllowedReflectorPort(args.server_address->address.port())) {
RTC_LOG(LS_ERROR) << "Attempt to use REFLECTOR to connect to port "
<< args.server_address->address.port();
return nullptr;
}
// Using `new` to access a non-public constructor.
return absl::WrapUnique(new ReflectorPort(args, underlying_socket_factory, socket, serverId, server_priority, standaloneReflectorMode, standaloneReflectorRoleId));
}
// Create a TURN port that will use a new socket, bound to `network` and
// using a port in the range between `min_port` and `max_port`.
static std::unique_ptr<ReflectorPort> Create(
const cricket::CreateRelayPortArgs& args,
rtc::SocketFactory *underlying_socket_factory,
uint16_t min_port,
uint16_t max_port,
uint8_t serverId,
int server_priority,
bool standaloneReflectorMode,
uint32_t standaloneReflectorRoleId
) {
// Do basic parameter validation.
if (args.config->credentials.username.size() > 32) {
RTC_LOG(LS_ERROR) << "Attempt to use TURN with a too long username "
<< "of length " << args.config->credentials.username.size();
return nullptr;
}
// Do not connect to low-numbered ports. The default STUN port is 3478.
if (!AllowedReflectorPort(args.server_address->address.port())) {
RTC_LOG(LS_ERROR) << "Attempt to use TURN to connect to port "
<< args.server_address->address.port();
return nullptr;
}
// Using `new` to access a non-public constructor.
return absl::WrapUnique(new ReflectorPort(args, underlying_socket_factory, min_port, max_port, serverId, server_priority, standaloneReflectorMode, standaloneReflectorRoleId));
}
~ReflectorPort() override;
const cricket::ProtocolAddress& server_address() const { return server_address_; }
// Returns an empty address if the local address has not been assigned.
rtc::SocketAddress GetLocalAddress() const;
bool ready() const { return state_ == STATE_READY; }
bool connected() const {
return state_ == STATE_READY || state_ == STATE_CONNECTED;
}
const cricket::RelayCredentials& credentials() const { return credentials_; }
cricket::ProtocolType GetProtocol() const override;
// Sets state to STATE_RECEIVEONLY.
void Release();
void PrepareAddress() override;
cricket::Connection* CreateConnection(const cricket::Candidate& c,
PortInterface::CandidateOrigin origin) override;
int SendTo(const void* data,
size_t size,
const rtc::SocketAddress& addr,
const rtc::PacketOptions& options,
bool payload) override;
int SetOption(rtc::Socket::Option opt, int value) override;
int GetOption(rtc::Socket::Option opt, int* value) override;
int GetError() override;
virtual bool HandleIncomingPacket(rtc::AsyncPacketSocket* socket,
const rtc::ReceivedPacket& packet) override;
bool CanHandleIncomingPacketsFrom(
const rtc::SocketAddress& addr) const override;
virtual void OnReadPacket(rtc::AsyncPacketSocket* socket, rtc::ReceivedPacket const &packet);
void OnSentPacket(rtc::AsyncPacketSocket* socket,
const rtc::SentPacket& sent_packet) override;
virtual void OnReadyToSend(rtc::AsyncPacketSocket* socket);
bool SupportsProtocol(absl::string_view protocol) const override;
void OnSocketConnect(rtc::AsyncPacketSocket* socket);
void OnSocketClose(rtc::AsyncPacketSocket* socket, int error);
int error() const { return error_; }
rtc::AsyncPacketSocket* socket() const { return socket_; }
// Signal with resolved server address.
// Parameters are port, server address and resolved server address.
// This signal will be sent only if server address is resolved successfully.
sigslot::
signal3<ReflectorPort*, const rtc::SocketAddress&, const rtc::SocketAddress&>
SignalResolvedServerAddress;
// Signal when ReflectorPort is closed,
// e.g remote socket closed (TCP)
// or receiveing a REFRESH response with lifetime 0.
sigslot::signal1<ReflectorPort*> SignalReflectorPortClosed;
// All public methods/signals below are for testing only.
sigslot::signal2<ReflectorPort*, int> SignalTurnRefreshResult;
sigslot::signal3<ReflectorPort*, const rtc::SocketAddress&, int>
SignalCreatePermissionResult;
// Visible for testing.
// Shuts down the turn port, usually because of some fatal errors.
void Close();
void HandleConnectionDestroyed(cricket::Connection* conn) override;
protected:
ReflectorPort(const cricket::CreateRelayPortArgs& args,
rtc::SocketFactory *underlying_socket_factory,
rtc::AsyncPacketSocket* socket,
uint8_t serverId,
int server_priority,
bool standaloneReflectorMode,
uint32_t standaloneReflectorRoleId);
ReflectorPort(const cricket::CreateRelayPortArgs& args,
rtc::SocketFactory *underlying_socket_factory,
uint16_t min_port,
uint16_t max_port,
uint8_t serverId,
int server_priority,
bool standaloneReflectorMode,
uint32_t standaloneReflectorRoleId);
rtc::DiffServCodePoint StunDscpValue() const override;
private:
typedef std::map<rtc::Socket::Option, int> SocketOptionsMap;
typedef std::set<rtc::SocketAddress> AttemptedServerSet;
static bool AllowedReflectorPort(int port);
bool CreateReflectorClientSocket();
void ResolveTurnAddress(const rtc::SocketAddress& address);
void OnResolveResult(rtc::AsyncResolverInterface* resolver);
void OnSendStunPacket(const void* data, size_t size, cricket::StunRequest* request);
void OnAllocateError(int error_code, const std::string& reason);
void DispatchPacket(rtc::ReceivedPacket const &packet, cricket::ProtocolType proto);
int Send(const void* data, size_t size, const rtc::PacketOptions& options);
// Marks the connection with remote address `address` failed and
// pruned (a.k.a. write-timed-out). Returns true if a connection is found.
bool FailAndPruneConnection(const rtc::SocketAddress& address);
// Reconstruct the URL of the server which the candidate is gathered from.
std::string ReconstructedServerUrl(bool use_hostname);
void SendReflectorHello();
rtc::CopyOnWriteBuffer peer_tag_;
uint32_t randomTag_ = 0;
cricket::ProtocolAddress server_address_;
uint8_t serverId_ = 0;
std::map<std::string, uint32_t> resolved_peer_tags_by_hostname_;
cricket::RelayCredentials credentials_;
AttemptedServerSet attempted_server_addresses_;
rtc::AsyncPacketSocket* socket_;
rtc::SocketFactory *underlying_socket_factory_;
SocketOptionsMap socket_options_;
std::unique_ptr<webrtc::AsyncDnsResolverInterface> resolver_;
int error_;
rtc::DiffServCodePoint stun_dscp_value_;
PortState state_;
// By default the value will be set to 0. This value will be used in
// calculating the candidate priority.
int server_priority_;
bool standaloneReflectorMode_ = false;
uint32_t standaloneReflectorRoleId_ = 0;
// Optional TurnCustomizer that can modify outgoing messages. Once set, this
// must outlive the ReflectorPort's lifetime.
webrtc::TurnCustomizer* turn_customizer_ = nullptr;
webrtc::ScopedTaskSafety task_safety_;
bool is_running_ping_task_ = false;
};
} // namespace tgcalls
#endif // TGCALLS_REFLECTOR_PORT_H_

View file

@ -0,0 +1,81 @@
#include "v2/ReflectorRelayPortFactory.h"
#include "p2p/base/turn_port.h"
#include "v2/ReflectorPort.h"
namespace tgcalls {
ReflectorRelayPortFactory::ReflectorRelayPortFactory(std::vector<RtcServer> servers, bool standaloneReflectorMode, uint32_t standaloneReflectorRoleId, rtc::SocketFactory *underlyingSocketFactory) :
_servers(servers),
_standaloneReflectorMode(standaloneReflectorMode),
_standaloneReflectorRoleId(standaloneReflectorRoleId),
_underlyingSocketFactory(underlyingSocketFactory) {
}
ReflectorRelayPortFactory::~ReflectorRelayPortFactory() {
}
std::unique_ptr<cricket::Port> ReflectorRelayPortFactory::Create(const cricket::CreateRelayPortArgs& args, rtc::AsyncPacketSocket* udp_socket) {
if (args.config->credentials.username == "reflector") {
uint8_t id = 0;
for (const auto &server : _servers) {
rtc::SocketAddress serverAddress(server.host, server.port);
if (args.server_address->address == serverAddress) {
id = server.id;
break;
}
}
if (id == 0) {
return nullptr;
}
auto port = ReflectorPort::Create(args, _underlyingSocketFactory, udp_socket, id, args.relative_priority, _standaloneReflectorMode, _standaloneReflectorRoleId);
if (!port) {
return nullptr;
}
return port;
} else {
auto port = cricket::TurnPort::Create(args, udp_socket);
if (!port) {
return nullptr;
}
port->SetTlsCertPolicy(args.config->tls_cert_policy);
port->SetTurnLoggingId(args.config->turn_logging_id);
return port;
}
}
std::unique_ptr<cricket::Port> ReflectorRelayPortFactory::Create(const cricket::CreateRelayPortArgs& args, int min_port, int max_port) {
if (args.config->credentials.username == "reflector") {
uint8_t id = 0;
for (const auto &server : _servers) {
rtc::SocketAddress serverAddress(server.host, server.port);
if (args.server_address->address == serverAddress) {
id = server.id;
break;
}
}
if (id == 0) {
return nullptr;
}
auto port = ReflectorPort::Create(args, _underlyingSocketFactory, min_port, max_port, id, args.relative_priority, _standaloneReflectorMode, _standaloneReflectorRoleId);
if (!port) {
return nullptr;
}
return port;
} else {
auto port = cricket::TurnPort::Create(args, min_port, max_port);
if (!port) {
return nullptr;
}
port->SetTlsCertPolicy(args.config->tls_cert_policy);
port->SetTurnLoggingId(args.config->turn_logging_id);
return port;
}
}
} // namespace tgcalls

View file

@ -0,0 +1,37 @@
#ifndef TGCALLS_REFLECTOR_RELAY_PORT_FACTORY_H
#define TGCALLS_REFLECTOR_RELAY_PORT_FACTORY_H
#include "p2p/client/relay_port_factory_interface.h"
#include "Instance.h"
namespace rtc {
class SocketFactory;
}
namespace tgcalls {
class ReflectorRelayPortFactory : public cricket::RelayPortFactoryInterface {
public:
ReflectorRelayPortFactory(std::vector<RtcServer> servers, bool standaloneReflectorMode, uint32_t standaloneReflectorRoleId, rtc::SocketFactory *underlyingSocketFactory);
~ReflectorRelayPortFactory() override;
// This variant is used for UDP connection to the relay server
// using a already existing shared socket.
virtual std::unique_ptr<cricket::Port> Create(const cricket::CreateRelayPortArgs& args, rtc::AsyncPacketSocket* udp_socket) override;
// This variant is used for the other cases.
virtual std::unique_ptr<cricket::Port> Create(const cricket::CreateRelayPortArgs& args, int min_port, int max_port) override;
private:
std::vector<RtcServer> _servers;
bool _standaloneReflectorMode = false;
uint32_t _standaloneReflectorRoleId = 0;
rtc::SocketFactory *_underlyingSocketFactory = nullptr;
};
} // namespace tgcalls
#endif

View file

@ -0,0 +1,820 @@
#include "v2/Signaling.h"
#include "third-party/json11.hpp"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include <sstream>
namespace tgcalls {
namespace signaling {
static std::string uint32ToString(uint32_t value) {
return std::to_string(value);
}
static uint32_t stringToUInt32(std::string const &string) {
std::stringstream stringStream(string);
uint32_t value = 0;
stringStream >> value;
return value;
}
json11::Json::object SsrcGroup_serialize(SsrcGroup const &ssrcGroup) {
json11::Json::object object;
json11::Json::array ssrcs;
for (auto ssrc : ssrcGroup.ssrcs) {
ssrcs.push_back(json11::Json(uint32ToString(ssrc)));
}
object.insert(std::make_pair("semantics", json11::Json(ssrcGroup.semantics)));
object.insert(std::make_pair("ssrcs", json11::Json(std::move(ssrcs))));
return object;
}
absl::optional<SsrcGroup> SsrcGroup_parse(json11::Json::object const &object) {
SsrcGroup result;
const auto semantics = object.find("semantics");
if (semantics == object.end() || !semantics->second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: semantics must be a string";
return absl::nullopt;
}
result.semantics = semantics->second.string_value();
const auto ssrcs = object.find("ssrcs");
if (ssrcs == object.end() || !ssrcs->second.is_array()) {
RTC_LOG(LS_ERROR) << "Signaling: ssrcs must be an array";
return absl::nullopt;
}
for (const auto &ssrc : ssrcs->second.array_items()) {
if (ssrc.is_string()) {
uint32_t parsedSsrc = stringToUInt32(ssrc.string_value());
if (parsedSsrc == 0) {
RTC_LOG(LS_ERROR) << "Signaling: parsedSsrc must not be 0";
return absl::nullopt;
}
result.ssrcs.push_back(parsedSsrc);
} else if (ssrc.is_number()) {
uint32_t parsedSsrc = (uint32_t)ssrc.number_value();
result.ssrcs.push_back(parsedSsrc);
} else {
RTC_LOG(LS_ERROR) << "Signaling: ssrcs item must be a string or a number";
return absl::nullopt;
}
}
return result;
}
json11::Json::object FeedbackType_serialize(FeedbackType const &feedbackType) {
json11::Json::object object;
object.insert(std::make_pair("type", json11::Json(feedbackType.type)));
object.insert(std::make_pair("subtype", json11::Json(feedbackType.subtype)));
return object;
}
absl::optional<FeedbackType> FeedbackType_parse(json11::Json::object const &object) {
FeedbackType result;
const auto type = object.find("type");
if (type == object.end() || !type->second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: type must be a string";
return absl::nullopt;
}
result.type = type->second.string_value();
const auto subtype = object.find("subtype");
if (subtype == object.end() || !subtype->second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: subtype must be a string";
return absl::nullopt;
}
result.subtype = subtype->second.string_value();
return result;
}
json11::Json::object RtpExtension_serialize(webrtc::RtpExtension const &rtpExtension) {
json11::Json::object object;
object.insert(std::make_pair("id", json11::Json(rtpExtension.id)));
object.insert(std::make_pair("uri", json11::Json(rtpExtension.uri)));
return object;
}
absl::optional<webrtc::RtpExtension> RtpExtension_parse(json11::Json::object const &object) {
const auto id = object.find("id");
if (id == object.end() || !id->second.is_number()) {
RTC_LOG(LS_ERROR) << "Signaling: id must be a number";
return absl::nullopt;
}
const auto uri = object.find("uri");
if (uri == object.end() || !uri->second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: uri must be a string";
return absl::nullopt;
}
return webrtc::RtpExtension(uri->second.string_value(), id->second.int_value());
}
json11::Json::object PayloadType_serialize(PayloadType const &payloadType) {
json11::Json::object object;
object.insert(std::make_pair("id", json11::Json((int)payloadType.id)));
object.insert(std::make_pair("name", json11::Json(payloadType.name)));
object.insert(std::make_pair("clockrate", json11::Json((int)payloadType.clockrate)));
object.insert(std::make_pair("channels", json11::Json((int)payloadType.channels)));
json11::Json::array feedbackTypes;
for (const auto &feedbackType : payloadType.feedbackTypes) {
feedbackTypes.push_back(FeedbackType_serialize(feedbackType));
}
object.insert(std::make_pair("feedbackTypes", json11::Json(std::move(feedbackTypes))));
json11::Json::object parameters;
for (auto it : payloadType.parameters) {
parameters.insert(std::make_pair(it.first, json11::Json(it.second)));
}
object.insert(std::make_pair("parameters", json11::Json(std::move(parameters))));
return object;
}
absl::optional<PayloadType> PayloadType_parse(json11::Json::object const &object) {
PayloadType result;
const auto id = object.find("id");
if (id == object.end() || !id->second.is_number()) {
RTC_LOG(LS_ERROR) << "Signaling: id must be a number";
return absl::nullopt;
}
result.id = id->second.int_value();
const auto name = object.find("name");
if (name == object.end() || !name->second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: name must be a string";
return absl::nullopt;
}
result.name = name->second.string_value();
const auto clockrate = object.find("clockrate");
if (clockrate == object.end() || !clockrate->second.is_number()) {
RTC_LOG(LS_ERROR) << "Signaling: clockrate must be a number";
return absl::nullopt;
}
result.clockrate = clockrate->second.int_value();
const auto channels = object.find("channels");
if (channels != object.end()) {
if (!channels->second.is_number()) {
RTC_LOG(LS_ERROR) << "Signaling: channels must be a number";
return absl::nullopt;
}
result.channels = channels->second.int_value();
}
const auto feedbackTypes = object.find("feedbackTypes");
if (feedbackTypes != object.end()) {
if (!feedbackTypes->second.is_array()) {
RTC_LOG(LS_ERROR) << "Signaling: feedbackTypes must be an array";
return absl::nullopt;
}
for (const auto &feedbackType : feedbackTypes->second.array_items()) {
if (!feedbackType.is_object()) {
RTC_LOG(LS_ERROR) << "Signaling: feedbackTypes items must be objects";
return absl::nullopt;
}
if (const auto parsedFeedbackType = FeedbackType_parse(feedbackType.object_items())) {
result.feedbackTypes.push_back(parsedFeedbackType.value());
} else {
RTC_LOG(LS_ERROR) << "Signaling: could not parse FeedbackType";
return absl::nullopt;
}
}
}
const auto parameters = object.find("parameters");
if (parameters != object.end()) {
if (!parameters->second.is_object()) {
RTC_LOG(LS_ERROR) << "Signaling: parameters must be an object";
return absl::nullopt;
}
for (const auto &item : parameters->second.object_items()) {
if (!item.second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: parameters items must be strings";
return absl::nullopt;
}
result.parameters.push_back(std::make_pair(item.first, item.second.string_value()));
}
}
return result;
}
json11::Json::object MediaContent_serialize(MediaContent const &mediaContent) {
json11::Json::object object;
std::string mappedType;
switch (mediaContent.type) {
case MediaContent::Type::Audio: {
mappedType = "audio";
break;
}
case MediaContent::Type::Video: {
mappedType = "video";
break;
}
default: {
RTC_FATAL() << "Unknown media type";
break;
}
}
object.insert(std::make_pair("type", mappedType));
object.insert(std::make_pair("ssrc", json11::Json(uint32ToString(mediaContent.ssrc))));
if (mediaContent.ssrcGroups.size() != 0) {
json11::Json::array ssrcGroups;
for (const auto &group : mediaContent.ssrcGroups) {
ssrcGroups.push_back(SsrcGroup_serialize(group));
}
object.insert(std::make_pair("ssrcGroups", json11::Json(std::move(ssrcGroups))));
}
if (mediaContent.payloadTypes.size() != 0) {
json11::Json::array payloadTypes;
for (const auto &payloadType : mediaContent.payloadTypes) {
payloadTypes.push_back(PayloadType_serialize(payloadType));
}
object.insert(std::make_pair("payloadTypes", json11::Json(std::move(payloadTypes))));
}
json11::Json::array rtpExtensions;
for (const auto &rtpExtension : mediaContent.rtpExtensions) {
rtpExtensions.push_back(RtpExtension_serialize(rtpExtension));
}
object.insert(std::make_pair("rtpExtensions", json11::Json(std::move(rtpExtensions))));
return object;
}
absl::optional<MediaContent> MediaContent_parse(json11::Json::object const &object) {
MediaContent result;
const auto type = object.find("type");
if (type == object.end() || !type->second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: type must be a string";
return absl::nullopt;
}
if (type->second.string_value() == "audio") {
result.type = MediaContent::Type::Audio;
} else if (type->second.string_value() == "video") {
result.type = MediaContent::Type::Video;
} else {
RTC_LOG(LS_ERROR) << "Signaling: type must be one of [\"audio\", \"video\"]";
return absl::nullopt;
}
const auto ssrc = object.find("ssrc");
if (ssrc == object.end()) {
RTC_LOG(LS_ERROR) << "Signaling: ssrc must be present";
return absl::nullopt;
}
if (ssrc->second.is_string()) {
result.ssrc = stringToUInt32(ssrc->second.string_value());
} else if (ssrc->second.is_number()) {
result.ssrc = (uint32_t)ssrc->second.number_value();
} else {
RTC_LOG(LS_ERROR) << "Signaling: ssrc must be a string or a number";
return absl::nullopt;
}
const auto ssrcGroups = object.find("ssrcGroups");
if (ssrcGroups != object.end()) {
if (!ssrcGroups->second.is_array()) {
RTC_LOG(LS_ERROR) << "Signaling: ssrcGroups must be an array";
return absl::nullopt;
}
for (const auto &ssrcGroup : ssrcGroups->second.array_items()) {
if (!ssrcGroup.is_object()) {
RTC_LOG(LS_ERROR) << "Signaling: ssrcsGroups items must be objects";
return absl::nullopt;
}
if (const auto parsedSsrcGroup = SsrcGroup_parse(ssrcGroup.object_items())) {
result.ssrcGroups.push_back(parsedSsrcGroup.value());
} else {
RTC_LOG(LS_ERROR) << "Signaling: could not parse SsrcGroup";
return absl::nullopt;
}
}
}
const auto payloadTypes = object.find("payloadTypes");
if (payloadTypes != object.end()) {
if (!payloadTypes->second.is_array()) {
RTC_LOG(LS_ERROR) << "Signaling: payloadTypes must be an array";
return absl::nullopt;
}
for (const auto &payloadType : payloadTypes->second.array_items()) {
if (!payloadType.is_object()) {
RTC_LOG(LS_ERROR) << "Signaling: payloadTypes items must be objects";
return absl::nullopt;
}
if (const auto parsedPayloadType = PayloadType_parse(payloadType.object_items())) {
result.payloadTypes.push_back(parsedPayloadType.value());
} else {
RTC_LOG(LS_ERROR) << "Signaling: could not parse PayloadType";
return absl::nullopt;
}
}
}
const auto rtpExtensions = object.find("rtpExtensions");
if (rtpExtensions != object.end()) {
if (!rtpExtensions->second.is_array()) {
RTC_LOG(LS_ERROR) << "Signaling: rtpExtensions must be an array";
return absl::nullopt;
}
for (const auto &rtpExtension : rtpExtensions->second.array_items()) {
if (!rtpExtension.is_object()) {
RTC_LOG(LS_ERROR) << "Signaling: rtpExtensions items must be objects";
return absl::nullopt;
}
if (const auto parsedRtpExtension = RtpExtension_parse(rtpExtension.object_items())) {
result.rtpExtensions.push_back(parsedRtpExtension.value());
} else {
RTC_LOG(LS_ERROR) << "Signaling: could not parse RtpExtension";
return absl::nullopt;
}
}
}
return result;
}
std::vector<uint8_t> InitialSetupMessage_serialize(const InitialSetupMessage * const message) {
json11::Json::object object;
object.insert(std::make_pair("@type", json11::Json("InitialSetup")));
object.insert(std::make_pair("ufrag", json11::Json(message->ufrag)));
object.insert(std::make_pair("pwd", json11::Json(message->pwd)));
object.insert(std::make_pair("renomination", json11::Json(message->supportsRenomination)));
json11::Json::array jsonFingerprints;
for (const auto &fingerprint : message->fingerprints) {
json11::Json::object jsonFingerprint;
jsonFingerprint.insert(std::make_pair("hash", json11::Json(fingerprint.hash)));
jsonFingerprint.insert(std::make_pair("setup", json11::Json(fingerprint.setup)));
jsonFingerprint.insert(std::make_pair("fingerprint", json11::Json(fingerprint.fingerprint)));
jsonFingerprints.emplace_back(std::move(jsonFingerprint));
}
object.insert(std::make_pair("fingerprints", json11::Json(std::move(jsonFingerprints))));
auto json = json11::Json(std::move(object));
std::string result = json.dump();
return std::vector<uint8_t>(result.begin(), result.end());
}
absl::optional<InitialSetupMessage> InitialSetupMessage_parse(json11::Json::object const &object) {
const auto ufrag = object.find("ufrag");
if (ufrag == object.end() || !ufrag->second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: ufrag must be a string";
return absl::nullopt;
}
const auto pwd = object.find("pwd");
if (pwd == object.end() || !pwd->second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: pwd must be a string";
return absl::nullopt;
}
const auto renomination = object.find("renomination");
bool renominationValue = false;
if (renomination != object.end() && renomination->second.is_bool()) {
renominationValue = renomination->second.bool_value();
}
const auto fingerprints = object.find("fingerprints");
if (fingerprints == object.end() || !fingerprints->second.is_array()) {
RTC_LOG(LS_ERROR) << "Signaling: fingerprints must be an array";
return absl::nullopt;
}
std::vector<DtlsFingerprint> parsedFingerprints;
for (const auto &fingerprintObject : fingerprints->second.array_items()) {
if (!fingerprintObject.is_object()) {
RTC_LOG(LS_ERROR) << "Signaling: fingerprints items must be objects";
return absl::nullopt;
}
const auto hash = fingerprintObject.object_items().find("hash");
if (hash == fingerprintObject.object_items().end() || !hash->second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: hash must be a string";
return absl::nullopt;
}
const auto setup = fingerprintObject.object_items().find("setup");
if (setup == fingerprintObject.object_items().end() || !setup->second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: setup must be a string";
return absl::nullopt;
}
const auto fingerprint = fingerprintObject.object_items().find("fingerprint");
if (fingerprint == fingerprintObject.object_items().end() || !fingerprint->second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: fingerprint must be a string";
return absl::nullopt;
}
DtlsFingerprint parsedFingerprint;
parsedFingerprint.hash = hash->second.string_value();
parsedFingerprint.setup = setup->second.string_value();
parsedFingerprint.fingerprint = fingerprint->second.string_value();
parsedFingerprints.push_back(std::move(parsedFingerprint));
}
InitialSetupMessage message;
message.ufrag = ufrag->second.string_value();
message.pwd = pwd->second.string_value();
message.supportsRenomination = renominationValue;
message.fingerprints = std::move(parsedFingerprints);
return message;
}
std::vector<uint8_t> NegotiateChannelsMessage_serialize(const NegotiateChannelsMessage * const message) {
json11::Json::object object;
object.insert(std::make_pair("@type", json11::Json("NegotiateChannels")));
object.insert(std::make_pair("exchangeId", json11::Json(uint32ToString(message->exchangeId))));
json11::Json::array contents;
for (const auto &content : message->contents) {
contents.push_back(json11::Json(MediaContent_serialize(content)));
}
object.insert(std::make_pair("contents", std::move(contents)));
auto json = json11::Json(std::move(object));
std::string result = json.dump();
return std::vector<uint8_t>(result.begin(), result.end());
}
absl::optional<NegotiateChannelsMessage> NegotiateChannelsMessage_parse(json11::Json::object const &object) {
NegotiateChannelsMessage message;
const auto exchangeId = object.find("exchangeId");
if (exchangeId == object.end()) {
RTC_LOG(LS_ERROR) << "Signaling: exchangeId must be present";
return absl::nullopt;
} else if (exchangeId->second.is_string()) {
message.exchangeId = stringToUInt32(exchangeId->second.string_value());
} else if (exchangeId->second.is_number()) {
message.exchangeId = (uint32_t)exchangeId->second.number_value();
} else {
RTC_LOG(LS_ERROR) << "Signaling: exchangeId must be a string or a number";
return absl::nullopt;
}
const auto contents = object.find("contents");
if (contents != object.end()) {
if (!contents->second.is_array()) {
RTC_LOG(LS_ERROR) << "Signaling: contents must be an array";
return absl::nullopt;
}
for (const auto &content : contents->second.array_items()) {
if (!content.is_object()) {
RTC_LOG(LS_ERROR) << "Signaling: contents items must be objects";
return absl::nullopt;
}
if (auto parsedContent = MediaContent_parse(content.object_items())) {
message.contents.push_back(std::move(parsedContent.value()));
} else {
RTC_LOG(LS_ERROR) << "Signaling: could not parse MediaContent";
return absl::nullopt;
}
}
}
return message;
}
json11::Json::object ConnectionAddress_serialize(ConnectionAddress const &connectionAddress) {
json11::Json::object object;
object.insert(std::make_pair("ip", json11::Json(connectionAddress.ip)));
object.insert(std::make_pair("port", json11::Json(connectionAddress.port)));
return object;
}
absl::optional<ConnectionAddress> ConnectionAddress_parse(json11::Json::object const &object) {
const auto ip = object.find("ip");
if (ip == object.end() || !ip->second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: ip must be a string";
return absl::nullopt;
}
const auto port = object.find("port");
if (port == object.end() || !port->second.is_number()) {
RTC_LOG(LS_ERROR) << "Signaling: port must be a number";
return absl::nullopt;
}
ConnectionAddress address;
address.ip = ip->second.string_value();
address.port = port->second.int_value();
return address;
}
std::vector<uint8_t> CandidatesMessage_serialize(const CandidatesMessage * const message) {
json11::Json::array candidates;
for (const auto &candidate : message->iceCandidates) {
json11::Json::object candidateObject;
candidateObject.insert(std::make_pair("sdpString", json11::Json(candidate.sdpString)));
candidates.emplace_back(std::move(candidateObject));
}
json11::Json::object object;
object.insert(std::make_pair("@type", json11::Json("Candidates")));
object.insert(std::make_pair("candidates", json11::Json(std::move(candidates))));
auto json = json11::Json(std::move(object));
std::string result = json.dump();
return std::vector<uint8_t>(result.begin(), result.end());
}
absl::optional<CandidatesMessage> CandidatesMessage_parse(json11::Json::object const &object) {
const auto candidates = object.find("candidates");
if (candidates == object.end() || !candidates->second.is_array()) {
RTC_LOG(LS_ERROR) << "Signaling: candidates must be an array";
return absl::nullopt;
}
std::vector<IceCandidate> parsedCandidates;
for (const auto &candidateObject : candidates->second.array_items()) {
if (!candidateObject.is_object()) {
RTC_LOG(LS_ERROR) << "Signaling: candidates items must be objects";
return absl::nullopt;
}
IceCandidate candidate;
const auto sdpString = candidateObject.object_items().find("sdpString");
if (sdpString == candidateObject.object_items().end() || !sdpString->second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: sdpString must be a string";
return absl::nullopt;
}
candidate.sdpString = sdpString->second.string_value();
parsedCandidates.push_back(std::move(candidate));
}
CandidatesMessage message;
message.iceCandidates = std::move(parsedCandidates);
return message;
}
std::vector<uint8_t> MediaStateMessage_serialize(const MediaStateMessage * const message) {
json11::Json::object object;
object.insert(std::make_pair("@type", json11::Json("MediaState")));
object.insert(std::make_pair("muted", json11::Json(message->isMuted)));
object.insert(std::make_pair("lowBattery", json11::Json(message->isBatteryLow)));
std::string videoStateValue;
switch (message->videoState) {
case MediaStateMessage::VideoState::Inactive: {
videoStateValue = "inactive";
break;
}
case MediaStateMessage::VideoState::Suspended: {
videoStateValue = "suspended";
break;
}
case MediaStateMessage::VideoState::Active: {
videoStateValue = "active";
break;
}
default: {
RTC_FATAL() << "Unknown videoState";
break;
}
}
object.insert(std::make_pair("videoState", json11::Json(videoStateValue)));
int videoRotationValue = 0;
switch (message->videoRotation) {
case MediaStateMessage::VideoRotation::Rotation0: {
videoRotationValue = 0;
break;
}
case MediaStateMessage::VideoRotation::Rotation90: {
videoRotationValue = 90;
break;
}
case MediaStateMessage::VideoRotation::Rotation180: {
videoRotationValue = 180;
break;
}
case MediaStateMessage::VideoRotation::Rotation270: {
videoRotationValue = 270;
break;
}
default: {
RTC_FATAL() << "Unknown videoRotation";
break;
}
}
object.insert(std::make_pair("videoRotation", json11::Json(videoRotationValue)));
std::string screencastStateValue;
switch (message->screencastState) {
case MediaStateMessage::VideoState::Inactive: {
screencastStateValue = "inactive";
break;
}
case MediaStateMessage::VideoState::Suspended: {
screencastStateValue = "suspended";
break;
}
case MediaStateMessage::VideoState::Active: {
screencastStateValue = "active";
break;
}
default: {
RTC_FATAL() << "Unknown videoState";
break;
}
}
object.insert(std::make_pair("screencastState", json11::Json(screencastStateValue)));
auto json = json11::Json(std::move(object));
std::string result = json.dump();
return std::vector<uint8_t>(result.begin(), result.end());
}
absl::optional<MediaStateMessage> MediaStateMessage_parse(json11::Json::object const &object) {
MediaStateMessage message;
const auto muted = object.find("muted");
if (muted != object.end()) {
if (!muted->second.is_bool()) {
RTC_LOG(LS_ERROR) << "Signaling: muted must be a bool";
return absl::nullopt;
}
message.isMuted = muted->second.bool_value();
}
const auto lowBattery = object.find("lowBattery");
if (lowBattery != object.end()) {
if (!lowBattery->second.is_bool()) {
RTC_LOG(LS_ERROR) << "Signaling: lowBattery must be a bool";
return absl::nullopt;
}
message.isBatteryLow = lowBattery->second.bool_value();
}
const auto videoState = object.find("videoState");
if (videoState != object.end()) {
if (!videoState->second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: videoState must be a string";
return absl::nullopt;
}
if (videoState->second.string_value() == "inactive") {
message.videoState = MediaStateMessage::VideoState::Inactive;
} else if (videoState->second.string_value() == "suspended") {
message.videoState = MediaStateMessage::VideoState::Suspended;
} else if (videoState->second.string_value() == "active") {
message.videoState = MediaStateMessage::VideoState::Active;
} else {
RTC_LOG(LS_ERROR) << "videoState must be one of [\"inactive\", \"suspended\", \"active\"]";
}
} else {
message.videoState = MediaStateMessage::VideoState::Inactive;
}
const auto screencastState = object.find("screencastState");
if (screencastState != object.end()) {
if (!screencastState->second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: screencastState must be a string";
return absl::nullopt;
}
if (screencastState->second.string_value() == "inactive") {
message.screencastState = MediaStateMessage::VideoState::Inactive;
} else if (screencastState->second.string_value() == "suspended") {
message.screencastState = MediaStateMessage::VideoState::Suspended;
} else if (screencastState->second.string_value() == "active") {
message.screencastState = MediaStateMessage::VideoState::Active;
} else {
RTC_LOG(LS_ERROR) << "Signaling: screencastState must be one of [\"inactive\", \"suspended\", \"active\"]";
}
} else {
message.screencastState = MediaStateMessage::VideoState::Inactive;
}
const auto videoRotation = object.find("videoRotation");
if (videoRotation != object.end()) {
if (!videoRotation->second.is_number()) {
RTC_LOG(LS_ERROR) << "Signaling: videoRotation must be a number";
return absl::nullopt;
}
if (videoState->second.int_value() == 0) {
message.videoRotation = MediaStateMessage::VideoRotation::Rotation0;
} else if (videoState->second.int_value() == 90) {
message.videoRotation = MediaStateMessage::VideoRotation::Rotation90;
} else if (videoState->second.int_value() == 180) {
message.videoRotation = MediaStateMessage::VideoRotation::Rotation180;
} else if (videoState->second.int_value() == 270) {
message.videoRotation = MediaStateMessage::VideoRotation::Rotation270;
} else {
RTC_LOG(LS_ERROR) << "Signaling: videoRotation must be one of [0, 90, 180, 270]";
message.videoRotation = MediaStateMessage::VideoRotation::Rotation0;
}
} else {
message.videoRotation = MediaStateMessage::VideoRotation::Rotation0;
}
return message;
}
std::vector<uint8_t> Message::serialize() const {
if (const auto initialSetup = absl::get_if<InitialSetupMessage>(&data)) {
return InitialSetupMessage_serialize(initialSetup);
} else if (const auto candidates = absl::get_if<CandidatesMessage>(&data)) {
return CandidatesMessage_serialize(candidates);
} else if (const auto mediaState = absl::get_if<MediaStateMessage>(&data)) {
return MediaStateMessage_serialize(mediaState);
} else if (const auto negotiateChannels = absl::get_if<NegotiateChannelsMessage>(&data)) {
return NegotiateChannelsMessage_serialize(negotiateChannels);
} else {
return {};
}
}
absl::optional<Message> Message::parse(const std::vector<uint8_t> &data) {
std::string parsingError;
auto json = json11::Json::parse(std::string(data.begin(), data.end()), parsingError);
if (json.type() != json11::Json::OBJECT) {
RTC_LOG(LS_ERROR) << "Signaling: message must be an object";
return absl::nullopt;
}
auto type = json.object_items().find("@type");
if (type == json.object_items().end()) {
RTC_LOG(LS_ERROR) << "Signaling: message does not contain @type attribute";
return absl::nullopt;
}
if (!type->second.is_string()) {
RTC_LOG(LS_ERROR) << "Signaling: @type attribute must be a string";
return absl::nullopt;
}
if (type->second.string_value() == "InitialSetup") {
auto parsed = InitialSetupMessage_parse(json.object_items());
if (!parsed) {
RTC_LOG(LS_ERROR) << "Signaling: could not parse " << type->second.string_value() << " message";
return absl::nullopt;
}
Message message;
message.data = std::move(parsed.value());
return message;
} else if (type->second.string_value() == "NegotiateChannels") {
auto parsed = NegotiateChannelsMessage_parse(json.object_items());
if (!parsed) {
RTC_LOG(LS_ERROR) << "Signaling: could not parse " << type->second.string_value() << " message";
return absl::nullopt;
}
Message message;
message.data = std::move(parsed.value());
return message;
} else if (type->second.string_value() == "Candidates") {
auto parsed = CandidatesMessage_parse(json.object_items());
if (!parsed) {
RTC_LOG(LS_ERROR) << "Signaling: could not parse " << type->second.string_value() << " message";
return absl::nullopt;
}
Message message;
message.data = std::move(parsed.value());
return message;
} else if (type->second.string_value() == "MediaState") {
auto parsed = MediaStateMessage_parse(json.object_items());
if (!parsed) {
RTC_LOG(LS_ERROR) << "Signaling: could not parse " << type->second.string_value() << " message";
return absl::nullopt;
}
Message message;
message.data = std::move(parsed.value());
return message;
} else {
RTC_LOG(LS_ERROR) << "Signaling: unknown message type " << type->second.string_value();
return absl::nullopt;
}
}
} // namespace signaling
} // namespace tgcalls

View file

@ -0,0 +1,191 @@
#ifndef TGCALLS_SIGNALING_H
#define TGCALLS_SIGNALING_H
#include <string>
#include <vector>
#include "absl/types/variant.h"
#include "absl/types/optional.h"
#include "api/rtp_parameters.h"
namespace tgcalls {
namespace signaling {
struct DtlsFingerprint {
std::string hash;
std::string setup;
std::string fingerprint;
};
struct ConnectionAddress {
std::string ip;
int port = 0;
};
struct IceCandidate {
std::string sdpString;
};
struct SsrcGroup {
std::vector<uint32_t> ssrcs;
std::string semantics;
bool operator==(SsrcGroup const &rhs) const {
if (ssrcs != rhs.ssrcs) {
return false;
}
if (semantics != rhs.semantics) {
return false;
}
return true;
}
};
struct FeedbackType {
std::string type;
std::string subtype;
bool operator==(FeedbackType const &rhs) const {
if (type != rhs.type) {
return false;
}
if (subtype != rhs.subtype) {
return false;
}
return true;
}
};
struct PayloadType {
uint32_t id = 0;
std::string name;
uint32_t clockrate = 0;
uint32_t channels = 0;
std::vector<FeedbackType> feedbackTypes;
std::vector<std::pair<std::string, std::string>> parameters;
bool operator==(PayloadType const &rhs) const {
if (id != rhs.id) {
return false;
}
if (name != rhs.name) {
return false;
}
if (clockrate != rhs.clockrate) {
return false;
}
if (channels != rhs.channels) {
return false;
}
if (feedbackTypes != rhs.feedbackTypes) {
return false;
}
if (parameters != rhs.parameters) {
return false;
}
return true;
}
};
struct MediaContent {
enum class Type {
Audio,
Video
};
Type type = Type::Audio;
uint32_t ssrc = 0;
std::vector<SsrcGroup> ssrcGroups;
std::vector<PayloadType> payloadTypes;
std::vector<webrtc::RtpExtension> rtpExtensions;
bool operator==(const MediaContent& rhs) const {
if (type != rhs.type) {
return false;
}
if (ssrc != rhs.ssrc) {
return false;
}
if (ssrcGroups != rhs.ssrcGroups) {
return false;
}
std::vector<PayloadType> sortedPayloadTypes = payloadTypes;
std::sort(sortedPayloadTypes.begin(), sortedPayloadTypes.end(), [](PayloadType const &lhs, PayloadType const &rhs) {
return lhs.id < rhs.id;
});
std::vector<PayloadType> sortedRhsPayloadTypes = rhs.payloadTypes;
std::sort(sortedRhsPayloadTypes.begin(), sortedRhsPayloadTypes.end(), [](PayloadType const &lhs, PayloadType const &rhs) {
return lhs.id < rhs.id;
});
if (sortedPayloadTypes != sortedRhsPayloadTypes) {
return false;
}
if (rtpExtensions != rhs.rtpExtensions) {
return false;
}
return true;
}
};
struct InitialSetupMessage {
std::string ufrag;
std::string pwd;
bool supportsRenomination = false;
std::vector<DtlsFingerprint> fingerprints;
};
struct NegotiateChannelsMessage {
uint32_t exchangeId = 0;
std::vector<MediaContent> contents;
};
struct CandidatesMessage {
std::vector<IceCandidate> iceCandidates;
};
struct MediaStateMessage {
enum class VideoState {
Inactive,
Suspended,
Active
};
enum class VideoRotation {
Rotation0,
Rotation90,
Rotation180,
Rotation270
};
bool isMuted = false;
VideoState videoState = VideoState::Inactive;
VideoRotation videoRotation = VideoRotation::Rotation0;
VideoState screencastState = VideoState::Inactive;
bool isBatteryLow = false;
};
struct Message {
absl::variant<
InitialSetupMessage,
NegotiateChannelsMessage,
CandidatesMessage,
MediaStateMessage> data;
std::vector<uint8_t> serialize() const;
static absl::optional<Message> parse(const std::vector<uint8_t> &data);
};
};
} // namespace tgcalls
#endif

View file

@ -0,0 +1,8 @@
#include "v2/SignalingConnection.h"
namespace tgcalls {
SignalingConnection::SignalingConnection() {
}
}

View file

@ -0,0 +1,26 @@
#ifndef TGCALLS_SIGNALING_CONNECTION_H_
#define TGCALLS_SIGNALING_CONNECTION_H_
#include <memory>
#include <vector>
namespace webrtc {
}
namespace tgcalls {
class SignalingConnection {
public:
SignalingConnection();
virtual ~SignalingConnection() = default;
virtual void start() = 0;
virtual void send(const std::vector<uint8_t> &data) = 0;
virtual void receiveExternal(const std::vector<uint8_t> &data) {
}
};
} // namespace tgcalls
#endif // TGCALLS_SIGNALING_CONNECTION_H_

View file

@ -0,0 +1,22 @@
#include "v2/SignalingEncryption.h"
namespace tgcalls {
SignalingEncryption::SignalingEncryption(EncryptionKey const &encryptionKey) {
_connection.reset(new EncryptedConnection(EncryptedConnection::Type::Signaling, encryptionKey, [](int, int) {
}));
}
SignalingEncryption::~SignalingEncryption() {
}
absl::optional<rtc::CopyOnWriteBuffer> SignalingEncryption::encryptOutgoing(std::vector<uint8_t> const &data) {
return _connection->encryptRawPacket(rtc::CopyOnWriteBuffer(data.data(), data.size()));
}
absl::optional<rtc::CopyOnWriteBuffer> SignalingEncryption::decryptIncoming(std::vector<uint8_t> const &data) {
return _connection->decryptRawPacket(rtc::CopyOnWriteBuffer(data.data(), data.size()));
}
} // namespace tgcalls

View file

@ -0,0 +1,23 @@
#ifndef TGCALLS_SIGNALING_ENCRYPTION_H
#define TGCALLS_SIGNALING_ENCRYPTION_H
#include "Instance.h"
#include "EncryptedConnection.h"
namespace tgcalls {
class SignalingEncryption {
public:
SignalingEncryption(EncryptionKey const &encryptionKey);
~SignalingEncryption();
absl::optional<rtc::CopyOnWriteBuffer> encryptOutgoing(std::vector<uint8_t> const &data);
absl::optional<rtc::CopyOnWriteBuffer> decryptIncoming(std::vector<uint8_t> const &data);
private:
std::unique_ptr<EncryptedConnection> _connection;
};
} // namespace tgcalls
#endif

View file

@ -0,0 +1,126 @@
#include "v2/SignalingKcpConnection.h"
#include <random>
#ifndef _MSC_VER
#include <sys/time.h>
#endif
#include "rtc_base/async_tcp_socket.h"
#include "p2p/base/basic_packet_socket_factory.h"
#include "rtc_base/logging.h"
#include "rtc_base/thread.h"
#include "p2p/base/packet_transport_internal.h"
#include "FieldTrialsConfig.h"
/* get system time */
static inline void itimeofday(long *sec, long *usec) {
#ifdef _MSC_VER
SYSTEMTIME system_time;
FILETIME file_time;
uint64_t time;
GetSystemTime( &system_time );
SystemTimeToFileTime( &system_time, &file_time );
time = ((uint64_t)file_time.dwLowDateTime ) ;
time += ((uint64_t)file_time.dwHighDateTime) << 32;
// Windows FILETIME epoch (1601-01-01) to Unix epoch (1970-01-01) in 100-nanosecond intervals
constexpr auto EPOCH = 11644473600000000ULL;
if (sec) *sec = (long) ((time - EPOCH) / 10000000L);
if (usec) *usec = (long) (system_time.wMilliseconds * 1000);
#else
struct timeval time;
gettimeofday(&time, NULL);
if (sec) *sec = time.tv_sec;
if (usec) *usec = time.tv_usec;
#endif
}
/* get clock in millisecond 64 */
static inline IINT64 iclock64(void) {
long s, u;
IINT64 value;
itimeofday(&s, &u);
value = ((IINT64)s) * 1000 + (u / 1000);
return value;
}
static inline IUINT32 iclock() {
return (IUINT32)(iclock64() & 0xfffffffful);
}
namespace tgcalls {
SignalingKcpConnection::SignalingKcpConnection(std::shared_ptr<Threads> threads, std::function<void(const std::vector<uint8_t> &)> onIncomingData, std::function<void(const std::vector<uint8_t> &)> emitData) :
_threads(threads),
_emitData(emitData),
_onIncomingData(onIncomingData) {
_receiveBuffer.resize(512 * 1024);
_kcp = ikcp_create(0, this);
_kcp->output = &SignalingKcpConnection::udpOutput;
ikcp_wndsize(_kcp, 128, 128);
ikcp_nodelay(_kcp, 0, 10, 0, 0);
//_onIncomingData
}
int SignalingKcpConnection::udpOutput(const char *buf, int len, ikcpcb *kcp, void *user) {
SignalingKcpConnection *connection = (SignalingKcpConnection *)user;
if (connection) {
connection->_emitData(std::vector<uint8_t>(buf, buf + len));
}
return 0;
}
SignalingKcpConnection::~SignalingKcpConnection() {
}
void SignalingKcpConnection::start() {
scheduleInternalUpdate(0);
}
void SignalingKcpConnection::scheduleInternalUpdate(int timeoutMs) {
const auto weak = std::weak_ptr<SignalingKcpConnection>(shared_from_this());
_threads->getMediaThread()->PostDelayedTask([weak]() {
auto strong = weak.lock();
if (!strong) {
return;
}
strong->performInternalUpdate();
strong->scheduleInternalUpdate(10);
}, webrtc::TimeDelta::Millis(timeoutMs));
}
void SignalingKcpConnection::performInternalUpdate() {
ikcp_update(_kcp, iclock());
while (true) {
int result = ikcp_recv(_kcp, (char *)_receiveBuffer.data(), (int)_receiveBuffer.size());
if (result >= 0) {
std::vector<uint8_t> packet;
packet.resize(result);
std::copy(_receiveBuffer.begin(), _receiveBuffer.begin() + result, packet.begin());
_onIncomingData(packet);
} else {
break;
}
}
}
void SignalingKcpConnection::receiveExternal(const std::vector<uint8_t> &data) {
ikcp_input(_kcp, (const char *)data.data(), (long)data.size());
}
void SignalingKcpConnection::send(const std::vector<uint8_t> &data) {
ikcp_send(_kcp, (const char *)data.data(), (int)data.size());
}
}

View file

@ -0,0 +1,57 @@
#ifndef TGCALLS_SIGNALING_KCP_CONNECTION_H_
#define TGCALLS_SIGNALING_KCP_CONNECTION_H_
#include "rtc_base/third_party/sigslot/sigslot.h"
#include "rtc_base/socket_address.h"
#include "rtc_base/copy_on_write_buffer.h"
#include "rtc_base/byte_buffer.h"
#include "media/base/media_channel.h"
#include <vector>
#include <absl/types/optional.h>
#include "StaticThreads.h"
#include "SignalingConnection.h"
#include "ikcp.h"
namespace rtc {
class Socket;
}
namespace cricket {
class SctpTransportFactory;
class SctpTransportInternal;
};
namespace tgcalls {
class SignalingPacketTransport;
class SignalingKcpConnection : public sigslot::has_slots<>, public SignalingConnection, public std::enable_shared_from_this<SignalingKcpConnection> {
public:
SignalingKcpConnection(std::shared_ptr<Threads> threads, std::function<void(const std::vector<uint8_t> &)> onIncomingData, std::function<void(const std::vector<uint8_t> &)> emitData);
virtual ~SignalingKcpConnection();
virtual void receiveExternal(const std::vector<uint8_t> &data) override;
virtual void start() override;
virtual void send(const std::vector<uint8_t> &data) override;
private:
void scheduleInternalUpdate(int timeoutMs);
void performInternalUpdate();
static int udpOutput(const char *buf, int len, ikcpcb *kcp, void *user);
private:
std::shared_ptr<Threads> _threads;
std::function<void(const std::vector<uint8_t> &)> _emitData;
std::function<void(const std::vector<uint8_t> &)> _onIncomingData;
ikcpcb *_kcp = nullptr;
std::vector<uint8_t> _receiveBuffer;
};
} // namespace tgcalls
#endif // TGCALLS_SIGNALING_SCTP_CONNECTION_H_

View file

@ -0,0 +1,187 @@
#include "v2/SignalingSctpConnection.h"
#include <random>
#include "rtc_base/async_tcp_socket.h"
#include "p2p/base/basic_packet_socket_factory.h"
#include "rtc_base/logging.h"
#include "p2p/base/packet_transport_internal.h"
#include "media/sctp/sctp_transport_factory.h"
#include "FieldTrialsConfig.h"
namespace tgcalls {
class SignalingPacketTransport : public rtc::PacketTransportInternal {
public:
SignalingPacketTransport(std::shared_ptr<Threads> threads, std::function<void(const std::vector<uint8_t> &)> emitData) :
_threads(threads),
_emitData(emitData),
_transportName("signaling") {
}
virtual ~SignalingPacketTransport() {
}
void receiveData(std::vector<uint8_t> const &data) {
RTC_LOG(LS_INFO) << "SignalingPacketTransport: adding data of " << data.size() << " bytes";
SignalReadPacket.emit(this, (const char *)data.data(), data.size(), -1, 0);
}
virtual const std::string& transport_name() const override {
return _transportName;
}
virtual bool writable() const override {
return true;
}
virtual bool receiving() const override {
return false;
}
// Attempts to send the given packet.
// The return value is < 0 on failure. The return value in failure case is not
// descriptive. Depending on failure cause and implementation details
// GetError() returns an descriptive errno.h error value.
// This mimics posix socket send() or sendto() behavior.
// TODO(johan): Reliable, meaningful, consistent error codes for all
// implementations would be nice.
// TODO(johan): Remove the default argument once channel code is updated.
virtual int SendPacket(const char* data,
size_t len,
const rtc::PacketOptions& options,
int flags = 0) override {
_emitData(std::vector<uint8_t>(data, data + len));
rtc::SentPacket sentPacket;
sentPacket.packet_id = options.packet_id;
SignalSentPacket.emit(this, sentPacket);
return (int)len;
}
virtual int SetOption(rtc::Socket::Option opt, int value) override {
return 0;
}
virtual bool GetOption(rtc::Socket::Option opt, int* value) override {
return 0;
}
virtual int GetError() override {
return 0;
}
virtual absl::optional<rtc::NetworkRoute> network_route() const override {
return absl::nullopt;
}
private:
std::shared_ptr<Threads> _threads;
std::function<void(const std::vector<uint8_t> &)> _onIncomingData;
std::function<void(const std::vector<uint8_t> &)> _emitData;
std::string _transportName;
};
SignalingSctpConnection::SignalingSctpConnection(std::shared_ptr<Threads> threads, std::function<void(const std::vector<uint8_t> &)> onIncomingData, std::function<void(const std::vector<uint8_t> &)> emitData) :
_threads(threads),
_emitData(emitData),
_onIncomingData(onIncomingData) {
_threads->getNetworkThread()->BlockingCall([&]() {
_packetTransport = std::make_unique<SignalingPacketTransport>(threads, emitData);
_sctpTransportFactory.reset(new cricket::SctpTransportFactory(_threads->getNetworkThread()));
_sctpTransport = _sctpTransportFactory->CreateSctpTransport(_packetTransport.get());
_sctpTransport->OpenStream(0);
_sctpTransport->SetDataChannelSink(this);
// TODO: should we disconnect the data channel sink?
_sctpTransport->Start(5000, 5000, 262144);
});
}
void SignalingSctpConnection::OnReadyToSend() {
assert(_threads->getNetworkThread()->IsCurrent());
_isReadyToSend = true;
auto pendingData = _pendingData;
_pendingData.clear();
for (const auto &data : pendingData) {
webrtc::SendDataParams params;
params.type = webrtc::DataMessageType::kBinary;
params.ordered = true;
rtc::CopyOnWriteBuffer payload;
payload.AppendData(data.data(), data.size());
webrtc::RTCError sendError = _sctpTransport->SendData(0, params, payload);
if (sendError.ok()) {
RTC_LOG(LS_INFO) << "SignalingSctpConnection: sent data of " << data.size() << " bytes";
} else {
_isReadyToSend = false;
_pendingData.push_back(data);
RTC_LOG(LS_INFO) << "SignalingSctpConnection: send error, storing data until ready to send (" << _pendingData.size() << " items)";
}
}
}
void SignalingSctpConnection::OnTransportClosed(webrtc::RTCError error) {
assert(_threads->getNetworkThread()->IsCurrent());
}
void SignalingSctpConnection::OnDataReceived(int channel_id, webrtc::DataMessageType type, const rtc::CopyOnWriteBuffer& buffer) {
assert(_threads->getNetworkThread()->IsCurrent());
_onIncomingData(std::vector<uint8_t>(buffer.data(), buffer.data() + buffer.size()));
}
SignalingSctpConnection::~SignalingSctpConnection() {
_threads->getNetworkThread()->BlockingCall([&]() {
_sctpTransport.reset();
_sctpTransportFactory.reset();
_packetTransport.reset();
});
}
void SignalingSctpConnection::start() {
}
void SignalingSctpConnection::receiveExternal(const std::vector<uint8_t> &data) {
_threads->getNetworkThread()->BlockingCall([&]() {
_packetTransport->receiveData(data);
});
}
void SignalingSctpConnection::send(const std::vector<uint8_t> &data) {
_threads->getNetworkThread()->BlockingCall([&]() {
if (_isReadyToSend) {
webrtc::SendDataParams params;
params.type = webrtc::DataMessageType::kBinary;
params.ordered = true;
rtc::CopyOnWriteBuffer payload;
payload.AppendData(data.data(), data.size());
webrtc::RTCError sendError = _sctpTransport->SendData(0, params, payload);
if (!sendError.ok()) {
_isReadyToSend = false;
_pendingData.push_back(data);
RTC_LOG(LS_INFO) << "SignalingSctpConnection: send error, storing data until ready to send (" << _pendingData.size() << " items)";
} else {
RTC_LOG(LS_INFO) << "SignalingSctpConnection: sent data of " << data.size() << " bytes";
}
} else {
_pendingData.push_back(data);
RTC_LOG(LS_INFO) << "SignalingSctpConnection: not ready to send, storing data until ready to send (" << _pendingData.size() << " items)";
}
});
}
}

View file

@ -0,0 +1,79 @@
#ifndef TGCALLS_SIGNALING_SCTP_CONNECTION_H_
#define TGCALLS_SIGNALING_SCTP_CONNECTION_H_
#ifdef WEBRTC_WIN
#include <WinSock2.h>
#endif // WEBRTC_WIN
#include "rtc_base/third_party/sigslot/sigslot.h"
#include "rtc_base/socket_address.h"
#include "rtc_base/copy_on_write_buffer.h"
#include "rtc_base/byte_buffer.h"
#include "media/base/media_channel.h"
#include <vector>
#include <absl/types/optional.h>
#include "StaticThreads.h"
#include "SignalingConnection.h"
namespace rtc {
class Socket;
}
namespace cricket {
class SctpTransportFactory;
class SctpTransportInternal;
};
namespace tgcalls {
class SignalingPacketTransport;
class SignalingSctpConnection : public sigslot::has_slots<>, public SignalingConnection, public webrtc::DataChannelSink {
private:
struct PacketReadState {
rtc::CopyOnWriteBuffer headerData;
int remainingHeaderSize = 0;
bool isHeaderCompleted = false;
rtc::CopyOnWriteBuffer data;
int remainingDataSize = 0;
bool isDataCompleted = false;
};
public:
SignalingSctpConnection(std::shared_ptr<Threads> threads, std::function<void(const std::vector<uint8_t> &)> onIncomingData, std::function<void(const std::vector<uint8_t> &)> emitData);
virtual ~SignalingSctpConnection();
virtual void receiveExternal(const std::vector<uint8_t> &data) override;
virtual void start() override;
virtual void send(const std::vector<uint8_t> &data) override;
virtual void OnDataReceived(int channel_id,
webrtc::DataMessageType type,
const rtc::CopyOnWriteBuffer& buffer) override;
virtual void OnReadyToSend() override;
virtual void OnTransportClosed(webrtc::RTCError error) override;
// Unused
virtual void OnChannelClosing(int channel_id) override{}
virtual void OnChannelClosed(int channel_id) override{}
private:
std::shared_ptr<Threads> _threads;
std::function<void(const std::vector<uint8_t> &)> _emitData;
std::function<void(const std::vector<uint8_t> &)> _onIncomingData;
std::unique_ptr<SignalingPacketTransport> _packetTransport;
std::unique_ptr<cricket::SctpTransportFactory> _sctpTransportFactory;
std::unique_ptr<cricket::SctpTransportInternal> _sctpTransport;
bool _isReadyToSend = false;
std::vector<std::vector<uint8_t>> _pendingData;
};
} // namespace tgcalls
#endif // TGCALLS_SIGNALING_SCTP_CONNECTION_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,416 @@
//=====================================================================
//
// KCP - A Better ARQ Protocol Implementation
// skywind3000 (at) gmail.com, 2010-2011
//
// Features:
// + Average RTT reduce 30% - 40% vs traditional ARQ like tcp.
// + Maximum RTT reduce three times vs tcp.
// + Lightweight, distributed as a single source file.
//
//=====================================================================
#ifndef __IKCP_H__
#define __IKCP_H__
#include <stddef.h>
#include <stdlib.h>
#include <assert.h>
//=====================================================================
// 32BIT INTEGER DEFINITION
//=====================================================================
#ifndef __INTEGER_32_BITS__
#define __INTEGER_32_BITS__
#if defined(_WIN64) || defined(WIN64) || defined(__amd64__) || \
defined(__x86_64) || defined(__x86_64__) || defined(_M_IA64) || \
defined(_M_AMD64)
typedef unsigned int ISTDUINT32;
typedef int ISTDINT32;
#elif defined(_WIN32) || defined(WIN32) || defined(__i386__) || \
defined(__i386) || defined(_M_X86)
typedef unsigned long ISTDUINT32;
typedef long ISTDINT32;
#elif defined(__MACOS__)
typedef UInt32 ISTDUINT32;
typedef SInt32 ISTDINT32;
#elif defined(__APPLE__) && defined(__MACH__)
#include <sys/types.h>
typedef u_int32_t ISTDUINT32;
typedef int32_t ISTDINT32;
#elif defined(__BEOS__)
#include <sys/inttypes.h>
typedef u_int32_t ISTDUINT32;
typedef int32_t ISTDINT32;
#elif (defined(_MSC_VER) || defined(__BORLANDC__)) && (!defined(__MSDOS__))
typedef unsigned __int32 ISTDUINT32;
typedef __int32 ISTDINT32;
#elif defined(__GNUC__)
#include <stdint.h>
typedef uint32_t ISTDUINT32;
typedef int32_t ISTDINT32;
#else
typedef unsigned long ISTDUINT32;
typedef long ISTDINT32;
#endif
#endif
//=====================================================================
// Integer Definition
//=====================================================================
#ifndef __IINT8_DEFINED
#define __IINT8_DEFINED
typedef char IINT8;
#endif
#ifndef __IUINT8_DEFINED
#define __IUINT8_DEFINED
typedef unsigned char IUINT8;
#endif
#ifndef __IUINT16_DEFINED
#define __IUINT16_DEFINED
typedef unsigned short IUINT16;
#endif
#ifndef __IINT16_DEFINED
#define __IINT16_DEFINED
typedef short IINT16;
#endif
#ifndef __IINT32_DEFINED
#define __IINT32_DEFINED
typedef ISTDINT32 IINT32;
#endif
#ifndef __IUINT32_DEFINED
#define __IUINT32_DEFINED
typedef ISTDUINT32 IUINT32;
#endif
#ifndef __IINT64_DEFINED
#define __IINT64_DEFINED
#if defined(_MSC_VER) || defined(__BORLANDC__)
typedef __int64 IINT64;
#else
typedef long long IINT64;
#endif
#endif
#ifndef __IUINT64_DEFINED
#define __IUINT64_DEFINED
#if defined(_MSC_VER) || defined(__BORLANDC__)
typedef unsigned __int64 IUINT64;
#else
typedef unsigned long long IUINT64;
#endif
#endif
#ifndef INLINE
#if defined(__GNUC__)
#if (__GNUC__ > 3) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1))
#define INLINE __inline__ __attribute__((always_inline))
#else
#define INLINE __inline__
#endif
#elif (defined(_MSC_VER) || defined(__BORLANDC__) || defined(__WATCOMC__))
#define INLINE __inline
#else
#define INLINE
#endif
#endif
#if (!defined(__cplusplus)) && (!defined(inline))
#define inline INLINE
#endif
//=====================================================================
// QUEUE DEFINITION
//=====================================================================
#ifndef __IQUEUE_DEF__
#define __IQUEUE_DEF__
struct IQUEUEHEAD {
struct IQUEUEHEAD *next, *prev;
};
typedef struct IQUEUEHEAD iqueue_head;
//---------------------------------------------------------------------
// queue init
//---------------------------------------------------------------------
#define IQUEUE_HEAD_INIT(name) { &(name), &(name) }
#define IQUEUE_HEAD(name) \
struct IQUEUEHEAD name = IQUEUE_HEAD_INIT(name)
#define IQUEUE_INIT(ptr) ( \
(ptr)->next = (ptr), (ptr)->prev = (ptr))
#define IOFFSETOF(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define ICONTAINEROF(ptr, type, member) ( \
(type*)( ((char*)((type*)ptr)) - IOFFSETOF(type, member)) )
#define IQUEUE_ENTRY(ptr, type, member) ICONTAINEROF(ptr, type, member)
//---------------------------------------------------------------------
// queue operation
//---------------------------------------------------------------------
#define IQUEUE_ADD(node, head) ( \
(node)->prev = (head), (node)->next = (head)->next, \
(head)->next->prev = (node), (head)->next = (node))
#define IQUEUE_ADD_TAIL(node, head) ( \
(node)->prev = (head)->prev, (node)->next = (head), \
(head)->prev->next = (node), (head)->prev = (node))
#define IQUEUE_DEL_BETWEEN(p, n) ((n)->prev = (p), (p)->next = (n))
#define IQUEUE_DEL(entry) (\
(entry)->next->prev = (entry)->prev, \
(entry)->prev->next = (entry)->next, \
(entry)->next = 0, (entry)->prev = 0)
#define IQUEUE_DEL_INIT(entry) do { \
IQUEUE_DEL(entry); IQUEUE_INIT(entry); } while (0)
#define IQUEUE_IS_EMPTY(entry) ((entry) == (entry)->next)
#define iqueue_init IQUEUE_INIT
#define iqueue_entry IQUEUE_ENTRY
#define iqueue_add IQUEUE_ADD
#define iqueue_add_tail IQUEUE_ADD_TAIL
#define iqueue_del IQUEUE_DEL
#define iqueue_del_init IQUEUE_DEL_INIT
#define iqueue_is_empty IQUEUE_IS_EMPTY
#define IQUEUE_FOREACH(iterator, head, TYPE, MEMBER) \
for ((iterator) = iqueue_entry((head)->next, TYPE, MEMBER); \
&((iterator)->MEMBER) != (head); \
(iterator) = iqueue_entry((iterator)->MEMBER.next, TYPE, MEMBER))
#define iqueue_foreach(iterator, head, TYPE, MEMBER) \
IQUEUE_FOREACH(iterator, head, TYPE, MEMBER)
#define iqueue_foreach_entry(pos, head) \
for( (pos) = (head)->next; (pos) != (head) ; (pos) = (pos)->next )
#define __iqueue_splice(list, head) do { \
iqueue_head *first = (list)->next, *last = (list)->prev; \
iqueue_head *at = (head)->next; \
(first)->prev = (head), (head)->next = (first); \
(last)->next = (at), (at)->prev = (last); } while (0)
#define iqueue_splice(list, head) do { \
if (!iqueue_is_empty(list)) __iqueue_splice(list, head); } while (0)
#define iqueue_splice_init(list, head) do { \
iqueue_splice(list, head); iqueue_init(list); } while (0)
#ifdef _MSC_VER
#pragma warning(disable:4311)
#pragma warning(disable:4312)
#pragma warning(disable:4996)
#endif
#endif
//---------------------------------------------------------------------
// BYTE ORDER & ALIGNMENT
//---------------------------------------------------------------------
#ifndef IWORDS_BIG_ENDIAN
#ifdef _BIG_ENDIAN_
#if _BIG_ENDIAN_
#define IWORDS_BIG_ENDIAN 1
#endif
#endif
#ifndef IWORDS_BIG_ENDIAN
#if defined(__hppa__) || \
defined(__m68k__) || defined(mc68000) || defined(_M_M68K) || \
(defined(__MIPS__) && defined(__MIPSEB__)) || \
defined(__ppc__) || defined(__POWERPC__) || defined(_M_PPC) || \
defined(__sparc__) || defined(__powerpc__) || \
defined(__mc68000__) || defined(__s390x__) || defined(__s390__)
#define IWORDS_BIG_ENDIAN 1
#endif
#endif
#ifndef IWORDS_BIG_ENDIAN
#define IWORDS_BIG_ENDIAN 0
#endif
#endif
#ifndef IWORDS_MUST_ALIGN
#if defined(__i386__) || defined(__i386) || defined(_i386_)
#define IWORDS_MUST_ALIGN 0
#elif defined(_M_IX86) || defined(_X86_) || defined(__x86_64__)
#define IWORDS_MUST_ALIGN 0
#elif defined(__amd64) || defined(__amd64__)
#define IWORDS_MUST_ALIGN 0
#else
#define IWORDS_MUST_ALIGN 1
#endif
#endif
//=====================================================================
// SEGMENT
//=====================================================================
struct IKCPSEG
{
struct IQUEUEHEAD node;
IUINT32 conv;
IUINT32 cmd;
IUINT32 frg;
IUINT32 wnd;
IUINT32 ts;
IUINT32 sn;
IUINT32 una;
IUINT32 len;
IUINT32 resendts;
IUINT32 rto;
IUINT32 fastack;
IUINT32 xmit;
char data[1];
};
//---------------------------------------------------------------------
// IKCPCB
//---------------------------------------------------------------------
struct IKCPCB
{
IUINT32 conv, mtu, mss, state;
IUINT32 snd_una, snd_nxt, rcv_nxt;
IUINT32 ts_recent, ts_lastack, ssthresh;
IINT32 rx_rttval, rx_srtt, rx_rto, rx_minrto;
IUINT32 snd_wnd, rcv_wnd, rmt_wnd, cwnd, probe;
IUINT32 current, interval, ts_flush, xmit;
IUINT32 nrcv_buf, nsnd_buf;
IUINT32 nrcv_que, nsnd_que;
IUINT32 nodelay, updated;
IUINT32 ts_probe, probe_wait;
IUINT32 dead_link, incr;
struct IQUEUEHEAD snd_queue;
struct IQUEUEHEAD rcv_queue;
struct IQUEUEHEAD snd_buf;
struct IQUEUEHEAD rcv_buf;
IUINT32 *acklist;
IUINT32 ackcount;
IUINT32 ackblock;
void *user;
char *buffer;
int fastresend;
int fastlimit;
int nocwnd, stream;
int logmask;
int (*output)(const char *buf, int len, struct IKCPCB *kcp, void *user);
void (*writelog)(const char *log, struct IKCPCB *kcp, void *user);
};
typedef struct IKCPCB ikcpcb;
#define IKCP_LOG_OUTPUT 1
#define IKCP_LOG_INPUT 2
#define IKCP_LOG_SEND 4
#define IKCP_LOG_RECV 8
#define IKCP_LOG_IN_DATA 16
#define IKCP_LOG_IN_ACK 32
#define IKCP_LOG_IN_PROBE 64
#define IKCP_LOG_IN_WINS 128
#define IKCP_LOG_OUT_DATA 256
#define IKCP_LOG_OUT_ACK 512
#define IKCP_LOG_OUT_PROBE 1024
#define IKCP_LOG_OUT_WINS 2048
#ifdef __cplusplus
extern "C" {
#endif
//---------------------------------------------------------------------
// interface
//---------------------------------------------------------------------
// create a new kcp control object, 'conv' must equal in two endpoint
// from the same connection. 'user' will be passed to the output callback
// output callback can be setup like this: 'kcp->output = my_udp_output'
ikcpcb* ikcp_create(IUINT32 conv, void *user);
// release kcp control object
void ikcp_release(ikcpcb *kcp);
// set output callback, which will be invoked by kcp
void ikcp_setoutput(ikcpcb *kcp, int (*output)(const char *buf, int len,
ikcpcb *kcp, void *user));
// user/upper level recv: returns size, returns below zero for EAGAIN
int ikcp_recv(ikcpcb *kcp, char *buffer, int len);
// user/upper level send, returns below zero for error
int ikcp_send(ikcpcb *kcp, const char *buffer, int len);
// update state (call it repeatedly, every 10ms-100ms), or you can ask
// ikcp_check when to call it again (without ikcp_input/_send calling).
// 'current' - current timestamp in millisec.
void ikcp_update(ikcpcb *kcp, IUINT32 current);
// Determine when should you invoke ikcp_update:
// returns when you should invoke ikcp_update in millisec, if there
// is no ikcp_input/_send calling. you can call ikcp_update in that
// time, instead of call update repeatly.
// Important to reduce unnacessary ikcp_update invoking. use it to
// schedule ikcp_update (eg. implementing an epoll-like mechanism,
// or optimize ikcp_update when handling massive kcp connections)
IUINT32 ikcp_check(const ikcpcb *kcp, IUINT32 current);
// when you received a low level packet (eg. UDP packet), call it
int ikcp_input(ikcpcb *kcp, const char *data, long size);
// flush pending data
void ikcp_flush(ikcpcb *kcp);
// check the size of next message in the recv queue
int ikcp_peeksize(const ikcpcb *kcp);
// change MTU size, default is 1400
int ikcp_setmtu(ikcpcb *kcp, int mtu);
// set maximum window size: sndwnd=32, rcvwnd=32 by default
int ikcp_wndsize(ikcpcb *kcp, int sndwnd, int rcvwnd);
// get how many packet is waiting to be sent
int ikcp_waitsnd(const ikcpcb *kcp);
// fastest: ikcp_nodelay(kcp, 1, 20, 2, 1)
// nodelay: 0:disable(default), 1:enable
// interval: internal update timer interval in millisec, default is 100ms
// resend: 0:disable fast resend(default), 1:enable fast resend
// nc: 0:normal congestion control(default), 1:disable congestion control
int ikcp_nodelay(ikcpcb *kcp, int nodelay, int interval, int resend, int nc);
void ikcp_log(ikcpcb *kcp, int mask, const char *fmt, ...);
// setup allocator
void ikcp_allocator(void* (*new_malloc)(size_t), void (*new_free)(void*));
// read conv
IUINT32 ikcp_getconv(const void *ptr);
#ifdef __cplusplus
}
#endif
#endif