/** * Looking Glass * Copyright © 2017-2026 The Looking Glass Authors * https://looking-glass.io * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "connection.h" #include "auth_crypto.h" #include "fingerprint.h" #include "common/debug.h" #include "common/event.h" #include "common/locking.h" #include "common/thread.h" #include #include #include #include #include #include #include #if !defined(QUIC_API_VERSION_2) #error "The network transport requires MsQuic API version 2" #endif #include #include #include #include #define NETWORK_CONNECT_POLL_MS 50U #define NETWORK_HANDSHAKE_TIMEOUT_MS 5000U #define NETWORK_IDLE_TIMEOUT_MS 30000U #define NETWORK_KEEPALIVE_INTERVAL_MS 1000U #define NETWORK_INITIAL_RTT_MS 1U #define NETWORK_MAX_PEER_STREAMS 16U #define NETWORK_STREAM_RECV_BUFFER (1U * 1024U * 1024U) #define NETWORK_STREAM_RECV_WINDOW (128U * 1024U * 1024U) #define NETWORK_CONN_RECV_WINDOW (256U * 1024U * 1024U) #define NETWORK_MAX_CONTROL_PAYLOAD \ (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_DATAGRAM_SEND_ENABLED UINT32_C(0x00010000) #define NETWORK_HELLO_REQUEST_ID UINT64_C(1) #define NETWORK_SEQUENCE_CONNECTION_EPOCH UINT64_C(1) #define NETWORK_AUTH_PBKDF2_ROUNDS_MIN 10000U #define NETWORK_AUTH_PBKDF2_ROUNDS_MAX 1000000U #define NETWORK_AUTH_SALT_LENGTH_MIN 16U #define NETWORK_AUTH_CHALLENGE_LENGTH_MIN 16U #define NETWORK_AUTH_RESPONSE_NONCE_LENGTH 32U #define NETWORK_SEND_SERVICE_COUNT \ (LG_NET_SERVICE_CONTROL + 1U) #define NETWORK_SEND_RELIABLE_COUNT_MAX 512U #define NETWORK_SEND_RELIABLE_BYTES_MAX \ (UINT64_C(128) * 1024U * 1024U) #define NETWORK_SEND_RELIABLE_COUNT_RESERVED 64U #define NETWORK_SEND_RELIABLE_BYTES_RESERVED \ (UINT64_C(32) * 1024U * 1024U) #define NETWORK_SEND_DATAGRAM_COUNT_MAX 512U #define NETWORK_SEND_DATAGRAM_BYTES_MAX \ (UINT64_C(1) * 1024U * 1024U) #define NETWORK_SEND_DATAGRAM_COUNT_RESERVED 64U #define NETWORK_SEND_DATAGRAM_BYTES_RESERVED \ (UINT64_C(256) * 1024U) #define NETWORK_SEND_BULK_COUNT_MAX 256U #define NETWORK_SEND_BULK_BYTES_MAX \ (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_RELIABLE_RESERVE 16U #define NETWORK_INPUT_SEND_POOL_DATA_SIZE 256U #define NETWORK_INPUT_SEND_READY_RELIABLE UINT8_C(0x01) #define NETWORK_INPUT_SEND_READY_DATAGRAM UINT8_C(0x02) _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"); _Static_assert(NETWORK_INPUT_SEND_RELIABLE_RESERVE < NETWORK_INPUT_SEND_POOL_COUNT, "Network input reliable reserve consumes the send pool"); 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; bool lossReported; bool pooled; }; struct NetworkInputSend { struct NetworkSend send; bool reliableReserved; uint8_t data[NETWORK_INPUT_SEND_POOL_DATA_SIZE]; }; struct NetworkSendLimits { uint32_t count; uint64_t bytes; }; struct NetworkSendAccounting { uint32_t count; uint64_t bytes; uint32_t serviceCount[NETWORK_SEND_SERVICE_COUNT]; uint64_t serviceBytes[NETWORK_SEND_SERVICE_COUNT]; bool serviceBlocked[NETWORK_SEND_SERVICE_COUNT]; uint64_t serviceBlockedBytes[NETWORK_SEND_SERVICE_COUNT]; }; struct NetworkBulkStream { struct NetworkBulkStream * next; NetworkConnection * connection; HQUIC handle; LGNetService service; uint64_t streamID; uint64_t sendSequence; uint64_t receiveSequence; bool outbound; bool bound; bool openingReturned; bool shutdownComplete; bool receivePending; bool receiveFailed; uint32_t receiveReferences; uint8_t bindingData[LG_NET_STREAM_BINDING_WIRE_SIZE]; size_t bindingLength; LGNetStreamParser receiveParser; uint8_t * receiveData; uint8_t * receivePayload; size_t receivePayloadSize; }; struct NetworkBulkReceive { struct NetworkBulkReceive * next; struct NetworkBulkStream * stream; HQUIC handle; uint64_t length; uint32_t bufferCount; QUIC_BUFFER buffers[]; }; struct NetworkConnection { const QUIC_API_TABLE * api; HQUIC registration; HQUIC configuration; HQUIC connection; HQUIC controlStream; HQUIC inputStream; LGEvent * connectEvent; LGEvent * closeEvent; LGEvent * bulkReceiveEvent; LGEvent * bulkReceiveIdle; LGThread * bulkReceiveThread; LG_Lock lock; LG_Lock datagramSendLock; LG_Lock inputStreamLock; LG_Lock inputSendPoolLock; LG_Lock stateCallbackLock; atomic_bool stopping; atomic_bool sessionValid; char * address; uint16_t port; char * password; char * endpoint; bool allowUnencrypted; NetworkKnownHosts * knownHosts; NetworkTrustPromptFn trustPrompt; NetworkTrustCancelFn trustCancel; NetworkPacketFn packet; NetworkConnectionStateFn state; NetworkSendReadyFn sendReady; NetworkDatagramLostFn datagramLost; void * opaque; LGNetCapability offeredCapabilities[LG_NET_SERVICE_CONTROL + 1U]; uint32_t offeredCapabilityCount; uint16_t offeredServices; bool certificatePending; char pendingFingerprint[NETWORK_FINGERPRINT_TEXT_SIZE]; LG_TransportStatus connectStatus; QUIC_STATUS lastTransportStatus; QUIC_UINT62 lastTransportError; QUIC_UINT62 lastPeerError; bool lastTransportErrorValid; bool lastPeerErrorValid; bool connecting; LG_TransportSession session; uint64_t sessionEpoch; uint64_t sendSequence; uint64_t inputSendSequence; uint64_t inputReceiveSequence; uint64_t datagramSendSequence; uint64_t receiveSequence; LGNetSequenceTracker datagramReceive; uint16_t protocolVersion; LGNetAuthMode authMode; bool helloAcknowledged; bool authenticationComplete; bool authChallengeReceived; bool authResponseSent; bool authResultReceived; bool authKeyValid; bool capabilitiesComplete; bool guestInfoReceived; bool connectFailed; uint32_t expectedCapabilities; uint32_t receivedCapabilities; uint16_t receivedServices; uint16_t reliableServices; uint16_t datagramServices; uint16_t bulkServices; 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 [LG_NET_SERVICE_CONTROL + 1U]; uint32_t maxPayloadLength; uint32_t maxDatagramPayload; _Atomic uint32_t datagramSendState; _Atomic uint16_t inputDatagramBlockedLength; uint32_t serviceMaxPayload[LG_NET_SERVICE_CONTROL + 1U]; uint32_t serviceMaxDatagram[LG_NET_SERVICE_CONTROL + 1U]; struct NetworkSendAccounting reliableSend; struct NetworkSendAccounting datagramSend; struct NetworkSendAccounting bulkSend; struct NetworkInputSend inputSendPool[NETWORK_INPUT_SEND_POOL_COUNT]; struct NetworkSend * inputSendFree; struct NetworkSend * inputReliableSendFree; uint8_t inputSendBlocked; bool inputStreamBound; struct NetworkBulkStream * outboundBulk [LG_NET_SERVICE_CONTROL + 1U]; struct NetworkBulkStream * inboundBulk; struct NetworkBulkReceive * bulkReceiveHead; struct NetworkBulkReceive * bulkReceiveTail; uint32_t bulkReceiveReferences; bool bulkReceiveStopping; uint8_t clientHelloNonce[LG_NET_HELLO_NONCE_LENGTH]; uint8_t serverHelloNonce[LG_NET_HELLO_NONCE_LENGTH]; LGNetPasswordChallenge authChallenge; LGNetPasswordResponse authResponse; uint8_t authKey[LG_NET_PASSWORD_SHA256_LENGTH]; 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 void networkConnectionNotifySendReady(NetworkConnection * this, uint16_t reliable, uint16_t datagram, uint16_t bulk); static char * networkDuplicate(const char * value) { if (!value) return NULL; const size_t length = strlen(value) + 1U; char * result = malloc(length); if (result) memcpy(result, value, length); return result; } static char * networkEndpoint(const char * address, uint16_t port) { const size_t length = strlen(address) + 10U; char * endpoint = malloc(length); if (!endpoint) return NULL; snprintf(endpoint, length, "[%s]:%u", address, port); return endpoint; } static uint8_t * networkSendData(struct NetworkSend * send) { if (send->pooled) return ((struct NetworkInputSend *)send)->data; return (uint8_t *)(send + 1); } static void networkConnectionInitSend(struct NetworkSend * send, NetworkConnection * this, bool pooled) { send->connection = this; send->next = NULL; send->componentEpoch = 0; send->applicationSequence = 0; send->accounted = false; send->lossReported = false; send->pooled = pooled; } static uint8_t networkConnectionInputReadyBit( NetworkPacketDelivery delivery) { switch (delivery) { case NETWORK_PACKET_DELIVERY_RELIABLE: return NETWORK_INPUT_SEND_READY_RELIABLE; case NETWORK_PACKET_DELIVERY_DATAGRAM: return NETWORK_INPUT_SEND_READY_DATAGRAM; case NETWORK_PACKET_DELIVERY_BULK: return 0; } return 0; } static bool networkConnectionNextDatagramSequence( NetworkConnection * this, uint64_t * sequence) { if (this->datagramSendSequence == UINT64_MAX) return false; *sequence = ++this->datagramSendSequence; return true; } static bool networkConnectionDatagramAvailable( NetworkConnection * this, uint64_t packetLength) { const uint32_t state = atomic_load_explicit( &this->datagramSendState, memory_order_acquire); return (state & NETWORK_DATAGRAM_SEND_ENABLED) && packetLength <= (uint16_t)state; } static bool networkConnectionPrepareInputDatagram( NetworkConnection * this, uint16_t packetLength) { if (networkConnectionDatagramAvailable(this, packetLength)) { atomic_store_explicit( &this->inputDatagramBlockedLength, 0, memory_order_release); return true; } atomic_store_explicit(&this->inputDatagramBlockedLength, packetLength, memory_order_release); if (!networkConnectionDatagramAvailable(this, packetLength)) return false; atomic_store_explicit( &this->inputDatagramBlockedLength, 0, memory_order_release); return true; } static struct NetworkSend * networkConnectionAllocSend( NetworkConnection * this, size_t dataSize) { struct NetworkSend * send = malloc(sizeof(*send) + dataSize); if (!send) return NULL; networkConnectionInitSend(send, this, false); return send; } static struct NetworkSend * networkConnectionAllocInputSend( NetworkConnection * this, size_t dataSize, NetworkPacketDelivery delivery) { if (dataSize > NETWORK_INPUT_SEND_POOL_DATA_SIZE) return NULL; LG_LOCK(this->inputSendPoolLock); struct NetworkSend * send = NULL; if (delivery == NETWORK_PACKET_DELIVERY_RELIABLE && this->inputReliableSendFree) { send = this->inputReliableSendFree; this->inputReliableSendFree = send->next; } else if (this->inputSendFree) { send = this->inputSendFree; this->inputSendFree = send->next; } if (!send) this->inputSendBlocked |= networkConnectionInputReadyBit(delivery); LG_UNLOCK(this->inputSendPoolLock); if (!send) return NULL; networkConnectionInitSend(send, this, true); send->service = LG_NET_SERVICE_INPUT; send->delivery = delivery; return send; } static void networkConnectionFreeSend(struct NetworkSend * send) { if (!send) return; if (!send->pooled) { free(send); return; } NetworkConnection * this = send->connection; struct NetworkInputSend * inputSend = (struct NetworkInputSend *)send; const uint16_t ready = UINT16_C(1) << LG_NET_SERVICE_INPUT; uint8_t deliveries; LG_LOCK(this->inputSendPoolLock); if (inputSend->reliableReserved) { send->next = this->inputReliableSendFree; this->inputReliableSendFree = send; deliveries = this->inputSendBlocked & NETWORK_INPUT_SEND_READY_RELIABLE; } else { send->next = this->inputSendFree; this->inputSendFree = send; deliveries = this->inputSendBlocked; } this->inputSendBlocked &= (uint8_t)~deliveries; LG_UNLOCK(this->inputSendPoolLock); networkConnectionNotifySendReady(this, (uint16_t)(deliveries & NETWORK_INPUT_SEND_READY_RELIABLE ? ready : 0), (uint16_t)(deliveries & NETWORK_INPUT_SEND_READY_DATAGRAM ? ready : 0), 0); } static bool networkGuestText(char * destination, size_t capacity, const uint8_t * source, uint16_t length) { if (!destination || !capacity || length >= capacity || (length && (!source || memchr(source, 0, length)))) return false; if (length) memcpy(destination, source, length); destination[length] = 0; return true; } static bool networkGuestOS(LGNetCoreGuestOS source, LG_TransportGuestOS * destination) { if (!destination) return false; switch (source) { case LG_NET_CORE_GUEST_OS_LINUX: *destination = LG_TRANSPORT_OS_LINUX; return true; case LG_NET_CORE_GUEST_OS_BSD: *destination = LG_TRANSPORT_OS_BSD; return true; case LG_NET_CORE_GUEST_OS_OSX: *destination = LG_TRANSPORT_OS_OSX; return true; case LG_NET_CORE_GUEST_OS_WINDOWS: *destination = LG_TRANSPORT_OS_WINDOWS; return true; case LG_NET_CORE_GUEST_OS_OTHER: *destination = LG_TRANSPORT_OS_OTHER; return true; } return false; } static bool networkCapabilityOfferValid( const LGNetCapability * capability) { const LGNetServiceVersionInfo * version = capability ? lgNetServiceVersionInfo(capability->service) : NULL; if (!lgNetCapabilityValid(capability) || !version || capability->service <= LG_NET_SERVICE_CORE || capability->service > LG_NET_SERVICE_CONTROL || capability->service == LG_NET_SERVICE_USB || capability->flags & LG_NET_CAPABILITY_SELECTED || capability->versionMin < version->minimum || capability->versionMax > version->maximum || capability->maxBidirectionalStreams != (capability->service == LG_NET_SERVICE_INPUT ? 1U : 0U) || capability->maxUnidirectionalStreams > NETWORK_MAX_PEER_STREAMS || (!!(capability->flags & LG_NET_CAPABILITY_BULK) != !!capability->maxUnidirectionalStreams) || !(capability->flags & (LG_NET_CAPABILITY_RELIABLE | LG_NET_CAPABILITY_DATAGRAM | LG_NET_CAPABILITY_BULK))) return false; return true; } static bool networkConnectionServiceValid(LGNetService service) { return service > LG_NET_SERVICE_CORE && service <= LG_NET_SERVICE_CONTROL && service != LG_NET_SERVICE_USB; } static bool networkBytesNonzero(const uint8_t * data, size_t size) { if (!data) return false; uint8_t value = 0; for (size_t index = 0; index < size; ++index) value |= data[index]; return value != 0; } static void networkConnectionClearAuthProofNL(NetworkConnection * this) { networkAuthSecureZero( this->clientHelloNonce, sizeof(this->clientHelloNonce)); networkAuthSecureZero( this->serverHelloNonce, sizeof(this->serverHelloNonce)); networkAuthSecureZero(&this->authChallenge, sizeof(this->authChallenge)); networkAuthSecureZero(&this->authResponse, sizeof(this->authResponse)); networkAuthSecureZero(this->authKey, sizeof(this->authKey)); this->authChallengeReceived = false; this->authResponseSent = false; this->authResultReceived = false; this->authKeyValid = false; } static void networkConnectionSetState(NetworkConnection * this, bool connected, LG_TransportStatus reason) { LG_LOCK(this->stateCallbackLock); atomic_store_explicit( &this->sessionValid, connected, memory_order_release); if (this->state) this->state(this->opaque, connected, reason); LG_UNLOCK(this->stateCallbackLock); } static void networkConnectionFail(NetworkConnection * this, LG_TransportStatus status) { const bool wasStopping = atomic_exchange_explicit( &this->stopping, true, memory_order_acq_rel); LG_TransportStatus reason; LG_LOCK(this->lock); if (this->connecting && !this->connectFailed) { this->connectStatus = wasStopping ? LG_TRANSPORT_DISCONNECTED : status; this->connectFailed = true; } reason = this->connectStatus == LG_TRANSPORT_OK ? status : this->connectStatus; networkConnectionClearAuthProofNL(this); LG_UNLOCK(this->lock); networkConnectionSetState(this, false, reason); lgSignalEvent(this->connectEvent); } static bool networkConnectionRecordTransportError(NetworkConnection * this, QUIC_STATUS status, QUIC_UINT62 error) { LG_LOCK(this->lock); const bool changed = !this->lastTransportErrorValid || this->lastTransportStatus != status || this->lastTransportError != error; this->lastTransportStatus = status; this->lastTransportError = error; this->lastTransportErrorValid = true; LG_UNLOCK(this->lock); return changed; } static bool networkConnectionRecordPeerError(NetworkConnection * this, QUIC_UINT62 error) { LG_LOCK(this->lock); const bool changed = !this->lastPeerErrorValid || this->lastPeerError != error; this->lastPeerError = error; this->lastPeerErrorValid = true; LG_UNLOCK(this->lock); return changed; } static struct NetworkSendLimits networkConnectionSendLimits( LGNetService service, NetworkPacketDelivery delivery) { if (delivery == NETWORK_PACKET_DELIVERY_DATAGRAM) { switch (service) { case LG_NET_SERVICE_VIDEO: return (struct NetworkSendLimits) { .count = 256U, .bytes = 768U * 1024U, }; case LG_NET_SERVICE_AUDIO: return (struct NetworkSendLimits) { .count = 256U, .bytes = 512U * 1024U, }; case LG_NET_SERVICE_INPUT: return (struct NetworkSendLimits) { .count = 128U, .bytes = 256U * 1024U, }; default: return (struct NetworkSendLimits) { .count = 64U, .bytes = 256U * 1024U, }; } } if (delivery == NETWORK_PACKET_DELIVERY_BULK) { switch (service) { case LG_NET_SERVICE_VIDEO: return (struct NetworkSendLimits) { .count = 128U, .bytes = UINT64_C(192) * 1024U * 1024U, }; case LG_NET_SERVICE_CLIPBOARD: case LG_NET_SERVICE_FILE: case LG_NET_SERVICE_USB: return (struct NetworkSendLimits) { .count = 128U, .bytes = UINT64_C(128) * 1024U * 1024U, }; default: return (struct NetworkSendLimits) { .count = 64U, .bytes = UINT64_C(64) * 1024U * 1024U, }; } } switch (service) { case LG_NET_SERVICE_CURSOR: return (struct NetworkSendLimits) { .count = 8U, .bytes = UINT64_C(32) * 1024U * 1024U, }; case LG_NET_SERVICE_VIDEO: return (struct NetworkSendLimits) { .count = 64U, .bytes = UINT64_C(72) * 1024U * 1024U, }; case LG_NET_SERVICE_AUDIO: return (struct NetworkSendLimits) { .count = 256U, .bytes = UINT64_C(16) * 1024U * 1024U, }; case LG_NET_SERVICE_CLIPBOARD: case LG_NET_SERVICE_FILE: return (struct NetworkSendLimits) { .count = 64U, .bytes = UINT64_C(32) * 1024U * 1024U, }; case LG_NET_SERVICE_CORE: return (struct NetworkSendLimits) { .count = 64U, .bytes = UINT64_C(20) * 1024U * 1024U, }; default: return (struct NetworkSendLimits) { .count = 64U, .bytes = UINT64_C(20) * 1024U * 1024U, }; } } static bool networkConnectionSendPriority(LGNetService service) { return service == LG_NET_SERVICE_CORE || service == LG_NET_SERVICE_RECOVERY || service == LG_NET_SERVICE_CURSOR || service == LG_NET_SERVICE_INPUT || service == LG_NET_SERVICE_CONTROL; } static void networkConnectionResetSendAccountingNL( NetworkConnection * this) { memset(&this->reliableSend, 0, sizeof(this->reliableSend)); memset(&this->datagramSend, 0, sizeof(this->datagramSend)); memset(&this->bulkSend, 0, sizeof(this->bulkSend)); } static struct NetworkSendAccounting * networkConnectionSendAccountingNL( NetworkConnection * this, NetworkPacketDelivery delivery) { switch (delivery) { case NETWORK_PACKET_DELIVERY_RELIABLE: return &this->reliableSend; case NETWORK_PACKET_DELIVERY_DATAGRAM: return &this->datagramSend; case NETWORK_PACKET_DELIVERY_BULK: return &this->bulkSend; } return NULL; } static LG_TransportStatus networkConnectionReserveSendNL( NetworkConnection * this, LGNetService service, NetworkPacketDelivery delivery, uint64_t packetSize) { if (service >= NETWORK_SEND_SERVICE_COUNT) return LG_TRANSPORT_ERROR; const struct NetworkSendLimits limits = networkConnectionSendLimits(service, delivery); uint32_t totalCountLimit; uint64_t totalByteLimit; switch (delivery) { case NETWORK_PACKET_DELIVERY_RELIABLE: totalCountLimit = NETWORK_SEND_RELIABLE_COUNT_MAX; totalByteLimit = NETWORK_SEND_RELIABLE_BYTES_MAX; break; case NETWORK_PACKET_DELIVERY_DATAGRAM: totalCountLimit = NETWORK_SEND_DATAGRAM_COUNT_MAX; totalByteLimit = NETWORK_SEND_DATAGRAM_BYTES_MAX; break; case NETWORK_PACKET_DELIVERY_BULK: totalCountLimit = NETWORK_SEND_BULK_COUNT_MAX; totalByteLimit = NETWORK_SEND_BULK_BYTES_MAX; break; default: return LG_TRANSPORT_ERROR; } if (delivery != NETWORK_PACKET_DELIVERY_BULK && !networkConnectionSendPriority(service)) { totalCountLimit -= delivery == NETWORK_PACKET_DELIVERY_DATAGRAM ? NETWORK_SEND_DATAGRAM_COUNT_RESERVED : NETWORK_SEND_RELIABLE_COUNT_RESERVED; totalByteLimit -= delivery == NETWORK_PACKET_DELIVERY_DATAGRAM ? NETWORK_SEND_DATAGRAM_BYTES_RESERVED : NETWORK_SEND_RELIABLE_BYTES_RESERVED; } struct NetworkSendAccounting * accounting = networkConnectionSendAccountingNL(this, delivery); if (!accounting) return LG_TRANSPORT_ERROR; if (packetSize > limits.bytes || packetSize > totalByteLimit) return LG_TRANSPORT_ERROR; if (accounting->count >= totalCountLimit || accounting->serviceCount[service] >= limits.count || accounting->bytes > totalByteLimit - packetSize || accounting->serviceBytes[service] > limits.bytes - packetSize) { accounting->serviceBlocked[service] = true; accounting->serviceBlockedBytes[service] = accounting->serviceBlockedBytes[service] > packetSize ? accounting->serviceBlockedBytes[service] : packetSize; return LG_TRANSPORT_UNAVAILABLE; } ++accounting->count; ++accounting->serviceCount[service]; accounting->bytes += packetSize; accounting->serviceBytes[service] += packetSize; return LG_TRANSPORT_OK; } static void networkConnectionMarkSendBlockedNL(NetworkConnection * this, LGNetService service, NetworkPacketDelivery delivery, uint64_t packetSize) { struct NetworkSendAccounting * accounting = networkConnectionSendAccountingNL(this, delivery); if (!accounting || service >= NETWORK_SEND_SERVICE_COUNT) return; accounting->serviceBlocked[service] = true; accounting->serviceBlockedBytes[service] = accounting->serviceBlockedBytes[service] > packetSize ? accounting->serviceBlockedBytes[service] : packetSize; } static bool networkConnectionSendCanResumeNL( NetworkConnection * this, LGNetService service, NetworkPacketDelivery delivery) { const struct NetworkSendLimits limits = networkConnectionSendLimits(service, delivery); uint32_t totalCountLimit; uint64_t totalByteLimit; switch (delivery) { case NETWORK_PACKET_DELIVERY_RELIABLE: totalCountLimit = NETWORK_SEND_RELIABLE_COUNT_MAX; totalByteLimit = NETWORK_SEND_RELIABLE_BYTES_MAX; break; case NETWORK_PACKET_DELIVERY_DATAGRAM: totalCountLimit = NETWORK_SEND_DATAGRAM_COUNT_MAX; totalByteLimit = NETWORK_SEND_DATAGRAM_BYTES_MAX; break; case NETWORK_PACKET_DELIVERY_BULK: totalCountLimit = NETWORK_SEND_BULK_COUNT_MAX; totalByteLimit = NETWORK_SEND_BULK_BYTES_MAX; break; default: return false; } if (delivery != NETWORK_PACKET_DELIVERY_BULK && !networkConnectionSendPriority(service)) { totalCountLimit -= delivery == NETWORK_PACKET_DELIVERY_DATAGRAM ? NETWORK_SEND_DATAGRAM_COUNT_RESERVED : NETWORK_SEND_RELIABLE_COUNT_RESERVED; totalByteLimit -= delivery == NETWORK_PACKET_DELIVERY_DATAGRAM ? NETWORK_SEND_DATAGRAM_BYTES_RESERVED : NETWORK_SEND_RELIABLE_BYTES_RESERVED; } const struct NetworkSendAccounting * accounting = networkConnectionSendAccountingNL(this, delivery); if (!accounting) return false; const uint32_t totalCount = accounting->count; const uint64_t totalBytes = accounting->bytes; const uint32_t serviceCount = accounting->serviceCount[service]; const uint64_t serviceBytes = accounting->serviceBytes[service]; const uint64_t blockedBytes = accounting->serviceBlockedBytes[service]; if (atomic_load_explicit(&this->stopping, memory_order_acquire) || !atomic_load_explicit(&this->sessionValid, memory_order_acquire) || (delivery == NETWORK_PACKET_DELIVERY_RELIABLE && (service == LG_NET_SERVICE_INPUT ? (!this->inputStream || !this->inputStreamBound) : !this->controlStream)) || (delivery == NETWORK_PACKET_DELIVERY_DATAGRAM && !networkConnectionDatagramAvailable(this, blockedBytes)) || (delivery == NETWORK_PACKET_DELIVERY_BULK && (!this->outboundBulk[service] || !this->outboundBulk[service]->bound))) return false; const uint32_t totalResumeCount = totalCountLimit * NETWORK_SEND_RESUME_NUMERATOR / NETWORK_SEND_RESUME_DENOMINATOR; const uint64_t totalResumeBytes = totalByteLimit * NETWORK_SEND_RESUME_NUMERATOR / NETWORK_SEND_RESUME_DENOMINATOR; const uint32_t serviceResumeCount = limits.count * NETWORK_SEND_RESUME_NUMERATOR / NETWORK_SEND_RESUME_DENOMINATOR; const uint64_t serviceResumeBytes = limits.bytes * NETWORK_SEND_RESUME_NUMERATOR / NETWORK_SEND_RESUME_DENOMINATOR; return totalCount <= totalResumeCount && totalBytes <= totalResumeBytes && serviceCount <= serviceResumeCount && serviceBytes <= serviceResumeBytes && totalCount < totalCountLimit && serviceCount < limits.count && blockedBytes <= totalByteLimit - totalBytes && blockedBytes <= limits.bytes - serviceBytes; } static uint16_t networkConnectionReadyServicesNL( NetworkConnection * this, NetworkPacketDelivery delivery) { uint16_t ready = 0; struct NetworkSendAccounting * accounting = networkConnectionSendAccountingNL(this, delivery); if (!accounting) return 0; for (LGNetService service = LG_NET_SERVICE_CORE; service < NETWORK_SEND_SERVICE_COUNT; ++service) { if (!accounting->serviceBlocked[service] || !networkConnectionSendCanResumeNL(this, service, delivery)) continue; accounting->serviceBlocked[service] = false; accounting->serviceBlockedBytes[service] = 0; ready |= UINT16_C(1) << service; } return ready; } static uint16_t networkConnectionReleaseSendNL( NetworkConnection * this, struct NetworkSend * send) { if (!send->accounted) return 0; send->accounted = false; if (send->service >= NETWORK_SEND_SERVICE_COUNT) { DEBUG_ERROR("Network send accounting service out of range"); return 0; } struct NetworkSendAccounting * accounting = networkConnectionSendAccountingNL(this, send->delivery); if (!accounting) { DEBUG_ERROR("Network send accounting delivery out of range"); return 0; } if (!accounting->count || !accounting->serviceCount[send->service] || accounting->bytes < send->length || accounting->serviceBytes[send->service] < send->length) { DEBUG_ERROR("Network send accounting underflow"); networkConnectionResetSendAccountingNL(this); return 0; } --accounting->count; --accounting->serviceCount[send->service]; accounting->bytes -= send->length; accounting->serviceBytes[send->service] -= send->length; return networkConnectionReadyServicesNL(this, send->delivery); } static void networkConnectionNotifySendReady(NetworkConnection * this, uint16_t reliable, uint16_t datagram, uint16_t bulk) { if (!this->sendReady) return; for (LGNetService service = LG_NET_SERVICE_CORE; service < NETWORK_SEND_SERVICE_COUNT; ++service) { const uint16_t serviceMask = UINT16_C(1) << service; if (reliable & serviceMask) this->sendReady(this->opaque, service, NETWORK_PACKET_DELIVERY_RELIABLE); if (datagram & serviceMask) this->sendReady(this->opaque, service, NETWORK_PACKET_DELIVERY_DATAGRAM); if (bulk & serviceMask) this->sendReady(this->opaque, service, NETWORK_PACKET_DELIVERY_BULK); } } 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 = networkConnectionAllocInputSend( this, packetSize, NETWORK_PACKET_DELIVERY_RELIABLE); if (!send) return LG_TRANSPORT_UNAVAILABLE; 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); HQUIC stream = this->inputStream; const bool sessionValid = atomic_load_explicit( &this->sessionValid, memory_order_acquire); if (!stream || !sessionValid) { LG_UNLOCK(this->inputStreamLock); networkConnectionFreeSend(send); return LG_TRANSPORT_DISCONNECTED; } 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 (!serviceReady || payloadLength > this->serviceMaxPayload[LG_NET_SERVICE_INPUT]) { LG_UNLOCK(this->inputStreamLock); networkConnectionFreeSend(send); return LG_TRANSPORT_UNAVAILABLE; } if (this->inputSendSequence == UINT64_MAX) { LG_UNLOCK(this->inputStreamLock); networkConnectionFreeSend(send); return LG_TRANSPORT_ERROR; } 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 = false; size_t wireSize; if (!lgNetPacketConstruct(data, packetSize, &wireSize, ¶meters, payload, payloadLength) || wireSize != packetSize) { --this->inputSendSequence; LG_UNLOCK(this->inputStreamLock); networkConnectionFreeSend(send); 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); if (QUIC_FAILED(status)) { --this->inputSendSequence; } LG_UNLOCK(this->inputStreamLock); if (QUIC_FAILED(status)) { networkConnectionFreeSend(send); return LG_TRANSPORT_ERROR; } return LG_TRANSPORT_OK; } static LG_TransportStatus networkConnectionSendPacket( 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 allowConnecting) { LGNetEnvelope routeEnvelope; lgNetEnvelopeInit(&routeEnvelope, service, messageType, serviceVersion, LG_NET_MESSAGE_VERSION_CURRENT); if (payloadLength > NETWORK_MAX_CONTROL_PAYLOAD || (payloadLength && !payload) || (service != LG_NET_SERVICE_CORE && !networkConnectionServiceValid(service)) || (flags & (LG_NET_ENVELOPE_DATAGRAM | LG_NET_ENVELOPE_EARLY_DATA)) || !lgNetEnvelopeRouteValid(&routeEnvelope, LG_NET_ROLE_CLIENT, LG_NET_ROUTE_DELIVERY_RELIABLE) || atomic_load_explicit(&this->stopping, memory_order_acquire)) return 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; LGNetPacketParameters parameters; lgNetPacketParametersInit(¶meters, service, messageType, serviceVersion, LG_NET_MESSAGE_VERSION_CURRENT); parameters.flags = flags | LG_NET_ENVELOPE_RELIABLE; parameters.componentEpoch = componentEpoch; parameters.requestID = requestID; struct NetworkSend * send = networkConnectionAllocSend( this, packetSize); if (!send) return LG_TRANSPORT_ERROR; uint8_t * data = networkSendData(send); LG_LOCK(this->lock); HQUIC stream = this->controlStream; const bool serviceReady = allowConnecting || service == LG_NET_SERVICE_CORE || ((this->receivedServices & (UINT16_C(1) << service)) && (this->reliableServices & (UINT16_C(1) << service)) && this->serviceVersions[service] == serviceVersion); const uint32_t maxPayloadLength = allowConnecting ? NETWORK_MAX_CONTROL_PAYLOAD : service == LG_NET_SERVICE_CORE ? this->maxPayloadLength : this->serviceMaxPayload[service]; if (!stream || (!allowConnecting && !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; } const LG_TransportStatus reserveStatus = networkConnectionReserveSendNL(this, service, NETWORK_PACKET_DELIVERY_RELIABLE, packetSize); if (reserveStatus != LG_TRANSPORT_OK) { LG_UNLOCK(this->lock); networkConnectionFreeSend(send); return reserveStatus; } parameters.sessionEpoch = this->sessionEpoch; parameters.sequence = ++this->sendSequence; send->service = service; send->length = (uint32_t)packetSize; send->delivery = NETWORK_PACKET_DELIVERY_RELIABLE; send->accounted = true; size_t wireSize; if (!lgNetPacketConstruct(data, packetSize, &wireSize, ¶meters, payload, payloadLength) || wireSize != packetSize) { --this->sendSequence; const uint16_t ready = networkConnectionReleaseSendNL(this, send); LG_UNLOCK(this->lock); 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_NONE, send); uint16_t ready = 0; if (QUIC_FAILED(status)) { --this->sendSequence; ready = networkConnectionReleaseSendNL(this, send); } LG_UNLOCK(this->lock); if (QUIC_FAILED(status)) { networkConnectionFreeSend(send); networkConnectionNotifySendReady(this, ready, 0, 0); return LG_TRANSPORT_ERROR; } return LG_TRANSPORT_OK; } static bool networkConnectionSendHello(NetworkConnection * this) { LGNetHello hello; lgNetHelloInit(&hello, LG_NET_ROLE_CLIENT); hello.maxPayloadLength = NETWORK_MAX_NEGOTIATED_PAYLOAD; hello.maxDatagramPayload = NETWORK_MAX_DATAGRAM_PAYLOAD; hello.capabilityCount = this->offeredCapabilityCount; hello.authModes = this->password && *this->password ? LG_NET_AUTH_MODE_FLAG(LG_NET_AUTH_PASSWORD) : LG_NET_AUTH_MODE_FLAG(LG_NET_AUTH_NONE); uint8_t payload[LG_NET_HELLO_WIRE_SIZE]; bool result = false; if (!networkAuthRandomBytes(hello.nonce, sizeof(hello.nonce)) || !networkBytesNonzero(hello.nonce, sizeof(hello.nonce)) || !lgNetHelloEncode(payload, sizeof(payload), &hello)) goto cleanup; LG_LOCK(this->lock); memcpy(this->clientHelloNonce, hello.nonce, sizeof(hello.nonce)); LG_UNLOCK(this->lock); if (networkConnectionSendPacket(this, LG_NET_SERVICE_CORE, LG_NET_CORE_MESSAGE_HELLO, LG_NET_CORE_VERSION_CURRENT, LG_NET_ENVELOPE_FINAL, 0, NETWORK_HELLO_REQUEST_ID, payload, sizeof(payload), true) != LG_TRANSPORT_OK) goto cleanup; for (LGNetService service = LG_NET_SERVICE_RECOVERY; service <= LG_NET_SERVICE_CONTROL; ++service) { if (service == LG_NET_SERVICE_USB) continue; if (!(this->offeredServices & (UINT16_C(1) << service))) continue; uint8_t capabilityPayload[LG_NET_CAPABILITY_WIRE_SIZE]; if (!lgNetCapabilityEncode(capabilityPayload, sizeof(capabilityPayload), &this->offeredCapabilities[service]) || networkConnectionSendPacket(this, LG_NET_SERVICE_CORE, LG_NET_CORE_MESSAGE_CAPABILITY, LG_NET_CORE_VERSION_CURRENT, LG_NET_ENVELOPE_FINAL, 0, NETWORK_HELLO_REQUEST_ID, capabilityPayload, sizeof(capabilityPayload), true) != LG_TRANSPORT_OK) goto cleanup; } result = networkConnectionSendPacket(this, LG_NET_SERVICE_CORE, LG_NET_CORE_MESSAGE_CAPABILITIES_DONE, LG_NET_CORE_VERSION_CURRENT, LG_NET_ENVELOPE_FINAL, 0, NETWORK_HELLO_REQUEST_ID, NULL, 0, true) == LG_TRANSPORT_OK; cleanup: networkAuthSecureZero(payload, sizeof(payload)); networkAuthSecureZero(&hello, sizeof(hello)); if (!result) { LG_LOCK(this->lock); networkConnectionClearAuthProofNL(this); LG_UNLOCK(this->lock); } return result; } static void networkConnectionReady(NetworkConnection * this) { LG_LOCK(this->stateCallbackLock); LG_LOCK(this->lock); if (!this->connecting || this->connectStatus == LG_TRANSPORT_OK || atomic_load_explicit(&this->stopping, memory_order_acquire)) { LG_UNLOCK(this->lock); LG_UNLOCK(this->stateCallbackLock); return; } this->connectStatus = LG_TRANSPORT_OK; this->lastTransportErrorValid = false; this->lastPeerErrorValid = false; atomic_store_explicit(&this->sessionValid, true, memory_order_release); LG_UNLOCK(this->lock); if (this->state) this->state(this->opaque, true, LG_TRANSPORT_OK); LG_UNLOCK(this->stateCallbackLock); lgSignalEvent(this->connectEvent); } static bool networkConnectionHandleCapability(NetworkConnection * this, const LGNetPacketView * packet) { LGNetCapability capability; if (lgNetCapabilityDecode(&capability, packet->payload, packet->payloadLength) != LG_NET_PARSE_OK) return false; if (!networkConnectionServiceValid(capability.service)) return false; LG_LOCK(this->lock); const uint16_t serviceMask = UINT16_C(1) << capability.service; const LGNetCapability offered = this->offeredCapabilities[capability.service]; const LGNetCapabilityFlags selectedFlags = capability.flags & (LG_NET_CAPABILITY_RELIABLE | LG_NET_CAPABILITY_DATAGRAM | LG_NET_CAPABILITY_BULK | LG_NET_CAPABILITY_MULTI_CLIENT); const LGNetCapabilityFlags offeredFlags = offered.flags & (LG_NET_CAPABILITY_RELIABLE | LG_NET_CAPABILITY_DATAGRAM | LG_NET_CAPABILITY_BULK | LG_NET_CAPABILITY_MULTI_CLIENT); if (!this->helloAcknowledged || this->capabilitiesComplete || this->receivedCapabilities >= this->expectedCapabilities || !(this->offeredServices & serviceMask) || !(capability.flags & LG_NET_CAPABILITY_SELECTED) || capability.flags & LG_NET_CAPABILITY_REQUIRED || !(selectedFlags & LG_NET_CAPABILITY_RELIABLE) || selectedFlags & ~offeredFlags || capability.features & ~offered.features || capability.versionMin < offered.versionMin || capability.versionMin > offered.versionMax || capability.maxPayloadLength > offered.maxPayloadLength || capability.maxPayloadLength > this->maxPayloadLength || capability.maxDatagramPayload > offered.maxDatagramPayload || capability.maxDatagramPayload > this->maxDatagramPayload || (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.service == LG_NET_SERVICE_INPUT ? 1U : 0U) || (!!(selectedFlags & LG_NET_CAPABILITY_BULK) != !!capability.maxUnidirectionalStreams) || capability.maxUnidirectionalStreams > NETWORK_MAX_PEER_STREAMS - this->maxPeerBulkStreams || (this->receivedServices & serviceMask)) { LG_UNLOCK(this->lock); return false; } const LGNetService service = capability.service; this->receivedServices |= serviceMask; if (capability.flags & LG_NET_CAPABILITY_RELIABLE) this->reliableServices |= UINT16_C(1) << service; if (capability.flags & LG_NET_CAPABILITY_DATAGRAM) this->datagramServices |= UINT16_C(1) << service; if (capability.flags & LG_NET_CAPABILITY_BULK) { this->bulkServices |= UINT16_C(1) << service; this->maxPeerBulkStreams += capability.maxUnidirectionalStreams; } this->serviceVersions[service] = capability.versionMin; this->serviceMaxBidirectional[service] = capability.maxBidirectionalStreams; this->serviceMaxUnidirectional[service] = capability.maxUnidirectionalStreams; this->serviceMaxPayload[service] = capability.maxPayloadLength; this->serviceMaxDatagram[service] = capability.maxDatagramPayload; ++this->receivedCapabilities; LG_UNLOCK(this->lock); return true; } static bool networkConnectionRequiredCapabilitiesSelected( const NetworkConnection * this) { for (LGNetService service = LG_NET_SERVICE_RECOVERY; service <= LG_NET_SERVICE_CONTROL; ++service) { if (service == LG_NET_SERVICE_USB) continue; const uint16_t serviceMask = UINT16_C(1) << service; if ((this->offeredServices & serviceMask) && (this->offeredCapabilities[service].flags & LG_NET_CAPABILITY_REQUIRED) && !(this->receivedServices & serviceMask)) return false; } return true; } static 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) && (!(this->receivedServices & inputMask) || this->inputStreamBound); } static void networkConnectionSetSessionFeatures(NetworkConnection * this) { const uint16_t services = this->receivedServices & this->reliableServices; if (services & (UINT16_C(1) << LG_NET_SERVICE_INPUT)) this->session.features |= LG_TRANSPORT_FEATURE_INPUT; if (services & (UINT16_C(1) << LG_NET_SERVICE_CLIPBOARD)) this->session.features |= LG_TRANSPORT_FEATURE_CLIPBOARD; if (services & (UINT16_C(1) << LG_NET_SERVICE_AUDIO)) this->session.features |= LG_TRANSPORT_FEATURE_AUDIO; if (services & (UINT16_C(1) << LG_NET_SERVICE_CONTROL)) this->session.features |= LG_TRANSPORT_FEATURE_SET_CURSOR_POS | LG_TRANSPORT_FEATURE_WINDOW_SIZE; } static bool networkConnectionHandleAuthChallenge(NetworkConnection * this, const LGNetPacketView * packet) { const LGNetEnvelopeFlags responseFlags = LG_NET_ENVELOPE_RESPONSE | LG_NET_ENVELOPE_FINAL | LG_NET_ENVELOPE_RELIABLE; LGNetPasswordChallenge challenge; if (packet->envelope.flags != responseFlags || packet->envelope.requestID != NETWORK_HELLO_REQUEST_ID || packet->envelope.componentEpoch != 0 || packet->envelope.serviceVersion != LG_NET_CORE_VERSION_CURRENT || lgNetPasswordChallengeDecode(&challenge, packet->payload, packet->payloadLength) != LG_NET_PARSE_OK || challenge.algorithm != LG_NET_PASSWORD_PBKDF2_HMAC_SHA256 || challenge.rounds < NETWORK_AUTH_PBKDF2_ROUNDS_MIN || challenge.rounds > NETWORK_AUTH_PBKDF2_ROUNDS_MAX || challenge.saltLength < NETWORK_AUTH_SALT_LENGTH_MIN || challenge.challengeLength < NETWORK_AUTH_CHALLENGE_LENGTH_MIN) return false; uint16_t protocolVersion; uint64_t sessionEpoch; uint8_t clientHelloNonce[LG_NET_HELLO_NONCE_LENGTH]; uint8_t serverHelloNonce[LG_NET_HELLO_NONCE_LENGTH]; LG_LOCK(this->lock); const bool validState = this->password && *this->password && this->helloAcknowledged && this->authMode == LG_NET_AUTH_PASSWORD && !this->authenticationComplete && !this->authChallengeReceived && !this->authResponseSent && packet->envelope.sessionEpoch == this->sessionEpoch && !atomic_load_explicit(&this->stopping, memory_order_acquire); if (validState) { this->authChallengeReceived = true; protocolVersion = this->protocolVersion; sessionEpoch = this->sessionEpoch; memcpy(clientHelloNonce, this->clientHelloNonce, sizeof(clientHelloNonce)); memcpy(serverHelloNonce, this->serverHelloNonce, sizeof(serverHelloNonce)); } LG_UNLOCK(this->lock); if (!validState) return false; LGNetPasswordResponse response; uint8_t key[LG_NET_PASSWORD_SHA256_LENGTH] = { 0 }; uint8_t transcript [LG_NET_PASSWORD_CLIENT_PROOF_TRANSCRIPT_MAX_SIZE] = { 0 }; uint8_t payload[LG_NET_PASSWORD_RESPONSE_HEADER_SIZE + LG_NET_PASSWORD_NONCE_MAX_LENGTH + LG_NET_PASSWORD_PROOF_MAX_LENGTH] = { 0 }; memset(&response, 0, sizeof(response)); response.algorithm = LG_NET_PASSWORD_PBKDF2_HMAC_SHA256; response.nonceLength = NETWORK_AUTH_RESPONSE_NONCE_LENGTH; response.proofLength = LG_NET_PASSWORD_SHA256_LENGTH; LGNetPasswordProofContext context = { .protocolVersion = protocolVersion, .sessionEpoch = sessionEpoch, .helloNonce = clientHelloNonce, .helloAckNonce = serverHelloNonce, .challenge = &challenge, .response = &response, }; size_t transcriptSize = 0; size_t payloadSize = 0; bool stored = false; bool sent = false; if (!networkAuthRandomBytes(response.nonce, response.nonceLength) || !networkBytesNonzero(response.nonce, response.nonceLength) || !networkAuthPBKDF2HMACSHA256(this->password, strlen(this->password), challenge.salt, challenge.saltLength, challenge.rounds, key, sizeof(key)) || !lgNetPasswordClientProofTranscriptSize(&context, &transcriptSize) || transcriptSize > sizeof(transcript) || !lgNetPasswordClientProofTranscriptEncode( transcript, transcriptSize, &context) || !networkAuthHMACSHA256(key, sizeof(key), transcript, transcriptSize, response.proof)) goto cleanup; payloadSize = lgNetPasswordResponseSize(&response); if (!payloadSize || payloadSize > sizeof(payload) || !lgNetPasswordResponseEncode(payload, payloadSize, &response)) goto cleanup; LG_LOCK(this->lock); stored = this->authChallengeReceived && !this->authResponseSent && !this->authenticationComplete && packet->envelope.sessionEpoch == this->sessionEpoch && !atomic_load_explicit(&this->stopping, memory_order_acquire); if (stored) { this->authChallenge = challenge; this->authResponse = response; memcpy(this->authKey, key, sizeof(key)); this->authResponseSent = true; this->authKeyValid = true; } LG_UNLOCK(this->lock); if (!stored) goto cleanup; sent = networkConnectionSendPacket(this, LG_NET_SERVICE_CORE, LG_NET_CORE_MESSAGE_AUTH_RESPONSE, LG_NET_CORE_VERSION_CURRENT, LG_NET_ENVELOPE_FINAL, 0, NETWORK_HELLO_REQUEST_ID, payload, (uint32_t)payloadSize, true) == LG_TRANSPORT_OK; cleanup: if (stored && !sent) { LG_LOCK(this->lock); networkConnectionClearAuthProofNL(this); LG_UNLOCK(this->lock); } networkAuthSecureZero(payload, sizeof(payload)); networkAuthSecureZero(transcript, sizeof(transcript)); networkAuthSecureZero(key, sizeof(key)); networkAuthSecureZero(&response, sizeof(response)); networkAuthSecureZero(&challenge, sizeof(challenge)); networkAuthSecureZero(clientHelloNonce, sizeof(clientHelloNonce)); networkAuthSecureZero(serverHelloNonce, sizeof(serverHelloNonce)); return sent; } static bool networkConnectionHandleAuthResult(NetworkConnection * this, const LGNetPacketView * packet) { const LGNetEnvelopeFlags responseFlags = LG_NET_ENVELOPE_RESPONSE | LG_NET_ENVELOPE_FINAL | LG_NET_ENVELOPE_RELIABLE; LGNetAuthResult result; if (packet->envelope.flags != responseFlags || packet->envelope.requestID != NETWORK_HELLO_REQUEST_ID || packet->envelope.componentEpoch != 0 || packet->envelope.serviceVersion != LG_NET_CORE_VERSION_CURRENT || lgNetAuthResultDecode(&result, packet->payload, packet->payloadLength) != LG_NET_PARSE_OK) return false; if (result.result != LG_NET_AUTH_RESULT_ACCEPTED) { DEBUG_ERROR("Network password authentication was rejected " "(result %u, retry after %u ms)", (unsigned)result.result, (unsigned)result.retryAfterMs); networkAuthSecureZero(&result, sizeof(result)); return false; } if (result.retryAfterMs || result.proofLength != LG_NET_PASSWORD_SHA256_LENGTH) { networkAuthSecureZero(&result, sizeof(result)); return false; } uint16_t protocolVersion; uint64_t sessionEpoch; uint8_t clientHelloNonce[LG_NET_HELLO_NONCE_LENGTH]; uint8_t serverHelloNonce[LG_NET_HELLO_NONCE_LENGTH]; LGNetPasswordChallenge challenge; LGNetPasswordResponse response; uint8_t key[LG_NET_PASSWORD_SHA256_LENGTH]; LG_LOCK(this->lock); const bool validState = this->helloAcknowledged && this->authMode == LG_NET_AUTH_PASSWORD && !this->authenticationComplete && this->authChallengeReceived && this->authResponseSent && !this->authResultReceived && this->authKeyValid && packet->envelope.sessionEpoch == this->sessionEpoch && !atomic_load_explicit(&this->stopping, memory_order_acquire); if (validState) { this->authResultReceived = true; protocolVersion = this->protocolVersion; sessionEpoch = this->sessionEpoch; memcpy(clientHelloNonce, this->clientHelloNonce, sizeof(clientHelloNonce)); memcpy(serverHelloNonce, this->serverHelloNonce, sizeof(serverHelloNonce)); challenge = this->authChallenge; response = this->authResponse; memcpy(key, this->authKey, sizeof(key)); } LG_UNLOCK(this->lock); if (!validState) { networkAuthSecureZero(&result, sizeof(result)); return false; } LGNetPasswordProofContext context = { .protocolVersion = protocolVersion, .sessionEpoch = sessionEpoch, .helloNonce = clientHelloNonce, .helloAckNonce = serverHelloNonce, .challenge = &challenge, .response = &response, }; uint8_t transcript [LG_NET_PASSWORD_SERVER_PROOF_TRANSCRIPT_MAX_SIZE] = { 0 }; uint8_t expectedProof[LG_NET_PASSWORD_SHA256_LENGTH] = { 0 }; size_t transcriptSize = 0; bool verified = false; if (lgNetPasswordServerProofTranscriptSize(&context, &transcriptSize) && transcriptSize <= sizeof(transcript) && lgNetPasswordServerProofTranscriptEncode( transcript, transcriptSize, &context) && networkAuthHMACSHA256(key, sizeof(key), transcript, transcriptSize, expectedProof)) verified = networkAuthConstantTimeEqual( expectedProof, result.proof, sizeof(expectedProof)); bool ready = false; bool stateValid = false; LG_LOCK(this->lock); stateValid = this->authResultReceived && !this->authenticationComplete && this->authResponseSent && this->authKeyValid && packet->envelope.sessionEpoch == this->sessionEpoch && !atomic_load_explicit(&this->stopping, memory_order_acquire); if (verified && stateValid) { this->authenticationComplete = true; ready = networkConnectionReadyToActivateNL(this); if (ready) networkConnectionSetSessionFeatures(this); } networkConnectionClearAuthProofNL(this); LG_UNLOCK(this->lock); if (!verified) DEBUG_ERROR("Network server password proof validation failed"); if (ready) networkConnectionReady(this); networkAuthSecureZero(expectedProof, sizeof(expectedProof)); networkAuthSecureZero(transcript, sizeof(transcript)); networkAuthSecureZero(key, sizeof(key)); networkAuthSecureZero(&response, sizeof(response)); networkAuthSecureZero(&challenge, sizeof(challenge)); networkAuthSecureZero(clientHelloNonce, sizeof(clientHelloNonce)); networkAuthSecureZero(serverHelloNonce, sizeof(serverHelloNonce)); networkAuthSecureZero(&result, sizeof(result)); return verified && stateValid; } static bool networkConnectionHandleCore(NetworkConnection * this, const LGNetPacketView * packet) { const LGNetEnvelopeFlags responseFlags = LG_NET_ENVELOPE_RESPONSE | LG_NET_ENVELOPE_FINAL | LG_NET_ENVELOPE_RELIABLE; uint64_t sessionEpoch; LG_LOCK(this->lock); sessionEpoch = this->sessionEpoch; LG_UNLOCK(this->lock); switch (packet->envelope.messageType) { case LG_NET_CORE_MESSAGE_HELLO_ACK: { LGNetHelloAck acknowledgement; const LGNetAuthMode expectedAuthMode = this->password && *this->password ? LG_NET_AUTH_PASSWORD : LG_NET_AUTH_NONE; if (packet->envelope.flags != responseFlags || packet->envelope.requestID != NETWORK_HELLO_REQUEST_ID || packet->envelope.componentEpoch != 0 || packet->envelope.serviceVersion != LG_NET_CORE_VERSION_CURRENT || lgNetHelloAckDecode(&acknowledgement, packet->payload, packet->payloadLength) != LG_NET_PARSE_OK || acknowledgement.protocolVersion != LG_NET_PROTOCOL_VERSION_CURRENT || acknowledgement.authMode != expectedAuthMode || acknowledgement.flags || acknowledgement.capabilityCount > this->offeredCapabilityCount || acknowledgement.maxPayloadLength > NETWORK_MAX_NEGOTIATED_PAYLOAD || acknowledgement.maxDatagramPayload > NETWORK_MAX_DATAGRAM_PAYLOAD || !networkBytesNonzero( acknowledgement.nonce, sizeof(acknowledgement.nonce)) || packet->envelope.sessionEpoch != acknowledgement.sessionEpoch) return false; LG_LOCK(this->lock); if (this->helloAcknowledged || !networkBytesNonzero( this->clientHelloNonce, sizeof(this->clientHelloNonce))) { LG_UNLOCK(this->lock); return false; } this->helloAcknowledged = true; this->authenticationComplete = acknowledgement.authMode == LG_NET_AUTH_NONE; this->expectedCapabilities = acknowledgement.capabilityCount; this->protocolVersion = acknowledgement.protocolVersion; this->authMode = acknowledgement.authMode; this->sessionEpoch = acknowledgement.sessionEpoch; this->maxPayloadLength = acknowledgement.maxPayloadLength; this->maxDatagramPayload = acknowledgement.maxDatagramPayload; memcpy(this->serverHelloNonce, acknowledgement.nonce, sizeof(this->serverHelloNonce)); const bool sequenceReady = lgNetSequenceTrackerInit( &this->datagramReceive, acknowledgement.sessionEpoch, NETWORK_SEQUENCE_CONNECTION_EPOCH, 64, false); if (this->authenticationComplete) networkConnectionClearAuthProofNL(this); LG_UNLOCK(this->lock); return sequenceReady; } case LG_NET_CORE_MESSAGE_CAPABILITY: if (packet->envelope.flags != responseFlags || packet->envelope.requestID != NETWORK_HELLO_REQUEST_ID || packet->envelope.componentEpoch != 0 || packet->envelope.sessionEpoch != sessionEpoch || packet->envelope.serviceVersion != LG_NET_CORE_VERSION_CURRENT) return false; return networkConnectionHandleCapability(this, packet); case LG_NET_CORE_MESSAGE_GUEST_INFO: { LGNetCoreGuestInfo info; if (packet->envelope.flags != responseFlags || packet->envelope.requestID != NETWORK_HELLO_REQUEST_ID || packet->envelope.componentEpoch != 0 || packet->envelope.sessionEpoch != sessionEpoch || packet->envelope.serviceVersion != LG_NET_CORE_VERSION_CURRENT || lgNetCoreGuestInfoDecode(&info, packet->payload, packet->payloadLength) != LG_NET_PARSE_OK) return false; LG_TransportSession guest = { 0 }; if (!networkGuestOS(info.os, &guest.os) || !networkGuestText(guest.version, sizeof(guest.version), info.version, info.versionLength) || !networkGuestText(guest.osName, sizeof(guest.osName), info.osName, info.osNameLength) || !networkGuestText(guest.capture, sizeof(guest.capture), info.capture, info.captureLength) || !networkGuestText(guest.cpuModel, sizeof(guest.cpuModel), info.cpuModel, info.cpuModelLength)) return false; memcpy(guest.uuid, info.uuid, sizeof(guest.uuid)); guest.uuidValid = networkBytesNonzero(guest.uuid, sizeof(guest.uuid)); guest.cpus = info.processors; guest.cores = info.cores; guest.sockets = info.sockets; LG_LOCK(this->lock); const bool valid = this->helloAcknowledged && this->authenticationComplete && !this->capabilitiesComplete && !this->guestInfoReceived && this->receivedCapabilities == this->expectedCapabilities && networkConnectionRequiredCapabilitiesSelected(this); if (valid) { memcpy(this->session.version, guest.version, sizeof(this->session.version)); memcpy(this->session.uuid, guest.uuid, sizeof(this->session.uuid)); memcpy(this->session.osName, guest.osName, sizeof(this->session.osName)); memcpy(this->session.capture, guest.capture, sizeof(this->session.capture)); memcpy(this->session.cpuModel, guest.cpuModel, sizeof(this->session.cpuModel)); this->session.uuidValid = guest.uuidValid; this->session.os = guest.os; this->session.cpus = guest.cpus; this->session.cores = guest.cores; this->session.sockets = guest.sockets; this->guestInfoReceived = true; } LG_UNLOCK(this->lock); return valid; } case LG_NET_CORE_MESSAGE_CAPABILITIES_DONE: { if (packet->envelope.flags != responseFlags || packet->envelope.requestID != NETWORK_HELLO_REQUEST_ID || packet->envelope.componentEpoch != 0 || packet->envelope.sessionEpoch != sessionEpoch || packet->envelope.serviceVersion != LG_NET_CORE_VERSION_CURRENT || packet->payloadLength) return false; bool ready; bool openInput; LG_LOCK(this->lock); const bool valid = !this->capabilitiesComplete && this->helloAcknowledged && this->guestInfoReceived && 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; } case LG_NET_CORE_MESSAGE_AUTH_CHALLENGE: return networkConnectionHandleAuthChallenge(this, packet); case LG_NET_CORE_MESSAGE_AUTH_RESULT: return networkConnectionHandleAuthResult(this, packet); default: return false; } } static bool networkConnectionHandlePacket(NetworkConnection * this, const LGNetPacketView * packet, NetworkPacketDelivery delivery) { if (packet->envelope.service == LG_NET_SERVICE_CORE) return delivery == NETWORK_PACKET_DELIVERY_RELIABLE && networkConnectionHandleCore(this, packet); if (!networkConnectionServiceValid(packet->envelope.service) || !atomic_load_explicit(&this->sessionValid, memory_order_acquire)) return false; LG_LOCK(this->lock); const uint16_t serviceMask = UINT16_C(1) << packet->envelope.service; uint16_t deliveryServices; switch (delivery) { case NETWORK_PACKET_DELIVERY_RELIABLE: deliveryServices = this->reliableServices; break; case NETWORK_PACKET_DELIVERY_DATAGRAM: deliveryServices = this->datagramServices; break; case NETWORK_PACKET_DELIVERY_BULK: deliveryServices = this->bulkServices; break; default: LG_UNLOCK(this->lock); return false; } const bool sessionValid = packet->envelope.sessionEpoch == this->sessionEpoch && (this->receivedServices & serviceMask) && (deliveryServices & serviceMask) && packet->envelope.serviceVersion == this->serviceVersions[packet->envelope.service] && packet->payloadLength <= this->serviceMaxPayload[packet->envelope.service]; LG_UNLOCK(this->lock); if (!sessionValid) return false; if (this->packet) this->packet(this->opaque, packet, delivery); return true; } static bool networkConnectionValidateControl(NetworkConnection * this, const LGNetPacketView * packet) { uint32_t maxPayloadLength; LG_LOCK(this->lock); maxPayloadLength = this->maxPayloadLength; LG_UNLOCK(this->lock); const bool intermediate = (packet->envelope.service == LG_NET_SERVICE_RECOVERY && packet->envelope.messageType == LG_NET_RECOVERY_MESSAGE_STATUS) || (packet->envelope.service == LG_NET_SERVICE_CONTROL && packet->envelope.messageType == LG_NET_CONTROL_MESSAGE_STATUS); return packet->envelope.headerSize == LG_NET_ENVELOPE_WIRE_SIZE && packet->payloadLength <= NETWORK_MAX_CONTROL_PAYLOAD && packet->payloadLength <= maxPayloadLength && packet->envelope.messageVersion == LG_NET_MESSAGE_VERSION_CURRENT && (packet->envelope.flags & LG_NET_ENVELOPE_RELIABLE) && (intermediate || (packet->envelope.flags & LG_NET_ENVELOPE_FINAL)) && !(packet->envelope.flags & (LG_NET_ENVELOPE_DATAGRAM | LG_NET_ENVELOPE_EARLY_DATA)) && lgNetEnvelopeRouteValid(&packet->envelope, LG_NET_ROLE_SERVER, LG_NET_ROUTE_DELIVERY_RELIABLE); } static bool networkConnectionDecodeDatagram(NetworkConnection * this, const QUIC_BUFFER * buffer, LGNetPacketView * packet) { if (!buffer || !buffer->Buffer || !packet || lgNetPacketDecode(packet, buffer->Buffer, buffer->Length) != LG_NET_PARSE_OK || packet->wireSize != buffer->Length || packet->envelope.service == LG_NET_SERVICE_CORE || !lgNetEnvelopeRouteValid(&packet->envelope, LG_NET_ROLE_SERVER, LG_NET_ROUTE_DELIVERY_DATAGRAM)) return false; if (!atomic_load_explicit( &this->sessionValid, memory_order_acquire) || (packet->envelope.service != LG_NET_SERVICE_CORE && !networkConnectionServiceValid(packet->envelope.service)) || packet->envelope.headerSize != LG_NET_ENVELOPE_WIRE_SIZE || !packet->envelope.sequence || packet->envelope.messageVersion != LG_NET_MESSAGE_VERSION_CURRENT || (packet->envelope.flags & (LG_NET_ENVELOPE_DATAGRAM | LG_NET_ENVELOPE_FINAL)) != (LG_NET_ENVELOPE_DATAGRAM | LG_NET_ENVELOPE_FINAL) || (packet->envelope.flags & (LG_NET_ENVELOPE_RELIABLE | LG_NET_ENVELOPE_EARLY_DATA))) return false; LG_LOCK(this->lock); const uint16_t serviceMask = UINT16_C(1) << packet->envelope.service; const bool valid = packet->envelope.sessionEpoch == this->sessionEpoch && (this->receivedServices & serviceMask) && (this->datagramServices & serviceMask) && packet->envelope.serviceVersion == this->serviceVersions[packet->envelope.service] && this->serviceMaxDatagram[packet->envelope.service] && packet->payloadLength <= this->serviceMaxDatagram[packet->envelope.service] && lgNetSequenceAccepted(lgNetSequenceTrackerCheck( &this->datagramReceive, packet->envelope.sessionEpoch, NETWORK_SEQUENCE_CONNECTION_EPOCH, packet->envelope.sequence)); LG_UNLOCK(this->lock); return valid; } static bool networkConnectionDrainReceive(NetworkConnection * this) { for (;;) { LGNetStreamPacket streamPacket; const LGNetStreamResult result = lgNetStreamParserNext( &this->receiveParser, &streamPacket, NULL); if (result == LG_NET_STREAM_NEED_DATA) return true; if (result != LG_NET_STREAM_PACKET) return false; LGNetPacketView packet = { .envelope = streamPacket.envelope, .payload = streamPacket.payload[0].data, .payloadLength = streamPacket.payloadLength, .wireSize = streamPacket.wireSize, }; if (streamPacket.payload[1].size) { memcpy(this->receivePayload, streamPacket.payload[0].data, streamPacket.payload[0].size); memcpy(this->receivePayload + streamPacket.payload[0].size, streamPacket.payload[1].data, streamPacket.payload[1].size); packet.payload = this->receivePayload; } LG_LOCK(this->lock); const bool sequenceValid = this->receiveSequence != UINT64_MAX && packet.envelope.sequence == this->receiveSequence + 1U; if (sequenceValid) this->receiveSequence = 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->receiveParser)) return false; } } static bool networkConnectionReceive(NetworkConnection * this, const QUIC_BUFFER * buffers, uint32_t count) { if (count && !buffers) return false; for (uint32_t i = 0; i < count; ++i) { if (buffers[i].Length && !buffers[i].Buffer) return false; size_t offset = 0; while (offset < buffers[i].Length) { size_t accepted; if (!lgNetStreamParserAppend(&this->receiveParser, buffers[i].Buffer + offset, buffers[i].Length - offset, &accepted)) return false; offset += accepted; if (!networkConnectionDrainReceive(this)) return false; if (!accepted && offset < buffers[i].Length) return false; } } return networkConnectionDrainReceive(this); } static 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) return; free(stream->receivePayload); free(stream->receiveData); free(stream); } static void networkBulkStreamDetachNL(struct NetworkBulkStream * stream) { NetworkConnection * this = stream->connection; if (stream->outbound) { if (stream->service < NETWORK_SEND_SERVICE_COUNT && this->outboundBulk[stream->service] == stream) this->outboundBulk[stream->service] = NULL; return; } struct NetworkBulkStream ** link = &this->inboundBulk; while (*link && *link != stream) link = &(*link)->next; if (*link != stream) return; *link = stream->next; if (this->peerBulkStreams) --this->peerBulkStreams; if (stream->bound && stream->service < NETWORK_SEND_SERVICE_COUNT && this->servicePeerBulkStreams[stream->service]) --this->servicePeerBulkStreams[stream->service]; } static bool networkBulkStreamActivateReceive( struct NetworkBulkStream * stream) { LGNetStreamBinding binding; if (lgNetStreamBindingDecode(&binding, stream->bindingData, sizeof(stream->bindingData)) != LG_NET_PARSE_OK || binding.originRole != LG_NET_ROLE_SERVER || binding.flags != (LG_NET_STREAM_BINDING_UNIDIRECTIONAL | LG_NET_STREAM_BINDING_BULK) || binding.streamID != stream->streamID || !networkConnectionServiceValid(binding.service)) return false; NetworkConnection * this = stream->connection; uint32_t maxPayloadLength; LG_LOCK(this->lock); const uint16_t serviceMask = UINT16_C(1) << binding.service; const bool valid = !atomic_load_explicit(&this->stopping, memory_order_acquire) && atomic_load_explicit(&this->sessionValid, memory_order_acquire) && binding.sessionEpoch == this->sessionEpoch && (this->receivedServices & serviceMask) && (this->bulkServices & serviceMask) && this->serviceMaxUnidirectional[binding.service] && this->servicePeerBulkStreams[binding.service] < this->serviceMaxUnidirectional[binding.service]; maxPayloadLength = valid ? this->serviceMaxPayload[binding.service] : 0; LG_UNLOCK(this->lock); if (!valid || !maxPayloadLength) return false; const size_t receiveSize = LG_NET_ENVELOPE_WIRE_SIZE + (size_t)maxPayloadLength; uint8_t * receiveData = malloc(receiveSize); if (!receiveData) return false; stream->receiveData = receiveData; if (!lgNetStreamParserInit(&stream->receiveParser, receiveData, receiveSize, maxPayloadLength)) { free(stream->receiveData); stream->receiveData = NULL; return false; } LG_LOCK(this->lock); const bool stateValid = !atomic_load_explicit(&this->stopping, memory_order_acquire) && atomic_load_explicit(&this->sessionValid, memory_order_acquire) && binding.sessionEpoch == this->sessionEpoch && (this->receivedServices & serviceMask) && (this->bulkServices & serviceMask) && this->serviceMaxPayload[binding.service] == maxPayloadLength && this->serviceMaxUnidirectional[binding.service] && this->servicePeerBulkStreams[binding.service] < this->serviceMaxUnidirectional[binding.service]; if (stateValid) { stream->service = binding.service; stream->bound = true; ++this->servicePeerBulkStreams[binding.service]; } LG_UNLOCK(this->lock); if (!stateValid) { free(stream->receiveData); stream->receiveData = NULL; return false; } return true; } static bool networkConnectionValidateBulk( const struct NetworkBulkStream * stream, const LGNetPacketView * packet) { return stream->bound && packet->envelope.service == stream->service && packet->envelope.headerSize == LG_NET_ENVELOPE_WIRE_SIZE && packet->envelope.messageVersion == LG_NET_MESSAGE_VERSION_CURRENT && (packet->envelope.flags & LG_NET_ENVELOPE_FINAL) && !(packet->envelope.flags & (LG_NET_ENVELOPE_RELIABLE | LG_NET_ENVELOPE_DATAGRAM | LG_NET_ENVELOPE_EARLY_DATA)) && lgNetEnvelopeRouteValid(&packet->envelope, LG_NET_ROLE_SERVER, LG_NET_ROUTE_DELIVERY_BULK); } static bool networkBulkStreamDrainReceive( struct NetworkBulkStream * stream) { NetworkConnection * this = stream->connection; for (;;) { LGNetStreamPacket streamPacket; const LGNetStreamResult result = lgNetStreamParserNext( &stream->receiveParser, &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) { if (stream->receivePayloadSize < streamPacket.payloadLength) { uint8_t * receivePayload = realloc( stream->receivePayload, streamPacket.payloadLength); if (!receivePayload) return false; stream->receivePayload = receivePayload; stream->receivePayloadSize = streamPacket.payloadLength; } memcpy(stream->receivePayload, streamPacket.payload[0].data, streamPacket.payload[0].size); memcpy(stream->receivePayload + streamPacket.payload[0].size, streamPacket.payload[1].data, streamPacket.payload[1].size); packet.payload = stream->receivePayload; } LG_LOCK(this->lock); const bool sequenceValid = stream->bound && stream->receiveSequence != UINT64_MAX && packet.envelope.sequence == stream->receiveSequence + 1U; if (sequenceValid) stream->receiveSequence = packet.envelope.sequence; LG_UNLOCK(this->lock); if (!sequenceValid || !networkConnectionValidateBulk(stream, &packet) || !networkConnectionHandlePacket(this, &packet, NETWORK_PACKET_DELIVERY_BULK) || !lgNetStreamParserConsume(&stream->receiveParser)) return false; if (!lgNetStreamParserBuffered(&stream->receiveParser)) lgNetStreamParserReset(&stream->receiveParser); } } static bool networkBulkStreamReceive(struct NetworkBulkStream * stream, 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) { if (!stream->bound) { const size_t remaining = buffers[index].Length - offset; size_t bindingLength = sizeof(stream->bindingData) - stream->bindingLength; if (bindingLength > remaining) bindingLength = remaining; memcpy(stream->bindingData + stream->bindingLength, buffers[index].Buffer + offset, bindingLength); stream->bindingLength += bindingLength; offset += bindingLength; if (stream->bindingLength == sizeof(stream->bindingData) && !networkBulkStreamActivateReceive(stream)) return false; continue; } size_t accepted; if (!lgNetStreamParserAppend(&stream->receiveParser, buffers[index].Buffer + offset, buffers[index].Length - offset, &accepted)) return false; offset += accepted; if (!networkBulkStreamDrainReceive(stream) || (!accepted && !lgNetStreamParserWritable(&stream->receiveParser))) return false; } } return !stream->bound || networkBulkStreamDrainReceive(stream); } static bool networkBulkStreamQueueReceive(struct NetworkBulkStream * stream, const QUIC_BUFFER * buffers, uint32_t bufferCount, uint64_t length) { const size_t bufferBytes = (size_t)bufferCount * sizeof(*buffers); if ((bufferCount && !buffers) || (bufferCount && bufferBytes / sizeof(*buffers) != bufferCount) || bufferBytes > SIZE_MAX - sizeof(struct NetworkBulkReceive)) return false; uint64_t bufferLength = 0; for (uint32_t index = 0; index < bufferCount; ++index) { if ((buffers[index].Length && !buffers[index].Buffer) || UINT64_MAX - bufferLength < buffers[index].Length) return false; bufferLength += buffers[index].Length; } if (bufferLength != length) return false; const size_t receiveSize = sizeof(struct NetworkBulkReceive) + bufferBytes; struct NetworkBulkReceive * receive = malloc(receiveSize); if (!receive) return false; receive->next = NULL; receive->stream = stream; receive->handle = stream->handle; receive->length = length; receive->bufferCount = bufferCount; /* MsQuic retains the payload while this receive is pending, but the buffer * descriptor array itself is only valid for the callback duration. */ if (bufferCount) memcpy(receive->buffers, buffers, bufferBytes); NetworkConnection * this = stream->connection; LG_LOCK(this->lock); /* Multi-receive is intentionally disabled for this connection. */ const bool accepted = !this->bulkReceiveStopping && !atomic_load_explicit(&this->stopping, memory_order_acquire) && !stream->shutdownComplete && !stream->receivePending && !stream->receiveFailed; if (accepted) { if (!this->bulkReceiveReferences) lgResetEvent(this->bulkReceiveIdle); ++this->bulkReceiveReferences; ++stream->receiveReferences; stream->receivePending = true; if (this->bulkReceiveTail) this->bulkReceiveTail->next = receive; else this->bulkReceiveHead = receive; this->bulkReceiveTail = receive; } LG_UNLOCK(this->lock); if (!accepted) { free(receive); return false; } lgSignalEvent(this->bulkReceiveEvent); 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) { struct NetworkBulkStream * stream = calloc(1, sizeof(*stream)); if (!stream) return LG_TRANSPORT_ERROR; stream->connection = this; stream->service = service; stream->outbound = true; LG_LOCK(this->lock); const uint16_t serviceMask = UINT16_C(1) << service; HQUIC connection = this->connection; if (!connection || atomic_load_explicit(&this->stopping, memory_order_acquire) || !atomic_load_explicit(&this->sessionValid, memory_order_acquire)) { LG_UNLOCK(this->lock); networkBulkStreamFree(stream); return LG_TRANSPORT_DISCONNECTED; } if (!(this->receivedServices & serviceMask) || !(this->bulkServices & serviceMask) || !this->serviceMaxUnidirectional[service]) { LG_UNLOCK(this->lock); networkBulkStreamFree(stream); return LG_TRANSPORT_UNAVAILABLE; } if (this->outboundBulk[service]) { this->outboundBulk[service]->openingReturned = true; networkConnectionMarkSendBlockedNL(this, service, NETWORK_PACKET_DELIVERY_BULK, packetSize); LG_UNLOCK(this->lock); networkBulkStreamFree(stream); return LG_TRANSPORT_UNAVAILABLE; } this->outboundBulk[service] = stream; networkConnectionMarkSendBlockedNL(this, service, NETWORK_PACKET_DELIVERY_BULK, packetSize); LG_UNLOCK(this->lock); HQUIC handle; QUIC_STATUS status = this->api->StreamOpen(connection, QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL, networkBulkStreamCallback, stream, &handle); if (QUIC_FAILED(status)) { LG_LOCK(this->lock); networkBulkStreamDetachNL(stream); this->bulkSend.serviceBlocked[service] = false; this->bulkSend.serviceBlockedBytes[service] = 0; LG_UNLOCK(this->lock); networkBulkStreamFree(stream); return LG_TRANSPORT_ERROR; } stream->handle = handle; status = this->api->StreamStart(handle, QUIC_STREAM_START_FLAG_IMMEDIATE); if (QUIC_FAILED(status)) { LG_LOCK(this->lock); networkBulkStreamDetachNL(stream); this->bulkSend.serviceBlocked[service] = false; this->bulkSend.serviceBlockedBytes[service] = 0; LG_UNLOCK(this->lock); this->api->StreamClose(handle); networkBulkStreamFree(stream); return LG_TRANSPORT_ERROR; } uint16_t ready = 0; LG_LOCK(this->lock); stream->openingReturned = true; if (stream->bound && !stream->shutdownComplete) ready = networkConnectionReadyServicesNL( this, NETWORK_PACKET_DELIVERY_BULK); const bool bound = stream->bound && !stream->shutdownComplete; const bool shutdownComplete = stream->shutdownComplete; LG_UNLOCK(this->lock); if (shutdownComplete) networkBulkStreamFree(stream); networkConnectionNotifySendReady(this, 0, 0, ready); return shutdownComplete ? LG_TRANSPORT_DISCONNECTED : bound ? LG_TRANSPORT_OK : LG_TRANSPORT_UNAVAILABLE; } static void networkConnectionProtocolFailure(NetworkConnection * this) { if (!atomic_load_explicit(&this->stopping, memory_order_acquire)) DEBUG_ERROR("Network protocol processing failed"); networkConnectionFail(this, LG_TRANSPORT_ERROR); LG_LOCK(this->lock); HQUIC connection = this->connection; LG_UNLOCK(this->lock); if (connection) this->api->ConnectionShutdown(connection, QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0); } static int networkBulkReceiveThread(void * opaque) { NetworkConnection * this = opaque; for (;;) { lgWaitEvent(this->bulkReceiveEvent, TIMEOUT_INFINITE); for (;;) { LG_LOCK(this->lock); struct NetworkBulkReceive * receive = this->bulkReceiveHead; if (receive) { this->bulkReceiveHead = receive->next; if (!this->bulkReceiveHead) this->bulkReceiveTail = NULL; } const bool stopping = this->bulkReceiveStopping; LG_UNLOCK(this->lock); if (!receive) { if (stopping) return 0; break; } struct NetworkBulkStream * stream = receive->stream; const bool received = networkBulkStreamReceive(stream, receive->buffers, receive->bufferCount); LG_LOCK(this->lock); stream->receivePending = false; if (!received) stream->receiveFailed = true; LG_UNLOCK(this->lock); this->api->StreamReceiveComplete(receive->handle, receive->length); if (!received && !atomic_load_explicit( &this->stopping, memory_order_acquire)) networkConnectionProtocolFailure(this); bool closeStream = false; bool freeStream = false; LG_LOCK(this->lock); if (stream->receiveReferences) --stream->receiveReferences; if (this->bulkReceiveReferences) { --this->bulkReceiveReferences; if (!this->bulkReceiveReferences) lgSignalEvent(this->bulkReceiveIdle); } if (!stream->receiveReferences && stream->shutdownComplete) { closeStream = true; freeStream = !stream->outbound || stream->openingReturned; } LG_UNLOCK(this->lock); if (closeStream) this->api->StreamClose(receive->handle); if (freeStream) networkBulkStreamFree(stream); free(receive); } } } static bool networkConnectionAcceptBulkStream(NetworkConnection * this, HQUIC handle, QUIC_STREAM_OPEN_FLAGS flags) { if (flags != QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL) return false; QUIC_UINT62 streamID; uint32_t streamIDSize = sizeof(streamID); if (QUIC_FAILED(this->api->GetParam(handle, QUIC_PARAM_STREAM_ID, &streamIDSize, &streamID)) || streamIDSize != sizeof(streamID) || (streamID & 3U) != 3U) return false; struct NetworkBulkStream * stream = calloc(1, sizeof(*stream)); if (!stream) return false; stream->connection = this; stream->handle = handle; stream->streamID = streamID; LG_LOCK(this->lock); const bool accepted = !atomic_load_explicit(&this->stopping, memory_order_acquire) && atomic_load_explicit(&this->sessionValid, memory_order_acquire) && this->maxPeerBulkStreams && this->peerBulkStreams < this->maxPeerBulkStreams; if (accepted) { stream->next = this->inboundBulk; this->inboundBulk = stream; ++this->peerBulkStreams; } LG_UNLOCK(this->lock); if (!accepted) { networkBulkStreamFree(stream); return false; } this->api->SetCallbackHandler(handle, (void *)networkBulkStreamCallback, stream); return true; } static void networkConnectionTrustDecision(void * opaque, bool trusted) { NetworkConnection * this = opaque; LG_LOCK(this->lock); if (!this->certificatePending || !this->connection) { LG_UNLOCK(this->lock); return; } trusted = trusted && !atomic_load_explicit( &this->stopping, memory_order_acquire); this->certificatePending = false; HQUIC connection = this->connection; char fingerprint[NETWORK_FINGERPRINT_TEXT_SIZE]; memcpy(fingerprint, this->pendingFingerprint, sizeof(fingerprint)); memset(this->pendingFingerprint, 0, sizeof(this->pendingFingerprint)); LG_UNLOCK(this->lock); if (trusted) trusted = networkKnownHostsRemember( this->knownHosts, this->endpoint, fingerprint); const QUIC_STATUS status = this->api->ConnectionCertificateValidationComplete(connection, trusted, trusted ? QUIC_TLS_ALERT_CODE_SUCCESS : QUIC_TLS_ALERT_CODE_BAD_CERTIFICATE); if (QUIC_FAILED(status)) { DEBUG_ERROR("MsQuic certificate validation completion failed: 0x%08x", (unsigned)status); networkConnectionFail(this, LG_TRANSPORT_ERROR); this->api->ConnectionShutdown(connection, QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0); } } static QUIC_STATUS networkConnectionCertificate(NetworkConnection * this, const QUIC_CONNECTION_EVENT * event) { const QUIC_BUFFER * certificate = (const QUIC_BUFFER *)event->PEER_CERTIFICATE_RECEIVED.Certificate; if (!certificate || !certificate->Buffer || !certificate->Length) { DEBUG_ERROR("Network server did not provide a usable certificate"); return QUIC_STATUS_BAD_CERTIFICATE; } uint8_t fingerprint[NETWORK_FINGERPRINT_SIZE]; char fingerprintText[NETWORK_FINGERPRINT_TEXT_SIZE]; if (!networkFingerprintSHA256(certificate->Buffer, certificate->Length, fingerprint)) { DEBUG_ERROR("Failed to fingerprint the network server certificate"); return QUIC_STATUS_BAD_CERTIFICATE; } networkFingerprintFormat(fingerprint, fingerprintText); switch (networkKnownHostsCheck( this->knownHosts, this->endpoint, fingerprintText)) { case NETWORK_KNOWN_HOST_MATCH: return QUIC_STATUS_SUCCESS; case NETWORK_KNOWN_HOST_CHANGED: DEBUG_ERROR("The certificate for %s changed; remove its entry from " "network_known_hosts before trusting a replacement", this->endpoint); return QUIC_STATUS_BAD_CERTIFICATE; case NETWORK_KNOWN_HOST_ERROR: DEBUG_ERROR("Failed to check the network server fingerprint"); return QUIC_STATUS_BAD_CERTIFICATE; case NETWORK_KNOWN_HOST_UNKNOWN: break; } if (!this->trustPrompt) return QUIC_STATUS_BAD_CERTIFICATE; LG_LOCK(this->lock); if (this->certificatePending || atomic_load_explicit(&this->stopping, memory_order_acquire)) { LG_UNLOCK(this->lock); return QUIC_STATUS_BAD_CERTIFICATE; } this->certificatePending = true; memcpy(this->pendingFingerprint, fingerprintText, sizeof(this->pendingFingerprint)); LG_UNLOCK(this->lock); if (!this->trustPrompt(this->opaque, this->endpoint, fingerprintText, networkConnectionTrustDecision, this)) { LG_LOCK(this->lock); const bool validationCompleted = !this->certificatePending; if (!validationCompleted) { this->certificatePending = false; memset(this->pendingFingerprint, 0, sizeof(this->pendingFingerprint)); } LG_UNLOCK(this->lock); return validationCompleted ? QUIC_STATUS_PENDING : QUIC_STATUS_BAD_CERTIFICATE; } /* MsQuic marks validation pending before invoking this callback and permits * ConnectionCertificateValidationComplete from the callback. Always return * PENDING once the prompt owns the decision, including during cancellation. */ return QUIC_STATUS_PENDING; } static QUIC_STATUS QUIC_API networkConnectionCallback(HQUIC handle, void * opaque, QUIC_CONNECTION_EVENT * event) { NetworkConnection * this = opaque; switch (event->Type) { case QUIC_CONNECTION_EVENT_CONNECTED: { if (atomic_load_explicit(&this->stopping, memory_order_acquire)) { this->api->ConnectionShutdown(handle, QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0); return QUIC_STATUS_SUCCESS; } BOOLEAN encryptionDisabled = FALSE; uint32_t encryptionDisabledLength = sizeof(encryptionDisabled); QUIC_STATUS status = this->api->GetParam(handle, QUIC_PARAM_CONN_DISABLE_1RTT_ENCRYPTION, &encryptionDisabledLength, &encryptionDisabled); if (QUIC_FAILED(status)) { DEBUG_ERROR("Failed to query 1-RTT encryption for %s: 0x%08x", this->endpoint, (unsigned)status); networkConnectionFail(this, LG_TRANSPORT_ERROR); this->api->ConnectionShutdown(handle, QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0); return QUIC_STATUS_SUCCESS; } DEBUG_INFO("Network connection to %s established in %s mode " "(%s 1-RTT payloads)", this->endpoint, encryptionDisabled ? "insecure" : "secure", encryptionDisabled ? "unencrypted" : "encrypted"); HQUIC stream = NULL; status = this->api->StreamOpen(handle, QUIC_STREAM_OPEN_FLAG_NONE, networkStreamCallback, this, &stream); if (QUIC_FAILED(status)) { DEBUG_ERROR("MsQuic control StreamOpen failed: 0x%08x", (unsigned)status); networkConnectionFail(this, LG_TRANSPORT_ERROR); this->api->ConnectionShutdown(handle, QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0); return QUIC_STATUS_SUCCESS; } LG_LOCK(this->lock); this->controlStream = stream; LG_UNLOCK(this->lock); status = this->api->StreamStart( stream, QUIC_STREAM_START_FLAG_IMMEDIATE | QUIC_STREAM_START_FLAG_SHUTDOWN_ON_FAIL); if (QUIC_FAILED(status)) { DEBUG_ERROR("MsQuic control StreamStart failed: 0x%08x", (unsigned)status); networkConnectionFail(this, LG_TRANSPORT_ERROR); this->api->ConnectionShutdown(handle, QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0); } return QUIC_STATUS_SUCCESS; } case QUIC_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_TRANSPORT: { const QUIC_STATUS status = event->SHUTDOWN_INITIATED_BY_TRANSPORT.Status; const QUIC_UINT62 error = event->SHUTDOWN_INITIATED_BY_TRANSPORT.ErrorCode; if (networkConnectionRecordTransportError(this, status, error)) DEBUG_WARN("Network transport closed the connection: status 0x%x, " "error 0x%llx", (unsigned)status, (unsigned long long)error); networkConnectionFail(this, LG_TRANSPORT_UNAVAILABLE); return QUIC_STATUS_SUCCESS; } case QUIC_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_PEER: { const QUIC_UINT62 error = event->SHUTDOWN_INITIATED_BY_PEER.ErrorCode; if (!atomic_load_explicit(&this->stopping, memory_order_acquire) && networkConnectionRecordPeerError(this, error)) DEBUG_WARN("Network server closed the connection: error 0x%llx", (unsigned long long)error); networkConnectionFail(this, LG_TRANSPORT_DISCONNECTED); return QUIC_STATUS_SUCCESS; } case QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE: networkConnectionSetState( this, false, LG_TRANSPORT_DISCONNECTED); lgSignalEvent(this->connectEvent); lgSignalEvent(this->closeEvent); return QUIC_STATUS_SUCCESS; case QUIC_CONNECTION_EVENT_PEER_STREAM_STARTED: if (!networkConnectionAcceptBulkStream(this, event->PEER_STREAM_STARTED.Stream, event->PEER_STREAM_STARTED.Flags)) { this->api->SetCallbackHandler(event->PEER_STREAM_STARTED.Stream, (void *)networkRejectedStreamCallback, this); this->api->StreamShutdown(event->PEER_STREAM_STARTED.Stream, QUIC_STREAM_SHUTDOWN_FLAG_ABORT | QUIC_STREAM_SHUTDOWN_FLAG_IMMEDIATE, 0); networkConnectionProtocolFailure(this); } return QUIC_STATUS_SUCCESS; case QUIC_CONNECTION_EVENT_DATAGRAM_RECEIVED: { LGNetPacketView packet; if (networkConnectionDecodeDatagram( this, event->DATAGRAM_RECEIVED.Buffer, &packet) && this->packet) this->packet(this->opaque, &packet, NETWORK_PACKET_DELIVERY_DATAGRAM); return QUIC_STATUS_SUCCESS; } case QUIC_CONNECTION_EVENT_DATAGRAM_STATE_CHANGED: { const bool sendEnabled = event->DATAGRAM_STATE_CHANGED.SendEnabled != FALSE; const uint16_t maxLength = event->DATAGRAM_STATE_CHANGED.MaxSendLength; const uint32_t sendState = (sendEnabled ? NETWORK_DATAGRAM_SEND_ENABLED : 0) | maxLength; uint16_t ready; LG_LOCK(this->lock); LG_LOCK(this->datagramSendLock); atomic_store_explicit( &this->datagramSendState, sendState, memory_order_release); LG_UNLOCK(this->datagramSendLock); ready = networkConnectionReadyServicesNL( this, NETWORK_PACKET_DELIVERY_DATAGRAM); LG_UNLOCK(this->lock); uint16_t blockedLength = atomic_load_explicit( &this->inputDatagramBlockedLength, memory_order_acquire); if (sendEnabled && blockedLength && maxLength >= blockedLength && atomic_compare_exchange_strong_explicit( &this->inputDatagramBlockedLength, &blockedLength, 0, memory_order_acq_rel, memory_order_acquire)) ready |= UINT16_C(1) << LG_NET_SERVICE_INPUT; networkConnectionNotifySendReady(this, 0, ready, 0); return QUIC_STATUS_SUCCESS; } case QUIC_CONNECTION_EVENT_DATAGRAM_SEND_STATE_CHANGED: { 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)) { if (send->accounted) { LG_LOCK(this->lock); ready = networkConnectionReleaseSendNL(this, send); LG_UNLOCK(this->lock); } } 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); default: return QUIC_STATUS_SUCCESS; } } static QUIC_STATUS QUIC_API networkStreamCallback(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 control stream start failed: 0x%08x", (unsigned)event->START_COMPLETE.Status); networkConnectionFail(this, LG_TRANSPORT_ERROR); this->api->ConnectionShutdown(this->connection, QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0); } else if (!networkConnectionSendHello(this)) { DEBUG_ERROR("Failed to send the network control handshake"); networkConnectionFail(this, LG_TRANSPORT_ERROR); this->api->ConnectionShutdown(this->connection, QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0); } return QUIC_STATUS_SUCCESS; case QUIC_STREAM_EVENT_RECEIVE: if (!networkConnectionReceive( this, event->RECEIVE.Buffers, event->RECEIVE.BufferCount)) { DEBUG_ERROR("Failed to process network control stream data"); networkConnectionFail(this, LG_TRANSPORT_ERROR); this->api->ConnectionShutdown(this->connection, QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0); } return QUIC_STATUS_SUCCESS; case QUIC_STREAM_EVENT_SEND_COMPLETE: { struct NetworkSend * send = event->SEND_COMPLETE.ClientContext; uint16_t ready = 0; if (send->accounted) { LG_LOCK(this->lock); 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: if (!atomic_load_explicit(&this->stopping, memory_order_acquire)) DEBUG_ERROR("Network server closed the control stream"); this->api->ConnectionShutdown(this->connection, QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0); return QUIC_STATUS_SUCCESS; case QUIC_STREAM_EVENT_PEER_SEND_ABORTED: if (!atomic_load_explicit(&this->stopping, memory_order_acquire)) DEBUG_ERROR("Network server aborted the control send stream: " "0x%llx", (unsigned long long)event->PEER_SEND_ABORTED.ErrorCode); this->api->ConnectionShutdown(this->connection, QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0); return QUIC_STATUS_SUCCESS; case QUIC_STREAM_EVENT_PEER_RECEIVE_ABORTED: if (!atomic_load_explicit(&this->stopping, memory_order_acquire)) DEBUG_ERROR("Network server aborted the control receive stream: " "0x%llx", (unsigned long long)event->PEER_RECEIVE_ABORTED.ErrorCode); this->api->ConnectionShutdown(this->connection, QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0); return QUIC_STATUS_SUCCESS; case QUIC_STREAM_EVENT_SHUTDOWN_COMPLETE: LG_LOCK(this->lock); if (this->controlStream == handle) this->controlStream = NULL; LG_UNLOCK(this->lock); this->api->StreamClose(handle); return QUIC_STATUS_SUCCESS; default: return QUIC_STATUS_SUCCESS; } } 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_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; uint16_t ready = 0; if (send->accounted) { LG_LOCK(this->lock); 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) { struct NetworkBulkStream * stream = opaque; NetworkConnection * this = stream->connection; switch (event->Type) { case QUIC_STREAM_EVENT_START_COMPLETE: { if (QUIC_FAILED(event->START_COMPLETE.Status)) { LG_LOCK(this->lock); networkBulkStreamDetachNL(stream); const bool deferFree = stream->outbound && !stream->openingReturned; stream->shutdownComplete = true; LG_UNLOCK(this->lock); this->api->StreamClose(handle); if (!deferFree) networkBulkStreamFree(stream); networkConnectionProtocolFailure(this); return QUIC_STATUS_SUCCESS; } if (!stream->outbound || !event->START_COMPLETE.ID || (event->START_COMPLETE.ID & 3U) != 2U) { this->api->StreamShutdown(handle, QUIC_STREAM_SHUTDOWN_FLAG_ABORT, 0); networkConnectionProtocolFailure(this); return QUIC_STATUS_SUCCESS; } uint64_t sessionEpoch; LG_LOCK(this->lock); const uint16_t serviceMask = UINT16_C(1) << stream->service; const bool stateValid = !atomic_load_explicit(&this->stopping, memory_order_acquire) && atomic_load_explicit(&this->sessionValid, memory_order_acquire) && this->outboundBulk[stream->service] == stream && (this->receivedServices & serviceMask) && (this->bulkServices & serviceMask); 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, stream->service, LG_NET_ROLE_CLIENT, event->START_COMPLETE.ID, sessionEpoch, LG_NET_STREAM_BINDING_UNIDIRECTIONAL | LG_NET_STREAM_BINDING_BULK); struct NetworkSend * send = networkConnectionAllocSend( this, 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 = stream->service; send->length = LG_NET_STREAM_BINDING_WIRE_SIZE; send->delivery = NETWORK_PACKET_DELIVERY_BULK; send->accounted = false; uint16_t ready = 0; LG_LOCK(this->lock); const bool sendValid = !atomic_load_explicit(&this->stopping, memory_order_acquire) && atomic_load_explicit(&this->sessionValid, memory_order_acquire) && this->sessionEpoch == sessionEpoch && this->outboundBulk[stream->service] == stream; const QUIC_STATUS status = sendValid ? this->api->StreamSend(handle, &send->buffer, 1, QUIC_SEND_FLAG_NONE, send) : QUIC_STATUS_ABORTED; if (QUIC_SUCCEEDED(status)) { stream->streamID = event->START_COMPLETE.ID; stream->bound = true; if (stream->openingReturned) ready = networkConnectionReadyServicesNL( this, NETWORK_PACKET_DELIVERY_BULK); } LG_UNLOCK(this->lock); 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; } networkConnectionNotifySendReady(this, 0, 0, ready); return QUIC_STATUS_SUCCESS; } case QUIC_STREAM_EVENT_RECEIVE: if (stream->outbound || !networkBulkStreamQueueReceive(stream, event->RECEIVE.Buffers, event->RECEIVE.BufferCount, event->RECEIVE.TotalBufferLength)) { if (!atomic_load_explicit( &this->stopping, memory_order_acquire)) networkConnectionProtocolFailure(this); return QUIC_STATUS_SUCCESS; } return QUIC_STATUS_PENDING; 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, 0, 0, ready); 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->lock); networkBulkStreamDetachNL(stream); const bool deferClose = stream->receiveReferences != 0; const bool deferFree = deferClose || (stream->outbound && !stream->openingReturned); stream->shutdownComplete = deferFree; LG_UNLOCK(this->lock); if (!deferClose) this->api->StreamClose(handle); if (!deferFree) networkBulkStreamFree(stream); return QUIC_STATUS_SUCCESS; } default: return QUIC_STATUS_SUCCESS; } } static QUIC_STATUS QUIC_API networkRejectedStreamCallback(HQUIC handle, void * opaque, QUIC_STREAM_EVENT * event) { NetworkConnection * this = opaque; if (event->Type == QUIC_STREAM_EVENT_SHUTDOWN_COMPLETE) this->api->StreamClose(handle); return QUIC_STATUS_SUCCESS; } bool networkConnectionCreate(NetworkConnection ** result, const NetworkConnectionParams * params) { if (!result || !params || !params->address || !params->knownHosts || (!!params->trustPrompt != !!params->trustCancel) || params->capabilityCount > LG_NET_MAX_CAPABILITY_RECORDS || (params->capabilityCount && !params->capabilities)) { DEBUG_ERROR("Invalid network connection parameters"); return false; } NetworkConnection * this = calloc(1, sizeof(*this)); if (!this) { DEBUG_ERROR("Failed to allocate the network connection"); return false; } LG_LOCK_INIT(this->lock); LG_LOCK_INIT(this->datagramSendLock); 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); atomic_init(&this->datagramSendState, 0); atomic_init(&this->inputDatagramBlockedLength, 0); for (size_t i = 0; i < NETWORK_INPUT_SEND_POOL_COUNT; ++i) { struct NetworkInputSend * inputSend = &this->inputSendPool[i]; struct NetworkSend * send = &inputSend->send; inputSend->reliableReserved = i < NETWORK_INPUT_SEND_RELIABLE_RESERVE; send->connection = this; send->pooled = true; if (inputSend->reliableReserved) { send->next = this->inputReliableSendFree; this->inputReliableSendFree = send; } else { send->next = this->inputSendFree; this->inputSendFree = send; } } uint16_t offeredBulkStreams = 0; for (uint32_t index = 0; index < params->capabilityCount; ++index) { const LGNetCapability * capability = ¶ms->capabilities[index]; if (!networkCapabilityOfferValid(capability) || capability->maxUnidirectionalStreams > NETWORK_MAX_PEER_STREAMS - offeredBulkStreams) { DEBUG_ERROR("Invalid network capability at index %u", (unsigned)index); goto fail; } const uint16_t serviceMask = UINT16_C(1) << capability->service; if (this->offeredServices & serviceMask) { DEBUG_ERROR("Duplicate network capability for service %u", (unsigned)capability->service); goto fail; } this->offeredCapabilities[capability->service] = *capability; this->offeredServices |= serviceMask; offeredBulkStreams += capability->maxUnidirectionalStreams; ++this->offeredCapabilityCount; } this->address = networkDuplicate(params->address); this->password = networkDuplicate(params->password); this->endpoint = networkEndpoint(params->address, params->port); if (!this->address || (params->password && !this->password) || !this->endpoint) { DEBUG_ERROR("Failed to allocate the network connection endpoint"); goto fail; } this->port = params->port; this->allowUnencrypted = params->allowUnencrypted; 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); this->bulkReceiveEvent = lgCreateEvent(true, 0); this->bulkReceiveIdle = lgCreateEvent(false, 0); if (!this->connectEvent || !this->closeEvent || !this->bulkReceiveEvent || !this->bulkReceiveIdle || !lgSignalEvent(this->bulkReceiveIdle)) { DEBUG_ERROR("Failed to initialize the network connection events"); goto fail; } 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) || !lgNetStreamParserInit(&this->inputReceiveParser, this->inputReceiveData, NETWORK_MAX_INPUT_BUFFER, NETWORK_MAX_INPUT_PAYLOAD)) { DEBUG_ERROR("Failed to initialize the network receive buffers"); goto fail; } QUIC_STATUS status = MsQuicOpen2(&this->api); if (QUIC_FAILED(status)) { DEBUG_ERROR("MsQuicOpen2 failed: 0x%08x", (unsigned)status); goto fail; } const QUIC_REGISTRATION_CONFIG registration = { .AppName = "looking-glass-client", .ExecutionProfile = QUIC_EXECUTION_PROFILE_TYPE_REAL_TIME, }; status = this->api->RegistrationOpen( ®istration, &this->registration); if (QUIC_FAILED(status)) { DEBUG_ERROR("MsQuic RegistrationOpen failed: 0x%08x", (unsigned)status); goto fail; } const QUIC_BUFFER alpn = { .Length = LG_NET_ALPN_LENGTH, .Buffer = (uint8_t *)LG_NET_ALPN, }; const QUIC_SETTINGS settings = { .IsSet = { .HandshakeIdleTimeoutMs = true, .IdleTimeoutMs = true, .StreamRecvBufferDefault = true, .ConnFlowControlWindow = true, .InitialRttMs = true, .MaxAckDelayMs = true, .KeepAliveIntervalMs = true, .PeerBidiStreamCount = true, .PeerUnidiStreamCount = true, .SendBufferingEnabled = true, .DatagramReceiveEnabled = true, .StreamRecvWindowUnidiDefault = true, }, .HandshakeIdleTimeoutMs = NETWORK_HANDSHAKE_TIMEOUT_MS, .IdleTimeoutMs = NETWORK_IDLE_TIMEOUT_MS, .StreamRecvBufferDefault = NETWORK_STREAM_RECV_BUFFER, .ConnFlowControlWindow = NETWORK_CONN_RECV_WINDOW, .InitialRttMs = NETWORK_INITIAL_RTT_MS, .MaxAckDelayMs = 1, .KeepAliveIntervalMs = NETWORK_KEEPALIVE_INTERVAL_MS, .PeerBidiStreamCount = NETWORK_MAX_PEER_STREAMS, .PeerUnidiStreamCount = NETWORK_MAX_PEER_STREAMS, .SendBufferingEnabled = false, .DatagramReceiveEnabled = true, .StreamRecvWindowUnidiDefault = NETWORK_STREAM_RECV_WINDOW, }; status = this->api->ConfigurationOpen(this->registration, &alpn, 1, &settings, sizeof(settings), NULL, &this->configuration); if (QUIC_FAILED(status)) { DEBUG_ERROR("MsQuic ConfigurationOpen failed: 0x%08x", (unsigned)status); goto fail; } /* PKI chain validation is disabled because identity is enforced by the * certificate callback and the persistent TOFU store above. */ const QUIC_CREDENTIAL_CONFIG credential = { .Type = QUIC_CREDENTIAL_TYPE_NONE, .Flags = QUIC_CREDENTIAL_FLAG_CLIENT | QUIC_CREDENTIAL_FLAG_NO_CERTIFICATE_VALIDATION | QUIC_CREDENTIAL_FLAG_INDICATE_CERTIFICATE_RECEIVED | QUIC_CREDENTIAL_FLAG_USE_PORTABLE_CERTIFICATES, }; status = this->api->ConfigurationLoadCredential( this->configuration, &credential); if (QUIC_FAILED(status)) { DEBUG_ERROR("MsQuic ConfigurationLoadCredential failed: 0x%08x", (unsigned)status); goto fail; } if (!lgCreateThread("networkReceive", networkBulkReceiveThread, this, &this->bulkReceiveThread)) { DEBUG_ERROR("Failed to create the network receive worker"); goto fail; } *result = this; return true; fail: networkConnectionDestroy(&this); return false; } void networkConnectionDestroy(NetworkConnection ** connection) { if (!connection || !*connection) return; NetworkConnection * this = *connection; LGThread * bulkReceiveThread; *connection = NULL; networkConnectionDisconnect(this); LG_LOCK(this->lock); this->bulkReceiveStopping = true; bulkReceiveThread = this->bulkReceiveThread; this->bulkReceiveThread = NULL; LG_UNLOCK(this->lock); if (this->bulkReceiveEvent) lgSignalEvent(this->bulkReceiveEvent); if (bulkReceiveThread && !lgJoinThread(bulkReceiveThread, NULL)) { DEBUG_ERROR("Failed to stop the network receive worker; " "preserving state"); return; } if (this->configuration) this->api->ConfigurationClose(this->configuration); if (this->registration) this->api->RegistrationClose(this->registration); if (this->api) MsQuicClose(this->api); if (this->bulkReceiveIdle) lgFreeEvent(this->bulkReceiveIdle); if (this->bulkReceiveEvent) lgFreeEvent(this->bulkReceiveEvent); if (this->closeEvent) lgFreeEvent(this->closeEvent); 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->datagramSendLock); LG_LOCK_FREE(this->lock); free(this->inputReceivePayload); free(this->inputReceiveData); free(this->receivePayload); free(this->receiveData); free(this->endpoint); if (this->password) networkAuthSecureZero(this->password, strlen(this->password)); free(this->password); free(this->address); free(this); } LG_TransportStatus networkConnectionConnect(NetworkConnection * this, LG_TransportSession * session, LG_TransportCancelledFn cancelled, void * cancelledOpaque) { if (!this || !session || this->connection) { DEBUG_ERROR("Invalid network connection state for connect"); return LG_TRANSPORT_ERROR; } if (cancelled && cancelled(cancelledOpaque)) return LG_TRANSPORT_DISCONNECTED; atomic_store_explicit(&this->stopping, false, memory_order_release); atomic_store_explicit(&this->sessionValid, false, memory_order_release); lgResetEvent(this->connectEvent); lgResetEvent(this->closeEvent); LG_LOCK(this->inputSendPoolLock); this->inputSendBlocked = 0; LG_UNLOCK(this->inputSendPoolLock); LG_LOCK(this->lock); this->connectStatus = LG_TRANSPORT_UNAVAILABLE; this->connecting = true; this->sessionEpoch = 0; this->sendSequence = 0; this->inputSendSequence = 0; this->inputReceiveSequence = 0; this->datagramSendSequence = 0; this->receiveSequence = 0; this->protocolVersion = 0; this->authMode = LG_NET_AUTH_NONE; this->helloAcknowledged = false; this->authenticationComplete = false; this->capabilitiesComplete = false; this->guestInfoReceived = false; this->connectFailed = false; this->expectedCapabilities = 0; this->receivedCapabilities = 0; this->receivedServices = 0; this->reliableServices = 0; this->datagramServices = 0; this->bulkServices = 0; this->maxPeerBulkStreams = 0; this->peerBulkStreams = 0; this->maxPayloadLength = NETWORK_MAX_NEGOTIATED_PAYLOAD; this->maxDatagramPayload = 0; atomic_store_explicit( &this->datagramSendState, 0, memory_order_release); atomic_store_explicit( &this->inputDatagramBlockedLength, 0, memory_order_release); 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, sizeof(this->servicePeerBulkStreams)); 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); this->session.os = LG_TRANSPORT_OS_OTHER; LG_UNLOCK(this->lock); QUIC_STATUS status = this->api->ConnectionOpen(this->registration, networkConnectionCallback, this, &this->connection); if (QUIC_FAILED(status)) { DEBUG_ERROR("MsQuic ConnectionOpen failed for %s: 0x%08x", this->endpoint, (unsigned)status); LG_LOCK(this->lock); this->connecting = false; this->connectStatus = LG_TRANSPORT_ERROR; LG_UNLOCK(this->lock); this->connection = NULL; return LG_TRANSPORT_ERROR; } if (this->allowUnencrypted) { const BOOLEAN disableEncryption = TRUE; status = this->api->SetParam(this->connection, QUIC_PARAM_CONN_DISABLE_1RTT_ENCRYPTION, sizeof(disableEncryption), &disableEncryption); if (QUIC_FAILED(status)) { DEBUG_ERROR("Failed to allow unencrypted 1-RTT payloads for %s: " "0x%08x", this->endpoint, (unsigned)status); this->api->ConnectionClose(this->connection); this->connection = NULL; LG_LOCK(this->lock); this->connecting = false; this->connectStatus = LG_TRANSPORT_ERROR; LG_UNLOCK(this->lock); return LG_TRANSPORT_ERROR; } } status = this->api->ConnectionStart(this->connection, this->configuration, QUIC_ADDRESS_FAMILY_UNSPEC, this->address, this->port); if (QUIC_FAILED(status)) { if (networkConnectionRecordTransportError(this, status, 0)) DEBUG_WARN("MsQuic ConnectionStart failed for %s: 0x%x", this->endpoint, (unsigned)status); this->api->ConnectionClose(this->connection); this->connection = NULL; LG_LOCK(this->lock); this->connecting = false; this->connectStatus = LG_TRANSPORT_UNAVAILABLE; LG_UNLOCK(this->lock); return LG_TRANSPORT_UNAVAILABLE; } while (!lgWaitEvent(this->connectEvent, NETWORK_CONNECT_POLL_MS)) if (cancelled && cancelled(cancelledOpaque)) { networkConnectionCancel(this); break; } LG_LOCK(this->lock); const LG_TransportStatus connectStatus = this->connectStatus; this->connecting = false; if (connectStatus == LG_TRANSPORT_OK) *session = this->session; LG_UNLOCK(this->lock); if (connectStatus != LG_TRANSPORT_OK) networkConnectionDisconnect(this); return connectStatus; } void networkConnectionCancel(NetworkConnection * this) { if (!this) return; atomic_store_explicit(&this->stopping, true, memory_order_release); if (this->trustCancel) this->trustCancel(this->opaque); LG_LOCK(this->stateCallbackLock); LG_LOCK(this->inputStreamLock); LG_LOCK(this->lock); HQUIC connection = this->connection; const bool certificatePending = this->certificatePending && connection; atomic_store_explicit(&this->sessionValid, false, memory_order_release); this->certificatePending = false; memset(this->pendingFingerprint, 0, sizeof(this->pendingFingerprint)); networkConnectionClearAuthProofNL(this); if (this->connecting) { this->connectStatus = LG_TRANSPORT_DISCONNECTED; this->connectFailed = true; } LG_UNLOCK(this->lock); LG_UNLOCK(this->inputStreamLock); LG_UNLOCK(this->stateCallbackLock); if (certificatePending) this->api->ConnectionCertificateValidationComplete(connection, false, QUIC_TLS_ALERT_CODE_BAD_CERTIFICATE); if (connection) this->api->ConnectionShutdown(connection, QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0); if (this->connectEvent) lgSignalEvent(this->connectEvent); } void networkConnectionDisconnect(NetworkConnection * this) { if (!this) return; networkConnectionCancel(this); LG_LOCK(this->lock); HQUIC connection = this->connection; LG_UNLOCK(this->lock); if (!connection) return; 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) { this->api->ConnectionClose(connection); atomic_store_explicit( &this->sessionValid, false, memory_order_release); if (this->state) this->state(this->opaque, false, LG_TRANSPORT_DISCONNECTED); } LG_UNLOCK(this->stateCallbackLock); } bool networkConnectionValid(const NetworkConnection * this) { return this && atomic_load_explicit( &this->sessionValid, memory_order_acquire); } bool networkConnectionServiceAvailable(NetworkConnection * this, LGNetService service, LGNetCapabilityFlags required) { if (!networkConnectionValid(this) || !networkConnectionServiceValid(service) || required & ~(LG_NET_CAPABILITY_RELIABLE | LG_NET_CAPABILITY_DATAGRAM | LG_NET_CAPABILITY_BULK)) return false; LG_LOCK(this->lock); const uint16_t serviceMask = UINT16_C(1) << service; const bool available = (this->receivedServices & serviceMask) && (!(required & LG_NET_CAPABILITY_RELIABLE) || (this->reliableServices & serviceMask)) && (!(required & LG_NET_CAPABILITY_DATAGRAM) || (this->datagramServices & serviceMask)) && (!(required & LG_NET_CAPABILITY_BULK) || (this->bulkServices & serviceMask)); LG_UNLOCK(this->lock); return available; } bool networkConnectionServiceVersion(NetworkConnection * this, LGNetService service, uint16_t * version) { if (!version || !networkConnectionValid(this) || !networkConnectionServiceValid(service)) return false; LG_LOCK(this->lock); const uint16_t serviceMask = UINT16_C(1) << service; const bool available = (this->receivedServices & serviceMask) != 0; if (available) *version = this->serviceVersions[service]; LG_UNLOCK(this->lock); return available; } bool networkConnectionAwaitInputSend(NetworkConnection * this, NetworkPacketDelivery delivery) { const uint8_t readyBit = networkConnectionInputReadyBit(delivery); if (!this || !readyBit) return false; LG_LOCK(this->inputSendPoolLock); const bool available = this->inputSendFree != NULL || (delivery == NETWORK_PACKET_DELIVERY_RELIABLE && this->inputReliableSendFree != NULL); if (!available) this->inputSendBlocked |= readyBit; LG_UNLOCK(this->inputSendPoolLock); return available; } LG_TransportStatus networkConnectionSend(NetworkConnection * this, LGNetService service, uint16_t messageType, uint16_t serviceVersion, LGNetEnvelopeFlags flags, uint64_t componentEpoch, uint64_t requestID, const void * payload, uint32_t payloadLength) { if (!this || !networkConnectionValid(this)) return LG_TRANSPORT_DISCONNECTED; return networkConnectionSendPacket(this, service, messageType, serviceVersion, flags, componentEpoch, requestID, payload, payloadLength, false); } static LG_TransportStatus networkConnectionSendInputDatagram( NetworkConnection * this, uint16_t messageType, uint16_t serviceVersion, LGNetEnvelopeFlags flags, uint64_t componentEpoch, uint64_t requestID, const void * payload, uint32_t payloadLength, uint64_t applicationSequence, bool discardIfBlocked) { const size_t packetSize = LG_NET_ENVELOPE_WIRE_SIZE + payloadLength; const uint16_t packetLength = (uint16_t)packetSize; struct NetworkSend * send = networkConnectionAllocInputSend( this, packetSize, NETWORK_PACKET_DELIVERY_DATAGRAM); if (!send) return LG_TRANSPORT_UNAVAILABLE; 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_DATAGRAM | LG_NET_ENVELOPE_FINAL; parameters.componentEpoch = componentEpoch; parameters.requestID = requestID; LG_LOCK(this->inputStreamLock); const uint16_t serviceMask = UINT16_C(1) << LG_NET_SERVICE_INPUT; HQUIC connection = this->connection; if (!connection || !atomic_load_explicit(&this->sessionValid, memory_order_acquire)) { LG_UNLOCK(this->inputStreamLock); networkConnectionFreeSend(send); return LG_TRANSPORT_DISCONNECTED; } if (!(this->receivedServices & serviceMask) || !(this->datagramServices & serviceMask) || this->serviceVersions[LG_NET_SERVICE_INPUT] != serviceVersion || payloadLength > this->serviceMaxDatagram[LG_NET_SERVICE_INPUT]) { LG_UNLOCK(this->inputStreamLock); networkConnectionFreeSend(send); return LG_TRANSPORT_UNAVAILABLE; } LG_LOCK(this->datagramSendLock); if (!networkConnectionPrepareInputDatagram(this, packetLength)) { LG_UNLOCK(this->datagramSendLock); LG_UNLOCK(this->inputStreamLock); networkConnectionFreeSend(send); return LG_TRANSPORT_UNAVAILABLE; } uint64_t sequence; if (!networkConnectionNextDatagramSequence(this, &sequence)) { LG_UNLOCK(this->datagramSendLock); LG_UNLOCK(this->inputStreamLock); networkConnectionFreeSend(send); return LG_TRANSPORT_ERROR; } parameters.sessionEpoch = this->sessionEpoch; parameters.sequence = sequence; send->length = (uint32_t)packetSize; send->componentEpoch = componentEpoch; send->applicationSequence = applicationSequence; send->accounted = false; size_t wireSize; if (!lgNetPacketConstruct(data, packetSize, &wireSize, ¶meters, payload, payloadLength) || wireSize != packetSize) { --this->datagramSendSequence; LG_UNLOCK(this->datagramSendLock); LG_UNLOCK(this->inputStreamLock); networkConnectionFreeSend(send); return LG_TRANSPORT_ERROR; } send->buffer.Buffer = data; send->buffer.Length = (uint32_t)packetSize; QUIC_SEND_FLAGS sendFlags = QUIC_SEND_FLAG_PRIORITY_WORK | QUIC_SEND_FLAG_DGRAM_PRIORITY; if (discardIfBlocked) sendFlags |= QUIC_SEND_FLAG_CANCEL_ON_BLOCKED; const QUIC_STATUS status = this->api->DatagramSend(connection, &send->buffer, 1, sendFlags, send); if (QUIC_FAILED(status)) --this->datagramSendSequence; LG_UNLOCK(this->datagramSendLock); LG_UNLOCK(this->inputStreamLock); if (QUIC_FAILED(status)) { networkConnectionFreeSend(send); return LG_TRANSPORT_ERROR; } return LG_TRANSPORT_OK; } 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, uint64_t applicationSequence, bool discardIfBlocked) { if (!this || !networkConnectionValid(this)) return LG_TRANSPORT_DISCONNECTED; LGNetEnvelope routeEnvelope; lgNetEnvelopeInit(&routeEnvelope, service, messageType, serviceVersion, LG_NET_MESSAGE_VERSION_CURRENT); if ((payloadLength && !payload) || !networkConnectionServiceValid(service) || flags & (LG_NET_ENVELOPE_RELIABLE | LG_NET_ENVELOPE_DATAGRAM | LG_NET_ENVELOPE_EARLY_DATA) || !lgNetEnvelopeRouteValid(&routeEnvelope, LG_NET_ROLE_CLIENT, LG_NET_ROUTE_DELIVERY_DATAGRAM)) return LG_TRANSPORT_ERROR; if (payloadLength > LG_NET_MAX_PAYLOAD_LENGTH) return LG_TRANSPORT_ERROR; if (service == LG_NET_SERVICE_INPUT) return networkConnectionSendInputDatagram(this, messageType, serviceVersion, flags, componentEpoch, requestID, payload, payloadLength, applicationSequence, discardIfBlocked); const size_t packetSize = LG_NET_ENVELOPE_WIRE_SIZE + payloadLength; LGNetPacketParameters parameters; lgNetPacketParametersInit(¶meters, service, messageType, serviceVersion, LG_NET_MESSAGE_VERSION_CURRENT); parameters.flags = flags | LG_NET_ENVELOPE_DATAGRAM | LG_NET_ENVELOPE_FINAL; parameters.componentEpoch = componentEpoch; parameters.requestID = requestID; struct NetworkSend * send = networkConnectionAllocSend( this, packetSize); if (!send) return LG_TRANSPORT_ERROR; uint8_t * data = networkSendData(send); LG_LOCK(this->lock); const uint16_t serviceMask = UINT16_C(1) << service; HQUIC connection = this->connection; if (!connection || !atomic_load_explicit(&this->sessionValid, memory_order_acquire)) { LG_UNLOCK(this->lock); networkConnectionFreeSend(send); return LG_TRANSPORT_DISCONNECTED; } if (!(this->receivedServices & serviceMask) || !(this->datagramServices & serviceMask) || this->serviceVersions[service] != serviceVersion || payloadLength > this->serviceMaxDatagram[service]) { LG_UNLOCK(this->lock); networkConnectionFreeSend(send); return LG_TRANSPORT_UNAVAILABLE; } if (!networkConnectionDatagramAvailable(this, packetSize)) { const struct NetworkSendLimits limits = networkConnectionSendLimits( service, NETWORK_PACKET_DELIVERY_DATAGRAM); if (packetSize <= limits.bytes && packetSize <= NETWORK_SEND_DATAGRAM_BYTES_MAX) { this->datagramSend.serviceBlocked[service] = true; this->datagramSend.serviceBlockedBytes[service] = this->datagramSend.serviceBlockedBytes[service] > packetSize ? this->datagramSend.serviceBlockedBytes[service] : packetSize; } LG_UNLOCK(this->lock); networkConnectionFreeSend(send); return LG_TRANSPORT_UNAVAILABLE; } const LG_TransportStatus reserveStatus = networkConnectionReserveSendNL(this, service, NETWORK_PACKET_DELIVERY_DATAGRAM, packetSize); if (reserveStatus != LG_TRANSPORT_OK) { LG_UNLOCK(this->lock); networkConnectionFreeSend(send); return reserveStatus; } send->service = service; send->length = (uint32_t)packetSize; send->componentEpoch = componentEpoch; send->applicationSequence = applicationSequence; send->delivery = NETWORK_PACKET_DELIVERY_DATAGRAM; send->accounted = true; LG_LOCK(this->datagramSendLock); uint64_t sequence; if (!networkConnectionNextDatagramSequence(this, &sequence)) { const uint16_t ready = networkConnectionReleaseSendNL(this, send); LG_UNLOCK(this->datagramSendLock); LG_UNLOCK(this->lock); networkConnectionFreeSend(send); networkConnectionNotifySendReady(this, 0, ready, 0); return LG_TRANSPORT_ERROR; } parameters.sessionEpoch = this->sessionEpoch; parameters.sequence = sequence; size_t wireSize; if (!lgNetPacketConstruct(data, packetSize, &wireSize, ¶meters, payload, payloadLength) || wireSize != packetSize) { --this->datagramSendSequence; const uint16_t ready = networkConnectionReleaseSendNL(this, send); LG_UNLOCK(this->datagramSendLock); LG_UNLOCK(this->lock); networkConnectionFreeSend(send); networkConnectionNotifySendReady(this, 0, ready, 0); return LG_TRANSPORT_ERROR; } send->buffer.Buffer = data; send->buffer.Length = (uint32_t)packetSize; QUIC_SEND_FLAGS sendFlags = QUIC_SEND_FLAG_PRIORITY_WORK; if (discardIfBlocked) sendFlags |= QUIC_SEND_FLAG_CANCEL_ON_BLOCKED; const QUIC_STATUS status = this->api->DatagramSend(connection, &send->buffer, 1, sendFlags, send); uint16_t ready = 0; if (QUIC_FAILED(status)) { --this->datagramSendSequence; ready = networkConnectionReleaseSendNL(this, send); } LG_UNLOCK(this->datagramSendLock); LG_UNLOCK(this->lock); if (QUIC_FAILED(status)) { networkConnectionFreeSend(send); networkConnectionNotifySendReady(this, 0, ready, 0); return LG_TRANSPORT_ERROR; } return LG_TRANSPORT_OK; } LG_TransportStatus networkConnectionSendBulk(NetworkConnection * this, LGNetService service, uint16_t messageType, uint16_t serviceVersion, LGNetEnvelopeFlags flags, uint64_t componentEpoch, uint64_t requestID, const void * payload, uint32_t payloadLength) { if (!this || !networkConnectionValid(this)) return LG_TRANSPORT_DISCONNECTED; LGNetEnvelope routeEnvelope; lgNetEnvelopeInit(&routeEnvelope, service, messageType, serviceVersion, LG_NET_MESSAGE_VERSION_CURRENT); if ((payloadLength && !payload) || !networkConnectionServiceValid(service) || flags & (LG_NET_ENVELOPE_RELIABLE | LG_NET_ENVELOPE_DATAGRAM | LG_NET_ENVELOPE_EARLY_DATA) || !lgNetEnvelopeRouteValid(&routeEnvelope, LG_NET_ROLE_CLIENT, LG_NET_ROUTE_DELIVERY_BULK) || payloadLength > LG_NET_MAX_PAYLOAD_LENGTH) return LG_TRANSPORT_ERROR; const size_t packetSize = LG_NET_ENVELOPE_WIRE_SIZE + payloadLength; const struct NetworkSendLimits limits = networkConnectionSendLimits(service, NETWORK_PACKET_DELIVERY_BULK); if (packetSize > limits.bytes || packetSize > NETWORK_SEND_BULK_BYTES_MAX) return LG_TRANSPORT_ERROR; LGNetPacketParameters parameters; lgNetPacketParametersInit(¶meters, service, messageType, serviceVersion, LG_NET_MESSAGE_VERSION_CURRENT); parameters.flags = flags | LG_NET_ENVELOPE_FINAL; parameters.componentEpoch = componentEpoch; parameters.requestID = requestID; retry: LG_LOCK(this->lock); const uint16_t serviceMask = UINT16_C(1) << service; if (!this->connection || atomic_load_explicit(&this->stopping, memory_order_acquire) || !atomic_load_explicit(&this->sessionValid, memory_order_acquire)) { LG_UNLOCK(this->lock); return LG_TRANSPORT_DISCONNECTED; } if (!(this->receivedServices & serviceMask) || !(this->bulkServices & serviceMask) || !this->serviceMaxUnidirectional[service] || this->serviceVersions[service] != serviceVersion || payloadLength > this->serviceMaxPayload[service]) { LG_UNLOCK(this->lock); return LG_TRANSPORT_UNAVAILABLE; } struct NetworkBulkStream * stream = this->outboundBulk[service]; if (!stream) { LG_UNLOCK(this->lock); const LG_TransportStatus openStatus = networkConnectionOpenBulkStream(this, service, packetSize); if (openStatus == LG_TRANSPORT_OK) goto retry; return openStatus; } if (!stream->bound) { stream->openingReturned = true; networkConnectionMarkSendBlockedNL(this, service, NETWORK_PACKET_DELIVERY_BULK, packetSize); LG_UNLOCK(this->lock); return LG_TRANSPORT_UNAVAILABLE; } if (stream->sendSequence == UINT64_MAX) { LG_UNLOCK(this->lock); return LG_TRANSPORT_ERROR; } const LG_TransportStatus reserveStatus = networkConnectionReserveSendNL(this, service, NETWORK_PACKET_DELIVERY_BULK, packetSize); if (reserveStatus != LG_TRANSPORT_OK) { LG_UNLOCK(this->lock); return reserveStatus; } struct NetworkSend * send = networkConnectionAllocSend( this, packetSize); if (!send) { struct NetworkSend rollback = { .service = service, .length = (uint32_t)packetSize, .delivery = NETWORK_PACKET_DELIVERY_BULK, .accounted = true, }; const uint16_t ready = networkConnectionReleaseSendNL(this, &rollback); LG_UNLOCK(this->lock); networkConnectionNotifySendReady(this, 0, 0, ready); return LG_TRANSPORT_ERROR; } parameters.sessionEpoch = this->sessionEpoch; parameters.sequence = ++stream->sendSequence; send->service = service; send->length = (uint32_t)packetSize; send->delivery = NETWORK_PACKET_DELIVERY_BULK; send->accounted = true; 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); networkConnectionFreeSend(send); networkConnectionNotifySendReady(this, 0, 0, ready); return LG_TRANSPORT_ERROR; } 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); uint16_t ready = 0; if (QUIC_FAILED(status)) { --stream->sendSequence; ready = networkConnectionReleaseSendNL(this, send); } LG_UNLOCK(this->lock); if (QUIC_FAILED(status)) { networkConnectionFreeSend(send); networkConnectionNotifySendReady(this, 0, 0, ready); return LG_TRANSPORT_ERROR; } return LG_TRANSPORT_OK; }