mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-11-12 23:07:21 +00:00
370 lines
11 KiB
C
370 lines
11 KiB
C
/**
|
|
* Looking Glass
|
|
* Copyright © 2017-2026 The Looking Glass Authors
|
|
* https://looking-glass.io
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the Free
|
|
* Software Foundation; either version 2 of the License, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
|
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "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;
|
|
}
|
|
}
|