From 1af2f1344070063bf3d4cecaefb4d973fbf6dd3f Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Thu, 8 Oct 2026 20:34:04 +1100 Subject: [PATCH] [client/idd] input: reduce network latency Route input through a dedicated high-priority stream, send pointer motion as cumulative datagrams, and recover suspected loss reliably. Reduce downstream input queuing with age-aware coalescing and input thread scheduling. --- client/transports/Network/audio.c | 2 +- client/transports/Network/connection.c | 816 +++++++- client/transports/Network/connection.h | 6 +- client/transports/Network/input.c | 695 +++++-- client/transports/Network/input.h | 4 + client/transports/Network/network.c | 19 +- client/transports/Network/video.c | 2 +- idd/LGCommon/CPipeEndpoint.cpp | 42 +- idd/LGCommon/CPipeEndpoint.h | 24 +- idd/LGIdd/ipc/CInputPipeServer.cpp | 143 +- idd/LGIdd/ipc/CInputPipeServer.h | 27 +- idd/LGIdd/transport/CInputHub.cpp | 368 +++- idd/LGIdd/transport/CInputHub.h | 21 +- .../transport/network/CNetworkServer.cpp | 1738 +++++++++++++---- idd/LGInput/CHIDDevice.cpp | 203 +- idd/LGInput/ipc/CInputPipeClient.cpp | 6 +- repos/LGProtocol | 2 +- 17 files changed, 3359 insertions(+), 759 deletions(-) diff --git a/client/transports/Network/audio.c b/client/transports/Network/audio.c index 9365a2e2..c45d58c7 100644 --- a/client/transports/Network/audio.c +++ b/client/transports/Network/audio.c @@ -601,7 +601,7 @@ static LG_TransportStatus networkAudioSendDatagramNL(NetworkAudio * audio, { return networkConnectionSendDatagram(audio->connection, LG_NET_SERVICE_AUDIO, messageType, audio->serviceVersion, 0, - audio->subscriptionEpoch, requestID, payload, payloadLength, false); + audio->subscriptionEpoch, requestID, payload, payloadLength, 0, false); } static bool networkAudioSendSubscribeNL(NetworkAudio * audio) diff --git a/client/transports/Network/connection.c b/client/transports/Network/connection.c index 6dba878b..86c68292 100644 --- a/client/transports/Network/connection.c +++ b/client/transports/Network/connection.c @@ -58,6 +58,9 @@ (LG_NET_CURSOR_SHAPE_HEADER_WIRE_SIZE + LG_NET_CURSOR_MAX_SHAPE_LENGTH) #define NETWORK_MAX_CONTROL_BUFFER \ (2U * (LG_NET_ENVELOPE_WIRE_SIZE + NETWORK_MAX_CONTROL_PAYLOAD)) +#define NETWORK_MAX_INPUT_PAYLOAD LG_NET_INPUT_ABSOLUTE_WIRE_SIZE +#define NETWORK_MAX_INPUT_BUFFER \ + (2U * (LG_NET_ENVELOPE_WIRE_SIZE + NETWORK_MAX_INPUT_PAYLOAD)) #define NETWORK_MAX_NEGOTIATED_PAYLOAD LG_NET_MAX_PAYLOAD_LENGTH #define NETWORK_MAX_DATAGRAM_PAYLOAD 1136U #define NETWORK_HELLO_REQUEST_ID UINT64_C(1) @@ -87,18 +90,42 @@ (UINT64_C(256) * 1024U * 1024U) #define NETWORK_SEND_RESUME_NUMERATOR 3U #define NETWORK_SEND_RESUME_DENOMINATOR 4U +#define NETWORK_INPUT_SEND_POOL_COUNT 64U +#define NETWORK_INPUT_SEND_POOL_DATA_SIZE 256U _Static_assert(NETWORK_AUTH_SHA256_SIZE == LG_NET_PASSWORD_SHA256_LENGTH, "Network authentication SHA-256 sizes differ"); +_Static_assert(LG_NET_INPUT_CLAIM_WIRE_SIZE <= NETWORK_MAX_INPUT_PAYLOAD && + LG_NET_INPUT_STATUS_WIRE_SIZE <= NETWORK_MAX_INPUT_PAYLOAD && + LG_NET_INPUT_RELATIVE_WIRE_SIZE <= NETWORK_MAX_INPUT_PAYLOAD && + LG_NET_INPUT_ABSOLUTE_WIRE_SIZE <= NETWORK_MAX_INPUT_PAYLOAD && + LG_NET_INPUT_KEYBOARD_WIRE_SIZE <= NETWORK_MAX_INPUT_PAYLOAD && + LG_NET_INPUT_LEDS_WIRE_SIZE <= NETWORK_MAX_INPUT_PAYLOAD && + LG_NET_INPUT_CONTROL_WIRE_SIZE <= NETWORK_MAX_INPUT_PAYLOAD, + "Network input receive buffer is too small"); +_Static_assert(NETWORK_INPUT_SEND_POOL_DATA_SIZE >= + LG_NET_ENVELOPE_WIRE_SIZE + NETWORK_MAX_INPUT_PAYLOAD, + "Network input send pool entries are too small"); struct NetworkSend { + NetworkConnection * connection; + struct NetworkSend * next; QUIC_BUFFER buffer; LGNetService service; uint32_t length; + uint64_t componentEpoch; + uint64_t applicationSequence; NetworkPacketDelivery delivery; bool accounted; - uint8_t data[]; + bool lossReported; + bool pooled; +}; + +struct NetworkInputSend +{ + struct NetworkSend send; + uint8_t data[NETWORK_INPUT_SEND_POOL_DATA_SIZE]; }; struct NetworkSendLimits @@ -158,6 +185,7 @@ struct NetworkConnection HQUIC configuration; HQUIC connection; HQUIC controlStream; + HQUIC inputStream; LGEvent * connectEvent; LGEvent * closeEvent; @@ -165,6 +193,8 @@ struct NetworkConnection LGEvent * bulkReceiveIdle; LGThread * bulkReceiveThread; LG_Lock lock; + LG_Lock inputStreamLock; + LG_Lock inputSendPoolLock; LG_Lock stateCallbackLock; atomic_bool stopping; @@ -181,6 +211,7 @@ struct NetworkConnection NetworkPacketFn packet; NetworkConnectionStateFn state; NetworkSendReadyFn sendReady; + NetworkDatagramLostFn datagramLost; void * opaque; LGNetCapability offeredCapabilities[LG_NET_SERVICE_CONTROL + 1U]; @@ -200,6 +231,8 @@ struct NetworkConnection LG_TransportSession session; uint64_t sessionEpoch; uint64_t sendSequence; + uint64_t inputSendSequence; + uint64_t inputReceiveSequence; uint64_t datagramSendSequence; uint64_t receiveSequence; LGNetSequenceTracker datagramReceive; @@ -223,6 +256,8 @@ struct NetworkConnection uint16_t maxPeerBulkStreams; uint16_t peerBulkStreams; uint16_t serviceVersions[LG_NET_SERVICE_CONTROL + 1U]; + uint16_t serviceMaxBidirectional + [LG_NET_SERVICE_CONTROL + 1U]; uint16_t serviceMaxUnidirectional [LG_NET_SERVICE_CONTROL + 1U]; uint16_t servicePeerBulkStreams @@ -238,6 +273,10 @@ struct NetworkConnection struct NetworkSendAccounting datagramSend; struct NetworkSendAccounting bulkSend; + struct NetworkInputSend inputSendPool[NETWORK_INPUT_SEND_POOL_COUNT]; + struct NetworkSend * inputSendFree; + bool inputStreamBound; + struct NetworkBulkStream * outboundBulk [LG_NET_SERVICE_CONTROL + 1U]; struct NetworkBulkStream * inboundBulk; @@ -256,16 +295,22 @@ struct NetworkConnection LGNetStreamParser receiveParser; uint8_t * receiveData; uint8_t * receivePayload; + LGNetStreamParser inputReceiveParser; + uint8_t * inputReceiveData; + uint8_t * inputReceivePayload; }; static QUIC_STATUS QUIC_API networkConnectionCallback(HQUIC handle, void * opaque, QUIC_CONNECTION_EVENT * event); static QUIC_STATUS QUIC_API networkStreamCallback(HQUIC handle, void * opaque, QUIC_STREAM_EVENT * event); +static QUIC_STATUS QUIC_API networkInputStreamCallback(HQUIC handle, + void * opaque, QUIC_STREAM_EVENT * event); static QUIC_STATUS QUIC_API networkBulkStreamCallback(HQUIC handle, void * opaque, QUIC_STREAM_EVENT * event); static QUIC_STATUS QUIC_API networkRejectedStreamCallback(HQUIC handle, void * opaque, QUIC_STREAM_EVENT * event); +static bool networkConnectionOpenInputStream(NetworkConnection * this); static char * networkDuplicate(const char * value) { @@ -290,6 +335,61 @@ static char * networkEndpoint(const char * address, uint16_t port) return endpoint; } +static uint8_t * networkSendData(struct NetworkSend * send) +{ + if (send->pooled) + return ((struct NetworkInputSend *)send)->data; + return (uint8_t *)(send + 1); +} + +static struct NetworkSend * networkConnectionAllocSend( + NetworkConnection * this, LGNetService service, size_t dataSize) +{ + struct NetworkSend * send = NULL; + if (service == LG_NET_SERVICE_INPUT && + dataSize <= NETWORK_INPUT_SEND_POOL_DATA_SIZE) + { + LG_LOCK(this->inputSendPoolLock); + send = this->inputSendFree; + if (send) + this->inputSendFree = send->next; + LG_UNLOCK(this->inputSendPoolLock); + } + + if (!send) + { + send = malloc(sizeof(*send) + dataSize); + if (!send) + return NULL; + send->pooled = false; + } + + send->connection = this; + send->next = NULL; + send->componentEpoch = 0; + send->applicationSequence = 0; + send->accounted = false; + send->lossReported = false; + return send; +} + +static void networkConnectionFreeSend(struct NetworkSend * send) +{ + if (!send) + return; + if (!send->pooled) + { + free(send); + return; + } + + NetworkConnection * this = send->connection; + LG_LOCK(this->inputSendPoolLock); + send->next = this->inputSendFree; + this->inputSendFree = send; + LG_UNLOCK(this->inputSendPoolLock); +} + static bool networkGuestText(char * destination, size_t capacity, const uint8_t * source, uint16_t length) { @@ -347,7 +447,8 @@ static bool networkCapabilityOfferValid( capability->flags & LG_NET_CAPABILITY_SELECTED || capability->versionMin < version->minimum || capability->versionMax > version->maximum || - capability->maxBidirectionalStreams || + capability->maxBidirectionalStreams != + (capability->service == LG_NET_SERVICE_INPUT ? 1U : 0U) || capability->maxUnidirectionalStreams > NETWORK_MAX_PEER_STREAMS || (!!(capability->flags & LG_NET_CAPABILITY_BULK) != !!capability->maxUnidirectionalStreams) || @@ -735,7 +836,9 @@ static bool networkConnectionSendCanResumeNL( if (atomic_load_explicit(&this->stopping, memory_order_acquire) || !atomic_load_explicit(&this->sessionValid, memory_order_acquire) || (delivery == NETWORK_PACKET_DELIVERY_RELIABLE && - !this->controlStream) || + (service == LG_NET_SERVICE_INPUT ? + (!this->inputStream || !this->inputStreamBound) : + !this->controlStream)) || (delivery == NETWORK_PACKET_DELIVERY_DATAGRAM && (!this->datagramSendEnabled || blockedBytes > this->maxDatagramSendLength)) || @@ -791,10 +894,11 @@ static uint16_t networkConnectionReadyServicesNL( } static uint16_t networkConnectionReleaseSendNL( - NetworkConnection * this, const struct NetworkSend * send) + NetworkConnection * this, struct NetworkSend * send) { if (!send->accounted) return 0; + send->accounted = false; if (send->service >= NETWORK_SEND_SERVICE_COUNT) { @@ -847,6 +951,115 @@ static void networkConnectionNotifySendReady(NetworkConnection * this, } } +static LG_TransportStatus networkConnectionSendInputPacket( + NetworkConnection * this, uint16_t messageType, uint16_t serviceVersion, + LGNetEnvelopeFlags flags, uint64_t componentEpoch, uint64_t requestID, + const void * payload, uint32_t payloadLength) +{ + const size_t packetSize = LG_NET_ENVELOPE_WIRE_SIZE + + payloadLength; + struct NetworkSend * send = networkConnectionAllocSend( + this, LG_NET_SERVICE_INPUT, packetSize); + if (!send) + return LG_TRANSPORT_ERROR; + uint8_t * data = networkSendData(send); + + LGNetPacketParameters parameters; + lgNetPacketParametersInit(¶meters, LG_NET_SERVICE_INPUT, messageType, + serviceVersion, LG_NET_MESSAGE_VERSION_CURRENT); + parameters.flags = flags | LG_NET_ENVELOPE_RELIABLE; + parameters.componentEpoch = componentEpoch; + parameters.requestID = requestID; + + LG_LOCK(this->inputStreamLock); + LG_LOCK(this->lock); + HQUIC stream = this->inputStream; + const uint16_t inputMask = UINT16_C(1) << LG_NET_SERVICE_INPUT; + const bool serviceReady = + (this->receivedServices & inputMask) && + (this->reliableServices & inputMask) && + this->serviceVersions[LG_NET_SERVICE_INPUT] == serviceVersion && + this->inputStreamBound; + if (!stream || + !atomic_load_explicit(&this->sessionValid, memory_order_acquire)) + { + LG_UNLOCK(this->lock); + LG_UNLOCK(this->inputStreamLock); + networkConnectionFreeSend(send); + return LG_TRANSPORT_DISCONNECTED; + } + if (!serviceReady || + payloadLength > this->serviceMaxPayload[LG_NET_SERVICE_INPUT]) + { + LG_UNLOCK(this->lock); + LG_UNLOCK(this->inputStreamLock); + networkConnectionFreeSend(send); + return LG_TRANSPORT_UNAVAILABLE; + } + if (this->inputSendSequence == UINT64_MAX) + { + LG_UNLOCK(this->lock); + LG_UNLOCK(this->inputStreamLock); + networkConnectionFreeSend(send); + return LG_TRANSPORT_ERROR; + } + + const LG_TransportStatus reserveStatus = + networkConnectionReserveSendNL(this, LG_NET_SERVICE_INPUT, + NETWORK_PACKET_DELIVERY_RELIABLE, packetSize); + if (reserveStatus != LG_TRANSPORT_OK) + { + LG_UNLOCK(this->lock); + LG_UNLOCK(this->inputStreamLock); + networkConnectionFreeSend(send); + return reserveStatus; + } + + parameters.sessionEpoch = this->sessionEpoch; + parameters.sequence = ++this->inputSendSequence; + send->service = LG_NET_SERVICE_INPUT; + send->length = (uint32_t)packetSize; + send->delivery = NETWORK_PACKET_DELIVERY_RELIABLE; + send->accounted = true; + LG_UNLOCK(this->lock); + + size_t wireSize; + if (!lgNetPacketConstruct(data, packetSize, &wireSize, + ¶meters, payload, payloadLength) || wireSize != packetSize) + { + LG_LOCK(this->lock); + --this->inputSendSequence; + const uint16_t ready = networkConnectionReleaseSendNL(this, send); + LG_UNLOCK(this->lock); + LG_UNLOCK(this->inputStreamLock); + networkConnectionFreeSend(send); + networkConnectionNotifySendReady(this, ready, 0, 0); + return LG_TRANSPORT_ERROR; + } + + send->buffer.Buffer = data; + send->buffer.Length = (uint32_t)packetSize; + const QUIC_STATUS status = this->api->StreamSend(stream, + &send->buffer, 1, QUIC_SEND_FLAG_PRIORITY_WORK, send); + uint16_t ready = 0; + if (QUIC_FAILED(status)) + { + LG_LOCK(this->lock); + --this->inputSendSequence; + ready = networkConnectionReleaseSendNL(this, send); + LG_UNLOCK(this->lock); + } + LG_UNLOCK(this->inputStreamLock); + + if (QUIC_FAILED(status)) + { + networkConnectionFreeSend(send); + networkConnectionNotifySendReady(this, ready, 0, 0); + return LG_TRANSPORT_ERROR; + } + return LG_TRANSPORT_OK; +} + static LG_TransportStatus networkConnectionSendPacket( NetworkConnection * this, LGNetService service, uint16_t messageType, uint16_t serviceVersion, @@ -867,6 +1080,11 @@ static LG_TransportStatus networkConnectionSendPacket( atomic_load_explicit(&this->stopping, memory_order_acquire)) return LG_TRANSPORT_ERROR; + if (service == LG_NET_SERVICE_INPUT && !allowConnecting) + return networkConnectionSendInputPacket(this, messageType, + serviceVersion, flags, componentEpoch, requestID, + payload, payloadLength); + const size_t packetSize = LG_NET_ENVELOPE_WIRE_SIZE + payloadLength; @@ -877,6 +1095,12 @@ static LG_TransportStatus networkConnectionSendPacket( parameters.componentEpoch = componentEpoch; parameters.requestID = requestID; + struct NetworkSend * send = networkConnectionAllocSend( + this, service, packetSize); + if (!send) + return LG_TRANSPORT_ERROR; + uint8_t * data = networkSendData(send); + LG_LOCK(this->lock); HQUIC stream = this->controlStream; const bool serviceReady = @@ -892,16 +1116,19 @@ static LG_TransportStatus networkConnectionSendPacket( !atomic_load_explicit(&this->sessionValid, memory_order_acquire))) { LG_UNLOCK(this->lock); + networkConnectionFreeSend(send); return LG_TRANSPORT_DISCONNECTED; } if (!serviceReady || payloadLength > maxPayloadLength) { LG_UNLOCK(this->lock); + networkConnectionFreeSend(send); return LG_TRANSPORT_UNAVAILABLE; } if (this->sendSequence == UINT64_MAX) { LG_UNLOCK(this->lock); + networkConnectionFreeSend(send); return LG_TRANSPORT_ERROR; } @@ -911,25 +1138,10 @@ static LG_TransportStatus networkConnectionSendPacket( if (reserveStatus != LG_TRANSPORT_OK) { LG_UNLOCK(this->lock); + networkConnectionFreeSend(send); return reserveStatus; } - struct NetworkSend * send = malloc(sizeof(*send) + packetSize); - if (!send) - { - const struct NetworkSend rollback = - { - .service = service, - .length = (uint32_t)packetSize, - .delivery = NETWORK_PACKET_DELIVERY_RELIABLE, - .accounted = true, - }; - const uint16_t ready = networkConnectionReleaseSendNL(this, &rollback); - LG_UNLOCK(this->lock); - networkConnectionNotifySendReady(this, ready, 0, 0); - return LG_TRANSPORT_ERROR; - } - parameters.sessionEpoch = this->sessionEpoch; parameters.sequence = ++this->sendSequence; send->service = service; @@ -937,18 +1149,18 @@ static LG_TransportStatus networkConnectionSendPacket( send->delivery = NETWORK_PACKET_DELIVERY_RELIABLE; send->accounted = true; size_t wireSize; - if (!lgNetPacketConstruct(send->data, packetSize, &wireSize, + if (!lgNetPacketConstruct(data, packetSize, &wireSize, ¶meters, payload, payloadLength) || wireSize != packetSize) { --this->sendSequence; const uint16_t ready = networkConnectionReleaseSendNL(this, send); LG_UNLOCK(this->lock); - free(send); + networkConnectionFreeSend(send); networkConnectionNotifySendReady(this, ready, 0, 0); return LG_TRANSPORT_ERROR; } - send->buffer.Buffer = send->data; + send->buffer.Buffer = data; send->buffer.Length = (uint32_t)packetSize; const QUIC_STATUS status = this->api->StreamSend(stream, @@ -963,7 +1175,7 @@ static LG_TransportStatus networkConnectionSendPacket( if (QUIC_FAILED(status)) { - free(send); + networkConnectionFreeSend(send); networkConnectionNotifySendReady(this, ready, 0, 0); return LG_TRANSPORT_ERROR; } @@ -1096,11 +1308,16 @@ static bool networkConnectionHandleCapability(NetworkConnection * this, capability.maxPayloadLength > this->maxPayloadLength || capability.maxDatagramPayload > offered.maxDatagramPayload || capability.maxDatagramPayload > this->maxDatagramPayload || + (capability.service == LG_NET_SERVICE_INPUT && + (capability.maxPayloadLength < NETWORK_MAX_INPUT_PAYLOAD || + ((selectedFlags & LG_NET_CAPABILITY_DATAGRAM) && + capability.maxDatagramPayload < NETWORK_MAX_INPUT_PAYLOAD))) || capability.maxBidirectionalStreams > offered.maxBidirectionalStreams || capability.maxUnidirectionalStreams > offered.maxUnidirectionalStreams || - capability.maxBidirectionalStreams || + capability.maxBidirectionalStreams != + (capability.service == LG_NET_SERVICE_INPUT ? 1U : 0U) || (!!(selectedFlags & LG_NET_CAPABILITY_BULK) != !!capability.maxUnidirectionalStreams) || capability.maxUnidirectionalStreams > @@ -1122,11 +1339,13 @@ static bool networkConnectionHandleCapability(NetworkConnection * this, this->bulkServices |= UINT16_C(1) << service; this->maxPeerBulkStreams += capability.maxUnidirectionalStreams; } - this->serviceVersions[service] = capability.versionMin; - this->serviceMaxUnidirectional[service] = + this->serviceVersions[service] = capability.versionMin; + this->serviceMaxBidirectional[service] = + capability.maxBidirectionalStreams; + this->serviceMaxUnidirectional[service] = capability.maxUnidirectionalStreams; - this->serviceMaxPayload[service] = capability.maxPayloadLength; - this->serviceMaxDatagram[service] = + this->serviceMaxPayload[service] = capability.maxPayloadLength; + this->serviceMaxDatagram[service] = capability.maxDatagramPayload; ++this->receivedCapabilities; LG_UNLOCK(this->lock); @@ -1154,12 +1373,14 @@ static bool networkConnectionRequiredCapabilitiesSelected( static bool networkConnectionReadyToActivateNL( const NetworkConnection * this) { + const uint16_t inputMask = UINT16_C(1) << LG_NET_SERVICE_INPUT; return this->helloAcknowledged && this->authenticationComplete && this->capabilitiesComplete && this->guestInfoReceived && this->receivedCapabilities == this->expectedCapabilities && - networkConnectionRequiredCapabilitiesSelected(this); + networkConnectionRequiredCapabilitiesSelected(this) && + (!(this->receivedServices & inputMask) || this->inputStreamBound); } static void networkConnectionSetSessionFeatures(NetworkConnection * this) @@ -1584,6 +1805,7 @@ static bool networkConnectionHandleCore(NetworkConnection * this, return false; bool ready; + bool openInput; LG_LOCK(this->lock); const bool valid = !this->capabilitiesComplete && this->helloAcknowledged && @@ -1591,11 +1813,16 @@ static bool networkConnectionHandleCore(NetworkConnection * this, this->receivedCapabilities == this->expectedCapabilities && networkConnectionRequiredCapabilitiesSelected(this); this->capabilitiesComplete = valid; + openInput = valid && + (this->receivedServices & + (UINT16_C(1) << LG_NET_SERVICE_INPUT)); ready = valid && networkConnectionReadyToActivateNL(this); if (ready) networkConnectionSetSessionFeatures(this); LG_UNLOCK(this->lock); + if (openInput && !networkConnectionOpenInputStream(this)) + return false; if (ready) networkConnectionReady(this); return valid; @@ -1765,7 +1992,9 @@ static bool networkConnectionDrainReceive(NetworkConnection * this) if (sequenceValid) this->receiveSequence = packet.envelope.sequence; LG_UNLOCK(this->lock); - if (!sequenceValid || !networkConnectionValidateControl(this, &packet) || + if (!sequenceValid || + packet.envelope.service == LG_NET_SERVICE_INPUT || + !networkConnectionValidateControl(this, &packet) || !networkConnectionHandlePacket(this, &packet, NETWORK_PACKET_DELIVERY_RELIABLE) || !lgNetStreamParserConsume(&this->receiveParser)) @@ -1802,6 +2031,80 @@ static bool networkConnectionReceive(NetworkConnection * this, return networkConnectionDrainReceive(this); } +static bool networkInputStreamDrainReceive(NetworkConnection * this) +{ + for (;;) + { + LGNetStreamPacket streamPacket; + const LGNetStreamResult result = lgNetStreamParserNext( + &this->inputReceiveParser, &streamPacket, NULL); + if (result == LG_NET_STREAM_NEED_DATA) + return true; + if (result != LG_NET_STREAM_PACKET) + return false; + + LGNetPacketView packet = + { + .envelope = streamPacket.envelope, + .payload = streamPacket.payload[0].data, + .payloadLength = streamPacket.payloadLength, + .wireSize = streamPacket.wireSize, + }; + if (streamPacket.payload[1].size) + { + memcpy(this->inputReceivePayload, streamPacket.payload[0].data, + streamPacket.payload[0].size); + memcpy(this->inputReceivePayload + streamPacket.payload[0].size, + streamPacket.payload[1].data, streamPacket.payload[1].size); + packet.payload = this->inputReceivePayload; + } + + LG_LOCK(this->lock); + const bool sequenceValid = this->inputStreamBound && + this->inputReceiveSequence != UINT64_MAX && + packet.envelope.sequence == this->inputReceiveSequence + 1U; + if (sequenceValid) + this->inputReceiveSequence = packet.envelope.sequence; + LG_UNLOCK(this->lock); + if (!sequenceValid || + packet.envelope.service != LG_NET_SERVICE_INPUT || + !networkConnectionValidateControl(this, &packet) || + !networkConnectionHandlePacket(this, &packet, + NETWORK_PACKET_DELIVERY_RELIABLE) || + !lgNetStreamParserConsume(&this->inputReceiveParser)) + return false; + } +} + +static bool networkInputStreamReceive(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->inputReceiveParser, + buffers[i].Buffer + offset, buffers[i].Length - offset, + &accepted)) + return false; + offset += accepted; + if (!networkInputStreamDrainReceive(this)) + return false; + if (!accepted && offset < buffers[i].Length) + return false; + } + } + + return networkInputStreamDrainReceive(this); +} + static void networkBulkStreamFree(struct NetworkBulkStream * stream) { if (!stream) @@ -2102,6 +2405,88 @@ static bool networkBulkStreamQueueReceive(struct NetworkBulkStream * stream, return true; } +static bool networkConnectionOpenInputStream(NetworkConnection * this) +{ + LG_LOCK(this->inputStreamLock); + LG_LOCK(this->lock); + HQUIC connection = this->connection; + const uint16_t inputMask = UINT16_C(1) << LG_NET_SERVICE_INPUT; + const bool valid = connection && !this->inputStream && + !atomic_load_explicit(&this->stopping, memory_order_acquire) && + this->capabilitiesComplete && + (this->receivedServices & inputMask) && + (this->reliableServices & inputMask) && + this->serviceMaxBidirectional[LG_NET_SERVICE_INPUT] == 1U; + LG_UNLOCK(this->lock); + if (!valid) + { + LG_UNLOCK(this->inputStreamLock); + return false; + } + + HQUIC stream = NULL; + QUIC_STATUS status = this->api->StreamOpen(connection, + QUIC_STREAM_OPEN_FLAG_NONE, networkInputStreamCallback, this, &stream); + if (QUIC_FAILED(status)) + { + DEBUG_ERROR("MsQuic input StreamOpen failed: 0x%08x", + (unsigned)status); + LG_UNLOCK(this->inputStreamLock); + return false; + } + + const uint16_t priority = UINT16_MAX; + status = this->api->SetParam(stream, QUIC_PARAM_STREAM_PRIORITY, + sizeof(priority), &priority); + if (QUIC_FAILED(status)) + { + DEBUG_ERROR("MsQuic input stream priority failed: 0x%08x", + (unsigned)status); + this->api->StreamClose(stream); + LG_UNLOCK(this->inputStreamLock); + return false; + } + + LG_LOCK(this->lock); + const bool stateValid = this->connection == connection && + !this->inputStream && + !atomic_load_explicit(&this->stopping, memory_order_acquire); + if (stateValid) + this->inputStream = stream; + LG_UNLOCK(this->lock); + LG_UNLOCK(this->inputStreamLock); + if (!stateValid) + { + this->api->StreamClose(stream); + return false; + } + + status = this->api->StreamStart(stream, + QUIC_STREAM_START_FLAG_IMMEDIATE | + QUIC_STREAM_START_FLAG_SHUTDOWN_ON_FAIL | + QUIC_STREAM_START_FLAG_PRIORITY_WORK); + if (QUIC_FAILED(status)) + { + DEBUG_ERROR("MsQuic input StreamStart failed: 0x%08x", + (unsigned)status); + LG_LOCK(this->inputStreamLock); + LG_LOCK(this->lock); + const bool closeStream = this->inputStream == stream; + if (this->inputStream == stream) + { + this->inputStream = NULL; + this->inputStreamBound = false; + } + LG_UNLOCK(this->lock); + LG_UNLOCK(this->inputStreamLock); + if (closeStream) + this->api->StreamClose(stream); + return false; + } + + return true; +} + static LG_TransportStatus networkConnectionOpenBulkStream( NetworkConnection * this, LGNetService service, uint64_t packetSize) { @@ -2554,19 +2939,38 @@ static QUIC_STATUS QUIC_API networkConnectionCallback(HQUIC handle, } case QUIC_CONNECTION_EVENT_DATAGRAM_SEND_STATE_CHANGED: - if (QUIC_DATAGRAM_SEND_STATE_IS_FINAL( - event->DATAGRAM_SEND_STATE_CHANGED.State)) + { + struct NetworkSend * send = + event->DATAGRAM_SEND_STATE_CHANGED.ClientContext; + const QUIC_DATAGRAM_SEND_STATE state = + event->DATAGRAM_SEND_STATE_CHANGED.State; + const bool lost = + state == QUIC_DATAGRAM_SEND_LOST_SUSPECT || + state == QUIC_DATAGRAM_SEND_LOST_DISCARDED || + state == QUIC_DATAGRAM_SEND_CANCELED; + const bool reportLoss = + lost && !send->lossReported; + uint16_t ready = 0; + if (reportLoss) + send->lossReported = true; + if (QUIC_DATAGRAM_SEND_STATE_IS_FINAL(state) || + (send->service == LG_NET_SERVICE_INPUT && + state == QUIC_DATAGRAM_SEND_SENT)) { - struct NetworkSend * send = - event->DATAGRAM_SEND_STATE_CHANGED.ClientContext; LG_LOCK(this->lock); - const uint16_t ready = - networkConnectionReleaseSendNL(this, send); + if (send->accounted) + ready = networkConnectionReleaseSendNL(this, send); LG_UNLOCK(this->lock); - free(send); - networkConnectionNotifySendReady(this, 0, ready, 0); } + if (reportLoss && this->datagramLost) + this->datagramLost(this->opaque, send->service, + send->componentEpoch, send->applicationSequence); + if (ready) + networkConnectionNotifySendReady(this, 0, ready, 0); + if (QUIC_DATAGRAM_SEND_STATE_IS_FINAL(state)) + networkConnectionFreeSend(send); return QUIC_STATUS_SUCCESS; + } case QUIC_CONNECTION_EVENT_PEER_CERTIFICATE_RECEIVED: return networkConnectionCertificate(this, event); @@ -2618,7 +3022,7 @@ static QUIC_STATUS QUIC_API networkStreamCallback(HQUIC handle, LG_LOCK(this->lock); const uint16_t ready = networkConnectionReleaseSendNL(this, send); LG_UNLOCK(this->lock); - free(send); + networkConnectionFreeSend(send); networkConnectionNotifySendReady(this, ready, 0, 0); return QUIC_STATUS_SUCCESS; } @@ -2661,6 +3065,163 @@ static QUIC_STATUS QUIC_API networkStreamCallback(HQUIC handle, } } +static QUIC_STATUS QUIC_API networkInputStreamCallback(HQUIC handle, + void * opaque, QUIC_STREAM_EVENT * event) +{ + NetworkConnection * this = opaque; + + switch (event->Type) + { + case QUIC_STREAM_EVENT_START_COMPLETE: + { + if (QUIC_FAILED(event->START_COMPLETE.Status)) + { + DEBUG_ERROR("MsQuic input stream start failed: 0x%08x", + (unsigned)event->START_COMPLETE.Status); + LG_LOCK(this->inputStreamLock); + LG_LOCK(this->lock); + const bool closeStream = this->inputStream == handle; + if (closeStream) + { + this->inputStream = NULL; + this->inputStreamBound = false; + } + LG_UNLOCK(this->lock); + LG_UNLOCK(this->inputStreamLock); + if (closeStream) + this->api->StreamClose(handle); + networkConnectionProtocolFailure(this); + return QUIC_STATUS_SUCCESS; + } + if ((event->START_COMPLETE.ID & 3U) != 0U) + { + this->api->StreamShutdown(handle, + QUIC_STREAM_SHUTDOWN_FLAG_ABORT, 0); + networkConnectionProtocolFailure(this); + return QUIC_STATUS_SUCCESS; + } + + uint64_t sessionEpoch; + LG_LOCK(this->lock); + const bool stateValid = this->inputStream == handle && + !atomic_load_explicit(&this->stopping, memory_order_acquire) && + this->capabilitiesComplete; + sessionEpoch = this->sessionEpoch; + LG_UNLOCK(this->lock); + if (!stateValid) + { + this->api->StreamShutdown(handle, + QUIC_STREAM_SHUTDOWN_FLAG_ABORT, 0); + return QUIC_STATUS_SUCCESS; + } + + LGNetStreamBinding binding; + lgNetStreamBindingInit(&binding, LG_NET_SERVICE_INPUT, + LG_NET_ROLE_CLIENT, event->START_COMPLETE.ID, sessionEpoch, 0); + struct NetworkSend * send = networkConnectionAllocSend(this, + LG_NET_SERVICE_INPUT, LG_NET_STREAM_BINDING_WIRE_SIZE); + uint8_t * data = send ? networkSendData(send) : NULL; + if (!send || !lgNetStreamBindingEncode(data, + LG_NET_STREAM_BINDING_WIRE_SIZE, &binding)) + { + networkConnectionFreeSend(send); + networkConnectionProtocolFailure(this); + return QUIC_STATUS_SUCCESS; + } + + send->buffer.Buffer = data; + send->buffer.Length = LG_NET_STREAM_BINDING_WIRE_SIZE; + send->service = LG_NET_SERVICE_INPUT; + send->length = LG_NET_STREAM_BINDING_WIRE_SIZE; + send->delivery = NETWORK_PACKET_DELIVERY_RELIABLE; + send->accounted = false; + + LG_LOCK(this->inputStreamLock); + LG_LOCK(this->lock); + const bool sendValid = this->inputStream == handle && + this->sessionEpoch == sessionEpoch && + !atomic_load_explicit(&this->stopping, memory_order_acquire); + LG_UNLOCK(this->lock); + const QUIC_STATUS status = sendValid ? + this->api->StreamSend(handle, &send->buffer, 1, + QUIC_SEND_FLAG_PRIORITY_WORK, send) : QUIC_STATUS_ABORTED; + bool ready = false; + LG_LOCK(this->lock); + if (QUIC_SUCCEEDED(status)) + { + const bool stateCurrent = this->inputStream == handle && + this->sessionEpoch == sessionEpoch && + !atomic_load_explicit(&this->stopping, memory_order_acquire); + this->inputStreamBound = stateCurrent; + ready = stateCurrent && + networkConnectionReadyToActivateNL(this); + if (ready) + networkConnectionSetSessionFeatures(this); + } + LG_UNLOCK(this->lock); + LG_UNLOCK(this->inputStreamLock); + + if (QUIC_FAILED(status)) + { + networkConnectionFreeSend(send); + if (sendValid) + networkConnectionProtocolFailure(this); + else + this->api->StreamShutdown(handle, + QUIC_STREAM_SHUTDOWN_FLAG_ABORT, 0); + return QUIC_STATUS_SUCCESS; + } + + if (ready) + networkConnectionReady(this); + return QUIC_STATUS_SUCCESS; + } + + case QUIC_STREAM_EVENT_RECEIVE: + if (!networkInputStreamReceive( + this, event->RECEIVE.Buffers, event->RECEIVE.BufferCount)) + { + DEBUG_ERROR("Failed to process network input stream data"); + networkConnectionProtocolFailure(this); + } + return QUIC_STATUS_SUCCESS; + + case QUIC_STREAM_EVENT_SEND_COMPLETE: + { + struct NetworkSend * send = event->SEND_COMPLETE.ClientContext; + LG_LOCK(this->lock); + const uint16_t ready = networkConnectionReleaseSendNL(this, send); + LG_UNLOCK(this->lock); + networkConnectionFreeSend(send); + networkConnectionNotifySendReady(this, ready, 0, 0); + return QUIC_STATUS_SUCCESS; + } + + case QUIC_STREAM_EVENT_PEER_SEND_SHUTDOWN: + case QUIC_STREAM_EVENT_PEER_SEND_ABORTED: + case QUIC_STREAM_EVENT_PEER_RECEIVE_ABORTED: + if (!atomic_load_explicit(&this->stopping, memory_order_acquire)) + networkConnectionProtocolFailure(this); + return QUIC_STATUS_SUCCESS; + + case QUIC_STREAM_EVENT_SHUTDOWN_COMPLETE: + LG_LOCK(this->inputStreamLock); + LG_LOCK(this->lock); + if (this->inputStream == handle) + { + this->inputStream = NULL; + this->inputStreamBound = false; + } + LG_UNLOCK(this->lock); + LG_UNLOCK(this->inputStreamLock); + this->api->StreamClose(handle); + return QUIC_STATUS_SUCCESS; + + default: + return QUIC_STATUS_SUCCESS; + } +} + static QUIC_STATUS QUIC_API networkBulkStreamCallback(HQUIC handle, void * opaque, QUIC_STREAM_EVENT * event) { @@ -2717,17 +3278,18 @@ static QUIC_STATUS QUIC_API networkBulkStreamCallback(HQUIC handle, event->START_COMPLETE.ID, sessionEpoch, LG_NET_STREAM_BINDING_UNIDIRECTIONAL | LG_NET_STREAM_BINDING_BULK); - struct NetworkSend * send = malloc( - sizeof(*send) + LG_NET_STREAM_BINDING_WIRE_SIZE); - if (!send || !lgNetStreamBindingEncode(send->data, + struct NetworkSend * send = networkConnectionAllocSend(this, + stream->service, LG_NET_STREAM_BINDING_WIRE_SIZE); + uint8_t * data = send ? networkSendData(send) : NULL; + if (!send || !lgNetStreamBindingEncode(data, LG_NET_STREAM_BINDING_WIRE_SIZE, &binding)) { - free(send); + networkConnectionFreeSend(send); networkConnectionProtocolFailure(this); return QUIC_STATUS_SUCCESS; } - send->buffer.Buffer = send->data; + send->buffer.Buffer = data; send->buffer.Length = LG_NET_STREAM_BINDING_WIRE_SIZE; send->service = stream->service; send->length = LG_NET_STREAM_BINDING_WIRE_SIZE; @@ -2756,7 +3318,7 @@ static QUIC_STATUS QUIC_API networkBulkStreamCallback(HQUIC handle, if (QUIC_FAILED(status)) { - free(send); + networkConnectionFreeSend(send); if (sendValid) networkConnectionProtocolFailure(this); else @@ -2787,7 +3349,7 @@ static QUIC_STATUS QUIC_API networkBulkStreamCallback(HQUIC handle, LG_LOCK(this->lock); const uint16_t ready = networkConnectionReleaseSendNL(this, send); LG_UNLOCK(this->lock); - free(send); + networkConnectionFreeSend(send); networkConnectionNotifySendReady(this, 0, 0, ready); return QUIC_STATUS_SUCCESS; } @@ -2849,9 +3411,19 @@ bool networkConnectionCreate(NetworkConnection ** result, } LG_LOCK_INIT(this->lock); + LG_LOCK_INIT(this->inputStreamLock); + LG_LOCK_INIT(this->inputSendPoolLock); LG_LOCK_INIT(this->stateCallbackLock); atomic_init(&this->stopping, false); atomic_init(&this->sessionValid, false); + for (size_t i = 0; i < NETWORK_INPUT_SEND_POOL_COUNT; ++i) + { + struct NetworkSend * send = &this->inputSendPool[i].send; + send->connection = this; + send->pooled = true; + send->next = this->inputSendFree; + this->inputSendFree = send; + } uint16_t offeredBulkStreams = 0; for (uint32_t index = 0; index < params->capabilityCount; ++index) @@ -2889,14 +3461,15 @@ bool networkConnectionCreate(NetworkConnection ** result, goto fail; } - this->port = params->port; - this->knownHosts = params->knownHosts; - this->trustPrompt = params->trustPrompt; - this->trustCancel = params->trustCancel; - this->packet = params->packet; - this->state = params->state; - this->sendReady = params->sendReady; - this->opaque = params->opaque; + this->port = params->port; + this->knownHosts = params->knownHosts; + this->trustPrompt = params->trustPrompt; + this->trustCancel = params->trustCancel; + this->packet = params->packet; + this->state = params->state; + this->sendReady = params->sendReady; + this->datagramLost = params->datagramLost; + this->opaque = params->opaque; this->connectEvent = lgCreateEvent(false, 0); this->closeEvent = lgCreateEvent(false, 0); @@ -2910,11 +3483,17 @@ bool networkConnectionCreate(NetworkConnection ** result, goto fail; } - this->receiveData = malloc(NETWORK_MAX_CONTROL_BUFFER); - this->receivePayload = malloc(NETWORK_MAX_CONTROL_PAYLOAD); + this->receiveData = malloc(NETWORK_MAX_CONTROL_BUFFER); + this->receivePayload = malloc(NETWORK_MAX_CONTROL_PAYLOAD); + this->inputReceiveData = malloc(NETWORK_MAX_INPUT_BUFFER); + this->inputReceivePayload = malloc(NETWORK_MAX_INPUT_PAYLOAD); if (!this->receiveData || !this->receivePayload || + !this->inputReceiveData || !this->inputReceivePayload || !lgNetStreamParserInit(&this->receiveParser, this->receiveData, - NETWORK_MAX_CONTROL_BUFFER, NETWORK_MAX_CONTROL_PAYLOAD)) + NETWORK_MAX_CONTROL_BUFFER, NETWORK_MAX_CONTROL_PAYLOAD) || + !lgNetStreamParserInit(&this->inputReceiveParser, + this->inputReceiveData, NETWORK_MAX_INPUT_BUFFER, + NETWORK_MAX_INPUT_PAYLOAD)) { DEBUG_ERROR("Failed to initialize the network receive buffers"); goto fail; @@ -3059,7 +3638,11 @@ void networkConnectionDestroy(NetworkConnection ** connection) if (this->connectEvent) lgFreeEvent(this->connectEvent); LG_LOCK_FREE(this->stateCallbackLock); + LG_LOCK_FREE(this->inputSendPoolLock); + LG_LOCK_FREE(this->inputStreamLock); LG_LOCK_FREE(this->lock); + free(this->inputReceivePayload); + free(this->inputReceiveData); free(this->receivePayload); free(this->receiveData); free(this->endpoint); @@ -3092,6 +3675,8 @@ LG_TransportStatus networkConnectionConnect(NetworkConnection * this, this->connecting = true; this->sessionEpoch = 0; this->sendSequence = 0; + this->inputSendSequence = 0; + this->inputReceiveSequence = 0; this->datagramSendSequence = 0; this->receiveSequence = 0; this->protocolVersion = 0; @@ -3113,9 +3698,12 @@ LG_TransportStatus networkConnectionConnect(NetworkConnection * this, this->maxDatagramPayload = 0; this->maxDatagramSendLength = 0; this->datagramSendEnabled = false; + this->inputStreamBound = false; networkConnectionResetSendAccountingNL(this); networkConnectionClearAuthProofNL(this); memset(this->serviceVersions, 0, sizeof(this->serviceVersions)); + memset(this->serviceMaxBidirectional, 0, + sizeof(this->serviceMaxBidirectional)); memset(this->serviceMaxUnidirectional, 0, sizeof(this->serviceMaxUnidirectional)); memset(this->servicePeerBulkStreams, 0, @@ -3123,6 +3711,7 @@ LG_TransportStatus networkConnectionConnect(NetworkConnection * this, memset(this->serviceMaxPayload, 0, sizeof(this->serviceMaxPayload)); memset(this->serviceMaxDatagram, 0, sizeof(this->serviceMaxDatagram)); lgNetStreamParserReset(&this->receiveParser); + lgNetStreamParserReset(&this->inputReceiveParser); memset(&this->session, 0, sizeof(this->session)); snprintf(this->session.name, sizeof(this->session.name), "%s", this->endpoint); @@ -3188,6 +3777,7 @@ void networkConnectionCancel(NetworkConnection * this) this->trustCancel(this->opaque); LG_LOCK(this->stateCallbackLock); + LG_LOCK(this->inputStreamLock); LG_LOCK(this->lock); HQUIC connection = this->connection; const bool certificatePending = @@ -3202,6 +3792,7 @@ void networkConnectionCancel(NetworkConnection * this) this->connectFailed = true; } LG_UNLOCK(this->lock); + LG_UNLOCK(this->inputStreamLock); LG_UNLOCK(this->stateCallbackLock); if (certificatePending) this->api->ConnectionCertificateValidationComplete(connection, false, @@ -3229,17 +3820,21 @@ void networkConnectionDisconnect(NetworkConnection * this) lgWaitEvent(this->closeEvent, TIMEOUT_INFINITE); lgWaitEvent(this->bulkReceiveIdle, TIMEOUT_INFINITE); LG_LOCK(this->stateCallbackLock); + LG_LOCK(this->inputStreamLock); LG_LOCK(this->lock); const bool ownsConnection = this->connection == connection; if (ownsConnection) { this->connection = NULL; this->controlStream = NULL; + this->inputStream = NULL; + this->inputStreamBound = false; this->certificatePending = false; networkConnectionResetSendAccountingNL(this); memset(this->pendingFingerprint, 0, sizeof(this->pendingFingerprint)); } LG_UNLOCK(this->lock); + LG_UNLOCK(this->inputStreamLock); if (ownsConnection) { @@ -3314,7 +3909,8 @@ LG_TransportStatus networkConnectionSend(NetworkConnection * this, LG_TransportStatus networkConnectionSendDatagram(NetworkConnection * this, LGNetService service, uint16_t messageType, uint16_t serviceVersion, LGNetEnvelopeFlags flags, uint64_t componentEpoch, uint64_t requestID, - const void * payload, uint32_t payloadLength, bool discardIfBlocked) + const void * payload, uint32_t payloadLength, + uint64_t applicationSequence, bool discardIfBlocked) { if (!this || !networkConnectionValid(this)) return LG_TRANSPORT_DISCONNECTED; @@ -3342,6 +3938,15 @@ LG_TransportStatus networkConnectionSendDatagram(NetworkConnection * this, parameters.componentEpoch = componentEpoch; parameters.requestID = requestID; + struct NetworkSend * send = networkConnectionAllocSend( + this, service, packetSize); + if (!send) + return LG_TRANSPORT_ERROR; + uint8_t * data = networkSendData(send); + const bool inputDatagram = service == LG_NET_SERVICE_INPUT; + + if (inputDatagram) + LG_LOCK(this->inputStreamLock); LG_LOCK(this->lock); const uint16_t serviceMask = UINT16_C(1) << service; HQUIC connection = this->connection; @@ -3349,6 +3954,9 @@ LG_TransportStatus networkConnectionSendDatagram(NetworkConnection * this, !atomic_load_explicit(&this->sessionValid, memory_order_acquire)) { LG_UNLOCK(this->lock); + if (inputDatagram) + LG_UNLOCK(this->inputStreamLock); + networkConnectionFreeSend(send); return LG_TRANSPORT_DISCONNECTED; } if (!(this->receivedServices & serviceMask) || @@ -3357,11 +3965,17 @@ LG_TransportStatus networkConnectionSendDatagram(NetworkConnection * this, payloadLength > this->serviceMaxDatagram[service]) { LG_UNLOCK(this->lock); + if (inputDatagram) + LG_UNLOCK(this->inputStreamLock); + networkConnectionFreeSend(send); return LG_TRANSPORT_UNAVAILABLE; } if (this->datagramSendSequence == UINT64_MAX) { LG_UNLOCK(this->lock); + if (inputDatagram) + LG_UNLOCK(this->inputStreamLock); + networkConnectionFreeSend(send); return LG_TRANSPORT_ERROR; } if (!this->datagramSendEnabled || @@ -3379,6 +3993,9 @@ LG_TransportStatus networkConnectionSendDatagram(NetworkConnection * this, this->datagramSend.serviceBlockedBytes[service] : packetSize; } LG_UNLOCK(this->lock); + if (inputDatagram) + LG_UNLOCK(this->inputStreamLock); + networkConnectionFreeSend(send); return LG_TRANSPORT_UNAVAILABLE; } @@ -3388,46 +4005,45 @@ LG_TransportStatus networkConnectionSendDatagram(NetworkConnection * this, if (reserveStatus != LG_TRANSPORT_OK) { LG_UNLOCK(this->lock); + if (inputDatagram) + LG_UNLOCK(this->inputStreamLock); + networkConnectionFreeSend(send); return reserveStatus; } - struct NetworkSend * send = malloc(sizeof(*send) + packetSize); - if (!send) - { - const struct NetworkSend rollback = - { - .service = service, - .length = (uint32_t)packetSize, - .delivery = NETWORK_PACKET_DELIVERY_DATAGRAM, - .accounted = true, - }; - const uint16_t ready = networkConnectionReleaseSendNL(this, &rollback); - LG_UNLOCK(this->lock); - networkConnectionNotifySendReady(this, 0, ready, 0); - return LG_TRANSPORT_ERROR; - } - parameters.sessionEpoch = this->sessionEpoch; parameters.sequence = ++this->datagramSendSequence; - send->service = service; - send->length = (uint32_t)packetSize; - send->delivery = NETWORK_PACKET_DELIVERY_DATAGRAM; - send->accounted = true; + + send->service = service; + send->length = (uint32_t)packetSize; + send->componentEpoch = componentEpoch; + send->applicationSequence = applicationSequence; + send->delivery = NETWORK_PACKET_DELIVERY_DATAGRAM; + send->accounted = true; + if (inputDatagram) + LG_UNLOCK(this->lock); size_t wireSize; - if (!lgNetPacketConstruct(send->data, packetSize, &wireSize, + if (!lgNetPacketConstruct(data, packetSize, &wireSize, ¶meters, payload, payloadLength) || wireSize != packetSize) { - --this->datagramSendSequence; + if (inputDatagram) + LG_LOCK(this->lock); + if (!inputDatagram) + --this->datagramSendSequence; const uint16_t ready = networkConnectionReleaseSendNL(this, send); LG_UNLOCK(this->lock); - free(send); + if (inputDatagram) + LG_UNLOCK(this->inputStreamLock); + networkConnectionFreeSend(send); networkConnectionNotifySendReady(this, 0, ready, 0); return LG_TRANSPORT_ERROR; } - send->buffer.Buffer = send->data; + send->buffer.Buffer = data; send->buffer.Length = (uint32_t)packetSize; QUIC_SEND_FLAGS sendFlags = QUIC_SEND_FLAG_PRIORITY_WORK; + if (service == LG_NET_SERVICE_INPUT) + sendFlags |= QUIC_SEND_FLAG_DGRAM_PRIORITY; if (discardIfBlocked) sendFlags |= QUIC_SEND_FLAG_CANCEL_ON_BLOCKED; const QUIC_STATUS status = this->api->DatagramSend(connection, @@ -3435,14 +4051,26 @@ LG_TransportStatus networkConnectionSendDatagram(NetworkConnection * this, uint16_t ready = 0; if (QUIC_FAILED(status)) { - --this->datagramSendSequence; + if (inputDatagram) + LG_LOCK(this->lock); + if (!inputDatagram) + --this->datagramSendSequence; ready = networkConnectionReleaseSendNL(this, send); + if (inputDatagram) + LG_UNLOCK(this->lock); + } + if (inputDatagram) + { + LG_UNLOCK(this->inputStreamLock); + } + else + { + LG_UNLOCK(this->lock); } - LG_UNLOCK(this->lock); if (QUIC_FAILED(status)) { - free(send); + networkConnectionFreeSend(send); networkConnectionNotifySendReady(this, 0, ready, 0); return LG_TRANSPORT_ERROR; } @@ -3537,10 +4165,11 @@ retry: return reserveStatus; } - struct NetworkSend * send = malloc(sizeof(*send) + packetSize); + struct NetworkSend * send = networkConnectionAllocSend( + this, service, packetSize); if (!send) { - const struct NetworkSend rollback = + struct NetworkSend rollback = { .service = service, .length = (uint32_t)packetSize, @@ -3559,19 +4188,20 @@ retry: send->length = (uint32_t)packetSize; send->delivery = NETWORK_PACKET_DELIVERY_BULK; send->accounted = true; - size_t wireSize; - if (!lgNetPacketConstruct(send->data, packetSize, &wireSize, + uint8_t * data = networkSendData(send); + size_t wireSize = 0; + if (!lgNetPacketConstruct(data, packetSize, &wireSize, ¶meters, payload, payloadLength) || wireSize != packetSize) { --stream->sendSequence; const uint16_t ready = networkConnectionReleaseSendNL(this, send); LG_UNLOCK(this->lock); - free(send); + networkConnectionFreeSend(send); networkConnectionNotifySendReady(this, 0, 0, ready); return LG_TRANSPORT_ERROR; } - send->buffer.Buffer = send->data; + send->buffer.Buffer = data; send->buffer.Length = (uint32_t)packetSize; const QUIC_STATUS status = this->api->StreamSend(stream->handle, &send->buffer, 1, QUIC_SEND_FLAG_NONE, send); @@ -3585,7 +4215,7 @@ retry: if (QUIC_FAILED(status)) { - free(send); + networkConnectionFreeSend(send); networkConnectionNotifySendReady(this, 0, 0, ready); return LG_TRANSPORT_ERROR; } diff --git a/client/transports/Network/connection.h b/client/transports/Network/connection.h index 19f5c879..e041f68f 100644 --- a/client/transports/Network/connection.h +++ b/client/transports/Network/connection.h @@ -51,6 +51,8 @@ typedef void (*NetworkConnectionStateFn)(void * opaque, bool connected, LG_TransportStatus reason); typedef void (*NetworkSendReadyFn)(void * opaque, LGNetService service, NetworkPacketDelivery delivery); +typedef void (*NetworkDatagramLostFn)(void * opaque, LGNetService service, + uint64_t componentEpoch, uint64_t applicationSequence); typedef struct NetworkConnectionParams { @@ -63,6 +65,7 @@ typedef struct NetworkConnectionParams NetworkPacketFn packet; NetworkConnectionStateFn state; NetworkSendReadyFn sendReady; + NetworkDatagramLostFn datagramLost; const LGNetCapability * capabilities; uint32_t capabilityCount; void * opaque; @@ -92,7 +95,8 @@ LG_TransportStatus networkConnectionSendDatagram( NetworkConnection * connection, LGNetService service, uint16_t messageType, uint16_t serviceVersion, LGNetEnvelopeFlags flags, uint64_t componentEpoch, uint64_t requestID, - const void * payload, uint32_t payloadLength, bool discardIfBlocked); + const void * payload, uint32_t payloadLength, + uint64_t applicationSequence, bool discardIfBlocked); LG_TransportStatus networkConnectionSendBulk(NetworkConnection * connection, LGNetService service, uint16_t messageType, uint16_t serviceVersion, LGNetEnvelopeFlags flags, uint64_t componentEpoch, uint64_t requestID, diff --git a/client/transports/Network/input.c b/client/transports/Network/input.c index cb741ca6..38085ce3 100644 --- a/client/transports/Network/input.c +++ b/client/transports/Network/input.c @@ -28,6 +28,7 @@ #include +#include #include #include #include @@ -41,6 +42,11 @@ #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_QUEUE_SIZE 128U +#define NETWORK_INPUT_QUEUE_RESERVE 2U +#define NETWORK_INPUT_QUEUE_CAPACITY \ + (NETWORK_INPUT_QUEUE_SIZE + NETWORK_INPUT_QUEUE_RESERVE) +#define NETWORK_INPUT_QUEUE_PAYLOAD_MAX LG_NET_INPUT_ABSOLUTE_WIRE_SIZE #define NETWORK_INPUT_CLAIM_FLAGS \ (LG_NET_INPUT_CLAIM_KEYBOARD | LG_NET_INPUT_CLAIM_POINTER | \ @@ -56,6 +62,29 @@ struct NetworkInputLockKey uint16_t usage; }; +typedef struct NetworkInputPkt +{ + uint8_t payload[NETWORK_INPUT_QUEUE_PAYLOAD_MAX]; + uint64_t componentEpoch; + uint64_t requestID; + uint32_t payloadLength; + LGNetEnvelopeFlags flags; + uint16_t messageType; +} +NetworkInputPkt; + +_Static_assert(LG_NET_INPUT_CLAIM_WIRE_SIZE <= + NETWORK_INPUT_QUEUE_PAYLOAD_MAX && + LG_NET_INPUT_RELATIVE_WIRE_SIZE <= + NETWORK_INPUT_QUEUE_PAYLOAD_MAX && + LG_NET_INPUT_ABSOLUTE_WIRE_SIZE <= + NETWORK_INPUT_QUEUE_PAYLOAD_MAX && + LG_NET_INPUT_KEYBOARD_WIRE_SIZE <= + NETWORK_INPUT_QUEUE_PAYLOAD_MAX && + LG_NET_INPUT_CONTROL_WIRE_SIZE <= + NETWORK_INPUT_QUEUE_PAYLOAD_MAX, + "Network input reliable queue payloads are too small"); + static const struct NetworkInputLockKey NETWORK_INPUT_LOCK_KEYS[] = { { LG_NET_KEYBOARD_LED_NUM_LOCK, 83 }, @@ -74,12 +103,14 @@ struct NetworkInput bool destroying; bool connected; bool serviceAvailable; + bool datagramAvailable; bool claimPending; bool claimed; bool available; uint64_t claimantID; uint64_t claimEpoch; uint64_t sequence; + uint64_t pointerSequence; uint64_t nextClaimNs; uint64_t nextKeepaliveNs; @@ -91,6 +122,16 @@ struct NetworkInput uint32_t absoluteY; uint32_t absoluteWidth; uint32_t absoluteHeight; + int64_t cumulativeX; + int64_t cumulativeY; + bool pendingRelative; + bool pendingAbsolute; + bool pendingMotionReliable; + bool motionBarrier; + uint64_t reliableBarrierSequence; + NetworkInputPkt reliableQueue[NETWORK_INPUT_QUEUE_CAPACITY]; + size_t reliableHead; + size_t reliableCount; /* 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]; @@ -112,11 +153,15 @@ struct NetworkInput uint32_t statusGeneration; bool statusNotifyPending; bool statusDispatching; + bool sendReadyPending; LG_InputStatusFn statusCallback; void * statusOpaque; }; static _Thread_local NetworkInput * l_networkInputCallback; +static _Thread_local NetworkInput * l_networkInputSending; + +static void networkInputResumeSendsNL(NetworkInput * input, bool * notify); static uint64_t networkInputNext64(uint64_t value) { @@ -316,32 +361,146 @@ static bool networkInputSetAvailableNL(NetworkInput * input, bool available) static void networkInputClearStateNL(NetworkInput * input) { - input->mouseButtons = 0; - input->mouseAbsolute = false; - input->absoluteX = 0; - input->absoluteY = 0; - input->absoluteWidth = 0; - input->absoluteHeight = 0; + input->mouseButtons = 0; + input->mouseAbsolute = false; + input->absoluteX = 0; + input->absoluteY = 0; + input->absoluteWidth = 0; + input->absoluteHeight = 0; + input->cumulativeX = 0; + input->cumulativeY = 0; + input->pointerSequence = 0; + input->pendingRelative = false; + input->pendingAbsolute = false; + input->pendingMotionReliable = false; + input->motionBarrier = true; + input->reliableBarrierSequence = 0; + input->sendReadyPending = false; memset(input->keyState, 0, sizeof(input->keyState)); input->modifiers = 0; networkInputClearLEDProjectionNL(input); } +static void networkInputClearReliableNL(NetworkInput * input) +{ + input->reliableHead = 0; + input->reliableCount = 0; +} + static LG_TransportStatus networkInputSendReliableNL(NetworkInput * input, + uint16_t messageType, LGNetEnvelopeFlags flags, uint64_t componentEpoch, + uint64_t requestID, const void * payload, uint32_t payloadLength) +{ + NetworkInput * previous; + LG_TransportStatus status; + + previous = l_networkInputSending; + l_networkInputSending = input; + status = networkConnectionSend( + input->connection, LG_NET_SERVICE_INPUT, + messageType, input->serviceVersion, + flags | LG_NET_ENVELOPE_FINAL, componentEpoch, requestID, + payload, payloadLength); + l_networkInputSending = previous; + return status; +} + +static LG_TransportStatus networkInputSendDatagramNL(NetworkInput * input, + uint16_t messageType, uint64_t applicationSequence, + const void * payload, uint32_t payloadLength) +{ + NetworkInput * previous; + LG_TransportStatus status; + + previous = l_networkInputSending; + l_networkInputSending = input; + status = networkConnectionSendDatagram( + input->connection, LG_NET_SERVICE_INPUT, messageType, + input->serviceVersion, 0, input->claimEpoch, 0, + payload, payloadLength, applicationSequence, true); + l_networkInputSending = previous; + return status; +} + +static bool networkInputTransportFatal(LG_TransportStatus status) +{ + return status != LG_TRANSPORT_OK && status != LG_TRANSPORT_UNAVAILABLE && + status != LG_TRANSPORT_TIMEOUT; +} + +static bool networkInputQueueReliableNL(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); + const bool emergency = messageType == LG_NET_INPUT_MESSAGE_RESET || + messageType == LG_NET_INPUT_MESSAGE_RELEASE; + const size_t limit = emergency ? + NETWORK_INPUT_QUEUE_CAPACITY : NETWORK_INPUT_QUEUE_SIZE; + if (input->reliableCount >= limit || + payloadLength > NETWORK_INPUT_QUEUE_PAYLOAD_MAX || + (payloadLength && !payload)) + return false; + + const size_t index = + (input->reliableHead + input->reliableCount) % + NETWORK_INPUT_QUEUE_CAPACITY; + NetworkInputPkt * packet = &input->reliableQueue[index]; + if (payloadLength) + memcpy(packet->payload, payload, payloadLength); + packet->componentEpoch = input->claimEpoch; + packet->requestID = requestID; + packet->payloadLength = payloadLength; + packet->flags = flags; + packet->messageType = messageType; + ++input->reliableCount; + return true; +} + +static LG_TransportStatus networkInputDrainReliableNL(NetworkInput * input) +{ + while (input->reliableCount) + { + const NetworkInputPkt * packet = + &input->reliableQueue[input->reliableHead]; + const LG_TransportStatus status = networkInputSendReliableNL(input, + packet->messageType, packet->flags, packet->componentEpoch, + packet->requestID, packet->payload, packet->payloadLength); + if (status != LG_TRANSPORT_OK) + return status; + + input->reliableHead = + (input->reliableHead + 1U) % NETWORK_INPUT_QUEUE_CAPACITY; + --input->reliableCount; + } + + input->reliableHead = 0; + return LG_TRANSPORT_OK; +} + +static LG_TransportStatus networkInputSubmitReliableNL(NetworkInput * input, + uint16_t messageType, LGNetEnvelopeFlags flags, uint64_t requestID, + const void * payload, uint32_t payloadLength) +{ + if (!input->reliableCount) + { + const LG_TransportStatus status = networkInputSendReliableNL(input, + messageType, flags, input->claimEpoch, requestID, + payload, payloadLength); + if (status == LG_TRANSPORT_OK || networkInputTransportFatal(status)) + return status; + } + + return networkInputQueueReliableNL(input, messageType, flags, requestID, + payload, payloadLength) ? LG_TRANSPORT_OK : LG_TRANSPORT_ERROR; } static bool networkInputSendClaimNL(NetworkInput * input) { - if (!input->connected || !input->serviceAvailable || input->destroying) + if (!input->connected || !input->serviceAvailable || input->destroying || + input->reliableCount) return false; + networkInputClearReliableNL(input); networkInputClearStateNL(input); networkInputInvalidateLEDsNL(input); const uint64_t claimEpoch = networkInputNext64(input->claimEpoch); @@ -358,9 +517,11 @@ static bool networkInputSendClaimNL(NetworkInput * input) 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) + const LG_TransportStatus status = networkInputSendReliableNL(input, + LG_NET_INPUT_MESSAGE_CLAIM, + LG_NET_ENVELOPE_ACK_REQUIRED, claimEpoch, claimEpoch, + payload, sizeof(payload)); + if (status != LG_TRANSPORT_OK) { input->nextClaimNs = networkInputDeadline( NETWORK_INPUT_CLAIM_RETRY_MS); @@ -375,7 +536,7 @@ static bool networkInputSendClaimNL(NetworkInput * input) return true; } -static bool networkInputSendControlNL(NetworkInput * input, +static LG_TransportStatus networkInputSendControlNL(NetworkInput * input, uint16_t messageType, uint32_t flags, uint32_t leaseMs) { const uint64_t sequence = networkInputNext64(input->sequence); @@ -388,18 +549,24 @@ static bool networkInputSendControlNL(NetworkInput * input, .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; + if (!lgNetInputControlEncode(payload, sizeof(payload), &control)) + return LG_TRANSPORT_ERROR; + + const LG_TransportStatus status = networkInputSubmitReliableNL(input, + messageType, 0, 0, payload, sizeof(payload)); + if (status != LG_TRANSPORT_OK) + return status; input->sequence = sequence; - return true; + return LG_TRANSPORT_OK; } static bool networkInputAbandonClaimNL(NetworkInput * input, bool tryReset) { + networkInputClearReliableNL(input); + networkInputClearStateNL(input); + networkInputInvalidateLEDsNL(input); if (input->claimed && networkConnectionValid(input->connection)) { if (tryReset) @@ -416,19 +583,28 @@ static bool networkInputAbandonClaimNL(NetworkInput * input, 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); + LG_TransportStatus status = LG_TRANSPORT_OK; + if (input->claimed) + { + networkInputClearReliableNL(input); + networkInputClearStateNL(input); + status = networkConnectionValid(input->connection) ? + networkInputSendControlNL(input, LG_NET_INPUT_MESSAGE_RESET, + LG_NET_INPUT_CLAIM_KEYBOARD | LG_NET_INPUT_CLAIM_POINTER, 0) : + LG_TRANSPORT_DISCONNECTED; + } + else + networkInputClearStateNL(input); + if (status == LG_TRANSPORT_DISCONNECTED) + networkInputClearReliableNL(input); + if (!networkInputTransportFatal(status)) + return false; + return networkInputAbandonClaimNL(input, false); } static bool networkInputSendReleaseNL(NetworkInput * input) @@ -437,7 +613,7 @@ static bool networkInputSendReleaseNL(NetworkInput * input) if ((input->claimed || input->claimPending) && networkConnectionValid(input->connection)) sent = networkInputSendControlNL(input, LG_NET_INPUT_MESSAGE_RELEASE, - input->claimFlags, 0); + input->claimFlags, 0) == LG_TRANSPORT_OK; input->claimPending = false; input->claimed = false; @@ -456,12 +632,12 @@ static bool networkInputNextSequenceNL(NetworkInput * input, return true; } -static bool networkInputSendKeyboardNL(NetworkInput * input, +static LG_TransportStatus networkInputSendKeyboardNL(NetworkInput * input, uint16_t usage, bool down, bool repeat) { uint64_t sequence; if (!networkInputNextSequenceNL(input, &sequence)) - return false; + return LG_TRANSPORT_UNAVAILABLE; LGNetInputKeyboard keyboard = { @@ -478,13 +654,16 @@ static bool networkInputSendKeyboardNL(NetworkInput * input, 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; + if (!lgNetInputKeyboardEncode(payload, sizeof(payload), &keyboard)) + return LG_TRANSPORT_ERROR; + + const LG_TransportStatus status = networkInputSubmitReliableNL(input, + LG_NET_INPUT_MESSAGE_KEYBOARD, 0, 0, payload, sizeof(payload)); + if (status != LG_TRANSPORT_OK) + return status; input->sequence = sequence; - return true; + return LG_TRANSPORT_OK; } static uint32_t networkInputLEDForUsage(uint16_t usage) @@ -512,10 +691,12 @@ static bool networkInputTapLockKeyNL(NetworkInput * input, input->keyState[key->usage]) return false; - if (!networkInputSendKeyboardNL(input, key->usage, true, false)) + if (networkInputSendKeyboardNL(input, key->usage, true, false) != + LG_TRANSPORT_OK) return false; networkInputRecordLEDToggleNL(input, key->led, false); - if (networkInputSendKeyboardNL(input, key->usage, false, false)) + if (networkInputSendKeyboardNL(input, key->usage, false, false) == + LG_TRANSPORT_OK) return true; *notify |= networkInputSendResetNL(input); @@ -590,6 +771,9 @@ static bool networkInputTimer(void * opaque) if (input->connected && input->serviceAvailable) { + if (input->sendReadyPending || input->reliableCount || + input->pendingRelative || input->pendingAbsolute) + networkInputResumeSendsNL(input, ¬ify); if (input->claimPending && now >= input->nextClaimNs) input->claimPending = false; if (!input->claimPending && !input->claimed && @@ -597,12 +781,13 @@ static bool networkInputTimer(void * opaque) networkInputSendClaimNL(input); else if (input->claimed && now >= input->nextKeepaliveNs) { - if (networkInputSendControlNL(input, - LG_NET_INPUT_MESSAGE_KEEPALIVE, - input->claimFlags, NETWORK_INPUT_LEASE_MS)) + const LG_TransportStatus status = networkInputSendControlNL(input, + LG_NET_INPUT_MESSAGE_KEEPALIVE, + input->claimFlags, NETWORK_INPUT_LEASE_MS); + if (status == LG_TRANSPORT_OK) input->nextKeepaliveNs = networkInputDeadline( NETWORK_INPUT_KEEPALIVE_MS); - else + else if (networkInputTransportFatal(status)) notify |= networkInputAbandonClaimNL(input, true); } @@ -705,7 +890,9 @@ static bool networkInputUpdateKey(void * opaque, int key, bool down) input->modifiers &= (uint8_t)~mask; } - if (!networkInputSendKeyboardNL(input, usage, down, false)) + const LG_TransportStatus status = + networkInputSendKeyboardNL(input, usage, down, false); + if (status != LG_TRANSPORT_OK) { input->keyState[usage] = previous; if (usage >= 224 && usage <= 231) @@ -716,7 +903,7 @@ static bool networkInputUpdateKey(void * opaque, int key, bool down) else input->modifiers &= (uint8_t)~mask; } - if (!down) + if (!down && networkInputTransportFatal(status)) notify = networkInputAbandonClaimNL(input, true); LG_UNLOCK(input->stateLock); if (notify) @@ -790,20 +977,30 @@ static void networkInputKeyboardLEDsReset(void * opaque) 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) +static int64_t networkInputSaturatingAdd(int64_t value, int32_t delta) +{ + if (delta > 0 && value > INT64_MAX - delta) + return INT64_MAX; + if (delta < 0 && value < INT64_MIN - delta) + return INT64_MIN; + return value + delta; +} + +static LG_TransportStatus networkInputTransmitRelativeNL( + NetworkInput * input, int32_t wheelX, int32_t wheelY, + uint32_t buttons, uint32_t changedButtons, bool datagram, + bool acknowledge) { uint64_t sequence; if (!networkInputNextSequenceNL(input, &sequence)) - return false; + return LG_TRANSPORT_UNAVAILABLE; LGNetInputRelative relative = { .sequence = sequence, .timestampNs = nanotime(), - .deltaX = x, - .deltaY = y, + .cumulativeX = input->cumulativeX, + .cumulativeY = input->cumulativeY, .wheelX = wheelX, .wheelY = wheelY, .buttons = buttons, @@ -814,40 +1011,43 @@ static bool networkInputSendRelativeNL(NetworkInput * input, uint8_t payload[LG_NET_INPUT_RELATIVE_WIRE_SIZE]; if (!lgNetInputRelativeEncode(payload, sizeof(payload), &relative)) - return false; + return LG_TRANSPORT_ERROR; - 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, + const LG_TransportStatus status = datagram ? + networkInputSendDatagramNL(input, + LG_NET_INPUT_MESSAGE_MOUSE_RELATIVE, sequence, + payload, sizeof(payload)) : + networkInputSubmitReliableNL(input, + LG_NET_INPUT_MESSAGE_MOUSE_RELATIVE, + acknowledge ? LG_NET_ENVELOPE_ACK_REQUIRED : 0, + acknowledge ? sequence : 0, payload, sizeof(payload)); if (status != LG_TRANSPORT_OK) - return false; + return status; - input->sequence = sequence; - input->mouseAbsolute = false; - return true; + input->sequence = sequence; + input->pointerSequence = sequence; + if (acknowledge) + input->reliableBarrierSequence = sequence; + return LG_TRANSPORT_OK; } -static bool networkInputSendAbsoluteNL(NetworkInput * input, - uint32_t x, uint32_t y, uint32_t width, uint32_t height, +static LG_TransportStatus networkInputTransmitAbsoluteNL( + 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) + uint32_t changedButtons, bool datagram, bool acknowledge) { uint64_t sequence; if (!networkInputNextSequenceNL(input, &sequence)) - return false; + return LG_TRANSPORT_UNAVAILABLE; LGNetInputAbsolute absolute = { .sequence = sequence, .timestampNs = nanotime(), + .cumulativeX = input->cumulativeX, + .cumulativeY = input->cumulativeY, .x = (int32_t)x, .y = (int32_t)y, .width = width, @@ -862,39 +1062,253 @@ static bool networkInputSendAbsoluteNL(NetworkInput * input, uint8_t payload[LG_NET_INPUT_ABSOLUTE_WIRE_SIZE]; if (!lgNetInputAbsoluteEncode(payload, sizeof(payload), &absolute)) - return false; + return LG_TRANSPORT_ERROR; - 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, + const LG_TransportStatus status = datagram ? + networkInputSendDatagramNL(input, + LG_NET_INPUT_MESSAGE_MOUSE_ABSOLUTE, sequence, + payload, sizeof(payload)) : + networkInputSubmitReliableNL(input, + LG_NET_INPUT_MESSAGE_MOUSE_ABSOLUTE, + acknowledge ? LG_NET_ENVELOPE_ACK_REQUIRED : 0, + acknowledge ? sequence : 0, payload, sizeof(payload)); if (status != LG_TRANSPORT_OK) - return false; + return status; - input->sequence = sequence; - input->mouseAbsolute = true; + input->sequence = sequence; + input->pointerSequence = sequence; + if (acknowledge) + input->reliableBarrierSequence = sequence; + return LG_TRANSPORT_OK; +} + +static LG_TransportStatus networkInputFlushMotionNL(NetworkInput * input) +{ + if (input->reliableCount) + return LG_TRANSPORT_UNAVAILABLE; + + if (input->pendingAbsolute) + { + const bool acknowledge = input->motionBarrier || + !input->mouseAbsolute; + const bool datagram = input->datagramAvailable && + !input->pendingMotionReliable && !input->motionBarrier && + !input->reliableBarrierSequence; + const LG_TransportStatus status = networkInputTransmitAbsoluteNL(input, + input->absoluteX, input->absoluteY, + input->absoluteWidth, input->absoluteHeight, + 0, 0, input->mouseButtons, 0, datagram, acknowledge); + if (status != LG_TRANSPORT_OK) + return status; + input->mouseAbsolute = true; + input->pendingRelative = false; + input->pendingAbsolute = false; + input->pendingMotionReliable = false; + input->motionBarrier = false; + return LG_TRANSPORT_OK; + } + + if (input->pendingRelative) + { + const bool acknowledge = input->motionBarrier || + input->mouseAbsolute; + const bool datagram = input->datagramAvailable && + !input->pendingMotionReliable && !input->motionBarrier && + !input->reliableBarrierSequence; + const LG_TransportStatus status = networkInputTransmitRelativeNL(input, + 0, 0, input->mouseButtons, 0, datagram, acknowledge); + if (status != LG_TRANSPORT_OK) + return status; + input->mouseAbsolute = false; + input->pendingRelative = false; + input->pendingAbsolute = false; + input->pendingMotionReliable = false; + input->motionBarrier = false; + } + return LG_TRANSPORT_OK; +} + +static LG_TransportStatus networkInputSendRelativeNL(NetworkInput * input, + int32_t x, int32_t y, int32_t wheelX, int32_t wheelY, + uint32_t buttons, uint32_t changedButtons, bool motion) +{ + if (motion) + { + const bool preserveReliable = input->pendingRelative && + input->pendingMotionReliable; + if (!input->pendingRelative && + (input->mouseAbsolute || input->pendingAbsolute)) + { + const LG_TransportStatus baselineStatus = + networkInputTransmitAbsoluteNL(input, + input->absoluteX, input->absoluteY, + input->absoluteWidth, input->absoluteHeight, + 0, 0, buttons, 0, false, true); + if (baselineStatus != LG_TRANSPORT_OK) + return baselineStatus; + + input->pendingAbsolute = false; + input->mouseAbsolute = true; + } + input->cumulativeX = + networkInputSaturatingAdd(input->cumulativeX, x); + input->cumulativeY = + networkInputSaturatingAdd(input->cumulativeY, y); + input->pendingRelative = true; + input->pendingAbsolute = false; + input->pendingMotionReliable = preserveReliable || input->mouseAbsolute || + input->motionBarrier || input->reliableCount; + return networkInputFlushMotionNL(input); + } + + const LG_TransportStatus status = networkInputTransmitRelativeNL(input, + wheelX, wheelY, buttons, changedButtons, false, true); + if (status == LG_TRANSPORT_OK) + { + input->mouseAbsolute = false; + input->pendingRelative = false; + input->pendingAbsolute = false; + input->pendingMotionReliable = false; + input->motionBarrier = false; + } + return status; +} + +static LG_TransportStatus 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) +{ input->absoluteX = x; input->absoluteY = y; input->absoluteWidth = width; input->absoluteHeight = height; - return true; + + if (motion) + { + const bool preserveReliable = input->pendingAbsolute && + input->pendingMotionReliable; + const bool rebaseline = input->pendingRelative; + input->pendingRelative = false; + input->pendingAbsolute = true; + input->pendingMotionReliable = preserveReliable || rebaseline || + !input->mouseAbsolute || input->motionBarrier || input->reliableCount; + return networkInputFlushMotionNL(input); + } + + const LG_TransportStatus status = networkInputTransmitAbsoluteNL(input, + x, y, width, height, wheelX, wheelY, + buttons, changedButtons, false, true); + if (status == LG_TRANSPORT_OK) + { + input->mouseAbsolute = true; + input->pendingRelative = false; + input->pendingAbsolute = false; + input->pendingMotionReliable = false; + input->motionBarrier = false; + } + return status; +} + +static void networkInputResumeSendsNL(NetworkInput * input, bool * notify) +{ + input->sendReadyPending = false; + if (!input->connected || !input->serviceAvailable || input->destroying) + return; + + const LG_TransportStatus reliableStatus = + networkInputDrainReliableNL(input); + if (networkInputTransportFatal(reliableStatus)) + { + *notify |= networkInputAbandonClaimNL(input, true); + return; + } + if (reliableStatus != LG_TRANSPORT_OK) + return; + + if (!input->claimPending && !input->claimed) + { + networkInputSendClaimNL(input); + return; + } + if (!input->claimed || + (!input->pendingRelative && !input->pendingAbsolute)) + return; + + const LG_TransportStatus status = networkInputFlushMotionNL(input); + if (networkInputTransportFatal(status)) + *notify |= networkInputAbandonClaimNL(input, true); +} + +void networkInputSendReady(NetworkInput * input, + NetworkPacketDelivery delivery) +{ + if (!input || delivery == NETWORK_PACKET_DELIVERY_BULK) + return; + if (l_networkInputSending == input) + { + input->sendReadyPending = true; + return; + } + + bool notify = false; + LG_LOCK(input->stateLock); + const bool motionReliable = input->reliableCount || + input->pendingMotionReliable || + input->motionBarrier || input->reliableBarrierSequence || + !input->datagramAvailable; + const bool relevant = delivery == (motionReliable ? + NETWORK_PACKET_DELIVERY_RELIABLE : + NETWORK_PACKET_DELIVERY_DATAGRAM); + if (relevant || (!input->claimPending && !input->claimed)) + networkInputResumeSendsNL(input, ¬ify); + LG_UNLOCK(input->stateLock); + if (notify) + networkInputNotifyStatus(input); +} + +void networkInputDatagramLost(NetworkInput * input, + uint64_t claimEpoch, uint64_t pointerSequence) +{ + if (!input) + return; + + bool notify = false; + LG_LOCK(input->stateLock); + if (!input->destroying && input->connected && + input->serviceAvailable && input->claimed && + input->claimEpoch == claimEpoch && pointerSequence && + input->pointerSequence == pointerSequence && + !input->motionBarrier && !input->reliableBarrierSequence) + { + if (!input->pendingRelative && !input->pendingAbsolute) + { + input->pendingRelative = !input->mouseAbsolute; + input->pendingAbsolute = input->mouseAbsolute; + } + input->pendingMotionReliable = true; + input->motionBarrier = true; + networkInputResumeSendsNL(input, ¬ify); + } + LG_UNLOCK(input->stateLock); + + if (notify) + networkInputNotifyStatus(input); } 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, + const bool eligible = input->available && + (input->claimFlags & LG_NET_INPUT_CLAIM_POINTER); + LG_TransportStatus status = LG_TRANSPORT_UNAVAILABLE; + if (eligible) + status = networkInputSendRelativeNL(input, x, y, 0, 0, input->mouseButtons, 0, true); + const bool result = eligible && + !networkInputTransportFatal(status); LG_UNLOCK(input->stateLock); return result; } @@ -910,10 +1324,14 @@ static bool networkInputMousePosition(void * opaque, uint32_t x, 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, + const bool eligible = input->available && + (input->claimFlags & LG_NET_INPUT_CLAIM_POINTER); + LG_TransportStatus status = LG_TRANSPORT_UNAVAILABLE; + if (eligible) + status = networkInputSendAbsoluteNL(input, x, y, width, height, 0, 0, input->mouseButtons, 0, true); + const bool result = eligible && + !networkInputTransportFatal(status); LG_UNLOCK(input->stateLock); return result; } @@ -975,26 +1393,28 @@ static bool networkInputUpdateButton(void * opaque, return false; } - const uint32_t buttons = down ? + const uint32_t buttons = down ? input->mouseButtons | mask : input->mouseButtons & ~mask; - bool result; - if (input->mouseAbsolute && input->absoluteWidth && + LG_TransportStatus status; + const bool absolute = input->pendingAbsolute || + (input->mouseAbsolute && !input->pendingRelative); + if (absolute && input->absoluteWidth && input->absoluteHeight) - result = networkInputSendAbsoluteNL(input, + status = networkInputSendAbsoluteNL(input, input->absoluteX, input->absoluteY, input->absoluteWidth, input->absoluteHeight, 0, wheelY, buttons, mask, false); else - result = networkInputSendRelativeNL(input, 0, 0, 0, wheelY, + status = networkInputSendRelativeNL(input, 0, 0, 0, wheelY, buttons, mask, false); - if (result && mask) + if (status == LG_TRANSPORT_OK && mask) input->mouseButtons = buttons; - else if (!result && !down && mask) + else if (networkInputTransportFatal(status) && !down && mask) notify = networkInputAbandonClaimNL(input, true); LG_UNLOCK(input->stateLock); if (notify) networkInputNotifyStatus(input); - return result; + return status == LG_TRANSPORT_OK; } static bool networkInputMousePress(void * opaque, unsigned int button) @@ -1089,8 +1509,10 @@ void networkInputDestroy(NetworkInput ** handle) LG_LOCK(input->stateLock); networkInputSendResetNL(input); networkInputSendReleaseNL(input); - input->connected = false; - input->serviceAvailable = false; + networkInputClearReliableNL(input); + input->connected = false; + input->serviceAvailable = false; + input->datagramAvailable = false; networkInputSetAvailableNL(input, false); LG_UNLOCK(input->stateLock); @@ -1110,36 +1532,43 @@ void networkInputConnectionState(NetworkInput * input, bool connected) if (!input) return; - uint16_t serviceVersion = 0; - const bool serviceAvailable = connected && + 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; + const bool datagramAvailable = serviceAvailable && + networkConnectionServiceAvailable(input->connection, + LG_NET_SERVICE_INPUT, LG_NET_CAPABILITY_DATAGRAM); + bool notify = false; LG_LOCK(input->stateLock); if (!connected || !serviceAvailable) { notify |= networkInputSendResetNL(input); networkInputSendReleaseNL(input); - input->connected = connected; - input->serviceAvailable = false; - input->serviceVersion = 0; + networkInputClearReliableNL(input); + input->connected = connected; + input->serviceAvailable = false; + input->datagramAvailable = 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; + input->connected = true; + input->serviceAvailable = true; + input->datagramAvailable = datagramAvailable; + input->serviceVersion = serviceVersion; + input->claimPending = false; + input->claimed = false; + input->claimFlags = 0; + input->sequence = 0; + input->nextClaimNs = 0; + networkInputClearReliableNL(input); networkInputClearStateNL(input); networkInputInvalidateLEDsNL(input); notify |= networkInputSetAvailableNL(input, false); @@ -1160,6 +1589,11 @@ static bool networkInputHandleStatus(NetworkInput * input, return false; if (packet->envelope.componentEpoch != status.claimEpoch) return false; + const bool barrierAck = + packet->envelope.messageType == LG_NET_INPUT_MESSAGE_STATUS && + (packet->envelope.flags & LG_NET_ENVELOPE_RESPONSE) && + packet->envelope.requestID && + packet->envelope.requestID <= status.lastSequence; bool notify = false; LG_LOCK(input->stateLock); @@ -1186,6 +1620,7 @@ static bool networkInputHandleStatus(NetworkInput * input, { input->nextClaimNs = networkInputDeadline( NETWORK_INPUT_CLAIM_RETRY_MS); + networkInputClearReliableNL(input); networkInputClearStateNL(input); networkInputInvalidateLEDsNL(input); } @@ -1193,10 +1628,12 @@ static bool networkInputHandleStatus(NetworkInput * input, break; case LG_NET_INPUT_STATUS_UNSUPPORTED: - input->claimPending = false; - input->claimed = false; - input->claimFlags = 0; - input->serviceAvailable = false; + input->claimPending = false; + input->claimed = false; + input->claimFlags = 0; + input->serviceAvailable = false; + input->datagramAvailable = false; + networkInputClearReliableNL(input); networkInputClearStateNL(input); networkInputInvalidateLEDsNL(input); notify = networkInputSetAvailableNL(input, false); @@ -1209,6 +1646,7 @@ static bool networkInputHandleStatus(NetworkInput * input, input->claimPending = false; input->claimed = false; input->claimFlags = 0; + networkInputClearReliableNL(input); networkInputClearStateNL(input); networkInputInvalidateLEDsNL(input); input->nextClaimNs = networkInputDeadline( @@ -1216,6 +1654,13 @@ static bool networkInputHandleStatus(NetworkInput * input, notify = networkInputSetAvailableNL(input, false); break; } + if (status.status == LG_NET_INPUT_STATUS_ACCEPTED && barrierAck && + input->reliableBarrierSequence && + packet->envelope.requestID >= input->reliableBarrierSequence) + { + input->reliableBarrierSequence = 0; + networkInputResumeSendsNL(input, ¬ify); + } LG_UNLOCK(input->stateLock); if (notify) @@ -1276,18 +1721,30 @@ bool networkInputPacket(NetworkInput * input, packet->envelope.service != LG_NET_SERVICE_INPUT) return false; + const LGNetEnvelopeFlags baseFlags = + LG_NET_ENVELOPE_RELIABLE | LG_NET_ENVELOPE_FINAL; switch (packet->envelope.messageType) { case LG_NET_INPUT_MESSAGE_CLAIM_RESULT: - if (!(packet->envelope.flags & LG_NET_ENVELOPE_RESPONSE) || + if (packet->envelope.flags != + (baseFlags | LG_NET_ENVELOPE_RESPONSE) || packet->envelope.requestID != packet->envelope.componentEpoch) return false; return networkInputHandleStatus(input, packet); case LG_NET_INPUT_MESSAGE_STATUS: + { + const LGNetEnvelopeFlags expectedFlags = packet->envelope.requestID ? + baseFlags | LG_NET_ENVELOPE_RESPONSE : baseFlags; + if (packet->envelope.flags != expectedFlags) + return false; return networkInputHandleStatus(input, packet); + } case LG_NET_INPUT_MESSAGE_KEYBOARD_LEDS: + if (packet->envelope.flags != baseFlags || + packet->envelope.requestID) + return false; return networkInputHandleLEDs(input, packet); default: diff --git a/client/transports/Network/input.h b/client/transports/Network/input.h index 83eb34d5..8a94d54c 100644 --- a/client/transports/Network/input.h +++ b/client/transports/Network/input.h @@ -37,5 +37,9 @@ void networkInputDestroy(NetworkInput ** input); const LG_InputOps * networkInputGetOps(void); void networkInputConnectionState(NetworkInput * input, bool connected); +void networkInputSendReady(NetworkInput * input, + NetworkPacketDelivery delivery); +void networkInputDatagramLost(NetworkInput * input, + uint64_t claimEpoch, uint64_t pointerSequence); bool networkInputPacket(NetworkInput * input, const LGNetPacketView * packet, bool datagram); diff --git a/client/transports/Network/network.c b/client/transports/Network/network.c index 7206deda..51c71bea 100644 --- a/client/transports/Network/network.c +++ b/client/transports/Network/network.c @@ -58,6 +58,7 @@ #define NETWORK_FILE_MAX_PAYLOAD \ (LG_NET_FILE_CHUNK_HEADER_WIRE_SIZE + NETWORK_TRANSFER_CHUNK) #define NETWORK_VIDEO_MAX_PAYLOAD LG_NET_MAX_PAYLOAD_LENGTH +#define NETWORK_INPUT_MAX_PAYLOAD LG_NET_INPUT_ABSOLUTE_WIRE_SIZE static const LGNetCapability NETWORK_CAPABILITIES[] = { @@ -91,9 +92,9 @@ static const LGNetCapability NETWORK_CAPABILITIES[] = .flags = LG_NET_CAPABILITY_RELIABLE | LG_NET_CAPABILITY_DATAGRAM, .features = 0, - .maxPayloadLength = NETWORK_MAX_PAYLOAD, - .maxDatagramPayload = NETWORK_MAX_DATAGRAM, - .maxBidirectionalStreams = 0, + .maxPayloadLength = NETWORK_INPUT_MAX_PAYLOAD, + .maxDatagramPayload = NETWORK_INPUT_MAX_PAYLOAD, + .maxBidirectionalStreams = 1, .maxUnidirectionalStreams = 0, }, { @@ -473,6 +474,8 @@ static void networkSendReady(void * opaque, LGNetService service, LG_Transport * this = opaque; const bool datagram = delivery == NETWORK_PACKET_DELIVERY_DATAGRAM; + if (service == LG_NET_SERVICE_INPUT) + networkInputSendReady(this->input, delivery); if (service == LG_NET_SERVICE_AUDIO && delivery != NETWORK_PACKET_DELIVERY_BULK) networkAudioSendReady(this->audio, datagram); @@ -484,6 +487,15 @@ static void networkSendReady(void * opaque, LGNetService service, networkVideoSendReady(this->video, datagram); } +static void networkDatagramLost(void * opaque, LGNetService service, + uint64_t componentEpoch, uint64_t applicationSequence) +{ + LG_Transport * this = opaque; + if (service == LG_NET_SERVICE_INPUT) + networkInputDatagramLost( + this->input, componentEpoch, applicationSequence); +} + static bool networkCreate(LG_Transport ** result) { if (!result) @@ -552,6 +564,7 @@ static bool networkCreate(LG_Transport ** result) .packet = networkPacket, .state = networkConnectionState, .sendReady = networkSendReady, + .datagramLost = networkDatagramLost, .capabilities = NETWORK_CAPABILITIES, .capabilityCount = sizeof(NETWORK_CAPABILITIES) / sizeof(NETWORK_CAPABILITIES[0]), diff --git a/client/transports/Network/video.c b/client/transports/Network/video.c index a5181b1b..8d44506f 100644 --- a/client/transports/Network/video.c +++ b/client/transports/Network/video.c @@ -592,7 +592,7 @@ static LG_TransportStatus networkVideoSendDatagramNL(NetworkVideo * video, { return networkConnectionSendDatagram(video->connection, LG_NET_SERVICE_VIDEO, messageType, video->serviceVersion, 0, - componentEpoch, 0, payload, payloadLength, true); + componentEpoch, 0, payload, payloadLength, 0, true); } static void networkVideoSendSubscribeNL(NetworkVideo * video, uint64_t now) diff --git a/idd/LGCommon/CPipeEndpoint.cpp b/idd/LGCommon/CPipeEndpoint.cpp index 388cc1b1..4c799da8 100644 --- a/idd/LGCommon/CPipeEndpoint.cpp +++ b/idd/LGCommon/CPipeEndpoint.cpp @@ -27,9 +27,12 @@ #include "CDebug.h" #include +#include #include #include +#pragma comment(lib, "avrt.lib") + const DWORD CPipeEndpoint::CLIENT_RETRY_INITIAL_MS = 100; const DWORD CPipeEndpoint::CLIENT_RETRY_MAX_MS = 2000; const DWORD CPipeEndpoint::SERVER_RETRY_MS = 1000; @@ -195,22 +198,24 @@ bool CPipeEndpoint::Start( size_t messageSize, const SECURITY_ATTRIBUTES * serverSecurity, DWORD serverOpenMode, - DWORD serverPipeMode) + DWORD serverPipeMode, + ThreadScheduling threadScheduling) { Stop(); if (!pipeName || !*pipeName || !messageSize || messageSize > MAXDWORD) return false; - m_pipeName = pipeName; - m_mode = mode; - m_messageSize = messageSize; - m_hasServerSecurity = serverSecurity != nullptr; - m_serverSecurity = serverSecurity ? *serverSecurity : + m_pipeName = pipeName; + m_mode = mode; + m_messageSize = messageSize; + m_threadScheduling = threadScheduling; + m_hasServerSecurity = serverSecurity != nullptr; + m_serverSecurity = serverSecurity ? *serverSecurity : SECURITY_ATTRIBUTES {}; - m_serverOpenMode = serverOpenMode; - m_serverPipeMode = serverPipeMode; - m_stopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); + m_serverOpenMode = serverOpenMode; + m_serverPipeMode = serverPipeMode; + m_stopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); if (!m_stopEvent) { const DWORD error = GetLastError(); @@ -362,11 +367,30 @@ DWORD WINAPI CPipeEndpoint::ThreadProc(void * context) void CPipeEndpoint::Thread() { + HANDLE avTaskHandle = nullptr; + if (m_threadScheduling == ThreadScheduling::Input) + { + DWORD avTask = 0; + bool prioritySet = false; + + avTaskHandle = AvSetMmThreadCharacteristicsW(L"Distribution", &avTask); + if (avTaskHandle) + prioritySet = + AvSetMmThreadPriority(avTaskHandle, AVRT_PRIORITY_HIGH) != FALSE; + if (!prioritySet && !SetThreadPriority( + GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL)) + DEBUG_WARN_HR(GetLastError(), + "Failed to raise input pipe thread priority"); + } + if (m_mode == Mode::Server) RunServer(); else RunClient(); + if (avTaskHandle) + AvRevertMmThreadCharacteristics(avTaskHandle); + Atomic::Store(m_running, false); Atomic::Store(m_connected, false); } diff --git a/idd/LGCommon/CPipeEndpoint.h b/idd/LGCommon/CPipeEndpoint.h index 9817f8c3..57c799d9 100644 --- a/idd/LGCommon/CPipeEndpoint.h +++ b/idd/LGCommon/CPipeEndpoint.h @@ -80,6 +80,12 @@ public: Client, }; + enum class ThreadScheduling + { + Normal, + Input, + }; + CPipeEndpoint() = default; ~CPipeEndpoint(); @@ -92,7 +98,8 @@ public: _In_ size_t messageSize, _In_opt_ const SECURITY_ATTRIBUTES * serverSecurity = nullptr, _In_ DWORD serverOpenMode = 0, - _In_ DWORD serverPipeMode = 0); + _In_ DWORD serverPipeMode = 0, + _In_ ThreadScheduling threadScheduling = ThreadScheduling::Normal); void Stop(); void DisconnectClient(); @@ -158,13 +165,14 @@ private: void ClosePipe(_In_ HANDLE pipe); std::wstring m_pipeName; - size_t m_messageSize = 0; - Mode m_mode = Mode::Client; - IPipeEndpointHandler * m_handler = nullptr; - SECURITY_ATTRIBUTES m_serverSecurity = {}; - bool m_hasServerSecurity = false; - DWORD m_serverOpenMode = 0; - DWORD m_serverPipeMode = 0; + size_t m_messageSize = 0; + Mode m_mode = Mode::Client; + ThreadScheduling m_threadScheduling = ThreadScheduling::Normal; + IPipeEndpointHandler * m_handler = nullptr; + SECURITY_ATTRIBUTES m_serverSecurity = {}; + bool m_hasServerSecurity = false; + DWORD m_serverOpenMode = 0; + DWORD m_serverPipeMode = 0; std::atomic m_running { false }; std::atomic m_connected { false }; diff --git a/idd/LGIdd/ipc/CInputPipeServer.cpp b/idd/LGIdd/ipc/CInputPipeServer.cpp index e53b523f..0a0f6d51 100644 --- a/idd/LGIdd/ipc/CInputPipeServer.cpp +++ b/idd/LGIdd/ipc/CInputPipeServer.cpp @@ -29,6 +29,7 @@ #include "CSRWLock.h" #include "InputPipeProtocol.h" +#include #include CInputPipeServer g_inputPipeServer; @@ -43,9 +44,18 @@ bool CInputPipeServer::Init() m_performanceFrequency.QuadPart = 0; if (!QueryPerformanceFrequency(&m_performanceFrequency)) m_performanceFrequency.QuadPart = 0; - m_lastStatistics = GetTickCount64(); - m_stopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); - m_queueEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); + static_assert(MOTION_AGE_BUDGET_MS && + 1000 % MOTION_AGE_BUDGET_MS == 0, + "motion age budget must evenly divide one second"); + const int64_t budgetDivisor = 1000 / MOTION_AGE_BUDGET_MS; + m_motionBudgetTicks = + m_performanceFrequency.QuadPart / budgetDivisor; + if (m_performanceFrequency.QuadPart % budgetDivisor) + ++m_motionBudgetTicks; + + m_lastStatistics = GetTickCount64(); + m_stopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); + m_queueEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); if (!m_stopEvent || !m_queueEvent) { DEBUG_ERROR_HR(GetLastError(), @@ -119,6 +129,89 @@ void CInputPipeServer::DeInit() m_statSlowWrites = 0; m_statWriteTicks = 0; m_statMaxWriteTicks = 0; + + m_motionBudgetTicks = 0; + m_performanceFrequency.QuadPart = 0; +} + +bool CInputPipeServer::MotionBudgetExceededLocked() const +{ + if (m_queueCount >= MOTION_EMERGENCY_DEPTH) + return true; + if (!m_queueCount || !m_motionBudgetTicks) + return false; + + LARGE_INTEGER now = {}; + if (!QueryPerformanceCounter(&now)) + return false; + + const int64_t oldest = m_queue[m_queueHead].enqueueTicks; + return oldest > 0 && now.QuadPart >= oldest && + now.QuadPart - oldest >= m_motionBudgetTicks; +} + +CInputPipeServer::QueueItem& CInputPipeServer::GetQueueItemLocked( + size_t position) +{ + return m_queue[(m_queueHead + position) % QUEUE_LENGTH]; +} + +void CInputPipeServer::RemoveQueueItemLocked(size_t position) +{ + for (size_t i = position; i + 1 < m_queueCount; ++i) + GetQueueItemLocked(i) = GetQueueItemLocked(i + 1); + --m_queueCount; +} + +bool CInputPipeServer::CompactQueuedMotionLocked() +{ + for (size_t i = 0; i + 1 < m_queueCount; ++i) + { + QueueItem& first = GetQueueItemLocked(i); + QueueItem& second = GetQueueItemLocked(i + 1); + if (!first.pureMotion || !second.pureMotion || + first.type != second.type || first.state != second.state) + continue; + + if (first.type == LG_INPUT_PIPE_MESSAGE_MOUSE_RELATIVE) + { + if (first.payload.mouseRelative.buttons != + second.payload.mouseRelative.buttons || + first.payload.mouseRelative.wheel || + second.payload.mouseRelative.wheel) + continue; + + const int64_t x = static_cast( + first.payload.mouseRelative.deltaX) + + second.payload.mouseRelative.deltaX; + const int64_t y = static_cast( + first.payload.mouseRelative.deltaY) + + second.payload.mouseRelative.deltaY; + if (x < LG_INPUT_MOUSE_DELTA_MIN || x > LG_INPUT_MOUSE_DELTA_MAX || + y < LG_INPUT_MOUSE_DELTA_MIN || y > LG_INPUT_MOUSE_DELTA_MAX) + continue; + + first.payload.mouseRelative.deltaX = static_cast(x); + first.payload.mouseRelative.deltaY = static_cast(y); + RemoveQueueItemLocked(i + 1); + ++m_statRelativeCoalesced; + return true; + } + + if (first.type != LG_INPUT_PIPE_MESSAGE_MOUSE_ABSOLUTE || + first.payload.mouseAbsolute.buttons != + second.payload.mouseAbsolute.buttons || + first.payload.mouseAbsolute.wheel || + second.payload.mouseAbsolute.wheel) + continue; + + first.payload.mouseAbsolute.x = second.payload.mouseAbsolute.x; + first.payload.mouseAbsolute.y = second.payload.mouseAbsolute.y; + RemoveQueueItemLocked(i + 1); + ++m_statAbsoluteCoalesced; + return true; + } + return false; } bool CInputPipeServer::QueueLocked( @@ -126,20 +219,26 @@ bool CInputPipeServer::QueueLocked( const KVMFRInputPayload& payload, bool pureMotion) { + const bool motionBudgetExceeded = MotionBudgetExceededLocked(); + if (motionBudgetExceeded) + while (CompactQueuedMotionLocked()) + { + } + if (m_queueCount) { const size_t tailIndex = (m_queueHead + m_queueCount - 1) % QUEUE_LENGTH; - QueueItem& tail = m_queue[tailIndex]; + QueueItem& tail = m_queue[tailIndex]; if (tail.type == type) { if (type == LG_INPUT_PIPE_MESSAGE_MOUSE_RELATIVE && - m_queueCount >= MOTION_COALESCE_THRESHOLD && tail.pureMotion && pureMotion && tail.payload.mouseRelative.wheel == 0 && payload.mouseRelative.wheel == 0 && tail.payload.mouseRelative.buttons == - payload.mouseRelative.buttons) + payload.mouseRelative.buttons && + motionBudgetExceeded) { const int64_t x = static_cast( tail.payload.mouseRelative.deltaX) + @@ -181,16 +280,23 @@ bool CInputPipeServer::QueueRawLocked( const KVMFRInputPayload& payload, bool pureMotion) { + if (MotionBudgetExceededLocked()) + while (CompactQueuedMotionLocked()) + { + } if (m_queueCount == QUEUE_LENGTH) return false; - const size_t index = + const size_t index = (m_queueHead + m_queueCount) % QUEUE_LENGTH; - m_queue[index].type = type; - m_queue[index].state = + LARGE_INTEGER enqueueTime = {}; + QueryPerformanceCounter(&enqueueTime); + m_queue[index].type = type; + m_queue[index].state = Atomic::Load(m_state, std::memory_order_relaxed); - m_queue[index].payload = payload; - m_queue[index].pureMotion = pureMotion; + m_queue[index].enqueueTicks = enqueueTime.QuadPart; + m_queue[index].payload = payload; + m_queue[index].pureMotion = pureMotion; ++m_queueCount; ++m_statEnqueued; if (m_queueCount > m_statQueueHighWater) @@ -564,6 +670,19 @@ DWORD WINAPI CInputPipeServer::ThreadProc(void * context) void CInputPipeServer::Thread() { + DWORD avTask = 0; + HANDLE avTaskHandle = nullptr; + bool prioritySet = false; + + avTaskHandle = AvSetMmThreadCharacteristicsW(L"Distribution", &avTask); + if (avTaskHandle) + prioritySet = + AvSetMmThreadPriority(avTaskHandle, AVRT_PRIORITY_HIGH) != FALSE; + if (!prioritySet && !SetThreadPriority( + GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL)) + DEBUG_WARN_HR(GetLastError(), + "Failed to raise LGInput sender thread priority"); + const HANDLE handles[] = { m_stopEvent, m_queueEvent }; for (;;) { @@ -588,6 +707,8 @@ void CInputPipeServer::Thread() } Invalidate(0, false); + if (avTaskHandle) + AvRevertMmThreadCharacteristics(avTaskHandle); } void CInputPipeServer::OnPipeConnected() diff --git a/idd/LGIdd/ipc/CInputPipeServer.h b/idd/LGIdd/ipc/CInputPipeServer.h index 777498f1..eb2d44c0 100644 --- a/idd/LGIdd/ipc/CInputPipeServer.h +++ b/idd/LGIdd/ipc/CInputPipeServer.h @@ -32,14 +32,16 @@ class CInputPipeServer : public IInputSink, private IPipeEndpointHandler { private: - static constexpr size_t QUEUE_LENGTH = 128; - static constexpr size_t MOTION_COALESCE_THRESHOLD = QUEUE_LENGTH / 2; - static constexpr DWORD STATISTICS_INTERVAL_MS = 5000; + static constexpr size_t QUEUE_LENGTH = 128; + static constexpr size_t MOTION_EMERGENCY_DEPTH = 8; + static constexpr DWORD MOTION_AGE_BUDGET_MS = 1; + static constexpr DWORD STATISTICS_INTERVAL_MS = 5000; struct QueueItem { KVMFRInputPayload payload; uint64_t state; + int64_t enqueueTicks; LGInputPipeMessageType type; bool pureMotion; }; @@ -84,15 +86,20 @@ private: uint64_t m_statResyncDiscarded = 0; size_t m_statQueueHighWater = 0; - uint64_t m_statSent = 0; - uint64_t m_statStale = 0; - uint64_t m_statWriteFailed = 0; - uint64_t m_statSlowWrites = 0; - int64_t m_statWriteTicks = 0; - int64_t m_statMaxWriteTicks = 0; - ULONGLONG m_lastStatistics = 0; + uint64_t m_statSent = 0; + uint64_t m_statStale = 0; + uint64_t m_statWriteFailed = 0; + uint64_t m_statSlowWrites = 0; + int64_t m_statWriteTicks = 0; + int64_t m_statMaxWriteTicks = 0; + int64_t m_motionBudgetTicks = 0; + ULONGLONG m_lastStatistics = 0; LARGE_INTEGER m_performanceFrequency = {}; + bool MotionBudgetExceededLocked() const; + QueueItem& GetQueueItemLocked(size_t position); + void RemoveQueueItemLocked(size_t position); + bool CompactQueuedMotionLocked(); bool QueueLocked(LGInputPipeMessageType type, const KVMFRInputPayload& payload, bool pureMotion); bool QueueRawLocked(LGInputPipeMessageType type, diff --git a/idd/LGIdd/transport/CInputHub.cpp b/idd/LGIdd/transport/CInputHub.cpp index be152403..cbd5922a 100644 --- a/idd/LGIdd/transport/CInputHub.cpp +++ b/idd/LGIdd/transport/CInputHub.cpp @@ -207,29 +207,31 @@ bool CInputHub::Bind( void CInputHub::Unbind(BackendId backend, uint32_t epoch) { CSRWExclusiveLock lifecycleLock(m_lifecycleLock); - Source * selected = nullptr; - IInputSource * input = nullptr; - bool stop = false; + Source * selected = nullptr; + IInputSource * input = nullptr; + bool stop = false; { + CSRWExclusiveLock authorityLock(m_authorityLock); CSRWExclusiveLock lock(m_lock); for (Source& source : m_sources) if ((source.active || source.failed || source.reserved) && source.backend == backend && source.epoch == epoch) { - source.active = false; - source.failed = false; + source.active = false; + source.failed = false; source.failurePending = false; - source.reserved = true; - input = source.endpoint; - stop = source.running; - source.running = false; - selected = &source; + source.reserved = true; + input = source.endpoint; + stop = source.running; + source.running = false; + selected = &source; if (m_owner.backend == backend && m_owner.epoch == epoch) { if (m_sink) m_sink->Reset(); - m_owner = {}; + m_owner = {}; m_ownerDeadline = 0; + AdvanceAuthorityGeneration(); } if (m_interactionOwner.backend == backend && m_interactionOwner.epoch == epoch) @@ -246,16 +248,17 @@ void CInputHub::Unbind(BackendId backend, uint32_t epoch) if (input && stop) input->Stop(); + CSRWExclusiveLock authorityLock(m_authorityLock); CSRWExclusiveLock lock(m_lock); if (selected->reserved && selected->backend == backend && selected->epoch == epoch) { - selected->backend = 0; - selected->epoch = 0; - selected->endpoint = nullptr; - selected->running = false; - selected->reserved = false; - selected->failed = false; + selected->backend = 0; + selected->epoch = 0; + selected->endpoint = nullptr; + selected->running = false; + selected->reserved = false; + selected->failed = false; selected->failurePending = false; } } @@ -267,6 +270,7 @@ InteractionResult CInputHub::CheckInteraction( if (!source.backend || !source.epoch || !source.client) return InteractionResult::STALE; + CSRWExclusiveLock authorityLock(m_authorityLock); CSRWExclusiveLock lock(m_lock); if (m_started && m_sink) CheckState(); @@ -313,6 +317,7 @@ void CInputHub::CommitInteraction( source != permit.source) return; + CSRWExclusiveLock authorityLock(m_authorityLock); CSRWExclusiveLock lock(m_lock); if (m_started && m_sink) CheckState(); @@ -367,9 +372,10 @@ bool CInputHub::TakeFailure(SourceKey& source) bool CInputHub::Start(IInputSink& sink) { CSRWExclusiveLock lifecycleLock(m_lifecycleLock); - Source * sources[MAX_SOURCES] = {}; - unsigned count = 0; + Source * sources[MAX_SOURCES] = {}; + unsigned count = 0; { + CSRWExclusiveLock authorityLock(m_authorityLock); CSRWExclusiveLock lock(m_lock); if (m_started) return m_sink == &sink; @@ -387,10 +393,11 @@ bool CInputHub::Start(IInputSink& sink) for (unsigned i = 0; i < count; ++i) { - Source& source = *sources[i]; + Source& source = *sources[i]; const bool started = source.endpoint->Start(source); - bool stop = !started; + bool stop = !started; { + CSRWExclusiveLock authorityLock(m_authorityLock); CSRWExclusiveLock lock(m_lock); if (!started || source.failed || !source.active || !source.reserved) { @@ -399,8 +406,9 @@ bool CInputHub::Start(IInputSink& sink) { if (m_sink) m_sink->Reset(); - m_owner = {}; + m_owner = {}; m_ownerDeadline = 0; + AdvanceAuthorityGeneration(); } if (m_interactionOwner.backend == source.backend && m_interactionOwner.epoch == source.epoch) @@ -432,11 +440,12 @@ bool CInputHub::Start(IInputSink& sink) void CInputHub::Stop() { CSRWExclusiveLock lifecycleLock(m_lifecycleLock); - Source * sources[MAX_SOURCES] = {}; - unsigned count = 0; - IInputSink * sink = nullptr; - bool reset = false; + Source * sources[MAX_SOURCES] = {}; + unsigned count = 0; + IInputSink * sink = nullptr; + bool reset = false; { + CSRWExclusiveLock authorityLock(m_authorityLock); CSRWExclusiveLock lock(m_lock); if (!m_started) { @@ -450,10 +459,12 @@ void CInputHub::Stop() source.running = false; sources[count++] = &source; } - sink = m_sink; - reset = m_owner.backend != 0; + sink = m_sink; + reset = m_owner.backend != 0; m_owner = {}; m_ownerDeadline = 0; + if (reset) + AdvanceAuthorityGeneration(); InvalidateInteraction(); } @@ -462,6 +473,7 @@ void CInputHub::Stop() if (reset && sink) sink->Reset(); + CSRWExclusiveLock authorityLock(m_authorityLock); CSRWExclusiveLock lock(m_lock); m_sink = nullptr; m_sinkState = 0; @@ -520,6 +532,41 @@ void CInputHub::AdvanceInteractionSerial() Seq::Inc(m_interactionSerial); } +void CInputHub::AdvanceAuthorityGeneration() +{ + Seq::Inc(m_authorityGeneration); + AdvanceInputGeneration(); +} + +void CInputHub::AdvanceInputGeneration() +{ + Seq::Inc(m_inputGeneration); +} + +bool CInputHub::StateAvailable() const +{ + if (!m_started || !m_sink) + return false; + + const uint64_t state = m_sink->GetState(); + if (state != m_sinkState || !(state & 1)) + return false; + return !m_owner.backend || GetTickCount64() < m_ownerDeadline; +} + +InputResult CInputHub::ReconcileAuthorityState(const SourceKey& source) +{ + CSRWExclusiveLock authorityLock(m_authorityLock); + CSRWExclusiveLock lock(m_lock); + if (!SourceValid(source)) + return InputResult::STALE; + if (!CheckState()) + return InputResult::UNAVAILABLE; + if (!OwnerValid(source)) + return m_owner.backend ? InputResult::BUSY : InputResult::STALE; + return InputResult::ACCEPTED; +} + bool CInputHub::CheckState() { if (!m_started || !m_sink) @@ -529,7 +576,10 @@ bool CInputHub::CheckState() if (state != m_sinkState) { if (m_owner.backend) + { m_sink->Reset(); + AdvanceAuthorityGeneration(); + } m_owner = {}; m_ownerDeadline = 0; InvalidateInteraction(); @@ -540,13 +590,31 @@ bool CInputHub::CheckState() m_sink->Reset(); m_owner = {}; m_ownerDeadline = 0; + AdvanceAuthorityGeneration(); InvalidateInteraction(); } return (state & 1) != 0; } +void CInputHub::HandleSinkFailure( + const SourceKey& source, bool reset, uint64_t inputGeneration) +{ + CSRWExclusiveLock authorityLock(m_authorityLock); + CSRWExclusiveLock lock(m_lock); + if (inputGeneration != m_inputGeneration || !OwnerValid(source)) + return; + + if (reset && m_sink) + m_sink->Reset(); + m_owner = {}; + m_ownerDeadline = 0; + AdvanceAuthorityGeneration(); + InvalidateInteraction(); +} + InputTargetState CInputHub::GetState(const SourceKey& source) { + CSRWExclusiveLock authorityLock(m_authorityLock); CSRWExclusiveLock lock(m_lock); InputTargetState result; if (!BindingPresent(source)) @@ -563,8 +631,8 @@ InputTargetState CInputHub::GetState(const SourceKey& source) result.available = true; else if (source.client && source.generation && m_owner == source) { - result.available = true; - result.owned = true; + result.available = true; + result.owned = true; result.owner.client = m_owner.client; result.owner.generation = m_owner.generation; } @@ -577,16 +645,43 @@ bool CInputHub::WithAuthority(const SourceKey& source, if (!action) return false; - CSRWExclusiveLock lock(m_lock); - if (!SourceValid(source) || !CheckState() || !OwnerValid(source)) - return false; + for (;;) + { + // Freeze ownership while allowing the input fast path to continue under + // the shared authority guard. The action must not hold the state lock. + CSRWSharedLock authorityLock(m_authorityLock); + uint64_t generation = 0; + { + CSRWExclusiveLock lock(m_lock); + if (!SourceValid(source)) + return false; + if (StateAvailable()) + { + if (!OwnerValid(source)) + return false; + generation = m_authorityGeneration; + } + } - action(opaque); - return true; + if (!generation) + { + authorityLock.Unlock(); + if (ReconcileAuthorityState(source) != InputResult::ACCEPTED) + return false; + continue; + } + + action(opaque); + + CSRWExclusiveLock lock(m_lock); + return generation == m_authorityGeneration && + SourceValid(source) && OwnerValid(source); + } } void CInputHub::Failed(Source& source) { + CSRWExclusiveLock authorityLock(m_authorityLock); CSRWExclusiveLock lock(m_lock); if (!source.active) return; @@ -594,8 +689,9 @@ void CInputHub::Failed(Source& source) { if (m_sink) m_sink->Reset(); - m_owner = {}; + m_owner = {}; m_ownerDeadline = 0; + AdvanceAuthorityGeneration(); } if (m_interactionOwner.backend == source.backend && m_interactionOwner.epoch == source.epoch) @@ -610,6 +706,7 @@ void CInputHub::Failed(Source& source) InputResult CInputHub::Claim(const SourceKey& source) { + CSRWExclusiveLock authorityLock(m_authorityLock); CSRWExclusiveLock lock(m_lock); if (!SourceValid(source)) return InputResult::STALE; @@ -627,25 +724,38 @@ InputResult CInputHub::Claim(const SourceKey& source) } m_owner = source; m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS; + AdvanceAuthorityGeneration(); InvalidateInteraction(); return InputResult::ACCEPTED; } InputResult CInputHub::Touch(const SourceKey& source) { - CSRWExclusiveLock lock(m_lock); - if (!SourceValid(source)) - return InputResult::STALE; - if (!CheckState()) - return InputResult::UNAVAILABLE; - if (!OwnerValid(source)) - return m_owner.backend ? InputResult::BUSY : InputResult::STALE; - m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS; - return InputResult::ACCEPTED; + for (;;) + { + { + CSRWSharedLock authorityLock(m_authorityLock); + CSRWExclusiveLock lock(m_lock); + if (!SourceValid(source)) + return InputResult::STALE; + if (StateAvailable()) + { + if (!OwnerValid(source)) + return m_owner.backend ? InputResult::BUSY : InputResult::STALE; + m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS; + return InputResult::ACCEPTED; + } + } + + const InputResult result = ReconcileAuthorityState(source); + if (result != InputResult::ACCEPTED) + return result; + } } InputResult CInputHub::Release(const SourceKey& source, bool reset) { + CSRWExclusiveLock authorityLock(m_authorityLock); CSRWExclusiveLock lock(m_lock); if (!SourceValid(source)) return InputResult::STALE; @@ -656,6 +766,7 @@ InputResult CInputHub::Release(const SourceKey& source, bool reset) accepted = m_sink->Reset(); m_owner = {}; m_ownerDeadline = 0; + AdvanceAuthorityGeneration(); InvalidateInteraction(); return accepted ? InputResult::ACCEPTED : InputResult::UNAVAILABLE; } @@ -663,85 +774,134 @@ InputResult CInputHub::Release(const SourceKey& source, bool reset) InputResult CInputHub::SendMouseRelative(const SourceKey& source, int32_t deltaX, int32_t deltaY, int32_t wheel, uint32_t buttons) { - CSRWExclusiveLock lock(m_lock); - if (!SourceValid(source)) - return InputResult::STALE; - if (!CheckState()) - return InputResult::UNAVAILABLE; - if (!OwnerValid(source)) - return m_owner.backend ? InputResult::BUSY : InputResult::STALE; - if (!m_sink->SendMouseRelative(deltaX, deltaY, wheel, buttons)) + for (;;) { - m_sink->Reset(); - m_owner = {}; - m_ownerDeadline = 0; - InvalidateInteraction(); - return InputResult::UNAVAILABLE; + { + CSRWSharedLock authorityLock(m_authorityLock); + CSRWExclusiveLock lock(m_lock); + if (!SourceValid(source)) + return InputResult::STALE; + if (StateAvailable()) + { + if (!OwnerValid(source)) + return m_owner.backend ? InputResult::BUSY : InputResult::STALE; + const uint64_t inputGeneration = m_inputGeneration; + if (!m_sink->SendMouseRelative(deltaX, deltaY, wheel, buttons)) + { + lock.Unlock(); + authorityLock.Unlock(); + HandleSinkFailure(source, true, inputGeneration); + return InputResult::UNAVAILABLE; + } + m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS; + AdvanceInputGeneration(); + return InputResult::ACCEPTED; + } + } + + const InputResult result = ReconcileAuthorityState(source); + if (result != InputResult::ACCEPTED) + return result; } - m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS; - return InputResult::ACCEPTED; } InputResult CInputHub::SendMouseAbsolute(const SourceKey& source, uint16_t x, uint16_t y, int32_t wheel, uint32_t buttons) { - CSRWExclusiveLock lock(m_lock); - if (!SourceValid(source)) - return InputResult::STALE; - if (!CheckState()) - return InputResult::UNAVAILABLE; - if (!OwnerValid(source)) - return m_owner.backend ? InputResult::BUSY : InputResult::STALE; - if (!m_sink->SendMouseAbsolute(x, y, wheel, buttons)) + for (;;) { - m_sink->Reset(); - m_owner = {}; - m_ownerDeadline = 0; - InvalidateInteraction(); - return InputResult::UNAVAILABLE; + { + CSRWSharedLock authorityLock(m_authorityLock); + CSRWExclusiveLock lock(m_lock); + if (!SourceValid(source)) + return InputResult::STALE; + if (StateAvailable()) + { + if (!OwnerValid(source)) + return m_owner.backend ? InputResult::BUSY : InputResult::STALE; + const uint64_t inputGeneration = m_inputGeneration; + if (!m_sink->SendMouseAbsolute(x, y, wheel, buttons)) + { + lock.Unlock(); + authorityLock.Unlock(); + HandleSinkFailure(source, true, inputGeneration); + return InputResult::UNAVAILABLE; + } + m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS; + AdvanceInputGeneration(); + return InputResult::ACCEPTED; + } + } + + const InputResult result = ReconcileAuthorityState(source); + if (result != InputResult::ACCEPTED) + return result; } - m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS; - return InputResult::ACCEPTED; } InputResult CInputHub::SendKeyboard(const SourceKey& source, uint8_t modifiers, const uint8_t * keys) { - CSRWExclusiveLock lock(m_lock); - if (!SourceValid(source)) - return InputResult::STALE; - if (!CheckState()) - return InputResult::UNAVAILABLE; - if (!OwnerValid(source)) - return m_owner.backend ? InputResult::BUSY : InputResult::STALE; - if (!m_sink->SendKeyboard(modifiers, keys)) + for (;;) { - m_sink->Reset(); - m_owner = {}; - m_ownerDeadline = 0; - InvalidateInteraction(); - return InputResult::UNAVAILABLE; + { + CSRWSharedLock authorityLock(m_authorityLock); + CSRWExclusiveLock lock(m_lock); + if (!SourceValid(source)) + return InputResult::STALE; + if (StateAvailable()) + { + if (!OwnerValid(source)) + return m_owner.backend ? InputResult::BUSY : InputResult::STALE; + const uint64_t inputGeneration = m_inputGeneration; + if (!m_sink->SendKeyboard(modifiers, keys)) + { + lock.Unlock(); + authorityLock.Unlock(); + HandleSinkFailure(source, true, inputGeneration); + return InputResult::UNAVAILABLE; + } + m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS; + AdvanceInputGeneration(); + return InputResult::ACCEPTED; + } + } + + const InputResult result = ReconcileAuthorityState(source); + if (result != InputResult::ACCEPTED) + return result; } - m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS; - return InputResult::ACCEPTED; } InputResult CInputHub::Reset(const SourceKey& source) { - CSRWExclusiveLock lock(m_lock); - if (!SourceValid(source)) - return InputResult::STALE; - if (!CheckState()) - return InputResult::UNAVAILABLE; - if (!OwnerValid(source)) - return m_owner.backend ? InputResult::BUSY : InputResult::STALE; - if (!m_sink->Reset()) + for (;;) { - m_owner = {}; - m_ownerDeadline = 0; - InvalidateInteraction(); - return InputResult::UNAVAILABLE; + { + CSRWSharedLock authorityLock(m_authorityLock); + CSRWExclusiveLock lock(m_lock); + if (!SourceValid(source)) + return InputResult::STALE; + if (StateAvailable()) + { + if (!OwnerValid(source)) + return m_owner.backend ? InputResult::BUSY : InputResult::STALE; + const uint64_t inputGeneration = m_inputGeneration; + if (!m_sink->Reset()) + { + lock.Unlock(); + authorityLock.Unlock(); + HandleSinkFailure(source, false, inputGeneration); + return InputResult::UNAVAILABLE; + } + m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS; + AdvanceInputGeneration(); + return InputResult::ACCEPTED; + } + } + + const InputResult result = ReconcileAuthorityState(source); + if (result != InputResult::ACCEPTED) + return result; } - m_ownerDeadline = GetTickCount64() + OWNER_LEASE_MS; - return InputResult::ACCEPTED; } diff --git a/idd/LGIdd/transport/CInputHub.h b/idd/LGIdd/transport/CInputHub.h index 87854c7c..ec7120f4 100644 --- a/idd/LGIdd/transport/CInputHub.h +++ b/idd/LGIdd/transport/CInputHub.h @@ -77,15 +77,18 @@ private: mutable CSRWLock m_lock; CSRWLock m_lifecycleLock; + CSRWLock m_authorityLock; Source m_sources[MAX_SOURCES]; - IInputSink * m_sink = nullptr; - uint64_t m_sinkState = 0; + IInputSink * m_sink = nullptr; + uint64_t m_sinkState = 0; SourceKey m_owner; - uint64_t m_ownerDeadline = 0; + uint64_t m_ownerDeadline = 0; + uint64_t m_authorityGeneration = 1; + uint64_t m_inputGeneration = 1; SourceKey m_interactionOwner; - uint64_t m_interactionDeadline = 0; - uint64_t m_interactionSerial = 1; - bool m_started = false; + uint64_t m_interactionDeadline = 0; + uint64_t m_interactionSerial = 1; + bool m_started = false; bool SourceValid(const SourceKey& source) const; bool BindingPresent(const SourceKey& source) const; @@ -94,7 +97,13 @@ private: void ClearInteraction(); void InvalidateInteraction(); void AdvanceInteractionSerial(); + void AdvanceAuthorityGeneration(); + void AdvanceInputGeneration(); + bool StateAvailable() const; + InputResult ReconcileAuthorityState(const SourceKey& source); bool CheckState(); + void HandleSinkFailure(const SourceKey& source, bool reset, + uint64_t inputGeneration); InputTargetState GetState(const SourceKey& source); bool WithAuthority(const SourceKey& source, InputAuthorityAction action, void * opaque); diff --git a/idd/LGIdd/transport/network/CNetworkServer.cpp b/idd/LGIdd/transport/network/CNetworkServer.cpp index 75c805ee..8f57edae 100644 --- a/idd/LGIdd/transport/network/CNetworkServer.cpp +++ b/idd/LGIdd/transport/network/CNetworkServer.cpp @@ -27,6 +27,7 @@ #include "CDebug.h" #include "CSRWLock.h" +#include "InputPipeProtocol.h" #include "platform/CPlatformInfo.h" #include "VersionInfo.h" #include @@ -74,7 +75,11 @@ namespace static constexpr uint32_t NETWORK_MAX_NEGOTIATED_PAYLOAD = LG_NET_MAX_PAYLOAD_LENGTH; static constexpr uint32_t NETWORK_MAX_DATAGRAM_PAYLOAD = 1136; + static constexpr uint32_t NETWORK_INPUT_MAX_PAYLOAD = + LG_NET_INPUT_RELATIVE_WIRE_SIZE > LG_NET_INPUT_ABSOLUTE_WIRE_SIZE ? + LG_NET_INPUT_RELATIVE_WIRE_SIZE : LG_NET_INPUT_ABSOLUTE_WIRE_SIZE; static constexpr uint32_t NETWORK_INPUT_LEASE_MS = 500; + static constexpr uint32_t NETWORK_INPUT_RECOVERY_LIMIT = 4; 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; @@ -110,6 +115,9 @@ namespace (LG_NET_ENVELOPE_WIRE_SIZE + LG_NET_MAX_PAYLOAD_LENGTH); static constexpr uint32_t NETWORK_SEND_RESUME_NUMERATOR = 3; static constexpr uint32_t NETWORK_SEND_RESUME_DENOMINATOR = 4; + static constexpr uint32_t NETWORK_INPUT_SEND_COUNT_MAX = 128; + static constexpr uint64_t NETWORK_INPUT_SEND_BYTES_MAX = + UINT64_C(1) * 1024U * 1024U; static constexpr uint32_t NETWORK_KEYBOARD_LED_MASK = LG_NET_KEYBOARD_LED_NUM_LOCK | @@ -377,6 +385,7 @@ struct CNetworkServer::Impl uint32_t length = 0; Delivery delivery = Delivery::RELIABLE; bool accounted = true; + bool inputAck = false; }; struct SendLimits @@ -385,6 +394,19 @@ struct CNetworkServer::Impl uint64_t bytes; }; + struct InputPointerMotion + { + uint64_t claimEpoch = 0; + uint64_t sequence = 0; + int64_t cumulativeX = 0; + int64_t cumulativeY = 0; + uint16_t absoluteX = 0; + uint16_t absoluteY = 0; + LGNetPointerButtons previousButtons = 0; + bool apply = false; + bool useAbsolute = false; + }; + struct SendAccounting { uint32_t count; @@ -462,6 +484,9 @@ struct CNetworkServer::Impl Impl& owner; CSRWLock lock; + CSRWLock inputLock; + CSRWLock inputDispatchLock; + CSRWLock inputSendLock; HQUIC handle = nullptr; HQUIC controlStream = nullptr; uint32_t client = 0; @@ -499,18 +524,62 @@ struct CNetworkServer::Impl bool datagramSendEnabled = false; bool closing = false; + uint32_t pins = 0; + bool detached = false; + bool cleanupComplete = false; HandshakeState handshake = HandshakeState::WAITING_HELLO; + + HQUIC inputStream = nullptr; + uint64_t inputStreamID = 0; + uint64_t inputStreamSessionEpoch = 0; + uint64_t inputStreamSendSequence = 0; + uint64_t inputStreamReceiveSequence = 0; + uint32_t inputStreamMaxPayload = 0; + uint32_t inputDatagramLimit = 0; + uint32_t inputStreamSendCount = 0; + uint64_t inputStreamSendBytes = 0; + uint16_t inputStreamServiceVersion = 0; + bool inputStreamBound = false; + bool inputClosing = false; + + uint8_t inputBindingData[LG_NET_STREAM_BINDING_WIRE_SIZE] = {}; + size_t inputBindingLength = 0; + LGNetStreamParser inputReceiveParser = {}; + + std::vector inputReceiveData; + std::vector inputReceivePayload; + std::shared_ptr inputSession; + + LGNetSequenceTracker inputDatagramReceive = {}; uint64_t inputClaimantID = 0; uint64_t inputClaimEpoch = 0; uint64_t inputReliableSequence = 0; uint64_t inputDatagramSequence = 0; + uint64_t inputMotionSequence = 0; + int64_t inputCumulativeX = 0; + int64_t inputCumulativeY = 0; + uint16_t inputAbsoluteX = 0; + uint16_t inputAbsoluteY = 0; uint64_t inputLEDSequence = 0; + uint64_t inputLEDCheckGeneration = 0; + uint64_t inputAckClaimEpoch = 0; + uint64_t inputAckRequestID = 0; LGNetInputClaimFlags inputFlags = 0; + LGNetInputStatusCode inputAckStatus = + LG_NET_INPUT_STATUS_ERROR; LGNetPointerButtons inputPointerButtons = 0; LGNetKeyboardLEDs inputLEDState = 0; uint8_t keyboardModifiers = 0; + bool inputClaimPending = false; + bool inputLEDCheckPending = false; + bool inputLEDWaitForSendReady = false; + bool inputMotionAbsolute = false; + bool inputMotionBaseline = false; bool inputLEDStateKnown = false; + bool inputAckPending = false; + bool inputAckSending = false; + bool inputAckInFlight = false; bool keyboardKeys[256] = {}; RecoveryRequest recoveryRequests[NETWORK_RECOVERY_REQUESTS] = {}; @@ -602,6 +671,7 @@ struct CNetworkServer::Impl std::vector connections; uint32_t nextClient = 1; unsigned activeCallbacks = 0; + unsigned activePins = 0; bool stopping = true; bool running = false; @@ -618,12 +688,121 @@ struct CNetworkServer::Impl CSRWExclusiveLock guard(lock); if (activeCallbacks) --activeCallbacks; - drained = stopping && connections.empty() && !activeCallbacks; + drained = stopping && connections.empty() && !activeCallbacks && + !activePins; } if (drained && drainedEvent) SetEvent(drainedEvent); } + void ReleaseConnectionPin(Connection& connection) + { + Connection * dispose = nullptr; + bool drained = false; + { + CSRWExclusiveLock guard(lock); + if (!connection.pins || !activePins) + { + DEBUG_ERROR("Network client %u connection pin underflow", + connection.client); + return; + } + --connection.pins; + --activePins; + if (!connection.pins && connection.detached && + connection.cleanupComplete) + dispose = &connection; + drained = stopping && connections.empty() && !activeCallbacks && + !activePins; + } + delete dispose; + if (drained && drainedEvent) + SetEvent(drainedEvent); + } + + struct ConnectionPin + { + ConnectionPin() = default; + + ConnectionPin(Impl& owner, Connection& connection) : + owner(&owner), connection(&connection) + { + } + + ConnectionPin(const ConnectionPin&) = delete; + ConnectionPin& operator=(const ConnectionPin&) = delete; + + ConnectionPin(ConnectionPin&& other) noexcept : + owner(other.owner), connection(other.connection) + { + other.owner = nullptr; + other.connection = nullptr; + } + + ConnectionPin& operator=(ConnectionPin&& other) noexcept + { + if (this == &other) + return *this; + Reset(); + owner = other.owner; + connection = other.connection; + other.owner = nullptr; + other.connection = nullptr; + return *this; + } + + ~ConnectionPin() + { + Reset(); + } + + explicit operator bool() const + { + return connection != nullptr; + } + + Connection * operator->() const + { + return connection; + } + + Connection& operator*() const + { + return *connection; + } + + void Reset() + { + if (owner && connection) + owner->ReleaseConnectionPin(*connection); + owner = nullptr; + connection = nullptr; + } + + Impl * owner = nullptr; + Connection * connection = nullptr; + }; + + template + ConnectionPin PinConnection(const Peer& peer) + { + CSRWExclusiveLock guard(lock); + if (stopping || !api) + return {}; + for (Connection * connection : connections) + { + if (connection->client != peer.client || + connection->generation != peer.generation || + connection->detached) + continue; + + ++connection->pins; + ++activePins; + return ConnectionPin(*this, *connection); + } + return {}; + } + uint32_t AllocateClientLocked() { for (uint64_t attempt = 0; attempt < UINT32_MAX; ++attempt) @@ -684,30 +863,24 @@ struct CNetworkServer::Impl bool AudioPeerInfo(const NetworkAudioPeer& peer, NetworkAudioPeerInfo& info) { - CSRWSharedLock guard(lock); - for (Connection * connection : connections) - { - if (connection->client != peer.client || - connection->generation != peer.generation) - continue; + ConnectionPin connection = PinConnection(peer); + if (!connection) + return false; - CSRWSharedLock connectionGuard(connection->lock); - const uint16_t serviceMask = - UINT16_C(1) << LG_NET_SERVICE_AUDIO; - if (connection->closing || - !PeerInfoAvailableLocked(*connection) || - !(connection->selectedServices & serviceMask)) - return false; + CSRWSharedLock connectionGuard(connection->lock); + const uint16_t serviceMask = UINT16_C(1) << LG_NET_SERVICE_AUDIO; + if (connection->closing || + !PeerInfoAvailableLocked(*connection) || + !(connection->selectedServices & serviceMask)) + return false; - const LGNetCapability& capability = - connection->selectedCapabilities[LG_NET_SERVICE_AUDIO]; - info.serviceVersion = capability.versionMin; - info.capabilityFlags = capability.flags; - info.maxReliablePayload = capability.maxPayloadLength; - info.maxDatagramPayload = capability.maxDatagramPayload; - return true; - } - return false; + const LGNetCapability& capability = + connection->selectedCapabilities[LG_NET_SERVICE_AUDIO]; + info.serviceVersion = capability.versionMin; + info.capabilityFlags = capability.flags; + info.maxReliablePayload = capability.maxPayloadLength; + info.maxDatagramPayload = capability.maxDatagramPayload; + return true; } NetworkAudioSendResult SendAudio( @@ -719,29 +892,25 @@ struct CNetworkServer::Impl if (service != LG_NET_SERVICE_AUDIO) return NetworkAudioSendResult::FAILED; - CSRWSharedLock guard(lock); - for (Connection * connection : connections) + ConnectionPin connection = PinConnection(peer); + if (!connection) + return NetworkAudioSendResult::FAILED; + + const SendResult result = datagram ? + TrySendDatagram(*connection, service, messageType, flags, + componentEpoch, requestID, payload, payloadLength, true) : + TrySendPacket(*connection, service, messageType, flags, + componentEpoch, requestID, payload, payloadLength); + switch (result) { - if (connection->client != peer.client || - connection->generation != peer.generation) - continue; + case SendResult::SENT: + return NetworkAudioSendResult::ACCEPTED; - const SendResult result = datagram ? - TrySendDatagram(*connection, service, messageType, flags, - componentEpoch, requestID, payload, payloadLength, true) : - TrySendPacket(*connection, service, messageType, flags, - componentEpoch, requestID, payload, payloadLength); - switch (result) - { - case SendResult::SENT: - return NetworkAudioSendResult::ACCEPTED; + case SendResult::BLOCKED: + return NetworkAudioSendResult::BLOCKED; - case SendResult::BLOCKED: - return NetworkAudioSendResult::BLOCKED; - - case SendResult::FAILED: - return NetworkAudioSendResult::FAILED; - } + case SendResult::FAILED: + return NetworkAudioSendResult::FAILED; } return NetworkAudioSendResult::FAILED; } @@ -749,25 +918,26 @@ struct CNetworkServer::Impl std::shared_ptr ClipboardInputSession( const NetworkClipboardPeer& peer) { + ConnectionPin connection = PinConnection(peer); + if (!connection) + return {}; + std::shared_ptr session; { - CSRWSharedLock guard(lock); - for (Connection * connection : connections) - { - if (connection->client != peer.client || - connection->generation != peer.generation) - continue; - - CSRWSharedLock connectionGuard(connection->lock); - const uint16_t inputMask = UINT16_C(1) << LG_NET_SERVICE_INPUT; - if (connection->closing || - connection->handshake != HandshakeState::READY || - !(connection->selectedServices & inputMask) || - !connection->inputClaimantID || !connection->inputClaimEpoch) - return {}; - session = connection->session; - break; - } + CSRWSharedLock connectionGuard(connection->lock); + const uint16_t inputMask = UINT16_C(1) << LG_NET_SERVICE_INPUT; + if (connection->closing || + connection->handshake != HandshakeState::READY || + !(connection->selectedServices & inputMask)) + return {}; + session = connection->session; + } + { + CSRWSharedLock inputGuard(connection->inputLock); + if (connection->inputClosing || !connection->inputStreamBound || + !connection->inputClaimantID || !connection->inputClaimEpoch || + connection->inputSession != session) + return {}; } return session; @@ -810,28 +980,31 @@ struct CNetworkServer::Impl { AuthorityContext& authorityContext = *static_cast(value); - CSRWSharedLock guard(authorityContext.owner->lock); - for (Connection * connection : authorityContext.owner->connections) - { - if (connection->client != authorityContext.peer.client || - connection->generation != authorityContext.peer.generation) - continue; - - { - CSRWSharedLock connectionGuard(connection->lock); - const uint16_t inputMask = UINT16_C(1) << LG_NET_SERVICE_INPUT; - if (connection->closing || - connection->handshake != HandshakeState::READY || - !(connection->selectedServices & inputMask) || - !connection->inputClaimantID || !connection->inputClaimEpoch || - connection->session.get() != authorityContext.session) - return; - } - - authorityContext.action(authorityContext.opaque); - authorityContext.invoked = true; + ConnectionPin connection = authorityContext.owner->PinConnection( + authorityContext.peer); + if (!connection) return; + + { + CSRWSharedLock connectionGuard(connection->lock); + const uint16_t inputMask = UINT16_C(1) << LG_NET_SERVICE_INPUT; + if (connection->closing || + connection->handshake != HandshakeState::READY || + !(connection->selectedServices & inputMask) || + connection->session.get() != authorityContext.session) + return; } + { + CSRWSharedLock inputGuard(connection->inputLock); + if (connection->inputClosing || !connection->inputStreamBound || + !connection->inputClaimantID || + !connection->inputClaimEpoch || + connection->inputSession.get() != authorityContext.session) + return; + } + + authorityContext.action(authorityContext.opaque); + authorityContext.invoked = true; }; return session->WithInputAuthority(invoke, &context) && context.invoked; } @@ -839,72 +1012,64 @@ struct CNetworkServer::Impl bool ClipboardPeerInfo(const NetworkClipboardPeer& peer, NetworkClipboardPeerInfo& info) { - CSRWSharedLock guard(lock); - for (Connection * connection : connections) - { - if (connection->client != peer.client || - connection->generation != peer.generation) - continue; + ConnectionPin connection = PinConnection(peer); + if (!connection) + return false; - CSRWSharedLock connectionGuard(connection->lock); - const uint16_t clipboardMask = - UINT16_C(1) << LG_NET_SERVICE_CLIPBOARD; - const uint16_t fileMask = UINT16_C(1) << LG_NET_SERVICE_FILE; - if (connection->closing || - !PeerInfoAvailableLocked(*connection) || - !(connection->selectedServices & clipboardMask) || - !(connection->selectedServices & fileMask)) - return false; + CSRWSharedLock connectionGuard(connection->lock); + const uint16_t clipboardMask = + UINT16_C(1) << LG_NET_SERVICE_CLIPBOARD; + const uint16_t fileMask = UINT16_C(1) << LG_NET_SERVICE_FILE; + if (connection->closing || + !PeerInfoAvailableLocked(*connection) || + !(connection->selectedServices & clipboardMask) || + !(connection->selectedServices & fileMask)) + return false; - const LGNetCapability& clipboardCapability = - connection->selectedCapabilities[LG_NET_SERVICE_CLIPBOARD]; - const LGNetCapability& fileCapability = - connection->selectedCapabilities[LG_NET_SERVICE_FILE]; - info.clipboardVersion = clipboardCapability.versionMin; - info.fileVersion = fileCapability.versionMin; - info.clipboardFlags = clipboardCapability.flags; - info.fileFlags = fileCapability.flags; - info.clipboardReliablePayload = clipboardCapability.maxPayloadLength; - info.fileReliablePayload = fileCapability.maxPayloadLength; - info.clipboardBulkPayload = - clipboardCapability.flags & LG_NET_CAPABILITY_BULK ? - clipboardCapability.maxPayloadLength : 0; - info.fileBulkPayload = - fileCapability.flags & LG_NET_CAPABILITY_BULK ? - fileCapability.maxPayloadLength : 0; - return true; - } - return false; + const LGNetCapability& clipboardCapability = + connection->selectedCapabilities[LG_NET_SERVICE_CLIPBOARD]; + const LGNetCapability& fileCapability = + connection->selectedCapabilities[LG_NET_SERVICE_FILE]; + info.clipboardVersion = clipboardCapability.versionMin; + info.fileVersion = fileCapability.versionMin; + info.clipboardFlags = clipboardCapability.flags; + info.fileFlags = fileCapability.flags; + info.clipboardReliablePayload = clipboardCapability.maxPayloadLength; + info.fileReliablePayload = fileCapability.maxPayloadLength; + info.clipboardBulkPayload = + clipboardCapability.flags & LG_NET_CAPABILITY_BULK ? + clipboardCapability.maxPayloadLength : 0; + info.fileBulkPayload = + fileCapability.flags & LG_NET_CAPABILITY_BULK ? + fileCapability.maxPayloadLength : 0; + return true; } - NetworkClipboardSendResult SendClipboardLocked( + NetworkClipboardSendResult SendClipboardToPeer( const NetworkClipboardPeer& peer, bool bulk, LGNetService service, uint16_t messageType, LGNetEnvelopeFlags flags, uint64_t componentEpoch, uint64_t requestID, const void * payload, uint32_t payloadLength) { - for (Connection * connection : connections) + ConnectionPin connection = PinConnection(peer); + if (!connection) + return NetworkClipboardSendResult::FAILED; + + const SendResult result = bulk ? + TrySendBulk(*connection, service, messageType, flags, + componentEpoch, requestID, payload, payloadLength) : + TrySendPacket(*connection, service, messageType, flags, + componentEpoch, requestID, payload, payloadLength); + switch (result) { - if (connection->client != peer.client || - connection->generation != peer.generation) - continue; + case SendResult::SENT: + return NetworkClipboardSendResult::ACCEPTED; - const SendResult result = bulk ? - TrySendBulk(*connection, service, messageType, flags, - componentEpoch, requestID, payload, payloadLength) : - TrySendPacket(*connection, service, messageType, flags, - componentEpoch, requestID, payload, payloadLength); - switch (result) - { - case SendResult::SENT: - return NetworkClipboardSendResult::ACCEPTED; + case SendResult::BLOCKED: + return NetworkClipboardSendResult::BLOCKED; - case SendResult::BLOCKED: - return NetworkClipboardSendResult::BLOCKED; - - case SendResult::FAILED: - return NetworkClipboardSendResult::FAILED; - } + case SendResult::FAILED: + return NetworkClipboardSendResult::FAILED; } return NetworkClipboardSendResult::FAILED; } @@ -923,11 +1088,8 @@ struct CNetworkServer::Impl service == LG_NET_SERVICE_CLIPBOARD && messageType == LG_NET_CLIPBOARD_MESSAGE_CLAIM_RESULT; if (claimResult) - { - CSRWSharedLock guard(lock); - return SendClipboardLocked(peer, bulk, service, messageType, flags, + return SendClipboardToPeer(peer, bulk, service, messageType, flags, componentEpoch, requestID, payload, payloadLength); - } struct SendContext { @@ -958,7 +1120,7 @@ struct CNetworkServer::Impl const auto send = [](void * value) { SendContext& sendContext = *static_cast(value); - sendContext.result = sendContext.owner->SendClipboardLocked( + sendContext.result = sendContext.owner->SendClipboardToPeer( sendContext.peer, sendContext.bulk, sendContext.service, sendContext.messageType, sendContext.flags, sendContext.componentEpoch, sendContext.requestID, @@ -971,33 +1133,28 @@ struct CNetworkServer::Impl bool VideoPeerInfo(const NetworkVideoPeer& peer, NetworkVideoPeerInfo& info) { - CSRWSharedLock guard(lock); - for (Connection * connection : connections) - { - if (connection->client != peer.client || - connection->generation != peer.generation) - continue; + ConnectionPin connection = PinConnection(peer); + if (!connection) + return false; - CSRWSharedLock connectionGuard(connection->lock); - const uint16_t serviceMask = UINT16_C(1) << LG_NET_SERVICE_VIDEO; - if (connection->closing || - !PeerInfoAvailableLocked(*connection) || - !(connection->selectedServices & serviceMask)) - return false; + CSRWSharedLock connectionGuard(connection->lock); + const uint16_t serviceMask = UINT16_C(1) << LG_NET_SERVICE_VIDEO; + if (connection->closing || + !PeerInfoAvailableLocked(*connection) || + !(connection->selectedServices & serviceMask)) + return false; - const LGNetCapability& capability = - connection->selectedCapabilities[LG_NET_SERVICE_VIDEO]; - info.serviceVersion = capability.versionMin; - info.capabilityFlags = capability.flags; - info.maxReliablePayload = (std::min)( - capability.maxPayloadLength, NETWORK_MAX_CONTROL_PAYLOAD); - info.maxDatagramPayload = capability.maxDatagramPayload; - info.maxBulkPayload = - capability.flags & LG_NET_CAPABILITY_BULK ? - capability.maxPayloadLength : 0; - return true; - } - return false; + const LGNetCapability& capability = + connection->selectedCapabilities[LG_NET_SERVICE_VIDEO]; + info.serviceVersion = capability.versionMin; + info.capabilityFlags = capability.flags; + info.maxReliablePayload = (std::min)( + capability.maxPayloadLength, NETWORK_MAX_CONTROL_PAYLOAD); + info.maxDatagramPayload = capability.maxDatagramPayload; + info.maxBulkPayload = + capability.flags & LG_NET_CAPABILITY_BULK ? + capability.maxPayloadLength : 0; + return true; } NetworkVideoSendResult SendVideo( @@ -1014,46 +1171,42 @@ struct CNetworkServer::Impl const auto payloadData = payloadLength ? static_cast(payload->data()) : nullptr; - CSRWSharedLock guard(lock); - for (Connection * connection : connections) + ConnectionPin connection = PinConnection(peer); + if (!connection) + return NetworkVideoSendResult::FAILED; + + SendResult result = SendResult::FAILED; + switch (delivery) { - if (connection->client != peer.client || - connection->generation != peer.generation) - continue; + case NetworkVideoDelivery::RELIABLE: + result = TrySendPacket(*connection, LG_NET_SERVICE_VIDEO, + messageType, flags, componentEpoch, requestID, + payloadData, payloadLength); + break; - SendResult result = SendResult::FAILED; - switch (delivery) - { - case NetworkVideoDelivery::RELIABLE: - result = TrySendPacket(*connection, LG_NET_SERVICE_VIDEO, - messageType, flags, componentEpoch, requestID, - payloadData, payloadLength); - break; + case NetworkVideoDelivery::DATAGRAM: + result = TrySendDatagram(*connection, LG_NET_SERVICE_VIDEO, + messageType, flags, componentEpoch, requestID, + payloadData, payloadLength, true, std::move(payload)); + break; - case NetworkVideoDelivery::DATAGRAM: - result = TrySendDatagram(*connection, LG_NET_SERVICE_VIDEO, - messageType, flags, componentEpoch, requestID, - payloadData, payloadLength, true, std::move(payload)); - break; + case NetworkVideoDelivery::BULK: + result = TrySendBulk(*connection, LG_NET_SERVICE_VIDEO, + messageType, flags, componentEpoch, requestID, + payloadData, payloadLength, std::move(payload)); + break; + } - case NetworkVideoDelivery::BULK: - result = TrySendBulk(*connection, LG_NET_SERVICE_VIDEO, - messageType, flags, componentEpoch, requestID, - payloadData, payloadLength, std::move(payload)); - break; - } + switch (result) + { + case SendResult::SENT: + return NetworkVideoSendResult::ACCEPTED; - switch (result) - { - case SendResult::SENT: - return NetworkVideoSendResult::ACCEPTED; + case SendResult::BLOCKED: + return NetworkVideoSendResult::BLOCKED; - case SendResult::BLOCKED: - return NetworkVideoSendResult::BLOCKED; - - case SendResult::FAILED: - return NetworkVideoSendResult::FAILED; - } + case SendResult::FAILED: + return NetworkVideoSendResult::FAILED; } return NetworkVideoSendResult::FAILED; } @@ -1068,6 +1221,10 @@ struct CNetworkServer::Impl connection.closing = true; handle = connection.handle; } + { + CSRWExclusiveLock guard(connection.inputLock); + connection.inputClosing = true; + } if (handle) api->ConnectionShutdown(handle, QUIC_CONNECTION_SHUTDOWN_FLAG_NONE, error); @@ -1462,11 +1619,133 @@ struct CNetworkServer::Impl video.SendReady(VideoPeer(connection), videoDelivery); } + static void ReleaseInputSendLocked(Connection& connection, + const Send& send) + { + if (!connection.inputStreamSendCount || + connection.inputStreamSendBytes < send.length) + { + DEBUG_ERROR("Network input send accounting underflow for client %u", + connection.client); + return; + } + + --connection.inputStreamSendCount; + connection.inputStreamSendBytes -= send.length; + } + + SendResult TrySendInputPacket(Connection& connection, + uint16_t messageType, LGNetEnvelopeFlags flags, + uint64_t componentEpoch, uint64_t requestID, const void * payload, + uint32_t payloadLength, bool inputAck) + { + const LGNetMessageRoute * route = + lgNetMessageRoute(LG_NET_SERVICE_INPUT, messageType); + if (!lgNetMessageRouteAllows(route, LG_NET_ROLE_SERVER, + LG_NET_ROUTE_DELIVERY_RELIABLE) || + (payloadLength && !payload) || + payloadLength > NETWORK_INPUT_MAX_PAYLOAD || + flags & (LG_NET_ENVELOPE_RELIABLE | LG_NET_ENVELOPE_DATAGRAM | + LG_NET_ENVELOPE_EARLY_DATA)) + return SendResult::FAILED; + + const size_t packetSize = + LG_NET_ENVELOPE_WIRE_SIZE + payloadLength; + std::unique_ptr send(new (std::nothrow) Send()); + if (!send) + return SendResult::FAILED; + try + { + send->data.resize(packetSize); + } + catch (...) + { + return SendResult::FAILED; + } + + send->service = LG_NET_SERVICE_INPUT; + send->length = static_cast(packetSize); + send->accounted = false; + send->inputAck = inputAck; + + QUIC_STATUS status = QUIC_STATUS_SUCCESS; + { + LGNetPacketParameters parameters = {}; + HQUIC stream = nullptr; + CSRWExclusiveLock sendGuard(connection.inputSendLock); + { + CSRWExclusiveLock guard(connection.inputLock); + if (connection.inputClosing || !connection.inputStreamBound || + !connection.inputStream || + payloadLength > connection.inputStreamMaxPayload || + connection.inputStreamServiceVersion < + route->serviceVersionMin || + connection.inputStreamSendSequence == UINT64_MAX) + return SendResult::FAILED; + if (connection.inputStreamSendCount >= NETWORK_INPUT_SEND_COUNT_MAX || + connection.inputStreamSendBytes > + NETWORK_INPUT_SEND_BYTES_MAX - packetSize) + return SendResult::BLOCKED; + + ++connection.inputStreamSendCount; + connection.inputStreamSendBytes += packetSize; + + lgNetPacketParametersInit(¶meters, LG_NET_SERVICE_INPUT, + messageType, connection.inputStreamServiceVersion, + route->messageVersion); + parameters.flags = flags | LG_NET_ENVELOPE_RELIABLE; + parameters.sessionEpoch = connection.inputStreamSessionEpoch; + parameters.componentEpoch = componentEpoch; + parameters.sequence = ++connection.inputStreamSendSequence; + parameters.requestID = requestID; + stream = connection.inputStream; + } + + size_t wireSize = 0; + if (!lgNetPacketConstruct(send->data.data(), send->data.size(), + &wireSize, ¶meters, payload, payloadLength) || + wireSize != packetSize) + { + CSRWExclusiveLock guard(connection.inputLock); + if (connection.inputStreamSendSequence == parameters.sequence) + --connection.inputStreamSendSequence; + ReleaseInputSendLocked(connection, *send); + return SendResult::FAILED; + } + + send->buffers[0].Buffer = send->data.data(); + send->buffers[0].Length = static_cast(wireSize); + status = api->StreamSend(stream, + send->buffers, send->bufferCount, QUIC_SEND_FLAG_PRIORITY_WORK, + send.get()); + if (QUIC_FAILED(status)) + { + CSRWExclusiveLock guard(connection.inputLock); + if (connection.inputStreamSendSequence == parameters.sequence) + --connection.inputStreamSendSequence; + ReleaseInputSendLocked(connection, *send); + } + } + + if (QUIC_FAILED(status)) + return SendResult::FAILED; + send.release(); + return SendResult::SENT; + } + SendResult TrySendPacket(Connection& connection, LGNetService service, uint16_t messageType, LGNetEnvelopeFlags flags, uint64_t componentEpoch, uint64_t requestID, const void * payload, uint32_t payloadLength, bool activateSession = false) { + if (service == LG_NET_SERVICE_INPUT) + { + if (activateSession) + return SendResult::FAILED; + return TrySendInputPacket(connection, messageType, flags, + componentEpoch, requestID, payload, payloadLength, false); + } + const LGNetServiceVersionInfo * version = lgNetServiceVersionInfo(service); const LGNetMessageRoute * route = @@ -2144,7 +2423,9 @@ retry: capability.service > LG_NET_SERVICE_CONTROL || capability.service == LG_NET_SERVICE_USB || capability.flags & LG_NET_CAPABILITY_SELECTED || - capability.maxBidirectionalStreams || + (capability.service == LG_NET_SERVICE_INPUT ? + capability.maxBidirectionalStreams != 1 : + capability.maxBidirectionalStreams != 0) || capability.maxUnidirectionalStreams > NETWORK_PEER_UNIDI_STREAMS || (!!(capability.flags & LG_NET_CAPABILITY_BULK) != !!capability.maxUnidirectionalStreams) || @@ -2230,7 +2511,9 @@ retry: offered.versionMax < LG_NET_AUDIO_OWNERSHIP_INTRODUCED_SERVICE_VERSION) || offered.maxPayloadLength < minimumPayload || - connection.maxPayload < minimumPayload) + connection.maxPayload < minimumPayload || + (offered.service == LG_NET_SERVICE_INPUT && + offered.maxBidirectionalStreams != 1)) return false; const uint16_t selectedVersion = offered.versionMax < version->current ? @@ -2251,8 +2534,12 @@ retry: selected.maxPayloadLength = offered.maxPayloadLength < connection.maxPayload ? offered.maxPayloadLength : connection.maxPayload; + if (offered.service == LG_NET_SERVICE_INPUT && + selected.maxPayloadLength > NETWORK_INPUT_MAX_PAYLOAD) + selected.maxPayloadLength = NETWORK_INPUT_MAX_PAYLOAD; selected.maxDatagramPayload = 0; - selected.maxBidirectionalStreams = 0; + selected.maxBidirectionalStreams = offered.service == + LG_NET_SERVICE_INPUT ? 1 : 0; selected.maxUnidirectionalStreams = 0; const uint32_t minimumDatagram = offered.service == LG_NET_SERVICE_VIDEO ? @@ -2272,6 +2559,8 @@ retry: selected.maxDatagramPayload = offered.maxDatagramPayload < connection.maxDatagramPayload ? offered.maxDatagramPayload : connection.maxDatagramPayload; + if (selected.maxDatagramPayload > selected.maxPayloadLength) + selected.maxDatagramPayload = selected.maxPayloadLength; } if (BulkServiceSupported(offered.service) && (offered.flags & LG_NET_CAPABILITY_BULK) && @@ -3145,18 +3434,37 @@ retry: return LG_NET_INPUT_STATUS_ERROR; } + static void ClearPendingInputAckLocked(Connection& connection) + { + connection.inputAckClaimEpoch = 0; + connection.inputAckRequestID = 0; + connection.inputAckStatus = LG_NET_INPUT_STATUS_ERROR; + connection.inputAckPending = false; + } + 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.inputClaimantID = 0; + connection.inputClaimEpoch = 0; + connection.inputFlags = 0; + connection.inputLEDState = 0; + connection.inputClaimPending = false; + connection.inputLEDCheckPending = false; + connection.inputLEDWaitForSendReady = false; + connection.inputLEDStateKnown = false; + ClearPendingInputAckLocked(connection); } connection.inputPointerButtons = 0; + connection.inputMotionSequence = 0; + connection.inputCumulativeX = 0; + connection.inputCumulativeY = 0; + connection.inputAbsoluteX = 0; + connection.inputAbsoluteY = 0; + connection.inputMotionAbsolute = false; + connection.inputMotionBaseline = false; connection.keyboardModifiers = 0; memset(connection.keyboardKeys, 0, sizeof(connection.keyboardKeys)); } @@ -3165,20 +3473,38 @@ retry: uint64_t claimEpoch) { { - CSRWExclusiveLock guard(connection.lock); + CSRWExclusiveLock guard(connection.inputLock); if (connection.inputClaimEpoch == claimEpoch) ClearInputStateLocked(connection, true); } clipboard.Process(GetTickCount64()); } - bool SendInputStatus(Connection& connection, uint16_t messageType, + static void RequestInputLEDCheckLocked(Connection& connection) + { + connection.inputLEDCheckPending = true; + ++connection.inputLEDCheckGeneration; + if (!connection.inputLEDCheckGeneration) + ++connection.inputLEDCheckGeneration; + } + + static void CompleteInputLEDCheck(Connection& connection, + uint64_t claimEpoch, uint64_t checkGeneration) + { + CSRWExclusiveLock guard(connection.inputLock); + if (connection.inputClaimEpoch == claimEpoch && + connection.inputLEDCheckGeneration == checkGeneration) + connection.inputLEDCheckPending = false; + } + + SendResult TrySendInputStatus(Connection& connection, + uint16_t messageType, LGNetInputStatusCode code, uint64_t claimEpoch, uint64_t requestID, - LGNetEnvelopeFlags flags) + LGNetEnvelopeFlags flags, bool inputAck) { LGNetInputStatus status = {}; { - CSRWSharedLock guard(connection.lock); + CSRWSharedLock guard(connection.inputLock); status.claimEpoch = claimEpoch; status.lastSequence = connection.inputReliableSequence > connection.inputDatagramSequence ? @@ -3193,56 +3519,157 @@ retry: } 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)); + if (!lgNetInputStatusEncode(payload, sizeof(payload), &status)) + return SendResult::FAILED; + return TrySendInputPacket(connection, messageType, + flags | LG_NET_ENVELOPE_FINAL, claimEpoch, requestID, + payload, sizeof(payload), inputAck); } - bool SendInputLEDs(Connection& connection) + bool SendInputStatus(Connection& connection, uint16_t messageType, + LGNetInputStatusCode code, uint64_t claimEpoch, uint64_t requestID, + LGNetEnvelopeFlags flags) { - std::shared_ptr session; - uint64_t claimEpoch; + return TrySendInputStatus(connection, messageType, code, + claimEpoch, requestID, flags, false) == SendResult::SENT; + } + + SendResult TrySendPendingInputAck(Connection& connection) + { + uint64_t claimEpoch = 0; + uint64_t requestID = 0; + LGNetInputStatusCode code = LG_NET_INPUT_STATUS_ERROR; { - CSRWSharedLock guard(connection.lock); - session = connection.session; - claimEpoch = connection.inputClaimEpoch; + CSRWExclusiveLock guard(connection.inputLock); + if (!connection.inputAckPending || connection.inputAckSending || + connection.inputAckInFlight) + return SendResult::SENT; + + claimEpoch = connection.inputAckClaimEpoch; + requestID = connection.inputAckRequestID; + code = connection.inputAckStatus; + + connection.inputAckSending = true; + connection.inputAckInFlight = true; + } + + const SendResult result = TrySendInputStatus(connection, + LG_NET_INPUT_MESSAGE_STATUS, code, claimEpoch, requestID, + LG_NET_ENVELOPE_RESPONSE, true); + { + CSRWExclusiveLock guard(connection.inputLock); + connection.inputAckSending = false; + if (result != SendResult::SENT) + connection.inputAckInFlight = false; + if (result == SendResult::SENT && connection.inputAckPending && + connection.inputAckClaimEpoch == claimEpoch && + connection.inputAckRequestID == requestID && + connection.inputAckStatus == code) + ClearPendingInputAckLocked(connection); + } + return result; + } + + bool QueueInputAck(Connection& connection, LGNetInputStatusCode code, + uint64_t claimEpoch, uint64_t requestID) + { + { + CSRWExclusiveLock guard(connection.inputLock); + connection.inputAckClaimEpoch = claimEpoch; + connection.inputAckRequestID = requestID; + connection.inputAckStatus = code; + connection.inputAckPending = true; + } + return TrySendPendingInputAck(connection) != SendResult::FAILED; + } + + SendResult TrySendInputLEDs(Connection& connection) + { + std::shared_ptr session = {}; + uint64_t claimEpoch = 0; + uint64_t checkGeneration = 0; + { + CSRWSharedLock guard(connection.inputLock); + if (!connection.inputLEDCheckPending || + connection.inputLEDWaitForSendReady) + return SendResult::SENT; + session = connection.inputSession; + claimEpoch = connection.inputClaimEpoch; + checkGeneration = connection.inputLEDCheckGeneration; + if (connection.inputClaimPending) + return SendResult::SENT; } if (!session || !claimEpoch) - return false; + { + CompleteInputLEDCheck(connection, claimEpoch, checkGeneration); + return SendResult::SENT; + } const InputTargetState state = session->InputState(); if (!state.owned || !state.keyboardLEDsValid) - return true; + { + CompleteInputLEDCheck(connection, claimEpoch, checkGeneration); + return SendResult::SENT; + } - LGNetInputLEDs leds = {}; + 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; + CSRWExclusiveLock guard(connection.inputLock); + if (connection.inputClaimEpoch != claimEpoch || + connection.inputLEDCheckGeneration != checkGeneration || + !connection.inputLEDCheckPending) + return SendResult::SENT; if (connection.inputLEDStateKnown && connection.inputLEDState == leds.state) - return true; + { + connection.inputLEDCheckPending = false; + return SendResult::SENT; + } if (connection.inputLEDSequence == UINT64_MAX) - return false; + return SendResult::FAILED; 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, + uint8_t payload[LG_NET_INPUT_LEDS_WIRE_SIZE]; + SendResult result = SendResult::FAILED; + if (lgNetInputLEDsEncode(payload, sizeof(payload), &leds)) + result = TrySendInputPacket(connection, LG_NET_INPUT_MESSAGE_KEYBOARD_LEDS, LG_NET_ENVELOPE_FINAL, - claimEpoch, 0, payload, sizeof(payload)); + claimEpoch, 0, payload, sizeof(payload), false); + { + CSRWExclusiveLock guard(connection.inputLock); + if (connection.inputClaimEpoch == claimEpoch && + connection.inputLEDSequence == leds.sequence && + connection.inputLEDState == leds.state) + { + if (result == SendResult::SENT) + { + connection.inputLEDWaitForSendReady = false; + if (connection.inputLEDCheckGeneration == checkGeneration) + connection.inputLEDCheckPending = false; + } + else + { + --connection.inputLEDSequence; + connection.inputLEDStateKnown = false; + connection.inputLEDWaitForSendReady = + result == SendResult::BLOCKED && + (connection.inputStreamSendCount || + connection.inputStreamSendBytes); + } + } + } + return result; } bool HandleInputClaim(Connection& connection, const LGNetPacketView& packet) { - LGNetInputClaim claim; + LGNetInputClaim claim = {}; if (lgNetInputClaimDecode(&claim, packet.payload, packet.payloadLength) != LG_NET_PARSE_OK || packet.envelope.componentEpoch != claim.claimEpoch || @@ -3251,15 +3678,15 @@ retry: std::shared_ptr session; { - CSRWSharedLock guard(connection.lock); - session = connection.session; + CSRWSharedLock guard(connection.inputLock); + session = connection.inputSession; } if (!session) return false; - bool hadClaim; + bool hadClaim = false; { - CSRWSharedLock guard(connection.lock); + CSRWSharedLock guard(connection.inputLock); hadClaim = connection.inputClaimantID != 0; } @@ -3267,31 +3694,44 @@ retry: if (result == InputResult::ACCEPTED && hadClaim) result = session->ResetInput(); { - CSRWExclusiveLock guard(connection.lock); + CSRWExclusiveLock guard(connection.inputLock); 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; + ClearPendingInputAckLocked(connection); + 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.inputClaimPending = accepted; + connection.inputLEDWaitForSendReady = false; + connection.inputLEDStateKnown = false; ClearInputStateLocked(connection, false); + if (accepted) + RequestInputLEDCheckLocked(connection); + else + connection.inputLEDCheckPending = false; } - clipboard.Process(GetTickCount64()); const LGNetInputStatusCode code = InputStatusCode(result); - return SendInputStatus(connection, LG_NET_INPUT_MESSAGE_CLAIM_RESULT, + const bool sent = SendInputStatus(connection, + LG_NET_INPUT_MESSAGE_CLAIM_RESULT, code, claim.claimEpoch, packet.envelope.requestID, - LG_NET_ENVELOPE_RESPONSE) && - (result != InputResult::ACCEPTED || SendInputLEDs(connection)); + LG_NET_ENVELOPE_RESPONSE); + { + CSRWExclusiveLock guard(connection.inputLock); + if (sent && connection.inputClaimEpoch == claim.claimEpoch) + connection.inputClaimPending = false; + } + clipboard.Process(GetTickCount64()); + return sent; } bool ValidateInputOwner(Connection& connection, uint64_t claimEpoch, uint64_t sequence, bool datagram, uint64_t claimantID, LGNetInputClaimFlags requiredFlags) { - CSRWExclusiveLock guard(connection.lock); + CSRWExclusiveLock guard(connection.inputLock); uint64_t& lastSequence = datagram ? connection.inputDatagramSequence : connection.inputReliableSequence; @@ -3307,35 +3747,147 @@ retry: return true; } - bool ValidateInputPointer(Connection& connection, uint64_t claimEpoch, - uint64_t sequence, bool datagram, LGNetPointerButtons& buttons) + static int32_t InputCumulativeStep(int64_t current, int64_t previous, + int64_t& next) { - CSRWExclusiveLock guard(connection.lock); + if (current >= previous) + { + const uint64_t delta = static_cast(current) - + static_cast(previous); + if (delta <= static_cast(LG_INPUT_MOUSE_DELTA_MAX)) + { + next = current; + return static_cast(delta); + } + next = previous + static_cast(LG_INPUT_MOUSE_DELTA_MAX); + return LG_INPUT_MOUSE_DELTA_MAX; + } + + const uint64_t delta = static_cast(previous) - + static_cast(current); + const uint64_t limit = static_cast( + -static_cast(LG_INPUT_MOUSE_DELTA_MIN)); + if (delta <= limit) + { + next = current; + return -static_cast(delta); + } + next = previous + static_cast(LG_INPUT_MOUSE_DELTA_MIN); + return LG_INPUT_MOUSE_DELTA_MIN; + } + + bool PrepareInputPointer(Connection& connection, uint64_t claimEpoch, + uint64_t sequence, bool datagram, bool relative, + int64_t cumulativeX, int64_t cumulativeY, + LGNetPointerButtons& buttons, InputPointerMotion& motion) + { + CSRWExclusiveLock guard(connection.inputLock); 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) + connection.inputMotionBaseline = true; + motion.claimEpoch = claimEpoch; + motion.sequence = sequence; + motion.previousButtons = connection.inputPointerButtons; if (datagram) buttons = connection.inputPointerButtons; else connection.inputPointerButtons = buttons; + + motion.apply = sequence > connection.inputMotionSequence; + if (motion.apply) + { + connection.inputMotionSequence = sequence; + if (relative) + { + motion.cumulativeX = cumulativeX; + motion.cumulativeY = cumulativeY; + } + else + { + connection.inputAbsoluteX = motion.absoluteX; + connection.inputAbsoluteY = motion.absoluteY; + connection.inputCumulativeX = cumulativeX; + connection.inputCumulativeY = cumulativeY; + } + connection.inputMotionAbsolute = !relative; + } + + motion.useAbsolute = connection.inputMotionAbsolute; + if (motion.useAbsolute) + { + motion.absoluteX = connection.inputAbsoluteX; + motion.absoluteY = connection.inputAbsoluteY; + } return true; } + InputResult SendInputRelativeMotion(Connection& connection, + const std::shared_ptr& session, + const InputPointerMotion& motion, int32_t wheel, + LGNetPointerButtons buttons) + { + int64_t cumulativeX = 0; + int64_t cumulativeY = 0; + { + CSRWSharedLock guard(connection.inputLock); + cumulativeX = connection.inputCumulativeX; + cumulativeY = connection.inputCumulativeY; + } + + InputResult result = InputResult::STALE; + for (uint32_t chunk = 0; chunk < NETWORK_INPUT_RECOVERY_LIMIT; ++chunk) + { + int64_t nextX = cumulativeX; + int64_t nextY = cumulativeY; + const int32_t deltaX = InputCumulativeStep( + motion.cumulativeX, cumulativeX, nextX); + const int32_t deltaY = InputCumulativeStep( + motion.cumulativeY, cumulativeY, nextY); + const bool final = + (nextX == motion.cumulativeX && nextY == motion.cumulativeY) || + chunk + 1U == NETWORK_INPUT_RECOVERY_LIMIT; + + result = session->SendMouseRelative(deltaX, deltaY, + final ? wheel : 0, final ? buttons : motion.previousButtons); + if (result != InputResult::ACCEPTED) + break; + + cumulativeX = nextX; + cumulativeY = nextY; + if (final) + break; + } + + { + CSRWExclusiveLock guard(connection.inputLock); + if (connection.inputClaimEpoch == motion.claimEpoch && + connection.inputMotionSequence == motion.sequence && + !connection.inputMotionAbsolute) + { + connection.inputCumulativeX = cumulativeX; + connection.inputCumulativeY = cumulativeY; + } + } + return result; + } + bool HandleInputControl(Connection& connection, const LGNetPacketView& packet, bool datagram) { - LGNetInputControl control; + LGNetInputControl control = {}; if (lgNetInputControlDecode(&control, packet.payload, packet.payloadLength) != LG_NET_PARSE_OK || packet.envelope.componentEpoch != control.claimEpoch || + packet.envelope.requestID || (datagram && packet.envelope.messageType != LG_NET_INPUT_MESSAGE_KEEPALIVE)) return false; @@ -3346,31 +3898,50 @@ retry: std::shared_ptr session; { - CSRWSharedLock guard(connection.lock); - session = connection.session; + CSRWSharedLock guard(connection.inputLock); + session = connection.inputSession; } if (!session) return false; - InputResult result; + InputResult result = InputResult::STALE; switch (packet.envelope.messageType) { case LG_NET_INPUT_MESSAGE_KEEPALIVE: result = session->TouchInput(); + if (result == InputResult::ACCEPTED) + { + CSRWExclusiveLock guard(connection.inputLock); + if (connection.inputClaimEpoch == control.claimEpoch) + RequestInputLEDCheckLocked(connection); + } break; case LG_NET_INPUT_MESSAGE_RESET: result = session->ResetInput(); { - CSRWExclusiveLock guard(connection.lock); - ClearInputStateLocked(connection, false); + CSRWExclusiveLock guard(connection.inputLock); + connection.inputPointerButtons = 0; + connection.keyboardModifiers = 0; + memset(connection.keyboardKeys, 0, + sizeof(connection.keyboardKeys)); + if (connection.inputMotionSequence <= control.sequence) + { + connection.inputMotionSequence = control.sequence; + connection.inputCumulativeX = 0; + connection.inputCumulativeY = 0; + connection.inputAbsoluteX = 0; + connection.inputAbsoluteY = 0; + connection.inputMotionAbsolute = false; + connection.inputMotionBaseline = false; + } } break; case LG_NET_INPUT_MESSAGE_RELEASE: result = session->ReleaseInput(false); { - CSRWExclusiveLock guard(connection.lock); + CSRWExclusiveLock guard(connection.inputLock); ClearInputStateLocked(connection, true); } break; @@ -3381,11 +3952,7 @@ retry: if (result == InputResult::ACCEPTED && packet.envelope.messageType == LG_NET_INPUT_MESSAGE_KEEPALIVE) - return SendInputLEDs(connection); - if (result == InputResult::ACCEPTED && - packet.envelope.messageType == LG_NET_INPUT_MESSAGE_RELEASE) - clipboard.Process(GetTickCount64()); - + return true; const LGNetInputStatusCode code = packet.envelope.messageType == LG_NET_INPUT_MESSAGE_RELEASE && result == InputResult::ACCEPTED ? @@ -3393,19 +3960,24 @@ retry: InputStatusCode(result); if (result != InputResult::ACCEPTED) InvalidateInputClaim(connection, control.claimEpoch); - return SendInputStatus(connection, LG_NET_INPUT_MESSAGE_STATUS, + const bool sent = SendInputStatus(connection, LG_NET_INPUT_MESSAGE_STATUS, code, control.claimEpoch, 0, 0); + if (result == InputResult::ACCEPTED && + packet.envelope.messageType == LG_NET_INPUT_MESSAGE_RELEASE) + clipboard.Process(GetTickCount64()); + return sent; } bool HandleInputKeyboard(Connection& connection, const LGNetPacketView& packet) { - LGNetInputKeyboard keyboard; + 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) + packet.envelope.componentEpoch == 0 || + packet.envelope.requestID) return false; if (!ValidateInputOwner(connection, packet.envelope.componentEpoch, @@ -3414,7 +3986,7 @@ retry: uint8_t keys[NETWORK_KEYBOARD_KEY_COUNT] = {}; { - CSRWExclusiveLock guard(connection.lock); + CSRWExclusiveLock guard(connection.inputLock); connection.keyboardModifiers = static_cast(keyboard.modifiers); if (keyboard.usage < 224 || keyboard.usage > 231) @@ -3434,11 +4006,11 @@ retry: memset(keys, NETWORK_KEYBOARD_ROLLOVER, sizeof(keys)); } - std::shared_ptr session; - uint8_t modifiers; + std::shared_ptr session = {}; + uint8_t modifiers = 0; { - CSRWSharedLock guard(connection.lock); - session = connection.session; + CSRWSharedLock guard(connection.inputLock); + session = connection.inputSession; modifiers = connection.keyboardModifiers; } if (!session) @@ -3451,7 +4023,7 @@ retry: return SendInputStatus(connection, LG_NET_INPUT_MESSAGE_STATUS, InputStatusCode(result), packet.envelope.componentEpoch, 0, 0); } - return SendInputLEDs(connection); + return true; } static uint16_t NormalizeAbsolute(int32_t position, uint32_t extent) @@ -3463,15 +4035,65 @@ retry: (extent - 1U)); } + static bool InputPointerEnvelopeValid(Connection& connection, + const LGNetPacketView& packet, uint64_t sequence, bool datagram, + bool relative, int32_t wheel, LGNetPointerButtons buttons, + LGNetPointerButtons changedButtons) + { + const bool acknowledge = + (packet.envelope.flags & LG_NET_ENVELOPE_ACK_REQUIRED) != 0; + if (!sequence) + return false; + if (datagram) + { + if (wheel || changedButtons || acknowledge || + packet.envelope.requestID) + return false; + } + else if (acknowledge ? packet.envelope.requestID != sequence : + packet.envelope.requestID != 0) + return false; + + bool current; + bool motionApplies; + bool baseline; + bool modeChange; + bool buttonChange; + { + CSRWSharedLock guard(connection.inputLock); + const uint64_t lastSequence = datagram ? + connection.inputDatagramSequence : + connection.inputReliableSequence; + current = connection.inputClaimantID && + connection.inputClaimEpoch == packet.envelope.componentEpoch && + (connection.inputFlags & LG_NET_INPUT_CLAIM_POINTER) && + sequence > lastSequence; + motionApplies = sequence > connection.inputMotionSequence; + baseline = connection.inputMotionBaseline; + modeChange = motionApplies && baseline && + connection.inputMotionAbsolute == relative; + buttonChange = buttons != connection.inputPointerButtons; + } + if (!current) + return true; + if (datagram) + return !motionApplies || (baseline && !modeChange); + + const bool transition = wheel || changedButtons || buttonChange || + (motionApplies && (!baseline || modeChange)); + if (transition && !acknowledge) + return false; + return true; + } + bool HandleInputPointer(Connection& connection, const LGNetPacketView& packet, bool datagram) { - uint64_t sequence; - InputResult result; - std::shared_ptr session; + InputResult result = InputResult::STALE; + std::shared_ptr session = {}; { - CSRWSharedLock guard(connection.lock); - session = connection.session; + CSRWSharedLock guard(connection.inputLock); + session = connection.inputSession; } if (!session) return false; @@ -3479,38 +4101,69 @@ retry: if (packet.envelope.messageType == LG_NET_INPUT_MESSAGE_MOUSE_RELATIVE) { - LGNetInputRelative relative; + LGNetInputRelative relative = {}; if (lgNetInputRelativeDecode(&relative, packet.payload, packet.payloadLength) != LG_NET_PARSE_OK || relative.wheelX || - (datagram && (relative.wheelY || relative.changedButtons))) + !InputPointerEnvelopeValid(connection, packet, relative.sequence, + datagram, true, relative.wheelY, relative.buttons, + relative.changedButtons)) return false; - sequence = relative.sequence; - if (!ValidateInputPointer(connection, packet.envelope.componentEpoch, - sequence, datagram, relative.buttons)) + InputPointerMotion motion; + if (!PrepareInputPointer(connection, + packet.envelope.componentEpoch, relative.sequence, datagram, + true, relative.cumulativeX, relative.cumulativeY, + relative.buttons, motion)) return true; - result = session->SendMouseRelative(relative.deltaX, relative.deltaY, - relative.wheelY, relative.buttons); + if (datagram && !motion.apply) + return true; + if (motion.useAbsolute) + result = session->SendMouseAbsolute( + motion.absoluteX, motion.absoluteY, + relative.wheelY, relative.buttons); + else if (motion.apply) + result = SendInputRelativeMotion(connection, session, motion, + relative.wheelY, relative.buttons); + else + result = session->SendMouseRelative(0, 0, + relative.wheelY, relative.buttons); } else { - LGNetInputAbsolute absolute; + LGNetInputAbsolute absolute = {}; if (lgNetInputAbsoluteDecode(&absolute, packet.payload, packet.payloadLength) != LG_NET_PARSE_OK || absolute.wheelX || - (datagram && (absolute.wheelY || absolute.changedButtons))) + !InputPointerEnvelopeValid(connection, packet, absolute.sequence, + datagram, false, absolute.wheelY, absolute.buttons, + absolute.changedButtons)) return false; - sequence = absolute.sequence; - if (!ValidateInputPointer(connection, packet.envelope.componentEpoch, - sequence, datagram, absolute.buttons)) + InputPointerMotion motion; + motion.absoluteX = NormalizeAbsolute(absolute.x, absolute.width); + motion.absoluteY = NormalizeAbsolute(absolute.y, absolute.height); + if (!PrepareInputPointer(connection, + packet.envelope.componentEpoch, absolute.sequence, datagram, + false, absolute.cumulativeX, absolute.cumulativeY, + absolute.buttons, motion)) return true; - result = session->SendMouseAbsolute( - NormalizeAbsolute(absolute.x, absolute.width), - NormalizeAbsolute(absolute.y, absolute.height), - absolute.wheelY, absolute.buttons); + if (datagram && !motion.apply) + return true; + if (motion.useAbsolute) + result = session->SendMouseAbsolute( + motion.absoluteX, motion.absoluteY, + absolute.wheelY, absolute.buttons); + else + result = session->SendMouseRelative( + 0, 0, absolute.wheelY, absolute.buttons); } - if (result == InputResult::ACCEPTED) + const bool acknowledge = + (packet.envelope.flags & LG_NET_ENVELOPE_ACK_REQUIRED) != 0; + if (result == InputResult::ACCEPTED && !acknowledge) return true; - InvalidateInputClaim(connection, packet.envelope.componentEpoch); + if (result != InputResult::ACCEPTED) + InvalidateInputClaim(connection, packet.envelope.componentEpoch); + if (acknowledge) + return QueueInputAck(connection, InputStatusCode(result), + packet.envelope.componentEpoch, packet.envelope.requestID); return SendInputStatus(connection, LG_NET_INPUT_MESSAGE_STATUS, InputStatusCode(result), packet.envelope.componentEpoch, 0, 0); } @@ -3518,14 +4171,19 @@ retry: 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; + if (datagram) + { + if (packet.envelope.flags & LG_NET_ENVELOPE_ACK_REQUIRED) + return false; + if (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; + } + CSRWExclusiveLock dispatchGuard(connection.inputDispatchLock); switch (packet.envelope.messageType) { case LG_NET_INPUT_MESSAGE_CLAIM: @@ -4010,6 +4668,10 @@ retry: LG_NET_ENVELOPE_RESPONSE | LG_NET_ENVELOPE_FINAL)) return false; + if (TrySendPendingInputAck(connection) == SendResult::FAILED || + TrySendInputLEDs(connection) == SendResult::FAILED) + return false; + return PublishCursor(connection); } @@ -4024,9 +4686,6 @@ retry: const bool videoSubscribe = packet.envelope.service == LG_NET_SERVICE_VIDEO && packet.envelope.messageType == LG_NET_VIDEO_MESSAGE_SUBSCRIBE; - const bool inputClaim = - packet.envelope.service == LG_NET_SERVICE_INPUT && - packet.envelope.messageType == LG_NET_INPUT_MESSAGE_CLAIM; const bool recoveryRequest = packet.envelope.service == LG_NET_SERVICE_RECOVERY && (packet.envelope.messageType == LG_NET_RECOVERY_MESSAGE_GET_INFO || @@ -4045,8 +4704,7 @@ retry: packet.envelope.service == LG_NET_SERVICE_FILE; LGNetEnvelopeFlags expectedFlags = LG_NET_ENVELOPE_RELIABLE | LG_NET_ENVELOPE_FINAL; - if (audioSubscribe || videoSubscribe || inputClaim || - recoveryRequest || controlRequest) + if (audioSubscribe || videoSubscribe || recoveryRequest || controlRequest) expectedFlags |= LG_NET_ENVELOPE_ACK_REQUIRED; if (cursorStatus || audioResponse) expectedFlags |= LG_NET_ENVELOPE_RESPONSE; @@ -4154,9 +4812,6 @@ retry: case LG_NET_SERVICE_CURSOR: return HandleCursorStatus(connection, packet); - case LG_NET_SERVICE_INPUT: - return HandleInput(connection, packet, false); - case LG_NET_SERVICE_AUDIO: return audio.HandlePacket(AudioPeer(connection), packet, false) != NetworkAudioPacketResult::INVALID; @@ -4180,6 +4835,312 @@ retry: } } + bool HandleInputStreamPacket(Connection& connection, + const LGNetPacketView& packet) + { + LGNetEnvelopeFlags expectedFlags = + LG_NET_ENVELOPE_RELIABLE | LG_NET_ENVELOPE_FINAL; + if (packet.envelope.messageType == LG_NET_INPUT_MESSAGE_CLAIM || + ((packet.envelope.messageType == + LG_NET_INPUT_MESSAGE_MOUSE_RELATIVE || + packet.envelope.messageType == + LG_NET_INPUT_MESSAGE_MOUSE_ABSOLUTE) && + (packet.envelope.flags & LG_NET_ENVELOPE_ACK_REQUIRED))) + expectedFlags |= LG_NET_ENVELOPE_ACK_REQUIRED; + if (packet.envelope.headerSize != LG_NET_ENVELOPE_WIRE_SIZE || + packet.envelope.service != LG_NET_SERVICE_INPUT || + packet.envelope.flags != expectedFlags || + !lgNetEnvelopeRouteValid(&packet.envelope, LG_NET_ROLE_CLIENT, + LG_NET_ROUTE_DELIVERY_RELIABLE)) + return false; + + { + CSRWSharedLock guard(connection.inputLock); + if (connection.inputClosing || !connection.inputStreamBound || + packet.envelope.sessionEpoch != + connection.inputStreamSessionEpoch || + packet.envelope.serviceVersion != + connection.inputStreamServiceVersion || + packet.envelope.payloadLength > connection.inputStreamMaxPayload) + return false; + } + return HandleInput(connection, packet, false); + } + + bool DrainInputReceive(Connection& connection) + { + for (;;) + { + LGNetStreamPacket streamPacket = {}; + const LGNetStreamResult result = lgNetStreamParserNext( + &connection.inputReceiveParser, &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 + { + if (streamPacket.payloadLength > + connection.inputReceivePayload.size()) + return false; + memcpy(connection.inputReceivePayload.data(), + streamPacket.payload[0].data, streamPacket.payload[0].size); + memcpy(connection.inputReceivePayload.data() + + streamPacket.payload[0].size, streamPacket.payload[1].data, + streamPacket.payload[1].size); + packet.payload = connection.inputReceivePayload.data(); + } + + { + CSRWExclusiveLock guard(connection.inputLock); + if (!connection.inputStreamBound || !packet.envelope.sequence || + connection.inputStreamReceiveSequence == UINT64_MAX || + packet.envelope.sequence != + connection.inputStreamReceiveSequence + 1) + return false; + connection.inputStreamReceiveSequence = packet.envelope.sequence; + } + if (!HandleInputStreamPacket(connection, packet) || + !lgNetStreamParserConsume(&connection.inputReceiveParser)) + return false; + + if (!lgNetStreamParserBuffered(&connection.inputReceiveParser)) + lgNetStreamParserReset(&connection.inputReceiveParser); + } + } + + bool ActivateInputReceive(Connection& connection) + { + LGNetStreamBinding binding = {}; + uint64_t streamID = 0; + { + CSRWSharedLock guard(connection.inputLock); + if (connection.inputBindingLength != + sizeof(connection.inputBindingData) || + connection.inputStreamBound) + return false; + streamID = connection.inputStreamID; + if (lgNetStreamBindingDecode(&binding, connection.inputBindingData, + sizeof(connection.inputBindingData)) != LG_NET_PARSE_OK) + return false; + } + if (binding.service != LG_NET_SERVICE_INPUT || + binding.originRole != LG_NET_ROLE_CLIENT || binding.flags || + binding.streamID != streamID) + return false; + + std::shared_ptr session = {}; + LGNetCapability capability = {}; + { + CSRWSharedLock guard(connection.lock); + const uint16_t inputMask = UINT16_C(1) << LG_NET_SERVICE_INPUT; + if (connection.closing || + connection.handshake != HandshakeState::READY || + binding.sessionEpoch != connection.sessionEpoch || + !(connection.selectedServices & inputMask) || + !(connection.reliableServices & inputMask) || + connection.selectedCapabilities + [LG_NET_SERVICE_INPUT].maxBidirectionalStreams != 1 || + !connection.session) + return false; + capability = + connection.selectedCapabilities[LG_NET_SERVICE_INPUT]; + session = connection.session; + } + + LGNetSequenceTracker datagramReceive = {}; + if (!lgNetSequenceTrackerInit(&datagramReceive, binding.sessionEpoch, + NETWORK_SEQUENCE_COMPONENT, NETWORK_DATAGRAM_WINDOW, false)) + return false; + + CSRWExclusiveLock guard(connection.inputLock); + if (connection.inputClosing || !connection.inputStream || + connection.inputStreamID != streamID || + connection.inputBindingLength != + sizeof(connection.inputBindingData) || + connection.inputStreamBound) + return false; + connection.inputStreamSessionEpoch = binding.sessionEpoch; + connection.inputStreamServiceVersion = capability.versionMin; + connection.inputStreamMaxPayload = capability.maxPayloadLength; + connection.inputDatagramLimit = + capability.maxDatagramPayload; + connection.inputSession = std::move(session); + connection.inputDatagramReceive = datagramReceive; + connection.inputStreamBound = true; + return true; + } + + bool ReceiveInput(Connection& connection, const QUIC_BUFFER * buffers, + uint32_t count) + { + if (count && !buffers) + return false; + + for (uint32_t index = 0; index < count; ++index) + { + if (buffers[index].Length && !buffers[index].Buffer) + return false; + + size_t offset = 0; + while (offset < buffers[index].Length) + { + bool bound = false; + { + CSRWSharedLock guard(connection.inputLock); + bound = connection.inputStreamBound; + } + if (!bound) + { + bool activate = false; + { + CSRWExclusiveLock guard(connection.inputLock); + const size_t remaining = buffers[index].Length - offset; + size_t length = sizeof(connection.inputBindingData) - + connection.inputBindingLength; + if (length > remaining) + length = remaining; + memcpy(connection.inputBindingData + + connection.inputBindingLength, + buffers[index].Buffer + offset, length); + connection.inputBindingLength += length; + offset += length; + activate = connection.inputBindingLength == + sizeof(connection.inputBindingData); + } + if (activate && !ActivateInputReceive(connection)) + return false; + continue; + } + + size_t accepted = 0; + if (!lgNetStreamParserAppend(&connection.inputReceiveParser, + buffers[index].Buffer + offset, + buffers[index].Length - offset, &accepted)) + return false; + offset += accepted; + if (!DrainInputReceive(connection) || + (!accepted && + !lgNetStreamParserWritable(&connection.inputReceiveParser))) + return false; + } + } + + bool bound = false; + { + CSRWSharedLock guard(connection.inputLock); + bound = connection.inputStreamBound; + } + return !bound || DrainInputReceive(connection); + } + + bool AcceptInputStream(Connection& connection, HQUIC handle, + QUIC_STREAM_OPEN_FLAGS flags) + { + if (flags != QUIC_STREAM_OPEN_FLAG_NONE) + return false; + + QUIC_UINT62 streamID = 0; + uint32_t streamIDSize = sizeof(streamID); + if (QUIC_FAILED(api->GetParam(handle, QUIC_PARAM_STREAM_ID, + &streamIDSize, &streamID)) || + streamIDSize != sizeof(streamID) || (streamID & 3U) != 0U) + return false; + + uint32_t maxPayload = 0; + { + CSRWSharedLock guard(connection.lock); + const uint16_t inputMask = UINT16_C(1) << LG_NET_SERVICE_INPUT; + if (connection.closing || + connection.handshake != HandshakeState::READY || + !(connection.selectedServices & inputMask) || + !(connection.reliableServices & inputMask) || + connection.selectedCapabilities + [LG_NET_SERVICE_INPUT].maxBidirectionalStreams != 1) + return false; + maxPayload = connection.selectedCapabilities + [LG_NET_SERVICE_INPUT].maxPayloadLength; + } + if (!maxPayload || maxPayload > NETWORK_INPUT_MAX_PAYLOAD) + return false; + + const uint16_t priority = UINT16_MAX; + if (QUIC_FAILED(api->SetParam(handle, QUIC_PARAM_STREAM_PRIORITY, + sizeof(priority), &priority))) + return false; + + std::vector receiveData; + std::vector receivePayload; + try + { + receiveData.resize(LG_NET_ENVELOPE_WIRE_SIZE + maxPayload); + receivePayload.resize(maxPayload); + } + catch (...) + { + return false; + } + + { + CSRWExclusiveLock guard(connection.inputLock); + if (connection.inputClosing || connection.inputStream) + return false; + connection.inputReceiveData.swap(receiveData); + connection.inputReceivePayload.swap(receivePayload); + if (!lgNetStreamParserInit(&connection.inputReceiveParser, + connection.inputReceiveData.data(), + connection.inputReceiveData.size(), maxPayload)) + { + connection.inputReceiveData.clear(); + connection.inputReceivePayload.clear(); + return false; + } + connection.inputStream = handle; + connection.inputStreamID = streamID; + connection.inputBindingLength = 0; + memset(connection.inputBindingData, 0, + sizeof(connection.inputBindingData)); + } + + api->SetCallbackHandler(handle, + reinterpret_cast(InputStreamCallback), &connection); + return true; + } + + bool HandleInputDatagram(Connection& connection, + const LGNetPacketView& packet) + { + { + CSRWExclusiveLock guard(connection.inputLock); + if (connection.inputClosing || !connection.inputStreamBound || + packet.envelope.sessionEpoch != + connection.inputStreamSessionEpoch || + packet.envelope.serviceVersion != + connection.inputStreamServiceVersion || + !connection.inputDatagramLimit || + packet.envelope.payloadLength > + connection.inputDatagramLimit) + return false; + + const LGNetSequenceResult sequenceResult = lgNetSequenceTrackerCheck( + &connection.inputDatagramReceive, 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; + } + return HandleInput(connection, packet, true); + } + bool HandleDatagram(Connection& connection, const QUIC_BUFFER * buffer) { LGNetPacketView packet; @@ -4199,6 +5160,9 @@ retry: !packet.envelope.sequence) return false; + if (packet.envelope.service == LG_NET_SERVICE_INPUT) + return HandleInputDatagram(connection, packet); + const uint16_t serviceMask = UINT16_C(1) << packet.envelope.service; { CSRWExclusiveLock guard(connection.lock); @@ -4238,8 +5202,6 @@ retry: 0, packet.envelope.requestID, packet.payload, static_cast(packet.payloadLength), true); - if (packet.envelope.service == LG_NET_SERVICE_INPUT) - return HandleInput(connection, packet, true); if (packet.envelope.service == LG_NET_SERVICE_AUDIO) return audio.HandlePacket(AudioPeer(connection), packet, true) != NetworkAudioPacketResult::INVALID; @@ -4600,6 +5562,7 @@ retry: connections.begin(), connections.end(), &connection); if (found != connections.end()) connections.erase(found); + connection.detached = true; } { @@ -4620,13 +5583,29 @@ retry: static_cast(service)); session = std::move(connection.session); } + { + CSRWExclusiveLock guard(connection.inputLock); + if (connection.inputStreamSendCount || + connection.inputStreamSendBytes) + DEBUG_ERROR("Network client %u closed with input sends in flight", + connection.client); + connection.inputSession.reset(); + } video.PeerDisconnected(VideoPeer(connection)); clipboard.PeerDisconnected(ClipboardPeer(connection)); audio.PeerDisconnected(AudioPeer(connection)); if (session) transport.RemoveSession(client, generation); session.reset(); - delete &connection; + + bool dispose = false; + { + CSRWExclusiveLock guard(lock); + connection.cleanupComplete = true; + dispose = connection.detached && !connection.pins; + } + if (dispose) + delete &connection; DEBUG_INFO("Network client %u disconnected", client); } @@ -4739,7 +5718,7 @@ retry: return QUIC_STATUS_SUCCESS; } - bool accepted = false; + bool controlAccepted = false; { CSRWExclusiveLock guard(connection.lock); if (flags == QUIC_STREAM_OPEN_FLAG_NONE && @@ -4747,13 +5726,16 @@ retry: !connection.controlStream) { connection.controlStream = event->PEER_STREAM_STARTED.Stream; - accepted = true; + controlAccepted = true; } } - if (accepted) + if (controlAccepted) owner.api->SetCallbackHandler(event->PEER_STREAM_STARTED.Stream, reinterpret_cast(StreamCallback), &connection); + else if (owner.AcceptInputStream(connection, + event->PEER_STREAM_STARTED.Stream, flags)) + return QUIC_STATUS_SUCCESS; else { owner.api->SetCallbackHandler(event->PEER_STREAM_STARTED.Stream, @@ -4810,8 +5792,14 @@ retry: event->SHUTDOWN_INITIATED_BY_TRANSPORT.Status), static_cast( event->SHUTDOWN_INITIATED_BY_TRANSPORT.ErrorCode)); - CSRWExclusiveLock guard(connection.lock); - connection.closing = true; + { + CSRWExclusiveLock guard(connection.lock); + connection.closing = true; + } + { + CSRWExclusiveLock guard(connection.inputLock); + connection.inputClosing = true; + } return QUIC_STATUS_SUCCESS; } @@ -4821,8 +5809,14 @@ retry: DEBUG_WARN("Network client %u closed the connection: error 0x%llx", connection.client, static_cast( event->SHUTDOWN_INITIATED_BY_PEER.ErrorCode)); - CSRWExclusiveLock guard(connection.lock); - connection.closing = true; + { + CSRWExclusiveLock guard(connection.lock); + connection.closing = true; + } + { + CSRWExclusiveLock guard(connection.inputLock); + connection.inputClosing = true; + } return QUIC_STATUS_SUCCESS; } @@ -4887,6 +5881,69 @@ retry: } } + static QUIC_STATUS QUIC_API InputStreamCallback(HQUIC handle, + void * context, QUIC_STREAM_EVENT * event) + { + Connection& connection = *static_cast(context); + Impl& owner = connection.owner; + CallbackGuard callback(owner); + + switch (event->Type) + { + case QUIC_STREAM_EVENT_RECEIVE: + if (!owner.ReceiveInput(connection, event->RECEIVE.Buffers, + event->RECEIVE.BufferCount)) + owner.Shutdown(connection, NETWORK_ERROR_PROTOCOL); + return QUIC_STATUS_SUCCESS; + + case QUIC_STREAM_EVENT_SEND_COMPLETE: + { + std::unique_ptr send( + static_cast(event->SEND_COMPLETE.ClientContext)); + { + CSRWExclusiveLock guard(connection.inputLock); + ReleaseInputSendLocked(connection, *send); + connection.inputLEDWaitForSendReady = false; + if (send->inputAck) + connection.inputAckInFlight = false; + } + if (owner.TrySendPendingInputAck(connection) == SendResult::FAILED || + owner.TrySendInputLEDs(connection) == SendResult::FAILED) + owner.Shutdown(connection, NETWORK_ERROR_PROTOCOL); + return QUIC_STATUS_SUCCESS; + } + + case QUIC_STREAM_EVENT_PEER_SEND_SHUTDOWN: + case QUIC_STREAM_EVENT_PEER_SEND_ABORTED: + case QUIC_STREAM_EVENT_PEER_RECEIVE_ABORTED: + owner.Shutdown(connection, NETWORK_ERROR_PROTOCOL); + return QUIC_STATUS_SUCCESS; + + case QUIC_STREAM_EVENT_SHUTDOWN_COMPLETE: + { + CSRWExclusiveLock sendGuard(connection.inputSendLock); + { + CSRWExclusiveLock guard(connection.inputLock); + if (connection.inputStream == handle) + { + connection.inputStream = nullptr; + connection.inputStreamBound = false; + connection.inputClosing = true; + connection.inputAckSending = false; + connection.inputAckInFlight = false; + ClearPendingInputAckLocked(connection); + connection.inputSession.reset(); + } + } + owner.api->StreamClose(handle); + return QUIC_STATUS_SUCCESS; + } + + default: + return QUIC_STATUS_SUCCESS; + } + } + static QUIC_STATUS QUIC_API BulkStreamCallback(HQUIC handle, void * context, QUIC_STREAM_EVENT * event) { @@ -5094,11 +6151,27 @@ retry: audio.Process(now); video.Process(now); clipboard.Process(now); - CSRWSharedLock guard(lock); - if (stopping || !api) - return; + std::vector pinned; + { + CSRWExclusiveLock guard(lock); + if (stopping || !api) + return; + try + { + pinned = connections; + } + catch (...) + { + return; + } + for (Connection * connection : pinned) + { + ++connection->pins; + ++activePins; + } + } - for (Connection * connection : connections) + for (Connection * connection : pinned) { HQUIC handle = nullptr; uint32_t client = 0; @@ -5117,13 +6190,21 @@ retry: { if (!PumpSession(*connection, now)) Shutdown(*connection, NETWORK_ERROR_PROTOCOL); + ReleaseConnectionPin(*connection); continue; } + { + CSRWExclusiveLock inputGuard(connection->inputLock); + connection->inputClosing = true; + } + DEBUG_WARN("Closing network client %u after an incomplete handshake", client); api->ConnectionShutdown(handle, QUIC_CONNECTION_SHUTDOWN_FLAG_NONE, NETWORK_ERROR_HANDSHAKE_TIMEOUT); + + ReleaseConnectionPin(*connection); } } @@ -5295,7 +6376,8 @@ retry: CSRWExclusiveLock guard(lock); stopping = true; running = false; - if (connections.empty() && !activeCallbacks && drainedEvent) + if (connections.empty() && !activeCallbacks && !activePins && + drainedEvent) SetEvent(drainedEvent); } diff --git a/idd/LGInput/CHIDDevice.cpp b/idd/LGInput/CHIDDevice.cpp index 5c1c7140..bca1ff5b 100644 --- a/idd/LGInput/CHIDDevice.cpp +++ b/idd/LGInput/CHIDDevice.cpp @@ -35,14 +35,15 @@ static constexpr USHORT LG_INPUT_VENDOR_ID = 0x0000; static constexpr USHORT LG_INPUT_PRODUCT_ID = 0x0000; static constexpr USHORT LG_INPUT_VERSION = 0x0001; static constexpr size_t REPORT_QUEUE_LENGTH = 64; -static constexpr size_t MOTION_COALESCE_THRESHOLD = - REPORT_QUEUE_LENGTH / 2; +static constexpr size_t MOTION_EMERGENCY_DEPTH = 8; +static constexpr DWORD MOTION_AGE_BUDGET_MS = 1; struct HIDQueuedReport { - UCHAR data[HID_MAX_INPUT_REPORT_SIZE]; - bool pureMotion; - size_t size; + UCHAR data[HID_MAX_INPUT_REPORT_SIZE]; + int64_t enqueueTicks; + size_t size; + bool pureMotion; }; struct HIDStatistics @@ -77,6 +78,9 @@ struct HIDDeviceContext uint16_t consumerUsage; uint16_t absoluteX; uint16_t absoluteY; + int64_t motionBudgetTicks; + HIDKeyboardReport keyboard; + bool keyboardValid; HIDStatistics statistics; HIDQueuedReport reports[REPORT_QUEUE_LENGTH]; CInputPipeClient * inputPipe; @@ -200,28 +204,71 @@ static void RemoveQueuedReport( --context->reportCount; } -static bool CompactStaleMotion( - _Inout_ HIDDeviceContext * context, - _In_ UCHAR incomingReportId) +static bool MotionBudgetExceeded(_In_ HIDDeviceContext * context) { - for (size_t i = 0; i < context->reportCount; ++i) + if (context->reportCount >= MOTION_EMERGENCY_DEPTH) + return true; + if (!context->reportCount || !context->motionBudgetTicks) + return false; + + LARGE_INTEGER now = {}; + if (!QueryPerformanceCounter(&now)) + return false; + + const int64_t oldest = GetQueuedReport(context, 0).enqueueTicks; + return oldest > 0 && now.QuadPart >= oldest && + now.QuadPart - oldest >= context->motionBudgetTicks; +} + +static bool CompactQueuedMotion(_Inout_ HIDDeviceContext * context) +{ + for (size_t i = 0; i + 1 < context->reportCount; ++i) { - HIDQueuedReport& report = GetQueuedReport(context, i); - if (!report.pureMotion || - report.data[0] != HID_REPORT_ID_MOUSE_ABSOLUTE) + HIDQueuedReport& first = GetQueuedReport(context, i); + HIDQueuedReport& second = GetQueuedReport(context, i + 1); + if (!first.pureMotion || !second.pureMotion || + first.data[0] != second.data[0]) continue; - bool superseded = incomingReportId == HID_REPORT_ID_MOUSE_ABSOLUTE; - for (size_t j = i + 1; !superseded && j < context->reportCount; ++j) - superseded = GetQueuedReport(context, j).data[0] == - HID_REPORT_ID_MOUSE_ABSOLUTE; - - if (superseded) + if (first.data[0] == HID_REPORT_ID_MOUSE_RELATIVE) { - RemoveQueuedReport(context, i); - ++context->statistics.staleAbsoluteCompacted; + HIDMouseRelativeReport * previous = + reinterpret_cast(first.data); + const HIDMouseRelativeReport * current = + reinterpret_cast(second.data); + const int32_t x = + static_cast(previous->x) + current->x; + const int32_t y = + static_cast(previous->y) + current->y; + if (x < INT16_MIN || x > INT16_MAX || + y < INT16_MIN || y > INT16_MAX || + previous->buttons != current->buttons || + previous->wheel || current->wheel) + continue; + + previous->x = static_cast(x); + previous->y = static_cast(y); + RemoveQueuedReport(context, i + 1); + ++context->statistics.relativeCoalesced; return true; } + + if (first.data[0] != HID_REPORT_ID_MOUSE_ABSOLUTE) + continue; + + const HIDMouseAbsoluteReport * previous = + reinterpret_cast(first.data); + const HIDMouseAbsoluteReport * current = + reinterpret_cast(second.data); + if (previous->buttons != current->buttons || + previous->wheel || current->wheel) + continue; + + CopyMemory(first.data, second.data, second.size); + first.size = second.size; + RemoveQueuedReport(context, i + 1); + ++context->statistics.staleAbsoluteCompacted; + return true; } return false; } @@ -232,7 +279,13 @@ static NTSTATUS QueueReport( _In_ size_t size, _In_ bool pureMotion) { - const UCHAR reportId = *static_cast(data); + const UCHAR reportId = *static_cast(data); + const bool motionBudgetExceeded = MotionBudgetExceeded(context); + if (motionBudgetExceeded) + while (CompactQueuedMotion(context)) + { + } + if (context->reportCount) { HIDQueuedReport& tail = @@ -240,20 +293,21 @@ static NTSTATUS QueueReport( if (tail.data[0] == reportId) { if (reportId == HID_REPORT_ID_MOUSE_RELATIVE && - context->reportCount >= MOTION_COALESCE_THRESHOLD && - tail.pureMotion && pureMotion) + tail.pureMotion && pureMotion && + motionBudgetExceeded) { - HIDMouseRelativeReport * previous = + HIDMouseRelativeReport * previous = reinterpret_cast(tail.data); - const HIDMouseRelativeReport * current = + const HIDMouseRelativeReport * current = static_cast(data); - const int32_t x = static_cast(previous->x) + - current->x; - const int32_t y = static_cast(previous->y) + - current->y; + const int32_t x = + static_cast(previous->x) + current->x; + const int32_t y = + static_cast(previous->y) + current->y; if (x >= INT16_MIN && x <= INT16_MAX && y >= INT16_MIN && y <= INT16_MAX && - previous->buttons == current->buttons) + previous->buttons == current->buttons && + !previous->wheel && !current->wheel) { previous->x = static_cast(x); previous->y = static_cast(y); @@ -266,9 +320,10 @@ static NTSTATUS QueueReport( { const HIDMouseAbsoluteReport * previous = reinterpret_cast(tail.data); - const HIDMouseAbsoluteReport * current = + const HIDMouseAbsoluteReport * current = static_cast(data); - if (previous->buttons == current->buttons) + if (previous->buttons == current->buttons && + !previous->wheel && !current->wheel) { CopyMemory(tail.data, data, size); tail.size = size; @@ -276,30 +331,24 @@ static NTSTATUS QueueReport( return STATUS_SUCCESS; } } - else if (reportId == HID_REPORT_ID_KEYBOARD && - tail.size == size && memcmp(tail.data, data, size) == 0) - { - ++context->statistics.keyboardDuplicates; - return STATUS_SUCCESS; - } } } if (context->reportCount == REPORT_QUEUE_LENGTH) { - if (!CompactStaleMotion(context, reportId)) - { - ++context->statistics.overflows; - return STATUS_BUFFER_OVERFLOW; - } + ++context->statistics.overflows; + return STATUS_BUFFER_OVERFLOW; } - const size_t index = + const size_t index = (context->reportHead + context->reportCount) % REPORT_QUEUE_LENGTH; - HIDQueuedReport * report = &context->reports[index]; + HIDQueuedReport * report = &context->reports[index]; + LARGE_INTEGER enqueueTime = {}; + QueryPerformanceCounter(&enqueueTime); CopyMemory(report->data, data, size); - report->size = size; - report->pureMotion = pureMotion; + report->enqueueTicks = enqueueTime.QuadPart; + report->size = size; + report->pureMotion = pureMotion; ++context->reportCount; ++context->statistics.queued; if (context->reportCount > context->statistics.queueHighWater) @@ -365,10 +414,11 @@ static NTSTATUS DeactivateDevice( if (context->deactivateRequest) return STATUS_DEVICE_BUSY; - context->active = false; - context->reportHead = 0; - context->reportCount = 0; - context->consumerUsage = UINT16_MAX; + context->active = false; + context->reportHead = 0; + context->reportCount = 0; + context->consumerUsage = UINT16_MAX; + context->keyboardValid = false; } context->deactivateRequest = request; @@ -432,10 +482,23 @@ NTSTATUS CHIDDevice::Create(_Inout_ PWDFDEVICE_INIT deviceInit) if (!NT_SUCCESS(status)) return status; - HIDDeviceContext * context = HIDGetDeviceContext(device); + HIDDeviceContext * context = HIDGetDeviceContext(device); + LARGE_INTEGER performanceFrequency = {}; new (context) HIDDeviceContext {}; - context->active = true; - context->inputPipe = new (std::nothrow) CInputPipeClient; + if (QueryPerformanceFrequency(&performanceFrequency)) + { + static_assert(MOTION_AGE_BUDGET_MS && + 1000 % MOTION_AGE_BUDGET_MS == 0, + "motion age budget must evenly divide one second"); + const int64_t budgetDivisor = 1000 / MOTION_AGE_BUDGET_MS; + context->motionBudgetTicks = + performanceFrequency.QuadPart / budgetDivisor; + if (performanceFrequency.QuadPart % budgetDivisor) + ++context->motionBudgetTicks; + } + context->active = true; + context->consumerUsage = UINT16_MAX; + context->inputPipe = new (std::nothrow) CInputPipeClient; if (!context->inputPipe) return STATUS_INSUFFICIENT_RESOURCES; @@ -503,6 +566,13 @@ NTSTATUS CHIDDevice::SubmitReport( ++context->statistics.consumerDuplicates; return STATUS_SUCCESS; } + if (reportId == HID_REPORT_ID_KEYBOARD && + context->keyboardValid && + memcmp(&context->keyboard, report, size) == 0) + { + ++context->statistics.keyboardDuplicates; + return STATUS_SUCCESS; + } bool pureMotion = false; switch (reportId) @@ -573,6 +643,11 @@ NTSTATUS CHIDDevice::SubmitReport( static_cast(report)->usage; break; + case HID_REPORT_ID_KEYBOARD: + CopyMemory(&context->keyboard, report, size); + context->keyboardValid = true; + break; + default: break; } @@ -608,12 +683,13 @@ NTSTATUS CHIDDevice::ResetReports() absoluteX = context->absoluteX; absoluteY = context->absoluteY; context->statistics.resetDiscarded += context->reportCount; - context->reportHead = 0; - context->reportCount = 0; - context->mouseMode = 0; - context->relativeButtons = 0; - context->absoluteButtons = 0; - context->consumerUsage = UINT16_MAX; + context->reportHead = 0; + context->reportCount = 0; + context->mouseMode = 0; + context->relativeButtons = 0; + context->absoluteButtons = 0; + context->consumerUsage = UINT16_MAX; + context->keyboardValid = false; } const HIDKeyboardReport keyboard = { @@ -804,9 +880,10 @@ VOID HIDEvtReportQueueCleanup(_In_ WDFOBJECT object) CSRWExclusiveLock lifecycleLock(context->lifecycleLock); { CSRWExclusiveLock reportLock(context->reportLock); - context->stopping = true; - context->reportHead = 0; - context->reportCount = 0; + context->stopping = true; + context->reportHead = 0; + context->reportCount = 0; + context->keyboardValid = false; } deactivateRequest = context->deactivateRequest; context->deactivateRequest = nullptr; diff --git a/idd/LGInput/ipc/CInputPipeClient.cpp b/idd/LGInput/ipc/CInputPipeClient.cpp index 8d7c14e8..db9830e3 100644 --- a/idd/LGInput/ipc/CInputPipeClient.cpp +++ b/idd/LGInput/ipc/CInputPipeClient.cpp @@ -57,7 +57,11 @@ bool CInputPipeClient::Start() return m_endpoint.Start( LG_INPUT_PIPE_NAME, CPipeEndpoint::Mode::Client, - sizeof(LGInputPipeMessage)); + sizeof(LGInputPipeMessage), + nullptr, + 0, + 0, + CPipeEndpoint::ThreadScheduling::Input); } void CInputPipeClient::Stop() diff --git a/repos/LGProtocol b/repos/LGProtocol index 7913a08a..189c9bc5 160000 --- a/repos/LGProtocol +++ b/repos/LGProtocol @@ -1 +1 @@ -Subproject commit 7913a08abad9d45e8c9954adff7360d35511ed27 +Subproject commit 189c9bc5b6805dda63dad3f121b8077c2850906d