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:
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user