Merge pull request #1469 from vircadia/webapp

Enable WebRTC connections to be made to the Domain, i.e., add Web client support.
This commit is contained in:
Kalila 2021-11-26 12:19:45 -05:00 committed by GitHub
commit aabcdeadbf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
61 changed files with 2908 additions and 224 deletions

View file

@ -948,7 +948,7 @@ In an international environment English is the preferred language.
#### [4.3.2] Use // for all comments, including multi-line comments.
An exception to this rule applies for jsdoc or Doxygen comments.
An exception to this rule applies to JSDoc and Doxygen comments.
```cpp
// Comment spanning
@ -1008,3 +1008,11 @@ These types of comments are explicitly not allowed. If you need to break up sect
//--------------------------------------------------------------------------------
```
#### [4.3.6] Doxygen comments should use "///"
Use the `///` style of [Doxygen](https://www.doxygen.nl/index.html) comments when documenting public interfaces.
Some editors can automatically create a Doxygen documentation stub if you type `///` in the line above the item to be
documented.
**Visual Studio:** To configure Visual Studio's Doxygen commenting behavior, search for "Doxygen" in Tools > Options.

View file

@ -14,6 +14,7 @@
#include <assert.h>
#include <QJsonDocument>
#include <QProcess>
#include <QSharedMemory>
#include <QThread>
@ -84,7 +85,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
_assignmentServerHostname = assignmentServerHostname;
}
_assignmentServerSocket = SockAddr(_assignmentServerHostname, assignmentServerPort, true);
_assignmentServerSocket = SockAddr(SocketType::UDP, _assignmentServerHostname, assignmentServerPort, true);
if (_assignmentServerSocket.isNull()) {
qCCritical(assignment_client) << "PAGE: Couldn't resolve domain server address" << _assignmentServerHostname;
}
@ -119,7 +120,8 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
// did we get an assignment-client monitor port?
if (assignmentMonitorPort > 0) {
_assignmentClientMonitorSocket = SockAddr(DEFAULT_ASSIGNMENT_CLIENT_MONITOR_HOSTNAME, assignmentMonitorPort);
_assignmentClientMonitorSocket = SockAddr(SocketType::UDP, DEFAULT_ASSIGNMENT_CLIENT_MONITOR_HOSTNAME,
assignmentMonitorPort);
_assignmentClientMonitorSocket.setObjectName("AssignmentClientMonitor");
qCDebug(assignment_client) << "Assignment-client monitor socket is" << _assignmentClientMonitorSocket;
@ -132,6 +134,18 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
PacketReceiver::makeUnsourcedListenerReference<AssignmentClient>(this, &AssignmentClient::handleCreateAssignmentPacket));
packetReceiver.registerListener(PacketType::StopNode,
PacketReceiver::makeUnsourcedListenerReference<AssignmentClient>(this, &AssignmentClient::handleStopNodePacket));
#if defined(WEBRTC_DATA_CHANNELS)
auto webrtcSocket = nodeList->getWebRTCSocket();
// Route inbound WebRTC signaling messages from the Domain Server.
packetReceiver.registerListener(PacketType::WebRTCSignaling,
PacketReceiver::makeUnsourcedListenerReference<AssignmentClient>(this, &AssignmentClient::handleWebRTCSignalingPacket));
connect(this, &AssignmentClient::webrtcSignalingMessageFromUserClient, webrtcSocket, &WebRTCSocket::onSignalingMessage);
// Route outbound WebRTC signaling messages via the Domain Server to the user client.
connect(webrtcSocket, &WebRTCSocket::sendSignalingMessage, this, &AssignmentClient::sendSignalingMessageToUserClient);
#endif
}
void AssignmentClient::stopAssignmentClient() {
@ -340,3 +354,45 @@ void AssignmentClient::assignmentCompleted() {
_isAssigned = false;
}
#if defined(WEBRTC_DATA_CHANNELS)
void AssignmentClient::handleWebRTCSignalingPacket(QSharedPointer<ReceivedMessage> message) {
auto messageString = message->readString();
auto json = QJsonDocument::fromJson(messageString.toUtf8()).object();
if (json.keys().contains("echo")) {
// Echo message back to sender.
if (!json.keys().contains("to") || !json.keys().contains("from")) {
qCDebug(assignment_client) << "Invalid WebRTC signaling echo message received.";
return;
}
// Swap to/from.
auto to = json.value("to");
json.insert("to", json.value("from"));
json.insert("from", to);
// Send back to sender via the Domain Server.
auto packetList = NLPacketList::create(PacketType::WebRTCSignaling, QByteArray(), true, true);
packetList->writeString(QJsonDocument(json).toJson(QJsonDocument::Compact));
auto nodeList = DependencyManager::get<NodeList>();
auto domainServerAddress = nodeList->getDomainHandler().getSockAddr();
nodeList->sendPacketList(std::move(packetList), domainServerAddress);
} else {
// WebRTC signaling message.
emit webrtcSignalingMessageFromUserClient(json);
}
}
// Sends a signaling message from the assignment client to the user client via the Domain Server.
void AssignmentClient::sendSignalingMessageToUserClient(const QJsonObject& json) {
auto packetList = NLPacketList::create(PacketType::WebRTCSignaling, QByteArray(), true, true);
packetList->writeString(QJsonDocument(json).toJson(QJsonDocument::Compact));
auto nodeList = DependencyManager::get<NodeList>();
auto domainServerAddress = nodeList->getDomainHandler().getSockAddr();
nodeList->sendPacketList(std::move(packetList), domainServerAddress);
}
#endif

View file

@ -17,6 +17,8 @@
#include <QtCore/QPointer>
#include <QtCore/QSharedPointer>
#include <shared/WebRTC.h>
#include "ThreadedAssignment.h"
class QSharedMemory;
@ -30,19 +32,26 @@ public:
bool disableDomainPortAutoDiscovery);
~AssignmentClient();
public slots:
void aboutToQuit();
private slots:
void sendAssignmentRequest();
void assignmentCompleted();
void handleAuthenticationRequest();
void sendStatusPacketToACM();
void stopAssignmentClient();
public slots:
void aboutToQuit();
private slots:
void handleCreateAssignmentPacket(QSharedPointer<ReceivedMessage> message);
void handleStopNodePacket(QSharedPointer<ReceivedMessage> message);
#if defined(WEBRTC_DATA_CHANNELS)
void handleWebRTCSignalingPacket(QSharedPointer<ReceivedMessage> message);
void sendSignalingMessageToUserClient(const QJsonObject& json);
#endif
signals:
#if defined(WEBRTC_DATA_CHANNELS)
void webrtcSignalingMessageFromUserClient(const QJsonObject& json);
#endif
private:
void setUpStatusToMonitor();

View file

@ -1,3 +1,3 @@
Source: webrtc
Version: 20190626
Version: 20210105
Description: WebRTC

View file

@ -0,0 +1,215 @@
# WebRTC
WebRTC Information:
- https://webrtc.org/
- https://webrtc.googlesource.com/src
- https://webrtc.googlesource.com/src/+/refs/heads/master/docs/native-code/index.md
- https://webrtc.googlesource.com/src/+/refs/heads/master/docs/native-code/development/prerequisite-sw/index.md
- https://webrtc.googlesource.com/src/+/refs/heads/master/docs/native-code/development/index.md
- https://www.chromium.org/developers/calendar
- https://github.com/microsoft/winrtc
- https://docs.microsoft.com/en-us/winrtc/getting-started
- https://groups.google.com/g/discuss-webrtc \
See "PSA" posts for release information.
- https://bugs.chromium.org/p/webrtc/issues/list
- https://stackoverflow.com/questions/27809193/webrtc-not-building-for-windows
- https://github.com/aisouard/libwebrtc/issues/57
## Windows - M84
WebRTC's M84 release is currently used because it corresponded to Microsoft's latest WinRTC release at the time of development,
and WinRTC is a source of potentially useful patches.
The following notes document how the M84-based Windows VCPKG was created, using Visual Studio 2019.
### Set Up depot_tools
Install Google's depot_tools.
- Download depot_tools.zip.
- Extract somewhere.
- Add the extracted directory to the start of the system `PATH` environment variable.
Configure depot_tools.
- Set an environment variable `DEPOT_TOOLS_WIN_TOOLCHAIN=0`
- Set an environment variable `GYP_MSVS_VERSION=2019`
Initialize depot_tools.
- VS2019 developer command prompt in the directory where the source tree will be created.
- `gclient`
### Get the Code
Fetch the code into a *\src* subdirectory. This may take some time!
- `fetch --nohooks webrtc`
Switch to the M84 branch.
- `cd src`
- `git checkout branch-heads/4147`
Fetch all the subrepositories.
- `gclient sync -D -r branch-heads/4147`
### Patch the Code
#### Modify compiler switches
- Edit *build\config\win\BUILD.gn*:
- Change all `/MT` to `/MD`, and `/MTd` to `/MDd`.
- Change all `cflags = [ "/MDd" ]` to `[ "/MDd", "-D_ITERATOR_DEBUG_LEVEL=2", "-D_HAS_ITERATOR_DEBUGGING=1" ]`.
- Edit *build\config\compiler\BUILD.gn*:\
Change:
```
if (is_win) {
if (is_clang) {
cflags = [ "/Z7" ] # Debug information in the .obj files.
} else {
cflags = [ "/Zi" ] # Produce PDB file, no edit and continue.
}
```
to:
```
if (is_win) {
if (is_clang) {
cflags = [ "/Z7", "/std:c++17", "/Zc:__cplusplus" ] # Debug information in the .obj files.
} else {
cflags = [ "/Zi", "/std:c++17", "/Zc:__cplusplus" ] # Produce PDB file, no edit and continue.
}
```
#### H265 Codec Fixes
https://bugs.webrtc.org/9213#c13
- Edit the following files:
- *modules\video_coding\codecs\h264\h264_color_space.h*
- *modules\video_coding\codecs\h264\h264_decoder_impl.h*
- *modules\video_coding\codecs\h264\h264_encoder_impl.h*
In each, comment out the following lines:
```
#if defined(WEBRTC_WIN) && !defined(__clang__)
#error "See: bugs.webrtc.org/9213#c13."
#endif
```
- Edit *third_party\ffmpeg\libavcodec\fft_template.c*:\
Comment out all of `ff_fft_init` except the fail clause at the end.
- Edit *third_party\ffmpeg\libavcodec\pcm.c*:\
Comment out last line, containing `PCM Archimedes VIDC`.
- Edit *third_party\ffmpeg\libavutil\x86\imgutils_init.c*:\
Add the following method to the end of the file:
```
void avpriv_emms_asm(void) {} // Fix missing symbol in FFMPEG.
```
#### Exclude BoringSSL
A separate OpenSSL VCPKG is used for building Vircadia.
The following patches are needed even though SSL is excluded in the `gn gen` build commands.
- Rename *third_party\boringssl* to *third_party\boringssl-NO*.
- Edit *third_party\libsrtp\BUILD.gn:\
Change:
```
public_deps = [
"//third_party/boringssl:boringssl",
]
```
To:
```
public_deps = [
# "//third_party/boringssl:boringssl",
]
```
- Edit *third_party\usrsctp\BUILD.gn*:\
Change:
```
deps = [ "//third_party/boringssl" ]
```
To:
```
deps = [
# "//third_party/boringssl"
]
```
- Edit *base\BUILD.gn*:\
In the code under:
```
# Use the base implementation of hash functions when building for
# NaCl. Otherwise, use boringssl.
```
Change:
```
if (is_nacl) {
```
To:
```
# if (is_nacl) {
if (true) {
```
- Edit *rtc_base\BUILD.gn*:\
Change:
```
if (rtc_build_ssl) {
deps += [ "//third_party/boringssl" ]
} else {
```
To:
```
if (rtc_build_ssl) {
# deps += [ "//third_party/boringssl" ]
} else {
```
### Set Up OpenSSL
Do one of the following to provide OpenSSL for building against:
a. If you have built Vircadia, find the **HIFI_VCPKG_BASE** subdirectory used in your build and make note of the path to and
including the *installed\x64-windows\include* directory (which includes an *openssl* directory).
a. Follow https://github.com/vircadia/vcpkg to install *vcpkg* and then *openssl*. Make note of the path to and including the
*packages\openssl-windows_x64-windows\include* directory (which includes an *openssl* directory).
Copy the *\<path\>\openssl* directory to the following locations (i.e., add as *openssl* subdirectories):
- *third_party\libsrtp\crypto\include*
- *third_party\usrsctp\usrsctplib\usrsctplib*
Also use the path in the `gn gen` commands, below, making sure to escape `\`s as `\\`s.
### Build and Package
Use a VS2019 developer command prompt in the *src* directory.
Create a release build of the WebRTC library:
- `gn gen --ide=vs2019 out\Release --filters=//:webrtc "--args=is_debug=false is_clang=false use_custom_libcxx=false libcxx_is_shared=true symbol_level=2 use_lld=false rtc_include_tests=false rtc_build_tools=false rtc_build_examples=false proprietary_codecs=true rtc_use_h264=true enable_libaom=false rtc_enable_protobuf=false rtc_build_ssl=false rtc_ssl_root=\"<path>\""`
- `ninja -C out\Release`
Create a debug build of the WebRTC library:
- `gn gen --ide=vs2019 out\Debug --filters=//:webrtc "--args=is_debug=true is_clang=false use_custom_libcxx=false libcxx_is_shared=true enable_iterator_debugging=true use_lld=false rtc_include_tests=false rtc_build_tools=false rtc_build_examples=false proprietary_codecs=true rtc_use_h264=true enable_libaom=false rtc_enable_protobuf=false rtc_build_ssl=false rtc_ssl_root=\"<path>\""`
- `ninja -C out\Debug`
Create VCPKG file:
- Assemble files in VCPKG directory structure: Use the *copy-VCPKG-files-win.cmd* batch file per instructions in it.
`cd ..`\
`copy-VCPKG-files-win`
- Zip up the VCPKG *webrtc* directory created by the batch file.
`cd vcpkg`\
`7z a -tzip webrtc-m84-yyyymmdd-windows.zip webrtc`
- Calculate the SHA512 of the zip file. E.g., using a Windows PowerShell command window:\
`Get-FileHash <filename> -Algorithm SHA512 | Format-List`
- Convert the SHA512 to lower case. E.g., using Microsoft Word, select the SHA512 text and use Shift-F3 to change the case.
- Host the zip file on the Web.
- Update *CONTROL* and *portfile.cmake* with version number (revision date), zip file Web URL, and SHA512.
### Tidying up
Disable the depot_tools:
- Rename the *depot_tools* directory so that these tools don't interfere with the rest of your development environment for
other work.
## Linux - M81
The original, High Fidelity-provided WebRTC VCPKG library is used for AEC (audio echo cancellation) only.
**TODO:** Update to M84 and include WebRTC components per Windows WebRTC.
## MacOS - M78
The original, High Fidelity-provided WebRTC VCPKG library is used for AEC (audio echo cancellation) only.
**TODO:** Update to M84 and include WebRTC components per Windows WebRTC.

View file

@ -0,0 +1,36 @@
rem Copy this file to a directory above the WebRTC \src directory and run it from there in a command window.
set WEBRTC_SRC_DIR=src
set RELEASE_LIB_DIR=%WEBRTC_SRC_DIR%\out\Release\obj
set DEBUG_LIB_DIR=%WEBRTC_SRC_DIR%\out\Debug\obj
set VCPKG_TGT_DIR=vcpkg
if exist %VCPKG_TGT_DIR% rd /s /q %VCPKG_TGT_DIR%
mkdir %VCPKG_TGT_DIR%
rem License and .lib files
mkdir %VCPKG_TGT_DIR%\webrtc\share\webrtc\
copy %WEBRTC_SRC_DIR%\LICENSE %VCPKG_TGT_DIR%\webrtc\share\webrtc\copyright
xcopy /v %RELEASE_LIB_DIR%\webrtc.lib %VCPKG_TGT_DIR%\webrtc\lib\
xcopy /v %DEBUG_LIB_DIR%\webrtc.lib %VCPKG_TGT_DIR%\webrtc\debug\lib\
rem Header files
mkdir %VCPKG_TGT_DIR%\webrtc\include\webrtc\
copy %WEBRTC_SRC_DIR%\common_types.h %VCPKG_TGT_DIR%\webrtc\include\webrtc
xcopy /v /s /i %WEBRTC_SRC_DIR%\api\*.h %VCPKG_TGT_DIR%\webrtc\include\webrtc\api
xcopy /v /s /i %WEBRTC_SRC_DIR%\audio\*.h %VCPKG_TGT_DIR%\webrtc\include\webrtc\audio
xcopy /v /s /i %WEBRTC_SRC_DIR%\base\*.h %VCPKG_TGT_DIR%\webrtc\include\webrtc\base
xcopy /v /s /i %WEBRTC_SRC_DIR%\call\*.h %VCPKG_TGT_DIR%\webrtc\include\webrtc\call
xcopy /v /s /i %WEBRTC_SRC_DIR%\common_audio\*.h %VCPKG_TGT_DIR%\webrtc\include\webrtc\common_audio
xcopy /v /s /i %WEBRTC_SRC_DIR%\common_video\*.h %VCPKG_TGT_DIR%\webrtc\include\webrtc\common_video
xcopy /v /s /i %WEBRTC_SRC_DIR%\logging\*.h %VCPKG_TGT_DIR%\webrtc\include\webrtc\logging
xcopy /v /s /i %WEBRTC_SRC_DIR%\media\*.h %VCPKG_TGT_DIR%\webrtc\include\webrtc\media
xcopy /v /s /i %WEBRTC_SRC_DIR%\modules\*.h %VCPKG_TGT_DIR%\webrtc\include\webrtc\modules
xcopy /v /s /i %WEBRTC_SRC_DIR%\p2p\*.h %VCPKG_TGT_DIR%\webrtc\include\webrtc\p2p
xcopy /v /s /i %WEBRTC_SRC_DIR%\pc\*.h %VCPKG_TGT_DIR%\webrtc\include\webrtc\pc
xcopy /v /s /i %WEBRTC_SRC_DIR%\rtc_base\*.h %VCPKG_TGT_DIR%\webrtc\include\webrtc\rtc_base
xcopy /v /s /i %WEBRTC_SRC_DIR%\rtc_tools\*.h %VCPKG_TGT_DIR%\webrtc\include\webrtc\rtc_tools
xcopy /v /s /i %WEBRTC_SRC_DIR%\stats\*.h %VCPKG_TGT_DIR%\webrtc\include\webrtc\stats
xcopy /v /s /i %WEBRTC_SRC_DIR%\system_wrappers\*.h %VCPKG_TGT_DIR%\webrtc\include\webrtc\system_wrappers
xcopy /v /s /i %WEBRTC_SRC_DIR%\third_party\abseil-cpp\absl\*.h %VCPKG_TGT_DIR%\webrtc\include\webrtc\absl
xcopy /v /s /i %WEBRTC_SRC_DIR%\third_party\libyuv\include\libyuv\*.h %VCPKG_TGT_DIR%\webrtc\include\webrtc\libyuv
xcopy /v /s /i %WEBRTC_SRC_DIR%\video\*.h %VCPKG_TGT_DIR%\webrtc\include\webrtc\video

View file

