[clipboard] lgmp: make payload streams mandatory

Carry DATA and FILE_DATA only through the duplex SPSC streams. Keep LGMP
queues for control and status. Remove grants, commits, ACKs, and
queue-payload transport negotiation.

Poll adaptively while ownership or release is active. Preserve FIFO
release and graceful draining. Quarantine payloads crossed by a newer
OFFER or CLEAR, and retire data-plane state on detach.
This commit is contained in:
Geoffrey McRae
2026-08-15 14:36:33 +10:00
parent 12801df353
commit be4bb19fa4
4 changed files with 277 additions and 596 deletions

View File

@@ -40,7 +40,6 @@
#define CLIPBOARD_PENDING_MAX 128U #define CLIPBOARD_PENDING_MAX 128U
#define CLIPBOARD_PENDING_NORMAL_MAX 64U #define CLIPBOARD_PENDING_NORMAL_MAX 64U
#define CLIPBOARD_DRAIN_MAX 64U #define CLIPBOARD_DRAIN_MAX 64U
#define CLIPBOARD_GRANTS KVMFR_CLIPBOARD_SLOT_COUNT
#define CLIPBOARD_IDLE_POLL_MS 10U #define CLIPBOARD_IDLE_POLL_MS 10U
#define CLIPBOARD_ACTIVE_POLL_MS 1U #define CLIPBOARD_ACTIVE_POLL_MS 1U
#define CLIPBOARD_KEEPALIVE_US UINT64_C(200000) #define CLIPBOARD_KEEPALIVE_US UINT64_C(200000)
@@ -51,14 +50,6 @@ struct PendingRecord
KVMFRClipboardMessage record; KVMFRClipboardMessage record;
}; };
struct Grant
{
KVMFRClipboardSlotHeader * header;
uint8_t * data;
uint32_t token;
bool available;
};
struct FileTransfer struct FileTransfer
{ {
struct FileTransfer * next; struct FileTransfer * next;
@@ -106,8 +97,7 @@ struct LGMPClipboard
bool statusValid; bool statusValid;
bool claimed; bool claimed;
bool ownerConfirmed; bool ownerConfirmed;
KVMFRClipboardTransportFlags transports; bool releasing;
KVMFRClipboardTransportFlags claimTransport;
KVMFRStreamDescriptor hostToClientDescriptor; KVMFRStreamDescriptor hostToClientDescriptor;
KVMFRStreamDescriptor clientToHostDescriptor; KVMFRStreamDescriptor clientToHostDescriptor;
uint32_t clientID; uint32_t clientID;
@@ -122,7 +112,6 @@ struct LGMPClipboard
unsigned pendingHead; unsigned pendingHead;
unsigned pendingCount; unsigned pendingCount;
struct Grant grants[CLIPBOARD_GRANTS];
bool writeBlocked; bool writeBlocked;
LG_ClipboardRequest writeBlockedRequest; LG_ClipboardRequest writeBlockedRequest;
LG_ClipboardRequest writeTransfer; LG_ClipboardRequest writeTransfer;
@@ -136,9 +125,7 @@ struct LGMPClipboard
bool held; bool held;
bool heldReady; bool heldReady;
bool heldStream;
enum HeldPhase heldPhase; enum HeldPhase heldPhase;
LGMPMessage heldMessage;
LGMPStreamBuffer heldStreamBuffer; LGMPStreamBuffer heldStreamBuffer;
KVMFRClipboardMessage heldRecord; KVMFRClipboardMessage heldRecord;
bool heldFile; bool heldFile;
@@ -229,12 +216,6 @@ static void detachStreamsNL(LGMPClipboard * clipboard)
static bool attachStreamsNL(LGMPClipboard * clipboard, static bool attachStreamsNL(LGMPClipboard * clipboard,
const KVMFRClipboardStatus * status) const KVMFRClipboardStatus * status)
{ {
if (!(status->transports & KVMFR_CLIPBOARD_TRANSPORT_STREAM))
{
detachStreamsNL(clipboard);
return false;
}
if (clipboard->hostToClientStream && clipboard->clientToHostStream && if (clipboard->hostToClientStream && clipboard->clientToHostStream &&
!memcmp(&clipboard->hostToClientDescriptor, &status->hostToClient, !memcmp(&clipboard->hostToClientDescriptor, &status->hostToClient,
sizeof(status->hostToClient)) && sizeof(status->hostToClient)) &&
@@ -659,14 +640,6 @@ static bool enqueueTypeNL(LGMPClipboard * clipboard,
return enqueueRecordNL(clipboard, record); return enqueueRecordNL(clipboard, record);
} }
static struct Grant * availableGrantNL(LGMPClipboard * clipboard)
{
for (unsigned i = 0; i < CLIPBOARD_GRANTS; ++i)
if (clipboard->grants[i].available)
return &clipboard->grants[i];
return NULL;
}
static bool streamWriteNL(LGMPClipboard * clipboard, static bool streamWriteNL(LGMPClipboard * clipboard,
const KVMFRClipboardMessage * record, const void * data) const KVMFRClipboardMessage * record, const void * data)
{ {
@@ -725,32 +698,12 @@ static bool enqueueDataNL(LGMPClipboard * clipboard,
record.version = KVMFR_CLIPBOARD_VERSION; record.version = KVMFR_CLIPBOARD_VERSION;
record.generation = clipboard->claimGeneration; record.generation = clipboard->claimGeneration;
if (clipboard->claimTransport == KVMFR_CLIPBOARD_TRANSPORT_STREAM) return streamWriteNL(clipboard, &record, data);
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);
if (!grant)
return false;
memcpy(grant->header, &record, sizeof(record));
if (record.length)
memcpy(grant->data, data, record.length);
KVMFRClipboardMessage commit = record;
commit.type = KVMFR_CLIPBOARD_MESSAGE_COMMIT;
commit.token = grant->token;
pendingAt(clipboard, clipboard->pendingCount++)->record = commit;
grant->available = false;
return true;
} }
static bool streamWritableNL(LGMPClipboard * clipboard) static bool streamWritableNL(LGMPClipboard * clipboard)
{ {
if (!clipboard->clientToHostStream || if (!clipboard->clientToHostStream || !clipboard->claimed)
clipboard->claimTransport != KVMFR_CLIPBOARD_TRANSPORT_STREAM)
return false; return false;
LGMPStreamBuffer buffer = { 0 }; LGMPStreamBuffer buffer = { 0 };
@@ -777,11 +730,7 @@ static bool streamWritableNL(LGMPClipboard * clipboard)
static bool dataWritableNL(LGMPClipboard * clipboard) static bool dataWritableNL(LGMPClipboard * clipboard)
{ {
if (clipboard->claimTransport == KVMFR_CLIPBOARD_TRANSPORT_STREAM) return streamWritableNL(clipboard);
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)
@@ -810,43 +759,43 @@ static void clearReadNL(LGMPClipboard * clipboard)
clipboard->readBegan = false; clipboard->readBegan = false;
} }
static void clearProtocolNL(LGMPClipboard * clipboard) static void clearDataPlaneNL(LGMPClipboard * clipboard)
{ {
clipboard->claimed = false;
clipboard->ownerConfirmed = false;
clipboard->claimTransport = 0;
clipboard->publishedClaimGeneration = 0;
clipboard->pendingHead = 0;
clipboard->pendingCount = 0;
clipboard->remoteClipboardGeneration = 0; clipboard->remoteClipboardGeneration = 0;
clipboard->remoteFormats = 0; clipboard->remoteFormats = 0;
clearWriteNL(clipboard); clearWriteNL(clipboard);
clearReadNL(clipboard); clearReadNL(clipboard);
clearFilesNL(clipboard); clearFilesNL(clipboard);
memset(clipboard->grants, 0, sizeof(clipboard->grants)); }
static void clearProtocolNL(LGMPClipboard * clipboard)
{
clipboard->claimed = false;
clipboard->ownerConfirmed = false;
clipboard->releasing = false;
clipboard->publishedClaimGeneration = 0;
clipboard->pendingHead = 0;
clipboard->pendingCount = 0;
clearDataPlaneNL(clipboard);
} }
static bool ensureClaimNL(LGMPClipboard * clipboard) static bool ensureClaimNL(LGMPClipboard * clipboard)
{ {
if (clipboard->claimed) if (clipboard->claimed)
return true; return true;
if (!clipboard->connected || !clipboard->available || if (clipboard->releasing || !clipboard->connected ||
!clipboard->transports) !clipboard->available ||
!clipboard->hostToClientStream || !clipboard->clientToHostStream)
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;
} }
@@ -892,13 +841,10 @@ static void connectionLostNL(LGMPClipboard * clipboard)
clipboard->connected = false; clipboard->connected = false;
clipboard->available = false; clipboard->available = false;
clipboard->statusValid = false; clipboard->statusValid = false;
clipboard->transports = 0;
detachStreamsNL(clipboard); 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, memset(&clipboard->heldStreamBuffer, 0,
sizeof(clipboard->heldStreamBuffer)); sizeof(clipboard->heldStreamBuffer));
memset(&clipboard->heldRecord, 0, sizeof(clipboard->heldRecord)); memset(&clipboard->heldRecord, 0, sizeof(clipboard->heldRecord));
@@ -930,35 +876,19 @@ 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[6] = { 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->streamVersion != KVMFR_CLIPBOARD_STREAM_VERSION ||
status->transports & ~KVMFR_CLIPBOARD_TRANSPORT_ALL || status->streamSlotCount != KVMFR_CLIPBOARD_STREAM_SLOT_COUNT ||
memcmp(status->reserved, emptyReserved, sizeof(emptyReserved))) memcmp(status->reserved, emptyReserved, sizeof(emptyReserved)) ||
return false; !validStreamDescriptor(&status->hostToClient,
LGMP_STREAM_HOST_TO_CLIENT) ||
const bool streams = !validStreamDescriptor(&status->clientToHost,
(status->transports & KVMFR_CLIPBOARD_TRANSPORT_STREAM) != 0; LGMP_STREAM_CLIENT_TO_HOST))
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 =
@@ -967,16 +897,12 @@ 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 :
kvmfrClipboardTransportValid(status->ownerTransport) && !status->ownerClientID && !status->ownerGeneration;
(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, bool stream)
KVMFRClipboardQueueType queueType, bool stream)
{ {
if (size < sizeof(*record) || if (size < sizeof(*record) ||
record->version != KVMFR_CLIPBOARD_VERSION || record->version != KVMFR_CLIPBOARD_VERSION ||
@@ -990,15 +916,10 @@ 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)
{ {
const size_t expected = sizeof(*record) + if (!stream || size != sizeof(*record) + record->length)
(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 (stream || size != sizeof(*record) || record->length ||
size != sizeof(*record) || record->length ||
(record->flags && (record->flags &&
record->type != KVMFR_CLIPBOARD_MESSAGE_FILE_REQUEST)) record->type != KVMFR_CLIPBOARD_MESSAGE_FILE_REQUEST))
return false; return false;
@@ -1500,32 +1421,27 @@ 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 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 hasOwner =
clipboard->claimed && status->ownerClientID == clipboard->clientID && (status->flags & KVMFR_CLIPBOARD_STATUS_HAS_OWNER) != 0;
status->ownerGeneration == clipboard->claimGeneration && const bool owned = hasOwner &&
status->ownerTransport == clipboard->claimTransport; (clipboard->claimed || clipboard->releasing) &&
const bool ownedByOther = status->ownerClientID == clipboard->clientID &&
(status->flags & KVMFR_CLIPBOARD_STATUS_HAS_OWNER) && !owned; status->ownerGeneration == clipboard->claimGeneration;
const bool selectedUnavailable = clipboard->claimed && const bool ownedByOther = hasOwner && !owned;
!(clipboard->transports & clipboard->claimTransport); const bool selectedUnavailable =
clipboard->available = endpointAvailable && !ownedByOther && (clipboard->claimed || clipboard->releasing) && !streamsAttached;
clipboard->transports; clipboard->available = endpointAvailable && streamsAttached &&
!ownedByOther && !clipboard->releasing;
bool restore = false; bool restore = false;
if (owned && clipboard->claimTransport == if (owned)
KVMFR_CLIPBOARD_TRANSPORT_STREAM)
{ {
const LGMP_STATUS activate = activateStreamsNL(clipboard); const LGMP_STATUS activate = activateStreamsNL(clipboard);
if (activate != LGMP_OK) if (activate != LGMP_OK)
@@ -1540,26 +1456,36 @@ static void applyStatusNL(LGMPClipboard * clipboard,
ownedByOther || selectedUnavailable) ownedByOther || selectedUnavailable)
{ {
clearProtocolNL(clipboard); clearProtocolNL(clipboard);
restore = endpointAvailable && !ownedByOther; restore = endpointAvailable && streamsAttached && !ownedByOther;
} }
else if (status->flags & KVMFR_CLIPBOARD_STATUS_HAS_OWNER) else if (hasOwner)
clipboard->ownerConfirmed = owned; clipboard->ownerConfirmed = owned;
else else
{ {
if (clipboard->claimed && clipboard->ownerConfirmed) if (clipboard->releasing)
{
clipboard->releasing = false;
clipboard->ownerConfirmed = false;
clipboard->available = endpointAvailable && streamsAttached;
}
else if (clipboard->claimed && clipboard->ownerConfirmed)
{ {
clearProtocolNL(clipboard); clearProtocolNL(clipboard);
restore = endpointAvailable; restore = endpointAvailable && streamsAttached;
} }
clipboard->ownerConfirmed = false; else
clipboard->ownerConfirmed = false;
} }
if (!wasAvailable && clipboard->available && !clipboard->claimed &&
clipboard->events)
restore = true;
if (restore && !restoreClipboardNL(clipboard)) if (restore && !restoreClipboardNL(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);
} }
@@ -1600,7 +1526,6 @@ 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; LGMPStreamBuffer streamBuffer;
LG_ClipboardFileRequest fileRequest; LG_ClipboardFileRequest fileRequest;
LG_LOCK(clipboard->lock); LG_LOCK(clipboard->lock);
@@ -1610,11 +1535,9 @@ static bool processHeld(LGMPClipboard * clipboard)
return true; return true;
} }
record = clipboard->heldRecord; record = clipboard->heldRecord;
stream = clipboard->heldStream;
streamBuffer = clipboard->heldStreamBuffer; streamBuffer = clipboard->heldStreamBuffer;
data = !record.length ? NULL : stream ? data = !record.length ? NULL :
(const uint8_t *)streamBuffer.data + sizeof(record) : (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;
@@ -1702,13 +1625,9 @@ static bool processHeld(LGMPClipboard * clipboard)
else if (matchingRead) else if (matchingRead)
clearReadNL(clipboard); clearReadNL(clipboard);
const LGMP_STATUS done = stream ? const LGMP_STATUS done = lgmpClientStreamReadRelease(
lgmpClientStreamReadRelease( clipboard->hostToClientStream, &streamBuffer);
clipboard->hostToClientStream, &streamBuffer) :
lgmpClientMessageDone(clipboard->queue);
clipboard->held = false; clipboard->held = false;
clipboard->heldStream = false;
memset(&clipboard->heldMessage, 0, sizeof(clipboard->heldMessage));
memset(&clipboard->heldStreamBuffer, 0, memset(&clipboard->heldStreamBuffer, 0,
sizeof(clipboard->heldStreamBuffer)); sizeof(clipboard->heldStreamBuffer));
memset(&clipboard->heldRecord, 0, sizeof(clipboard->heldRecord)); memset(&clipboard->heldRecord, 0, sizeof(clipboard->heldRecord));
@@ -1731,7 +1650,7 @@ static bool processStream(LGMPClipboard * clipboard, bool * processed)
*processed = false; *processed = false;
LG_LOCK(clipboard->lock); LG_LOCK(clipboard->lock);
if (!clipboard->connected || clipboard->held || if (!clipboard->connected || clipboard->held ||
clipboard->claimTransport != KVMFR_CLIPBOARD_TRANSPORT_STREAM || (!clipboard->claimed && !clipboard->releasing) ||
!clipboard->hostToClientStream) !clipboard->hostToClientStream)
{ {
LG_UNLOCK(clipboard->lock); LG_UNLOCK(clipboard->lock);
@@ -1756,12 +1675,23 @@ static bool processStream(LGMPClipboard * clipboard, bool * processed)
} }
*processed = true; *processed = true;
if (clipboard->releasing)
{
status = lgmpClientStreamReadRelease(
clipboard->hostToClientStream, &buffer);
if (status != LGMP_OK && status != LGMP_ERR_STREAM_UNBOUND &&
status != LGMP_ERR_STREAM_STALE)
connectionFailed(clipboard, status);
LG_UNLOCK(clipboard->lock);
return status == LGMP_OK || status == LGMP_ERR_STREAM_UNBOUND ||
status == LGMP_ERR_STREAM_STALE;
}
KVMFRClipboardMessage record = { 0 }; KVMFRClipboardMessage record = { 0 };
if (buffer.data && buffer.size >= sizeof(record)) if (buffer.data && buffer.size >= sizeof(record))
memcpy(&record, buffer.data, sizeof(record)); memcpy(&record, buffer.data, sizeof(record));
const bool valid = buffer.data && const bool valid = buffer.data &&
validateRecord(clipboard, &record, buffer.size, validateRecord(clipboard, &record, buffer.size, true);
KVMFR_CLIPBOARD_QUEUE_DATA, true);
if (!valid) if (!valid)
{ {
KVMFRClipboardMessage cancellation = { 0 }; KVMFRClipboardMessage cancellation = { 0 };
@@ -1810,7 +1740,6 @@ static bool processStream(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 = true;
clipboard->heldStreamBuffer = buffer; clipboard->heldStreamBuffer = buffer;
clipboard->heldRecord = record; clipboard->heldRecord = record;
clipboard->heldFile = file; clipboard->heldFile = file;
@@ -1875,66 +1804,16 @@ static bool processMessage(LGMPClipboard * clipboard, bool * processed)
return status == LGMP_OK; return status == LGMP_OK;
} }
if (type == KVMFR_CLIPBOARD_QUEUE_GRANT) if (!clipboard->claimed)
{ {
uint64_t fileReady[CLIPBOARD_PENDING_MAX];
size_t fileReadyCount = 0;
KVMFRClipboardSlotHeader * header = message.mem;
const uint32_t token = KVMFR_CLIPBOARD_QUEUE_SERIAL(message.udata);
const bool valid = clipboard->claimTransport ==
KVMFR_CLIPBOARD_TRANSPORT_LEGACY &&
message.size ==
sizeof(*header) + KVMFR_CLIPBOARD_DATA_BYTES &&
header->version == KVMFR_CLIPBOARD_VERSION &&
header->type == KVMFR_CLIPBOARD_MESSAGE_GRANT &&
header->generation == clipboard->claimGeneration &&
header->size == KVMFR_CLIPBOARD_DATA_BYTES &&
header->token == token && token > 0 &&
token <= KVMFR_CLIPBOARD_SLOT_COUNT &&
!header->sequence && !header->clipboardGeneration &&
!header->transfer && !header->offset && !header->format &&
!header->flags && !header->length;
bool stored = false;
if (valid)
{
struct Grant * grant = &clipboard->grants[token - 1];
if (!grant->available)
{
grant->header = header;
grant->data = (uint8_t *)(header + 1);
grant->token = token;
grant->available = true;
stored = true;
}
}
status = lgmpClientMessageDone(clipboard->queue); status = lgmpClientMessageDone(clipboard->queue);
const bool ready = stored && clipboard->writeBlocked;
const LG_ClipboardRequest request = clipboard->writeBlockedRequest;
if (ready)
clipboard->writeBlocked = false;
if (stored)
for (struct FileTransfer * transfer = clipboard->fileWrites;
transfer && fileReadyCount < CLIPBOARD_PENDING_MAX;
transfer = transfer->next)
if (transfer->blocked)
{
transfer->blocked = false;
fileReady[fileReadyCount++] = transfer->request.request;
}
if (status != LGMP_OK) if (status != LGMP_OK)
connectionFailed(clipboard, status); connectionFailed(clipboard, status);
LG_UNLOCK(clipboard->lock); LG_UNLOCK(clipboard->lock);
if (!stored)
DEBUG_WARN("Ignoring invalid LGMP clipboard grant");
if (ready)
dispatchReady(clipboard, request);
for (size_t i = 0; i < fileReadyCount; ++i)
dispatchFileReady(clipboard, fileReady[i]);
return status == LGMP_OK; return status == LGMP_OK;
} }
if ((type != KVMFR_CLIPBOARD_QUEUE_MESSAGE && if (type != KVMFR_CLIPBOARD_QUEUE_MESSAGE ||
type != KVMFR_CLIPBOARD_QUEUE_DATA) ||
message.size < sizeof(KVMFRClipboardMessage)) message.size < sizeof(KVMFRClipboardMessage))
{ {
status = lgmpClientMessageDone(clipboard->queue); status = lgmpClientMessageDone(clipboard->queue);
@@ -1947,8 +1826,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, false);
clipboard, &record, message.size, type, false);
if (!valid) if (!valid)
{ {
KVMFRClipboardMessage cancellation = { 0 }; KVMFRClipboardMessage cancellation = { 0 };
@@ -1967,47 +1845,6 @@ static bool processMessage(LGMPClipboard * clipboard, bool * processed)
return status == LGMP_OK; return status == LGMP_OK;
} }
if (record.type == KVMFR_CLIPBOARD_MESSAGE_DATA ||
record.type == KVMFR_CLIPBOARD_MESSAGE_FILE_DATA)
{
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 = lgmpClientMessageDone(clipboard->queue);
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 data");
return status == LGMP_OK;
}
if (!file)
record.transfer = clipboard->readRequest;
clipboard->held = true;
clipboard->heldReady = true;
clipboard->heldStream = false;
clipboard->heldMessage = message;
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);
}
status = lgmpClientMessageDone(clipboard->queue); status = lgmpClientMessageDone(clipboard->queue);
if (status != LGMP_OK) if (status != LGMP_OK)
{ {
@@ -2308,9 +2145,25 @@ static bool processMessage(LGMPClipboard * clipboard, bool * processed)
static int clipboardThread(void * opaque) static int clipboardThread(void * opaque)
{ {
LGMPClipboard * clipboard = opaque; LGMPClipboard * clipboard = opaque;
LGMPStreamPollState streamPoll;
const LGMP_STATUS pollStatus = lgmpStreamPollInit(&streamPoll,
(struct LGMPStreamPollConfig)
{
.spinCount = 32U,
.minWaitUs = 50U,
.maxWaitUs = 1000U,
});
if (pollStatus != LGMP_OK)
{
DEBUG_ERROR("Failed to initialize LGMP clipboard polling: %s",
lgmpStatusString(pollStatus));
return -1;
}
while (!atomic_load_explicit(&clipboard->stop, memory_order_acquire)) while (!atomic_load_explicit(&clipboard->stop, memory_order_acquire))
{ {
bool running = true; bool running = true;
bool streamProgress = false;
unsigned drained = 0; unsigned drained = 0;
for (; drained < CLIPBOARD_DRAIN_MAX; ++drained) for (; drained < CLIPBOARD_DRAIN_MAX; ++drained)
{ {
@@ -2334,6 +2187,7 @@ static int clipboardThread(void * opaque)
running = false; running = false;
break; break;
} }
streamProgress |= streamProcessed;
dispatchRetiredFiles(clipboard); dispatchRetiredFiles(clipboard);
if (!queueProcessed && !streamProcessed) if (!queueProcessed && !streamProcessed)
break; break;
@@ -2362,6 +2216,8 @@ static int clipboardThread(void * opaque)
const bool writable = const bool writable =
(clipboard->writeBlocked || blockedFile) && (clipboard->writeBlocked || blockedFile) &&
dataWritableNL(clipboard); dataWritableNL(clipboard);
if (writable)
streamProgress = true;
if (clipboard->writeBlocked && writable) if (clipboard->writeBlocked && writable)
{ {
readyRequest = clipboard->writeBlockedRequest; readyRequest = clipboard->writeBlockedRequest;
@@ -2380,6 +2236,8 @@ static int clipboardThread(void * opaque)
clipboard->pendingCount || clipboard->held || clipboard->pendingCount || clipboard->held ||
clipboard->writeBlocked || clipboard->fileReads || clipboard->writeBlocked || clipboard->fileReads ||
clipboard->fileWrites; clipboard->fileWrites;
const bool streamActive = clipboard->connected &&
(clipboard->claimed || clipboard->releasing);
LG_UNLOCK(clipboard->lock); LG_UNLOCK(clipboard->lock);
if (readyRequest != LG_CLIPBOARD_REQUEST_INVALID) if (readyRequest != LG_CLIPBOARD_REQUEST_INVALID)
@@ -2390,9 +2248,30 @@ 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) if (drained == CLIPBOARD_DRAIN_MAX)
{
if (streamProgress)
lgmpStreamPollActivity(&streamPoll);
continue; continue;
lgWaitEvent(clipboard->event, }
active ? CLIPBOARD_ACTIVE_POLL_MS : CLIPBOARD_IDLE_POLL_MS);
bool signaled;
if (streamActive)
{
if (streamProgress)
lgmpStreamPollActivity(&streamPoll);
const uint32_t waitUs = lgmpStreamPollIdle(&streamPoll);
if (!waitUs)
continue;
signaled = lgWaitEventNS(clipboard->event, waitUs * 1000U);
}
else
{
lgmpStreamPollActivity(&streamPoll);
signaled = lgWaitEvent(clipboard->event,
active ? CLIPBOARD_ACTIVE_POLL_MS : CLIPBOARD_IDLE_POLL_MS);
}
if (signaled)
lgmpStreamPollActivity(&streamPoll);
} }
dispatchRetiredFiles(clipboard); dispatchRetiredFiles(clipboard);
notifyStatus(clipboard); notifyStatus(clipboard);
@@ -2481,9 +2360,9 @@ 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->releasing = false;
clipboard->clientID = clientID; clipboard->clientID = clientID;
clipboard->endpointGeneration = 0; clipboard->endpointGeneration = 0;
clipboard->statusSerial = 0; clipboard->statusSerial = 0;
@@ -2496,7 +2375,6 @@ bool lgmpClipboard_connect(LGMPClipboard * clipboard, uint32_t clientID)
clipboard->remoteClipboardGeneration = 0; clipboard->remoteClipboardGeneration = 0;
clearReadNL(clipboard); clearReadNL(clipboard);
clipboard->publishedClaimGeneration = 0; clipboard->publishedClaimGeneration = 0;
memset(clipboard->grants, 0, sizeof(clipboard->grants));
atomic_store_explicit(&clipboard->stop, false, memory_order_release); atomic_store_explicit(&clipboard->stop, false, memory_order_release);
LGThread * thread; LGThread * thread;
@@ -2596,10 +2474,8 @@ void lgmpClipboard_disconnect(LGMPClipboard * clipboard)
LG_LOCK(clipboard->lock); LG_LOCK(clipboard->lock);
if (clipboard->held) if (clipboard->held)
{ {
const LGMP_STATUS status = clipboard->heldStream ? const LGMP_STATUS status = lgmpClientStreamReadRelease(
lgmpClientStreamReadRelease(clipboard->hostToClientStream, clipboard->hostToClientStream, &clipboard->heldStreamBuffer);
&clipboard->heldStreamBuffer) : clipboard->queue ?
lgmpClientMessageDone(clipboard->queue) : LGMP_OK;
if (status != LGMP_OK && status != LGMP_ERR_STREAM_STALE && if (status != LGMP_OK && status != LGMP_ERR_STREAM_STALE &&
status != LGMP_ERR_STREAM_UNBOUND) status != LGMP_ERR_STREAM_UNBOUND)
DEBUG_WARN("Failed to release held clipboard data during " DEBUG_WARN("Failed to release held clipboard data during "
@@ -2611,10 +2487,8 @@ void lgmpClipboard_disconnect(LGMPClipboard * clipboard)
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, memset(&clipboard->heldStreamBuffer, 0,
sizeof(clipboard->heldStreamBuffer)); sizeof(clipboard->heldStreamBuffer));
clipboard->transports = 0;
detachStreamsNL(clipboard); detachStreamsNL(clipboard);
clearProtocolNL(clipboard); clearProtocolNL(clipboard);
LG_UNLOCK(clipboard->lock); LG_UNLOCK(clipboard->lock);
@@ -2688,21 +2562,31 @@ static bool attach(void * opaque, const LG_ClipboardEventOps * events,
static void detach(void * opaque) static void detach(void * opaque)
{ {
LGMPClipboard * clipboard = opaque; LGMPClipboard * clipboard = opaque;
bool wake = false; bool releaseQueued = false;
bool releaseRequired = false;
LG_LOCK(clipboard->eventLock); LG_LOCK(clipboard->eventLock);
LG_LOCK(clipboard->lock); LG_LOCK(clipboard->lock);
clipboard->events = NULL; clipboard->events = NULL;
clipboard->eventOpaque = NULL; clipboard->eventOpaque = NULL;
if (clipboard->claimed) if (clipboard->claimed)
{ {
wake = enqueueTypeNL( releaseRequired = true;
clipboard, KVMFR_CLIPBOARD_MESSAGE_RELEASE); KVMFRClipboardMessage release = { 0 };
clipboard->claimed = false; release.type = KVMFR_CLIPBOARD_MESSAGE_RELEASE;
releaseQueued = enqueueUrgentRecordNL(clipboard, release);
clipboard->claimed = false;
clipboard->releasing = true;
clipboard->available = false;
} }
clearDataPlaneNL(clipboard);
if (clipboard->held)
clipboard->heldReady = true;
LG_UNLOCK(clipboard->lock); LG_UNLOCK(clipboard->lock);
LG_UNLOCK(clipboard->eventLock); LG_UNLOCK(clipboard->eventLock);
if (wake) if (!releaseQueued && releaseRequired)
signalWorker(clipboard); DEBUG_WARN("Failed to queue LGMP clipboard release; ownership will "
"expire");
signalWorker(clipboard);
} }
static bool releaseClipboard(void * opaque) static bool releaseClipboard(void * opaque)

View File

@@ -31,10 +31,10 @@
#define KVMFR_CLIPBOARD_VERSION 4U #define KVMFR_CLIPBOARD_VERSION 4U
#define KVMFR_CLIPBOARD_STREAM_VERSION 1U #define KVMFR_CLIPBOARD_STREAM_VERSION 1U
/* Physical transport geometry. Keep these independent from the maximum /* Physical stream geometry. Keep these independent from the maximum logical
* logical request and representation sizes below: one response may span * request and representation sizes below: one response may span several
* several stream slots. The legacy grant transport uses the same geometry so * stream slots. The generic names are also used by the Helper/IDD clipboard
* it remains a bounded fallback while stream adoption is staged. */ * ring, which intentionally has the same geometry. */
#define KVMFR_CLIPBOARD_STREAM_SLOT_COUNT 4U #define KVMFR_CLIPBOARD_STREAM_SLOT_COUNT 4U
#define KVMFR_CLIPBOARD_STREAM_SLOT_BYTES (256U * 1024U) #define KVMFR_CLIPBOARD_STREAM_SLOT_BYTES (256U * 1024U)
#define KVMFR_CLIPBOARD_STREAM_WINDOW_BYTES \ #define KVMFR_CLIPBOARD_STREAM_WINDOW_BYTES \
@@ -131,16 +131,13 @@ enum
KVMFR_CLIPBOARD_MESSAGE_CLEAR = 5, KVMFR_CLIPBOARD_MESSAGE_CLEAR = 5,
KVMFR_CLIPBOARD_MESSAGE_REQUEST = 6, KVMFR_CLIPBOARD_MESSAGE_REQUEST = 6,
KVMFR_CLIPBOARD_MESSAGE_DATA = 7, KVMFR_CLIPBOARD_MESSAGE_DATA = 7,
KVMFR_CLIPBOARD_MESSAGE_COMMIT = 8, KVMFR_CLIPBOARD_MESSAGE_CANCEL = 8,
KVMFR_CLIPBOARD_MESSAGE_CANCEL = 9, KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRE = 9,
KVMFR_CLIPBOARD_MESSAGE_ACK = 10, KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRED = 10,
KVMFR_CLIPBOARD_MESSAGE_GRANT = 11, KVMFR_CLIPBOARD_MESSAGE_FILE_RELEASE = 11,
KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRE = 12, KVMFR_CLIPBOARD_MESSAGE_FILE_REQUEST = 12,
KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRED = 13, KVMFR_CLIPBOARD_MESSAGE_FILE_DATA = 13,
KVMFR_CLIPBOARD_MESSAGE_FILE_RELEASE = 14, KVMFR_CLIPBOARD_MESSAGE_FILE_CANCEL = 14
KVMFR_CLIPBOARD_MESSAGE_FILE_REQUEST = 15,
KVMFR_CLIPBOARD_MESSAGE_FILE_DATA = 16,
KVMFR_CLIPBOARD_MESSAGE_FILE_CANCEL = 17
}; };
typedef uint32_t KVMFRClipboardMessageType; typedef uint32_t KVMFRClipboardMessageType;
@@ -153,30 +150,12 @@ enum
typedef uint32_t KVMFRClipboardFlags; typedef uint32_t KVMFRClipboardFlags;
enum
{
KVMFR_CLIPBOARD_TRANSPORT_LEGACY = 1U << 0,
KVMFR_CLIPBOARD_TRANSPORT_STREAM = 1U << 1,
KVMFR_CLIPBOARD_TRANSPORT_ALL =
KVMFR_CLIPBOARD_TRANSPORT_LEGACY |
KVMFR_CLIPBOARD_TRANSPORT_STREAM,
};
typedef uint32_t KVMFRClipboardTransportFlags;
static inline int kvmfrClipboardTransportValid(
KVMFRClipboardTransportFlags transport)
{
return transport && !(transport & (transport - 1U)) &&
!(transport & ~KVMFR_CLIPBOARD_TRANSPORT_ALL);
}
/* /*
* Bidirectional control record. Client-to-host records must fit LGMP's * Bidirectional control record. Client-to-host records must fit LGMP's
* 64-byte client message area. Host-to-client records use a small payload * 64-byte client message area. Host-to-client records use a small payload
* pool and the same layout. CLAIM flags select exactly one transport offered * pool and the same layout. DATA and FILE_DATA records are carried only by
* by KVMFRClipboardStatus. The selected transport is fixed until RELEASE and * the duplex streams advertised in KVMFRClipboardStatus; all other records
* a subsequent CLAIM with a new generation. * use the clipboard queue. CLAIM flags are reserved and must be zero.
*/ */
typedef struct KVMFRClipboardMessage typedef struct KVMFRClipboardMessage
{ {
@@ -369,14 +348,12 @@ static inline int kvmfrClipboardFileMessageValid(
} }
/* Type-specific fields: /* Type-specific fields:
* CLAIM: flags selects exactly one KVMFRClipboardTransportFlags value and * CLAIM: token is the endpoint generation from the latest status.
* token is the endpoint generation from the latest status.
* OFFER: token is KVMFRClipboardFormatFlags. * OFFER: token is KVMFRClipboardFormatFlags.
* REQUEST: format and transfer identify the requested representation. * REQUEST: format and transfer identify the requested representation.
* DATA: size is an optional total hint on BEGIN and authoritative on END; * DATA: size is an optional total hint on BEGIN and authoritative on END;
* offset/length describe this record's borrowed payload. * offset/length describe this record's borrowed payload.
* CANCEL: token is a KVMFRClipboardCancelReason-compatible reason. * CANCEL: token is a KVMFRClipboardCancelReason-compatible reason. */
* ACK/GRANT: token identifies the acknowledged or writable legacy slot. */
enum enum
{ {
@@ -386,11 +363,8 @@ enum
typedef uint32_t KVMFRClipboardStatusFlags; typedef uint32_t KVMFRClipboardStatusFlags;
/* transports advertises available data planes. When HAS_OWNER is set, /* The duplex stream descriptors are always valid. Their slot size includes
* ownerTransport identifies the single data plane selected by that owner's * KVMFRClipboardSlotHeader as well as slotBytes of payload. */
* CLAIM. Stream descriptors are valid only when STREAM is advertised; their
* slot size includes KVMFRClipboardSlotHeader as well as slotBytes of payload.
*/
typedef struct KVMFRClipboardStatus typedef struct KVMFRClipboardStatus
{ {
uint32_t version; uint32_t version;
@@ -401,11 +375,9 @@ typedef struct KVMFRClipboardStatus
uint32_t lease; uint32_t lease;
KVMFRClipboardFormatFlags formats; KVMFRClipboardFormatFlags formats;
uint32_t slotBytes; uint32_t slotBytes;
KVMFRClipboardTransportFlags transports;
KVMFRClipboardTransportFlags ownerTransport;
uint32_t streamVersion; uint32_t streamVersion;
uint32_t streamSlotCount; uint32_t streamSlotCount;
uint32_t reserved[4]; uint32_t reserved[6];
KVMFRStreamDescriptor hostToClient; KVMFRStreamDescriptor hostToClient;
KVMFRStreamDescriptor clientToHost; KVMFRStreamDescriptor clientToHost;
} }
@@ -420,9 +392,7 @@ typedef KVMFRClipboardMessage KVMFRClipboardSlotHeader;
enum enum
{ {
KVMFR_CLIPBOARD_QUEUE_STATUS = 1, KVMFR_CLIPBOARD_QUEUE_STATUS = 1,
KVMFR_CLIPBOARD_QUEUE_MESSAGE = 2, KVMFR_CLIPBOARD_QUEUE_MESSAGE = 2
KVMFR_CLIPBOARD_QUEUE_GRANT = 3,
KVMFR_CLIPBOARD_QUEUE_DATA = 4
}; };
typedef uint32_t KVMFRClipboardQueueType; typedef uint32_t KVMFRClipboardQueueType;
@@ -438,8 +408,8 @@ static_assert(sizeof(KVMFRClipboardMessage) == 64,
"KVMFR clipboard control message layout changed"); "KVMFR clipboard control message layout changed");
static_assert(sizeof(KVMFRClipboardFileEntry) == 40, static_assert(sizeof(KVMFRClipboardFileEntry) == 40,
"KVMFR clipboard file entry layout changed"); "KVMFR clipboard file entry layout changed");
static_assert(offsetof(KVMFRClipboardStatus, transports) == 32, static_assert(offsetof(KVMFRClipboardStatus, streamVersion) == 32,
"KVMFR clipboard transport status layout changed"); "KVMFR clipboard stream status layout changed");
static_assert(offsetof(KVMFRClipboardStatus, hostToClient) == 64, static_assert(offsetof(KVMFRClipboardStatus, hostToClient) == 64,
"KVMFR clipboard stream discovery layout changed"); "KVMFR clipboard stream discovery layout changed");
static_assert(sizeof(KVMFRClipboardStatus) == 128, static_assert(sizeof(KVMFRClipboardStatus) == 128,
@@ -453,8 +423,8 @@ _Static_assert(sizeof(KVMFRClipboardMessage) == 64,
"KVMFR clipboard control message layout changed"); "KVMFR clipboard control message layout changed");
_Static_assert(sizeof(KVMFRClipboardFileEntry) == 40, _Static_assert(sizeof(KVMFRClipboardFileEntry) == 40,
"KVMFR clipboard file entry layout changed"); "KVMFR clipboard file entry layout changed");
_Static_assert(offsetof(KVMFRClipboardStatus, transports) == 32, _Static_assert(offsetof(KVMFRClipboardStatus, streamVersion) == 32,
"KVMFR clipboard transport status layout changed"); "KVMFR clipboard stream status layout changed");
_Static_assert(offsetof(KVMFRClipboardStatus, hostToClient) == 64, _Static_assert(offsetof(KVMFRClipboardStatus, hostToClient) == 64,
"KVMFR clipboard stream discovery layout changed"); "KVMFR clipboard stream discovery layout changed");
_Static_assert(sizeof(KVMFRClipboardStatus) == 128, _Static_assert(sizeof(KVMFRClipboardStatus) == 128,

View File

@@ -49,11 +49,11 @@ namespace
}; };
bool EmptyControl(const KVMFRClipboardMessage& message, bool EmptyControl(const KVMFRClipboardMessage& message,
bool keepToken = false, bool keepFlags = false) bool keepToken = false)
{ {
return !message.clipboardGeneration && !message.transfer && return !message.clipboardGeneration && !message.transfer &&
!message.offset && !message.size && !message.format && !message.offset && !message.size && !message.format &&
(keepFlags || !message.flags) && (keepToken || !message.token) && !message.flags && (keepToken || !message.token) &&
!message.length && !message.sequence; !message.length && !message.sequence;
} }
@@ -177,10 +177,6 @@ void CLGMPClipboardTransport::DeInit()
{ {
Stop(); Stop();
DeInitStreams(); DeInitStreams();
for (PLGMPMemory& memory : m_dataMemory)
lgmpHostMemFree(&memory);
for (PLGMPMemory& memory : m_grantMemory)
lgmpHostMemFree(&memory);
for (PLGMPMemory& memory : m_messageMemory) for (PLGMPMemory& memory : m_messageMemory)
lgmpHostMemFree(&memory); lgmpHostMemFree(&memory);
for (PLGMPMemory& memory : m_statusMemory) for (PLGMPMemory& memory : m_statusMemory)
@@ -242,7 +238,7 @@ void CLGMPClipboardTransport::DeInitStreams()
m_hostToClientDescriptor = {}; m_hostToClientDescriptor = {};
} }
bool CLGMPClipboardTransport::BindStreams(uint32_t clientID) bool CLGMPClipboardTransport::BindStreams(uint32_t clientID) const
{ {
if (!m_hostToClientStream || !m_clientToHostStream) if (!m_hostToClientStream || !m_clientToHostStream)
return false; return false;
@@ -301,7 +297,7 @@ bool CLGMPClipboardTransport::TryUnbindStreams()
return complete; return complete;
} }
void CLGMPClipboardTransport::ForceUnbindStreams() void CLGMPClipboardTransport::ForceUnbindStreams() const
{ {
if (m_clientToHostStream) if (m_clientToHostStream)
{ {
@@ -321,7 +317,7 @@ void CLGMPClipboardTransport::ForceUnbindStreams()
} }
} }
void CLGMPClipboardTransport::Wake() void CLGMPClipboardTransport::Wake() const
{ {
if (m_wakeEvent) if (m_wakeEvent)
SetEvent(m_wakeEvent); SetEvent(m_wakeEvent);
@@ -557,7 +553,7 @@ bool CLGMPClipboardTransport::QueueInternalTarget(
const KVMFRClipboardMessage& record) const KVMFRClipboardMessage& record)
{ {
if (!m_pendingTarget.valid && !m_internalTargetCount) if (!m_pendingTarget.valid && !m_internalTargetCount)
return BeginTarget(record, nullptr, -2); return BeginTarget(record, nullptr, false);
if (m_internalTargetCount == INTERNAL_TARGET_COUNT) if (m_internalTargetCount == INTERNAL_TARGET_COUNT)
{ {
m_failed = true; m_failed = true;
@@ -576,7 +572,7 @@ bool CLGMPClipboardTransport::PumpInternalTarget()
for (unsigned i = 1; i < m_internalTargetCount; ++i) for (unsigned i = 1; i < m_internalTargetCount; ++i)
m_internalTarget[i - 1] = m_internalTarget[i]; m_internalTarget[i - 1] = m_internalTarget[i];
m_internalTarget[--m_internalTargetCount] = {}; m_internalTarget[--m_internalTargetCount] = {};
return BeginTarget(record, nullptr, -2); return BeginTarget(record, nullptr, false);
} }
void CLGMPClipboardTransport::ClearStreamTargets() void CLGMPClipboardTransport::ClearStreamTargets()
@@ -606,8 +602,7 @@ bool CLGMPClipboardTransport::QueueStreamTarget(
bool CLGMPClipboardTransport::ProcessStreamTarget( bool CLGMPClipboardTransport::ProcessStreamTarget(
const KVMFRClipboardMessage& record, const uint8_t * data) const KVMFRClipboardMessage& record, const uint8_t * data)
{ {
if (!m_ownerClientID || if (!m_ownerClientID)
m_ownerTransport != KVMFR_CLIPBOARD_TRANSPORT_STREAM)
return true; return true;
if (m_ownerReleasing && !m_releaseClearHelper) if (m_ownerReleasing && !m_releaseClearHelper)
return true; return true;
@@ -638,7 +633,7 @@ bool CLGMPClipboardTransport::ProcessStreamTarget(
KVMFRClipboardMessage forwarded = record; KVMFRClipboardMessage forwarded = record;
forwarded.generation = m_endpointGeneration; forwarded.generation = m_endpointGeneration;
if (!BeginTarget(forwarded, data, -2)) if (!BeginTarget(forwarded, data, false))
{ {
ReleaseOwner("failed to stage clipboard stream data", true); ReleaseOwner("failed to stage clipboard stream data", true);
return true; return true;
@@ -666,9 +661,7 @@ bool CLGMPClipboardTransport::PumpStreamTarget()
bool CLGMPClipboardTransport::DrainStream(bool& received) bool CLGMPClipboardTransport::DrainStream(bool& received)
{ {
received = false; received = false;
if (!m_ownerClientID || if (!m_ownerClientID || !m_clientToHostStream)
m_ownerTransport != KVMFR_CLIPBOARD_TRANSPORT_STREAM ||
!m_clientToHostStream)
return true; return true;
for (unsigned drained = 0; for (unsigned drained = 0;
@@ -744,12 +737,6 @@ void CLGMPClipboardTransport::ReleaseOwner(
m_ownerDrainDeadline = GetTickCount64() + STREAM_DRAIN_TIMEOUT_MS; m_ownerDrainDeadline = GetTickCount64() + STREAM_DRAIN_TIMEOUT_MS;
m_replayPending = false; m_replayPending = false;
ClearOutboundBlock(); ClearOutboundBlock();
for (Grant& grant : m_grants)
{
grant.generation = 0;
grant.offered = false;
grant.committed = false;
}
} }
else if (force) else if (force)
{ {
@@ -757,12 +744,6 @@ void CLGMPClipboardTransport::ReleaseOwner(
m_ownerReleaseReason = reason; m_ownerReleaseReason = reason;
} }
if (m_ownerTransport != KVMFR_CLIPBOARD_TRANSPORT_STREAM)
{
FinishOwnerRelease();
return;
}
if (force) if (force)
{ {
ForceUnbindStreams(); ForceUnbindStreams();
@@ -789,7 +770,6 @@ void CLGMPClipboardTransport::FinishOwnerRelease()
const bool clearHelper = m_releaseClearHelper; const bool clearHelper = m_releaseClearHelper;
m_ownerClientID = 0; m_ownerClientID = 0;
m_ownerGeneration = 0; m_ownerGeneration = 0;
m_ownerTransport = 0;
m_ownerReleasing = false; m_ownerReleasing = false;
m_releaseClearHelper = false; m_releaseClearHelper = false;
m_ownerReleaseReason = nullptr; m_ownerReleaseReason = nullptr;
@@ -827,12 +807,6 @@ bool CLGMPClipboardTransport::RetryOwnerRelease()
if (!m_ownerReleasing) if (!m_ownerReleasing)
return true; return true;
if (m_ownerTransport != KVMFR_CLIPBOARD_TRANSPORT_STREAM)
{
FinishOwnerRelease();
return true;
}
if (GetTickCount64() >= m_ownerDrainDeadline) if (GetTickCount64() >= m_ownerDrainDeadline)
{ {
DEBUG_WARN("Timed out draining clipboard streams for owner %u " DEBUG_WARN("Timed out draining clipboard streams for owner %u "
@@ -947,14 +921,10 @@ bool CLGMPClipboardTransport::PublishStatus()
clipboardStatus.generation = m_endpointGeneration; clipboardStatus.generation = m_endpointGeneration;
clipboardStatus.lease = static_cast<uint32_t>(OWNER_LEASE_MS); clipboardStatus.lease = static_cast<uint32_t>(OWNER_LEASE_MS);
clipboardStatus.slotBytes = KVMFR_CLIPBOARD_DATA_BYTES; clipboardStatus.slotBytes = KVMFR_CLIPBOARD_DATA_BYTES;
if (m_hostToClientStream && m_clientToHostStream) clipboardStatus.streamVersion = KVMFR_CLIPBOARD_STREAM_VERSION;
{ clipboardStatus.streamSlotCount = KVMFR_CLIPBOARD_STREAM_SLOT_COUNT;
clipboardStatus.transports = KVMFR_CLIPBOARD_TRANSPORT_STREAM; clipboardStatus.hostToClient = m_hostToClientDescriptor;
clipboardStatus.streamVersion = KVMFR_CLIPBOARD_STREAM_VERSION; clipboardStatus.clientToHost = m_clientToHostDescriptor;
clipboardStatus.streamSlotCount = KVMFR_CLIPBOARD_STREAM_SLOT_COUNT;
clipboardStatus.hostToClient = m_hostToClientDescriptor;
clipboardStatus.clientToHost = m_clientToHostDescriptor;
}
if (m_available) if (m_available)
{ {
clipboardStatus.flags |= KVMFR_CLIPBOARD_STATUS_AVAILABLE; clipboardStatus.flags |= KVMFR_CLIPBOARD_STATUS_AVAILABLE;
@@ -965,7 +935,6 @@ bool CLGMPClipboardTransport::PublishStatus()
clipboardStatus.flags |= KVMFR_CLIPBOARD_STATUS_HAS_OWNER; clipboardStatus.flags |= KVMFR_CLIPBOARD_STATUS_HAS_OWNER;
clipboardStatus.ownerClientID = m_ownerClientID; clipboardStatus.ownerClientID = m_ownerClientID;
clipboardStatus.ownerGeneration = m_ownerGeneration; clipboardStatus.ownerGeneration = m_ownerGeneration;
clipboardStatus.ownerTransport = m_ownerTransport;
} }
memcpy(lgmpHostMemPtr(memory), &clipboardStatus, memcpy(lgmpHostMemPtr(memory), &clipboardStatus,
sizeof(clipboardStatus)); sizeof(clipboardStatus));
@@ -990,48 +959,6 @@ bool CLGMPClipboardTransport::PublishStatus()
return true; return true;
} }
bool CLGMPClipboardTransport::PostGrants()
{
if (!m_available || !m_ownerClientID || m_ownerReleasing ||
m_ownerTransport != KVMFR_CLIPBOARD_TRANSPORT_LEGACY)
return true;
for (unsigned i = 0; i < MEMORY_COUNT; ++i)
{
Grant& grant = m_grants[i];
if (grant.offered || grant.committed ||
lgmpHostQueuePayloadPending(m_queue, m_grantMemory[i]))
continue;
KVMFRClipboardSlotHeader header = {};
header.version = KVMFR_CLIPBOARD_VERSION;
header.type = KVMFR_CLIPBOARD_MESSAGE_GRANT;
header.generation = m_ownerGeneration;
header.size = KVMFR_CLIPBOARD_DATA_BYTES;
header.token = i + 1;
memcpy(lgmpHostMemPtr(m_grantMemory[i]), &header, sizeof(header));
const PostResult result = PostForOwner(
KVMFR_CLIPBOARD_QUEUE_UDATA(
KVMFR_CLIPBOARD_QUEUE_GRANT, i + 1), m_grantMemory[i]);
if (result == PostResult::POSTED)
{
grant.generation = m_ownerGeneration;
grant.offered = true;
}
else if (result == PostResult::BUSY)
return true;
else if (result == PostResult::GONE)
{
ReleaseOwner("subscriber disappeared", true, true);
return true;
}
else
return false;
}
return true;
}
bool CLGMPClipboardTransport::ReplayClipboard() bool CLGMPClipboardTransport::ReplayClipboard()
{ {
if (!m_replayPending || !m_ownerClientID || m_ownerReleasing) if (!m_replayPending || !m_ownerClientID || m_ownerReleasing)
@@ -1066,15 +993,11 @@ bool CLGMPClipboardTransport::ReplayClipboard()
bool CLGMPClipboardTransport::ValidateClaim( bool CLGMPClipboardTransport::ValidateClaim(
const KVMFRClipboardMessage& message) const const KVMFRClipboardMessage& message) const
{ {
KVMFRClipboardTransportFlags transports = 0;
if (m_hostToClientStream && m_clientToHostStream)
transports = KVMFR_CLIPBOARD_TRANSPORT_STREAM;
return message.version == KVMFR_CLIPBOARD_VERSION && return message.version == KVMFR_CLIPBOARD_VERSION &&
message.type == KVMFR_CLIPBOARD_MESSAGE_CLAIM && message.type == KVMFR_CLIPBOARD_MESSAGE_CLAIM &&
message.generation && message.token == m_endpointGeneration && message.generation && message.token == m_endpointGeneration &&
kvmfrClipboardTransportValid(message.flags) && m_hostToClientStream && m_clientToHostStream &&
(message.flags & transports) && EmptyControl(message, true);
EmptyControl(message, true, true);
} }
bool CLGMPClipboardTransport::ValidateOwnedControl( bool CLGMPClipboardTransport::ValidateOwnedControl(
@@ -1103,17 +1026,6 @@ bool CLGMPClipboardTransport::ValidateOwnedControl(
case KVMFR_CLIPBOARD_MESSAGE_FILE_CANCEL: case KVMFR_CLIPBOARD_MESSAGE_FILE_CANCEL:
return ValidateInboundRecord(message); return ValidateInboundRecord(message);
case KVMFR_CLIPBOARD_MESSAGE_COMMIT:
return m_ownerTransport == KVMFR_CLIPBOARD_TRANSPORT_LEGACY &&
message.token >= 1 && message.token <= MEMORY_COUNT &&
message.clipboardGeneration && message.transfer &&
(kvmfrClipboardRepresentationFormatValid(message.format) ||
message.format == KVMFR_CLIPBOARD_FORMAT_FILES) &&
message.length <= KVMFR_CLIPBOARD_DATA_BYTES &&
!(message.flags & ~(KVMFR_CLIPBOARD_FLAG_BEGIN |
KVMFR_CLIPBOARD_FLAG_END)) &&
AddValid(message.offset, message.length);
default: default:
return false; return false;
} }
@@ -1266,12 +1178,16 @@ void CLGMPClipboardTransport::ApplyInbound(
case KVMFR_CLIPBOARD_MESSAGE_OFFER: case KVMFR_CLIPBOARD_MESSAGE_OFFER:
m_clientClipboardGeneration = message.clipboardGeneration; m_clientClipboardGeneration = message.clipboardGeneration;
m_clientFormats = message.token; m_clientFormats = message.token;
if (m_clientToHelper.Active())
m_discardClientToHelper = m_clientToHelper.transfer;
m_clientToHelper.Clear(); m_clientToHelper.Clear();
break; break;
case KVMFR_CLIPBOARD_MESSAGE_CLEAR: case KVMFR_CLIPBOARD_MESSAGE_CLEAR:
m_clientClipboardGeneration = 0; m_clientClipboardGeneration = 0;
m_clientFormats = 0; m_clientFormats = 0;
if (m_clientToHelper.Active())
m_discardClientToHelper = m_clientToHelper.transfer;
m_clientToHelper.Clear(); m_clientToHelper.Clear();
break; break;
@@ -1352,7 +1268,8 @@ void CLGMPClipboardTransport::ApplyOutbound(
} }
bool CLGMPClipboardTransport::BeginTarget( bool CLGMPClipboardTransport::BeginTarget(
const KVMFRClipboardMessage& message, const uint8_t * data, int grant) const KVMFRClipboardMessage& message, const uint8_t * data,
bool acknowledge)
{ {
if (m_pendingTarget.valid || if (m_pendingTarget.valid ||
(message.length && !data) || (message.length && !data) ||
@@ -1360,8 +1277,7 @@ bool CLGMPClipboardTransport::BeginTarget(
return false; return false;
m_pendingTarget.valid = true; m_pendingTarget.valid = true;
m_pendingTarget.acknowledge = grant >= -1; m_pendingTarget.acknowledge = acknowledge;
m_pendingTarget.grant = grant;
m_pendingTarget.record = message; m_pendingTarget.record = message;
if (message.length) if (message.length)
memcpy(m_pendingTarget.data, data, message.length); memcpy(m_pendingTarget.data, data, message.length);
@@ -1371,15 +1287,12 @@ bool CLGMPClipboardTransport::BeginTarget(
void CLGMPClipboardTransport::FinishTarget(bool accepted) void CLGMPClipboardTransport::FinishTarget(bool accepted)
{ {
const KVMFRClipboardMessage message = m_pendingTarget.record; const KVMFRClipboardMessage message = m_pendingTarget.record;
const int grant = m_pendingTarget.grant;
if (accepted) if (accepted)
{ {
ApplyInbound(message); ApplyInbound(message);
if (m_ownerClientID && !m_ownerReleasing) if (m_ownerClientID && !m_ownerReleasing)
RenewLease(); RenewLease();
} }
if (grant >= 0 && static_cast<unsigned>(grant) < MEMORY_COUNT)
m_grants[grant] = {};
m_pendingTarget.Clear(); m_pendingTarget.Clear();
if (!accepted) if (!accepted)
@@ -1426,9 +1339,6 @@ void CLGMPClipboardTransport::DropPendingTarget()
return; return;
const bool acknowledge = m_pendingTarget.acknowledge; const bool acknowledge = m_pendingTarget.acknowledge;
const int grant = m_pendingTarget.grant;
if (grant >= 0 && static_cast<unsigned>(grant) < MEMORY_COUNT)
m_grants[grant] = {};
m_pendingTarget.Clear(); m_pendingTarget.Clear();
if (acknowledge && m_queue) if (acknowledge && m_queue)
{ {
@@ -1453,8 +1363,7 @@ bool CLGMPClipboardTransport::ProcessMessage(
return true; return true;
} }
if (message.flags == KVMFR_CLIPBOARD_TRANSPORT_STREAM && if (!BindStreams(clientID))
!BindStreams(clientID))
{ {
DeInitStreams(); DeInitStreams();
m_failed = true; m_failed = true;
@@ -1465,7 +1374,6 @@ bool CLGMPClipboardTransport::ProcessMessage(
m_ownerClientID = clientID; m_ownerClientID = clientID;
m_ownerGeneration = message.generation; m_ownerGeneration = message.generation;
m_ownerTransport = message.flags;
RenewLease(); RenewLease();
m_statusDirty = true; m_statusDirty = true;
m_replayPending = m_cachedValid; m_replayPending = m_cachedValid;
@@ -1511,66 +1419,6 @@ bool CLGMPClipboardTransport::ProcessMessage(
return true; return true;
} }
if (message.type == KVMFR_CLIPBOARD_MESSAGE_COMMIT)
{
const unsigned grantIndex = message.token - 1;
Grant& grant = m_grants[grantIndex];
if (!grant.offered || grant.committed ||
grant.generation != m_ownerGeneration)
{
ReleaseOwner("invalid clipboard grant", true);
return true;
}
const uint8_t * slot = static_cast<const uint8_t *>(
lgmpHostMemPtr(m_grantMemory[grantIndex]));
KVMFRClipboardMessage dataMessage = {};
memcpy(&dataMessage, slot, sizeof(dataMessage));
const bool fileData = dataMessage.type ==
KVMFR_CLIPBOARD_MESSAGE_FILE_DATA;
const bool matchesCommit =
dataMessage.version == KVMFR_CLIPBOARD_VERSION &&
(dataMessage.type == KVMFR_CLIPBOARD_MESSAGE_DATA ||
fileData) &&
dataMessage.generation == m_ownerGeneration &&
dataMessage.clipboardGeneration == message.clipboardGeneration &&
dataMessage.transfer == message.transfer &&
dataMessage.offset == message.offset &&
dataMessage.size == message.size &&
dataMessage.format == message.format &&
dataMessage.flags == message.flags &&
dataMessage.length == message.length &&
dataMessage.sequence == message.sequence;
const bool staleFileData = fileData && m_files.IsStaleTerminal(
dataMessage, CLGMPClipboardFiles::Direction::CLIENT_TO_HELPER);
const bool validData = fileData ?
(staleFileData || m_files.Validate(dataMessage,
CLGMPClipboardFiles::Direction::CLIENT_TO_HELPER, 0, false)) :
(!dataMessage.token &&
kvmfrClipboardTransferFromHelper(dataMessage.transfer) &&
ValidateChunk(m_clientToHelper, dataMessage));
if (!matchesCommit || !validData)
{
ReleaseOwner("invalid clipboard commit", true);
return true;
}
if (staleFileData)
{
grant = {};
RenewLease();
return true;
}
grant.committed = true;
dataMessage.generation = m_endpointGeneration;
RenewLease();
BeginTarget(dataMessage,
slot + sizeof(KVMFRClipboardSlotHeader),
static_cast<int>(grantIndex));
return false;
}
if (m_files.IsStaleTerminal(message, if (m_files.IsStaleTerminal(message,
CLGMPClipboardFiles::Direction::CLIENT_TO_HELPER)) CLGMPClipboardFiles::Direction::CLIENT_TO_HELPER))
{ {
@@ -1601,7 +1449,7 @@ bool CLGMPClipboardTransport::ProcessMessage(
KVMFRClipboardMessage forwarded = message; KVMFRClipboardMessage forwarded = message;
forwarded.generation = m_endpointGeneration; forwarded.generation = m_endpointGeneration;
RenewLease(); RenewLease();
BeginTarget(forwarded, nullptr, -1); BeginTarget(forwarded, nullptr, true);
return false; return false;
} }
@@ -1692,95 +1540,61 @@ ClipboardChannelResult CLGMPClipboardTransport::SendData(
KVMFRClipboardMessage message = record; KVMFRClipboardMessage message = record;
message.generation = m_ownerGeneration; message.generation = m_ownerGeneration;
if (m_ownerTransport == KVMFR_CLIPBOARD_TRANSPORT_STREAM) LGMPStreamBuffer buffer = {};
LGMP_STATUS status =
lgmpHostStreamWriteAcquire(m_hostToClientStream, &buffer);
if (status == LGMP_ERR_STREAM_FULL)
return ClipboardChannelResult::BUSY;
if (status == LGMP_ERR_STREAM_UNBOUND ||
status == LGMP_ERR_STREAM_STALE)
{ {
LGMPStreamBuffer buffer = {}; ReleaseOwner("clipboard stream binding lost", true);
LGMP_STATUS status = return ClipboardChannelResult::ACCEPTED;
lgmpHostStreamWriteAcquire(m_hostToClientStream, &buffer); }
if (status == LGMP_ERR_STREAM_FULL) if (status != LGMP_OK)
return ClipboardChannelResult::BUSY; {
if (status == LGMP_ERR_STREAM_UNBOUND || Fail("lgmpHostStreamWriteAcquire", status);
status == LGMP_ERR_STREAM_STALE)
{
ReleaseOwner("clipboard stream binding lost", true);
return ClipboardChannelResult::ACCEPTED;
}
if (status != LGMP_OK)
{
Fail("lgmpHostStreamWriteAcquire", status);
return ClipboardChannelResult::FAILED;
}
const uint32_t bytes = sizeof(message) + message.length;
if (!buffer.data || buffer.capacity < bytes)
{
const LGMP_STATUS cancel =
lgmpHostStreamWriteCancel(m_hostToClientStream, &buffer);
if (cancel != LGMP_OK)
DEBUG_ERROR("Failed to cancel an undersized host-to-client "
"clipboard stream reservation: %s", lgmpStatusString(cancel));
DEBUG_ERROR("Host-to-client clipboard stream returned an "
"undersized slot: capacity=%u required=%u",
buffer.capacity, bytes);
m_failed = true;
if (m_stopEvent)
SetEvent(m_stopEvent);
return ClipboardChannelResult::FAILED;
}
memcpy(buffer.data, &message, sizeof(message));
if (message.length)
memcpy(static_cast<uint8_t *>(buffer.data) + sizeof(message), data,
message.length);
status = lgmpHostStreamWriteCommit(
m_hostToClientStream, &buffer, bytes);
if (status == LGMP_OK)
{
ApplyOutbound(record);
return ClipboardChannelResult::ACCEPTED;
}
DEBUG_ERROR("Failed to commit host-to-client clipboard stream data: %s",
lgmpStatusString(status));
if (status == LGMP_ERR_STREAM_UNBOUND ||
status == LGMP_ERR_STREAM_STALE)
{
ReleaseOwner("clipboard stream binding lost", true);
return ClipboardChannelResult::ACCEPTED;
}
Fail("lgmpHostStreamWriteCommit", status);
return ClipboardChannelResult::FAILED; return ClipboardChannelResult::FAILED;
} }
if (m_ownerTransport != KVMFR_CLIPBOARD_TRANSPORT_LEGACY) const uint32_t bytes = sizeof(message) + message.length;
return ClipboardChannelResult::FAILED; if (!buffer.data || buffer.capacity < bytes)
{
PLGMPMemory memory = FindAvailable(m_dataMemory); const LGMP_STATUS cancel =
if (!memory) lgmpHostStreamWriteCancel(m_hostToClientStream, &buffer);
return ClipboardChannelResult::BUSY; if (cancel != LGMP_OK)
DEBUG_ERROR("Failed to cancel an undersized host-to-client "
uint8_t * slot = static_cast<uint8_t *>(lgmpHostMemPtr(memory)); "clipboard stream reservation: %s", lgmpStatusString(cancel));
memcpy(slot, &message, sizeof(message)); DEBUG_ERROR("Host-to-client clipboard stream returned an "
if (message.length) "undersized slot: capacity=%u required=%u",
memcpy(slot + sizeof(KVMFRClipboardSlotHeader), data, message.length); buffer.capacity, bytes);
m_failed = true;
const uint32_t serial = Seq::Next(m_dataSerial); if (m_stopEvent)
const PostResult result = PostForOwner( SetEvent(m_stopEvent);
KVMFR_CLIPBOARD_QUEUE_UDATA( return ClipboardChannelResult::FAILED;
KVMFR_CLIPBOARD_QUEUE_DATA, serial), memory); }
if (result == PostResult::POSTED)
memcpy(buffer.data, &message, sizeof(message));
if (message.length)
memcpy(static_cast<uint8_t *>(buffer.data) + sizeof(message), data,
message.length);
status = lgmpHostStreamWriteCommit(
m_hostToClientStream, &buffer, bytes);
if (status == LGMP_OK)
{ {
m_dataSerial = serial;
ApplyOutbound(record); ApplyOutbound(record);
return ClipboardChannelResult::ACCEPTED; return ClipboardChannelResult::ACCEPTED;
} }
if (result == PostResult::BUSY)
return ClipboardChannelResult::BUSY; DEBUG_ERROR("Failed to commit host-to-client clipboard stream data: %s",
if (result == PostResult::GONE) lgmpStatusString(status));
if (status == LGMP_ERR_STREAM_UNBOUND ||
status == LGMP_ERR_STREAM_STALE)
{ {
ReleaseOwner("subscriber disappeared", true, true); ReleaseOwner("clipboard stream binding lost", true);
return ClipboardChannelResult::ACCEPTED; return ClipboardChannelResult::ACCEPTED;
} }
Fail("lgmpHostStreamWriteCommit", status);
return ClipboardChannelResult::FAILED; return ClipboardChannelResult::FAILED;
} }
@@ -2002,6 +1816,24 @@ DWORD CALLBACK CLGMPClipboardTransport::ThreadProc(void * context)
void CLGMPClipboardTransport::Thread() void CLGMPClipboardTransport::Thread()
{ {
LGMPStreamPollState streamPoll = {};
const LGMPStreamPollConfig pollConfig =
{
32U,
50U,
1000U,
};
const LGMP_STATUS pollStatus = lgmpStreamPollInit(&streamPoll,
pollConfig);
if (pollStatus != LGMP_OK)
{
DEBUG_ERROR("Failed to initialize LGMP clipboard polling: %s",
lgmpStatusString(pollStatus));
if (m_target)
m_target->ClipboardFailed();
return;
}
bool notifyFailed = false; bool notifyFailed = false;
for (;;) for (;;)
{ {
@@ -2034,7 +1866,7 @@ void CLGMPClipboardTransport::Thread()
!DrainStream(streamReceived) || !DrainStream(streamReceived) ||
!RetryOwnerRelease() || !RetryOwnerRelease() ||
!PublishStatus() || !PublishStatus() ||
!ReplayClipboard() || !PostGrants()) !ReplayClipboard())
{ {
notifyFailed = true; notifyFailed = true;
break; break;
@@ -2048,10 +1880,15 @@ void CLGMPClipboardTransport::Thread()
if (m_pendingTarget.valid || m_internalTargetCount || if (m_pendingTarget.valid || m_internalTargetCount ||
m_streamTargetCount || m_ownerClientID) m_streamTargetCount || m_ownerClientID)
timeout = ACTIVE_POLL_MS; timeout = ACTIVE_POLL_MS;
if (m_ownerTransport == KVMFR_CLIPBOARD_TRANSPORT_STREAM) if (m_ownerClientID)
timeout = STREAM_POLL_MS; {
if (streamReceived) if (streamReceived)
timeout = 0; lgmpStreamPollActivity(&streamPoll);
const uint32_t waitUs = lgmpStreamPollIdle(&streamPoll);
timeout = waitUs ? (waitUs + 999U) / 1000U : 0U;
}
else
lgmpStreamPollActivity(&streamPoll);
} }
// ClipboardReceiveReady may synchronously retry SendClipboard, which // ClipboardReceiveReady may synchronously retry SendClipboard, which
@@ -2064,6 +1901,11 @@ void CLGMPClipboardTransport::Thread()
_countof(handles), handles, FALSE, timeout); _countof(handles), handles, FALSE, timeout);
if (wait == WAIT_FIRST_OBJECT_VALUE) if (wait == WAIT_FIRST_OBJECT_VALUE)
break; break;
if (wait == WAIT_FIRST_OBJECT_VALUE + 1)
{
lgmpStreamPollActivity(&streamPoll);
continue;
}
if (wait != WAIT_FIRST_OBJECT_VALUE + 1 && wait != WAIT_TIMEOUT) if (wait != WAIT_FIRST_OBJECT_VALUE + 1 && wait != WAIT_TIMEOUT)
{ {
DEBUG_ERROR_HR(GetLastError(), DEBUG_ERROR_HR(GetLastError(),

View File

@@ -41,14 +41,14 @@ class CLGMPHost;
class CLGMPClipboardTransport final : public IClipboardSource class CLGMPClipboardTransport final : public IClipboardSource
{ {
private: private:
static constexpr unsigned MEMORY_COUNT = KVMFR_CLIPBOARD_SLOT_COUNT; static constexpr unsigned MEMORY_COUNT =
KVMFR_CLIPBOARD_STREAM_SLOT_COUNT;
static constexpr unsigned INTERNAL_TARGET_COUNT = static constexpr unsigned INTERNAL_TARGET_COUNT =
CLGMPClipboardFiles::MAX_ACQUISITIONS + CLGMPClipboardFiles::MAX_ACQUISITIONS +
CLGMPClipboardFiles::MAX_REQUESTS + 3; CLGMPClipboardFiles::MAX_REQUESTS + 3;
static constexpr ULONGLONG OWNER_LEASE_MS = 1000; static constexpr ULONGLONG OWNER_LEASE_MS = 1000;
static constexpr ULONGLONG STREAM_DRAIN_TIMEOUT_MS = 1000; static constexpr ULONGLONG STREAM_DRAIN_TIMEOUT_MS = 1000;
static constexpr DWORD ACTIVE_POLL_MS = 5; static constexpr DWORD ACTIVE_POLL_MS = 5;
static constexpr DWORD STREAM_POLL_MS = 1;
static constexpr DWORD IDLE_POLL_MS = 50; static constexpr DWORD IDLE_POLL_MS = 50;
static constexpr unsigned STREAM_TARGET_COUNT = static constexpr unsigned STREAM_TARGET_COUNT =
KVMFR_CLIPBOARD_STREAM_SLOT_COUNT; KVMFR_CLIPBOARD_STREAM_SLOT_COUNT;
@@ -78,18 +78,10 @@ private:
bool Active() const { return transfer != 0; } bool Active() const { return transfer != 0; }
}; };
struct Grant
{
uint32_t generation = 0;
bool offered = false;
bool committed = false;
};
struct PendingTarget struct PendingTarget
{ {
bool valid = false; bool valid = false;
bool acknowledge = false; bool acknowledge = false;
int grant = -1;
KVMFRClipboardMessage record = {}; KVMFRClipboardMessage record = {};
uint8_t data[KVMFR_CLIPBOARD_DATA_BYTES] = {}; uint8_t data[KVMFR_CLIPBOARD_DATA_BYTES] = {};
@@ -97,7 +89,6 @@ private:
{ {
valid = false; valid = false;
acknowledge = false; acknowledge = false;
grant = -1;
record = {}; record = {};
} }
}; };
@@ -117,8 +108,6 @@ private:
PLGMPHostStream m_clientToHostStream = nullptr; PLGMPHostStream m_clientToHostStream = nullptr;
PLGMPMemory m_statusMemory [MEMORY_COUNT] = {}; PLGMPMemory m_statusMemory [MEMORY_COUNT] = {};
PLGMPMemory m_messageMemory[MEMORY_COUNT] = {}; PLGMPMemory m_messageMemory[MEMORY_COUNT] = {};
PLGMPMemory m_grantMemory [MEMORY_COUNT] = {};
PLGMPMemory m_dataMemory [MEMORY_COUNT] = {};
CSRWLock m_lifecycleLock; CSRWLock m_lifecycleLock;
CSRWLock m_lock; CSRWLock m_lock;
@@ -138,10 +127,8 @@ private:
uint32_t m_endpointGeneration = 0; uint32_t m_endpointGeneration = 0;
uint32_t m_ownerClientID = 0; uint32_t m_ownerClientID = 0;
uint32_t m_ownerGeneration = 0; uint32_t m_ownerGeneration = 0;
KVMFRClipboardTransportFlags m_ownerTransport = 0;
uint32_t m_statusSerial = 0; uint32_t m_statusSerial = 0;
uint32_t m_messageSerial = 0; uint32_t m_messageSerial = 0;
uint32_t m_dataSerial = 0;
uint32_t m_helperFormats = 0; uint32_t m_helperFormats = 0;
uint32_t m_clientFormats = 0; uint32_t m_clientFormats = 0;
uint32_t m_blockedOwnerClientID = 0; uint32_t m_blockedOwnerClientID = 0;
@@ -158,7 +145,6 @@ private:
KVMFRStreamDescriptor m_clientToHostDescriptor = {}; KVMFRStreamDescriptor m_clientToHostDescriptor = {};
Transfer m_clientToHelper; Transfer m_clientToHelper;
Transfer m_helperToClient; Transfer m_helperToClient;
Grant m_grants[MEMORY_COUNT];
PendingTarget m_pendingTarget; PendingTarget m_pendingTarget;
StreamTarget m_streamTarget[STREAM_TARGET_COUNT]; StreamTarget m_streamTarget[STREAM_TARGET_COUNT];
unsigned m_streamTargetHead = 0; unsigned m_streamTargetHead = 0;
@@ -171,13 +157,13 @@ private:
void DeInit(); void DeInit();
bool InitializeStreams(); bool InitializeStreams();
void DeInitStreams(); void DeInitStreams();
bool BindStreams(uint32_t clientID); bool BindStreams(uint32_t clientID) const;
bool TryUnbindStreams(); bool TryUnbindStreams();
void ForceUnbindStreams(); void ForceUnbindStreams() const;
static DWORD CALLBACK ThreadProc(void * context); static DWORD CALLBACK ThreadProc(void * context);
void Thread(); void Thread();
void Wake(); void Wake() const;
bool IsOwner(uint32_t clientID, uint32_t generation) const; bool IsOwner(uint32_t clientID, uint32_t generation) const;
bool OwnerSubscribed() const; bool OwnerSubscribed() const;
@@ -210,7 +196,6 @@ private:
PLGMPMemory FindAvailable(PLGMPMemory (&memory)[MEMORY_COUNT]) const; PLGMPMemory FindAvailable(PLGMPMemory (&memory)[MEMORY_COUNT]) const;
PostResult PostForOwner(uint64_t udata, PLGMPMemory memory); PostResult PostForOwner(uint64_t udata, PLGMPMemory memory);
bool PublishStatus(); bool PublishStatus();
bool PostGrants();
bool ReplayClipboard(); bool ReplayClipboard();
bool DrainMessage(); bool DrainMessage();
bool ProcessMessage(uint32_t clientID, bool ProcessMessage(uint32_t clientID,
@@ -231,7 +216,7 @@ private:
void ApplyInbound(const KVMFRClipboardMessage& message); void ApplyInbound(const KVMFRClipboardMessage& message);
void ApplyOutbound(const KVMFRClipboardMessage& message); void ApplyOutbound(const KVMFRClipboardMessage& message);
bool BeginTarget(const KVMFRClipboardMessage& message, bool BeginTarget(const KVMFRClipboardMessage& message,
const uint8_t * data, int grant); const uint8_t * data, bool acknowledge);
ClipboardChannelResult SendControl( ClipboardChannelResult SendControl(
const KVMFRClipboardMessage& record); const KVMFRClipboardMessage& record);