mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 07:01:30 +00:00
Replace legacy clipboard grant and queued input fixtures with bound SPSC stream endpoints. Verify stream-only payload delivery, credit recovery, release completion, and four 256 KiB records for a 1 MiB response. Keep queue assertions for status, discovery, ownership, and control.
1987 lines
71 KiB
C
1987 lines
71 KiB
C
/**
|
|
* Looking Glass
|
|
* Copyright © 2017-2026 The Looking Glass Authors
|
|
* https://looking-glass.io
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the Free
|
|
* Software Foundation; either version 2 of the License, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
|
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "clipboard.h"
|
|
|
|
#include "common/KVMFRClipboard.h"
|
|
#include "common/LGMPConfig.h"
|
|
#include "common/debug.h"
|
|
|
|
#include <lgmp/client.h>
|
|
#include <lgmp/host.h>
|
|
#include <lgmp/stream.h>
|
|
|
|
#include <pthread.h>
|
|
#include <stdatomic.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/mman.h>
|
|
#include <unistd.h>
|
|
|
|
#define TEST_WAIT_MS 2500U
|
|
#define TEST_QUIET_MS 50U
|
|
#define TEST_MEMORY_MAX 128U
|
|
#define SLOT_BYTES KVMFR_CLIPBOARD_STREAM_RECORD_BYTES
|
|
#define TEST_SHM_OVERHEAD (1024U * 1024U)
|
|
/* Match the production duplex streams and leave room for LGMP metadata. */
|
|
#define TEST_SHM_SIZE \
|
|
(2U * KVMFR_CLIPBOARD_STREAM_SLOT_COUNT * SLOT_BYTES + \
|
|
TEST_SHM_OVERHEAD)
|
|
|
|
#define CHECK(x) \
|
|
do \
|
|
{ \
|
|
if (!(x)) \
|
|
{ \
|
|
fprintf(stderr, "check failed at %s:%d: %s\n", \
|
|
__FILE__, __LINE__, #x); \
|
|
return false; \
|
|
} \
|
|
} \
|
|
while (0)
|
|
|
|
typedef struct EventTrace
|
|
{
|
|
atomic_uint notice;
|
|
atomic_uint release;
|
|
atomic_uint request;
|
|
atomic_uint requestCancel;
|
|
atomic_uint dataBegin;
|
|
atomic_uint dataChunk;
|
|
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;
|
|
LG_ClipboardData requestType;
|
|
LG_ClipboardRequest streamID;
|
|
LG_ClipboardData streamType;
|
|
uint64_t sizeHint;
|
|
uint64_t chunkOffset;
|
|
size_t chunkSize;
|
|
uint8_t chunkData[64];
|
|
uint64_t finalSize;
|
|
LG_ClipboardRequest cancelID;
|
|
LG_ClipboardCancelReason cancelReason;
|
|
uint64_t fileDataset;
|
|
uint64_t fileAcquisition;
|
|
LG_ClipboardFileError fileError;
|
|
LG_ClipboardFileRequest fileRequestValue;
|
|
}
|
|
EventTrace;
|
|
|
|
typedef struct TestState
|
|
{
|
|
void * memory;
|
|
PLGMPHost host;
|
|
PLGMPHostQueue queue;
|
|
PLGMPClient client;
|
|
PLGMPHostStream hostToClientStream;
|
|
PLGMPHostStream clientToHostStream;
|
|
KVMFRStreamDescriptor hostToClientDescriptor;
|
|
KVMFRStreamDescriptor clientToHostDescriptor;
|
|
bool streamsBound;
|
|
LGMPClipboard * clipboard;
|
|
const LG_ClipboardOps * ops;
|
|
uint32_t clientID;
|
|
uint32_t hostSerial;
|
|
uint64_t localClipboardGeneration;
|
|
PLGMPMemory memories[TEST_MEMORY_MAX];
|
|
unsigned memoryCount;
|
|
atomic_uint statusCount;
|
|
atomic_bool statusAvailable;
|
|
atomic_uint statusGeneration;
|
|
atomic_bool autoAttach;
|
|
EventTrace events;
|
|
}
|
|
TestState;
|
|
|
|
typedef struct DisconnectTask
|
|
{
|
|
LGMPClipboard * clipboard;
|
|
atomic_bool done;
|
|
}
|
|
DisconnectTask;
|
|
|
|
static bool disconnectAndDrain(TestState * state,
|
|
uint32_t * releaseGeneration);
|
|
|
|
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));
|
|
atomic_fetch_add_explicit(&trace->notice, 1, memory_order_release);
|
|
}
|
|
|
|
static void releaseNotice(void * opaque)
|
|
{
|
|
EventTrace * trace = opaque;
|
|
atomic_fetch_add_explicit(&trace->release, 1, memory_order_release);
|
|
}
|
|
|
|
static bool request(void * opaque, LG_ClipboardRequest id,
|
|
LG_ClipboardData type)
|
|
{
|
|
EventTrace * trace = opaque;
|
|
trace->requestID = id;
|
|
trace->requestType = type;
|
|
atomic_fetch_add_explicit(&trace->request, 1, memory_order_release);
|
|
return atomic_load_explicit(&trace->acceptRequest, memory_order_acquire);
|
|
}
|
|
|
|
static LG_ClipboardResult dataBegin(void * opaque,
|
|
LG_ClipboardRequest id, LG_ClipboardData type, uint64_t sizeHint)
|
|
{
|
|
EventTrace * trace = opaque;
|
|
trace->streamID = id;
|
|
trace->streamType = type;
|
|
trace->sizeHint = sizeHint;
|
|
atomic_fetch_add_explicit(&trace->dataBegin, 1, memory_order_release);
|
|
return LG_CLIPBOARD_RESULT_ACCEPTED;
|
|
}
|
|
|
|
static LG_ClipboardResult dataChunk(void * opaque,
|
|
LG_ClipboardRequest id, uint64_t offset,
|
|
const void * data, size_t size)
|
|
{
|
|
EventTrace * trace = opaque;
|
|
trace->streamID = id;
|
|
trace->chunkOffset = offset;
|
|
trace->chunkSize = size;
|
|
if (size <= sizeof(trace->chunkData))
|
|
memcpy(trace->chunkData, data, size);
|
|
atomic_fetch_add_explicit(&trace->dataChunk, 1, memory_order_release);
|
|
if (atomic_exchange_explicit(&trace->blockChunkOnce, false,
|
|
memory_order_acq_rel))
|
|
return LG_CLIPBOARD_RESULT_BLOCKED;
|
|
return LG_CLIPBOARD_RESULT_ACCEPTED;
|
|
}
|
|
|
|
static LG_ClipboardResult dataEnd(void * opaque,
|
|
LG_ClipboardRequest id, uint64_t size)
|
|
{
|
|
EventTrace * trace = opaque;
|
|
trace->streamID = id;
|
|
trace->finalSize = size;
|
|
atomic_fetch_add_explicit(&trace->dataEnd, 1, memory_order_release);
|
|
return LG_CLIPBOARD_RESULT_ACCEPTED;
|
|
}
|
|
|
|
static void dataCancelEvent(void * opaque, LG_ClipboardRequest id,
|
|
LG_ClipboardCancelReason reason)
|
|
{
|
|
EventTrace * trace = opaque;
|
|
trace->cancelID = id;
|
|
trace->cancelReason = reason;
|
|
atomic_fetch_add_explicit(&trace->dataCancel, 1, memory_order_release);
|
|
}
|
|
|
|
static void dataReadyEvent(void * opaque, LG_ClipboardRequest id)
|
|
{
|
|
EventTrace * trace = opaque;
|
|
trace->streamID = id;
|
|
atomic_fetch_add_explicit(&trace->dataReady, 1, memory_order_release);
|
|
}
|
|
|
|
static void requestCancelEvent(void * opaque, LG_ClipboardRequest id,
|
|
LG_ClipboardCancelReason reason)
|
|
{
|
|
EventTrace * trace = opaque;
|
|
trace->cancelID = id;
|
|
trace->cancelReason = reason;
|
|
atomic_fetch_add_explicit(
|
|
&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,
|
|
.dataBegin = dataBegin,
|
|
.dataChunk = dataChunk,
|
|
.dataEnd = dataEnd,
|
|
.dataCancel = dataCancelEvent,
|
|
.dataReady = dataReadyEvent,
|
|
.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)
|
|
{
|
|
TestState * state = opaque;
|
|
atomic_store_explicit(&state->statusAvailable,
|
|
status->available, memory_order_release);
|
|
atomic_store_explicit(&state->statusGeneration,
|
|
status->generation, memory_order_release);
|
|
atomic_fetch_add_explicit(&state->statusCount, 1, memory_order_release);
|
|
|
|
if (status->available &&
|
|
atomic_load_explicit(&state->autoAttach, memory_order_acquire) &&
|
|
!atomic_exchange_explicit(
|
|
&state->events.attached, true, memory_order_acq_rel))
|
|
{
|
|
const bool attached = state->ops->attach(
|
|
state->clipboard, &EVENT_OPS, &state->events);
|
|
atomic_store_explicit(
|
|
&state->events.attached, attached, memory_order_release);
|
|
}
|
|
}
|
|
|
|
static bool hostProcess(TestState * state)
|
|
{
|
|
CHECK(lgmpHostProcess(state->host) == LGMP_OK);
|
|
return true;
|
|
}
|
|
|
|
static KVMFRStreamDescriptor exportStreamDescriptor(
|
|
const struct LGMPStreamDescriptor * descriptor)
|
|
{
|
|
return (KVMFRStreamDescriptor)
|
|
{
|
|
.magic = descriptor->magic,
|
|
.version = descriptor->version,
|
|
.size = descriptor->size,
|
|
.offset = descriptor->offset,
|
|
.regionSize = descriptor->regionSize,
|
|
.direction = descriptor->direction,
|
|
.policy = descriptor->policy,
|
|
.slotCount = descriptor->slotCount,
|
|
.slotSize = descriptor->slotSize,
|
|
};
|
|
}
|
|
|
|
static bool waitAtomic(TestState * state, atomic_uint * value,
|
|
unsigned expected)
|
|
{
|
|
for (unsigned i = 0; i < TEST_WAIT_MS; ++i)
|
|
{
|
|
CHECK(hostProcess(state));
|
|
if (atomic_load_explicit(value, memory_order_acquire) >= expected)
|
|
return true;
|
|
usleep(1000);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool waitMemory(TestState * state, PLGMPMemory memory)
|
|
{
|
|
for (unsigned i = 0; i < TEST_WAIT_MS; ++i)
|
|
{
|
|
CHECK(hostProcess(state));
|
|
if (!lgmpHostQueuePayloadPending(state->queue, memory))
|
|
return true;
|
|
usleep(1000);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool allocMemory(TestState * state, size_t size,
|
|
PLGMPMemory * result)
|
|
{
|
|
CHECK(state->memoryCount < TEST_MEMORY_MAX);
|
|
CHECK(lgmpHostMemAlloc(state->host, (uint32_t)size, result) == LGMP_OK);
|
|
state->memories[state->memoryCount++] = *result;
|
|
memset(lgmpHostMemPtr(*result), 0, size);
|
|
return true;
|
|
}
|
|
|
|
static bool postMemory(TestState * state, PLGMPMemory memory,
|
|
KVMFRClipboardQueueType type, uint32_t serial)
|
|
{
|
|
const uint64_t udata = KVMFR_CLIPBOARD_QUEUE_UDATA(type, serial);
|
|
for (unsigned i = 0; i < TEST_WAIT_MS; ++i)
|
|
{
|
|
unsigned recipients = 0;
|
|
const LGMP_STATUS status = lgmpHostQueuePostForClients(state->queue,
|
|
udata, memory, &state->clientID, 1, &recipients);
|
|
if (status == LGMP_OK)
|
|
return recipients == 1;
|
|
CHECK(status == LGMP_ERR_QUEUE_FULL);
|
|
CHECK(hostProcess(state));
|
|
usleep(1000);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static uint32_t nextSerial(TestState * state)
|
|
{
|
|
if (++state->hostSerial == 0)
|
|
++state->hostSerial;
|
|
return state->hostSerial;
|
|
}
|
|
|
|
static bool unbindStream(TestState * state, PLGMPHostStream stream)
|
|
{
|
|
for (unsigned i = 0; i < TEST_WAIT_MS; ++i)
|
|
{
|
|
const LGMP_STATUS status = lgmpHostStreamUnbind(stream);
|
|
if (status == LGMP_OK)
|
|
return true;
|
|
CHECK(status == LGMP_ERR_STREAM_BUSY);
|
|
CHECK(hostProcess(state));
|
|
usleep(1000);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool postStatus(TestState * state, uint32_t endpointGeneration,
|
|
uint32_t ownerClientID, uint32_t ownerGeneration,
|
|
bool available, KVMFRClipboardFormatFlags formats)
|
|
{
|
|
if (!ownerClientID && state->streamsBound)
|
|
{
|
|
CHECK(unbindStream(state, state->clientToHostStream));
|
|
CHECK(unbindStream(state, state->hostToClientStream));
|
|
state->streamsBound = false;
|
|
}
|
|
|
|
PLGMPMemory memory;
|
|
CHECK(allocMemory(state, sizeof(KVMFRClipboardStatus), &memory));
|
|
KVMFRClipboardStatus * status = lgmpHostMemPtr(memory);
|
|
*status = (KVMFRClipboardStatus)
|
|
{
|
|
.version = KVMFR_CLIPBOARD_VERSION,
|
|
.flags = (available ? KVMFR_CLIPBOARD_STATUS_AVAILABLE : 0) |
|
|
(ownerClientID ? KVMFR_CLIPBOARD_STATUS_HAS_OWNER : 0),
|
|
.generation = endpointGeneration,
|
|
.ownerClientID = ownerClientID,
|
|
.ownerGeneration = ownerGeneration,
|
|
.lease = 1000,
|
|
.formats = available ? formats : 0,
|
|
.slotBytes = KVMFR_CLIPBOARD_STREAM_SLOT_BYTES,
|
|
.streamVersion = KVMFR_CLIPBOARD_STREAM_VERSION,
|
|
.streamSlotCount = KVMFR_CLIPBOARD_STREAM_SLOT_COUNT,
|
|
.hostToClient = state->hostToClientDescriptor,
|
|
.clientToHost = state->clientToHostDescriptor,
|
|
};
|
|
CHECK(postMemory(state, memory, KVMFR_CLIPBOARD_QUEUE_STATUS,
|
|
nextSerial(state)));
|
|
CHECK(waitMemory(state, memory));
|
|
return true;
|
|
}
|
|
|
|
static bool postRecord(TestState * state,
|
|
const KVMFRClipboardMessage * record,
|
|
KVMFRClipboardQueueType type, const void * data)
|
|
{
|
|
CHECK(type == KVMFR_CLIPBOARD_QUEUE_MESSAGE);
|
|
CHECK(record->length == 0);
|
|
CHECK(data == NULL);
|
|
PLGMPMemory memory;
|
|
CHECK(allocMemory(state, sizeof(*record), &memory));
|
|
memcpy(lgmpHostMemPtr(memory), record, sizeof(*record));
|
|
CHECK(postMemory(state, memory, type, nextSerial(state)));
|
|
return true;
|
|
}
|
|
|
|
static bool postStreamRecord(TestState * state,
|
|
const KVMFRClipboardMessage * record, const void * data)
|
|
{
|
|
CHECK(state->streamsBound);
|
|
CHECK(record->length <= KVMFR_CLIPBOARD_STREAM_SLOT_BYTES);
|
|
CHECK(!record->length || data);
|
|
for (unsigned i = 0; i < TEST_WAIT_MS; ++i)
|
|
{
|
|
CHECK(hostProcess(state));
|
|
LGMPStreamBuffer buffer = { 0 };
|
|
const LGMP_STATUS status = lgmpHostStreamWriteAcquire(
|
|
state->hostToClientStream, &buffer);
|
|
if (status == LGMP_ERR_STREAM_FULL)
|
|
{
|
|
usleep(1000);
|
|
continue;
|
|
}
|
|
CHECK(status == LGMP_OK);
|
|
const uint32_t bytes = sizeof(*record) + record->length;
|
|
CHECK(buffer.capacity >= bytes);
|
|
memcpy(buffer.data, record, sizeof(*record));
|
|
if (record->length)
|
|
memcpy((uint8_t *)buffer.data + sizeof(*record), data,
|
|
record->length);
|
|
CHECK(lgmpHostStreamWriteCommit(state->hostToClientStream,
|
|
&buffer, bytes) == LGMP_OK);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool settleHostStream(TestState * state)
|
|
{
|
|
for (unsigned i = 0; i < TEST_QUIET_MS; ++i)
|
|
{
|
|
CHECK(hostProcess(state));
|
|
usleep(1000);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool expectPayloadQueueClean(TestState * state)
|
|
{
|
|
for (;;)
|
|
{
|
|
KVMFRClipboardMessage record;
|
|
size_t size = sizeof(record);
|
|
uint32_t clientID = 0;
|
|
const LGMP_STATUS status = lgmpHostReadDataWithSource(
|
|
state->queue, &record, &size, &clientID);
|
|
if (status == LGMP_ERR_QUEUE_EMPTY)
|
|
return true;
|
|
CHECK(status == LGMP_OK);
|
|
CHECK(size == sizeof(record));
|
|
CHECK(clientID == state->clientID);
|
|
CHECK(lgmpHostAckData(state->queue) == LGMP_OK);
|
|
CHECK(record.type == KVMFR_CLIPBOARD_MESSAGE_KEEPALIVE);
|
|
}
|
|
}
|
|
|
|
static bool readClientStream(TestState * state,
|
|
KVMFRClipboardMessage * record, LGMPStreamBuffer * buffer,
|
|
const void ** data)
|
|
{
|
|
for (unsigned i = 0; i < TEST_WAIT_MS; ++i)
|
|
{
|
|
CHECK(hostProcess(state));
|
|
CHECK(expectPayloadQueueClean(state));
|
|
memset(buffer, 0, sizeof(*buffer));
|
|
const LGMP_STATUS status = lgmpHostStreamReadPeek(
|
|
state->clientToHostStream, buffer);
|
|
if (status == LGMP_ERR_STREAM_EMPTY)
|
|
{
|
|
usleep(1000);
|
|
continue;
|
|
}
|
|
CHECK(status == LGMP_OK);
|
|
CHECK(buffer->size >= sizeof(*record));
|
|
memcpy(record, buffer->data, sizeof(*record));
|
|
CHECK(buffer->size == sizeof(*record) + record->length);
|
|
*data = (const uint8_t *)buffer->data + sizeof(*record);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool releaseClientStream(TestState * state,
|
|
LGMPStreamBuffer * buffer)
|
|
{
|
|
CHECK(lgmpHostStreamReadRelease(
|
|
state->clientToHostStream, buffer) == LGMP_OK);
|
|
memset(buffer, 0, sizeof(*buffer));
|
|
return true;
|
|
}
|
|
|
|
static bool readClient(TestState * state, KVMFRClipboardMessage * record)
|
|
{
|
|
for (unsigned i = 0; i < TEST_WAIT_MS; ++i)
|
|
{
|
|
CHECK(hostProcess(state));
|
|
size_t size = sizeof(*record);
|
|
uint32_t clientID = 0;
|
|
const LGMP_STATUS status = lgmpHostReadDataWithSource(
|
|
state->queue, record, &size, &clientID);
|
|
if (status == LGMP_ERR_QUEUE_EMPTY)
|
|
{
|
|
usleep(1000);
|
|
continue;
|
|
}
|
|
CHECK(status == LGMP_OK);
|
|
CHECK(size == sizeof(*record));
|
|
CHECK(clientID == state->clientID);
|
|
CHECK(record->type != KVMFR_CLIPBOARD_MESSAGE_DATA);
|
|
CHECK(record->type != KVMFR_CLIPBOARD_MESSAGE_FILE_DATA);
|
|
CHECK(lgmpHostAckData(state->queue) == LGMP_OK);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool readType(TestState * state, KVMFRClipboardMessageType type,
|
|
KVMFRClipboardMessage * record)
|
|
{
|
|
for (unsigned i = 0; i < 32; ++i)
|
|
{
|
|
CHECK(readClient(state, record));
|
|
if (record->type == type)
|
|
return true;
|
|
}
|
|
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 noClientControl(TestState * state)
|
|
{
|
|
for (unsigned i = 0; i < TEST_QUIET_MS; ++i)
|
|
{
|
|
CHECK(hostProcess(state));
|
|
KVMFRClipboardMessage record;
|
|
size_t size = sizeof(record);
|
|
uint32_t clientID = 0;
|
|
const LGMP_STATUS status = lgmpHostReadDataWithSource(
|
|
state->queue, &record, &size, &clientID);
|
|
if (status == LGMP_ERR_QUEUE_EMPTY)
|
|
{
|
|
usleep(1000);
|
|
continue;
|
|
}
|
|
if (status == LGMP_OK)
|
|
lgmpHostAckData(state->queue);
|
|
CHECK(status == LGMP_ERR_QUEUE_EMPTY);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool claim(TestState * state, uint32_t endpointGeneration,
|
|
bool autoAttach, KVMFRClipboardMessage * result)
|
|
{
|
|
atomic_store(&state->autoAttach, autoAttach);
|
|
CHECK(postStatus(state, endpointGeneration, 0, 0, true, 0));
|
|
if (autoAttach)
|
|
{
|
|
for (unsigned i = 0; i < TEST_WAIT_MS &&
|
|
!atomic_load(&state->events.attached); ++i)
|
|
{
|
|
CHECK(hostProcess(state));
|
|
usleep(1000);
|
|
}
|
|
CHECK(atomic_load(&state->events.attached));
|
|
}
|
|
else
|
|
{
|
|
CHECK(state->ops->attach(
|
|
state->clipboard, &EVENT_OPS, &state->events));
|
|
atomic_store(&state->events.attached, true);
|
|
}
|
|
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_CLAIM, result));
|
|
CHECK(result->version == KVMFR_CLIPBOARD_VERSION);
|
|
CHECK(result->generation != 0);
|
|
CHECK(result->token == endpointGeneration);
|
|
CHECK(result->flags == 0);
|
|
return true;
|
|
}
|
|
|
|
static bool own(TestState * state, uint32_t endpointGeneration,
|
|
uint32_t claimGeneration)
|
|
{
|
|
if (!state->streamsBound)
|
|
{
|
|
CHECK(lgmpHostStreamBind(state->hostToClientStream,
|
|
state->clientID, NULL) == LGMP_OK);
|
|
if (lgmpHostStreamBind(state->clientToHostStream,
|
|
state->clientID, NULL) != LGMP_OK)
|
|
{
|
|
lgmpHostStreamUnbind(state->hostToClientStream);
|
|
return false;
|
|
}
|
|
state->streamsBound = true;
|
|
}
|
|
CHECK(postStatus(state, endpointGeneration, state->clientID,
|
|
claimGeneration, true, 0));
|
|
return true;
|
|
}
|
|
|
|
static KVMFRClipboardMessage hostRecord(
|
|
KVMFRClipboardMessageType type, uint32_t generation)
|
|
{
|
|
return (KVMFRClipboardMessage)
|
|
{
|
|
.version = KVMFR_CLIPBOARD_VERSION,
|
|
.type = type,
|
|
.generation = generation,
|
|
};
|
|
}
|
|
|
|
static bool offerAndRequest(TestState * state, uint32_t generation,
|
|
uint64_t clipboardGeneration, LG_ClipboardRequest appRequest,
|
|
KVMFRClipboardMessage * wireRequest)
|
|
{
|
|
KVMFRClipboardMessage offer =
|
|
hostRecord(KVMFR_CLIPBOARD_MESSAGE_OFFER, generation);
|
|
offer.clipboardGeneration = clipboardGeneration;
|
|
offer.token = KVMFR_CLIPBOARD_FORMAT_MASK_TEXT |
|
|
KVMFR_CLIPBOARD_FORMAT_MASK_PNG;
|
|
const unsigned notices = atomic_load(&state->events.notice) + 1;
|
|
CHECK(postRecord(state, &offer, KVMFR_CLIPBOARD_QUEUE_MESSAGE, NULL));
|
|
CHECK(waitAtomic(state, &state->events.notice, notices));
|
|
CHECK(state->events.noticeCount == 2);
|
|
CHECK(state->events.noticeTypes[0] == LG_CLIPBOARD_DATA_TEXT);
|
|
CHECK(state->events.noticeTypes[1] == LG_CLIPBOARD_DATA_PNG);
|
|
CHECK(!state->ops->request(state->clipboard,
|
|
appRequest + 1, LG_CLIPBOARD_DATA_JPEG));
|
|
CHECK(state->ops->request(state->clipboard,
|
|
appRequest, LG_CLIPBOARD_DATA_TEXT));
|
|
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_REQUEST, wireRequest));
|
|
CHECK(kvmfrClipboardTransferFromClient(wireRequest->transfer));
|
|
CHECK(wireRequest->generation == generation);
|
|
CHECK(wireRequest->clipboardGeneration == clipboardGeneration);
|
|
CHECK(wireRequest->format == KVMFR_CLIPBOARD_FORMAT_TEXT);
|
|
return true;
|
|
}
|
|
|
|
static bool testClaim(TestState * state)
|
|
{
|
|
KVMFRClipboardMessage claimRecord;
|
|
CHECK(claim(state, 10, true, &claimRecord));
|
|
CHECK(own(state, 10, claimRecord.generation));
|
|
|
|
const LG_ClipboardData formats[] = { LG_CLIPBOARD_DATA_TEXT };
|
|
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);
|
|
return true;
|
|
}
|
|
|
|
static bool testOfferRequest(TestState * state)
|
|
{
|
|
KVMFRClipboardMessage claimRecord;
|
|
CHECK(claim(state, 20, false, &claimRecord));
|
|
CHECK(own(state, 20, claimRecord.generation));
|
|
KVMFRClipboardMessage requestRecord;
|
|
CHECK(offerAndRequest(state, claimRecord.generation,
|
|
200, 77, &requestRecord));
|
|
return true;
|
|
}
|
|
|
|
static bool testInboundStream(TestState * state)
|
|
{
|
|
KVMFRClipboardMessage claimRecord;
|
|
CHECK(claim(state, 30, false, &claimRecord));
|
|
CHECK(own(state, 30, claimRecord.generation));
|
|
KVMFRClipboardMessage requestRecord;
|
|
CHECK(offerAndRequest(state, claimRecord.generation,
|
|
300, 88, &requestRecord));
|
|
|
|
const uint8_t bytes[] = { 1, 2, 3, 4, 5 };
|
|
KVMFRClipboardMessage data =
|
|
hostRecord(KVMFR_CLIPBOARD_MESSAGE_DATA, claimRecord.generation);
|
|
data.clipboardGeneration = 300;
|
|
data.transfer = requestRecord.transfer;
|
|
data.size = 5;
|
|
data.format = KVMFR_CLIPBOARD_FORMAT_TEXT;
|
|
data.flags = KVMFR_CLIPBOARD_FLAG_BEGIN;
|
|
CHECK(postStreamRecord(state, &data, NULL));
|
|
CHECK(waitAtomic(state, &state->events.dataBegin, 1));
|
|
CHECK(atomic_load(&state->events.dataBegin) == 1);
|
|
CHECK(atomic_load(&state->events.dataChunk) == 0);
|
|
CHECK(state->events.streamID == 88);
|
|
CHECK(state->events.sizeHint == 5);
|
|
|
|
data.sequence = 1;
|
|
data.size = KVMFR_CLIPBOARD_SIZE_UNKNOWN;
|
|
data.flags = 0;
|
|
data.length = sizeof(bytes);
|
|
CHECK(postStreamRecord(state, &data, bytes));
|
|
CHECK(waitAtomic(state, &state->events.dataChunk, 1));
|
|
CHECK(state->events.chunkOffset == 0);
|
|
CHECK(state->events.chunkSize == sizeof(bytes));
|
|
CHECK(memcmp(state->events.chunkData, bytes, sizeof(bytes)) == 0);
|
|
|
|
data.sequence = 2;
|
|
data.offset = sizeof(bytes);
|
|
data.size = sizeof(bytes);
|
|
data.flags = KVMFR_CLIPBOARD_FLAG_END;
|
|
data.length = 0;
|
|
CHECK(postStreamRecord(state, &data, NULL));
|
|
CHECK(waitAtomic(state, &state->events.dataEnd, 1));
|
|
CHECK(atomic_load(&state->events.dataChunk) == 1);
|
|
CHECK(state->events.finalSize == 5);
|
|
|
|
KVMFRClipboardMessage request2;
|
|
CHECK(offerAndRequest(state, claimRecord.generation,
|
|
301, 89, &request2));
|
|
data = hostRecord(KVMFR_CLIPBOARD_MESSAGE_DATA,
|
|
claimRecord.generation);
|
|
data.clipboardGeneration = 301;
|
|
data.transfer = request2.transfer;
|
|
data.format = KVMFR_CLIPBOARD_FORMAT_TEXT;
|
|
data.flags = KVMFR_CLIPBOARD_FLAG_BEGIN |
|
|
KVMFR_CLIPBOARD_FLAG_END;
|
|
CHECK(postStreamRecord(state, &data, NULL));
|
|
CHECK(waitAtomic(state, &state->events.dataEnd, 2));
|
|
CHECK(atomic_load(&state->events.dataBegin) == 2);
|
|
CHECK(state->events.finalSize == 0);
|
|
return true;
|
|
}
|
|
|
|
static bool prepareOutbound(TestState * state, uint32_t endpoint,
|
|
KVMFRClipboardMessage * claimRecord, uint64_t transfer)
|
|
{
|
|
CHECK(claim(state, endpoint, false, claimRecord));
|
|
CHECK(own(state, endpoint, claimRecord->generation));
|
|
const LG_ClipboardData formats[] = { LG_CLIPBOARD_DATA_TEXT };
|
|
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,
|
|
claimRecord->generation);
|
|
requestRecord.clipboardGeneration = offer.clipboardGeneration;
|
|
requestRecord.transfer = KVMFR_CLIPBOARD_TRANSFER_HELPER | transfer;
|
|
requestRecord.format = KVMFR_CLIPBOARD_FORMAT_TEXT;
|
|
const unsigned requests = atomic_load(&state->events.request) + 1;
|
|
CHECK(postRecord(state, &requestRecord,
|
|
KVMFR_CLIPBOARD_QUEUE_MESSAGE, NULL));
|
|
CHECK(waitAtomic(state, &state->events.request, requests));
|
|
CHECK(state->events.requestID == requestRecord.transfer);
|
|
CHECK(state->events.requestType == LG_CLIPBOARD_DATA_TEXT);
|
|
return true;
|
|
}
|
|
|
|
static bool checkDataRecord(TestState * state,
|
|
KVMFRClipboardMessage * record, LGMPStreamBuffer * buffer,
|
|
const void ** data)
|
|
{
|
|
CHECK(readClientStream(state, record, buffer, data));
|
|
CHECK(record->type == KVMFR_CLIPBOARD_MESSAGE_DATA);
|
|
CHECK(record->version == KVMFR_CLIPBOARD_VERSION);
|
|
return true;
|
|
}
|
|
|
|
static bool checkFileDataRecord(TestState * state,
|
|
KVMFRClipboardFileOperation operation,
|
|
KVMFRClipboardMessage * record, LGMPStreamBuffer * buffer,
|
|
const void ** data)
|
|
{
|
|
CHECK(readClientStream(state, record, buffer, data));
|
|
CHECK(record->type == KVMFR_CLIPBOARD_MESSAGE_FILE_DATA);
|
|
CHECK(record->version == KVMFR_CLIPBOARD_VERSION);
|
|
CHECK(record->token == operation);
|
|
return true;
|
|
}
|
|
|
|
static bool testOutboundStream(TestState * state)
|
|
{
|
|
KVMFRClipboardMessage claimRecord;
|
|
CHECK(prepareOutbound(state, 40, &claimRecord, 1));
|
|
const LG_ClipboardRequest transfer = state->events.requestID;
|
|
|
|
CHECK(state->ops->dataBegin(state->clipboard, transfer,
|
|
LG_CLIPBOARD_DATA_TEXT, 5) == LG_CLIPBOARD_RESULT_ACCEPTED);
|
|
CHECK(noClientControl(state));
|
|
const uint8_t first[] = { 1, 2, 3 };
|
|
CHECK(state->ops->dataChunk(state->clipboard, transfer,
|
|
0, first, sizeof(first)) == LG_CLIPBOARD_RESULT_ACCEPTED);
|
|
KVMFRClipboardMessage record;
|
|
LGMPStreamBuffer buffer;
|
|
const void * data = NULL;
|
|
CHECK(checkDataRecord(state, &record, &buffer, &data));
|
|
CHECK(record.flags == KVMFR_CLIPBOARD_FLAG_BEGIN);
|
|
CHECK(record.sequence == 0 && record.offset == 0 && record.size == 5);
|
|
CHECK(memcmp(data, first, sizeof(first)) == 0);
|
|
CHECK(releaseClientStream(state, &buffer));
|
|
|
|
const uint8_t second[] = { 4, 5 };
|
|
CHECK(state->ops->dataChunk(state->clipboard, transfer,
|
|
3, second, sizeof(second)) == LG_CLIPBOARD_RESULT_ACCEPTED);
|
|
CHECK(checkDataRecord(state, &record, &buffer, &data));
|
|
CHECK(record.flags == 0 && record.sequence == 1 && record.offset == 3);
|
|
CHECK(record.size == KVMFR_CLIPBOARD_SIZE_UNKNOWN);
|
|
CHECK(memcmp(data, second, sizeof(second)) == 0);
|
|
CHECK(releaseClientStream(state, &buffer));
|
|
|
|
CHECK(state->ops->dataEnd(state->clipboard, transfer, 5) ==
|
|
LG_CLIPBOARD_RESULT_ACCEPTED);
|
|
CHECK(checkDataRecord(state, &record, &buffer, &data));
|
|
CHECK(record.flags == KVMFR_CLIPBOARD_FLAG_END);
|
|
CHECK(record.sequence == 2 && record.offset == 5 && record.size == 5);
|
|
CHECK(record.length == 0);
|
|
CHECK(releaseClientStream(state, &buffer));
|
|
|
|
KVMFRClipboardMessage requestRecord =
|
|
hostRecord(KVMFR_CLIPBOARD_MESSAGE_REQUEST,
|
|
claimRecord.generation);
|
|
requestRecord.clipboardGeneration = record.clipboardGeneration;
|
|
requestRecord.transfer = KVMFR_CLIPBOARD_TRANSFER_HELPER | 2;
|
|
requestRecord.format = KVMFR_CLIPBOARD_FORMAT_TEXT;
|
|
CHECK(postRecord(state, &requestRecord,
|
|
KVMFR_CLIPBOARD_QUEUE_MESSAGE, NULL));
|
|
CHECK(waitAtomic(state, &state->events.request, 2));
|
|
CHECK(state->ops->dataBegin(state->clipboard, requestRecord.transfer,
|
|
LG_CLIPBOARD_DATA_TEXT, 0) == LG_CLIPBOARD_RESULT_ACCEPTED);
|
|
CHECK(state->ops->dataEnd(state->clipboard,
|
|
requestRecord.transfer, 0) == LG_CLIPBOARD_RESULT_ACCEPTED);
|
|
CHECK(checkDataRecord(state, &record, &buffer, &data));
|
|
CHECK(record.flags == (KVMFR_CLIPBOARD_FLAG_BEGIN |
|
|
KVMFR_CLIPBOARD_FLAG_END));
|
|
CHECK(record.sequence == 0 && record.offset == 0 && record.size == 0);
|
|
CHECK(releaseClientStream(state, &buffer));
|
|
return true;
|
|
}
|
|
|
|
static bool testBlocked(TestState * state)
|
|
{
|
|
KVMFRClipboardMessage claimRecord;
|
|
CHECK(prepareOutbound(state, 50, &claimRecord, 3));
|
|
const LG_ClipboardRequest transfer = state->events.requestID;
|
|
CHECK(state->ops->dataBegin(state->clipboard, transfer,
|
|
LG_CLIPBOARD_DATA_TEXT, 5) == LG_CLIPBOARD_RESULT_ACCEPTED);
|
|
const uint8_t bytes[] = { 8, 9, 10, 11, 12 };
|
|
for (unsigned i = 0; i < KVMFR_CLIPBOARD_STREAM_SLOT_COUNT; ++i)
|
|
CHECK(state->ops->dataChunk(state->clipboard, transfer,
|
|
i, &bytes[i], 1) == LG_CLIPBOARD_RESULT_ACCEPTED);
|
|
CHECK(state->ops->dataChunk(state->clipboard, transfer,
|
|
4, &bytes[4], 1) == LG_CLIPBOARD_RESULT_BLOCKED);
|
|
|
|
KVMFRClipboardMessage record;
|
|
LGMPStreamBuffer buffer;
|
|
const void * data = NULL;
|
|
CHECK(checkDataRecord(state, &record, &buffer, &data));
|
|
CHECK(record.sequence == 0 && record.offset == 0 && record.length == 1);
|
|
CHECK(record.flags == KVMFR_CLIPBOARD_FLAG_BEGIN);
|
|
CHECK(*(const uint8_t *)data == bytes[0]);
|
|
CHECK(releaseClientStream(state, &buffer));
|
|
CHECK(waitAtomic(state, &state->events.dataReady, 1));
|
|
CHECK(state->events.streamID == transfer);
|
|
CHECK(state->ops->dataChunk(state->clipboard, transfer,
|
|
4, &bytes[4], 1) == LG_CLIPBOARD_RESULT_ACCEPTED);
|
|
for (unsigned i = 1; i < 5; ++i)
|
|
{
|
|
CHECK(checkDataRecord(state, &record, &buffer, &data));
|
|
CHECK(record.sequence == i && record.offset == i &&
|
|
record.length == 1);
|
|
CHECK(*(const uint8_t *)data == bytes[i]);
|
|
CHECK(releaseClientStream(state, &buffer));
|
|
}
|
|
CHECK(state->ops->dataEnd(state->clipboard, transfer, 5) ==
|
|
LG_CLIPBOARD_RESULT_ACCEPTED);
|
|
CHECK(checkDataRecord(state, &record, &buffer, &data));
|
|
CHECK(record.flags == KVMFR_CLIPBOARD_FLAG_END);
|
|
CHECK(record.sequence == 5 && record.offset == 5 && record.size == 5);
|
|
CHECK(releaseClientStream(state, &buffer));
|
|
|
|
KVMFRClipboardMessage requestRecord;
|
|
CHECK(offerAndRequest(state, claimRecord.generation,
|
|
501, 5001, &requestRecord));
|
|
atomic_store(&state->events.blockChunkOnce, true);
|
|
KVMFRClipboardMessage wire =
|
|
hostRecord(KVMFR_CLIPBOARD_MESSAGE_DATA, claimRecord.generation);
|
|
wire.clipboardGeneration = 501;
|
|
wire.transfer = requestRecord.transfer;
|
|
wire.size = 1;
|
|
wire.format = KVMFR_CLIPBOARD_FORMAT_TEXT;
|
|
wire.flags = KVMFR_CLIPBOARD_FLAG_BEGIN |
|
|
KVMFR_CLIPBOARD_FLAG_END;
|
|
wire.length = 1;
|
|
CHECK(postStreamRecord(state, &wire, bytes));
|
|
CHECK(waitAtomic(state, &state->events.dataChunk, 1));
|
|
CHECK(atomic_load(&state->events.dataEnd) == 0);
|
|
CHECK(state->ops->dataReady(state->clipboard, 5001));
|
|
CHECK(waitAtomic(state, &state->events.dataEnd, 1));
|
|
CHECK(atomic_load(&state->events.dataChunk) == 2);
|
|
return true;
|
|
}
|
|
|
|
static bool testCancel(TestState * state)
|
|
{
|
|
KVMFRClipboardMessage claimRecord;
|
|
CHECK(prepareOutbound(state, 60, &claimRecord, 4));
|
|
const LG_ClipboardRequest outbound = state->events.requestID;
|
|
KVMFRClipboardMessage cancel =
|
|
hostRecord(KVMFR_CLIPBOARD_MESSAGE_CANCEL, claimRecord.generation);
|
|
cancel.clipboardGeneration = state->localClipboardGeneration;
|
|
cancel.transfer = outbound;
|
|
cancel.format = KVMFR_CLIPBOARD_FORMAT_TEXT;
|
|
cancel.token = LG_CLIPBOARD_CANCEL_ABORTED;
|
|
CHECK(postRecord(state, &cancel, KVMFR_CLIPBOARD_QUEUE_MESSAGE, NULL));
|
|
CHECK(waitAtomic(state, &state->events.requestCancel, 1));
|
|
CHECK(state->events.cancelID == outbound);
|
|
|
|
KVMFRClipboardMessage requestRecord;
|
|
CHECK(offerAndRequest(state, claimRecord.generation,
|
|
601, 6001, &requestRecord));
|
|
cancel.clipboardGeneration = 601;
|
|
cancel.transfer = requestRecord.transfer;
|
|
cancel.token = LG_CLIPBOARD_CANCEL_REPLACED;
|
|
CHECK(postRecord(state, &cancel, KVMFR_CLIPBOARD_QUEUE_MESSAGE, NULL));
|
|
CHECK(waitAtomic(state, &state->events.dataCancel, 1));
|
|
CHECK(state->events.cancelID == 6001);
|
|
CHECK(state->events.cancelReason == LG_CLIPBOARD_CANCEL_REPLACED);
|
|
|
|
KVMFRClipboardMessage outboundRequest =
|
|
hostRecord(KVMFR_CLIPBOARD_MESSAGE_REQUEST,
|
|
claimRecord.generation);
|
|
outboundRequest.clipboardGeneration = state->localClipboardGeneration;
|
|
outboundRequest.transfer = KVMFR_CLIPBOARD_TRANSFER_HELPER | 5;
|
|
outboundRequest.format = KVMFR_CLIPBOARD_FORMAT_TEXT;
|
|
CHECK(postRecord(state, &outboundRequest,
|
|
KVMFR_CLIPBOARD_QUEUE_MESSAGE, NULL));
|
|
CHECK(waitAtomic(state, &state->events.request, 2));
|
|
CHECK(state->ops->dataCancel(state->clipboard,
|
|
outboundRequest.transfer, LG_CLIPBOARD_CANCEL_ABORTED));
|
|
KVMFRClipboardMessage wireCancel;
|
|
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_CANCEL, &wireCancel));
|
|
CHECK(wireCancel.transfer == outboundRequest.transfer);
|
|
CHECK(wireCancel.clipboardGeneration ==
|
|
state->localClipboardGeneration);
|
|
CHECK(wireCancel.format == KVMFR_CLIPBOARD_FORMAT_TEXT);
|
|
CHECK(wireCancel.token == LG_CLIPBOARD_CANCEL_ABORTED);
|
|
return true;
|
|
}
|
|
|
|
static bool testRestart(TestState * state)
|
|
{
|
|
KVMFRClipboardMessage oldClaim;
|
|
CHECK(claim(state, 70, false, &oldClaim));
|
|
CHECK(own(state, 70, oldClaim.generation));
|
|
const LG_ClipboardData formats[] = { LG_CLIPBOARD_DATA_TEXT };
|
|
CHECK(state->ops->notifyTypes(state->clipboard, formats, 1));
|
|
KVMFRClipboardMessage oldOffer;
|
|
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_OFFER, &oldOffer));
|
|
|
|
CHECK(postStatus(state, 71, 0, 0, true, 0));
|
|
KVMFRClipboardMessage newClaim;
|
|
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_CLAIM, &newClaim));
|
|
CHECK(newClaim.generation != oldClaim.generation);
|
|
CHECK(newClaim.token == 71);
|
|
KVMFRClipboardMessage replay;
|
|
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_OFFER, &replay));
|
|
CHECK(replay.generation == newClaim.generation);
|
|
CHECK(replay.clipboardGeneration == oldOffer.clipboardGeneration);
|
|
CHECK(own(state, 71, newClaim.generation));
|
|
|
|
CHECK(postStatus(state, 71, 0, 0, true, 0));
|
|
KVMFRClipboardMessage reclaimed;
|
|
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_CLAIM, &reclaimed));
|
|
CHECK(reclaimed.generation != newClaim.generation);
|
|
CHECK(reclaimed.token == 71);
|
|
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(postStreamRecord(state, &malformed, &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(postStreamRecord(state, &malformed, &byte));
|
|
CHECK(settleHostStream(state));
|
|
CHECK(atomic_load(&state->events.fileCancel) == cancellations + 1);
|
|
return true;
|
|
}
|
|
|
|
static bool testFileStream(TestState * state)
|
|
{
|
|
static uint8_t bytes[KVMFR_CLIPBOARD_FILE_READ_BYTES];
|
|
for (size_t i = 0; i < sizeof(bytes); ++i)
|
|
bytes[i] = (uint8_t)(i * 131U + 17U);
|
|
|
|
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 = sizeof(bytes);
|
|
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 == sizeof(bytes));
|
|
CHECK(descriptor.operation == LG_CLIPBOARD_FILE_READ);
|
|
CHECK(state->ops->fileDataBegin(state->clipboard,
|
|
&descriptor, sizeof(bytes)) == LG_CLIPBOARD_RESULT_ACCEPTED);
|
|
|
|
for (unsigned i = 0; i < KVMFR_CLIPBOARD_STREAM_SLOT_COUNT; ++i)
|
|
{
|
|
const uint64_t offset =
|
|
(uint64_t)i * KVMFR_CLIPBOARD_STREAM_SLOT_BYTES;
|
|
CHECK(state->ops->fileDataChunk(state->clipboard,
|
|
&descriptor, offset, bytes + offset,
|
|
KVMFR_CLIPBOARD_STREAM_SLOT_BYTES,
|
|
i + 1 == KVMFR_CLIPBOARD_STREAM_SLOT_COUNT) ==
|
|
LG_CLIPBOARD_RESULT_ACCEPTED);
|
|
}
|
|
KVMFRClipboardMessage record;
|
|
LGMPStreamBuffer buffer;
|
|
const void * data = NULL;
|
|
for (unsigned i = 0; i < KVMFR_CLIPBOARD_STREAM_SLOT_COUNT; ++i)
|
|
{
|
|
const uint64_t offset =
|
|
(uint64_t)i * KVMFR_CLIPBOARD_STREAM_SLOT_BYTES;
|
|
CHECK(checkFileDataRecord(state, KVMFR_CLIPBOARD_FILE_OP_READ,
|
|
&record, &buffer, &data));
|
|
CHECK(record.offset == offset && record.sequence == i);
|
|
CHECK(record.length == KVMFR_CLIPBOARD_STREAM_SLOT_BYTES);
|
|
if (i == 0)
|
|
{
|
|
CHECK(record.flags == KVMFR_CLIPBOARD_FLAG_BEGIN);
|
|
CHECK(record.size == sizeof(bytes));
|
|
}
|
|
else if (i + 1 == KVMFR_CLIPBOARD_STREAM_SLOT_COUNT)
|
|
{
|
|
CHECK(record.flags == KVMFR_CLIPBOARD_FLAG_END);
|
|
CHECK(record.size == sizeof(bytes));
|
|
}
|
|
else
|
|
{
|
|
CHECK(record.flags == 0);
|
|
CHECK(record.size == KVMFR_CLIPBOARD_SIZE_UNKNOWN);
|
|
}
|
|
CHECK(memcmp(data, bytes + offset,
|
|
KVMFR_CLIPBOARD_STREAM_SLOT_BYTES) == 0);
|
|
CHECK(releaseClientStream(state, &buffer));
|
|
}
|
|
|
|
CHECK(state->ops->fileDataEnd(state->clipboard,
|
|
&descriptor, sizeof(bytes)) == LG_CLIPBOARD_RESULT_ACCEPTED);
|
|
CHECK(noClientControl(state));
|
|
|
|
const unsigned requests = atomic_load(&state->events.fileRequest);
|
|
request.transfer = KVMFR_CLIPBOARD_TRANSFER_HELPER | UINT64_C(0x7803);
|
|
request.offset = 0;
|
|
request.size = KVMFR_CLIPBOARD_FILE_ROOT_NODE;
|
|
request.flags = 0;
|
|
request.token = KVMFR_CLIPBOARD_FILE_OP_LIST;
|
|
CHECK(postRecord(state, &request,
|
|
KVMFR_CLIPBOARD_QUEUE_MESSAGE, NULL));
|
|
CHECK(waitAtomic(state, &state->events.fileRequest, requests + 1));
|
|
const LG_ClipboardFileRequest empty = state->events.fileRequestValue;
|
|
CHECK(empty.operation == LG_CLIPBOARD_FILE_LIST);
|
|
CHECK(state->ops->fileDataBegin(state->clipboard,
|
|
&empty, KVMFR_CLIPBOARD_SIZE_UNKNOWN) ==
|
|
LG_CLIPBOARD_RESULT_ACCEPTED);
|
|
CHECK(state->ops->fileDataEnd(state->clipboard,
|
|
&empty, 0) == LG_CLIPBOARD_RESULT_ACCEPTED);
|
|
CHECK(checkFileDataRecord(state, KVMFR_CLIPBOARD_FILE_OP_LIST,
|
|
&record, &buffer, &data));
|
|
CHECK(record.flags == (KVMFR_CLIPBOARD_FLAG_BEGIN |
|
|
KVMFR_CLIPBOARD_FLAG_END));
|
|
CHECK(record.offset == 0 && record.sequence == 0 && record.size == 0);
|
|
CHECK(record.length == 0);
|
|
CHECK(releaseClientStream(state, &buffer));
|
|
return true;
|
|
}
|
|
|
|
static void * disconnectThread(void * opaque)
|
|
{
|
|
DisconnectTask * task = opaque;
|
|
lgmpClipboard_disconnect(task->clipboard);
|
|
atomic_store(&task->done, true);
|
|
return NULL;
|
|
}
|
|
|
|
static bool disconnectAndDrain(TestState * state,
|
|
uint32_t * releaseGeneration)
|
|
{
|
|
DisconnectTask task = { .clipboard = state->clipboard };
|
|
atomic_init(&task.done, false);
|
|
pthread_t thread;
|
|
CHECK(pthread_create(&thread, NULL, disconnectThread, &task) == 0);
|
|
bool valid = true;
|
|
for (unsigned i = 0; i < TEST_WAIT_MS && !atomic_load(&task.done); ++i)
|
|
{
|
|
if (lgmpHostProcess(state->host) != LGMP_OK)
|
|
valid = false;
|
|
for (;;)
|
|
{
|
|
KVMFRClipboardMessage record;
|
|
size_t size = sizeof(record);
|
|
uint32_t clientID = 0;
|
|
const LGMP_STATUS status = lgmpHostReadDataWithSource(
|
|
state->queue, &record, &size, &clientID);
|
|
if (status == LGMP_ERR_QUEUE_EMPTY)
|
|
break;
|
|
if (status != LGMP_OK)
|
|
{
|
|
valid = false;
|
|
break;
|
|
}
|
|
valid &= size == sizeof(record);
|
|
valid &= clientID == state->clientID;
|
|
if (record.type == KVMFR_CLIPBOARD_MESSAGE_RELEASE &&
|
|
releaseGeneration)
|
|
*releaseGeneration = record.generation;
|
|
if (lgmpHostAckData(state->queue) != LGMP_OK)
|
|
valid = false;
|
|
}
|
|
usleep(1000);
|
|
}
|
|
valid &= pthread_join(thread, NULL) == 0;
|
|
valid &= atomic_load(&task.done);
|
|
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);
|
|
KVMFRClipboardMessage release;
|
|
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_RELEASE, &release));
|
|
CHECK(release.generation != 0);
|
|
const uint32_t endpointGeneration =
|
|
atomic_load(&state->statusGeneration);
|
|
CHECK(endpointGeneration != 0);
|
|
CHECK(postStatus(state, endpointGeneration, 0, 0, true, 0));
|
|
}
|
|
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(postStreamRecord(state, &staleData, NULL));
|
|
CHECK(settleHostStream(state));
|
|
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(postStreamRecord(state, &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;
|
|
CHECK(claim(state, 80, false, &claimRecord));
|
|
CHECK(own(state, 80, claimRecord.generation));
|
|
uint32_t releaseGeneration = 0;
|
|
CHECK(disconnectAndDrain(state, &releaseGeneration));
|
|
CHECK(releaseGeneration == claimRecord.generation);
|
|
return true;
|
|
}
|
|
|
|
static bool testMalformed(TestState * state)
|
|
{
|
|
KVMFRClipboardMessage claimRecord;
|
|
CHECK(claim(state, 90, false, &claimRecord));
|
|
CHECK(own(state, 90, claimRecord.generation));
|
|
|
|
const unsigned statusCount = atomic_load(&state->statusCount);
|
|
PLGMPMemory badStatusMemory;
|
|
CHECK(allocMemory(state, sizeof(KVMFRClipboardStatus),
|
|
&badStatusMemory));
|
|
KVMFRClipboardStatus * badStatus = lgmpHostMemPtr(badStatusMemory);
|
|
*badStatus = (KVMFRClipboardStatus)
|
|
{
|
|
.version = KVMFR_CLIPBOARD_VERSION,
|
|
.flags = KVMFR_CLIPBOARD_STATUS_AVAILABLE,
|
|
.generation = 91,
|
|
.lease = 1000,
|
|
};
|
|
CHECK(postMemory(state, badStatusMemory,
|
|
KVMFR_CLIPBOARD_QUEUE_STATUS, nextSerial(state)));
|
|
CHECK(waitMemory(state, badStatusMemory));
|
|
CHECK(atomic_load(&state->statusCount) == statusCount);
|
|
|
|
KVMFRClipboardMessage malformed =
|
|
hostRecord(KVMFR_CLIPBOARD_MESSAGE_OFFER, claimRecord.generation + 1);
|
|
malformed.clipboardGeneration = 900;
|
|
malformed.token = KVMFR_CLIPBOARD_FORMAT_MASK_TEXT;
|
|
CHECK(postRecord(state, &malformed,
|
|
KVMFR_CLIPBOARD_QUEUE_MESSAGE, NULL));
|
|
CHECK(waitMemory(state, state->memories[state->memoryCount - 1]));
|
|
CHECK(atomic_load(&state->events.notice) == 0);
|
|
|
|
malformed.generation = claimRecord.generation;
|
|
CHECK(postStreamRecord(state, &malformed, NULL));
|
|
CHECK(settleHostStream(state));
|
|
CHECK(atomic_load(&state->events.notice) == 0);
|
|
|
|
KVMFRClipboardMessage requestRecord;
|
|
CHECK(offerAndRequest(state, claimRecord.generation,
|
|
901, 9001, &requestRecord));
|
|
KVMFRClipboardMessage data =
|
|
hostRecord(KVMFR_CLIPBOARD_MESSAGE_DATA, claimRecord.generation);
|
|
data.clipboardGeneration = 901;
|
|
data.transfer = requestRecord.transfer;
|
|
data.offset = 1;
|
|
data.size = 1;
|
|
data.format = KVMFR_CLIPBOARD_FORMAT_TEXT;
|
|
data.flags = KVMFR_CLIPBOARD_FLAG_BEGIN |
|
|
KVMFR_CLIPBOARD_FLAG_END;
|
|
CHECK(postStreamRecord(state, &data, NULL));
|
|
CHECK(settleHostStream(state));
|
|
CHECK(atomic_load(&state->events.dataBegin) == 0);
|
|
|
|
const uint8_t byte = 42;
|
|
data.offset = 0;
|
|
data.length = 1;
|
|
CHECK(postStreamRecord(state, &data, &byte));
|
|
CHECK(waitAtomic(state, &state->events.dataEnd, 1));
|
|
CHECK(atomic_load(&state->events.dataBegin) == 1);
|
|
CHECK(atomic_load(&state->events.dataChunk) == 1);
|
|
CHECK(state->events.streamID == 9001);
|
|
CHECK(state->events.chunkData[0] == byte);
|
|
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(noClientControl(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(noClientControl(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(postStreamRecord(state, &data, NULL));
|
|
CHECK(settleHostStream(state));
|
|
CHECK(atomic_load(&state->events.dataBegin) == 0);
|
|
return true;
|
|
}
|
|
|
|
static bool stateInit(TestState * state)
|
|
{
|
|
memset(state, 0, sizeof(*state));
|
|
state->memory = MAP_FAILED;
|
|
atomic_init(&state->statusCount, 0);
|
|
atomic_init(&state->statusAvailable, false);
|
|
atomic_init(&state->statusGeneration, 0);
|
|
atomic_init(&state->autoAttach, false);
|
|
atomic_init(&state->events.notice, 0);
|
|
atomic_init(&state->events.release, 0);
|
|
atomic_init(&state->events.request, 0);
|
|
atomic_init(&state->events.requestCancel, 0);
|
|
atomic_init(&state->events.dataBegin, 0);
|
|
atomic_init(&state->events.dataChunk, 0);
|
|
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);
|
|
CHECK(state->memory != MAP_FAILED);
|
|
const uint32_t sessionData = UINT32_C(0x12345678);
|
|
CHECK(lgmpHostInit(state->memory, TEST_SHM_SIZE, &state->host,
|
|
sizeof(sessionData), (uint8_t *)&sessionData) == LGMP_OK);
|
|
const struct LGMPQueueConfig config =
|
|
{
|
|
.queueID = LGMP_Q_CLIPBOARD,
|
|
.numMessages = LGMP_Q_CLIPBOARD_LEN,
|
|
.subTimeout = 1000,
|
|
};
|
|
CHECK(lgmpHostQueueNew(state->host, config, &state->queue) == LGMP_OK);
|
|
CHECK(lgmpClientInit(state->memory, TEST_SHM_SIZE, &state->client) ==
|
|
LGMP_OK);
|
|
usleep(300000);
|
|
CHECK(hostProcess(state));
|
|
uint32_t dataSize;
|
|
uint32_t remoteVersion;
|
|
uint8_t * data;
|
|
CHECK(lgmpClientSessionInit(state->client, &dataSize, &data,
|
|
&state->clientID, &remoteVersion) == LGMP_OK);
|
|
CHECK(state->clientID != 0);
|
|
CHECK(dataSize == sizeof(sessionData));
|
|
CHECK(memcmp(data, &sessionData, sizeof(sessionData)) == 0);
|
|
|
|
const struct LGMPStreamConfig hostToClientConfig =
|
|
{
|
|
.direction = LGMP_STREAM_HOST_TO_CLIENT,
|
|
.policy = LGMP_STREAM_RELIABLE_FIFO,
|
|
.slotCount = KVMFR_CLIPBOARD_STREAM_SLOT_COUNT,
|
|
.slotSize = SLOT_BYTES,
|
|
};
|
|
CHECK(lgmpHostStreamNew(state->host, hostToClientConfig,
|
|
&state->hostToClientStream) == LGMP_OK);
|
|
struct LGMPStreamDescriptor descriptor;
|
|
lgmpHostStreamGetDescriptor(state->hostToClientStream, &descriptor);
|
|
state->hostToClientDescriptor = exportStreamDescriptor(&descriptor);
|
|
|
|
const struct LGMPStreamConfig clientToHostConfig =
|
|
{
|
|
.direction = LGMP_STREAM_CLIENT_TO_HOST,
|
|
.policy = LGMP_STREAM_RELIABLE_FIFO,
|
|
.slotCount = KVMFR_CLIPBOARD_STREAM_SLOT_COUNT,
|
|
.slotSize = SLOT_BYTES,
|
|
};
|
|
CHECK(lgmpHostStreamNew(state->host, clientToHostConfig,
|
|
&state->clientToHostStream) == LGMP_OK);
|
|
lgmpHostStreamGetDescriptor(state->clientToHostStream, &descriptor);
|
|
state->clientToHostDescriptor = exportStreamDescriptor(&descriptor);
|
|
|
|
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);
|
|
CHECK(atomic_load(&state->statusCount) == 1);
|
|
CHECK(!atomic_load(&state->statusAvailable));
|
|
|
|
for (unsigned i = 0; i < TEST_WAIT_MS; ++i)
|
|
{
|
|
CHECK(hostProcess(state));
|
|
if (lgmpHostQueueHasSubs(state->queue))
|
|
return true;
|
|
usleep(1000);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void stateFree(TestState * state)
|
|
{
|
|
if (state->clipboard)
|
|
{
|
|
state->ops->setStatusListener(state->clipboard, NULL, NULL);
|
|
if (atomic_load(&state->events.attached))
|
|
state->ops->detach(state->clipboard);
|
|
disconnectAndDrain(state, NULL);
|
|
lgmpClipboard_destroy(&state->clipboard);
|
|
}
|
|
lgmpClientFree(&state->client);
|
|
for (unsigned i = 0; i < state->memoryCount; ++i)
|
|
lgmpHostMemFree(&state->memories[i]);
|
|
lgmpHostStreamFree(&state->clientToHostStream);
|
|
lgmpHostStreamFree(&state->hostToClientStream);
|
|
lgmpHostFree(&state->host);
|
|
if (state->memory != MAP_FAILED)
|
|
munmap(state->memory, TEST_SHM_SIZE);
|
|
}
|
|
|
|
typedef bool (*TestFn)(TestState * state);
|
|
|
|
static const struct
|
|
{
|
|
const char * name;
|
|
TestFn run;
|
|
}
|
|
TESTS[] =
|
|
{
|
|
{ "claim" , testClaim },
|
|
{ "offer-request" , testOfferRequest },
|
|
{ "inbound-stream" , testInboundStream },
|
|
{ "outbound-stream" , testOutboundStream },
|
|
{ "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[])
|
|
{
|
|
if (argc != 2)
|
|
{
|
|
fprintf(stderr, "usage: %s <test-case>\n", argv[0]);
|
|
return 2;
|
|
}
|
|
TestFn test = NULL;
|
|
for (unsigned i = 0; i < sizeof(TESTS) / sizeof(TESTS[0]); ++i)
|
|
if (strcmp(argv[1], TESTS[i].name) == 0)
|
|
{
|
|
test = TESTS[i].run;
|
|
break;
|
|
}
|
|
if (!test)
|
|
{
|
|
fprintf(stderr, "unknown test case: %s\n", argv[1]);
|
|
return 2;
|
|
}
|
|
|
|
debug_init();
|
|
TestState state = { 0 };
|
|
state.memory = MAP_FAILED;
|
|
const bool initialized = stateInit(&state);
|
|
const bool passed = initialized && test(&state);
|
|
stateFree(&state);
|
|
return passed ? 0 : 1;
|
|
}
|