mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-11-13 15:27:21 +00:00
[client/idd] network: add recovery, cursor, and control
This commit is contained in:
@@ -7,9 +7,12 @@ endif()
|
||||
|
||||
set(TRANSPORT_NETWORK_SOURCES
|
||||
connection.c
|
||||
control.c
|
||||
cursor.c
|
||||
input.c
|
||||
known_hosts.c
|
||||
network.c
|
||||
recovery.c
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <LGProtocol/NetworkPacket.h>
|
||||
#include <LGProtocol/NetworkRoute.h>
|
||||
#include <LGProtocol/NetworkSequence.h>
|
||||
#include <LGProtocol/NetworkServices.h>
|
||||
#include <LGProtocol/NetworkStream.h>
|
||||
|
||||
#include <msquic.h>
|
||||
@@ -49,7 +50,8 @@
|
||||
#define NETWORK_KEEPALIVE_INTERVAL_MS 1000U
|
||||
#define NETWORK_INITIAL_RTT_MS 1U
|
||||
#define NETWORK_MAX_PEER_STREAMS 16U
|
||||
#define NETWORK_MAX_CONTROL_PAYLOAD (1024U * 1024U)
|
||||
#define NETWORK_MAX_CONTROL_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
|
||||
@@ -98,7 +100,7 @@ struct NetworkConnection
|
||||
NetworkConnectionStateFn state;
|
||||
void * opaque;
|
||||
|
||||
LGNetCapability offeredCapabilities[LG_NET_SERVICE_USB + 1U];
|
||||
LGNetCapability offeredCapabilities[LG_NET_SERVICE_CONTROL + 1U];
|
||||
uint32_t offeredCapabilityCount;
|
||||
uint16_t offeredServices;
|
||||
|
||||
@@ -128,13 +130,13 @@ struct NetworkConnection
|
||||
uint16_t receivedServices;
|
||||
uint16_t reliableServices;
|
||||
uint16_t datagramServices;
|
||||
uint16_t serviceVersions[LG_NET_SERVICE_USB + 1U];
|
||||
uint16_t serviceVersions[LG_NET_SERVICE_CONTROL + 1U];
|
||||
uint32_t maxPayloadLength;
|
||||
uint32_t maxDatagramPayload;
|
||||
uint16_t maxDatagramSendLength;
|
||||
bool datagramSendEnabled;
|
||||
uint32_t serviceMaxPayload[LG_NET_SERVICE_USB + 1U];
|
||||
uint32_t serviceMaxDatagram[LG_NET_SERVICE_USB + 1U];
|
||||
uint32_t serviceMaxPayload[LG_NET_SERVICE_CONTROL + 1U];
|
||||
uint32_t serviceMaxDatagram[LG_NET_SERVICE_CONTROL + 1U];
|
||||
|
||||
uint8_t clientHelloNonce[LG_NET_HELLO_NONCE_LENGTH];
|
||||
uint8_t serverHelloNonce[LG_NET_HELLO_NONCE_LENGTH];
|
||||
@@ -185,7 +187,8 @@ static bool networkCapabilityOfferValid(
|
||||
if (!lgNetCapabilityValid(capability) ||
|
||||
!version ||
|
||||
capability->service <= LG_NET_SERVICE_CORE ||
|
||||
capability->service >= LG_NET_SERVICE_USB ||
|
||||
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 ||
|
||||
@@ -196,6 +199,12 @@ static bool networkCapabilityOfferValid(
|
||||
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)
|
||||
@@ -265,6 +274,8 @@ static bool networkConnectionSendPacket(NetworkConnection * this,
|
||||
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,
|
||||
@@ -364,8 +375,10 @@ static bool networkConnectionSendHello(NetworkConnection * this)
|
||||
goto cleanup;
|
||||
|
||||
for (LGNetService service = LG_NET_SERVICE_RECOVERY;
|
||||
service < LG_NET_SERVICE_USB; ++service)
|
||||
service <= LG_NET_SERVICE_CONTROL; ++service)
|
||||
{
|
||||
if (service == LG_NET_SERVICE_USB)
|
||||
continue;
|
||||
if (!(this->offeredServices & (UINT16_C(1) << service)))
|
||||
continue;
|
||||
|
||||
@@ -426,8 +439,7 @@ static bool networkConnectionHandleCapability(NetworkConnection * this,
|
||||
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK)
|
||||
return false;
|
||||
|
||||
if (capability.service <= LG_NET_SERVICE_CORE ||
|
||||
capability.service >= LG_NET_SERVICE_USB)
|
||||
if (!networkConnectionServiceValid(capability.service))
|
||||
return false;
|
||||
|
||||
LG_LOCK(this->lock);
|
||||
@@ -486,8 +498,10 @@ static bool networkConnectionRequiredCapabilitiesSelected(
|
||||
const NetworkConnection * this)
|
||||
{
|
||||
for (LGNetService service = LG_NET_SERVICE_RECOVERY;
|
||||
service < LG_NET_SERVICE_USB; ++service)
|
||||
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 &
|
||||
@@ -518,6 +532,11 @@ static void networkConnectionSetSessionFeatures(NetworkConnection * this)
|
||||
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 |
|
||||
LG_TRANSPORT_FEATURE_FRAME_SCHEDULE;
|
||||
}
|
||||
|
||||
static bool networkConnectionHandleAuthChallenge(NetworkConnection * this,
|
||||
@@ -899,7 +918,8 @@ static bool networkConnectionHandlePacket(NetworkConnection * this,
|
||||
if (packet->envelope.service == LG_NET_SERVICE_CORE)
|
||||
return networkConnectionHandleCore(this, packet);
|
||||
|
||||
if (!atomic_load_explicit(&this->sessionValid, memory_order_acquire))
|
||||
if (!networkConnectionServiceValid(packet->envelope.service) ||
|
||||
!atomic_load_explicit(&this->sessionValid, memory_order_acquire))
|
||||
return false;
|
||||
|
||||
LG_LOCK(this->lock);
|
||||
@@ -930,12 +950,16 @@ static bool networkConnectionValidateControl(NetworkConnection * this,
|
||||
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 | LG_NET_ENVELOPE_FINAL)) ==
|
||||
(LG_NET_ENVELOPE_RELIABLE | LG_NET_ENVELOPE_FINAL) &&
|
||||
(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,
|
||||
@@ -956,6 +980,8 @@ static bool networkConnectionDecodeDatagram(NetworkConnection * this,
|
||||
|
||||
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 ||
|
||||
@@ -1645,7 +1671,7 @@ bool networkConnectionServiceAvailable(NetworkConnection * this,
|
||||
LGNetService service, LGNetCapabilityFlags required)
|
||||
{
|
||||
if (!networkConnectionValid(this) ||
|
||||
service <= LG_NET_SERVICE_CORE || service > LG_NET_SERVICE_USB ||
|
||||
!networkConnectionServiceValid(service) ||
|
||||
required & ~(LG_NET_CAPABILITY_RELIABLE |
|
||||
LG_NET_CAPABILITY_DATAGRAM))
|
||||
return false;
|
||||
@@ -1666,7 +1692,7 @@ bool networkConnectionServiceVersion(NetworkConnection * this,
|
||||
LGNetService service, uint16_t * version)
|
||||
{
|
||||
if (!version || !networkConnectionValid(this) ||
|
||||
service <= LG_NET_SERVICE_CORE || service >= LG_NET_SERVICE_USB)
|
||||
!networkConnectionServiceValid(service))
|
||||
return false;
|
||||
|
||||
LG_LOCK(this->lock);
|
||||
@@ -1703,7 +1729,8 @@ LG_TransportStatus networkConnectionSendDatagram(NetworkConnection * this,
|
||||
LGNetEnvelope routeEnvelope;
|
||||
lgNetEnvelopeInit(&routeEnvelope, service, messageType, serviceVersion,
|
||||
LG_NET_MESSAGE_VERSION_CURRENT);
|
||||
if ((payloadLength && !payload) || service == LG_NET_SERVICE_CORE ||
|
||||
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,
|
||||
|
||||
369
client/transports/Network/control.c
Normal file
369
client/transports/Network/control.c
Normal file
@@ -0,0 +1,369 @@
|
||||
/**
|
||||
* 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 "control.h"
|
||||
|
||||
#include "common/locking.h"
|
||||
|
||||
#include <LGProtocol/NetworkServices.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define NETWORK_CONTROL_STATUS_CAPACITY 64U
|
||||
|
||||
struct NetworkControlEntry
|
||||
{
|
||||
uint64_t id;
|
||||
LGNetControlStatusCode status;
|
||||
bool used;
|
||||
};
|
||||
|
||||
struct NetworkControl
|
||||
{
|
||||
NetworkConnection * connection;
|
||||
LG_Lock lock;
|
||||
|
||||
bool connected;
|
||||
bool available;
|
||||
uint16_t serviceVersion;
|
||||
uint64_t nextID;
|
||||
unsigned nextEntry;
|
||||
|
||||
struct NetworkControlEntry entries[NETWORK_CONTROL_STATUS_CAPACITY];
|
||||
};
|
||||
|
||||
static uint64_t networkControlNextID(uint64_t id)
|
||||
{
|
||||
++id;
|
||||
return id ? id : 1U;
|
||||
}
|
||||
|
||||
static bool networkControlTerminal(LGNetControlStatusCode status)
|
||||
{
|
||||
return status != LG_NET_CONTROL_STATUS_PENDING;
|
||||
}
|
||||
|
||||
static struct NetworkControlEntry * networkControlFindNL(
|
||||
NetworkControl * control, uint64_t id)
|
||||
{
|
||||
for (size_t index = 0; index < NETWORK_CONTROL_STATUS_CAPACITY; ++index)
|
||||
if (control->entries[index].used && control->entries[index].id == id)
|
||||
return &control->entries[index];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct NetworkControlEntry * networkControlReserveNL(
|
||||
NetworkControl * control, uint64_t id)
|
||||
{
|
||||
for (size_t offset = 0;
|
||||
offset < NETWORK_CONTROL_STATUS_CAPACITY; ++offset)
|
||||
{
|
||||
const unsigned index =
|
||||
(control->nextEntry + offset) % NETWORK_CONTROL_STATUS_CAPACITY;
|
||||
struct NetworkControlEntry * entry = &control->entries[index];
|
||||
if (entry->used && !networkControlTerminal(entry->status))
|
||||
continue;
|
||||
|
||||
*entry = (struct NetworkControlEntry)
|
||||
{
|
||||
.id = id,
|
||||
.status = LG_NET_CONTROL_STATUS_PENDING,
|
||||
.used = true,
|
||||
};
|
||||
control->nextEntry =
|
||||
(index + 1U) % NETWORK_CONTROL_STATUS_CAPACITY;
|
||||
return entry;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool networkControlCreate(NetworkControl ** result,
|
||||
NetworkConnection * connection)
|
||||
{
|
||||
if (!result || !connection)
|
||||
return false;
|
||||
|
||||
NetworkControl * control = calloc(1, sizeof(*control));
|
||||
if (!control)
|
||||
return false;
|
||||
|
||||
LG_LOCK_INIT(control->lock);
|
||||
control->connection = connection;
|
||||
*result = control;
|
||||
return true;
|
||||
}
|
||||
|
||||
void networkControlDestroy(NetworkControl ** result)
|
||||
{
|
||||
if (!result || !*result)
|
||||
return;
|
||||
|
||||
NetworkControl * control = *result;
|
||||
LG_LOCK_FREE(control->lock);
|
||||
free(control);
|
||||
*result = NULL;
|
||||
}
|
||||
|
||||
void networkControlConnectionState(NetworkControl * control, bool connected)
|
||||
{
|
||||
uint16_t version = 0;
|
||||
const bool available = connected &&
|
||||
networkConnectionServiceAvailable(control->connection,
|
||||
LG_NET_SERVICE_CONTROL, LG_NET_CAPABILITY_RELIABLE) &&
|
||||
networkConnectionServiceVersion(control->connection,
|
||||
LG_NET_SERVICE_CONTROL, &version);
|
||||
|
||||
LG_LOCK(control->lock);
|
||||
control->connected = connected;
|
||||
control->available = available;
|
||||
control->serviceVersion = available ? version : 0;
|
||||
control->nextEntry = 0;
|
||||
memset(control->entries, 0, sizeof(control->entries));
|
||||
LG_UNLOCK(control->lock);
|
||||
}
|
||||
|
||||
bool networkControlPacket(NetworkControl * control,
|
||||
const LGNetPacketView * packet, bool datagram)
|
||||
{
|
||||
if (!control || !packet || datagram ||
|
||||
packet->envelope.service != LG_NET_SERVICE_CONTROL ||
|
||||
packet->envelope.messageType != LG_NET_CONTROL_MESSAGE_STATUS ||
|
||||
packet->envelope.componentEpoch != 0 ||
|
||||
!packet->envelope.requestID)
|
||||
return false;
|
||||
|
||||
LGNetControlStatus status;
|
||||
if (lgNetControlStatusDecode(&status,
|
||||
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
|
||||
status.controlID != packet->envelope.requestID)
|
||||
return false;
|
||||
|
||||
const bool terminal =
|
||||
status.status != LG_NET_CONTROL_STATUS_PENDING;
|
||||
const LGNetEnvelopeFlags expected =
|
||||
LG_NET_ENVELOPE_RESPONSE |
|
||||
LG_NET_ENVELOPE_RELIABLE |
|
||||
(terminal ? LG_NET_ENVELOPE_FINAL : 0);
|
||||
if (packet->envelope.flags != expected)
|
||||
return false;
|
||||
|
||||
LG_LOCK(control->lock);
|
||||
struct NetworkControlEntry * entry =
|
||||
networkControlFindNL(control, status.controlID);
|
||||
const bool valid =
|
||||
control->connected && control->available && entry;
|
||||
if (valid)
|
||||
entry->status = status.status;
|
||||
LG_UNLOCK(control->lock);
|
||||
return valid;
|
||||
}
|
||||
|
||||
static bool networkControlFrameFlags(
|
||||
LG_TransportFrameScheduleFlags source,
|
||||
LGNetControlFrameScheduleFlags * destination)
|
||||
{
|
||||
const LG_TransportFrameScheduleFlags known =
|
||||
LG_TRANSPORT_FRAME_SCHEDULE_ACTIVE |
|
||||
LG_TRANSPORT_FRAME_SCHEDULE_RELEASE |
|
||||
LG_TRANSPORT_FRAME_SCHEDULE_RESET |
|
||||
LG_TRANSPORT_FRAME_SCHEDULE_IMMEDIATE;
|
||||
if (!destination || !source || (source & ~known))
|
||||
return false;
|
||||
|
||||
*destination = 0;
|
||||
if (source & LG_TRANSPORT_FRAME_SCHEDULE_ACTIVE)
|
||||
*destination |= LG_NET_CONTROL_FRAME_SCHEDULE_ACTIVE;
|
||||
if (source & LG_TRANSPORT_FRAME_SCHEDULE_RELEASE)
|
||||
*destination |= LG_NET_CONTROL_FRAME_SCHEDULE_RELEASE;
|
||||
if (source & LG_TRANSPORT_FRAME_SCHEDULE_RESET)
|
||||
*destination |= LG_NET_CONTROL_FRAME_SCHEDULE_RESET;
|
||||
if (source & LG_TRANSPORT_FRAME_SCHEDULE_IMMEDIATE)
|
||||
*destination |= LG_NET_CONTROL_FRAME_SCHEDULE_IMMEDIATE;
|
||||
return true;
|
||||
}
|
||||
|
||||
LG_TransportStatus networkControlSend(NetworkControl * control,
|
||||
const LG_TransportControl * request, LG_TransportControlToken * token)
|
||||
{
|
||||
if (!control || !request || !token)
|
||||
return LG_TRANSPORT_ERROR;
|
||||
|
||||
uint64_t id;
|
||||
uint16_t version;
|
||||
LG_LOCK(control->lock);
|
||||
if (!control->connected)
|
||||
{
|
||||
LG_UNLOCK(control->lock);
|
||||
return LG_TRANSPORT_DISCONNECTED;
|
||||
}
|
||||
if (!control->available)
|
||||
{
|
||||
LG_UNLOCK(control->lock);
|
||||
return LG_TRANSPORT_UNAVAILABLE;
|
||||
}
|
||||
|
||||
id = networkControlNextID(control->nextID);
|
||||
if (!networkControlReserveNL(control, id))
|
||||
{
|
||||
LG_UNLOCK(control->lock);
|
||||
return LG_TRANSPORT_UNAVAILABLE;
|
||||
}
|
||||
control->nextID = id;
|
||||
version = control->serviceVersion;
|
||||
LG_UNLOCK(control->lock);
|
||||
|
||||
uint8_t payload[LG_NET_CONTROL_FRAME_SCHEDULE_WIRE_SIZE];
|
||||
uint32_t payloadLength;
|
||||
uint16_t messageType;
|
||||
bool encoded;
|
||||
switch (request->type)
|
||||
{
|
||||
case LG_TRANSPORT_CONTROL_SET_CURSOR_POS:
|
||||
{
|
||||
const LGNetControlCursorPosition position =
|
||||
{
|
||||
.controlID = id,
|
||||
.x = request->cursorPos.x,
|
||||
.y = request->cursorPos.y,
|
||||
};
|
||||
messageType = LG_NET_CONTROL_MESSAGE_CURSOR_POSITION;
|
||||
payloadLength = LG_NET_CONTROL_CURSOR_POSITION_WIRE_SIZE;
|
||||
encoded = lgNetControlCursorPositionEncode(
|
||||
payload, payloadLength, &position);
|
||||
break;
|
||||
}
|
||||
|
||||
case LG_TRANSPORT_CONTROL_WINDOW_SIZE:
|
||||
{
|
||||
const LGNetControlDisplaySize display =
|
||||
{
|
||||
.controlID = id,
|
||||
.width = request->windowSize.width,
|
||||
.height = request->windowSize.height,
|
||||
};
|
||||
messageType = LG_NET_CONTROL_MESSAGE_DISPLAY_SIZE;
|
||||
payloadLength = LG_NET_CONTROL_DISPLAY_SIZE_WIRE_SIZE;
|
||||
encoded = lgNetControlDisplaySizeEncode(
|
||||
payload, payloadLength, &display);
|
||||
break;
|
||||
}
|
||||
|
||||
case LG_TRANSPORT_CONTROL_FRAME_SCHEDULE:
|
||||
{
|
||||
LGNetControlFrameScheduleFlags flags;
|
||||
if (!networkControlFrameFlags(
|
||||
request->frameSchedule.flags, &flags))
|
||||
encoded = false;
|
||||
else
|
||||
{
|
||||
const LGNetControlFrameSchedule schedule =
|
||||
{
|
||||
.controlID = id,
|
||||
.generation = request->frameSchedule.generation,
|
||||
.flags = flags,
|
||||
.periodNs = request->frameSchedule.period,
|
||||
.targetSlackNs = request->frameSchedule.targetSlack,
|
||||
.phaseErrorNs = request->frameSchedule.phaseError,
|
||||
.feedbackFrameSerial =
|
||||
request->frameSchedule.feedbackFrameSerial,
|
||||
.feedbackScheduleEpoch =
|
||||
request->frameSchedule.feedbackScheduleEpoch,
|
||||
.feedbackDeadlineSerial =
|
||||
request->frameSchedule.feedbackDeadlineSerial,
|
||||
.leaseMs = request->frameSchedule.lease,
|
||||
};
|
||||
encoded = lgNetControlFrameScheduleEncode(
|
||||
payload, sizeof(payload), &schedule);
|
||||
}
|
||||
messageType = LG_NET_CONTROL_MESSAGE_FRAME_SCHEDULE;
|
||||
payloadLength = LG_NET_CONTROL_FRAME_SCHEDULE_WIRE_SIZE;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
encoded = false;
|
||||
messageType = 0;
|
||||
payloadLength = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
LG_TransportStatus status = LG_TRANSPORT_ERROR;
|
||||
if (encoded)
|
||||
status = networkConnectionSend(control->connection,
|
||||
LG_NET_SERVICE_CONTROL, messageType, version,
|
||||
LG_NET_ENVELOPE_ACK_REQUIRED | LG_NET_ENVELOPE_FINAL,
|
||||
0, id, payload, payloadLength);
|
||||
|
||||
if (status != LG_TRANSPORT_OK)
|
||||
{
|
||||
LG_LOCK(control->lock);
|
||||
struct NetworkControlEntry * entry = networkControlFindNL(control, id);
|
||||
if (entry)
|
||||
*entry = (struct NetworkControlEntry) { 0 };
|
||||
LG_UNLOCK(control->lock);
|
||||
return status;
|
||||
}
|
||||
|
||||
*token = id;
|
||||
return LG_TRANSPORT_OK;
|
||||
}
|
||||
|
||||
LG_TransportStatus networkControlStatus(NetworkControl * control,
|
||||
LG_TransportControlToken token)
|
||||
{
|
||||
if (!control || !token)
|
||||
return LG_TRANSPORT_ERROR;
|
||||
|
||||
LG_LOCK(control->lock);
|
||||
if (!control->connected)
|
||||
{
|
||||
LG_UNLOCK(control->lock);
|
||||
return LG_TRANSPORT_DISCONNECTED;
|
||||
}
|
||||
if (!control->available)
|
||||
{
|
||||
LG_UNLOCK(control->lock);
|
||||
return LG_TRANSPORT_UNAVAILABLE;
|
||||
}
|
||||
|
||||
const struct NetworkControlEntry * entry =
|
||||
networkControlFindNL(control, token);
|
||||
const LGNetControlStatusCode status = entry ? entry->status : 0;
|
||||
LG_UNLOCK(control->lock);
|
||||
|
||||
switch (status)
|
||||
{
|
||||
case LG_NET_CONTROL_STATUS_APPLIED:
|
||||
return LG_TRANSPORT_OK;
|
||||
|
||||
case LG_NET_CONTROL_STATUS_PENDING:
|
||||
return LG_TRANSPORT_UNAVAILABLE;
|
||||
|
||||
case LG_NET_CONTROL_STATUS_BUSY:
|
||||
case LG_NET_CONTROL_STATUS_UNSUPPORTED:
|
||||
case LG_NET_CONTROL_STATUS_STALE:
|
||||
case LG_NET_CONTROL_STATUS_INVALID:
|
||||
case LG_NET_CONTROL_STATUS_ERROR:
|
||||
default:
|
||||
return LG_TRANSPORT_ERROR;
|
||||
}
|
||||
}
|
||||
44
client/transports/Network/control.h
Normal file
44
client/transports/Network/control.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2026 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "connection.h"
|
||||
|
||||
#include "interface/transport.h"
|
||||
|
||||
#include <LGProtocol/NetworkProtocol.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct NetworkControl NetworkControl;
|
||||
|
||||
bool networkControlCreate(NetworkControl ** control,
|
||||
NetworkConnection * connection);
|
||||
void networkControlDestroy(NetworkControl ** control);
|
||||
|
||||
void networkControlConnectionState(NetworkControl * control, bool connected);
|
||||
bool networkControlPacket(NetworkControl * control,
|
||||
const LGNetPacketView * packet, bool datagram);
|
||||
|
||||
LG_TransportStatus networkControlSend(NetworkControl * control,
|
||||
const LG_TransportControl * request, LG_TransportControlToken * token);
|
||||
LG_TransportStatus networkControlStatus(NetworkControl * control,
|
||||
LG_TransportControlToken token);
|
||||
813
client/transports/Network/cursor.c
Normal file
813
client/transports/Network/cursor.c
Normal file
@@ -0,0 +1,813 @@
|
||||
/**
|
||||
* 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 "cursor.h"
|
||||
|
||||
#include "common/event.h"
|
||||
#include "common/locking.h"
|
||||
|
||||
#include <LGProtocol/NetworkServices.h>
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define NETWORK_CURSOR_QUEUE_CAPACITY 64U
|
||||
|
||||
struct NetworkCursorItem
|
||||
{
|
||||
struct NetworkCursorItem * next;
|
||||
LG_TransportPointer pointer;
|
||||
uint8_t * shape;
|
||||
KVMFRColorTransform * colorTransform;
|
||||
};
|
||||
|
||||
struct NetworkCursorStatusReply
|
||||
{
|
||||
bool send;
|
||||
uint64_t stateSequence;
|
||||
uint64_t appliedShapeID;
|
||||
uint64_t appliedPositionID;
|
||||
uint64_t appliedTransformID;
|
||||
LGNetCursorStatusCode status;
|
||||
uint32_t detail;
|
||||
};
|
||||
|
||||
struct NetworkCursor
|
||||
{
|
||||
NetworkConnection * connection;
|
||||
LGEvent * event;
|
||||
LG_Lock lock;
|
||||
|
||||
bool connected;
|
||||
bool available;
|
||||
bool cancelled;
|
||||
uint16_t serviceVersion;
|
||||
|
||||
struct NetworkCursorItem * head;
|
||||
struct NetworkCursorItem * tail;
|
||||
struct NetworkCursorItem * outstanding;
|
||||
unsigned count;
|
||||
|
||||
LGNetCursorShape shape;
|
||||
uint8_t * shapeData;
|
||||
bool shapeValid;
|
||||
bool shapeSupported;
|
||||
|
||||
LGNetCursorPosition position;
|
||||
bool positionValid;
|
||||
bool positionSupported;
|
||||
|
||||
LGNetCursorTransform transform;
|
||||
bool transformValid;
|
||||
bool transformSupported;
|
||||
|
||||
LGNetCursorState pendingState;
|
||||
bool pendingStateValid;
|
||||
bool stateApplied;
|
||||
uint64_t appliedStateSequence;
|
||||
uint64_t appliedShapeID;
|
||||
uint64_t appliedPositionID;
|
||||
uint64_t appliedTransformID;
|
||||
bool visible;
|
||||
|
||||
uint64_t colorUpdateID;
|
||||
uint32_t sdrWhiteLevel;
|
||||
};
|
||||
|
||||
static void networkCursorItemFree(struct NetworkCursorItem * item)
|
||||
{
|
||||
if (!item)
|
||||
return;
|
||||
free(item->colorTransform);
|
||||
free(item->shape);
|
||||
free(item);
|
||||
}
|
||||
|
||||
static void networkCursorQueueClearNL(NetworkCursor * cursor)
|
||||
{
|
||||
while (cursor->head)
|
||||
{
|
||||
struct NetworkCursorItem * item = cursor->head;
|
||||
cursor->head = item->next;
|
||||
networkCursorItemFree(item);
|
||||
}
|
||||
cursor->tail = NULL;
|
||||
cursor->count = 0;
|
||||
}
|
||||
|
||||
static void networkCursorCacheClearNL(NetworkCursor * cursor)
|
||||
{
|
||||
free(cursor->shapeData);
|
||||
cursor->shapeData = NULL;
|
||||
cursor->shape = (LGNetCursorShape) { 0 };
|
||||
cursor->shapeValid = false;
|
||||
cursor->shapeSupported = false;
|
||||
cursor->position = (LGNetCursorPosition) { 0 };
|
||||
cursor->positionValid = false;
|
||||
cursor->positionSupported = false;
|
||||
cursor->transform = (LGNetCursorTransform) { 0 };
|
||||
cursor->transformValid = false;
|
||||
cursor->transformSupported = false;
|
||||
cursor->pendingState = (LGNetCursorState) { 0 };
|
||||
cursor->pendingStateValid = false;
|
||||
cursor->stateApplied = false;
|
||||
cursor->appliedStateSequence = 0;
|
||||
cursor->appliedShapeID = 0;
|
||||
cursor->appliedPositionID = 0;
|
||||
cursor->appliedTransformID = 0;
|
||||
cursor->visible = false;
|
||||
cursor->colorUpdateID = 0;
|
||||
cursor->sdrWhiteLevel = LG_SDR_WHITE_LEVEL_DEFAULT;
|
||||
}
|
||||
|
||||
static bool networkCursorItemCoalescible(
|
||||
const struct NetworkCursorItem * item)
|
||||
{
|
||||
return item && !(item->pointer.flags &
|
||||
(LG_TRANSPORT_POINTER_SHAPE |
|
||||
LG_TRANSPORT_POINTER_COLOR_TRANSFORM));
|
||||
}
|
||||
|
||||
static bool networkCursorQueueNL(NetworkCursor * cursor,
|
||||
struct NetworkCursorItem * item)
|
||||
{
|
||||
if (networkCursorItemCoalescible(item) &&
|
||||
networkCursorItemCoalescible(cursor->tail))
|
||||
{
|
||||
cursor->tail->pointer = item->pointer;
|
||||
networkCursorItemFree(item);
|
||||
lgSignalEvent(cursor->event);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (cursor->count == NETWORK_CURSOR_QUEUE_CAPACITY)
|
||||
{
|
||||
struct NetworkCursorItem * previous = NULL;
|
||||
struct NetworkCursorItem * current = cursor->head;
|
||||
while (current && !networkCursorItemCoalescible(current))
|
||||
{
|
||||
previous = current;
|
||||
current = current->next;
|
||||
}
|
||||
if (!current)
|
||||
return false;
|
||||
if (previous)
|
||||
previous->next = current->next;
|
||||
else
|
||||
cursor->head = current->next;
|
||||
if (cursor->tail == current)
|
||||
cursor->tail = previous;
|
||||
--cursor->count;
|
||||
networkCursorItemFree(current);
|
||||
}
|
||||
|
||||
item->next = NULL;
|
||||
if (cursor->tail)
|
||||
cursor->tail->next = item;
|
||||
else
|
||||
cursor->head = item;
|
||||
cursor->tail = item;
|
||||
++cursor->count;
|
||||
lgSignalEvent(cursor->event);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool networkCursorShapeType(LGNetCursorFormat format,
|
||||
KVMFRCursorType * type)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case LG_NET_CURSOR_FORMAT_BGRA8_PREMULTIPLIED:
|
||||
case LG_NET_CURSOR_FORMAT_RGBA8_PREMULTIPLIED:
|
||||
*type = CURSOR_TYPE_COLOR;
|
||||
return true;
|
||||
case LG_NET_CURSOR_FORMAT_MASKED_COLOR:
|
||||
*type = CURSOR_TYPE_MASKED_COLOR;
|
||||
return true;
|
||||
case LG_NET_CURSOR_FORMAT_MONOCHROME:
|
||||
*type = CURSOR_TYPE_MONOCHROME;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool networkCursorPositionSupported(
|
||||
const LGNetCursorPosition * position)
|
||||
{
|
||||
return position->displayID == 0 &&
|
||||
!(position->flags & LG_NET_CURSOR_POSITION_RELATIVE) &&
|
||||
position->x >= INT16_MIN && position->x <= INT16_MAX &&
|
||||
position->y >= INT16_MIN && position->y <= INT16_MAX;
|
||||
}
|
||||
|
||||
static bool networkCursorTransformSupported(
|
||||
const LGNetCursorTransform * transform)
|
||||
{
|
||||
return transform->displayID == 0 &&
|
||||
transform->rotation == LG_NET_CURSOR_ROTATION_0 && !transform->flags &&
|
||||
transform->sourceWidth == transform->targetWidth &&
|
||||
transform->sourceHeight == transform->targetHeight &&
|
||||
transform->offsetX == 0 && transform->offsetY == 0 &&
|
||||
transform->scaleNumerator == transform->scaleDenominator;
|
||||
}
|
||||
|
||||
static bool networkCursorShapeCopy(NetworkCursor * cursor,
|
||||
const LGNetCursorShape * shape)
|
||||
{
|
||||
KVMFRCursorType type;
|
||||
const bool supported = networkCursorShapeType(shape->format, &type) &&
|
||||
!(shape->flags & LG_NET_CURSOR_SHAPE_ANIMATED) &&
|
||||
shape->hotspotX <= INT16_MAX && shape->hotspotY <= INT16_MAX;
|
||||
|
||||
uint8_t * data = malloc(shape->dataLength);
|
||||
if (!data)
|
||||
return false;
|
||||
memcpy(data, shape->data, shape->dataLength);
|
||||
|
||||
if (shape->format == LG_NET_CURSOR_FORMAT_RGBA8_PREMULTIPLIED)
|
||||
for (uint32_t y = 0; y < shape->height; ++y)
|
||||
for (uint32_t x = 0; x < shape->width; ++x)
|
||||
{
|
||||
uint8_t * pixel = data + (size_t)y * shape->pitch + x * 4U;
|
||||
const uint8_t red = pixel[0];
|
||||
pixel[0] = pixel[2];
|
||||
pixel[2] = red;
|
||||
}
|
||||
|
||||
free(cursor->shapeData);
|
||||
cursor->shape = *shape;
|
||||
cursor->shapeData = data;
|
||||
cursor->shape.data = data;
|
||||
cursor->shapeValid = true;
|
||||
cursor->shapeSupported = supported;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool networkCursorStateMissingNL(const NetworkCursor * cursor,
|
||||
const LGNetCursorState * state, bool * impossible)
|
||||
{
|
||||
*impossible = false;
|
||||
if (state->flags & LG_NET_CURSOR_STATE_SHAPE_VALID)
|
||||
{
|
||||
if (!cursor->shapeValid)
|
||||
return true;
|
||||
if (cursor->shape.shapeID != state->shapeID)
|
||||
{
|
||||
*impossible = cursor->shape.shapeID > state->shapeID;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (state->flags & LG_NET_CURSOR_STATE_POSITION_VALID)
|
||||
{
|
||||
if (!cursor->positionValid)
|
||||
return true;
|
||||
if (cursor->position.updateID != state->positionID)
|
||||
{
|
||||
*impossible = cursor->position.updateID > state->positionID;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (state->flags & LG_NET_CURSOR_STATE_TRANSFORM_VALID)
|
||||
{
|
||||
if (!cursor->transformValid)
|
||||
return true;
|
||||
if (cursor->transform.transformID != state->transformID)
|
||||
{
|
||||
*impossible = cursor->transform.transformID > state->transformID;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static struct NetworkCursorStatusReply networkCursorApplyStateNL(
|
||||
NetworkCursor * cursor)
|
||||
{
|
||||
struct NetworkCursorStatusReply reply = { 0 };
|
||||
if (!cursor->pendingStateValid)
|
||||
return reply;
|
||||
|
||||
const LGNetCursorState state = cursor->pendingState;
|
||||
bool impossible;
|
||||
if (networkCursorStateMissingNL(cursor, &state, &impossible))
|
||||
{
|
||||
if (!impossible)
|
||||
return reply;
|
||||
reply.send = true;
|
||||
reply.stateSequence = state.stateSequence;
|
||||
reply.status = LG_NET_CURSOR_STATUS_INVALID;
|
||||
cursor->pendingStateValid = false;
|
||||
return reply;
|
||||
}
|
||||
|
||||
const bool shapeValid =
|
||||
state.flags & LG_NET_CURSOR_STATE_SHAPE_VALID;
|
||||
const bool positionValid =
|
||||
state.flags & LG_NET_CURSOR_STATE_POSITION_VALID;
|
||||
const bool transformValid =
|
||||
state.flags & LG_NET_CURSOR_STATE_TRANSFORM_VALID;
|
||||
if (state.displayID != 0 ||
|
||||
(shapeValid && !cursor->shapeSupported) ||
|
||||
(positionValid && (!cursor->positionSupported ||
|
||||
cursor->position.displayID != state.displayID)) ||
|
||||
(transformValid && (!cursor->transformSupported ||
|
||||
cursor->transform.displayID != state.displayID)))
|
||||
{
|
||||
reply.send = true;
|
||||
reply.stateSequence = state.stateSequence;
|
||||
reply.status = LG_NET_CURSOR_STATUS_UNSUPPORTED;
|
||||
cursor->pendingStateValid = false;
|
||||
return reply;
|
||||
}
|
||||
|
||||
const bool newShape = shapeValid &&
|
||||
cursor->appliedShapeID != state.shapeID;
|
||||
const bool newPosition = positionValid &&
|
||||
cursor->appliedPositionID != state.positionID;
|
||||
const bool visible =
|
||||
!!(state.flags & LG_NET_CURSOR_STATE_VISIBLE) &&
|
||||
!(shapeValid &&
|
||||
(cursor->shape.flags & LG_NET_CURSOR_SHAPE_HIDDEN));
|
||||
const bool visibilityChanged =
|
||||
!cursor->stateApplied || cursor->visible != visible;
|
||||
|
||||
struct NetworkCursorItem * item = NULL;
|
||||
if (newShape || newPosition || visibilityChanged)
|
||||
{
|
||||
item = calloc(1, sizeof(*item));
|
||||
if (!item)
|
||||
goto capacity;
|
||||
|
||||
item->pointer.flags = LG_TRANSPORT_POINTER_VISIBLE_VALID;
|
||||
item->pointer.sdrWhiteLevel = cursor->sdrWhiteLevel;
|
||||
if (visible)
|
||||
item->pointer.flags |= LG_TRANSPORT_POINTER_VISIBLE;
|
||||
if (newPosition)
|
||||
{
|
||||
item->pointer.flags |= LG_TRANSPORT_POINTER_POSITION;
|
||||
item->pointer.x = (int16_t)cursor->position.x;
|
||||
item->pointer.y = (int16_t)cursor->position.y;
|
||||
}
|
||||
if (newShape)
|
||||
{
|
||||
if (!networkCursorShapeType(cursor->shape.format,
|
||||
&item->pointer.type))
|
||||
goto capacity;
|
||||
item->shape = malloc(cursor->shape.dataLength);
|
||||
if (!item->shape)
|
||||
goto capacity;
|
||||
memcpy(item->shape, cursor->shapeData, cursor->shape.dataLength);
|
||||
item->pointer.flags |= LG_TRANSPORT_POINTER_SHAPE;
|
||||
item->pointer.hx = (int16_t)cursor->shape.hotspotX;
|
||||
item->pointer.hy = (int16_t)cursor->shape.hotspotY;
|
||||
item->pointer.width = cursor->shape.width;
|
||||
item->pointer.height = cursor->shape.height;
|
||||
item->pointer.pitch = cursor->shape.pitch;
|
||||
item->pointer.shape = item->shape;
|
||||
}
|
||||
if (!networkCursorQueueNL(cursor, item))
|
||||
goto capacity;
|
||||
item = NULL;
|
||||
}
|
||||
|
||||
cursor->stateApplied = true;
|
||||
cursor->appliedStateSequence = state.stateSequence;
|
||||
cursor->appliedShapeID = shapeValid ? state.shapeID : 0;
|
||||
cursor->appliedPositionID = positionValid ? state.positionID : 0;
|
||||
cursor->appliedTransformID = transformValid ? state.transformID : 0;
|
||||
cursor->visible = visible;
|
||||
cursor->pendingStateValid = false;
|
||||
reply.send = true;
|
||||
reply.stateSequence = state.stateSequence;
|
||||
reply.appliedShapeID = cursor->appliedShapeID;
|
||||
reply.appliedPositionID = cursor->appliedPositionID;
|
||||
reply.appliedTransformID = cursor->appliedTransformID;
|
||||
reply.status = LG_NET_CURSOR_STATUS_APPLIED;
|
||||
return reply;
|
||||
|
||||
capacity:
|
||||
networkCursorItemFree(item);
|
||||
cursor->pendingStateValid = false;
|
||||
reply.send = true;
|
||||
reply.stateSequence = state.stateSequence;
|
||||
reply.status = LG_NET_CURSOR_STATUS_ERROR;
|
||||
return reply;
|
||||
}
|
||||
|
||||
static bool networkCursorSendStatus(NetworkCursor * cursor,
|
||||
const struct NetworkCursorStatusReply * reply)
|
||||
{
|
||||
if (!reply->send)
|
||||
return true;
|
||||
|
||||
uint16_t version;
|
||||
LG_LOCK(cursor->lock);
|
||||
const bool available = cursor->connected && cursor->available;
|
||||
version = cursor->serviceVersion;
|
||||
LG_UNLOCK(cursor->lock);
|
||||
if (!available)
|
||||
return false;
|
||||
|
||||
const LGNetCursorStatus status =
|
||||
{
|
||||
.stateSequence = reply->stateSequence,
|
||||
.appliedShapeID = reply->appliedShapeID,
|
||||
.appliedPositionID = reply->appliedPositionID,
|
||||
.appliedTransformID = reply->appliedTransformID,
|
||||
.status = reply->status,
|
||||
.detail = reply->detail,
|
||||
};
|
||||
uint8_t payload[LG_NET_CURSOR_STATUS_WIRE_SIZE];
|
||||
return lgNetCursorStatusEncode(payload, sizeof(payload), &status) &&
|
||||
networkConnectionSend(cursor->connection, LG_NET_SERVICE_CURSOR,
|
||||
LG_NET_CURSOR_MESSAGE_STATUS, version,
|
||||
LG_NET_ENVELOPE_RESPONSE | LG_NET_ENVELOPE_FINAL,
|
||||
0, status.stateSequence, payload, sizeof(payload)) == LG_TRANSPORT_OK;
|
||||
}
|
||||
|
||||
bool networkCursorCreate(NetworkCursor ** result,
|
||||
NetworkConnection * connection)
|
||||
{
|
||||
if (!result || !connection)
|
||||
return false;
|
||||
|
||||
NetworkCursor * cursor = calloc(1, sizeof(*cursor));
|
||||
if (!cursor)
|
||||
return false;
|
||||
|
||||
LG_LOCK_INIT(cursor->lock);
|
||||
cursor->event = lgCreateEvent(true, 0);
|
||||
if (!cursor->event)
|
||||
{
|
||||
LG_LOCK_FREE(cursor->lock);
|
||||
free(cursor);
|
||||
return false;
|
||||
}
|
||||
|
||||
cursor->connection = connection;
|
||||
cursor->sdrWhiteLevel = LG_SDR_WHITE_LEVEL_DEFAULT;
|
||||
*result = cursor;
|
||||
return true;
|
||||
}
|
||||
|
||||
void networkCursorDestroy(NetworkCursor ** result)
|
||||
{
|
||||
if (!result || !*result)
|
||||
return;
|
||||
|
||||
NetworkCursor * cursor = *result;
|
||||
LG_LOCK(cursor->lock);
|
||||
networkCursorQueueClearNL(cursor);
|
||||
networkCursorItemFree(cursor->outstanding);
|
||||
cursor->outstanding = NULL;
|
||||
networkCursorCacheClearNL(cursor);
|
||||
LG_UNLOCK(cursor->lock);
|
||||
lgSignalEvent(cursor->event);
|
||||
lgFreeEvent(cursor->event);
|
||||
LG_LOCK_FREE(cursor->lock);
|
||||
free(cursor);
|
||||
*result = NULL;
|
||||
}
|
||||
|
||||
bool networkCursorConnectionState(NetworkCursor * cursor, bool connected)
|
||||
{
|
||||
uint16_t version = 0;
|
||||
const bool available = connected &&
|
||||
networkConnectionServiceAvailable(cursor->connection,
|
||||
LG_NET_SERVICE_CURSOR, LG_NET_CAPABILITY_RELIABLE) &&
|
||||
networkConnectionServiceVersion(cursor->connection,
|
||||
LG_NET_SERVICE_CURSOR, &version);
|
||||
|
||||
LG_LOCK(cursor->lock);
|
||||
cursor->connected = connected;
|
||||
cursor->available = available;
|
||||
cursor->cancelled = false;
|
||||
cursor->serviceVersion = available ? version : 0;
|
||||
networkCursorQueueClearNL(cursor);
|
||||
networkCursorCacheClearNL(cursor);
|
||||
LG_UNLOCK(cursor->lock);
|
||||
lgSignalEvent(cursor->event);
|
||||
return available;
|
||||
}
|
||||
|
||||
static bool networkCursorEnvelope(const LGNetPacketView * packet,
|
||||
bool datagram, uint64_t id)
|
||||
{
|
||||
const LGNetEnvelopeFlags expected = LG_NET_ENVELOPE_FINAL |
|
||||
(datagram ? LG_NET_ENVELOPE_DATAGRAM : LG_NET_ENVELOPE_RELIABLE);
|
||||
return packet->envelope.flags == expected &&
|
||||
packet->envelope.componentEpoch == 0 &&
|
||||
packet->envelope.requestID == id;
|
||||
}
|
||||
|
||||
static bool networkCursorPositionPacket(NetworkCursor * cursor,
|
||||
const LGNetPacketView * packet, bool datagram)
|
||||
{
|
||||
LGNetCursorPosition position;
|
||||
if (lgNetCursorPositionDecode(&position,
|
||||
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
|
||||
!networkCursorEnvelope(packet, datagram, position.updateID))
|
||||
return false;
|
||||
|
||||
struct NetworkCursorStatusReply reply = { 0 };
|
||||
LG_LOCK(cursor->lock);
|
||||
const bool valid = cursor->connected && cursor->available;
|
||||
if (valid && (!cursor->positionValid ||
|
||||
position.updateID > cursor->position.updateID))
|
||||
{
|
||||
cursor->position = position;
|
||||
cursor->positionValid = true;
|
||||
cursor->positionSupported = networkCursorPositionSupported(&position);
|
||||
reply = networkCursorApplyStateNL(cursor);
|
||||
}
|
||||
LG_UNLOCK(cursor->lock);
|
||||
return valid && networkCursorSendStatus(cursor, &reply);
|
||||
}
|
||||
|
||||
static bool networkCursorShapePacket(NetworkCursor * cursor,
|
||||
const LGNetPacketView * packet, bool datagram)
|
||||
{
|
||||
if (datagram)
|
||||
return false;
|
||||
|
||||
LGNetCursorShape shape;
|
||||
if (lgNetCursorShapeDecode(&shape,
|
||||
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
|
||||
!networkCursorEnvelope(packet, false, shape.shapeID))
|
||||
return false;
|
||||
|
||||
struct NetworkCursorStatusReply reply = { 0 };
|
||||
LG_LOCK(cursor->lock);
|
||||
const bool valid = cursor->connected && cursor->available;
|
||||
bool copied = true;
|
||||
if (valid && (!cursor->shapeValid || shape.shapeID > cursor->shape.shapeID))
|
||||
{
|
||||
copied = networkCursorShapeCopy(cursor, &shape);
|
||||
if (copied)
|
||||
reply = networkCursorApplyStateNL(cursor);
|
||||
}
|
||||
LG_UNLOCK(cursor->lock);
|
||||
return valid && copied && networkCursorSendStatus(cursor, &reply);
|
||||
}
|
||||
|
||||
static bool networkCursorTransformPacket(NetworkCursor * cursor,
|
||||
const LGNetPacketView * packet, bool datagram)
|
||||
{
|
||||
if (datagram)
|
||||
return false;
|
||||
|
||||
LGNetCursorTransform transform;
|
||||
if (lgNetCursorTransformDecode(&transform,
|
||||
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
|
||||
!networkCursorEnvelope(packet, false, transform.transformID))
|
||||
return false;
|
||||
|
||||
struct NetworkCursorStatusReply reply = { 0 };
|
||||
LG_LOCK(cursor->lock);
|
||||
const bool valid = cursor->connected && cursor->available;
|
||||
if (valid && (!cursor->transformValid ||
|
||||
transform.transformID > cursor->transform.transformID))
|
||||
{
|
||||
cursor->transform = transform;
|
||||
cursor->transformValid = true;
|
||||
cursor->transformSupported =
|
||||
networkCursorTransformSupported(&transform);
|
||||
reply = networkCursorApplyStateNL(cursor);
|
||||
}
|
||||
LG_UNLOCK(cursor->lock);
|
||||
return valid && networkCursorSendStatus(cursor, &reply);
|
||||
}
|
||||
|
||||
static bool networkCursorStatePacket(NetworkCursor * cursor,
|
||||
const LGNetPacketView * packet, bool datagram)
|
||||
{
|
||||
if (datagram)
|
||||
return false;
|
||||
|
||||
LGNetCursorState state;
|
||||
if (lgNetCursorStateDecode(&state,
|
||||
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
|
||||
!networkCursorEnvelope(packet, false, state.stateSequence))
|
||||
return false;
|
||||
|
||||
struct NetworkCursorStatusReply reply = { 0 };
|
||||
LG_LOCK(cursor->lock);
|
||||
const bool valid = cursor->connected && cursor->available;
|
||||
if (valid && state.stateSequence > cursor->appliedStateSequence &&
|
||||
(!cursor->pendingStateValid ||
|
||||
state.stateSequence > cursor->pendingState.stateSequence))
|
||||
{
|
||||
cursor->pendingState = state;
|
||||
cursor->pendingStateValid = true;
|
||||
reply = networkCursorApplyStateNL(cursor);
|
||||
}
|
||||
LG_UNLOCK(cursor->lock);
|
||||
return valid && networkCursorSendStatus(cursor, &reply);
|
||||
}
|
||||
|
||||
static bool networkCursorColorPacket(NetworkCursor * cursor,
|
||||
const LGNetPacketView * packet, bool datagram)
|
||||
{
|
||||
if (datagram)
|
||||
return false;
|
||||
|
||||
LGNetCursorColorTransform transform;
|
||||
if (lgNetCursorColorTransformDecode(&transform,
|
||||
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
|
||||
!networkCursorEnvelope(packet, false, transform.updateID))
|
||||
return false;
|
||||
|
||||
struct NetworkCursorItem * item = calloc(1, sizeof(*item));
|
||||
if (!item)
|
||||
return false;
|
||||
item->colorTransform = calloc(1, sizeof(*item->colorTransform));
|
||||
if (!item->colorTransform)
|
||||
{
|
||||
networkCursorItemFree(item);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (transform.flags & LG_NET_CURSOR_COLOR_TRANSFORM_MATRIX)
|
||||
item->colorTransform->flags |= LG_COLOR_TRANSFORM_MATRIX;
|
||||
if (transform.flags & LG_NET_CURSOR_COLOR_TRANSFORM_LUT)
|
||||
item->colorTransform->flags |= LG_COLOR_TRANSFORM_LUT;
|
||||
memcpy(item->colorTransform->matrix, transform.matrix,
|
||||
sizeof(transform.matrix));
|
||||
item->colorTransform->scalar = transform.scalar;
|
||||
if ((transform.flags & LG_NET_CURSOR_COLOR_TRANSFORM_LUT) &&
|
||||
!lgNetCursorColorLUTDecode((float *)item->colorTransform->lut,
|
||||
transform.lut, LG_NET_CURSOR_COLOR_LUT_BYTES))
|
||||
{
|
||||
networkCursorItemFree(item);
|
||||
return false;
|
||||
}
|
||||
item->pointer.flags = LG_TRANSPORT_POINTER_COLOR_TRANSFORM;
|
||||
item->pointer.sdrWhiteLevel = transform.sdrWhiteLevel;
|
||||
item->pointer.colorTransform = item->colorTransform;
|
||||
|
||||
LG_LOCK(cursor->lock);
|
||||
const bool valid = cursor->connected && cursor->available;
|
||||
bool queued = true;
|
||||
if (valid && transform.updateID > cursor->colorUpdateID)
|
||||
{
|
||||
cursor->colorUpdateID = transform.updateID;
|
||||
cursor->sdrWhiteLevel = transform.sdrWhiteLevel;
|
||||
if (cursor->stateApplied)
|
||||
{
|
||||
item->pointer.flags |= LG_TRANSPORT_POINTER_VISIBLE_VALID;
|
||||
if (cursor->visible)
|
||||
item->pointer.flags |= LG_TRANSPORT_POINTER_VISIBLE;
|
||||
}
|
||||
queued = networkCursorQueueNL(cursor, item);
|
||||
if (queued)
|
||||
item = NULL;
|
||||
}
|
||||
LG_UNLOCK(cursor->lock);
|
||||
networkCursorItemFree(item);
|
||||
return valid && queued;
|
||||
}
|
||||
|
||||
static bool networkCursorStatusPacket(const LGNetPacketView * packet,
|
||||
bool datagram)
|
||||
{
|
||||
if (datagram)
|
||||
return false;
|
||||
|
||||
LGNetCursorStatus status;
|
||||
if (lgNetCursorStatusDecode(&status,
|
||||
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
|
||||
packet->envelope.componentEpoch != 0 ||
|
||||
packet->envelope.requestID != status.stateSequence)
|
||||
return false;
|
||||
|
||||
const LGNetEnvelopeFlags base =
|
||||
LG_NET_ENVELOPE_FINAL | LG_NET_ENVELOPE_RELIABLE;
|
||||
return packet->envelope.flags == base ||
|
||||
packet->envelope.flags == (base | LG_NET_ENVELOPE_RESPONSE);
|
||||
}
|
||||
|
||||
bool networkCursorPacket(NetworkCursor * cursor,
|
||||
const LGNetPacketView * packet, bool datagram)
|
||||
{
|
||||
if (!cursor || !packet ||
|
||||
packet->envelope.service != LG_NET_SERVICE_CURSOR)
|
||||
return false;
|
||||
|
||||
switch (packet->envelope.messageType)
|
||||
{
|
||||
case LG_NET_CURSOR_MESSAGE_STATE:
|
||||
return networkCursorStatePacket(cursor, packet, datagram);
|
||||
case LG_NET_CURSOR_MESSAGE_POSITION:
|
||||
return networkCursorPositionPacket(cursor, packet, datagram);
|
||||
case LG_NET_CURSOR_MESSAGE_SHAPE:
|
||||
return networkCursorShapePacket(cursor, packet, datagram);
|
||||
case LG_NET_CURSOR_MESSAGE_TRANSFORM:
|
||||
return networkCursorTransformPacket(cursor, packet, datagram);
|
||||
case LG_NET_CURSOR_MESSAGE_STATUS:
|
||||
return networkCursorStatusPacket(packet, datagram);
|
||||
case LG_NET_CURSOR_MESSAGE_COLOR_TRANSFORM:
|
||||
return networkCursorColorPacket(cursor, packet, datagram);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
LG_TransportStatus networkCursorNext(NetworkCursor * cursor,
|
||||
LG_TransportPointer * pointer)
|
||||
{
|
||||
if (!cursor || !pointer)
|
||||
return LG_TRANSPORT_ERROR;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
LG_LOCK(cursor->lock);
|
||||
if (!cursor->connected)
|
||||
{
|
||||
LG_UNLOCK(cursor->lock);
|
||||
return LG_TRANSPORT_DISCONNECTED;
|
||||
}
|
||||
if (!cursor->available)
|
||||
{
|
||||
LG_UNLOCK(cursor->lock);
|
||||
return LG_TRANSPORT_UNAVAILABLE;
|
||||
}
|
||||
if (cursor->cancelled)
|
||||
{
|
||||
cursor->cancelled = false;
|
||||
LG_UNLOCK(cursor->lock);
|
||||
return LG_TRANSPORT_TIMEOUT;
|
||||
}
|
||||
if (cursor->outstanding)
|
||||
{
|
||||
LG_UNLOCK(cursor->lock);
|
||||
return LG_TRANSPORT_ERROR;
|
||||
}
|
||||
if (cursor->head)
|
||||
{
|
||||
struct NetworkCursorItem * item = cursor->head;
|
||||
cursor->head = item->next;
|
||||
if (!cursor->head)
|
||||
cursor->tail = NULL;
|
||||
--cursor->count;
|
||||
item->next = NULL;
|
||||
cursor->outstanding = item;
|
||||
*pointer = item->pointer;
|
||||
if (cursor->head)
|
||||
lgSignalEvent(cursor->event);
|
||||
LG_UNLOCK(cursor->lock);
|
||||
return LG_TRANSPORT_OK;
|
||||
}
|
||||
LG_UNLOCK(cursor->lock);
|
||||
lgWaitEvent(cursor->event, TIMEOUT_INFINITE);
|
||||
}
|
||||
}
|
||||
|
||||
void networkCursorRelease(NetworkCursor * cursor,
|
||||
LG_TransportPointer * pointer)
|
||||
{
|
||||
if (!cursor || !pointer)
|
||||
return;
|
||||
|
||||
LG_LOCK(cursor->lock);
|
||||
struct NetworkCursorItem * item = cursor->outstanding;
|
||||
cursor->outstanding = NULL;
|
||||
LG_UNLOCK(cursor->lock);
|
||||
networkCursorItemFree(item);
|
||||
memset(pointer, 0, sizeof(*pointer));
|
||||
}
|
||||
|
||||
void networkCursorCancel(NetworkCursor * cursor)
|
||||
{
|
||||
if (!cursor)
|
||||
return;
|
||||
|
||||
LG_LOCK(cursor->lock);
|
||||
cursor->cancelled = true;
|
||||
LG_UNLOCK(cursor->lock);
|
||||
lgSignalEvent(cursor->event);
|
||||
}
|
||||
|
||||
void networkCursorStop(NetworkCursor * cursor)
|
||||
{
|
||||
networkCursorCancel(cursor);
|
||||
}
|
||||
46
client/transports/Network/cursor.h
Normal file
46
client/transports/Network/cursor.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2026 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "connection.h"
|
||||
|
||||
#include "interface/transport.h"
|
||||
|
||||
#include <LGProtocol/NetworkProtocol.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct NetworkCursor NetworkCursor;
|
||||
|
||||
bool networkCursorCreate(NetworkCursor ** cursor,
|
||||
NetworkConnection * connection);
|
||||
void networkCursorDestroy(NetworkCursor ** cursor);
|
||||
|
||||
bool networkCursorConnectionState(NetworkCursor * cursor, bool connected);
|
||||
bool networkCursorPacket(NetworkCursor * cursor,
|
||||
const LGNetPacketView * packet, bool datagram);
|
||||
|
||||
LG_TransportStatus networkCursorNext(NetworkCursor * cursor,
|
||||
LG_TransportPointer * pointer);
|
||||
void networkCursorRelease(NetworkCursor * cursor,
|
||||
LG_TransportPointer * pointer);
|
||||
void networkCursorCancel(NetworkCursor * cursor);
|
||||
void networkCursorStop(NetworkCursor * cursor);
|
||||
@@ -20,8 +20,11 @@
|
||||
|
||||
#include "network.h"
|
||||
#include "connection.h"
|
||||
#include "control.h"
|
||||
#include "cursor.h"
|
||||
#include "input.h"
|
||||
#include "known_hosts.h"
|
||||
#include "recovery.h"
|
||||
|
||||
#include "app.h"
|
||||
#include "interface/renderer.h"
|
||||
@@ -30,6 +33,7 @@
|
||||
#include "common/event.h"
|
||||
#include "common/locking.h"
|
||||
#include <LGProtocol/NetworkProtocol.h>
|
||||
#include <LGProtocol/NetworkServices.h>
|
||||
#include "common/option.h"
|
||||
|
||||
#include <stddef.h>
|
||||
@@ -40,11 +44,35 @@
|
||||
|
||||
#define NETWORK_ADDRESS_MAX 255U
|
||||
#define NETWORK_ENDPOINT_TEXT_SIZE (NETWORK_ADDRESS_MAX + 9U)
|
||||
#define NETWORK_MAX_PAYLOAD (1024U * 1024U)
|
||||
#define NETWORK_MAX_PAYLOAD \
|
||||
(LG_NET_CURSOR_SHAPE_HEADER_WIRE_SIZE + LG_NET_CURSOR_MAX_SHAPE_LENGTH)
|
||||
#define NETWORK_MAX_DATAGRAM 1136U
|
||||
|
||||
static const LGNetCapability NETWORK_CAPABILITIES[] =
|
||||
{
|
||||
{
|
||||
.service = LG_NET_SERVICE_RECOVERY,
|
||||
.versionMin = LG_NET_RECOVERY_VERSION_MIN,
|
||||
.versionMax = LG_NET_RECOVERY_VERSION_CURRENT,
|
||||
.flags = LG_NET_CAPABILITY_RELIABLE,
|
||||
.features = 0,
|
||||
.maxPayloadLength = NETWORK_MAX_PAYLOAD,
|
||||
.maxDatagramPayload = 0,
|
||||
.maxBidirectionalStreams = 0,
|
||||
.maxUnidirectionalStreams = 0,
|
||||
},
|
||||
{
|
||||
.service = LG_NET_SERVICE_CURSOR,
|
||||
.versionMin = LG_NET_CURSOR_VERSION_MIN,
|
||||
.versionMax = LG_NET_CURSOR_VERSION_CURRENT,
|
||||
.flags = LG_NET_CAPABILITY_RELIABLE |
|
||||
LG_NET_CAPABILITY_DATAGRAM,
|
||||
.features = 0,
|
||||
.maxPayloadLength = NETWORK_MAX_PAYLOAD,
|
||||
.maxDatagramPayload = NETWORK_MAX_DATAGRAM,
|
||||
.maxBidirectionalStreams = 0,
|
||||
.maxUnidirectionalStreams = 0,
|
||||
},
|
||||
{
|
||||
.service = LG_NET_SERVICE_INPUT,
|
||||
.versionMin = LG_NET_INPUT_VERSION_MIN,
|
||||
@@ -57,6 +85,17 @@ static const LGNetCapability NETWORK_CAPABILITIES[] =
|
||||
.maxBidirectionalStreams = 0,
|
||||
.maxUnidirectionalStreams = 0,
|
||||
},
|
||||
{
|
||||
.service = LG_NET_SERVICE_CONTROL,
|
||||
.versionMin = LG_NET_CONTROL_VERSION_MIN,
|
||||
.versionMax = LG_NET_CONTROL_VERSION_CURRENT,
|
||||
.flags = LG_NET_CAPABILITY_RELIABLE,
|
||||
.features = 0,
|
||||
.maxPayloadLength = NETWORK_MAX_PAYLOAD,
|
||||
.maxDatagramPayload = 0,
|
||||
.maxBidirectionalStreams = 0,
|
||||
.maxUnidirectionalStreams = 0,
|
||||
},
|
||||
};
|
||||
|
||||
struct NetworkTrustPrompt
|
||||
@@ -69,7 +108,10 @@ struct NetworkTrustPrompt
|
||||
struct LG_Transport
|
||||
{
|
||||
NetworkConnection * connection;
|
||||
NetworkControl * control;
|
||||
NetworkCursor * cursor;
|
||||
NetworkInput * input;
|
||||
NetworkRecovery * recovery;
|
||||
NetworkKnownHosts knownHosts;
|
||||
LGEvent * frameEvent;
|
||||
LGEvent * promptIdle;
|
||||
@@ -265,6 +307,14 @@ static void networkSetUnavailable(LG_VideoComponentStatus * status,
|
||||
status->reason = reason;
|
||||
}
|
||||
|
||||
static void networkSetAvailable(LG_VideoComponentStatus * status)
|
||||
{
|
||||
if (!status->available)
|
||||
status->epoch = networkNextEpoch(status->epoch);
|
||||
status->available = true;
|
||||
status->reason = LG_TRANSPORT_OK;
|
||||
}
|
||||
|
||||
static void networkConnectionState(void * opaque, bool connected,
|
||||
LG_TransportStatus reason)
|
||||
{
|
||||
@@ -273,10 +323,17 @@ static void networkConnectionState(void * opaque, bool connected,
|
||||
LG_TRANSPORT_UNAVAILABLE : reason;
|
||||
|
||||
networkInputConnectionState(this->input, connected);
|
||||
networkControlConnectionState(this->control, connected);
|
||||
networkRecoveryConnectionState(this->recovery, connected);
|
||||
const bool cursorAvailable =
|
||||
networkCursorConnectionState(this->cursor, connected);
|
||||
|
||||
LG_LOCK(this->stateLock);
|
||||
networkSetUnavailable(&this->videoStatus.frame, status);
|
||||
networkSetUnavailable(&this->videoStatus.pointer, status);
|
||||
if (cursorAvailable)
|
||||
networkSetAvailable(&this->videoStatus.pointer);
|
||||
else
|
||||
networkSetUnavailable(&this->videoStatus.pointer, status);
|
||||
LG_UNLOCK(this->stateLock);
|
||||
|
||||
lgSignalEvent(this->frameEvent);
|
||||
@@ -287,10 +344,31 @@ static void networkPacket(void * opaque, const LGNetPacketView * packet,
|
||||
bool datagram)
|
||||
{
|
||||
LG_Transport * this = opaque;
|
||||
if (packet->envelope.service == LG_NET_SERVICE_INPUT &&
|
||||
!networkInputPacket(this->input, packet, datagram))
|
||||
bool valid;
|
||||
switch (packet->envelope.service)
|
||||
{
|
||||
DEBUG_ERROR("Invalid network input message");
|
||||
case LG_NET_SERVICE_RECOVERY:
|
||||
valid = networkRecoveryPacket(this->recovery, packet, datagram);
|
||||
break;
|
||||
case LG_NET_SERVICE_CURSOR:
|
||||
valid = networkCursorPacket(this->cursor, packet, datagram);
|
||||
break;
|
||||
case LG_NET_SERVICE_INPUT:
|
||||
valid = networkInputPacket(this->input, packet, datagram);
|
||||
break;
|
||||
case LG_NET_SERVICE_CONTROL:
|
||||
valid = networkControlPacket(this->control, packet, datagram);
|
||||
break;
|
||||
default:
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
DEBUG_ERROR("Invalid network service %u message %u",
|
||||
(unsigned)packet->envelope.service,
|
||||
(unsigned)packet->envelope.messageType);
|
||||
networkConnectionCancel(this->connection);
|
||||
}
|
||||
}
|
||||
@@ -351,14 +429,20 @@ static bool networkCreate(LG_Transport ** result)
|
||||
};
|
||||
if (!networkConnectionCreate(&this->connection, ¶ms))
|
||||
goto fail;
|
||||
if (!networkInputCreate(&this->input, this->connection))
|
||||
if (!networkControlCreate(&this->control, this->connection) ||
|
||||
!networkCursorCreate(&this->cursor, this->connection) ||
|
||||
!networkInputCreate(&this->input, this->connection) ||
|
||||
!networkRecoveryCreate(&this->recovery, this->connection))
|
||||
goto fail;
|
||||
|
||||
*result = this;
|
||||
return true;
|
||||
|
||||
fail:
|
||||
networkRecoveryDestroy(&this->recovery);
|
||||
networkInputDestroy(&this->input);
|
||||
networkCursorDestroy(&this->cursor);
|
||||
networkControlDestroy(&this->control);
|
||||
networkConnectionDestroy(&this->connection);
|
||||
if (this->frameEvent)
|
||||
lgFreeEvent(this->frameEvent);
|
||||
@@ -389,7 +473,10 @@ static void networkDestroy(LG_Transport ** transport)
|
||||
LG_UNLOCK(this->stateLock);
|
||||
LG_UNLOCK(this->callbackLock);
|
||||
|
||||
networkRecoveryDestroy(&this->recovery);
|
||||
networkInputDestroy(&this->input);
|
||||
networkCursorDestroy(&this->cursor);
|
||||
networkControlDestroy(&this->control);
|
||||
networkConnectionDestroy(&this->connection);
|
||||
lgFreeEvent(this->frameEvent);
|
||||
lgFreeEvent(this->promptIdle);
|
||||
@@ -418,6 +505,9 @@ static LG_TransportStatus networkConnect(LG_Transport * this,
|
||||
static void networkDisconnect(LG_Transport * this)
|
||||
{
|
||||
networkInputConnectionState(this->input, false);
|
||||
networkControlConnectionState(this->control, false);
|
||||
networkRecoveryConnectionState(this->recovery, false);
|
||||
networkCursorConnectionState(this->cursor, false);
|
||||
networkConnectionDisconnect(this->connection);
|
||||
lgSignalEvent(this->frameEvent);
|
||||
}
|
||||
@@ -516,6 +606,36 @@ static void networkStopFrame(LG_Transport * this)
|
||||
lgSignalEvent(this->frameEvent);
|
||||
}
|
||||
|
||||
static LG_TransportStatus networkNextPointer(LG_Transport * this,
|
||||
LG_TransportPointer * pointer)
|
||||
{
|
||||
const LG_TransportStatus status =
|
||||
networkCursorNext(this->cursor, pointer);
|
||||
if (status != LG_TRANSPORT_OK)
|
||||
return status;
|
||||
|
||||
LG_LOCK(this->stateLock);
|
||||
pointer->epoch = this->videoStatus.pointer.epoch;
|
||||
LG_UNLOCK(this->stateLock);
|
||||
return LG_TRANSPORT_OK;
|
||||
}
|
||||
|
||||
static void networkReleasePointer(LG_Transport * this,
|
||||
LG_TransportPointer * pointer)
|
||||
{
|
||||
networkCursorRelease(this->cursor, pointer);
|
||||
}
|
||||
|
||||
static void networkCancelPointerWait(LG_Transport * this)
|
||||
{
|
||||
networkCursorCancel(this->cursor);
|
||||
}
|
||||
|
||||
static void networkStopPointer(LG_Transport * this)
|
||||
{
|
||||
networkCursorStop(this->cursor);
|
||||
}
|
||||
|
||||
static const LG_FrameOps networkFrameOps =
|
||||
{
|
||||
.setStatusListener = networkSetStatusListener,
|
||||
@@ -527,6 +647,10 @@ static const LG_FrameOps networkFrameOps =
|
||||
.releaseFrame = networkReleaseFrame,
|
||||
.cancelFrameWait = networkCancelFrameWait,
|
||||
.stopFrame = networkStopFrame,
|
||||
.nextPointer = networkNextPointer,
|
||||
.releasePointer = networkReleasePointer,
|
||||
.cancelPointerWait = networkCancelPointerWait,
|
||||
.stopPointer = networkStopPointer,
|
||||
};
|
||||
|
||||
static const LG_VideoOps networkVideoOps =
|
||||
@@ -545,6 +669,10 @@ static const LG_VideoOps * networkGetVideoOps(LG_Transport * this)
|
||||
static const LG_InputOps * networkGetInputOps(LG_Transport * this,
|
||||
void ** opaque)
|
||||
{
|
||||
*opaque = NULL;
|
||||
if (!networkConnectionServiceAvailable(this->connection,
|
||||
LG_NET_SERVICE_INPUT, LG_NET_CAPABILITY_RELIABLE))
|
||||
return NULL;
|
||||
*opaque = this->input;
|
||||
return networkInputGetOps();
|
||||
}
|
||||
@@ -568,18 +696,26 @@ static const LG_ClipboardOps * networkGetClipboardOps(LG_Transport * this,
|
||||
static LG_TransportStatus networkSendControl(LG_Transport * this,
|
||||
const LG_TransportControl * control, LG_TransportControlToken * token)
|
||||
{
|
||||
(void)control;
|
||||
(void)token;
|
||||
return networkSessionValid(this) ?
|
||||
LG_TRANSPORT_UNAVAILABLE : LG_TRANSPORT_DISCONNECTED;
|
||||
return networkControlSend(this->control, control, token);
|
||||
}
|
||||
|
||||
static LG_TransportStatus networkControlStatus(LG_Transport * this,
|
||||
static LG_TransportStatus networkControlStatusOp(LG_Transport * this,
|
||||
LG_TransportControlToken token)
|
||||
{
|
||||
(void)token;
|
||||
return networkSessionValid(this) ?
|
||||
LG_TRANSPORT_UNAVAILABLE : LG_TRANSPORT_DISCONNECTED;
|
||||
return networkControlStatus(this->control, token);
|
||||
}
|
||||
|
||||
static LG_TransportStatus networkGetRecoveryInfo(LG_Transport * this,
|
||||
LG_RecoveryInfo * info)
|
||||
{
|
||||
return networkRecoveryGetInfo(this->recovery, info);
|
||||
}
|
||||
|
||||
static LG_TransportStatus networkRequestRecovery(LG_Transport * this,
|
||||
LG_RecoveryRequest request, uint64_t * instance, uint32_t * serial)
|
||||
{
|
||||
return networkRecoveryRequest(
|
||||
this->recovery, request, instance, serial);
|
||||
}
|
||||
|
||||
const LG_TransportOps LGT_Network =
|
||||
@@ -596,6 +732,8 @@ const LG_TransportOps LGT_Network =
|
||||
.getInputOps = networkGetInputOps,
|
||||
.getAudioOps = networkGetAudioOps,
|
||||
.getClipboardOps = networkGetClipboardOps,
|
||||
.getRecoveryInfo = networkGetRecoveryInfo,
|
||||
.requestRecovery = networkRequestRecovery,
|
||||
.sendControl = networkSendControl,
|
||||
.controlStatus = networkControlStatus,
|
||||
.controlStatus = networkControlStatusOp,
|
||||
};
|
||||
|
||||
575
client/transports/Network/recovery.c
Normal file
575
client/transports/Network/recovery.c
Normal file
@@ -0,0 +1,575 @@
|
||||
/**
|
||||
* 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 "recovery.h"
|
||||
|
||||
#include "common/event.h"
|
||||
#include "common/locking.h"
|
||||
|
||||
#include <LGProtocol/NetworkServices.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define NETWORK_RECOVERY_QUERY_TIMEOUT_MS 500U
|
||||
#define NETWORK_RECOVERY_DEFAULT_TIMEOUT_MS 30000U
|
||||
#define NETWORK_RECOVERY_REQUEST_CAPACITY 16U
|
||||
|
||||
struct NetworkRecoveryRequestEntry
|
||||
{
|
||||
uint64_t id;
|
||||
uint64_t session;
|
||||
uint32_t serial;
|
||||
LG_RecoveryRequest request;
|
||||
bool acknowledged;
|
||||
};
|
||||
|
||||
struct NetworkRecovery
|
||||
{
|
||||
NetworkConnection * connection;
|
||||
LGEvent * queryEvent;
|
||||
LG_Lock lock;
|
||||
|
||||
bool connected;
|
||||
bool available;
|
||||
uint16_t serviceVersion;
|
||||
uint64_t nextID;
|
||||
uint32_t nextSerial;
|
||||
|
||||
bool queryPending;
|
||||
uint64_t queryID;
|
||||
bool infoValid;
|
||||
LGNetRecoveryInfo info;
|
||||
char version[LG_NET_RECOVERY_MAX_VERSION_LENGTH + 1U];
|
||||
|
||||
uint32_t requestSerial;
|
||||
LG_RecoveryRequest request;
|
||||
uint32_t ackSerial;
|
||||
LG_RecoveryRequest ackRequest;
|
||||
|
||||
struct NetworkRecoveryRequestEntry requests[NETWORK_RECOVERY_REQUEST_CAPACITY];
|
||||
unsigned nextRequest;
|
||||
};
|
||||
|
||||
static uint64_t networkRecoveryNextID(uint64_t id)
|
||||
{
|
||||
++id;
|
||||
return id ? id : 1U;
|
||||
}
|
||||
|
||||
static uint32_t networkRecoveryNextSerial(uint32_t serial)
|
||||
{
|
||||
++serial;
|
||||
return serial ? serial : 1U;
|
||||
}
|
||||
|
||||
static bool networkRecoverySequenceNewer(uint64_t sequence,
|
||||
uint64_t reference)
|
||||
{
|
||||
return sequence > reference;
|
||||
}
|
||||
|
||||
static struct NetworkRecoveryRequestEntry * networkRecoveryFindRequestNL(
|
||||
NetworkRecovery * recovery, uint64_t id)
|
||||
{
|
||||
for (size_t index = 0;
|
||||
index < NETWORK_RECOVERY_REQUEST_CAPACITY; ++index)
|
||||
if (recovery->requests[index].id == id)
|
||||
return &recovery->requests[index];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct NetworkRecoveryRequestEntry * networkRecoveryReserveRequestNL(
|
||||
NetworkRecovery * recovery)
|
||||
{
|
||||
for (size_t offset = 0;
|
||||
offset < NETWORK_RECOVERY_REQUEST_CAPACITY; ++offset)
|
||||
{
|
||||
const unsigned index =
|
||||
(recovery->nextRequest + offset) % NETWORK_RECOVERY_REQUEST_CAPACITY;
|
||||
struct NetworkRecoveryRequestEntry * entry = &recovery->requests[index];
|
||||
if (entry->id && !entry->acknowledged)
|
||||
continue;
|
||||
|
||||
recovery->nextRequest =
|
||||
(index + 1U) % NETWORK_RECOVERY_REQUEST_CAPACITY;
|
||||
return entry;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool networkRecoveryState(
|
||||
LGNetRecoveryState state, LG_RecoveryState * result)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case LG_NET_RECOVERY_STATE_UNKNOWN:
|
||||
*result = LG_RECOVERY_STATE_UNKNOWN;
|
||||
return true;
|
||||
case LG_NET_RECOVERY_STATE_NORMAL:
|
||||
*result = LG_RECOVERY_STATE_NORMAL;
|
||||
return true;
|
||||
case LG_NET_RECOVERY_STATE_SWITCHING:
|
||||
*result = LG_RECOVERY_STATE_SWITCHING;
|
||||
return true;
|
||||
case LG_NET_RECOVERY_STATE_ACTIVE:
|
||||
*result = LG_RECOVERY_STATE_ACTIVE;
|
||||
return true;
|
||||
case LG_NET_RECOVERY_STATE_FAILED:
|
||||
*result = LG_RECOVERY_STATE_FAILED;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool networkRecoveryError(
|
||||
LGNetRecoveryError error, LG_RecoveryError * result)
|
||||
{
|
||||
switch (error)
|
||||
{
|
||||
case LG_NET_RECOVERY_ERROR_NONE:
|
||||
*result = LG_RECOVERY_ERR_NONE;
|
||||
return true;
|
||||
case LG_NET_RECOVERY_ERROR_UNSUPPORTED:
|
||||
*result = LG_RECOVERY_ERR_UNSUPPORTED;
|
||||
return true;
|
||||
case LG_NET_RECOVERY_ERROR_HELPER_UNAVAILABLE:
|
||||
*result = LG_RECOVERY_ERR_HELPER_UNAVAILABLE;
|
||||
return true;
|
||||
case LG_NET_RECOVERY_ERROR_TOPOLOGY_FAILED:
|
||||
*result = LG_RECOVERY_ERR_TOPOLOGY_FAILED;
|
||||
return true;
|
||||
case LG_NET_RECOVERY_ERROR_NO_FALLBACK_DISPLAY:
|
||||
*result = LG_RECOVERY_ERR_NO_FALLBACK_DISPLAY;
|
||||
return true;
|
||||
case LG_NET_RECOVERY_ERROR_BUSY:
|
||||
*result = LG_RECOVERY_ERR_BUSY;
|
||||
return true;
|
||||
case LG_NET_RECOVERY_ERROR_CAPACITY:
|
||||
*result = LG_RECOVERY_ERR_CAPACITY;
|
||||
return true;
|
||||
case LG_NET_RECOVERY_ERROR_TIMEOUT:
|
||||
/* The public transport ABI has no timeout-specific recovery error. */
|
||||
*result = LG_RECOVERY_ERR_HELPER_UNAVAILABLE;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void networkRecoveryClearNL(NetworkRecovery * recovery)
|
||||
{
|
||||
recovery->queryPending = false;
|
||||
recovery->queryID = 0;
|
||||
recovery->infoValid = false;
|
||||
recovery->info = (LGNetRecoveryInfo) { 0 };
|
||||
recovery->version[0] = '\0';
|
||||
recovery->requestSerial = 0;
|
||||
recovery->request = LG_RECOVERY_REQ_NONE;
|
||||
recovery->ackSerial = 0;
|
||||
recovery->ackRequest = LG_RECOVERY_REQ_NONE;
|
||||
recovery->nextRequest = 0;
|
||||
memset(recovery->requests, 0, sizeof(recovery->requests));
|
||||
}
|
||||
|
||||
static bool networkRecoveryBeginQuery(NetworkRecovery * recovery,
|
||||
uint64_t * queryID, uint16_t * version)
|
||||
{
|
||||
LG_LOCK(recovery->lock);
|
||||
if (!recovery->connected || !recovery->available)
|
||||
{
|
||||
LG_UNLOCK(recovery->lock);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (recovery->queryPending)
|
||||
{
|
||||
*queryID = recovery->queryID;
|
||||
*version = recovery->serviceVersion;
|
||||
LG_UNLOCK(recovery->lock);
|
||||
return true;
|
||||
}
|
||||
|
||||
recovery->nextID = networkRecoveryNextID(recovery->nextID);
|
||||
recovery->queryID = recovery->nextID;
|
||||
recovery->queryPending = true;
|
||||
*queryID = recovery->queryID;
|
||||
*version = recovery->serviceVersion;
|
||||
lgResetEvent(recovery->queryEvent);
|
||||
LG_UNLOCK(recovery->lock);
|
||||
|
||||
const LG_TransportStatus status = networkConnectionSend(
|
||||
recovery->connection, LG_NET_SERVICE_RECOVERY,
|
||||
LG_NET_RECOVERY_MESSAGE_GET_INFO, *version,
|
||||
LG_NET_ENVELOPE_ACK_REQUIRED | LG_NET_ENVELOPE_FINAL,
|
||||
0, *queryID, NULL, 0);
|
||||
if (status == LG_TRANSPORT_OK)
|
||||
return true;
|
||||
|
||||
LG_LOCK(recovery->lock);
|
||||
if (recovery->queryID == *queryID)
|
||||
{
|
||||
recovery->queryPending = false;
|
||||
recovery->queryID = 0;
|
||||
}
|
||||
LG_UNLOCK(recovery->lock);
|
||||
lgSignalEvent(recovery->queryEvent);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool networkRecoveryCreate(NetworkRecovery ** result,
|
||||
NetworkConnection * connection)
|
||||
{
|
||||
if (!result || !connection)
|
||||
return false;
|
||||
|
||||
NetworkRecovery * recovery = calloc(1, sizeof(*recovery));
|
||||
if (!recovery)
|
||||
return false;
|
||||
|
||||
LG_LOCK_INIT(recovery->lock);
|
||||
recovery->queryEvent = lgCreateEvent(false, 0);
|
||||
if (!recovery->queryEvent)
|
||||
{
|
||||
LG_LOCK_FREE(recovery->lock);
|
||||
free(recovery);
|
||||
return false;
|
||||
}
|
||||
|
||||
recovery->connection = connection;
|
||||
*result = recovery;
|
||||
return true;
|
||||
}
|
||||
|
||||
void networkRecoveryDestroy(NetworkRecovery ** result)
|
||||
{
|
||||
if (!result || !*result)
|
||||
return;
|
||||
|
||||
NetworkRecovery * recovery = *result;
|
||||
lgSignalEvent(recovery->queryEvent);
|
||||
lgFreeEvent(recovery->queryEvent);
|
||||
LG_LOCK_FREE(recovery->lock);
|
||||
free(recovery);
|
||||
*result = NULL;
|
||||
}
|
||||
|
||||
void networkRecoveryConnectionState(NetworkRecovery * recovery,
|
||||
bool connected)
|
||||
{
|
||||
uint16_t version = 0;
|
||||
const bool available = connected &&
|
||||
networkConnectionServiceAvailable(recovery->connection,
|
||||
LG_NET_SERVICE_RECOVERY, LG_NET_CAPABILITY_RELIABLE) &&
|
||||
networkConnectionServiceVersion(recovery->connection,
|
||||
LG_NET_SERVICE_RECOVERY, &version);
|
||||
|
||||
LG_LOCK(recovery->lock);
|
||||
recovery->connected = connected;
|
||||
recovery->available = available;
|
||||
recovery->serviceVersion = available ? version : 0;
|
||||
networkRecoveryClearNL(recovery);
|
||||
LG_UNLOCK(recovery->lock);
|
||||
lgSignalEvent(recovery->queryEvent);
|
||||
|
||||
if (available)
|
||||
{
|
||||
uint64_t queryID;
|
||||
networkRecoveryBeginQuery(recovery, &queryID, &version);
|
||||
}
|
||||
}
|
||||
|
||||
static bool networkRecoveryInfoPacket(NetworkRecovery * recovery,
|
||||
const LGNetPacketView * packet)
|
||||
{
|
||||
const LGNetEnvelopeFlags expected =
|
||||
LG_NET_ENVELOPE_RESPONSE |
|
||||
LG_NET_ENVELOPE_FINAL |
|
||||
LG_NET_ENVELOPE_RELIABLE;
|
||||
if (packet->envelope.flags != expected ||
|
||||
packet->envelope.componentEpoch != 0 ||
|
||||
!packet->envelope.requestID)
|
||||
return false;
|
||||
|
||||
LGNetRecoveryInfo info;
|
||||
if (lgNetRecoveryInfoDecode(&info,
|
||||
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK)
|
||||
return false;
|
||||
|
||||
char version[LG_NET_RECOVERY_MAX_VERSION_LENGTH + 1U];
|
||||
if (info.versionLength)
|
||||
memcpy(version, info.version, info.versionLength);
|
||||
version[info.versionLength] = '\0';
|
||||
info.version = NULL;
|
||||
|
||||
LG_LOCK(recovery->lock);
|
||||
const bool valid = recovery->connected && recovery->available &&
|
||||
recovery->queryPending &&
|
||||
recovery->queryID == packet->envelope.requestID;
|
||||
if (valid)
|
||||
{
|
||||
if (!recovery->infoValid ||
|
||||
!networkRecoverySequenceNewer(
|
||||
recovery->info.statusSequence, info.statusSequence))
|
||||
{
|
||||
recovery->info = info;
|
||||
memcpy(recovery->version, version, info.versionLength + 1U);
|
||||
}
|
||||
recovery->infoValid = true;
|
||||
recovery->queryPending = false;
|
||||
recovery->queryID = 0;
|
||||
}
|
||||
LG_UNLOCK(recovery->lock);
|
||||
if (valid)
|
||||
lgSignalEvent(recovery->queryEvent);
|
||||
return valid;
|
||||
}
|
||||
|
||||
static bool networkRecoveryStatusPacket(NetworkRecovery * recovery,
|
||||
const LGNetPacketView * packet)
|
||||
{
|
||||
LGNetRecoveryStatus status;
|
||||
if (lgNetRecoveryStatusDecode(&status,
|
||||
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
|
||||
status.requestID != packet->envelope.requestID ||
|
||||
packet->envelope.componentEpoch != 0)
|
||||
return false;
|
||||
|
||||
const bool terminal =
|
||||
status.state == LG_NET_RECOVERY_STATE_NORMAL ||
|
||||
status.state == LG_NET_RECOVERY_STATE_ACTIVE ||
|
||||
status.state == LG_NET_RECOVERY_STATE_FAILED;
|
||||
const LGNetEnvelopeFlags expected = LG_NET_ENVELOPE_RELIABLE |
|
||||
(status.requestID ? LG_NET_ENVELOPE_RESPONSE : 0) |
|
||||
(terminal ? LG_NET_ENVELOPE_FINAL : 0);
|
||||
if (packet->envelope.flags != expected)
|
||||
return false;
|
||||
|
||||
LG_LOCK(recovery->lock);
|
||||
struct NetworkRecoveryRequestEntry * request = status.requestID ?
|
||||
networkRecoveryFindRequestNL(recovery, status.requestID) : NULL;
|
||||
const bool valid =
|
||||
recovery->connected && recovery->available &&
|
||||
recovery->infoValid && status.sessionID == recovery->info.sessionID &&
|
||||
(!status.requestID ||
|
||||
(request && request->session == status.sessionID));
|
||||
if (valid && (!networkRecoverySequenceNewer(
|
||||
recovery->info.statusSequence, status.statusSequence)))
|
||||
{
|
||||
recovery->info.statusSequence = status.statusSequence;
|
||||
recovery->info.state = status.state;
|
||||
recovery->info.error = status.error;
|
||||
recovery->info.flags = status.flags;
|
||||
if (request && terminal)
|
||||
{
|
||||
request->acknowledged = true;
|
||||
recovery->ackSerial = request->serial;
|
||||
recovery->ackRequest = request->request;
|
||||
}
|
||||
}
|
||||
LG_UNLOCK(recovery->lock);
|
||||
return valid;
|
||||
}
|
||||
|
||||
bool networkRecoveryPacket(NetworkRecovery * recovery,
|
||||
const LGNetPacketView * packet, bool datagram)
|
||||
{
|
||||
if (!recovery || !packet || datagram ||
|
||||
packet->envelope.service != LG_NET_SERVICE_RECOVERY)
|
||||
return false;
|
||||
|
||||
switch (packet->envelope.messageType)
|
||||
{
|
||||
case LG_NET_RECOVERY_MESSAGE_INFO:
|
||||
return networkRecoveryInfoPacket(recovery, packet);
|
||||
|
||||
case LG_NET_RECOVERY_MESSAGE_STATUS:
|
||||
return networkRecoveryStatusPacket(recovery, packet);
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool networkRecoveryCopyInfoNL(const NetworkRecovery * recovery,
|
||||
LG_RecoveryInfo * result)
|
||||
{
|
||||
LG_RecoveryState state;
|
||||
LG_RecoveryError error;
|
||||
if (!recovery->infoValid ||
|
||||
!networkRecoveryState(recovery->info.state, &state) ||
|
||||
!networkRecoveryError(recovery->info.error, &error))
|
||||
return false;
|
||||
|
||||
memset(result, 0, sizeof(*result));
|
||||
result->abiVersion = recovery->serviceVersion;
|
||||
if (recovery->info.capabilities & LG_NET_RECOVERY_CAP_DISPLAY)
|
||||
result->capabilities |= LG_RECOVERY_CAP_DISPLAY;
|
||||
result->instance = recovery->info.sessionID;
|
||||
result->heartbeat = (uint32_t)recovery->info.statusSequence;
|
||||
const size_t versionLength = recovery->info.versionLength <
|
||||
sizeof(result->producerVersion) - 1U ?
|
||||
recovery->info.versionLength : sizeof(result->producerVersion) - 1U;
|
||||
memcpy(result->producerVersion, recovery->version, versionLength);
|
||||
result->producerVersion[versionLength] = '\0';
|
||||
result->versionCount = 1;
|
||||
memcpy(result->versions[0].component, "Network", sizeof("Network"));
|
||||
result->versions[0].version = recovery->serviceVersion;
|
||||
|
||||
result->requestSerial = recovery->requestSerial;
|
||||
result->request = recovery->request;
|
||||
result->ackSerial = recovery->ackSerial;
|
||||
result->ackRequest = recovery->ackRequest;
|
||||
result->state = state;
|
||||
result->error = error;
|
||||
return true;
|
||||
}
|
||||
|
||||
LG_TransportStatus networkRecoveryGetInfo(NetworkRecovery * recovery,
|
||||
LG_RecoveryInfo * info)
|
||||
{
|
||||
if (!recovery || !info)
|
||||
return LG_TRANSPORT_ERROR;
|
||||
|
||||
uint64_t queryID;
|
||||
uint16_t version;
|
||||
if (!networkRecoveryBeginQuery(recovery, &queryID, &version))
|
||||
{
|
||||
LG_LOCK(recovery->lock);
|
||||
const bool connected = recovery->connected;
|
||||
const bool copied = networkRecoveryCopyInfoNL(recovery, info);
|
||||
LG_UNLOCK(recovery->lock);
|
||||
if (copied)
|
||||
return LG_TRANSPORT_OK;
|
||||
return connected ? LG_TRANSPORT_UNAVAILABLE : LG_TRANSPORT_DISCONNECTED;
|
||||
}
|
||||
|
||||
LG_LOCK(recovery->lock);
|
||||
bool copied = networkRecoveryCopyInfoNL(recovery, info);
|
||||
LG_UNLOCK(recovery->lock);
|
||||
if (copied)
|
||||
return LG_TRANSPORT_OK;
|
||||
|
||||
lgWaitEvent(recovery->queryEvent, NETWORK_RECOVERY_QUERY_TIMEOUT_MS);
|
||||
LG_LOCK(recovery->lock);
|
||||
const bool connected = recovery->connected;
|
||||
copied = networkRecoveryCopyInfoNL(recovery, info);
|
||||
LG_UNLOCK(recovery->lock);
|
||||
if (copied)
|
||||
return LG_TRANSPORT_OK;
|
||||
return connected ? LG_TRANSPORT_UNAVAILABLE : LG_TRANSPORT_DISCONNECTED;
|
||||
}
|
||||
|
||||
LG_TransportStatus networkRecoveryRequest(NetworkRecovery * recovery,
|
||||
LG_RecoveryRequest request, uint64_t * instance, uint32_t * serial)
|
||||
{
|
||||
if (!recovery || (request != LG_RECOVERY_REQ_NORMAL &&
|
||||
request != LG_RECOVERY_REQ_RECOVERY))
|
||||
return LG_TRANSPORT_ERROR;
|
||||
|
||||
LG_RecoveryInfo current;
|
||||
const LG_TransportStatus infoStatus =
|
||||
networkRecoveryGetInfo(recovery, ¤t);
|
||||
if (infoStatus != LG_TRANSPORT_OK)
|
||||
return infoStatus;
|
||||
if (!(current.capabilities & LG_RECOVERY_CAP_DISPLAY))
|
||||
return LG_TRANSPORT_UNAVAILABLE;
|
||||
|
||||
uint64_t id;
|
||||
uint32_t requestSerial;
|
||||
uint16_t version;
|
||||
LGNetRecoveryRequest wire;
|
||||
LG_LOCK(recovery->lock);
|
||||
struct NetworkRecoveryRequestEntry * entry =
|
||||
networkRecoveryReserveRequestNL(recovery);
|
||||
if (!entry || !recovery->connected || !recovery->available ||
|
||||
!recovery->infoValid ||
|
||||
recovery->info.sessionID != current.instance)
|
||||
{
|
||||
LG_UNLOCK(recovery->lock);
|
||||
return LG_TRANSPORT_UNAVAILABLE;
|
||||
}
|
||||
|
||||
id = networkRecoveryNextID(recovery->nextID);
|
||||
requestSerial = networkRecoveryNextSerial(recovery->nextSerial);
|
||||
recovery->nextID = id;
|
||||
recovery->nextSerial = requestSerial;
|
||||
version = recovery->serviceVersion;
|
||||
const uint32_t timeout = recovery->info.maxTransitionMs ?
|
||||
recovery->info.maxTransitionMs : NETWORK_RECOVERY_DEFAULT_TIMEOUT_MS;
|
||||
wire = (LGNetRecoveryRequest)
|
||||
{
|
||||
.requestID = id,
|
||||
.sessionID = recovery->info.sessionID,
|
||||
.expectedStatusSequence = recovery->info.statusSequence,
|
||||
.timeoutMs = timeout > LG_NET_RECOVERY_MAX_TIMEOUT_MS ?
|
||||
LG_NET_RECOVERY_MAX_TIMEOUT_MS : timeout,
|
||||
.flags = request == LG_RECOVERY_REQ_RECOVERY ?
|
||||
LG_NET_RECOVERY_REQUEST_ACTIVE : 0,
|
||||
};
|
||||
*entry = (struct NetworkRecoveryRequestEntry)
|
||||
{
|
||||
.id = id,
|
||||
.session = wire.sessionID,
|
||||
.serial = requestSerial,
|
||||
.request = request,
|
||||
.acknowledged = false,
|
||||
};
|
||||
LG_UNLOCK(recovery->lock);
|
||||
|
||||
uint8_t payload[LG_NET_RECOVERY_REQUEST_WIRE_SIZE];
|
||||
if (!lgNetRecoveryRequestEncode(payload, sizeof(payload), &wire))
|
||||
goto fail;
|
||||
|
||||
const LG_TransportStatus status = networkConnectionSend(
|
||||
recovery->connection, LG_NET_SERVICE_RECOVERY,
|
||||
LG_NET_RECOVERY_MESSAGE_REQUEST, version,
|
||||
LG_NET_ENVELOPE_ACK_REQUIRED | LG_NET_ENVELOPE_FINAL,
|
||||
0, id, payload, sizeof(payload));
|
||||
if (status != LG_TRANSPORT_OK)
|
||||
{
|
||||
LG_LOCK(recovery->lock);
|
||||
entry = networkRecoveryFindRequestNL(recovery, id);
|
||||
if (entry)
|
||||
*entry = (struct NetworkRecoveryRequestEntry) { 0 };
|
||||
LG_UNLOCK(recovery->lock);
|
||||
return status;
|
||||
}
|
||||
|
||||
LG_LOCK(recovery->lock);
|
||||
recovery->requestSerial = requestSerial;
|
||||
recovery->request = request;
|
||||
LG_UNLOCK(recovery->lock);
|
||||
if (instance)
|
||||
*instance = wire.sessionID;
|
||||
if (serial)
|
||||
*serial = requestSerial;
|
||||
return LG_TRANSPORT_OK;
|
||||
|
||||
fail:
|
||||
LG_LOCK(recovery->lock);
|
||||
entry = networkRecoveryFindRequestNL(recovery, id);
|
||||
if (entry)
|
||||
*entry = (struct NetworkRecoveryRequestEntry) { 0 };
|
||||
LG_UNLOCK(recovery->lock);
|
||||
return LG_TRANSPORT_ERROR;
|
||||
}
|
||||
45
client/transports/Network/recovery.h
Normal file
45
client/transports/Network/recovery.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2026 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "connection.h"
|
||||
|
||||
#include "interface/transport.h"
|
||||
|
||||
#include <LGProtocol/NetworkProtocol.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct NetworkRecovery NetworkRecovery;
|
||||
|
||||
bool networkRecoveryCreate(NetworkRecovery ** recovery,
|
||||
NetworkConnection * connection);
|
||||
void networkRecoveryDestroy(NetworkRecovery ** recovery);
|
||||
|
||||
void networkRecoveryConnectionState(NetworkRecovery * recovery,
|
||||
bool connected);
|
||||
bool networkRecoveryPacket(NetworkRecovery * recovery,
|
||||
const LGNetPacketView * packet, bool datagram);
|
||||
|
||||
LG_TransportStatus networkRecoveryGetInfo(NetworkRecovery * recovery,
|
||||
LG_RecoveryInfo * info);
|
||||
LG_TransportStatus networkRecoveryRequest(NetworkRecovery * recovery,
|
||||
LG_RecoveryRequest request, uint64_t * instance, uint32_t * serial);
|
||||
Reference in New Issue
Block a user