mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 15:11:31 +00:00
[client] lgmp: transport clipboard files
This commit is contained in:
@@ -123,8 +123,14 @@ set(LGMP_CLIPBOARD_CASES
|
|||||||
blocked
|
blocked
|
||||||
cancel
|
cancel
|
||||||
restart
|
restart
|
||||||
|
process-restart
|
||||||
|
file-stream
|
||||||
|
file-limits
|
||||||
|
file-owner-loss
|
||||||
|
file-malformed
|
||||||
disconnect
|
disconnect
|
||||||
malformed
|
malformed
|
||||||
|
file-format
|
||||||
)
|
)
|
||||||
foreach(name IN LISTS LGMP_CLIPBOARD_CASES)
|
foreach(name IN LISTS LGMP_CLIPBOARD_CASES)
|
||||||
add_test(NAME lgmp-clipboard-${name}
|
add_test(NAME lgmp-clipboard-${name}
|
||||||
|
|||||||
@@ -65,9 +65,15 @@ typedef struct EventTrace
|
|||||||
atomic_uint dataEnd;
|
atomic_uint dataEnd;
|
||||||
atomic_uint dataCancel;
|
atomic_uint dataCancel;
|
||||||
atomic_uint dataReady;
|
atomic_uint dataReady;
|
||||||
|
atomic_uint fileOffer;
|
||||||
|
atomic_uint fileAcquire;
|
||||||
|
atomic_uint fileAcquired;
|
||||||
|
atomic_uint fileRequest;
|
||||||
|
atomic_uint fileCancel;
|
||||||
atomic_bool acceptRequest;
|
atomic_bool acceptRequest;
|
||||||
atomic_bool blockChunkOnce;
|
atomic_bool blockChunkOnce;
|
||||||
atomic_bool attached;
|
atomic_bool attached;
|
||||||
|
atomic_bool fileOfferBeforeNotice;
|
||||||
LG_ClipboardData noticeTypes[LG_CLIPBOARD_DATA_NONE];
|
LG_ClipboardData noticeTypes[LG_CLIPBOARD_DATA_NONE];
|
||||||
size_t noticeCount;
|
size_t noticeCount;
|
||||||
LG_ClipboardRequest requestID;
|
LG_ClipboardRequest requestID;
|
||||||
@@ -81,6 +87,10 @@ typedef struct EventTrace
|
|||||||
uint64_t finalSize;
|
uint64_t finalSize;
|
||||||
LG_ClipboardRequest cancelID;
|
LG_ClipboardRequest cancelID;
|
||||||
LG_ClipboardCancelReason cancelReason;
|
LG_ClipboardCancelReason cancelReason;
|
||||||
|
uint64_t fileDataset;
|
||||||
|
uint64_t fileAcquisition;
|
||||||
|
LG_ClipboardFileError fileError;
|
||||||
|
LG_ClipboardFileRequest fileRequestValue;
|
||||||
}
|
}
|
||||||
EventTrace;
|
EventTrace;
|
||||||
|
|
||||||
@@ -94,6 +104,7 @@ typedef struct TestState
|
|||||||
const LG_ClipboardOps * ops;
|
const LG_ClipboardOps * ops;
|
||||||
uint32_t clientID;
|
uint32_t clientID;
|
||||||
uint32_t hostSerial;
|
uint32_t hostSerial;
|
||||||
|
uint64_t localClipboardGeneration;
|
||||||
PLGMPMemory memories[TEST_MEMORY_MAX];
|
PLGMPMemory memories[TEST_MEMORY_MAX];
|
||||||
unsigned memoryCount;
|
unsigned memoryCount;
|
||||||
PLGMPMemory grantMemory[KVMFR_CLIPBOARD_SLOT_COUNT];
|
PLGMPMemory grantMemory[KVMFR_CLIPBOARD_SLOT_COUNT];
|
||||||
@@ -119,6 +130,10 @@ static void notice(void * opaque, const LG_ClipboardData types[],
|
|||||||
size_t count)
|
size_t count)
|
||||||
{
|
{
|
||||||
EventTrace * trace = opaque;
|
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;
|
trace->noticeCount = count;
|
||||||
if (count)
|
if (count)
|
||||||
memcpy(trace->noticeTypes, types, count * sizeof(*types));
|
memcpy(trace->noticeTypes, types, count * sizeof(*types));
|
||||||
@@ -202,7 +217,51 @@ static void requestCancelEvent(void * opaque, LG_ClipboardRequest id,
|
|||||||
trace->cancelID = id;
|
trace->cancelID = id;
|
||||||
trace->cancelReason = reason;
|
trace->cancelReason = reason;
|
||||||
atomic_fetch_add_explicit(
|
atomic_fetch_add_explicit(
|
||||||
&trace->requestCancel, 1, memory_order_release);
|
&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 =
|
static const LG_ClipboardEventOps EVENT_OPS =
|
||||||
@@ -216,6 +275,11 @@ static const LG_ClipboardEventOps EVENT_OPS =
|
|||||||
.requestCancel = requestCancelEvent,
|
.requestCancel = requestCancelEvent,
|
||||||
.release = releaseNotice,
|
.release = releaseNotice,
|
||||||
.request = request,
|
.request = request,
|
||||||
|
.fileOffer = fileOfferEvent,
|
||||||
|
.fileAcquire = fileAcquireEvent,
|
||||||
|
.fileAcquired = fileAcquiredEvent,
|
||||||
|
.fileRequest = fileRequestEvent,
|
||||||
|
.fileCancel = fileCancelEvent,
|
||||||
};
|
};
|
||||||
|
|
||||||
static void statusChanged(void * opaque, const LG_ClipboardStatus * status)
|
static void statusChanged(void * opaque, const LG_ClipboardStatus * status)
|
||||||
@@ -402,6 +466,18 @@ static bool readType(TestState * state, KVMFRClipboardMessageType type,
|
|||||||
return false;
|
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)
|
static bool noClientData(TestState * state)
|
||||||
{
|
{
|
||||||
for (unsigned i = 0; i < TEST_QUIET_MS; ++i)
|
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));
|
CHECK(state->ops->notifyTypes(state->clipboard, formats, 1));
|
||||||
KVMFRClipboardMessage offer;
|
KVMFRClipboardMessage offer;
|
||||||
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_OFFER, &offer));
|
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_OFFER, &offer));
|
||||||
|
state->localClipboardGeneration = offer.clipboardGeneration;
|
||||||
CHECK(offer.generation == claimRecord.generation);
|
CHECK(offer.generation == claimRecord.generation);
|
||||||
CHECK(offer.clipboardGeneration != 0);
|
CHECK(offer.clipboardGeneration != 0);
|
||||||
CHECK(offer.token == KVMFR_CLIPBOARD_FORMAT_MASK_TEXT);
|
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));
|
CHECK(state->ops->notifyTypes(state->clipboard, formats, 1));
|
||||||
KVMFRClipboardMessage offer;
|
KVMFRClipboardMessage offer;
|
||||||
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_OFFER, &offer));
|
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_OFFER, &offer));
|
||||||
|
state->localClipboardGeneration = offer.clipboardGeneration;
|
||||||
|
|
||||||
KVMFRClipboardMessage requestRecord =
|
KVMFRClipboardMessage requestRecord =
|
||||||
hostRecord(KVMFR_CLIPBOARD_MESSAGE_REQUEST,
|
hostRecord(KVMFR_CLIPBOARD_MESSAGE_REQUEST,
|
||||||
@@ -634,6 +712,30 @@ static bool checkCommit(TestState * state, uint32_t token,
|
|||||||
return true;
|
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)
|
static bool testOutboundGrant(TestState * state)
|
||||||
{
|
{
|
||||||
KVMFRClipboardMessage claimRecord;
|
KVMFRClipboardMessage claimRecord;
|
||||||
@@ -746,10 +848,10 @@ static bool testCancel(TestState * state)
|
|||||||
const LG_ClipboardRequest outbound = state->events.requestID;
|
const LG_ClipboardRequest outbound = state->events.requestID;
|
||||||
KVMFRClipboardMessage cancel =
|
KVMFRClipboardMessage cancel =
|
||||||
hostRecord(KVMFR_CLIPBOARD_MESSAGE_CANCEL, claimRecord.generation);
|
hostRecord(KVMFR_CLIPBOARD_MESSAGE_CANCEL, claimRecord.generation);
|
||||||
cancel.clipboardGeneration = 1;
|
cancel.clipboardGeneration = state->localClipboardGeneration;
|
||||||
cancel.transfer = outbound;
|
cancel.transfer = outbound;
|
||||||
cancel.format = KVMFR_CLIPBOARD_FORMAT_TEXT;
|
cancel.format = KVMFR_CLIPBOARD_FORMAT_TEXT;
|
||||||
cancel.token = LG_CLIPBOARD_CANCEL_ABORTED;
|
cancel.token = LG_CLIPBOARD_CANCEL_ABORTED;
|
||||||
CHECK(postRecord(state, &cancel, KVMFR_CLIPBOARD_QUEUE_MESSAGE, NULL));
|
CHECK(postRecord(state, &cancel, KVMFR_CLIPBOARD_QUEUE_MESSAGE, NULL));
|
||||||
CHECK(waitAtomic(state, &state->events.requestCancel, 1));
|
CHECK(waitAtomic(state, &state->events.requestCancel, 1));
|
||||||
CHECK(state->events.cancelID == outbound);
|
CHECK(state->events.cancelID == outbound);
|
||||||
@@ -768,7 +870,7 @@ static bool testCancel(TestState * state)
|
|||||||
KVMFRClipboardMessage outboundRequest =
|
KVMFRClipboardMessage outboundRequest =
|
||||||
hostRecord(KVMFR_CLIPBOARD_MESSAGE_REQUEST,
|
hostRecord(KVMFR_CLIPBOARD_MESSAGE_REQUEST,
|
||||||
claimRecord.generation);
|
claimRecord.generation);
|
||||||
outboundRequest.clipboardGeneration = 1;
|
outboundRequest.clipboardGeneration = state->localClipboardGeneration;
|
||||||
outboundRequest.transfer = KVMFR_CLIPBOARD_TRANSFER_HELPER | 5;
|
outboundRequest.transfer = KVMFR_CLIPBOARD_TRANSFER_HELPER | 5;
|
||||||
outboundRequest.format = KVMFR_CLIPBOARD_FORMAT_TEXT;
|
outboundRequest.format = KVMFR_CLIPBOARD_FORMAT_TEXT;
|
||||||
CHECK(postRecord(state, &outboundRequest,
|
CHECK(postRecord(state, &outboundRequest,
|
||||||
@@ -779,7 +881,8 @@ static bool testCancel(TestState * state)
|
|||||||
KVMFRClipboardMessage wireCancel;
|
KVMFRClipboardMessage wireCancel;
|
||||||
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_CANCEL, &wireCancel));
|
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_CANCEL, &wireCancel));
|
||||||
CHECK(wireCancel.transfer == outboundRequest.transfer);
|
CHECK(wireCancel.transfer == outboundRequest.transfer);
|
||||||
CHECK(wireCancel.clipboardGeneration == 1);
|
CHECK(wireCancel.clipboardGeneration ==
|
||||||
|
state->localClipboardGeneration);
|
||||||
CHECK(wireCancel.format == KVMFR_CLIPBOARD_FORMAT_TEXT);
|
CHECK(wireCancel.format == KVMFR_CLIPBOARD_FORMAT_TEXT);
|
||||||
CHECK(wireCancel.token == LG_CLIPBOARD_CANCEL_ABORTED);
|
CHECK(wireCancel.token == LG_CLIPBOARD_CANCEL_ABORTED);
|
||||||
return true;
|
return true;
|
||||||
@@ -814,6 +917,381 @@ static bool testRestart(TestState * state)
|
|||||||
return true;
|
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)
|
static void * disconnectThread(void * opaque)
|
||||||
{
|
{
|
||||||
DisconnectTask * task = opaque;
|
DisconnectTask * task = opaque;
|
||||||
@@ -863,6 +1341,165 @@ static bool disconnectAndDrain(TestState * state,
|
|||||||
return valid;
|
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)
|
static bool testDisconnect(TestState * state)
|
||||||
{
|
{
|
||||||
KVMFRClipboardMessage claimRecord;
|
KVMFRClipboardMessage claimRecord;
|
||||||
@@ -944,6 +1581,59 @@ static bool testMalformed(TestState * state)
|
|||||||
return true;
|
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)
|
static bool stateInit(TestState * state)
|
||||||
{
|
{
|
||||||
memset(state, 0, sizeof(*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.dataEnd, 0);
|
||||||
atomic_init(&state->events.dataCancel, 0);
|
atomic_init(&state->events.dataCancel, 0);
|
||||||
atomic_init(&state->events.dataReady, 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.acceptRequest, true);
|
||||||
atomic_init(&state->events.blockChunkOnce, false);
|
atomic_init(&state->events.blockChunkOnce, false);
|
||||||
atomic_init(&state->events.attached, false);
|
atomic_init(&state->events.attached, false);
|
||||||
|
atomic_init(&state->events.fileOfferBeforeNotice, false);
|
||||||
|
|
||||||
state->memory = mmap(NULL, TEST_SHM_SIZE, PROT_READ | PROT_WRITE,
|
state->memory = mmap(NULL, TEST_SHM_SIZE, PROT_READ | PROT_WRITE,
|
||||||
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
|
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
|
||||||
@@ -1037,15 +1733,21 @@ static const struct
|
|||||||
}
|
}
|
||||||
TESTS[] =
|
TESTS[] =
|
||||||
{
|
{
|
||||||
{ "claim" , testClaim },
|
{ "claim" , testClaim },
|
||||||
{ "offer-request" , testOfferRequest },
|
{ "offer-request" , testOfferRequest },
|
||||||
{ "inbound-stream", testInboundStream },
|
{ "inbound-stream" , testInboundStream },
|
||||||
{ "outbound-grant", testOutboundGrant },
|
{ "outbound-grant" , testOutboundGrant },
|
||||||
{ "blocked" , testBlocked },
|
{ "blocked" , testBlocked },
|
||||||
{ "cancel" , testCancel },
|
{ "cancel" , testCancel },
|
||||||
{ "restart" , testRestart },
|
{ "restart" , testRestart },
|
||||||
{ "disconnect" , testDisconnect },
|
{ "process-restart" , testProcessRestart },
|
||||||
{ "malformed" , testMalformed },
|
{ "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[])
|
int main(int argc, char * argv[])
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user