From 5ba1bda0c8327362657824903acd1d4e5a429cf2 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Wed, 30 Sep 2026 14:23:47 +1000 Subject: [PATCH] [common] network: define transport wire protocol --- common/CMakeLists.txt | 2 + common/include/common/network/protocol.h | 488 +++++++++++++++ common/include/common/network/wire.h | 97 +++ common/src/network/protocol.c | 730 +++++++++++++++++++++++ common/src/network/wire.c | 355 +++++++++++ idd/LGCommon/LGCommon.vcxproj | 4 + 6 files changed, 1676 insertions(+) create mode 100644 common/include/common/network/protocol.h create mode 100644 common/include/common/network/wire.h create mode 100644 common/src/network/protocol.c create mode 100644 common/src/network/wire.c diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index d2077195..5b312d79 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -45,6 +45,8 @@ set(COMMON_SOURCES src/cpuinfo.c src/debug.c src/ll.c + src/network/protocol.c + src/network/wire.c ) add_library(lg_common STATIC ${COMMON_SOURCES}) diff --git a/common/include/common/network/protocol.h b/common/include/common/network/protocol.h new file mode 100644 index 00000000..c7fec9df --- /dev/null +++ b/common/include/common/network/protocol.h @@ -0,0 +1,488 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _H_LG_COMMON_NETWORK_PROTOCOL_ +#define _H_LG_COMMON_NETWORK_PROTOCOL_ + +#include "common/network/wire.h" + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Encoded as the byte sequence "LGNW" by the little-endian wire writer. */ +#define LG_NET_WIRE_MAGIC UINT32_C(0x574E474C) +#define LG_NET_WIRE_VERSION_MAJOR 1U +#define LG_NET_WIRE_VERSION_MINOR 0U +#define LG_NET_ENVELOPE_WIRE_SIZE 64U +#define LG_NET_MAX_PAYLOAD_LENGTH (64U * 1024U * 1024U) +#define LG_NET_MESSAGE_VERSION_CURRENT 1U +#define LG_NET_MESSAGE_VERSION_INITIAL 1U +#define LG_NET_PROTOCOL_VERSION_CURRENT 1U +#define LG_NET_PROTOCOL_VERSION_MIN 1U +#define LG_NET_PROTOCOL_VERSION_MAX LG_NET_PROTOCOL_VERSION_CURRENT +#define LG_NET_MAX_CAPABILITY_RECORDS 64U +#define LG_NET_HELLO_NONCE_LENGTH 32U +#define LG_NET_PASSWORD_SALT_MAX_LENGTH 64U +#define LG_NET_PASSWORD_CHALLENGE_MAX_LENGTH 64U +#define LG_NET_PASSWORD_NONCE_MAX_LENGTH 64U +#define LG_NET_PASSWORD_PROOF_MAX_LENGTH 64U +/* 19527 is 0x4c47 (ASCII "LG") and is currently IANA-unassigned. */ +#define LG_NET_DEFAULT_PORT 19527U +#define LG_NET_ALPN "looking-glass/1" +#define LG_NET_ALPN_LENGTH (sizeof(LG_NET_ALPN) - 1U) + +#define LG_NET_CORE_VERSION_CURRENT 1U +#define LG_NET_CORE_VERSION_MIN 1U +#define LG_NET_CORE_VERSION_MAX LG_NET_CORE_VERSION_CURRENT +#define LG_NET_RECOVERY_VERSION_CURRENT 1U +#define LG_NET_RECOVERY_VERSION_MIN 1U +#define LG_NET_RECOVERY_VERSION_MAX LG_NET_RECOVERY_VERSION_CURRENT +#define LG_NET_VIDEO_VERSION_CURRENT 1U +#define LG_NET_VIDEO_VERSION_MIN 1U +#define LG_NET_VIDEO_VERSION_MAX LG_NET_VIDEO_VERSION_CURRENT +#define LG_NET_CURSOR_VERSION_CURRENT 1U +#define LG_NET_CURSOR_VERSION_MIN 1U +#define LG_NET_CURSOR_VERSION_MAX LG_NET_CURSOR_VERSION_CURRENT +#define LG_NET_INPUT_VERSION_CURRENT 1U +#define LG_NET_INPUT_VERSION_MIN 1U +#define LG_NET_INPUT_VERSION_MAX LG_NET_INPUT_VERSION_CURRENT +#define LG_NET_AUDIO_VERSION_CURRENT 1U +#define LG_NET_AUDIO_VERSION_MIN 1U +#define LG_NET_AUDIO_VERSION_MAX LG_NET_AUDIO_VERSION_CURRENT +#define LG_NET_CLIPBOARD_VERSION_CURRENT 1U +#define LG_NET_CLIPBOARD_VERSION_MIN 1U +#define LG_NET_CLIPBOARD_VERSION_MAX LG_NET_CLIPBOARD_VERSION_CURRENT +#define LG_NET_FILE_VERSION_CURRENT 1U +#define LG_NET_FILE_VERSION_MIN 1U +#define LG_NET_FILE_VERSION_MAX LG_NET_FILE_VERSION_CURRENT +/* Reserved now so a future USB implementation cannot collide with a service. */ +#define LG_NET_USB_VERSION_RESERVED 1U + +typedef uint16_t LGNetService; + +enum +{ + LG_NET_SERVICE_CORE = 1, + LG_NET_SERVICE_RECOVERY = 2, + LG_NET_SERVICE_VIDEO = 3, + LG_NET_SERVICE_CURSOR = 4, + LG_NET_SERVICE_INPUT = 5, + LG_NET_SERVICE_AUDIO = 6, + LG_NET_SERVICE_CLIPBOARD = 7, + LG_NET_SERVICE_FILE = 8, + LG_NET_SERVICE_USB = 9, +}; + +typedef uint16_t LGNetCoreMessage; + +enum +{ + LG_NET_CORE_MESSAGE_HELLO = 1, + LG_NET_CORE_MESSAGE_HELLO_ACK = 2, + LG_NET_CORE_MESSAGE_AUTH_CHALLENGE = 3, + LG_NET_CORE_MESSAGE_AUTH_RESPONSE = 4, + LG_NET_CORE_MESSAGE_AUTH_RESULT = 5, + LG_NET_CORE_MESSAGE_CAPABILITY = 6, + LG_NET_CORE_MESSAGE_CAPABILITIES_DONE = 7, + LG_NET_CORE_MESSAGE_SESSION_INFO = 8, + LG_NET_CORE_MESSAGE_STATUS = 9, + LG_NET_CORE_MESSAGE_PING = 10, + LG_NET_CORE_MESSAGE_PONG = 11, + LG_NET_CORE_MESSAGE_GOODBYE = 12, + LG_NET_CORE_MESSAGE_ERROR = 13, +}; + +typedef uint16_t LGNetRecoveryMessage; + +enum +{ + LG_NET_RECOVERY_MESSAGE_GET_INFO = 1, + LG_NET_RECOVERY_MESSAGE_INFO = 2, + LG_NET_RECOVERY_MESSAGE_REQUEST = 3, + LG_NET_RECOVERY_MESSAGE_STATUS = 4, +}; + +typedef uint16_t LGNetVideoMessage; + +enum +{ + LG_NET_VIDEO_MESSAGE_SUBSCRIBE = 1, + LG_NET_VIDEO_MESSAGE_UNSUBSCRIBE = 2, + LG_NET_VIDEO_MESSAGE_STREAM_CONFIG = 3, + LG_NET_VIDEO_MESSAGE_FRAME = 4, + LG_NET_VIDEO_MESSAGE_FRAME_FRAGMENT = 5, + LG_NET_VIDEO_MESSAGE_KEYFRAME_REQUEST = 6, + LG_NET_VIDEO_MESSAGE_SCHEDULE = 7, + LG_NET_VIDEO_MESSAGE_FEEDBACK = 8, + LG_NET_VIDEO_MESSAGE_STATUS = 9, +}; + +typedef uint16_t LGNetCursorMessage; + +enum +{ + LG_NET_CURSOR_MESSAGE_STATE = 1, + LG_NET_CURSOR_MESSAGE_POSITION = 2, + LG_NET_CURSOR_MESSAGE_SHAPE = 3, + LG_NET_CURSOR_MESSAGE_TRANSFORM = 4, + LG_NET_CURSOR_MESSAGE_STATUS = 5, +}; + +typedef uint16_t LGNetInputMessage; + +enum +{ + LG_NET_INPUT_MESSAGE_CLAIM = 1, + LG_NET_INPUT_MESSAGE_CLAIM_RESULT = 2, + LG_NET_INPUT_MESSAGE_KEEPALIVE = 3, + LG_NET_INPUT_MESSAGE_RELEASE = 4, + LG_NET_INPUT_MESSAGE_RESET = 5, + LG_NET_INPUT_MESSAGE_MOUSE_RELATIVE = 6, + LG_NET_INPUT_MESSAGE_MOUSE_ABSOLUTE = 7, + LG_NET_INPUT_MESSAGE_KEYBOARD = 8, + LG_NET_INPUT_MESSAGE_STATUS = 9, + LG_NET_INPUT_MESSAGE_KEYBOARD_LEDS = 10, +}; + +typedef uint16_t LGNetAudioMessage; + +enum +{ + LG_NET_AUDIO_MESSAGE_SUBSCRIBE = 1, + LG_NET_AUDIO_MESSAGE_STATUS = 2, + LG_NET_AUDIO_MESSAGE_PLAYBACK_START = 3, + LG_NET_AUDIO_MESSAGE_PLAYBACK_STOP = 4, + LG_NET_AUDIO_MESSAGE_PLAYBACK_DATA = 5, + LG_NET_AUDIO_MESSAGE_PLAYBACK_VOLUME = 6, + LG_NET_AUDIO_MESSAGE_PLAYBACK_MUTE = 7, + LG_NET_AUDIO_MESSAGE_CAPTURE_START = 8, + LG_NET_AUDIO_MESSAGE_CAPTURE_STOP = 9, + LG_NET_AUDIO_MESSAGE_CAPTURE_DATA = 10, + LG_NET_AUDIO_MESSAGE_CAPTURE_VOLUME = 11, + LG_NET_AUDIO_MESSAGE_CAPTURE_MUTE = 12, + LG_NET_AUDIO_MESSAGE_CLOCK_FEEDBACK = 13, + LG_NET_AUDIO_MESSAGE_KEEPALIVE = 14, + LG_NET_AUDIO_MESSAGE_RELEASE = 15, + LG_NET_AUDIO_MESSAGE_STATE_BARRIER = 16, + LG_NET_AUDIO_MESSAGE_STATE_ACK = 17, +}; + +typedef uint16_t LGNetClipboardMessage; + +enum +{ + LG_NET_CLIPBOARD_MESSAGE_CLAIM = 1, + LG_NET_CLIPBOARD_MESSAGE_CLAIM_RESULT = 2, + LG_NET_CLIPBOARD_MESSAGE_KEEPALIVE = 3, + LG_NET_CLIPBOARD_MESSAGE_RELEASE = 4, + LG_NET_CLIPBOARD_MESSAGE_OFFER = 5, + LG_NET_CLIPBOARD_MESSAGE_CLEAR = 6, + LG_NET_CLIPBOARD_MESSAGE_REQUEST = 7, + LG_NET_CLIPBOARD_MESSAGE_DATA_BEGIN = 8, + LG_NET_CLIPBOARD_MESSAGE_DATA_CHUNK = 9, + LG_NET_CLIPBOARD_MESSAGE_DATA_END = 10, + LG_NET_CLIPBOARD_MESSAGE_DATA_READY = 11, + LG_NET_CLIPBOARD_MESSAGE_CANCEL = 12, + LG_NET_CLIPBOARD_MESSAGE_STATUS = 13, +}; + +typedef uint16_t LGNetFileMessage; + +enum +{ + LG_NET_FILE_MESSAGE_OFFER = 1, + LG_NET_FILE_MESSAGE_ACQUIRE = 2, + LG_NET_FILE_MESSAGE_ACQUIRED = 3, + LG_NET_FILE_MESSAGE_RELEASE = 4, + LG_NET_FILE_MESSAGE_REQUEST = 5, + LG_NET_FILE_MESSAGE_ENTRY = 6, + LG_NET_FILE_MESSAGE_DATA_BEGIN = 7, + LG_NET_FILE_MESSAGE_DATA_CHUNK = 8, + LG_NET_FILE_MESSAGE_DATA_END = 9, + LG_NET_FILE_MESSAGE_DATA_READY = 10, + LG_NET_FILE_MESSAGE_CANCEL = 11, + LG_NET_FILE_MESSAGE_STATUS = 12, +}; + +/* Message IDs are reserved for the future USB service; their payloads are not + * defined by this protocol version. */ +typedef uint16_t LGNetUSBMessage; + +enum +{ + LG_NET_USB_MESSAGE_CAPABILITIES = 1, + LG_NET_USB_MESSAGE_DEVICE = 2, + LG_NET_USB_MESSAGE_CLAIM = 3, + LG_NET_USB_MESSAGE_CONTROL = 4, + LG_NET_USB_MESSAGE_ENDPOINT = 5, + LG_NET_USB_MESSAGE_CANCEL = 6, + LG_NET_USB_MESSAGE_RESET = 7, + LG_NET_USB_MESSAGE_STATUS = 8, +}; + +typedef uint32_t LGNetEnvelopeFlags; + +enum +{ + LG_NET_ENVELOPE_RESPONSE = 1U << 0, + LG_NET_ENVELOPE_FINAL = 1U << 1, + LG_NET_ENVELOPE_RELIABLE = 1U << 2, + LG_NET_ENVELOPE_DATAGRAM = 1U << 3, + LG_NET_ENVELOPE_ACK_REQUIRED = 1U << 4, + LG_NET_ENVELOPE_ERROR = 1U << 5, + LG_NET_ENVELOPE_EARLY_DATA = 1U << 6, +}; + +typedef struct LGNetEnvelope +{ + uint16_t wireMajor; + uint16_t wireMinor; + uint16_t headerSize; + LGNetService service; + uint16_t messageType; + uint16_t serviceVersion; + uint16_t messageVersion; + LGNetEnvelopeFlags flags; + uint32_t payloadLength; + uint64_t sessionEpoch; + uint64_t componentEpoch; + uint64_t sequence; + uint64_t requestID; +} +LGNetEnvelope; + +typedef struct LGNetPacketView +{ + LGNetEnvelope envelope; + const uint8_t * payload; + size_t payloadLength; + size_t wireSize; +} +LGNetPacketView; + +typedef uint16_t LGNetRole; + +enum +{ + LG_NET_ROLE_CLIENT = 1, + LG_NET_ROLE_SERVER = 2, +}; + +typedef uint16_t LGNetAuthMode; + +enum +{ + LG_NET_AUTH_NONE = 0, + LG_NET_AUTH_PASSWORD = 1, +}; + +#define LG_NET_AUTH_MODE_FLAG(mode) (UINT32_C(1) << (mode)) +#define LG_NET_AUTH_MODE_DEFAULT LG_NET_AUTH_NONE +#define LG_NET_AUTH_MODES_DEFAULT LG_NET_AUTH_MODE_FLAG(LG_NET_AUTH_NONE) +#define LG_NET_AUTH_MODES_KNOWN \ + (LG_NET_AUTH_MODE_FLAG(LG_NET_AUTH_NONE) | \ + LG_NET_AUTH_MODE_FLAG(LG_NET_AUTH_PASSWORD)) + +typedef struct LGNetHello +{ + LGNetRole role; + uint16_t protocolMin; + uint16_t protocolMax; + uint32_t authModes; + uint32_t maxPayloadLength; + uint32_t maxDatagramPayload; + uint32_t capabilityCount; + uint8_t nonce[LG_NET_HELLO_NONCE_LENGTH]; +} +LGNetHello; + +typedef uint32_t LGNetHelloAckFlags; + +enum +{ + LG_NET_HELLO_ACK_RESUMED = 1U << 0, +}; + +typedef struct LGNetHelloAck +{ + uint16_t protocolVersion; + LGNetAuthMode authMode; + LGNetHelloAckFlags flags; + uint32_t maxPayloadLength; + uint32_t maxDatagramPayload; + uint32_t capabilityCount; + uint64_t sessionEpoch; + uint8_t nonce[LG_NET_HELLO_NONCE_LENGTH]; +} +LGNetHelloAck; + +#define LG_NET_HELLO_WIRE_SIZE 56U +#define LG_NET_HELLO_ACK_WIRE_SIZE 64U + +typedef uint16_t LGNetCapabilityFlags; + +enum +{ + LG_NET_CAPABILITY_REQUIRED = 1U << 0, + LG_NET_CAPABILITY_SELECTED = 1U << 1, + LG_NET_CAPABILITY_RELIABLE = 1U << 2, + LG_NET_CAPABILITY_DATAGRAM = 1U << 3, + LG_NET_CAPABILITY_BULK = 1U << 4, + LG_NET_CAPABILITY_MULTI_CLIENT = 1U << 5, +}; + +typedef struct LGNetCapability +{ + LGNetService service; + uint16_t versionMin; + uint16_t versionMax; + LGNetCapabilityFlags flags; + uint64_t features; + uint32_t maxPayloadLength; + uint32_t maxDatagramPayload; + uint16_t maxBidirectionalStreams; + uint16_t maxUnidirectionalStreams; +} +LGNetCapability; + +#define LG_NET_CAPABILITY_WIRE_SIZE 32U + +/* Password proofs are carried by this library but derived by the application. + * A password must never be sent on the wire. The negotiated algorithm defines + * the proof and transcript binding; cryptography is outside this wire codec. */ +typedef uint16_t LGNetPasswordAlgorithm; + +enum +{ + LG_NET_PASSWORD_PBKDF2_HMAC_SHA256 = 1, +}; + +typedef struct LGNetPasswordChallenge +{ + LGNetPasswordAlgorithm algorithm; + uint32_t rounds; + uint16_t saltLength; + uint16_t challengeLength; + uint8_t salt [LG_NET_PASSWORD_SALT_MAX_LENGTH]; + uint8_t challenge[LG_NET_PASSWORD_CHALLENGE_MAX_LENGTH]; +} +LGNetPasswordChallenge; + +typedef struct LGNetPasswordResponse +{ + LGNetPasswordAlgorithm algorithm; + uint16_t nonceLength; + uint16_t proofLength; + uint8_t nonce[LG_NET_PASSWORD_NONCE_MAX_LENGTH]; + uint8_t proof[LG_NET_PASSWORD_PROOF_MAX_LENGTH]; +} +LGNetPasswordResponse; + +typedef uint16_t LGNetAuthResultCode; + +enum +{ + LG_NET_AUTH_RESULT_ACCEPTED = 1, + LG_NET_AUTH_RESULT_REJECTED = 2, + LG_NET_AUTH_RESULT_UNSUPPORTED = 3, + LG_NET_AUTH_RESULT_ERROR = 4, +}; + +typedef struct LGNetAuthResult +{ + LGNetAuthResultCode result; + uint16_t proofLength; + uint32_t retryAfterMs; + uint8_t proof[LG_NET_PASSWORD_PROOF_MAX_LENGTH]; +} +LGNetAuthResult; + +#define LG_NET_PASSWORD_CHALLENGE_HEADER_SIZE 16U +#define LG_NET_PASSWORD_RESPONSE_HEADER_SIZE 12U +#define LG_NET_AUTH_RESULT_HEADER_SIZE 12U + +void lgNetEnvelopeInit(LGNetEnvelope * envelope, LGNetService service, + uint16_t messageType, uint16_t serviceVersion, + uint16_t messageVersion); +bool lgNetEnvelopeEncode(void * data, size_t size, + const LGNetEnvelope * envelope); +LGNetParseResult lgNetEnvelopeDecode(LGNetEnvelope * envelope, + const void * data, size_t size); +bool lgNetPacketSize(const LGNetEnvelope * envelope, size_t * size); +bool lgNetPacketEncode(void * data, size_t size, + const LGNetEnvelope * envelope, const void * payload); +LGNetParseResult lgNetPacketDecode(LGNetPacketView * packet, + const void * data, size_t size); + +bool lgNetServiceKnown(LGNetService service); +bool lgNetMessageKnown(LGNetService service, uint16_t messageType); + +void lgNetHelloInit(LGNetHello * hello, LGNetRole role); +bool lgNetHelloValid(const LGNetHello * hello); +bool lgNetHelloEncode(void * data, size_t size, const LGNetHello * hello); +LGNetParseResult lgNetHelloDecode(LGNetHello * hello, + const void * data, size_t size); + +void lgNetHelloAckInit(LGNetHelloAck * hello, uint64_t sessionEpoch); +bool lgNetHelloAckValid(const LGNetHelloAck * hello); +bool lgNetHelloAckEncode( + void * data, size_t size, const LGNetHelloAck * hello); +LGNetParseResult lgNetHelloAckDecode(LGNetHelloAck * hello, + const void * data, size_t size); + +bool lgNetCapabilityValid(const LGNetCapability * capability); +bool lgNetCapabilityEncode(void * data, size_t size, + const LGNetCapability * capability); +LGNetParseResult lgNetCapabilityDecode(LGNetCapability * capability, + const void * data, size_t size); + +size_t lgNetPasswordChallengeSize( + const LGNetPasswordChallenge * challenge); +bool lgNetPasswordChallengeValid( + const LGNetPasswordChallenge * challenge); +bool lgNetPasswordChallengeEncode(void * data, size_t size, + const LGNetPasswordChallenge * challenge); +LGNetParseResult lgNetPasswordChallengeDecode( + LGNetPasswordChallenge * challenge, const void * data, size_t size); + +size_t lgNetPasswordResponseSize(const LGNetPasswordResponse * response); +bool lgNetPasswordResponseValid(const LGNetPasswordResponse * response); +bool lgNetPasswordResponseEncode(void * data, size_t size, + const LGNetPasswordResponse * response); +LGNetParseResult lgNetPasswordResponseDecode( + LGNetPasswordResponse * response, const void * data, size_t size); + +size_t lgNetAuthResultSize(const LGNetAuthResult * result); +bool lgNetAuthResultValid(const LGNetAuthResult * result); +bool lgNetAuthResultEncode( + void * data, size_t size, const LGNetAuthResult * result); +LGNetParseResult lgNetAuthResultDecode(LGNetAuthResult * result, + const void * data, size_t size); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/common/include/common/network/wire.h b/common/include/common/network/wire.h new file mode 100644 index 00000000..dc965aa9 --- /dev/null +++ b/common/include/common/network/wire.h @@ -0,0 +1,97 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _H_LG_COMMON_NETWORK_WIRE_ +#define _H_LG_COMMON_NETWORK_WIRE_ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum LGNetParseResult +{ + LG_NET_PARSE_OK, + LG_NET_PARSE_TRUNCATED, + LG_NET_PARSE_INVALID_MAGIC, + LG_NET_PARSE_INVALID_VERSION, + LG_NET_PARSE_INVALID_HEADER, + LG_NET_PARSE_INVALID_LENGTH, + LG_NET_PARSE_INVALID_VALUE, +} +LGNetParseResult; + +typedef struct LGNetWriter +{ + uint8_t * data; + size_t capacity; + size_t offset; + bool failed; +} +LGNetWriter; + +typedef struct LGNetReader +{ + const uint8_t * data; + size_t size; + size_t offset; + bool failed; +} +LGNetReader; + +void lgNetWriterInit(LGNetWriter * writer, void * data, size_t capacity); +size_t lgNetWriterSize(const LGNetWriter * writer); +size_t lgNetWriterRemaining(const LGNetWriter * writer); +bool lgNetWriterValid(const LGNetWriter * writer); +bool lgNetWriterU8(LGNetWriter * writer, uint8_t value); +bool lgNetWriterU16(LGNetWriter * writer, uint16_t value); +bool lgNetWriterU32(LGNetWriter * writer, uint32_t value); +bool lgNetWriterU64(LGNetWriter * writer, uint64_t value); +bool lgNetWriterI32(LGNetWriter * writer, int32_t value); +bool lgNetWriterI64(LGNetWriter * writer, int64_t value); +bool lgNetWriterBytes( + LGNetWriter * writer, const void * data, size_t size); +bool lgNetWriterZero(LGNetWriter * writer, size_t size); + +void lgNetReaderInit( + LGNetReader * reader, const void * data, size_t size); +size_t lgNetReaderConsumed(const LGNetReader * reader); +size_t lgNetReaderRemaining(const LGNetReader * reader); +bool lgNetReaderValid(const LGNetReader * reader); +bool lgNetReaderU8(LGNetReader * reader, uint8_t * value); +bool lgNetReaderU16(LGNetReader * reader, uint16_t * value); +bool lgNetReaderU32(LGNetReader * reader, uint32_t * value); +bool lgNetReaderU64(LGNetReader * reader, uint64_t * value); +bool lgNetReaderI32(LGNetReader * reader, int32_t * value); +bool lgNetReaderI64(LGNetReader * reader, int64_t * value); +bool lgNetReaderBytes(LGNetReader * reader, void * data, size_t size); +bool lgNetReaderView( + LGNetReader * reader, const uint8_t ** data, size_t size); +bool lgNetReaderSkip(LGNetReader * reader, size_t size); +bool lgNetReaderZero(LGNetReader * reader, size_t size); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/common/src/network/protocol.c b/common/src/network/protocol.c new file mode 100644 index 00000000..ba3cee62 --- /dev/null +++ b/common/src/network/protocol.c @@ -0,0 +1,730 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "common/network/protocol.h" + +#include +#include + +static const LGNetEnvelopeFlags ENVELOPE_FLAGS = + LG_NET_ENVELOPE_RESPONSE | + LG_NET_ENVELOPE_FINAL | + LG_NET_ENVELOPE_RELIABLE | + LG_NET_ENVELOPE_DATAGRAM | + LG_NET_ENVELOPE_ACK_REQUIRED | + LG_NET_ENVELOPE_ERROR | + LG_NET_ENVELOPE_EARLY_DATA; + +static const LGNetCapabilityFlags CAPABILITY_FLAGS = + LG_NET_CAPABILITY_REQUIRED | + LG_NET_CAPABILITY_SELECTED | + LG_NET_CAPABILITY_RELIABLE | + LG_NET_CAPABILITY_DATAGRAM | + LG_NET_CAPABILITY_BULK | + LG_NET_CAPABILITY_MULTI_CLIENT; + +static bool envelopeValid(const LGNetEnvelope * envelope, bool encode) +{ + if (!envelope || envelope->wireMajor != LG_NET_WIRE_VERSION_MAJOR || + envelope->headerSize < LG_NET_ENVELOPE_WIRE_SIZE || + (encode && envelope->wireMinor != LG_NET_WIRE_VERSION_MINOR) || + (encode && envelope->headerSize != LG_NET_ENVELOPE_WIRE_SIZE) || + !envelope->service || !envelope->messageType || + !envelope->serviceVersion || !envelope->messageVersion || + envelope->payloadLength > LG_NET_MAX_PAYLOAD_LENGTH) + return false; + + return true; +} + +static bool authModeKnown(LGNetAuthMode mode) +{ + return mode == LG_NET_AUTH_NONE || mode == LG_NET_AUTH_PASSWORD; +} + +static bool passwordAlgorithmKnown(LGNetPasswordAlgorithm algorithm) +{ + return algorithm == LG_NET_PASSWORD_PBKDF2_HMAC_SHA256; +} + +static LGNetParseResult fixedDecodeResult( + const LGNetReader * reader, size_t expected, size_t actual) +{ + if (!reader || !lgNetReaderValid(reader)) + return LG_NET_PARSE_INVALID_VALUE; + if (actual != expected || lgNetReaderConsumed(reader) != expected) + return LG_NET_PARSE_INVALID_LENGTH; + return LG_NET_PARSE_OK; +} + +void lgNetEnvelopeInit(LGNetEnvelope * envelope, LGNetService service, + uint16_t messageType, uint16_t serviceVersion, + uint16_t messageVersion) +{ + if (!envelope) + return; + + memset(envelope, 0, sizeof(*envelope)); + envelope->wireMajor = LG_NET_WIRE_VERSION_MAJOR; + envelope->wireMinor = LG_NET_WIRE_VERSION_MINOR; + envelope->headerSize = LG_NET_ENVELOPE_WIRE_SIZE; + envelope->service = service; + envelope->messageType = messageType; + envelope->serviceVersion = serviceVersion; + envelope->messageVersion = messageVersion; +} + +bool lgNetEnvelopeEncode(void * data, size_t size, + const LGNetEnvelope * envelope) +{ + if (!data || size < LG_NET_ENVELOPE_WIRE_SIZE || + !envelopeValid(envelope, true) || + envelope->flags & ~ENVELOPE_FLAGS) + return false; + + LGNetWriter writer; + lgNetWriterInit(&writer, data, size); + return + lgNetWriterU32(&writer, LG_NET_WIRE_MAGIC) && + lgNetWriterU16(&writer, envelope->wireMajor) && + lgNetWriterU16(&writer, envelope->wireMinor) && + lgNetWriterU16(&writer, envelope->headerSize) && + lgNetWriterU16(&writer, envelope->service) && + lgNetWriterU16(&writer, envelope->messageType) && + lgNetWriterU16(&writer, envelope->serviceVersion) && + lgNetWriterU16(&writer, envelope->messageVersion) && + lgNetWriterU16(&writer, 0) && + lgNetWriterU32(&writer, envelope->flags) && + lgNetWriterU32(&writer, envelope->payloadLength) && + lgNetWriterU32(&writer, 0) && + lgNetWriterU64(&writer, envelope->sessionEpoch) && + lgNetWriterU64(&writer, envelope->componentEpoch) && + lgNetWriterU64(&writer, envelope->sequence) && + lgNetWriterU64(&writer, envelope->requestID) && + lgNetWriterSize(&writer) == LG_NET_ENVELOPE_WIRE_SIZE; +} + +LGNetParseResult lgNetEnvelopeDecode(LGNetEnvelope * envelope, + const void * data, size_t size) +{ + if (!envelope || !data) + return LG_NET_PARSE_INVALID_VALUE; + if (size < LG_NET_ENVELOPE_WIRE_SIZE) + return LG_NET_PARSE_TRUNCATED; + + LGNetEnvelope decoded; + uint32_t magic; + LGNetReader reader; + memset(&decoded, 0, sizeof(decoded)); + lgNetReaderInit(&reader, data, LG_NET_ENVELOPE_WIRE_SIZE); + if (!lgNetReaderU32(&reader, &magic) || + !lgNetReaderU16(&reader, &decoded.wireMajor) || + !lgNetReaderU16(&reader, &decoded.wireMinor) || + !lgNetReaderU16(&reader, &decoded.headerSize) || + !lgNetReaderU16(&reader, &decoded.service) || + !lgNetReaderU16(&reader, &decoded.messageType) || + !lgNetReaderU16(&reader, &decoded.serviceVersion) || + !lgNetReaderU16(&reader, &decoded.messageVersion) || + !lgNetReaderZero(&reader, 2) || + !lgNetReaderU32(&reader, &decoded.flags) || + !lgNetReaderU32(&reader, &decoded.payloadLength) || + !lgNetReaderZero(&reader, 4) || + !lgNetReaderU64(&reader, &decoded.sessionEpoch) || + !lgNetReaderU64(&reader, &decoded.componentEpoch) || + !lgNetReaderU64(&reader, &decoded.sequence) || + !lgNetReaderU64(&reader, &decoded.requestID)) + return LG_NET_PARSE_INVALID_HEADER; + + if (magic != LG_NET_WIRE_MAGIC) + return LG_NET_PARSE_INVALID_MAGIC; + if (decoded.wireMajor != LG_NET_WIRE_VERSION_MAJOR) + return LG_NET_PARSE_INVALID_VERSION; + if (decoded.headerSize < LG_NET_ENVELOPE_WIRE_SIZE) + return LG_NET_PARSE_INVALID_HEADER; + if (decoded.headerSize > size) + return LG_NET_PARSE_TRUNCATED; + if (!envelopeValid(&decoded, false) || decoded.flags & ~ENVELOPE_FLAGS) + return LG_NET_PARSE_INVALID_VALUE; + + *envelope = decoded; + return LG_NET_PARSE_OK; +} + +bool lgNetPacketSize(const LGNetEnvelope * envelope, size_t * size) +{ + if (!size || !envelopeValid(envelope, false) || + envelope->headerSize > SIZE_MAX - envelope->payloadLength) + return false; + + *size = (size_t)envelope->headerSize + envelope->payloadLength; + return true; +} + +bool lgNetPacketEncode(void * data, size_t size, + const LGNetEnvelope * envelope, const void * payload) +{ + uint8_t header[LG_NET_ENVELOPE_WIRE_SIZE]; + size_t wireSize; + if (!data || !envelope || (envelope->payloadLength && !payload) || + !lgNetPacketSize(envelope, &wireSize) || wireSize > size || + !lgNetEnvelopeEncode(header, sizeof(header), envelope)) + return false; + + if (envelope->payloadLength) + memmove((uint8_t *)data + envelope->headerSize, payload, + envelope->payloadLength); + memcpy(data, header, sizeof(header)); + return true; +} + +LGNetParseResult lgNetPacketDecode(LGNetPacketView * packet, + const void * data, size_t size) +{ + if (!packet) + return LG_NET_PARSE_INVALID_VALUE; + + LGNetPacketView decoded; + memset(&decoded, 0, sizeof(decoded)); + const LGNetParseResult result = lgNetEnvelopeDecode( + &decoded.envelope, data, size); + if (result != LG_NET_PARSE_OK) + return result; + if (!lgNetPacketSize(&decoded.envelope, &decoded.wireSize)) + return LG_NET_PARSE_INVALID_LENGTH; + if (decoded.wireSize > size) + return LG_NET_PARSE_TRUNCATED; + + decoded.payload = (const uint8_t *)data + decoded.envelope.headerSize; + decoded.payloadLength = decoded.envelope.payloadLength; + *packet = decoded; + return LG_NET_PARSE_OK; +} + +bool lgNetServiceKnown(LGNetService service) +{ + return service >= LG_NET_SERVICE_CORE && service <= LG_NET_SERVICE_USB; +} + +bool lgNetMessageKnown(LGNetService service, uint16_t messageType) +{ + if (!messageType) + return false; + + switch (service) + { + case LG_NET_SERVICE_CORE: + return messageType <= LG_NET_CORE_MESSAGE_ERROR; + + case LG_NET_SERVICE_RECOVERY: + return messageType <= LG_NET_RECOVERY_MESSAGE_STATUS; + + case LG_NET_SERVICE_VIDEO: + return messageType <= LG_NET_VIDEO_MESSAGE_STATUS; + + case LG_NET_SERVICE_CURSOR: + return messageType <= LG_NET_CURSOR_MESSAGE_STATUS; + + case LG_NET_SERVICE_INPUT: + return messageType <= LG_NET_INPUT_MESSAGE_KEYBOARD_LEDS; + + case LG_NET_SERVICE_AUDIO: + return messageType <= LG_NET_AUDIO_MESSAGE_STATE_ACK; + + case LG_NET_SERVICE_CLIPBOARD: + return messageType <= LG_NET_CLIPBOARD_MESSAGE_STATUS; + + case LG_NET_SERVICE_FILE: + return messageType <= LG_NET_FILE_MESSAGE_STATUS; + + case LG_NET_SERVICE_USB: + return messageType <= LG_NET_USB_MESSAGE_STATUS; + } + + return false; +} + +void lgNetHelloInit(LGNetHello * hello, LGNetRole role) +{ + if (!hello) + return; + + memset(hello, 0, sizeof(*hello)); + hello->role = role; + hello->protocolMin = LG_NET_PROTOCOL_VERSION_MIN; + hello->protocolMax = LG_NET_PROTOCOL_VERSION_CURRENT; + hello->authModes = LG_NET_AUTH_MODES_DEFAULT; + hello->maxPayloadLength = LG_NET_MAX_PAYLOAD_LENGTH; +} + +bool lgNetHelloValid(const LGNetHello * hello) +{ + return hello && + (hello->role == LG_NET_ROLE_CLIENT || hello->role == LG_NET_ROLE_SERVER) && + hello->protocolMin && hello->protocolMin <= hello->protocolMax && + hello->authModes && + hello->maxPayloadLength && + hello->maxPayloadLength <= LG_NET_MAX_PAYLOAD_LENGTH && + hello->maxDatagramPayload <= hello->maxPayloadLength && + hello->capabilityCount <= LG_NET_MAX_CAPABILITY_RECORDS; +} + +bool lgNetHelloEncode(void * data, size_t size, const LGNetHello * hello) +{ + if (!data || size < LG_NET_HELLO_WIRE_SIZE || !lgNetHelloValid(hello)) + return false; + + LGNetWriter writer; + lgNetWriterInit(&writer, data, size); + return + lgNetWriterU16(&writer, hello->role) && + lgNetWriterU16(&writer, hello->protocolMin) && + lgNetWriterU16(&writer, hello->protocolMax) && + lgNetWriterU16(&writer, 0) && + lgNetWriterU32(&writer, hello->authModes) && + lgNetWriterU32(&writer, hello->maxPayloadLength) && + lgNetWriterU32(&writer, hello->maxDatagramPayload) && + lgNetWriterU32(&writer, hello->capabilityCount) && + lgNetWriterBytes(&writer, hello->nonce, sizeof(hello->nonce)) && + lgNetWriterSize(&writer) == LG_NET_HELLO_WIRE_SIZE; +} + +LGNetParseResult lgNetHelloDecode(LGNetHello * hello, + const void * data, size_t size) +{ + if (!hello || !data) + return LG_NET_PARSE_INVALID_VALUE; + if (size < LG_NET_HELLO_WIRE_SIZE) + return LG_NET_PARSE_TRUNCATED; + + LGNetHello decoded; + LGNetReader reader; + memset(&decoded, 0, sizeof(decoded)); + lgNetReaderInit(&reader, data, size); + if (!lgNetReaderU16(&reader, &decoded.role) || + !lgNetReaderU16(&reader, &decoded.protocolMin) || + !lgNetReaderU16(&reader, &decoded.protocolMax) || + !lgNetReaderZero(&reader, 2) || + !lgNetReaderU32(&reader, &decoded.authModes) || + !lgNetReaderU32(&reader, &decoded.maxPayloadLength) || + !lgNetReaderU32(&reader, &decoded.maxDatagramPayload) || + !lgNetReaderU32(&reader, &decoded.capabilityCount) || + !lgNetReaderBytes(&reader, decoded.nonce, + sizeof(decoded.nonce))) + return LG_NET_PARSE_INVALID_VALUE; + + const LGNetParseResult result = fixedDecodeResult( + &reader, LG_NET_HELLO_WIRE_SIZE, size); + if (result != LG_NET_PARSE_OK) + return result; + if (!lgNetHelloValid(&decoded)) + return LG_NET_PARSE_INVALID_VALUE; + + *hello = decoded; + return LG_NET_PARSE_OK; +} + +void lgNetHelloAckInit(LGNetHelloAck * hello, uint64_t sessionEpoch) +{ + if (!hello) + return; + + memset(hello, 0, sizeof(*hello)); + hello->protocolVersion = LG_NET_PROTOCOL_VERSION_CURRENT; + hello->authMode = LG_NET_AUTH_MODE_DEFAULT; + hello->maxPayloadLength = LG_NET_MAX_PAYLOAD_LENGTH; + hello->sessionEpoch = sessionEpoch; +} + +bool lgNetHelloAckValid(const LGNetHelloAck * hello) +{ + return hello && + hello->protocolVersion >= LG_NET_PROTOCOL_VERSION_MIN && + hello->protocolVersion <= LG_NET_PROTOCOL_VERSION_MAX && + authModeKnown(hello->authMode) && + !(hello->flags & ~LG_NET_HELLO_ACK_RESUMED) && + hello->maxPayloadLength && + hello->maxPayloadLength <= LG_NET_MAX_PAYLOAD_LENGTH && + hello->maxDatagramPayload <= hello->maxPayloadLength && + hello->capabilityCount <= LG_NET_MAX_CAPABILITY_RECORDS && + hello->sessionEpoch; +} + +bool lgNetHelloAckEncode( + void * data, size_t size, const LGNetHelloAck * hello) +{ + if (!data || size < LG_NET_HELLO_ACK_WIRE_SIZE || + !lgNetHelloAckValid(hello)) + return false; + + LGNetWriter writer; + lgNetWriterInit(&writer, data, size); + return + lgNetWriterU16(&writer, hello->protocolVersion) && + lgNetWriterU16(&writer, hello->authMode) && + lgNetWriterU32(&writer, hello->flags) && + lgNetWriterU32(&writer, hello->maxPayloadLength) && + lgNetWriterU32(&writer, hello->maxDatagramPayload) && + lgNetWriterU32(&writer, hello->capabilityCount) && + lgNetWriterU32(&writer, 0) && + lgNetWriterU64(&writer, hello->sessionEpoch) && + lgNetWriterBytes(&writer, hello->nonce, sizeof(hello->nonce)) && + lgNetWriterSize(&writer) == LG_NET_HELLO_ACK_WIRE_SIZE; +} + +LGNetParseResult lgNetHelloAckDecode(LGNetHelloAck * hello, + const void * data, size_t size) +{ + if (!hello || !data) + return LG_NET_PARSE_INVALID_VALUE; + if (size < LG_NET_HELLO_ACK_WIRE_SIZE) + return LG_NET_PARSE_TRUNCATED; + + LGNetHelloAck decoded; + LGNetReader reader; + memset(&decoded, 0, sizeof(decoded)); + lgNetReaderInit(&reader, data, size); + if (!lgNetReaderU16(&reader, &decoded.protocolVersion) || + !lgNetReaderU16(&reader, &decoded.authMode) || + !lgNetReaderU32(&reader, &decoded.flags) || + !lgNetReaderU32(&reader, &decoded.maxPayloadLength) || + !lgNetReaderU32(&reader, &decoded.maxDatagramPayload) || + !lgNetReaderU32(&reader, &decoded.capabilityCount) || + !lgNetReaderZero(&reader, 4) || + !lgNetReaderU64(&reader, &decoded.sessionEpoch) || + !lgNetReaderBytes(&reader, decoded.nonce, + sizeof(decoded.nonce))) + return LG_NET_PARSE_INVALID_VALUE; + + const LGNetParseResult result = fixedDecodeResult( + &reader, LG_NET_HELLO_ACK_WIRE_SIZE, size); + if (result != LG_NET_PARSE_OK) + return result; + if (!lgNetHelloAckValid(&decoded)) + return LG_NET_PARSE_INVALID_VALUE; + + *hello = decoded; + return LG_NET_PARSE_OK; +} + +bool lgNetCapabilityValid(const LGNetCapability * capability) +{ + if (!capability || !capability->service || + !capability->versionMin || + capability->versionMin > capability->versionMax || + capability->flags & ~CAPABILITY_FLAGS || + !capability->maxPayloadLength || + capability->maxPayloadLength > LG_NET_MAX_PAYLOAD_LENGTH || + capability->maxDatagramPayload > capability->maxPayloadLength) + return false; + + if ((capability->flags & LG_NET_CAPABILITY_SELECTED) && + capability->versionMin != capability->versionMax) + return false; + if (!!(capability->flags & LG_NET_CAPABILITY_DATAGRAM) != + !!capability->maxDatagramPayload) + return false; + return true; +} + +bool lgNetCapabilityEncode(void * data, size_t size, + const LGNetCapability * capability) +{ + if (!data || size < LG_NET_CAPABILITY_WIRE_SIZE || + !lgNetCapabilityValid(capability)) + return false; + + LGNetWriter writer; + lgNetWriterInit(&writer, data, size); + return + lgNetWriterU16(&writer, capability->service) && + lgNetWriterU16(&writer, capability->versionMin) && + lgNetWriterU16(&writer, capability->versionMax) && + lgNetWriterU16(&writer, capability->flags) && + lgNetWriterU64(&writer, capability->features) && + lgNetWriterU32(&writer, capability->maxPayloadLength) && + lgNetWriterU32(&writer, capability->maxDatagramPayload) && + lgNetWriterU16(&writer, capability->maxBidirectionalStreams) && + lgNetWriterU16(&writer, capability->maxUnidirectionalStreams) && + lgNetWriterU32(&writer, 0) && + lgNetWriterSize(&writer) == LG_NET_CAPABILITY_WIRE_SIZE; +} + +LGNetParseResult lgNetCapabilityDecode(LGNetCapability * capability, + const void * data, size_t size) +{ + if (!capability || !data) + return LG_NET_PARSE_INVALID_VALUE; + if (size < LG_NET_CAPABILITY_WIRE_SIZE) + return LG_NET_PARSE_TRUNCATED; + + LGNetCapability decoded; + LGNetReader reader; + memset(&decoded, 0, sizeof(decoded)); + lgNetReaderInit(&reader, data, size); + if (!lgNetReaderU16(&reader, &decoded.service) || + !lgNetReaderU16(&reader, &decoded.versionMin) || + !lgNetReaderU16(&reader, &decoded.versionMax) || + !lgNetReaderU16(&reader, &decoded.flags) || + !lgNetReaderU64(&reader, &decoded.features) || + !lgNetReaderU32(&reader, &decoded.maxPayloadLength) || + !lgNetReaderU32(&reader, &decoded.maxDatagramPayload) || + !lgNetReaderU16(&reader, &decoded.maxBidirectionalStreams) || + !lgNetReaderU16(&reader, &decoded.maxUnidirectionalStreams) || + !lgNetReaderZero(&reader, 4)) + return LG_NET_PARSE_INVALID_VALUE; + + const LGNetParseResult result = fixedDecodeResult( + &reader, LG_NET_CAPABILITY_WIRE_SIZE, size); + if (result != LG_NET_PARSE_OK) + return result; + if (!lgNetCapabilityValid(&decoded)) + return LG_NET_PARSE_INVALID_VALUE; + + *capability = decoded; + return LG_NET_PARSE_OK; +} + +size_t lgNetPasswordChallengeSize( + const LGNetPasswordChallenge * challenge) +{ + if (!lgNetPasswordChallengeValid(challenge)) + return 0; + return LG_NET_PASSWORD_CHALLENGE_HEADER_SIZE + + challenge->saltLength + challenge->challengeLength; +} + +bool lgNetPasswordChallengeValid( + const LGNetPasswordChallenge * challenge) +{ + return challenge && passwordAlgorithmKnown(challenge->algorithm) && + challenge->rounds && challenge->saltLength && + challenge->saltLength <= LG_NET_PASSWORD_SALT_MAX_LENGTH && + challenge->challengeLength && + challenge->challengeLength <= LG_NET_PASSWORD_CHALLENGE_MAX_LENGTH; +} + +bool lgNetPasswordChallengeEncode(void * data, size_t size, + const LGNetPasswordChallenge * challenge) +{ + const size_t required = lgNetPasswordChallengeSize(challenge); + if (!data || !required || size < required) + return false; + + LGNetWriter writer; + lgNetWriterInit(&writer, data, size); + return + lgNetWriterU16(&writer, challenge->algorithm) && + lgNetWriterU16(&writer, 0) && + lgNetWriterU32(&writer, challenge->rounds) && + lgNetWriterU16(&writer, challenge->saltLength) && + lgNetWriterU16(&writer, challenge->challengeLength) && + lgNetWriterU32(&writer, 0) && + lgNetWriterBytes(&writer, challenge->salt, + challenge->saltLength) && + lgNetWriterBytes(&writer, challenge->challenge, + challenge->challengeLength) && + lgNetWriterSize(&writer) == required; +} + +LGNetParseResult lgNetPasswordChallengeDecode( + LGNetPasswordChallenge * challenge, const void * data, size_t size) +{ + if (!challenge || !data) + return LG_NET_PARSE_INVALID_VALUE; + if (size < LG_NET_PASSWORD_CHALLENGE_HEADER_SIZE) + return LG_NET_PARSE_TRUNCATED; + + LGNetPasswordChallenge decoded; + LGNetReader reader; + memset(&decoded, 0, sizeof(decoded)); + lgNetReaderInit(&reader, data, size); + if (!lgNetReaderU16(&reader, &decoded.algorithm) || + !lgNetReaderZero(&reader, 2) || + !lgNetReaderU32(&reader, &decoded.rounds) || + !lgNetReaderU16(&reader, &decoded.saltLength) || + !lgNetReaderU16(&reader, &decoded.challengeLength) || + !lgNetReaderZero(&reader, 4)) + return LG_NET_PARSE_INVALID_VALUE; + + if (decoded.saltLength > LG_NET_PASSWORD_SALT_MAX_LENGTH || + decoded.challengeLength > LG_NET_PASSWORD_CHALLENGE_MAX_LENGTH) + return LG_NET_PARSE_INVALID_LENGTH; + + const size_t expected = LG_NET_PASSWORD_CHALLENGE_HEADER_SIZE + + decoded.saltLength + decoded.challengeLength; + if (size < expected) + return LG_NET_PARSE_TRUNCATED; + if (size != expected || + !lgNetReaderBytes(&reader, decoded.salt, decoded.saltLength) || + !lgNetReaderBytes(&reader, decoded.challenge, + decoded.challengeLength)) + return LG_NET_PARSE_INVALID_LENGTH; + if (!lgNetPasswordChallengeValid(&decoded)) + return LG_NET_PARSE_INVALID_VALUE; + + *challenge = decoded; + return LG_NET_PARSE_OK; +} + +size_t lgNetPasswordResponseSize(const LGNetPasswordResponse * response) +{ + if (!lgNetPasswordResponseValid(response)) + return 0; + return LG_NET_PASSWORD_RESPONSE_HEADER_SIZE + + response->nonceLength + response->proofLength; +} + +bool lgNetPasswordResponseValid(const LGNetPasswordResponse * response) +{ + return response && passwordAlgorithmKnown(response->algorithm) && + response->nonceLength && + response->nonceLength <= LG_NET_PASSWORD_NONCE_MAX_LENGTH && + response->proofLength && + response->proofLength <= LG_NET_PASSWORD_PROOF_MAX_LENGTH; +} + +bool lgNetPasswordResponseEncode(void * data, size_t size, + const LGNetPasswordResponse * response) +{ + const size_t required = lgNetPasswordResponseSize(response); + if (!data || !required || size < required) + return false; + + LGNetWriter writer; + lgNetWriterInit(&writer, data, size); + return + lgNetWriterU16(&writer, response->algorithm) && + lgNetWriterU16(&writer, response->nonceLength) && + lgNetWriterU16(&writer, response->proofLength) && + lgNetWriterU16(&writer, 0) && + lgNetWriterU32(&writer, 0) && + lgNetWriterBytes(&writer, response->nonce, + response->nonceLength) && + lgNetWriterBytes(&writer, response->proof, + response->proofLength) && + lgNetWriterSize(&writer) == required; +} + +LGNetParseResult lgNetPasswordResponseDecode( + LGNetPasswordResponse * response, const void * data, size_t size) +{ + if (!response || !data) + return LG_NET_PARSE_INVALID_VALUE; + if (size < LG_NET_PASSWORD_RESPONSE_HEADER_SIZE) + return LG_NET_PARSE_TRUNCATED; + + LGNetPasswordResponse decoded; + LGNetReader reader; + memset(&decoded, 0, sizeof(decoded)); + lgNetReaderInit(&reader, data, size); + if (!lgNetReaderU16(&reader, &decoded.algorithm) || + !lgNetReaderU16(&reader, &decoded.nonceLength) || + !lgNetReaderU16(&reader, &decoded.proofLength) || + !lgNetReaderZero(&reader, 2) || + !lgNetReaderZero(&reader, 4)) + return LG_NET_PARSE_INVALID_VALUE; + + if (decoded.nonceLength > LG_NET_PASSWORD_NONCE_MAX_LENGTH || + decoded.proofLength > LG_NET_PASSWORD_PROOF_MAX_LENGTH) + return LG_NET_PARSE_INVALID_LENGTH; + + const size_t expected = LG_NET_PASSWORD_RESPONSE_HEADER_SIZE + + decoded.nonceLength + decoded.proofLength; + if (size < expected) + return LG_NET_PARSE_TRUNCATED; + if (size != expected || + !lgNetReaderBytes(&reader, decoded.nonce, decoded.nonceLength) || + !lgNetReaderBytes(&reader, decoded.proof, decoded.proofLength)) + return LG_NET_PARSE_INVALID_LENGTH; + if (!lgNetPasswordResponseValid(&decoded)) + return LG_NET_PARSE_INVALID_VALUE; + + *response = decoded; + return LG_NET_PARSE_OK; +} + +size_t lgNetAuthResultSize(const LGNetAuthResult * result) +{ + if (!lgNetAuthResultValid(result)) + return 0; + return LG_NET_AUTH_RESULT_HEADER_SIZE + result->proofLength; +} + +bool lgNetAuthResultValid(const LGNetAuthResult * result) +{ + return result && + result->result >= LG_NET_AUTH_RESULT_ACCEPTED && + result->result <= LG_NET_AUTH_RESULT_ERROR && + result->proofLength <= LG_NET_PASSWORD_PROOF_MAX_LENGTH; +} + +bool lgNetAuthResultEncode( + void * data, size_t size, const LGNetAuthResult * result) +{ + const size_t required = lgNetAuthResultSize(result); + if (!data || !required || size < required) + return false; + + LGNetWriter writer; + lgNetWriterInit(&writer, data, size); + return + lgNetWriterU16(&writer, result->result) && + lgNetWriterU16(&writer, result->proofLength) && + lgNetWriterU32(&writer, result->retryAfterMs) && + lgNetWriterU32(&writer, 0) && + lgNetWriterBytes(&writer, result->proof, + result->proofLength) && + lgNetWriterSize(&writer) == required; +} + +LGNetParseResult lgNetAuthResultDecode(LGNetAuthResult * result, + const void * data, size_t size) +{ + if (!result || !data) + return LG_NET_PARSE_INVALID_VALUE; + if (size < LG_NET_AUTH_RESULT_HEADER_SIZE) + return LG_NET_PARSE_TRUNCATED; + + LGNetAuthResult decoded; + LGNetReader reader; + memset(&decoded, 0, sizeof(decoded)); + lgNetReaderInit(&reader, data, size); + if (!lgNetReaderU16(&reader, &decoded.result) || + !lgNetReaderU16(&reader, &decoded.proofLength) || + !lgNetReaderU32(&reader, &decoded.retryAfterMs) || + !lgNetReaderZero(&reader, 4)) + return LG_NET_PARSE_INVALID_VALUE; + + if (decoded.proofLength > LG_NET_PASSWORD_PROOF_MAX_LENGTH) + return LG_NET_PARSE_INVALID_LENGTH; + + const size_t expected = LG_NET_AUTH_RESULT_HEADER_SIZE + + decoded.proofLength; + if (size < expected) + return LG_NET_PARSE_TRUNCATED; + if (size != expected || + !lgNetReaderBytes(&reader, decoded.proof, decoded.proofLength)) + return LG_NET_PARSE_INVALID_LENGTH; + if (!lgNetAuthResultValid(&decoded)) + return LG_NET_PARSE_INVALID_VALUE; + + *result = decoded; + return LG_NET_PARSE_OK; +} diff --git a/common/src/network/wire.c b/common/src/network/wire.c new file mode 100644 index 00000000..956f1755 --- /dev/null +++ b/common/src/network/wire.c @@ -0,0 +1,355 @@ +/** + * Looking Glass + * Copyright © 2017-2026 The Looking Glass Authors + * https://looking-glass.io + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 59 + * Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "common/network/wire.h" + +#include + +static bool writerReserve(LGNetWriter * writer, size_t size) +{ + if (!writer || writer->failed || writer->offset > writer->capacity || + size > writer->capacity - writer->offset) + { + if (writer) + writer->failed = true; + return false; + } + + return true; +} + +static bool readerReserve(LGNetReader * reader, size_t size) +{ + if (!reader || reader->failed || reader->offset > reader->size || + size > reader->size - reader->offset) + { + if (reader) + reader->failed = true; + return false; + } + + return true; +} + +void lgNetWriterInit(LGNetWriter * writer, void * data, size_t capacity) +{ + if (!writer) + return; + + writer->data = data; + writer->capacity = capacity; + writer->offset = 0; + writer->failed = capacity && !data; +} + +size_t lgNetWriterSize(const LGNetWriter * writer) +{ + return writer ? writer->offset : 0; +} + +size_t lgNetWriterRemaining(const LGNetWriter * writer) +{ + return writer && !writer->failed && writer->offset <= writer->capacity ? + writer->capacity - writer->offset : 0; +} + +bool lgNetWriterValid(const LGNetWriter * writer) +{ + return writer && !writer->failed && writer->offset <= writer->capacity && + (!writer->capacity || writer->data); +} + +bool lgNetWriterU8(LGNetWriter * writer, uint8_t value) +{ + if (!writerReserve(writer, 1)) + return false; + + writer->data[writer->offset++] = value; + return true; +} + +bool lgNetWriterU16(LGNetWriter * writer, uint16_t value) +{ + if (!writerReserve(writer, 2)) + return false; + + uint8_t * data = writer->data + writer->offset; + data[0] = (uint8_t)(value ); + data[1] = (uint8_t)(value >> 8); + writer->offset += 2; + return true; +} + +bool lgNetWriterU32(LGNetWriter * writer, uint32_t value) +{ + if (!writerReserve(writer, 4)) + return false; + + uint8_t * data = writer->data + writer->offset; + data[0] = (uint8_t)(value ); + data[1] = (uint8_t)(value >> 8); + data[2] = (uint8_t)(value >> 16); + data[3] = (uint8_t)(value >> 24); + writer->offset += 4; + return true; +} + +bool lgNetWriterU64(LGNetWriter * writer, uint64_t value) +{ + if (!writerReserve(writer, 8)) + return false; + + uint8_t * data = writer->data + writer->offset; + data[0] = (uint8_t)(value ); + data[1] = (uint8_t)(value >> 8); + data[2] = (uint8_t)(value >> 16); + data[3] = (uint8_t)(value >> 24); + data[4] = (uint8_t)(value >> 32); + data[5] = (uint8_t)(value >> 40); + data[6] = (uint8_t)(value >> 48); + data[7] = (uint8_t)(value >> 56); + writer->offset += 8; + return true; +} + +bool lgNetWriterI32(LGNetWriter * writer, int32_t value) +{ + uint32_t bits; + memcpy(&bits, &value, sizeof(bits)); + return lgNetWriterU32(writer, bits); +} + +bool lgNetWriterI64(LGNetWriter * writer, int64_t value) +{ + uint64_t bits; + memcpy(&bits, &value, sizeof(bits)); + return lgNetWriterU64(writer, bits); +} + +bool lgNetWriterBytes( + LGNetWriter * writer, const void * data, size_t size) +{ + if (!writerReserve(writer, size) || (size && !data)) + { + if (writer) + writer->failed = true; + return false; + } + + if (size) + memcpy(writer->data + writer->offset, data, size); + writer->offset += size; + return true; +} + +bool lgNetWriterZero(LGNetWriter * writer, size_t size) +{ + if (!writerReserve(writer, size)) + return false; + + if (size) + memset(writer->data + writer->offset, 0, size); + writer->offset += size; + return true; +} + +void lgNetReaderInit( + LGNetReader * reader, const void * data, size_t size) +{ + if (!reader) + return; + + reader->data = data; + reader->size = size; + reader->offset = 0; + reader->failed = size && !data; +} + +size_t lgNetReaderConsumed(const LGNetReader * reader) +{ + return reader ? reader->offset : 0; +} + +size_t lgNetReaderRemaining(const LGNetReader * reader) +{ + return reader && !reader->failed && reader->offset <= reader->size ? + reader->size - reader->offset : 0; +} + +bool lgNetReaderValid(const LGNetReader * reader) +{ + return reader && !reader->failed && reader->offset <= reader->size && + (!reader->size || reader->data); +} + +bool lgNetReaderU8(LGNetReader * reader, uint8_t * value) +{ + if (!readerReserve(reader, 1) || !value) + { + if (reader) + reader->failed = true; + return false; + } + + *value = reader->data[reader->offset++]; + return true; +} + +bool lgNetReaderU16(LGNetReader * reader, uint16_t * value) +{ + if (!readerReserve(reader, 2) || !value) + { + if (reader) + reader->failed = true; + return false; + } + + const uint8_t * data = reader->data + reader->offset; + *value = + (uint16_t)data[0] | + (uint16_t)data[1] << 8; + reader->offset += 2; + return true; +} + +bool lgNetReaderU32(LGNetReader * reader, uint32_t * value) +{ + if (!readerReserve(reader, 4) || !value) + { + if (reader) + reader->failed = true; + return false; + } + + const uint8_t * data = reader->data + reader->offset; + *value = + (uint32_t)data[0] | + (uint32_t)data[1] << 8 | + (uint32_t)data[2] << 16 | + (uint32_t)data[3] << 24; + reader->offset += 4; + return true; +} + +bool lgNetReaderU64(LGNetReader * reader, uint64_t * value) +{ + if (!readerReserve(reader, 8) || !value) + { + if (reader) + reader->failed = true; + return false; + } + + const uint8_t * data = reader->data + reader->offset; + *value = + (uint64_t)data[0] | + (uint64_t)data[1] << 8 | + (uint64_t)data[2] << 16 | + (uint64_t)data[3] << 24 | + (uint64_t)data[4] << 32 | + (uint64_t)data[5] << 40 | + (uint64_t)data[6] << 48 | + (uint64_t)data[7] << 56; + reader->offset += 8; + return true; +} + +bool lgNetReaderI32(LGNetReader * reader, int32_t * value) +{ + uint32_t bits; + if (!value || !lgNetReaderU32(reader, &bits)) + { + if (reader) + reader->failed = true; + return false; + } + + memcpy(value, &bits, sizeof(*value)); + return true; +} + +bool lgNetReaderI64(LGNetReader * reader, int64_t * value) +{ + uint64_t bits; + if (!value || !lgNetReaderU64(reader, &bits)) + { + if (reader) + reader->failed = true; + return false; + } + + memcpy(value, &bits, sizeof(*value)); + return true; +} + +bool lgNetReaderBytes(LGNetReader * reader, void * data, size_t size) +{ + if (!readerReserve(reader, size) || (size && !data)) + { + if (reader) + reader->failed = true; + return false; + } + + if (size) + memcpy(data, reader->data + reader->offset, size); + reader->offset += size; + return true; +} + +bool lgNetReaderView( + LGNetReader * reader, const uint8_t ** data, size_t size) +{ + if (!readerReserve(reader, size) || !data) + { + if (reader) + reader->failed = true; + return false; + } + + *data = size ? reader->data + reader->offset : NULL; + reader->offset += size; + return true; +} + +bool lgNetReaderSkip(LGNetReader * reader, size_t size) +{ + if (!readerReserve(reader, size)) + return false; + + reader->offset += size; + return true; +} + +bool lgNetReaderZero(LGNetReader * reader, size_t size) +{ + if (!readerReserve(reader, size)) + return false; + + for (size_t i = 0; i < size; ++i) + if (reader->data[reader->offset + i]) + { + reader->failed = true; + return false; + } + + reader->offset += size; + return true; +} diff --git a/idd/LGCommon/LGCommon.vcxproj b/idd/LGCommon/LGCommon.vcxproj index 233d07bb..4cfaf41d 100644 --- a/idd/LGCommon/LGCommon.vcxproj +++ b/idd/LGCommon/LGCommon.vcxproj @@ -69,6 +69,8 @@ + + @@ -86,6 +88,8 @@ + +