From 5138f470b762449ca8018d4d7b987cbdf2d99a37 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Wed, 30 Sep 2026 18:51:14 +1000 Subject: [PATCH] [client/idd] network: add negotiated input service --- client/transports/Network/CMakeLists.txt | 1 + client/transports/Network/connection.c | 596 ++++---- client/transports/Network/connection.h | 4 + client/transports/Network/input.c | 1296 +++++++++++++++++ client/transports/Network/input.h | 41 + client/transports/Network/network.c | 66 +- idd/LGCommon/LGCommon.vcxproj | 1 + idd/LGCommon/LGCommon.vcxproj.filters | 3 + .../transport/network/CNetworkServer.cpp | 1138 +++++++++++++-- .../transport/network/CNetworkTransport.cpp | 29 + .../transport/network/CNetworkTransport.h | 1 + 11 files changed, 2790 insertions(+), 386 deletions(-) create mode 100644 client/transports/Network/input.c create mode 100644 client/transports/Network/input.h diff --git a/client/transports/Network/CMakeLists.txt b/client/transports/Network/CMakeLists.txt index 42cd8530..9931e9b8 100644 --- a/client/transports/Network/CMakeLists.txt +++ b/client/transports/Network/CMakeLists.txt @@ -7,6 +7,7 @@ endif() set(TRANSPORT_NETWORK_SOURCES connection.c + input.c known_hosts.c network.c ) diff --git a/client/transports/Network/connection.c b/client/transports/Network/connection.c index fabfa671..1016bab0 100644 --- a/client/transports/Network/connection.c +++ b/client/transports/Network/connection.c @@ -25,6 +25,11 @@ #include "common/event.h" #include "common/locking.h" +#include +#include +#include +#include + #include #if !defined(QUIC_API_VERSION_2) @@ -36,18 +41,18 @@ #include #include -#define NETWORK_CONNECT_POLL_MS 50U -#define NETWORK_HANDSHAKE_TIMEOUT_MS 5000U -#define NETWORK_IDLE_TIMEOUT_MS 10000U -#define NETWORK_KEEPALIVE_INTERVAL_MS 1000U -#define NETWORK_INITIAL_RTT_MS 1U -#define NETWORK_MAX_PEER_STREAMS 16U -#define NETWORK_MAX_HEADER_SIZE 4096U -#define NETWORK_MAX_CONTROL_PAYLOAD (1024U * 1024U) -#define NETWORK_MAX_CONTROL_BUFFER \ - (2U * (NETWORK_MAX_HEADER_SIZE + NETWORK_MAX_CONTROL_PAYLOAD)) -#define NETWORK_MAX_DATAGRAM_PAYLOAD 1136U -#define NETWORK_HELLO_REQUEST_ID UINT64_C(1) +#define NETWORK_CONNECT_POLL_MS 50U +#define NETWORK_HANDSHAKE_TIMEOUT_MS 5000U +#define NETWORK_IDLE_TIMEOUT_MS 10000U +#define NETWORK_KEEPALIVE_INTERVAL_MS 1000U +#define NETWORK_INITIAL_RTT_MS 1U +#define NETWORK_MAX_PEER_STREAMS 16U +#define NETWORK_MAX_CONTROL_PAYLOAD (1024U * 1024U) +#define NETWORK_MAX_CONTROL_BUFFER \ + (2U * (LG_NET_ENVELOPE_WIRE_SIZE + NETWORK_MAX_CONTROL_PAYLOAD)) +#define NETWORK_MAX_DATAGRAM_PAYLOAD 1136U +#define NETWORK_HELLO_REQUEST_ID UINT64_C(1) +#define NETWORK_SEQUENCE_CONNECTION_EPOCH UINT64_C(1) struct NetworkSend { @@ -83,35 +88,41 @@ struct NetworkConnection NetworkConnectionStateFn state; void * opaque; + LGNetCapability offeredCapabilities[LG_NET_SERVICE_USB + 1U]; + uint32_t offeredCapabilityCount; + uint16_t offeredServices; + bool certificatePending; char pendingFingerprint[NETWORK_FINGERPRINT_TEXT_SIZE]; - LG_TransportStatus connectStatus; - bool connecting; - LG_TransportSession session; - uint64_t sessionEpoch; - uint64_t sendSequence; - uint64_t datagramSendSequence; - uint64_t receiveSequence; - bool helloAcknowledged; - bool authenticationComplete; - bool capabilitiesComplete; - bool connectFailed; - uint32_t expectedCapabilities; - uint32_t receivedCapabilities; - uint16_t receivedServices; - uint16_t reliableServices; - uint16_t datagramServices; - uint32_t maxPayloadLength; - uint32_t maxDatagramPayload; - uint16_t maxDatagramSendLength; - bool datagramSendEnabled; - uint32_t serviceMaxPayload[LG_NET_SERVICE_USB + 1U]; - uint32_t serviceMaxDatagram[LG_NET_SERVICE_USB + 1U]; + LG_TransportStatus connectStatus; + bool connecting; + LG_TransportSession session; + uint64_t sessionEpoch; + uint64_t sendSequence; + uint64_t datagramSendSequence; + uint64_t receiveSequence; + LGNetSequenceTracker datagramReceive; + bool helloAcknowledged; + bool authenticationComplete; + bool capabilitiesComplete; + bool connectFailed; + uint32_t expectedCapabilities; + uint32_t receivedCapabilities; + uint16_t receivedServices; + uint16_t reliableServices; + uint16_t datagramServices; + uint16_t serviceVersions[LG_NET_SERVICE_USB + 1U]; + uint32_t maxPayloadLength; + uint32_t maxDatagramPayload; + uint16_t maxDatagramSendLength; + bool datagramSendEnabled; + uint32_t serviceMaxPayload[LG_NET_SERVICE_USB + 1U]; + uint32_t serviceMaxDatagram[LG_NET_SERVICE_USB + 1U]; - uint8_t * receiveData; - size_t receiveSize; - size_t receiveCapacity; + LGNetStreamParser receiveParser; + uint8_t * receiveData; + uint8_t * receivePayload; }; static QUIC_STATUS QUIC_API networkConnectionCallback(HQUIC handle, @@ -144,52 +155,23 @@ static char * networkEndpoint(const char * address, uint16_t port) return endpoint; } -static uint16_t networkServiceVersion(LGNetService service) +static bool networkCapabilityOfferValid( + const LGNetCapability * capability) { - switch (service) - { - case LG_NET_SERVICE_CORE: - return LG_NET_CORE_VERSION_CURRENT; + const LGNetServiceVersionInfo * version = capability ? + lgNetServiceVersionInfo(capability->service) : NULL; + if (!lgNetCapabilityValid(capability) || + !version || + capability->service <= LG_NET_SERVICE_CORE || + capability->service >= LG_NET_SERVICE_USB || + capability->flags & LG_NET_CAPABILITY_SELECTED || + capability->versionMin < version->minimum || + capability->versionMax > version->maximum || + !(capability->flags & (LG_NET_CAPABILITY_RELIABLE | + LG_NET_CAPABILITY_DATAGRAM | LG_NET_CAPABILITY_BULK))) + return false; - case LG_NET_SERVICE_RECOVERY: - return LG_NET_RECOVERY_VERSION_CURRENT; - - case LG_NET_SERVICE_VIDEO: - return LG_NET_VIDEO_VERSION_CURRENT; - - case LG_NET_SERVICE_CURSOR: - return LG_NET_CURSOR_VERSION_CURRENT; - - case LG_NET_SERVICE_INPUT: - return LG_NET_INPUT_VERSION_CURRENT; - - case LG_NET_SERVICE_AUDIO: - return LG_NET_AUDIO_VERSION_CURRENT; - - case LG_NET_SERVICE_CLIPBOARD: - return LG_NET_CLIPBOARD_VERSION_CURRENT; - - case LG_NET_SERVICE_FILE: - return LG_NET_FILE_VERSION_CURRENT; - - case LG_NET_SERVICE_USB: - return LG_NET_USB_VERSION_RESERVED; - } - - return 0; -} - -static uint16_t networkReadU16(const uint8_t * data) -{ - return (uint16_t)data[0] | (uint16_t)((uint16_t)data[1] << 8); -} - -static uint32_t networkReadU32(const uint8_t * data) -{ - return (uint32_t)data[0] | - ((uint32_t)data[1] << 8) | - ((uint32_t)data[2] << 16) | - ((uint32_t)data[3] << 24); + return true; } static void networkConnectionSetState(NetworkConnection * this, @@ -229,13 +211,15 @@ static bool networkConnectionSendPacket(NetworkConnection * this, LGNetEnvelopeFlags flags, uint64_t componentEpoch, uint64_t requestID, const void * payload, uint32_t payloadLength, bool allowConnecting) { + LGNetEnvelope routeEnvelope; + lgNetEnvelopeInit(&routeEnvelope, service, messageType, serviceVersion, + LG_NET_MESSAGE_VERSION_CURRENT); if (payloadLength > NETWORK_MAX_CONTROL_PAYLOAD || (payloadLength && !payload) || - !lgNetServiceKnown(service) || - !lgNetMessageKnown(service, messageType) || - serviceVersion != networkServiceVersion(service) || (flags & (LG_NET_ENVELOPE_DATAGRAM | LG_NET_ENVELOPE_EARLY_DATA)) || + !lgNetEnvelopeRouteValid(&routeEnvelope, LG_NET_ROLE_CLIENT, + LG_NET_ROUTE_DELIVERY_RELIABLE) || atomic_load_explicit(&this->stopping, memory_order_acquire)) return false; @@ -245,20 +229,20 @@ static bool networkConnectionSendPacket(NetworkConnection * this, if (!send) return false; - LGNetEnvelope envelope; - lgNetEnvelopeInit(&envelope, service, messageType, serviceVersion, + LGNetPacketParameters parameters; + lgNetPacketParametersInit(¶meters, service, messageType, serviceVersion, LG_NET_MESSAGE_VERSION_CURRENT); - envelope.flags = flags | LG_NET_ENVELOPE_RELIABLE; - envelope.payloadLength = payloadLength; - envelope.componentEpoch = componentEpoch; - envelope.requestID = requestID; + parameters.flags = flags | LG_NET_ENVELOPE_RELIABLE; + parameters.componentEpoch = componentEpoch; + parameters.requestID = requestID; LG_LOCK(this->lock); HQUIC stream = this->controlStream; const bool serviceReady = allowConnecting || service == LG_NET_SERVICE_CORE || ((this->receivedServices & (UINT16_C(1) << service)) && - (this->reliableServices & (UINT16_C(1) << service))); + (this->reliableServices & (UINT16_C(1) << service)) && + this->serviceVersions[service] == serviceVersion); const uint32_t maxPayloadLength = allowConnecting ? NETWORK_MAX_CONTROL_PAYLOAD : service == LG_NET_SERVICE_CORE ? @@ -273,10 +257,13 @@ static bool networkConnectionSendPacket(NetworkConnection * this, return false; } - envelope.sessionEpoch = this->sessionEpoch; - envelope.sequence = ++this->sendSequence; - if (!lgNetPacketEncode(send->data, packetSize, &envelope, payload)) + parameters.sessionEpoch = this->sessionEpoch; + parameters.sequence = ++this->sendSequence; + size_t wireSize; + if (!lgNetPacketConstruct(send->data, packetSize, &wireSize, + ¶meters, payload, payloadLength) || wireSize != packetSize) { + --this->sendSequence; LG_UNLOCK(this->lock); free(send); return false; @@ -287,6 +274,8 @@ static bool networkConnectionSendPacket(NetworkConnection * this, const QUIC_STATUS status = this->api->StreamSend(stream, &send->buffer, 1, QUIC_SEND_FLAG_NONE, send); + if (QUIC_FAILED(status)) + --this->sendSequence; LG_UNLOCK(this->lock); if (QUIC_FAILED(status)) @@ -303,20 +292,41 @@ static bool networkConnectionSendHello(NetworkConnection * this) lgNetHelloInit(&hello, LG_NET_ROLE_CLIENT); hello.maxPayloadLength = NETWORK_MAX_CONTROL_PAYLOAD; hello.maxDatagramPayload = NETWORK_MAX_DATAGRAM_PAYLOAD; - if (this->password && *this->password) - { - DEBUG_ERROR("Network password authentication is not implemented yet"); - return false; - } + hello.capabilityCount = this->offeredCapabilityCount; + hello.authModes = this->password && *this->password ? + LG_NET_AUTH_MODE_FLAG(LG_NET_AUTH_PASSWORD) : + LG_NET_AUTH_MODE_FLAG(LG_NET_AUTH_NONE); uint8_t payload[LG_NET_HELLO_WIRE_SIZE]; if (!lgNetHelloEncode(payload, sizeof(payload), &hello)) return false; - return networkConnectionSendPacket(this, LG_NET_SERVICE_CORE, + if (!networkConnectionSendPacket(this, LG_NET_SERVICE_CORE, LG_NET_CORE_MESSAGE_HELLO, LG_NET_CORE_VERSION_CURRENT, LG_NET_ENVELOPE_FINAL, 0, NETWORK_HELLO_REQUEST_ID, - payload, sizeof(payload), true); + payload, sizeof(payload), true)) + return false; + + for (LGNetService service = LG_NET_SERVICE_RECOVERY; + service < LG_NET_SERVICE_USB; ++service) + { + if (!(this->offeredServices & (UINT16_C(1) << service))) + continue; + + uint8_t capabilityPayload[LG_NET_CAPABILITY_WIRE_SIZE]; + if (!lgNetCapabilityEncode(capabilityPayload, + sizeof(capabilityPayload), &this->offeredCapabilities[service]) || + !networkConnectionSendPacket(this, LG_NET_SERVICE_CORE, + LG_NET_CORE_MESSAGE_CAPABILITY, LG_NET_CORE_VERSION_CURRENT, + LG_NET_ENVELOPE_FINAL, 0, NETWORK_HELLO_REQUEST_ID, + capabilityPayload, sizeof(capabilityPayload), true)) + return false; + } + + return networkConnectionSendPacket(this, LG_NET_SERVICE_CORE, + LG_NET_CORE_MESSAGE_CAPABILITIES_DONE, + LG_NET_CORE_VERSION_CURRENT, LG_NET_ENVELOPE_FINAL, + 0, NETWORK_HELLO_REQUEST_ID, NULL, 0, true); } static void networkConnectionReady(NetworkConnection * this) @@ -349,26 +359,53 @@ static bool networkConnectionHandleCapability(NetworkConnection * this, packet->payload, packet->payloadLength) != LG_NET_PARSE_OK) return false; + if (capability.service <= LG_NET_SERVICE_CORE || + capability.service >= LG_NET_SERVICE_USB) + return false; + LG_LOCK(this->lock); + const uint16_t serviceMask = + UINT16_C(1) << capability.service; + const LGNetCapability offered = + this->offeredCapabilities[capability.service]; + const LGNetCapabilityFlags selectedFlags = + capability.flags & + (LG_NET_CAPABILITY_RELIABLE | LG_NET_CAPABILITY_DATAGRAM | + LG_NET_CAPABILITY_BULK | LG_NET_CAPABILITY_MULTI_CLIENT); + const LGNetCapabilityFlags offeredFlags = + offered.flags & + (LG_NET_CAPABILITY_RELIABLE | LG_NET_CAPABILITY_DATAGRAM | + LG_NET_CAPABILITY_BULK | LG_NET_CAPABILITY_MULTI_CLIENT); if (!this->helloAcknowledged || this->capabilitiesComplete || this->receivedCapabilities >= this->expectedCapabilities || - capability.service <= LG_NET_SERVICE_CORE || - capability.service > LG_NET_SERVICE_USB || + !(this->offeredServices & serviceMask) || !(capability.flags & LG_NET_CAPABILITY_SELECTED) || - capability.versionMin != networkServiceVersion(capability.service) || + capability.flags & LG_NET_CAPABILITY_REQUIRED || + !(selectedFlags & LG_NET_CAPABILITY_RELIABLE) || + selectedFlags & ~offeredFlags || + capability.features & ~offered.features || + capability.versionMin < offered.versionMin || + capability.versionMin > offered.versionMax || + capability.maxPayloadLength > offered.maxPayloadLength || capability.maxPayloadLength > this->maxPayloadLength || + capability.maxDatagramPayload > offered.maxDatagramPayload || capability.maxDatagramPayload > this->maxDatagramPayload || - (this->receivedServices & (UINT16_C(1) << capability.service))) + capability.maxBidirectionalStreams > + offered.maxBidirectionalStreams || + capability.maxUnidirectionalStreams > + offered.maxUnidirectionalStreams || + (this->receivedServices & serviceMask)) { LG_UNLOCK(this->lock); return false; } - this->receivedServices |= UINT16_C(1) << capability.service; + this->receivedServices |= serviceMask; if (capability.flags & LG_NET_CAPABILITY_RELIABLE) this->reliableServices |= UINT16_C(1) << capability.service; if (capability.flags & LG_NET_CAPABILITY_DATAGRAM) this->datagramServices |= UINT16_C(1) << capability.service; + this->serviceVersions[capability.service] = capability.versionMin; this->serviceMaxPayload[capability.service] = capability.maxPayloadLength; this->serviceMaxDatagram[capability.service] = @@ -378,6 +415,34 @@ static bool networkConnectionHandleCapability(NetworkConnection * this, return true; } +static bool networkConnectionRequiredCapabilitiesSelected( + const NetworkConnection * this) +{ + for (LGNetService service = LG_NET_SERVICE_RECOVERY; + service < LG_NET_SERVICE_USB; ++service) + { + const uint16_t serviceMask = UINT16_C(1) << service; + if ((this->offeredServices & serviceMask) && + (this->offeredCapabilities[service].flags & + LG_NET_CAPABILITY_REQUIRED) && + !(this->receivedServices & serviceMask)) + return false; + } + return true; +} + +static void networkConnectionSetSessionFeatures(NetworkConnection * this) +{ + const uint16_t services = + this->receivedServices & this->reliableServices; + if (services & (UINT16_C(1) << LG_NET_SERVICE_INPUT)) + this->session.features |= LG_TRANSPORT_FEATURE_INPUT; + if (services & (UINT16_C(1) << LG_NET_SERVICE_CLIPBOARD)) + this->session.features |= LG_TRANSPORT_FEATURE_CLIPBOARD; + if (services & (UINT16_C(1) << LG_NET_SERVICE_AUDIO)) + this->session.features |= LG_TRANSPORT_FEATURE_AUDIO; +} + static bool networkConnectionHandleCore(NetworkConnection * this, const LGNetPacketView * packet) { @@ -385,7 +450,8 @@ static bool networkConnectionHandleCore(NetworkConnection * this, LG_NET_ENVELOPE_RESPONSE | LG_NET_ENVELOPE_FINAL | LG_NET_ENVELOPE_RELIABLE; - uint64_t sessionEpoch; + + uint64_t sessionEpoch; LG_LOCK(this->lock); sessionEpoch = this->sessionEpoch; LG_UNLOCK(this->lock); @@ -422,8 +488,11 @@ static bool networkConnectionHandleCore(NetworkConnection * this, this->sessionEpoch = acknowledgement.sessionEpoch; this->maxPayloadLength = acknowledgement.maxPayloadLength; this->maxDatagramPayload = acknowledgement.maxDatagramPayload; + const bool sequenceReady = lgNetSequenceTrackerInit( + &this->datagramReceive, acknowledgement.sessionEpoch, + NETWORK_SEQUENCE_CONNECTION_EPOCH, 64, false); LG_UNLOCK(this->lock); - return true; + return sequenceReady; } case LG_NET_CORE_MESSAGE_CAPABILITY: @@ -448,8 +517,11 @@ static bool networkConnectionHandleCore(NetworkConnection * this, ready = !this->capabilitiesComplete && this->helloAcknowledged && this->authenticationComplete && - this->receivedCapabilities == this->expectedCapabilities; + this->receivedCapabilities == this->expectedCapabilities && + networkConnectionRequiredCapabilitiesSelected(this); this->capabilitiesComplete = ready; + if (ready) + networkConnectionSetSessionFeatures(this); LG_UNLOCK(this->lock); if (ready) networkConnectionReady(this); @@ -484,6 +556,8 @@ static bool networkConnectionHandlePacket(NetworkConnection * this, (UINT16_C(1) << packet->envelope.service)) && (this->reliableServices & (UINT16_C(1) << packet->envelope.service)) && + packet->envelope.serviceVersion == + this->serviceVersions[packet->envelope.service] && packet->payloadLength <= this->serviceMaxPayload[packet->envelope.service]; LG_UNLOCK(this->lock); @@ -503,7 +577,7 @@ static bool networkConnectionValidateControl(NetworkConnection * this, maxPayloadLength = this->maxPayloadLength; LG_UNLOCK(this->lock); - return packet->envelope.headerSize <= NETWORK_MAX_HEADER_SIZE && + return packet->envelope.headerSize == LG_NET_ENVELOPE_WIRE_SIZE && packet->payloadLength <= maxPayloadLength && packet->envelope.messageVersion == LG_NET_MESSAGE_VERSION_CURRENT && (packet->envelope.flags & @@ -511,11 +585,8 @@ static bool networkConnectionValidateControl(NetworkConnection * this, (LG_NET_ENVELOPE_RELIABLE | LG_NET_ENVELOPE_FINAL) && !(packet->envelope.flags & (LG_NET_ENVELOPE_DATAGRAM | LG_NET_ENVELOPE_EARLY_DATA)) && - lgNetServiceKnown(packet->envelope.service) && - lgNetMessageKnown(packet->envelope.service, - packet->envelope.messageType) && - packet->envelope.serviceVersion == - networkServiceVersion(packet->envelope.service); + lgNetEnvelopeRouteValid(&packet->envelope, LG_NET_ROLE_SERVER, + LG_NET_ROUTE_DELIVERY_RELIABLE); } static bool networkConnectionDecodeDatagram(NetworkConnection * this, @@ -526,117 +597,67 @@ static bool networkConnectionDecodeDatagram(NetworkConnection * this, buffer->Length) != LG_NET_PARSE_OK || packet->wireSize != buffer->Length || packet->envelope.service == LG_NET_SERVICE_CORE || - !lgNetServiceKnown(packet->envelope.service) || - !lgNetMessageKnown(packet->envelope.service, - packet->envelope.messageType) || - packet->envelope.serviceVersion != - networkServiceVersion(packet->envelope.service)) + !lgNetEnvelopeRouteValid(&packet->envelope, LG_NET_ROLE_SERVER, + LG_NET_ROUTE_DELIVERY_DATAGRAM)) + return false; + + if (!atomic_load_explicit( + &this->sessionValid, memory_order_acquire) || + packet->envelope.headerSize != LG_NET_ENVELOPE_WIRE_SIZE || + !packet->envelope.sequence || + packet->envelope.messageVersion != LG_NET_MESSAGE_VERSION_CURRENT || + (packet->envelope.flags & + (LG_NET_ENVELOPE_DATAGRAM | LG_NET_ENVELOPE_FINAL)) != + (LG_NET_ENVELOPE_DATAGRAM | LG_NET_ENVELOPE_FINAL) || + (packet->envelope.flags & + (LG_NET_ENVELOPE_RELIABLE | LG_NET_ENVELOPE_EARLY_DATA))) return false; - uint64_t sessionEpoch; - uint16_t receivedServices; - uint16_t datagramServices; - uint32_t serviceMaxDatagram; LG_LOCK(this->lock); - sessionEpoch = this->sessionEpoch; - receivedServices = this->receivedServices; - datagramServices = this->datagramServices; - serviceMaxDatagram = - this->serviceMaxDatagram[packet->envelope.service]; + const uint16_t serviceMask = UINT16_C(1) << packet->envelope.service; + const bool valid = + packet->envelope.sessionEpoch == this->sessionEpoch && + (this->receivedServices & serviceMask) && + (this->datagramServices & serviceMask) && + packet->envelope.serviceVersion == + this->serviceVersions[packet->envelope.service] && + this->serviceMaxDatagram[packet->envelope.service] && + packet->payloadLength <= + this->serviceMaxDatagram[packet->envelope.service] && + lgNetSequenceAccepted(lgNetSequenceTrackerCheck( + &this->datagramReceive, packet->envelope.sessionEpoch, + NETWORK_SEQUENCE_CONNECTION_EPOCH, packet->envelope.sequence)); LG_UNLOCK(this->lock); - - return atomic_load_explicit( - &this->sessionValid, memory_order_acquire) && - (receivedServices & (UINT16_C(1) << packet->envelope.service)) && - (datagramServices & (UINT16_C(1) << packet->envelope.service)) && - serviceMaxDatagram && - packet->envelope.headerSize <= NETWORK_MAX_HEADER_SIZE && - packet->payloadLength <= serviceMaxDatagram && - packet->envelope.sessionEpoch == sessionEpoch && - packet->envelope.sequence != 0 && - packet->envelope.messageVersion == LG_NET_MESSAGE_VERSION_CURRENT && - (packet->envelope.flags & LG_NET_ENVELOPE_DATAGRAM) && - !(packet->envelope.flags & - (LG_NET_ENVELOPE_RELIABLE | LG_NET_ENVELOPE_EARLY_DATA)); + return valid; } -static bool networkConnectionReserveReceive(NetworkConnection * this, - size_t additional) +static bool networkConnectionDrainReceive(NetworkConnection * this) { - if (this->receiveSize > NETWORK_MAX_CONTROL_BUFFER || - additional > NETWORK_MAX_CONTROL_BUFFER - this->receiveSize) - return false; - - const size_t required = this->receiveSize + additional; - if (required <= this->receiveCapacity) - return true; - - size_t capacity = this->receiveCapacity ? this->receiveCapacity : 4096U; - while (capacity < required) + for (;;) { - const size_t next = capacity * 2U; - if (next < capacity || next > NETWORK_MAX_CONTROL_BUFFER) + LGNetStreamPacket streamPacket; + const LGNetStreamResult result = lgNetStreamParserNext( + &this->receiveParser, &streamPacket, NULL); + if (result == LG_NET_STREAM_NEED_DATA) + return true; + if (result != LG_NET_STREAM_PACKET) + return false; + + LGNetPacketView packet = { - capacity = NETWORK_MAX_CONTROL_BUFFER; - break; + .envelope = streamPacket.envelope, + .payload = streamPacket.payload[0].data, + .payloadLength = streamPacket.payloadLength, + .wireSize = streamPacket.wireSize, + }; + if (streamPacket.payload[1].size) + { + memcpy(this->receivePayload, streamPacket.payload[0].data, + streamPacket.payload[0].size); + memcpy(this->receivePayload + streamPacket.payload[0].size, + streamPacket.payload[1].data, streamPacket.payload[1].size); + packet.payload = this->receivePayload; } - capacity = next; - } - - uint8_t * data = realloc(this->receiveData, capacity); - if (!data) - return false; - this->receiveData = data; - this->receiveCapacity = capacity; - return true; -} - -static bool networkConnectionReceive(NetworkConnection * this, - const QUIC_BUFFER * buffers, uint32_t count) -{ - if (count && !buffers) - return false; - - size_t additional = 0; - for (uint32_t i = 0; i < count; ++i) - { - if (buffers[i].Length && !buffers[i].Buffer) - return false; - if (buffers[i].Length > SIZE_MAX - additional) - return false; - additional += buffers[i].Length; - } - - if (!networkConnectionReserveReceive(this, additional)) - return false; - - for (uint32_t i = 0; i < count; ++i) - { - if (!buffers[i].Length) - continue; - memcpy(this->receiveData + this->receiveSize, - buffers[i].Buffer, buffers[i].Length); - this->receiveSize += buffers[i].Length; - } - - while (this->receiveSize >= LG_NET_ENVELOPE_WIRE_SIZE) - { - const uint16_t headerSize = networkReadU16(this->receiveData + 8U); - const uint32_t payloadSize = networkReadU32(this->receiveData + 24U); - if (headerSize < LG_NET_ENVELOPE_WIRE_SIZE || - headerSize > NETWORK_MAX_HEADER_SIZE || - payloadSize > NETWORK_MAX_CONTROL_PAYLOAD || - payloadSize > NETWORK_MAX_CONTROL_BUFFER - headerSize) - return false; - - const size_t packetSize = (size_t)headerSize + payloadSize; - if (packetSize > this->receiveSize) - break; - - LGNetPacketView packet; - if (lgNetPacketDecode(&packet, this->receiveData, - packetSize) != LG_NET_PARSE_OK) - return false; LG_LOCK(this->lock); const bool sequenceValid = this->receiveSequence != UINT64_MAX && @@ -645,14 +666,39 @@ static bool networkConnectionReceive(NetworkConnection * this, this->receiveSequence = packet.envelope.sequence; LG_UNLOCK(this->lock); if (!sequenceValid || !networkConnectionValidateControl(this, &packet) || - !networkConnectionHandlePacket(this, &packet, false)) + !networkConnectionHandlePacket(this, &packet, false) || + !lgNetStreamParserConsume(&this->receiveParser)) return false; - - this->receiveSize -= packetSize; - memmove(this->receiveData, this->receiveData + packetSize, - this->receiveSize); } - return true; +} + +static bool networkConnectionReceive(NetworkConnection * this, + const QUIC_BUFFER * buffers, uint32_t count) +{ + if (count && !buffers) + return false; + + for (uint32_t i = 0; i < count; ++i) + { + if (buffers[i].Length && !buffers[i].Buffer) + return false; + size_t offset = 0; + while (offset < buffers[i].Length) + { + size_t accepted; + if (!lgNetStreamParserAppend(&this->receiveParser, + buffers[i].Buffer + offset, buffers[i].Length - offset, + &accepted)) + return false; + offset += accepted; + if (!networkConnectionDrainReceive(this)) + return false; + if (!accepted && offset < buffers[i].Length) + return false; + } + } + + return networkConnectionDrainReceive(this); } static void networkConnectionTrustDecision(void * opaque, bool trusted) @@ -922,7 +968,9 @@ bool networkConnectionCreate(NetworkConnection ** result, const NetworkConnectionParams * params) { if (!result || !params || !params->address || !params->knownHosts || - (!!params->trustPrompt != !!params->trustCancel)) + (!!params->trustPrompt != !!params->trustCancel) || + params->capabilityCount > LG_NET_MAX_CAPABILITY_RECORDS || + (params->capabilityCount && !params->capabilities)) return false; NetworkConnection * this = calloc(1, sizeof(*this)); @@ -934,6 +982,20 @@ bool networkConnectionCreate(NetworkConnection ** result, atomic_init(&this->stopping, false); atomic_init(&this->sessionValid, false); + for (uint32_t index = 0; index < params->capabilityCount; ++index) + { + const LGNetCapability * capability = ¶ms->capabilities[index]; + if (!networkCapabilityOfferValid(capability)) + goto fail; + + const uint16_t serviceMask = UINT16_C(1) << capability->service; + if (this->offeredServices & serviceMask) + goto fail; + this->offeredCapabilities[capability->service] = *capability; + this->offeredServices |= serviceMask; + ++this->offeredCapabilityCount; + } + this->address = networkDuplicate(params->address); this->password = networkDuplicate(params->password); this->endpoint = networkEndpoint(params->address, params->port); @@ -954,6 +1016,13 @@ bool networkConnectionCreate(NetworkConnection ** result, if (!this->connectEvent || !this->closeEvent) goto fail; + this->receiveData = malloc(NETWORK_MAX_CONTROL_BUFFER); + this->receivePayload = malloc(NETWORK_MAX_CONTROL_PAYLOAD); + if (!this->receiveData || !this->receivePayload || + !lgNetStreamParserInit(&this->receiveParser, this->receiveData, + NETWORK_MAX_CONTROL_BUFFER, NETWORK_MAX_CONTROL_PAYLOAD)) + goto fail; + if (QUIC_FAILED(MsQuicOpen2(&this->api))) goto fail; @@ -1037,6 +1106,7 @@ void networkConnectionDestroy(NetworkConnection ** connection) lgFreeEvent(this->connectEvent); LG_LOCK_FREE(this->stateCallbackLock); LG_LOCK_FREE(this->lock); + free(this->receivePayload); free(this->receiveData); free(this->endpoint); free(this->password); @@ -1084,9 +1154,10 @@ LG_TransportStatus networkConnectionConnect(NetworkConnection * this, this->maxDatagramPayload = 0; this->maxDatagramSendLength = 0; this->datagramSendEnabled = false; + memset(this->serviceVersions, 0, sizeof(this->serviceVersions)); memset(this->serviceMaxPayload, 0, sizeof(this->serviceMaxPayload)); memset(this->serviceMaxDatagram, 0, sizeof(this->serviceMaxDatagram)); - this->receiveSize = 0; + lgNetStreamParserReset(&this->receiveParser); memset(&this->session, 0, sizeof(this->session)); snprintf(this->session.version, sizeof(this->session.version), "LG network protocol %u", LG_NET_PROTOCOL_VERSION_CURRENT); @@ -1237,6 +1308,23 @@ bool networkConnectionServiceAvailable(NetworkConnection * this, return available; } +bool networkConnectionServiceVersion(NetworkConnection * this, + LGNetService service, uint16_t * version) +{ + if (!version || !networkConnectionValid(this) || + service <= LG_NET_SERVICE_CORE || service >= LG_NET_SERVICE_USB) + return false; + + LG_LOCK(this->lock); + const uint16_t serviceMask = UINT16_C(1) << service; + const bool available = + (this->receivedServices & serviceMask) != 0; + if (available) + *version = this->serviceVersions[service]; + LG_UNLOCK(this->lock); + return available; +} + LG_TransportStatus networkConnectionSend(NetworkConnection * this, LGNetService service, uint16_t messageType, uint16_t serviceVersion, LGNetEnvelopeFlags flags, uint64_t componentEpoch, uint64_t requestID, @@ -1258,28 +1346,28 @@ LG_TransportStatus networkConnectionSendDatagram(NetworkConnection * this, { if (!this || !networkConnectionValid(this)) return LG_TRANSPORT_DISCONNECTED; - if ((payloadLength && !payload) || - !lgNetServiceKnown(service) || service == LG_NET_SERVICE_CORE || - !lgNetMessageKnown(service, messageType) || - serviceVersion != networkServiceVersion(service) || - flags & (LG_NET_ENVELOPE_RELIABLE | LG_NET_ENVELOPE_DATAGRAM | - LG_NET_ENVELOPE_EARLY_DATA)) - return LG_TRANSPORT_ERROR; - - const size_t packetSize = LG_NET_ENVELOPE_WIRE_SIZE + - payloadLength; - struct NetworkSend * send = malloc(sizeof(*send) + packetSize); - if (!send) - return LG_TRANSPORT_ERROR; - - LGNetEnvelope envelope; - lgNetEnvelopeInit(&envelope, service, messageType, serviceVersion, + LGNetEnvelope routeEnvelope; + lgNetEnvelopeInit(&routeEnvelope, service, messageType, serviceVersion, LG_NET_MESSAGE_VERSION_CURRENT); - envelope.flags = flags | LG_NET_ENVELOPE_DATAGRAM | + if ((payloadLength && !payload) || service == LG_NET_SERVICE_CORE || + flags & (LG_NET_ENVELOPE_RELIABLE | LG_NET_ENVELOPE_DATAGRAM | + LG_NET_ENVELOPE_EARLY_DATA) || + !lgNetEnvelopeRouteValid(&routeEnvelope, LG_NET_ROLE_CLIENT, + LG_NET_ROUTE_DELIVERY_DATAGRAM)) + return LG_TRANSPORT_ERROR; + + if (payloadLength > LG_NET_MAX_PAYLOAD_LENGTH) + return LG_TRANSPORT_ERROR; + + const size_t packetSize = LG_NET_ENVELOPE_WIRE_SIZE + payloadLength; + + LGNetPacketParameters parameters; + lgNetPacketParametersInit(¶meters, service, messageType, serviceVersion, + LG_NET_MESSAGE_VERSION_CURRENT); + parameters.flags = flags | LG_NET_ENVELOPE_DATAGRAM | LG_NET_ENVELOPE_FINAL; - envelope.payloadLength = payloadLength; - envelope.componentEpoch = componentEpoch; - envelope.requestID = requestID; + parameters.componentEpoch = componentEpoch; + parameters.requestID = requestID; LG_LOCK(this->lock); const uint16_t serviceMask = UINT16_C(1) << service; @@ -1288,20 +1376,30 @@ LG_TransportStatus networkConnectionSendDatagram(NetworkConnection * this, !atomic_load_explicit(&this->sessionValid, memory_order_acquire) || !(this->receivedServices & serviceMask) || !(this->datagramServices & serviceMask) || + this->serviceVersions[service] != serviceVersion || !this->datagramSendEnabled || payloadLength > this->serviceMaxDatagram[service] || packetSize > this->maxDatagramSendLength || this->datagramSendSequence == UINT64_MAX) { LG_UNLOCK(this->lock); - free(send); return LG_TRANSPORT_UNAVAILABLE; } - envelope.sessionEpoch = this->sessionEpoch; - envelope.sequence = ++this->datagramSendSequence; - if (!lgNetPacketEncode(send->data, packetSize, &envelope, payload)) + struct NetworkSend * send = malloc(sizeof(*send) + packetSize); + if (!send) { + LG_UNLOCK(this->lock); + return LG_TRANSPORT_ERROR; + } + + parameters.sessionEpoch = this->sessionEpoch; + parameters.sequence = ++this->datagramSendSequence; + size_t wireSize; + if (!lgNetPacketConstruct(send->data, packetSize, &wireSize, + ¶meters, payload, payloadLength) || wireSize != packetSize) + { + --this->datagramSendSequence; LG_UNLOCK(this->lock); free(send); return LG_TRANSPORT_ERROR; @@ -1314,6 +1412,8 @@ LG_TransportStatus networkConnectionSendDatagram(NetworkConnection * this, sendFlags |= QUIC_SEND_FLAG_CANCEL_ON_BLOCKED; const QUIC_STATUS status = this->api->DatagramSend(connection, &send->buffer, 1, sendFlags, send); + if (QUIC_FAILED(status)) + --this->datagramSendSequence; LG_UNLOCK(this->lock); if (QUIC_FAILED(status)) diff --git a/client/transports/Network/connection.h b/client/transports/Network/connection.h index 12aef4d7..26bf193e 100644 --- a/client/transports/Network/connection.h +++ b/client/transports/Network/connection.h @@ -52,6 +52,8 @@ typedef struct NetworkConnectionParams NetworkTrustCancelFn trustCancel; NetworkPacketFn packet; NetworkConnectionStateFn state; + const LGNetCapability * capabilities; + uint32_t capabilityCount; void * opaque; } NetworkConnectionParams; @@ -68,6 +70,8 @@ void networkConnectionDisconnect(NetworkConnection * connection); bool networkConnectionValid(const NetworkConnection * connection); bool networkConnectionServiceAvailable(NetworkConnection * connection, LGNetService service, LGNetCapabilityFlags required); +bool networkConnectionServiceVersion(NetworkConnection * connection, + LGNetService service, uint16_t * version); LG_TransportStatus networkConnectionSend(NetworkConnection * connection, LGNetService service, uint16_t messageType, uint16_t serviceVersion, diff --git a/client/transports/Network/input.c b/client/transports/Network/input.c new file mode 100644 index 00000000..cb741ca6 --- /dev/null +++ b/client/transports/Network/input.c @@ -0,0 +1,1296 @@ +/** + * 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 "input.h" + +#include "kb.h" + +#include "common/event.h" +#include "common/locking.h" +#include "common/time.h" + +#include + +#include +#include +#include +#include + +#define NETWORK_INPUT_TIMER_MS 50U +#define NETWORK_INPUT_LEASE_MS 500U +#define NETWORK_INPUT_KEEPALIVE_MS 200U +#define NETWORK_INPUT_CLAIM_RETRY_MS 500U +#define NETWORK_INPUT_LED_SYNC_DELAY_MS 20U +#define NETWORK_INPUT_LED_ACK_TIMEOUT_MS 1000U +#define NETWORK_INPUT_USAGE_PAGE_KEYBOARD 0x07U +#define NETWORK_INPUT_KEYBOARD_USAGE_MAX 231U + +#define NETWORK_INPUT_CLAIM_FLAGS \ + (LG_NET_INPUT_CLAIM_KEYBOARD | LG_NET_INPUT_CLAIM_POINTER | \ + LG_NET_INPUT_CLAIM_EXCLUSIVE) + +#define NETWORK_INPUT_LED_MASK \ + (LG_NET_KEYBOARD_LED_NUM_LOCK | LG_NET_KEYBOARD_LED_CAPS_LOCK | \ + LG_NET_KEYBOARD_LED_SCROLL_LOCK) + +struct NetworkInputLockKey +{ + uint32_t led; + uint16_t usage; +}; + +static const struct NetworkInputLockKey NETWORK_INPUT_LOCK_KEYS[] = +{ + { LG_NET_KEYBOARD_LED_NUM_LOCK, 83 }, + { LG_NET_KEYBOARD_LED_CAPS_LOCK, 57 }, + { LG_NET_KEYBOARD_LED_SCROLL_LOCK, 71 }, +}; + +struct NetworkInput +{ + NetworkConnection * connection; + LGTimer * timer; + LGEvent * statusIdle; + + LG_Lock stateLock; + + bool destroying; + bool connected; + bool serviceAvailable; + bool claimPending; + bool claimed; + bool available; + uint64_t claimantID; + uint64_t claimEpoch; + uint64_t sequence; + uint64_t nextClaimNs; + uint64_t nextKeepaliveNs; + + uint32_t claimFlags; + uint16_t serviceVersion; + uint32_t mouseButtons; + bool mouseAbsolute; + uint32_t absoluteX; + uint32_t absoluteY; + uint32_t absoluteWidth; + uint32_t absoluteHeight; + /* Preserve every held usage. The receiver reconstructs a six-key boot + * report and emits ErrorRollOver while more than six keys are held. */ + bool keyState[NETWORK_INPUT_KEYBOARD_USAGE_MAX + 1U]; + uint8_t modifiers; + bool keyboardLEDsValid; + uint8_t keyboardLEDs; + uint32_t keyboardLEDsValidMask; + uint32_t pendingKeyboardLEDs; + uint16_t pendingKeyboardLEDCounts[ + sizeof(NETWORK_INPUT_LOCK_KEYS) / sizeof(*NETWORK_INPUT_LOCK_KEYS)]; + uint64_t pendingKeyboardLEDsUntil; + uint64_t keyboardLEDsSyncAfter; + uint32_t staleKeyboardLEDs; + uint64_t staleKeyboardLEDsUntil; + uint64_t keyboardLEDSequence; + bool desiredKeyboardLEDsValid; + uint8_t desiredKeyboardLEDs; + + uint32_t statusGeneration; + bool statusNotifyPending; + bool statusDispatching; + LG_InputStatusFn statusCallback; + void * statusOpaque; +}; + +static _Thread_local NetworkInput * l_networkInputCallback; + +static uint64_t networkInputNext64(uint64_t value) +{ + ++value; + return value ? value : 1U; +} + +static uint32_t networkInputNext32(uint32_t value) +{ + ++value; + return value ? value : 1U; +} + +static uint64_t networkInputDeadline(uint32_t milliseconds) +{ + return nanotime() + (uint64_t)milliseconds * UINT64_C(1000000); +} + +static bool networkInputKeyboardHeldNL(const NetworkInput * input) +{ + for (size_t usage = 1; + usage <= NETWORK_INPUT_KEYBOARD_USAGE_MAX; ++usage) + if (input->keyState[usage]) + return true; + return false; +} + +static bool networkInputLEDTransitionsNL(const NetworkInput * input) +{ + for (size_t i = 0; + i < sizeof(NETWORK_INPUT_LOCK_KEYS) / + sizeof(*NETWORK_INPUT_LOCK_KEYS); ++i) + if (input->pendingKeyboardLEDCounts[i]) + return true; + return false; +} + +static uint32_t networkInputProjectedLEDsNL(const NetworkInput * input) +{ + return input->keyboardLEDs ^ input->pendingKeyboardLEDs; +} + +static void networkInputClearLEDProjectionNL(NetworkInput * input) +{ + input->pendingKeyboardLEDs = 0; + input->pendingKeyboardLEDsUntil = 0; + memset(input->pendingKeyboardLEDCounts, 0, + sizeof(input->pendingKeyboardLEDCounts)); +} + +static void networkInputInvalidateLEDsNL(NetworkInput * input) +{ + input->keyboardLEDsValid = false; + input->keyboardLEDs = 0; + input->keyboardLEDsValidMask = 0; + input->keyboardLEDSequence = 0; + input->keyboardLEDsSyncAfter = 0; + input->staleKeyboardLEDs = 0; + input->staleKeyboardLEDsUntil = 0; + networkInputClearLEDProjectionNL(input); +} + +static void networkInputRecordLEDToggleNL(NetworkInput * input, + uint32_t led, bool physical) +{ + for (size_t i = 0; + i < sizeof(NETWORK_INPUT_LOCK_KEYS) / + sizeof(*NETWORK_INPUT_LOCK_KEYS); ++i) + { + if (NETWORK_INPUT_LOCK_KEYS[i].led != led) + continue; + + if (input->pendingKeyboardLEDCounts[i] != UINT16_MAX) + ++input->pendingKeyboardLEDCounts[i]; + input->pendingKeyboardLEDs ^= led; + input->pendingKeyboardLEDsUntil = networkInputDeadline( + NETWORK_INPUT_LED_ACK_TIMEOUT_MS); + break; + } + + if (physical) + { + if (!input->desiredKeyboardLEDsValid || + ((networkInputProjectedLEDsNL(input) ^ + input->desiredKeyboardLEDs) & led)) + input->staleKeyboardLEDs |= led; + else + input->staleKeyboardLEDs &= ~led; + + input->staleKeyboardLEDsUntil = input->staleKeyboardLEDs ? + networkInputDeadline(NETWORK_INPUT_LED_ACK_TIMEOUT_MS) : 0; + } + + input->keyboardLEDsSyncAfter = networkInputDeadline( + NETWORK_INPUT_LED_SYNC_DELAY_MS); +} + +static void networkInputAcknowledgeLEDsNL(NetworkInput * input, + uint32_t changed) +{ + for (size_t i = 0; + i < sizeof(NETWORK_INPUT_LOCK_KEYS) / + sizeof(*NETWORK_INPUT_LOCK_KEYS); ++i) + { + const uint32_t led = NETWORK_INPUT_LOCK_KEYS[i].led; + if (!(changed & led) || !input->pendingKeyboardLEDCounts[i]) + continue; + + --input->pendingKeyboardLEDCounts[i]; + input->pendingKeyboardLEDs ^= led; + } + + if (!networkInputLEDTransitionsNL(input)) + input->pendingKeyboardLEDsUntil = 0; +} + +static LG_InputStatus networkInputStatusNL(const NetworkInput * input) +{ + return (LG_InputStatus) + { + .available = input->available, + .generation = input->statusGeneration, + .keyboardLEDsValid = input->available && input->keyboardLEDsValid, + .keyboardLEDs = input->keyboardLEDs, + }; +} + +static void networkInputDispatchStatus(NetworkInput * input, + LG_InputStatusFn callback, void * opaque, LG_InputStatus status) +{ + while (callback) + { + NetworkInput * previous = l_networkInputCallback; + l_networkInputCallback = input; + callback(opaque, &status); + l_networkInputCallback = previous; + + LG_LOCK(input->stateLock); + if (input->statusNotifyPending && input->statusCallback) + { + input->statusNotifyPending = false; + callback = input->statusCallback; + opaque = input->statusOpaque; + status = networkInputStatusNL(input); + LG_UNLOCK(input->stateLock); + continue; + } + + input->statusDispatching = false; + lgSignalEvent(input->statusIdle); + LG_UNLOCK(input->stateLock); + break; + } +} + +static void networkInputNotifyStatus(NetworkInput * input) +{ + LG_InputStatusFn callback; + void * opaque; + LG_InputStatus status; + + LG_LOCK(input->stateLock); + if (!input->statusNotifyPending || input->statusDispatching) + { + LG_UNLOCK(input->stateLock); + return; + } + + input->statusNotifyPending = false; + callback = input->statusCallback; + opaque = input->statusOpaque; + status = networkInputStatusNL(input); + if (!callback) + { + LG_UNLOCK(input->stateLock); + return; + } + + input->statusDispatching = true; + lgResetEvent(input->statusIdle); + LG_UNLOCK(input->stateLock); + + networkInputDispatchStatus(input, callback, opaque, status); +} + +static bool networkInputSetAvailableNL(NetworkInput * input, bool available) +{ + if (input->available == available) + return false; + + input->available = available; + input->statusGeneration = + networkInputNext32(input->statusGeneration); + input->statusNotifyPending = true; + return true; +} + +static void networkInputClearStateNL(NetworkInput * input) +{ + input->mouseButtons = 0; + input->mouseAbsolute = false; + input->absoluteX = 0; + input->absoluteY = 0; + input->absoluteWidth = 0; + input->absoluteHeight = 0; + memset(input->keyState, 0, sizeof(input->keyState)); + input->modifiers = 0; + networkInputClearLEDProjectionNL(input); +} + +static LG_TransportStatus networkInputSendReliableNL(NetworkInput * input, + uint16_t messageType, LGNetEnvelopeFlags flags, uint64_t requestID, + const void * payload, uint32_t payloadLength) +{ + return networkConnectionSend(input->connection, LG_NET_SERVICE_INPUT, + messageType, input->serviceVersion, + flags | LG_NET_ENVELOPE_FINAL, input->claimEpoch, requestID, + payload, payloadLength); +} + +static bool networkInputSendClaimNL(NetworkInput * input) +{ + if (!input->connected || !input->serviceAvailable || input->destroying) + return false; + + networkInputClearStateNL(input); + networkInputInvalidateLEDsNL(input); + const uint64_t claimEpoch = networkInputNext64(input->claimEpoch); + const LGNetInputClaim claim = + { + .claimantID = input->claimantID, + .claimEpoch = claimEpoch, + .leaseMs = NETWORK_INPUT_LEASE_MS, + .flags = NETWORK_INPUT_CLAIM_FLAGS, + }; + uint8_t payload[LG_NET_INPUT_CLAIM_WIRE_SIZE]; + if (!lgNetInputClaimEncode(payload, sizeof(payload), &claim)) + return false; + + input->claimEpoch = claimEpoch; + input->sequence = 0; + if (networkInputSendReliableNL(input, LG_NET_INPUT_MESSAGE_CLAIM, + LG_NET_ENVELOPE_ACK_REQUIRED, claimEpoch, + payload, sizeof(payload)) != LG_TRANSPORT_OK) + { + input->nextClaimNs = networkInputDeadline( + NETWORK_INPUT_CLAIM_RETRY_MS); + return false; + } + + input->claimPending = true; + input->claimed = false; + input->claimFlags = 0; + input->nextClaimNs = networkInputDeadline(NETWORK_INPUT_LEASE_MS); + input->nextKeepaliveNs = 0; + return true; +} + +static bool networkInputSendControlNL(NetworkInput * input, + uint16_t messageType, uint32_t flags, uint32_t leaseMs) +{ + const uint64_t sequence = networkInputNext64(input->sequence); + const LGNetInputControl control = + { + .claimantID = input->claimantID, + .claimEpoch = input->claimEpoch, + .sequence = sequence, + .flags = flags, + .leaseMs = leaseMs, + }; + uint8_t payload[LG_NET_INPUT_CONTROL_WIRE_SIZE]; + if (!lgNetInputControlEncode(payload, sizeof(payload), &control) || + networkInputSendReliableNL(input, messageType, + 0, 0, payload, sizeof(payload)) != LG_TRANSPORT_OK) + return false; + + input->sequence = sequence; + return true; +} + +static bool networkInputAbandonClaimNL(NetworkInput * input, + bool tryReset) +{ + if (input->claimed && networkConnectionValid(input->connection)) + { + if (tryReset) + networkInputSendControlNL(input, LG_NET_INPUT_MESSAGE_RESET, + LG_NET_INPUT_CLAIM_KEYBOARD | LG_NET_INPUT_CLAIM_POINTER, 0); + networkInputSendControlNL(input, LG_NET_INPUT_MESSAGE_RELEASE, + input->claimFlags, 0); + } + + input->claimPending = false; + input->claimed = false; + input->claimFlags = 0; + input->claimEpoch = networkInputNext64(input->claimEpoch); + input->nextKeepaliveNs = 0; + input->nextClaimNs = networkInputDeadline( + NETWORK_INPUT_CLAIM_RETRY_MS); + networkInputClearStateNL(input); + networkInputInvalidateLEDsNL(input); + return networkInputSetAvailableNL(input, false); +} + +static bool networkInputSendResetNL(NetworkInput * input) +{ + const bool failed = input->claimed && + (!networkConnectionValid(input->connection) || + !networkInputSendControlNL(input, LG_NET_INPUT_MESSAGE_RESET, + LG_NET_INPUT_CLAIM_KEYBOARD | LG_NET_INPUT_CLAIM_POINTER, 0)); + networkInputClearStateNL(input); + return failed && networkInputAbandonClaimNL(input, false); +} + +static bool networkInputSendReleaseNL(NetworkInput * input) +{ + bool sent = true; + if ((input->claimed || input->claimPending) && + networkConnectionValid(input->connection)) + sent = networkInputSendControlNL(input, LG_NET_INPUT_MESSAGE_RELEASE, + input->claimFlags, 0); + + input->claimPending = false; + input->claimed = false; + input->claimFlags = 0; + input->nextKeepaliveNs = 0; + return sent; +} + +static bool networkInputNextSequenceNL(NetworkInput * input, + uint64_t * sequence) +{ + if (!input->available || !input->claimed) + return false; + + *sequence = networkInputNext64(input->sequence); + return true; +} + +static bool networkInputSendKeyboardNL(NetworkInput * input, + uint16_t usage, bool down, bool repeat) +{ + uint64_t sequence; + if (!networkInputNextSequenceNL(input, &sequence)) + return false; + + LGNetInputKeyboard keyboard = + { + .sequence = sequence, + .timestampNs = nanotime(), + .usagePage = NETWORK_INPUT_USAGE_PAGE_KEYBOARD, + .usage = usage, + .scanCode = 0, + .flags = (down ? LG_NET_KEYBOARD_DOWN : 0) | + (repeat ? LG_NET_KEYBOARD_REPEAT : 0), + .modifiers = input->modifiers, + }; + if (!keyboard.timestampNs) + keyboard.timestampNs = 1; + + uint8_t payload[LG_NET_INPUT_KEYBOARD_WIRE_SIZE]; + if (!lgNetInputKeyboardEncode(payload, sizeof(payload), &keyboard) || + networkInputSendReliableNL(input, LG_NET_INPUT_MESSAGE_KEYBOARD, + 0, 0, payload, sizeof(payload)) != LG_TRANSPORT_OK) + return false; + + input->sequence = sequence; + return true; +} + +static uint32_t networkInputLEDForUsage(uint16_t usage) +{ + switch (usage) + { + case 83: + return LG_NET_KEYBOARD_LED_NUM_LOCK; + + case 57: + return LG_NET_KEYBOARD_LED_CAPS_LOCK; + + case 71: + return LG_NET_KEYBOARD_LED_SCROLL_LOCK; + + default: + return 0; + } +} + +static bool networkInputTapLockKeyNL(NetworkInput * input, + const struct NetworkInputLockKey * key, bool * notify) +{ + if (key->usage > NETWORK_INPUT_KEYBOARD_USAGE_MAX || + input->keyState[key->usage]) + return false; + + if (!networkInputSendKeyboardNL(input, key->usage, true, false)) + return false; + networkInputRecordLEDToggleNL(input, key->led, false); + if (networkInputSendKeyboardNL(input, key->usage, false, false)) + return true; + + *notify |= networkInputSendResetNL(input); + input->keyboardLEDsSyncAfter = networkInputDeadline( + NETWORK_INPUT_LED_ACK_TIMEOUT_MS); + return false; +} + +static void networkInputSyncKeyboardLEDsNL(NetworkInput * input, + bool * notify) +{ + const uint64_t now = nanotime(); + if (networkInputLEDTransitionsNL(input) && + now >= input->pendingKeyboardLEDsUntil) + { + networkInputClearLEDProjectionNL(input); + input->keyboardLEDsSyncAfter = now + + (uint64_t)NETWORK_INPUT_LED_SYNC_DELAY_MS * UINT64_C(1000000); + } + + if (input->staleKeyboardLEDs && + now >= input->staleKeyboardLEDsUntil) + { + input->desiredKeyboardLEDsValid = false; + input->staleKeyboardLEDs = 0; + input->staleKeyboardLEDsUntil = 0; + } + + if (!input->available || !input->keyboardLEDsValid || + !input->desiredKeyboardLEDsValid || + networkInputLEDTransitionsNL(input) || + now < input->keyboardLEDsSyncAfter) + return; + + const uint32_t difference = + (networkInputProjectedLEDsNL(input) ^ + input->desiredKeyboardLEDs) & + NETWORK_INPUT_LED_MASK & ~input->staleKeyboardLEDs; + if (!difference) + { + if (!input->staleKeyboardLEDs) + input->desiredKeyboardLEDsValid = false; + return; + } + if (networkInputKeyboardHeldNL(input)) + return; + + for (size_t i = 0; + i < sizeof(NETWORK_INPUT_LOCK_KEYS) / + sizeof(*NETWORK_INPUT_LOCK_KEYS); ++i) + { + if (!(difference & NETWORK_INPUT_LOCK_KEYS[i].led)) + continue; + if (!networkInputTapLockKeyNL(input, + &NETWORK_INPUT_LOCK_KEYS[i], notify)) + break; + } +} + +static bool networkInputTimer(void * opaque) +{ + NetworkInput * input = opaque; + bool notify = false; + LG_LOCK(input->stateLock); + if (input->destroying) + { + LG_UNLOCK(input->stateLock); + return false; + } + + const uint64_t now = nanotime(); + + if (input->connected && input->serviceAvailable) + { + if (input->claimPending && now >= input->nextClaimNs) + input->claimPending = false; + if (!input->claimPending && !input->claimed && + now >= input->nextClaimNs) + networkInputSendClaimNL(input); + else if (input->claimed && now >= input->nextKeepaliveNs) + { + if (networkInputSendControlNL(input, + LG_NET_INPUT_MESSAGE_KEEPALIVE, + input->claimFlags, NETWORK_INPUT_LEASE_MS)) + input->nextKeepaliveNs = networkInputDeadline( + NETWORK_INPUT_KEEPALIVE_MS); + else + notify |= networkInputAbandonClaimNL(input, true); + } + + networkInputSyncKeyboardLEDsNL(input, ¬ify); + } + LG_UNLOCK(input->stateLock); + + networkInputNotifyStatus(input); + return true; +} + +static bool networkInputSupports(void * opaque, LG_InputSupport support) +{ + NetworkInput * input = opaque; + if (support != LG_INPUT_SUPPORT_MOUSE_ABSOLUTE) + return false; + + LG_LOCK(input->stateLock); + const bool supported = input->available && + (input->claimFlags & LG_NET_INPUT_CLAIM_POINTER); + LG_UNLOCK(input->stateLock); + return supported; +} + +static void networkInputSetStatusListener(void * opaque, + LG_InputStatusFn callback, void * callbackOpaque) +{ + NetworkInput * input = opaque; + const bool reentrant = l_networkInputCallback == input; + + LG_LOCK(input->stateLock); + input->statusCallback = NULL; + input->statusOpaque = NULL; + input->statusNotifyPending = false; + const bool dispatching = input->statusDispatching; + LG_UNLOCK(input->stateLock); + + if (dispatching && !reentrant) + lgWaitEvent(input->statusIdle, TIMEOUT_INFINITE); + if (!callback) + return; + + LG_LOCK(input->stateLock); + input->statusCallback = callback; + input->statusOpaque = callbackOpaque; + input->statusNotifyPending = false; + const LG_InputStatus status = networkInputStatusNL(input); + const bool startDispatch = !input->statusDispatching; + if (startDispatch) + { + input->statusDispatching = true; + lgResetEvent(input->statusIdle); + } + LG_UNLOCK(input->stateLock); + + if (startDispatch) + networkInputDispatchStatus(input, callback, callbackOpaque, status); + else + { + NetworkInput * previous = l_networkInputCallback; + l_networkInputCallback = input; + callback(callbackOpaque, &status); + l_networkInputCallback = previous; + } +} + +static bool networkInputUpdateKey(void * opaque, int key, bool down) +{ + if (key < 0 || key >= LG_KEY_MAX) + return false; + + const uint16_t usage = lg_keycode_to_hid[key]; + if (!usage || usage > NETWORK_INPUT_KEYBOARD_USAGE_MAX) + return true; + + NetworkInput * input = opaque; + bool notify = false; + LG_LOCK(input->stateLock); + if (!input->available || + !(input->claimFlags & LG_NET_INPUT_CLAIM_KEYBOARD)) + { + LG_UNLOCK(input->stateLock); + return false; + } + + const bool previous = input->keyState[usage]; + if (previous == down) + { + LG_UNLOCK(input->stateLock); + return true; + } + + input->keyState[usage] = down; + if (usage >= 224 && usage <= 231) + { + const uint8_t mask = UINT8_C(1) << (usage - 224); + if (down) + input->modifiers |= mask; + else + input->modifiers &= (uint8_t)~mask; + } + + if (!networkInputSendKeyboardNL(input, usage, down, false)) + { + input->keyState[usage] = previous; + if (usage >= 224 && usage <= 231) + { + const uint8_t mask = UINT8_C(1) << (usage - 224); + if (previous) + input->modifiers |= mask; + else + input->modifiers &= (uint8_t)~mask; + } + if (!down) + notify = networkInputAbandonClaimNL(input, true); + LG_UNLOCK(input->stateLock); + if (notify) + networkInputNotifyStatus(input); + return false; + } + + const uint32_t led = networkInputLEDForUsage(usage); + if (down && led) + { + if (input->keyboardLEDsValid) + networkInputRecordLEDToggleNL(input, led, true); + else + { + input->desiredKeyboardLEDsValid = false; + input->keyboardLEDsSyncAfter = networkInputDeadline( + NETWORK_INPUT_LED_SYNC_DELAY_MS); + } + } + LG_UNLOCK(input->stateLock); + return true; +} + +static bool networkInputKeyDown(void * opaque, int key) +{ + return networkInputUpdateKey(opaque, key, true); +} + +static bool networkInputKeyUp(void * opaque, int key) +{ + return networkInputUpdateKey(opaque, key, false); +} + +static bool networkInputKeyboardLEDs(void * opaque, bool numLock, + bool capsLock, bool scrollLock) +{ + NetworkInput * input = opaque; + bool notify = false; + const uint8_t desired = + (numLock ? LG_NET_KEYBOARD_LED_NUM_LOCK : 0) | + (capsLock ? LG_NET_KEYBOARD_LED_CAPS_LOCK : 0) | + (scrollLock ? LG_NET_KEYBOARD_LED_SCROLL_LOCK : 0); + + LG_LOCK(input->stateLock); + input->desiredKeyboardLEDs = desired; + input->desiredKeyboardLEDsValid = true; + input->staleKeyboardLEDs &= + networkInputProjectedLEDsNL(input) ^ desired; + if (!input->staleKeyboardLEDs) + input->staleKeyboardLEDsUntil = 0; + const uint64_t syncAfter = networkInputDeadline( + NETWORK_INPUT_LED_SYNC_DELAY_MS); + if (input->keyboardLEDsSyncAfter < syncAfter) + input->keyboardLEDsSyncAfter = syncAfter; + networkInputSyncKeyboardLEDsNL(input, ¬ify); + const bool available = input->available; + LG_UNLOCK(input->stateLock); + if (notify) + networkInputNotifyStatus(input); + return available; +} + +static void networkInputKeyboardLEDsReset(void * opaque) +{ + NetworkInput * input = opaque; + LG_LOCK(input->stateLock); + input->desiredKeyboardLEDsValid = false; + input->keyboardLEDsSyncAfter = 0; + input->staleKeyboardLEDs = 0; + input->staleKeyboardLEDsUntil = 0; + LG_UNLOCK(input->stateLock); +} + +static bool networkInputSendRelativeNL(NetworkInput * input, + int32_t x, int32_t y, int32_t wheelX, int32_t wheelY, + uint32_t buttons, uint32_t changedButtons, bool motion) +{ + uint64_t sequence; + if (!networkInputNextSequenceNL(input, &sequence)) + return false; + + LGNetInputRelative relative = + { + .sequence = sequence, + .timestampNs = nanotime(), + .deltaX = x, + .deltaY = y, + .wheelX = wheelX, + .wheelY = wheelY, + .buttons = buttons, + .changedButtons = changedButtons, + }; + if (!relative.timestampNs) + relative.timestampNs = 1; + + uint8_t payload[LG_NET_INPUT_RELATIVE_WIRE_SIZE]; + if (!lgNetInputRelativeEncode(payload, sizeof(payload), &relative)) + return false; + + LG_TransportStatus status = LG_TRANSPORT_UNAVAILABLE; + if (motion && networkConnectionServiceAvailable(input->connection, + LG_NET_SERVICE_INPUT, LG_NET_CAPABILITY_DATAGRAM)) + status = networkConnectionSendDatagram(input->connection, + LG_NET_SERVICE_INPUT, LG_NET_INPUT_MESSAGE_MOUSE_RELATIVE, + input->serviceVersion, 0, input->claimEpoch, 0, + payload, sizeof(payload), true); + if (status != LG_TRANSPORT_OK) + status = networkInputSendReliableNL(input, + LG_NET_INPUT_MESSAGE_MOUSE_RELATIVE, 0, 0, + payload, sizeof(payload)); + if (status != LG_TRANSPORT_OK) + return false; + + input->sequence = sequence; + input->mouseAbsolute = false; + return true; +} + +static bool networkInputSendAbsoluteNL(NetworkInput * input, + uint32_t x, uint32_t y, uint32_t width, uint32_t height, + int32_t wheelX, int32_t wheelY, uint32_t buttons, + uint32_t changedButtons, bool motion) +{ + uint64_t sequence; + if (!networkInputNextSequenceNL(input, &sequence)) + return false; + + LGNetInputAbsolute absolute = + { + .sequence = sequence, + .timestampNs = nanotime(), + .x = (int32_t)x, + .y = (int32_t)y, + .width = width, + .height = height, + .wheelX = wheelX, + .wheelY = wheelY, + .buttons = buttons, + .changedButtons = changedButtons, + }; + if (!absolute.timestampNs) + absolute.timestampNs = 1; + + uint8_t payload[LG_NET_INPUT_ABSOLUTE_WIRE_SIZE]; + if (!lgNetInputAbsoluteEncode(payload, sizeof(payload), &absolute)) + return false; + + LG_TransportStatus status = LG_TRANSPORT_UNAVAILABLE; + if (motion && networkConnectionServiceAvailable(input->connection, + LG_NET_SERVICE_INPUT, LG_NET_CAPABILITY_DATAGRAM)) + status = networkConnectionSendDatagram(input->connection, + LG_NET_SERVICE_INPUT, LG_NET_INPUT_MESSAGE_MOUSE_ABSOLUTE, + input->serviceVersion, 0, input->claimEpoch, 0, + payload, sizeof(payload), true); + if (status != LG_TRANSPORT_OK) + status = networkInputSendReliableNL(input, + LG_NET_INPUT_MESSAGE_MOUSE_ABSOLUTE, 0, 0, + payload, sizeof(payload)); + if (status != LG_TRANSPORT_OK) + return false; + + input->sequence = sequence; + input->mouseAbsolute = true; + input->absoluteX = x; + input->absoluteY = y; + input->absoluteWidth = width; + input->absoluteHeight = height; + return true; +} + +static bool networkInputMouseMotion(void * opaque, int32_t x, int32_t y) +{ + NetworkInput * input = opaque; + LG_LOCK(input->stateLock); + const bool result = input->available && + (input->claimFlags & LG_NET_INPUT_CLAIM_POINTER) && + networkInputSendRelativeNL(input, x, y, 0, 0, + input->mouseButtons, 0, true); + LG_UNLOCK(input->stateLock); + return result; +} + +static bool networkInputMousePosition(void * opaque, uint32_t x, + uint32_t y, uint32_t width, uint32_t height) +{ + if (!width || !height || + width > LG_NET_INPUT_ABSOLUTE_MAX_WIDTH || + height > LG_NET_INPUT_ABSOLUTE_MAX_HEIGHT || + x >= width || y >= height) + return false; + + NetworkInput * input = opaque; + LG_LOCK(input->stateLock); + const bool result = input->available && + (input->claimFlags & LG_NET_INPUT_CLAIM_POINTER) && + networkInputSendAbsoluteNL(input, x, y, width, height, 0, 0, + input->mouseButtons, 0, true); + LG_UNLOCK(input->stateLock); + return result; +} + +static uint32_t networkInputButtonMask(unsigned int button) +{ + switch (button) + { + case 1: + return LG_NET_POINTER_BUTTON_LEFT; + + case 2: + return LG_NET_POINTER_BUTTON_MIDDLE; + + case 3: + return LG_NET_POINTER_BUTTON_RIGHT; + + case 6: + return LG_NET_POINTER_BUTTON_BACK; + + case 7: + return LG_NET_POINTER_BUTTON_FORWARD; + + default: + if (button >= 8 && button < 32) + return UINT32_C(1) << (button - 3U); + return 0; + } +} + +static bool networkInputUpdateButton(void * opaque, + unsigned int button, bool down) +{ + NetworkInput * input = opaque; + bool notify = false; + LG_LOCK(input->stateLock); + if (!input->available || + !(input->claimFlags & LG_NET_INPUT_CLAIM_POINTER)) + { + LG_UNLOCK(input->stateLock); + return false; + } + + int32_t wheelY = 0; + if (button == 4 || button == 5) + { + if (!down) + { + LG_UNLOCK(input->stateLock); + return true; + } + wheelY = button == 4 ? 1 : -1; + } + + const uint32_t mask = networkInputButtonMask(button); + if (!mask && !wheelY) + { + LG_UNLOCK(input->stateLock); + return false; + } + + const uint32_t buttons = down ? + input->mouseButtons | mask : input->mouseButtons & ~mask; + bool result; + if (input->mouseAbsolute && input->absoluteWidth && + input->absoluteHeight) + result = networkInputSendAbsoluteNL(input, + input->absoluteX, input->absoluteY, + input->absoluteWidth, input->absoluteHeight, + 0, wheelY, buttons, mask, false); + else + result = networkInputSendRelativeNL(input, 0, 0, 0, wheelY, + buttons, mask, false); + if (result && mask) + input->mouseButtons = buttons; + else if (!result && !down && mask) + notify = networkInputAbandonClaimNL(input, true); + LG_UNLOCK(input->stateLock); + if (notify) + networkInputNotifyStatus(input); + return result; +} + +static bool networkInputMousePress(void * opaque, unsigned int button) +{ + return networkInputUpdateButton(opaque, button, true); +} + +static bool networkInputMouseRelease(void * opaque, unsigned int button) +{ + return networkInputUpdateButton(opaque, button, false); +} + +static void networkInputReset(void * opaque) +{ + NetworkInput * input = opaque; + bool notify = false; + LG_LOCK(input->stateLock); + notify = networkInputSendResetNL(input); + LG_UNLOCK(input->stateLock); + if (notify) + networkInputNotifyStatus(input); +} + +static const LG_InputOps NETWORK_INPUT_OPS = +{ + .name = "Network", + .supports = networkInputSupports, + .setStatusListener = networkInputSetStatusListener, + .keyDown = networkInputKeyDown, + .keyUp = networkInputKeyUp, + .keyboardLEDs = networkInputKeyboardLEDs, + .keyboardLEDsReset = networkInputKeyboardLEDsReset, + .mouseMotion = networkInputMouseMotion, + .mousePosition = networkInputMousePosition, + .mousePress = networkInputMousePress, + .mouseRelease = networkInputMouseRelease, + .reset = networkInputReset, +}; + +bool networkInputCreate(NetworkInput ** result, + NetworkConnection * connection) +{ + if (!result || !connection) + return false; + + NetworkInput * input = calloc(1, sizeof(*input)); + if (!input) + return false; + + LG_LOCK_INIT(input->stateLock); + input->connection = connection; + input->claimantID = nanotime() ^ + ((uint64_t)(uintptr_t)input << 17); + input->statusGeneration = 1; + if (!input->claimantID) + input->claimantID = 1; + + input->statusIdle = lgCreateEvent(false, 0); + if (!input->statusIdle) + { + LG_LOCK_FREE(input->stateLock); + free(input); + return false; + } + lgSignalEvent(input->statusIdle); + + if (!lgCreateTimer(NETWORK_INPUT_TIMER_MS, + networkInputTimer, input, &input->timer)) + { + lgFreeEvent(input->statusIdle); + LG_LOCK_FREE(input->stateLock); + free(input); + return false; + } + + *result = input; + return true; +} + +void networkInputDestroy(NetworkInput ** handle) +{ + if (!handle || !*handle) + return; + + NetworkInput * input = *handle; + LG_LOCK(input->stateLock); + input->destroying = true; + LG_UNLOCK(input->stateLock); + lgTimerDestroy(input->timer); + + networkInputSetStatusListener(input, NULL, NULL); + LG_LOCK(input->stateLock); + networkInputSendResetNL(input); + networkInputSendReleaseNL(input); + input->connected = false; + input->serviceAvailable = false; + networkInputSetAvailableNL(input, false); + LG_UNLOCK(input->stateLock); + + lgFreeEvent(input->statusIdle); + LG_LOCK_FREE(input->stateLock); + free(input); + *handle = NULL; +} + +const LG_InputOps * networkInputGetOps(void) +{ + return &NETWORK_INPUT_OPS; +} + +void networkInputConnectionState(NetworkInput * input, bool connected) +{ + if (!input) + return; + + uint16_t serviceVersion = 0; + const bool serviceAvailable = connected && + networkConnectionServiceAvailable(input->connection, + LG_NET_SERVICE_INPUT, LG_NET_CAPABILITY_RELIABLE) && + networkConnectionServiceVersion(input->connection, + LG_NET_SERVICE_INPUT, &serviceVersion); + bool notify = false; + + LG_LOCK(input->stateLock); + if (!connected || !serviceAvailable) + { + notify |= networkInputSendResetNL(input); + networkInputSendReleaseNL(input); + input->connected = connected; + input->serviceAvailable = false; + input->serviceVersion = 0; + networkInputInvalidateLEDsNL(input); + notify |= + networkInputSetAvailableNL(input, false); + } + else + { + input->connected = true; + input->serviceAvailable = true; + input->serviceVersion = serviceVersion; + input->claimPending = false; + input->claimed = false; + input->claimFlags = 0; + input->sequence = 0; + input->nextClaimNs = 0; + networkInputClearStateNL(input); + networkInputInvalidateLEDsNL(input); + notify |= networkInputSetAvailableNL(input, false); + networkInputSendClaimNL(input); + } + LG_UNLOCK(input->stateLock); + + if (notify) + networkInputNotifyStatus(input); +} + +static bool networkInputHandleStatus(NetworkInput * input, + const LGNetPacketView * packet) +{ + LGNetInputStatus status; + if (lgNetInputStatusDecode(&status, + packet->payload, packet->payloadLength) != LG_NET_PARSE_OK) + return false; + if (packet->envelope.componentEpoch != status.claimEpoch) + return false; + + bool notify = false; + LG_LOCK(input->stateLock); + if (!input->connected || status.claimEpoch != input->claimEpoch) + { + LG_UNLOCK(input->stateLock); + return true; + } + + switch (status.status) + { + case LG_NET_INPUT_STATUS_ACCEPTED: + input->claimPending = false; + input->claimed = + (status.flags & NETWORK_INPUT_CLAIM_FLAGS) == + NETWORK_INPUT_CLAIM_FLAGS; + input->claimFlags = input->claimed ? status.flags : 0; + input->nextKeepaliveNs = networkInputDeadline( + status.leaseRemainingMs > NETWORK_INPUT_KEEPALIVE_MS ? + NETWORK_INPUT_KEEPALIVE_MS : + status.leaseRemainingMs > 1U ? + status.leaseRemainingMs / 2U : 1U); + if (!input->claimed) + { + input->nextClaimNs = networkInputDeadline( + NETWORK_INPUT_CLAIM_RETRY_MS); + networkInputClearStateNL(input); + networkInputInvalidateLEDsNL(input); + } + notify = networkInputSetAvailableNL(input, input->claimed); + break; + + case LG_NET_INPUT_STATUS_UNSUPPORTED: + input->claimPending = false; + input->claimed = false; + input->claimFlags = 0; + input->serviceAvailable = false; + networkInputClearStateNL(input); + networkInputInvalidateLEDsNL(input); + notify = networkInputSetAvailableNL(input, false); + break; + + case LG_NET_INPUT_STATUS_REJECTED: + case LG_NET_INPUT_STATUS_RELEASED: + case LG_NET_INPUT_STATUS_EXPIRED: + case LG_NET_INPUT_STATUS_ERROR: + input->claimPending = false; + input->claimed = false; + input->claimFlags = 0; + networkInputClearStateNL(input); + networkInputInvalidateLEDsNL(input); + input->nextClaimNs = networkInputDeadline( + NETWORK_INPUT_CLAIM_RETRY_MS); + notify = networkInputSetAvailableNL(input, false); + break; + } + LG_UNLOCK(input->stateLock); + + if (notify) + networkInputNotifyStatus(input); + return true; +} + +static bool networkInputHandleLEDs(NetworkInput * input, + const LGNetPacketView * packet) +{ + LGNetInputLEDs leds; + if (lgNetInputLEDsDecode(&leds, + packet->payload, packet->payloadLength) != LG_NET_PARSE_OK) + return false; + + bool notify = false; + LG_LOCK(input->stateLock); + if (!input->connected || !input->claimed || + packet->envelope.componentEpoch != input->claimEpoch || + leds.sequence <= input->keyboardLEDSequence) + { + LG_UNLOCK(input->stateLock); + return true; + } + + const bool oldValid = input->keyboardLEDsValid; + const uint8_t oldState = input->keyboardLEDs; + const uint32_t oldValidMask = input->keyboardLEDsValidMask; + const uint32_t mergedState = + (input->keyboardLEDs & ~leds.validMask) | leds.state; + const uint32_t changed = + (input->keyboardLEDs ^ mergedState) & + leds.validMask & oldValidMask; + + input->keyboardLEDSequence = leds.sequence; + input->keyboardLEDsValidMask |= leds.validMask; + input->keyboardLEDs = (uint8_t)mergedState; + input->keyboardLEDsValid = + (input->keyboardLEDsValidMask & NETWORK_INPUT_LED_MASK) == + NETWORK_INPUT_LED_MASK; + networkInputAcknowledgeLEDsNL(input, changed); + networkInputSyncKeyboardLEDsNL(input, ¬ify); + notify |= oldValid != input->keyboardLEDsValid || + oldState != input->keyboardLEDs; + if (notify) + input->statusNotifyPending = true; + LG_UNLOCK(input->stateLock); + + if (notify) + networkInputNotifyStatus(input); + return true; +} + +bool networkInputPacket(NetworkInput * input, + const LGNetPacketView * packet, bool datagram) +{ + if (!input || !packet || datagram || + packet->envelope.service != LG_NET_SERVICE_INPUT) + return false; + + switch (packet->envelope.messageType) + { + case LG_NET_INPUT_MESSAGE_CLAIM_RESULT: + if (!(packet->envelope.flags & LG_NET_ENVELOPE_RESPONSE) || + packet->envelope.requestID != packet->envelope.componentEpoch) + return false; + return networkInputHandleStatus(input, packet); + + case LG_NET_INPUT_MESSAGE_STATUS: + return networkInputHandleStatus(input, packet); + + case LG_NET_INPUT_MESSAGE_KEYBOARD_LEDS: + return networkInputHandleLEDs(input, packet); + + default: + return false; + } +} diff --git a/client/transports/Network/input.h b/client/transports/Network/input.h new file mode 100644 index 00000000..83eb34d5 --- /dev/null +++ b/client/transports/Network/input.h @@ -0,0 +1,41 @@ +/** + * 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 + */ + +#pragma once + +#include "connection.h" + +#include "interface/input.h" + +#include + +#include + +typedef struct NetworkInput NetworkInput; + +bool networkInputCreate(NetworkInput ** input, + NetworkConnection * connection); +void networkInputDestroy(NetworkInput ** input); + +const LG_InputOps * networkInputGetOps(void); + +void networkInputConnectionState(NetworkInput * input, bool connected); +bool networkInputPacket(NetworkInput * input, + const LGNetPacketView * packet, bool datagram); diff --git a/client/transports/Network/network.c b/client/transports/Network/network.c index d02f68ba..4926796e 100644 --- a/client/transports/Network/network.c +++ b/client/transports/Network/network.c @@ -20,6 +20,7 @@ #include "network.h" #include "connection.h" +#include "input.h" #include "known_hosts.h" #include "app.h" @@ -39,6 +40,24 @@ #define NETWORK_ADDRESS_MAX 255U #define NETWORK_ENDPOINT_TEXT_SIZE (NETWORK_ADDRESS_MAX + 9U) +#define NETWORK_MAX_PAYLOAD (1024U * 1024U) +#define NETWORK_MAX_DATAGRAM 1136U + +static const LGNetCapability NETWORK_CAPABILITIES[] = +{ + { + .service = LG_NET_SERVICE_INPUT, + .versionMin = LG_NET_INPUT_VERSION_MIN, + .versionMax = LG_NET_INPUT_VERSION_CURRENT, + .flags = LG_NET_CAPABILITY_RELIABLE | + LG_NET_CAPABILITY_DATAGRAM, + .features = 0, + .maxPayloadLength = NETWORK_MAX_PAYLOAD, + .maxDatagramPayload = NETWORK_MAX_DATAGRAM, + .maxBidirectionalStreams = 0, + .maxUnidirectionalStreams = 0, + }, +}; struct NetworkTrustPrompt { @@ -50,6 +69,7 @@ struct NetworkTrustPrompt struct LG_Transport { NetworkConnection * connection; + NetworkInput * input; NetworkKnownHosts knownHosts; LGEvent * frameEvent; LGEvent * promptIdle; @@ -252,6 +272,8 @@ static void networkConnectionState(void * opaque, bool connected, const LG_TransportStatus status = connected ? LG_TRANSPORT_UNAVAILABLE : reason; + networkInputConnectionState(this->input, connected); + LG_LOCK(this->stateLock); networkSetUnavailable(&this->videoStatus.frame, status); networkSetUnavailable(&this->videoStatus.pointer, status); @@ -264,11 +286,13 @@ static void networkConnectionState(void * opaque, bool connected, static void networkPacket(void * opaque, const LGNetPacketView * packet, bool datagram) { - (void)opaque; - (void)packet; - (void)datagram; - /* Media and service dispatch is added behind this boundary in the next - * implementation stage. The connection still validates all wire framing. */ + LG_Transport * this = opaque; + if (packet->envelope.service == LG_NET_SERVICE_INPUT && + !networkInputPacket(this->input, packet, datagram)) + { + DEBUG_ERROR("Invalid network input message"); + networkConnectionCancel(this->connection); + } } static bool networkCreate(LG_Transport ** result) @@ -312,23 +336,30 @@ static bool networkCreate(LG_Transport ** result) const NetworkConnectionParams params = { - .address = address, - .port = port, - .password = option_get_string("network", "password"), - .knownHosts = &this->knownHosts, - .trustPrompt = networkTrustPrompt, - .trustCancel = networkCancelTrustPrompt, - .packet = networkPacket, - .state = networkConnectionState, - .opaque = this, + .address = address, + .port = port, + .password = option_get_string("network", "password"), + .knownHosts = &this->knownHosts, + .trustPrompt = networkTrustPrompt, + .trustCancel = networkCancelTrustPrompt, + .packet = networkPacket, + .state = networkConnectionState, + .capabilities = NETWORK_CAPABILITIES, + .capabilityCount = sizeof(NETWORK_CAPABILITIES) / + sizeof(NETWORK_CAPABILITIES[0]), + .opaque = this, }; if (!networkConnectionCreate(&this->connection, ¶ms)) goto fail; + if (!networkInputCreate(&this->input, this->connection)) + goto fail; *result = this; return true; fail: + networkInputDestroy(&this->input); + networkConnectionDestroy(&this->connection); if (this->frameEvent) lgFreeEvent(this->frameEvent); if (this->promptIdle) @@ -358,6 +389,7 @@ static void networkDestroy(LG_Transport ** transport) LG_UNLOCK(this->stateLock); LG_UNLOCK(this->callbackLock); + networkInputDestroy(&this->input); networkConnectionDestroy(&this->connection); lgFreeEvent(this->frameEvent); lgFreeEvent(this->promptIdle); @@ -385,6 +417,7 @@ static LG_TransportStatus networkConnect(LG_Transport * this, static void networkDisconnect(LG_Transport * this) { + networkInputConnectionState(this->input, false); networkConnectionDisconnect(this->connection); lgSignalEvent(this->frameEvent); } @@ -512,9 +545,8 @@ static const LG_VideoOps * networkGetVideoOps(LG_Transport * this) static const LG_InputOps * networkGetInputOps(LG_Transport * this, void ** opaque) { - (void)this; - *opaque = NULL; - return NULL; + *opaque = this->input; + return networkInputGetOps(); } static const LG_AudioOps * networkGetAudioOps(LG_Transport * this, diff --git a/idd/LGCommon/LGCommon.vcxproj b/idd/LGCommon/LGCommon.vcxproj index 523f4c20..f73fb647 100644 --- a/idd/LGCommon/LGCommon.vcxproj +++ b/idd/LGCommon/LGCommon.vcxproj @@ -72,6 +72,7 @@ + diff --git a/idd/LGCommon/LGCommon.vcxproj.filters b/idd/LGCommon/LGCommon.vcxproj.filters index 04854c7b..e737b746 100644 --- a/idd/LGCommon/LGCommon.vcxproj.filters +++ b/idd/LGCommon/LGCommon.vcxproj.filters @@ -33,6 +33,9 @@ Source Files + + Source Files + Source Files diff --git a/idd/LGIdd/transport/network/CNetworkServer.cpp b/idd/LGIdd/transport/network/CNetworkServer.cpp index 50ca75ce..6efffe23 100644 --- a/idd/LGIdd/transport/network/CNetworkServer.cpp +++ b/idd/LGIdd/transport/network/CNetworkServer.cpp @@ -27,7 +27,11 @@ #include "CDebug.h" #include "CSRWLock.h" #include -#include +#include +#include +#include +#include +#include #include #include @@ -49,10 +53,24 @@ namespace static constexpr uint32_t NETWORK_INITIAL_RTT_MS = 1; static constexpr uint16_t NETWORK_PEER_BIDI_STREAMS = 16; static constexpr uint16_t NETWORK_PEER_UNIDI_STREAMS = 16; - static constexpr size_t NETWORK_MAX_HEADER_SIZE = 4096; static constexpr size_t NETWORK_MAX_CONTROL_PAYLOAD = 1024 * 1024; static constexpr size_t NETWORK_MAX_CONTROL_BUFFER = - NETWORK_MAX_HEADER_SIZE + NETWORK_MAX_CONTROL_PAYLOAD; + LG_NET_ENVELOPE_WIRE_SIZE + NETWORK_MAX_CONTROL_PAYLOAD; + static constexpr uint32_t NETWORK_MAX_DATAGRAM_PAYLOAD = 1136; + static constexpr uint32_t NETWORK_INPUT_LEASE_MS = 500; + static constexpr uint8_t NETWORK_KEYBOARD_ROLLOVER = 0x01; + static constexpr uint16_t NETWORK_KEYBOARD_USAGE_PAGE = 0x07; + static constexpr size_t NETWORK_KEYBOARD_KEY_COUNT = 6; + static constexpr size_t NETWORK_SERVICE_COUNT = + LG_NET_SERVICE_USB + 1U; + static constexpr uint64_t NETWORK_SEQUENCE_COMPONENT = UINT64_C(1); + static constexpr uint8_t NETWORK_DATAGRAM_WINDOW = 64; + static constexpr uint32_t NETWORK_KEYBOARD_LED_MASK = + LG_NET_KEYBOARD_LED_NUM_LOCK | + LG_NET_KEYBOARD_LED_CAPS_LOCK | + LG_NET_KEYBOARD_LED_SCROLL_LOCK | + LG_NET_KEYBOARD_LED_COMPOSE | + LG_NET_KEYBOARD_LED_KANA; static constexpr QUIC_UINT62 NETWORK_ERROR_PROTOCOL = 1; static constexpr QUIC_UINT62 NETWORK_ERROR_SERVER_STOPPING = 2; @@ -60,20 +78,6 @@ namespace static const wchar_t NETWORK_REGISTRY_KEY[] = L"Software\\LookingGlass\\IDD"; - uint16_t ReadU16(const uint8_t * data) - { - return static_cast(data[0]) | - static_cast(static_cast(data[1]) << 8); - } - - uint32_t ReadU32(const uint8_t * data) - { - return static_cast(data[0]) | - (static_cast(data[1]) << 8) | - (static_cast(data[2]) << 16) | - (static_cast(data[3]) << 24); - } - bool FillRandom(void * data, size_t size) { if (!data || size > ULONG_MAX) @@ -157,6 +161,7 @@ struct CNetworkServer::Impl enum class HandshakeState { WAITING_HELLO, + WAITING_CAPABILITIES, READY, }; @@ -166,20 +171,49 @@ struct CNetworkServer::Impl Impl& owner; CSRWLock lock; - HQUIC handle = nullptr; - HQUIC controlStream = nullptr; - uint32_t client = 0; - uint32_t generation = 0; - uint64_t sessionEpoch = 0; - uint64_t sendSequence = 0; - uint64_t receiveSequence = 0; - uint32_t maxPayload = + HQUIC handle = nullptr; + HQUIC controlStream = nullptr; + uint32_t client = 0; + uint32_t generation = 0; + uint64_t sessionEpoch = 0; + uint64_t sendSequence = 0; + uint64_t datagramSendSequence = 0; + uint64_t receiveSequence = 0; + uint64_t handshakeRequestID = 0; + uint32_t maxPayload = static_cast(NETWORK_MAX_CONTROL_PAYLOAD); - bool closing = false; - HandshakeState handshake = + uint32_t maxDatagramPayload = 0; + uint16_t maxDatagramSendLength = 0; + uint32_t expectedCapabilities = 0; + uint32_t receivedCapabilities = 0; + uint16_t offeredServices = 0; + uint16_t selectedServices = 0; + uint16_t reliableServices = 0; + uint16_t datagramServices = 0; + LGNetCapability offeredCapabilities [NETWORK_SERVICE_COUNT] = {}; + LGNetCapability selectedCapabilities[NETWORK_SERVICE_COUNT] = {}; + bool datagramSendEnabled = false; + bool closing = false; + HandshakeState handshake = HandshakeState::WAITING_HELLO; + uint64_t inputClaimantID = 0; + uint64_t inputClaimEpoch = 0; + uint64_t inputReliableSequence = 0; + uint64_t inputDatagramSequence = 0; + uint64_t inputLEDSequence = 0; + LGNetInputClaimFlags inputFlags = 0; + LGNetPointerButtons inputPointerButtons = 0; + LGNetKeyboardLEDs inputLEDState = 0; + uint8_t keyboardModifiers = 0; + bool inputLEDStateKnown = false; + bool keyboardKeys[256] = {}; + uint8_t clientNonce[LG_NET_HELLO_NONCE_LENGTH] = {}; + uint8_t serverNonce[LG_NET_HELLO_NONCE_LENGTH] = {}; std::shared_ptr session; + LGNetSequenceTracker datagramReceive = {}; + LGNetStreamParser receiveParser = {}; std::vector receiveData; + std::vector receivePayload; }; struct CallbackGuard @@ -281,11 +315,21 @@ struct CNetworkServer::Impl } bool SendPacket(Connection& connection, LGNetService service, - uint16_t messageType, LGNetEnvelopeFlags flags, uint64_t requestID, - const void * payload, uint32_t payloadLength) + uint16_t messageType, LGNetEnvelopeFlags flags, + uint64_t componentEpoch, uint64_t requestID, const void * payload, + uint32_t payloadLength) { - if ((payloadLength && !payload) || - payloadLength > connection.maxPayload) + const LGNetServiceVersionInfo * version = + lgNetServiceVersionInfo(service); + const LGNetMessageRoute * route = + lgNetMessageRoute(service, messageType); + if (!version || + !lgNetMessageRouteAllows(route, LG_NET_ROLE_SERVER, + LG_NET_ROUTE_DELIVERY_RELIABLE) || + (payloadLength && !payload) || + payloadLength > NETWORK_MAX_CONTROL_PAYLOAD || + flags & (LG_NET_ENVELOPE_RELIABLE | LG_NET_ENVELOPE_DATAGRAM | + LG_NET_ENVELOPE_EARLY_DATA)) return false; std::unique_ptr send(new (std::nothrow) Send()); @@ -302,29 +346,141 @@ struct CNetworkServer::Impl return false; } - LGNetEnvelope envelope; - lgNetEnvelopeInit(&envelope, service, messageType, - LG_NET_CORE_VERSION_CURRENT, LG_NET_MESSAGE_VERSION_CURRENT); - envelope.flags = flags | LG_NET_ENVELOPE_RELIABLE; - envelope.payloadLength = payloadLength; - envelope.requestID = requestID; - - send->buffer.Buffer = send->data.data(); - send->buffer.Length = static_cast(send->data.size()); QUIC_STATUS status; { CSRWExclusiveLock guard(connection.lock); - if (connection.closing || !connection.controlStream) + const uint16_t serviceMask = UINT16_C(1) << service; + if (connection.closing || !connection.controlStream || + (service != LG_NET_SERVICE_CORE && + (!(connection.selectedServices & serviceMask) || + !(connection.reliableServices & serviceMask)))) return false; - if (connection.sendSequence == UINT64_MAX) + const uint32_t maxPayload = service == LG_NET_SERVICE_CORE ? + connection.maxPayload : + connection.selectedCapabilities[service].maxPayloadLength; + const uint16_t serviceVersion = service == LG_NET_SERVICE_CORE ? + version->current : + connection.selectedCapabilities[service].versionMin; + if (payloadLength > maxPayload || + !lgNetServiceVersionSupported(service, serviceVersion) || + serviceVersion < route->serviceVersionMin || + connection.sendSequence == UINT64_MAX) return false; - envelope.sessionEpoch = connection.sessionEpoch; - envelope.sequence = ++connection.sendSequence; - if (!lgNetPacketEncode(send->data.data(), send->data.size(), - &envelope, payload)) + + LGNetPacketParameters parameters; + lgNetPacketParametersInit(¶meters, service, messageType, + serviceVersion, route->messageVersion); + parameters.flags = flags | LG_NET_ENVELOPE_RELIABLE; + parameters.sessionEpoch = connection.sessionEpoch; + parameters.componentEpoch = componentEpoch; + parameters.sequence = ++connection.sendSequence; + parameters.requestID = requestID; + + size_t wireSize; + if (!lgNetPacketConstruct(send->data.data(), send->data.size(), + &wireSize, ¶meters, payload, payloadLength) || + wireSize > UINT32_MAX) + { + --connection.sendSequence; return false; + } + + send->buffer.Buffer = send->data.data(); + send->buffer.Length = static_cast(wireSize); status = api->StreamSend(connection.controlStream, &send->buffer, 1, QUIC_SEND_FLAG_NONE, send.get()); + if (QUIC_FAILED(status)) + --connection.sendSequence; + } + if (QUIC_FAILED(status)) + return false; + send.release(); + return true; + } + + bool SendDatagram(Connection& connection, LGNetService service, + uint16_t messageType, LGNetEnvelopeFlags flags, + uint64_t componentEpoch, uint64_t requestID, const void * payload, + uint32_t payloadLength, bool discardIfBlocked) + { + const LGNetServiceVersionInfo * version = + lgNetServiceVersionInfo(service); + const LGNetMessageRoute * route = + lgNetMessageRoute(service, messageType); + if (!version || + !lgNetMessageRouteAllows(route, LG_NET_ROLE_SERVER, + LG_NET_ROUTE_DELIVERY_DATAGRAM) || + (payloadLength && !payload) || + payloadLength > NETWORK_MAX_DATAGRAM_PAYLOAD || + flags & (LG_NET_ENVELOPE_RELIABLE | LG_NET_ENVELOPE_DATAGRAM | + LG_NET_ENVELOPE_EARLY_DATA)) + return false; + + std::unique_ptr send(new (std::nothrow) Send()); + if (!send) + return false; + + const size_t packetSize = LG_NET_ENVELOPE_WIRE_SIZE + payloadLength; + try + { + send->data.resize(packetSize); + } + catch (...) + { + return false; + } + + QUIC_STATUS status; + { + CSRWExclusiveLock guard(connection.lock); + const uint16_t serviceMask = UINT16_C(1) << service; + const bool serviceReady = service == LG_NET_SERVICE_CORE || + ((connection.selectedServices & serviceMask) && + (connection.datagramServices & serviceMask)); + const uint32_t maxPayload = service == LG_NET_SERVICE_CORE ? + connection.maxDatagramPayload : + connection.selectedCapabilities[service].maxDatagramPayload; + const uint16_t serviceVersion = service == LG_NET_SERVICE_CORE ? + version->current : + connection.selectedCapabilities[service].versionMin; + if (connection.closing || !connection.handle || + connection.handshake != HandshakeState::READY || + !serviceReady || !connection.datagramSendEnabled || + !maxPayload || payloadLength > maxPayload || + packetSize > connection.maxDatagramSendLength || + !lgNetServiceVersionSupported(service, serviceVersion) || + serviceVersion < route->serviceVersionMin || + connection.datagramSendSequence == UINT64_MAX) + return false; + + LGNetPacketParameters parameters; + lgNetPacketParametersInit(¶meters, service, messageType, + serviceVersion, route->messageVersion); + parameters.flags = flags | LG_NET_ENVELOPE_DATAGRAM | + LG_NET_ENVELOPE_FINAL; + parameters.sessionEpoch = connection.sessionEpoch; + parameters.componentEpoch = componentEpoch; + parameters.sequence = ++connection.datagramSendSequence; + parameters.requestID = requestID; + + size_t wireSize; + if (!lgNetPacketConstruct(send->data.data(), send->data.size(), + &wireSize, ¶meters, payload, payloadLength) || + wireSize > UINT32_MAX) + { + --connection.datagramSendSequence; + return false; + } + + send->buffer.Buffer = send->data.data(); + send->buffer.Length = static_cast(wireSize); + QUIC_SEND_FLAGS sendFlags = QUIC_SEND_FLAG_PRIORITY_WORK; + if (discardIfBlocked) + sendFlags |= QUIC_SEND_FLAG_CANCEL_ON_BLOCKED; + status = api->DatagramSend(connection.handle, &send->buffer, 1, + sendFlags, send.get()); + if (QUIC_FAILED(status)) + --connection.datagramSendSequence; } if (QUIC_FAILED(status)) return false; @@ -342,7 +498,7 @@ struct CNetworkServer::Impl LGNetHello hello; if (lgNetHelloDecode(&hello, packet.payload, packet.payloadLength) != LG_NET_PARSE_OK || - hello.role != LG_NET_ROLE_CLIENT || hello.capabilityCount != 0 || + hello.role != LG_NET_ROLE_CLIENT || hello.protocolMin > LG_NET_PROTOCOL_VERSION_CURRENT || hello.protocolMax < LG_NET_PROTOCOL_VERSION_CURRENT || (hello.authModes & ~LG_NET_AUTH_MODES_KNOWN) != 0 || @@ -350,13 +506,18 @@ struct CNetworkServer::Impl return false; uint64_t sessionEpoch = 0; - uint8_t nonce[LG_NET_HELLO_NONCE_LENGTH]; + uint8_t serverNonce[LG_NET_HELLO_NONCE_LENGTH]; if (!FillRandom(&sessionEpoch, sizeof(sessionEpoch)) || - !FillRandom(nonce, sizeof(nonce))) + !FillRandom(serverNonce, sizeof(serverNonce))) return false; if (!sessionEpoch) sessionEpoch = GetTickCount64() | 1; + LGNetSequenceTracker datagramReceive; + if (!lgNetSequenceTrackerInit(&datagramReceive, sessionEpoch, + NETWORK_SEQUENCE_COMPONENT, NETWORK_DATAGRAM_WINDOW, false)) + return false; + std::shared_ptr session = transport.CreateSession( connection.client, connection.generation); if (!session) @@ -370,31 +531,198 @@ struct CNetworkServer::Impl transport.RemoveSession(connection.client, connection.generation); return false; } - connection.session = std::move(session); - connection.sessionEpoch = sessionEpoch; - connection.maxPayload = hello.maxPayloadLength < + connection.session = std::move(session); + connection.sessionEpoch = sessionEpoch; + connection.datagramReceive = datagramReceive; + connection.handshakeRequestID = packet.envelope.requestID; + connection.expectedCapabilities = hello.capabilityCount; + connection.receivedCapabilities = 0; + connection.maxPayload = hello.maxPayloadLength < NETWORK_MAX_CONTROL_PAYLOAD ? hello.maxPayloadLength : static_cast(NETWORK_MAX_CONTROL_PAYLOAD); + connection.maxDatagramPayload = hello.maxDatagramPayload < + NETWORK_MAX_DATAGRAM_PAYLOAD ? hello.maxDatagramPayload : + NETWORK_MAX_DATAGRAM_PAYLOAD; + connection.offeredServices = 0; + connection.selectedServices = 0; + connection.reliableServices = 0; + connection.datagramServices = 0; + memcpy(connection.clientNonce, hello.nonce, + sizeof(connection.clientNonce)); + memcpy(connection.serverNonce, serverNonce, + sizeof(connection.serverNonce)); + connection.handshake = + HandshakeState::WAITING_CAPABILITIES; + } + + return true; + } + + bool HandleCapability(Connection& connection, + const LGNetPacketView& packet) + { + LGNetCapability capability; + if (lgNetCapabilityDecode(&capability, packet.payload, + packet.payloadLength) != LG_NET_PARSE_OK || + capability.service <= LG_NET_SERVICE_CORE || + capability.service >= LG_NET_SERVICE_USB || + capability.flags & LG_NET_CAPABILITY_SELECTED) + return false; + + const LGNetServiceVersionInfo * version = + lgNetServiceVersionInfo(capability.service); + if (!version || capability.versionMax < version->minimum || + capability.versionMin > version->maximum) + return false; + + const uint16_t serviceMask = UINT16_C(1) << capability.service; + CSRWExclusiveLock guard(connection.lock); + if (connection.closing || + connection.handshake != HandshakeState::WAITING_CAPABILITIES || + packet.envelope.requestID != connection.handshakeRequestID || + packet.envelope.sessionEpoch != 0 || + packet.envelope.componentEpoch != 0 || + connection.receivedCapabilities >= + connection.expectedCapabilities || + connection.offeredServices & serviceMask) + return false; + + connection.offeredCapabilities[capability.service] = capability; + connection.offeredServices |= serviceMask; + ++connection.receivedCapabilities; + return true; + } + + bool SelectCapability(const Connection& connection, + const LGNetCapability& offered, LGNetCapability& selected) const + { + const LGNetServiceVersionInfo * version = + lgNetServiceVersionInfo(offered.service); + if (offered.service != LG_NET_SERVICE_INPUT || + !version || + !transport.ServiceEnabled(offered.service) || + !(offered.flags & LG_NET_CAPABILITY_RELIABLE) || + offered.versionMin > version->current || + offered.versionMax < version->current || + offered.maxPayloadLength < LG_NET_INPUT_ABSOLUTE_WIRE_SIZE || + connection.maxPayload < LG_NET_INPUT_ABSOLUTE_WIRE_SIZE) + return false; + + selected = {}; + selected.service = offered.service; + selected.versionMin = version->current; + selected.versionMax = version->current; + selected.flags = LG_NET_CAPABILITY_SELECTED | + LG_NET_CAPABILITY_RELIABLE; + selected.features = 0; + selected.maxPayloadLength = offered.maxPayloadLength < + connection.maxPayload ? offered.maxPayloadLength : + connection.maxPayload; + selected.maxDatagramPayload = 0; + selected.maxBidirectionalStreams = 0; + selected.maxUnidirectionalStreams = 0; + if ((offered.flags & LG_NET_CAPABILITY_DATAGRAM) && + offered.maxDatagramPayload >= LG_NET_INPUT_ABSOLUTE_WIRE_SIZE && + connection.maxDatagramPayload >= LG_NET_INPUT_ABSOLUTE_WIRE_SIZE) + { + selected.flags |= LG_NET_CAPABILITY_DATAGRAM; + selected.maxDatagramPayload = offered.maxDatagramPayload < + connection.maxDatagramPayload ? offered.maxDatagramPayload : + connection.maxDatagramPayload; + } + return lgNetCapabilityValid(&selected); + } + + bool FinishCapabilities(Connection& connection, + const LGNetPacketView& packet) + { + LGNetCapability selected[NETWORK_SERVICE_COUNT] = {}; + uint32_t selectedCount = 0; + uint64_t sessionEpoch; + uint32_t maxPayload; + uint32_t maxDatagramPayload; + uint8_t serverNonce[LG_NET_HELLO_NONCE_LENGTH]; + { + CSRWExclusiveLock guard(connection.lock); + if (connection.closing || + connection.handshake != HandshakeState::WAITING_CAPABILITIES || + packet.envelope.requestID != connection.handshakeRequestID || + packet.envelope.sessionEpoch != 0 || + packet.envelope.componentEpoch != 0 || packet.payloadLength || + connection.receivedCapabilities != + connection.expectedCapabilities) + return false; + + for (LGNetService service = LG_NET_SERVICE_RECOVERY; + service < LG_NET_SERVICE_USB; ++service) + { + const uint16_t serviceMask = UINT16_C(1) << service; + if (!(connection.offeredServices & serviceMask)) + continue; + + LGNetCapability capability; + if (!SelectCapability(connection, + connection.offeredCapabilities[service], capability)) + { + if (connection.offeredCapabilities[service].flags & + LG_NET_CAPABILITY_REQUIRED) + return false; + continue; + } + + selected[service] = capability; + connection.selectedCapabilities[service] = capability; + connection.selectedServices |= serviceMask; + if (capability.flags & LG_NET_CAPABILITY_RELIABLE) + connection.reliableServices |= serviceMask; + if (capability.flags & LG_NET_CAPABILITY_DATAGRAM) + connection.datagramServices |= serviceMask; + ++selectedCount; + } + + sessionEpoch = connection.sessionEpoch; + maxPayload = connection.maxPayload; + maxDatagramPayload = connection.maxDatagramPayload; + memcpy(serverNonce, connection.serverNonce, sizeof(serverNonce)); } LGNetHelloAck acknowledgement; lgNetHelloAckInit(&acknowledgement, sessionEpoch); acknowledgement.authMode = LG_NET_AUTH_NONE; - acknowledgement.maxPayloadLength = connection.maxPayload; - acknowledgement.maxDatagramPayload = 0; - acknowledgement.capabilityCount = 0; - memcpy(acknowledgement.nonce, nonce, sizeof(nonce)); + acknowledgement.maxPayloadLength = maxPayload; + acknowledgement.maxDatagramPayload = maxDatagramPayload; + acknowledgement.capabilityCount = selectedCount; + memcpy(acknowledgement.nonce, serverNonce, sizeof(serverNonce)); uint8_t payload[LG_NET_HELLO_ACK_WIRE_SIZE]; if (!lgNetHelloAckEncode(payload, sizeof(payload), &acknowledgement) || !SendPacket(connection, LG_NET_SERVICE_CORE, LG_NET_CORE_MESSAGE_HELLO_ACK, LG_NET_ENVELOPE_RESPONSE | LG_NET_ENVELOPE_FINAL, - packet.envelope.requestID, payload, sizeof(payload)) || - !SendPacket(connection, LG_NET_SERVICE_CORE, + 0, packet.envelope.requestID, payload, sizeof(payload))) + return false; + + for (LGNetService service = LG_NET_SERVICE_RECOVERY; + service < LG_NET_SERVICE_USB; ++service) + { + if (!selected[service].service) + continue; + + uint8_t capabilityPayload[LG_NET_CAPABILITY_WIRE_SIZE]; + if (!lgNetCapabilityEncode(capabilityPayload, + sizeof(capabilityPayload), &selected[service]) || + !SendPacket(connection, LG_NET_SERVICE_CORE, + LG_NET_CORE_MESSAGE_CAPABILITY, + LG_NET_ENVELOPE_RESPONSE | LG_NET_ENVELOPE_FINAL, + 0, packet.envelope.requestID, capabilityPayload, + sizeof(capabilityPayload))) + return false; + } + + if (!SendPacket(connection, LG_NET_SERVICE_CORE, LG_NET_CORE_MESSAGE_CAPABILITIES_DONE, LG_NET_ENVELOPE_RESPONSE | LG_NET_ENVELOPE_FINAL, - packet.envelope.requestID, nullptr, 0)) + 0, packet.envelope.requestID, nullptr, 0)) return false; { @@ -408,27 +736,443 @@ struct CNetworkServer::Impl return true; } + static LGNetInputStatusCode InputStatusCode(InputResult result) + { + switch (result) + { + case InputResult::ACCEPTED: + return LG_NET_INPUT_STATUS_ACCEPTED; + + case InputResult::BUSY: + return LG_NET_INPUT_STATUS_REJECTED; + + case InputResult::UNAVAILABLE: + return LG_NET_INPUT_STATUS_UNSUPPORTED; + + case InputResult::STALE: + return LG_NET_INPUT_STATUS_EXPIRED; + } + + return LG_NET_INPUT_STATUS_ERROR; + } + + static void ClearInputStateLocked(Connection& connection, + bool clearClaim) + { + if (clearClaim) + { + connection.inputClaimantID = 0; + connection.inputClaimEpoch = 0; + connection.inputFlags = 0; + connection.inputLEDState = 0; + connection.inputLEDStateKnown = false; + } + connection.inputPointerButtons = 0; + connection.keyboardModifiers = 0; + memset(connection.keyboardKeys, 0, sizeof(connection.keyboardKeys)); + } + + static void InvalidateInputClaim(Connection& connection, + uint64_t claimEpoch) + { + CSRWExclusiveLock guard(connection.lock); + if (connection.inputClaimEpoch == claimEpoch) + ClearInputStateLocked(connection, true); + } + + bool SendInputStatus(Connection& connection, uint16_t messageType, + LGNetInputStatusCode code, uint64_t claimEpoch, uint64_t requestID, + LGNetEnvelopeFlags flags) + { + LGNetInputStatus status = {}; + { + CSRWSharedLock guard(connection.lock); + status.claimEpoch = claimEpoch; + status.lastSequence = connection.inputReliableSequence > + connection.inputDatagramSequence ? + connection.inputReliableSequence : + connection.inputDatagramSequence; + status.status = code; + status.flags = code == LG_NET_INPUT_STATUS_ACCEPTED ? + connection.inputFlags : 0; + status.leaseRemainingMs = code == LG_NET_INPUT_STATUS_ACCEPTED ? + NETWORK_INPUT_LEASE_MS : 0; + status.detail = 0; + } + + uint8_t payload[LG_NET_INPUT_STATUS_WIRE_SIZE]; + return lgNetInputStatusEncode(payload, sizeof(payload), &status) && + SendPacket(connection, LG_NET_SERVICE_INPUT, messageType, + flags | LG_NET_ENVELOPE_FINAL, claimEpoch, requestID, + payload, sizeof(payload)); + } + + bool SendInputLEDs(Connection& connection) + { + std::shared_ptr session; + uint64_t claimEpoch; + { + CSRWSharedLock guard(connection.lock); + session = connection.session; + claimEpoch = connection.inputClaimEpoch; + } + if (!session || !claimEpoch) + return false; + + const InputTargetState state = session->InputState(); + if (!state.owned || !state.keyboardLEDsValid) + return true; + + LGNetInputLEDs leds = {}; + leds.state = state.keyboardLEDs & NETWORK_KEYBOARD_LED_MASK; + leds.validMask = NETWORK_KEYBOARD_LED_MASK; + { + CSRWExclusiveLock guard(connection.lock); + if (connection.inputClaimEpoch != claimEpoch) + return false; + if (connection.inputLEDStateKnown && + connection.inputLEDState == leds.state) + return true; + if (connection.inputLEDSequence == UINT64_MAX) + return false; + leds.sequence = ++connection.inputLEDSequence; + connection.inputLEDState = leds.state; + connection.inputLEDStateKnown = true; + } + + uint8_t payload[LG_NET_INPUT_LEDS_WIRE_SIZE]; + return lgNetInputLEDsEncode(payload, sizeof(payload), &leds) && + SendPacket(connection, LG_NET_SERVICE_INPUT, + LG_NET_INPUT_MESSAGE_KEYBOARD_LEDS, LG_NET_ENVELOPE_FINAL, + claimEpoch, 0, payload, sizeof(payload)); + } + + bool HandleInputClaim(Connection& connection, + const LGNetPacketView& packet) + { + LGNetInputClaim claim; + if (lgNetInputClaimDecode(&claim, packet.payload, + packet.payloadLength) != LG_NET_PARSE_OK || + packet.envelope.componentEpoch != claim.claimEpoch || + packet.envelope.requestID != claim.claimEpoch) + return false; + + std::shared_ptr session; + { + CSRWSharedLock guard(connection.lock); + session = connection.session; + } + if (!session) + return false; + + bool hadClaim; + { + CSRWSharedLock guard(connection.lock); + hadClaim = connection.inputClaimantID != 0; + } + + InputResult result = session->ClaimInput(); + if (result == InputResult::ACCEPTED && hadClaim) + result = session->ResetInput(); + { + CSRWExclusiveLock guard(connection.lock); + const bool accepted = result == InputResult::ACCEPTED; + connection.inputClaimantID = accepted ? claim.claimantID : 0; + connection.inputClaimEpoch = accepted ? claim.claimEpoch : 0; + connection.inputReliableSequence = 0; + connection.inputDatagramSequence = 0; + connection.inputFlags = accepted ? claim.flags : 0; + connection.inputLEDState = 0; + connection.inputLEDStateKnown = false; + ClearInputStateLocked(connection, false); + } + + const LGNetInputStatusCode code = InputStatusCode(result); + return SendInputStatus(connection, LG_NET_INPUT_MESSAGE_CLAIM_RESULT, + code, claim.claimEpoch, packet.envelope.requestID, + LG_NET_ENVELOPE_RESPONSE) && + (result != InputResult::ACCEPTED || SendInputLEDs(connection)); + } + + bool ValidateInputOwner(Connection& connection, + uint64_t claimEpoch, uint64_t sequence, bool datagram, + uint64_t claimantID, LGNetInputClaimFlags requiredFlags) + { + CSRWExclusiveLock guard(connection.lock); + uint64_t& lastSequence = datagram ? + connection.inputDatagramSequence : + connection.inputReliableSequence; + if (!connection.inputClaimantID || + connection.inputClaimEpoch != claimEpoch || + (claimantID && connection.inputClaimantID != claimantID) || + (connection.inputFlags & requiredFlags) != requiredFlags || + (datagram && sequence <= connection.inputReliableSequence) || + sequence <= lastSequence) + return false; + + lastSequence = sequence; + return true; + } + + bool ValidateInputPointer(Connection& connection, uint64_t claimEpoch, + uint64_t sequence, bool datagram, LGNetPointerButtons& buttons) + { + CSRWExclusiveLock guard(connection.lock); + uint64_t& lastSequence = datagram ? + connection.inputDatagramSequence : + connection.inputReliableSequence; + if (!connection.inputClaimantID || + connection.inputClaimEpoch != claimEpoch || + !(connection.inputFlags & LG_NET_INPUT_CLAIM_POINTER) || + (datagram && sequence <= connection.inputReliableSequence) || + sequence <= lastSequence) + return false; + + lastSequence = sequence; + if (datagram) + buttons = connection.inputPointerButtons; + else + connection.inputPointerButtons = buttons; + return true; + } + + bool HandleInputControl(Connection& connection, + const LGNetPacketView& packet, bool datagram) + { + LGNetInputControl control; + if (lgNetInputControlDecode(&control, packet.payload, + packet.payloadLength) != LG_NET_PARSE_OK || + packet.envelope.componentEpoch != control.claimEpoch || + (datagram && packet.envelope.messageType != + LG_NET_INPUT_MESSAGE_KEEPALIVE)) + return false; + + if (!ValidateInputOwner(connection, control.claimEpoch, + control.sequence, datagram, control.claimantID, 0)) + return true; + + std::shared_ptr session; + { + CSRWSharedLock guard(connection.lock); + session = connection.session; + } + if (!session) + return false; + + InputResult result; + switch (packet.envelope.messageType) + { + case LG_NET_INPUT_MESSAGE_KEEPALIVE: + result = session->TouchInput(); + break; + + case LG_NET_INPUT_MESSAGE_RESET: + result = session->ResetInput(); + { + CSRWExclusiveLock guard(connection.lock); + ClearInputStateLocked(connection, false); + } + break; + + case LG_NET_INPUT_MESSAGE_RELEASE: + result = session->ReleaseInput(false); + { + CSRWExclusiveLock guard(connection.lock); + ClearInputStateLocked(connection, true); + } + break; + + default: + return false; + } + + if (result == InputResult::ACCEPTED && + packet.envelope.messageType == LG_NET_INPUT_MESSAGE_KEEPALIVE) + return SendInputLEDs(connection); + + const LGNetInputStatusCode code = + packet.envelope.messageType == LG_NET_INPUT_MESSAGE_RELEASE && + result == InputResult::ACCEPTED ? + LG_NET_INPUT_STATUS_RELEASED : InputStatusCode(result); + if (result != InputResult::ACCEPTED) + InvalidateInputClaim(connection, control.claimEpoch); + return SendInputStatus(connection, LG_NET_INPUT_MESSAGE_STATUS, + code, control.claimEpoch, 0, 0); + } + + bool HandleInputKeyboard(Connection& connection, + const LGNetPacketView& packet) + { + LGNetInputKeyboard keyboard; + if (lgNetInputKeyboardDecode(&keyboard, packet.payload, + packet.payloadLength) != LG_NET_PARSE_OK || + keyboard.usagePage != NETWORK_KEYBOARD_USAGE_PAGE || + !keyboard.usage || keyboard.usage >= 256 || + packet.envelope.componentEpoch == 0) + return false; + + if (!ValidateInputOwner(connection, packet.envelope.componentEpoch, + keyboard.sequence, false, 0, LG_NET_INPUT_CLAIM_KEYBOARD)) + return true; + + uint8_t keys[NETWORK_KEYBOARD_KEY_COUNT] = {}; + { + CSRWExclusiveLock guard(connection.lock); + connection.keyboardModifiers = + static_cast(keyboard.modifiers); + if (keyboard.usage < 224 || keyboard.usage > 231) + connection.keyboardKeys[keyboard.usage] = + (keyboard.flags & LG_NET_KEYBOARD_DOWN) != 0; + + size_t count = 0; + for (size_t usage = 1; usage < 224; ++usage) + { + if (!connection.keyboardKeys[usage]) + continue; + if (count < NETWORK_KEYBOARD_KEY_COUNT) + keys[count] = static_cast(usage); + ++count; + } + if (count > NETWORK_KEYBOARD_KEY_COUNT) + memset(keys, NETWORK_KEYBOARD_ROLLOVER, sizeof(keys)); + } + + std::shared_ptr session; + uint8_t modifiers; + { + CSRWSharedLock guard(connection.lock); + session = connection.session; + modifiers = connection.keyboardModifiers; + } + if (!session) + return false; + + const InputResult result = session->SendKeyboard(modifiers, keys); + if (result != InputResult::ACCEPTED) + { + InvalidateInputClaim(connection, packet.envelope.componentEpoch); + return SendInputStatus(connection, LG_NET_INPUT_MESSAGE_STATUS, + InputStatusCode(result), packet.envelope.componentEpoch, 0, 0); + } + return SendInputLEDs(connection); + } + + static uint16_t NormalizeAbsolute(int32_t position, uint32_t extent) + { + if (extent <= 1) + return 0; + return static_cast( + static_cast(position) * UINT16_C(0x7FFF) / + (extent - 1U)); + } + + bool HandleInputPointer(Connection& connection, + const LGNetPacketView& packet, bool datagram) + { + uint64_t sequence; + InputResult result; + std::shared_ptr session; + { + CSRWSharedLock guard(connection.lock); + session = connection.session; + } + if (!session) + return false; + + if (packet.envelope.messageType == + LG_NET_INPUT_MESSAGE_MOUSE_RELATIVE) + { + LGNetInputRelative relative; + if (lgNetInputRelativeDecode(&relative, packet.payload, + packet.payloadLength) != LG_NET_PARSE_OK || relative.wheelX || + (datagram && (relative.wheelY || relative.changedButtons))) + return false; + sequence = relative.sequence; + if (!ValidateInputPointer(connection, packet.envelope.componentEpoch, + sequence, datagram, relative.buttons)) + return true; + result = session->SendMouseRelative(relative.deltaX, relative.deltaY, + relative.wheelY, relative.buttons); + } + else + { + LGNetInputAbsolute absolute; + if (lgNetInputAbsoluteDecode(&absolute, packet.payload, + packet.payloadLength) != LG_NET_PARSE_OK || absolute.wheelX || + (datagram && (absolute.wheelY || absolute.changedButtons))) + return false; + sequence = absolute.sequence; + if (!ValidateInputPointer(connection, packet.envelope.componentEpoch, + sequence, datagram, absolute.buttons)) + return true; + result = session->SendMouseAbsolute( + NormalizeAbsolute(absolute.x, absolute.width), + NormalizeAbsolute(absolute.y, absolute.height), + absolute.wheelY, absolute.buttons); + } + + if (result == InputResult::ACCEPTED) + return true; + InvalidateInputClaim(connection, packet.envelope.componentEpoch); + return SendInputStatus(connection, LG_NET_INPUT_MESSAGE_STATUS, + InputStatusCode(result), packet.envelope.componentEpoch, 0, 0); + } + + bool HandleInput(Connection& connection, const LGNetPacketView& packet, + bool datagram) + { + if (datagram && + packet.envelope.messageType != LG_NET_INPUT_MESSAGE_KEEPALIVE && + packet.envelope.messageType != + LG_NET_INPUT_MESSAGE_MOUSE_RELATIVE && + packet.envelope.messageType != + LG_NET_INPUT_MESSAGE_MOUSE_ABSOLUTE) + return false; + + switch (packet.envelope.messageType) + { + case LG_NET_INPUT_MESSAGE_CLAIM: + return HandleInputClaim(connection, packet); + + case LG_NET_INPUT_MESSAGE_KEEPALIVE: + case LG_NET_INPUT_MESSAGE_RELEASE: + case LG_NET_INPUT_MESSAGE_RESET: + return HandleInputControl(connection, packet, datagram); + + case LG_NET_INPUT_MESSAGE_KEYBOARD: + return HandleInputKeyboard(connection, packet); + + case LG_NET_INPUT_MESSAGE_MOUSE_RELATIVE: + case LG_NET_INPUT_MESSAGE_MOUSE_ABSOLUTE: + return HandleInputPointer(connection, packet, datagram); + + default: + return false; + } + } + bool HandlePacket(Connection& connection, const LGNetPacketView& packet) { - if (packet.envelope.headerSize > NETWORK_MAX_HEADER_SIZE || + if (packet.envelope.headerSize != LG_NET_ENVELOPE_WIRE_SIZE || packet.envelope.payloadLength > NETWORK_MAX_CONTROL_PAYLOAD || - packet.envelope.messageVersion != LG_NET_MESSAGE_VERSION_CURRENT || packet.envelope.flags & (LG_NET_ENVELOPE_RESPONSE | LG_NET_ENVELOPE_DATAGRAM | LG_NET_ENVELOPE_ERROR | LG_NET_ENVELOPE_EARLY_DATA) || - !(packet.envelope.flags & LG_NET_ENVELOPE_RELIABLE) || - !(packet.envelope.flags & LG_NET_ENVELOPE_FINAL) || - !lgNetServiceKnown(packet.envelope.service) || - !lgNetMessageKnown(packet.envelope.service, - packet.envelope.messageType)) + (packet.envelope.flags & + (LG_NET_ENVELOPE_RELIABLE | LG_NET_ENVELOPE_FINAL)) != + (LG_NET_ENVELOPE_RELIABLE | LG_NET_ENVELOPE_FINAL) || + !lgNetEnvelopeRouteValid(&packet.envelope, LG_NET_ROLE_CLIENT, + LG_NET_ROUTE_DELIVERY_RELIABLE)) return false; HandshakeState handshake; uint64_t sessionEpoch; + uint32_t maxPayload; { CSRWSharedLock guard(connection.lock); handshake = connection.handshake; sessionEpoch = connection.sessionEpoch; + maxPayload = connection.maxPayload; } if (handshake == HandshakeState::WAITING_HELLO) @@ -437,70 +1181,160 @@ struct CNetworkServer::Impl packet.envelope.serviceVersion == LG_NET_CORE_VERSION_CURRENT && HandleHello(connection, packet); + if (handshake == HandshakeState::WAITING_CAPABILITIES) + { + if (packet.envelope.service != LG_NET_SERVICE_CORE || + packet.envelope.serviceVersion != LG_NET_CORE_VERSION_CURRENT || + packet.envelope.payloadLength > maxPayload) + return false; + + if (packet.envelope.messageType == LG_NET_CORE_MESSAGE_CAPABILITY) + return HandleCapability(connection, packet); + if (packet.envelope.messageType == + LG_NET_CORE_MESSAGE_CAPABILITIES_DONE) + return FinishCapabilities(connection, packet); + return false; + } + if (packet.envelope.sessionEpoch != sessionEpoch || - packet.envelope.service != LG_NET_SERVICE_CORE || - packet.envelope.serviceVersion != LG_NET_CORE_VERSION_CURRENT) + packet.envelope.payloadLength > maxPayload || + (packet.envelope.service == LG_NET_SERVICE_CORE && + packet.envelope.serviceVersion != LG_NET_CORE_VERSION_CURRENT)) return false; - if (packet.envelope.messageType == LG_NET_CORE_MESSAGE_PING) - return SendPacket(connection, LG_NET_SERVICE_CORE, - LG_NET_CORE_MESSAGE_PONG, - LG_NET_ENVELOPE_RESPONSE | LG_NET_ENVELOPE_FINAL, - packet.envelope.requestID, packet.payload, - static_cast(packet.payloadLength)); - - if (packet.envelope.messageType == LG_NET_CORE_MESSAGE_GOODBYE && - packet.payloadLength == 0) + if (packet.envelope.service == LG_NET_SERVICE_CORE) { - Shutdown(connection, 0); - return true; + if (packet.envelope.messageType == LG_NET_CORE_MESSAGE_PING) + return SendPacket(connection, LG_NET_SERVICE_CORE, + LG_NET_CORE_MESSAGE_PONG, + LG_NET_ENVELOPE_RESPONSE | LG_NET_ENVELOPE_FINAL, + 0, packet.envelope.requestID, packet.payload, + static_cast(packet.payloadLength)); + + if (packet.envelope.messageType == LG_NET_CORE_MESSAGE_GOODBYE && + packet.payloadLength == 0) + { + Shutdown(connection, 0); + return true; + } + return false; } - return false; + + const uint16_t serviceMask = UINT16_C(1) << packet.envelope.service; + { + CSRWSharedLock guard(connection.lock); + if (!(connection.selectedServices & serviceMask) || + !(connection.reliableServices & serviceMask) || + packet.envelope.serviceVersion != + connection.selectedCapabilities + [packet.envelope.service].versionMin || + packet.envelope.payloadLength > + connection.selectedCapabilities + [packet.envelope.service].maxPayloadLength) + return false; + } + + return packet.envelope.service == LG_NET_SERVICE_INPUT && + HandleInput(connection, packet, false); } - bool Receive(Connection& connection, const QUIC_BUFFER * buffers, - uint32_t count) + bool HandleDatagram(Connection& connection, const QUIC_BUFFER * buffer) { - size_t additional = 0; - for (uint32_t i = 0; i < count; ++i) - { - if (buffers[i].Length > NETWORK_MAX_CONTROL_BUFFER - additional) - return false; - additional += buffers[i].Length; - } - if (connection.receiveData.size() > - NETWORK_MAX_CONTROL_BUFFER - additional) + LGNetPacketView packet; + if (!buffer || !buffer->Buffer || + lgNetPacketDecode(&packet, buffer->Buffer, + buffer->Length) != LG_NET_PARSE_OK || + packet.wireSize != buffer->Length || + packet.envelope.headerSize != LG_NET_ENVELOPE_WIRE_SIZE || + !lgNetEnvelopeRouteValid(&packet.envelope, LG_NET_ROLE_CLIENT, + LG_NET_ROUTE_DELIVERY_DATAGRAM) || + (packet.envelope.flags & + (LG_NET_ENVELOPE_DATAGRAM | LG_NET_ENVELOPE_FINAL)) != + (LG_NET_ENVELOPE_DATAGRAM | LG_NET_ENVELOPE_FINAL) || + packet.envelope.flags & (LG_NET_ENVELOPE_RELIABLE | + LG_NET_ENVELOPE_RESPONSE | LG_NET_ENVELOPE_ERROR | + LG_NET_ENVELOPE_EARLY_DATA) || + !packet.envelope.sequence) return false; - try + const uint16_t serviceMask = UINT16_C(1) << packet.envelope.service; { - for (uint32_t i = 0; i < count; ++i) - connection.receiveData.insert(connection.receiveData.end(), - buffers[i].Buffer, buffers[i].Buffer + buffers[i].Length); - } - catch (...) - { - return false; - } - - while (connection.receiveData.size() >= LG_NET_ENVELOPE_WIRE_SIZE) - { - const uint16_t headerSize = ReadU16(connection.receiveData.data() + 8); - const uint32_t payloadSize = ReadU32(connection.receiveData.data() + 24); - if (headerSize < LG_NET_ENVELOPE_WIRE_SIZE || - headerSize > NETWORK_MAX_HEADER_SIZE || - payloadSize > NETWORK_MAX_CONTROL_PAYLOAD || - payloadSize > NETWORK_MAX_CONTROL_BUFFER - headerSize) + CSRWExclusiveLock guard(connection.lock); + const bool serviceReady = packet.envelope.service == + LG_NET_SERVICE_CORE || + ((connection.selectedServices & serviceMask) && + (connection.datagramServices & serviceMask)); + const uint32_t maxPayload = packet.envelope.service == + LG_NET_SERVICE_CORE ? connection.maxDatagramPayload : + connection.selectedCapabilities + [packet.envelope.service].maxDatagramPayload; + const uint16_t serviceVersion = packet.envelope.service == + LG_NET_SERVICE_CORE ? LG_NET_CORE_VERSION_CURRENT : + connection.selectedCapabilities + [packet.envelope.service].versionMin; + if (connection.handshake != HandshakeState::READY || + packet.envelope.sessionEpoch != connection.sessionEpoch || + !serviceReady || !maxPayload || + packet.envelope.serviceVersion != serviceVersion || + packet.envelope.payloadLength > maxPayload) return false; - const size_t packetSize = static_cast(headerSize) + payloadSize; - if (connection.receiveData.size() < packetSize) - break; - - LGNetPacketView packet; - if (lgNetPacketDecode(&packet, connection.receiveData.data(), - packetSize) != LG_NET_PARSE_OK || packet.wireSize != packetSize) + const LGNetSequenceResult sequenceResult = lgNetSequenceTrackerCheck( + &connection.datagramReceive, packet.envelope.sessionEpoch, + NETWORK_SEQUENCE_COMPONENT, packet.envelope.sequence); + if (sequenceResult == LG_NET_SEQUENCE_DUPLICATE || + sequenceResult == LG_NET_SEQUENCE_TOO_OLD) + return true; + if (!lgNetSequenceAccepted(sequenceResult)) return false; + } + + if (packet.envelope.service == LG_NET_SERVICE_CORE) + return packet.envelope.messageType == LG_NET_CORE_MESSAGE_PING && + SendDatagram(connection, LG_NET_SERVICE_CORE, + LG_NET_CORE_MESSAGE_PONG, LG_NET_ENVELOPE_RESPONSE, + 0, packet.envelope.requestID, packet.payload, + static_cast(packet.payloadLength), true); + + return packet.envelope.service == LG_NET_SERVICE_INPUT && + HandleInput(connection, packet, true); + } + + bool DrainReceive(Connection& connection) + { + for (;;) + { + LGNetStreamPacket streamPacket; + const LGNetStreamResult result = lgNetStreamParserNext( + &connection.receiveParser, &streamPacket, nullptr); + if (result == LG_NET_STREAM_NEED_DATA) + return true; + if (result != LG_NET_STREAM_PACKET) + return false; + + LGNetPacketView packet = {}; + packet.envelope = streamPacket.envelope; + packet.payloadLength = streamPacket.payloadLength; + packet.wireSize = streamPacket.wireSize; + if (!streamPacket.payload[1].size) + packet.payload = streamPacket.payload[0].data; + else + { + try + { + connection.receivePayload.resize(streamPacket.payloadLength); + } + catch (...) + { + return false; + } + memcpy(connection.receivePayload.data(), + streamPacket.payload[0].data, streamPacket.payload[0].size); + memcpy(connection.receivePayload.data() + + streamPacket.payload[0].size, streamPacket.payload[1].data, + streamPacket.payload[1].size); + packet.payload = connection.receivePayload.data(); + } { CSRWExclusiveLock guard(connection.lock); @@ -510,13 +1344,40 @@ struct CNetworkServer::Impl return false; connection.receiveSequence = packet.envelope.sequence; } - if (!HandlePacket(connection, packet)) + if (!HandlePacket(connection, packet) || + !lgNetStreamParserConsume(&connection.receiveParser)) + return false; + } + } + + bool Receive(Connection& connection, const QUIC_BUFFER * buffers, + uint32_t count) + { + if (count && !buffers) + return false; + + for (uint32_t i = 0; i < count; ++i) + { + if (buffers[i].Length && !buffers[i].Buffer) return false; - connection.receiveData.erase(connection.receiveData.begin(), - connection.receiveData.begin() + packetSize); + size_t offset = 0; + while (offset < buffers[i].Length) + { + size_t accepted; + if (!lgNetStreamParserAppend(&connection.receiveParser, + buffers[i].Buffer + offset, buffers[i].Length - offset, + &accepted)) + return false; + + offset += accepted; + if (!DrainReceive(connection) || + (!accepted && + !lgNetStreamParserWritable(&connection.receiveParser))) + return false; + } } - return true; + return DrainReceive(connection); } void ConnectionClosed(Connection& connection) @@ -557,6 +1418,22 @@ struct CNetworkServer::Impl connection = new (std::nothrow) Connection(owner); if (!connection) return QUIC_STATUS_OUT_OF_MEMORY; + try + { + connection->receiveData.resize(NETWORK_MAX_CONTROL_BUFFER); + } + catch (...) + { + delete connection; + return QUIC_STATUS_OUT_OF_MEMORY; + } + if (!lgNetStreamParserInit(&connection->receiveParser, + connection->receiveData.data(), connection->receiveData.size(), + static_cast(NETWORK_MAX_CONTROL_PAYLOAD))) + { + delete connection; + return QUIC_STATUS_OUT_OF_MEMORY; + } connection->handle = event->NEW_CONNECTION.Connection; connection->client = owner.AllocateClientLocked(); connection->generation = 1; @@ -640,7 +1517,26 @@ struct CNetworkServer::Impl } case QUIC_CONNECTION_EVENT_DATAGRAM_RECEIVED: - owner.Shutdown(connection, NETWORK_ERROR_PROTOCOL); + if (!owner.HandleDatagram( + connection, event->DATAGRAM_RECEIVED.Buffer)) + owner.Shutdown(connection, NETWORK_ERROR_PROTOCOL); + return QUIC_STATUS_SUCCESS; + + case QUIC_CONNECTION_EVENT_DATAGRAM_STATE_CHANGED: + { + CSRWExclusiveLock guard(connection.lock); + connection.datagramSendEnabled = + event->DATAGRAM_STATE_CHANGED.SendEnabled != FALSE; + connection.maxDatagramSendLength = + event->DATAGRAM_STATE_CHANGED.MaxSendLength; + return QUIC_STATUS_SUCCESS; + } + + case QUIC_CONNECTION_EVENT_DATAGRAM_SEND_STATE_CHANGED: + if (QUIC_DATAGRAM_SEND_STATE_IS_FINAL( + event->DATAGRAM_SEND_STATE_CHANGED.State)) + delete static_cast( + event->DATAGRAM_SEND_STATE_CHANGED.ClientContext); return QUIC_STATUS_SUCCESS; case QUIC_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_TRANSPORT: diff --git a/idd/LGIdd/transport/network/CNetworkTransport.cpp b/idd/LGIdd/transport/network/CNetworkTransport.cpp index 48880650..27a2c274 100644 --- a/idd/LGIdd/transport/network/CNetworkTransport.cpp +++ b/idd/LGIdd/transport/network/CNetworkTransport.cpp @@ -230,6 +230,35 @@ IInputSource * CNetworkTransport::Input() return (m_config.services & TRANSPORT_SERVICE_INPUT) ? &m_input : nullptr; } +bool CNetworkTransport::ServiceEnabled(LGNetService service) const +{ + switch (service) + { + case LG_NET_SERVICE_RECOVERY: + case LG_NET_SERVICE_CURSOR: + return (m_config.services & TRANSPORT_SERVICE_CONTROL) != 0; + + case LG_NET_SERVICE_INPUT: + return (m_config.services & TRANSPORT_SERVICE_INPUT) != 0; + + case LG_NET_SERVICE_AUDIO: + return (m_config.services & TRANSPORT_SERVICE_AUDIO) != 0; + + case LG_NET_SERVICE_CLIPBOARD: + case LG_NET_SERVICE_FILE: + return (m_config.services & TRANSPORT_SERVICE_CLIPBOARD) != 0; + + case LG_NET_SERVICE_VIDEO: + return (m_config.services & TRANSPORT_SERVICE_FRAME) != 0; + + case LG_NET_SERVICE_CORE: + case LG_NET_SERVICE_USB: + return false; + } + + return false; +} + bool CNetworkTransport::ControlSnapshot( NetworkControlSnapshot& state) const { diff --git a/idd/LGIdd/transport/network/CNetworkTransport.h b/idd/LGIdd/transport/network/CNetworkTransport.h index 16dd12c5..3608ad5b 100644 --- a/idd/LGIdd/transport/network/CNetworkTransport.h +++ b/idd/LGIdd/transport/network/CNetworkTransport.h @@ -93,6 +93,7 @@ public: IInputSource * Input() override; const NetworkSettings& Settings() const { return m_settings; } + bool ServiceEnabled(LGNetService service) const; bool ControlSnapshot(NetworkControlSnapshot& state) const; // The QUIC provider calls these only after authenticating a peer. Keeping