Files
LookingGlass/client/transports/Network/clipboard.c
2026-11-06 14:37:38 +11:00

4420 lines
143 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/debug.h"
#include "common/locking.h"
#include "common/thread.h"
#include "common/time.h"
#include <LGProtocol/KVMFRClipboard.h>
#include <LGProtocol/NetworkClipboard.h>
#include <LGProtocol/NetworkServices.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#define NETWORK_CLIPBOARD_TIMER_MS 20U
#define NETWORK_CLIPBOARD_LEASE_MS 2000U
#define NETWORK_CLIPBOARD_KEEPALIVE_MS 750U
#define NETWORK_CLIPBOARD_CLAIM_RETRY_MS 500U
#define NETWORK_CLIPBOARD_PENDING_MAX 128U
#define NETWORK_CLIPBOARD_PENDING_BYTES (8U * 1024U * 1024U)
#define NETWORK_CLIPBOARD_INBOUND_MAX 128U
#define NETWORK_CLIPBOARD_INBOUND_BYTES (8U * 1024U * 1024U)
#define NETWORK_CLIPBOARD_PROCESS_MAX 64U
#define NETWORK_CLIPBOARD_EMITTED_CHUNK (256U * 1024U)
#define NETWORK_CLIPBOARD_FILE_READ_MAX (1024U * 1024U)
#define NETWORK_CLIPBOARD_FILE_ACQUISITIONS 8U
#define NETWORK_CLIPBOARD_FILE_REQUESTS 32U
#define NETWORK_CLIPBOARD_FORMATS \
LG_NET_CLIPBOARD_KVMFR_FORMAT_COUNT
#define NETWORK_CLIPBOARD_CLAIM_FLAGS \
(LG_NET_CLIPBOARD_CLAIM_READ | LG_NET_CLIPBOARD_CLAIM_WRITE)
struct NetworkClipboardSend
{
struct NetworkClipboardSend * next;
LGNetService service;
uint16_t messageType;
uint16_t serviceVersion;
LGNetEnvelopeFlags flags;
NetworkPacketDelivery delivery;
uint64_t componentEpoch;
uint64_t requestID;
uint32_t payloadLength;
uint8_t payload[];
};
struct NetworkClipboardBatch
{
struct NetworkClipboardSend * head;
struct NetworkClipboardSend * tail;
unsigned count;
size_t bytes;
};
struct NetworkClipboardInbound
{
struct NetworkClipboardInbound * next;
LGNetEnvelope envelope;
NetworkPacketDelivery delivery;
size_t payloadLength;
uint8_t payload[];
};
struct NetworkClipboardTransfer
{
struct NetworkClipboardTransfer * next;
LG_ClipboardRequest localRequest;
uint64_t wireRequest;
uint64_t offerID;
LG_ClipboardData type;
uint32_t formatIndex;
uint32_t maxChunkLength;
uint64_t sizeHint;
uint64_t offset;
NetworkPacketDelivery delivery;
bool began;
bool blocked;
};
struct NetworkFileTransfer
{
struct NetworkFileTransfer * next;
LG_ClipboardFileRequest request;
uint64_t wireRequest;
uint64_t entryID;
uint64_t sizeHint;
uint64_t responseOffset;
NetworkPacketDelivery delivery;
bool began;
bool blocked;
bool wireEnded;
};
struct NetworkFileAcquisition
{
struct NetworkFileAcquisition * next;
uint64_t dataset;
uint64_t localAcquisition;
uint64_t wireClientID;
uint64_t leaseEpoch;
bool fromRemote;
bool active;
};
struct NetworkRemoteOffer
{
uint64_t epoch;
uint64_t offerID;
uint64_t serial;
uint32_t formatCount;
uint32_t received;
uint32_t formatMask;
LG_ClipboardData types[NETWORK_CLIPBOARD_FORMATS];
uint32_t indices[LG_CLIPBOARD_DATA_NONE];
bool complete;
bool published;
bool fileOffer;
};
struct NetworkLocalOffer
{
uint64_t offerID;
uint64_t serial;
uint32_t count;
LG_ClipboardData types[NETWORK_CLIPBOARD_FORMATS];
uint32_t indices[LG_CLIPBOARD_DATA_NONE];
bool files;
};
struct NetworkClipboard
{
NetworkConnection * connection;
LGTimer * timer;
LG_Lock stateLock;
LG_Lock eventLock;
LG_Lock statusLock;
bool destroying;
bool connected;
bool clipboardService;
bool fileService;
bool clipboardBulk;
bool fileBulk;
bool available;
bool attached;
bool processing;
bool claimPending;
bool claimed;
bool inboundBlocked;
bool inboundReady;
bool sendProgress;
LG_ClipboardRequest inboundBlockedRequest;
uint16_t clipboardVersion;
uint16_t fileVersion;
uint32_t statusGeneration;
bool statusNotifyPending;
uint64_t ownerID;
uint64_t claimEpoch;
uint64_t claimSerial;
uint64_t lastClaimStatusSerial;
uint64_t nextClaimNs;
uint64_t nextKeepaliveNs;
uint64_t localSerial;
uint64_t wireSerial;
uint64_t remoteSerial;
struct NetworkClipboardSend * sendHead;
struct NetworkClipboardSend * sendTail;
unsigned sendCount;
size_t sendBytes;
struct NetworkClipboardInbound * inboundHead;
struct NetworkClipboardInbound * inboundTail;
unsigned inboundCount;
size_t inboundBytes;
struct NetworkClipboardTransfer * clipboardReads;
struct NetworkClipboardTransfer * clipboardWrites;
struct NetworkFileTransfer * fileReads;
struct NetworkFileTransfer * fileWrites;
struct NetworkFileAcquisition * fileAcquisitions;
struct NetworkRemoteOffer remoteOffer;
struct NetworkLocalOffer localOffer;
const LG_ClipboardEventOps * events;
void * eventOpaque;
LG_ClipboardStatusFn statusCallback;
void * statusOpaque;
};
static uint64_t networkClipboardNext64(uint64_t value)
{
++value;
return value ? value : 1U;
}
static uint32_t networkClipboardNext32(uint32_t value)
{
++value;
return value ? value : 1U;
}
static uint64_t networkClipboardDeadline(uint32_t milliseconds)
{
return nanotime() + (uint64_t)milliseconds * UINT64_C(1000000);
}
static uint64_t networkClipboardNextLocalIDNL(
NetworkClipboard * clipboard, bool remote)
{
clipboard->localSerial = networkClipboardNext64(clipboard->localSerial);
clipboard->localSerial &= ~KVMFR_CLIPBOARD_TRANSFER_HELPER;
if (!clipboard->localSerial)
clipboard->localSerial = 1;
return remote ? clipboard->localSerial |
KVMFR_CLIPBOARD_TRANSFER_HELPER : clipboard->localSerial;
}
static uint64_t networkClipboardNextWireIDNL(
NetworkClipboard * clipboard)
{
clipboard->wireSerial = networkClipboardNext64(clipboard->wireSerial);
return clipboard->wireSerial;
}
static LG_ClipboardData networkClipboardDataFromFormat(
KVMFRClipboardFormat format)
{
switch (format)
{
case KVMFR_CLIPBOARD_FORMAT_TEXT : return LG_CLIPBOARD_DATA_TEXT;
case KVMFR_CLIPBOARD_FORMAT_PNG : return LG_CLIPBOARD_DATA_PNG;
case KVMFR_CLIPBOARD_FORMAT_BMP : return LG_CLIPBOARD_DATA_BMP;
case KVMFR_CLIPBOARD_FORMAT_TIFF : return LG_CLIPBOARD_DATA_TIFF;
case KVMFR_CLIPBOARD_FORMAT_JPEG : return LG_CLIPBOARD_DATA_JPEG;
case KVMFR_CLIPBOARD_FORMAT_FILES: return LG_CLIPBOARD_DATA_FILES;
default: return LG_CLIPBOARD_DATA_NONE;
}
}
static KVMFRClipboardFormat networkClipboardFormatFromData(
LG_ClipboardData type)
{
switch (type)
{
case LG_CLIPBOARD_DATA_TEXT : return KVMFR_CLIPBOARD_FORMAT_TEXT;
case LG_CLIPBOARD_DATA_PNG : return KVMFR_CLIPBOARD_FORMAT_PNG;
case LG_CLIPBOARD_DATA_BMP : return KVMFR_CLIPBOARD_FORMAT_BMP;
case LG_CLIPBOARD_DATA_TIFF : return KVMFR_CLIPBOARD_FORMAT_TIFF;
case LG_CLIPBOARD_DATA_JPEG : return KVMFR_CLIPBOARD_FORMAT_JPEG;
case LG_CLIPBOARD_DATA_FILES: return KVMFR_CLIPBOARD_FORMAT_FILES;
default: return KVMFR_CLIPBOARD_FORMAT_NONE;
}
}
static bool networkClipboardDataValid(LG_ClipboardData type)
{
return type >= LG_CLIPBOARD_DATA_TEXT &&
type < LG_CLIPBOARD_DATA_NONE;
}
static bool networkClipboardFileErrorValid(LG_ClipboardFileError error)
{
return error >= LG_CLIPBOARD_FILE_ERROR_NONE &&
error <= LG_CLIPBOARD_FILE_ERROR_STALE;
}
static KVMFRClipboardFileError networkClipboardFileErrorToKVMFR(
LG_ClipboardFileError error)
{
return networkClipboardFileErrorValid(error) ?
(KVMFRClipboardFileError)error :
KVMFR_CLIPBOARD_FILE_ERROR_INVALID;
}
static LG_ClipboardFileError networkClipboardFileErrorFromStatus(
LGNetFileStatusCode status, uint32_t detail)
{
return (LG_ClipboardFileError)lgNetFileKVMFRErrorFromStatus(
status, detail);
}
static LGNetFileStatusCode networkClipboardFileStatus(
LG_ClipboardFileError error)
{
return lgNetFileStatusFromKVMFRError(
networkClipboardFileErrorToKVMFR(error));
}
static LG_ClipboardStatus networkClipboardStatusNL(
const NetworkClipboard * clipboard)
{
return (LG_ClipboardStatus)
{
.available = clipboard->available,
.generation = clipboard->statusGeneration,
};
}
static void networkClipboardSetAvailableNL(NetworkClipboard * clipboard,
bool available)
{
if (clipboard->available == available)
return;
clipboard->available = available;
clipboard->statusGeneration =
networkClipboardNext32(clipboard->statusGeneration);
clipboard->statusNotifyPending = true;
}
static void networkClipboardNotifyStatus(NetworkClipboard * clipboard)
{
LG_ClipboardStatusFn callback;
void * opaque;
LG_ClipboardStatus status;
LG_LOCK(clipboard->statusLock);
LG_LOCK(clipboard->stateLock);
if (!clipboard->statusNotifyPending)
{
LG_UNLOCK(clipboard->stateLock);
LG_UNLOCK(clipboard->statusLock);
return;
}
clipboard->statusNotifyPending = false;
callback = clipboard->statusCallback;
opaque = clipboard->statusOpaque;
status = networkClipboardStatusNL(clipboard);
LG_UNLOCK(clipboard->stateLock);
if (callback)
callback(opaque, &status);
LG_UNLOCK(clipboard->statusLock);
}
static struct NetworkClipboardSend * networkClipboardSendNew(
LGNetService service, uint16_t messageType, uint16_t serviceVersion,
LGNetEnvelopeFlags flags, uint64_t componentEpoch, uint64_t requestID,
uint32_t payloadLength)
{
const size_t allocation = sizeof(struct NetworkClipboardSend) +
(size_t)payloadLength;
if (payloadLength > LG_NET_MAX_PAYLOAD_LENGTH ||
allocation < payloadLength)
return NULL;
struct NetworkClipboardSend * send =
calloc(1, allocation);
if (!send)
return NULL;
send->service = service;
send->messageType = messageType;
send->serviceVersion = serviceVersion;
send->flags = flags | LG_NET_ENVELOPE_FINAL;
send->delivery = NETWORK_PACKET_DELIVERY_RELIABLE;
send->componentEpoch = componentEpoch;
send->requestID = requestID;
send->payloadLength = payloadLength;
return send;
}
static struct NetworkClipboardSend * networkClipboardSendWithDelivery(
struct NetworkClipboardSend * send, NetworkPacketDelivery delivery)
{
if (send)
send->delivery = delivery;
return send;
}
static void networkClipboardBatchAppend(
struct NetworkClipboardBatch * batch,
struct NetworkClipboardSend * send)
{
if (batch->tail)
batch->tail->next = send;
else
batch->head = send;
batch->tail = send;
++batch->count;
batch->bytes += sizeof(*send) + send->payloadLength;
}
static void networkClipboardBatchFree(struct NetworkClipboardBatch * batch)
{
while (batch->head)
{
struct NetworkClipboardSend * send = batch->head;
batch->head = send->next;
free(send);
}
memset(batch, 0, sizeof(*batch));
}
static bool networkClipboardQueueBatchNL(NetworkClipboard * clipboard,
struct NetworkClipboardBatch * batch)
{
if (!batch->head || !clipboard->connected ||
batch->count > NETWORK_CLIPBOARD_PENDING_MAX ||
batch->bytes > NETWORK_CLIPBOARD_PENDING_BYTES ||
clipboard->sendCount > NETWORK_CLIPBOARD_PENDING_MAX - batch->count ||
clipboard->sendBytes > NETWORK_CLIPBOARD_PENDING_BYTES - batch->bytes)
return false;
if (batch->head->service == LG_NET_SERVICE_CLIPBOARD &&
!clipboard->clipboardService)
return false;
if (batch->head->service == LG_NET_SERVICE_FILE &&
!clipboard->fileService)
return false;
if (clipboard->sendTail)
clipboard->sendTail->next = batch->head;
else
clipboard->sendHead = batch->head;
clipboard->sendTail = batch->tail;
clipboard->sendCount += batch->count;
clipboard->sendBytes += batch->bytes;
memset(batch, 0, sizeof(*batch));
return true;
}
static void networkClipboardClearSendNL(NetworkClipboard * clipboard)
{
while (clipboard->sendHead)
{
struct NetworkClipboardSend * send = clipboard->sendHead;
clipboard->sendHead = send->next;
free(send);
}
clipboard->sendTail = NULL;
clipboard->sendCount = 0;
clipboard->sendBytes = 0;
clipboard->sendProgress = false;
}
static bool networkClipboardFlushNL(NetworkClipboard * clipboard)
{
bool progress = false;
uint8_t blocked = 0;
while (clipboard->sendHead && clipboard->connected &&
blocked != ((UINT8_C(1) << NETWORK_PACKET_DELIVERY_RELIABLE) |
(UINT8_C(1) << NETWORK_PACKET_DELIVERY_BULK)))
{
struct NetworkClipboardSend ** link = &clipboard->sendHead;
while (*link && (blocked & (UINT8_C(1) << (*link)->delivery)))
link = &(*link)->next;
if (!*link)
break;
struct NetworkClipboardSend * send = *link;
LG_TransportStatus status;
switch (send->delivery)
{
case NETWORK_PACKET_DELIVERY_RELIABLE:
status = networkConnectionSend(
clipboard->connection, send->service, send->messageType,
send->serviceVersion, send->flags, send->componentEpoch,
send->requestID, send->payload, send->payloadLength);
break;
case NETWORK_PACKET_DELIVERY_BULK:
status = networkConnectionSendBulk(
clipboard->connection, send->service, send->messageType,
send->serviceVersion, send->flags, send->componentEpoch,
send->requestID, send->payload, send->payloadLength);
break;
case NETWORK_PACKET_DELIVERY_DATAGRAM:
default:
return progress;
}
if (status == LG_TRANSPORT_UNAVAILABLE)
{
blocked |= UINT8_C(1) << send->delivery;
continue;
}
if (status != LG_TRANSPORT_OK)
break;
*link = send->next;
if (clipboard->sendTail == send)
{
clipboard->sendTail = NULL;
for (struct NetworkClipboardSend * item = clipboard->sendHead;
item; item = item->next)
clipboard->sendTail = item;
}
--clipboard->sendCount;
clipboard->sendBytes -= sizeof(*send) + send->payloadLength;
free(send);
progress = true;
}
return progress;
}
static void networkClipboardFlushQueued(NetworkClipboard * clipboard)
{
LG_LOCK(clipboard->stateLock);
if (!clipboard->destroying)
clipboard->sendProgress |= networkClipboardFlushNL(clipboard);
LG_UNLOCK(clipboard->stateLock);
}
static void networkClipboardClearInboundNL(NetworkClipboard * clipboard)
{
while (clipboard->inboundHead)
{
struct NetworkClipboardInbound * packet = clipboard->inboundHead;
clipboard->inboundHead = packet->next;
free(packet);
}
clipboard->inboundTail = NULL;
clipboard->inboundCount = 0;
clipboard->inboundBytes = 0;
clipboard->inboundBlocked = false;
clipboard->inboundReady = false;
clipboard->inboundBlockedRequest = LG_CLIPBOARD_REQUEST_INVALID;
}
static struct NetworkClipboardTransfer * networkClipboardTransferFind(
struct NetworkClipboardTransfer * transfers, uint64_t localRequest)
{
for (; transfers; transfers = transfers->next)
if (transfers->localRequest == localRequest)
return transfers;
return NULL;
}
static struct NetworkClipboardTransfer * networkClipboardTransferFindWire(
struct NetworkClipboardTransfer * transfers, uint64_t wireRequest)
{
for (; transfers; transfers = transfers->next)
if (transfers->wireRequest == wireRequest)
return transfers;
return NULL;
}
static struct NetworkClipboardTransfer * networkClipboardTransferTake(
struct NetworkClipboardTransfer ** transfers, uint64_t localRequest)
{
while (*transfers && (*transfers)->localRequest != localRequest)
transfers = &(*transfers)->next;
if (!*transfers)
return NULL;
struct NetworkClipboardTransfer * result = *transfers;
*transfers = result->next;
result->next = NULL;
return result;
}
static struct NetworkClipboardTransfer * networkClipboardTransferTakeWire(
struct NetworkClipboardTransfer ** transfers, uint64_t wireRequest)
{
while (*transfers && (*transfers)->wireRequest != wireRequest)
transfers = &(*transfers)->next;
if (!*transfers)
return NULL;
struct NetworkClipboardTransfer * result = *transfers;
*transfers = result->next;
result->next = NULL;
return result;
}
static struct NetworkFileTransfer * networkClipboardFileTransferFind(
struct NetworkFileTransfer * transfers, uint64_t localRequest)
{
for (; transfers; transfers = transfers->next)
if (transfers->request.request == localRequest)
return transfers;
return NULL;
}
static struct NetworkFileTransfer * networkClipboardFileTransferFindWire(
struct NetworkFileTransfer * transfers, uint64_t wireRequest)
{
for (; transfers; transfers = transfers->next)
if (transfers->wireRequest == wireRequest)
return transfers;
return NULL;
}
static struct NetworkFileTransfer * networkClipboardFileTransferTake(
struct NetworkFileTransfer ** transfers, uint64_t localRequest)
{
while (*transfers && (*transfers)->request.request != localRequest)
transfers = &(*transfers)->next;
if (!*transfers)
return NULL;
struct NetworkFileTransfer * result = *transfers;
*transfers = result->next;
result->next = NULL;
return result;
}
static struct NetworkFileTransfer * networkClipboardFileTransferTakeWire(
struct NetworkFileTransfer ** transfers, uint64_t wireRequest)
{
while (*transfers && (*transfers)->wireRequest != wireRequest)
transfers = &(*transfers)->next;
if (!*transfers)
return NULL;
struct NetworkFileTransfer * result = *transfers;
*transfers = result->next;
result->next = NULL;
return result;
}
static struct NetworkFileAcquisition * networkClipboardAcquisitionFind(
NetworkClipboard * clipboard, uint64_t dataset,
uint64_t localAcquisition, bool fromRemote)
{
for (struct NetworkFileAcquisition * item = clipboard->fileAcquisitions;
item; item = item->next)
if (item->dataset == dataset &&
item->localAcquisition == localAcquisition &&
item->fromRemote == fromRemote)
return item;
return NULL;
}
static struct NetworkFileAcquisition *
networkClipboardAcquisitionFindWire(NetworkClipboard * clipboard,
uint64_t dataset, uint64_t wireClientID, bool fromRemote)
{
for (struct NetworkFileAcquisition * item = clipboard->fileAcquisitions;
item; item = item->next)
if (item->dataset == dataset && item->wireClientID == wireClientID &&
item->fromRemote == fromRemote)
return item;
return NULL;
}
static struct NetworkFileAcquisition * networkClipboardAcquisitionTake(
NetworkClipboard * clipboard, uint64_t dataset,
uint64_t localAcquisition, bool fromRemote)
{
struct NetworkFileAcquisition ** item = &clipboard->fileAcquisitions;
while (*item && ((*item)->dataset != dataset ||
(*item)->localAcquisition != localAcquisition ||
(*item)->fromRemote != fromRemote))
item = &(*item)->next;
if (!*item)
return NULL;
struct NetworkFileAcquisition * result = *item;
*item = result->next;
result->next = NULL;
return result;
}
static unsigned networkClipboardAcquisitionCountNL(
const NetworkClipboard * clipboard)
{
unsigned count = 0;
for (const struct NetworkFileAcquisition * item =
clipboard->fileAcquisitions; item; item = item->next)
++count;
return count;
}
static unsigned networkClipboardFileTransferCountNL(
const NetworkClipboard * clipboard)
{
unsigned count = 0;
for (const struct NetworkFileTransfer * item = clipboard->fileReads;
item; item = item->next)
++count;
for (const struct NetworkFileTransfer * item = clipboard->fileWrites;
item; item = item->next)
++count;
return count;
}
static unsigned networkClipboardTransferCount(
const struct NetworkClipboardTransfer * transfers)
{
unsigned count = 0;
for (; transfers; transfers = transfers->next)
++count;
return count;
}
static bool networkClipboardDatasetAcquiredNL(
const NetworkClipboard * clipboard, uint64_t dataset,
bool fromRemote)
{
for (const struct NetworkFileAcquisition * item =
clipboard->fileAcquisitions; item; item = item->next)
if (item->dataset == dataset && item->fromRemote == fromRemote &&
item->active)
return true;
return false;
}
static void networkClipboardTransfersFree(
struct NetworkClipboardTransfer * transfers)
{
while (transfers)
{
struct NetworkClipboardTransfer * next = transfers->next;
free(transfers);
transfers = next;
}
}
static void networkClipboardFileTransfersFree(
struct NetworkFileTransfer * transfers)
{
while (transfers)
{
struct NetworkFileTransfer * next = transfers->next;
free(transfers);
transfers = next;
}
}
static void networkClipboardAcquisitionsFree(
struct NetworkFileAcquisition * acquisitions)
{
while (acquisitions)
{
struct NetworkFileAcquisition * next = acquisitions->next;
free(acquisitions);
acquisitions = next;
}
}
static bool networkClipboardSameFileRequest(
const LG_ClipboardFileRequest * left,
const LG_ClipboardFileRequest * right)
{
return left->dataset == right->dataset &&
left->request == right->request && left->node == right->node &&
left->offset == right->offset && left->length == right->length &&
left->operation == right->operation;
}
static struct NetworkClipboardSend * networkClipboardControlSendNew(
NetworkClipboard * clipboard, uint16_t messageType,
const LGNetClipboardControl * control, LGNetEnvelopeFlags flags,
uint64_t requestID)
{
struct NetworkClipboardSend * send = networkClipboardSendNew(
LG_NET_SERVICE_CLIPBOARD, messageType, clipboard->clipboardVersion,
flags, clipboard->claimEpoch, requestID,
LG_NET_CLIPBOARD_CONTROL_WIRE_SIZE);
if (!send || !lgNetClipboardControlEncode(
send->payload, send->payloadLength, control))
{
free(send);
return NULL;
}
return send;
}
static struct NetworkClipboardSend * networkClipboardTransferSendNew(
NetworkClipboard * clipboard, uint16_t messageType,
const LGNetClipboardTransfer * transfer, LGNetEnvelopeFlags flags)
{
struct NetworkClipboardSend * send = networkClipboardSendNew(
LG_NET_SERVICE_CLIPBOARD, messageType, clipboard->clipboardVersion,
flags, clipboard->claimEpoch, transfer->requestID,
LG_NET_CLIPBOARD_TRANSFER_WIRE_SIZE);
if (!send || !lgNetClipboardTransferEncode(
send->payload, send->payloadLength, transfer))
{
free(send);
return NULL;
}
return send;
}
static struct NetworkClipboardSend * networkClipboardChunkSendNew(
NetworkClipboard * clipboard, const LGNetClipboardChunk * chunk)
{
const size_t payloadLength = lgNetClipboardChunkSize(chunk);
if (!payloadLength || payloadLength > UINT32_MAX)
return NULL;
struct NetworkClipboardSend * send = networkClipboardSendNew(
LG_NET_SERVICE_CLIPBOARD, LG_NET_CLIPBOARD_MESSAGE_DATA_CHUNK,
clipboard->clipboardVersion, 0, clipboard->claimEpoch,
chunk->requestID, (uint32_t)payloadLength);
if (!send || !lgNetClipboardChunkEncode(
send->payload, send->payloadLength, chunk))
{
free(send);
return NULL;
}
return send;
}
static struct NetworkClipboardSend * networkClipboardStatusSendNew(
NetworkClipboard * clipboard, const LGNetClipboardStatus * status,
LGNetEnvelopeFlags flags)
{
struct NetworkClipboardSend * send = networkClipboardSendNew(
LG_NET_SERVICE_CLIPBOARD, LG_NET_CLIPBOARD_MESSAGE_STATUS,
clipboard->clipboardVersion, flags, clipboard->claimEpoch,
status->requestID, LG_NET_CLIPBOARD_STATUS_WIRE_SIZE);
if (!send || !lgNetClipboardStatusEncode(
send->payload, send->payloadLength, status))
{
free(send);
return NULL;
}
return send;
}
static struct NetworkClipboardSend * networkClipboardFileLeaseSendNew(
NetworkClipboard * clipboard, uint16_t messageType,
const LGNetFileLease * lease, LGNetEnvelopeFlags flags)
{
struct NetworkClipboardSend * send = networkClipboardSendNew(
LG_NET_SERVICE_FILE, messageType, clipboard->fileVersion, flags,
clipboard->claimEpoch, lease->clientID, LG_NET_FILE_LEASE_WIRE_SIZE);
if (!send || !lgNetFileLeaseEncode(
send->payload, send->payloadLength, lease))
{
free(send);
return NULL;
}
return send;
}
static struct NetworkClipboardSend * networkClipboardFileTransferSendNew(
NetworkClipboard * clipboard, uint16_t messageType,
const LGNetFileTransfer * transfer, LGNetEnvelopeFlags flags)
{
struct NetworkClipboardSend * send = networkClipboardSendNew(
LG_NET_SERVICE_FILE, messageType, clipboard->fileVersion, flags,
clipboard->claimEpoch, transfer->requestID,
LG_NET_FILE_TRANSFER_WIRE_SIZE);
if (!send || !lgNetFileTransferEncode(
send->payload, send->payloadLength, transfer))
{
free(send);
return NULL;
}
return send;
}
static struct NetworkClipboardSend * networkClipboardFileChunkSendNew(
NetworkClipboard * clipboard, const LGNetFileChunk * chunk)
{
const size_t payloadLength = lgNetFileChunkSize(chunk);
if (!payloadLength || payloadLength > UINT32_MAX)
return NULL;
struct NetworkClipboardSend * send = networkClipboardSendNew(
LG_NET_SERVICE_FILE, LG_NET_FILE_MESSAGE_DATA_CHUNK,
clipboard->fileVersion, 0, clipboard->claimEpoch,
chunk->requestID, (uint32_t)payloadLength);
if (!send || !lgNetFileChunkEncode(
send->payload, send->payloadLength, chunk))
{
free(send);
return NULL;
}
return send;
}
static struct NetworkClipboardSend * networkClipboardFileStatusSendNew(
NetworkClipboard * clipboard, const LGNetFileStatus * status,
LGNetEnvelopeFlags flags)
{
struct NetworkClipboardSend * send = networkClipboardSendNew(
LG_NET_SERVICE_FILE, LG_NET_FILE_MESSAGE_STATUS,
clipboard->fileVersion, flags, clipboard->claimEpoch,
status->requestID, LG_NET_FILE_STATUS_WIRE_SIZE);
if (!send || !lgNetFileStatusEncode(
send->payload, send->payloadLength, status))
{
free(send);
return NULL;
}
return send;
}
static struct NetworkClipboardSend * networkClipboardOfferSendNew(
NetworkClipboard * clipboard, uint64_t offerID, uint64_t serial,
uint32_t formatIndex, uint32_t formatCount, LG_ClipboardData type)
{
const KVMFRClipboardFormat format =
networkClipboardFormatFromData(type);
const uint8_t * mime;
uint16_t mimeLength;
if (!lgNetClipboardKVMFRFormatMime(format, &mime, &mimeLength))
return NULL;
const LGNetClipboardOffer offer =
{
.offerID = offerID,
.serial = serial,
.estimatedSize = 0,
.formatIndex = formatIndex,
.formatCount = formatCount,
.mimeLength = mimeLength,
.flags = LG_NET_CLIPBOARD_OFFER_LAZY,
.mime = mime,
};
const size_t payloadLength = lgNetClipboardOfferSize(&offer);
if (!payloadLength || payloadLength > UINT32_MAX)
return NULL;
struct NetworkClipboardSend * send = networkClipboardSendNew(
LG_NET_SERVICE_CLIPBOARD, LG_NET_CLIPBOARD_MESSAGE_OFFER,
clipboard->clipboardVersion, 0, clipboard->claimEpoch, offerID,
(uint32_t)payloadLength);
if (!send || !lgNetClipboardOfferEncode(
send->payload, send->payloadLength, &offer))
{
free(send);
return NULL;
}
return send;
}
static struct NetworkClipboardSend * networkClipboardRequestSendNew(
NetworkClipboard * clipboard, const LGNetClipboardRequest * request)
{
struct NetworkClipboardSend * send = networkClipboardSendNew(
LG_NET_SERVICE_CLIPBOARD, LG_NET_CLIPBOARD_MESSAGE_REQUEST,
clipboard->clipboardVersion, LG_NET_ENVELOPE_ACK_REQUIRED,
clipboard->claimEpoch, request->requestID,
LG_NET_CLIPBOARD_REQUEST_WIRE_SIZE);
if (!send || !lgNetClipboardRequestEncode(
send->payload, send->payloadLength, request))
{
free(send);
return NULL;
}
return send;
}
static struct NetworkClipboardSend * networkClipboardFileOfferSendNew(
NetworkClipboard * clipboard, uint64_t offerID)
{
const LGNetFileOffer offer =
{
.offerID = offerID,
.totalBytes = 0,
.entryCount = 1,
.flags = LG_NET_FILE_OFFER_COPY |
LG_NET_FILE_OFFER_RECURSIVE,
.labelLength = 0,
.label = NULL,
};
const size_t payloadLength = lgNetFileOfferSize(&offer);
if (!payloadLength || payloadLength > UINT32_MAX)
return NULL;
struct NetworkClipboardSend * send = networkClipboardSendNew(
LG_NET_SERVICE_FILE, LG_NET_FILE_MESSAGE_OFFER,
clipboard->fileVersion, 0, clipboard->claimEpoch, offerID,
(uint32_t)payloadLength);
if (!send || !lgNetFileOfferEncode(
send->payload, send->payloadLength, &offer))
{
free(send);
return NULL;
}
return send;
}
static struct NetworkClipboardSend * networkClipboardFileRequestSendNew(
NetworkClipboard * clipboard, const LGNetFileRequest * request)
{
struct NetworkClipboardSend * send = networkClipboardSendNew(
LG_NET_SERVICE_FILE, LG_NET_FILE_MESSAGE_REQUEST,
clipboard->fileVersion, LG_NET_ENVELOPE_ACK_REQUIRED,
clipboard->claimEpoch, request->requestID,
LG_NET_FILE_REQUEST_WIRE_SIZE);
if (!send || !lgNetFileRequestEncode(
send->payload, send->payloadLength, request))
{
free(send);
return NULL;
}
return send;
}
static bool networkClipboardQueueClaimNL(NetworkClipboard * clipboard)
{
if (!clipboard->connected || !clipboard->clipboardService ||
clipboard->destroying || !clipboard->attached ||
clipboard->claimPending || clipboard->claimed)
return false;
const uint64_t claimEpoch = networkClipboardNext64(
clipboard->claimEpoch);
const LGNetClipboardClaim claim =
{
.ownerID = clipboard->ownerID,
.claimEpoch = claimEpoch,
.leaseMs = NETWORK_CLIPBOARD_LEASE_MS,
.flags = NETWORK_CLIPBOARD_CLAIM_FLAGS,
};
struct NetworkClipboardSend * send = networkClipboardSendNew(
LG_NET_SERVICE_CLIPBOARD, LG_NET_CLIPBOARD_MESSAGE_CLAIM,
clipboard->clipboardVersion, LG_NET_ENVELOPE_ACK_REQUIRED,
claimEpoch, claimEpoch, LG_NET_CLIPBOARD_CLAIM_WIRE_SIZE);
if (!send || !lgNetClipboardClaimEncode(
send->payload, send->payloadLength, &claim))
{
free(send);
return false;
}
struct NetworkClipboardBatch batch = { 0 };
networkClipboardBatchAppend(&batch, send);
if (!networkClipboardQueueBatchNL(clipboard, &batch))
{
networkClipboardBatchFree(&batch);
return false;
}
clipboard->claimEpoch = claimEpoch;
clipboard->claimPending = true;
clipboard->claimed = false;
clipboard->claimSerial = 0;
clipboard->lastClaimStatusSerial = 0;
clipboard->nextClaimNs = networkClipboardDeadline(
NETWORK_CLIPBOARD_CLAIM_RETRY_MS);
clipboard->nextKeepaliveNs = 0;
return true;
}
static bool networkClipboardQueueControlNL(NetworkClipboard * clipboard,
uint16_t messageType, uint64_t offerID)
{
const uint64_t serial =
networkClipboardNext64(clipboard->claimSerial);
const LGNetClipboardControl control =
{
.ownerID = clipboard->ownerID,
.claimEpoch = clipboard->claimEpoch,
.serial = serial,
.offerID = offerID,
};
struct NetworkClipboardSend * send = networkClipboardControlSendNew(
clipboard, messageType, &control, 0, offerID);
if (!send)
return false;
struct NetworkClipboardBatch batch = { 0 };
networkClipboardBatchAppend(&batch, send);
if (!networkClipboardQueueBatchNL(clipboard, &batch))
{
networkClipboardBatchFree(&batch);
return false;
}
clipboard->claimSerial = serial;
return true;
}
static bool networkClipboardQueueReleaseNL(NetworkClipboard * clipboard)
{
if ((!clipboard->claimPending && !clipboard->claimed) ||
!clipboard->connected || !clipboard->clipboardService)
return false;
const bool queued = networkClipboardQueueControlNL(clipboard,
LG_NET_CLIPBOARD_MESSAGE_RELEASE, 0);
clipboard->claimPending = false;
clipboard->claimed = false;
clipboard->nextClaimNs = 0;
clipboard->nextKeepaliveNs = 0;
return queued;
}
static bool networkClipboardQueueLocalOfferNL(
NetworkClipboard * clipboard)
{
if (!clipboard->localOffer.offerID || !clipboard->localOffer.count ||
(!clipboard->claimPending && !clipboard->claimed))
return false;
struct NetworkClipboardBatch batch = { 0 };
if (clipboard->localOffer.files)
{
struct NetworkClipboardSend * file = networkClipboardFileOfferSendNew(
clipboard, clipboard->localOffer.offerID);
if (!file)
return false;
networkClipboardBatchAppend(&batch, file);
}
for (uint32_t index = 0; index < clipboard->localOffer.count; ++index)
{
struct NetworkClipboardSend * offer = networkClipboardOfferSendNew(
clipboard, clipboard->localOffer.offerID,
clipboard->localOffer.serial, index,
clipboard->localOffer.count, clipboard->localOffer.types[index]);
if (!offer)
{
networkClipboardBatchFree(&batch);
return false;
}
networkClipboardBatchAppend(&batch, offer);
}
if (!networkClipboardQueueBatchNL(clipboard, &batch))
{
networkClipboardBatchFree(&batch);
return false;
}
return true;
}
static void networkClipboardDispatchNotice(NetworkClipboard * clipboard,
uint64_t dataset, const LG_ClipboardData types[], size_t count,
bool files)
{
LG_LOCK(clipboard->eventLock);
LG_LOCK(clipboard->stateLock);
const LG_ClipboardEventOps * events = clipboard->attached ?
clipboard->events : NULL;
void * opaque = clipboard->eventOpaque;
LG_UNLOCK(clipboard->stateLock);
if (events)
{
if (files && events->fileOffer)
events->fileOffer(opaque, dataset);
if (events->notice)
events->notice(opaque, types, count);
}
LG_UNLOCK(clipboard->eventLock);
}
static void networkClipboardDispatchRelease(NetworkClipboard * clipboard)
{
LG_LOCK(clipboard->eventLock);
LG_LOCK(clipboard->stateLock);
const LG_ClipboardEventOps * events = clipboard->attached ?
clipboard->events : NULL;
void * opaque = clipboard->eventOpaque;
LG_UNLOCK(clipboard->stateLock);
if (events && events->release)
events->release(opaque);
LG_UNLOCK(clipboard->eventLock);
}
static bool networkClipboardDispatchRequest(NetworkClipboard * clipboard,
LG_ClipboardRequest request, LG_ClipboardData type)
{
LG_LOCK(clipboard->eventLock);
LG_LOCK(clipboard->stateLock);
const LG_ClipboardEventOps * events = clipboard->attached ?
clipboard->events : NULL;
void * opaque = clipboard->eventOpaque;
LG_UNLOCK(clipboard->stateLock);
const bool accepted = events && events->request &&
events->request(opaque, request, type);
LG_UNLOCK(clipboard->eventLock);
return accepted;
}
static LG_ClipboardResult networkClipboardDispatchDataBegin(
NetworkClipboard * clipboard, LG_ClipboardRequest request,
LG_ClipboardData type, uint64_t sizeHint)
{
LG_LOCK(clipboard->eventLock);
LG_LOCK(clipboard->stateLock);
const LG_ClipboardEventOps * events = clipboard->attached ?
clipboard->events : NULL;
void * opaque = clipboard->eventOpaque;
LG_UNLOCK(clipboard->stateLock);
const LG_ClipboardResult result = events && events->dataBegin ?
events->dataBegin(opaque, request, type, sizeHint) :
LG_CLIPBOARD_RESULT_FAILED;
LG_UNLOCK(clipboard->eventLock);
return result;
}
static LG_ClipboardResult networkClipboardDispatchDataChunk(
NetworkClipboard * clipboard, LG_ClipboardRequest request,
uint64_t offset, const void * data, size_t size)
{
LG_LOCK(clipboard->eventLock);
LG_LOCK(clipboard->stateLock);
const LG_ClipboardEventOps * events = clipboard->attached ?
clipboard->events : NULL;
void * opaque = clipboard->eventOpaque;
LG_UNLOCK(clipboard->stateLock);
const LG_ClipboardResult result = events && events->dataChunk ?
events->dataChunk(opaque, request, offset, data, size) :
LG_CLIPBOARD_RESULT_FAILED;
LG_UNLOCK(clipboard->eventLock);
return result;
}
static LG_ClipboardResult networkClipboardDispatchDataEnd(
NetworkClipboard * clipboard, LG_ClipboardRequest request,
uint64_t finalSize)
{
LG_LOCK(clipboard->eventLock);
LG_LOCK(clipboard->stateLock);
const LG_ClipboardEventOps * events = clipboard->attached ?
clipboard->events : NULL;
void * opaque = clipboard->eventOpaque;
LG_UNLOCK(clipboard->stateLock);
const LG_ClipboardResult result = events && events->dataEnd ?
events->dataEnd(opaque, request, finalSize) :
LG_CLIPBOARD_RESULT_FAILED;
LG_UNLOCK(clipboard->eventLock);
return result;
}
static void networkClipboardDispatchDataCancel(
NetworkClipboard * clipboard, LG_ClipboardRequest request,
LG_ClipboardCancelReason reason)
{
LG_LOCK(clipboard->eventLock);
LG_LOCK(clipboard->stateLock);
const LG_ClipboardEventOps * events = clipboard->attached ?
clipboard->events : NULL;
void * opaque = clipboard->eventOpaque;
LG_UNLOCK(clipboard->stateLock);
if (events && events->dataCancel)
events->dataCancel(opaque, request, reason);
LG_UNLOCK(clipboard->eventLock);
}
static void networkClipboardDispatchRequestCancel(
NetworkClipboard * clipboard, LG_ClipboardRequest request,
LG_ClipboardCancelReason reason)
{
LG_LOCK(clipboard->eventLock);
LG_LOCK(clipboard->stateLock);
const LG_ClipboardEventOps * events = clipboard->attached ?
clipboard->events : NULL;
void * opaque = clipboard->eventOpaque;
LG_UNLOCK(clipboard->stateLock);
if (events && events->requestCancel)
events->requestCancel(opaque, request, reason);
LG_UNLOCK(clipboard->eventLock);
}
static void networkClipboardDispatchDataReady(
NetworkClipboard * clipboard, LG_ClipboardRequest request)
{
LG_LOCK(clipboard->eventLock);
LG_LOCK(clipboard->stateLock);
const LG_ClipboardEventOps * events = clipboard->attached ?
clipboard->events : NULL;
void * opaque = clipboard->eventOpaque;
LG_UNLOCK(clipboard->stateLock);
if (events && events->dataReady)
events->dataReady(opaque, request);
LG_UNLOCK(clipboard->eventLock);
}
static void networkClipboardDispatchFileAcquire(
NetworkClipboard * clipboard, uint64_t dataset, uint64_t acquisition)
{
LG_LOCK(clipboard->eventLock);
LG_LOCK(clipboard->stateLock);
const LG_ClipboardEventOps * events = clipboard->attached ?
clipboard->events : NULL;
void * opaque = clipboard->eventOpaque;
LG_UNLOCK(clipboard->stateLock);
if (events && events->fileAcquire)
events->fileAcquire(opaque, dataset, acquisition);
LG_UNLOCK(clipboard->eventLock);
}
static void networkClipboardDispatchFileAcquired(
NetworkClipboard * clipboard, uint64_t dataset, uint64_t acquisition,
LG_ClipboardFileError error)
{
LG_LOCK(clipboard->eventLock);
LG_LOCK(clipboard->stateLock);
const LG_ClipboardEventOps * events = clipboard->attached ?
clipboard->events : NULL;
void * opaque = clipboard->eventOpaque;
LG_UNLOCK(clipboard->stateLock);
if (events && events->fileAcquired)
events->fileAcquired(opaque, dataset, acquisition, error);
LG_UNLOCK(clipboard->eventLock);
}
static void networkClipboardDispatchFileRelease(
NetworkClipboard * clipboard, uint64_t dataset, uint64_t acquisition)
{
LG_LOCK(clipboard->eventLock);
LG_LOCK(clipboard->stateLock);
const LG_ClipboardEventOps * events = clipboard->attached ?
clipboard->events : NULL;
void * opaque = clipboard->eventOpaque;
LG_UNLOCK(clipboard->stateLock);
if (events && events->fileRelease)
events->fileRelease(opaque, dataset, acquisition);
LG_UNLOCK(clipboard->eventLock);
}
static void networkClipboardDispatchFileRequest(
NetworkClipboard * clipboard,
const LG_ClipboardFileRequest * request)
{
LG_LOCK(clipboard->eventLock);
LG_LOCK(clipboard->stateLock);
const LG_ClipboardEventOps * events = clipboard->attached ?
clipboard->events : NULL;
void * opaque = clipboard->eventOpaque;
LG_UNLOCK(clipboard->stateLock);
if (events && events->fileRequest)
events->fileRequest(opaque, request);
LG_UNLOCK(clipboard->eventLock);
}
static LG_ClipboardResult networkClipboardDispatchFileDataBegin(
NetworkClipboard * clipboard,
const LG_ClipboardFileRequest * request, uint64_t sizeHint)
{
LG_LOCK(clipboard->eventLock);
LG_LOCK(clipboard->stateLock);
const LG_ClipboardEventOps * events = clipboard->attached ?
clipboard->events : NULL;
void * opaque = clipboard->eventOpaque;
LG_UNLOCK(clipboard->stateLock);
const LG_ClipboardResult result = events && events->fileDataBegin ?
events->fileDataBegin(opaque, request, sizeHint) :
LG_CLIPBOARD_RESULT_FAILED;
LG_UNLOCK(clipboard->eventLock);
return result;
}
static LG_ClipboardResult networkClipboardDispatchFileDataChunk(
NetworkClipboard * clipboard,
const LG_ClipboardFileRequest * request, uint64_t offset,
const void * data, size_t size)
{
LG_LOCK(clipboard->eventLock);
LG_LOCK(clipboard->stateLock);
const LG_ClipboardEventOps * events = clipboard->attached ?
clipboard->events : NULL;
void * opaque = clipboard->eventOpaque;
LG_UNLOCK(clipboard->stateLock);
const LG_ClipboardResult result = events && events->fileDataChunk ?
events->fileDataChunk(opaque, request, offset, data, size) :
LG_CLIPBOARD_RESULT_FAILED;
LG_UNLOCK(clipboard->eventLock);
return result;
}
static LG_ClipboardResult networkClipboardDispatchFileDataEnd(
NetworkClipboard * clipboard,
const LG_ClipboardFileRequest * request, uint64_t finalSize)
{
LG_LOCK(clipboard->eventLock);
LG_LOCK(clipboard->stateLock);
const LG_ClipboardEventOps * events = clipboard->attached ?
clipboard->events : NULL;
void * opaque = clipboard->eventOpaque;
LG_UNLOCK(clipboard->stateLock);
const LG_ClipboardResult result = events && events->fileDataEnd ?
events->fileDataEnd(opaque, request, finalSize) :
LG_CLIPBOARD_RESULT_FAILED;
LG_UNLOCK(clipboard->eventLock);
return result;
}
static void networkClipboardDispatchFileReady(
NetworkClipboard * clipboard, uint64_t request)
{
LG_LOCK(clipboard->eventLock);
LG_LOCK(clipboard->stateLock);
const LG_ClipboardEventOps * events = clipboard->attached ?
clipboard->events : NULL;
void * opaque = clipboard->eventOpaque;
LG_UNLOCK(clipboard->stateLock);
if (events && events->fileDataReady)
events->fileDataReady(opaque, request);
LG_UNLOCK(clipboard->eventLock);
}
static void networkClipboardDispatchFileCancel(
NetworkClipboard * clipboard, uint64_t dataset, uint64_t request,
LG_ClipboardFileError reason)
{
LG_LOCK(clipboard->eventLock);
LG_LOCK(clipboard->stateLock);
const LG_ClipboardEventOps * events = clipboard->attached ?
clipboard->events : NULL;
void * opaque = clipboard->eventOpaque;
LG_UNLOCK(clipboard->stateLock);
if (events && events->fileCancel)
events->fileCancel(opaque, dataset, request, reason);
LG_UNLOCK(clipboard->eventLock);
}
static bool networkClipboardPublishRemoteOffer(
NetworkClipboard * clipboard)
{
LG_ClipboardData types[NETWORK_CLIPBOARD_FORMATS];
uint64_t dataset;
uint32_t count;
bool files;
LG_LOCK(clipboard->stateLock);
struct NetworkRemoteOffer * offer = &clipboard->remoteOffer;
if (!offer->complete || offer->published ||
((offer->formatMask &
(UINT32_C(1) << LG_CLIPBOARD_DATA_FILES)) && !offer->fileOffer))
{
LG_UNLOCK(clipboard->stateLock);
return false;
}
count = offer->formatCount;
dataset = offer->offerID;
files = (offer->formatMask &
(UINT32_C(1) << LG_CLIPBOARD_DATA_FILES)) != 0;
memcpy(types, offer->types, count * sizeof(*types));
offer->published = true;
LG_UNLOCK(clipboard->stateLock);
networkClipboardDispatchNotice(
clipboard, dataset, types, count, files);
return true;
}
static bool networkClipboardQueueSendNL(NetworkClipboard * clipboard,
struct NetworkClipboardSend * send)
{
if (!send)
return false;
struct NetworkClipboardBatch batch = { 0 };
networkClipboardBatchAppend(&batch, send);
if (networkClipboardQueueBatchNL(clipboard, &batch))
return true;
networkClipboardBatchFree(&batch);
return false;
}
static void networkClipboardRemoteOfferResetNL(
NetworkClipboard * clipboard)
{
memset(&clipboard->remoteOffer, 0, sizeof(clipboard->remoteOffer));
for (size_t index = 0; index < LG_CLIPBOARD_DATA_NONE; ++index)
clipboard->remoteOffer.indices[index] = UINT32_MAX;
}
static bool networkClipboardEnvelopeValid(
const struct NetworkClipboardInbound * packet, LGNetService service)
{
const bool reliable =
packet->delivery == NETWORK_PACKET_DELIVERY_RELIABLE;
const bool bulk =
packet->delivery == NETWORK_PACKET_DELIVERY_BULK;
const LGNetEnvelopeFlags required = LG_NET_ENVELOPE_FINAL |
(reliable ? LG_NET_ENVELOPE_RELIABLE : 0);
const LGNetEnvelopeFlags prohibited = LG_NET_ENVELOPE_DATAGRAM |
LG_NET_ENVELOPE_EARLY_DATA |
(bulk ? LG_NET_ENVELOPE_RELIABLE : 0);
return packet->envelope.service == service &&
(reliable || bulk) &&
(packet->envelope.flags & required) == required &&
!(packet->envelope.flags & prohibited);
}
static bool networkClipboardEpochCurrentNL(
const NetworkClipboard * clipboard,
const struct NetworkClipboardInbound * packet)
{
return (clipboard->claimPending || clipboard->claimed) &&
packet->envelope.componentEpoch == clipboard->claimEpoch;
}
static bool networkClipboardQueueClipboardStatusNL(
NetworkClipboard * clipboard, uint64_t wireRequest, uint64_t offerID,
LGNetClipboardStatusCode code, uint32_t detail, uint64_t processed)
{
const LGNetClipboardStatus status =
{
.requestID = wireRequest,
.offerID = offerID,
.status = code,
.detail = detail,
.processedLength = processed,
};
return networkClipboardQueueSendNL(clipboard,
networkClipboardStatusSendNew(clipboard, &status,
LG_NET_ENVELOPE_RESPONSE));
}
static bool networkClipboardQueueFileStatusNL(
NetworkClipboard * clipboard, uint64_t wireRequest, uint64_t offerID,
uint64_t entryID, LG_ClipboardFileError error, uint64_t transferred)
{
const LGNetFileStatus status =
{
.requestID = wireRequest,
.offerID = offerID,
.entryID = entryID,
.status = networkClipboardFileStatus(error),
.detail = networkClipboardFileErrorToKVMFR(error),
.transferredLength = transferred,
};
return networkClipboardQueueSendNL(clipboard,
networkClipboardFileStatusSendNew(clipboard, &status,
LG_NET_ENVELOPE_RESPONSE));
}
enum NetworkClipboardProcessResult
{
NETWORK_CLIPBOARD_PROCESS_INVALID,
NETWORK_CLIPBOARD_PROCESS_CONSUMED,
NETWORK_CLIPBOARD_PROCESS_BLOCKED,
};
static enum NetworkClipboardProcessResult
networkClipboardProcessClaimResult(NetworkClipboard * clipboard,
const struct NetworkClipboardInbound * packet)
{
LGNetClipboardClaimStatus status;
if (!(packet->envelope.flags & LG_NET_ENVELOPE_RESPONSE) ||
lgNetClipboardClaimStatusDecode(&status,
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
status.claimEpoch != packet->envelope.requestID)
return NETWORK_CLIPBOARD_PROCESS_INVALID;
LG_LOCK(clipboard->stateLock);
if (!networkClipboardEpochCurrentNL(clipboard, packet) ||
status.claimEpoch != clipboard->claimEpoch)
{
LG_UNLOCK(clipboard->stateLock);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
if (clipboard->lastClaimStatusSerial &&
status.serial <= clipboard->lastClaimStatusSerial)
{
LG_UNLOCK(clipboard->stateLock);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
clipboard->lastClaimStatusSerial = status.serial;
clipboard->claimPending = false;
if (status.status == LG_NET_CLIPBOARD_CLAIM_ACCEPTED &&
(status.flags & NETWORK_CLIPBOARD_CLAIM_FLAGS) ==
NETWORK_CLIPBOARD_CLAIM_FLAGS)
{
clipboard->claimed = true;
clipboard->nextClaimNs = 0;
clipboard->nextKeepaliveNs = networkClipboardDeadline(
NETWORK_CLIPBOARD_KEEPALIVE_MS);
if (clipboard->localOffer.offerID)
networkClipboardQueueLocalOfferNL(clipboard);
}
else
{
clipboard->claimed = false;
clipboard->nextClaimNs = networkClipboardDeadline(
NETWORK_CLIPBOARD_CLAIM_RETRY_MS);
clipboard->nextKeepaliveNs = 0;
}
LG_UNLOCK(clipboard->stateLock);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
static enum NetworkClipboardProcessResult networkClipboardProcessOffer(
NetworkClipboard * clipboard,
const struct NetworkClipboardInbound * packet)
{
LGNetClipboardOffer offer;
if (packet->envelope.flags & LG_NET_ENVELOPE_RESPONSE ||
lgNetClipboardOfferDecode(&offer,
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
offer.formatCount > NETWORK_CLIPBOARD_FORMATS ||
packet->envelope.requestID != offer.offerID)
return NETWORK_CLIPBOARD_PROCESS_INVALID;
const KVMFRClipboardFormat format =
lgNetClipboardKVMFRFormatFromMime(offer.mime, offer.mimeLength);
const LG_ClipboardData type = networkClipboardDataFromFormat(format);
if (!networkClipboardDataValid(type))
return NETWORK_CLIPBOARD_PROCESS_INVALID;
LG_LOCK(clipboard->stateLock);
if (!networkClipboardEpochCurrentNL(clipboard, packet))
{
LG_UNLOCK(clipboard->stateLock);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
struct NetworkRemoteOffer * remote = &clipboard->remoteOffer;
if (!remote->formatCount || remote->epoch != packet->envelope.componentEpoch ||
remote->offerID != offer.offerID || remote->serial != offer.serial)
{
const bool fileOffer = remote->fileOffer &&
remote->epoch == packet->envelope.componentEpoch &&
remote->offerID == offer.offerID;
networkClipboardRemoteOfferResetNL(clipboard);
remote->epoch = packet->envelope.componentEpoch;
remote->offerID = offer.offerID;
remote->serial = offer.serial;
remote->formatCount = offer.formatCount;
remote->fileOffer = fileOffer;
}
else if (remote->formatCount != offer.formatCount)
{
LG_UNLOCK(clipboard->stateLock);
return NETWORK_CLIPBOARD_PROCESS_INVALID;
}
const uint32_t indexBit = UINT32_C(1) << offer.formatIndex;
const uint32_t typeBit = UINT32_C(1) << type;
if ((remote->received & indexBit) || (remote->formatMask & typeBit))
{
LG_UNLOCK(clipboard->stateLock);
return NETWORK_CLIPBOARD_PROCESS_INVALID;
}
remote->types[offer.formatIndex] = type;
remote->indices[type] = offer.formatIndex;
remote->received |= indexBit;
remote->formatMask |= typeBit;
const uint32_t completeMask =
(UINT32_C(1) << remote->formatCount) - 1U;
remote->complete = remote->received == completeMask;
LG_UNLOCK(clipboard->stateLock);
networkClipboardPublishRemoteOffer(clipboard);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
static enum NetworkClipboardProcessResult networkClipboardProcessClear(
NetworkClipboard * clipboard,
const struct NetworkClipboardInbound * packet)
{
LGNetClipboardControl control;
if (packet->envelope.flags & LG_NET_ENVELOPE_RESPONSE ||
lgNetClipboardControlDecode(&control,
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK)
return NETWORK_CLIPBOARD_PROCESS_INVALID;
bool released = false;
LG_LOCK(clipboard->stateLock);
if (!networkClipboardEpochCurrentNL(clipboard, packet))
{
LG_UNLOCK(clipboard->stateLock);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
if (clipboard->remoteOffer.offerID &&
(!control.offerID ||
control.offerID == clipboard->remoteOffer.offerID))
{
released = clipboard->remoteOffer.published;
networkClipboardRemoteOfferResetNL(clipboard);
}
LG_UNLOCK(clipboard->stateLock);
if (released)
networkClipboardDispatchRelease(clipboard);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
static enum NetworkClipboardProcessResult networkClipboardProcessRequest(
NetworkClipboard * clipboard,
const struct NetworkClipboardInbound * packet)
{
LGNetClipboardRequest request;
if (packet->envelope.flags & LG_NET_ENVELOPE_RESPONSE ||
!(packet->envelope.flags & LG_NET_ENVELOPE_ACK_REQUIRED) ||
lgNetClipboardRequestDecode(&request,
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
request.requestID != packet->envelope.requestID || request.offset)
return NETWORK_CLIPBOARD_PROCESS_INVALID;
struct NetworkClipboardTransfer * transfer = calloc(1, sizeof(*transfer));
if (!transfer)
return NETWORK_CLIPBOARD_PROCESS_INVALID;
LG_LOCK(clipboard->stateLock);
if (!networkClipboardEpochCurrentNL(clipboard, packet) ||
request.offerID != clipboard->localOffer.offerID ||
request.formatIndex >= clipboard->localOffer.count ||
networkClipboardTransferCount(clipboard->clipboardWrites) >=
NETWORK_CLIPBOARD_FILE_REQUESTS ||
networkClipboardTransferFindWire(
clipboard->clipboardWrites, request.requestID))
{
LG_UNLOCK(clipboard->stateLock);
free(transfer);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
transfer->localRequest = networkClipboardNextLocalIDNL(clipboard, true);
transfer->wireRequest = request.requestID;
transfer->offerID = request.offerID;
transfer->type = clipboard->localOffer.types[request.formatIndex];
transfer->formatIndex = request.formatIndex;
transfer->maxChunkLength = request.maxChunkLength <
NETWORK_CLIPBOARD_EMITTED_CHUNK ? request.maxChunkLength :
NETWORK_CLIPBOARD_EMITTED_CHUNK;
transfer->next = clipboard->clipboardWrites;
clipboard->clipboardWrites = transfer;
const LG_ClipboardRequest localRequest = transfer->localRequest;
const LG_ClipboardData type = transfer->type;
LG_UNLOCK(clipboard->stateLock);
if (networkClipboardDispatchRequest(clipboard, localRequest, type))
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
LG_LOCK(clipboard->stateLock);
transfer = networkClipboardTransferTake(
&clipboard->clipboardWrites, localRequest);
if (transfer)
networkClipboardQueueClipboardStatusNL(clipboard,
transfer->wireRequest, transfer->offerID,
LG_NET_CLIPBOARD_STATUS_UNSUPPORTED, 0, 0);
LG_UNLOCK(clipboard->stateLock);
free(transfer);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
static void networkClipboardRejectRead(NetworkClipboard * clipboard,
uint64_t wireRequest, LG_ClipboardCancelReason reason)
{
LG_ClipboardRequest localRequest = LG_CLIPBOARD_REQUEST_INVALID;
LG_LOCK(clipboard->stateLock);
struct NetworkClipboardTransfer * transfer =
networkClipboardTransferTakeWire(
&clipboard->clipboardReads, wireRequest);
if (transfer)
{
localRequest = transfer->localRequest;
networkClipboardQueueClipboardStatusNL(clipboard,
transfer->wireRequest, transfer->offerID,
LG_NET_CLIPBOARD_STATUS_CANCELLED, (uint32_t)reason,
transfer->offset);
}
LG_UNLOCK(clipboard->stateLock);
free(transfer);
if (localRequest)
networkClipboardDispatchDataCancel(
clipboard, localRequest, reason);
}
static enum NetworkClipboardProcessResult
networkClipboardProcessDataBegin(NetworkClipboard * clipboard,
const struct NetworkClipboardInbound * packet)
{
LGNetClipboardTransfer wire;
if (packet->envelope.flags & LG_NET_ENVELOPE_RESPONSE ||
lgNetClipboardTransferDecode(&wire,
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
wire.requestID != packet->envelope.requestID || wire.processedLength)
return NETWORK_CLIPBOARD_PROCESS_INVALID;
LG_ClipboardRequest localRequest;
LG_ClipboardData type;
LG_LOCK(clipboard->stateLock);
if (!networkClipboardEpochCurrentNL(clipboard, packet))
{
LG_UNLOCK(clipboard->stateLock);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
struct NetworkClipboardTransfer * transfer =
networkClipboardTransferFindWire(
clipboard->clipboardReads, wire.requestID);
if (!transfer || transfer->offerID != wire.offerID || transfer->began)
{
LG_UNLOCK(clipboard->stateLock);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
localRequest = transfer->localRequest;
type = transfer->type;
LG_UNLOCK(clipboard->stateLock);
const LG_ClipboardResult result = networkClipboardDispatchDataBegin(
clipboard, localRequest, type, wire.totalLength);
if (result == LG_CLIPBOARD_RESULT_BLOCKED)
{
LG_LOCK(clipboard->stateLock);
clipboard->inboundBlockedRequest = localRequest;
LG_UNLOCK(clipboard->stateLock);
return NETWORK_CLIPBOARD_PROCESS_BLOCKED;
}
if (result != LG_CLIPBOARD_RESULT_ACCEPTED)
{
networkClipboardRejectRead(
clipboard, wire.requestID, LG_CLIPBOARD_CANCEL_INVALID);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
LG_LOCK(clipboard->stateLock);
transfer = networkClipboardTransferFindWire(
clipboard->clipboardReads, wire.requestID);
if (transfer && !transfer->began)
{
transfer->began = true;
transfer->sizeHint = wire.totalLength;
transfer->offset = 0;
transfer->delivery = packet->delivery;
}
LG_UNLOCK(clipboard->stateLock);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
static enum NetworkClipboardProcessResult
networkClipboardProcessDataChunk(NetworkClipboard * clipboard,
const struct NetworkClipboardInbound * packet)
{
LGNetClipboardChunk chunk;
if (packet->envelope.flags & LG_NET_ENVELOPE_RESPONSE ||
lgNetClipboardChunkDecode(&chunk,
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
chunk.requestID != packet->envelope.requestID ||
chunk.dataLength > NETWORK_CLIPBOARD_EMITTED_CHUNK)
return NETWORK_CLIPBOARD_PROCESS_INVALID;
LG_ClipboardRequest localRequest;
LG_LOCK(clipboard->stateLock);
if (!networkClipboardEpochCurrentNL(clipboard, packet))
{
LG_UNLOCK(clipboard->stateLock);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
struct NetworkClipboardTransfer * transfer =
networkClipboardTransferFindWire(
clipboard->clipboardReads, chunk.requestID);
if (!transfer || !transfer->began || transfer->offerID != chunk.offerID ||
transfer->delivery != packet->delivery ||
transfer->offset != chunk.offset ||
(transfer->sizeHint != LG_CLIPBOARD_SIZE_UNKNOWN &&
transfer->sizeHint != chunk.totalLength))
{
LG_UNLOCK(clipboard->stateLock);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
localRequest = transfer->localRequest;
LG_UNLOCK(clipboard->stateLock);
const LG_ClipboardResult result = networkClipboardDispatchDataChunk(
clipboard, localRequest, chunk.offset, chunk.data, chunk.dataLength);
if (result == LG_CLIPBOARD_RESULT_BLOCKED)
{
LG_LOCK(clipboard->stateLock);
clipboard->inboundBlockedRequest = localRequest;
LG_UNLOCK(clipboard->stateLock);
return NETWORK_CLIPBOARD_PROCESS_BLOCKED;
}
if (result != LG_CLIPBOARD_RESULT_ACCEPTED)
{
networkClipboardRejectRead(
clipboard, chunk.requestID, LG_CLIPBOARD_CANCEL_INVALID);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
LG_LOCK(clipboard->stateLock);
transfer = networkClipboardTransferFindWire(
clipboard->clipboardReads, chunk.requestID);
if (transfer && transfer->offset == chunk.offset)
transfer->offset += chunk.dataLength;
LG_UNLOCK(clipboard->stateLock);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
static enum NetworkClipboardProcessResult networkClipboardProcessDataEnd(
NetworkClipboard * clipboard,
const struct NetworkClipboardInbound * packet)
{
LGNetClipboardTransfer wire;
if (packet->envelope.flags & LG_NET_ENVELOPE_RESPONSE ||
lgNetClipboardTransferDecode(&wire,
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
wire.requestID != packet->envelope.requestID ||
wire.processedLength != wire.totalLength)
return NETWORK_CLIPBOARD_PROCESS_INVALID;
LG_ClipboardRequest localRequest;
LG_LOCK(clipboard->stateLock);
if (!networkClipboardEpochCurrentNL(clipboard, packet))
{
LG_UNLOCK(clipboard->stateLock);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
struct NetworkClipboardTransfer * transfer =
networkClipboardTransferFindWire(
clipboard->clipboardReads, wire.requestID);
if (!transfer || !transfer->began || transfer->offerID != wire.offerID ||
transfer->delivery != packet->delivery ||
transfer->offset != wire.processedLength ||
(transfer->sizeHint != LG_CLIPBOARD_SIZE_UNKNOWN &&
transfer->sizeHint != wire.totalLength))
{
LG_UNLOCK(clipboard->stateLock);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
localRequest = transfer->localRequest;
LG_UNLOCK(clipboard->stateLock);
const LG_ClipboardResult result = networkClipboardDispatchDataEnd(
clipboard, localRequest, wire.totalLength);
if (result == LG_CLIPBOARD_RESULT_BLOCKED)
{
LG_LOCK(clipboard->stateLock);
clipboard->inboundBlockedRequest = localRequest;
LG_UNLOCK(clipboard->stateLock);
return NETWORK_CLIPBOARD_PROCESS_BLOCKED;
}
if (result != LG_CLIPBOARD_RESULT_ACCEPTED)
{
networkClipboardRejectRead(
clipboard, wire.requestID, LG_CLIPBOARD_CANCEL_INVALID);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
LG_LOCK(clipboard->stateLock);
transfer = networkClipboardTransferTakeWire(
&clipboard->clipboardReads, wire.requestID);
LG_UNLOCK(clipboard->stateLock);
free(transfer);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
static enum NetworkClipboardProcessResult
networkClipboardProcessDataReadyMessage(NetworkClipboard * clipboard,
const struct NetworkClipboardInbound * packet)
{
LGNetClipboardTransfer wire;
if (packet->envelope.flags & LG_NET_ENVELOPE_RESPONSE ||
lgNetClipboardTransferDecode(&wire,
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
wire.requestID != packet->envelope.requestID || wire.processedLength)
return NETWORK_CLIPBOARD_PROCESS_INVALID;
LG_ClipboardRequest localRequest = LG_CLIPBOARD_REQUEST_INVALID;
LG_LOCK(clipboard->stateLock);
if (networkClipboardEpochCurrentNL(clipboard, packet))
{
struct NetworkClipboardTransfer * transfer =
networkClipboardTransferFindWire(
clipboard->clipboardWrites, wire.requestID);
if (transfer && transfer->offerID == wire.offerID &&
transfer->blocked)
{
transfer->blocked = false;
localRequest = transfer->localRequest;
}
}
LG_UNLOCK(clipboard->stateLock);
if (localRequest)
networkClipboardDispatchDataReady(clipboard, localRequest);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
static enum NetworkClipboardProcessResult networkClipboardProcessCancel(
NetworkClipboard * clipboard,
const struct NetworkClipboardInbound * packet)
{
LGNetClipboardTransfer wire;
if (lgNetClipboardTransferDecode(&wire,
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
wire.requestID != packet->envelope.requestID)
return NETWORK_CLIPBOARD_PROCESS_INVALID;
LG_ClipboardRequest localRead = LG_CLIPBOARD_REQUEST_INVALID;
LG_ClipboardRequest localWrite = LG_CLIPBOARD_REQUEST_INVALID;
LG_LOCK(clipboard->stateLock);
if (networkClipboardEpochCurrentNL(clipboard, packet))
{
struct NetworkClipboardTransfer * read =
networkClipboardTransferFindWire(
clipboard->clipboardReads, wire.requestID);
struct NetworkClipboardTransfer * write =
networkClipboardTransferFindWire(
clipboard->clipboardWrites, wire.requestID);
if (read && read->offerID == wire.offerID)
{
read = networkClipboardTransferTakeWire(
&clipboard->clipboardReads, wire.requestID);
localRead = read->localRequest;
}
else
read = NULL;
if (write && write->offerID == wire.offerID)
{
write = networkClipboardTransferTakeWire(
&clipboard->clipboardWrites, wire.requestID);
localWrite = write->localRequest;
}
else
write = NULL;
free(read);
free(write);
}
LG_UNLOCK(clipboard->stateLock);
if (localRead)
networkClipboardDispatchDataCancel(
clipboard, localRead, LG_CLIPBOARD_CANCEL_ABORTED);
if (localWrite)
networkClipboardDispatchRequestCancel(
clipboard, localWrite, LG_CLIPBOARD_CANCEL_ABORTED);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
static enum NetworkClipboardProcessResult networkClipboardProcessStatus(
NetworkClipboard * clipboard,
const struct NetworkClipboardInbound * packet)
{
LGNetClipboardStatus status;
if (!(packet->envelope.flags & LG_NET_ENVELOPE_RESPONSE) ||
lgNetClipboardStatusDecode(&status,
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
status.requestID != packet->envelope.requestID)
return NETWORK_CLIPBOARD_PROCESS_INVALID;
if (status.status == LG_NET_CLIPBOARD_STATUS_OK)
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
const LG_ClipboardCancelReason reason =
status.status == LG_NET_CLIPBOARD_STATUS_CANCELLED &&
status.detail <= LG_CLIPBOARD_CANCEL_INVALID ?
(LG_ClipboardCancelReason)status.detail :
LG_CLIPBOARD_CANCEL_UNAVAILABLE;
LG_ClipboardRequest localRead = LG_CLIPBOARD_REQUEST_INVALID;
LG_ClipboardRequest localWrite = LG_CLIPBOARD_REQUEST_INVALID;
LG_LOCK(clipboard->stateLock);
if (networkClipboardEpochCurrentNL(clipboard, packet))
{
struct NetworkClipboardTransfer * read =
networkClipboardTransferFindWire(
clipboard->clipboardReads, status.requestID);
struct NetworkClipboardTransfer * write =
networkClipboardTransferFindWire(
clipboard->clipboardWrites, status.requestID);
if (read && read->offerID == status.offerID)
{
read = networkClipboardTransferTakeWire(
&clipboard->clipboardReads, status.requestID);
localRead = read->localRequest;
}
else
read = NULL;
if (write && write->offerID == status.offerID)
{
write = networkClipboardTransferTakeWire(
&clipboard->clipboardWrites, status.requestID);
localWrite = write->localRequest;
}
else
write = NULL;
free(read);
free(write);
}
LG_UNLOCK(clipboard->stateLock);
if (localRead)
networkClipboardDispatchDataCancel(
clipboard, localRead, reason);
if (localWrite)
networkClipboardDispatchRequestCancel(
clipboard, localWrite, reason);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
static enum NetworkClipboardProcessResult networkClipboardProcessFileOffer(
NetworkClipboard * clipboard,
const struct NetworkClipboardInbound * packet)
{
LGNetFileOffer offer;
if (packet->envelope.flags & LG_NET_ENVELOPE_RESPONSE ||
lgNetFileOfferDecode(&offer,
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
packet->envelope.requestID != offer.offerID || offer.totalBytes ||
offer.entryCount != 1 || offer.labelLength ||
offer.flags != (LG_NET_FILE_OFFER_COPY |
LG_NET_FILE_OFFER_RECURSIVE))
return NETWORK_CLIPBOARD_PROCESS_INVALID;
LG_LOCK(clipboard->stateLock);
if (!networkClipboardEpochCurrentNL(clipboard, packet))
{
LG_UNLOCK(clipboard->stateLock);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
struct NetworkRemoteOffer * remote = &clipboard->remoteOffer;
if (remote->offerID &&
(remote->epoch != packet->envelope.componentEpoch ||
remote->offerID != offer.offerID))
networkClipboardRemoteOfferResetNL(clipboard);
remote->epoch = packet->envelope.componentEpoch;
remote->offerID = offer.offerID;
remote->fileOffer = true;
LG_UNLOCK(clipboard->stateLock);
networkClipboardPublishRemoteOffer(clipboard);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
static enum NetworkClipboardProcessResult
networkClipboardProcessFileAcquire(NetworkClipboard * clipboard,
const struct NetworkClipboardInbound * packet)
{
LGNetFileLease lease;
if (packet->envelope.flags & LG_NET_ENVELOPE_RESPONSE ||
!(packet->envelope.flags & LG_NET_ENVELOPE_ACK_REQUIRED) ||
lgNetFileLeaseDecode(&lease,
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
lease.clientID != packet->envelope.requestID || lease.leaseEpoch ||
lease.flags != LG_NET_FILE_LEASE_READ)
return NETWORK_CLIPBOARD_PROCESS_INVALID;
struct NetworkFileAcquisition * acquisition =
calloc(1, sizeof(*acquisition));
if (!acquisition)
return NETWORK_CLIPBOARD_PROCESS_INVALID;
LG_LOCK(clipboard->stateLock);
if (!networkClipboardEpochCurrentNL(clipboard, packet) ||
lease.offerID != clipboard->localOffer.offerID ||
!clipboard->localOffer.files ||
networkClipboardAcquisitionCountNL(clipboard) >=
NETWORK_CLIPBOARD_FILE_ACQUISITIONS ||
networkClipboardAcquisitionFindWire(
clipboard, lease.offerID, lease.clientID, true))
{
LG_UNLOCK(clipboard->stateLock);
free(acquisition);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
acquisition->dataset = lease.offerID;
acquisition->localAcquisition =
networkClipboardNextLocalIDNL(clipboard, true);
acquisition->wireClientID = lease.clientID;
acquisition->fromRemote = true;
acquisition->next = clipboard->fileAcquisitions;
clipboard->fileAcquisitions = acquisition;
const uint64_t localAcquisition = acquisition->localAcquisition;
LG_UNLOCK(clipboard->stateLock);
networkClipboardDispatchFileAcquire(
clipboard, lease.offerID, localAcquisition);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
static enum NetworkClipboardProcessResult
networkClipboardProcessFileAcquired(NetworkClipboard * clipboard,
const struct NetworkClipboardInbound * packet)
{
LGNetFileLease lease;
if (!(packet->envelope.flags & LG_NET_ENVELOPE_RESPONSE) ||
lgNetFileLeaseDecode(&lease,
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
lease.clientID != packet->envelope.requestID || !lease.leaseEpoch ||
lease.flags != (LG_NET_FILE_LEASE_READ |
LG_NET_FILE_LEASE_ACQUIRED))
return NETWORK_CLIPBOARD_PROCESS_INVALID;
uint64_t localAcquisition = 0;
LG_LOCK(clipboard->stateLock);
if (networkClipboardEpochCurrentNL(clipboard, packet))
{
struct NetworkFileAcquisition * acquisition =
networkClipboardAcquisitionFindWire(
clipboard, lease.offerID, lease.clientID, false);
if (acquisition && !acquisition->active)
{
acquisition->leaseEpoch = lease.leaseEpoch;
acquisition->active = true;
localAcquisition = acquisition->localAcquisition;
}
}
LG_UNLOCK(clipboard->stateLock);
if (localAcquisition)
networkClipboardDispatchFileAcquired(clipboard, lease.offerID,
localAcquisition, LG_CLIPBOARD_FILE_ERROR_NONE);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
static enum NetworkClipboardProcessResult
networkClipboardProcessFileReleaseMessage(NetworkClipboard * clipboard,
const struct NetworkClipboardInbound * packet)
{
LGNetFileLease lease;
if (packet->envelope.flags & LG_NET_ENVELOPE_RESPONSE ||
lgNetFileLeaseDecode(&lease,
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
lease.clientID != packet->envelope.requestID || !lease.leaseEpoch ||
lease.leaseMs || lease.flags != LG_NET_FILE_LEASE_READ)
return NETWORK_CLIPBOARD_PROCESS_INVALID;
uint64_t localAcquisition = 0;
LG_LOCK(clipboard->stateLock);
if (networkClipboardEpochCurrentNL(clipboard, packet))
{
struct NetworkFileAcquisition * acquisition =
networkClipboardAcquisitionFindWire(
clipboard, lease.offerID, lease.clientID, true);
if (acquisition && acquisition->active &&
acquisition->leaseEpoch == lease.leaseEpoch)
{
localAcquisition = acquisition->localAcquisition;
acquisition = networkClipboardAcquisitionTake(clipboard,
lease.offerID, localAcquisition, true);
free(acquisition);
}
}
LG_UNLOCK(clipboard->stateLock);
if (localAcquisition)
networkClipboardDispatchFileRelease(
clipboard, lease.offerID, localAcquisition);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
static enum NetworkClipboardProcessResult
networkClipboardProcessFileRequestMessage(NetworkClipboard * clipboard,
const struct NetworkClipboardInbound * packet)
{
LGNetFileRequest wire;
if (packet->envelope.flags & LG_NET_ENVELOPE_RESPONSE ||
!(packet->envelope.flags & LG_NET_ENVELOPE_ACK_REQUIRED) ||
lgNetFileRequestDecode(&wire,
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
wire.requestID != packet->envelope.requestID)
return NETWORK_CLIPBOARD_PROCESS_INVALID;
const KVMFRClipboardFileOperation operation =
lgNetFileKVMFROperationFromRequestFlags(wire.flags);
uint64_t node;
if (!operation ||
!lgNetFileKVMFRNodeFromEntryID(wire.entryID, &node) ||
(operation == KVMFR_CLIPBOARD_FILE_OP_READ &&
wire.length > NETWORK_CLIPBOARD_FILE_READ_MAX))
return NETWORK_CLIPBOARD_PROCESS_INVALID;
struct NetworkFileTransfer * transfer = calloc(1, sizeof(*transfer));
if (!transfer)
return NETWORK_CLIPBOARD_PROCESS_INVALID;
LG_LOCK(clipboard->stateLock);
if (!networkClipboardEpochCurrentNL(clipboard, packet) ||
wire.offerID != clipboard->localOffer.offerID ||
!clipboard->localOffer.files ||
!networkClipboardDatasetAcquiredNL(
clipboard, wire.offerID, true) ||
networkClipboardFileTransferCountNL(clipboard) >=
NETWORK_CLIPBOARD_FILE_REQUESTS ||
networkClipboardFileTransferFindWire(
clipboard->fileWrites, wire.requestID))
{
LG_UNLOCK(clipboard->stateLock);
free(transfer);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
transfer->request = (LG_ClipboardFileRequest)
{
.dataset = wire.offerID,
.request = networkClipboardNextLocalIDNL(clipboard, true),
.node = node,
.offset = wire.offset,
.length = wire.length,
.operation = operation == KVMFR_CLIPBOARD_FILE_OP_READ ?
LG_CLIPBOARD_FILE_READ : LG_CLIPBOARD_FILE_LIST,
};
transfer->wireRequest = wire.requestID;
transfer->entryID = wire.entryID;
transfer->next = clipboard->fileWrites;
clipboard->fileWrites = transfer;
const LG_ClipboardFileRequest request = transfer->request;
LG_UNLOCK(clipboard->stateLock);
networkClipboardDispatchFileRequest(clipboard, &request);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
static void networkClipboardRejectFileRead(NetworkClipboard * clipboard,
uint64_t wireRequest, LG_ClipboardFileError reason)
{
LG_ClipboardFileRequest request = { 0 };
LG_LOCK(clipboard->stateLock);
struct NetworkFileTransfer * transfer =
networkClipboardFileTransferTakeWire(
&clipboard->fileReads, wireRequest);
if (transfer)
{
request = transfer->request;
networkClipboardQueueFileStatusNL(clipboard,
transfer->wireRequest, transfer->request.dataset,
transfer->entryID, reason, transfer->responseOffset);
}
LG_UNLOCK(clipboard->stateLock);
free(transfer);
if (request.request)
networkClipboardDispatchFileCancel(
clipboard, request.dataset, request.request, reason);
}
static enum NetworkClipboardProcessResult
networkClipboardProcessFileDataBegin(NetworkClipboard * clipboard,
const struct NetworkClipboardInbound * packet)
{
LGNetFileTransfer wire;
if (packet->envelope.flags & LG_NET_ENVELOPE_RESPONSE ||
lgNetFileTransferDecode(&wire,
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
wire.requestID != packet->envelope.requestID || wire.processedLength)
return NETWORK_CLIPBOARD_PROCESS_INVALID;
LG_ClipboardFileRequest request;
LG_LOCK(clipboard->stateLock);
if (!networkClipboardEpochCurrentNL(clipboard, packet))
{
LG_UNLOCK(clipboard->stateLock);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
struct NetworkFileTransfer * transfer =
networkClipboardFileTransferFindWire(
clipboard->fileReads, wire.requestID);
if (!transfer || transfer->began ||
transfer->request.dataset != wire.offerID ||
transfer->entryID != wire.entryID ||
transfer->request.offset != wire.offset ||
(transfer->request.operation == LG_CLIPBOARD_FILE_READ &&
(wire.totalLength - wire.offset > NETWORK_CLIPBOARD_FILE_READ_MAX ||
wire.totalLength - wire.offset > transfer->request.length)))
{
LG_UNLOCK(clipboard->stateLock);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
request = transfer->request;
LG_UNLOCK(clipboard->stateLock);
const LG_ClipboardResult result = networkClipboardDispatchFileDataBegin(
clipboard, &request, wire.totalLength - wire.offset);
if (result == LG_CLIPBOARD_RESULT_BLOCKED)
{
networkClipboardRejectFileRead(clipboard, wire.requestID,
LG_CLIPBOARD_FILE_ERROR_IO);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
if (result != LG_CLIPBOARD_RESULT_ACCEPTED)
{
networkClipboardRejectFileRead(clipboard, wire.requestID,
LG_CLIPBOARD_FILE_ERROR_INVALID);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
LG_LOCK(clipboard->stateLock);
transfer = networkClipboardFileTransferFindWire(
clipboard->fileReads, wire.requestID);
if (transfer && !transfer->began)
{
transfer->began = true;
transfer->sizeHint = wire.totalLength - wire.offset;
transfer->responseOffset = 0;
transfer->delivery = packet->delivery;
}
LG_UNLOCK(clipboard->stateLock);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
static enum NetworkClipboardProcessResult
networkClipboardProcessFileDataChunk(NetworkClipboard * clipboard,
const struct NetworkClipboardInbound * packet)
{
LGNetFileChunk chunk;
if (packet->envelope.flags & LG_NET_ENVELOPE_RESPONSE ||
lgNetFileChunkDecode(&chunk,
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
chunk.requestID != packet->envelope.requestID ||
chunk.dataLength > NETWORK_CLIPBOARD_EMITTED_CHUNK ||
chunk.offset > chunk.totalLength)
return NETWORK_CLIPBOARD_PROCESS_INVALID;
LG_ClipboardFileRequest request;
uint64_t responseOffset;
LG_LOCK(clipboard->stateLock);
if (!networkClipboardEpochCurrentNL(clipboard, packet))
{
LG_UNLOCK(clipboard->stateLock);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
struct NetworkFileTransfer * transfer =
networkClipboardFileTransferFindWire(
clipboard->fileReads, chunk.requestID);
if (!transfer || !transfer->began ||
transfer->delivery != packet->delivery ||
transfer->request.dataset != chunk.offerID ||
transfer->entryID != chunk.entryID ||
transfer->responseOffset != chunk.offset ||
transfer->sizeHint != chunk.totalLength)
{
LG_UNLOCK(clipboard->stateLock);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
request = transfer->request;
responseOffset = transfer->responseOffset;
LG_UNLOCK(clipboard->stateLock);
const LG_ClipboardResult result = networkClipboardDispatchFileDataChunk(
clipboard, &request, responseOffset, chunk.data, chunk.dataLength);
if (result != LG_CLIPBOARD_RESULT_ACCEPTED)
{
networkClipboardRejectFileRead(clipboard, chunk.requestID,
result == LG_CLIPBOARD_RESULT_BLOCKED ?
LG_CLIPBOARD_FILE_ERROR_IO : LG_CLIPBOARD_FILE_ERROR_INVALID);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
LG_LOCK(clipboard->stateLock);
transfer = networkClipboardFileTransferFindWire(
clipboard->fileReads, chunk.requestID);
if (transfer && transfer->responseOffset == responseOffset)
transfer->responseOffset += chunk.dataLength;
LG_UNLOCK(clipboard->stateLock);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
static enum NetworkClipboardProcessResult
networkClipboardProcessFileDataEnd(NetworkClipboard * clipboard,
const struct NetworkClipboardInbound * packet)
{
LGNetFileTransfer wire;
if (packet->envelope.flags & LG_NET_ENVELOPE_RESPONSE ||
lgNetFileTransferDecode(&wire,
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
wire.requestID != packet->envelope.requestID ||
wire.processedLength != wire.totalLength - wire.offset)
return NETWORK_CLIPBOARD_PROCESS_INVALID;
LG_ClipboardFileRequest request;
LG_LOCK(clipboard->stateLock);
if (!networkClipboardEpochCurrentNL(clipboard, packet))
{
LG_UNLOCK(clipboard->stateLock);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
struct NetworkFileTransfer * transfer =
networkClipboardFileTransferFindWire(
clipboard->fileReads, wire.requestID);
if (!transfer || !transfer->began ||
transfer->delivery != packet->delivery ||
transfer->request.dataset != wire.offerID ||
transfer->entryID != wire.entryID ||
transfer->request.offset != wire.offset ||
transfer->responseOffset != wire.processedLength ||
transfer->request.offset > UINT64_MAX - transfer->sizeHint ||
transfer->request.offset + transfer->sizeHint != wire.totalLength ||
(transfer->request.operation == LG_CLIPBOARD_FILE_READ &&
transfer->sizeHint > NETWORK_CLIPBOARD_FILE_READ_MAX))
{
LG_UNLOCK(clipboard->stateLock);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
request = transfer->request;
LG_UNLOCK(clipboard->stateLock);
const LG_ClipboardResult result = networkClipboardDispatchFileDataEnd(
clipboard, &request, wire.processedLength);
if (result != LG_CLIPBOARD_RESULT_ACCEPTED)
{
networkClipboardRejectFileRead(clipboard, wire.requestID,
result == LG_CLIPBOARD_RESULT_BLOCKED ?
LG_CLIPBOARD_FILE_ERROR_IO : LG_CLIPBOARD_FILE_ERROR_INVALID);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
LG_LOCK(clipboard->stateLock);
transfer = networkClipboardFileTransferTakeWire(
&clipboard->fileReads, wire.requestID);
LG_UNLOCK(clipboard->stateLock);
free(transfer);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
static enum NetworkClipboardProcessResult
networkClipboardProcessFileReady(NetworkClipboard * clipboard,
const struct NetworkClipboardInbound * packet)
{
LGNetFileTransfer wire;
if (packet->envelope.flags & LG_NET_ENVELOPE_RESPONSE ||
lgNetFileTransferDecode(&wire,
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
wire.requestID != packet->envelope.requestID || wire.processedLength)
return NETWORK_CLIPBOARD_PROCESS_INVALID;
uint64_t localRequest = 0;
LG_LOCK(clipboard->stateLock);
if (networkClipboardEpochCurrentNL(clipboard, packet))
{
struct NetworkFileTransfer * transfer =
networkClipboardFileTransferFindWire(
clipboard->fileWrites, wire.requestID);
if (transfer && transfer->request.dataset == wire.offerID &&
transfer->entryID == wire.entryID && transfer->blocked)
{
transfer->blocked = false;
localRequest = transfer->request.request;
}
}
LG_UNLOCK(clipboard->stateLock);
if (localRequest)
networkClipboardDispatchFileReady(clipboard, localRequest);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
static enum NetworkClipboardProcessResult networkClipboardProcessFileCancel(
NetworkClipboard * clipboard,
const struct NetworkClipboardInbound * packet)
{
LGNetFileTransfer wire;
if (lgNetFileTransferDecode(&wire,
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
wire.requestID != packet->envelope.requestID)
return NETWORK_CLIPBOARD_PROCESS_INVALID;
LG_ClipboardFileRequest readRequest = { 0 };
LG_ClipboardFileRequest writeRequest = { 0 };
LG_LOCK(clipboard->stateLock);
if (networkClipboardEpochCurrentNL(clipboard, packet))
{
struct NetworkFileTransfer * read =
networkClipboardFileTransferFindWire(
clipboard->fileReads, wire.requestID);
struct NetworkFileTransfer * write =
networkClipboardFileTransferFindWire(
clipboard->fileWrites, wire.requestID);
if (read && read->request.dataset == wire.offerID &&
read->entryID == wire.entryID)
{
read = networkClipboardFileTransferTakeWire(
&clipboard->fileReads, wire.requestID);
readRequest = read->request;
}
else
read = NULL;
if (write && write->request.dataset == wire.offerID &&
write->entryID == wire.entryID)
{
write = networkClipboardFileTransferTakeWire(
&clipboard->fileWrites, wire.requestID);
writeRequest = write->request;
}
else
write = NULL;
free(read);
free(write);
}
LG_UNLOCK(clipboard->stateLock);
if (readRequest.request)
networkClipboardDispatchFileCancel(clipboard,
readRequest.dataset, readRequest.request,
LG_CLIPBOARD_FILE_ERROR_CANCELLED);
if (writeRequest.request)
networkClipboardDispatchFileCancel(clipboard,
writeRequest.dataset, writeRequest.request,
LG_CLIPBOARD_FILE_ERROR_CANCELLED);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
static enum NetworkClipboardProcessResult networkClipboardProcessFileStatus(
NetworkClipboard * clipboard,
const struct NetworkClipboardInbound * packet)
{
LGNetFileStatus status;
if (!(packet->envelope.flags & LG_NET_ENVELOPE_RESPONSE) ||
lgNetFileStatusDecode(&status,
packet->payload, packet->payloadLength) != LG_NET_PARSE_OK ||
status.requestID != packet->envelope.requestID)
return NETWORK_CLIPBOARD_PROCESS_INVALID;
const LG_ClipboardFileError error =
networkClipboardFileErrorFromStatus(status.status, status.detail);
uint64_t acquisition = 0;
LG_ClipboardFileRequest readRequest = { 0 };
LG_ClipboardFileRequest writeRequest = { 0 };
LG_LOCK(clipboard->stateLock);
if (networkClipboardEpochCurrentNL(clipboard, packet))
{
if (!status.entryID && status.status != LG_NET_FILE_STATUS_OK)
{
struct NetworkFileAcquisition * item =
networkClipboardAcquisitionFindWire(
clipboard, status.offerID, status.requestID, false);
if (item)
{
acquisition = item->localAcquisition;
item = networkClipboardAcquisitionTake(clipboard,
status.offerID, acquisition, false);
free(item);
}
}
else if (status.status != LG_NET_FILE_STATUS_OK)
{
struct NetworkFileTransfer * read =
networkClipboardFileTransferFindWire(
clipboard->fileReads, status.requestID);
struct NetworkFileTransfer * write =
networkClipboardFileTransferFindWire(
clipboard->fileWrites, status.requestID);
if (read && read->request.dataset == status.offerID &&
read->entryID == status.entryID)
{
read = networkClipboardFileTransferTakeWire(
&clipboard->fileReads, status.requestID);
readRequest = read->request;
}
else
read = NULL;
if (write && write->request.dataset == status.offerID &&
write->entryID == status.entryID)
{
write = networkClipboardFileTransferTakeWire(
&clipboard->fileWrites, status.requestID);
writeRequest = write->request;
}
else
write = NULL;
free(read);
free(write);
}
}
LG_UNLOCK(clipboard->stateLock);
if (acquisition)
networkClipboardDispatchFileAcquired(
clipboard, status.offerID, acquisition, error);
if (readRequest.request)
networkClipboardDispatchFileCancel(
clipboard, readRequest.dataset, readRequest.request, error);
if (writeRequest.request)
networkClipboardDispatchFileCancel(
clipboard, writeRequest.dataset, writeRequest.request, error);
return NETWORK_CLIPBOARD_PROCESS_CONSUMED;
}
static enum NetworkClipboardProcessResult networkClipboardProcessPacket(
NetworkClipboard * clipboard,
const struct NetworkClipboardInbound * packet)
{
if (packet->envelope.service == LG_NET_SERVICE_CLIPBOARD)
{
if (!networkClipboardEnvelopeValid(packet, LG_NET_SERVICE_CLIPBOARD))
return NETWORK_CLIPBOARD_PROCESS_INVALID;
switch (packet->envelope.messageType)
{
case LG_NET_CLIPBOARD_MESSAGE_CLAIM_RESULT:
return networkClipboardProcessClaimResult(clipboard, packet);
case LG_NET_CLIPBOARD_MESSAGE_KEEPALIVE:
{
LGNetClipboardControl control;
return lgNetClipboardControlDecode(&control,
packet->payload, packet->payloadLength) == LG_NET_PARSE_OK ?
NETWORK_CLIPBOARD_PROCESS_CONSUMED :
NETWORK_CLIPBOARD_PROCESS_INVALID;
}
case LG_NET_CLIPBOARD_MESSAGE_RELEASE:
case LG_NET_CLIPBOARD_MESSAGE_CLEAR:
return networkClipboardProcessClear(clipboard, packet);
case LG_NET_CLIPBOARD_MESSAGE_OFFER:
return networkClipboardProcessOffer(clipboard, packet);
case LG_NET_CLIPBOARD_MESSAGE_REQUEST:
return networkClipboardProcessRequest(clipboard, packet);
case LG_NET_CLIPBOARD_MESSAGE_DATA_BEGIN:
return networkClipboardProcessDataBegin(clipboard, packet);
case LG_NET_CLIPBOARD_MESSAGE_DATA_CHUNK:
return networkClipboardProcessDataChunk(clipboard, packet);
case LG_NET_CLIPBOARD_MESSAGE_DATA_END:
return networkClipboardProcessDataEnd(clipboard, packet);
case LG_NET_CLIPBOARD_MESSAGE_DATA_READY:
return networkClipboardProcessDataReadyMessage(clipboard, packet);
case LG_NET_CLIPBOARD_MESSAGE_CANCEL:
return networkClipboardProcessCancel(clipboard, packet);
case LG_NET_CLIPBOARD_MESSAGE_STATUS:
return networkClipboardProcessStatus(clipboard, packet);
case LG_NET_CLIPBOARD_MESSAGE_CLAIM:
default:
return NETWORK_CLIPBOARD_PROCESS_INVALID;
}
}
if (packet->envelope.service != LG_NET_SERVICE_FILE ||
!networkClipboardEnvelopeValid(packet, LG_NET_SERVICE_FILE))
return NETWORK_CLIPBOARD_PROCESS_INVALID;
switch (packet->envelope.messageType)
{
case LG_NET_FILE_MESSAGE_OFFER:
return networkClipboardProcessFileOffer(clipboard, packet);
case LG_NET_FILE_MESSAGE_ACQUIRE:
return networkClipboardProcessFileAcquire(clipboard, packet);
case LG_NET_FILE_MESSAGE_ACQUIRED:
return networkClipboardProcessFileAcquired(clipboard, packet);
case LG_NET_FILE_MESSAGE_RELEASE:
return networkClipboardProcessFileReleaseMessage(clipboard, packet);
case LG_NET_FILE_MESSAGE_REQUEST:
return networkClipboardProcessFileRequestMessage(clipboard, packet);
case LG_NET_FILE_MESSAGE_DATA_BEGIN:
return networkClipboardProcessFileDataBegin(clipboard, packet);
case LG_NET_FILE_MESSAGE_DATA_CHUNK:
return networkClipboardProcessFileDataChunk(clipboard, packet);
case LG_NET_FILE_MESSAGE_DATA_END:
return networkClipboardProcessFileDataEnd(clipboard, packet);
case LG_NET_FILE_MESSAGE_DATA_READY:
return networkClipboardProcessFileReady(clipboard, packet);
case LG_NET_FILE_MESSAGE_CANCEL:
return networkClipboardProcessFileCancel(clipboard, packet);
case LG_NET_FILE_MESSAGE_STATUS:
return networkClipboardProcessFileStatus(clipboard, packet);
case LG_NET_FILE_MESSAGE_ENTRY:
default:
return NETWORK_CLIPBOARD_PROCESS_INVALID;
}
}
static void networkClipboardProcessInbound(NetworkClipboard * clipboard)
{
for (unsigned processed = 0;
processed < NETWORK_CLIPBOARD_PROCESS_MAX; ++processed)
{
LG_LOCK(clipboard->stateLock);
struct NetworkClipboardInbound * packet = clipboard->inboundHead;
if (!packet || (clipboard->inboundBlocked &&
!clipboard->inboundReady))
{
LG_UNLOCK(clipboard->stateLock);
return;
}
clipboard->inboundReady = false;
LG_UNLOCK(clipboard->stateLock);
const enum NetworkClipboardProcessResult result =
networkClipboardProcessPacket(clipboard, packet);
if (result == NETWORK_CLIPBOARD_PROCESS_BLOCKED)
{
LG_LOCK(clipboard->stateLock);
if (clipboard->inboundHead == packet)
clipboard->inboundBlocked = true;
LG_UNLOCK(clipboard->stateLock);
return;
}
LG_LOCK(clipboard->stateLock);
if (clipboard->inboundHead == packet)
{
clipboard->inboundHead = packet->next;
if (!clipboard->inboundHead)
clipboard->inboundTail = NULL;
--clipboard->inboundCount;
clipboard->inboundBytes -=
sizeof(*packet) + packet->payloadLength;
clipboard->inboundBlocked = false;
clipboard->inboundBlockedRequest = LG_CLIPBOARD_REQUEST_INVALID;
free(packet);
}
LG_UNLOCK(clipboard->stateLock);
if (result == NETWORK_CLIPBOARD_PROCESS_INVALID)
{
DEBUG_ERROR("Invalid network clipboard service message");
networkConnectionCancel(clipboard->connection);
return;
}
}
}
static void networkClipboardSetStatusListener(void * opaque,
LG_ClipboardStatusFn callback, void * callbackOpaque)
{
NetworkClipboard * clipboard = opaque;
LG_LOCK(clipboard->statusLock);
LG_LOCK(clipboard->stateLock);
clipboard->statusCallback = callback;
clipboard->statusOpaque = callbackOpaque;
clipboard->statusNotifyPending = false;
const LG_ClipboardStatus status = networkClipboardStatusNL(clipboard);
LG_UNLOCK(clipboard->stateLock);
if (callback)
callback(callbackOpaque, &status);
LG_UNLOCK(clipboard->statusLock);
}
static bool networkClipboardAttach(void * opaque,
const LG_ClipboardEventOps * events, void * eventOpaque)
{
NetworkClipboard * clipboard = opaque;
if (!events)
return false;
LG_ClipboardData replay[NETWORK_CLIPBOARD_FORMATS];
uint64_t dataset = 0;
uint32_t count = 0;
bool files = false;
LG_LOCK(clipboard->eventLock);
LG_LOCK(clipboard->stateLock);
bool admitted = !clipboard->destroying && clipboard->connected &&
clipboard->clipboardService && !clipboard->attached;
if (admitted)
{
clipboard->events = events;
clipboard->eventOpaque = eventOpaque;
clipboard->attached = true;
if (!clipboard->claimPending && !clipboard->claimed &&
!networkClipboardQueueClaimNL(clipboard))
{
clipboard->events = NULL;
clipboard->eventOpaque = NULL;
clipboard->attached = false;
admitted = false;
}
}
if (admitted && clipboard->remoteOffer.published)
{
dataset = clipboard->remoteOffer.offerID;
count = clipboard->remoteOffer.formatCount;
files = (clipboard->remoteOffer.formatMask &
(UINT32_C(1) << LG_CLIPBOARD_DATA_FILES)) != 0;
memcpy(replay, clipboard->remoteOffer.types,
count * sizeof(*replay));
}
LG_UNLOCK(clipboard->stateLock);
if (admitted && count)
{
if (files && events->fileOffer)
events->fileOffer(eventOpaque, dataset);
if (events->notice)
events->notice(eventOpaque, replay, count);
}
LG_UNLOCK(clipboard->eventLock);
if (admitted)
networkClipboardFlushQueued(clipboard);
return admitted;
}
static void networkClipboardDetach(void * opaque)
{
NetworkClipboard * clipboard = opaque;
LG_LOCK(clipboard->eventLock);
LG_LOCK(clipboard->stateLock);
struct NetworkClipboardTransfer * clipboardReads =
clipboard->clipboardReads;
struct NetworkClipboardTransfer * clipboardWrites =
clipboard->clipboardWrites;
struct NetworkFileTransfer * fileReads = clipboard->fileReads;
struct NetworkFileTransfer * fileWrites = clipboard->fileWrites;
struct NetworkFileAcquisition * acquisitions =
clipboard->fileAcquisitions;
clipboard->clipboardReads = NULL;
clipboard->clipboardWrites = NULL;
clipboard->fileReads = NULL;
clipboard->fileWrites = NULL;
clipboard->fileAcquisitions = NULL;
networkClipboardClearSendNL(clipboard);
if (clipboard->attached)
networkClipboardQueueReleaseNL(clipboard);
clipboard->events = NULL;
clipboard->eventOpaque = NULL;
clipboard->attached = false;
clipboard->inboundReady = true;
clipboard->localOffer = (struct NetworkLocalOffer) { 0 };
networkClipboardRemoteOfferResetNL(clipboard);
LG_UNLOCK(clipboard->stateLock);
LG_UNLOCK(clipboard->eventLock);
networkClipboardTransfersFree(clipboardReads);
networkClipboardTransfersFree(clipboardWrites);
networkClipboardFileTransfersFree(fileReads);
networkClipboardFileTransfersFree(fileWrites);
networkClipboardAcquisitionsFree(acquisitions);
networkClipboardFlushQueued(clipboard);
}
static bool networkClipboardRelease(void * opaque)
{
NetworkClipboard * clipboard = opaque;
LG_LOCK(clipboard->stateLock);
if ((!clipboard->claimPending && !clipboard->claimed) ||
!clipboard->localOffer.offerID)
{
LG_UNLOCK(clipboard->stateLock);
return false;
}
const uint64_t offerID = clipboard->localOffer.offerID;
const bool queued = networkClipboardQueueControlNL(clipboard,
LG_NET_CLIPBOARD_MESSAGE_CLEAR, offerID);
if (queued)
clipboard->localOffer = (struct NetworkLocalOffer) { 0 };
LG_UNLOCK(clipboard->stateLock);
if (queued)
networkClipboardFlushQueued(clipboard);
return queued;
}
static bool networkClipboardNotifyTypes(void * opaque,
const LG_ClipboardData types[], size_t count)
{
NetworkClipboard * clipboard = opaque;
if (!types || !count || count > NETWORK_CLIPBOARD_FORMATS)
return false;
uint32_t mask = 0;
for (size_t index = 0; index < count; ++index)
{
if (!networkClipboardDataValid(types[index]) ||
types[index] == LG_CLIPBOARD_DATA_FILES)
return false;
const uint32_t bit = UINT32_C(1) << types[index];
if (mask & bit)
return false;
mask |= bit;
}
LG_LOCK(clipboard->stateLock);
if ((!clipboard->claimPending && !clipboard->claimed) ||
!clipboard->attached)
{
LG_UNLOCK(clipboard->stateLock);
return false;
}
struct NetworkLocalOffer previous = clipboard->localOffer;
memset(&clipboard->localOffer, 0, sizeof(clipboard->localOffer));
clipboard->localOffer.offerID =
networkClipboardNextLocalIDNL(clipboard, false);
clipboard->localOffer.serial =
networkClipboardNext64(clipboard->remoteSerial);
clipboard->remoteSerial = clipboard->localOffer.serial;
clipboard->localOffer.count = (uint32_t)count;
for (uint32_t index = 0; index < count; ++index)
{
clipboard->localOffer.types[index] = types[index];
clipboard->localOffer.indices[types[index]] = index;
}
const bool queued = networkClipboardQueueLocalOfferNL(clipboard);
if (!queued)
clipboard->localOffer = previous;
LG_UNLOCK(clipboard->stateLock);
if (queued)
networkClipboardFlushQueued(clipboard);
return queued;
}
static bool networkClipboardOfferFiles(void * opaque, uint64_t dataset)
{
NetworkClipboard * clipboard = opaque;
if (!dataset || kvmfrClipboardTransferFromHelper(dataset))
return false;
LG_LOCK(clipboard->stateLock);
if ((!clipboard->claimPending && !clipboard->claimed) ||
!clipboard->attached || !clipboard->fileService)
{
LG_UNLOCK(clipboard->stateLock);
return false;
}
struct NetworkLocalOffer previous = clipboard->localOffer;
memset(&clipboard->localOffer, 0, sizeof(clipboard->localOffer));
clipboard->localOffer.offerID = dataset;
clipboard->localOffer.serial =
networkClipboardNext64(clipboard->remoteSerial);
clipboard->remoteSerial = clipboard->localOffer.serial;
clipboard->localOffer.count = 1;
clipboard->localOffer.types[0] =
LG_CLIPBOARD_DATA_FILES;
clipboard->localOffer.indices[LG_CLIPBOARD_DATA_FILES] = 0;
clipboard->localOffer.files = true;
const bool queued = networkClipboardQueueLocalOfferNL(clipboard);
if (!queued)
clipboard->localOffer = previous;
LG_UNLOCK(clipboard->stateLock);
if (queued)
networkClipboardFlushQueued(clipboard);
return queued;
}
static LG_ClipboardResult networkClipboardDataBegin(void * opaque,
LG_ClipboardRequest request, LG_ClipboardData type, uint64_t sizeHint)
{
NetworkClipboard * clipboard = opaque;
if (!kvmfrClipboardTransferFromHelper(request) ||
!networkClipboardDataValid(type) || type == LG_CLIPBOARD_DATA_FILES)
return LG_CLIPBOARD_RESULT_FAILED;
LG_LOCK(clipboard->stateLock);
struct NetworkClipboardTransfer * transfer =
networkClipboardTransferFind(clipboard->clipboardWrites, request);
if (!transfer || transfer->began || transfer->type != type)
{
LG_UNLOCK(clipboard->stateLock);
return LG_CLIPBOARD_RESULT_FAILED;
}
transfer->delivery = clipboard->clipboardBulk ?
NETWORK_PACKET_DELIVERY_BULK : NETWORK_PACKET_DELIVERY_RELIABLE;
const LGNetClipboardTransfer wire =
{
.requestID = transfer->wireRequest,
.offerID = transfer->offerID,
.totalLength = sizeHint,
.processedLength = 0,
};
struct NetworkClipboardSend * send = networkClipboardSendWithDelivery(
networkClipboardTransferSendNew(clipboard,
LG_NET_CLIPBOARD_MESSAGE_DATA_BEGIN, &wire, 0),
transfer->delivery);
if (!send)
{
LG_UNLOCK(clipboard->stateLock);
return LG_CLIPBOARD_RESULT_FAILED;
}
if (!networkClipboardQueueSendNL(clipboard, send))
{
transfer->blocked = true;
LG_UNLOCK(clipboard->stateLock);
return LG_CLIPBOARD_RESULT_BLOCKED;
}
transfer->began = true;
transfer->blocked = false;
transfer->sizeHint = sizeHint;
transfer->offset = 0;
LG_UNLOCK(clipboard->stateLock);
networkClipboardFlushQueued(clipboard);
return LG_CLIPBOARD_RESULT_ACCEPTED;
}
static LG_ClipboardResult networkClipboardDataChunk(void * opaque,
LG_ClipboardRequest request, uint64_t offset,
const void * data, size_t size)
{
NetworkClipboard * clipboard = opaque;
if (!kvmfrClipboardTransferFromHelper(request) || !data || !size ||
size > NETWORK_CLIPBOARD_EMITTED_CHUNK || size > UINT32_MAX ||
offset > UINT64_MAX - size)
return LG_CLIPBOARD_RESULT_FAILED;
LG_LOCK(clipboard->stateLock);
struct NetworkClipboardTransfer * transfer =
networkClipboardTransferFind(clipboard->clipboardWrites, request);
if (!transfer || !transfer->began || transfer->offset != offset ||
size > transfer->maxChunkLength ||
(transfer->sizeHint != LG_CLIPBOARD_SIZE_UNKNOWN &&
offset + size > transfer->sizeHint))
{
LG_UNLOCK(clipboard->stateLock);
return LG_CLIPBOARD_RESULT_FAILED;
}
LGNetClipboardChunkFlags flags = offset ? 0 :
LG_NET_CLIPBOARD_CHUNK_FIRST;
if (transfer->sizeHint != LG_CLIPBOARD_SIZE_UNKNOWN &&
offset + size == transfer->sizeHint)
flags |= LG_NET_CLIPBOARD_CHUNK_FINAL;
const LGNetClipboardChunk chunk =
{
.requestID = transfer->wireRequest,
.offerID = transfer->offerID,
.offset = offset,
.totalLength = transfer->sizeHint,
.dataLength = (uint32_t)size,
.flags = flags,
.data = data,
};
struct NetworkClipboardSend * send =
networkClipboardSendWithDelivery(
networkClipboardChunkSendNew(clipboard, &chunk),
transfer->delivery);
if (!send)
{
LG_UNLOCK(clipboard->stateLock);
return LG_CLIPBOARD_RESULT_FAILED;
}
if (!networkClipboardQueueSendNL(clipboard, send))
{
transfer->blocked = true;
LG_UNLOCK(clipboard->stateLock);
return LG_CLIPBOARD_RESULT_BLOCKED;
}
transfer->offset += size;
transfer->blocked = false;
LG_UNLOCK(clipboard->stateLock);
networkClipboardFlushQueued(clipboard);
return LG_CLIPBOARD_RESULT_ACCEPTED;
}
static LG_ClipboardResult networkClipboardDataEnd(void * opaque,
LG_ClipboardRequest request, uint64_t finalSize)
{
NetworkClipboard * clipboard = opaque;
if (!kvmfrClipboardTransferFromHelper(request))
return LG_CLIPBOARD_RESULT_FAILED;
LG_LOCK(clipboard->stateLock);
struct NetworkClipboardTransfer * transfer =
networkClipboardTransferFind(clipboard->clipboardWrites, request);
if (!transfer || !transfer->began || transfer->offset != finalSize ||
(transfer->sizeHint != LG_CLIPBOARD_SIZE_UNKNOWN &&
transfer->sizeHint != finalSize))
{
LG_UNLOCK(clipboard->stateLock);
return LG_CLIPBOARD_RESULT_FAILED;
}
const LGNetClipboardTransfer wire =
{
.requestID = transfer->wireRequest,
.offerID = transfer->offerID,
.totalLength = finalSize,
.processedLength = finalSize,
};
struct NetworkClipboardSend * send = networkClipboardSendWithDelivery(
networkClipboardTransferSendNew(clipboard,
LG_NET_CLIPBOARD_MESSAGE_DATA_END, &wire, 0),
transfer->delivery);
if (!send)
{
LG_UNLOCK(clipboard->stateLock);
return LG_CLIPBOARD_RESULT_FAILED;
}
if (!networkClipboardQueueSendNL(clipboard, send))
{
transfer->blocked = true;
LG_UNLOCK(clipboard->stateLock);
return LG_CLIPBOARD_RESULT_BLOCKED;
}
transfer = networkClipboardTransferTake(
&clipboard->clipboardWrites, request);
LG_UNLOCK(clipboard->stateLock);
free(transfer);
networkClipboardFlushQueued(clipboard);
return LG_CLIPBOARD_RESULT_ACCEPTED;
}
static bool networkClipboardData(void * opaque,
LG_ClipboardRequest request, LG_ClipboardData type,
const void * data, size_t size)
{
NetworkClipboard * clipboard = opaque;
if (!kvmfrClipboardTransferFromHelper(request) ||
(type != LG_CLIPBOARD_DATA_NONE &&
(!networkClipboardDataValid(type) ||
type == LG_CLIPBOARD_DATA_FILES)) ||
(size && !data) || (type == LG_CLIPBOARD_DATA_NONE && size))
return false;
LG_LOCK(clipboard->stateLock);
struct NetworkClipboardTransfer * transfer =
networkClipboardTransferFind(clipboard->clipboardWrites, request);
if (!transfer || transfer->began ||
(type != LG_CLIPBOARD_DATA_NONE && transfer->type != type))
{
LG_UNLOCK(clipboard->stateLock);
return false;
}
if (type == LG_CLIPBOARD_DATA_NONE)
{
const bool queued = networkClipboardQueueClipboardStatusNL(clipboard,
transfer->wireRequest, transfer->offerID,
LG_NET_CLIPBOARD_STATUS_UNSUPPORTED, 0, 0);
if (queued)
transfer = networkClipboardTransferTake(
&clipboard->clipboardWrites, request);
LG_UNLOCK(clipboard->stateLock);
if (queued)
{
free(transfer);
networkClipboardFlushQueued(clipboard);
}
return queued;
}
const size_t maxChunkLength = transfer->maxChunkLength <
NETWORK_CLIPBOARD_EMITTED_CHUNK ? transfer->maxChunkLength :
NETWORK_CLIPBOARD_EMITTED_CHUNK;
const size_t chunkCount = size ?
1U + (size - 1U) / maxChunkLength : 0;
if (chunkCount > NETWORK_CLIPBOARD_PENDING_MAX - 2U)
{
LG_UNLOCK(clipboard->stateLock);
return false;
}
const size_t fixedBytes =
2U * (sizeof(struct NetworkClipboardSend) +
LG_NET_CLIPBOARD_TRANSFER_WIRE_SIZE) +
chunkCount * (sizeof(struct NetworkClipboardSend) +
LG_NET_CLIPBOARD_CHUNK_HEADER_WIRE_SIZE);
if (fixedBytes > NETWORK_CLIPBOARD_PENDING_BYTES ||
size > NETWORK_CLIPBOARD_PENDING_BYTES - fixedBytes)
{
LG_UNLOCK(clipboard->stateLock);
return false;
}
const NetworkPacketDelivery delivery = clipboard->clipboardBulk ?
NETWORK_PACKET_DELIVERY_BULK : NETWORK_PACKET_DELIVERY_RELIABLE;
struct NetworkClipboardBatch batch = { 0 };
const LGNetClipboardTransfer begin =
{
.requestID = transfer->wireRequest,
.offerID = transfer->offerID,
.totalLength = size,
.processedLength = 0,
};
struct NetworkClipboardSend * send = networkClipboardSendWithDelivery(
networkClipboardTransferSendNew(clipboard,
LG_NET_CLIPBOARD_MESSAGE_DATA_BEGIN, &begin, 0), delivery);
if (!send)
goto fail;
networkClipboardBatchAppend(&batch, send);
size_t offset = 0;
while (offset < size)
{
size_t chunkLength = size - offset;
if (chunkLength > transfer->maxChunkLength)
chunkLength = transfer->maxChunkLength;
if (chunkLength > NETWORK_CLIPBOARD_EMITTED_CHUNK)
chunkLength = NETWORK_CLIPBOARD_EMITTED_CHUNK;
const bool final = offset + chunkLength == size;
const LGNetClipboardChunk chunk =
{
.requestID = transfer->wireRequest,
.offerID = transfer->offerID,
.offset = offset,
.totalLength = size,
.dataLength = (uint32_t)chunkLength,
.flags = (offset ? 0 : LG_NET_CLIPBOARD_CHUNK_FIRST) |
(final ? LG_NET_CLIPBOARD_CHUNK_FINAL : 0),
.data = (const uint8_t *)data + offset,
};
send = networkClipboardSendWithDelivery(
networkClipboardChunkSendNew(clipboard, &chunk), delivery);
if (!send)
goto fail;
networkClipboardBatchAppend(&batch, send);
offset += chunkLength;
}
const LGNetClipboardTransfer end =
{
.requestID = transfer->wireRequest,
.offerID = transfer->offerID,
.totalLength = size,
.processedLength = size,
};
send = networkClipboardSendWithDelivery(
networkClipboardTransferSendNew(clipboard,
LG_NET_CLIPBOARD_MESSAGE_DATA_END, &end, 0), delivery);
if (!send)
goto fail;
networkClipboardBatchAppend(&batch, send);
if (!networkClipboardQueueBatchNL(clipboard, &batch))
goto fail;
transfer = networkClipboardTransferTake(
&clipboard->clipboardWrites, request);
LG_UNLOCK(clipboard->stateLock);
free(transfer);
networkClipboardFlushQueued(clipboard);
return true;
fail:
networkClipboardBatchFree(&batch);
LG_UNLOCK(clipboard->stateLock);
return false;
}
static bool networkClipboardDataCancel(void * opaque,
LG_ClipboardRequest request, LG_ClipboardCancelReason reason)
{
NetworkClipboard * clipboard = opaque;
if (!kvmfrClipboardTransferFromHelper(request) ||
reason > LG_CLIPBOARD_CANCEL_INVALID)
return false;
LG_LOCK(clipboard->stateLock);
struct NetworkClipboardTransfer * transfer =
networkClipboardTransferFind(clipboard->clipboardWrites, request);
if (!transfer)
{
LG_UNLOCK(clipboard->stateLock);
return false;
}
const LGNetClipboardTransfer wire =
{
.requestID = transfer->wireRequest,
.offerID = transfer->offerID,
.totalLength = transfer->sizeHint,
.processedLength = transfer->offset,
};
struct NetworkClipboardSend * send = networkClipboardTransferSendNew(
clipboard, LG_NET_CLIPBOARD_MESSAGE_CANCEL, &wire, 0);
const bool queued = networkClipboardQueueSendNL(clipboard, send);
if (queued)
transfer = networkClipboardTransferTake(
&clipboard->clipboardWrites, request);
LG_UNLOCK(clipboard->stateLock);
if (queued)
{
free(transfer);
networkClipboardFlushQueued(clipboard);
}
return queued;
}
static bool networkClipboardDataReady(void * opaque,
LG_ClipboardRequest request)
{
NetworkClipboard * clipboard = opaque;
if (!request)
return false;
LG_LOCK(clipboard->stateLock);
struct NetworkClipboardTransfer * transfer =
networkClipboardTransferFind(clipboard->clipboardReads, request);
if (!transfer || !clipboard->inboundBlocked ||
clipboard->inboundBlockedRequest != request)
{
LG_UNLOCK(clipboard->stateLock);
return false;
}
clipboard->inboundReady = true;
LG_UNLOCK(clipboard->stateLock);
networkClipboardFlushQueued(clipboard);
return true;
}
static bool networkClipboardRequest(void * opaque,
LG_ClipboardRequest request, LG_ClipboardData type)
{
NetworkClipboard * clipboard = opaque;
if (!request || kvmfrClipboardTransferFromHelper(request) ||
!networkClipboardDataValid(type) || type == LG_CLIPBOARD_DATA_FILES)
return false;
struct NetworkClipboardTransfer * transfer = calloc(1, sizeof(*transfer));
if (!transfer)
return false;
LG_LOCK(clipboard->stateLock);
const struct NetworkRemoteOffer * offer = &clipboard->remoteOffer;
if (!offer->published || offer->indices[type] == UINT32_MAX ||
networkClipboardTransferCount(clipboard->clipboardReads) >=
NETWORK_CLIPBOARD_FILE_REQUESTS ||
networkClipboardTransferFind(clipboard->clipboardReads, request))
{
LG_UNLOCK(clipboard->stateLock);
free(transfer);
return false;
}
transfer->localRequest = request;
transfer->wireRequest = networkClipboardNextWireIDNL(clipboard);
transfer->offerID = offer->offerID;
transfer->type = type;
transfer->formatIndex = offer->indices[type];
transfer->maxChunkLength = NETWORK_CLIPBOARD_EMITTED_CHUNK;
const LGNetClipboardRequest wire =
{
.offerID = transfer->offerID,
.requestID = transfer->wireRequest,
.formatIndex = transfer->formatIndex,
.maxChunkLength = transfer->maxChunkLength,
.offset = 0,
};
struct NetworkClipboardSend * send =
networkClipboardRequestSendNew(clipboard, &wire);
if (!send || !networkClipboardQueueSendNL(clipboard, send))
{
LG_UNLOCK(clipboard->stateLock);
free(transfer);
return false;
}
transfer->sizeHint = LG_CLIPBOARD_SIZE_UNKNOWN;
transfer->next = clipboard->clipboardReads;
clipboard->clipboardReads = transfer;
LG_UNLOCK(clipboard->stateLock);
networkClipboardFlushQueued(clipboard);
return true;
}
static bool networkClipboardFileAcquire(void * opaque, uint64_t dataset,
uint64_t acquisitionID)
{
NetworkClipboard * clipboard = opaque;
if (!dataset || !kvmfrClipboardTransferFromClient(acquisitionID))
return false;
struct NetworkFileAcquisition * acquisition =
calloc(1, sizeof(*acquisition));
if (!acquisition)
return false;
LG_LOCK(clipboard->stateLock);
if (!clipboard->remoteOffer.published ||
!clipboard->remoteOffer.fileOffer ||
clipboard->remoteOffer.offerID != dataset ||
networkClipboardAcquisitionCountNL(clipboard) >=
NETWORK_CLIPBOARD_FILE_ACQUISITIONS ||
networkClipboardAcquisitionFind(
clipboard, dataset, acquisitionID, false))
{
LG_UNLOCK(clipboard->stateLock);
free(acquisition);
return false;
}
acquisition->dataset = dataset;
acquisition->localAcquisition = acquisitionID;
acquisition->wireClientID = networkClipboardNextWireIDNL(clipboard);
acquisition->fromRemote = false;
const LGNetFileLease lease =
{
.clientID = acquisition->wireClientID,
.offerID = dataset,
.leaseEpoch = 0,
.leaseMs = LG_NET_FILE_MAX_LEASE_MS,
.flags = LG_NET_FILE_LEASE_READ,
};
struct NetworkClipboardSend * send = networkClipboardFileLeaseSendNew(
clipboard, LG_NET_FILE_MESSAGE_ACQUIRE, &lease,
LG_NET_ENVELOPE_ACK_REQUIRED);
if (!send || !networkClipboardQueueSendNL(clipboard, send))
{
LG_UNLOCK(clipboard->stateLock);
free(acquisition);
return false;
}
acquisition->next = clipboard->fileAcquisitions;
clipboard->fileAcquisitions = acquisition;
LG_UNLOCK(clipboard->stateLock);
networkClipboardFlushQueued(clipboard);
return true;
}
static bool networkClipboardFileAcquired(void * opaque, uint64_t dataset,
uint64_t acquisitionID, LG_ClipboardFileError error)
{
NetworkClipboard * clipboard = opaque;
if (!dataset || !kvmfrClipboardTransferFromHelper(acquisitionID) ||
!networkClipboardFileErrorValid(error))
return false;
LG_LOCK(clipboard->stateLock);
struct NetworkFileAcquisition * acquisition =
networkClipboardAcquisitionFind(
clipboard, dataset, acquisitionID, true);
if (!acquisition || acquisition->active)
{
LG_UNLOCK(clipboard->stateLock);
return false;
}
bool queued;
if (error == LG_CLIPBOARD_FILE_ERROR_NONE)
{
const uint64_t leaseEpoch =
networkClipboardNextWireIDNL(clipboard);
const LGNetFileLease lease =
{
.clientID = acquisition->wireClientID,
.offerID = dataset,
.leaseEpoch = leaseEpoch,
.leaseMs = LG_NET_FILE_MAX_LEASE_MS,
.flags = LG_NET_FILE_LEASE_READ |
LG_NET_FILE_LEASE_ACQUIRED,
};
queued = networkClipboardQueueSendNL(clipboard,
networkClipboardFileLeaseSendNew(clipboard,
LG_NET_FILE_MESSAGE_ACQUIRED, &lease,
LG_NET_ENVELOPE_RESPONSE));
if (queued)
{
acquisition->leaseEpoch = leaseEpoch;
acquisition->active = true;
}
}
else
{
queued = networkClipboardQueueFileStatusNL(clipboard,
acquisition->wireClientID, dataset, 0, error, 0);
if (queued)
acquisition = networkClipboardAcquisitionTake(
clipboard, dataset, acquisitionID, true);
}
LG_UNLOCK(clipboard->stateLock);
if (queued)
{
if (error != LG_CLIPBOARD_FILE_ERROR_NONE)
free(acquisition);
networkClipboardFlushQueued(clipboard);
}
return queued;
}
static bool networkClipboardFileRelease(void * opaque, uint64_t dataset,
uint64_t acquisitionID)
{
NetworkClipboard * clipboard = opaque;
if (!dataset || !kvmfrClipboardTransferFromClient(acquisitionID))
return false;
LG_LOCK(clipboard->stateLock);
struct NetworkFileAcquisition * acquisition =
networkClipboardAcquisitionFind(
clipboard, dataset, acquisitionID, false);
if (!acquisition || !acquisition->active)
{
LG_UNLOCK(clipboard->stateLock);
return false;
}
const LGNetFileLease lease =
{
.clientID = acquisition->wireClientID,
.offerID = dataset,
.leaseEpoch = acquisition->leaseEpoch,
.leaseMs = 0,
.flags = LG_NET_FILE_LEASE_READ,
};
const bool queued = networkClipboardQueueSendNL(clipboard,
networkClipboardFileLeaseSendNew(clipboard,
LG_NET_FILE_MESSAGE_RELEASE, &lease, 0));
if (queued)
acquisition = networkClipboardAcquisitionTake(
clipboard, dataset, acquisitionID, false);
LG_UNLOCK(clipboard->stateLock);
if (queued)
{
free(acquisition);
networkClipboardFlushQueued(clipboard);
}
return queued;
}
static bool networkClipboardFileRequest(void * opaque,
const LG_ClipboardFileRequest * descriptor)
{
NetworkClipboard * clipboard = opaque;
if (!descriptor || !descriptor->dataset ||
!kvmfrClipboardTransferFromClient(descriptor->request) ||
(descriptor->operation != LG_CLIPBOARD_FILE_LIST &&
descriptor->operation != LG_CLIPBOARD_FILE_READ) ||
(descriptor->operation == LG_CLIPBOARD_FILE_LIST &&
(descriptor->offset || descriptor->length)) ||
(descriptor->operation == LG_CLIPBOARD_FILE_READ &&
(!descriptor->length ||
descriptor->length > NETWORK_CLIPBOARD_FILE_READ_MAX ||
descriptor->offset > UINT64_MAX - descriptor->length)))
return false;
uint64_t entryID;
if (!lgNetFileEntryIDFromKVMFRNode(descriptor->node, &entryID))
return false;
struct NetworkFileTransfer * transfer = calloc(1, sizeof(*transfer));
if (!transfer)
return false;
LG_LOCK(clipboard->stateLock);
if (!networkClipboardDatasetAcquiredNL(
clipboard, descriptor->dataset, false) ||
networkClipboardFileTransferCountNL(clipboard) >=
NETWORK_CLIPBOARD_FILE_REQUESTS ||
networkClipboardFileTransferFind(
clipboard->fileReads, descriptor->request))
{
LG_UNLOCK(clipboard->stateLock);
free(transfer);
return false;
}
transfer->request = *descriptor;
transfer->wireRequest = networkClipboardNextWireIDNL(clipboard);
transfer->entryID = entryID;
transfer->sizeHint = LG_CLIPBOARD_SIZE_UNKNOWN;
const LGNetFileRequest request =
{
.requestID = transfer->wireRequest,
.offerID = descriptor->dataset,
.entryID = entryID,
.offset = descriptor->offset,
.length = descriptor->length,
.flags = descriptor->operation == LG_CLIPBOARD_FILE_READ ?
LG_NET_FILE_REQUEST_DATA : LG_NET_FILE_REQUEST_METADATA,
};
struct NetworkClipboardSend * send =
networkClipboardFileRequestSendNew(clipboard, &request);
if (!send || !networkClipboardQueueSendNL(clipboard, send))
{
LG_UNLOCK(clipboard->stateLock);
free(transfer);
return false;
}
transfer->next = clipboard->fileReads;
clipboard->fileReads = transfer;
LG_UNLOCK(clipboard->stateLock);
networkClipboardFlushQueued(clipboard);
return true;
}
static LG_ClipboardResult networkClipboardFileDataBegin(void * opaque,
const LG_ClipboardFileRequest * descriptor, uint64_t sizeHint)
{
NetworkClipboard * clipboard = opaque;
if (!descriptor || !kvmfrClipboardTransferFromHelper(
descriptor->request) ||
descriptor->offset > UINT64_MAX - sizeHint)
return LG_CLIPBOARD_RESULT_FAILED;
LG_LOCK(clipboard->stateLock);
struct NetworkFileTransfer * transfer =
networkClipboardFileTransferFind(
clipboard->fileWrites, descriptor->request);
if (!transfer || transfer->began ||
!networkClipboardSameFileRequest(&transfer->request, descriptor) ||
(descriptor->operation == LG_CLIPBOARD_FILE_READ &&
(sizeHint > NETWORK_CLIPBOARD_FILE_READ_MAX ||
sizeHint > descriptor->length)))
{
LG_UNLOCK(clipboard->stateLock);
return LG_CLIPBOARD_RESULT_FAILED;
}
transfer->delivery = clipboard->fileBulk ?
NETWORK_PACKET_DELIVERY_BULK : NETWORK_PACKET_DELIVERY_RELIABLE;
const LGNetFileTransfer wire =
{
.requestID = transfer->wireRequest,
.offerID = descriptor->dataset,
.entryID = transfer->entryID,
.offset = descriptor->offset,
.totalLength = descriptor->offset + sizeHint,
.processedLength = 0,
};
struct NetworkClipboardSend * send = networkClipboardSendWithDelivery(
networkClipboardFileTransferSendNew(clipboard,
LG_NET_FILE_MESSAGE_DATA_BEGIN, &wire, 0), transfer->delivery);
if (!send)
{
LG_UNLOCK(clipboard->stateLock);
return LG_CLIPBOARD_RESULT_FAILED;
}
if (!networkClipboardQueueSendNL(clipboard, send))
{
transfer->blocked = true;
LG_UNLOCK(clipboard->stateLock);
return LG_CLIPBOARD_RESULT_BLOCKED;
}
transfer->began = true;
transfer->blocked = false;
transfer->sizeHint = sizeHint;
transfer->responseOffset = 0;
transfer->wireEnded = false;
LG_UNLOCK(clipboard->stateLock);
networkClipboardFlushQueued(clipboard);
return LG_CLIPBOARD_RESULT_ACCEPTED;
}
static LG_ClipboardResult networkClipboardFileDataChunk(void * opaque,
const LG_ClipboardFileRequest * descriptor, uint64_t responseOffset,
const void * data, size_t size, bool end)
{
NetworkClipboard * clipboard = opaque;
if (!descriptor || !kvmfrClipboardTransferFromHelper(
descriptor->request) || !data || !size ||
size > NETWORK_CLIPBOARD_EMITTED_CHUNK || size > UINT32_MAX ||
responseOffset > UINT64_MAX - size)
return LG_CLIPBOARD_RESULT_FAILED;
LG_LOCK(clipboard->stateLock);
struct NetworkFileTransfer * transfer =
networkClipboardFileTransferFind(
clipboard->fileWrites, descriptor->request);
if (!transfer || !transfer->began || transfer->wireEnded ||
!networkClipboardSameFileRequest(&transfer->request, descriptor) ||
transfer->responseOffset != responseOffset ||
responseOffset + size > transfer->sizeHint ||
(end && responseOffset + size != transfer->sizeHint) ||
descriptor->offset > UINT64_MAX - transfer->sizeHint)
{
LG_UNLOCK(clipboard->stateLock);
return LG_CLIPBOARD_RESULT_FAILED;
}
const bool final = responseOffset + size == transfer->sizeHint;
LGNetFileChunkFlags flags = responseOffset ? 0 :
LG_NET_FILE_CHUNK_FIRST;
if (final)
flags |= LG_NET_FILE_CHUNK_FINAL;
const LGNetFileChunk chunk =
{
.requestID = transfer->wireRequest,
.offerID = descriptor->dataset,
.entryID = transfer->entryID,
.offset = responseOffset,
.totalLength = transfer->sizeHint,
.dataLength = (uint32_t)size,
.flags = flags,
.data = data,
};
struct NetworkClipboardSend * send =
networkClipboardSendWithDelivery(
networkClipboardFileChunkSendNew(clipboard, &chunk),
transfer->delivery);
if (!send)
{
LG_UNLOCK(clipboard->stateLock);
return LG_CLIPBOARD_RESULT_FAILED;
}
if (!networkClipboardQueueSendNL(clipboard, send))
{
transfer->blocked = true;
LG_UNLOCK(clipboard->stateLock);
return LG_CLIPBOARD_RESULT_BLOCKED;
}
transfer->responseOffset += size;
transfer->blocked = false;
transfer->wireEnded = final;
LG_UNLOCK(clipboard->stateLock);
networkClipboardFlushQueued(clipboard);
return LG_CLIPBOARD_RESULT_ACCEPTED;
}
static LG_ClipboardResult networkClipboardFileDataEnd(void * opaque,
const LG_ClipboardFileRequest * descriptor, uint64_t finalSize)
{
NetworkClipboard * clipboard = opaque;
if (!descriptor || !kvmfrClipboardTransferFromHelper(
descriptor->request) ||
descriptor->offset > UINT64_MAX - finalSize)
return LG_CLIPBOARD_RESULT_FAILED;
LG_LOCK(clipboard->stateLock);
struct NetworkFileTransfer * transfer =
networkClipboardFileTransferFind(
clipboard->fileWrites, descriptor->request);
if (!transfer || !transfer->began ||
!networkClipboardSameFileRequest(&transfer->request, descriptor) ||
transfer->responseOffset != finalSize ||
transfer->sizeHint != finalSize)
{
LG_UNLOCK(clipboard->stateLock);
return LG_CLIPBOARD_RESULT_FAILED;
}
const LGNetFileTransfer wire =
{
.requestID = transfer->wireRequest,
.offerID = descriptor->dataset,
.entryID = transfer->entryID,
.offset = descriptor->offset,
.totalLength = descriptor->offset + finalSize,
.processedLength = finalSize,
};
struct NetworkClipboardSend * send = networkClipboardSendWithDelivery(
networkClipboardFileTransferSendNew(clipboard,
LG_NET_FILE_MESSAGE_DATA_END, &wire, 0), transfer->delivery);
if (!send)
{
LG_UNLOCK(clipboard->stateLock);
return LG_CLIPBOARD_RESULT_FAILED;
}
if (!networkClipboardQueueSendNL(clipboard, send))
{
transfer->blocked = true;
LG_UNLOCK(clipboard->stateLock);
return LG_CLIPBOARD_RESULT_BLOCKED;
}
transfer = networkClipboardFileTransferTake(
&clipboard->fileWrites, descriptor->request);
LG_UNLOCK(clipboard->stateLock);
free(transfer);
networkClipboardFlushQueued(clipboard);
return LG_CLIPBOARD_RESULT_ACCEPTED;
}
static bool networkClipboardFileCancel(void * opaque, uint64_t dataset,
uint64_t request, LG_ClipboardFileError reason)
{
NetworkClipboard * clipboard = opaque;
if (!dataset || !request || reason == LG_CLIPBOARD_FILE_ERROR_NONE ||
!networkClipboardFileErrorValid(reason))
return false;
LG_LOCK(clipboard->stateLock);
struct NetworkFileTransfer * read =
networkClipboardFileTransferFind(clipboard->fileReads, request);
struct NetworkFileTransfer * write =
networkClipboardFileTransferFind(clipboard->fileWrites, request);
struct NetworkFileAcquisition * acquisition =
networkClipboardAcquisitionFind(clipboard, dataset, request,
kvmfrClipboardTransferFromHelper(request));
uint64_t wireRequest = 0;
uint64_t entryID = 0;
uint64_t offset = 0;
uint64_t totalLength = 0;
uint64_t processed = 0;
if (read && read->request.dataset == dataset)
{
wireRequest = read->wireRequest;
entryID = read->entryID;
offset = read->request.offset;
totalLength = read->sizeHint == LG_CLIPBOARD_SIZE_UNKNOWN ?
UINT64_MAX : offset + read->sizeHint;
processed = read->responseOffset;
}
else if (write && write->request.dataset == dataset)
{
wireRequest = write->wireRequest;
entryID = write->entryID;
offset = write->request.offset;
totalLength = offset + write->sizeHint;
processed = write->responseOffset;
}
else if (acquisition)
wireRequest = acquisition->wireClientID;
else
{
LG_UNLOCK(clipboard->stateLock);
return false;
}
bool queued;
if (entryID)
{
const LGNetFileTransfer wire =
{
.requestID = wireRequest,
.offerID = dataset,
.entryID = entryID,
.offset = offset,
.totalLength = totalLength,
.processedLength = processed,
};
queued = networkClipboardQueueSendNL(clipboard,
networkClipboardFileTransferSendNew(clipboard,
LG_NET_FILE_MESSAGE_CANCEL, &wire, 0));
}
else
queued = networkClipboardQueueFileStatusNL(clipboard,
wireRequest, dataset, 0, reason, 0);
if (queued)
{
free(networkClipboardFileTransferTake(&clipboard->fileReads, request));
free(networkClipboardFileTransferTake(&clipboard->fileWrites, request));
if (acquisition)
free(networkClipboardAcquisitionTake(clipboard,
dataset, request, acquisition->fromRemote));
}
LG_UNLOCK(clipboard->stateLock);
if (queued)
networkClipboardFlushQueued(clipboard);
return queued;
}
static void networkClipboardDispatchDisconnected(
NetworkClipboard * clipboard,
struct NetworkClipboardTransfer * clipboardReads,
struct NetworkClipboardTransfer * clipboardWrites,
struct NetworkFileTransfer * fileReads,
struct NetworkFileTransfer * fileWrites,
struct NetworkFileAcquisition * acquisitions,
bool release)
{
LG_LOCK(clipboard->eventLock);
LG_LOCK(clipboard->stateLock);
const LG_ClipboardEventOps * events = clipboard->attached ?
clipboard->events : NULL;
void * opaque = clipboard->eventOpaque;
LG_UNLOCK(clipboard->stateLock);
if (events)
{
for (const struct NetworkClipboardTransfer * item = clipboardReads;
item; item = item->next)
if (events->dataCancel)
events->dataCancel(opaque, item->localRequest,
LG_CLIPBOARD_CANCEL_UNAVAILABLE);
for (const struct NetworkClipboardTransfer * item = clipboardWrites;
item; item = item->next)
if (events->requestCancel)
events->requestCancel(opaque, item->localRequest,
LG_CLIPBOARD_CANCEL_UNAVAILABLE);
for (const struct NetworkFileTransfer * item = fileReads;
item; item = item->next)
if (events->fileCancel)
events->fileCancel(opaque, item->request.dataset,
item->request.request, LG_CLIPBOARD_FILE_ERROR_DISCONNECTED);
for (const struct NetworkFileTransfer * item = fileWrites;
item; item = item->next)
if (events->fileCancel)
events->fileCancel(opaque, item->request.dataset,
item->request.request, LG_CLIPBOARD_FILE_ERROR_DISCONNECTED);
for (const struct NetworkFileAcquisition * item = acquisitions;
item; item = item->next)
if (events->fileCancel)
events->fileCancel(opaque, item->dataset,
item->localAcquisition, LG_CLIPBOARD_FILE_ERROR_DISCONNECTED);
if (release && events->release)
events->release(opaque);
}
LG_UNLOCK(clipboard->eventLock);
}
static bool networkClipboardTimer(void * opaque)
{
NetworkClipboard * clipboard = opaque;
LG_LOCK(clipboard->stateLock);
const bool destroying = clipboard->destroying;
LG_UNLOCK(clipboard->stateLock);
if (destroying)
return false;
networkClipboardProcess(clipboard);
return true;
}
bool networkClipboardCreate(NetworkClipboard ** result,
NetworkConnection * connection)
{
if (!result || !connection)
return false;
NetworkClipboard * clipboard = calloc(1, sizeof(*clipboard));
if (!clipboard)
return false;
LG_LOCK_INIT(clipboard->stateLock);
LG_LOCK_INIT(clipboard->eventLock);
LG_LOCK_INIT(clipboard->statusLock);
clipboard->connection = connection;
clipboard->statusGeneration = 1;
clipboard->ownerID = nanotime() ^
((uint64_t)(uintptr_t)clipboard << 23);
clipboard->localSerial = nanotime() ^
((uint64_t)(uintptr_t)clipboard << 11);
clipboard->wireSerial = clipboard->localSerial ^
UINT64_C(0x4c47434c49504244);
clipboard->remoteSerial = clipboard->wireSerial ^
UINT64_C(0x4e4554574f524b43);
clipboard->localSerial &= ~KVMFR_CLIPBOARD_TRANSFER_HELPER;
if (!clipboard->ownerID)
clipboard->ownerID = 1;
if (!clipboard->localSerial)
clipboard->localSerial = 1;
if (!clipboard->wireSerial)
clipboard->wireSerial = 1;
if (!clipboard->remoteSerial)
clipboard->remoteSerial = 1;
networkClipboardRemoteOfferResetNL(clipboard);
if (!lgCreateTimer(NETWORK_CLIPBOARD_TIMER_MS,
networkClipboardTimer, clipboard, &clipboard->timer))
{
LG_LOCK_FREE(clipboard->statusLock);
LG_LOCK_FREE(clipboard->eventLock);
LG_LOCK_FREE(clipboard->stateLock);
free(clipboard);
return false;
}
*result = clipboard;
return true;
}
void networkClipboardConnectionState(NetworkClipboard * clipboard,
bool connected)
{
if (!clipboard)
return;
uint16_t clipboardVersion = 0;
uint16_t fileVersion = 0;
const bool clipboardService = connected &&
networkConnectionServiceAvailable(clipboard->connection,
LG_NET_SERVICE_CLIPBOARD, LG_NET_CAPABILITY_RELIABLE) &&
networkConnectionServiceVersion(clipboard->connection,
LG_NET_SERVICE_CLIPBOARD, &clipboardVersion) &&
clipboardVersion == LG_NET_CLIPBOARD_VERSION_CURRENT;
const bool fileService = clipboardService &&
networkConnectionServiceAvailable(clipboard->connection,
LG_NET_SERVICE_FILE, LG_NET_CAPABILITY_RELIABLE) &&
networkConnectionServiceVersion(clipboard->connection,
LG_NET_SERVICE_FILE, &fileVersion) &&
fileVersion == LG_NET_FILE_VERSION_CURRENT;
const bool clipboardBulk = clipboardService &&
networkConnectionServiceAvailable(clipboard->connection,
LG_NET_SERVICE_CLIPBOARD, LG_NET_CAPABILITY_BULK);
const bool fileBulk = fileService &&
networkConnectionServiceAvailable(clipboard->connection,
LG_NET_SERVICE_FILE, LG_NET_CAPABILITY_BULK);
LG_LOCK(clipboard->stateLock);
struct NetworkClipboardTransfer * clipboardReads =
clipboard->clipboardReads;
struct NetworkClipboardTransfer * clipboardWrites =
clipboard->clipboardWrites;
struct NetworkFileTransfer * fileReads = clipboard->fileReads;
struct NetworkFileTransfer * fileWrites = clipboard->fileWrites;
struct NetworkFileAcquisition * acquisitions =
clipboard->fileAcquisitions;
const bool release = clipboard->remoteOffer.published;
clipboard->clipboardReads = NULL;
clipboard->clipboardWrites = NULL;
clipboard->fileReads = NULL;
clipboard->fileWrites = NULL;
clipboard->fileAcquisitions = NULL;
clipboard->connected = connected;
clipboard->clipboardService = clipboardService;
clipboard->fileService = fileService;
clipboard->clipboardBulk = clipboardBulk;
clipboard->fileBulk = fileBulk;
clipboard->clipboardVersion = clipboardService ? clipboardVersion : 0;
clipboard->fileVersion = fileService ? fileVersion : 0;
clipboard->claimPending = false;
clipboard->claimed = false;
clipboard->claimSerial = 0;
clipboard->nextClaimNs = 0;
clipboard->nextKeepaliveNs = 0;
networkClipboardClearSendNL(clipboard);
networkClipboardClearInboundNL(clipboard);
networkClipboardRemoteOfferResetNL(clipboard);
networkClipboardSetAvailableNL(clipboard, clipboardService);
if (clipboardService && clipboard->attached)
networkClipboardQueueClaimNL(clipboard);
LG_UNLOCK(clipboard->stateLock);
networkClipboardDispatchDisconnected(clipboard,
clipboardReads, clipboardWrites, fileReads, fileWrites,
acquisitions, release);
networkClipboardTransfersFree(clipboardReads);
networkClipboardTransfersFree(clipboardWrites);
networkClipboardFileTransfersFree(fileReads);
networkClipboardFileTransfersFree(fileWrites);
networkClipboardAcquisitionsFree(acquisitions);
networkClipboardProcess(clipboard);
}
void networkClipboardConnect(NetworkClipboard * clipboard)
{
networkClipboardConnectionState(clipboard, true);
}
void networkClipboardDisconnect(NetworkClipboard * clipboard)
{
networkClipboardConnectionState(clipboard, false);
}
void networkClipboardDestroy(NetworkClipboard ** result)
{
if (!result || !*result)
return;
NetworkClipboard * clipboard = *result;
networkClipboardSetStatusListener(clipboard, NULL, NULL);
networkClipboardDetach(clipboard);
LG_LOCK(clipboard->stateLock);
clipboard->destroying = true;
LG_UNLOCK(clipboard->stateLock);
lgTimerDestroy(clipboard->timer);
networkClipboardConnectionState(clipboard, false);
LG_LOCK(clipboard->stateLock);
networkClipboardClearSendNL(clipboard);
networkClipboardClearInboundNL(clipboard);
LG_UNLOCK(clipboard->stateLock);
LG_LOCK_FREE(clipboard->statusLock);
LG_LOCK_FREE(clipboard->eventLock);
LG_LOCK_FREE(clipboard->stateLock);
free(clipboard);
*result = NULL;
}
void networkClipboardProcess(NetworkClipboard * clipboard)
{
if (!clipboard)
return;
LG_LOCK(clipboard->stateLock);
if (clipboard->destroying || clipboard->processing)
{
LG_UNLOCK(clipboard->stateLock);
return;
}
clipboard->processing = true;
bool progress = clipboard->sendProgress;
clipboard->sendProgress = false;
const uint64_t now = nanotime();
if (clipboard->connected && clipboard->clipboardService &&
clipboard->attached)
{
if (clipboard->claimPending && now >= clipboard->nextClaimNs)
{
clipboard->claimPending = false;
clipboard->claimed = false;
}
if (!clipboard->claimPending && !clipboard->claimed &&
now >= clipboard->nextClaimNs)
networkClipboardQueueClaimNL(clipboard);
else if (clipboard->claimed && now >= clipboard->nextKeepaliveNs)
{
if (networkClipboardQueueControlNL(clipboard,
LG_NET_CLIPBOARD_MESSAGE_KEEPALIVE, 0))
clipboard->nextKeepaliveNs = networkClipboardDeadline(
NETWORK_CLIPBOARD_KEEPALIVE_MS);
}
}
progress |= networkClipboardFlushNL(clipboard);
LG_UNLOCK(clipboard->stateLock);
networkClipboardProcessInbound(clipboard);
LG_ClipboardRequest clipboardReady[NETWORK_CLIPBOARD_FILE_REQUESTS];
uint64_t fileReady[NETWORK_CLIPBOARD_FILE_REQUESTS];
unsigned clipboardReadyCount = 0;
unsigned fileReadyCount = 0;
LG_LOCK(clipboard->stateLock);
progress |= clipboard->sendProgress;
clipboard->sendProgress = false;
progress |= networkClipboardFlushNL(clipboard);
if (progress)
{
for (struct NetworkClipboardTransfer * item =
clipboard->clipboardWrites; item &&
clipboardReadyCount < NETWORK_CLIPBOARD_FILE_REQUESTS;
item = item->next)
{
if (!item->blocked)
continue;
item->blocked = false;
clipboardReady[clipboardReadyCount++] = item->localRequest;
}
for (struct NetworkFileTransfer * item = clipboard->fileWrites;
item && fileReadyCount < NETWORK_CLIPBOARD_FILE_REQUESTS;
item = item->next)
{
if (!item->blocked)
continue;
item->blocked = false;
fileReady[fileReadyCount++] = item->request.request;
}
}
LG_UNLOCK(clipboard->stateLock);
for (unsigned index = 0; index < clipboardReadyCount; ++index)
networkClipboardDispatchDataReady(
clipboard, clipboardReady[index]);
for (unsigned index = 0; index < fileReadyCount; ++index)
networkClipboardDispatchFileReady(clipboard, fileReady[index]);
networkClipboardNotifyStatus(clipboard);
LG_LOCK(clipboard->stateLock);
clipboard->processing = false;
LG_UNLOCK(clipboard->stateLock);
}
void networkClipboardSendReady(NetworkClipboard * clipboard,
LGNetService service, NetworkPacketDelivery delivery)
{
if (!clipboard || (service != LG_NET_SERVICE_CLIPBOARD &&
service != LG_NET_SERVICE_FILE) ||
(delivery != NETWORK_PACKET_DELIVERY_RELIABLE &&
delivery != NETWORK_PACKET_DELIVERY_BULK))
return;
networkClipboardProcess(clipboard);
}
bool networkClipboardPacket(NetworkClipboard * clipboard,
const LGNetPacketView * packet, NetworkPacketDelivery delivery)
{
if (!clipboard || !packet ||
(packet->envelope.service != LG_NET_SERVICE_CLIPBOARD &&
packet->envelope.service != LG_NET_SERVICE_FILE) ||
(delivery != NETWORK_PACKET_DELIVERY_RELIABLE &&
delivery != NETWORK_PACKET_DELIVERY_BULK) ||
(packet->payloadLength && !packet->payload) ||
packet->payloadLength > LG_NET_MAX_PAYLOAD_LENGTH ||
packet->payloadLength >
SIZE_MAX - sizeof(struct NetworkClipboardInbound))
return false;
struct NetworkClipboardInbound * inbound =
malloc(sizeof(*inbound) + packet->payloadLength);
if (!inbound)
return false;
inbound->next = NULL;
inbound->envelope = packet->envelope;
inbound->delivery = delivery;
inbound->payloadLength = packet->payloadLength;
if (packet->payloadLength)
memcpy(inbound->payload, packet->payload, packet->payloadLength);
const size_t bytes = sizeof(*inbound) + inbound->payloadLength;
LG_LOCK(clipboard->stateLock);
const bool serviceAvailable = clipboard->connected &&
((packet->envelope.service == LG_NET_SERVICE_CLIPBOARD &&
clipboard->clipboardService) ||
(packet->envelope.service == LG_NET_SERVICE_FILE &&
clipboard->fileService));
const bool claimResult = packet->envelope.service ==
LG_NET_SERVICE_CLIPBOARD && packet->envelope.messageType ==
LG_NET_CLIPBOARD_MESSAGE_CLAIM_RESULT;
const bool stale = !claimResult &&
packet->envelope.componentEpoch != clipboard->claimEpoch;
if (!serviceAvailable || stale)
{
LG_UNLOCK(clipboard->stateLock);
free(inbound);
return true;
}
if (clipboard->inboundCount >= NETWORK_CLIPBOARD_INBOUND_MAX ||
bytes > NETWORK_CLIPBOARD_INBOUND_BYTES ||
clipboard->inboundBytes > NETWORK_CLIPBOARD_INBOUND_BYTES - bytes)
{
LG_UNLOCK(clipboard->stateLock);
free(inbound);
return false;
}
if (clipboard->inboundTail)
clipboard->inboundTail->next = inbound;
else
clipboard->inboundHead = inbound;
clipboard->inboundTail = inbound;
++clipboard->inboundCount;
clipboard->inboundBytes += bytes;
LG_UNLOCK(clipboard->stateLock);
networkClipboardProcess(clipboard);
return true;
}
static const LG_ClipboardOps NETWORK_CLIPBOARD_OPS =
{
.name = "Network",
.setStatusListener = networkClipboardSetStatusListener,
.attach = networkClipboardAttach,
.detach = networkClipboardDetach,
.release = networkClipboardRelease,
.notifyTypes = networkClipboardNotifyTypes,
.offerFiles = networkClipboardOfferFiles,
.data = networkClipboardData,
.dataBegin = networkClipboardDataBegin,
.dataChunk = networkClipboardDataChunk,
.dataEnd = networkClipboardDataEnd,
.dataCancel = networkClipboardDataCancel,
.dataReady = networkClipboardDataReady,
.request = networkClipboardRequest,
.fileAcquire = networkClipboardFileAcquire,
.fileAcquired = networkClipboardFileAcquired,
.fileRelease = networkClipboardFileRelease,
.fileRequest = networkClipboardFileRequest,
.fileDataBegin = networkClipboardFileDataBegin,
.fileDataChunk = networkClipboardFileDataChunk,
.fileDataEnd = networkClipboardFileDataEnd,
.fileCancel = networkClipboardFileCancel,
};
const LG_ClipboardOps * networkClipboardGetOps(void)
{
return &NETWORK_CLIPBOARD_OPS;
}