[client] clipboard: use LGMP payload streams

Attach the advertised clipboard lanes before claiming ownership, prefer
the stream transport, and activate only the acknowledged client and
epoch binding.

Move representation and file payloads through fixed-slot reservations
while status and lifecycle control remain on the LGMP queue. Retain a
legacy v4 fallback for other hosts.

Hold each host-to-client reservation until the consumer accepts its
record, return credits in order, and drain queued traffic in bounded
bursts before sleeping.
This commit is contained in:
Geoffrey McRae
2026-08-15 13:55:53 +10:00
parent 72522dadb8
commit d28f576145

View File

@@ -28,6 +28,8 @@
#include "common/thread.h" #include "common/thread.h"
#include "common/time.h" #include "common/time.h"
#include <lgmp/stream.h>
#include <errno.h> #include <errno.h>
#include <stdatomic.h> #include <stdatomic.h>
#include <stdint.h> #include <stdint.h>
@@ -90,6 +92,8 @@ struct LGMPClipboard
{ {
PLGMPClient client; PLGMPClient client;
PLGMPClientQueue queue; PLGMPClientQueue queue;
PLGMPClientStream hostToClientStream;
PLGMPClientStream clientToHostStream;
LG_Lock lock; LG_Lock lock;
LG_Lock eventLock; LG_Lock eventLock;
LG_Lock statusLock; LG_Lock statusLock;
@@ -102,6 +106,10 @@ struct LGMPClipboard
bool statusValid; bool statusValid;
bool claimed; bool claimed;
bool ownerConfirmed; bool ownerConfirmed;
KVMFRClipboardTransportFlags transports;
KVMFRClipboardTransportFlags claimTransport;
KVMFRStreamDescriptor hostToClientDescriptor;
KVMFRStreamDescriptor clientToHostDescriptor;
uint32_t clientID; uint32_t clientID;
uint32_t endpointGeneration; uint32_t endpointGeneration;
uint32_t providerGeneration; uint32_t providerGeneration;
@@ -128,8 +136,10 @@ struct LGMPClipboard
bool held; bool held;
bool heldReady; bool heldReady;
bool heldStream;
enum HeldPhase heldPhase; enum HeldPhase heldPhase;
LGMPMessage heldMessage; LGMPMessage heldMessage;
LGMPStreamBuffer heldStreamBuffer;
KVMFRClipboardMessage heldRecord; KVMFRClipboardMessage heldRecord;
bool heldFile; bool heldFile;
LG_ClipboardFileRequest heldFileRequest; LG_ClipboardFileRequest heldFileRequest;
@@ -162,6 +172,118 @@ struct LGMPClipboard
void * statusOpaque; void * statusOpaque;
}; };
static void connectionFailed(LGMPClipboard * clipboard,
LGMP_STATUS status);
static void connectionCorrupted(LGMPClipboard * clipboard,
const char * reason);
_Static_assert(sizeof(struct LGMPStreamDescriptor) ==
sizeof(KVMFRStreamDescriptor),
"LGMP and KVMFR stream descriptor sizes differ");
_Static_assert(offsetof(struct LGMPStreamDescriptor, offset) ==
offsetof(KVMFRStreamDescriptor, offset),
"LGMP and KVMFR stream descriptor offsets differ");
_Static_assert(offsetof(struct LGMPStreamDescriptor, slotSize) ==
offsetof(KVMFRStreamDescriptor, slotSize),
"LGMP and KVMFR stream descriptor geometry differs");
static struct LGMPStreamDescriptor streamDescriptorFromWire(
const KVMFRStreamDescriptor * wire)
{
return (struct LGMPStreamDescriptor)
{
.magic = wire->magic,
.version = wire->version,
.size = wire->size,
.offset = wire->offset,
.regionSize = wire->regionSize,
.direction = wire->direction,
.policy = wire->policy,
.slotCount = wire->slotCount,
.slotSize = wire->slotSize,
};
}
static bool validStreamDescriptor(const KVMFRStreamDescriptor * descriptor,
enum LGMPStreamDirection direction)
{
return descriptor->magic == LGMP_STREAM_DESCRIPTOR_MAGIC &&
descriptor->version == LGMP_STREAM_DESCRIPTOR_VERSION &&
descriptor->size == sizeof(*descriptor) && descriptor->offset &&
descriptor->regionSize && descriptor->direction == (uint32_t)direction &&
descriptor->policy == LGMP_STREAM_RELIABLE_FIFO &&
descriptor->slotCount == KVMFR_CLIPBOARD_STREAM_SLOT_COUNT &&
descriptor->slotSize == KVMFR_CLIPBOARD_STREAM_RECORD_BYTES;
}
static void detachStreamsNL(LGMPClipboard * clipboard)
{
lgmpClientStreamDetach(&clipboard->clientToHostStream);
lgmpClientStreamDetach(&clipboard->hostToClientStream);
memset(&clipboard->clientToHostDescriptor, 0,
sizeof(clipboard->clientToHostDescriptor));
memset(&clipboard->hostToClientDescriptor, 0,
sizeof(clipboard->hostToClientDescriptor));
}
static bool attachStreamsNL(LGMPClipboard * clipboard,
const KVMFRClipboardStatus * status)
{
if (!(status->transports & KVMFR_CLIPBOARD_TRANSPORT_STREAM))
{
detachStreamsNL(clipboard);
return false;
}
if (clipboard->hostToClientStream && clipboard->clientToHostStream &&
!memcmp(&clipboard->hostToClientDescriptor, &status->hostToClient,
sizeof(status->hostToClient)) &&
!memcmp(&clipboard->clientToHostDescriptor, &status->clientToHost,
sizeof(status->clientToHost)))
return true;
detachStreamsNL(clipboard);
const struct LGMPStreamDescriptor hostToClient =
streamDescriptorFromWire(&status->hostToClient);
LGMP_STATUS result = lgmpClientStreamAttach(clipboard->client,
&hostToClient, &clipboard->hostToClientStream);
if (result != LGMP_OK)
{
DEBUG_WARN("Failed to attach host-to-client clipboard stream: %s",
lgmpStatusString(result));
return false;
}
const struct LGMPStreamDescriptor clientToHost =
streamDescriptorFromWire(&status->clientToHost);
result = lgmpClientStreamAttach(clipboard->client,
&clientToHost, &clipboard->clientToHostStream);
if (result != LGMP_OK)
{
DEBUG_WARN("Failed to attach client-to-host clipboard stream: %s",
lgmpStatusString(result));
detachStreamsNL(clipboard);
return false;
}
clipboard->hostToClientDescriptor = status->hostToClient;
clipboard->clientToHostDescriptor = status->clientToHost;
return true;
}
static LGMP_STATUS activateStreamsNL(LGMPClipboard * clipboard)
{
if (!clipboard->hostToClientStream || !clipboard->clientToHostStream)
return LGMP_ERR_STREAM_UNBOUND;
LGMP_STATUS status = lgmpClientStreamActivate(
clipboard->hostToClientStream, NULL);
if (status != LGMP_OK)
return status;
return lgmpClientStreamActivate(
clipboard->clientToHostStream, NULL);
}
static LG_ClipboardData fromWireFormat(KVMFRClipboardFormat format) static LG_ClipboardData fromWireFormat(KVMFRClipboardFormat format)
{ {
switch (format) switch (format)
@@ -545,21 +667,74 @@ static struct Grant * availableGrantNL(LGMPClipboard * clipboard)
return NULL; return NULL;
} }
static bool streamWriteNL(LGMPClipboard * clipboard,
const KVMFRClipboardMessage * record, const void * data)
{
if (!clipboard->clientToHostStream)
return false;
LGMPStreamBuffer buffer = { 0 };
LGMP_STATUS status = lgmpClientStreamWriteAcquire(
clipboard->clientToHostStream, &buffer);
if (status == LGMP_ERR_STREAM_FULL ||
status == LGMP_ERR_STREAM_UNBOUND ||
status == LGMP_ERR_STREAM_STALE)
return false;
if (status != LGMP_OK)
{
connectionFailed(clipboard, status);
return false;
}
const uint32_t bytes = sizeof(*record) + record->length;
if (!buffer.data || buffer.capacity < bytes)
{
const LGMP_STATUS cancel = lgmpClientStreamWriteCancel(
clipboard->clientToHostStream, &buffer);
if (cancel != LGMP_OK)
DEBUG_WARN("Failed to cancel an undersized clipboard stream "
"reservation: %s", lgmpStatusString(cancel));
DEBUG_WARN("Client-to-host clipboard stream returned an undersized "
"slot: capacity=%u required=%u", buffer.capacity, bytes);
connectionCorrupted(clipboard, "undersized stream slot");
return false;
}
memcpy(buffer.data, record, sizeof(*record));
if (record->length)
memcpy((uint8_t *)buffer.data + sizeof(*record), data,
record->length);
status = lgmpClientStreamWriteCommit(
clipboard->clientToHostStream, &buffer, bytes);
if (status == LGMP_OK)
return true;
DEBUG_WARN("Failed to commit client-to-host clipboard stream data: %s",
lgmpStatusString(status));
connectionFailed(clipboard, status);
return false;
}
static bool enqueueDataNL(LGMPClipboard * clipboard, static bool enqueueDataNL(LGMPClipboard * clipboard,
KVMFRClipboardMessage record, const void * data) KVMFRClipboardMessage record, const void * data)
{ {
if (!clipboard->connected || !clipboard->queue || if (!clipboard->connected || !clipboard->queue ||
clipboard->pendingCount >= CLIPBOARD_PENDING_NORMAL_MAX ||
record.length > KVMFR_CLIPBOARD_DATA_BYTES || record.length > KVMFR_CLIPBOARD_DATA_BYTES ||
(record.length && !data)) (record.length && !data))
return false; return false;
record.version = KVMFR_CLIPBOARD_VERSION;
record.generation = clipboard->claimGeneration;
if (clipboard->claimTransport == KVMFR_CLIPBOARD_TRANSPORT_STREAM)
return streamWriteNL(clipboard, &record, data);
if (clipboard->claimTransport != KVMFR_CLIPBOARD_TRANSPORT_LEGACY ||
clipboard->pendingCount >= CLIPBOARD_PENDING_NORMAL_MAX)
return false;
struct Grant * grant = availableGrantNL(clipboard); struct Grant * grant = availableGrantNL(clipboard);
if (!grant) if (!grant)
return false; return false;
record.version = KVMFR_CLIPBOARD_VERSION;
record.generation = clipboard->claimGeneration;
memcpy(grant->header, &record, sizeof(record)); memcpy(grant->header, &record, sizeof(record));
if (record.length) if (record.length)
memcpy(grant->data, data, record.length); memcpy(grant->data, data, record.length);
@@ -572,6 +747,43 @@ static bool enqueueDataNL(LGMPClipboard * clipboard,
return true; return true;
} }
static bool streamWritableNL(LGMPClipboard * clipboard)
{
if (!clipboard->clientToHostStream ||
clipboard->claimTransport != KVMFR_CLIPBOARD_TRANSPORT_STREAM)
return false;
LGMPStreamBuffer buffer = { 0 };
LGMP_STATUS status = lgmpClientStreamWriteAcquire(
clipboard->clientToHostStream, &buffer);
if (status == LGMP_ERR_STREAM_FULL ||
status == LGMP_ERR_STREAM_UNBOUND ||
status == LGMP_ERR_STREAM_STALE)
return false;
if (status != LGMP_OK)
{
connectionFailed(clipboard, status);
return false;
}
status = lgmpClientStreamWriteCancel(
clipboard->clientToHostStream, &buffer);
if (status == LGMP_OK)
return true;
if (status != LGMP_ERR_STREAM_STALE)
connectionFailed(clipboard, status);
return false;
}
static bool dataWritableNL(LGMPClipboard * clipboard)
{
if (clipboard->claimTransport == KVMFR_CLIPBOARD_TRANSPORT_STREAM)
return streamWritableNL(clipboard);
return clipboard->claimTransport == KVMFR_CLIPBOARD_TRANSPORT_LEGACY &&
clipboard->pendingCount < CLIPBOARD_PENDING_NORMAL_MAX &&
availableGrantNL(clipboard);
}
static void clearWriteNL(LGMPClipboard * clipboard) static void clearWriteNL(LGMPClipboard * clipboard)
{ {
clipboard->writeBlocked = false; clipboard->writeBlocked = false;
@@ -602,6 +814,7 @@ static void clearProtocolNL(LGMPClipboard * clipboard)
{ {
clipboard->claimed = false; clipboard->claimed = false;
clipboard->ownerConfirmed = false; clipboard->ownerConfirmed = false;
clipboard->claimTransport = 0;
clipboard->publishedClaimGeneration = 0; clipboard->publishedClaimGeneration = 0;
clipboard->pendingHead = 0; clipboard->pendingHead = 0;
clipboard->pendingCount = 0; clipboard->pendingCount = 0;
@@ -617,17 +830,23 @@ static bool ensureClaimNL(LGMPClipboard * clipboard)
{ {
if (clipboard->claimed) if (clipboard->claimed)
return true; return true;
if (!clipboard->connected || !clipboard->available) if (!clipboard->connected || !clipboard->available ||
!clipboard->transports)
return false; return false;
KVMFRClipboardMessage claim = { 0 }; KVMFRClipboardMessage claim = { 0 };
nextNonzero(&clipboard->claimGeneration); nextNonzero(&clipboard->claimGeneration);
claim.type = KVMFR_CLIPBOARD_MESSAGE_CLAIM; claim.type = KVMFR_CLIPBOARD_MESSAGE_CLAIM;
claim.token = clipboard->endpointGeneration; claim.token = clipboard->endpointGeneration;
claim.flags =
(clipboard->transports & KVMFR_CLIPBOARD_TRANSPORT_STREAM) ?
KVMFR_CLIPBOARD_TRANSPORT_STREAM :
KVMFR_CLIPBOARD_TRANSPORT_LEGACY;
if (!enqueueRecordNL(clipboard, claim)) if (!enqueueRecordNL(clipboard, claim))
return false; return false;
clipboard->claimed = true; clipboard->claimed = true;
clipboard->ownerConfirmed = false; clipboard->ownerConfirmed = false;
clipboard->claimTransport = claim.flags;
return true; return true;
} }
@@ -667,32 +886,79 @@ static void notifyStatus(LGMPClipboard * clipboard)
LG_UNLOCK(clipboard->statusLock); LG_UNLOCK(clipboard->statusLock);
} }
static void connectionFailed(LGMPClipboard * clipboard, LGMP_STATUS status) static void connectionLostNL(LGMPClipboard * clipboard)
{ {
const bool changed = clipboard->connected || clipboard->available; const bool changed = clipboard->connected || clipboard->available;
if (clipboard->connected)
DEBUG_WARN("LGMP clipboard transport failed: %s",
lgmpStatusString(status));
clipboard->connected = false; clipboard->connected = false;
clipboard->available = false; clipboard->available = false;
clipboard->statusValid = false; clipboard->statusValid = false;
clipboard->transports = 0;
detachStreamsNL(clipboard);
clearProtocolNL(clipboard); clearProtocolNL(clipboard);
clipboard->held = false; clipboard->held = false;
clipboard->heldReady = false; clipboard->heldReady = false;
clipboard->heldStream = false;
memset(&clipboard->heldMessage, 0, sizeof(clipboard->heldMessage));
memset(&clipboard->heldStreamBuffer, 0,
sizeof(clipboard->heldStreamBuffer));
memset(&clipboard->heldRecord, 0, sizeof(clipboard->heldRecord));
clipboard->heldFile = false;
memset(&clipboard->heldFileRequest, 0,
sizeof(clipboard->heldFileRequest));
if (changed) if (changed)
nextNonzero(&clipboard->providerGeneration); nextNonzero(&clipboard->providerGeneration);
atomic_store_explicit(&clipboard->stop, true, memory_order_release); atomic_store_explicit(&clipboard->stop, true, memory_order_release);
} }
static void connectionFailed(LGMPClipboard * clipboard, LGMP_STATUS status)
{
if (clipboard->connected)
DEBUG_WARN("LGMP clipboard transport failed: %s",
lgmpStatusString(status));
connectionLostNL(clipboard);
}
static void connectionCorrupted(LGMPClipboard * clipboard,
const char * reason)
{
if (clipboard->connected)
DEBUG_WARN("LGMP clipboard transport is corrupted: %s", reason);
connectionLostNL(clipboard);
}
static bool validStatus(const KVMFRClipboardStatus * status) static bool validStatus(const KVMFRClipboardStatus * status)
{ {
const uint32_t validFlags = KVMFR_CLIPBOARD_STATUS_AVAILABLE | const uint32_t validFlags = KVMFR_CLIPBOARD_STATUS_AVAILABLE |
KVMFR_CLIPBOARD_STATUS_HAS_OWNER; KVMFR_CLIPBOARD_STATUS_HAS_OWNER;
const KVMFRStreamDescriptor emptyDescriptor = { 0 };
const uint32_t emptyReserved[4] = { 0 };
if (status->version != KVMFR_CLIPBOARD_VERSION || if (status->version != KVMFR_CLIPBOARD_VERSION ||
status->flags & ~validFlags || !status->generation || status->flags & ~validFlags || !status->generation ||
status->formats & ~KVMFR_CLIPBOARD_FORMAT_MASK_ALL || status->formats & ~KVMFR_CLIPBOARD_FORMAT_MASK_ALL ||
!status->lease || !status->lease ||
status->slotBytes != KVMFR_CLIPBOARD_DATA_BYTES) status->slotBytes != KVMFR_CLIPBOARD_DATA_BYTES ||
!status->transports ||
status->transports & ~KVMFR_CLIPBOARD_TRANSPORT_ALL ||
memcmp(status->reserved, emptyReserved, sizeof(emptyReserved)))
return false;
const bool streams =
(status->transports & KVMFR_CLIPBOARD_TRANSPORT_STREAM) != 0;
if (streams)
{
if (status->streamVersion != KVMFR_CLIPBOARD_STREAM_VERSION ||
status->streamSlotCount != KVMFR_CLIPBOARD_STREAM_SLOT_COUNT ||
!validStreamDescriptor(&status->hostToClient,
LGMP_STREAM_HOST_TO_CLIENT) ||
!validStreamDescriptor(&status->clientToHost,
LGMP_STREAM_CLIENT_TO_HOST))
return false;
}
else if (status->streamVersion || status->streamSlotCount ||
memcmp(&status->hostToClient, &emptyDescriptor,
sizeof(emptyDescriptor)) ||
memcmp(&status->clientToHost, &emptyDescriptor,
sizeof(emptyDescriptor)))
return false; return false;
const bool available = const bool available =
@@ -701,13 +967,16 @@ static bool validStatus(const KVMFRClipboardStatus * status)
(status->flags & KVMFR_CLIPBOARD_STATUS_HAS_OWNER) != 0; (status->flags & KVMFR_CLIPBOARD_STATUS_HAS_OWNER) != 0;
if (!available && (status->formats || owner)) if (!available && (status->formats || owner))
return false; return false;
return owner ? status->ownerClientID && status->ownerGeneration : return owner ? status->ownerClientID && status->ownerGeneration &&
!status->ownerClientID && !status->ownerGeneration; kvmfrClipboardTransportValid(status->ownerTransport) &&
(status->transports & status->ownerTransport) :
!status->ownerClientID && !status->ownerGeneration &&
!status->ownerTransport;
} }
static bool validateRecord(const LGMPClipboard * clipboard, static bool validateRecord(const LGMPClipboard * clipboard,
const KVMFRClipboardMessage * record, size_t size, const KVMFRClipboardMessage * record, size_t size,
KVMFRClipboardQueueType queueType) KVMFRClipboardQueueType queueType, bool stream)
{ {
if (size < sizeof(*record) || if (size < sizeof(*record) ||
record->version != KVMFR_CLIPBOARD_VERSION || record->version != KVMFR_CLIPBOARD_VERSION ||
@@ -721,8 +990,11 @@ static bool validateRecord(const LGMPClipboard * clipboard,
if (record->type == KVMFR_CLIPBOARD_MESSAGE_DATA || if (record->type == KVMFR_CLIPBOARD_MESSAGE_DATA ||
record->type == KVMFR_CLIPBOARD_MESSAGE_FILE_DATA) record->type == KVMFR_CLIPBOARD_MESSAGE_FILE_DATA)
{ {
if (size != sizeof(*record) + KVMFR_CLIPBOARD_DATA_BYTES || const size_t expected = sizeof(*record) +
queueType != KVMFR_CLIPBOARD_QUEUE_DATA) (stream ? record->length : KVMFR_CLIPBOARD_DATA_BYTES);
if (size != expected || queueType != KVMFR_CLIPBOARD_QUEUE_DATA ||
stream != (clipboard->claimTransport ==
KVMFR_CLIPBOARD_TRANSPORT_STREAM))
return false; return false;
} }
else if (queueType != KVMFR_CLIPBOARD_QUEUE_MESSAGE || else if (queueType != KVMFR_CLIPBOARD_QUEUE_MESSAGE ||
@@ -1228,22 +1500,44 @@ static void applyStatusNL(LGMPClipboard * clipboard,
const bool wasValid = clipboard->statusValid; const bool wasValid = clipboard->statusValid;
const bool wasAvailable = clipboard->available; const bool wasAvailable = clipboard->available;
const uint32_t oldGeneration = clipboard->endpointGeneration; const uint32_t oldGeneration = clipboard->endpointGeneration;
const KVMFRClipboardTransportFlags oldTransports =
clipboard->transports;
clipboard->statusValid = true; clipboard->statusValid = true;
clipboard->statusSerial = serial; clipboard->statusSerial = serial;
const bool streamsAttached = attachStreamsNL(clipboard, status);
const bool endpointAvailable = const bool endpointAvailable =
(status->flags & KVMFR_CLIPBOARD_STATUS_AVAILABLE) != 0; (status->flags & KVMFR_CLIPBOARD_STATUS_AVAILABLE) != 0;
clipboard->endpointGeneration = status->generation; clipboard->endpointGeneration = status->generation;
clipboard->transports = status->transports;
if (!streamsAttached)
clipboard->transports &= ~KVMFR_CLIPBOARD_TRANSPORT_STREAM;
const bool owned = (status->flags & KVMFR_CLIPBOARD_STATUS_HAS_OWNER) && const bool owned = (status->flags & KVMFR_CLIPBOARD_STATUS_HAS_OWNER) &&
clipboard->claimed && status->ownerClientID == clipboard->clientID && clipboard->claimed && status->ownerClientID == clipboard->clientID &&
status->ownerGeneration == clipboard->claimGeneration; status->ownerGeneration == clipboard->claimGeneration &&
status->ownerTransport == clipboard->claimTransport;
const bool ownedByOther = const bool ownedByOther =
(status->flags & KVMFR_CLIPBOARD_STATUS_HAS_OWNER) && !owned; (status->flags & KVMFR_CLIPBOARD_STATUS_HAS_OWNER) && !owned;
clipboard->available = endpointAvailable && !ownedByOther; const bool selectedUnavailable = clipboard->claimed &&
!(clipboard->transports & clipboard->claimTransport);
clipboard->available = endpointAvailable && !ownedByOther &&
clipboard->transports;
bool restore = false; bool restore = false;
if (owned && clipboard->claimTransport ==
KVMFR_CLIPBOARD_TRANSPORT_STREAM)
{
const LGMP_STATUS activate = activateStreamsNL(clipboard);
if (activate != LGMP_OK)
{
connectionFailed(clipboard, activate);
*changed = true;
return;
}
}
if (!endpointAvailable || oldGeneration != status->generation || if (!endpointAvailable || oldGeneration != status->generation ||
ownedByOther) ownedByOther || selectedUnavailable)
{ {
clearProtocolNL(clipboard); clearProtocolNL(clipboard);
restore = endpointAvailable && !ownedByOther; restore = endpointAvailable && !ownedByOther;
@@ -1264,7 +1558,8 @@ static void applyStatusNL(LGMPClipboard * clipboard,
clearProtocolNL(clipboard); clearProtocolNL(clipboard);
*changed = !wasValid || wasAvailable != clipboard->available || *changed = !wasValid || wasAvailable != clipboard->available ||
oldGeneration != clipboard->endpointGeneration; oldGeneration != clipboard->endpointGeneration ||
oldTransports != clipboard->transports;
if (*changed) if (*changed)
nextNonzero(&clipboard->providerGeneration); nextNonzero(&clipboard->providerGeneration);
} }
@@ -1305,6 +1600,8 @@ static bool processHeld(LGMPClipboard * clipboard)
const uint8_t * data; const uint8_t * data;
enum HeldPhase phase; enum HeldPhase phase;
bool file; bool file;
bool stream;
LGMPStreamBuffer streamBuffer;
LG_ClipboardFileRequest fileRequest; LG_ClipboardFileRequest fileRequest;
LG_LOCK(clipboard->lock); LG_LOCK(clipboard->lock);
if (!clipboard->held || !clipboard->heldReady) if (!clipboard->held || !clipboard->heldReady)
@@ -1313,8 +1610,11 @@ static bool processHeld(LGMPClipboard * clipboard)
return true; return true;
} }
record = clipboard->heldRecord; record = clipboard->heldRecord;
data = record.length ? stream = clipboard->heldStream;
(const uint8_t *)clipboard->heldMessage.mem + sizeof(record) : NULL; streamBuffer = clipboard->heldStreamBuffer;
data = !record.length ? NULL : stream ?
(const uint8_t *)streamBuffer.data + sizeof(record) :
(const uint8_t *)clipboard->heldMessage.mem + sizeof(record);
phase = clipboard->heldPhase; phase = clipboard->heldPhase;
file = clipboard->heldFile; file = clipboard->heldFile;
fileRequest = clipboard->heldFileRequest; fileRequest = clipboard->heldFileRequest;
@@ -1402,9 +1702,15 @@ static bool processHeld(LGMPClipboard * clipboard)
else if (matchingRead) else if (matchingRead)
clearReadNL(clipboard); clearReadNL(clipboard);
const LGMP_STATUS done = lgmpClientMessageDone(clipboard->queue); const LGMP_STATUS done = stream ?
lgmpClientStreamReadRelease(
clipboard->hostToClientStream, &streamBuffer) :
lgmpClientMessageDone(clipboard->queue);
clipboard->held = false; clipboard->held = false;
clipboard->heldStream = false;
memset(&clipboard->heldMessage, 0, sizeof(clipboard->heldMessage)); memset(&clipboard->heldMessage, 0, sizeof(clipboard->heldMessage));
memset(&clipboard->heldStreamBuffer, 0,
sizeof(clipboard->heldStreamBuffer));
memset(&clipboard->heldRecord, 0, sizeof(clipboard->heldRecord)); memset(&clipboard->heldRecord, 0, sizeof(clipboard->heldRecord));
clipboard->heldFile = false; clipboard->heldFile = false;
memset(&clipboard->heldFileRequest, 0, memset(&clipboard->heldFileRequest, 0,
@@ -1420,6 +1726,102 @@ static bool processHeld(LGMPClipboard * clipboard)
} }
} }
static bool processStream(LGMPClipboard * clipboard, bool * processed)
{
*processed = false;
LG_LOCK(clipboard->lock);
if (!clipboard->connected || clipboard->held ||
clipboard->claimTransport != KVMFR_CLIPBOARD_TRANSPORT_STREAM ||
!clipboard->hostToClientStream)
{
LG_UNLOCK(clipboard->lock);
return true;
}
LGMPStreamBuffer buffer = { 0 };
LGMP_STATUS status = lgmpClientStreamReadPeek(
clipboard->hostToClientStream, &buffer);
if (status == LGMP_ERR_STREAM_EMPTY ||
status == LGMP_ERR_STREAM_UNBOUND ||
status == LGMP_ERR_STREAM_STALE)
{
LG_UNLOCK(clipboard->lock);
return true;
}
if (status != LGMP_OK)
{
connectionFailed(clipboard, status);
LG_UNLOCK(clipboard->lock);
return false;
}
*processed = true;
KVMFRClipboardMessage record = { 0 };
if (buffer.data && buffer.size >= sizeof(record))
memcpy(&record, buffer.data, sizeof(record));
const bool valid = buffer.data &&
validateRecord(clipboard, &record, buffer.size,
KVMFR_CLIPBOARD_QUEUE_DATA, true);
if (!valid)
{
KVMFRClipboardMessage cancellation = { 0 };
bool queued = false;
const bool cancelled = rejectMalformedFileReadNL(
clipboard, &record, &cancellation, &queued);
status = lgmpClientStreamReadRelease(
clipboard->hostToClientStream, &buffer);
if (status != LGMP_OK)
connectionFailed(clipboard, status);
LG_UNLOCK(clipboard->lock);
if (cancelled)
dispatchFileCancel(clipboard, &cancellation);
if (queued)
signalWorker(clipboard);
DEBUG_WARN("Ignoring malformed LGMP clipboard stream record");
return status == LGMP_OK;
}
LG_ClipboardFileRequest fileRequest = { 0 };
const bool file =
record.type == KVMFR_CLIPBOARD_MESSAGE_FILE_DATA;
const bool chunkValid = file ? validateFileReadChunkNL(
clipboard, &record, &fileRequest) :
validateReadChunkNL(clipboard, &record);
if (!chunkValid)
{
KVMFRClipboardMessage cancellation = { 0 };
bool queued = false;
const bool cancelled = file && rejectMalformedFileReadNL(
clipboard, &record, &cancellation, &queued);
status = lgmpClientStreamReadRelease(
clipboard->hostToClientStream, &buffer);
if (status != LGMP_OK)
connectionFailed(clipboard, status);
LG_UNLOCK(clipboard->lock);
if (cancelled)
dispatchFileCancel(clipboard, &cancellation);
if (queued)
signalWorker(clipboard);
DEBUG_WARN("Ignoring stale or malformed LGMP clipboard stream data");
return status == LGMP_OK;
}
if (!file)
record.transfer = clipboard->readRequest;
clipboard->held = true;
clipboard->heldReady = true;
clipboard->heldStream = true;
clipboard->heldStreamBuffer = buffer;
clipboard->heldRecord = record;
clipboard->heldFile = file;
clipboard->heldFileRequest = fileRequest;
clipboard->heldPhase =
(record.flags & KVMFR_CLIPBOARD_FLAG_BEGIN) ? HELD_PHASE_BEGIN :
record.length ? HELD_PHASE_CHUNK : HELD_PHASE_END;
LG_UNLOCK(clipboard->lock);
return processHeld(clipboard);
}
static bool processMessage(LGMPClipboard * clipboard, bool * processed) static bool processMessage(LGMPClipboard * clipboard, bool * processed)
{ {
*processed = false; *processed = false;
@@ -1479,7 +1881,9 @@ static bool processMessage(LGMPClipboard * clipboard, bool * processed)
size_t fileReadyCount = 0; size_t fileReadyCount = 0;
KVMFRClipboardSlotHeader * header = message.mem; KVMFRClipboardSlotHeader * header = message.mem;
const uint32_t token = KVMFR_CLIPBOARD_QUEUE_SERIAL(message.udata); const uint32_t token = KVMFR_CLIPBOARD_QUEUE_SERIAL(message.udata);
const bool valid = message.size == const bool valid = clipboard->claimTransport ==
KVMFR_CLIPBOARD_TRANSPORT_LEGACY &&
message.size ==
sizeof(*header) + KVMFR_CLIPBOARD_DATA_BYTES && sizeof(*header) + KVMFR_CLIPBOARD_DATA_BYTES &&
header->version == KVMFR_CLIPBOARD_VERSION && header->version == KVMFR_CLIPBOARD_VERSION &&
header->type == KVMFR_CLIPBOARD_MESSAGE_GRANT && header->type == KVMFR_CLIPBOARD_MESSAGE_GRANT &&
@@ -1544,7 +1948,7 @@ static bool processMessage(LGMPClipboard * clipboard, bool * processed)
KVMFRClipboardMessage record; KVMFRClipboardMessage record;
memcpy(&record, message.mem, sizeof(record)); memcpy(&record, message.mem, sizeof(record));
const bool valid = validateRecord( const bool valid = validateRecord(
clipboard, &record, message.size, type); clipboard, &record, message.size, type, false);
if (!valid) if (!valid)
{ {
KVMFRClipboardMessage cancellation = { 0 }; KVMFRClipboardMessage cancellation = { 0 };
@@ -1592,6 +1996,7 @@ static bool processMessage(LGMPClipboard * clipboard, bool * processed)
record.transfer = clipboard->readRequest; record.transfer = clipboard->readRequest;
clipboard->held = true; clipboard->held = true;
clipboard->heldReady = true; clipboard->heldReady = true;
clipboard->heldStream = false;
clipboard->heldMessage = message; clipboard->heldMessage = message;
clipboard->heldRecord = record; clipboard->heldRecord = record;
clipboard->heldFile = file; clipboard->heldFile = file;
@@ -1916,14 +2321,21 @@ static int clipboardThread(void * opaque)
} }
dispatchRetiredFiles(clipboard); dispatchRetiredFiles(clipboard);
bool processed = false; bool queueProcessed = false;
if (!processMessage(clipboard, &processed)) if (!processMessage(clipboard, &queueProcessed))
{ {
running = false; running = false;
break; break;
} }
dispatchRetiredFiles(clipboard); dispatchRetiredFiles(clipboard);
if (!processed) bool streamProcessed = false;
if (!processStream(clipboard, &streamProcessed))
{
running = false;
break;
}
dispatchRetiredFiles(clipboard);
if (!queueProcessed && !streamProcessed)
break; break;
} }
if (!running) if (!running)
@@ -1939,15 +2351,23 @@ static int clipboardThread(void * opaque)
!clipboard->pendingCount && !clipboard->pendingCount &&
now - clipboard->lastSend >= CLIPBOARD_KEEPALIVE_US) now - clipboard->lastSend >= CLIPBOARD_KEEPALIVE_US)
enqueueTypeNL(clipboard, KVMFR_CLIPBOARD_MESSAGE_KEEPALIVE); enqueueTypeNL(clipboard, KVMFR_CLIPBOARD_MESSAGE_KEEPALIVE);
if (clipboard->writeBlocked && bool blockedFile = false;
clipboard->pendingCount < CLIPBOARD_PENDING_NORMAL_MAX && for (struct FileTransfer * transfer = clipboard->fileWrites;
availableGrantNL(clipboard)) transfer; transfer = transfer->next)
if (transfer->blocked)
{
blockedFile = true;
break;
}
const bool writable =
(clipboard->writeBlocked || blockedFile) &&
dataWritableNL(clipboard);
if (clipboard->writeBlocked && writable)
{ {
readyRequest = clipboard->writeBlockedRequest; readyRequest = clipboard->writeBlockedRequest;
clipboard->writeBlocked = false; clipboard->writeBlocked = false;
} }
if (clipboard->pendingCount < CLIPBOARD_PENDING_NORMAL_MAX && if (writable)
availableGrantNL(clipboard))
for (struct FileTransfer * transfer = clipboard->fileWrites; for (struct FileTransfer * transfer = clipboard->fileWrites;
transfer && fileReadyCount < CLIPBOARD_PENDING_MAX; transfer && fileReadyCount < CLIPBOARD_PENDING_MAX;
transfer = transfer->next) transfer = transfer->next)
@@ -1969,6 +2389,8 @@ static int clipboardThread(void * opaque)
if (atomic_load_explicit(&clipboard->stop, memory_order_acquire)) if (atomic_load_explicit(&clipboard->stop, memory_order_acquire))
break; break;
if (drained == CLIPBOARD_DRAIN_MAX)
continue;
lgWaitEvent(clipboard->event, lgWaitEvent(clipboard->event,
active ? CLIPBOARD_ACTIVE_POLL_MS : CLIPBOARD_IDLE_POLL_MS); active ? CLIPBOARD_ACTIVE_POLL_MS : CLIPBOARD_IDLE_POLL_MS);
} }
@@ -2059,6 +2481,7 @@ bool lgmpClipboard_connect(LGMPClipboard * clipboard, uint32_t clientID)
clipboard->connected = true; clipboard->connected = true;
clipboard->available = false; clipboard->available = false;
clipboard->statusValid = false; clipboard->statusValid = false;
clipboard->transports = 0;
clipboard->claimed = false; clipboard->claimed = false;
clipboard->ownerConfirmed = false; clipboard->ownerConfirmed = false;
clipboard->clientID = clientID; clipboard->clientID = clientID;
@@ -2171,14 +2594,28 @@ void lgmpClipboard_disconnect(LGMPClipboard * clipboard)
lgJoinThread(thread, NULL); lgJoinThread(thread, NULL);
LG_LOCK(clipboard->lock); LG_LOCK(clipboard->lock);
if (clipboard->held && clipboard->queue) if (clipboard->held)
lgmpClientMessageDone(clipboard->queue); {
const LGMP_STATUS status = clipboard->heldStream ?
lgmpClientStreamReadRelease(clipboard->hostToClientStream,
&clipboard->heldStreamBuffer) : clipboard->queue ?
lgmpClientMessageDone(clipboard->queue) : LGMP_OK;
if (status != LGMP_OK && status != LGMP_ERR_STREAM_STALE &&
status != LGMP_ERR_STREAM_UNBOUND)
DEBUG_WARN("Failed to release held clipboard data during "
"disconnect: %s", lgmpStatusString(status));
}
releaseOnDisconnect(clipboard); releaseOnDisconnect(clipboard);
PLGMPClientQueue queue = clipboard->queue; PLGMPClientQueue queue = clipboard->queue;
clipboard->queue = NULL; clipboard->queue = NULL;
clipboard->thread = NULL; clipboard->thread = NULL;
clipboard->event = NULL; clipboard->event = NULL;
clipboard->held = false; clipboard->held = false;
clipboard->heldStream = false;
memset(&clipboard->heldStreamBuffer, 0,
sizeof(clipboard->heldStreamBuffer));
clipboard->transports = 0;
detachStreamsNL(clipboard);
clearProtocolNL(clipboard); clearProtocolNL(clipboard);
LG_UNLOCK(clipboard->lock); LG_UNLOCK(clipboard->lock);
dispatchRetiredFiles(clipboard); dispatchRetiredFiles(clipboard);