diff --git a/CODING_STANDARD.md b/CODING_STANDARD.md index 1c2f3cb27e1..3d4516f99fd 100644 --- a/CODING_STANDARD.md +++ b/CODING_STANDARD.md @@ -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. diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index d8dac8ef361..134696b852c 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -14,6 +14,7 @@ #include +#include #include #include #include @@ -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(this, &AssignmentClient::handleCreateAssignmentPacket)); packetReceiver.registerListener(PacketType::StopNode, PacketReceiver::makeUnsourcedListenerReference(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(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 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(); + 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(); + auto domainServerAddress = nodeList->getDomainHandler().getSockAddr(); + nodeList->sendPacketList(std::move(packetList), domainServerAddress); +} + +#endif diff --git a/assignment-client/src/AssignmentClient.h b/assignment-client/src/AssignmentClient.h index 2cf130b727f..3c87783012d 100644 --- a/assignment-client/src/AssignmentClient.h +++ b/assignment-client/src/AssignmentClient.h @@ -17,6 +17,8 @@ #include #include +#include + #include "ThreadedAssignment.h" class QSharedMemory; @@ -30,19 +32,26 @@ class AssignmentClient : public QObject { 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 message); void handleStopNodePacket(QSharedPointer message); +#if defined(WEBRTC_DATA_CHANNELS) + void handleWebRTCSignalingPacket(QSharedPointer message); + void sendSignalingMessageToUserClient(const QJsonObject& json); +#endif + +signals: +#if defined(WEBRTC_DATA_CHANNELS) + void webrtcSignalingMessageFromUserClient(const QJsonObject& json); +#endif private: void setUpStatusToMonitor(); diff --git a/cmake/ports/webrtc/CONTROL b/cmake/ports/webrtc/CONTROL index 12a76920b9d..b705809c1a6 100644 --- a/cmake/ports/webrtc/CONTROL +++ b/cmake/ports/webrtc/CONTROL @@ -1,3 +1,3 @@ Source: webrtc -Version: 20190626 +Version: 20210105 Description: WebRTC diff --git a/cmake/ports/webrtc/README.md b/cmake/ports/webrtc/README.md new file mode 100644 index 00000000000..a4053b679e4 --- /dev/null +++ b/cmake/ports/webrtc/README.md @@ -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 *\\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=\"\""` +- `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=\"\""` +- `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 -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. diff --git a/cmake/ports/webrtc/copy-VCPKG-file-win.cmd b/cmake/ports/webrtc/copy-VCPKG-file-win.cmd new file mode 100644 index 00000000000..45ec95c1281 --- /dev/null +++ b/cmake/ports/webrtc/copy-VCPKG-file-win.cmd @@ -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 diff --git a/cmake/ports/webrtc/portfile.cmake b/cmake/ports/webrtc/portfile.cmake index fdc653d6a5e..0986507e898 100644 --- a/cmake/ports/webrtc/portfile.cmake +++ b/cmake/ports/webrtc/portfile.cmake @@ -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 () diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index b91a10a17c5..f6882d6a053 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -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", diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index f338f25791b..d14a909a1a2 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -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(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(); + + // 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(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(); + 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 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 diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 403db778b17..13c715d5e3d 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -27,6 +27,8 @@ #include #include #include +#include +#include #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 message); +#endif + void aboutToQuit(); signals: @@ -154,6 +161,12 @@ private slots: 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 slots: std::initializer_list 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 slots: std::unordered_map> _pendingContentFiles; QThread _assetClientThread; + +#if defined(WEBRTC_DATA_CHANNELS) + std::unique_ptr _webrtcSignalingServer { nullptr }; +#endif }; diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 7b04c72845a..912a771ab3c 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -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; diff --git a/domain-server/src/NodeConnectionData.cpp b/domain-server/src/NodeConnectionData.cpp index 1ef8ebf6a9c..d7d813e3df3 100644 --- a/domain-server/src/NodeConnectionData.cpp +++ b/domain-server/src/NodeConnectionData.cpp @@ -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; diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index d5eb52b2161..0e3997a0ea0 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -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 packet) { processPacket(std::move(packet)); }); diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index b6a2ea3ed98..fe0e83dfa04 100755 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -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()->editEntity(_otherAvatarOrbMeshPlaceholderID, properties); } } diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index c2f1abdd94f..9c1c4fa7847 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -551,7 +551,7 @@ void setupPreferences() { auto getter = [nodeListWeak] { auto nodeList = nodeListWeak.lock(); if (nodeList) { - return static_cast(nodeList->getSocketLocalPort()); + return static_cast(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(preset)); + nodeList->setSocketLocalPort(SocketType::UDP, static_cast(preset)); } }; auto preference = new IntSpinnerPreference(NETWORKING, "Listening Port", getter, setter); diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 38635870fd5..b77ebbee21f 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -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 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(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()); } } -} \ No newline at end of file +} diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 8041eb559c6..9c25c2d4f22 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -58,6 +58,12 @@ #include "AudioFileWav.h" #include "HifiAudioDeviceInfo.h" +#if defined(WEBRTC_AUDIO) +# define WEBRTC_APM_DEBUG_DUMP 0 +# include +# 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 @@ public slots: 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 @@ public slots: 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; diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index 7a81b8a67ae..e946c6afb7e 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -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 diff --git a/libraries/audio/src/InboundAudioStream.h b/libraries/audio/src/InboundAudioStream.h index c10a86cb693..b42609d5769 100644 --- a/libraries/audio/src/InboundAudioStream.h +++ b/libraries/audio/src/InboundAudioStream.h @@ -132,7 +132,7 @@ public slots: /// 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); diff --git a/libraries/audio/src/MixedProcessedAudioStream.cpp b/libraries/audio/src/MixedProcessedAudioStream.cpp index 6510f0bfc98..d1312edb956 100644 --- a/libraries/audio/src/MixedProcessedAudioStream.cpp +++ b/libraries/audio/src/MixedProcessedAudioStream.cpp @@ -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 diff --git a/libraries/audio/src/MixedProcessedAudioStream.h b/libraries/audio/src/MixedProcessedAudioStream.h index 14da1d45af0..5732f32e902 100644 --- a/libraries/audio/src/MixedProcessedAudioStream.h +++ b/libraries/audio/src/MixedProcessedAudioStream.h @@ -34,7 +34,7 @@ class MixedProcessedAudioStream : public InboundAudioStream { 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: diff --git a/libraries/networking/CMakeLists.txt b/libraries/networking/CMakeLists.txt index 9f63f2cb00f..c635059d1b7 100644 --- a/libraries/networking/CMakeLists.txt +++ b/libraries/networking/CMakeLists.txt @@ -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) diff --git a/libraries/networking/src/BaseAssetScriptingInterface.cpp b/libraries/networking/src/BaseAssetScriptingInterface.cpp index d7d14496baf..872913082ac 100644 --- a/libraries/networking/src/BaseAssetScriptingInterface.cpp +++ b/libraries/networking/src/BaseAssetScriptingInterface.cpp @@ -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 #include +#include +#include #include #include diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 4ba5565a745..4881ba23ead 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -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(); @@ -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 diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index bcb89afe3aa..84ef909620c 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -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 @@ class DomainHandler : public QObject { }; 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 packetList); @@ -245,7 +253,7 @@ public slots: void processDomainServerConnectionDeniedPacket(QSharedPointer 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 @@ private slots: 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); diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index d45aca2f89c..2b487b8b2c0 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -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"); 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 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(); } } diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 04b17c6266d..2a4f9ceeb61 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -38,7 +38,6 @@ #include #include -#include "DomainHandler.h" #include "NetworkingConstants.h" #include "Node.h" #include "NLPacket.h" @@ -122,7 +121,7 @@ class LimitedNodeList : public QObject, public Dependency { 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 @@ class LimitedNodeList : public QObject, public Dependency { 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 slots: qint64 sendPacket(std::unique_ptr 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 slots: 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; diff --git a/libraries/networking/src/NLPacketList.h b/libraries/networking/src/NLPacketList.h index d463f43af83..76939bb4a0d 100644 --- a/libraries/networking/src/NLPacketList.h +++ b/libraries/networking/src/NLPacketList.h @@ -35,7 +35,6 @@ class NLPacketList : public udt::PacketList { virtual std::unique_ptr createPacket() override; - PacketVersion _packetVersion; NLPacket::LocalID _sourceID; }; diff --git a/libraries/networking/src/NetworkLogging.cpp b/libraries/networking/src/NetworkLogging.cpp index 3d7c2fc5d53..daa8c8c4161 100644 --- a/libraries/networking/src/NetworkLogging.cpp +++ b/libraries/networking/src/NetworkLogging.cpp @@ -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") diff --git a/libraries/networking/src/NetworkLogging.h b/libraries/networking/src/NetworkLogging.h index 8247c600962..906947b7c44 100644 --- a/libraries/networking/src/NetworkLogging.h +++ b/libraries/networking/src/NetworkLogging.h @@ -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 diff --git a/libraries/networking/src/NetworkPeer.cpp b/libraries/networking/src/NetworkPeer.cpp index e76b8c31a64..0b3b82ca65b 100644 --- a/libraries/networking/src/NetworkPeer.cpp +++ b/libraries/networking/src/NetworkPeer.cpp @@ -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) { diff --git a/libraries/networking/src/Node.cpp b/libraries/networking/src/Node.cpp index aa13dd65657..de9fb6bddf1 100644 --- a/libraries/networking/src/Node.cpp +++ b/libraries/networking/src/Node.cpp @@ -46,6 +46,22 @@ static const QHash TYPE_NAME_HASH { { NodeType::Unassigned, "Unassigned" } }; +static const QHash 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; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 5f066b3eb30..58d161a7975 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -147,7 +147,7 @@ NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort) auto& packetReceiver = getPacketReceiver(); packetReceiver.registerListener(PacketType::DomainList, - PacketReceiver::makeUnsourcedListenerReference(this, &NodeList::processDomainServerList)); + PacketReceiver::makeUnsourcedListenerReference(this, &NodeList::processDomainList)); packetReceiver.registerListener(PacketType::Ping, PacketReceiver::makeSourcedListenerReference(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(); 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()->getPlaceName(); if (!domainIsConnected) { @@ -711,7 +714,9 @@ void NodeList::processDomainServerConnectionTokenPacket(QSharedPointer message) { +void NodeList::processDomainList(QSharedPointer 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 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 diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index a09ee168ce5..5ba982aec54 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -112,7 +112,7 @@ public slots: void sendDomainServerCheckIn(); void handleDSPathQuery(const QString& newPath); - void processDomainServerList(QSharedPointer message); + void processDomainList(QSharedPointer message); void processDomainServerAddedNode(QSharedPointer message); void processDomainServerRemovedNode(QSharedPointer message); void processDomainServerPathResponse(QSharedPointer 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); diff --git a/libraries/networking/src/NodeType.h b/libraries/networking/src/NodeType.h index 2b2cc4e0115..55754888c45 100644 --- a/libraries/networking/src/NodeType.h +++ b/libraries/networking/src/NodeType.h @@ -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 NodeSet; diff --git a/libraries/networking/src/PacketReceiver.cpp b/libraries/networking/src/PacketReceiver.cpp index 384355b53c7..d99b25e6834 100644 --- a/libraries/networking/src/PacketReceiver.cpp +++ b/libraries/networking/src/PacketReceiver.cpp @@ -140,9 +140,7 @@ void PacketReceiver::handleVerifiedPacket(std::unique_ptr packet) { if (_shouldDropPackets) { return; } - - auto nodeList = DependencyManager::get(); - + // setup an NLPacket from the packet we were passed auto nlPacket = NLPacket::fromBase(std::move(packet)); auto receivedMessage = QSharedPointer::create(*nlPacket); @@ -163,7 +161,7 @@ void PacketReceiver::handleVerifiedMessagePacket(std::unique_ptr 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); diff --git a/libraries/networking/src/SockAddr.cpp b/libraries/networking/src/SockAddr.cpp index e61ae5bccc8..e8eb6c4b863 100644 --- a/libraries/networking/src/SockAddr.cpp +++ b/libraries/networking/src/SockAddr.cpp @@ -28,21 +28,22 @@ int sockAddrMetaTypeId = qRegisterMetaType(); 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(sockaddr)->sin_port); - } else { - _port = ntohs(reinterpret_cast(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; } diff --git a/libraries/networking/src/SockAddr.h b/libraries/networking/src/SockAddr.h index 07b3e8c306b..877bae4ee47 100644 --- a/libraries/networking/src/SockAddr.h +++ b/libraries/networking/src/SockAddr.h @@ -20,14 +20,16 @@ struct sockaddr; #include +#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 @@ class SockAddr : public QObject { 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 @@ class SockAddr : public QObject { 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; }; diff --git a/libraries/networking/src/SocketType.h b/libraries/networking/src/SocketType.h new file mode 100644 index 00000000000..d9b613a7e1e --- /dev/null +++ b/libraries/networking/src/SocketType.h @@ -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., "WebRTC". +/// @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 diff --git a/libraries/networking/src/udt/BasePacket.cpp b/libraries/networking/src/udt/BasePacket.cpp index 442ed8a2c84..385255c8409 100644 --- a/libraries/networking/src/udt/BasePacket.cpp +++ b/libraries/networking/src/udt/BasePacket.cpp @@ -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]()); diff --git a/libraries/networking/src/udt/Connection.cpp b/libraries/networking/src/udt/Connection.cpp index 66bf3f66134..529f26d2397 100644 --- a/libraries/networking/src/udt/Connection.cpp +++ b/libraries/networking/src/udt/Connection.cpp @@ -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) { "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(); diff --git a/libraries/networking/src/udt/Connection.h b/libraries/networking/src/udt/Connection.h index 2869b7f57de..460238fe6a2 100644 --- a/libraries/networking/src/udt/Connection.h +++ b/libraries/networking/src/udt/Connection.h @@ -43,9 +43,7 @@ class PendingReceivedMessage { std::list> _packets; private: - bool _hasLastPacket { false }; Packet::MessagePartNumber _nextPartNumber = 0; - unsigned int _numPackets { 0 }; }; class Connection : public QObject { @@ -112,9 +110,6 @@ private slots: 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 diff --git a/libraries/networking/src/udt/Constants.h b/libraries/networking/src/udt/Constants.h index 243fa4edda0..d6b208a0128 100644 --- a/libraries/networking/src/udt/Constants.h +++ b/libraries/networking/src/udt/Constants.h @@ -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; diff --git a/libraries/networking/src/udt/ControlPacket.cpp b/libraries/networking/src/udt/ControlPacket.cpp index b56f455030a..4dced78d0e2 100644 --- a/libraries/networking/src/udt/ControlPacket.cpp +++ b/libraries/networking/src/udt/ControlPacket.cpp @@ -98,10 +98,11 @@ void ControlPacket::writeType() { void ControlPacket::readType() { ControlBitAndType bitAndType = *reinterpret_cast(_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; diff --git a/libraries/networking/src/udt/NetworkSocket.cpp b/libraries/networking/src/udt/NetworkSocket.cpp new file mode 100644 index 00000000000..cc28cbfc734 --- /dev/null +++ b/libraries/networking/src/udt/NetworkSocket.cpp @@ -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); +} diff --git a/libraries/networking/src/udt/NetworkSocket.h b/libraries/networking/src/udt/NetworkSocket.h new file mode 100644 index 00000000000..030f27e1197 --- /dev/null +++ b/libraries/networking/src/udt/NetworkSocket.h @@ -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 +#include + +#include + +#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 0. + 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 -1. + 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 -1. + 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 true if there is a datagram waiting to be read, false 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 -1. + 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 diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index f7c1192886b..e561bfe21ec 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -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(DomainListVersion::HasConnectReason); + return static_cast(DomainListVersion::SocketTypes); case PacketType::EntityAdd: case PacketType::EntityClone: case PacketType::EntityEdit: @@ -72,10 +72,12 @@ PacketVersion versionForPacketType(PacketType packetType) { return static_cast(DomainConnectionDeniedVersion::IncludesExtraInfo); case PacketType::DomainConnectRequest: - return static_cast(DomainConnectRequestVersion::HasCompressedSystemInfo); + return static_cast(DomainConnectRequestVersion::SocketTypes); + case PacketType::DomainListRequest: + return static_cast(DomainListRequestVersion::SocketTypes); case PacketType::DomainServerAddedNode: - return static_cast(DomainServerAddedNodeVersion::PermissionsGrid); + return static_cast(DomainServerAddedNodeVersion::SocketTypes); case PacketType::EntityScriptCallMethod: return static_cast(EntityScriptCallMethodVersion::ClientCallable); diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 819551045ac..64b2e481c52 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -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 @@ class PacketTypeEnum { BulkAvatarTraitsAck, StopInjector, AvatarZonePresence, + WebRTCSignaling, NUM_PACKET_TYPE }; @@ -188,7 +191,7 @@ class PacketTypeEnum { << 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 { diff --git a/libraries/networking/src/udt/SequenceNumber.h b/libraries/networking/src/udt/SequenceNumber.h index 13bd83d4f2c..beefaa50760 100644 --- a/libraries/networking/src/udt/SequenceNumber.h +++ b/libraries/networking/src/udt/SequenceNumber.h @@ -25,8 +25,8 @@ class SequenceNumber { 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) {} diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index 241ca1db856..7beeedb1eba 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -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(SocketType socketType, quint16 localPort) { + _networkSocket.abort(socketType); + bind(socketType, QHostAddress::AnyIPv4, localPort); } -void Socket::rebind(quint16 localPort) { - _udpSocket.abort(); - bind(QHostAddress::AnyIPv4, localPort); +#if defined(WEBRTC_DATA_CHANNELS) +const WebRTCSocket* Socket::getWebRTCSocket() { + return _networkSocket.getWebRTCSocket(); } +#endif -void Socket::setSystemBufferSizes() { +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, const Soc return 0; } - // Unerliable and Unordered + // Unreliable and Unordered qint64 totalBytesSent = 0; while (!packetList->_packets.empty()) { totalBytesSent += writePacket(packetList->takeFront(), 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 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(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 Socket::getConnectionSockAddrs() { return addr; } -void Socket::handleSocketError(QAbstractSocket::SocketError socketError) { +void Socket::handleSocketError(SocketType socketType, QAbstractSocket::SocketError socketError) { int wsaError = 0; static std::atomic 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; } } diff --git a/libraries/networking/src/udt/Socket.h b/libraries/networking/src/udt/Socket.h index 8c46a223358..ab9699bb8f4 100644 --- a/libraries/networking/src/udt/Socket.h +++ b/libraries/networking/src/udt/Socket.h @@ -22,11 +22,11 @@ #include #include -#include #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>; - + 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 @@ class Socket : public QObject { 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 @@ class Socket : public QObject { 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 slots: 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; diff --git a/libraries/networking/src/webrtc/WebRTCDataChannels.cpp b/libraries/networking/src/webrtc/WebRTCDataChannels.cpp new file mode 100644 index 00000000000..44c1c5e82cb --- /dev/null +++ b/libraries/networking/src/webrtc/WebRTCDataChannels.cpp @@ -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 +#include + +#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 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 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(); + _createSessionDescriptionObserver = new rtc::RefCountedObject(this); + _dataChannelObserver = std::make_shared(this); + _peerConnectionObserver = std::make_shared(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 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(), (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 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 WebRTCDataChannels::createPeerConnection( + const std::shared_ptr 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 diff --git a/libraries/networking/src/webrtc/WebRTCDataChannels.h b/libraries/networking/src/webrtc/WebRTCDataChannels.h new file mode 100644 index 00000000000..68bb439b619 --- /dev/null +++ b/libraries/networking/src/webrtc/WebRTCDataChannels.h @@ -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 + +#if defined(WEBRTC_DATA_CHANNELS) + + +#include +#include + +#undef emit // Avoid conflict between Qt signals/slots and the WebRTC library's. +#include +#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 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 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 _setSessionDescriptionObserver { nullptr }; + rtc::scoped_refptr _createSessionDescriptionObserver { nullptr }; + + std::shared_ptr _dataChannelObserver { nullptr }; + rtc::scoped_refptr _dataChannel { nullptr }; + + std::shared_ptr _peerConnectionObserver { nullptr }; + rtc::scoped_refptr _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 createPeerConnection( + const std::shared_ptr 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 _rtcNetworkThread { nullptr }; + std::unique_ptr _rtcWorkerThread { nullptr }; + std::unique_ptr _rtcSignalingThread { nullptr }; + + rtc::scoped_refptr _peerConnectionFactory { nullptr }; + + QHash _connectionsByID; // + // 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 diff --git a/libraries/networking/src/webrtc/WebRTCSignalingServer.cpp b/libraries/networking/src/webrtc/WebRTCSignalingServer.cpp new file mode 100644 index 00000000000..0534639419b --- /dev/null +++ b/libraries/networking/src/webrtc/WebRTCSignalingServer.cpp @@ -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 +#include +#include + +#include +#include + +#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(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(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 diff --git a/libraries/networking/src/webrtc/WebRTCSignalingServer.h b/libraries/networking/src/webrtc/WebRTCSignalingServer.h new file mode 100644 index 00000000000..3a2eebbcefe --- /dev/null +++ b/libraries/networking/src/webrtc/WebRTCSignalingServer.h @@ -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 + +#if defined(WEBRTC_DATA_CHANNELS) + +#include +#include +#include + +#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 true if the WebSocket was successfully bound, false 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 _webSockets; // + // 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 diff --git a/libraries/networking/src/webrtc/WebRTCSocket.cpp b/libraries/networking/src/webrtc/WebRTCSocket.cpp new file mode 100644 index 00000000000..e34dc5d5a24 --- /dev/null +++ b/libraries/networking/src/webrtc/WebRTCSocket.cpp @@ -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 + +#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(source, message)); + emit readyRead(); +} + +#endif // WEBRTC_DATA_CHANNELS diff --git a/libraries/networking/src/webrtc/WebRTCSocket.h b/libraries/networking/src/webrtc/WebRTCSocket.h new file mode 100644 index 00000000000..04856f50f1f --- /dev/null +++ b/libraries/networking/src/webrtc/WebRTCSocket.h @@ -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 + +#if defined(WEBRTC_DATA_CHANNELS) + +#include +#include +#include + +#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 SendBufferSizeSocketOption and ReceiveBufferSizeSocketOption 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 SendBufferSizeSocketOption and ReceiveBufferSizeSocketOption 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 true. + 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 0 + quint16 localPort() const { return 0; } + + /// @brief Nominally gets the socket descriptor. + /// Included for compatibility with the QUdpSocket interface. + /// @return -1 + 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 -1. + 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 true if there's a datagram waiting to be read, false if there isn't. + bool hasPendingDatagrams() const; + + /// @brief Gets the size of the first pending datagram. + /// @return the size of the first pending datagram; -1 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; -1 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> _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 diff --git a/libraries/shared/src/shared/WebRTC.h b/libraries/shared/src/shared/WebRTC.h index 2c683151f8a..3f4c9e156f9 100644 --- a/libraries/shared/src/shared/WebRTC.h +++ b/libraries/shared/src/shared/WebRTC.h @@ -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 +#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 -# include "modules/audio_processing/audio_processing_impl.h" +// # define WEBRTC_LEGACY 1 #endif #endif // hifi_WebRTC_h diff --git a/tools/doxygen/README.md b/tools/doxygen/README.md index a8363abd6c1..8b8fdc5c264 100644 --- a/tools/doxygen/README.md +++ b/tools/doxygen/README.md @@ -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 diff --git a/tools/ice-client/src/ICEClientApp.cpp b/tools/ice-client/src/ICEClientApp.cpp index 233b5e71f07..b10ef46b628 100644 --- a/tools/ice-client/src/ICEClientApp.cpp +++ b/tools/ice-client/src/ICEClientApp.cpp @@ -64,7 +64,7 @@ ICEClientApp::ICEClientApp(int argc, char* argv[]) : const_cast(&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 packet) { processPacket(std::move(packet)); }); _socket->addUnfilteredHandler(_stunSockAddr, [this](std::unique_ptr 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 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; } diff --git a/vircadia-web b/vircadia-web index d2e383cab81..0afaa769d46 160000 --- a/vircadia-web +++ b/vircadia-web @@ -1 +1 @@ -Subproject commit d2e383cab811018422433fe0d8f224e53e0506cf +Subproject commit 0afaa769d46683d461c9288aa31468f64cba0233