@ -1,5 +1,5 @@
include(vcpkg_common_functions)
set(WEBRTC_VERSION 20190626)
set(WEBRTC_VERSION 20210105)
set(MASTER_COPY_SOURCE_PATH ${CURRENT_BUILDTREES_DIR}/src)
file(READ "${VCPKG_ROOT_DIR}/_env/EXTERNAL_BUILD_ASSETS.txt" EXTERNAL_BUILD_ASSETS)
@ -9,9 +9,9 @@ if (ANDROID)
elseif (WIN32)
vcpkg_download_distfile(
WEBRTC_SOURCE_ARCHIVE
URLS "${EXTERNAL_BUILD_ASSETS}/seth/webrtc-20190626-windows.zip"
SHA512 c0848eddb1579b3bb0496b8785e24f30470f3c477145035fd729264a326a467b9467ae9f426aa5d72d168ad9e9bf2c279150744832736bdf39064d24b04de1a3
FILENAME webrtc-20190626-windows.zip
URLS "${EXTERNAL_BUILD_ASSETS}/dependencies/vcpkg/webrtc-m84-20210105-windows.zip"
SHA512 12847f7e9df2e0539a6b017db88012a8978b1aa37ff2e8dbf019eb7438055395fdda3a74dc669b0a30330973a83bc57e86eca6f59b1c9eff8e2145a7ea4a532a
FILENAME webrtc-m84-20210105-windows.zip
)
elseif (APPLE)
vcpkg_download_distfile(
@ -24,9 +24,9 @@ else ()
# else Linux desktop
vcpkg_download_distfile(
WEBRTC_SOURCE_ARCHIVE
URLS "${EXTERNAL_BUILD_ASSETS}/seth/webrtc-20190626-linux.tar.gz"
SHA512 07d7776551aa78cb09a3ef088a8dee7762735c168c243053b262083d90a1d258cec66dc386f6903da5c4461921a3c2db157a1ee106a2b47e7756cb424b66cc43
FILENAME webrtc-20190626-linux.tar.gz
URLS "${EXTERNAL_BUILD_ASSETS}/dependencies/vcpkg/webrtc-m84-gcc-linux.tar.xz"
SHA512 f7c5f93566e2e79241cbb9628ab47302dd48739bb6a022c351be75553060fac4221892d094306a572cb3ec94c5031d7e812f07e7b3c0102be8c01b8c231f8ea0
FILENAME webrtc-m84-gcc-linux.tar.xz
)
endif ()

View file

@ -1,5 +1,5 @@
{
"version": 2.5,
"version": 2.6,
"settings": [
{
"name": "metaverse",
@ -73,6 +73,28 @@
}
]
},
{
"name": "webrtc",
"label": "Networking / WebRTC",
"settings": [
{
"name": "enable_webrtc",
"label": "Enable WebRTC Client Connections",
"help": "Allow web clients to connect over WebRTC data channels.",
"type": "checkbox",
"default": false,
"advanced": true
},
{
"name": "enable_webrtc_websocket_ssl",
"label": "Enable WebRTC WebSocket SSL",
"help": "Use secure WebSocket (wss:// protocol) for WebRTC signaling channel. If \"on\", the key, cert, and CA files are expected to be in the local Vircadia app directory, in a /domain-server/ subdirectory with filenames vircadia-cert.key, vircadia-cert.crt, and vircadia-crt-ca.crt.",
"type": "checkbox",
"default": false,
"advanced": true
}
]
},
{
"name": "authentication",
"label": "Networking / WordPress OAuth2",

View file

@ -194,7 +194,8 @@ bool DomainServer::forwardMetaverseAPIRequest(HTTPConnection* connection,
DomainServer::DomainServer(int argc, char* argv[]) :
QCoreApplication(argc, argv),
_gatekeeper(this),
_httpManager(QHostAddress::AnyIPv4, DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this)
_httpManager(QHostAddress::AnyIPv4, DOMAIN_SERVER_HTTP_PORT,
QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this)
{
if (_parentPID != -1) {
watchParentProcess(_parentPID);
@ -270,12 +271,32 @@ DomainServer::DomainServer(int argc, char* argv[]) :
_settingsManager.apiRefreshGroupInformation();
#if defined(WEBRTC_DATA_CHANNELS)
const QString WEBRTC_ENABLE = "webrtc.enable_webrtc";
bool isWebRTCEnabled = _settingsManager.valueForKeyPath(WEBRTC_ENABLE).toBool();
qCDebug(domain_server) << "WebRTC enabled:" << isWebRTCEnabled;
// The domain server's WebRTC signaling server is used by the domain server and the assignment clients, so disabling it
// disables WebRTC for the server as a whole.
if (isWebRTCEnabled) {
const QString WEBRTC_WSS_ENABLE = "webrtc.enable_webrtc_websocket_ssl";
bool isWebRTCEnabled = _settingsManager.valueForKeyPath(WEBRTC_WSS_ENABLE).toBool();
qCDebug(domain_server) << "WebRTC WSS enabled:" << isWebRTCEnabled;
_webrtcSignalingServer.reset(new WebRTCSignalingServer(this, isWebRTCEnabled));
}
#endif
setupNodeListAndAssignments();
updateReplicatedNodes();
updateDownstreamNodes();
updateUpstreamNodes();
#if defined(WEBRTC_DATA_CHANNELS)
if (isWebRTCEnabled) {
setUpWebRTCSignalingServer();
}
#endif
if (_type != NonMetaverse) {
// if we have a metaverse domain, we'll use an access token for API calls
resetAccountManagerAccessToken();
@ -762,7 +783,8 @@ void DomainServer::setupNodeListAndAssignments() {
auto nodeList = DependencyManager::set<LimitedNodeList>(domainServerPort, domainServerDTLSPort);
// no matter the local port, save it to shared mem so that local assignment clients can ask what it is
nodeList->putLocalPortIntoSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, this, nodeList->getSocketLocalPort());
nodeList->putLocalPortIntoSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, this,
nodeList->getSocketLocalPort(SocketType::UDP));
// store our local http ports in shared memory
quint16 localHttpPort = DOMAIN_SERVER_HTTP_PORT;
@ -873,6 +895,73 @@ void DomainServer::setupNodeListAndAssignments() {
addStaticAssignmentsToQueue();
}
#if defined(WEBRTC_DATA_CHANNELS)
// Sets up the WebRTC signaling server that's hosted by the domain server.
void DomainServer::setUpWebRTCSignalingServer() {
// Bind the WebRTC signaling server's WebSocket to its port.
bool isBound = _webrtcSignalingServer->bind(QHostAddress::AnyIPv4, DEFAULT_DOMAIN_SERVER_WS_PORT);
if (!isBound) {
qWarning() << "WebRTC signaling server not bound to port. WebRTC connections are not supported.";
return;
}
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
// Route inbound WebRTC signaling messages received from user clients.
connect(_webrtcSignalingServer.get(), &WebRTCSignalingServer::messageReceived,
this, &DomainServer::routeWebRTCSignalingMessage);
// Route domain server signaling messages.
auto webrtcSocket = limitedNodeList->getWebRTCSocket();
connect(this, &DomainServer::webrtcSignalingMessageForDomainServer, webrtcSocket, &WebRTCSocket::onSignalingMessage);
connect(webrtcSocket, &WebRTCSocket::sendSignalingMessage,
_webrtcSignalingServer.get(), &WebRTCSignalingServer::sendMessage);
// Forward signaling messages received from assignment clients to user client.
PacketReceiver& packetReceiver = limitedNodeList->getPacketReceiver();
packetReceiver.registerListener(PacketType::WebRTCSignaling,
PacketReceiver::makeUnsourcedListenerReference<DomainServer>(this,
&DomainServer::forwardAssignmentClientSignalingMessageToUserClient));
connect(this, &DomainServer::webrtcSignalingMessageForUserClient,
_webrtcSignalingServer.get(), &WebRTCSignalingServer::sendMessage);
}
// Routes an inbound WebRTC signaling message received from a client app to the appropriate recipient.
void DomainServer::routeWebRTCSignalingMessage(const QJsonObject& json) {
if (json.value("to").toString() == NodeType::DomainServer) {
emit webrtcSignalingMessageForDomainServer(json);
} else {
sendWebRTCSignalingMessageToAssignmentClient(json);
}
}
// Sends a WebRTC signaling message to the target AC contained in the message.
void DomainServer::sendWebRTCSignalingMessageToAssignmentClient(const QJsonObject& json) {
NodeType_t destinationNodeType = NodeType::fromChar(json.value("to").toString().at(0));
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
auto destinationNode = limitedNodeList->soloNodeOfType(destinationNodeType);
if (!destinationNode) {
qWarning() << NodeType::getNodeTypeName(destinationNodeType) << "not found for WebRTC signaling message.";
return;
}
// Use an NLPacketList because the signaling message is not necessarily small.
auto packetList = NLPacketList::create(PacketType::WebRTCSignaling, QByteArray(), true, true);
packetList->writeString(QJsonDocument(json).toJson(QJsonDocument::Compact));
limitedNodeList->sendPacketList(std::move(packetList), *destinationNode);
}
// Forwards a WebRTC signaling message received from an assignment client to the relevant user client.
void DomainServer::forwardAssignmentClientSignalingMessageToUserClient(QSharedPointer<ReceivedMessage> message) {
auto messageString = message->readString();
auto json = QJsonDocument::fromJson(messageString.toUtf8()).object();
emit webrtcSignalingMessageForUserClient(json);
}
#endif
bool DomainServer::resetAccountManagerAccessToken() {
if (!_oauthProviderURL.isEmpty()) {
// check for an access-token in our settings, can optionally be overidden by env value
@ -3071,6 +3160,7 @@ ReplicationServerInfo serverInformationFromSettings(QVariantMap serverMap, Repli
// read the address and port and construct a SockAddr from them
serverInfo.sockAddr = {
SocketType::UDP,
serverMap[REPLICATION_SERVER_ADDRESS].toString(),
(quint16) serverMap[REPLICATION_SERVER_PORT].toString().toInt()
};
@ -3651,7 +3741,7 @@ void DomainServer::randomizeICEServerAddress(bool shouldTriggerHostLookup) {
indexToTry = distribution(generator);
}
_iceServerSocket = SockAddr { candidateICEAddresses[indexToTry], ICE_SERVER_DEFAULT_PORT };
_iceServerSocket = SockAddr { SocketType::UDP, candidateICEAddresses[indexToTry], ICE_SERVER_DEFAULT_PORT };
qCInfo(domain_server_ice) << "Set candidate ice-server socket to" << _iceServerSocket;
// clear our number of hearbeat denials, this should be re-set on ice-server change

View file

@ -27,6 +27,8 @@
#include <Assignment.h>
#include <HTTPSConnection.h>
#include <LimitedNodeList.h>
#include <shared/WebRTC.h>
#include <webrtc/WebRTCSignalingServer.h>
#include "AssetsBackupHandler.h"
#include "DomainGatekeeper.h"
@ -141,12 +143,17 @@ private slots:
void updateReplicatedNodes();
void updateDownstreamNodes();
void updateUpstreamNodes();
void initializeExporter();
void initializeMetadataExporter();
void tokenGrantFinished();
void profileRequestFinished();
#if defined(WEBRTC_DATA_CHANNELS)
void forwardAssignmentClientSignalingMessageToUserClient(QSharedPointer<ReceivedMessage> message);
#endif
void aboutToQuit();
signals:
@ -154,6 +161,12 @@ signals:
void userConnected();
void userDisconnected();
#if defined(WEBRTC_DATA_CHANNELS)
void webrtcSignalingMessageForDomainServer(const QJsonObject& json);
void webrtcSignalingMessageForUserClient(const QJsonObject& json);
#endif
private:
QUuid getID();
@ -235,6 +248,12 @@ private:
std::initializer_list<QString> optionalData = { },
bool requireAccessToken = true);
#if defined(WEBRTC_DATA_CHANNELS)
void setUpWebRTCSignalingServer();
void routeWebRTCSignalingMessage(const QJsonObject& json);
void sendWebRTCSignalingMessageToAssignmentClient(const QJsonObject& json);
#endif
QString operationToString(const QNetworkAccessManager::Operation &op);
SubnetList _acSubnetWhitelist;
@ -312,6 +331,10 @@ private:
std::unordered_map<int, std::unique_ptr<QTemporaryFile>> _pendingContentFiles;
QThread _assetClientThread;
#if defined(WEBRTC_DATA_CHANNELS)
std::unique_ptr<WebRTCSignalingServer> _webrtcSignalingServer { nullptr };
#endif
};

View file

@ -551,6 +551,8 @@ void DomainServerSettingsManager::setupConfigMap(const QString& userConfigFilena
packPermissions();
}
// No migration needed to version 2.6.
// write the current description version to our settings
*versionVariant = _descriptionVersion;

View file

@ -17,7 +17,7 @@
NodeConnectionData NodeConnectionData::fromDataStream(QDataStream& dataStream, const SockAddr& senderSockAddr,
bool isConnectRequest) {
NodeConnectionData newHeader;
if (isConnectRequest) {
dataStream >> newHeader.connectUUID;
@ -51,9 +51,29 @@ NodeConnectionData NodeConnectionData::fromDataStream(QDataStream& dataStream, c
dataStream >> newHeader.lastPingTimestamp;
SocketType publicSocketType, localSocketType;
dataStream >> newHeader.nodeType
>> newHeader.publicSockAddr >> newHeader.localSockAddr
>> publicSocketType >> newHeader.publicSockAddr >> localSocketType >> newHeader.localSockAddr
>> newHeader.interestList >> newHeader.placeName;
newHeader.publicSockAddr.setType(publicSocketType);
newHeader.localSockAddr.setType(localSocketType);
// For WebRTC connections, the user client's signaling channel WebSocket address is used instead of the actual data
// channel's address.
if (senderSockAddr.getType() == SocketType::WebRTC) {
if (newHeader.publicSockAddr.getType() != SocketType::WebRTC
|| newHeader.localSockAddr.getType() != SocketType::WebRTC) {
qDebug() << "Inconsistent WebRTC socket types!";
}
// We don't know whether it's a public or local connection so set both the same.
auto address = senderSockAddr.getAddress();
auto port = senderSockAddr.getPort();
newHeader.publicSockAddr.setAddress(address);
newHeader.publicSockAddr.setPort(port);
newHeader.localSockAddr.setAddress(address);
newHeader.localSockAddr.setPort(port);
}
newHeader.senderSockAddr = senderSockAddr;

View file

@ -16,6 +16,7 @@
#include <QtCore/QDataStream>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <QtCore/QTimer>
#include <QtNetwork/QNetworkReply>
#include <QtNetwork/QNetworkRequest>
@ -39,7 +40,7 @@ IceServer::IceServer(int argc, char* argv[]) :
{
// start the ice-server socket
qDebug() << "ice-server socket is listening on" << ICE_SERVER_DEFAULT_PORT;
_serverSocket.bind(QHostAddress::AnyIPv4, ICE_SERVER_DEFAULT_PORT);
_serverSocket.bind(SocketType::UDP, QHostAddress::AnyIPv4, ICE_SERVER_DEFAULT_PORT);
// set processPacket as the verified packet callback for the udt::Socket
_serverSocket.setPacketHandler([this](std::unique_ptr<udt::Packet> packet) { processPacket(std::move(packet)); });

View file

@ -64,7 +64,11 @@ void OtherAvatar::removeOrb() {
void OtherAvatar::updateOrbPosition() {
if (!_otherAvatarOrbMeshPlaceholderID.isNull()) {
EntityItemProperties properties;
properties.setPosition(getHead()->getPosition());
glm::vec3 headPosition;
if (!_skeletonModel->getHeadPosition(headPosition)) {
headPosition = getWorldPosition();
}
properties.setPosition(headPosition);
DependencyManager::get<EntityScriptingInterface>()->editEntity(_otherAvatarOrbMeshPlaceholderID, properties);
}
}

View file

@ -551,7 +551,7 @@ void setupPreferences() {
auto getter = [nodeListWeak] {
auto nodeList = nodeListWeak.lock();
if (nodeList) {
return static_cast<int>(nodeList->getSocketLocalPort());
return static_cast<int>(nodeList->getSocketLocalPort(SocketType::UDP));
} else {
return -1;
}
@ -559,7 +559,7 @@ void setupPreferences() {
auto setter = [nodeListWeak](int preset) {
auto nodeList = nodeListWeak.lock();
if (nodeList) {
nodeList->setSocketLocalPort(static_cast<quint16>(preset));
nodeList->setSocketLocalPort(SocketType::UDP, static_cast<quint16>(preset));
}
};
auto preference = new IntSpinnerPreference(NETWORKING, "Listening Port", getter, setter);

View file

@ -4,6 +4,7 @@
//
// Created by Stephen Birarda on 1/22/13.
// Copyright 2013 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
@ -363,7 +364,7 @@ AudioClient::AudioClient() {
configureReverb();
#if defined(WEBRTC_ENABLED)
#if defined(WEBRTC_AUDIO)
configureWebrtc();
#endif
@ -941,7 +942,7 @@ void AudioClient::Gate::flush() {
void AudioClient::handleNoisyMutePacket(QSharedPointer<ReceivedMessage> message) {
if (!_muted) {
if (!_isMuted) {
setMuted(true);
// have the audio scripting interface emit a signal to say we were muted by the mixer
@ -988,7 +989,7 @@ void AudioClient::selectAudioFormat(const QString& selectedCodecName) {
_selectedCodecName = selectedCodecName;
qCDebug(audioclient) << "Selected Codec:" << _selectedCodecName << "isStereoInput:" << _isStereoInput;
qCDebug(audioclient) << "Selected codec:" << _selectedCodecName << "; Is stereo input:" << _isStereoInput;
// release any old codec encoder/decoder first...
if (_codec && _encoder) {
@ -1004,7 +1005,7 @@ void AudioClient::selectAudioFormat(const QString& selectedCodecName) {
_codec = plugin;
_receivedAudioStream.setupCodec(plugin, _selectedCodecName, AudioConstants::STEREO);
_encoder = plugin->createEncoder(AudioConstants::SAMPLE_RATE, _isStereoInput ? AudioConstants::STEREO : AudioConstants::MONO);
qCDebug(audioclient) << "Selected Codec Plugin:" << _codec.get();
qCDebug(audioclient) << "Selected codec plugin:" << _codec.get();
break;
}
}
@ -1142,7 +1143,7 @@ void AudioClient::setReverbOptions(const AudioEffectOptions* options) {
}
}
#if defined(WEBRTC_ENABLED)
#if defined(WEBRTC_AUDIO)
static void deinterleaveToFloat(const int16_t* src, float* const* dst, int numFrames, int numChannels) {
for (int i = 0; i < numFrames; i++) {
@ -1175,7 +1176,9 @@ void AudioClient::configureWebrtc() {
config.high_pass_filter.enabled = false;
config.echo_canceller.enabled = true;
config.echo_canceller.mobile_mode = false;
#if defined(WEBRTC_LEGACY)
config.echo_canceller.use_legacy_aec = false;
#endif
config.noise_suppression.enabled = false;
config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kModerate;
config.voice_detection.enabled = false;
@ -1261,12 +1264,12 @@ void AudioClient::processWebrtcNearEnd(int16_t* samples, int numFrames, int numC
}
}
#endif // WEBRTC_ENABLED
#endif // WEBRTC_AUDIO
void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) {
// If there is server echo, reverb will be applied to the recieved audio stream so no need to have it here.
bool hasReverb = _reverb || _receivedAudioStream.hasReverb();
if ((_muted && !_shouldEchoLocally) || !_audioOutput || (!_shouldEchoLocally && !hasReverb) || !_audioGateOpen) {
if ((_isMuted && !_shouldEchoLocally) || !_audioOutput || (!_shouldEchoLocally && !hasReverb) || !_audioGateOpen) {
return;
}
@ -1354,7 +1357,7 @@ void AudioClient::handleAudioInput(QByteArray& audioBuffer) {
bool audioGateOpen = false;
if (!_muted) {
if (!_isMuted) {
int16_t* samples = reinterpret_cast<int16_t*>(audioBuffer.data());
int numSamples = audioBuffer.size() / AudioConstants::SAMPLE_SIZE;
int numFrames = numSamples / (_isStereoInput ? AudioConstants::STEREO : AudioConstants::MONO);
@ -1375,7 +1378,7 @@ void AudioClient::handleAudioInput(QByteArray& audioBuffer) {
}
// loudness after mute/gate
_lastInputLoudness = (_muted || !audioGateOpen) ? 0.0f : _lastRawInputLoudness;
_lastInputLoudness = (_isMuted || !audioGateOpen) ? 0.0f : _lastRawInputLoudness;
// detect gate opening and closing
bool openedInLastBlock = !_audioGateOpen && audioGateOpen; // the gate just opened
@ -1462,7 +1465,7 @@ void AudioClient::handleMicAudioInput() {
}
isClipping = (_timeSinceLastClip >= 0.0f) && (_timeSinceLastClip < 2.0f); // 2 second hold time
#if defined(WEBRTC_ENABLED)
#if defined(WEBRTC_AUDIO)
if (_isAECEnabled) {
processWebrtcNearEnd(inputAudioSamples.get(), inputSamplesRequired / _inputFormat.channelCount(),
_inputFormat.channelCount(), _inputFormat.sampleRate());
@ -1479,7 +1482,7 @@ void AudioClient::handleMicAudioInput() {
emit inputLoudnessChanged(_lastSmoothedRawInputLoudness, isClipping);
if (!_muted) {
if (!_isMuted) {
possibleResampling(_inputToNetworkResampler,
inputAudioSamples.get(), networkAudioSamples,
inputSamplesRequired, numNetworkSamples,
@ -1745,10 +1748,10 @@ void AudioClient::sendMuteEnvironmentPacket() {
}
void AudioClient::setMuted(bool muted, bool emitSignal) {
if (_muted != muted) {
_muted = muted;
if (_isMuted != muted) {
_isMuted = muted;
if (emitSignal) {
emit muteToggled(_muted);
emit muteToggled(_isMuted);
}
}
}
@ -1893,7 +1896,6 @@ bool AudioClient::switchInputToAudioDevice(const HifiAudioDeviceInfo inputDevice
if (_dummyAudioInput) {
_dummyAudioInput->stop();
_dummyAudioInput->deleteLater();
_dummyAudioInput = NULL;
}
@ -2420,7 +2422,7 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) {
// limit the audio
_audio->_audioLimiter.render(mixBuffer, scratchBuffer, framesPopped);
#if defined(WEBRTC_ENABLED)
#if defined(WEBRTC_AUDIO)
if (_audio->_isAECEnabled) {
_audio->processWebrtcFarEnd(scratchBuffer, framesPopped, OUTPUT_CHANNEL_COUNT, _audio->_outputFormat.sampleRate());
}
@ -2505,4 +2507,4 @@ void AudioClient::setInputVolume(float volume, bool emitSignal) {
emit inputVolumeChanged(_audioInput->volume());
}
}
}
}

View file

@ -58,6 +58,12 @@
#include "AudioFileWav.h"
#include "HifiAudioDeviceInfo.h"
#if defined(WEBRTC_AUDIO)
# define WEBRTC_APM_DEBUG_DUMP 0
# include <modules/audio_processing/include/audio_processing.h>
# include "modules/audio_processing/audio_processing_impl.h"
#endif
#ifdef _WIN32
#pragma warning( push )
#pragma warning( disable : 4273 )
@ -212,7 +218,7 @@ public slots:
void audioMixerKilled();
void setMuted(bool muted, bool emitSignal = true);
bool isMuted() { return _muted; }
bool isMuted() { return _isMuted; }
virtual bool setIsStereoInput(bool stereo) override;
virtual bool isStereoInput() override { return _isStereoInput; }
@ -405,7 +411,7 @@ private:
float _timeSinceLastClip{ -1.0f };
int _totalInputAudioSamples;
bool _muted{ false };
bool _isMuted{ false };
bool _shouldEchoLocally{ false };
bool _shouldEchoToServer{ false };
bool _isNoiseGateEnabled{ true };
@ -452,7 +458,7 @@ private:
void updateReverbOptions();
void handleLocalEchoAndReverb(QByteArray& inputByteArray);
#if defined(WEBRTC_ENABLED)
#if defined(WEBRTC_AUDIO)
static const int WEBRTC_SAMPLE_RATE_MAX = 96000;
static const int WEBRTC_CHANNELS_MAX = 2;
static const int WEBRTC_FRAMES_MAX = webrtc::AudioProcessing::kChunkSizeMs * WEBRTC_SAMPLE_RATE_MAX / 1000;

View file

@ -167,7 +167,7 @@ int InboundAudioStream::parseData(ReceivedMessage& message) {
bool packetPCM = codecInPacket == "pcm" || codecInPacket == "";
if (codecInPacket == _selectedCodecName || (packetPCM && selectedPCM)) {
auto afterProperties = message.readWithoutCopy(message.getBytesLeftToRead());
parseAudioData(message.getType(), afterProperties);
parseAudioData(afterProperties);
_mismatchedAudioCodecCount = 0;
} else {
@ -267,7 +267,7 @@ int InboundAudioStream::lostAudioData(int numPackets) {
return 0;
}
int InboundAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties) {
int InboundAudioStream::parseAudioData(const QByteArray& packetAfterStreamProperties) {
QByteArray decodedBuffer;
// may block on the real-time thread, which is acceptible as

View file

@ -132,7 +132,7 @@ protected:
/// parses the audio data in the network packet.
/// default implementation assumes packet contains raw audio samples after stream properties
virtual int parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties);
virtual int parseAudioData(const QByteArray& packetAfterStreamProperties);
/// produces audio data for lost network packets.
virtual int lostAudioData(int numPackets);

View file

@ -61,7 +61,7 @@ int MixedProcessedAudioStream::lostAudioData(int numPackets) {
return 0;
}
int MixedProcessedAudioStream::parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties) {
int MixedProcessedAudioStream::parseAudioData(const QByteArray& packetAfterStreamProperties) {
QByteArray decodedBuffer;
// may block on the real-time thread, which is acceptible as

View file

@ -34,7 +34,7 @@ public:
protected:
int writeDroppableSilentFrames(int silentFrames) override;
int parseAudioData(PacketType type, const QByteArray& packetAfterStreamProperties) override;
int parseAudioData(const QByteArray& packetAfterStreamProperties) override;
int lostAudioData(int numPackets) override;
private:

View file

@ -1,13 +1,18 @@
set(TARGET_NAME networking)
setup_hifi_library(Network)
setup_hifi_library(Network WebSockets)
link_hifi_libraries(shared platform)
target_openssl()
target_tbb()
if (WIN32 OR (UNIX AND NOT APPLE))
target_webrtc()
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

@ -3,14 +3,16 @@
// libraries/networking/src
//
// Copyright 2017 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
#include "BaseAssetScriptingInterface.h"
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QMimeDatabase>
#include <QThread>

View file

@ -37,7 +37,7 @@
DomainHandler::DomainHandler(QObject* parent) :
QObject(parent),
_sockAddr(SockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)),
_sockAddr(SockAddr(SocketType::UDP, QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)),
_icePeer(this),
_settingsTimer(this),
_apiRefreshTimer(this)
@ -282,7 +282,7 @@ void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname,
SockAddr* replaceableSockAddr = &_iceServerSockAddr;
replaceableSockAddr->~SockAddr();
replaceableSockAddr = new (replaceableSockAddr) SockAddr(iceServerHostname, ICE_SERVER_DEFAULT_PORT);
replaceableSockAddr = new (replaceableSockAddr) SockAddr(SocketType::UDP, iceServerHostname, ICE_SERVER_DEFAULT_PORT);
_iceServerSockAddr.setObjectName("IceServer");
auto nodeList = DependencyManager::get<NodeList>();
@ -367,7 +367,7 @@ void DomainHandler::setIsConnected(bool isConnected) {
emit connectedToDomain(_domainURL);
// FIXME: Reinstate the requestDomainSettings() call here in version 2021.2.0 instead of having it in
// NodeList::processDomainServerList().
// NodeList::processDomainList().
/*
if (_domainURL.scheme() == URL_SCHEME_VIRCADIA && !_domainURL.host().isEmpty()) {
// we've connected to new domain - time to ask it for global settings

View file

@ -41,7 +41,15 @@ const unsigned short DEFAULT_DOMAIN_SERVER_PORT =
? QProcessEnvironment::systemEnvironment()
.value("HIFI_DOMAIN_SERVER_PORT")
.toUShort()
: 40102;
: 40102; // UDP
const unsigned short DEFAULT_DOMAIN_SERVER_WS_PORT =
QProcessEnvironment::systemEnvironment()
.contains("VIRCADIA_DOMAIN_SERVER_WS_PORT")
? QProcessEnvironment::systemEnvironment()
.value("VIRCADIA_DOMAIN_SERVER_WS_PORT")
.toUShort()
: 40102; // TCP
const unsigned short DEFAULT_DOMAIN_SERVER_DTLS_PORT =
QProcessEnvironment::systemEnvironment()
@ -235,7 +243,7 @@ public:
};
public slots:
void setURLAndID(QUrl domainURL, QUuid id);
void setURLAndID(QUrl domainURL, QUuid domainID);
void setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id);
void processSettingsPacketList(QSharedPointer<ReceivedMessage> packetList);
@ -245,7 +253,7 @@ public slots:
void processDomainServerConnectionDeniedPacket(QSharedPointer<ReceivedMessage> message);
// sets domain handler in error state.
void setRedirectErrorState(QUrl errorUrl, QString reasonMessage = "", int reason = -1, const QString& extraInfo = "");
void setRedirectErrorState(QUrl errorUrl, QString reasonMessage = "", int reasonCode = -1, const QString& extraInfo = "");
bool isInErrorState() { return _isInErrorState; }
@ -271,7 +279,7 @@ signals:
void settingsReceived(const QJsonObject& domainSettingsObject);
void settingsReceiveFail();
void domainConnectionRefused(QString reasonMessage, int reason, const QString& extraInfo);
void domainConnectionRefused(QString reasonMessage, int reasonCode, const QString& extraInfo);
void redirectToErrorDomainURL(QUrl errorDomainURL);
void redirectErrorStateChanged(bool isInErrorState);

View file

@ -51,17 +51,17 @@ using namespace std::chrono_literals;
static const std::chrono::milliseconds CONNECTION_RATE_INTERVAL_MS = 1s;
LimitedNodeList::LimitedNodeList(int socketListenPort, int dtlsListenPort) :
_nodeSocket(this),
_nodeSocket(this, true),
_packetReceiver(new PacketReceiver(this))
{
qRegisterMetaType<ConnectionStep>("ConnectionStep");
auto port = (socketListenPort != INVALID_PORT) ? socketListenPort : LIMITED_NODELIST_LOCAL_PORT.get();
_nodeSocket.bind(QHostAddress::AnyIPv4, port);
quint16 assignedPort = _nodeSocket.localPort();
_nodeSocket.bind(SocketType::UDP, QHostAddress::AnyIPv4, port);
quint16 assignedPort = _nodeSocket.localPort(SocketType::UDP);
if (socketListenPort != INVALID_PORT && socketListenPort != 0 && socketListenPort != assignedPort) {
qCCritical(networking) << "PAGE: NodeList is unable to assign requested port of" << socketListenPort;
qCCritical(networking) << "PAGE: NodeList is unable to assign requested UDP port of" << socketListenPort;
}
qCDebug(networking) << "NodeList socket is listening on" << assignedPort;
qCDebug(networking) << "NodeList UDP socket is listening on" << assignedPort;
if (dtlsListenPort != INVALID_PORT) {
// only create the DTLS socket during constructor if a custom port is passed
@ -74,6 +74,8 @@ LimitedNodeList::LimitedNodeList(int socketListenPort, int dtlsListenPort) :
qCDebug(networking) << "NodeList DTLS socket is listening on" << _dtlsSocket->localPort();
}
_nodeSocket.bind(SocketType::WebRTC, QHostAddress::AnyIPv4);
// check for local socket updates every so often
const int LOCAL_SOCKET_UPDATE_INTERVAL_MSECS = 5 * 1000;
QTimer* localSocketUpdate = new QTimer(this);
@ -205,15 +207,20 @@ void LimitedNodeList::setPermissions(const NodePermissions& newPermissions) {
}
}
void LimitedNodeList::setSocketLocalPort(quint16 socketLocalPort) {
void LimitedNodeList::setSocketLocalPort(SocketType socketType, quint16 socketLocalPort) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setSocketLocalPort", Qt::QueuedConnection,
Q_ARG(quint16, socketLocalPort));
return;
}
if (_nodeSocket.localPort() != socketLocalPort) {
_nodeSocket.rebind(socketLocalPort);
LIMITED_NODELIST_LOCAL_PORT.set(socketLocalPort);
if (_nodeSocket.localPort(socketType) != socketLocalPort) {
_nodeSocket.rebind(socketType, socketLocalPort);
if (socketType == SocketType::UDP) {
LIMITED_NODELIST_LOCAL_PORT.set(socketLocalPort);
} else {
// WEBRTC TODO: Add WebRTC equivalent?
qCWarning(networking_webrtc) << "LIMITED_NODELIST_LOCAL_PORT not set for WebRTC socket";
}
}
}
@ -234,6 +241,12 @@ QUdpSocket& LimitedNodeList::getDTLSSocket() {
return *_dtlsSocket;
}
#if defined(WEBRTC_DATA_CHANNELS)
const WebRTCSocket* LimitedNodeList::getWebRTCSocket() {
return _nodeSocket.getWebRTCSocket();
}
#endif
bool LimitedNodeList::isPacketVerifiedWithSource(const udt::Packet& packet, Node* sourceNode) {
// We track bandwidth when doing packet verification to avoid needing to do a node lookup
// later when we already do it in packetSourceAndHashMatchAndTrackBandwidth. A node lookup
@ -492,8 +505,8 @@ qint64 LimitedNodeList::sendUnreliableUnorderedPacketList(NLPacketList& packetLi
}
return bytesSent;
} else {
qCDebug(networking) << "LimitedNodeList::sendPacketList called without active socket for node" << destinationNode
<< " - not sending.";
qCDebug(networking) << "LimitedNodeList::sendUnreliableUnorderedPacketList called without active socket for node"
<< destinationNode << " - not sending.";
return ERROR_SENDING_PACKET_BYTES;
}
}
@ -1106,7 +1119,7 @@ void LimitedNodeList::processSTUNResponse(std::unique_ptr<udt::BasePacket> packe
_publicSockAddr.getAddress().toString().toLocal8Bit().constData(),
_publicSockAddr.getPort());
_publicSockAddr = SockAddr(newPublicAddress, newPublicPort);
_publicSockAddr = SockAddr(SocketType::UDP, newPublicAddress, newPublicPort);
if (!_hasCompletedInitialSTUN) {
// if we're here we have definitely completed our initial STUN sequence
@ -1187,7 +1200,7 @@ void LimitedNodeList::stopInitialSTUNUpdate(bool success) {
qCDebug(networking) << "LimitedNodeList public socket will be set with local port and null QHostAddress.";
// reset the public address and port to a null address
_publicSockAddr = SockAddr(QHostAddress(), _nodeSocket.localPort());
_publicSockAddr = SockAddr(SocketType::UDP, QHostAddress(), _nodeSocket.localPort(SocketType::UDP));
// we have changed the publicSockAddr, so emit our signal
emit publicSockAddrChanged(_publicSockAddr);
@ -1214,7 +1227,7 @@ void LimitedNodeList::stopInitialSTUNUpdate(bool success) {
void LimitedNodeList::updateLocalSocket() {
// when update is called, if the local socket is empty then start with the guessed local socket
if (_localSockAddr.isNull()) {
setLocalSocket(SockAddr { getGuessedLocalAddress(), _nodeSocket.localPort() });
setLocalSocket(SockAddr { SocketType::UDP, getGuessedLocalAddress(), _nodeSocket.localPort(SocketType::UDP) });
}
// attempt to use Google's DNS to confirm that local IP
@ -1237,7 +1250,7 @@ void LimitedNodeList::connectedForLocalSocketTest() {
auto localHostAddress = localIPTestSocket->localAddress();
if (localHostAddress.protocol() == QAbstractSocket::IPv4Protocol) {
setLocalSocket(SockAddr { localHostAddress, _nodeSocket.localPort() });
setLocalSocket(SockAddr { SocketType::UDP, localHostAddress, _nodeSocket.localPort(SocketType::UDP) });
_hasTCPCheckedLocalSocket = true;
}
@ -1253,7 +1266,7 @@ void LimitedNodeList::errorTestingLocalSocket() {
// error connecting to the test socket - if we've never set our local socket using this test socket
// then use our possibly updated guessed local address as fallback
if (!_hasTCPCheckedLocalSocket) {
setLocalSocket(SockAddr { getGuessedLocalAddress(), _nodeSocket.localPort() });
setLocalSocket(SockAddr { SocketType::UDP, getGuessedLocalAddress(), _nodeSocket.localPort(SocketType::UDP) });
qCCritical(networking) << "PAGE: Can't connect to Google DNS service via TCP, falling back to guessed local address"
<< getLocalSockAddr();
}
@ -1273,8 +1286,8 @@ void LimitedNodeList::setLocalSocket(const SockAddr& sockAddr) {
_localSockAddr = sockAddr;
if (_hasTCPCheckedLocalSocket) { // Force a port change for NAT:
reset("local socket change");
_nodeSocket.rebind(0);
_localSockAddr.setPort(_nodeSocket.localPort());
_nodeSocket.rebind(SocketType::UDP, 0);
_localSockAddr.setPort(_nodeSocket.localPort(SocketType::UDP));
qCInfo(networking) << "Local port changed to" << _localSockAddr.getPort();
}
}

View file

@ -38,7 +38,6 @@
#include <DependencyManager.h>
#include <SharedUtil.h>
#include "DomainHandler.h"
#include "NetworkingConstants.h"
#include "Node.h"
#include "NLPacket.h"
@ -122,7 +121,7 @@ public:
QUuid getSessionUUID() const;
void setSessionUUID(const QUuid& sessionUUID);
Node::LocalID getSessionLocalID() const;
void setSessionLocalID(Node::LocalID localID);
void setSessionLocalID(Node::LocalID sessionLocalID);
void setPermissions(const NodePermissions& newPermissions);
bool isAllowedEditor() const { return _permissions.can(NodePermissions::Permission::canAdjustLocks); }
@ -136,10 +135,14 @@ public:
bool getThisNodeCanGetAndSetPrivateUserData() const { return _permissions.can(NodePermissions::Permission::canGetAndSetPrivateUserData); }
bool getThisNodeCanRezAvatarEntities() const { return _permissions.can(NodePermissions::Permission::canRezAvatarEntities); }
quint16 getSocketLocalPort() const { return _nodeSocket.localPort(); }
Q_INVOKABLE void setSocketLocalPort(quint16 socketLocalPort);
quint16 getSocketLocalPort(SocketType socketType) const { return _nodeSocket.localPort(socketType); }
Q_INVOKABLE void setSocketLocalPort(SocketType socketType, quint16 socketLocalPort);
QUdpSocket& getDTLSSocket();
#if defined(WEBRTC_DATA_CHANNELS)
const WebRTCSocket* getWebRTCSocket();
#endif
PacketReceiver& getPacketReceiver() { return *_packetReceiver; }
@ -420,7 +423,6 @@ protected:
qint64 sendPacket(std::unique_ptr<NLPacket> packet, const Node& destinationNode,
const SockAddr& overridenSockAddr);
void fillPacketHeader(const NLPacket& packet, HMACAuth* hmacAuth = nullptr);
void setLocalSocket(const SockAddr& sockAddr);
@ -447,7 +449,7 @@ protected:
QUdpSocket* _dtlsSocket { nullptr };
SockAddr _localSockAddr;
SockAddr _publicSockAddr;
SockAddr _stunSockAddr { STUN_SERVER_HOSTNAME, STUN_SERVER_PORT };
SockAddr _stunSockAddr { SocketType::UDP, STUN_SERVER_HOSTNAME, STUN_SERVER_PORT };
bool _hasTCPCheckedLocalSocket { false };
bool _useAuthentication { true };
@ -487,6 +489,8 @@ private slots:
void addSTUNHandlerToUnfiltered(); // called once STUN socket known
private:
void fillPacketHeader(const NLPacket& packet, HMACAuth* hmacAuth = nullptr);
mutable QReadWriteLock _sessionUUIDLock;
QUuid _sessionUUID;
using LocalIDMapping = tbb::concurrent_unordered_map<Node::LocalID, SharedNodePointer>;

View file

@ -35,7 +35,6 @@ private:
virtual std::unique_ptr<udt::Packet> createPacket() override;
PacketVersion _packetVersion;
NLPacket::LocalID _sourceID;
};

View file

@ -16,3 +16,4 @@ Q_LOGGING_CATEGORY(networking_ice, "hifi.networking.ice")
Q_LOGGING_CATEGORY(resourceLog, "hifi.networking.resource")
Q_LOGGING_CATEGORY(asset_client, "hifi.networking.asset_client")
Q_LOGGING_CATEGORY(messages_client, "hifi.networking.messages_client")
Q_LOGGING_CATEGORY(networking_webrtc, "hifi.networking.webrtc")

View file

@ -19,5 +19,6 @@ Q_DECLARE_LOGGING_CATEGORY(networking)
Q_DECLARE_LOGGING_CATEGORY(networking_ice)
Q_DECLARE_LOGGING_CATEGORY(asset_client)
Q_DECLARE_LOGGING_CATEGORY(messages_client)
Q_DECLARE_LOGGING_CATEGORY(networking_webrtc)
#endif // hifi_NetworkLogging_h

View file

@ -117,7 +117,7 @@ void NetworkPeer::setActiveSocket(SockAddr* discoveredSocket) {
// we have an active socket, stop our ping timer
stopPingTimer();
// we're now considered connected to this peer - reset the number of connection attemps
// we're now considered connected to this peer - reset the number of connection attempts
resetConnectionAttempts();
if (_activeSocket) {

View file

@ -46,6 +46,22 @@ static const QHash<NodeType_t, QString> TYPE_NAME_HASH {
{ NodeType::Unassigned, "Unassigned" }
};
static const QHash<NodeType_t, QString> TYPE_CHAR_HASH {
{ NodeType::DomainServer, "D" },
{ NodeType::EntityServer, "o" },
{ NodeType::Agent, "I" },
{ NodeType::AudioMixer, "M" },
{ NodeType::AvatarMixer, "W" },
{ NodeType::AssetServer, "A" },
{ NodeType::MessagesMixer, "m" },
{ NodeType::EntityScriptServer, "S" },
{ NodeType::UpstreamAudioMixer, "B" },
{ NodeType::UpstreamAvatarMixer, "C" },
{ NodeType::DownstreamAudioMixer, "a" },
{ NodeType::DownstreamAvatarMixer, "w" },
{ NodeType::Unassigned, QChar(1) }
};
const QString& NodeType::getNodeTypeName(NodeType_t nodeType) {
const auto matchedTypeName = TYPE_NAME_HASH.find(nodeType);
return matchedTypeName != TYPE_NAME_HASH.end() ? matchedTypeName.value() : UNKNOWN_NodeType_t_NAME;
@ -85,6 +101,9 @@ NodeType_t NodeType::fromString(QString type) {
return TYPE_NAME_HASH.key(type, NodeType::Unassigned);
}
NodeType_t NodeType::fromChar(QChar type) {
return TYPE_CHAR_HASH.key(type, NodeType::Unassigned);
}
Node::Node(const QUuid& uuid, NodeType_t type, const SockAddr& publicSocket,
const SockAddr& localSocket, QObject* parent) :
@ -177,7 +196,9 @@ bool Node::isIgnoringNodeWithID(const QUuid& nodeID) const {
QDataStream& operator<<(QDataStream& out, const Node& node) {
out << node._type;
out << node._uuid;
out << node._publicSocket.getType();
out << node._publicSocket;
out << node._localSocket.getType();
out << node._localSocket;
out << node._permissions;
out << node._isReplicated;
@ -186,10 +207,15 @@ QDataStream& operator<<(QDataStream& out, const Node& node) {
}
QDataStream& operator>>(QDataStream& in, Node& node) {
SocketType publicSocketType, localSocketType;
in >> node._type;
in >> node._uuid;
in >> publicSocketType;
in >> node._publicSocket;
node._publicSocket.setType(publicSocketType);
in >> localSocketType;
in >> node._localSocket;
node._localSocket.setType(localSocketType);
in >> node._permissions;
in >> node._isReplicated;
in >> node._localID;

View file

@ -147,7 +147,7 @@ NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort)
auto& packetReceiver = getPacketReceiver();
packetReceiver.registerListener(PacketType::DomainList,
PacketReceiver::makeUnsourcedListenerReference<NodeList>(this, &NodeList::processDomainServerList));
PacketReceiver::makeUnsourcedListenerReference<NodeList>(this, &NodeList::processDomainList));
packetReceiver.registerListener(PacketType::Ping,
PacketReceiver::makeSourcedListenerReference<NodeList>(this, &NodeList::processPingPacket));
packetReceiver.registerListener(PacketType::PingReply,
@ -357,7 +357,7 @@ void NodeList::sendDomainServerCheckIn() {
if (publicSockAddr.isNull()) {
// we don't know our public socket and we need to send it to the domain server
qCDebug(networking_ice) << "Waiting for inital public socket from STUN. Will not send domain-server check in.";
qCDebug(networking_ice) << "Waiting for initial public socket from STUN. Will not send domain-server check in.";
} else if (domainHandlerIp.isNull() && _domainHandler.requiresICE()) {
qCDebug(networking_ice) << "Waiting for ICE discovered domain-server socket. Will not send domain-server check in.";
handleICEConnectionToDomainServer();
@ -401,6 +401,8 @@ void NodeList::sendDomainServerCheckIn() {
return;
}
// WEBRTC TODO: Move code into packet library. And update reference in DomainConnectRequest.js.
auto domainPacket = NLPacket::create(domainPacketType);
QDataStream packetStream(domainPacket.get());
@ -409,7 +411,6 @@ void NodeList::sendDomainServerCheckIn() {
if (domainPacketType == PacketType::DomainConnectRequest) {
#if (PR_BUILD || DEV_BUILD)
// #######
if (_shouldSendNewerVersion) {
domainPacket->setVersion(versionForPacketType(domainPacketType) + 1);
}
@ -453,7 +454,6 @@ void NodeList::sendDomainServerCheckIn() {
packetStream << hardwareAddress;
// now add the machine fingerprint
auto accountManager = DependencyManager::get<AccountManager>();
packetStream << FingerprintUtils::getMachineFingerprint();
platform::json all = platform::getAll();
@ -470,10 +470,12 @@ void NodeList::sendDomainServerCheckIn() {
QByteArray compressedSystemInfo = qCompress(systemInfo);
if (compressedSystemInfo.size() > MAX_SYSTEM_INFO_SIZE) {
// FIXME
// Highly unlikely, as not even unreasonable machines will
// overflow the max size, but prevent MTU overflow anyway.
// We could do something sophisticated like clearing specific
// values if they're too big, but we'll save that for later.
// Alternative solution would be to write system info at the end of the packet, only if there is space.
compressedSystemInfo.clear();
}
@ -494,7 +496,8 @@ void NodeList::sendDomainServerCheckIn() {
// pack our data to send to the domain-server including
// the hostname information (so the domain-server can see which place name we came in on)
packetStream << _ownerType.load() << publicSockAddr << localSockAddr << _nodeTypesOfInterest.toList();
packetStream << _ownerType.load() << publicSockAddr.getType() << publicSockAddr << localSockAddr.getType()
<< localSockAddr << _nodeTypesOfInterest.toList();
packetStream << DependencyManager::get<AddressManager>()->getPlaceName();
if (!domainIsConnected) {
@ -711,7 +714,9 @@ void NodeList::processDomainServerConnectionTokenPacket(QSharedPointer<ReceivedM
sendDomainServerCheckIn();
}
void NodeList::processDomainServerList(QSharedPointer<ReceivedMessage> message) {
void NodeList::processDomainList(QSharedPointer<ReceivedMessage> message) {
// WEBRTC TODO: Move code into packet library. And update reference in DomainServerList.js.
// parse header information
QDataStream packetStream(message->getMessage());
@ -877,14 +882,19 @@ void NodeList::processDomainServerRemovedNode(QSharedPointer<ReceivedMessage> me
void NodeList::parseNodeFromPacketStream(QDataStream& packetStream) {
NewNodeInfo info;
SocketType publicSocketType, localSocketType;
packetStream >> info.type
>> info.uuid
>> publicSocketType
>> info.publicSocket
>> localSocketType
>> info.localSocket
>> info.permissions
>> info.isReplicated
>> info.sessionLocalID
>> info.connectionSecretUUID;
info.publicSocket.setType(publicSocketType);
info.localSocket.setType(localSocketType);
// if the public socket address is 0 then it's reachable at the same IP
// as the domain server

View file

@ -112,7 +112,7 @@ public slots:
void sendDomainServerCheckIn();
void handleDSPathQuery(const QString& newPath);
void processDomainServerList(QSharedPointer<ReceivedMessage> message);
void processDomainList(QSharedPointer<ReceivedMessage> message);
void processDomainServerAddedNode(QSharedPointer<ReceivedMessage> message);
void processDomainServerRemovedNode(QSharedPointer<ReceivedMessage> message);
void processDomainServerPathResponse(QSharedPointer<ReceivedMessage> message);
@ -157,7 +157,9 @@ private slots:
private:
Q_DISABLE_COPY(NodeList)
NodeList() : LimitedNodeList(INVALID_PORT, INVALID_PORT) { assert(false); } // Not implemented, needed for DependencyManager templates compile
NodeList() : LimitedNodeList(INVALID_PORT, INVALID_PORT) {
assert(false); // Not implemented, needed for DependencyManager templates compile
}
NodeList(char ownerType, int socketListenPort = INVALID_PORT, int dtlsListenPort = INVALID_PORT);
void processDomainServerAuthRequest(const QByteArray& packet);

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,8 +42,8 @@ namespace NodeType {
NodeType_t upstreamType(NodeType_t primaryType);
NodeType_t downstreamType(NodeType_t primaryType);
NodeType_t fromString(QString type);
NodeType_t fromChar(QChar type);
}
typedef QSet<NodeType_t> NodeSet;

View file

@ -140,9 +140,7 @@ void PacketReceiver::handleVerifiedPacket(std::unique_ptr<udt::Packet> packet) {
if (_shouldDropPackets) {
return;
}
auto nodeList = DependencyManager::get<LimitedNodeList>();
// setup an NLPacket from the packet we were passed
auto nlPacket = NLPacket::fromBase(std::move(packet));
auto receivedMessage = QSharedPointer<ReceivedMessage>::create(*nlPacket);
@ -163,7 +161,7 @@ void PacketReceiver::handleVerifiedMessagePacket(std::unique_ptr<udt::Packet> pa
if (!message->isComplete()) {
_pendingMessages[key] = message;
}
handleVerifiedMessage(message, true);
handleVerifiedMessage(message, true); // Handler may handle first message packet immediately when it arrives.
} else {
message = it->second;
message->appendPacket(*nlPacket);

View file

@ -28,21 +28,22 @@
int sockAddrMetaTypeId = qRegisterMetaType<SockAddr>();
SockAddr::SockAddr() :
_socketType(SocketType::Unknown),
_address(),
_port(0)
{
}
SockAddr::SockAddr(const QHostAddress& address, quint16 port) :
SockAddr::SockAddr(SocketType socketType, const QHostAddress& address, quint16 port) :
_socketType(socketType),
_address(address),
_port(port)
{
}
SockAddr::SockAddr(const SockAddr& otherSockAddr) :
QObject(),
_socketType(otherSockAddr._socketType),
_address(otherSockAddr._address),
_port(otherSockAddr._port)
{
@ -51,12 +52,14 @@ SockAddr::SockAddr(const SockAddr& otherSockAddr) :
SockAddr& SockAddr::operator=(const SockAddr& rhsSockAddr) {
setObjectName(rhsSockAddr.objectName());
_socketType = rhsSockAddr._socketType;
_address = rhsSockAddr._address;
_port = rhsSockAddr._port;
return *this;
}
SockAddr::SockAddr(const QString& hostname, quint16 hostOrderPort, bool shouldBlockForLookup) :
SockAddr::SockAddr(SocketType socketType, const QString& hostname, quint16 hostOrderPort, bool shouldBlockForLookup) :
_socketType(socketType),
_address(hostname),
_port(hostOrderPort)
{
@ -74,19 +77,10 @@ SockAddr::SockAddr(const QString& hostname, quint16 hostOrderPort, bool shouldBl
}
}
SockAddr::SockAddr(const sockaddr* sockaddr) {
_address = QHostAddress(sockaddr);
if (sockaddr->sa_family == AF_INET) {
_port = ntohs(reinterpret_cast<const sockaddr_in*>(sockaddr)->sin_port);
} else {
_port = ntohs(reinterpret_cast<const sockaddr_in6*>(sockaddr)->sin6_port);
}
}
void SockAddr::swap(SockAddr& otherSockAddr) {
using std::swap;
swap(_socketType, otherSockAddr._socketType);
swap(_address, otherSockAddr._address);
swap(_port, otherSockAddr._port);
@ -97,7 +91,7 @@ void SockAddr::swap(SockAddr& otherSockAddr) {
}
bool SockAddr::operator==(const SockAddr& rhsSockAddr) const {
return _address == rhsSockAddr._address && _port == rhsSockAddr._port;
return _socketType == rhsSockAddr._socketType && _address == rhsSockAddr._address && _port == rhsSockAddr._port;
}
void SockAddr::handleLookupResult(const QHostInfo& hostInfo) {
@ -119,6 +113,10 @@ void SockAddr::handleLookupResult(const QHostInfo& hostInfo) {
}
QString SockAddr::toString() const {
return socketTypeToString(_socketType) + " " + _address.toString() + ":" + QString::number(_port);
}
QString SockAddr::toShortString() const {
return _address.toString() + ":" + QString::number(_port);
}
@ -135,16 +133,21 @@ bool SockAddr::hasPrivateAddress() const {
}
QDebug operator<<(QDebug debug, const SockAddr& sockAddr) {
debug.nospace() << sockAddr._address.toString().toLocal8Bit().constData() << ":" << sockAddr._port;
debug.nospace()
<< (sockAddr._socketType != SocketType::Unknown
? (socketTypeToString(sockAddr._socketType) + " ").toLocal8Bit().constData() : "")
<< sockAddr._address.toString().toLocal8Bit().constData() << ":" << sockAddr._port;
return debug.space();
}
QDataStream& operator<<(QDataStream& dataStream, const SockAddr& sockAddr) {
// Don't include socket type because ICE packets must not have it.
dataStream << sockAddr._address << sockAddr._port;
return dataStream;
}
QDataStream& operator>>(QDataStream& dataStream, SockAddr& sockAddr) {
// Don't include socket type because ICE packets must not have it.
dataStream >> sockAddr._address >> sockAddr._port;
return dataStream;
}

View file

@ -20,14 +20,16 @@ struct sockaddr;
#include <QtNetwork/QHostInfo>
#include "SocketType.h"
class SockAddr : public QObject {
Q_OBJECT
public:
SockAddr();
SockAddr(const QHostAddress& address, quint16 port);
SockAddr(SocketType socketType, const QHostAddress& address, quint16 port);
SockAddr(const SockAddr& otherSockAddr);
SockAddr(const QString& hostname, quint16 hostOrderPort, bool shouldBlockForLookup = false);
SockAddr(const sockaddr* sockaddr);
SockAddr(SocketType socketType, const QString& hostname, quint16 hostOrderPort, bool shouldBlockForLookup = false);
bool isNull() const { return _address.isNull() && _port == 0; }
void clear() { _address.clear(); _port = 0;}
@ -38,6 +40,10 @@ public:
bool operator==(const SockAddr& rhsSockAddr) const;
bool operator!=(const SockAddr& rhsSockAddr) const { return !(*this == rhsSockAddr); }
SocketType getType() const { return _socketType; }
SocketType* getSocketTypePointer() { return &_socketType; }
void setType(const SocketType socketType) { _socketType = socketType; }
const QHostAddress& getAddress() const { return _address; }
QHostAddress* getAddressPointer() { return &_address; }
void setAddress(const QHostAddress& address) { _address = address; }
@ -50,19 +56,21 @@ public:
static int unpackSockAddr(const unsigned char* packetData, SockAddr& unpackDestSockAddr);
QString toString() const;
QString toShortString() const;
bool hasPrivateAddress() const; // checks if the address behind this sock addr is private per RFC 1918
friend QDebug operator<<(QDebug debug, const SockAddr& sockAddr);
friend QDataStream& operator<<(QDataStream& dataStream, const SockAddr& sockAddr);
friend QDataStream& operator>>(QDataStream& dataStream, SockAddr& sockAddr);
private slots:
void handleLookupResult(const QHostInfo& hostInfo);
signals:
void lookupCompleted();
void lookupFailed();
private:
SocketType _socketType { SocketType::Unknown };
QHostAddress _address;
quint16 _port;
};

View file

@ -0,0 +1,39 @@
//
// SocketType.h
// libraries/networking/src
//
// Created by David Rowe on 17 May 2021.
// Copyright 2021 Vircadia contributors.
//
// Handles UDP and WebRTC sockets in parallel.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef vircadia_SocketType_h
#define vircadia_SocketType_h
/// @addtogroup Networking
/// @{
/// @brief The types of network socket.
enum class SocketType : uint8_t {
Unknown, ///< Socket type unknown or not set.
UDP, ///< UDP socket.
WebRTC ///< WebRTC socket. A WebRTC data channel presented as a UDP-style socket.
};
/// @brief Returns the name of a SocketType value, e.g., <code>"WebRTC"</code>.
/// @param socketType The SocketType value.
/// @return The name of the SocketType value.
static QString socketTypeToString(SocketType socketType) {
static QStringList SOCKET_TYPE_STRINGS { "Unknown", "UDP", "WebRTC" };
return SOCKET_TYPE_STRINGS[(int)socketType];
}
/// @}
#endif // vircadia_SocketType_h

View file

@ -58,7 +58,7 @@ BasePacket::BasePacket(qint64 size) {
}
// Sanity check
Q_ASSERT(size >= 0 || size < maxPayload);
Q_ASSERT(size >= 0 && size <= maxPayload);
_packetSize = size;
_packet.reset(new char[_packetSize]());

View file

@ -54,7 +54,7 @@ Connection::Connection(Socket* parentSocket, SockAddr destination, std::unique_p
static std::mt19937 generator(rd());
static std::uniform_int_distribution<> distribution(0, SequenceNumber::MAX);
// randomize the intial sequence number
// randomize the initial sequence number
_initialSequenceNumber = SequenceNumber(distribution(generator));
}
@ -255,9 +255,6 @@ bool Connection::processReceivedSequenceNumber(SequenceNumber sequenceNumber, in
return false;
}
// mark our last receive time as now (to push the potential expiry farther)
_lastReceiveTime = p_high_resolution_clock::now();
// If this is not the next sequence number, report loss
if (sequenceNumber > _lastReceivedSequenceNumber + 1) {
if (_lastReceivedSequenceNumber + 1 == sequenceNumber - 1) {
@ -417,9 +414,6 @@ void Connection::resetReceiveState() {
// clear the loss list
_lossList.clear();
// clear sync variables
_connectionStart = p_high_resolution_clock::now();
// clear any pending received messages
for (auto& pendingMessage : _pendingReceivedMessages) {
_parentSocket->messageFailed(this, pendingMessage.first);
@ -451,12 +445,6 @@ void PendingReceivedMessage::enqueuePacket(std::unique_ptr<Packet> packet) {
"PendingReceivedMessage::enqueuePacket",
"called with a packet that is not part of a message");
if (packet->getPacketPosition() == Packet::PacketPosition::LAST ||
packet->getPacketPosition() == Packet::PacketPosition::ONLY) {
_hasLastPacket = true;
_numPackets = packet->getMessagePartNumber() + 1;
}
// Insert into the packets list in sorted order. Because we generally expect to receive packets in order, begin
// searching from the end of the list.
auto messagePartNumber = packet->getMessagePartNumber();

View file

@ -43,9 +43,7 @@ public:
std::list<std::unique_ptr<Packet>> _packets;
private:
bool _hasLastPacket { false };
Packet::MessagePartNumber _nextPartNumber = 0;
unsigned int _numPackets { 0 };
};
class Connection : public QObject {
@ -112,9 +110,6 @@ private:
bool _hasReceivedHandshakeACK { false }; // flag for receipt of handshake ACK from client
bool _didRequestHandshake { false }; // flag for request of handshake from server
p_high_resolution_clock::time_point _connectionStart = p_high_resolution_clock::now(); // holds the time_point for creation of this connection
p_high_resolution_clock::time_point _lastReceiveTime; // holds the last time we received anything from sender
SequenceNumber _initialSequenceNumber; // Randomized on Connection creation, identifies connection during re-connect requests
SequenceNumber _initialReceiveSequenceNumber; // Randomized by peer Connection on creation, identifies connection during re-connect requests

View file

@ -26,6 +26,8 @@ namespace udt {
static const int CONNECTION_SEND_BUFFER_SIZE_PACKETS = 8192;
static const int UDP_SEND_BUFFER_SIZE_BYTES = 1048576;
static const int UDP_RECEIVE_BUFFER_SIZE_BYTES = 1048576;
static const int WEBRTC_SEND_BUFFER_SIZE_BYTES = 1048576;
static const int WEBRTC_RECEIVE_BUFFER_SIZE_BYTES = 1048576;
static const int DEFAULT_SYN_INTERVAL_USECS = 10 * 1000;

View file

@ -98,10 +98,11 @@ void ControlPacket::writeType() {
void ControlPacket::readType() {
ControlBitAndType bitAndType = *reinterpret_cast<ControlBitAndType*>(_packet.get());
Q_ASSERT_X(bitAndType & CONTROL_BIT_MASK, "ControlPacket::readHeader()", "This should be a control packet");
Q_ASSERT_X(bitAndType & CONTROL_BIT_MASK, "ControlPacket::readType()", "This should be a control packet");
uint16_t packetType = (bitAndType & ~CONTROL_BIT_MASK) >> (8 * sizeof(Type));
Q_ASSERT_X(packetType <= ControlPacket::Type::HandshakeRequest, "ControlPacket::readType()", "Received a control packet with wrong type");
Q_ASSERT_X(packetType <= ControlPacket::Type::HandshakeRequest, "ControlPacket::readType()",
"Received a control packet with invalid type");
// read the type
_type = (Type) packetType;

View file

@ -0,0 +1,291 @@
//
// NetworkSocket.cpp
// libraries/networking/src/udt
//
// Created by David Rowe on 21 Jun 2021.
// Copyright 2021 Vircadia contributors.
//
#include "NetworkSocket.h"
#include "../NetworkLogging.h"
NetworkSocket::NetworkSocket(QObject* parent) :
QObject(parent),
_parent(parent),
_udpSocket(this)
#if defined(WEBRTC_DATA_CHANNELS)
,
_webrtcSocket(this)
#endif
{
connect(&_udpSocket, &QUdpSocket::readyRead, this, &NetworkSocket::readyRead);
connect(&_udpSocket, &QAbstractSocket::stateChanged, this, &NetworkSocket::onUDPStateChanged);
// Use old SIGNAL/SLOT mechanism for Android builds.
connect(&_udpSocket, SIGNAL(error(QAbstractSocket::SocketError)),
this, SLOT(onUDPSocketError(QAbstractSocket::SocketError)));
#if defined(WEBRTC_DATA_CHANNELS)
connect(&_webrtcSocket, &WebRTCSocket::readyRead, this, &NetworkSocket::readyRead);
connect(&_webrtcSocket, &WebRTCSocket::stateChanged, this, &NetworkSocket::onWebRTCStateChanged);
// WEBRTC TODO: Add similar for errorOccurred
#endif
}
void NetworkSocket::setSocketOption(SocketType socketType, QAbstractSocket::SocketOption option, const QVariant& value) {
switch (socketType) {
case SocketType::UDP:
_udpSocket.setSocketOption(option, value);
break;
#if defined(WEBRTC_DATA_CHANNELS)
case SocketType::WebRTC:
_webrtcSocket.setSocketOption(option, value);
break;
#endif
default:
qCCritical(networking) << "Socket type not specified in setSocketOption()";
}
}
QVariant NetworkSocket::socketOption(SocketType socketType, QAbstractSocket::SocketOption option) {
switch (socketType) {
case SocketType::UDP:
return _udpSocket.socketOption(option);
#if defined(WEBRTC_DATA_CHANNELS)
case SocketType::WebRTC:
return _webrtcSocket.socketOption(option);
#endif
default:
qCCritical(networking) << "Socket type not specified in socketOption()";
return "";
}
}
void NetworkSocket::bind(SocketType socketType, const QHostAddress& address, quint16 port) {
switch (socketType) {
case SocketType::UDP:
_udpSocket.bind(address, port);
break;
#if defined(WEBRTC_DATA_CHANNELS)
case SocketType::WebRTC:
_webrtcSocket.bind(address, port);
break;
#endif
default:
qCCritical(networking) << "Socket type not specified in bind()";
}
}
void NetworkSocket::abort(SocketType socketType) {
switch (socketType) {
case SocketType::UDP:
_udpSocket.abort();
break;
#if defined(WEBRTC_DATA_CHANNELS)
case SocketType::WebRTC:
_webrtcSocket.abort();
break;
#endif
default:
qCCritical(networking) << "Socket type not specified in abort()";
}
}
quint16 NetworkSocket::localPort(SocketType socketType) const {
switch (socketType) {
case SocketType::UDP:
return _udpSocket.localPort();
#if defined(WEBRTC_DATA_CHANNELS)
case SocketType::WebRTC:
return _webrtcSocket.localPort();
#endif
default:
qCCritical(networking) << "Socket type not specified in localPort()";
return 0;
}
}
qintptr NetworkSocket::socketDescriptor(SocketType socketType) const {
switch (socketType) {
case SocketType::UDP:
return _udpSocket.socketDescriptor();
#if defined(WEBRTC_DATA_CHANNELS)
case SocketType::WebRTC:
return _webrtcSocket.socketDescriptor();
return 0;
#endif
default:
qCCritical(networking) << "Socket type not specified in socketDescriptor()";
return 0;
}
}
qint64 NetworkSocket::writeDatagram(const QByteArray& datagram, const SockAddr& sockAddr) {
switch (sockAddr.getType()) {
case SocketType::UDP:
// WEBRTC TODO: The Qt documentation says that the following call shouldn't be used if the UDP socket is connected!!!
// https://doc.qt.io/qt-5/qudpsocket.html#writeDatagram
return _udpSocket.writeDatagram(datagram, sockAddr.getAddress(), sockAddr.getPort());
#if defined(WEBRTC_DATA_CHANNELS)
case SocketType::WebRTC:
return _webrtcSocket.writeDatagram(datagram, sockAddr);
#endif
default:
qCCritical(networking) << "Socket type not specified in writeDatagram() address";
return 0;
}
}
qint64 NetworkSocket::bytesToWrite(SocketType socketType, const SockAddr& address) const {
switch (socketType) {
case SocketType::UDP:
return _udpSocket.bytesToWrite();
#if defined(WEBRTC_DATA_CHANNELS)
case SocketType::WebRTC:
return _webrtcSocket.bytesToWrite(address);
#endif
default:
qCCritical(networking) << "Socket type not specified in bytesToWrite()";
return 0;
}
}
bool NetworkSocket::hasPendingDatagrams() const {
return
#if defined(WEBRTC_DATA_CHANNELS)
_webrtcSocket.hasPendingDatagrams() ||
#endif
_udpSocket.hasPendingDatagrams();
}
qint64 NetworkSocket::pendingDatagramSize() {
#if defined(WEBRTC_DATA_CHANNELS)
// Alternate socket types, remembering the socket type used so that the same socket type is used next readDatagram().
if (_lastSocketTypeRead == SocketType::UDP) {
if (_webrtcSocket.hasPendingDatagrams()) {
_pendingDatagramSizeSocketType = SocketType::WebRTC;
return _webrtcSocket.pendingDatagramSize();
} else {
_pendingDatagramSizeSocketType = SocketType::UDP;
return _udpSocket.pendingDatagramSize();
}
} else {
if (_udpSocket.hasPendingDatagrams()) {
_pendingDatagramSizeSocketType = SocketType::UDP;
return _udpSocket.pendingDatagramSize();
} else {
_pendingDatagramSizeSocketType = SocketType::WebRTC;
return _webrtcSocket.pendingDatagramSize();
}
}
#else
return _udpSocket.pendingDatagramSize();
#endif
}
qint64 NetworkSocket::readDatagram(char* data, qint64 maxSize, SockAddr* sockAddr) {
#if defined(WEBRTC_DATA_CHANNELS)
// Read per preceding pendingDatagramSize() if any, otherwise alternate socket types.
if (_pendingDatagramSizeSocketType == SocketType::UDP
|| _pendingDatagramSizeSocketType == SocketType::Unknown && _lastSocketTypeRead == SocketType::WebRTC) {
_lastSocketTypeRead = SocketType::UDP;
_pendingDatagramSizeSocketType = SocketType::Unknown;
if (sockAddr) {
sockAddr->setType(SocketType::UDP);
return _udpSocket.readDatagram(data, maxSize, sockAddr->getAddressPointer(), sockAddr->getPortPointer());
} else {
return _udpSocket.readDatagram(data, maxSize);
}
} else {
_lastSocketTypeRead = SocketType::WebRTC;
_pendingDatagramSizeSocketType = SocketType::Unknown;
if (sockAddr) {
sockAddr->setType(SocketType::WebRTC);
return _webrtcSocket.readDatagram(data, maxSize, sockAddr->getAddressPointer(), sockAddr->getPortPointer());
} else {
return _webrtcSocket.readDatagram(data, maxSize);
}
}
#else
if (sockAddr) {
sockAddr->setType(SocketType::UDP);
return _udpSocket.readDatagram(data, maxSize, sockAddr->getAddressPointer(), sockAddr->getPortPointer());
} else {
return _udpSocket.readDatagram(data, maxSize);
}
#endif
}
QAbstractSocket::SocketState NetworkSocket::state(SocketType socketType) const {
switch (socketType) {
case SocketType::UDP:
return _udpSocket.state();
#if defined(WEBRTC_DATA_CHANNELS)
case SocketType::WebRTC:
return _webrtcSocket.state();
#endif
default:
qCCritical(networking) << "Socket type not specified in state()";
return QAbstractSocket::SocketState::UnconnectedState;
}
}
QAbstractSocket::SocketError NetworkSocket::error(SocketType socketType) const {
switch (socketType) {
case SocketType::UDP:
return _udpSocket.error();
#if defined(WEBRTC_DATA_CHANNELS)
case SocketType::WebRTC:
return _webrtcSocket.error();
#endif
default:
qCCritical(networking) << "Socket type not specified in error()";
return QAbstractSocket::SocketError::UnknownSocketError;
}
}
QString NetworkSocket::errorString(SocketType socketType) const {
switch (socketType) {
case SocketType::UDP:
return _udpSocket.errorString();
#if defined(WEBRTC_DATA_CHANNELS)
case SocketType::WebRTC:
return _webrtcSocket.errorString();
#endif
default:
qCCritical(networking) << "Socket type not specified in errorString()";
return "";
}
}
#if defined(WEBRTC_DATA_CHANNELS)
const WebRTCSocket* NetworkSocket::getWebRTCSocket() {
return &_webrtcSocket;
}
#endif
void NetworkSocket::onUDPStateChanged(QAbstractSocket::SocketState socketState) {
emit stateChanged(SocketType::UDP, socketState);
}
void NetworkSocket::onWebRTCStateChanged(QAbstractSocket::SocketState socketState) {
emit stateChanged(SocketType::WebRTC, socketState);
}
void NetworkSocket::onUDPSocketError(QAbstractSocket::SocketError socketError) {
emit NetworkSocket::socketError(SocketType::UDP, socketError);
}
void NetworkSocket::onWebRTCSocketError(QAbstractSocket::SocketError socketError) {
emit NetworkSocket::socketError(SocketType::WebRTC, socketError);
}

View file

@ -0,0 +1,170 @@
//
// NetworkSocket.h
// libraries/networking/src/udt
//
// Created by David Rowe on 21 Jun 2021.
// Copyright 2021 Vircadia contributors.
//
#ifndef vircadia_NetworkSocket_h
#define vircadia_NetworkSocket_h
#include <QObject>
#include <QUdpSocket>
#include <shared/WebRTC.h>
#include "../SockAddr.h"
#include "../NodeType.h"
#include "../SocketType.h"
#if defined(WEBRTC_DATA_CHANNELS)
#include "../webrtc/WebRTCSocket.h"
#endif
/// @addtogroup Networking
/// @{
/// @brief Multiplexes a QUdpSocket and a WebRTCSocket so that they appear as a single QUdpSocket-style socket.
class NetworkSocket : public QObject {
Q_OBJECT
public:
/// @brief Constructs a new NetworkSocket object.
/// @param parent Qt parent object.
NetworkSocket(QObject* parent);
/// @brief Set the value of a UDP or WebRTC socket option.
/// @param socketType The type of socket for which to set the option value.
/// @param option The option to set the value of.
/// @param value The option value.
void setSocketOption(SocketType socketType, QAbstractSocket::SocketOption option, const QVariant& value);
/// @brief Gets the value of a UDP or WebRTC socket option.
/// @param socketType The type of socket for which to get the option value.
/// @param option The option to get the value of.
/// @return The option value.
QVariant socketOption(SocketType socketType, QAbstractSocket::SocketOption option);
/// @brief Binds the UDP or WebRTC socket to an address and port.
/// @param socketType The type of socket to bind.
/// @param address The address to bind to.
/// @param port The port to bind to.
void bind(SocketType socketType, const QHostAddress& address, quint16 port = 0);
/// @brief Immediately closes and resets the socket.
/// @param socketType The type of socket to close and reset.
void abort(SocketType socketType);
/// @brief Gets the UDP or WebRTC local port number.
/// @param socketType The type of socket for which to the get local port number.
/// @return The UDP or WebRTC local port number if available, otherwise <code>0</code>.
quint16 localPort(SocketType socketType) const;
/// @brief Returns the native socket descriptor of the UDP or WebRTC socket.
/// @param socketType The type of socket to get the socket descriptor for.
/// @return The native socket descriptor if available, otherwise <code>-1</code>.
qintptr socketDescriptor(SocketType socketType) const;
/// @brief Sends a datagram to a network address.
/// @param datagram The datagram to send.
/// @param sockAddr The address to send to.
/// @return The number of bytes if successfully sent, otherwise <code>-1</code>.
qint64 writeDatagram(const QByteArray& datagram, const SockAddr& sockAddr);
/// @brief Gets the number of bytes waiting to be written.
/// @details For UDP, there's a single buffer used for all destinations. For WebRTC, each destination has its own buffer.
/// @param socketType The type of socket for which to get the number of bytes waiting to be written.
/// @param address If a WebRTCSocket, the destination address for which to get the number of bytes waiting.
/// @param port If a WebRTC socket, the destination port for which to get the number of bytes waiting.
/// @return The number of bytes waiting to be written.
qint64 bytesToWrite(SocketType socketType, const SockAddr& address = SockAddr()) const;
/// @brief Gets whether there is a pending datagram waiting to be read.
/// @return <code>true</code> if there is a datagram waiting to be read, <code>false</code> if there isn't.
bool hasPendingDatagrams() const;
/// @brief Gets the size of the next pending datagram, alternating between socket types if both have datagrams to read.
/// @return The size of the next pending datagram.
qint64 pendingDatagramSize();
/// @brief Reads the next datagram per the most recent pendingDatagramSize call if made, otherwise alternating between
/// socket types if both have datagrams to read.
/// @param data The destination to write the data into.
/// @param maxSize The maximum number of bytes to read.
/// @param sockAddr The destination to write the source network address into.
/// @return The number of bytes if successfully read, otherwise <code>-1</code>.
qint64 readDatagram(char* data, qint64 maxSize, SockAddr* sockAddr = nullptr);
/// @brief Gets the state of the UDP or WebRTC socket.
/// @param socketType The type of socket for which to get the state.
/// @return The socket state.
QAbstractSocket::SocketState state(SocketType socketType) const;
/// @brief Gets the type of error that last occurred.
/// @param socketType The type of socket for which to get the last error.
/// @return The type of error that last occurred.
QAbstractSocket::SocketError error(SocketType socketType) const;
/// @brief Gets the description of the error that last occurred.
/// @param socketType The type of socket for which to get the last error's description.
/// @return The description of the error that last occurred.
QString errorString(SocketType socketType) const;
#if defined(WEBRTC_DATA_CHANNELS)
/// @brief Gets a pointer to the WebRTC socket object.
/// @return A pointer to the WebRTC socket object.
const WebRTCSocket* getWebRTCSocket();
#endif
signals:
/// @brief Emitted each time new data becomes available for reading.
void readyRead();
/// @brief Emitted when the state of the underlying UDP or WebRTC socket changes.
/// @param socketType The type of socket that changed state.
/// @param socketState The socket's new state.
void stateChanged(SocketType socketType, QAbstractSocket::SocketState socketState);
/// @brief
/// @param socketType
/// @param socketError
void socketError(SocketType socketType, QAbstractSocket::SocketError socketError);
private slots:
void onUDPStateChanged(QAbstractSocket::SocketState socketState);
void onWebRTCStateChanged(QAbstractSocket::SocketState socketState);
void onUDPSocketError(QAbstractSocket::SocketError socketError);
void onWebRTCSocketError(QAbstractSocket::SocketError socketError);
private:
QObject* _parent;
QUdpSocket _udpSocket;
#if defined(WEBRTC_DATA_CHANNELS)
WebRTCSocket _webrtcSocket;
#endif
#if defined(WEBRTC_DATA_CHANNELS)
SocketType _pendingDatagramSizeSocketType { SocketType::Unknown };
SocketType _lastSocketTypeRead { SocketType::Unknown };
#endif
};
/// @}
#endif // vircadia_NetworkSocket_h

View file

@ -27,7 +27,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
case PacketType::DomainConnectRequestPending: // keeping the old version to maintain the protocol hash
return 17;
case PacketType::DomainList:
return static_cast<PacketVersion>(DomainListVersion::HasConnectReason);
return static_cast<PacketVersion>(DomainListVersion::SocketTypes);
case PacketType::EntityAdd:
case PacketType::EntityClone:
case PacketType::EntityEdit:
@ -72,10 +72,12 @@ PacketVersion versionForPacketType(PacketType packetType) {
return static_cast<PacketVersion>(DomainConnectionDeniedVersion::IncludesExtraInfo);
case PacketType::DomainConnectRequest:
return static_cast<PacketVersion>(DomainConnectRequestVersion::HasCompressedSystemInfo);
return static_cast<PacketVersion>(DomainConnectRequestVersion::SocketTypes);
case PacketType::DomainListRequest:
return static_cast<PacketVersion>(DomainListRequestVersion::SocketTypes);
case PacketType::DomainServerAddedNode:
return static_cast<PacketVersion>(DomainServerAddedNodeVersion::PermissionsGrid);
return static_cast<PacketVersion>(DomainServerAddedNodeVersion::SocketTypes);
case PacketType::EntityScriptCallMethod:
return static_cast<PacketVersion>(EntityScriptCallMethodVersion::ClientCallable);

View file

@ -10,6 +10,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// WEBRTC TODO: Rename / split up into files with better names.
#ifndef hifi_PacketHeaders_h
#define hifi_PacketHeaders_h
@ -137,6 +139,7 @@ public:
BulkAvatarTraitsAck,
StopInjector,
AvatarZonePresence,
WebRTCSignaling,
NUM_PACKET_TYPE
};
@ -188,7 +191,7 @@ public:
<< PacketTypeEnum::Value::ReplicatedMicrophoneAudioWithEcho << PacketTypeEnum::Value::ReplicatedInjectAudio
<< PacketTypeEnum::Value::ReplicatedSilentAudioFrame << PacketTypeEnum::Value::ReplicatedAvatarIdentity
<< PacketTypeEnum::Value::ReplicatedKillAvatar << PacketTypeEnum::Value::ReplicatedBulkAvatarData
<< PacketTypeEnum::Value::AvatarZonePresence;
<< PacketTypeEnum::Value::AvatarZonePresence << PacketTypeEnum::Value::WebRTCSignaling;
return NON_SOURCED_PACKETS;
}
@ -365,7 +368,13 @@ enum class DomainConnectRequestVersion : PacketVersion {
HasTimestamp,
HasReason,
HasSystemInfo,
HasCompressedSystemInfo
HasCompressedSystemInfo,
SocketTypes
};
enum class DomainListRequestVersion : PacketVersion {
PreSocketTypes = 22,
SocketTypes
};
enum class DomainConnectionDeniedVersion : PacketVersion {
@ -376,7 +385,8 @@ enum class DomainConnectionDeniedVersion : PacketVersion {
enum class DomainServerAddedNodeVersion : PacketVersion {
PrePermissionsGrid = 17,
PermissionsGrid
PermissionsGrid,
SocketTypes
};
enum class DomainListVersion : PacketVersion {
@ -386,7 +396,8 @@ enum class DomainListVersion : PacketVersion {
GetMachineFingerprintFromUUIDSupport,
AuthenticationOptional,
HasTimestamp,
HasConnectReason
HasConnectReason,
SocketTypes
};
enum class AudioVersion : PacketVersion {

View file

@ -25,8 +25,8 @@ public:
using UType = uint32_t;
// Values are for 27 bit SequenceNumber
static const Type THRESHOLD = 0x03FFFFFF; // threshold for comparing sequence numbers
static const Type MAX = 0x07FFFFFF; // maximum sequence number used in UDT
static const Type THRESHOLD = 0x03FFFFFF; // Threshold for comparing sequence numbers.
static const Type MAX = 0x07FFFFFF; // Maximum sequence number used in UDT.
SequenceNumber() = default;
SequenceNumber(const SequenceNumber& other) : _value(other._value) {}

View file

@ -42,16 +42,15 @@ using namespace udt;
Socket::Socket(QObject* parent, bool shouldChangeSocketOptions) :
QObject(parent),
_udpSocket(parent),
_networkSocket(parent),
_readyReadBackupTimer(new QTimer(this)),
_shouldChangeSocketOptions(shouldChangeSocketOptions)
{
connect(&_udpSocket, &QUdpSocket::readyRead, this, &Socket::readPendingDatagrams);
connect(&_networkSocket, &NetworkSocket::readyRead, this, &Socket::readPendingDatagrams);
// make sure we hear about errors and state changes from the underlying socket
connect(&_udpSocket, SIGNAL(error(QAbstractSocket::SocketError)),
this, SLOT(handleSocketError(QAbstractSocket::SocketError)));
connect(&_udpSocket, &QAbstractSocket::stateChanged, this, &Socket::handleStateChanged);
connect(&_networkSocket, &NetworkSocket::socketError, this, &Socket::handleSocketError);
connect(&_networkSocket, &NetworkSocket::stateChanged, this, &Socket::handleStateChanged);
// in order to help track down the zombie server bug, add a timer to check if we missed a readyRead
const int READY_READ_BACKUP_CHECK_MSECS = 2 * 1000;
@ -59,19 +58,21 @@ Socket::Socket(QObject* parent, bool shouldChangeSocketOptions) :
_readyReadBackupTimer->start(READY_READ_BACKUP_CHECK_MSECS);
}
void Socket::bind(const QHostAddress& address, quint16 port) {
_udpSocket.bind(address, port);
void Socket::bind(SocketType socketType, const QHostAddress& address, quint16 port) {
_networkSocket.bind(socketType, address, port);
if (_shouldChangeSocketOptions) {
setSystemBufferSizes();
setSystemBufferSizes(socketType);
if (socketType == SocketType::WebRTC) {
return;
}
#if defined(Q_OS_LINUX)
auto sd = _udpSocket.socketDescriptor();
auto sd = _networkSocket.socketDescriptor(socketType);
int val = IP_PMTUDISC_DONT;
setsockopt(sd, IPPROTO_IP, IP_MTU_DISCOVER, &val, sizeof(val));
#elif defined(Q_OS_WIN)
auto sd = _udpSocket.socketDescriptor();
auto sd = _networkSocket.socketDescriptor(socketType);
int val = 0; // false
if (setsockopt(sd, IPPROTO_IP, IP_DONTFRAGMENT, (const char *)&val, sizeof(val))) {
auto wsaErr = WSAGetLastError();
@ -81,16 +82,22 @@ void Socket::bind(const QHostAddress& address, quint16 port) {
}
}
void Socket::rebind() {
rebind(_udpSocket.localPort());
void Socket::rebind(SocketType socketType) {
rebind(socketType, _networkSocket.localPort(socketType));
}
void Socket::rebind(quint16 localPort) {
_udpSocket.abort();
bind(QHostAddress::AnyIPv4, localPort);
void Socket::rebind(SocketType socketType, quint16 localPort) {
_networkSocket.abort(socketType);
bind(socketType, QHostAddress::AnyIPv4, localPort);
}
void Socket::setSystemBufferSizes() {
#if defined(WEBRTC_DATA_CHANNELS)
const WebRTCSocket* Socket::getWebRTCSocket() {
return _networkSocket.getWebRTCSocket();
}
#endif
void Socket::setSystemBufferSizes(SocketType socketType) {
for (int i = 0; i < 2; i++) {
QAbstractSocket::SocketOption bufferOpt;
QString bufferTypeString;
@ -99,20 +106,22 @@ void Socket::setSystemBufferSizes() {
if (i == 0) {
bufferOpt = QAbstractSocket::SendBufferSizeSocketOption;
numBytes = udt::UDP_SEND_BUFFER_SIZE_BYTES;
numBytes = socketType == SocketType::UDP
? udt::UDP_SEND_BUFFER_SIZE_BYTES : udt::WEBRTC_SEND_BUFFER_SIZE_BYTES;
bufferTypeString = "send";
} else {
bufferOpt = QAbstractSocket::ReceiveBufferSizeSocketOption;
numBytes = udt::UDP_RECEIVE_BUFFER_SIZE_BYTES;
numBytes = socketType == SocketType::UDP
? udt::UDP_RECEIVE_BUFFER_SIZE_BYTES : udt::WEBRTC_RECEIVE_BUFFER_SIZE_BYTES;
bufferTypeString = "receive";
}
int oldBufferSize = _udpSocket.socketOption(bufferOpt).toInt();
int oldBufferSize = _networkSocket.socketOption(socketType, bufferOpt).toInt();
if (oldBufferSize < numBytes) {
_udpSocket.setSocketOption(bufferOpt, QVariant(numBytes));
int newBufferSize = _udpSocket.socketOption(bufferOpt).toInt();
_networkSocket.setSocketOption(socketType, bufferOpt, QVariant(numBytes));
int newBufferSize = _networkSocket.socketOption(socketType, bufferOpt).toInt();
qCDebug(networking) << "Changed socket" << bufferTypeString << "buffer size from" << oldBufferSize << "to"
<< newBufferSize << "bytes";
@ -198,7 +207,7 @@ qint64 Socket::writePacketList(std::unique_ptr<PacketList> packetList, const Soc
return 0;
}
// Unerliable and Unordered
// Unreliable and Unordered
qint64 totalBytesSent = 0;
while (!packetList->_packets.empty()) {
totalBytesSent += writePacket(packetList->takeFront<Packet>(), sockAddr);
@ -236,16 +245,18 @@ qint64 Socket::writeDatagram(const char* data, qint64 size, const SockAddr& sock
}
qint64 Socket::writeDatagram(const QByteArray& datagram, const SockAddr& sockAddr) {
auto socketType = sockAddr.getType();
// don't attempt to write the datagram if we're unbound. Just drop it.
// _udpSocket.writeDatagram will return an error anyway, but there are
// _networkSocket.writeDatagram will return an error anyway, but there are
// potential crashes in Qt when that happens.
if (_udpSocket.state() != QAbstractSocket::BoundState) {
if (_networkSocket.state(socketType) != QAbstractSocket::BoundState) {
qCDebug(networking) << "Attempt to writeDatagram when in unbound state to" << sockAddr;
return -1;
}
qint64 bytesWritten = _udpSocket.writeDatagram(datagram, sockAddr.getAddress(), sockAddr.getPort());
int pending = _udpSocket.bytesToWrite();
qint64 bytesWritten = _networkSocket.writeDatagram(datagram, sockAddr);
int pending = _networkSocket.bytesToWrite(socketType, sockAddr);
if (bytesWritten < 0 || pending) {
int wsaError = 0;
static std::atomic<int> previousWsaError (0);
@ -253,8 +264,8 @@ qint64 Socket::writeDatagram(const QByteArray& datagram, const SockAddr& sockAdd
wsaError = WSAGetLastError();
#endif
QString errorString;
QDebug(&errorString) << "udt::writeDatagram (" << _udpSocket.state() << sockAddr << ") error - "
<< wsaError << _udpSocket.error() << "(" << _udpSocket.errorString() << ")"
QDebug(&errorString) << "udt::writeDatagram (" << _networkSocket.state(socketType) << sockAddr << ") error - "
<< wsaError << _networkSocket.error(socketType) << "(" << _networkSocket.errorString(socketType) << ")"
<< (pending ? "pending bytes:" : "pending:") << pending;
if (previousWsaError.exchange(wsaError) != wsaError) {
@ -344,7 +355,7 @@ void Socket::messageFailed(Connection* connection, Packet::MessageNumber message
}
void Socket::checkForReadyReadBackup() {
if (_udpSocket.hasPendingDatagrams()) {
if (_networkSocket.hasPendingDatagrams()) {
qCDebug(networking) << "Socket::checkForReadyReadBackup() detected blocked readyRead signal. Flushing pending datagrams.";
// so that birarda can possibly figure out how the heck we get into this state in the first place
@ -358,8 +369,8 @@ void Socket::checkForReadyReadBackup() {
// drop all of the pending datagrams on the floor
int droppedCount = 0;
while (_udpSocket.hasPendingDatagrams()) {
_udpSocket.readDatagram(nullptr, 0);
while (_networkSocket.hasPendingDatagrams()) {
_networkSocket.readDatagram(nullptr, 0);
++droppedCount;
}
qCDebug(networking) << "Flushed" << droppedCount << "Packets";
@ -372,8 +383,8 @@ void Socket::readPendingDatagrams() {
const auto abortTime = system_clock::now() + MAX_PROCESS_TIME;
int packetSizeWithHeader = -1;
while (_udpSocket.hasPendingDatagrams() &&
(packetSizeWithHeader = _udpSocket.pendingDatagramSize()) != -1) {
while (_networkSocket.hasPendingDatagrams() &&
(packetSizeWithHeader = _networkSocket.pendingDatagramSize()) != -1) {
if (system_clock::now() > abortTime) {
// We've been running for too long, stop processing packets for now
// Once we've processed the event queue, we'll come back to packet processing
@ -398,8 +409,7 @@ void Socket::readPendingDatagrams() {
auto buffer = std::unique_ptr<char[]>(new char[packetSizeWithHeader]);
// pull the datagram
auto sizeRead = _udpSocket.readDatagram(buffer.get(), packetSizeWithHeader,
senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer());
auto sizeRead = _networkSocket.readDatagram(buffer.get(), packetSizeWithHeader, &senderSockAddr);
// save information for this packet, in case it is the one that sticks readyRead
_lastPacketSizeRead = sizeRead;
@ -541,17 +551,17 @@ std::vector<SockAddr> Socket::getConnectionSockAddrs() {
return addr;
}
void Socket::handleSocketError(QAbstractSocket::SocketError socketError) {
void Socket::handleSocketError(SocketType socketType, QAbstractSocket::SocketError socketError) {
int wsaError = 0;
static std::atomic<int> previousWsaError(0);
#ifdef WIN32
wsaError = WSAGetLastError();
#endif
int pending = _udpSocket.bytesToWrite();
int pending = _networkSocket.bytesToWrite(socketType);
QString errorString;
QDebug(&errorString) << "udt::Socket (" << _udpSocket.state() << ") error - " << wsaError << socketError <<
"(" << _udpSocket.errorString() << ")" << (pending ? "pending bytes:" : "pending:")
<< pending;
QDebug(&errorString) << "udt::Socket (" << socketTypeToString(socketType) << _networkSocket.state(socketType)
<< ") error - " << wsaError << socketError << "(" << _networkSocket.errorString(socketType) << ")"
<< (pending ? "pending bytes:" : "pending:") << pending;
if (previousWsaError.exchange(wsaError) != wsaError) {
qCDebug(networking).noquote() << errorString;
@ -564,9 +574,9 @@ void Socket::handleSocketError(QAbstractSocket::SocketError socketError) {
}
}
void Socket::handleStateChanged(QAbstractSocket::SocketState socketState) {
void Socket::handleStateChanged(SocketType socketType, QAbstractSocket::SocketState socketState) {
if (socketState != QAbstractSocket::BoundState) {
qCDebug(networking) << "udt::Socket state changed - state is now" << socketState;
qCDebug(networking) << socketTypeToString(socketType) << "socket state changed - state is now" << socketState;
}
}

View file

@ -22,11 +22,11 @@
#include <QtCore/QObject>
#include <QtCore/QTimer>
#include <QtNetwork/QUdpSocket>
#include "../SockAddr.h"
#include "TCPVegasCC.h"
#include "Connection.h"
#include "NetworkSocket.h"
//#define UDT_CONNECTION_DEBUG
@ -55,10 +55,10 @@ class Socket : public QObject {
public:
using StatsVector = std::vector<std::pair<SockAddr, ConnectionStats::Stats>>;
Socket(QObject* object = 0, bool shouldChangeSocketOptions = true);
quint16 localPort() const { return _udpSocket.localPort(); }
quint16 localPort(SocketType socketType) const { return _networkSocket.localPort(socketType); }
// Simple functions writing to the socket with no processing
qint64 writeBasePacket(const BasePacket& packet, const SockAddr& sockAddr);
@ -68,9 +68,9 @@ public:
qint64 writeDatagram(const char* data, qint64 size, const SockAddr& sockAddr);
qint64 writeDatagram(const QByteArray& datagram, const SockAddr& sockAddr);
void bind(const QHostAddress& address, quint16 port = 0);
void rebind(quint16 port);
void rebind();
void bind(SocketType socketType, const QHostAddress& address, quint16 port = 0);
void rebind(SocketType socketType, quint16 port);
void rebind(SocketType socketType);
void setPacketFilterOperator(PacketFilterOperator filterOperator) { _packetFilterOperator = filterOperator; }
void setPacketHandler(PacketHandler handler) { _packetHandler = handler; }
@ -90,6 +90,10 @@ public:
StatsVector sampleStatsForAllConnections();
#if defined(WEBRTC_DATA_CHANNELS)
const WebRTCSocket* getWebRTCSocket();
#endif
#if (PR_BUILD || DEV_BUILD)
void sendFakedHandshakeRequest(const SockAddr& sockAddr);
#endif
@ -106,11 +110,11 @@ private slots:
void readPendingDatagrams();
void checkForReadyReadBackup();
void handleSocketError(QAbstractSocket::SocketError socketError);
void handleStateChanged(QAbstractSocket::SocketState socketState);
void handleSocketError(SocketType socketType, QAbstractSocket::SocketError socketError);
void handleStateChanged(SocketType socketType, QAbstractSocket::SocketState socketState);
private:
void setSystemBufferSizes();
void setSystemBufferSizes(SocketType socketType);
Connection* findOrCreateConnection(const SockAddr& sockAddr, bool filterCreation = false);
// privatized methods used by UDTTest - they are private since they must be called on the Socket thread
@ -122,7 +126,7 @@ private:
Q_INVOKABLE void writeReliablePacket(Packet* packet, const SockAddr& sockAddr);
Q_INVOKABLE void writeReliablePacketList(PacketList* packetList, const SockAddr& sockAddr);
QUdpSocket _udpSocket { this };
NetworkSocket _networkSocket;
PacketFilterOperator _packetFilterOperator;
PacketHandler _packetHandler;
MessageHandler _messageHandler;

View file

@ -0,0 +1,653 @@
//
// 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
// FIXME: stun:ice.vircadia.com:7337 doesn't work for WebRTC.
// Firefox warns: "WebRTC: Using more than two STUN/TURN servers slows down discovery"
const std::list<std::string> ICE_SERVER_URIS = {
"stun:stun1.l.google.com:19302",
"stun:stun.schlund.de"
};
const int MAX_WEBRTC_BUFFER_SIZE = 16777216; // 16MB
// #define WEBRTC_DEBUG
using namespace webrtc;
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
QStringList states {
"New",
"Gathering",
"Complete"
};
qCDebug(networking_webrtc) << "WDCPeerConnectionObserver::OnIceGatheringChange() :" << newState << states[newState];
#endif
}
void WDCPeerConnectionObserver::OnIceCandidate(const IceCandidateInterface* candidate) {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WDCPeerConnectionObserver::OnIceCandidate()";
#endif
_parent->sendIceCandidate(candidate);
}
void WDCPeerConnectionObserver::OnIceConnectionChange(PeerConnectionInterface::IceConnectionState newState) {
#ifdef WEBRTC_DEBUG
QStringList states {
"New",
"Checking",
"Connected",
"Completed",
"Failed",
"Disconnected",
"Closed",
"Max"
};
qCDebug(networking_webrtc) << "WDCPeerConnectionObserver::OnIceConnectionChange() :" << newState << states[newState];
#endif
}
void WDCPeerConnectionObserver::OnStandardizedIceConnectionChange(PeerConnectionInterface::IceConnectionState newState) {
#ifdef WEBRTC_DEBUG
QStringList states {
"New",
"Checking",
"Connected",
"Completed",
"Failed",
"Disconnected",
"Closed",
"Max"
};
qCDebug(networking_webrtc) << "WDCPeerConnectionObserver::OnStandardizedIceConnectionChange() :" << newState
<< states[newState];
#endif
}
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
QStringList states {
"New",
"Connecting",
"Connected",
"Disconnected",
"Failed",
"Closed"
};
qCDebug(networking_webrtc) << "WDCPeerConnectionObserver::OnConnectionChange() :" << (uint)newState
<< states[(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, const QString& dataChannelID) :
_parent(parent),
_dataChannelID(dataChannelID)
{
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WDCConnection::WDCConnection() :" << dataChannelID;
#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", _dataChannelID);
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", _dataChannelID);
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
QStringList 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;
_dataChannel->RegisterObserver(_dataChannelObserver.get());
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WDCConnection::onDataChannelOpened() : channel ID:" << _dataChannelID;
#endif
_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) {
// Finish with the data channel.
_dataChannel->UnregisterObserver();
// Don't set _dataChannel = nullptr because it is a scoped_refptr.
_dataChannelObserver = nullptr;
// 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
auto addressParts = _dataChannelID.split(":");
if (addressParts.length() != 2) {
qCWarning(networking_webrtc) << "Invalid dataChannelID:" << _dataChannelID;
return;
}
auto address = SockAddr(SocketType::WebRTC, QHostAddress(addressParts[0]), addressParts[1].toInt());
_parent->sendDataMessage(address, byteArray); // Use parent method to exercise the code stack.
return;
}
_parent->emitDataMessage(_dataChannelID, byteArray);
}
qint64 WDCConnection::getBufferedAmount() const {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WDCConnection::getBufferedAmount()";
#endif
return _dataChannel && _dataChannel->state() != DataChannelInterface::kClosing
&& _dataChannel->state() != DataChannelInterface::kClosed
? _dataChannel->buffered_amount() : 0;
}
bool WDCConnection::sendDataMessage(const DataBuffer& buffer) {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WDCConnection::sendDataMessage()";
if (!_dataChannel || _dataChannel->state() == DataChannelInterface::kClosing
|| _dataChannel->state() == DataChannelInterface::kClosed) {
qCDebug(networking_webrtc) << "No data channel to send on";
}
#endif
if (!_dataChannel || _dataChannel->state() == DataChannelInterface::kClosing
|| _dataChannel->state() == DataChannelInterface::kClosed) {
// Data channel may have been closed while message to send was being prepared.
return false;
} else if (_dataChannel->buffered_amount() + buffer.size() > MAX_WEBRTC_BUFFER_SIZE) {
// Don't send, otherwise the data channel will be closed.
qCDebug(networking_webrtc) << "WebRTC send buffer overflow";
return false;
}
return _dataChannel->Send(buffer);
}
void WDCConnection::closePeerConnection() {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WDCConnection::closePeerConnection() :" << (int)_peerConnection->peer_connection_state();
#endif
_peerConnection->Close();
// Don't set _peerConnection = nullptr because it is a scoped_refptr.
_peerConnectionObserver = nullptr;
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "Disposed of peer connection";
#endif
}
WebRTCDataChannels::WebRTCDataChannels(QObject* parent) :
QObject(parent),
_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
reset();
_peerConnectionFactory = nullptr;
_rtcSignalingThread->Stop();
_rtcSignalingThread = nullptr;
_rtcWorkerThread->Stop();
_rtcWorkerThread = nullptr;
_rtcNetworkThread->Stop();
_rtcNetworkThread = nullptr;
}
void WebRTCDataChannels::reset() {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WebRTCDataChannels::reset() :" << _connectionsByID.count();
#endif
QHashIterator<QString, WDCConnection*> i(_connectionsByID);
while (i.hasNext()) {
i.next();
delete i.value();
}
_connectionsByID.clear();
}
void WebRTCDataChannels::onDataChannelOpened(WDCConnection* connection, const QString& dataChannelID) {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WebRTCDataChannels::onDataChannelOpened() :" << dataChannelID;
#endif
_connectionsByID.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;
const QRegularExpression DATA_CHANNEL_ID_REGEX{ "^[1-9]\\d*\\.\\d+\\.\\d+\\.\\d+:\\d+$" };
auto data = message.value("data").isObject() ? message.value("data").toObject() : QJsonObject();
auto from = message.value("from").toString();
auto to = NodeType::fromChar(message.value("to").toString().at(0));
if (!DATA_CHANNEL_ID_REGEX.match(from).hasMatch() || to == NodeType::Unassigned
|| !data.contains("description") && !data.contains("candidate")) {
qCWarning(networking_webrtc) << "Invalid or unexpected signaling message:"
<< QJsonDocument(message).toJson(QJsonDocument::Compact).left(MAX_DEBUG_DETAIL_LENGTH);
return;
}
// Remember this node's type for the reply.
_nodeType = to;
// Find or create a connection.
WDCConnection* connection;
if (_connectionsByID.contains(from)) {
connection = _connectionsByID.value(from);
} else {
connection = new WDCConnection(this, from);
_connectionsByID.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")) {
auto candidate = data.value("candidate").toObject();
connection->addIceCandidate(candidate);
}
}
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(const QString& dataChannelID, const QByteArray& byteArray) {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WebRTCDataChannels::emitDataMessage() :" << dataChannelID << byteArray.toHex()
<< byteArray.length();
#endif
auto addressParts = dataChannelID.split(":");
if (addressParts.length() != 2) {
qCWarning(networking_webrtc) << "Invalid dataChannelID:" << dataChannelID;
return;
}
auto address = SockAddr(SocketType::WebRTC, QHostAddress(addressParts[0]), addressParts[1].toInt());
emit dataMessage(address, byteArray);
}
bool WebRTCDataChannels::sendDataMessage(const SockAddr& destination, const QByteArray& byteArray) {
auto dataChannelID = destination.toShortString();
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WebRTCDataChannels::sendDataMessage() :" << dataChannelID;
#endif
if (!_connectionsByID.contains(dataChannelID)) {
qCWarning(networking_webrtc) << "Could not find WebRTC data channel to send message on!";
return false;
}
auto connection = _connectionsByID.value(dataChannelID);
DataBuffer buffer(byteArray.toStdString(), true);
return connection->sendDataMessage(buffer);
}
qint64 WebRTCDataChannels::getBufferedAmount(const SockAddr& address) const {
auto dataChannelID = address.toShortString();
if (!_connectionsByID.contains(dataChannelID)) {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WebRTCDataChannels::getBufferedAmount() : Channel doesn't exist:" << dataChannelID;
#endif
return 0;
}
auto connection = _connectionsByID.value(dataChannelID);
return connection->getBufferedAmount();
}
rtc::scoped_refptr<PeerConnectionInterface> WebRTCDataChannels::createPeerConnection(
const std::shared_ptr<WDCPeerConnectionObserver> peerConnectionObserver) {
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "WebRTCDataChannels::createPeerConnection()";
#endif
PeerConnectionInterface::RTCConfiguration configuration;
for (const auto& uri : ICE_SERVER_URIS) {
PeerConnectionInterface::IceServer iceServer;
iceServer.uri = 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.
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "Dispose of connection for channel:" << connection->getDataChannelID();
#endif
_connectionsByID.remove(connection->getDataChannelID());
delete connection;
#ifdef WEBRTC_DEBUG
qCDebug(networking_webrtc) << "Disposed of connection";
#endif
}
#endif // WEBRTC_DATA_CHANNELS

View file

@ -0,0 +1,340 @@
//
// 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"
#include "../SockAddr.h"
class WebRTCDataChannels;
class WDCConnection;
/// @addtogroup Networking
/// @{
/// @brief A WebRTC session description observer.
class WDCSetSessionDescriptionObserver : public webrtc::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(webrtc::RTCError error) override;
};
/// @brief A WebRTC create session description observer.
class WDCCreateSessionDescriptionObserver : public webrtc::CreateSessionDescriptionObserver {
public:
/// @brief Constructs a session description observer.
/// @param parent The parent connection object.
WDCCreateSessionDescriptionObserver(WDCConnection* parent);
/// @brief The call to CreateAnswer succeeded.
/// @param desc The session description.
void OnSuccess(webrtc::SessionDescriptionInterface* desc) override;
/// @brief The call to CreateAnswer failed.
/// @param error Error information.
void OnFailure(webrtc::RTCError error) override;
private:
WDCConnection* _parent;
};
/// @brief A WebRTC peer connection observer.
class WDCPeerConnectionObserver : public webrtc::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(webrtc::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(webrtc::PeerConnectionInterface::IceGatheringState newState) override;
/// @brief Called when a new ICE candidate has been gathered.
/// @param candidate The new ICE candidate.
void OnIceCandidate(const webrtc::IceCandidateInterface* candidate) override;
/// @brief Called when the legacy ICE connection state changes.
/// @param new_state The new ICE connection state.
virtual void OnIceConnectionChange(webrtc::PeerConnectionInterface::IceConnectionState newState) override;
/// @brief Called when the standards-compliant ICE connection state changes.
/// @param new_state The new ICE connection state.
virtual void OnStandardizedIceConnectionChange(webrtc::PeerConnectionInterface::IceConnectionState newState) override;
/// @brief Called when a remote peer opens a data channel.
/// @param dataChannel The data channel.
void OnDataChannel(rtc::scoped_refptr<webrtc::DataChannelInterface> dataChannel) override;
/// @brief Called when the peer connection state changes.
/// @param newState The new peer connection state.
void OnConnectionChange(webrtc::PeerConnectionInterface::PeerConnectionState newState) override;
private:
WDCConnection* _parent;
};
/// @brief A WebRTC data channel observer.
class WDCDataChannelObserver : public webrtc::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 webrtc::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 dataChannelID The data channel ID.
WDCConnection(WebRTCDataChannels* parent, const QString& dataChannelID);
/// @brief Gets the data channel ID.
/// @return The data channel ID.
QString getDataChannelID() const { 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(webrtc::SessionDescriptionInterface* description);
/// @brief Sets the local session description on the WebRTC data channel being connected.
/// @param description The local session description.
void setLocalDescription(webrtc::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 client via the signaling channel.
/// @param candidate The ICE candidate.
void sendIceCandidate(const webrtc::IceCandidateInterface* candidate);
/// @brief Monitors the peer connection state.
/// @param state The new peer connection state.
void onPeerConnectionStateChanged(webrtc::PeerConnectionInterface::PeerConnectionState state);
/// @brief Handles the WebRTC data channel being opened.
/// @param dataChannel The WebRTC data channel.
void onDataChannelOpened(rtc::scoped_refptr<webrtc::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 webrtc::DataBuffer& buffer);
/// @brief Gets the number of bytes waiting to be sent on the WebRTC data channel.
/// @return The number of bytes waiting to be sent on the WebRTC data channel.
qint64 getBufferedAmount() const;
/// @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 webrtc::DataBuffer& buffer);
/// @brief Closes the WebRTC peer connection.
void closePeerConnection();
private:
WebRTCDataChannels* _parent;
QString _dataChannelID;
rtc::scoped_refptr<WDCSetSessionDescriptionObserver> _setSessionDescriptionObserver { nullptr };
rtc::scoped_refptr<WDCCreateSessionDescriptionObserver> _createSessionDescriptionObserver { nullptr };
std::shared_ptr<WDCDataChannelObserver> _dataChannelObserver { nullptr };
rtc::scoped_refptr<webrtc::DataChannelInterface> _dataChannel { nullptr };
std::shared_ptr<WDCPeerConnectionObserver> _peerConnectionObserver { nullptr };
rtc::scoped_refptr<webrtc::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.
///
/// A WebRTC data channel is identified by the IP address and port of the client WebSocket that was used when opening the data
/// channel - this is considered to be the WebRTC data channel's address. The IP address and port of the actual WebRTC
/// connection is not used.
class WebRTCDataChannels : public QObject {
Q_OBJECT
public:
/// @brief Constructs a new WebRTCDataChannels object.
/// @param parent The parent Qt object.
WebRTCDataChannels(QObject* parent);
/// @brief Destroys a WebRTCDataChannels object.
~WebRTCDataChannels();
/// @brief Gets the type of node that the WebRTCDataChannels object is being used in.
/// @return The type of node.
NodeType_t getNodeType() {
return _nodeType;
}
/// @brief Immediately closes all connections and resets the socket.
void reset();
/// @brief Handles a WebRTC data channel opening.
/// @param connection The WebRTC data channel connection.
/// @param dataChannelID The IP address and port of the signaling WebSocket that the client used to connect, `"n.n.n.n:n"`.
void onDataChannelOpened(WDCConnection* connection, const QString& 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 IP address and port of the signaling WebSocket that the client used to connect, `"n.n.n.n:n"`.
/// @param byteArray The data message received.
void emitDataMessage(const QString& dataChannelID, const QByteArray& byteArray);
/// @brief Sends a data message to an Interface client.
/// @param dataChannelID The IP address and port of the signaling WebSocket that the client used to connect, `"n.n.n.n:n"`.
/// @param message The data message to send.
/// @return `true` if the data message was sent, otherwise `false`.
bool sendDataMessage(const SockAddr& destination, const QByteArray& message);
/// @brief Gets the number of bytes waiting to be sent on a data channel.
/// @param address The address of the signaling WebSocket that the client used to connect.
/// @return The number of bytes waiting to be sent on the data channel.
qint64 getBufferedAmount(const SockAddr& address) const;
/// @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<webrtc::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 address The address of the signaling WebSocket that the client used to connect.
/// @param byteArray The Vircadia protocol message.
void dataMessage(const SockAddr& address, 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 { NodeType::Unassigned };
std::unique_ptr<rtc::Thread> _rtcNetworkThread { nullptr };
std::unique_ptr<rtc::Thread> _rtcWorkerThread { nullptr };
std::unique_ptr<rtc::Thread> _rtcSignalingThread { nullptr };
rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> _peerConnectionFactory { nullptr };
QHash<QString, WDCConnection*> _connectionsByID; // <client data channel ID, WDCConnection>
// The client's WebSocket IP and port is used as the data channel ID to uniquely identify each.
// The WebSocket IP address and port is formatted as "n.n.n.n:n", the same as used in WebRTCSignalingServer.
};
/// @}
#endif // WEBRTC_DATA_CHANNELS
#endif // vircadia_WebRTCDataChannels_h

View file

@ -0,0 +1,147 @@
//
// WebRTCSignalingServer.cpp
// libraries/networking/src/webrtc
//
// Created by David Rowe on 16 May 2021.
// Copyright 2021 Vircadia contributors.
//
#include "WebRTCSignalingServer.h"
#if defined(WEBRTC_DATA_CHANNELS)
#include <QSslKey>
#include <QtCore>
#include <QWebSocket>
#include <BuildInfo.h>
#include <PathUtils.h>
#include "../NetworkLogging.h"
#include "../NodeType.h"
const int WEBRTC_SOCKET_CHECK_INTERVAL_IN_MS = 30000;
WebRTCSignalingServer::WebRTCSignalingServer(QObject* parent, bool isWSSEnabled) :
QObject(parent)
{
if (isWSSEnabled) {
_webSocketServer = (new QWebSocketServer(QStringLiteral("WebRTC Signaling Server"), QWebSocketServer::SecureMode,
this));
auto dsDirPath = PathUtils::getAppLocalDataPath();
const QString KEY_FILENAME = "vircadia-cert.key";
const QString CRT_FILENAME = "vircadia-cert.crt";
const QString CA_CRT_FILENAME = "vircadia-cert-ca.crt";
qCDebug(networking_webrtc) << "WebSocket WSS key file:" << dsDirPath + KEY_FILENAME;
qCDebug(networking_webrtc) << "WebSocket WSS cert file:" << dsDirPath + CRT_FILENAME;
qCDebug(networking_webrtc) << "WebSocket WSS CA cert file:" << dsDirPath + CA_CRT_FILENAME;
QFile sslCaFile(dsDirPath + CA_CRT_FILENAME);
sslCaFile.open(QIODevice::ReadOnly);
QSslCertificate sslCaCertificate(&sslCaFile, QSsl::Pem);
sslCaFile.close();
QSslConfiguration sslConfiguration;
QFile sslCrtFile(dsDirPath + CRT_FILENAME);
sslCrtFile.open(QIODevice::ReadOnly);
QSslCertificate sslCertificate(&sslCrtFile, QSsl::Pem);
sslCrtFile.close();
QFile sslKeyFile(dsDirPath + KEY_FILENAME);
sslKeyFile.open(QIODevice::ReadOnly);
QSslKey sslKey(&sslKeyFile, QSsl::Rsa, QSsl::Pem);
sslKeyFile.close();
if (!sslCaCertificate.isNull() && !sslKey.isNull() && !sslCertificate.isNull()) {
sslConfiguration.setPeerVerifyMode(QSslSocket::VerifyNone);
sslConfiguration.addCaCertificate(sslCaCertificate);
sslConfiguration.setLocalCertificate(sslCertificate);
sslConfiguration.setPrivateKey(sslKey);
_webSocketServer->setSslConfiguration(sslConfiguration);
qCDebug(networking_webrtc) << "WebSocket SSL mode enabled:"
<< (_webSocketServer->secureMode() == QWebSocketServer::SecureMode);
} else {
qCWarning(networking_webrtc) << "Error creating WebSocket SSL key.";
}
} else {
_webSocketServer = (new QWebSocketServer(QStringLiteral("WebRTC Signaling Server"), QWebSocketServer::NonSecureMode,
this));
}
connect(_webSocketServer, &QWebSocketServer::newConnection, this, &WebRTCSignalingServer::newWebSocketConnection);
// Automatically recover from network interruptions.
_isWebSocketServerListeningTimer = new QTimer(this);
connect(_isWebSocketServerListeningTimer, &QTimer::timeout, this, &WebRTCSignalingServer::checkWebSocketServerIsListening);
_isWebSocketServerListeningTimer->start(WEBRTC_SOCKET_CHECK_INTERVAL_IN_MS);
}
bool WebRTCSignalingServer::bind(const QHostAddress& address, quint16 port) {
_address = address;
_port = port;
auto success = _webSocketServer->listen(_address, _port);
if (!success) {
qCWarning(networking_webrtc) << "Failed to open WebSocket for WebRTC signaling.";
}
return success;
}
void WebRTCSignalingServer::checkWebSocketServerIsListening() {
if (!_webSocketServer->isListening()) {
qCWarning(networking_webrtc) << "WebSocket on port " << QString::number(_port) << " is no longer listening";
_webSockets.clear();
_webSocketServer->listen(_address, _port);
}
}
void WebRTCSignalingServer::webSocketTextMessageReceived(const QString& message) {
auto source = qobject_cast<QWebSocket*>(sender());
if (source) {
QJsonObject json = QJsonDocument::fromJson(message.toUtf8()).object();
// WEBRTC TODO: Move domain server echoing into domain server.
if (json.keys().contains("echo") && json.value("to").toString() == QString(QChar(NodeType::DomainServer))) {
// Domain server echo request - echo message back to sender.
json.remove("to");
json.insert("from", QString(QChar(NodeType::DomainServer)));
QString echo = QJsonDocument(json).toJson();
source->sendTextMessage(echo);
} else {
// WebRTC message or assignment client echo request. (Send both to target.)
auto from = source->peerAddress().toString() + ":" + QString::number(source->peerPort());
json.insert("from", from);
emit messageReceived(json);
}
} else {
qCWarning(networking_webrtc) << "Failed to find WebSocket for incoming WebRTC signaling message.";
}
}
void WebRTCSignalingServer::sendMessage(const QJsonObject& message) {
auto destinationAddress = message.value("to").toString();
if (_webSockets.contains(destinationAddress)) {
_webSockets.value(destinationAddress)->sendTextMessage(QString(QJsonDocument(message).toJson()));
} else {
qCWarning(networking_webrtc) << "Failed to find WebSocket for outgoing WebRTC signaling message.";
}
}
void WebRTCSignalingServer::webSocketDisconnected() {
auto source = qobject_cast<QWebSocket*>(sender());
if (source) {
auto address = source->peerAddress().toString() + ":" + QString::number(source->peerPort());
_webSockets.remove(address);
source->deleteLater();
}
}
void WebRTCSignalingServer::newWebSocketConnection() {
auto webSocket = _webSocketServer->nextPendingConnection();
connect(webSocket, &QWebSocket::textMessageReceived, this, &WebRTCSignalingServer::webSocketTextMessageReceived);
connect(webSocket, &QWebSocket::disconnected, this, &WebRTCSignalingServer::webSocketDisconnected);
auto webSocketAddress = webSocket->peerAddress().toString() + ":" + QString::number(webSocket->peerPort());
_webSockets.insert(webSocketAddress, webSocket);
}
#endif // WEBRTC_DATA_CHANNELS

View file

@ -0,0 +1,113 @@
//
// WebRTCSignalingServer.h
// libraries/networking/src/webrtc
//
// Created by David Rowe on 16 May 2021.
// Copyright 2021 Vircadia contributors.
//
#ifndef vircadia_WebRTCSignalingServer_h
#define vircadia_WebRTCSignalingServer_h
#include <shared/WebRTC.h>
#if defined(WEBRTC_DATA_CHANNELS)
#include <QObject>
#include <QtCore/QTimer>
#include <QWebSocketServer>
#include "../SockAddr.h"
/// @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
/// and the assignment clients (i.e., separate WebRTC data channels for each but only a single signaling WebSocket). The
/// assignment client signaling messages are expected to be relayed - by the domain server - via Vircadia protocol messages on
/// the UDP connections between the domain server and assignment clients.
///
/// Additionally, for debugging purposes, instead of containing a WebRTC payload a signaling message may be an echo request.
/// This is bounced back to the client from the WebRTCSignalingServer if the domain server was the target, otherwise it is
/// expected to be bounced back upon receipt by the relevant assignment client.
///
/// The signaling messages are sent and received as JSON objects, with `to` and `from` fields in addition to either the WebRTC
/// signaling `data` payload or an `echo` request:
///
/// | Interface -> Server ||
/// | -------- | ---------------------------------------- |
/// | `to` | NodeType |
/// | `from` | WebSocket IP address & port, "n.n.n.n:n" |
/// | [`data`] | WebRTC signaling payload |
/// | [`echo`] | Echo request |
///
/// `*` The `from` field is filled in upon receipt by the WebRTCSignalingServer.
///
/// | Server -> Interface ||
/// | -------- | ---------------------------------------- |
/// | `to` | WebSocket IP address & port, "n.n.n.n:n" |
/// | `from` | NodeType |
/// | [`data`] | WebRTC signaling payload |
/// | [`echo`] | Echo response |
///
class WebRTCSignalingServer : public QObject {
Q_OBJECT
public:
/// @brief Constructs a new WebRTCSignalingServer object.
/// @param parent Qt parent object.
/// @param isWSSEnabled Whether the WebSocket used for WebRTC signaling should be secure (WSS protocol).
WebRTCSignalingServer(QObject* parent, bool isWSSEnabled);
/// @brief Binds the WebRTC signaling server's WebSocket to an address and port.
/// @param address The address to use for the WebSocket.
/// @param port The port to use for the WebSocket.
/// @return <code>true</code> if the WebSocket was successfully bound, <code>false</code> if it wasn't.
bool bind(const QHostAddress& address, quint16 port);
public slots:
/// @brief Send a WebRTC signaling channel message to an Interface client.
/// @param message The message to send to the Interface client. Includes details of the sender and the destination in
/// addition to the WebRTC signaling channel payload.
void sendMessage(const QJsonObject& message);
signals:
/// @brief A WebRTC signaling channel message was received from an Interface client.
/// @param message The message received from the Interface client. Includes details of the sender and the destination in
/// addition to the WebRTC signaling channel payload.\n
/// Not emitted if the message was an echo request for the domain server.
void messageReceived(const QJsonObject& message);
private slots:
void newWebSocketConnection();
void webSocketTextMessageReceived(const QString& message);
void webSocketDisconnected();
private:
void checkWebSocketServerIsListening();
QWebSocketServer* _webSocketServer;
QHostAddress _address;
quint16 _port { 0 };
QHash<QString, QWebSocket*> _webSockets; // <client WebSocket IP address and port, client connection WebSocket object>
// The WebSocket IP address and port is formatted as "n.n.n.n:n".
// A QString is used rather than a SockAddr, to make signaling easier.
QTimer* _isWebSocketServerListeningTimer;
};
/// @}
#endif // WEBRTC_DATA_CHANNELS
#endif // vircadia_WebRTCSignalingServer_h

View file

@ -0,0 +1,155 @@
//
// WebRTCSocket.cpp
// libraries/networking/src/webrtc
//
// Created by David Rowe on 21 Jun 2021.
// Copyright 2021 Vircadia contributors.
//
#include "WebRTCSocket.h"
#if defined(WEBRTC_DATA_CHANNELS)
#include <QHostAddress>
#include "../NetworkLogging.h"
#include "../udt/Constants.h"
WebRTCSocket::WebRTCSocket(QObject* parent) :
QObject(parent),
_dataChannels(this)
{
// Route signaling messages.
connect(this, &WebRTCSocket::onSignalingMessage, &_dataChannels, &WebRTCDataChannels::onSignalingMessage);
connect(&_dataChannels, &WebRTCDataChannels::signalingMessage, this, &WebRTCSocket::sendSignalingMessage);
// Route received data channel messages.
connect(&_dataChannels, &WebRTCDataChannels::dataMessage, this, &WebRTCSocket::onDataChannelReceivedMessage);
}
void WebRTCSocket::setSocketOption(QAbstractSocket::SocketOption option, const QVariant& value) {
clearError();
switch (option) {
case QAbstractSocket::SocketOption::ReceiveBufferSizeSocketOption:
case QAbstractSocket::SocketOption::SendBufferSizeSocketOption:
// WebRTC doesn't provide access to setting these buffer sizes.
break;
default:
setError(QAbstractSocket::SocketError::UnsupportedSocketOperationError, "Failed to set socket option");
qCCritical(networking_webrtc) << "WebRTCSocket::setSocketOption() not implemented for option:" << option;
}
}
QVariant WebRTCSocket::socketOption(QAbstractSocket::SocketOption option) {
clearError();
switch (option) {
case QAbstractSocket::SocketOption::ReceiveBufferSizeSocketOption:
// WebRTC doesn't provide access to the receive buffer size. Just use the default buffer size.
return udt::WEBRTC_RECEIVE_BUFFER_SIZE_BYTES;
case QAbstractSocket::SocketOption::SendBufferSizeSocketOption:
// WebRTC doesn't provide access to the send buffer size though it's probably 16MB. Just use the default buffer size.
return udt::WEBRTC_SEND_BUFFER_SIZE_BYTES;
default:
setError(QAbstractSocket::SocketError::UnsupportedSocketOperationError, "Failed to get socket option");
qCCritical(networking_webrtc) << "WebRTCSocket::getSocketOption() not implemented for option:" << option;
}
return QVariant();
}
bool WebRTCSocket::bind(const QHostAddress& address, quint16 port, QAbstractSocket::BindMode mode) {
// WebRTC data channels aren't bound to ports so just treat this as a successful operation.
auto wasBound = _isBound;
_isBound = true;
if (_isBound != wasBound) {
emit stateChanged(_isBound ? QAbstractSocket::BoundState : QAbstractSocket::UnconnectedState);
}
return _isBound;
}
QAbstractSocket::SocketState WebRTCSocket::state() const {
return _isBound ? QAbstractSocket::BoundState : QAbstractSocket::UnconnectedState;
}
void WebRTCSocket::abort() {
_dataChannels.reset();
}
qint64 WebRTCSocket::writeDatagram(const QByteArray& datagram, const SockAddr& destination) {
clearError();
if (_dataChannels.sendDataMessage(destination, datagram)) {
return datagram.length();
}
setError(QAbstractSocket::SocketError::UnknownSocketError, "Failed to write datagram");
return -1;
}
qint64 WebRTCSocket::bytesToWrite(const SockAddr& destination) const {
return _dataChannels.getBufferedAmount(destination);
}
bool WebRTCSocket::hasPendingDatagrams() const {
return _receivedQueue.length() > 0;
}
qint64 WebRTCSocket::pendingDatagramSize() const {
if (_receivedQueue.length() > 0) {
return _receivedQueue.head().second.length();
}
return -1;
}
qint64 WebRTCSocket::readDatagram(char* data, qint64 maxSize, QHostAddress* address, quint16* port) {
clearError();
if (_receivedQueue.length() > 0) {
auto datagram = _receivedQueue.dequeue();
auto length = std::min((qint64)datagram.second.length(), maxSize);
if (data) {
memcpy(data, datagram.second.constData(), length);
}
if (address) {
*address = datagram.first.getAddress();
}
if (port) {
*port = datagram.first.getPort();
}
return length;
}
setError(QAbstractSocket::SocketError::UnknownSocketError, "Failed to read datagram");
return -1;
}
QAbstractSocket::SocketError WebRTCSocket::error() const {
return _lastErrorType;
}
QString WebRTCSocket::errorString() const {
return _lastErrorString;
}
void WebRTCSocket::setError(QAbstractSocket::SocketError errorType, QString errorString) {
_lastErrorType = errorType;
}
void WebRTCSocket::clearError() {
_lastErrorType = QAbstractSocket::SocketError();
_lastErrorString = QString();
}
void WebRTCSocket::onDataChannelReceivedMessage(const SockAddr& source, const QByteArray& message) {
_receivedQueue.enqueue(QPair<SockAddr, QByteArray>(source, message));
emit readyRead();
}
#endif // WEBRTC_DATA_CHANNELS

View file

@ -0,0 +1,171 @@
//
// WebRTCSocket.h
// libraries/networking/src/webrtc
//
// Created by David Rowe on 21 Jun 2021.
// Copyright 2021 Vircadia contributors.
//
#ifndef vircadia_WebRTCSocket_h
#define vircadia_WebRTCSocket_h
#include <shared/WebRTC.h>
#if defined(WEBRTC_DATA_CHANNELS)
#include <QAbstractSocket>
#include <QObject>
#include <QQueue>
#include "WebRTCDataChannels.h"
/// @addtogroup Networking
/// @{
/// @brief Provides a QUdpSocket-style interface for using WebRTCDataChannels.
///
/// @details A WebRTC data channel is identified by the IP address and port of the client WebSocket that was used when opening
/// the data channel - this is considered to be the WebRTC data channel's address. The IP address and port of the actual WebRTC
/// connection is not used.
class WebRTCSocket : public QObject {
Q_OBJECT
public:
/// @brief Constructs a new WebRTCSocket object.
/// @param parent Qt parent object.
WebRTCSocket(QObject* parent);
/// @brief Nominally sets the value of a socket option.
/// @details Only <code>SendBufferSizeSocketOption</code> and <code>ReceiveBufferSizeSocketOption</code> options are handled
/// and for these no action is taken because these buffer sizes are not configurable in WebRTC.
/// Included for compatibility with the QUdpSocket interface.
/// @param option The socket option.
/// @param value The value of the socket option.
void setSocketOption(QAbstractSocket::SocketOption option, const QVariant& value);
/// @brief Nominally gets the value of a socket option.
/// @details Only <code>SendBufferSizeSocketOption</code> and <code>ReceiveBufferSizeSocketOption</code> options are handled
/// and for these only default values are returned because these buffer sizes are not configurable in WebRTC.
/// Included for compatibility with the QUdpSocket interface.
/// @param option The socket option.
/// @return The value of the socket option.
QVariant socketOption(QAbstractSocket::SocketOption option);
/// @brief Nominally binds the WebRTC socket to an address and port.
/// @details WebRTC data connections aren't actually bound to an address or port. Their ports are negotiated as part of the
/// WebRTC peer connection process.
/// Included for compatibility with the QUdpSocket interface.
/// @param address The address.
/// @param port The port.
/// @param mode The bind mode.
/// @return <code>true</code>.
bool bind(const QHostAddress& address, quint16 port = 0, QAbstractSocket::BindMode mode
= QAbstractSocket::DefaultForPlatform);
/// @brief Gets the state of the socket.
/// @details In particular, QAbstractSocket::BoundState is returned if the socket is bound,
/// QAbstractSocket::UnconnectedState if it isn't.
/// @return The state of the socket.
QAbstractSocket::SocketState state() const;
/// @brief Immediately closes all connections and resets the socket.
void abort();
/// @brief Nominally gets the host port number.
/// Included for compatibility with the QUdpSocket interface.
/// @return <code>0</code>
quint16 localPort() const { return 0; }
/// @brief Nominally gets the socket descriptor.
/// Included for compatibility with the QUdpSocket interface.
/// @return <code>-1</code>
qintptr socketDescriptor() const { return -1; }
/// @brief Sends a datagram.
/// @param datagram The datagram to send.
/// @param destination The destination WebRTC data channel address.
/// @return The number of bytes if successfully sent, otherwise <code>-1</code>.
qint64 writeDatagram(const QByteArray& datagram, const SockAddr& destination);
/// @brief Gets the number of bytes waiting to be written.
/// @param destination The destination WebRTC data channel address.
/// @return The number of bytes waiting to be written.
qint64 bytesToWrite(const SockAddr& destination) const;
/// @brief Gets whether there's a datagram waiting to be read.
/// @return <code>true</code> if there's a datagram waiting to be read, <code>false</code> if there isn't.
bool hasPendingDatagrams() const;
/// @brief Gets the size of the first pending datagram.
/// @return the size of the first pending datagram; <code>-1</code> if there is no pending datagram.
qint64 pendingDatagramSize() const;
/// @brief Reads the next datagram, up to a maximum number of bytes.
/// @details Any remaining data in the datagram is lost.
/// @param data The destination to read the datagram into.
/// @param maxSize The maximum number of bytes to read.
/// @param address The destination to put the WebRTC data channel's IP address.
/// @param port The destination to put the WebRTC data channel's port.
/// @return The number of bytes read on success; <code>-1</code> if reading unsuccessful.
qint64 readDatagram(char* data, qint64 maxSize, QHostAddress* address = nullptr, quint16* port = nullptr);
/// @brief Gets the type of error that last occurred.
/// @return The type of error that last occurred.
QAbstractSocket::SocketError error() const;
/// @brief Gets the description of the error that last occurred.
/// @return The description of the error that last occurred.
QString errorString() const;
public slots:
/// @brief Handles the WebRTC data channel receiving a message.
/// @details Queues the message to be read via readDatagram.
/// @param source The WebRTC data channel that the message was received on.
/// @param message The message that was received.
void onDataChannelReceivedMessage(const SockAddr& source, const QByteArray& message);
signals:
/// @brief Emitted when the state of the socket changes.
/// @param socketState The new state of the socket.
void stateChanged(QAbstractSocket::SocketState socketState);
/// @brief Emitted each time new data becomes available for reading.
void readyRead();
/// @brief Emitted when a WebRTC signaling message has been received from the signaling server for this WebRTCSocket.
/// @param json The signaling message.
void onSignalingMessage(const QJsonObject& json);
/// @brief Emitted when there's a WebRTC signaling message to send via the signaling server.
/// @param json The signaling message.
void sendSignalingMessage(const QJsonObject& message);
private:
void setError(QAbstractSocket::SocketError errorType, QString errorString);
void clearError();
WebRTCDataChannels _dataChannels;
bool _isBound { false };
QQueue<QPair<SockAddr, QByteArray>> _receivedQueue; // Messages received are queued for reading from the "socket".
QAbstractSocket::SocketError _lastErrorType { QAbstractSocket::UnknownSocketError };
QString _lastErrorString;
};
/// @}
#endif // WEBRTC_DATA_CHANNELS
#endif // vircadia_WebRTCSocket_h

View file

@ -3,6 +3,7 @@
// libraries/shared/src/shared/
//
// Copyright 2019 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
@ -11,31 +12,38 @@
#ifndef hifi_WebRTC_h
#define hifi_WebRTC_h
#ifndef QSYSTEMDETECTION_H
#include <qsystemdetection.h>
#endif
// WEBRTC_AUDIO: WebRTC audio features, e.g., echo canceling.
// WEBRTC_DATA_CHANNELS: WebRTC client-server connections in parallel with UDP.
#if defined(Q_OS_MAC)
# define WEBRTC_ENABLED 1
# define WEBRTC_AUDIO 1
# define WEBRTC_POSIX 1
# define WEBRTC_LEGACY 1
#elif defined(Q_OS_WIN)
# define WEBRTC_ENABLED 1
# define WEBRTC_AUDIO 1
# define WEBRTC_DATA_CHANNELS 1
# define WEBRTC_WIN 1
# define NOMINMAX 1
# define WIN32_LEAN_AND_MEAN 1
#elif defined(Q_OS_ANDROID)
// I don't yet have a working libwebrtc for android
// # define WEBRTC_ENABLED 1
// # define WEBRTC_AUDIO 1
// # define WEBRTC_POSIX 1
// # define WEBRTC_LEGACY 1
#elif defined(Q_OS_LINUX) && defined(Q_PROCESSOR_X86_64)
# define WEBRTC_ENABLED 1
# define WEBRTC_AUDIO 1
# define WEBRTC_POSIX 1
# define WEBRTC_DATA_CHANNELS 1
#elif defined(Q_OS_LINUX) && defined(Q_PROCESSOR_ARM)
// WebRTC is basically impossible to build on aarch64 Linux.
// I am looking at https://gitlab.freedesktop.org/pulseaudio/webrtc-audio-processing for an alternative.
// # define WEBRTC_ENABLED 1
// # define WEBRTC_AUDIO 1
// # define WEBRTC_POSIX 1
#endif
#if defined(WEBRTC_ENABLED)
# include <modules/audio_processing/include/audio_processing.h>
# include "modules/audio_processing/audio_processing_impl.h"
// # define WEBRTC_LEGACY 1
#endif
#endif // hifi_WebRTC_h

View file

@ -10,6 +10,11 @@
If you want to run Doxygen from a command prompt, add the Doxygen install's `/bin` directory to your system PATH.
## Documenting
See section 4.3.6 of the [*Coding Standard*](../../CODING_STANDARD.md).
## Running
### Using the Doxywizard GUI

View file

@ -64,7 +64,7 @@ ICEClientApp::ICEClientApp(int argc, char* argv[]) :
const_cast<QLoggingCategory*>(&networking())->setEnabled(QtWarningMsg, false);
}
_stunSockAddr = SockAddr(STUN_SERVER_HOSTNAME, STUN_SERVER_PORT, true);
_stunSockAddr = SockAddr(SocketType::UDP, STUN_SERVER_HOSTNAME, STUN_SERVER_PORT, true);
_cacheSTUNResult = parser.isSet(cacheSTUNOption);
@ -81,7 +81,7 @@ ICEClientApp::ICEClientApp(int argc, char* argv[]) :
}
}
_iceServerAddr = SockAddr("127.0.0.1", ICE_SERVER_DEFAULT_PORT);
_iceServerAddr = SockAddr(SocketType::UDP, "127.0.0.1", ICE_SERVER_DEFAULT_PORT);
if (parser.isSet(iceServerAddressOption)) {
// parse the IP and port combination for this target
QString hostnamePortString = parser.value(iceServerAddressOption);
@ -98,7 +98,7 @@ ICEClientApp::ICEClientApp(int argc, char* argv[]) :
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
} else {
_iceServerAddr = SockAddr(address, port);
_iceServerAddr = SockAddr(SocketType::UDP, address, port);
}
}
@ -134,7 +134,7 @@ void ICEClientApp::openSocket() {
_socket = new udt::Socket();
unsigned int localPort = 0;
_socket->bind(QHostAddress::AnyIPv4, localPort);
_socket->bind(SocketType::UDP, QHostAddress::AnyIPv4, localPort);
_socket->setPacketHandler([this](std::unique_ptr<udt::Packet> packet) { processPacket(std::move(packet)); });
_socket->addUnfilteredHandler(_stunSockAddr,
[this](std::unique_ptr<udt::BasePacket> packet) {
@ -142,10 +142,10 @@ void ICEClientApp::openSocket() {
});
if (_verbose) {
qDebug() << "local port is" << _socket->localPort();
qDebug() << "local port is" << _socket->localPort(SocketType::UDP);
}
_localSockAddr = SockAddr("127.0.0.1", _socket->localPort());
_publicSockAddr = SockAddr("127.0.0.1", _socket->localPort());
_localSockAddr = SockAddr(SocketType::UDP, "127.0.0.1", _socket->localPort(SocketType::UDP));
_publicSockAddr = SockAddr(SocketType::UDP, "127.0.0.1", _socket->localPort(SocketType::UDP));
_domainPingCount = 0;
}
@ -190,7 +190,7 @@ void ICEClientApp::doSomething() {
if (_verbose) {
qDebug() << "using cached STUN response";
}
_publicSockAddr.setPort(_socket->localPort());
_publicSockAddr.setPort(_socket->localPort(SocketType::UDP));
setState(talkToIceServer);
}
@ -305,7 +305,7 @@ void ICEClientApp::processSTUNResponse(std::unique_ptr<udt::BasePacket> packet)
uint16_t newPublicPort;
QHostAddress newPublicAddress;
if (LimitedNodeList::parseSTUNResponse(packet.get(), newPublicAddress, newPublicPort)) {
_publicSockAddr = SockAddr(newPublicAddress, newPublicPort);
_publicSockAddr = SockAddr(SocketType::UDP, newPublicAddress, newPublicPort);
if (_verbose) {
qDebug() << "My public address is" << _publicSockAddr;
}

@ -1 +1 @@
Subproject commit d2e383cab811018422433fe0d8f224e53e0506cf
Subproject commit 0afaa769d46683d461c9288aa31468f64cba0233