[client] lgmp: transport clipboard files

This commit is contained in:
Geoffrey McRae
2026-08-14 16:08:22 +10:00
parent a91343be96
commit a841be3515
3 changed files with 2031 additions and 123 deletions

View File

@@ -123,8 +123,14 @@ set(LGMP_CLIPBOARD_CASES
blocked
cancel
restart
process-restart
file-stream
file-limits
file-owner-loss
file-malformed
disconnect
malformed
file-format
)
foreach(name IN LISTS LGMP_CLIPBOARD_CASES)
add_test(NAME lgmp-clipboard-${name}

View File

@@ -65,9 +65,15 @@ typedef struct EventTrace
atomic_uint dataEnd;
atomic_uint dataCancel;
atomic_uint dataReady;
atomic_uint fileOffer;
atomic_uint fileAcquire;
atomic_uint fileAcquired;
atomic_uint fileRequest;
atomic_uint fileCancel;
atomic_bool acceptRequest;
atomic_bool blockChunkOnce;
atomic_bool attached;
atomic_bool fileOfferBeforeNotice;
LG_ClipboardData noticeTypes[LG_CLIPBOARD_DATA_NONE];
size_t noticeCount;
LG_ClipboardRequest requestID;
@@ -81,6 +87,10 @@ typedef struct EventTrace
uint64_t finalSize;
LG_ClipboardRequest cancelID;
LG_ClipboardCancelReason cancelReason;
uint64_t fileDataset;
uint64_t fileAcquisition;
LG_ClipboardFileError fileError;
LG_ClipboardFileRequest fileRequestValue;
}
EventTrace;
@@ -94,6 +104,7 @@ typedef struct TestState
const LG_ClipboardOps * ops;
uint32_t clientID;
uint32_t hostSerial;
uint64_t localClipboardGeneration;
PLGMPMemory memories[TEST_MEMORY_MAX];
unsigned memoryCount;
PLGMPMemory grantMemory[KVMFR_CLIPBOARD_SLOT_COUNT];
@@ -119,6 +130,10 @@ static void notice(void * opaque, const LG_ClipboardData types[],
size_t count)
{
EventTrace * trace = opaque;
if (count == 1 && types[0] == LG_CLIPBOARD_DATA_FILES)
atomic_store_explicit(&trace->fileOfferBeforeNotice,
atomic_load_explicit(&trace->fileOffer, memory_order_acquire) != 0,
memory_order_release);
trace->noticeCount = count;
if (count)
memcpy(trace->noticeTypes, types, count * sizeof(*types));
@@ -205,6 +220,50 @@ static void requestCancelEvent(void * opaque, LG_ClipboardRequest id,
&trace->requestCancel, 1, memory_order_release);
}
static void fileOfferEvent(void * opaque, uint64_t dataset)
{
EventTrace * trace = opaque;
trace->fileDataset = dataset;
atomic_fetch_add_explicit(&trace->fileOffer, 1, memory_order_release);
}
static void fileAcquireEvent(void * opaque, uint64_t dataset,
uint64_t acquisition)
{
EventTrace * trace = opaque;
trace->fileDataset = dataset;
trace->fileAcquisition = acquisition;
atomic_fetch_add_explicit(&trace->fileAcquire, 1, memory_order_release);
}
static void fileAcquiredEvent(void * opaque, uint64_t dataset,
uint64_t acquisition, LG_ClipboardFileError error)
{
EventTrace * trace = opaque;
trace->fileDataset = dataset;
trace->fileAcquisition = acquisition;
trace->fileError = error;
atomic_fetch_add_explicit(&trace->fileAcquired, 1, memory_order_release);
}
static void fileRequestEvent(void * opaque,
const LG_ClipboardFileRequest * request)
{
EventTrace * trace = opaque;
trace->fileRequestValue = *request;
atomic_fetch_add_explicit(&trace->fileRequest, 1, memory_order_release);
}
static void fileCancelEvent(void * opaque, uint64_t dataset,
uint64_t request, LG_ClipboardFileError error)
{
EventTrace * trace = opaque;
trace->fileDataset = dataset;
trace->fileAcquisition = request;
trace->fileError = error;
atomic_fetch_add_explicit(&trace->fileCancel, 1, memory_order_release);
}
static const LG_ClipboardEventOps EVENT_OPS =
{
.notice = notice,
@@ -216,6 +275,11 @@ static const LG_ClipboardEventOps EVENT_OPS =
.requestCancel = requestCancelEvent,
.release = releaseNotice,
.request = request,
.fileOffer = fileOfferEvent,
.fileAcquire = fileAcquireEvent,
.fileAcquired = fileAcquiredEvent,
.fileRequest = fileRequestEvent,
.fileCancel = fileCancelEvent,
};
static void statusChanged(void * opaque, const LG_ClipboardStatus * status)
@@ -402,6 +466,18 @@ static bool readType(TestState * state, KVMFRClipboardMessageType type,
return false;
}
static bool readNonKeepalive(TestState * state,
KVMFRClipboardMessage * record)
{
for (unsigned i = 0; i < 32; ++i)
{
CHECK(readClient(state, record));
if (record->type != KVMFR_CLIPBOARD_MESSAGE_KEEPALIVE)
return true;
}
return false;
}
static bool noClientData(TestState * state)
{
for (unsigned i = 0; i < TEST_QUIET_MS; ++i)
@@ -508,6 +584,7 @@ static bool testClaim(TestState * state)
CHECK(state->ops->notifyTypes(state->clipboard, formats, 1));
KVMFRClipboardMessage offer;
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_OFFER, &offer));
state->localClipboardGeneration = offer.clipboardGeneration;
CHECK(offer.generation == claimRecord.generation);
CHECK(offer.clipboardGeneration != 0);
CHECK(offer.token == KVMFR_CLIPBOARD_FORMAT_MASK_TEXT);
@@ -595,6 +672,7 @@ static bool prepareOutbound(TestState * state, uint32_t endpoint,
CHECK(state->ops->notifyTypes(state->clipboard, formats, 1));
KVMFRClipboardMessage offer;
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_OFFER, &offer));
state->localClipboardGeneration = offer.clipboardGeneration;
KVMFRClipboardMessage requestRecord =
hostRecord(KVMFR_CLIPBOARD_MESSAGE_REQUEST,
@@ -634,6 +712,30 @@ static bool checkCommit(TestState * state, uint32_t token,
return true;
}
static bool checkFileCommit(TestState * state, uint32_t grantToken,
KVMFRClipboardMessage * commit, KVMFRClipboardMessage * slot,
const void ** data)
{
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_COMMIT, commit));
CHECK(commit->token == grantToken);
*slot = *(KVMFRClipboardMessage *)
lgmpHostMemPtr(state->grantMemory[grantToken - 1]);
CHECK(slot->type == KVMFR_CLIPBOARD_MESSAGE_FILE_DATA);
CHECK(slot->generation == commit->generation);
CHECK(slot->clipboardGeneration == commit->clipboardGeneration);
CHECK(slot->transfer == commit->transfer);
CHECK(slot->offset == commit->offset);
CHECK(slot->size == commit->size);
CHECK(slot->format == commit->format);
CHECK(slot->flags == commit->flags);
CHECK(slot->length == commit->length);
CHECK(slot->sequence == commit->sequence);
CHECK(slot->token == KVMFR_CLIPBOARD_FILE_OP_READ);
*data = (const uint8_t *)lgmpHostMemPtr(
state->grantMemory[grantToken - 1]) + sizeof(*slot);
return true;
}
static bool testOutboundGrant(TestState * state)
{
KVMFRClipboardMessage claimRecord;
@@ -746,7 +848,7 @@ static bool testCancel(TestState * state)
const LG_ClipboardRequest outbound = state->events.requestID;
KVMFRClipboardMessage cancel =
hostRecord(KVMFR_CLIPBOARD_MESSAGE_CANCEL, claimRecord.generation);
cancel.clipboardGeneration = 1;
cancel.clipboardGeneration = state->localClipboardGeneration;
cancel.transfer = outbound;
cancel.format = KVMFR_CLIPBOARD_FORMAT_TEXT;
cancel.token = LG_CLIPBOARD_CANCEL_ABORTED;
@@ -768,7 +870,7 @@ static bool testCancel(TestState * state)
KVMFRClipboardMessage outboundRequest =
hostRecord(KVMFR_CLIPBOARD_MESSAGE_REQUEST,
claimRecord.generation);
outboundRequest.clipboardGeneration = 1;
outboundRequest.clipboardGeneration = state->localClipboardGeneration;
outboundRequest.transfer = KVMFR_CLIPBOARD_TRANSFER_HELPER | 5;
outboundRequest.format = KVMFR_CLIPBOARD_FORMAT_TEXT;
CHECK(postRecord(state, &outboundRequest,
@@ -779,7 +881,8 @@ static bool testCancel(TestState * state)
KVMFRClipboardMessage wireCancel;
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_CANCEL, &wireCancel));
CHECK(wireCancel.transfer == outboundRequest.transfer);
CHECK(wireCancel.clipboardGeneration == 1);
CHECK(wireCancel.clipboardGeneration ==
state->localClipboardGeneration);
CHECK(wireCancel.format == KVMFR_CLIPBOARD_FORMAT_TEXT);
CHECK(wireCancel.token == LG_CLIPBOARD_CANCEL_ABORTED);
return true;
@@ -814,6 +917,381 @@ static bool testRestart(TestState * state)
return true;
}
static bool testFileLimits(TestState * state)
{
KVMFRClipboardMessage claimRecord;
CHECK(claim(state, 75, false, &claimRecord));
CHECK(own(state, 75, claimRecord.generation));
const uint64_t localDataset = UINT64_C(0x1234000012340001);
const uint64_t remoteDataset = KVMFR_CLIPBOARD_TRANSFER_HELPER |
UINT64_C(0x5678000056780001);
CHECK(state->ops->offerFiles(state->clipboard, localDataset));
KVMFRClipboardMessage wire;
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_OFFER, &wire));
CHECK(wire.clipboardGeneration == localDataset);
KVMFRClipboardMessage offer =
hostRecord(KVMFR_CLIPBOARD_MESSAGE_OFFER, claimRecord.generation);
offer.clipboardGeneration = remoteDataset;
offer.token = KVMFR_CLIPBOARD_FORMAT_MASK_FILES;
CHECK(postRecord(state, &offer, KVMFR_CLIPBOARD_QUEUE_MESSAGE, NULL));
CHECK(waitAtomic(state, &state->events.notice, 1));
CHECK(atomic_load(&state->events.fileOffer) == 1);
CHECK(atomic_load(&state->events.fileOfferBeforeNotice));
CHECK(state->events.fileDataset == remoteDataset);
KVMFRClipboardMessage staleAcquire =
hostRecord(KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRE,
claimRecord.generation);
staleAcquire.clipboardGeneration = localDataset + 1U;
staleAcquire.transfer = KVMFR_CLIPBOARD_TRANSFER_HELPER |
UINT64_C(0x98);
staleAcquire.format = KVMFR_CLIPBOARD_FORMAT_FILES;
CHECK(postRecord(state, &staleAcquire,
KVMFR_CLIPBOARD_QUEUE_MESSAGE, NULL));
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRED, &wire));
CHECK(wire.clipboardGeneration == staleAcquire.clipboardGeneration);
CHECK(wire.transfer == staleAcquire.transfer);
CHECK(wire.token == KVMFR_CLIPBOARD_FILE_ERROR_STALE);
CHECK(atomic_load(&state->events.fileAcquire) == 0);
KVMFRClipboardMessage staleRequest =
hostRecord(KVMFR_CLIPBOARD_MESSAGE_FILE_REQUEST,
claimRecord.generation);
staleRequest.clipboardGeneration = localDataset;
staleRequest.transfer = KVMFR_CLIPBOARD_TRANSFER_HELPER |
UINT64_C(0x99);
staleRequest.format = KVMFR_CLIPBOARD_FORMAT_FILES;
staleRequest.token = KVMFR_CLIPBOARD_FILE_OP_LIST;
CHECK(postRecord(state, &staleRequest,
KVMFR_CLIPBOARD_QUEUE_MESSAGE, NULL));
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_FILE_CANCEL, &wire));
CHECK(wire.clipboardGeneration == staleRequest.clipboardGeneration);
CHECK(wire.transfer == staleRequest.transfer);
CHECK(wire.token == KVMFR_CLIPBOARD_FILE_ERROR_STALE);
CHECK(atomic_load(&state->events.fileRequest) == 0);
for (uint64_t i = 0; i < 4; ++i)
{
const uint64_t acquisition = UINT64_C(0x100) + i;
CHECK(state->ops->fileAcquire(
state->clipboard, remoteDataset, acquisition));
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRE, &wire));
CHECK(wire.clipboardGeneration == remoteDataset);
CHECK(wire.transfer == acquisition);
KVMFRClipboardMessage acquired =
hostRecord(KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRED,
claimRecord.generation);
acquired.clipboardGeneration = remoteDataset;
acquired.transfer = acquisition;
acquired.format = KVMFR_CLIPBOARD_FORMAT_FILES;
CHECK(postRecord(state, &acquired,
KVMFR_CLIPBOARD_QUEUE_MESSAGE, NULL));
CHECK(waitAtomic(state, &state->events.fileAcquired, (unsigned)i + 1));
CHECK(state->events.fileError == LG_CLIPBOARD_FILE_ERROR_NONE);
}
for (uint64_t i = 0; i < 4; ++i)
{
const uint64_t acquisition = KVMFR_CLIPBOARD_TRANSFER_HELPER |
(UINT64_C(0x200) + i);
KVMFRClipboardMessage acquire =
hostRecord(KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRE,
claimRecord.generation);
acquire.clipboardGeneration = localDataset;
acquire.transfer = acquisition;
acquire.format = KVMFR_CLIPBOARD_FORMAT_FILES;
CHECK(postRecord(state, &acquire,
KVMFR_CLIPBOARD_QUEUE_MESSAGE, NULL));
CHECK(waitAtomic(state, &state->events.fileAcquire, (unsigned)i + 1));
CHECK(state->events.fileDataset == localDataset);
CHECK(state->events.fileAcquisition == acquisition);
CHECK(state->ops->fileAcquired(state->clipboard,
localDataset, acquisition, LG_CLIPBOARD_FILE_ERROR_NONE));
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRED, &wire));
CHECK(wire.transfer == acquisition);
CHECK(wire.token == KVMFR_CLIPBOARD_FILE_ERROR_NONE);
}
CHECK(!state->ops->fileAcquire(
state->clipboard, remoteDataset, UINT64_C(0x999)));
KVMFRClipboardMessage overflowAcquire =
hostRecord(KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRE,
claimRecord.generation);
overflowAcquire.clipboardGeneration = localDataset;
overflowAcquire.transfer = KVMFR_CLIPBOARD_TRANSFER_HELPER |
UINT64_C(0x999);
overflowAcquire.format = KVMFR_CLIPBOARD_FORMAT_FILES;
CHECK(postRecord(state, &overflowAcquire,
KVMFR_CLIPBOARD_QUEUE_MESSAGE, NULL));
CHECK(readNonKeepalive(state, &wire));
CHECK(wire.type == KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRED);
CHECK(wire.transfer == overflowAcquire.transfer);
CHECK(wire.token == KVMFR_CLIPBOARD_FILE_ERROR_NO_SPACE);
CHECK(atomic_load(&state->events.fileAcquire) == 4);
for (uint64_t i = 0; i < 16; ++i)
{
const LG_ClipboardFileRequest request =
{
.dataset = remoteDataset,
.request = UINT64_C(0x1000) + i,
.operation = LG_CLIPBOARD_FILE_LIST,
};
CHECK(state->ops->fileRequest(state->clipboard, &request));
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_FILE_REQUEST, &wire));
CHECK(wire.clipboardGeneration == remoteDataset);
CHECK(wire.transfer == request.request);
CHECK(wire.token == KVMFR_CLIPBOARD_FILE_OP_LIST);
}
for (uint64_t i = 0; i < 16; ++i)
{
KVMFRClipboardMessage request =
hostRecord(KVMFR_CLIPBOARD_MESSAGE_FILE_REQUEST,
claimRecord.generation);
request.clipboardGeneration = localDataset;
request.transfer = KVMFR_CLIPBOARD_TRANSFER_HELPER |
(UINT64_C(0x2000) + i);
request.format = KVMFR_CLIPBOARD_FORMAT_FILES;
request.token = KVMFR_CLIPBOARD_FILE_OP_LIST;
CHECK(postRecord(state, &request,
KVMFR_CLIPBOARD_QUEUE_MESSAGE, NULL));
CHECK(waitAtomic(state, &state->events.fileRequest,
(unsigned)i + 1));
CHECK(state->events.fileRequestValue.dataset == localDataset);
CHECK(state->events.fileRequestValue.request == request.transfer);
}
const LG_ClipboardFileRequest overflowRequest =
{
.dataset = remoteDataset,
.request = UINT64_C(0x9999),
.operation = LG_CLIPBOARD_FILE_LIST,
};
CHECK(!state->ops->fileRequest(state->clipboard, &overflowRequest));
KVMFRClipboardMessage inboundOverflow =
hostRecord(KVMFR_CLIPBOARD_MESSAGE_FILE_REQUEST,
claimRecord.generation);
inboundOverflow.clipboardGeneration = localDataset;
inboundOverflow.transfer = KVMFR_CLIPBOARD_TRANSFER_HELPER |
UINT64_C(0x9999);
inboundOverflow.format = KVMFR_CLIPBOARD_FORMAT_FILES;
inboundOverflow.token = KVMFR_CLIPBOARD_FILE_OP_LIST;
CHECK(postRecord(state, &inboundOverflow,
KVMFR_CLIPBOARD_QUEUE_MESSAGE, NULL));
CHECK(readNonKeepalive(state, &wire));
CHECK(wire.type == KVMFR_CLIPBOARD_MESSAGE_FILE_CANCEL);
CHECK(wire.transfer == inboundOverflow.transfer);
CHECK(wire.token == KVMFR_CLIPBOARD_FILE_ERROR_NO_SPACE);
CHECK(atomic_load(&state->events.fileRequest) == 16);
return true;
}
static bool testFileOwnerLoss(TestState * state)
{
KVMFRClipboardMessage claimRecord;
CHECK(claim(state, 79, false, &claimRecord));
CHECK(own(state, 79, claimRecord.generation));
const uint64_t dataset = KVMFR_CLIPBOARD_TRANSFER_HELPER |
UINT64_C(0x7900000179000001);
KVMFRClipboardMessage offer =
hostRecord(KVMFR_CLIPBOARD_MESSAGE_OFFER, claimRecord.generation);
offer.clipboardGeneration = dataset;
offer.token = KVMFR_CLIPBOARD_FORMAT_MASK_FILES;
CHECK(postRecord(state, &offer, KVMFR_CLIPBOARD_QUEUE_MESSAGE, NULL));
CHECK(waitAtomic(state, &state->events.fileOffer, 1));
const uint64_t acquisition = UINT64_C(0x7901);
CHECK(state->ops->fileAcquire(
state->clipboard, dataset, acquisition));
KVMFRClipboardMessage wire;
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRE, &wire));
KVMFRClipboardMessage acquired =
hostRecord(KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRED,
claimRecord.generation);
acquired.clipboardGeneration = dataset;
acquired.transfer = acquisition;
acquired.format = KVMFR_CLIPBOARD_FORMAT_FILES;
CHECK(postRecord(state, &acquired,
KVMFR_CLIPBOARD_QUEUE_MESSAGE, NULL));
CHECK(waitAtomic(state, &state->events.fileAcquired, 1));
const LG_ClipboardFileRequest request =
{
.dataset = dataset,
.request = UINT64_C(0x7902),
.operation = LG_CLIPBOARD_FILE_LIST,
};
CHECK(state->ops->fileRequest(state->clipboard, &request));
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_FILE_REQUEST, &wire));
const unsigned cancellations =
atomic_load(&state->events.fileCancel);
CHECK(postStatus(state, 80, 0, 0, true, 0));
CHECK(waitAtomic(state, &state->events.fileCancel,
cancellations + 2));
CHECK(state->events.fileDataset == dataset);
CHECK(state->events.fileAcquisition == acquisition);
CHECK(state->events.fileError == LG_CLIPBOARD_FILE_ERROR_DISCONNECTED);
return true;
}
static bool testMalformedFileData(TestState * state)
{
KVMFRClipboardMessage claimRecord;
CHECK(claim(state, 81, false, &claimRecord));
CHECK(own(state, 81, claimRecord.generation));
const uint64_t dataset = KVMFR_CLIPBOARD_TRANSFER_HELPER |
UINT64_C(0x8100000181000001);
KVMFRClipboardMessage offer =
hostRecord(KVMFR_CLIPBOARD_MESSAGE_OFFER, claimRecord.generation);
offer.clipboardGeneration = dataset;
offer.token = KVMFR_CLIPBOARD_FORMAT_MASK_FILES;
CHECK(postRecord(state, &offer, KVMFR_CLIPBOARD_QUEUE_MESSAGE, NULL));
CHECK(waitAtomic(state, &state->events.fileOffer, 1));
const uint64_t acquisition = UINT64_C(0x8101);
CHECK(state->ops->fileAcquire(state->clipboard, dataset, acquisition));
KVMFRClipboardMessage wire;
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRE, &wire));
KVMFRClipboardMessage acquired =
hostRecord(KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRED,
claimRecord.generation);
acquired.clipboardGeneration = dataset;
acquired.transfer = acquisition;
acquired.format = KVMFR_CLIPBOARD_FORMAT_FILES;
CHECK(postRecord(state, &acquired,
KVMFR_CLIPBOARD_QUEUE_MESSAGE, NULL));
CHECK(waitAtomic(state, &state->events.fileAcquired, 1));
const LG_ClipboardFileRequest request =
{
.dataset = dataset,
.request = UINT64_C(0x8102),
.node = 2,
.length = 4,
.operation = LG_CLIPBOARD_FILE_READ,
};
CHECK(state->ops->fileRequest(state->clipboard, &request));
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_FILE_REQUEST, &wire));
KVMFRClipboardMessage malformed =
hostRecord(KVMFR_CLIPBOARD_MESSAGE_FILE_DATA,
claimRecord.generation);
malformed.clipboardGeneration = dataset;
malformed.transfer = request.request;
malformed.offset = 1;
malformed.size = request.length;
malformed.format = KVMFR_CLIPBOARD_FORMAT_FILES;
malformed.flags = KVMFR_CLIPBOARD_FLAG_BEGIN;
malformed.length = 1;
malformed.token = KVMFR_CLIPBOARD_FILE_OP_READ;
const uint8_t byte = 0;
const unsigned cancellations =
atomic_load(&state->events.fileCancel);
CHECK(postRecord(state, &malformed,
KVMFR_CLIPBOARD_QUEUE_DATA, &byte));
CHECK(waitAtomic(state, &state->events.fileCancel, cancellations + 1));
CHECK(state->events.fileDataset == dataset);
CHECK(state->events.fileAcquisition == request.request);
CHECK(state->events.fileError == LG_CLIPBOARD_FILE_ERROR_INVALID);
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_FILE_CANCEL, &wire));
CHECK(wire.clipboardGeneration == dataset);
CHECK(wire.transfer == request.request);
CHECK(wire.token == KVMFR_CLIPBOARD_FILE_ERROR_INVALID);
CHECK(!state->ops->fileCancel(state->clipboard,
dataset, request.request, LG_CLIPBOARD_FILE_ERROR_CANCELLED));
malformed.transfer = request.request + 1U;
CHECK(postRecord(state, &malformed,
KVMFR_CLIPBOARD_QUEUE_DATA, &byte));
CHECK(waitMemory(state, state->memories[state->memoryCount - 1]));
CHECK(atomic_load(&state->events.fileCancel) == cancellations + 1);
return true;
}
static bool testFileStream(TestState * state)
{
KVMFRClipboardMessage claimRecord;
CHECK(claim(state, 78, false, &claimRecord));
CHECK(own(state, 78, claimRecord.generation));
const uint64_t dataset = UINT64_C(0x7800000178000001);
CHECK(state->ops->offerFiles(state->clipboard, dataset));
KVMFRClipboardMessage wire;
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_OFFER, &wire));
const uint64_t acquisition = KVMFR_CLIPBOARD_TRANSFER_HELPER |
UINT64_C(0x7801);
KVMFRClipboardMessage acquire =
hostRecord(KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRE,
claimRecord.generation);
acquire.clipboardGeneration = dataset;
acquire.transfer = acquisition;
acquire.format = KVMFR_CLIPBOARD_FORMAT_FILES;
CHECK(postRecord(state, &acquire,
KVMFR_CLIPBOARD_QUEUE_MESSAGE, NULL));
CHECK(waitAtomic(state, &state->events.fileAcquire, 1));
CHECK(state->ops->fileAcquired(state->clipboard,
dataset, acquisition, LG_CLIPBOARD_FILE_ERROR_NONE));
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRED, &wire));
KVMFRClipboardMessage request =
hostRecord(KVMFR_CLIPBOARD_MESSAGE_FILE_REQUEST,
claimRecord.generation);
request.clipboardGeneration = dataset;
request.transfer = KVMFR_CLIPBOARD_TRANSFER_HELPER |
UINT64_C(0x7802);
request.offset = 123;
request.size = 42;
request.format = KVMFR_CLIPBOARD_FORMAT_FILES;
request.flags = 3;
request.token = KVMFR_CLIPBOARD_FILE_OP_READ;
CHECK(postRecord(state, &request,
KVMFR_CLIPBOARD_QUEUE_MESSAGE, NULL));
CHECK(waitAtomic(state, &state->events.fileRequest, 1));
const LG_ClipboardFileRequest descriptor =
state->events.fileRequestValue;
CHECK(descriptor.dataset == dataset);
CHECK(descriptor.request == request.transfer);
CHECK(descriptor.node == 42);
CHECK(descriptor.offset == 123);
CHECK(descriptor.length == 3);
CHECK(descriptor.operation == LG_CLIPBOARD_FILE_READ);
CHECK(state->ops->fileDataBegin(state->clipboard,
&descriptor, 3) == LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(postGrant(state, claimRecord.generation, 1));
CHECK(waitMemory(state, state->grantMemory[0]));
static const uint8_t bytes[] = { 9, 8, 7 };
CHECK(state->ops->fileDataChunk(state->clipboard,
&descriptor, 0, bytes, sizeof(bytes)) ==
LG_CLIPBOARD_RESULT_ACCEPTED);
KVMFRClipboardMessage commit;
KVMFRClipboardMessage slot;
const void * data;
CHECK(checkFileCommit(state, 1, &commit, &slot, &data));
CHECK(slot.flags == KVMFR_CLIPBOARD_FLAG_BEGIN);
CHECK(slot.offset == 0 && slot.sequence == 0 && slot.size == 3);
CHECK(memcmp(data, bytes, sizeof(bytes)) == 0);
CHECK(postGrant(state, claimRecord.generation, 1));
CHECK(waitMemory(state, state->grantMemory[0]));
CHECK(state->ops->fileDataEnd(state->clipboard,
&descriptor, 3) == LG_CLIPBOARD_RESULT_ACCEPTED);
CHECK(checkFileCommit(state, 1, &commit, &slot, &data));
CHECK(slot.flags == KVMFR_CLIPBOARD_FLAG_END);
CHECK(slot.offset == 3 && slot.sequence == 1 && slot.size == 3);
CHECK(slot.length == 0);
return true;
}
static void * disconnectThread(void * opaque)
{
DisconnectTask * task = opaque;
@@ -863,6 +1341,165 @@ static bool disconnectAndDrain(TestState * state,
return valid;
}
static bool recreateClipboard(TestState * state)
{
state->ops->setStatusListener(state->clipboard, NULL, NULL);
if (atomic_load(&state->events.attached))
{
state->ops->detach(state->clipboard);
atomic_store(&state->events.attached, false);
}
CHECK(disconnectAndDrain(state, NULL));
lgmpClipboard_destroy(&state->clipboard);
CHECK(lgmpClipboard_create(state->client, &state->clipboard));
CHECK(lgmpClipboard_connect(state->clipboard, state->clientID));
state->ops = lgmpClipboard_getOps();
CHECK(state->ops);
state->ops->setStatusListener(state->clipboard, statusChanged, state);
for (unsigned i = 0; i < TEST_WAIT_MS; ++i)
{
CHECK(hostProcess(state));
if (lgmpHostQueueHasSubs(state->queue))
return true;
usleep(1000);
}
return false;
}
static bool testProcessRestart(TestState * state)
{
KVMFRClipboardMessage oldClaim;
CHECK(claim(state, 76, false, &oldClaim));
CHECK(own(state, 76, oldClaim.generation));
const LG_ClipboardData formats[] = { LG_CLIPBOARD_DATA_TEXT };
CHECK(state->ops->notifyTypes(state->clipboard, formats, 1));
KVMFRClipboardMessage oldLocalOffer;
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_OFFER, &oldLocalOffer));
KVMFRClipboardMessage oldRequest;
CHECK(offerAndRequest(state, oldClaim.generation,
760, 7600, &oldRequest));
const uint64_t oldFileDataset = KVMFR_CLIPBOARD_TRANSFER_HELPER |
UINT64_C(0x76000001);
const uint64_t oldFileAcquisition = UINT64_C(0x76010001);
KVMFRClipboardMessage fileOffer =
hostRecord(KVMFR_CLIPBOARD_MESSAGE_OFFER, oldClaim.generation);
fileOffer.clipboardGeneration = oldFileDataset;
fileOffer.token = KVMFR_CLIPBOARD_FORMAT_MASK_FILES;
const unsigned oldFileOffers = atomic_load(&state->events.fileOffer);
CHECK(postRecord(state, &fileOffer,
KVMFR_CLIPBOARD_QUEUE_MESSAGE, NULL));
CHECK(waitAtomic(state, &state->events.fileOffer, oldFileOffers + 1));
CHECK(state->ops->fileAcquire(state->clipboard,
oldFileDataset, oldFileAcquisition));
KVMFRClipboardMessage fileWire;
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRE, &fileWire));
CHECK(recreateClipboard(state));
KVMFRClipboardMessage newClaim;
CHECK(claim(state, 77, false, &newClaim));
CHECK(own(state, 77, newClaim.generation));
CHECK(state->ops->notifyTypes(state->clipboard, formats, 1));
KVMFRClipboardMessage newLocalOffer;
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_OFFER, &newLocalOffer));
CHECK(newLocalOffer.clipboardGeneration !=
oldLocalOffer.clipboardGeneration);
KVMFRClipboardMessage newRequest;
CHECK(offerAndRequest(state, newClaim.generation,
770, 7700, &newRequest));
CHECK(newRequest.transfer != oldRequest.transfer);
const unsigned cancelCount = atomic_load(&state->events.dataCancel);
KVMFRClipboardMessage staleCancel =
hostRecord(KVMFR_CLIPBOARD_MESSAGE_CANCEL, newClaim.generation);
staleCancel.clipboardGeneration = 760;
staleCancel.transfer = oldRequest.transfer;
staleCancel.format = KVMFR_CLIPBOARD_FORMAT_TEXT;
staleCancel.token = LG_CLIPBOARD_CANCEL_ABORTED;
CHECK(postRecord(state, &staleCancel,
KVMFR_CLIPBOARD_QUEUE_MESSAGE, NULL));
CHECK(waitMemory(state, state->memories[state->memoryCount - 1]));
CHECK(atomic_load(&state->events.dataCancel) == cancelCount);
const unsigned beginCount = atomic_load(&state->events.dataBegin);
KVMFRClipboardMessage staleData =
hostRecord(KVMFR_CLIPBOARD_MESSAGE_DATA, newClaim.generation);
staleData.clipboardGeneration = 760;
staleData.transfer = oldRequest.transfer;
staleData.format = KVMFR_CLIPBOARD_FORMAT_TEXT;
staleData.flags = KVMFR_CLIPBOARD_FLAG_BEGIN |
KVMFR_CLIPBOARD_FLAG_END;
CHECK(postRecord(state, &staleData, KVMFR_CLIPBOARD_QUEUE_DATA, NULL));
CHECK(waitMemory(state, state->memories[state->memoryCount - 1]));
CHECK(atomic_load(&state->events.dataBegin) == beginCount);
KVMFRClipboardMessage data =
hostRecord(KVMFR_CLIPBOARD_MESSAGE_DATA, newClaim.generation);
data.clipboardGeneration = 770;
data.transfer = newRequest.transfer;
data.format = KVMFR_CLIPBOARD_FORMAT_TEXT;
data.flags = KVMFR_CLIPBOARD_FLAG_BEGIN |
KVMFR_CLIPBOARD_FLAG_END;
const unsigned endCount = atomic_load(&state->events.dataEnd);
CHECK(postRecord(state, &data, KVMFR_CLIPBOARD_QUEUE_DATA, NULL));
CHECK(waitAtomic(state, &state->events.dataEnd, endCount + 1));
CHECK(atomic_load(&state->events.dataBegin) == beginCount + 1);
const uint64_t newFileDataset = KVMFR_CLIPBOARD_TRANSFER_HELPER |
UINT64_C(0x77000001);
const uint64_t newFileAcquisition = UINT64_C(0x77010001);
fileOffer = hostRecord(
KVMFR_CLIPBOARD_MESSAGE_OFFER, newClaim.generation);
fileOffer.clipboardGeneration = newFileDataset;
fileOffer.token = KVMFR_CLIPBOARD_FORMAT_MASK_FILES;
const unsigned newFileOffers = atomic_load(&state->events.fileOffer);
CHECK(postRecord(state, &fileOffer,
KVMFR_CLIPBOARD_QUEUE_MESSAGE, NULL));
CHECK(waitAtomic(state, &state->events.fileOffer, newFileOffers + 1));
CHECK(state->ops->fileAcquire(state->clipboard,
newFileDataset, newFileAcquisition));
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRE, &fileWire));
const unsigned acquiredCount =
atomic_load(&state->events.fileAcquired);
KVMFRClipboardMessage staleAcquired =
hostRecord(KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRED,
newClaim.generation);
staleAcquired.clipboardGeneration = oldFileDataset;
staleAcquired.transfer = oldFileAcquisition;
staleAcquired.format = KVMFR_CLIPBOARD_FORMAT_FILES;
CHECK(postRecord(state, &staleAcquired,
KVMFR_CLIPBOARD_QUEUE_MESSAGE, NULL));
CHECK(waitMemory(state, state->memories[state->memoryCount - 1]));
CHECK(atomic_load(&state->events.fileAcquired) == acquiredCount);
KVMFRClipboardMessage staleFileCancel =
hostRecord(KVMFR_CLIPBOARD_MESSAGE_FILE_CANCEL,
newClaim.generation);
staleFileCancel.clipboardGeneration = oldFileDataset;
staleFileCancel.transfer = oldFileAcquisition;
staleFileCancel.format = KVMFR_CLIPBOARD_FORMAT_FILES;
staleFileCancel.token = KVMFR_CLIPBOARD_FILE_ERROR_STALE;
CHECK(postRecord(state, &staleFileCancel,
KVMFR_CLIPBOARD_QUEUE_MESSAGE, NULL));
CHECK(waitMemory(state, state->memories[state->memoryCount - 1]));
CHECK(atomic_load(&state->events.fileCancel) == 0);
KVMFRClipboardMessage acquired =
hostRecord(KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRED,
newClaim.generation);
acquired.clipboardGeneration = newFileDataset;
acquired.transfer = newFileAcquisition;
acquired.format = KVMFR_CLIPBOARD_FORMAT_FILES;
CHECK(postRecord(state, &acquired,
KVMFR_CLIPBOARD_QUEUE_MESSAGE, NULL));
CHECK(waitAtomic(state, &state->events.fileAcquired,
acquiredCount + 1));
CHECK(state->events.fileDataset == newFileDataset);
CHECK(state->events.fileAcquisition == newFileAcquisition);
return true;
}
static bool testDisconnect(TestState * state)
{
KVMFRClipboardMessage claimRecord;
@@ -944,6 +1581,59 @@ static bool testMalformed(TestState * state)
return true;
}
static bool testFileFormatIsolation(TestState * state)
{
KVMFRClipboardMessage claimRecord;
CHECK(claim(state, 91, false, &claimRecord));
CHECK(own(state, 91, claimRecord.generation));
const LG_ClipboardData files[] = { LG_CLIPBOARD_DATA_FILES };
CHECK(!state->ops->notifyTypes(state->clipboard, files, 1));
CHECK(noClientData(state));
const uint64_t localDataset = UINT64_C(0x123456789abcdef);
CHECK(state->ops->offerFiles(state->clipboard, localDataset));
KVMFRClipboardMessage localOffer;
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_OFFER, &localOffer));
CHECK(localOffer.clipboardGeneration == localDataset);
KVMFRClipboardMessage request =
hostRecord(KVMFR_CLIPBOARD_MESSAGE_REQUEST, claimRecord.generation);
request.clipboardGeneration = localDataset;
request.transfer = KVMFR_CLIPBOARD_TRANSFER_HELPER | UINT64_C(1);
request.format = KVMFR_CLIPBOARD_FORMAT_FILES;
CHECK(postRecord(state, &request, KVMFR_CLIPBOARD_QUEUE_MESSAGE, NULL));
CHECK(waitMemory(state, state->memories[state->memoryCount - 1]));
CHECK(atomic_load(&state->events.request) == 0);
CHECK(state->ops->dataBegin(state->clipboard, request.transfer,
LG_CLIPBOARD_DATA_FILES, 0) == LG_CLIPBOARD_RESULT_FAILED);
KVMFRClipboardMessage remoteOffer =
hostRecord(KVMFR_CLIPBOARD_MESSAGE_OFFER, claimRecord.generation);
remoteOffer.clipboardGeneration = UINT64_C(0x2222333344445555);
remoteOffer.token = KVMFR_CLIPBOARD_FORMAT_MASK_FILES;
CHECK(postRecord(state, &remoteOffer,
KVMFR_CLIPBOARD_QUEUE_MESSAGE, NULL));
CHECK(waitAtomic(state, &state->events.notice, 1));
CHECK(state->events.noticeCount == 1);
CHECK(state->events.noticeTypes[0] == LG_CLIPBOARD_DATA_FILES);
CHECK(!state->ops->request(state->clipboard,
100, LG_CLIPBOARD_DATA_FILES));
CHECK(noClientData(state));
KVMFRClipboardMessage data =
hostRecord(KVMFR_CLIPBOARD_MESSAGE_DATA, claimRecord.generation);
data.clipboardGeneration = remoteOffer.clipboardGeneration;
data.transfer = UINT64_C(0x2345);
data.format = KVMFR_CLIPBOARD_FORMAT_FILES;
data.flags = KVMFR_CLIPBOARD_FLAG_BEGIN |
KVMFR_CLIPBOARD_FLAG_END;
CHECK(postRecord(state, &data, KVMFR_CLIPBOARD_QUEUE_DATA, NULL));
CHECK(waitMemory(state, state->memories[state->memoryCount - 1]));
CHECK(atomic_load(&state->events.dataBegin) == 0);
return true;
}
static bool stateInit(TestState * state)
{
memset(state, 0, sizeof(*state));
@@ -961,9 +1651,15 @@ static bool stateInit(TestState * state)
atomic_init(&state->events.dataEnd, 0);
atomic_init(&state->events.dataCancel, 0);
atomic_init(&state->events.dataReady, 0);
atomic_init(&state->events.fileOffer, 0);
atomic_init(&state->events.fileAcquire, 0);
atomic_init(&state->events.fileAcquired, 0);
atomic_init(&state->events.fileRequest, 0);
atomic_init(&state->events.fileCancel, 0);
atomic_init(&state->events.acceptRequest, true);
atomic_init(&state->events.blockChunkOnce, false);
atomic_init(&state->events.attached, false);
atomic_init(&state->events.fileOfferBeforeNotice, false);
state->memory = mmap(NULL, TEST_SHM_SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
@@ -1044,8 +1740,14 @@ TESTS[] =
{ "blocked" , testBlocked },
{ "cancel" , testCancel },
{ "restart" , testRestart },
{ "process-restart" , testProcessRestart },
{ "file-stream" , testFileStream },
{ "file-limits" , testFileLimits },
{ "file-owner-loss" , testFileOwnerLoss },
{ "file-malformed" , testMalformedFileData },
{ "disconnect" , testDisconnect },
{ "malformed" , testMalformed },
{ "file-format" , testFileFormatIsolation },
};
int main(int argc, char * argv[])

File diff suppressed because it is too large Load Diff