Merge pull request #1238 from ctrlaltdavid/dev/webrtc-datachannel

WebRTC Data Channel
This commit is contained in:
Kalila 2021-07-07 18:49:15 -04:00 committed by GitHub
commit 5a2ca4869a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 926 additions and 21 deletions

View file

@ -165,8 +165,9 @@ bool DomainServer::forwardMetaverseAPIRequest(HTTPConnection* connection,
DomainServer::DomainServer(int argc, char* argv[]) :
QCoreApplication(argc, argv),
_gatekeeper(this),
#ifdef WEBRTC_DATA_CHANNEL
#ifdef WEBRTC_DATA_CHANNELS
_webrtcSignalingServer(QHostAddress::AnyIPv4, DEFAULT_DOMAIN_SERVER_WS_PORT, this),
_webrtcDataChannels(NodeType::DomainServer, this),
#endif
_httpManager(QHostAddress::AnyIPv4, DOMAIN_SERVER_HTTP_PORT,
QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this)
@ -251,6 +252,8 @@ DomainServer::DomainServer(int argc, char* argv[]) :
updateDownstreamNodes();
updateUpstreamNodes();
setUpWebRTC();
if (_type != NonMetaverse) {
// if we have a metaverse domain, we'll use an access token for API calls
resetAccountManagerAccessToken();
@ -3136,6 +3139,20 @@ void DomainServer::updateUpstreamNodes() {
updateReplicationNodes(Upstream);
}
void DomainServer::setUpWebRTC() {
#ifdef WEBRTC_DATA_CHANNELS
// Inbound WebRTC signaling messages received from a client.
connect(&_webrtcSignalingServer, &WebRTCSignalingServer::messageReceived,
&_webrtcDataChannels, &WebRTCDataChannels::onSignalingMessage);
// Outbound WebRTC signaling messages being sent to a client.
connect(&_webrtcDataChannels, &WebRTCDataChannels::signalingMessage,
&_webrtcSignalingServer, &WebRTCSignalingServer::sendMessage);
#endif
}
void DomainServer::initializeExporter() {
static const QString ENABLE_EXPORTER = "monitoring.enable_prometheus_exporter";
static const QString EXPORTER_PORT = "monitoring.prometheus_exporter_port";

View file

@ -28,7 +28,8 @@
#include <HTTPSConnection.h>
#include <LimitedNodeList.h>
#include <shared/WebRTC.h>
#if defined(WEBRTC_DATA_CHANNEL)
#if defined(WEBRTC_DATA_CHANNELS)
#include <webrtc/WebRTCDataChannels.h>
#include <webrtc/WebRTCSignalingServer.h>
#endif
@ -145,6 +146,9 @@ private slots:
void updateReplicatedNodes();
void updateDownstreamNodes();
void updateUpstreamNodes();
void setUpWebRTC();
void initializeExporter();
void initializeMetadataExporter();
@ -316,8 +320,9 @@ private:
QThread _assetClientThread;
#ifdef WEBRTC_DATA_CHANNEL
#ifdef WEBRTC_DATA_CHANNELS
WebRTCSignalingServer _webrtcSignalingServer;
WebRTCDataChannels _webrtcDataChannels;
#endif
};

View file

@ -11,7 +11,8 @@ endif ()
if (WIN32)
# we need ws2_32.lib on windows, but it's static so we don't bubble it up
target_link_libraries(${TARGET_NAME} ws2_32.lib)
# Libraries needed for WebRTC: security.lib winmm.lib
target_link_libraries(${TARGET_NAME} ws2_32.lib security.lib winmm.lib)
elseif(APPLE)
# IOKit is needed for getting machine fingerprint
find_library(FRAMEWORK_IOKIT IOKit)

View file

@ -4,6 +4,7 @@
//
// Created by Stephen Birarda on 05/29/15.
// Copyright 2015 High Fidelity, Inc.
// Copyright 2021 Vircadia contributors.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -14,6 +15,10 @@
#pragma once
/// @file
/// @brief NodeType
/// @brief An 8-bit value identifying the type of a node - domain server, audio mixer, etc.
typedef quint8 NodeType_t;
namespace NodeType {
@ -37,7 +42,6 @@ namespace NodeType {
NodeType_t upstreamType(NodeType_t primaryType);
NodeType_t downstreamType(NodeType_t primaryType);
NodeType_t fromString(QString type);
}

View file

@ -0,0 +1,548 @@
//
// WebRTCDataChannels.cpp
// libraries/networking/src/webrtc
//
// Created by David Rowe on 21 May 2021.
// Copyright 2021 Vircadia contributors.
//
#include "WebRTCDataChannels.h"
#if defined(WEBRTC_DATA_CHANNELS)
#include <QJsonDocument>
#include <QJsonObject>
#include "../NetworkLogging.h"
// References:
// - https://webrtc.github.io/webrtc-org/native-code/native-apis/
// - https://webrtc.googlesource.com/src/+/master/api/peer_connection_interface.h
const std::string ICE_SERVER_URI = "stun://ice.vircadia.com:7337";
const int MAX_WEBRTC_BUFFER_SIZE = 16777216; // 16MB
// #define WEBRTC_DEBUG
void WDCSetSessionDescriptionObserver::OnSuccess() {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WDCSetSessionDescriptionObserver::OnSuccess()";
#endif
}
void WDCSetSessionDescriptionObserver::OnFailure(RTCError error) {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WDCSetSessionDescriptionObserver::OnFailure() :" << error.message();
#endif
}
WDCCreateSessionDescriptionObserver::WDCCreateSessionDescriptionObserver(WDCConnection* parent) :
_parent(parent)
{ }
void WDCCreateSessionDescriptionObserver::OnSuccess(SessionDescriptionInterface* description) {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WDCCreateSessionDescriptionObserver::OnSuccess()";
#endif
_parent->sendAnswer(description);
_parent->setLocalDescription(description);
}
void WDCCreateSessionDescriptionObserver::OnFailure(RTCError error) {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WDCCreateSessionDescriptionObserver::OnFailure() :" << error.message();
#endif
}
WDCPeerConnectionObserver::WDCPeerConnectionObserver(WDCConnection* parent) :
_parent(parent)
{ }
void WDCPeerConnectionObserver::OnSignalingChange(PeerConnectionInterface::SignalingState newState) {
#ifdef WEBRTC_DEBUG
QStringList states{
"Stable",
"HaveLocalOffer",
"HaveLocalPrAnswer",
"HaveRemoteOffer",
"HaveRemotePrAnswer",
"Closed"
};
qCDebug(networking_webrtc) << "WDCPeerConnectionObserver::OnSignalingChange()" << newState << states[newState];
#endif
}
void WDCPeerConnectionObserver::OnRenegotiationNeeded() {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WDCPeerConnectionObserver::OnRenegotiationNeeded()";
#endif
}
void WDCPeerConnectionObserver::OnIceGatheringChange(PeerConnectionInterface::IceGatheringState newState) {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WDCPeerConnectionObserver::OnIceGatheringChange()" << newState;
#endif
}
void WDCPeerConnectionObserver::OnIceCandidate(const IceCandidateInterface* candidate) {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WDCPeerConnectionObserver::OnIceCandidate()";
#endif
_parent->sendIceCandidate(candidate);
}
void WDCPeerConnectionObserver::OnDataChannel(rtc::scoped_refptr<DataChannelInterface> dataChannel) {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WDCPeerConnectionObserver::OnDataChannel()";
#endif
_parent->onDataChannelOpened(dataChannel);
}
void WDCPeerConnectionObserver::OnConnectionChange(PeerConnectionInterface::PeerConnectionState newState) {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WDCPeerConnectionObserver::OnConnectionChange()" << (uint)newState;
#endif
_parent->onPeerConnectionStateChanged(newState);
}
WDCDataChannelObserver::WDCDataChannelObserver(WDCConnection* parent) :
_parent(parent)
{ }
void WDCDataChannelObserver::OnStateChange() {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WDCDataChannelObserver::OnStateChange()";
#endif
_parent->onDataChannelStateChanged();
}
void WDCDataChannelObserver::OnMessage(const DataBuffer& buffer) {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WDCDataChannelObserver::OnMessage()";
#endif
_parent->onDataChannelMessageReceived(buffer);
}
WDCConnection::WDCConnection(WebRTCDataChannels* parent, quint16 webSocketID) :
_parent(parent),
_webSocketID(webSocketID)
{
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WDCConnection::WDCConnection() :" << webSocketID;
#endif
// Create observers.
_setSessionDescriptionObserver = new rtc::RefCountedObject<WDCSetSessionDescriptionObserver>();
_createSessionDescriptionObserver = new rtc::RefCountedObject<WDCCreateSessionDescriptionObserver>(this);
_dataChannelObserver = std::make_shared<WDCDataChannelObserver>(this);
_peerConnectionObserver = std::make_shared<WDCPeerConnectionObserver>(this);
// Create new peer connection.
_peerConnection = _parent->createPeerConnection(_peerConnectionObserver);
};
void WDCConnection::setRemoteDescription(QJsonObject& description) {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WDCConnection::setRemoteDescription() :" << description;
#endif
SdpParseError sdpParseError;
auto sessionDescription = CreateSessionDescription(
description.value("type").toString().toStdString(),
description.value("sdp").toString().toStdString(),
&sdpParseError);
if (!sessionDescription) {
qCWarning(networking_webrtc) << "Error creating WebRTC remote description:"
<< QString::fromStdString(sdpParseError.description);
return;
}
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "3. Set remote description:" << sessionDescription;
#endif
_peerConnection->SetRemoteDescription(_setSessionDescriptionObserver, sessionDescription);
}
void WDCConnection::createAnswer() {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WDCConnection::createAnswer()";
qCDebug(networking_webrtc) << "4.a Create answer";
#endif
_peerConnection->CreateAnswer(_createSessionDescriptionObserver, PeerConnectionInterface::RTCOfferAnswerOptions());
}
void WDCConnection::sendAnswer(SessionDescriptionInterface* description) {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WDCConnection::sendAnswer()";
qCDebug(networking_webrtc) << "4.b Send answer to the remote peer";
#endif
QJsonObject jsonDescription;
std::string descriptionString;
description->ToString(&descriptionString);
jsonDescription.insert("sdp", QString::fromStdString(descriptionString));
jsonDescription.insert("type", "answer");
QJsonObject jsonWebRTCPayload;
jsonWebRTCPayload.insert("description", jsonDescription);
QJsonObject jsonObject;
jsonObject.insert("from", QString(_parent->getNodeType()));
jsonObject.insert("to", _webSocketID);
jsonObject.insert("data", jsonWebRTCPayload);
_parent->sendSignalingMessage(jsonObject);
}
void WDCConnection::setLocalDescription(SessionDescriptionInterface* description) {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WDCConnection::setLocalDescription()";
qCDebug(networking_webrtc) << "5. Set local description";
#endif
_peerConnection->SetLocalDescription(_setSessionDescriptionObserver, description);
}
void WDCConnection::addIceCandidate(QJsonObject& data) {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WDCConnection::addIceCandidate()";
#endif
SdpParseError sdpParseError;
auto iceCandidate = CreateIceCandidate(
data.value("sdpMid").toString().toStdString(),
data.value("sdpMLineIndex").toInt(),
data.value("candidate").toString().toStdString(),
&sdpParseError);
if (!iceCandidate) {
qCWarning(networking_webrtc) << "Error adding WebRTC ICE candidate:"
<< QString::fromStdString(sdpParseError.description);
return;
}
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "6. Add ICE candidate";
#endif
_peerConnection->AddIceCandidate(iceCandidate);
}
void WDCConnection::sendIceCandidate(const IceCandidateInterface* candidate) {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WDCConnection::sendIceCandidate()";
#endif
std::string candidateString;
candidate->ToString(&candidateString);
QJsonObject jsonCandidate;
jsonCandidate.insert("candidate", QString::fromStdString(candidateString));
jsonCandidate.insert("sdpMid", QString::fromStdString(candidate->sdp_mid()));
jsonCandidate.insert("sdpMLineIndex", candidate->sdp_mline_index());
QJsonObject jsonWebRTCData;
jsonWebRTCData.insert("candidate", jsonCandidate);
QJsonObject jsonObject;
jsonObject.insert("from", QString(_parent->getNodeType()));
jsonObject.insert("to", _webSocketID);
jsonObject.insert("data", jsonWebRTCData);
QJsonDocument jsonDocument = QJsonDocument(jsonObject);
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "7. Send ICE candidate to the remote peer";
#endif
_parent->sendSignalingMessage(jsonObject);
}
void WDCConnection::onPeerConnectionStateChanged(PeerConnectionInterface::PeerConnectionState state) {
#ifdef WEBRTC_DEBUG
const char* STATES[] = {
"New",
"Connecting",
"Connected",
"Disconnected",
"Failed",
"Closed"
};
qCDebug(networking_webrtc) << "WDCConnection::onPeerConnectionStateChanged() :" << (int)state << STATES[(int)state];
#endif
}
void WDCConnection::onDataChannelOpened(rtc::scoped_refptr<DataChannelInterface> dataChannel) {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WDCConnection::onDataChannelOpened() :"
<< dataChannel->id()
<< QString::fromStdString(dataChannel->label())
<< QString::fromStdString(dataChannel->protocol())
<< dataChannel->negotiated()
<< dataChannel->maxRetransmitTime()
<< dataChannel->maxRetransmits()
<< dataChannel->maxPacketLifeTime().value_or(-1)
<< dataChannel->maxRetransmitsOpt().value_or(-1);
#endif
_dataChannel = dataChannel;
_dataChannelID = _parent->getNewDataChannelID(); // Not dataChannel->id() because it's only unique per peer connection.
_dataChannel->RegisterObserver(_dataChannelObserver.get());
_parent->onDataChannelOpened(this, _dataChannelID);
}
void WDCConnection::onDataChannelStateChanged() {
auto state = _dataChannel->state();
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WDCConnection::onDataChannelStateChanged() :" << (int)state
<< DataChannelInterface::DataStateString(state);
#endif
if (state == DataChannelInterface::kClosed) {
// Close data channel.
_dataChannel->UnregisterObserver();
_dataChannelObserver = nullptr;
_dataChannel = nullptr;
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "Disposed of data channel";
#endif
// Close peer connection.
_parent->closePeerConnection(this);
}
}
void WDCConnection::onDataChannelMessageReceived(const DataBuffer& buffer) {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WDCConnection::onDataChannelMessageReceived()";
#endif
auto byteArray = QByteArray(buffer.data.data<char>(), (int)buffer.data.size());
// Echo message back to sender.
if (byteArray.startsWith("echo:")) {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "Echo message back";
#endif
_parent->sendDataMessage(_dataChannelID, byteArray); // Use parent method to exercise the code stack.
return;
}
_parent->emitDataMessage(_dataChannelID, byteArray);
}
bool WDCConnection::sendDataMessage(const DataBuffer& buffer) {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WDCConnection::sendDataMessage()";
#endif
if (_dataChannel->buffered_amount() + buffer.size() > MAX_WEBRTC_BUFFER_SIZE) {
// Don't send, otherwise the data channel will be closed.
return false;
} else {
qCDebug(networking_webrtc) << "WebRTC send buffer overflow";
}
return _dataChannel->Send(buffer);
}
void WDCConnection::closePeerConnection() {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WDCConnection::closePeerConnection()";
#endif
_peerConnection->Close();
_peerConnection = nullptr;
_peerConnectionObserver = nullptr;
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "Disposed of peer connection";
#endif
}
WebRTCDataChannels::WebRTCDataChannels(NodeType_t nodeType, QObject* parent) :
_nodeType(nodeType),
_parent(parent)
{
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WebRTCDataChannels::WebRTCDataChannels()";
#endif
// Create a peer connection factory.
#ifdef WEBRTC_DEBUG
// Numbers are per WebRTC's peer_connection_interface.h.
qCDebug(networking_webrtc) << "1. Create a new PeerConnectionFactoryInterface";
#endif
_rtcNetworkThread = rtc::Thread::CreateWithSocketServer();
_rtcNetworkThread->Start();
_rtcWorkerThread = rtc::Thread::Create();
_rtcWorkerThread->Start();
_rtcSignalingThread = rtc::Thread::Create();
_rtcSignalingThread->Start();
PeerConnectionFactoryDependencies dependencies;
dependencies.network_thread = _rtcNetworkThread.get();
dependencies.worker_thread = _rtcWorkerThread.get();
dependencies.signaling_thread = _rtcSignalingThread.get();
_peerConnectionFactory = CreateModularPeerConnectionFactory(std::move(dependencies));
if (!_peerConnectionFactory) {
qCWarning(networking_webrtc) << "Failed to create WebRTC peer connection factory";
}
// Set up mechanism for closing peer connections.
connect(this, &WebRTCDataChannels::closePeerConnectionSoon, this, &WebRTCDataChannels::closePeerConnectionNow);
}
WebRTCDataChannels::~WebRTCDataChannels() {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WebRTCDataChannels::~WebRTCDataChannels()";
#endif
QHashIterator<quint16, WDCConnection*> i(_connectionsByDataChannel);
while (i.hasNext()) {
i.next();
delete i.value();
}
_connectionsByWebSocket.clear();
_connectionsByDataChannel.clear();
_peerConnectionFactory = nullptr;
_rtcSignalingThread->Stop();
_rtcSignalingThread = nullptr;
_rtcWorkerThread->Stop();
_rtcWorkerThread = nullptr;
_rtcNetworkThread->Stop();
_rtcNetworkThread = nullptr;
}
quint16 WebRTCDataChannels::getNewDataChannelID() {
static const int QUINT16_LIMIT = std::numeric_limits<uint16_t>::max() + 1;
_lastDataChannelID = std::max((_lastDataChannelID + 1) % QUINT16_LIMIT, 1);
return _lastDataChannelID;
}
void WebRTCDataChannels::onDataChannelOpened(WDCConnection* connection, quint16 dataChannelID) {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WebRTCDataChannels::onDataChannelOpened() :" << dataChannelID;
#endif
_connectionsByDataChannel.insert(dataChannelID, connection);
}
void WebRTCDataChannels::onSignalingMessage(const QJsonObject& message) {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WebRTCDataChannel::onSignalingMessage()" << message;
#endif
// Validate message.
const int MAX_DEBUG_DETAIL_LENGTH = 64;
auto data = message.value("data").isObject() ? message.value("data").toObject() : QJsonObject();
int from = message.value("from").isDouble() ? (quint16)(message.value("from").toInt()) : 0;
if (from <= 0 || from > MAXUINT16 || !data.contains("description") && !data.contains("candidate")) {
qCWarning(networking_webrtc) << "Unexpected signaling message:"
<< QJsonDocument(message).toJson(QJsonDocument::Compact).left(MAX_DEBUG_DETAIL_LENGTH);
return;
}
// Find or create a connection.
WDCConnection* connection;
if (_connectionsByWebSocket.contains(from)) {
connection = _connectionsByWebSocket.value(from);
} else {
connection = new WDCConnection(this, from);
_connectionsByWebSocket.insert(from, connection);
}
// Set the remote description and reply with an answer.
if (data.contains("description")) {
auto description = data.value("description").toObject();
if (description.value("type").toString() == "offer") {
connection->setRemoteDescription(description);
connection->createAnswer();
} else {
qCWarning(networking_webrtc) << "Unexpected signaling description:"
<< QJsonDocument(description).toJson(QJsonDocument::Compact).left(MAX_DEBUG_DETAIL_LENGTH);
}
}
// Add a remote ICE candidate.
if (data.contains("candidate")) {
connection->addIceCandidate(data);
}
}
void WebRTCDataChannels::sendSignalingMessage(const QJsonObject& message) {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WebRTCDataChannels::sendSignalingMessage() :" << QJsonDocument(message).toJson(QJsonDocument::Compact);
#endif
emit signalingMessage(message);
}
void WebRTCDataChannels::emitDataMessage(int dataChannelID, const QByteArray& byteArray) {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WebRTCDataChannels::emitDataMessage() :" << dataChannelID;
#endif
emit dataMessage(dataChannelID, byteArray);
}
bool WebRTCDataChannels::sendDataMessage(int dataChannelID, const QByteArray& byteArray) {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WebRTCDataChannels::sendDataMessage() :" << dataChannelID;
#endif
// Find connection.
if (!_connectionsByDataChannel.contains(dataChannelID)) {
qCWarning(networking_webrtc) << "Could not find data channel to send message on!";
return false;
}
auto connection = _connectionsByDataChannel.value(dataChannelID);
DataBuffer buffer(byteArray.toStdString(), true);
return connection->sendDataMessage(buffer);
}
rtc::scoped_refptr<PeerConnectionInterface> WebRTCDataChannels::createPeerConnection(
const std::shared_ptr<WDCPeerConnectionObserver> peerConnectionObserver) {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WebRTCDataChannels::createPeerConnection()";
#endif
PeerConnectionInterface::RTCConfiguration configuration;
PeerConnectionInterface::IceServer iceServer;
iceServer.uri = ICE_SERVER_URI;
configuration.servers.push_back(iceServer);
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "2. Create a new peer connection";
#endif
PeerConnectionDependencies dependencies(peerConnectionObserver.get());
auto result = _peerConnectionFactory->CreatePeerConnection(configuration, std::move(dependencies));
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "Created peer connection";
#endif
return result;
}
void WebRTCDataChannels::closePeerConnection(WDCConnection* connection) {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WebRTCDataChannels::closePeerConnection()";
#endif
// Use Qt's signals/slots mechanism to close the peer connection on its own call stack, separate from the DataChannel
// callback that initiated the peer connection.
// https://bugs.chromium.org/p/webrtc/issues/detail?id=3721
emit closePeerConnectionSoon(connection);
}
void WebRTCDataChannels::closePeerConnectionNow(WDCConnection* connection) {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WebRTCDataChannels::closePeerConnectionNow()";
#endif
// Close the peer connection.
connection->closePeerConnection();
// Delete the WDCConnection.
_connectionsByWebSocket.remove(connection->getWebSocketID());
_connectionsByDataChannel.remove(connection->getDataChannelID());
delete connection;
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "Disposed of connection";
#endif
}
#endif // WEBRTC_DATA_CHANNELS

View file

@ -0,0 +1,328 @@
//
// WebRTCDataChannels.h
// libraries/networking/src/webrtc
//
// Created by David Rowe on 21 May 2021.
// Copyright 2021 Vircadia contributors.
//
#ifndef vircadia_WebRTCDataChannels_h
#define vircadia_WebRTCDataChannels_h
#include <shared/WebRTC.h>
#if defined(WEBRTC_DATA_CHANNELS)
#include <QObject>
#include <QHash>
#undef emit // Avoid conflict between Qt signals/slots and the WebRTC library's.
#include <api/peer_connection_interface.h>
#define emit
#include "../NodeType.h"
using namespace webrtc;
class WebRTCDataChannels;
class WDCConnection;
/// @addtogroup Networking
/// @{
/// @brief A WebRTC session description observer.
class WDCSetSessionDescriptionObserver : public SetSessionDescriptionObserver {
public:
/// @brief The call to SetLocalDescription or SetRemoteDescription succeeded.
void OnSuccess() override;
/// @brief The call to SetLocalDescription or SetRemoteDescription failed.
/// @param error Error information.
void OnFailure(RTCError error) override;
};
/// @brief A WebRTC create session description observer.
class WDCCreateSessionDescriptionObserver : public CreateSessionDescriptionObserver {
public:
/// @brief Constructs a session description observer.
/// @param parent The parent connection object.
WDCCreateSessionDescriptionObserver(WDCConnection* parent);
/// @brief The call to CreateAnswer succeeded.
/// @param The session description.
void OnSuccess(SessionDescriptionInterface* desc) override;
//@ @brief The call to CreateAnswer failed.
/// @param error Error information.
void OnFailure(RTCError error) override;
private:
WDCConnection* _parent;
};
/// @brief A WebRTC peer connection observer.
class WDCPeerConnectionObserver : public PeerConnectionObserver {
public:
/// @brief Constructs a peer connection observer.
/// @param parent The parent connection object.
WDCPeerConnectionObserver(WDCConnection* parent);
/// @brief Called when the SignalingState changes.
/// @param newState The new signaling state.
void OnSignalingChange(PeerConnectionInterface::SignalingState newState) override;
/// @brief Called when renegotiation is needed. For example, an ICE restart has begun.
void OnRenegotiationNeeded() override;
/// @brief Called when the ICE gather state changes.
/// @param newState The new ICE gathering state.
void OnIceGatheringChange(PeerConnectionInterface::IceGatheringState newState) override;
/// @brief Called when a new ICE candidate has been gathered.
/// @param candidate The new ICE candidate.
void OnIceCandidate(const IceCandidateInterface* candidate) override;
/// @brief Called when a remote peer opens a data channel.
/// @param dataChannel The data channel.
void OnDataChannel(rtc::scoped_refptr<DataChannelInterface> dataChannel) override;
/// @brief Called when the peer connection state changes.
/// @param newState The new peer connection state.
void OnConnectionChange(PeerConnectionInterface::PeerConnectionState newState) override;
private:
WDCConnection* _parent;
};
/// @brief A WebRTC data channel observer.
class WDCDataChannelObserver : public DataChannelObserver {
public:
/// @brief Constructs a data channel observer.
/// @param parent The parent connection object.
WDCDataChannelObserver(WDCConnection* parent);
/// @brief The data channel state changed.
void OnStateChange() override;
/// @brief A data channel message was received.
/// @param The message received.
void OnMessage(const DataBuffer& buffer) override;
private:
WDCConnection* _parent;
};
/// @brief A WebRTC data channel connection.
/// @details Opens and manages a WebRTC data channel connection.
class WDCConnection {
public:
/// @brief Constructs a new WDCConnection and opens a WebRTC data connection.
/// @param parent The parent WebRTCDataChannels object.
/// @param webSocketID The signaling channel that initiated the opening of the WebRTC data channel.
WDCConnection(WebRTCDataChannels* parent, quint16 webSocketID);
/// @brief Gets the WebSocket ID.
/// @return The ID of the WebSocket.
quint16 getWebSocketID() { return _webSocketID; }
/// @brief Gets the WebRTC data channel ID.
/// @return The WebRTC data channel ID. `-1` if not open yet.
int getDataChannelID() { return _dataChannelID; }
/// @brief Sets the remote session description received from the remote client via the signaling channel.
/// @param description The remote session description.
void setRemoteDescription(QJsonObject& description);
/// @brief Creates an answer to an offer received from the remote client via the signaling channel.
void createAnswer();
/// @brief Sends an answer to the remote client via the signaling channel.
/// @param description The answer.
void sendAnswer(SessionDescriptionInterface* description);
/// @brief Sets the local session description on the WebRTC data channel being connected.
/// @param description The local session description.
void setLocalDescription(SessionDescriptionInterface* description);
/// @brief Adds an ICE candidate received from the remote client via the signaling channel.
/// @param data The ICE candidate.
void addIceCandidate(QJsonObject& data);
/// @brief Sends an ICE candidate to the remote vlient via the signaling channel.
/// @param candidate The ICE candidate.
void sendIceCandidate(const IceCandidateInterface* candidate);
/// @brief Monitors the peer connection state.
/// @param state The new peer connection state.
void onPeerConnectionStateChanged(PeerConnectionInterface::PeerConnectionState state);
/// @brief Handles the WebRTC data channel being opened.
/// @param dataChannel The WebRTC data channel.
void onDataChannelOpened(rtc::scoped_refptr<DataChannelInterface> dataChannel);
/// @brief Handles a change in the state of the WebRTC data channel.
void onDataChannelStateChanged();
/// @brief Handles a message being received on the WebRTC data channel.
/// @param buffer The message received.
void onDataChannelMessageReceived(const DataBuffer& buffer);
/// @brief Sends a message on the WebRTC data channel.
/// @param buffer The message to send.
/// @return `true` if the message was sent, otherwise `false`.
bool sendDataMessage(const DataBuffer& buffer);
/// @brief Closes the WebRTC peer connection.
void closePeerConnection();
private:
WebRTCDataChannels* _parent;
quint16 _webSocketID { 0 };
int _dataChannelID { -1 };
rtc::scoped_refptr<WDCSetSessionDescriptionObserver> _setSessionDescriptionObserver { nullptr };
rtc::scoped_refptr<WDCCreateSessionDescriptionObserver> _createSessionDescriptionObserver { nullptr };
std::shared_ptr<WDCDataChannelObserver> _dataChannelObserver { nullptr };
rtc::scoped_refptr<DataChannelInterface> _dataChannel { nullptr };
std::shared_ptr<WDCPeerConnectionObserver> _peerConnectionObserver { nullptr };
rtc::scoped_refptr<PeerConnectionInterface> _peerConnection { nullptr };
};
/// @brief Manages WebRTC data channels on the domain server or an assignment client that Interface clients can connect to.
///
/// @details Presents multiple individual WebRTC data channels as a single one-to-many WebRTCDataChannels object. Interface
/// clients may use WebRTC data channels for Vircadia protocol network communications instead of UDP.
/// A WebRTCSignalingServer is used in the process of setting up a WebRTC data channel between an Interface client and the
/// domain server or assignment client.
/// The Interface client initiates the connection - including initiating the data channel - and the domain server or assignment
/// client responds.
///
/// Additionally, for debugging purposes, instead of containing a Vircadia protocol payload, a WebRTC message may be an echo
/// request. This is bounced back to the client.
///
class WebRTCDataChannels : public QObject {
Q_OBJECT
public:
/// @brief Constructs a new WebRTCDataChannels object.
/// @param nodeType The type of node that the WebRTCDataChannels object is being used in.
/// @param parent The parent Qt object.
WebRTCDataChannels(NodeType_t nodeType, QObject* parent);
/// @brief Destroys a WebRTCDataChannels object.
~WebRTCDataChannels();
/// @brief Returns the type of node that the WebRTCDataChannels object is being used in.
/// @return The type of node.
NodeType_t getNodeType() {
return _nodeType;
}
/// @brief Get a new data channel ID to uniquely identify a WDCConnection.
/// @return A new data channel ID.
quint16 getNewDataChannelID();
/// @brief Handles a WebRTC data channel opening.
/// @param connection The WebRTC data channel connection.
/// @param dataChannelID The WebRTC data channel ID.
void onDataChannelOpened(WDCConnection* connection, quint16 dataChannelID);
/// @brief Emits a signalingMessage to be sent to the Interface client.
/// @param message The WebRTC signaling message to send.
void sendSignalingMessage(const QJsonObject& message);
/// @brief Emits a dataMessage received from the Interface client.
/// @param dataChannelID The WebRTC data channel the message was received on.
/// @param byteArray The data message received.
void emitDataMessage(int dataChannelID, const QByteArray& byteArray);
/// @brief Sends a data message to an Interface client.
/// @param dataChannelID The WebRTC channel ID of the Interface client.
/// @param message The data message to send.
/// @return `true` if the data message was sent, otherwise `false`.
bool sendDataMessage(int dataChannelID, const QByteArray& message);
/// @brief Creates a new WebRTC peer connection for connecting to an Interface client.
/// @param peerConnectionObserver An observer to monitor the WebRTC peer connection.
/// @return The new WebRTC peer connection.
rtc::scoped_refptr<PeerConnectionInterface> createPeerConnection(
const std::shared_ptr<WDCPeerConnectionObserver> peerConnectionObserver);
/// @brief Initiates closing the peer connection for a WebRTC data channel.
/// @details Emits a {@link WebRTCDataChannels.closePeerConnectionSoon} signal which is connected to
/// {@link WebRTCDataChannels.closePeerConnectionNow} in order to close the peer connection on a new call stack. This is
/// necessary to work around a WebRTC library limitation.
/// @param connection The WebRTC data channel connection.
void closePeerConnection(WDCConnection* connection);
public slots:
/// @brief Handles a WebRTC signaling message received from the Interface client.
/// @param message The WebRTC signaling message.
void onSignalingMessage(const QJsonObject& message);
/// @brief Closes the peer connection for a WebRTC data channel.
/// @details Used by {@link WebRTCDataChannels.closePeerConnection}.
/// @param connection The WebRTC data channel connection.
void closePeerConnectionNow(WDCConnection* connection);
signals:
/// @brief A WebRTC signaling message to be sent to the Interface client.
/// @details This message is for the WebRTCSignalingServer to send.
/// @param message The WebRTC signaling message to send.
void signalingMessage(const QJsonObject& message);
/// @brief A WebRTC data message received from the Interface client.
/// @details This message is for handling at a higher level in the Vircadia protocol.
/// @param dataChannelID The WebRTC data channel ID.
/// @param byteArray The Vircadia protocol message.
void dataMessage(int dataChannelID, const QByteArray& byteArray);
/// @brief Signals that the peer connection for a WebRTC data channel should be closed.
/// @details Used by {@link WebRTCDataChannels.closePeerConnection}.
/// @param connection The WebRTC data channel connection.
void closePeerConnectionSoon(WDCConnection* connection);
private:
QObject* _parent;
NodeType_t _nodeType;
std::unique_ptr<rtc::Thread> _rtcNetworkThread { nullptr };
std::unique_ptr<rtc::Thread> _rtcWorkerThread { nullptr };
std::unique_ptr<rtc::Thread> _rtcSignalingThread { nullptr };
rtc::scoped_refptr<PeerConnectionFactoryInterface> _peerConnectionFactory { nullptr };
quint16 _lastDataChannelID { 0 };
QHash<quint16, WDCConnection*> _connectionsByWebSocket;
QHash<quint16, WDCConnection*> _connectionsByDataChannel;
};
/// @}
#endif // WEBRTC_DATA_CHANNELS
#endif // vircadia_WebRTCDataChannels_h

View file

@ -8,7 +8,7 @@
#include "WebRTCSignalingServer.h"
#if defined(WEBRTC_DATA_CHANNEL)
#if defined(WEBRTC_DATA_CHANNELS)
#include <QtCore>
#include <QWebSocket>
@ -93,4 +93,4 @@ void WebRTCSignalingServer::newWebSocketConnection() {
_webSockets.insert(webSocket->peerPort(), webSocket);
}
#endif // WEBRTC_DATA_CHANNEL
#endif // WEBRTC_DATA_CHANNELS

View file

@ -2,18 +2,16 @@
// WebRTCSignalingServer.h
// libraries/networking/src/webrtc
//
// Provides a signaling channel for setting up WebRTC connections between the Web app and the domain servers and mixers.
//
// Created by David Rowe on 16 May 2021.
// Copyright 2021 Vircadia contributors.
//
#ifndef vircadia_SignalingServer_h
#define vircadia_SignalingServer_h
#ifndef vircadia_WebRTCSignalingServer_h
#define vircadia_WebRTCSignalingServer_h
#include <shared/WebRTC.h>
#if defined(WEBRTC_DATA_CHANNEL)
#if defined(WEBRTC_DATA_CHANNELS)
#include <QObject>
#include <QtCore/QTimer>
@ -21,8 +19,11 @@
#include "../HifiSockAddr.h"
/// @brief WebRTC signaling server that Interface clients can use to initiate WebRTC connections to the domain server and
/// assignment clients.
/// @addtogroup Networking
/// @{
/// @brief Provides a WebRTC signaling server that Interface clients can use to initiate WebRTC connections to the domain server
/// and its assignment clients.
///
/// @details The signaling server is expected to be hosted in the domain server. It provides a WebSocket for Interface clients
/// to use in the WebRTC signaling handshake process to establish WebRTC data channel connections to each of the domain server
@ -50,14 +51,14 @@
/// | `to` | WebSocket port number |
/// | `from` | NodeType |
/// | [`data`] | WebRTC payload |
/// | [`echo`] | Echo request |
/// | [`echo`] | Echo response |
///
class WebRTCSignalingServer : public QObject {
Q_OBJECT
public:
/// @brief Constructs a new WebRTCSignalingServer.
/// @brief Constructs a new WebRTCSignalingServer object.
/// @param address The IP address to use for the WebSocket.
/// @param port The port to use for the WebSocket.
/// @param parent Qt parent object.
@ -98,7 +99,8 @@ private:
QTimer* _isWebSocketServerListeningTimer;
};
/// @}
#endif // WEBRTC_DATA_CHANNEL
#endif // WEBRTC_DATA_CHANNELS
#endif // vircadia_SignalingServer_h
#endif // vircadia_WebRTCSignalingServer_h

View file

@ -17,7 +17,7 @@
#endif
// WEBRTC_AUDIO: WebRTC audio features, e.g., echo canceling.
// WEBRTC_DATA_CHANNEL: WebRTC client-server connections in parallel with UDP.
// WEBRTC_DATA_CHANNELS: WebRTC client-server connections in parallel with UDP.
#if defined(Q_OS_MAC)
# define WEBRTC_AUDIO 1
@ -25,7 +25,7 @@
# define WEBRTC_LEGACY 1
#elif defined(Q_OS_WIN)
# define WEBRTC_AUDIO 1
# define WEBRTC_DATA_CHANNEL 1
# define WEBRTC_DATA_CHANNELS 1
# define WEBRTC_WIN 1
# define NOMINMAX 1
# define WIN32_LEAN_AND_MEAN 1

@ -1 +1 @@
Subproject commit ac1f13a39c702ee54bf2cda8bc35e5d34f7f0756
Subproject commit ab6c8b1a54aec359b1894f70722f69cfec7f04f1