mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 15:11:31 +00:00
1079 lines
36 KiB
C
1079 lines
36 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 "../transports/LGMP/clipboard.h"
|
|
|
|
#include "common/KVMFRClipboard.h"
|
|
#include "common/LGMPConfig.h"
|
|
#include "common/debug.h"
|
|
|
|
#include <lgmp/client.h>
|
|
#include <lgmp/host.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_SHM_SIZE (8U * 1024U * 1024U)
|
|
#define TEST_WAIT_MS 2500U
|
|
#define TEST_QUIET_MS 50U
|
|
#define TEST_MEMORY_MAX 128U
|
|
#define SLOT_BYTES \
|
|
(sizeof(KVMFRClipboardSlotHeader) + KVMFR_CLIPBOARD_DATA_BYTES)
|
|
|
|
#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_bool acceptRequest;
|
|
atomic_bool blockChunkOnce;
|
|
atomic_bool attached;
|
|
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;
|
|
}
|
|
EventTrace;
|
|
|
|
typedef struct TestState
|
|
{
|
|
void * memory;
|
|
PLGMPHost host;
|
|
PLGMPHostQueue queue;
|
|
PLGMPClient client;
|
|
LGMPClipboard * clipboard;
|
|
const LG_ClipboardOps * ops;
|
|
uint32_t clientID;
|
|
uint32_t hostSerial;
|
|
PLGMPMemory memories[TEST_MEMORY_MAX];
|
|
unsigned memoryCount;
|
|
PLGMPMemory grantMemory[KVMFR_CLIPBOARD_SLOT_COUNT];
|
|
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;
|
|
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 const LG_ClipboardEventOps EVENT_OPS =
|
|
{
|
|
.notice = notice,
|
|
.dataBegin = dataBegin,
|
|
.dataChunk = dataChunk,
|
|
.dataEnd = dataEnd,
|
|
.dataCancel = dataCancelEvent,
|
|
.dataReady = dataReadyEvent,
|
|
.requestCancel = requestCancelEvent,
|
|
.release = releaseNotice,
|
|
.request = request,
|
|
};
|
|
|
|
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 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 postStatus(TestState * state, uint32_t endpointGeneration,
|
|
uint32_t ownerClientID, uint32_t ownerGeneration,
|
|
bool available, KVMFRClipboardFormatFlags formats)
|
|
{
|
|
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_DATA_BYTES,
|
|
};
|
|
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)
|
|
{
|
|
const size_t bytes = type == KVMFR_CLIPBOARD_QUEUE_DATA ?
|
|
SLOT_BYTES : sizeof(*record);
|
|
PLGMPMemory memory;
|
|
CHECK(allocMemory(state, bytes, &memory));
|
|
memcpy(lgmpHostMemPtr(memory), record, sizeof(*record));
|
|
if (record->length && data)
|
|
memcpy((uint8_t *)lgmpHostMemPtr(memory) + sizeof(*record),
|
|
data, record->length);
|
|
CHECK(postMemory(state, memory, type, nextSerial(state)));
|
|
return true;
|
|
}
|
|
|
|
static bool postGrant(TestState * state, uint32_t generation,
|
|
uint32_t token)
|
|
{
|
|
CHECK(token >= 1 && token <= KVMFR_CLIPBOARD_SLOT_COUNT);
|
|
PLGMPMemory * memory = &state->grantMemory[token - 1];
|
|
if (!*memory)
|
|
CHECK(allocMemory(state, SLOT_BYTES, memory));
|
|
else
|
|
CHECK(waitMemory(state, *memory));
|
|
|
|
KVMFRClipboardSlotHeader * header = lgmpHostMemPtr(*memory);
|
|
memset(header, 0, sizeof(*header));
|
|
header->version = KVMFR_CLIPBOARD_VERSION;
|
|
header->type = KVMFR_CLIPBOARD_MESSAGE_GRANT;
|
|
header->generation = generation;
|
|
header->size = KVMFR_CLIPBOARD_DATA_BYTES;
|
|
header->token = token;
|
|
return postMemory(state, *memory,
|
|
KVMFR_CLIPBOARD_QUEUE_GRANT, token);
|
|
}
|
|
|
|
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(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 noClientData(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);
|
|
return true;
|
|
}
|
|
|
|
static bool own(TestState * state, uint32_t endpointGeneration,
|
|
uint32_t claimGeneration)
|
|
{
|
|
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));
|
|
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(postRecord(state, &data, KVMFR_CLIPBOARD_QUEUE_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(postRecord(state, &data, KVMFR_CLIPBOARD_QUEUE_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(postRecord(state, &data, KVMFR_CLIPBOARD_QUEUE_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(postRecord(state, &data, KVMFR_CLIPBOARD_QUEUE_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));
|
|
|
|
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 checkCommit(TestState * state, uint32_t token,
|
|
KVMFRClipboardMessage * commit, KVMFRClipboardMessage * slot,
|
|
const void ** data)
|
|
{
|
|
CHECK(readType(state, KVMFR_CLIPBOARD_MESSAGE_COMMIT, commit));
|
|
CHECK(commit->token == token);
|
|
*slot = *(KVMFRClipboardMessage *)
|
|
lgmpHostMemPtr(state->grantMemory[token - 1]);
|
|
CHECK(slot->type == KVMFR_CLIPBOARD_MESSAGE_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);
|
|
*data = (const uint8_t *)lgmpHostMemPtr(
|
|
state->grantMemory[token - 1]) + sizeof(*slot);
|
|
return true;
|
|
}
|
|
|
|
static bool testOutboundGrant(TestState * state)
|
|
{
|
|
KVMFRClipboardMessage claimRecord;
|
|
CHECK(prepareOutbound(state, 40, &claimRecord, 1));
|
|
const LG_ClipboardRequest transfer = state->events.requestID;
|
|
CHECK(postGrant(state, claimRecord.generation, 1));
|
|
CHECK(waitMemory(state, state->grantMemory[0]));
|
|
|
|
CHECK(state->ops->dataBegin(state->clipboard, transfer,
|
|
LG_CLIPBOARD_DATA_TEXT, 5) == LG_CLIPBOARD_RESULT_ACCEPTED);
|
|
CHECK(noClientData(state));
|
|
const uint8_t first[] = { 1, 2, 3 };
|
|
CHECK(state->ops->dataChunk(state->clipboard, transfer,
|
|
0, first, sizeof(first)) == LG_CLIPBOARD_RESULT_ACCEPTED);
|
|
KVMFRClipboardMessage commit;
|
|
KVMFRClipboardMessage slot;
|
|
const void * data;
|
|
CHECK(checkCommit(state, 1, &commit, &slot, &data));
|
|
CHECK(slot.flags == KVMFR_CLIPBOARD_FLAG_BEGIN);
|
|
CHECK(slot.sequence == 0 && slot.offset == 0 && slot.size == 5);
|
|
CHECK(memcmp(data, first, sizeof(first)) == 0);
|
|
|
|
CHECK(postGrant(state, claimRecord.generation, 1));
|
|
CHECK(waitMemory(state, state->grantMemory[0]));
|
|
const uint8_t second[] = { 4, 5 };
|
|
CHECK(state->ops->dataChunk(state->clipboard, transfer,
|
|
3, second, sizeof(second)) == LG_CLIPBOARD_RESULT_ACCEPTED);
|
|
CHECK(checkCommit(state, 1, &commit, &slot, &data));
|
|
CHECK(slot.flags == 0 && slot.sequence == 1 && slot.offset == 3);
|
|
CHECK(slot.size == KVMFR_CLIPBOARD_SIZE_UNKNOWN);
|
|
CHECK(memcmp(data, second, sizeof(second)) == 0);
|
|
|
|
CHECK(postGrant(state, claimRecord.generation, 1));
|
|
CHECK(waitMemory(state, state->grantMemory[0]));
|
|
CHECK(state->ops->dataEnd(state->clipboard, transfer, 5) ==
|
|
LG_CLIPBOARD_RESULT_ACCEPTED);
|
|
CHECK(checkCommit(state, 1, &commit, &slot, &data));
|
|
CHECK(slot.flags == KVMFR_CLIPBOARD_FLAG_END);
|
|
CHECK(slot.sequence == 2 && slot.offset == 5 && slot.size == 5);
|
|
CHECK(slot.length == 0);
|
|
|
|
KVMFRClipboardMessage requestRecord =
|
|
hostRecord(KVMFR_CLIPBOARD_MESSAGE_REQUEST,
|
|
claimRecord.generation);
|
|
requestRecord.clipboardGeneration = slot.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(postGrant(state, claimRecord.generation, 1));
|
|
CHECK(waitMemory(state, state->grantMemory[0]));
|
|
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(checkCommit(state, 1, &commit, &slot, &data));
|
|
CHECK(slot.flags == (KVMFR_CLIPBOARD_FLAG_BEGIN |
|
|
KVMFR_CLIPBOARD_FLAG_END));
|
|
CHECK(slot.sequence == 0 && slot.offset == 0 && slot.size == 0);
|
|
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, 2) == LG_CLIPBOARD_RESULT_ACCEPTED);
|
|
const uint8_t bytes[] = { 8, 9 };
|
|
CHECK(state->ops->dataChunk(state->clipboard, transfer,
|
|
0, bytes, sizeof(bytes)) == LG_CLIPBOARD_RESULT_BLOCKED);
|
|
CHECK(postGrant(state, claimRecord.generation, 1));
|
|
CHECK(waitAtomic(state, &state->events.dataReady, 1));
|
|
CHECK(state->events.streamID == transfer);
|
|
CHECK(state->ops->dataChunk(state->clipboard, transfer,
|
|
0, bytes, sizeof(bytes)) == LG_CLIPBOARD_RESULT_ACCEPTED);
|
|
KVMFRClipboardMessage commit;
|
|
KVMFRClipboardMessage slot;
|
|
const void * data;
|
|
CHECK(checkCommit(state, 1, &commit, &slot, &data));
|
|
|
|
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(postRecord(state, &wire, KVMFR_CLIPBOARD_QUEUE_DATA, 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 = 1;
|
|
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 = 1;
|
|
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 == 1);
|
|
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 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 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(postRecord(state, &malformed, KVMFR_CLIPBOARD_QUEUE_DATA, NULL));
|
|
CHECK(waitMemory(state, state->memories[state->memoryCount - 1]));
|
|
CHECK(atomic_load(&state->events.notice) == 0);
|
|
|
|
CHECK(postGrant(state, claimRecord.generation, 1));
|
|
CHECK(waitMemory(state, state->grantMemory[0]));
|
|
CHECK(postGrant(state, claimRecord.generation, 1));
|
|
CHECK(waitMemory(state, state->grantMemory[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(postRecord(state, &data, KVMFR_CLIPBOARD_QUEUE_DATA, NULL));
|
|
CHECK(waitMemory(state, state->memories[state->memoryCount - 1]));
|
|
CHECK(atomic_load(&state->events.dataBegin) == 0);
|
|
|
|
const uint8_t byte = 42;
|
|
data.offset = 0;
|
|
data.length = 1;
|
|
CHECK(postRecord(state, &data, KVMFR_CLIPBOARD_QUEUE_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 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.acceptRequest, true);
|
|
atomic_init(&state->events.blockChunkOnce, false);
|
|
atomic_init(&state->events.attached, 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);
|
|
|
|
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]);
|
|
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-grant", testOutboundGrant },
|
|
{ "blocked" , testBlocked },
|
|
{ "cancel" , testCancel },
|
|
{ "restart" , testRestart },
|
|
{ "disconnect" , testDisconnect },
|
|
{ "malformed" , testMalformed },
|
|
};
|
|
|
|
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;
|
|
}
|