From be4bb19fa4c4ea247eb483112001fa1038a3b523 Mon Sep 17 00:00:00 2001 From: Geoffrey McRae Date: Sat, 15 Aug 2026 14:36:33 +1000 Subject: [PATCH] [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. --- client/transports/LGMP/clipboard.c | 406 +++++++----------- common/include/common/KVMFRClipboard.h | 78 ++-- .../lgmp/CLGMPClipboardTransport.cpp | 362 +++++----------- .../transport/lgmp/CLGMPClipboardTransport.h | 27 +- 4 files changed, 277 insertions(+), 596 deletions(-) diff --git a/client/transports/LGMP/clipboard.c b/client/transports/LGMP/clipboard.c index c9d627f8..f5f32e13 100644 --- a/client/transports/LGMP/clipboard.c +++ b/client/transports/LGMP/clipboard.c @@ -40,7 +40,6 @@ #define CLIPBOARD_PENDING_MAX 128U #define CLIPBOARD_PENDING_NORMAL_MAX 64U #define CLIPBOARD_DRAIN_MAX 64U -#define CLIPBOARD_GRANTS KVMFR_CLIPBOARD_SLOT_COUNT #define CLIPBOARD_IDLE_POLL_MS 10U #define CLIPBOARD_ACTIVE_POLL_MS 1U #define CLIPBOARD_KEEPALIVE_US UINT64_C(200000) @@ -51,14 +50,6 @@ struct PendingRecord KVMFRClipboardMessage record; }; -struct Grant -{ - KVMFRClipboardSlotHeader * header; - uint8_t * data; - uint32_t token; - bool available; -}; - struct FileTransfer { struct FileTransfer * next; @@ -106,8 +97,7 @@ struct LGMPClipboard bool statusValid; bool claimed; bool ownerConfirmed; - KVMFRClipboardTransportFlags transports; - KVMFRClipboardTransportFlags claimTransport; + bool releasing; KVMFRStreamDescriptor hostToClientDescriptor; KVMFRStreamDescriptor clientToHostDescriptor; uint32_t clientID; @@ -122,7 +112,6 @@ struct LGMPClipboard unsigned pendingHead; unsigned pendingCount; - struct Grant grants[CLIPBOARD_GRANTS]; bool writeBlocked; LG_ClipboardRequest writeBlockedRequest; LG_ClipboardRequest writeTransfer; @@ -136,9 +125,7 @@ struct LGMPClipboard bool held; bool heldReady; - bool heldStream; enum HeldPhase heldPhase; - LGMPMessage heldMessage; LGMPStreamBuffer heldStreamBuffer; KVMFRClipboardMessage heldRecord; bool heldFile; @@ -229,12 +216,6 @@ static void detachStreamsNL(LGMPClipboard * clipboard) 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)) && @@ -659,14 +640,6 @@ static bool enqueueTypeNL(LGMPClipboard * clipboard, 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, const KVMFRClipboardMessage * record, const void * data) { @@ -725,32 +698,12 @@ static bool enqueueDataNL(LGMPClipboard * clipboard, 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); - 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; + return streamWriteNL(clipboard, &record, data); } static bool streamWritableNL(LGMPClipboard * clipboard) { - if (!clipboard->clientToHostStream || - clipboard->claimTransport != KVMFR_CLIPBOARD_TRANSPORT_STREAM) + if (!clipboard->clientToHostStream || !clipboard->claimed) return false; LGMPStreamBuffer buffer = { 0 }; @@ -777,11 +730,7 @@ static bool streamWritableNL(LGMPClipboard * clipboard) 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); + return streamWritableNL(clipboard); } static void clearWriteNL(LGMPClipboard * clipboard) @@ -810,43 +759,43 @@ static void clearReadNL(LGMPClipboard * clipboard) 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->remoteFormats = 0; clearWriteNL(clipboard); clearReadNL(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) { if (clipboard->claimed) return true; - if (!clipboard->connected || !clipboard->available || - !clipboard->transports) + if (clipboard->releasing || !clipboard->connected || + !clipboard->available || + !clipboard->hostToClientStream || !clipboard->clientToHostStream) return false; KVMFRClipboardMessage claim = { 0 }; nextNonzero(&clipboard->claimGeneration); claim.type = KVMFR_CLIPBOARD_MESSAGE_CLAIM; claim.token = clipboard->endpointGeneration; - claim.flags = - (clipboard->transports & KVMFR_CLIPBOARD_TRANSPORT_STREAM) ? - KVMFR_CLIPBOARD_TRANSPORT_STREAM : - KVMFR_CLIPBOARD_TRANSPORT_LEGACY; if (!enqueueRecordNL(clipboard, claim)) return false; clipboard->claimed = true; clipboard->ownerConfirmed = false; - clipboard->claimTransport = claim.flags; return true; } @@ -892,13 +841,10 @@ static void connectionLostNL(LGMPClipboard * clipboard) clipboard->connected = false; clipboard->available = false; clipboard->statusValid = false; - clipboard->transports = 0; detachStreamsNL(clipboard); clearProtocolNL(clipboard); clipboard->held = 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)); @@ -930,35 +876,19 @@ static bool validStatus(const KVMFRClipboardStatus * status) { const uint32_t validFlags = KVMFR_CLIPBOARD_STATUS_AVAILABLE | KVMFR_CLIPBOARD_STATUS_HAS_OWNER; - const KVMFRStreamDescriptor emptyDescriptor = { 0 }; - const uint32_t emptyReserved[4] = { 0 }; + const uint32_t emptyReserved[6] = { 0 }; if (status->version != KVMFR_CLIPBOARD_VERSION || status->flags & ~validFlags || !status->generation || status->formats & ~KVMFR_CLIPBOARD_FORMAT_MASK_ALL || !status->lease || 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))) + status->streamVersion != KVMFR_CLIPBOARD_STREAM_VERSION || + status->streamSlotCount != KVMFR_CLIPBOARD_STREAM_SLOT_COUNT || + memcmp(status->reserved, emptyReserved, sizeof(emptyReserved)) || + !validStreamDescriptor(&status->hostToClient, + LGMP_STREAM_HOST_TO_CLIENT) || + !validStreamDescriptor(&status->clientToHost, + LGMP_STREAM_CLIENT_TO_HOST)) return false; const bool available = @@ -967,16 +897,12 @@ static bool validStatus(const KVMFRClipboardStatus * status) (status->flags & KVMFR_CLIPBOARD_STATUS_HAS_OWNER) != 0; if (!available && (status->formats || owner)) return false; - return owner ? status->ownerClientID && status->ownerGeneration && - kvmfrClipboardTransportValid(status->ownerTransport) && - (status->transports & status->ownerTransport) : - !status->ownerClientID && !status->ownerGeneration && - !status->ownerTransport; + return owner ? status->ownerClientID && status->ownerGeneration : + !status->ownerClientID && !status->ownerGeneration; } static bool validateRecord(const LGMPClipboard * clipboard, - const KVMFRClipboardMessage * record, size_t size, - KVMFRClipboardQueueType queueType, bool stream) + const KVMFRClipboardMessage * record, size_t size, bool stream) { if (size < sizeof(*record) || record->version != KVMFR_CLIPBOARD_VERSION || @@ -990,15 +916,10 @@ static bool validateRecord(const LGMPClipboard * clipboard, if (record->type == KVMFR_CLIPBOARD_MESSAGE_DATA || record->type == KVMFR_CLIPBOARD_MESSAGE_FILE_DATA) { - const size_t expected = sizeof(*record) + - (stream ? record->length : KVMFR_CLIPBOARD_DATA_BYTES); - if (size != expected || queueType != KVMFR_CLIPBOARD_QUEUE_DATA || - stream != (clipboard->claimTransport == - KVMFR_CLIPBOARD_TRANSPORT_STREAM)) + if (!stream || size != sizeof(*record) + record->length) return false; } - else if (queueType != KVMFR_CLIPBOARD_QUEUE_MESSAGE || - size != sizeof(*record) || record->length || + else if (stream || size != sizeof(*record) || record->length || (record->flags && record->type != KVMFR_CLIPBOARD_MESSAGE_FILE_REQUEST)) return false; @@ -1500,32 +1421,27 @@ static void applyStatusNL(LGMPClipboard * clipboard, const bool wasValid = clipboard->statusValid; const bool wasAvailable = clipboard->available; const uint32_t oldGeneration = clipboard->endpointGeneration; - const KVMFRClipboardTransportFlags oldTransports = - clipboard->transports; clipboard->statusValid = true; clipboard->statusSerial = serial; const bool streamsAttached = attachStreamsNL(clipboard, status); const bool endpointAvailable = (status->flags & KVMFR_CLIPBOARD_STATUS_AVAILABLE) != 0; 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) && - clipboard->claimed && status->ownerClientID == clipboard->clientID && - status->ownerGeneration == clipboard->claimGeneration && - status->ownerTransport == clipboard->claimTransport; - const bool ownedByOther = - (status->flags & KVMFR_CLIPBOARD_STATUS_HAS_OWNER) && !owned; - const bool selectedUnavailable = clipboard->claimed && - !(clipboard->transports & clipboard->claimTransport); - clipboard->available = endpointAvailable && !ownedByOther && - clipboard->transports; + const bool hasOwner = + (status->flags & KVMFR_CLIPBOARD_STATUS_HAS_OWNER) != 0; + const bool owned = hasOwner && + (clipboard->claimed || clipboard->releasing) && + status->ownerClientID == clipboard->clientID && + status->ownerGeneration == clipboard->claimGeneration; + const bool ownedByOther = hasOwner && !owned; + const bool selectedUnavailable = + (clipboard->claimed || clipboard->releasing) && !streamsAttached; + clipboard->available = endpointAvailable && streamsAttached && + !ownedByOther && !clipboard->releasing; bool restore = false; - if (owned && clipboard->claimTransport == - KVMFR_CLIPBOARD_TRANSPORT_STREAM) + if (owned) { const LGMP_STATUS activate = activateStreamsNL(clipboard); if (activate != LGMP_OK) @@ -1540,26 +1456,36 @@ static void applyStatusNL(LGMPClipboard * clipboard, ownedByOther || selectedUnavailable) { clearProtocolNL(clipboard); - restore = endpointAvailable && !ownedByOther; + restore = endpointAvailable && streamsAttached && !ownedByOther; } - else if (status->flags & KVMFR_CLIPBOARD_STATUS_HAS_OWNER) + else if (hasOwner) clipboard->ownerConfirmed = owned; 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); - 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)) clearProtocolNL(clipboard); *changed = !wasValid || wasAvailable != clipboard->available || - oldGeneration != clipboard->endpointGeneration || - oldTransports != clipboard->transports; + oldGeneration != clipboard->endpointGeneration; if (*changed) nextNonzero(&clipboard->providerGeneration); } @@ -1600,7 +1526,6 @@ static bool processHeld(LGMPClipboard * clipboard) const uint8_t * data; enum HeldPhase phase; bool file; - bool stream; LGMPStreamBuffer streamBuffer; LG_ClipboardFileRequest fileRequest; LG_LOCK(clipboard->lock); @@ -1610,11 +1535,9 @@ static bool processHeld(LGMPClipboard * clipboard) return true; } record = clipboard->heldRecord; - stream = clipboard->heldStream; streamBuffer = clipboard->heldStreamBuffer; - data = !record.length ? NULL : stream ? - (const uint8_t *)streamBuffer.data + sizeof(record) : - (const uint8_t *)clipboard->heldMessage.mem + sizeof(record); + data = !record.length ? NULL : + (const uint8_t *)streamBuffer.data + sizeof(record); phase = clipboard->heldPhase; file = clipboard->heldFile; fileRequest = clipboard->heldFileRequest; @@ -1702,13 +1625,9 @@ static bool processHeld(LGMPClipboard * clipboard) else if (matchingRead) clearReadNL(clipboard); - const LGMP_STATUS done = stream ? - lgmpClientStreamReadRelease( - clipboard->hostToClientStream, &streamBuffer) : - lgmpClientMessageDone(clipboard->queue); + const LGMP_STATUS done = lgmpClientStreamReadRelease( + clipboard->hostToClientStream, &streamBuffer); clipboard->held = 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)); @@ -1731,7 +1650,7 @@ 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->claimed && !clipboard->releasing) || !clipboard->hostToClientStream) { LG_UNLOCK(clipboard->lock); @@ -1756,12 +1675,23 @@ static bool processStream(LGMPClipboard * clipboard, bool * processed) } *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 }; 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); + validateRecord(clipboard, &record, buffer.size, true); if (!valid) { KVMFRClipboardMessage cancellation = { 0 }; @@ -1810,7 +1740,6 @@ static bool processStream(LGMPClipboard * clipboard, bool * processed) record.transfer = clipboard->readRequest; clipboard->held = true; clipboard->heldReady = true; - clipboard->heldStream = true; clipboard->heldStreamBuffer = buffer; clipboard->heldRecord = record; clipboard->heldFile = file; @@ -1875,66 +1804,16 @@ static bool processMessage(LGMPClipboard * clipboard, bool * processed) 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); - 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) connectionFailed(clipboard, status); 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; } - if ((type != KVMFR_CLIPBOARD_QUEUE_MESSAGE && - type != KVMFR_CLIPBOARD_QUEUE_DATA) || + if (type != KVMFR_CLIPBOARD_QUEUE_MESSAGE || message.size < sizeof(KVMFRClipboardMessage)) { status = lgmpClientMessageDone(clipboard->queue); @@ -1947,8 +1826,7 @@ static bool processMessage(LGMPClipboard * clipboard, bool * processed) KVMFRClipboardMessage record; memcpy(&record, message.mem, sizeof(record)); - const bool valid = validateRecord( - clipboard, &record, message.size, type, false); + const bool valid = validateRecord(clipboard, &record, message.size, false); if (!valid) { KVMFRClipboardMessage cancellation = { 0 }; @@ -1967,47 +1845,6 @@ static bool processMessage(LGMPClipboard * clipboard, bool * processed) 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); if (status != LGMP_OK) { @@ -2308,9 +2145,25 @@ static bool processMessage(LGMPClipboard * clipboard, bool * processed) static int clipboardThread(void * 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)) { bool running = true; + bool streamProgress = false; unsigned drained = 0; for (; drained < CLIPBOARD_DRAIN_MAX; ++drained) { @@ -2334,6 +2187,7 @@ static int clipboardThread(void * opaque) running = false; break; } + streamProgress |= streamProcessed; dispatchRetiredFiles(clipboard); if (!queueProcessed && !streamProcessed) break; @@ -2362,6 +2216,8 @@ static int clipboardThread(void * opaque) const bool writable = (clipboard->writeBlocked || blockedFile) && dataWritableNL(clipboard); + if (writable) + streamProgress = true; if (clipboard->writeBlocked && writable) { readyRequest = clipboard->writeBlockedRequest; @@ -2380,6 +2236,8 @@ static int clipboardThread(void * opaque) clipboard->pendingCount || clipboard->held || clipboard->writeBlocked || clipboard->fileReads || clipboard->fileWrites; + const bool streamActive = clipboard->connected && + (clipboard->claimed || clipboard->releasing); LG_UNLOCK(clipboard->lock); if (readyRequest != LG_CLIPBOARD_REQUEST_INVALID) @@ -2390,9 +2248,30 @@ static int clipboardThread(void * opaque) if (atomic_load_explicit(&clipboard->stop, memory_order_acquire)) break; if (drained == CLIPBOARD_DRAIN_MAX) + { + if (streamProgress) + lgmpStreamPollActivity(&streamPoll); 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); notifyStatus(clipboard); @@ -2481,9 +2360,9 @@ bool lgmpClipboard_connect(LGMPClipboard * clipboard, uint32_t clientID) clipboard->connected = true; clipboard->available = false; clipboard->statusValid = false; - clipboard->transports = 0; clipboard->claimed = false; clipboard->ownerConfirmed = false; + clipboard->releasing = false; clipboard->clientID = clientID; clipboard->endpointGeneration = 0; clipboard->statusSerial = 0; @@ -2496,7 +2375,6 @@ bool lgmpClipboard_connect(LGMPClipboard * clipboard, uint32_t clientID) clipboard->remoteClipboardGeneration = 0; clearReadNL(clipboard); clipboard->publishedClaimGeneration = 0; - memset(clipboard->grants, 0, sizeof(clipboard->grants)); atomic_store_explicit(&clipboard->stop, false, memory_order_release); LGThread * thread; @@ -2596,10 +2474,8 @@ void lgmpClipboard_disconnect(LGMPClipboard * clipboard) LG_LOCK(clipboard->lock); if (clipboard->held) { - const LGMP_STATUS status = clipboard->heldStream ? - lgmpClientStreamReadRelease(clipboard->hostToClientStream, - &clipboard->heldStreamBuffer) : clipboard->queue ? - lgmpClientMessageDone(clipboard->queue) : LGMP_OK; + const LGMP_STATUS status = lgmpClientStreamReadRelease( + clipboard->hostToClientStream, &clipboard->heldStreamBuffer); if (status != LGMP_OK && status != LGMP_ERR_STREAM_STALE && status != LGMP_ERR_STREAM_UNBOUND) DEBUG_WARN("Failed to release held clipboard data during " @@ -2611,10 +2487,8 @@ void lgmpClipboard_disconnect(LGMPClipboard * clipboard) clipboard->thread = NULL; clipboard->event = NULL; clipboard->held = false; - clipboard->heldStream = false; memset(&clipboard->heldStreamBuffer, 0, sizeof(clipboard->heldStreamBuffer)); - clipboard->transports = 0; detachStreamsNL(clipboard); clearProtocolNL(clipboard); LG_UNLOCK(clipboard->lock); @@ -2688,21 +2562,31 @@ static bool attach(void * opaque, const LG_ClipboardEventOps * events, static void detach(void * opaque) { LGMPClipboard * clipboard = opaque; - bool wake = false; + bool releaseQueued = false; + bool releaseRequired = false; LG_LOCK(clipboard->eventLock); LG_LOCK(clipboard->lock); clipboard->events = NULL; clipboard->eventOpaque = NULL; if (clipboard->claimed) { - wake = enqueueTypeNL( - clipboard, KVMFR_CLIPBOARD_MESSAGE_RELEASE); - clipboard->claimed = false; + releaseRequired = true; + KVMFRClipboardMessage release = { 0 }; + 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->eventLock); - if (wake) - signalWorker(clipboard); + if (!releaseQueued && releaseRequired) + DEBUG_WARN("Failed to queue LGMP clipboard release; ownership will " + "expire"); + signalWorker(clipboard); } static bool releaseClipboard(void * opaque) diff --git a/common/include/common/KVMFRClipboard.h b/common/include/common/KVMFRClipboard.h index af4e6d85..8e8158ad 100644 --- a/common/include/common/KVMFRClipboard.h +++ b/common/include/common/KVMFRClipboard.h @@ -31,10 +31,10 @@ #define KVMFR_CLIPBOARD_VERSION 4U #define KVMFR_CLIPBOARD_STREAM_VERSION 1U -/* Physical transport geometry. Keep these independent from the maximum - * logical request and representation sizes below: one response may span - * several stream slots. The legacy grant transport uses the same geometry so - * it remains a bounded fallback while stream adoption is staged. */ +/* Physical stream geometry. Keep these independent from the maximum logical + * request and representation sizes below: one response may span several + * stream slots. The generic names are also used by the Helper/IDD clipboard + * ring, which intentionally has the same geometry. */ #define KVMFR_CLIPBOARD_STREAM_SLOT_COUNT 4U #define KVMFR_CLIPBOARD_STREAM_SLOT_BYTES (256U * 1024U) #define KVMFR_CLIPBOARD_STREAM_WINDOW_BYTES \ @@ -131,16 +131,13 @@ enum KVMFR_CLIPBOARD_MESSAGE_CLEAR = 5, KVMFR_CLIPBOARD_MESSAGE_REQUEST = 6, KVMFR_CLIPBOARD_MESSAGE_DATA = 7, - KVMFR_CLIPBOARD_MESSAGE_COMMIT = 8, - KVMFR_CLIPBOARD_MESSAGE_CANCEL = 9, - KVMFR_CLIPBOARD_MESSAGE_ACK = 10, - KVMFR_CLIPBOARD_MESSAGE_GRANT = 11, - KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRE = 12, - KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRED = 13, - KVMFR_CLIPBOARD_MESSAGE_FILE_RELEASE = 14, - KVMFR_CLIPBOARD_MESSAGE_FILE_REQUEST = 15, - KVMFR_CLIPBOARD_MESSAGE_FILE_DATA = 16, - KVMFR_CLIPBOARD_MESSAGE_FILE_CANCEL = 17 + KVMFR_CLIPBOARD_MESSAGE_CANCEL = 8, + KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRE = 9, + KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRED = 10, + KVMFR_CLIPBOARD_MESSAGE_FILE_RELEASE = 11, + KVMFR_CLIPBOARD_MESSAGE_FILE_REQUEST = 12, + KVMFR_CLIPBOARD_MESSAGE_FILE_DATA = 13, + KVMFR_CLIPBOARD_MESSAGE_FILE_CANCEL = 14 }; typedef uint32_t KVMFRClipboardMessageType; @@ -153,30 +150,12 @@ enum 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 * 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 - * by KVMFRClipboardStatus. The selected transport is fixed until RELEASE and - * a subsequent CLAIM with a new generation. + * pool and the same layout. DATA and FILE_DATA records are carried only by + * the duplex streams advertised in KVMFRClipboardStatus; all other records + * use the clipboard queue. CLAIM flags are reserved and must be zero. */ typedef struct KVMFRClipboardMessage { @@ -369,14 +348,12 @@ static inline int kvmfrClipboardFileMessageValid( } /* Type-specific fields: - * CLAIM: flags selects exactly one KVMFRClipboardTransportFlags value and - * token is the endpoint generation from the latest status. + * CLAIM: token is the endpoint generation from the latest status. * OFFER: token is KVMFRClipboardFormatFlags. * REQUEST: format and transfer identify the requested representation. * DATA: size is an optional total hint on BEGIN and authoritative on END; * offset/length describe this record's borrowed payload. - * CANCEL: token is a KVMFRClipboardCancelReason-compatible reason. - * ACK/GRANT: token identifies the acknowledged or writable legacy slot. */ + * CANCEL: token is a KVMFRClipboardCancelReason-compatible reason. */ enum { @@ -386,11 +363,8 @@ enum typedef uint32_t KVMFRClipboardStatusFlags; -/* transports advertises available data planes. When HAS_OWNER is set, - * ownerTransport identifies the single data plane selected by that owner's - * CLAIM. Stream descriptors are valid only when STREAM is advertised; their - * slot size includes KVMFRClipboardSlotHeader as well as slotBytes of payload. - */ +/* The duplex stream descriptors are always valid. Their slot size includes + * KVMFRClipboardSlotHeader as well as slotBytes of payload. */ typedef struct KVMFRClipboardStatus { uint32_t version; @@ -401,11 +375,9 @@ typedef struct KVMFRClipboardStatus uint32_t lease; KVMFRClipboardFormatFlags formats; uint32_t slotBytes; - KVMFRClipboardTransportFlags transports; - KVMFRClipboardTransportFlags ownerTransport; uint32_t streamVersion; uint32_t streamSlotCount; - uint32_t reserved[4]; + uint32_t reserved[6]; KVMFRStreamDescriptor hostToClient; KVMFRStreamDescriptor clientToHost; } @@ -420,9 +392,7 @@ typedef KVMFRClipboardMessage KVMFRClipboardSlotHeader; enum { KVMFR_CLIPBOARD_QUEUE_STATUS = 1, - KVMFR_CLIPBOARD_QUEUE_MESSAGE = 2, - KVMFR_CLIPBOARD_QUEUE_GRANT = 3, - KVMFR_CLIPBOARD_QUEUE_DATA = 4 + KVMFR_CLIPBOARD_QUEUE_MESSAGE = 2 }; typedef uint32_t KVMFRClipboardQueueType; @@ -438,8 +408,8 @@ static_assert(sizeof(KVMFRClipboardMessage) == 64, "KVMFR clipboard control message layout changed"); static_assert(sizeof(KVMFRClipboardFileEntry) == 40, "KVMFR clipboard file entry layout changed"); -static_assert(offsetof(KVMFRClipboardStatus, transports) == 32, - "KVMFR clipboard transport status layout changed"); +static_assert(offsetof(KVMFRClipboardStatus, streamVersion) == 32, + "KVMFR clipboard stream status layout changed"); static_assert(offsetof(KVMFRClipboardStatus, hostToClient) == 64, "KVMFR clipboard stream discovery layout changed"); static_assert(sizeof(KVMFRClipboardStatus) == 128, @@ -453,8 +423,8 @@ _Static_assert(sizeof(KVMFRClipboardMessage) == 64, "KVMFR clipboard control message layout changed"); _Static_assert(sizeof(KVMFRClipboardFileEntry) == 40, "KVMFR clipboard file entry layout changed"); -_Static_assert(offsetof(KVMFRClipboardStatus, transports) == 32, - "KVMFR clipboard transport status layout changed"); +_Static_assert(offsetof(KVMFRClipboardStatus, streamVersion) == 32, + "KVMFR clipboard stream status layout changed"); _Static_assert(offsetof(KVMFRClipboardStatus, hostToClient) == 64, "KVMFR clipboard stream discovery layout changed"); _Static_assert(sizeof(KVMFRClipboardStatus) == 128, diff --git a/idd/LGIdd/transport/lgmp/CLGMPClipboardTransport.cpp b/idd/LGIdd/transport/lgmp/CLGMPClipboardTransport.cpp index 5e872d03..db935308 100644 --- a/idd/LGIdd/transport/lgmp/CLGMPClipboardTransport.cpp +++ b/idd/LGIdd/transport/lgmp/CLGMPClipboardTransport.cpp @@ -49,11 +49,11 @@ namespace }; bool EmptyControl(const KVMFRClipboardMessage& message, - bool keepToken = false, bool keepFlags = false) + bool keepToken = false) { return !message.clipboardGeneration && !message.transfer && !message.offset && !message.size && !message.format && - (keepFlags || !message.flags) && (keepToken || !message.token) && + !message.flags && (keepToken || !message.token) && !message.length && !message.sequence; } @@ -177,10 +177,6 @@ void CLGMPClipboardTransport::DeInit() { Stop(); DeInitStreams(); - for (PLGMPMemory& memory : m_dataMemory) - lgmpHostMemFree(&memory); - for (PLGMPMemory& memory : m_grantMemory) - lgmpHostMemFree(&memory); for (PLGMPMemory& memory : m_messageMemory) lgmpHostMemFree(&memory); for (PLGMPMemory& memory : m_statusMemory) @@ -242,7 +238,7 @@ void CLGMPClipboardTransport::DeInitStreams() m_hostToClientDescriptor = {}; } -bool CLGMPClipboardTransport::BindStreams(uint32_t clientID) +bool CLGMPClipboardTransport::BindStreams(uint32_t clientID) const { if (!m_hostToClientStream || !m_clientToHostStream) return false; @@ -301,7 +297,7 @@ bool CLGMPClipboardTransport::TryUnbindStreams() return complete; } -void CLGMPClipboardTransport::ForceUnbindStreams() +void CLGMPClipboardTransport::ForceUnbindStreams() const { if (m_clientToHostStream) { @@ -321,7 +317,7 @@ void CLGMPClipboardTransport::ForceUnbindStreams() } } -void CLGMPClipboardTransport::Wake() +void CLGMPClipboardTransport::Wake() const { if (m_wakeEvent) SetEvent(m_wakeEvent); @@ -557,7 +553,7 @@ bool CLGMPClipboardTransport::QueueInternalTarget( const KVMFRClipboardMessage& record) { if (!m_pendingTarget.valid && !m_internalTargetCount) - return BeginTarget(record, nullptr, -2); + return BeginTarget(record, nullptr, false); if (m_internalTargetCount == INTERNAL_TARGET_COUNT) { m_failed = true; @@ -576,7 +572,7 @@ bool CLGMPClipboardTransport::PumpInternalTarget() for (unsigned i = 1; i < m_internalTargetCount; ++i) m_internalTarget[i - 1] = m_internalTarget[i]; m_internalTarget[--m_internalTargetCount] = {}; - return BeginTarget(record, nullptr, -2); + return BeginTarget(record, nullptr, false); } void CLGMPClipboardTransport::ClearStreamTargets() @@ -606,8 +602,7 @@ bool CLGMPClipboardTransport::QueueStreamTarget( bool CLGMPClipboardTransport::ProcessStreamTarget( const KVMFRClipboardMessage& record, const uint8_t * data) { - if (!m_ownerClientID || - m_ownerTransport != KVMFR_CLIPBOARD_TRANSPORT_STREAM) + if (!m_ownerClientID) return true; if (m_ownerReleasing && !m_releaseClearHelper) return true; @@ -638,7 +633,7 @@ bool CLGMPClipboardTransport::ProcessStreamTarget( KVMFRClipboardMessage forwarded = record; forwarded.generation = m_endpointGeneration; - if (!BeginTarget(forwarded, data, -2)) + if (!BeginTarget(forwarded, data, false)) { ReleaseOwner("failed to stage clipboard stream data", true); return true; @@ -666,9 +661,7 @@ bool CLGMPClipboardTransport::PumpStreamTarget() bool CLGMPClipboardTransport::DrainStream(bool& received) { received = false; - if (!m_ownerClientID || - m_ownerTransport != KVMFR_CLIPBOARD_TRANSPORT_STREAM || - !m_clientToHostStream) + if (!m_ownerClientID || !m_clientToHostStream) return true; for (unsigned drained = 0; @@ -744,12 +737,6 @@ void CLGMPClipboardTransport::ReleaseOwner( m_ownerDrainDeadline = GetTickCount64() + STREAM_DRAIN_TIMEOUT_MS; m_replayPending = false; ClearOutboundBlock(); - for (Grant& grant : m_grants) - { - grant.generation = 0; - grant.offered = false; - grant.committed = false; - } } else if (force) { @@ -757,12 +744,6 @@ void CLGMPClipboardTransport::ReleaseOwner( m_ownerReleaseReason = reason; } - if (m_ownerTransport != KVMFR_CLIPBOARD_TRANSPORT_STREAM) - { - FinishOwnerRelease(); - return; - } - if (force) { ForceUnbindStreams(); @@ -789,7 +770,6 @@ void CLGMPClipboardTransport::FinishOwnerRelease() const bool clearHelper = m_releaseClearHelper; m_ownerClientID = 0; m_ownerGeneration = 0; - m_ownerTransport = 0; m_ownerReleasing = false; m_releaseClearHelper = false; m_ownerReleaseReason = nullptr; @@ -827,12 +807,6 @@ bool CLGMPClipboardTransport::RetryOwnerRelease() if (!m_ownerReleasing) return true; - if (m_ownerTransport != KVMFR_CLIPBOARD_TRANSPORT_STREAM) - { - FinishOwnerRelease(); - return true; - } - if (GetTickCount64() >= m_ownerDrainDeadline) { DEBUG_WARN("Timed out draining clipboard streams for owner %u " @@ -947,14 +921,10 @@ bool CLGMPClipboardTransport::PublishStatus() clipboardStatus.generation = m_endpointGeneration; clipboardStatus.lease = static_cast(OWNER_LEASE_MS); clipboardStatus.slotBytes = KVMFR_CLIPBOARD_DATA_BYTES; - if (m_hostToClientStream && m_clientToHostStream) - { - clipboardStatus.transports = KVMFR_CLIPBOARD_TRANSPORT_STREAM; - clipboardStatus.streamVersion = KVMFR_CLIPBOARD_STREAM_VERSION; - clipboardStatus.streamSlotCount = KVMFR_CLIPBOARD_STREAM_SLOT_COUNT; - clipboardStatus.hostToClient = m_hostToClientDescriptor; - clipboardStatus.clientToHost = m_clientToHostDescriptor; - } + clipboardStatus.streamVersion = KVMFR_CLIPBOARD_STREAM_VERSION; + clipboardStatus.streamSlotCount = KVMFR_CLIPBOARD_STREAM_SLOT_COUNT; + clipboardStatus.hostToClient = m_hostToClientDescriptor; + clipboardStatus.clientToHost = m_clientToHostDescriptor; if (m_available) { clipboardStatus.flags |= KVMFR_CLIPBOARD_STATUS_AVAILABLE; @@ -965,7 +935,6 @@ bool CLGMPClipboardTransport::PublishStatus() clipboardStatus.flags |= KVMFR_CLIPBOARD_STATUS_HAS_OWNER; clipboardStatus.ownerClientID = m_ownerClientID; clipboardStatus.ownerGeneration = m_ownerGeneration; - clipboardStatus.ownerTransport = m_ownerTransport; } memcpy(lgmpHostMemPtr(memory), &clipboardStatus, sizeof(clipboardStatus)); @@ -990,48 +959,6 @@ bool CLGMPClipboardTransport::PublishStatus() 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() { if (!m_replayPending || !m_ownerClientID || m_ownerReleasing) @@ -1066,15 +993,11 @@ bool CLGMPClipboardTransport::ReplayClipboard() bool CLGMPClipboardTransport::ValidateClaim( const KVMFRClipboardMessage& message) const { - KVMFRClipboardTransportFlags transports = 0; - if (m_hostToClientStream && m_clientToHostStream) - transports = KVMFR_CLIPBOARD_TRANSPORT_STREAM; return message.version == KVMFR_CLIPBOARD_VERSION && message.type == KVMFR_CLIPBOARD_MESSAGE_CLAIM && message.generation && message.token == m_endpointGeneration && - kvmfrClipboardTransportValid(message.flags) && - (message.flags & transports) && - EmptyControl(message, true, true); + m_hostToClientStream && m_clientToHostStream && + EmptyControl(message, true); } bool CLGMPClipboardTransport::ValidateOwnedControl( @@ -1103,17 +1026,6 @@ bool CLGMPClipboardTransport::ValidateOwnedControl( case KVMFR_CLIPBOARD_MESSAGE_FILE_CANCEL: 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: return false; } @@ -1266,12 +1178,16 @@ void CLGMPClipboardTransport::ApplyInbound( case KVMFR_CLIPBOARD_MESSAGE_OFFER: m_clientClipboardGeneration = message.clipboardGeneration; m_clientFormats = message.token; + if (m_clientToHelper.Active()) + m_discardClientToHelper = m_clientToHelper.transfer; m_clientToHelper.Clear(); break; case KVMFR_CLIPBOARD_MESSAGE_CLEAR: m_clientClipboardGeneration = 0; m_clientFormats = 0; + if (m_clientToHelper.Active()) + m_discardClientToHelper = m_clientToHelper.transfer; m_clientToHelper.Clear(); break; @@ -1352,7 +1268,8 @@ void CLGMPClipboardTransport::ApplyOutbound( } 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 || (message.length && !data) || @@ -1360,8 +1277,7 @@ bool CLGMPClipboardTransport::BeginTarget( return false; m_pendingTarget.valid = true; - m_pendingTarget.acknowledge = grant >= -1; - m_pendingTarget.grant = grant; + m_pendingTarget.acknowledge = acknowledge; m_pendingTarget.record = message; if (message.length) memcpy(m_pendingTarget.data, data, message.length); @@ -1371,15 +1287,12 @@ bool CLGMPClipboardTransport::BeginTarget( void CLGMPClipboardTransport::FinishTarget(bool accepted) { const KVMFRClipboardMessage message = m_pendingTarget.record; - const int grant = m_pendingTarget.grant; if (accepted) { ApplyInbound(message); if (m_ownerClientID && !m_ownerReleasing) RenewLease(); } - if (grant >= 0 && static_cast(grant) < MEMORY_COUNT) - m_grants[grant] = {}; m_pendingTarget.Clear(); if (!accepted) @@ -1426,9 +1339,6 @@ void CLGMPClipboardTransport::DropPendingTarget() return; const bool acknowledge = m_pendingTarget.acknowledge; - const int grant = m_pendingTarget.grant; - if (grant >= 0 && static_cast(grant) < MEMORY_COUNT) - m_grants[grant] = {}; m_pendingTarget.Clear(); if (acknowledge && m_queue) { @@ -1453,8 +1363,7 @@ bool CLGMPClipboardTransport::ProcessMessage( return true; } - if (message.flags == KVMFR_CLIPBOARD_TRANSPORT_STREAM && - !BindStreams(clientID)) + if (!BindStreams(clientID)) { DeInitStreams(); m_failed = true; @@ -1465,7 +1374,6 @@ bool CLGMPClipboardTransport::ProcessMessage( m_ownerClientID = clientID; m_ownerGeneration = message.generation; - m_ownerTransport = message.flags; RenewLease(); m_statusDirty = true; m_replayPending = m_cachedValid; @@ -1511,66 +1419,6 @@ bool CLGMPClipboardTransport::ProcessMessage( 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( - 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(grantIndex)); - return false; - } - if (m_files.IsStaleTerminal(message, CLGMPClipboardFiles::Direction::CLIENT_TO_HELPER)) { @@ -1601,7 +1449,7 @@ bool CLGMPClipboardTransport::ProcessMessage( KVMFRClipboardMessage forwarded = message; forwarded.generation = m_endpointGeneration; RenewLease(); - BeginTarget(forwarded, nullptr, -1); + BeginTarget(forwarded, nullptr, true); return false; } @@ -1692,95 +1540,61 @@ ClipboardChannelResult CLGMPClipboardTransport::SendData( KVMFRClipboardMessage message = record; 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 = {}; - 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) - { - 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(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); + ReleaseOwner("clipboard stream binding lost", true); + return ClipboardChannelResult::ACCEPTED; + } + if (status != LGMP_OK) + { + Fail("lgmpHostStreamWriteAcquire", status); return ClipboardChannelResult::FAILED; } - if (m_ownerTransport != KVMFR_CLIPBOARD_TRANSPORT_LEGACY) - return ClipboardChannelResult::FAILED; - - PLGMPMemory memory = FindAvailable(m_dataMemory); - if (!memory) - return ClipboardChannelResult::BUSY; - - uint8_t * slot = static_cast(lgmpHostMemPtr(memory)); - memcpy(slot, &message, sizeof(message)); - if (message.length) - memcpy(slot + sizeof(KVMFRClipboardSlotHeader), data, message.length); - - const uint32_t serial = Seq::Next(m_dataSerial); - const PostResult result = PostForOwner( - KVMFR_CLIPBOARD_QUEUE_UDATA( - KVMFR_CLIPBOARD_QUEUE_DATA, serial), memory); - if (result == PostResult::POSTED) + 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(buffer.data) + sizeof(message), data, + message.length); + status = lgmpHostStreamWriteCommit( + m_hostToClientStream, &buffer, bytes); + if (status == LGMP_OK) { - m_dataSerial = serial; ApplyOutbound(record); return ClipboardChannelResult::ACCEPTED; } - if (result == PostResult::BUSY) - return ClipboardChannelResult::BUSY; - if (result == PostResult::GONE) + + 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("subscriber disappeared", true, true); + ReleaseOwner("clipboard stream binding lost", true); return ClipboardChannelResult::ACCEPTED; } + Fail("lgmpHostStreamWriteCommit", status); return ClipboardChannelResult::FAILED; } @@ -2002,6 +1816,24 @@ DWORD CALLBACK CLGMPClipboardTransport::ThreadProc(void * context) 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; for (;;) { @@ -2034,7 +1866,7 @@ void CLGMPClipboardTransport::Thread() !DrainStream(streamReceived) || !RetryOwnerRelease() || !PublishStatus() || - !ReplayClipboard() || !PostGrants()) + !ReplayClipboard()) { notifyFailed = true; break; @@ -2048,10 +1880,15 @@ void CLGMPClipboardTransport::Thread() if (m_pendingTarget.valid || m_internalTargetCount || m_streamTargetCount || m_ownerClientID) timeout = ACTIVE_POLL_MS; - if (m_ownerTransport == KVMFR_CLIPBOARD_TRANSPORT_STREAM) - timeout = STREAM_POLL_MS; - if (streamReceived) - timeout = 0; + if (m_ownerClientID) + { + if (streamReceived) + lgmpStreamPollActivity(&streamPoll); + const uint32_t waitUs = lgmpStreamPollIdle(&streamPoll); + timeout = waitUs ? (waitUs + 999U) / 1000U : 0U; + } + else + lgmpStreamPollActivity(&streamPoll); } // ClipboardReceiveReady may synchronously retry SendClipboard, which @@ -2064,6 +1901,11 @@ void CLGMPClipboardTransport::Thread() _countof(handles), handles, FALSE, timeout); if (wait == WAIT_FIRST_OBJECT_VALUE) break; + if (wait == WAIT_FIRST_OBJECT_VALUE + 1) + { + lgmpStreamPollActivity(&streamPoll); + continue; + } if (wait != WAIT_FIRST_OBJECT_VALUE + 1 && wait != WAIT_TIMEOUT) { DEBUG_ERROR_HR(GetLastError(), diff --git a/idd/LGIdd/transport/lgmp/CLGMPClipboardTransport.h b/idd/LGIdd/transport/lgmp/CLGMPClipboardTransport.h index 3ed0a69c..a63429cf 100644 --- a/idd/LGIdd/transport/lgmp/CLGMPClipboardTransport.h +++ b/idd/LGIdd/transport/lgmp/CLGMPClipboardTransport.h @@ -41,14 +41,14 @@ class CLGMPHost; class CLGMPClipboardTransport final : public IClipboardSource { 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 = CLGMPClipboardFiles::MAX_ACQUISITIONS + CLGMPClipboardFiles::MAX_REQUESTS + 3; static constexpr ULONGLONG OWNER_LEASE_MS = 1000; static constexpr ULONGLONG STREAM_DRAIN_TIMEOUT_MS = 1000; static constexpr DWORD ACTIVE_POLL_MS = 5; - static constexpr DWORD STREAM_POLL_MS = 1; static constexpr DWORD IDLE_POLL_MS = 50; static constexpr unsigned STREAM_TARGET_COUNT = KVMFR_CLIPBOARD_STREAM_SLOT_COUNT; @@ -78,18 +78,10 @@ private: bool Active() const { return transfer != 0; } }; - struct Grant - { - uint32_t generation = 0; - bool offered = false; - bool committed = false; - }; - struct PendingTarget { bool valid = false; bool acknowledge = false; - int grant = -1; KVMFRClipboardMessage record = {}; uint8_t data[KVMFR_CLIPBOARD_DATA_BYTES] = {}; @@ -97,7 +89,6 @@ private: { valid = false; acknowledge = false; - grant = -1; record = {}; } }; @@ -117,8 +108,6 @@ private: PLGMPHostStream m_clientToHostStream = nullptr; PLGMPMemory m_statusMemory [MEMORY_COUNT] = {}; PLGMPMemory m_messageMemory[MEMORY_COUNT] = {}; - PLGMPMemory m_grantMemory [MEMORY_COUNT] = {}; - PLGMPMemory m_dataMemory [MEMORY_COUNT] = {}; CSRWLock m_lifecycleLock; CSRWLock m_lock; @@ -138,10 +127,8 @@ private: uint32_t m_endpointGeneration = 0; uint32_t m_ownerClientID = 0; uint32_t m_ownerGeneration = 0; - KVMFRClipboardTransportFlags m_ownerTransport = 0; uint32_t m_statusSerial = 0; uint32_t m_messageSerial = 0; - uint32_t m_dataSerial = 0; uint32_t m_helperFormats = 0; uint32_t m_clientFormats = 0; uint32_t m_blockedOwnerClientID = 0; @@ -158,7 +145,6 @@ private: KVMFRStreamDescriptor m_clientToHostDescriptor = {}; Transfer m_clientToHelper; Transfer m_helperToClient; - Grant m_grants[MEMORY_COUNT]; PendingTarget m_pendingTarget; StreamTarget m_streamTarget[STREAM_TARGET_COUNT]; unsigned m_streamTargetHead = 0; @@ -171,13 +157,13 @@ private: void DeInit(); bool InitializeStreams(); void DeInitStreams(); - bool BindStreams(uint32_t clientID); + bool BindStreams(uint32_t clientID) const; bool TryUnbindStreams(); - void ForceUnbindStreams(); + void ForceUnbindStreams() const; static DWORD CALLBACK ThreadProc(void * context); void Thread(); - void Wake(); + void Wake() const; bool IsOwner(uint32_t clientID, uint32_t generation) const; bool OwnerSubscribed() const; @@ -210,7 +196,6 @@ private: PLGMPMemory FindAvailable(PLGMPMemory (&memory)[MEMORY_COUNT]) const; PostResult PostForOwner(uint64_t udata, PLGMPMemory memory); bool PublishStatus(); - bool PostGrants(); bool ReplayClipboard(); bool DrainMessage(); bool ProcessMessage(uint32_t clientID, @@ -231,7 +216,7 @@ private: void ApplyInbound(const KVMFRClipboardMessage& message); void ApplyOutbound(const KVMFRClipboardMessage& message); bool BeginTarget(const KVMFRClipboardMessage& message, - const uint8_t * data, int grant); + const uint8_t * data, bool acknowledge); ClipboardChannelResult SendControl( const KVMFRClipboardMessage& record);