mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-11-12 23:07:21 +00:00
3182 lines
106 KiB
C
3182 lines
106 KiB
C
/**
|
|
* 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 <LGProtocol/NetworkAuth.h>
|
|
#include <LGProtocol/NetworkPacket.h>
|
|
#include <LGProtocol/NetworkRoute.h>
|
|
#include <LGProtocol/NetworkSequence.h>
|
|
#include <LGProtocol/NetworkServices.h>
|
|
#include <LGProtocol/NetworkStream.h>
|
|
|
|
#include <msquic.h>
|
|
|
|
#if !defined(QUIC_API_VERSION_2)
|
|
#error "The network transport requires MsQuic API version 2"
|
|
#endif
|
|
|
|
#include <stdatomic.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define NETWORK_CONNECT_POLL_MS 50U
|
|
#define NETWORK_HANDSHAKE_TIMEOUT_MS 5000U
|
|
#define NETWORK_IDLE_TIMEOUT_MS 10000U
|
|
#define NETWORK_KEEPALIVE_INTERVAL_MS 1000U
|
|
#define NETWORK_INITIAL_RTT_MS 1U
|
|
#define NETWORK_MAX_PEER_STREAMS 16U
|
|
#define NETWORK_MAX_CONTROL_PAYLOAD \
|
|
(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_DATAGRAM_PAYLOAD 1136U
|
|
#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
|
|
|
|
_Static_assert(NETWORK_AUTH_SHA256_SIZE == LG_NET_PASSWORD_SHA256_LENGTH,
|
|
"Network authentication SHA-256 sizes differ");
|
|
|
|
struct NetworkSend
|
|
{
|
|
QUIC_BUFFER buffer;
|
|
LGNetService service;
|
|
uint32_t length;
|
|
NetworkPacketDelivery delivery;
|
|
bool accounted;
|
|
uint8_t data[];
|
|
};
|
|
|
|
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;
|
|
uint8_t bindingData[LG_NET_STREAM_BINDING_WIRE_SIZE];
|
|
size_t bindingLength;
|
|
LGNetStreamParser receiveParser;
|
|
uint8_t * receiveData;
|
|
uint8_t * receivePayload;
|
|
size_t receivePayloadSize;
|
|
};
|
|
|
|
struct NetworkConnection
|
|
{
|
|
const QUIC_API_TABLE * api;
|
|
HQUIC registration;
|
|
HQUIC configuration;
|
|
HQUIC connection;
|
|
HQUIC controlStream;
|
|
|
|
LGEvent * connectEvent;
|
|
LGEvent * closeEvent;
|
|
LG_Lock lock;
|
|
LG_Lock stateCallbackLock;
|
|
|
|
atomic_bool stopping;
|
|
atomic_bool sessionValid;
|
|
|
|
char * address;
|
|
uint16_t port;
|
|
char * password;
|
|
char * endpoint;
|
|
|
|
NetworkKnownHosts * knownHosts;
|
|
NetworkTrustPromptFn trustPrompt;
|
|
NetworkTrustCancelFn trustCancel;
|
|
NetworkPacketFn packet;
|
|
NetworkConnectionStateFn state;
|
|
NetworkSendReadyFn sendReady;
|
|
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 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 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 serviceMaxUnidirectional
|
|
[LG_NET_SERVICE_CONTROL + 1U];
|
|
uint16_t servicePeerBulkStreams
|
|
[LG_NET_SERVICE_CONTROL + 1U];
|
|
uint32_t maxPayloadLength;
|
|
uint32_t maxDatagramPayload;
|
|
uint16_t maxDatagramSendLength;
|
|
bool datagramSendEnabled;
|
|
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 NetworkBulkStream * outboundBulk
|
|
[LG_NET_SERVICE_CONTROL + 1U];
|
|
struct NetworkBulkStream * inboundBulk;
|
|
|
|
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;
|
|
};
|
|
|
|
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 networkBulkStreamCallback(HQUIC handle,
|
|
void * opaque, QUIC_STREAM_EVENT * event);
|
|
static QUIC_STATUS QUIC_API networkRejectedStreamCallback(HQUIC handle,
|
|
void * opaque, QUIC_STREAM_EVENT * event);
|
|
|
|
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 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->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 &&
|
|
!this->controlStream) ||
|
|
(delivery == NETWORK_PACKET_DELIVERY_DATAGRAM &&
|
|
(!this->datagramSendEnabled ||
|
|
blockedBytes > this->maxDatagramSendLength)) ||
|
|
(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, const struct NetworkSend * send)
|
|
{
|
|
if (!send->accounted)
|
|
return 0;
|
|
|
|
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 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;
|
|
|
|
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;
|
|
|
|
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);
|
|
return LG_TRANSPORT_DISCONNECTED;
|
|
}
|
|
if (!serviceReady || payloadLength > maxPayloadLength)
|
|
{
|
|
LG_UNLOCK(this->lock);
|
|
return LG_TRANSPORT_UNAVAILABLE;
|
|
}
|
|
if (this->sendSequence == UINT64_MAX)
|
|
{
|
|
LG_UNLOCK(this->lock);
|
|
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);
|
|
return reserveStatus;
|
|
}
|
|
|
|
struct NetworkSend * send = malloc(sizeof(*send) + packetSize);
|
|
if (!send)
|
|
{
|
|
const struct NetworkSend rollback =
|
|
{
|
|
.service = service,
|
|
.length = (uint32_t)packetSize,
|
|
.delivery = NETWORK_PACKET_DELIVERY_RELIABLE,
|
|
.accounted = true,
|
|
};
|
|
const uint16_t ready = networkConnectionReleaseSendNL(this, &rollback);
|
|
LG_UNLOCK(this->lock);
|
|
networkConnectionNotifySendReady(this, ready, 0, 0);
|
|
return LG_TRANSPORT_ERROR;
|
|
}
|
|
|
|
parameters.sessionEpoch = this->sessionEpoch;
|
|
parameters.sequence = ++this->sendSequence;
|
|
send->service = service;
|
|
send->length = (uint32_t)packetSize;
|
|
send->delivery = NETWORK_PACKET_DELIVERY_RELIABLE;
|
|
send->accounted = true;
|
|
size_t wireSize;
|
|
if (!lgNetPacketConstruct(send->data, packetSize, &wireSize,
|
|
¶meters, payload, payloadLength) || wireSize != packetSize)
|
|
{
|
|
--this->sendSequence;
|
|
const uint16_t ready = networkConnectionReleaseSendNL(this, send);
|
|
LG_UNLOCK(this->lock);
|
|
free(send);
|
|
networkConnectionNotifySendReady(this, ready, 0, 0);
|
|
return LG_TRANSPORT_ERROR;
|
|
}
|
|
|
|
send->buffer.Buffer = send->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))
|
|
{
|
|
free(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_CONTROL_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.maxBidirectionalStreams >
|
|
offered.maxBidirectionalStreams ||
|
|
capability.maxUnidirectionalStreams >
|
|
offered.maxUnidirectionalStreams ||
|
|
capability.maxBidirectionalStreams ||
|
|
(!!(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->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)
|
|
{
|
|
return this->helloAcknowledged &&
|
|
this->authenticationComplete &&
|
|
this->capabilitiesComplete &&
|
|
this->receivedCapabilities == this->expectedCapabilities &&
|
|
networkConnectionRequiredCapabilitiesSelected(this);
|
|
}
|
|
|
|
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_CONTROL_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)
|
|
return false;
|
|
return networkConnectionHandleCapability(this, packet);
|
|
|
|
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->payloadLength)
|
|
return false;
|
|
|
|
bool ready;
|
|
LG_LOCK(this->lock);
|
|
const bool valid = !this->capabilitiesComplete &&
|
|
this->helloAcknowledged &&
|
|
this->receivedCapabilities == this->expectedCapabilities &&
|
|
networkConnectionRequiredCapabilitiesSelected(this);
|
|
this->capabilitiesComplete = valid;
|
|
ready =
|
|
valid && networkConnectionReadyToActivateNL(this);
|
|
if (ready)
|
|
networkConnectionSetSessionFeatures(this);
|
|
LG_UNLOCK(this->lock);
|
|
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 <= 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 || !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 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 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)
|
|
{
|
|
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 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))
|
|
{
|
|
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)
|
|
return QUIC_STATUS_BAD_CERTIFICATE;
|
|
|
|
uint8_t fingerprint[NETWORK_FINGERPRINT_SIZE];
|
|
char fingerprintText[NETWORK_FINGERPRINT_TEXT_SIZE];
|
|
if (!networkFingerprintSHA256(certificate->Buffer,
|
|
certificate->Length, fingerprint))
|
|
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;
|
|
}
|
|
|
|
HQUIC stream = NULL;
|
|
QUIC_STATUS status = this->api->StreamOpen(handle,
|
|
QUIC_STREAM_OPEN_FLAG_NONE, networkStreamCallback, this, &stream);
|
|
if (QUIC_FAILED(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))
|
|
{
|
|
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 (error && 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:
|
|
{
|
|
uint16_t ready;
|
|
LG_LOCK(this->lock);
|
|
this->datagramSendEnabled =
|
|
event->DATAGRAM_STATE_CHANGED.SendEnabled != FALSE;
|
|
this->maxDatagramSendLength =
|
|
event->DATAGRAM_STATE_CHANGED.MaxSendLength;
|
|
ready =
|
|
networkConnectionReadyServicesNL(
|
|
this, NETWORK_PACKET_DELIVERY_DATAGRAM);
|
|
LG_UNLOCK(this->lock);
|
|
networkConnectionNotifySendReady(this, 0, ready, 0);
|
|
return QUIC_STATUS_SUCCESS;
|
|
}
|
|
|
|
case QUIC_CONNECTION_EVENT_DATAGRAM_SEND_STATE_CHANGED:
|
|
if (QUIC_DATAGRAM_SEND_STATE_IS_FINAL(
|
|
event->DATAGRAM_SEND_STATE_CHANGED.State))
|
|
{
|
|
struct NetworkSend * send =
|
|
event->DATAGRAM_SEND_STATE_CHANGED.ClientContext;
|
|
LG_LOCK(this->lock);
|
|
const uint16_t ready =
|
|
networkConnectionReleaseSendNL(this, send);
|
|
LG_UNLOCK(this->lock);
|
|
free(send);
|
|
networkConnectionNotifySendReady(this, 0, ready, 0);
|
|
}
|
|
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) ||
|
|
!networkConnectionSendHello(this))
|
|
{
|
|
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))
|
|
{
|
|
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;
|
|
LG_LOCK(this->lock);
|
|
const uint16_t ready = networkConnectionReleaseSendNL(this, send);
|
|
LG_UNLOCK(this->lock);
|
|
free(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:
|
|
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 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 = malloc(
|
|
sizeof(*send) + LG_NET_STREAM_BINDING_WIRE_SIZE);
|
|
if (!send || !lgNetStreamBindingEncode(send->data,
|
|
LG_NET_STREAM_BINDING_WIRE_SIZE, &binding))
|
|
{
|
|
free(send);
|
|
networkConnectionProtocolFailure(this);
|
|
return QUIC_STATUS_SUCCESS;
|
|
}
|
|
|
|
send->buffer.Buffer = send->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))
|
|
{
|
|
free(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 || !networkBulkStreamReceive(stream,
|
|
event->RECEIVE.Buffers, event->RECEIVE.BufferCount))
|
|
networkConnectionProtocolFailure(this);
|
|
return QUIC_STATUS_SUCCESS;
|
|
|
|
case QUIC_STREAM_EVENT_SEND_COMPLETE:
|
|
{
|
|
struct NetworkSend * send = event->SEND_COMPLETE.ClientContext;
|
|
LG_LOCK(this->lock);
|
|
const uint16_t ready = networkConnectionReleaseSendNL(this, send);
|
|
LG_UNLOCK(this->lock);
|
|
free(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 deferFree =
|
|
stream->outbound && !stream->openingReturned;
|
|
stream->shutdownComplete = deferFree;
|
|
LG_UNLOCK(this->lock);
|
|
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))
|
|
return false;
|
|
|
|
NetworkConnection * this = calloc(1, sizeof(*this));
|
|
if (!this)
|
|
return false;
|
|
|
|
LG_LOCK_INIT(this->lock);
|
|
LG_LOCK_INIT(this->stateCallbackLock);
|
|
atomic_init(&this->stopping, false);
|
|
atomic_init(&this->sessionValid, false);
|
|
|
|
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)
|
|
goto fail;
|
|
|
|
const uint16_t serviceMask = UINT16_C(1) << capability->service;
|
|
if (this->offeredServices & serviceMask)
|
|
goto fail;
|
|
this->offeredCapabilities[capability->service] = *capability;
|
|
this->offeredServices |= serviceMask;
|
|
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)
|
|
goto fail;
|
|
|
|
this->port = params->port;
|
|
this->knownHosts = params->knownHosts;
|
|
this->trustPrompt = params->trustPrompt;
|
|
this->trustCancel = params->trustCancel;
|
|
this->packet = params->packet;
|
|
this->state = params->state;
|
|
this->sendReady = params->sendReady;
|
|
this->opaque = params->opaque;
|
|
|
|
this->connectEvent = lgCreateEvent(false, 0);
|
|
this->closeEvent = lgCreateEvent(false, 0);
|
|
if (!this->connectEvent || !this->closeEvent)
|
|
goto fail;
|
|
|
|
this->receiveData = malloc(NETWORK_MAX_CONTROL_BUFFER);
|
|
this->receivePayload = malloc(NETWORK_MAX_CONTROL_PAYLOAD);
|
|
if (!this->receiveData || !this->receivePayload ||
|
|
!lgNetStreamParserInit(&this->receiveParser, this->receiveData,
|
|
NETWORK_MAX_CONTROL_BUFFER, NETWORK_MAX_CONTROL_PAYLOAD))
|
|
goto fail;
|
|
|
|
if (QUIC_FAILED(MsQuicOpen2(&this->api)))
|
|
goto fail;
|
|
|
|
const QUIC_REGISTRATION_CONFIG registration =
|
|
{
|
|
.AppName = "looking-glass-client",
|
|
.ExecutionProfile = QUIC_EXECUTION_PROFILE_LOW_LATENCY,
|
|
};
|
|
if (QUIC_FAILED(this->api->RegistrationOpen(
|
|
®istration, &this->registration)))
|
|
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,
|
|
.InitialRttMs = true,
|
|
.KeepAliveIntervalMs = true,
|
|
.PeerBidiStreamCount = true,
|
|
.PeerUnidiStreamCount = true,
|
|
.DatagramReceiveEnabled = true,
|
|
},
|
|
.HandshakeIdleTimeoutMs = NETWORK_HANDSHAKE_TIMEOUT_MS,
|
|
.IdleTimeoutMs = NETWORK_IDLE_TIMEOUT_MS,
|
|
.InitialRttMs = NETWORK_INITIAL_RTT_MS,
|
|
.KeepAliveIntervalMs = NETWORK_KEEPALIVE_INTERVAL_MS,
|
|
.PeerBidiStreamCount = NETWORK_MAX_PEER_STREAMS,
|
|
.PeerUnidiStreamCount = NETWORK_MAX_PEER_STREAMS,
|
|
.DatagramReceiveEnabled = true,
|
|
};
|
|
if (QUIC_FAILED(this->api->ConfigurationOpen(this->registration,
|
|
&alpn, 1, &settings, sizeof(settings), NULL,
|
|
&this->configuration)))
|
|
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,
|
|
};
|
|
if (QUIC_FAILED(this->api->ConfigurationLoadCredential(
|
|
this->configuration, &credential)))
|
|
goto fail;
|
|
|
|
*result = this;
|
|
return true;
|
|
|
|
fail:
|
|
networkConnectionDestroy(&this);
|
|
return false;
|
|
}
|
|
|
|
void networkConnectionDestroy(NetworkConnection ** connection)
|
|
{
|
|
if (!connection || !*connection)
|
|
return;
|
|
|
|
NetworkConnection * this = *connection;
|
|
networkConnectionDisconnect(this);
|
|
if (this->configuration)
|
|
this->api->ConfigurationClose(this->configuration);
|
|
if (this->registration)
|
|
this->api->RegistrationClose(this->registration);
|
|
if (this->api)
|
|
MsQuicClose(this->api);
|
|
if (this->closeEvent)
|
|
lgFreeEvent(this->closeEvent);
|
|
if (this->connectEvent)
|
|
lgFreeEvent(this->connectEvent);
|
|
LG_LOCK_FREE(this->stateCallbackLock);
|
|
LG_LOCK_FREE(this->lock);
|
|
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);
|
|
*connection = NULL;
|
|
}
|
|
|
|
LG_TransportStatus networkConnectionConnect(NetworkConnection * this,
|
|
LG_TransportSession * session, LG_TransportCancelledFn cancelled,
|
|
void * cancelledOpaque)
|
|
{
|
|
if (!this || !session || this->connection)
|
|
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->lock);
|
|
this->connectStatus = LG_TRANSPORT_UNAVAILABLE;
|
|
this->connecting = true;
|
|
this->sessionEpoch = 0;
|
|
this->sendSequence = 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->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_CONTROL_PAYLOAD;
|
|
this->maxDatagramPayload = 0;
|
|
this->maxDatagramSendLength = 0;
|
|
this->datagramSendEnabled = false;
|
|
networkConnectionResetSendAccountingNL(this);
|
|
networkConnectionClearAuthProofNL(this);
|
|
memset(this->serviceVersions, 0, sizeof(this->serviceVersions));
|
|
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);
|
|
memset(&this->session, 0, sizeof(this->session));
|
|
snprintf(this->session.version, sizeof(this->session.version),
|
|
"LG network protocol %u", LG_NET_PROTOCOL_VERSION_CURRENT);
|
|
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))
|
|
{
|
|
LG_LOCK(this->lock);
|
|
this->connecting = false;
|
|
this->connectStatus = LG_TRANSPORT_ERROR;
|
|
LG_UNLOCK(this->lock);
|
|
this->connection = NULL;
|
|
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->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->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);
|
|
LG_LOCK(this->stateCallbackLock);
|
|
LG_LOCK(this->lock);
|
|
const bool ownsConnection = this->connection == connection;
|
|
if (ownsConnection)
|
|
{
|
|
this->connection = NULL;
|
|
this->controlStream = NULL;
|
|
this->certificatePending = false;
|
|
networkConnectionResetSendAccountingNL(this);
|
|
memset(this->pendingFingerprint, 0, sizeof(this->pendingFingerprint));
|
|
}
|
|
LG_UNLOCK(this->lock);
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
LG_TransportStatus networkConnectionSendDatagram(NetworkConnection * this,
|
|
LGNetService service, uint16_t messageType, uint16_t serviceVersion,
|
|
LGNetEnvelopeFlags flags, uint64_t componentEpoch, uint64_t requestID,
|
|
const void * payload, uint32_t payloadLength, bool discardIfBlocked)
|
|
{
|
|
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;
|
|
|
|
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;
|
|
|
|
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);
|
|
return LG_TRANSPORT_DISCONNECTED;
|
|
}
|
|
if (!(this->receivedServices & serviceMask) ||
|
|
!(this->datagramServices & serviceMask) ||
|
|
this->serviceVersions[service] != serviceVersion ||
|
|
payloadLength > this->serviceMaxDatagram[service])
|
|
{
|
|
LG_UNLOCK(this->lock);
|
|
return LG_TRANSPORT_UNAVAILABLE;
|
|
}
|
|
if (this->datagramSendSequence == UINT64_MAX)
|
|
{
|
|
LG_UNLOCK(this->lock);
|
|
return LG_TRANSPORT_ERROR;
|
|
}
|
|
if (!this->datagramSendEnabled ||
|
|
packetSize > this->maxDatagramSendLength)
|
|
{
|
|
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);
|
|
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);
|
|
return reserveStatus;
|
|
}
|
|
|
|
struct NetworkSend * send = malloc(sizeof(*send) + packetSize);
|
|
if (!send)
|
|
{
|
|
const struct NetworkSend rollback =
|
|
{
|
|
.service = service,
|
|
.length = (uint32_t)packetSize,
|
|
.delivery = NETWORK_PACKET_DELIVERY_DATAGRAM,
|
|
.accounted = true,
|
|
};
|
|
const uint16_t ready = networkConnectionReleaseSendNL(this, &rollback);
|
|
LG_UNLOCK(this->lock);
|
|
networkConnectionNotifySendReady(this, 0, ready, 0);
|
|
return LG_TRANSPORT_ERROR;
|
|
}
|
|
|
|
parameters.sessionEpoch = this->sessionEpoch;
|
|
parameters.sequence = ++this->datagramSendSequence;
|
|
send->service = service;
|
|
send->length = (uint32_t)packetSize;
|
|
send->delivery = NETWORK_PACKET_DELIVERY_DATAGRAM;
|
|
send->accounted = true;
|
|
size_t wireSize;
|
|
if (!lgNetPacketConstruct(send->data, packetSize, &wireSize,
|
|
¶meters, payload, payloadLength) || wireSize != packetSize)
|
|
{
|
|
--this->datagramSendSequence;
|
|
const uint16_t ready = networkConnectionReleaseSendNL(this, send);
|
|
LG_UNLOCK(this->lock);
|
|
free(send);
|
|
networkConnectionNotifySendReady(this, 0, ready, 0);
|
|
return LG_TRANSPORT_ERROR;
|
|
}
|
|
|
|
send->buffer.Buffer = send->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->lock);
|
|
|
|
if (QUIC_FAILED(status))
|
|
{
|
|
free(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 = malloc(sizeof(*send) + packetSize);
|
|
if (!send)
|
|
{
|
|
const 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;
|
|
size_t wireSize;
|
|
if (!lgNetPacketConstruct(send->data, packetSize, &wireSize,
|
|
¶meters, payload, payloadLength) || wireSize != packetSize)
|
|
{
|
|
--stream->sendSequence;
|
|
const uint16_t ready = networkConnectionReleaseSendNL(this, send);
|
|
LG_UNLOCK(this->lock);
|
|
free(send);
|
|
networkConnectionNotifySendReady(this, 0, 0, ready);
|
|
return LG_TRANSPORT_ERROR;
|
|
}
|
|
|
|
send->buffer.Buffer = send->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))
|
|
{
|
|
free(send);
|
|
networkConnectionNotifySendReady(this, 0, 0, ready);
|
|
return LG_TRANSPORT_ERROR;
|
|
}
|
|
return LG_TRANSPORT_OK;
|
|
}
|