diff --git a/LICENSE b/LICENSE index 02fef6ca74..dcbbcee40c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ Copyright (c) 2013-2019, High Fidelity, Inc. Copyright (c) 2019-2021, Vircadia contributors. -Copyright (c) 2022-2023, Overte e.V. +Copyright (c) 2022-2024, Overte e.V. All rights reserved. https://overte.org diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 7e8c1afff3..c1854de446 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -97,7 +97,7 @@ int main(int argc, const char* argv[]) { ); QCommandLineOption protocolVersionOption( "protocolVersion", - "Writes the protocol version base64 signature to a file?", + "Writes the protocol version base64 signature to a file", "path" ); QCommandLineOption noUpdaterOption( @@ -275,6 +275,14 @@ int main(int argc, const char* argv[]) { "abortAfterInit", "Debug option. Aborts after initialization, right before the program starts running the event loop." ); + QCommandLineOption getProtocolVersionHashOption( + "getProtocolVersionHash", + "Debug option. Returns the network protocol version MD5 hash." + ); + QCommandLineOption getProtocolVersionDataOption( + "getProtocolVersionData", + "Debug option. Returns the network protocol detailed data in JSON." + ); // "--qmljsdebugger", which appears in output from "--help-all". // Those below don't seem to be optional. @@ -321,6 +329,8 @@ int main(int argc, const char* argv[]) { parser.addOption(abortAfterStartupOption); parser.addOption(abortAfterInitOption); parser.addOption(getPluginsOption); + parser.addOption(getProtocolVersionHashOption); + parser.addOption(getProtocolVersionDataOption); QString applicationPath; @@ -455,6 +465,34 @@ int main(int argc, const char* argv[]) { return 1; } } + if (parser.isSet(getProtocolVersionHashOption)) { + std::cout << protocolVersionsSignatureHex().toStdString() << std::endl; + return 0; + } + if (parser.isSet(getProtocolVersionDataOption)) { + auto protocolMap = protocolVersionsSignatureMap(); + QMetaEnum packetMetaEnum = QMetaEnum::fromType(); + + QJsonArray packetTypesList; + auto keyList = protocolMap.keys(); + std::sort(keyList.begin(), keyList.end()); // Sort by numeric value + + for(const auto packet : keyList) { + QJsonObject data; + int intValue = static_cast(packet); + QString keyName = packetMetaEnum.valueToKey(intValue); + + data["name"] = keyName; + data["value"] = intValue; + data["version"] = versionForPacketType(packet); + + packetTypesList.append(data); + } + + std::cout << QJsonDocument(packetTypesList).toJson().toStdString() << std::endl; + return 0; + } + static const QString APPLICATION_CONFIG_FILENAME = "config.json"; QDir applicationDir(applicationPath); diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index e561bfe21e..2aafc5501e 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -130,6 +130,10 @@ void sendWrongProtocolVersionsSignature(bool sendWrongVersion) { static QByteArray protocolVersionSignature; static QString protocolVersionSignatureBase64; +static QString protocolVersionSignatureHex; +static QMap protocolVersionMap; + + static void ensureProtocolVersionsSignature() { static std::once_flag once; std::call_once(once, [&] { @@ -139,12 +143,14 @@ static void ensureProtocolVersionsSignature() { stream << numberOfProtocols; for (uint8_t packetType = 0; packetType < numberOfProtocols; packetType++) { uint8_t packetTypeVersion = static_cast(versionForPacketType(static_cast(packetType))); + protocolVersionMap[static_cast(packetType)] = packetTypeVersion; stream << packetTypeVersion; } QCryptographicHash hash(QCryptographicHash::Md5); hash.addData(buffer); protocolVersionSignature = hash.result(); protocolVersionSignatureBase64 = protocolVersionSignature.toBase64(); + protocolVersionSignatureHex = protocolVersionSignature.toHex(0); }); } QByteArray protocolVersionsSignature() { @@ -161,3 +167,13 @@ QString protocolVersionsSignatureBase64() { ensureProtocolVersionsSignature(); return protocolVersionSignatureBase64; } + +QString protocolVersionsSignatureHex() { + ensureProtocolVersionsSignature(); + return protocolVersionSignatureHex; +} + +QMap protocolVersionsSignatureMap() { + ensureProtocolVersionsSignature(); + return protocolVersionMap; +} \ No newline at end of file diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 3badfdf418..bc36d9444a 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -31,8 +31,15 @@ class PacketTypeEnum { Q_GADGET Q_ENUMS(Value) public: - // If adding a new packet packetType, you can replace one marked usable or add at the end. - // This enum must hold 256 or fewer packet types (so the value is <= 255) since it is statically typed as a uint8_t + + /** + * @brief Packet type identifier + * + * Identifies the type of packet being sent. + * + * @note If adding a new packet packetType, you can replace one marked usable or add at the end. + * @note This enum must hold 256 or fewer packet types (so the value is <= 255) since it is statically typed as a uint8_t + */ enum class Value : uint8_t { Unknown, DomainConnectRequestPending, @@ -143,6 +150,8 @@ public: NUM_PACKET_TYPE }; + Q_ENUM(Value) + const static QHash getReplicatedPacketMapping() { const static QHash REPLICATED_PACKET_MAPPING { { PacketTypeEnum::Value::MicrophoneAudioNoEcho, PacketTypeEnum::Value::ReplicatedMicrophoneAudioNoEcho }, @@ -219,10 +228,60 @@ const int NUM_BYTES_MD5_HASH = 16; // NOTE: There is a max limit of 255, hopefully we have a better way to manage this by then. typedef uint8_t PacketVersion; +/** + * @brief Returns the version number of the given packet type + * + * If the implementation of a packet type is modified in an incompatible way, the implementation + * of this function needs to be modified to return an incremented value. + * + * This is used to determine whether the protocol is compatible between client and server. + * + * @note Version is limited to a max of 255. + * + * @param packetType Type of packet + * @return PacketVersion Version + */ PacketVersion versionForPacketType(PacketType packetType); -QByteArray protocolVersionsSignature(); /// returns a unique signature for all the current protocols + +/** + * @brief Returns a unique signature for all the current protocols + * + * This computes a MD5 hash that expresses the state of the protocol's specification. The calculation + * is done in ensureProtocolVersionsSignature and accounts for the following: + * + * * Number of known packet types + * * versionForPacketType(type) for each packet type. + * + * There's no provision for backwards compatibility, anything that changes this calculation is a protocol break. + * + * @return QByteArray MD5 digest as a byte array + */ +QByteArray protocolVersionsSignature(); + +/*** + * @brief Returns a unique signature for all the current protocols + * + * Same as protocolVersionsSignature(), in base64. + */ QString protocolVersionsSignatureBase64(); +/*** + * @brief Returns a unique signature for all the current protocols + * + * Same as protocolVersionsSignature(), in hex; + */ +QString protocolVersionsSignatureHex(); + +/*** + * @brief Returns the data used to compute the protocol version + * + * The key is the packet type. The value is the version for that packet type. + * + * Used for aiding in development. + */ +QMap protocolVersionsSignatureMap(); + + #if (PR_BUILD || DEV_BUILD) void sendWrongProtocolVersionsSignature(bool sendWrongVersion); /// for debugging version negotiation #endif @@ -428,4 +487,5 @@ enum class AvatarQueryVersion : PacketVersion { ConicalFrustums = 22 }; + #endif // hifi_PacketHeaders_h diff --git a/scripts/communityScripts/armored-chat/armored_chat.qml b/scripts/communityScripts/armored-chat/armored_chat.qml index 13506c226a..ede60fca71 100644 --- a/scripts/communityScripts/armored-chat/armored_chat.qml +++ b/scripts/communityScripts/armored-chat/armored_chat.qml @@ -510,7 +510,7 @@ Rectangle { mess = mess.replace(arrow, "<"); var link = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g; - mess = mess.replace(link, (match) => {return "" + match + " "}); + mess = mess.replace(link, (match) => {return `` + match + ` `}); var newline = /\n/gi; mess = mess.replace(newline, "
"); diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index 2901f7c024..6d0cd6f96e 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -58,7 +58,7 @@ function runDefaultsTogether() { function runDefaultsSeparately() { for (var i in CONTOLLER_SCRIPTS) { if (CONTOLLER_SCRIPTS.hasOwnProperty(i)) { - print("loading " + CONTOLLER_SCRIPTS[j]); + print("loading " + CONTOLLER_SCRIPTS[i]); Script.load(CONTOLLER_SCRIPTS[i]); } } diff --git a/scripts/system/controllers/mouseLook.js b/scripts/system/controllers/mouseLook.js index e29a741865..c8e822646d 100644 --- a/scripts/system/controllers/mouseLook.js +++ b/scripts/system/controllers/mouseLook.js @@ -31,6 +31,7 @@ // Program ---- function onMouseLookChanged(newMouseLook) { + disableMouseLook(); mouseLookEnabled = newMouseLook; } @@ -38,8 +39,6 @@ // Toggle using the m key if (event.text.toLowerCase() === "m") { if (Camera.captureMouse) { - mouseLookActive = false; - Settings.setValue("mouselook-active", false); disableMouseLook(); } else { mouseLookActive = true; @@ -82,12 +81,16 @@ if (hmdActive) return; if (tablet.tabletShown) return; if (overlayActive) return; + if (!mouseLookEnabled) return; // Mouse look disabled via setting if (!mouseLookActive) return; // Mouse look disabled via the hotkey Camera.captureMouse = true; } function disableMouseLook() { + mouseLookActive = false; + Settings.setValue("mouselook-active", false); + Camera.captureMouse = false; } diff --git a/tools/dissectors/1-hfudt.lua b/tools/dissectors/1-hfudt.lua index 5a03331fc6..0a7937e980 100644 --- a/tools/dissectors/1-hfudt.lua +++ b/tools/dissectors/1-hfudt.lua @@ -1,4 +1,5 @@ print("Loading hfudt") +bit32 = require("bit32") -- create the HFUDT protocol p_hfudt = Proto("hfudt", "HFUDT Protocol") @@ -154,19 +155,55 @@ local packet_types = { [99] = "EntityQueryInitialResultsComplete", [100] = "BulkAvatarTraits", [101] = "AudioSoloRequest", - [102] = "BulkAvatarTraitsAck" + [102] = "BulkAvatarTraitsAck", + [103] = "StopInjector", + [104] = "AvatarZonePresence", + [105] = "WebRTCSignaling" } +-- PacketHeaders.h, getNonSourcedPackets() local unsourced_packet_types = { - ["DomainList"] = true, + ["DomainConnectRequestPending"] = true, + ["CreateAssignment"] = true, + ["RequestAssignment"] = true, + ["DomainServerRequireDTLS"] = true, ["DomainConnectRequest"] = true, - ["ICEPing"] = true, - ["ICEPingReply"] = true, + ["DomainList"] = true, + ["DomainConnectionDenied"] = true, + ["DomainServerPathQuery"] = true, + ["DomainServerPathResponse"] = true, + ["DomainServerAddedNode"] = true, ["DomainServerConnectionToken"] = true, ["DomainSettingsRequest"] = true, - ["ICEServerHeartbeatACK"] = true + ["OctreeDataFileRequest"] = true, + ["OctreeDataFileReply"] = true, + ["OctreeDataPersist"] = true, + ["DomainContentReplacementFromUrl"] = true, + ["DomainSettings"] = true, + ["ICEServerPeerInformation"] = true, + ["ICEServerQuery"] = true, + ["ICEServerHeartbeat"] = true, + ["ICEServerHeartbeatACK"] = true, + ["ICEPing"] = true, + ["ICEPingReply"] = true, + ["ICEServerHeartbeatDenied"] = true, + ["AssignmentClientStatus"] = true, + ["StopNode"] = true, + ["DomainServerRemovedNode"] = true, + ["UsernameFromIDReply"] = true, + ["OctreeFileReplacement"] = true, + ["ReplicatedMicrophoneAudioNoEcho"] = true, + ["ReplicatedMicrophoneAudioWithEcho"] = true, + ["ReplicatedInjectAudio"] = true, + ["ReplicatedSilentAudioFrame"] = true, + ["ReplicatedAvatarIdentity"] = true, + ["ReplicatedKillAvatar"] = true, + ["ReplicatedBulkAvatarData"] = true, + ["AvatarZonePresence"] = true, + ["WebRTCSignaling"] = true } +-- PacketHeaders.h, getNonVerifiedPackets() local nonverified_packet_types = { ["NodeJsonStats"] = true, ["EntityQuery"] = true, @@ -222,6 +259,7 @@ function p_hfudt.dissector(buf, pinfo, tree) type:append_text(" (".. control_types[shifted_type][1] .. ")") subtree:add(f_control_type_text, control_types[shifted_type][1]) + pinfo.cols.info:append(" [" .. control_types[shifted_type][1] .. "]") end if shifted_type == 0 then @@ -257,7 +295,7 @@ function p_hfudt.dissector(buf, pinfo, tree) -- read the obfuscation level local obfuscation_bits = bit32.band(0x03, bit32.rshift(first_word, 27)) subtree:add(f_obfuscation_level, obfuscation_bits) - + -- read the sequence number subtree:add(f_sequence_number, bit32.band(first_word, SEQUENCE_NUMBER_MASK)) @@ -300,10 +338,12 @@ function p_hfudt.dissector(buf, pinfo, tree) local packet_type = buf(payload_offset, 1):le_uint() local ptype = subtree:add_le(f_type, buf(payload_offset, 1)) local packet_type_text = packet_types[packet_type] + if packet_type_text ~= nil then subtree:add(f_type_text, packet_type_text) -- if we know this packet type then add the name ptype:append_text(" (".. packet_type_text .. ")") + pinfo.cols.info:append(" [" .. packet_type_text .. "]") end -- read the version @@ -431,12 +471,12 @@ function deobfuscate(message_bit, buf, level) else return end - + local start = 4 if message_bit == 1 then local start = 12 end - + local p = 0 for i = start, buf:len() - 1 do out:set_index(i, bit.bxor(buf(i, 1):le_uint(), key:get_index(7 - (p % 8))) ) diff --git a/tools/dissectors/2-hf-audio.lua b/tools/dissectors/2-hf-audio.lua index fa4d50fab1..1e6b0b6431 100644 --- a/tools/dissectors/2-hf-audio.lua +++ b/tools/dissectors/2-hf-audio.lua @@ -1,5 +1,5 @@ print("Loading hf-audio") - +bit32 = require("bit32") -- create the audio protocol p_hf_audio = Proto("hf-audio", "HF Audio Protocol") diff --git a/tools/dissectors/3-hf-avatar.lua b/tools/dissectors/3-hf-avatar.lua index 9b8567c55f..8104649b0d 100644 --- a/tools/dissectors/3-hf-avatar.lua +++ b/tools/dissectors/3-hf-avatar.lua @@ -1,4 +1,5 @@ print("Loading hf-avatar") +bit32 = require("bit32") -- create the avatar protocol p_hf_avatar = Proto("hf-avatar", "HF Avatar Protocol") diff --git a/tools/dissectors/4-hf-entity.lua b/tools/dissectors/4-hf-entity.lua index 568eb5baa3..7de5eeee4d 100644 --- a/tools/dissectors/4-hf-entity.lua +++ b/tools/dissectors/4-hf-entity.lua @@ -1,4 +1,5 @@ print("Loading hf-entity") +bit32 = require("bit32") -- create the entity protocol p_hf_entity = Proto("hf-entity", "HF Entity Protocol") diff --git a/tools/dissectors/5-hf-domain.lua b/tools/dissectors/5-hf-domain.lua index 093026bc92..e2f9da4d9b 100644 --- a/tools/dissectors/5-hf-domain.lua +++ b/tools/dissectors/5-hf-domain.lua @@ -1,4 +1,6 @@ -- create the domain protocol +print("Loading hf-domain") +bit32 = require("bit32") p_hf_domain = Proto("hf-domain", "HF Domain Protocol") -- domain packet fields diff --git a/tools/dissectors/README.md b/tools/dissectors/README.md index 1e618a7b4c..3b391f60c0 100644 --- a/tools/dissectors/README.md +++ b/tools/dissectors/README.md @@ -1,14 +1,73 @@ -High Fidelity Wireshark Plugins ---------------------------------- +# High Fidelity Wireshark Plugins -Install wireshark 2.4.6 or higher. -Copy these lua files into c:\Users\username\AppData\Roaming\Wireshark\Plugins +## Installation -After a capture any detected High Fidelity Packets should be easily identifiable by one of the following protocols -* HF-AUDIO - Streaming audio packets -* HF-AVATAR - Streaming avatar mixer packets -* HF-ENTITY - Entity server traffic -* HF-DOMAIN - Domain server traffic -* HFUDT - All other UDP traffic +* Install wireshark 2.4.6 or higher. +* Copy these lua files into `c:\Users\username\AppData\Roaming\Wireshark\Plugins` on Windows, or `$HOME/.local/lib/wireshark/plugins` on Linux. + +## Lua version + +This is a Lua plugin, which requires the bit32 module to be installed. You can find the Lua version wireshark uses in the About dialog, eg: + + Version 4.2.5 (Git commit 798e06a0f7be). + + Compiled (64-bit) using GCC 14.1.1 20240507 (Red Hat 14.1.1-1), with GLib + 2.80.2, with Qt 6.7.0, with libpcap, with POSIX capabilities (Linux), with libnl + 3, with zlib 1.3.0.zlib-ng, with PCRE2, with Lua 5.1.5, with GnuTLS 3.8.5 and + +This indicates Lua 5.1 is used (see on the last line) + + +## Requirements + +On Fedora 40: + +* wireshark-devel +* lua5.1-bit32 + + +## Usage + +After a capture any detected Overte Packets should be easily identifiable by one of the following protocols + +* `HF-AUDIO` - Streaming audio packets +* `HF-AVATAR` - Streaming avatar mixer packets +* `HF-ENTITY` - Entity server traffic +* `HF-DOMAIN` - Domain server traffic +* `HFUDT` - All other UDP traffic + + + + +## Troubleshooting + +### attempt to index global 'bit32' (a nil value) + +`[Expert Info (Error/Undecoded): Lua Error: /home/dale/.local/lib/wireshark/plugins/1-hfudt.lua:207: attempt to index global 'bit32' (a nil value)]` + +See the installation requirements, you need to install the bit32 Lua module for the right Lua version. + +## Development hints + + +* Symlink files from the development tree to `$HOME/.local/lib/wireshark/plugins`, to have Wireshark work on the latest dissector code. +* Capture packets for later analysis in a PCAPNG file. +* Only save needed packets in the dump + +Decode on the commandline with: + + tshark -r packets.pcapng.gz -V + +Decode only the first packet: + + tshark -r packets.pcapng.gz -V -c 1 + +### Useful tshark arguments + +* `-x` hex dump +* `-c N` Only decode first N packets +* `-O hfudt,hf-domain,hf-entity,hf-avatar,hf-audio` Only dump Overte protocol data, skip dumping UDP/etc parts. +* `-V` decode protocols +* \ No newline at end of file