/**
 * Looking Glass
 * Copyright © 2017-2026 The Looking Glass Authors
 * https://looking-glass.io
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc., 59
 * Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#include "clipboard.h"

#include "common/KVMFRClipboard.h"
#include "common/LGMPConfig.h"
#include "common/debug.h"
#include "common/event.h"
#include "common/locking.h"
#include "common/thread.h"
#include "common/time.h"

#include <lgmp/stream.h>

#include <errno.h>
#include <stdatomic.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/random.h>

#define CLIPBOARD_PENDING_MAX         128U
#define CLIPBOARD_PENDING_NORMAL_MAX  64U
#define CLIPBOARD_DRAIN_MAX           64U
#define CLIPBOARD_IDLE_POLL_MS        10U
#define CLIPBOARD_ACTIVE_POLL_MS      1U
#define CLIPBOARD_KEEPALIVE_US        UINT64_C(200000)
#define CLIPBOARD_RELEASE_TIMEOUT_US  UINT64_C(100000)

struct PendingRecord
{
  KVMFRClipboardMessage record;
};

struct FileTransfer
{
  struct FileTransfer       * next;
  LG_ClipboardFileRequest     request;
  uint64_t                    sizeHint;
  uint64_t                    responseOffset;
  uint32_t                    sequence;
  bool                        began;
  bool                        blocked;
  bool                        wireEnded;
};

struct FileAcquisition
{
  struct FileAcquisition * next;
  uint64_t                 dataset;
  uint64_t                 acquisition;
  bool                     fromHelper;
  bool                     active;
};

enum HeldPhase
{
  HELD_PHASE_NONE,
  HELD_PHASE_BEGIN,
  HELD_PHASE_CHUNK,
  HELD_PHASE_END,
};

struct LGMPClipboard
{
  PLGMPClient                  client;
  PLGMPClientQueue             queue;
  PLGMPClientStream            hostToClientStream;
  PLGMPClientStream            clientToHostStream;
  LG_Lock                      lock;
  LG_Lock                      eventLock;
  LG_Lock                      statusLock;
  LGEvent                    * event;
  LGThread                   * thread;
  atomic_bool                  stop;

  bool                         connected;
  bool                         available;
  bool                         statusValid;
  bool                         claimed;
  bool                         ownerConfirmed;
  bool                         releasing;
  KVMFRStreamDescriptor        hostToClientDescriptor;
  KVMFRStreamDescriptor        clientToHostDescriptor;
  uint32_t                     clientID;
  uint32_t                     endpointGeneration;
  uint32_t                     providerGeneration;
  uint32_t                     claimGeneration;
  uint32_t                     publishedClaimGeneration;
  uint32_t                     statusSerial;
  uint64_t                     lastSend;

  struct PendingRecord         pending[CLIPBOARD_PENDING_MAX];
  unsigned                     pendingHead;
  unsigned                     pendingCount;

  bool                         writeBlocked;
  LG_ClipboardRequest          writeBlockedRequest;
  LG_ClipboardRequest          writeTransfer;
  uint64_t                     writeClipboardGeneration;
  uint64_t                     writeSizeHint;
  KVMFRClipboardFormat         writeFormat;
  uint64_t                     writeOffset;
  uint32_t                     writeSequence;
  bool                         writeBegan;
  bool                         writeWireBegan;

  bool                         held;
  bool                         heldReady;
  enum HeldPhase               heldPhase;
  LGMPStreamBuffer             heldStreamBuffer;
  KVMFRClipboardMessage        heldRecord;
  bool                         heldFile;
  LG_ClipboardFileRequest      heldFileRequest;

  uint64_t                     localClipboardGeneration;
  uint64_t                     localGenerationSerial;
  KVMFRClipboardFormatFlags    localFormats;
  uint64_t                     remoteClipboardGeneration;
  KVMFRClipboardFormatFlags    remoteFormats;
  LG_ClipboardRequest          readRequest;
  uint64_t                     readTransfer;
  uint64_t                     readClipboardGeneration;
  uint64_t                     readSizeHint;
  uint64_t                     readOffset;
  KVMFRClipboardFormat         readFormat;
  uint32_t                     readSequence;
  bool                         readBegan;
  uint64_t                     transferSerial;

  struct FileTransfer        * fileReads;
  struct FileTransfer        * fileWrites;
  struct FileAcquisition     * fileAcquisitions;
  struct FileTransfer        * retiredFileReads;
  struct FileTransfer        * retiredFileWrites;
  struct FileAcquisition     * retiredFileAcquisitions;

  const LG_ClipboardEventOps * events;
  void                       * eventOpaque;
  LG_ClipboardStatusFn         statusCallback;
  void                       * statusOpaque;
};

static void connectionFailed(LGMPClipboard * clipboard,
    LGMP_STATUS status);
static void connectionCorrupted(LGMPClipboard * clipboard,
    const char * reason);

_Static_assert(sizeof(struct LGMPStreamDescriptor) ==
    sizeof(KVMFRStreamDescriptor),
    "LGMP and KVMFR stream descriptor sizes differ");
_Static_assert(offsetof(struct LGMPStreamDescriptor, offset) ==
    offsetof(KVMFRStreamDescriptor, offset),
    "LGMP and KVMFR stream descriptor offsets differ");
_Static_assert(offsetof(struct LGMPStreamDescriptor, slotSize) ==
    offsetof(KVMFRStreamDescriptor, slotSize),
    "LGMP and KVMFR stream descriptor geometry differs");

static struct LGMPStreamDescriptor streamDescriptorFromWire(
    const KVMFRStreamDescriptor * wire)
{
  return (struct LGMPStreamDescriptor)
  {
    .magic      = wire->magic,
    .version    = wire->version,
    .size       = wire->size,
    .offset     = wire->offset,
    .regionSize = wire->regionSize,
    .direction  = wire->direction,
    .policy     = wire->policy,
    .slotCount  = wire->slotCount,
    .slotSize   = wire->slotSize,
  };
}

static bool validStreamDescriptor(const KVMFRStreamDescriptor * descriptor,
    enum LGMPStreamDirection direction)
{
  return descriptor->magic == LGMP_STREAM_DESCRIPTOR_MAGIC &&
    descriptor->version == LGMP_STREAM_DESCRIPTOR_VERSION &&
    descriptor->size == sizeof(*descriptor) && descriptor->offset &&
    descriptor->regionSize && descriptor->direction == (uint32_t)direction &&
    descriptor->policy == LGMP_STREAM_RELIABLE_FIFO &&
    descriptor->slotCount == KVMFR_CLIPBOARD_STREAM_SLOT_COUNT &&
    descriptor->slotSize == KVMFR_CLIPBOARD_STREAM_RECORD_BYTES;
}

static void detachStreamsNL(LGMPClipboard * clipboard)
{
  lgmpClientStreamDetach(&clipboard->clientToHostStream);
  lgmpClientStreamDetach(&clipboard->hostToClientStream);
  memset(&clipboard->clientToHostDescriptor, 0,
      sizeof(clipboard->clientToHostDescriptor));
  memset(&clipboard->hostToClientDescriptor, 0,
      sizeof(clipboard->hostToClientDescriptor));
}

static bool attachStreamsNL(LGMPClipboard * clipboard,
    const KVMFRClipboardStatus * status)
{
  if (clipboard->hostToClientStream && clipboard->clientToHostStream &&
      !memcmp(&clipboard->hostToClientDescriptor, &status->hostToClient,
        sizeof(status->hostToClient)) &&
      !memcmp(&clipboard->clientToHostDescriptor, &status->clientToHost,
        sizeof(status->clientToHost)))
    return true;

  detachStreamsNL(clipboard);
  const struct LGMPStreamDescriptor hostToClient =
    streamDescriptorFromWire(&status->hostToClient);
  LGMP_STATUS result = lgmpClientStreamAttach(clipboard->client,
    &hostToClient, &clipboard->hostToClientStream);
  if (result != LGMP_OK)
  {
    DEBUG_WARN("Failed to attach host-to-client clipboard stream: %s",
      lgmpStatusString(result));
    return false;
  }

  const struct LGMPStreamDescriptor clientToHost =
    streamDescriptorFromWire(&status->clientToHost);
  result = lgmpClientStreamAttach(clipboard->client,
    &clientToHost, &clipboard->clientToHostStream);
  if (result != LGMP_OK)
  {
    DEBUG_WARN("Failed to attach client-to-host clipboard stream: %s",
      lgmpStatusString(result));
    detachStreamsNL(clipboard);
    return false;
  }

  clipboard->hostToClientDescriptor = status->hostToClient;
  clipboard->clientToHostDescriptor = status->clientToHost;
  return true;
}

static LGMP_STATUS activateStreamsNL(LGMPClipboard * clipboard)
{
  if (!clipboard->hostToClientStream || !clipboard->clientToHostStream)
    return LGMP_ERR_STREAM_UNBOUND;

  LGMP_STATUS status = lgmpClientStreamActivate(
    clipboard->hostToClientStream, NULL);
  if (status != LGMP_OK)
    return status;
  return lgmpClientStreamActivate(
    clipboard->clientToHostStream, NULL);
}

static LG_ClipboardData fromWireFormat(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 toWireFormat(LG_ClipboardData format)
{
  switch (format)
  {
    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 LG_ClipboardFileOperation fromWireFileOperation(uint32_t operation)
{
  return operation == KVMFR_CLIPBOARD_FILE_OP_READ ?
    LG_CLIPBOARD_FILE_READ : LG_CLIPBOARD_FILE_LIST;
}

static uint32_t toWireFileOperation(LG_ClipboardFileOperation operation)
{
  return operation == LG_CLIPBOARD_FILE_READ ?
    KVMFR_CLIPBOARD_FILE_OP_READ : KVMFR_CLIPBOARD_FILE_OP_LIST;
}

static LG_ClipboardCancelReason fromWireCancel(uint32_t reason)
{
  return reason <= LG_CLIPBOARD_CANCEL_INVALID ?
    (LG_ClipboardCancelReason)reason : LG_CLIPBOARD_CANCEL_INVALID;
}

static KVMFRClipboardFormatFlags formatMask(
    const LG_ClipboardData formats[], size_t count)
{
  KVMFRClipboardFormatFlags result = 0;
  for (size_t i = 0; i < count; ++i)
    result |= kvmfrClipboardFormatFlag(toWireFormat(formats[i]));
  return result;
}

static unsigned formatsFromMask(KVMFRClipboardFormatFlags mask,
    LG_ClipboardData formats[LG_CLIPBOARD_DATA_NONE])
{
  unsigned count = 0;
  for (KVMFRClipboardFormat format = KVMFR_CLIPBOARD_FORMAT_TEXT;
       format <= KVMFR_CLIPBOARD_FORMAT_FILES; ++format)
    if (mask & kvmfrClipboardFormatFlag(format))
      formats[count++] = fromWireFormat(format);
  return count;
}

static uint32_t nextNonzero(uint32_t * value)
{
  if (++*value == 0)
    ++*value;
  return *value;
}

static uint64_t nextClientTransfer(LGMPClipboard * clipboard)
{
  if (++clipboard->transferSerial == 0 ||
      kvmfrClipboardTransferFromHelper(clipboard->transferSerial))
    clipboard->transferSerial = 1;
  return clipboard->transferSerial;
}

static uint64_t nextClientGeneration(LGMPClipboard * clipboard)
{
  if (++clipboard->localGenerationSerial == 0 ||
      kvmfrClipboardTransferFromHelper(
        clipboard->localGenerationSerial))
    clipboard->localGenerationSerial = 1;
  return clipboard->localGenerationSerial;
}

static bool randomBytes(void * buffer, size_t size)
{
  uint8_t * output = buffer;
  while (size)
  {
    const ssize_t received = getrandom(output, size, 0);
    if (received < 0 && errno == EINTR)
      continue;
    if (received <= 0)
    {
      if (!received)
        errno = EIO;
      return false;
    }
    output += (size_t)received;
    size   -= (size_t)received;
  }
  return true;
}

static LG_ClipboardFileError fromWireFileError(uint32_t error)
{
  switch (error)
  {
    case KVMFR_CLIPBOARD_FILE_ERROR_NONE:
      return LG_CLIPBOARD_FILE_ERROR_NONE;
    case KVMFR_CLIPBOARD_FILE_ERROR_NOT_FOUND:
      return LG_CLIPBOARD_FILE_ERROR_NOT_FOUND;
    case KVMFR_CLIPBOARD_FILE_ERROR_ACCESS:
      return LG_CLIPBOARD_FILE_ERROR_ACCESS;
    case KVMFR_CLIPBOARD_FILE_ERROR_NOT_DIRECTORY:
      return LG_CLIPBOARD_FILE_ERROR_NOT_DIRECTORY;
    case KVMFR_CLIPBOARD_FILE_ERROR_IS_DIRECTORY:
      return LG_CLIPBOARD_FILE_ERROR_IS_DIRECTORY;
    case KVMFR_CLIPBOARD_FILE_ERROR_IO:
      return LG_CLIPBOARD_FILE_ERROR_IO;
    case KVMFR_CLIPBOARD_FILE_ERROR_INVALID:
      return LG_CLIPBOARD_FILE_ERROR_INVALID;
    case KVMFR_CLIPBOARD_FILE_ERROR_NO_MEMORY:
      return LG_CLIPBOARD_FILE_ERROR_NO_MEMORY;
    case KVMFR_CLIPBOARD_FILE_ERROR_NO_SPACE:
      return LG_CLIPBOARD_FILE_ERROR_NO_SPACE;
    case KVMFR_CLIPBOARD_FILE_ERROR_DISCONNECTED:
      return LG_CLIPBOARD_FILE_ERROR_DISCONNECTED;
    case KVMFR_CLIPBOARD_FILE_ERROR_CANCELLED:
      return LG_CLIPBOARD_FILE_ERROR_CANCELLED;
    case KVMFR_CLIPBOARD_FILE_ERROR_NOT_SUPPORTED:
      return LG_CLIPBOARD_FILE_ERROR_NOT_SUPPORTED;
    case KVMFR_CLIPBOARD_FILE_ERROR_STALE:
      return LG_CLIPBOARD_FILE_ERROR_STALE;
    default:
      return LG_CLIPBOARD_FILE_ERROR_INVALID;
  }
}

static KVMFRClipboardFileError toWireFileError(
    LG_ClipboardFileError error)
{
  switch (error)
  {
    case LG_CLIPBOARD_FILE_ERROR_NONE:
      return KVMFR_CLIPBOARD_FILE_ERROR_NONE;
    case LG_CLIPBOARD_FILE_ERROR_NOT_FOUND:
      return KVMFR_CLIPBOARD_FILE_ERROR_NOT_FOUND;
    case LG_CLIPBOARD_FILE_ERROR_ACCESS:
      return KVMFR_CLIPBOARD_FILE_ERROR_ACCESS;
    case LG_CLIPBOARD_FILE_ERROR_NOT_DIRECTORY:
      return KVMFR_CLIPBOARD_FILE_ERROR_NOT_DIRECTORY;
    case LG_CLIPBOARD_FILE_ERROR_IS_DIRECTORY:
      return KVMFR_CLIPBOARD_FILE_ERROR_IS_DIRECTORY;
    case LG_CLIPBOARD_FILE_ERROR_IO:
      return KVMFR_CLIPBOARD_FILE_ERROR_IO;
    case LG_CLIPBOARD_FILE_ERROR_INVALID:
      return KVMFR_CLIPBOARD_FILE_ERROR_INVALID;
    case LG_CLIPBOARD_FILE_ERROR_NO_MEMORY:
      return KVMFR_CLIPBOARD_FILE_ERROR_NO_MEMORY;
    case LG_CLIPBOARD_FILE_ERROR_NO_SPACE:
      return KVMFR_CLIPBOARD_FILE_ERROR_NO_SPACE;
    case LG_CLIPBOARD_FILE_ERROR_DISCONNECTED:
      return KVMFR_CLIPBOARD_FILE_ERROR_DISCONNECTED;
    case LG_CLIPBOARD_FILE_ERROR_CANCELLED:
      return KVMFR_CLIPBOARD_FILE_ERROR_CANCELLED;
    case LG_CLIPBOARD_FILE_ERROR_NOT_SUPPORTED:
      return KVMFR_CLIPBOARD_FILE_ERROR_NOT_SUPPORTED;
    case LG_CLIPBOARD_FILE_ERROR_STALE:
      return KVMFR_CLIPBOARD_FILE_ERROR_STALE;
  }
  return KVMFR_CLIPBOARD_FILE_ERROR_INVALID;
}

static bool validFileError(LG_ClipboardFileError error)
{
  return error >= LG_CLIPBOARD_FILE_ERROR_NONE &&
    error <= LG_CLIPBOARD_FILE_ERROR_STALE;
}

static struct FileTransfer * fileTransferFindNL(
    struct FileTransfer * transfers, uint64_t request)
{
  for (; transfers; transfers = transfers->next)
    if (transfers->request.request == request)
      return transfers;
  return NULL;
}

static struct FileTransfer * fileTransferTakeNL(
    struct FileTransfer ** transfers, uint64_t request)
{
  while (*transfers && (*transfers)->request.request != request)
    transfers = &(*transfers)->next;
  if (!*transfers)
    return NULL;
  struct FileTransfer * result = *transfers;
  *transfers = result->next;
  result->next = NULL;
  return result;
}

static struct FileAcquisition * fileAcquisitionFindNL(
    LGMPClipboard * clipboard, uint64_t dataset, uint64_t acquisition,
    bool fromHelper)
{
  for (struct FileAcquisition * item = clipboard->fileAcquisitions;
      item; item = item->next)
    if (item->dataset == dataset && item->acquisition == acquisition &&
        item->fromHelper == fromHelper)
      return item;
  return NULL;
}

static bool fileDatasetAcquiredNL(LGMPClipboard * clipboard,
    uint64_t dataset, bool fromHelper)
{
  for (struct FileAcquisition * item = clipboard->fileAcquisitions;
      item; item = item->next)
    if (item->dataset == dataset && item->fromHelper == fromHelper &&
        item->active)
      return true;
  return false;
}

static struct FileAcquisition * fileAcquisitionTakeNL(
    LGMPClipboard * clipboard, uint64_t dataset, uint64_t acquisition,
    bool fromHelper)
{
  struct FileAcquisition ** link = &clipboard->fileAcquisitions;
  while (*link && ((*link)->dataset != dataset ||
      (*link)->acquisition != acquisition ||
      (*link)->fromHelper != fromHelper))
    link = &(*link)->next;
  if (!*link)
    return NULL;
  struct FileAcquisition * result = *link;
  *link = result->next;
  result->next = NULL;
  return result;
}

static unsigned fileAcquisitionCountNL(const LGMPClipboard * clipboard)
{
  unsigned count = 0;
  for (const struct FileAcquisition * item = clipboard->fileAcquisitions;
      item; item = item->next)
    ++count;
  return count;
}

static unsigned fileTransferCountNL(const LGMPClipboard * clipboard)
{
  unsigned count = 0;
  for (const struct FileTransfer * item = clipboard->fileReads;
      item; item = item->next)
    ++count;
  for (const struct FileTransfer * item = clipboard->fileWrites;
      item; item = item->next)
    ++count;
  return count;
}

static void clearFilesNL(LGMPClipboard * clipboard)
{
  struct FileTransfer ** readTail = &clipboard->fileReads;
  while (*readTail)
    readTail = &(*readTail)->next;
  *readTail = clipboard->retiredFileReads;
  clipboard->retiredFileReads = clipboard->fileReads;
  clipboard->fileReads = NULL;

  struct FileTransfer ** writeTail = &clipboard->fileWrites;
  while (*writeTail)
    writeTail = &(*writeTail)->next;
  *writeTail = clipboard->retiredFileWrites;
  clipboard->retiredFileWrites = clipboard->fileWrites;
  clipboard->fileWrites = NULL;

  struct FileAcquisition ** acquisitionTail =
    &clipboard->fileAcquisitions;
  while (*acquisitionTail)
    acquisitionTail = &(*acquisitionTail)->next;
  *acquisitionTail = clipboard->retiredFileAcquisitions;
  clipboard->retiredFileAcquisitions = clipboard->fileAcquisitions;
  clipboard->fileAcquisitions = NULL;
}

static void freeFileTransfers(struct FileTransfer * transfers)
{
  while (transfers)
  {
    struct FileTransfer * next = transfers->next;
    free(transfers);
    transfers = next;
  }
}

static void freeFileAcquisitions(struct FileAcquisition * acquisitions)
{
  while (acquisitions)
  {
    struct FileAcquisition * next = acquisitions->next;
    free(acquisitions);
    acquisitions = next;
  }
}

static struct PendingRecord * pendingAt(
    LGMPClipboard * clipboard, unsigned position)
{
  return &clipboard->pending[
    (clipboard->pendingHead + position) % CLIPBOARD_PENDING_MAX];
}

static void signalWorker(LGMPClipboard * clipboard)
{
  if (clipboard->event)
    lgSignalEvent(clipboard->event);
}

static bool enqueueRecordLimitNL(LGMPClipboard * clipboard,
    KVMFRClipboardMessage record, unsigned limit)
{
  if (!clipboard->connected || !clipboard->queue ||
      clipboard->pendingCount >= limit)
    return false;

  record.version = KVMFR_CLIPBOARD_VERSION;
  record.generation = clipboard->claimGeneration;
  pendingAt(clipboard, clipboard->pendingCount++)->record = record;
  return true;
}

static bool enqueueRecordNL(LGMPClipboard * clipboard,
    KVMFRClipboardMessage record)
{
  return enqueueRecordLimitNL(
      clipboard, record, CLIPBOARD_PENDING_NORMAL_MAX);
}

static bool enqueueUrgentRecordNL(LGMPClipboard * clipboard,
    KVMFRClipboardMessage record)
{
  return enqueueRecordLimitNL(clipboard, record, CLIPBOARD_PENDING_MAX);
}

static bool enqueueFileFailureNL(LGMPClipboard * clipboard,
    const KVMFRClipboardMessage * request, LG_ClipboardFileError error)
{
  KVMFRClipboardMessage response = { 0 };
  response.type                = request->type == KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRE ?
    KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRED :
    KVMFR_CLIPBOARD_MESSAGE_FILE_CANCEL;
  response.clipboardGeneration = request->clipboardGeneration;
  response.transfer            = request->transfer;
  response.format              = KVMFR_CLIPBOARD_FORMAT_FILES;
  response.token               = toWireFileError(error);
  return enqueueUrgentRecordNL(clipboard, response);
}

static bool enqueueTypeNL(LGMPClipboard * clipboard,
    KVMFRClipboardMessageType type)
{
  KVMFRClipboardMessage record = { 0 };
  record.type = type;
  return enqueueRecordNL(clipboard, record);
}

static bool streamWriteNL(LGMPClipboard * clipboard,
    const KVMFRClipboardMessage * record, const void * data)
{
  if (!clipboard->clientToHostStream)
    return false;

  LGMPStreamBuffer buffer = { 0 };
  LGMP_STATUS status = lgmpClientStreamWriteAcquire(
    clipboard->clientToHostStream, &buffer);
  if (status == LGMP_ERR_STREAM_FULL ||
      status == LGMP_ERR_STREAM_UNBOUND ||
      status == LGMP_ERR_STREAM_STALE)
    return false;
  if (status != LGMP_OK)
  {
    connectionFailed(clipboard, status);
    return false;
  }

  const uint32_t bytes = sizeof(*record) + record->length;
  if (!buffer.data || buffer.capacity < bytes)
  {
    const LGMP_STATUS cancel = lgmpClientStreamWriteCancel(
      clipboard->clientToHostStream, &buffer);
    if (cancel != LGMP_OK)
      DEBUG_WARN("Failed to cancel an undersized clipboard stream "
        "reservation: %s", lgmpStatusString(cancel));
    DEBUG_WARN("Client-to-host clipboard stream returned an undersized "
      "slot: capacity=%u required=%u", buffer.capacity, bytes);
    connectionCorrupted(clipboard, "undersized stream slot");
    return false;
  }

  memcpy(buffer.data, record, sizeof(*record));
  if (record->length)
    memcpy((uint8_t *)buffer.data + sizeof(*record), data,
      record->length);
  status = lgmpClientStreamWriteCommit(
    clipboard->clientToHostStream, &buffer, bytes);
  if (status == LGMP_OK)
    return true;

  DEBUG_WARN("Failed to commit client-to-host clipboard stream data: %s",
    lgmpStatusString(status));
  connectionFailed(clipboard, status);
  return false;
}

static bool enqueueDataNL(LGMPClipboard * clipboard,
    KVMFRClipboardMessage record, const void * data)
{
  if (!clipboard->connected || !clipboard->queue ||
      record.length > KVMFR_CLIPBOARD_DATA_BYTES ||
      (record.length && !data))
    return false;

  record.version    = KVMFR_CLIPBOARD_VERSION;
  record.generation = clipboard->claimGeneration;
  return streamWriteNL(clipboard, &record, data);
}

static bool streamWritableNL(LGMPClipboard * clipboard)
{
  if (!clipboard->clientToHostStream || !clipboard->claimed)
    return false;

  LGMPStreamBuffer buffer = { 0 };
  LGMP_STATUS status = lgmpClientStreamWriteAcquire(
    clipboard->clientToHostStream, &buffer);
  if (status == LGMP_ERR_STREAM_FULL ||
      status == LGMP_ERR_STREAM_UNBOUND ||
      status == LGMP_ERR_STREAM_STALE)
    return false;
  if (status != LGMP_OK)
  {
    connectionFailed(clipboard, status);
    return false;
  }

  status = lgmpClientStreamWriteCancel(
    clipboard->clientToHostStream, &buffer);
  if (status == LGMP_OK)
    return true;
  if (status != LGMP_ERR_STREAM_STALE)
    connectionFailed(clipboard, status);
  return false;
}

static bool dataWritableNL(LGMPClipboard * clipboard)
{
  return streamWritableNL(clipboard);
}

static void clearWriteNL(LGMPClipboard * clipboard)
{
  clipboard->writeBlocked             = false;
  clipboard->writeBlockedRequest      = LG_CLIPBOARD_REQUEST_INVALID;
  clipboard->writeTransfer            = LG_CLIPBOARD_REQUEST_INVALID;
  clipboard->writeClipboardGeneration = 0;
  clipboard->writeSizeHint            = 0;
  clipboard->writeFormat              = KVMFR_CLIPBOARD_FORMAT_NONE;
  clipboard->writeOffset              = 0;
  clipboard->writeSequence            = 0;
  clipboard->writeBegan               = false;
  clipboard->writeWireBegan           = false;
}

static void clearReadNL(LGMPClipboard * clipboard)
{
  clipboard->readRequest             = LG_CLIPBOARD_REQUEST_INVALID;
  clipboard->readTransfer            = 0;
  clipboard->readClipboardGeneration = 0;
  clipboard->readSizeHint            = 0;
  clipboard->readOffset              = 0;
  clipboard->readFormat              = KVMFR_CLIPBOARD_FORMAT_NONE;
  clipboard->readSequence            = 0;
  clipboard->readBegan               = false;
}

static void clearDataPlaneNL(LGMPClipboard * clipboard)
{
  clipboard->remoteClipboardGeneration = 0;
  clipboard->remoteFormats             = 0;
  clearWriteNL(clipboard);
  clearReadNL(clipboard);
  clearFilesNL(clipboard);
}

static void clearProtocolNL(LGMPClipboard * clipboard)
{
  clipboard->claimed                   = false;
  clipboard->ownerConfirmed            = false;
  clipboard->releasing                 = false;
  clipboard->publishedClaimGeneration  = 0;
  clipboard->pendingHead               = 0;
  clipboard->pendingCount              = 0;
  clearDataPlaneNL(clipboard);
}

static bool ensureClaimNL(LGMPClipboard * clipboard)
{
  if (clipboard->claimed)
    return true;
  if (clipboard->releasing || !clipboard->connected ||
      !clipboard->available ||
      !clipboard->hostToClientStream || !clipboard->clientToHostStream)
    return false;

  KVMFRClipboardMessage claim = { 0 };
  nextNonzero(&clipboard->claimGeneration);
  claim.type  = KVMFR_CLIPBOARD_MESSAGE_CLAIM;
  claim.token = clipboard->endpointGeneration;
  if (!enqueueRecordNL(clipboard, claim))
    return false;
  clipboard->claimed        = true;
  clipboard->ownerConfirmed = false;
  return true;
}

static bool restoreClipboardNL(LGMPClipboard * clipboard)
{
  if (!clipboard->events || !ensureClaimNL(clipboard))
    return false;
  if (!clipboard->localClipboardGeneration)
    return true;

  KVMFRClipboardMessage record = { 0 };
  record.type                = clipboard->localFormats ?
    KVMFR_CLIPBOARD_MESSAGE_OFFER : KVMFR_CLIPBOARD_MESSAGE_CLEAR;
  record.clipboardGeneration = clipboard->localClipboardGeneration;
  record.token               = clipboard->localFormats;
  return enqueueRecordNL(clipboard, record);
}

static void notifyStatus(LGMPClipboard * clipboard)
{
  LG_ClipboardStatusFn callback;
  void * opaque;
  LG_ClipboardStatus status;

  LG_LOCK(clipboard->statusLock);
  LG_LOCK(clipboard->lock);
  callback = clipboard->statusCallback;
  opaque   = clipboard->statusOpaque;
  status = (LG_ClipboardStatus)
  {
    .available  = clipboard->connected && clipboard->available,
    .generation = clipboard->providerGeneration,
  };
  LG_UNLOCK(clipboard->lock);
  if (callback)
    callback(opaque, &status);
  LG_UNLOCK(clipboard->statusLock);
}

static void connectionLostNL(LGMPClipboard * clipboard)
{
  const bool changed = clipboard->connected || clipboard->available;
  clipboard->connected       = false;
  clipboard->available       = false;
  clipboard->statusValid     = false;
  detachStreamsNL(clipboard);
  clearProtocolNL(clipboard);
  clipboard->held            = false;
  clipboard->heldReady       = false;
  memset(&clipboard->heldStreamBuffer, 0,
      sizeof(clipboard->heldStreamBuffer));
  memset(&clipboard->heldRecord, 0, sizeof(clipboard->heldRecord));
  clipboard->heldFile = false;
  memset(&clipboard->heldFileRequest, 0,
      sizeof(clipboard->heldFileRequest));
  if (changed)
    nextNonzero(&clipboard->providerGeneration);
  atomic_store_explicit(&clipboard->stop, true, memory_order_release);
}

static void connectionFailed(LGMPClipboard * clipboard, LGMP_STATUS status)
{
  if (clipboard->connected)
    DEBUG_WARN("LGMP clipboard transport failed: %s",
      lgmpStatusString(status));
  connectionLostNL(clipboard);
}

static void connectionCorrupted(LGMPClipboard * clipboard,
    const char * reason)
{
  if (clipboard->connected)
    DEBUG_WARN("LGMP clipboard transport is corrupted: %s", reason);
  connectionLostNL(clipboard);
}

static bool validStatus(const KVMFRClipboardStatus * status)
{
  const uint32_t validFlags = KVMFR_CLIPBOARD_STATUS_AVAILABLE |
    KVMFR_CLIPBOARD_STATUS_HAS_OWNER;
  const uint32_t emptyReserved[6] = { 0 };
  if (status->version != KVMFR_CLIPBOARD_VERSION ||
      status->flags & ~validFlags || !status->generation ||
      status->formats & ~KVMFR_CLIPBOARD_FORMAT_MASK_ALL ||
      !status->lease ||
      status->slotBytes != KVMFR_CLIPBOARD_DATA_BYTES ||
      status->streamVersion != KVMFR_CLIPBOARD_STREAM_VERSION ||
      status->streamSlotCount != KVMFR_CLIPBOARD_STREAM_SLOT_COUNT ||
      memcmp(status->reserved, emptyReserved, sizeof(emptyReserved)) ||
      !validStreamDescriptor(&status->hostToClient,
        LGMP_STREAM_HOST_TO_CLIENT) ||
      !validStreamDescriptor(&status->clientToHost,
        LGMP_STREAM_CLIENT_TO_HOST))
    return false;

  const bool available =
    (status->flags & KVMFR_CLIPBOARD_STATUS_AVAILABLE) != 0;
  const bool owner =
    (status->flags & KVMFR_CLIPBOARD_STATUS_HAS_OWNER) != 0;
  if (!available && (status->formats || owner))
    return false;
  return owner ? status->ownerClientID && status->ownerGeneration :
    !status->ownerClientID && !status->ownerGeneration;
}

static bool validateRecord(const LGMPClipboard * clipboard,
    const KVMFRClipboardMessage * record, size_t size, bool stream)
{
  if (size < sizeof(*record) ||
      record->version != KVMFR_CLIPBOARD_VERSION ||
      record->type < KVMFR_CLIPBOARD_MESSAGE_OFFER ||
      record->type > KVMFR_CLIPBOARD_MESSAGE_FILE_CANCEL ||
      record->generation != clipboard->claimGeneration ||
      record->length > KVMFR_CLIPBOARD_DATA_BYTES ||
      record->offset > UINT64_MAX - record->length)
    return false;

  if (record->type == KVMFR_CLIPBOARD_MESSAGE_DATA ||
      record->type == KVMFR_CLIPBOARD_MESSAGE_FILE_DATA)
  {
    if (!stream || size != sizeof(*record) + record->length)
      return false;
  }
  else if (stream || size != sizeof(*record) || record->length ||
      (record->flags &&
       record->type != KVMFR_CLIPBOARD_MESSAGE_FILE_REQUEST))
    return false;

  switch (record->type)
  {
    case KVMFR_CLIPBOARD_MESSAGE_OFFER:
      return record->clipboardGeneration && record->token &&
        !(record->token & ~KVMFR_CLIPBOARD_FORMAT_MASK_ALL) &&
        !record->transfer && !record->offset && !record->size &&
        !record->format && !record->flags && !record->length &&
        !record->sequence;

    case KVMFR_CLIPBOARD_MESSAGE_CLEAR:
      return record->clipboardGeneration && !record->token &&
        !record->transfer && !record->offset && !record->size &&
        !record->format && !record->flags && !record->length &&
        !record->sequence;

    case KVMFR_CLIPBOARD_MESSAGE_REQUEST:
      return record->clipboardGeneration &&
        kvmfrClipboardTransferFromHelper(record->transfer) &&
        kvmfrClipboardRepresentationFormatValid(record->format) &&
        !record->offset && !record->size && !record->flags &&
        !record->token && !record->length && !record->sequence;

    case KVMFR_CLIPBOARD_MESSAGE_DATA:
    {
      const uint64_t end = record->offset + record->length;
      const unsigned operations =
        !!(record->flags & KVMFR_CLIPBOARD_FLAG_BEGIN) +
        !!record->length + !!(record->flags & KVMFR_CLIPBOARD_FLAG_END);
      if (!record->clipboardGeneration || record->token ||
          kvmfrClipboardTransferFromHelper(record->transfer) ||
          !kvmfrClipboardRepresentationFormatValid(record->format) ||
          record->flags & ~(KVMFR_CLIPBOARD_FLAG_BEGIN |
            KVMFR_CLIPBOARD_FLAG_END) || !operations ||
          ((record->flags & KVMFR_CLIPBOARD_FLAG_BEGIN) &&
           (record->offset || record->sequence)) ||
          ((record->flags & KVMFR_CLIPBOARD_FLAG_END) &&
           record->size != end))
        return false;
      if ((record->flags & KVMFR_CLIPBOARD_FLAG_BEGIN) &&
          record->size != KVMFR_CLIPBOARD_SIZE_UNKNOWN &&
          end > record->size)
        return false;
      return true;
    }

    case KVMFR_CLIPBOARD_MESSAGE_CANCEL:
      return record->transfer && !record->offset && !record->size &&
        (!record->format ||
         kvmfrClipboardRepresentationFormatValid(record->format)) &&
        !record->flags && !record->length &&
        !record->sequence;

    case KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRE:
    case KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRED:
    case KVMFR_CLIPBOARD_MESSAGE_FILE_RELEASE:
    case KVMFR_CLIPBOARD_MESSAGE_FILE_REQUEST:
    case KVMFR_CLIPBOARD_MESSAGE_FILE_DATA:
    case KVMFR_CLIPBOARD_MESSAGE_FILE_CANCEL:
      return kvmfrClipboardFileMessageValid(record);

    default:
      return false;
  }
}

static bool validateReadChunkNL(const LGMPClipboard * clipboard,
    const KVMFRClipboardMessage * record)
{
  if (clipboard->readRequest == LG_CLIPBOARD_REQUEST_INVALID ||
      record->transfer != clipboard->readTransfer ||
      record->clipboardGeneration !=
        clipboard->readClipboardGeneration ||
      record->format != clipboard->readFormat ||
      record->offset != clipboard->readOffset ||
      record->sequence != clipboard->readSequence)
    return false;

  const uint64_t end = record->offset + record->length;
  if (!record->length &&
      !(record->flags & (KVMFR_CLIPBOARD_FLAG_BEGIN |
        KVMFR_CLIPBOARD_FLAG_END)))
    return false;
  if (!clipboard->readBegan)
  {
    if (record->offset || record->sequence ||
        !(record->flags & KVMFR_CLIPBOARD_FLAG_BEGIN))
      return false;
  }
  else if (record->flags & KVMFR_CLIPBOARD_FLAG_BEGIN)
    return false;
  else if (!(record->flags & KVMFR_CLIPBOARD_FLAG_END) &&
      record->size != KVMFR_CLIPBOARD_SIZE_UNKNOWN)
    return false;

  const uint64_t hint = clipboard->readBegan ?
    clipboard->readSizeHint : record->size;
  if (hint != KVMFR_CLIPBOARD_SIZE_UNKNOWN && end > hint)
    return false;
  if (record->flags & KVMFR_CLIPBOARD_FLAG_END)
    return record->size == end &&
      (hint == KVMFR_CLIPBOARD_SIZE_UNKNOWN || hint == end);
  return true;
}

static void advanceReadNL(LGMPClipboard * clipboard,
    const KVMFRClipboardMessage * record)
{
  if (!clipboard->readBegan)
  {
    clipboard->readBegan    = true;
    clipboard->readSizeHint = record->size;
  }
  clipboard->readOffset += record->length;
  ++clipboard->readSequence;
  if (record->flags & KVMFR_CLIPBOARD_FLAG_END)
    clearReadNL(clipboard);
}

static bool validateFileReadChunkNL(const LGMPClipboard * clipboard,
    const KVMFRClipboardMessage * record,
    LG_ClipboardFileRequest * descriptor)
{
  struct FileTransfer * transfer = fileTransferFindNL(
      clipboard->fileReads, record->transfer);
  if (!transfer || record->clipboardGeneration != transfer->request.dataset ||
      record->format != KVMFR_CLIPBOARD_FORMAT_FILES ||
      record->token != toWireFileOperation(transfer->request.operation) ||
      record->offset != transfer->responseOffset ||
      record->sequence != transfer->sequence)
    return false;
  if (!transfer->began)
  {
    if (record->offset || record->sequence ||
        !(record->flags & KVMFR_CLIPBOARD_FLAG_BEGIN))
      return false;
  }
  else if (record->flags & KVMFR_CLIPBOARD_FLAG_BEGIN)
    return false;
  else if (!(record->flags & KVMFR_CLIPBOARD_FLAG_END) &&
      record->size != KVMFR_CLIPBOARD_SIZE_UNKNOWN)
    return false;

  const uint64_t end = record->offset + record->length;
  const uint64_t hint = transfer->began ? transfer->sizeHint : record->size;
  if (hint != KVMFR_CLIPBOARD_SIZE_UNKNOWN && end > hint)
    return false;
  if (transfer->request.operation == LG_CLIPBOARD_FILE_READ &&
      end > transfer->request.length)
    return false;
  if ((record->flags & KVMFR_CLIPBOARD_FLAG_END) &&
      (record->size != end ||
       (hint != KVMFR_CLIPBOARD_SIZE_UNKNOWN && hint != end)))
    return false;
  *descriptor = transfer->request;
  return true;
}

static void advanceFileReadNL(LGMPClipboard * clipboard,
    const KVMFRClipboardMessage * record)
{
  struct FileTransfer * transfer = fileTransferFindNL(
      clipboard->fileReads, record->transfer);
  if (!transfer)
    return;
  if (!transfer->began)
  {
    transfer->began    = true;
    transfer->sizeHint = record->size;
  }
  transfer->responseOffset += record->length;
  ++transfer->sequence;
  if (record->flags & KVMFR_CLIPBOARD_FLAG_END)
    free(fileTransferTakeNL(&clipboard->fileReads, record->transfer));
}

static bool rejectMalformedFileReadNL(LGMPClipboard * clipboard,
    const KVMFRClipboardMessage * record,
    KVMFRClipboardMessage * cancellation, bool * queued)
{
  if (record->type != KVMFR_CLIPBOARD_MESSAGE_FILE_DATA)
    return false;

  struct FileTransfer * transfer = fileTransferFindNL(
      clipboard->fileReads, record->transfer);
  if (!transfer ||
      transfer->request.dataset != record->clipboardGeneration)
    return false;

  *cancellation = (KVMFRClipboardMessage)
  {
    .type                = KVMFR_CLIPBOARD_MESSAGE_FILE_CANCEL,
    .clipboardGeneration = transfer->request.dataset,
    .transfer            = transfer->request.request,
    .format              = KVMFR_CLIPBOARD_FORMAT_FILES,
    .token               = KVMFR_CLIPBOARD_FILE_ERROR_INVALID,
  };
  free(fileTransferTakeNL(&clipboard->fileReads, record->transfer));
  *queued = enqueueUrgentRecordNL(clipboard, *cancellation);
  return true;
}

static void dispatchOffer(LGMPClipboard * clipboard,
    uint64_t dataset, KVMFRClipboardFormatFlags mask)
{
  LG_ClipboardData formats[LG_CLIPBOARD_DATA_NONE];
  const unsigned count = formatsFromMask(mask, formats);

  LG_LOCK(clipboard->eventLock);
  LG_LOCK(clipboard->lock);
  const LG_ClipboardEventOps * events = clipboard->events;
  void * opaque = clipboard->eventOpaque;
  LG_UNLOCK(clipboard->lock);
  if (events && events->fileOffer &&
      (mask & KVMFR_CLIPBOARD_FORMAT_MASK_FILES))
    events->fileOffer(opaque, dataset);
  if (events && events->notice)
    events->notice(opaque, formats, count);
  LG_UNLOCK(clipboard->eventLock);
}

static void dispatchFileAcquire(LGMPClipboard * clipboard,
    const KVMFRClipboardMessage * record)
{
  LG_LOCK(clipboard->eventLock);
  LG_LOCK(clipboard->lock);
  const LG_ClipboardEventOps * events = clipboard->events;
  void * opaque = clipboard->eventOpaque;
  LG_UNLOCK(clipboard->lock);
  if (events && events->fileAcquire)
    events->fileAcquire(opaque,
        record->clipboardGeneration, record->transfer);
  LG_UNLOCK(clipboard->eventLock);
}

static void dispatchFileAcquired(LGMPClipboard * clipboard,
    const KVMFRClipboardMessage * record)
{
  LG_LOCK(clipboard->eventLock);
  LG_LOCK(clipboard->lock);
  const LG_ClipboardEventOps * events = clipboard->events;
  void * opaque = clipboard->eventOpaque;
  LG_UNLOCK(clipboard->lock);
  if (events && events->fileAcquired)
    events->fileAcquired(opaque, record->clipboardGeneration,
        record->transfer, fromWireFileError(record->token));
  LG_UNLOCK(clipboard->eventLock);
}

static void dispatchFileRelease(LGMPClipboard * clipboard,
    const KVMFRClipboardMessage * record)
{
  LG_LOCK(clipboard->eventLock);
  LG_LOCK(clipboard->lock);
  const LG_ClipboardEventOps * events = clipboard->events;
  void * opaque = clipboard->eventOpaque;
  LG_UNLOCK(clipboard->lock);
  if (events && events->fileRelease)
    events->fileRelease(opaque,
        record->clipboardGeneration, record->transfer);
  LG_UNLOCK(clipboard->eventLock);
}

static void dispatchFileRequest(LGMPClipboard * clipboard,
    const LG_ClipboardFileRequest * request)
{
  LG_LOCK(clipboard->eventLock);
  LG_LOCK(clipboard->lock);
  const LG_ClipboardEventOps * events = clipboard->events;
  void * opaque = clipboard->eventOpaque;
  LG_UNLOCK(clipboard->lock);
  if (events && events->fileRequest)
    events->fileRequest(opaque, request);
  LG_UNLOCK(clipboard->eventLock);
}

static LG_ClipboardResult dispatchFileData(LGMPClipboard * clipboard,
    const KVMFRClipboardMessage * record,
    const LG_ClipboardFileRequest * request, const uint8_t * data,
    enum HeldPhase phase)
{
  LG_LOCK(clipboard->eventLock);
  LG_LOCK(clipboard->lock);
  const LG_ClipboardEventOps * events = clipboard->events;
  void * opaque = clipboard->eventOpaque;
  LG_UNLOCK(clipboard->lock);
  LG_ClipboardResult result = LG_CLIPBOARD_RESULT_FAILED;
  if (events)
    switch (phase)
    {
      case HELD_PHASE_BEGIN:
        if (events->fileDataBegin)
          result = events->fileDataBegin(opaque, request, record->size);
        break;
      case HELD_PHASE_CHUNK:
        if (events->fileDataChunk)
          result = events->fileDataChunk(opaque, request,
              record->offset, data, record->length);
        break;
      case HELD_PHASE_END:
        if (events->fileDataEnd)
          result = events->fileDataEnd(opaque, request, record->size);
        break;
      case HELD_PHASE_NONE:
        break;
    }
  LG_UNLOCK(clipboard->eventLock);
  return result;
}

static void dispatchFileCancel(LGMPClipboard * clipboard,
    const KVMFRClipboardMessage * record)
{
  LG_LOCK(clipboard->eventLock);
  LG_LOCK(clipboard->lock);
  const LG_ClipboardEventOps * events = clipboard->events;
  void * opaque = clipboard->eventOpaque;
  LG_UNLOCK(clipboard->lock);
  if (events && events->fileCancel)
    events->fileCancel(opaque, record->clipboardGeneration,
        record->transfer, fromWireFileError(record->token));
  LG_UNLOCK(clipboard->eventLock);
}

static void dispatchRetiredFiles(LGMPClipboard * clipboard)
{
  LG_LOCK(clipboard->eventLock);
  LG_LOCK(clipboard->lock);
  struct FileTransfer * reads = clipboard->retiredFileReads;
  struct FileTransfer * writes = clipboard->retiredFileWrites;
  struct FileAcquisition * acquisitions =
    clipboard->retiredFileAcquisitions;
  clipboard->retiredFileReads        = NULL;
  clipboard->retiredFileWrites       = NULL;
  clipboard->retiredFileAcquisitions = NULL;
  const LG_ClipboardEventOps * events = clipboard->events;
  void * opaque = clipboard->eventOpaque;
  LG_UNLOCK(clipboard->lock);

  if (events && events->fileCancel)
  {
    for (struct FileTransfer * transfer = reads; transfer;
        transfer = transfer->next)
      events->fileCancel(opaque, transfer->request.dataset,
          transfer->request.request,
          LG_CLIPBOARD_FILE_ERROR_DISCONNECTED);
    for (struct FileTransfer * transfer = writes; transfer;
        transfer = transfer->next)
      events->fileCancel(opaque, transfer->request.dataset,
          transfer->request.request,
          LG_CLIPBOARD_FILE_ERROR_DISCONNECTED);
    for (struct FileAcquisition * acquisition = acquisitions;
        acquisition; acquisition = acquisition->next)
      events->fileCancel(opaque, acquisition->dataset,
          acquisition->acquisition,
          LG_CLIPBOARD_FILE_ERROR_DISCONNECTED);
  }
  freeFileTransfers(reads);
  freeFileTransfers(writes);
  freeFileAcquisitions(acquisitions);
  LG_UNLOCK(clipboard->eventLock);
}

static void dispatchFileReady(LGMPClipboard * clipboard, uint64_t request)
{
  LG_LOCK(clipboard->eventLock);
  LG_LOCK(clipboard->lock);
  const LG_ClipboardEventOps * events = clipboard->events;
  void * opaque = clipboard->eventOpaque;
  LG_UNLOCK(clipboard->lock);
  if (events && events->fileDataReady)
    events->fileDataReady(opaque, request);
  LG_UNLOCK(clipboard->eventLock);
}

static void dispatchRelease(LGMPClipboard * clipboard)
{
  LG_LOCK(clipboard->eventLock);
  LG_LOCK(clipboard->lock);
  const LG_ClipboardEventOps * events = clipboard->events;
  void * opaque = clipboard->eventOpaque;
  LG_UNLOCK(clipboard->lock);
  if (events && events->release)
    events->release(opaque);
  LG_UNLOCK(clipboard->eventLock);
}

static bool dispatchRequest(LGMPClipboard * clipboard,
    const KVMFRClipboardMessage * record)
{
  LG_LOCK(clipboard->eventLock);
  LG_LOCK(clipboard->lock);
  const LG_ClipboardEventOps * events = clipboard->events;
  void * opaque = clipboard->eventOpaque;
  LG_UNLOCK(clipboard->lock);
  const bool result = events && events->request && events->request(opaque,
    record->transfer, fromWireFormat(record->format));
  LG_UNLOCK(clipboard->eventLock);
  return result;
}

static void dispatchCancel(LGMPClipboard * clipboard,
    const KVMFRClipboardMessage * record)
{
  LG_LOCK(clipboard->eventLock);
  LG_LOCK(clipboard->lock);
  const LG_ClipboardEventOps * events = clipboard->events;
  void * opaque = clipboard->eventOpaque;
  LG_UNLOCK(clipboard->lock);
  if (events)
  {
    const LG_ClipboardCancelReason reason = fromWireCancel(record->token);
    if (kvmfrClipboardTransferFromHelper(record->transfer))
    {
      if (events->requestCancel)
        events->requestCancel(opaque, record->transfer, reason);
    }
    else if (events->dataCancel)
      events->dataCancel(opaque, record->transfer, reason);
  }
  LG_UNLOCK(clipboard->eventLock);
}

static void dispatchDataCancel(LGMPClipboard * clipboard,
    LG_ClipboardRequest request, uint32_t reason)
{
  LG_LOCK(clipboard->eventLock);
  LG_LOCK(clipboard->lock);
  const LG_ClipboardEventOps * events = clipboard->events;
  void * opaque = clipboard->eventOpaque;
  LG_UNLOCK(clipboard->lock);
  if (events && events->dataCancel)
    events->dataCancel(
      opaque, request, fromWireCancel(reason));
  LG_UNLOCK(clipboard->eventLock);
}

static LG_ClipboardResult dispatchData(LGMPClipboard * clipboard,
    const KVMFRClipboardMessage * record, const uint8_t * data,
    enum HeldPhase phase)
{
  LG_LOCK(clipboard->eventLock);
  LG_LOCK(clipboard->lock);
  const LG_ClipboardEventOps * events = clipboard->events;
  void * opaque = clipboard->eventOpaque;
  LG_UNLOCK(clipboard->lock);

  LG_ClipboardResult result = LG_CLIPBOARD_RESULT_FAILED;
  if (events)
  {
    switch (phase)
    {
      case HELD_PHASE_BEGIN:
        if (events->dataBegin)
          result = events->dataBegin(opaque, record->transfer,
            fromWireFormat(record->format), record->size);
        break;

      case HELD_PHASE_CHUNK:
        if (events->dataChunk)
          result = events->dataChunk(opaque, record->transfer,
            record->offset, data, record->length);
        break;

      case HELD_PHASE_END:
        if (events->dataEnd)
          result = events->dataEnd(
            opaque, record->transfer, record->size);
        break;

      case HELD_PHASE_NONE:
        break;
    }
  }
  LG_UNLOCK(clipboard->eventLock);
  return result;
}

static void dispatchReady(LGMPClipboard * clipboard,
    LG_ClipboardRequest request)
{
  LG_LOCK(clipboard->eventLock);
  LG_LOCK(clipboard->lock);
  const LG_ClipboardEventOps * events = clipboard->events;
  void * opaque = clipboard->eventOpaque;
  LG_UNLOCK(clipboard->lock);
  if (events && events->dataReady)
    events->dataReady(opaque, request);
  LG_UNLOCK(clipboard->eventLock);
}

static void applyStatusNL(LGMPClipboard * clipboard,
    const KVMFRClipboardStatus * status, uint32_t serial,
    bool * changed)
{
  const bool     wasValid      = clipboard->statusValid;
  const bool     wasAvailable  = clipboard->available;
  const uint32_t oldGeneration = clipboard->endpointGeneration;
  clipboard->statusValid         = true;
  clipboard->statusSerial        = serial;
  const bool streamsAttached = attachStreamsNL(clipboard, status);
  const bool endpointAvailable =
    (status->flags & KVMFR_CLIPBOARD_STATUS_AVAILABLE) != 0;
  clipboard->endpointGeneration = status->generation;

  const bool hasOwner =
    (status->flags & KVMFR_CLIPBOARD_STATUS_HAS_OWNER) != 0;
  const bool owned = hasOwner &&
    (clipboard->claimed || clipboard->releasing) &&
    status->ownerClientID == clipboard->clientID &&
    status->ownerGeneration == clipboard->claimGeneration;
  const bool ownedByOther = hasOwner && !owned;
  const bool selectedUnavailable =
    (clipboard->claimed || clipboard->releasing) && !streamsAttached;
  clipboard->available = endpointAvailable && streamsAttached &&
    !ownedByOther && !clipboard->releasing;
  bool restore         = false;

  if (owned)
  {
    const LGMP_STATUS activate = activateStreamsNL(clipboard);
    if (activate != LGMP_OK)
    {
      connectionFailed(clipboard, activate);
      *changed = true;
      return;
    }
  }

  if (!endpointAvailable || oldGeneration != status->generation ||
      ownedByOther || selectedUnavailable)
  {
    clearProtocolNL(clipboard);
    restore = endpointAvailable && streamsAttached && !ownedByOther;
  }
  else if (hasOwner)
    clipboard->ownerConfirmed = owned;
  else
  {
    if (clipboard->releasing)
    {
      clipboard->releasing      = false;
      clipboard->ownerConfirmed = false;
      clipboard->available      = endpointAvailable && streamsAttached;
    }
    else if (clipboard->claimed && clipboard->ownerConfirmed)
    {
      clearProtocolNL(clipboard);
      restore = endpointAvailable && streamsAttached;
    }
    else
      clipboard->ownerConfirmed = false;
  }

  if (!wasAvailable && clipboard->available && !clipboard->claimed &&
      clipboard->events)
    restore = true;

  if (restore && !restoreClipboardNL(clipboard))
    clearProtocolNL(clipboard);

  *changed = !wasValid || wasAvailable != clipboard->available ||
    oldGeneration != clipboard->endpointGeneration;
  if (*changed)
    nextNonzero(&clipboard->providerGeneration);
}

static bool flushPendingNL(LGMPClipboard * clipboard)
{
  while (clipboard->pendingCount)
  {
    KVMFRClipboardMessage * record =
      &pendingAt(clipboard, 0)->record;
    const LGMP_STATUS status = lgmpClientTrySendData(
      clipboard->queue, record, sizeof(*record), NULL);
    if (status == LGMP_ERR_QUEUE_BUSY || status == LGMP_ERR_QUEUE_FULL)
      return true;
    if (status != LGMP_OK)
    {
      connectionFailed(clipboard, status);
      return false;
    }
    if (record->type == KVMFR_CLIPBOARD_MESSAGE_CLAIM)
      clipboard->publishedClaimGeneration = record->generation;
    else if (record->type == KVMFR_CLIPBOARD_MESSAGE_RELEASE &&
        clipboard->publishedClaimGeneration == record->generation)
      clipboard->publishedClaimGeneration = 0;
    clipboard->lastSend = microtime();
    clipboard->pendingHead =
      (clipboard->pendingHead + 1) % CLIPBOARD_PENDING_MAX;
    --clipboard->pendingCount;
  }
  return true;
}

static bool processHeld(LGMPClipboard * clipboard)
{
  for (;;)
  {
    KVMFRClipboardMessage record;
    const uint8_t * data;
    enum HeldPhase phase;
    bool file;
    LGMPStreamBuffer streamBuffer;
    LG_ClipboardFileRequest fileRequest;
    LG_LOCK(clipboard->lock);
    if (!clipboard->held || !clipboard->heldReady)
    {
      LG_UNLOCK(clipboard->lock);
      return true;
    }
    record = clipboard->heldRecord;
    streamBuffer = clipboard->heldStreamBuffer;
    data = !record.length ? NULL :
      (const uint8_t *)streamBuffer.data + sizeof(record);
    phase = clipboard->heldPhase;
    file = clipboard->heldFile;
    fileRequest = clipboard->heldFileRequest;
    clipboard->heldReady = false;
    LG_UNLOCK(clipboard->lock);

    const LG_ClipboardResult result = file ?
      dispatchFileData(clipboard, &record, &fileRequest, data, phase) :
      dispatchData(clipboard, &record, data, phase);
    LG_LOCK(clipboard->lock);
    if (!clipboard->held)
    {
      LG_UNLOCK(clipboard->lock);
      return true;
    }
    if (result == LG_CLIPBOARD_RESULT_BLOCKED)
    {
      LG_UNLOCK(clipboard->lock);
      return true;
    }

    if (result == LG_CLIPBOARD_RESULT_ACCEPTED)
    {
      switch (phase)
      {
        case HELD_PHASE_BEGIN:
          clipboard->heldPhase = record.length ? HELD_PHASE_CHUNK :
            (record.flags & KVMFR_CLIPBOARD_FLAG_END) ?
              HELD_PHASE_END : HELD_PHASE_NONE;
          break;

        case HELD_PHASE_CHUNK:
          clipboard->heldPhase =
            (record.flags & KVMFR_CLIPBOARD_FLAG_END) ?
              HELD_PHASE_END : HELD_PHASE_NONE;
          break;

        case HELD_PHASE_END:
          clipboard->heldPhase = HELD_PHASE_NONE;
          break;

        case HELD_PHASE_NONE:
          break;
      }
    }
    else
      clipboard->heldPhase = HELD_PHASE_NONE;

    if (clipboard->heldPhase != HELD_PHASE_NONE)
    {
      clipboard->heldReady = true;
      LG_UNLOCK(clipboard->lock);
      continue;
    }

    const bool matchingRead = !file &&
      clipboard->readRequest == record.transfer &&
      clipboard->readTransfer;
    struct FileTransfer * matchingFile = file ? fileTransferFindNL(
        clipboard->fileReads, record.transfer) : NULL;
    if (result == LG_CLIPBOARD_RESULT_FAILED && (matchingRead || matchingFile))
    {
      KVMFRClipboardMessage cancel = { 0 };
      cancel.type = file ? KVMFR_CLIPBOARD_MESSAGE_FILE_CANCEL :
        KVMFR_CLIPBOARD_MESSAGE_CANCEL;
      cancel.clipboardGeneration = file ? fileRequest.dataset :
        clipboard->readClipboardGeneration;
      cancel.transfer = file ? fileRequest.request : clipboard->readTransfer;
      cancel.format = file ? KVMFR_CLIPBOARD_FORMAT_FILES :
        clipboard->readFormat;
      cancel.token = file ?
        toWireFileError(LG_CLIPBOARD_FILE_ERROR_INVALID) :
        LG_CLIPBOARD_CANCEL_INVALID;
      if (file)
        enqueueUrgentRecordNL(clipboard, cancel);
      else
        enqueueRecordNL(clipboard, cancel);
    }
    if (matchingFile && result == LG_CLIPBOARD_RESULT_ACCEPTED)
      advanceFileReadNL(clipboard, &record);
    else if (matchingFile)
      free(fileTransferTakeNL(&clipboard->fileReads, record.transfer));
    else if (matchingRead && result == LG_CLIPBOARD_RESULT_ACCEPTED)
      advanceReadNL(clipboard, &record);
    else if (matchingRead)
      clearReadNL(clipboard);

    const LGMP_STATUS done = lgmpClientStreamReadRelease(
      clipboard->hostToClientStream, &streamBuffer);
    clipboard->held = false;
    memset(&clipboard->heldStreamBuffer, 0,
        sizeof(clipboard->heldStreamBuffer));
    memset(&clipboard->heldRecord, 0, sizeof(clipboard->heldRecord));
    clipboard->heldFile = false;
    memset(&clipboard->heldFileRequest, 0,
        sizeof(clipboard->heldFileRequest));
    if (done != LGMP_OK)
    {
      connectionFailed(clipboard, done);
      LG_UNLOCK(clipboard->lock);
      return false;
    }
    LG_UNLOCK(clipboard->lock);
    return true;
  }
}

static bool processStream(LGMPClipboard * clipboard, bool * processed)
{
  *processed = false;
  LG_LOCK(clipboard->lock);
  if (!clipboard->connected || clipboard->held ||
      (!clipboard->claimed && !clipboard->releasing) ||
      !clipboard->hostToClientStream)
  {
    LG_UNLOCK(clipboard->lock);
    return true;
  }

  LGMPStreamBuffer buffer = { 0 };
  LGMP_STATUS status = lgmpClientStreamReadPeek(
    clipboard->hostToClientStream, &buffer);
  if (status == LGMP_ERR_STREAM_EMPTY ||
      status == LGMP_ERR_STREAM_UNBOUND ||
      status == LGMP_ERR_STREAM_STALE)
  {
    LG_UNLOCK(clipboard->lock);
    return true;
  }
  if (status != LGMP_OK)
  {
    connectionFailed(clipboard, status);
    LG_UNLOCK(clipboard->lock);
    return false;
  }
  *processed = true;

  if (clipboard->releasing)
  {
    status = lgmpClientStreamReadRelease(
      clipboard->hostToClientStream, &buffer);
    if (status != LGMP_OK && status != LGMP_ERR_STREAM_UNBOUND &&
        status != LGMP_ERR_STREAM_STALE)
      connectionFailed(clipboard, status);
    LG_UNLOCK(clipboard->lock);
    return status == LGMP_OK || status == LGMP_ERR_STREAM_UNBOUND ||
      status == LGMP_ERR_STREAM_STALE;
  }

  KVMFRClipboardMessage record = { 0 };
  if (buffer.data && buffer.size >= sizeof(record))
    memcpy(&record, buffer.data, sizeof(record));
  const bool valid = buffer.data &&
    validateRecord(clipboard, &record, buffer.size, true);
  if (!valid)
  {
    KVMFRClipboardMessage cancellation = { 0 };
    bool queued = false;
    const bool cancelled = rejectMalformedFileReadNL(
      clipboard, &record, &cancellation, &queued);
    status = lgmpClientStreamReadRelease(
      clipboard->hostToClientStream, &buffer);
    if (status != LGMP_OK)
      connectionFailed(clipboard, status);
    LG_UNLOCK(clipboard->lock);
    if (cancelled)
      dispatchFileCancel(clipboard, &cancellation);
    if (queued)
      signalWorker(clipboard);
    DEBUG_WARN("Ignoring malformed LGMP clipboard stream record");
    return status == LGMP_OK;
  }

  LG_ClipboardFileRequest fileRequest = { 0 };
  const bool file =
    record.type == KVMFR_CLIPBOARD_MESSAGE_FILE_DATA;
  const bool chunkValid = file ? validateFileReadChunkNL(
      clipboard, &record, &fileRequest) :
    validateReadChunkNL(clipboard, &record);
  if (!chunkValid)
  {
    KVMFRClipboardMessage cancellation = { 0 };
    bool queued = false;
    const bool cancelled = file && rejectMalformedFileReadNL(
      clipboard, &record, &cancellation, &queued);
    status = lgmpClientStreamReadRelease(
      clipboard->hostToClientStream, &buffer);
    if (status != LGMP_OK)
      connectionFailed(clipboard, status);
    LG_UNLOCK(clipboard->lock);
    if (cancelled)
      dispatchFileCancel(clipboard, &cancellation);
    if (queued)
      signalWorker(clipboard);
    DEBUG_WARN("Ignoring stale or malformed LGMP clipboard stream data");
    return status == LGMP_OK;
  }

  if (!file)
    record.transfer = clipboard->readRequest;
  clipboard->held             = true;
  clipboard->heldReady        = true;
  clipboard->heldStreamBuffer = buffer;
  clipboard->heldRecord       = record;
  clipboard->heldFile         = file;
  clipboard->heldFileRequest  = fileRequest;
  clipboard->heldPhase        =
    (record.flags & KVMFR_CLIPBOARD_FLAG_BEGIN) ? HELD_PHASE_BEGIN :
    record.length ? HELD_PHASE_CHUNK : HELD_PHASE_END;
  LG_UNLOCK(clipboard->lock);
  return processHeld(clipboard);
}

static bool processMessage(LGMPClipboard * clipboard, bool * processed)
{
  *processed = false;
  LG_LOCK(clipboard->lock);
  if (!clipboard->connected || !clipboard->queue || clipboard->held)
  {
    LG_UNLOCK(clipboard->lock);
    return true;
  }

  LGMPMessage message;
  LGMP_STATUS status = lgmpClientProcess(clipboard->queue, &message);
  if (status == LGMP_ERR_QUEUE_EMPTY)
  {
    LG_UNLOCK(clipboard->lock);
    return true;
  }
  if (status != LGMP_OK)
  {
    connectionFailed(clipboard, status);
    LG_UNLOCK(clipboard->lock);
    return false;
  }
  *processed = true;

  const KVMFRClipboardQueueType type =
    KVMFR_CLIPBOARD_QUEUE_TYPE(message.udata);
  if (type == KVMFR_CLIPBOARD_QUEUE_STATUS)
  {
    KVMFRClipboardStatus snapshot = { 0 };
    const bool valid = message.size == sizeof(snapshot);
    if (valid)
      memcpy(&snapshot, message.mem, sizeof(snapshot));
    status = lgmpClientMessageDone(clipboard->queue);
    bool changed = false;
    if (status == LGMP_OK && valid && validStatus(&snapshot))
    {
      const uint32_t serial = KVMFR_CLIPBOARD_QUEUE_SERIAL(message.udata);
      if (!clipboard->statusValid ||
          (int32_t)(serial - clipboard->statusSerial) > 0)
        applyStatusNL(clipboard, &snapshot, serial, &changed);
    }
    else if (status == LGMP_OK)
      DEBUG_WARN("Ignoring invalid LGMP clipboard status");
    if (status != LGMP_OK)
      connectionFailed(clipboard, status);
    LG_UNLOCK(clipboard->lock);
    dispatchRetiredFiles(clipboard);
    if (changed)
      notifyStatus(clipboard);
    return status == LGMP_OK;
  }

  if (!clipboard->claimed)
  {
    status = lgmpClientMessageDone(clipboard->queue);
    if (status != LGMP_OK)
      connectionFailed(clipboard, status);
    LG_UNLOCK(clipboard->lock);
    return status == LGMP_OK;
  }

  if (type != KVMFR_CLIPBOARD_QUEUE_MESSAGE ||
      message.size < sizeof(KVMFRClipboardMessage))
  {
    status = lgmpClientMessageDone(clipboard->queue);
    if (status != LGMP_OK)
      connectionFailed(clipboard, status);
    LG_UNLOCK(clipboard->lock);
    DEBUG_WARN("Ignoring invalid LGMP clipboard message");
    return status == LGMP_OK;
  }

  KVMFRClipboardMessage record;
  memcpy(&record, message.mem, sizeof(record));
  const bool valid = validateRecord(clipboard, &record, message.size, false);
  if (!valid)
  {
    KVMFRClipboardMessage cancellation = { 0 };
    bool queued = false;
    const bool cancelled = rejectMalformedFileReadNL(
        clipboard, &record, &cancellation, &queued);
    status = lgmpClientMessageDone(clipboard->queue);
    if (status != LGMP_OK)
      connectionFailed(clipboard, status);
    LG_UNLOCK(clipboard->lock);
    if (cancelled)
      dispatchFileCancel(clipboard, &cancellation);
    if (queued)
      signalWorker(clipboard);
    DEBUG_WARN("Ignoring malformed LGMP clipboard record");
    return status == LGMP_OK;
  }

  status = lgmpClientMessageDone(clipboard->queue);
  if (status != LGMP_OK)
  {
    connectionFailed(clipboard, status);
    LG_UNLOCK(clipboard->lock);
    return false;
  }
  bool dispatch = true;
  bool fileRejected = false;
  LG_ClipboardRequest cancelledRead = LG_CLIPBOARD_REQUEST_INVALID;
  LG_ClipboardFileRequest fileRequest = { 0 };
  switch (record.type)
  {
    case KVMFR_CLIPBOARD_MESSAGE_OFFER:
      clipboard->remoteClipboardGeneration = record.clipboardGeneration;
      clipboard->remoteFormats = record.token;
      clearReadNL(clipboard);
      break;

    case KVMFR_CLIPBOARD_MESSAGE_CLEAR:
      clipboard->remoteClipboardGeneration = record.clipboardGeneration;
      clipboard->remoteFormats = 0;
      clearReadNL(clipboard);
      break;

    case KVMFR_CLIPBOARD_MESSAGE_REQUEST:
      if (clipboard->writeTransfer != LG_CLIPBOARD_REQUEST_INVALID ||
          record.clipboardGeneration !=
            clipboard->localClipboardGeneration ||
          !(clipboard->localFormats &
            kvmfrClipboardFormatFlag(record.format)))
      {
        dispatch = false;
        break;
      }
      clearWriteNL(clipboard);
      clipboard->writeTransfer = record.transfer;
      clipboard->writeClipboardGeneration = record.clipboardGeneration;
      clipboard->writeFormat = record.format;
      break;

    case KVMFR_CLIPBOARD_MESSAGE_CANCEL:
    {
      const bool write = clipboard->writeTransfer == record.transfer;
      const bool read = clipboard->readTransfer == record.transfer;
      if (!write && !read)
      {
        dispatch = false;
        break;
      }
      const uint64_t clipboardGeneration = write ?
        clipboard->writeClipboardGeneration :
        clipboard->readClipboardGeneration;
      const KVMFRClipboardFormat format = write ?
        clipboard->writeFormat : clipboard->readFormat;
      if ((record.clipboardGeneration &&
           record.clipboardGeneration != clipboardGeneration) ||
          (record.format && record.format != format))
      {
        dispatch = false;
        break;
      }
      if (write)
        clearWriteNL(clipboard);
      if (read)
      {
        cancelledRead = clipboard->readRequest;
        clearReadNL(clipboard);
      }
      break;
    }

    case KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRE:
    {
      if (!kvmfrClipboardTransferFromHelper(record.transfer))
      {
        dispatch = false;
        break;
      }
      if (fileAcquisitionFindNL(clipboard, record.clipboardGeneration,
            record.transfer, true))
      {
        fileRejected = enqueueFileFailureNL(clipboard, &record,
            LG_CLIPBOARD_FILE_ERROR_INVALID);
        dispatch = false;
        break;
      }
      if (record.clipboardGeneration !=
            clipboard->localClipboardGeneration ||
          !(clipboard->localFormats & KVMFR_CLIPBOARD_FORMAT_MASK_FILES))
      {
        fileRejected = enqueueFileFailureNL(clipboard, &record,
            LG_CLIPBOARD_FILE_ERROR_STALE);
        dispatch = false;
        break;
      }
      if (fileAcquisitionCountNL(clipboard) >=
          KVMFR_CLIPBOARD_FILE_MAX_ACQUISITIONS)
      {
        fileRejected = enqueueFileFailureNL(clipboard, &record,
            LG_CLIPBOARD_FILE_ERROR_NO_SPACE);
        dispatch = false;
        break;
      }
      struct FileAcquisition * acquisition = calloc(1, sizeof(*acquisition));
      if (!acquisition)
      {
        fileRejected = enqueueFileFailureNL(clipboard, &record,
            LG_CLIPBOARD_FILE_ERROR_NO_MEMORY);
        dispatch = false;
        break;
      }
      *acquisition = (struct FileAcquisition)
      {
        .dataset     = record.clipboardGeneration,
        .acquisition = record.transfer,
        .fromHelper  = true,
        .next        = clipboard->fileAcquisitions,
      };
      clipboard->fileAcquisitions = acquisition;
      break;
    }

    case KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRED:
    {
      if (kvmfrClipboardTransferFromHelper(record.transfer))
      {
        dispatch = false;
        break;
      }
      struct FileAcquisition * acquisition = fileAcquisitionFindNL(
          clipboard, record.clipboardGeneration, record.transfer, false);
      if (!acquisition || acquisition->active)
      {
        dispatch = false;
        break;
      }
      if (record.token)
        free(fileAcquisitionTakeNL(clipboard,
            record.clipboardGeneration, record.transfer, false));
      else
        acquisition->active = true;
      break;
    }

    case KVMFR_CLIPBOARD_MESSAGE_FILE_RELEASE:
    {
      struct FileAcquisition * acquisition = fileAcquisitionTakeNL(
          clipboard, record.clipboardGeneration, record.transfer, true);
      if (!acquisition)
      {
        dispatch = false;
        break;
      }
      free(acquisition);
      break;
    }

    case KVMFR_CLIPBOARD_MESSAGE_FILE_REQUEST:
    {
      if (!kvmfrClipboardTransferFromHelper(record.transfer))
      {
        dispatch = false;
        break;
      }
      if (fileTransferFindNL(clipboard->fileWrites, record.transfer))
      {
        fileRejected = enqueueFileFailureNL(clipboard, &record,
            LG_CLIPBOARD_FILE_ERROR_INVALID);
        dispatch = false;
        break;
      }
      if (!fileDatasetAcquiredNL(clipboard,
            record.clipboardGeneration, true))
      {
        fileRejected = enqueueFileFailureNL(clipboard, &record,
            LG_CLIPBOARD_FILE_ERROR_STALE);
        dispatch = false;
        break;
      }
      if (fileTransferCountNL(clipboard) >=
          KVMFR_CLIPBOARD_FILE_MAX_REQUESTS)
      {
        fileRejected = enqueueFileFailureNL(clipboard, &record,
            LG_CLIPBOARD_FILE_ERROR_NO_SPACE);
        dispatch = false;
        break;
      }
      struct FileTransfer * transfer = calloc(1, sizeof(*transfer));
      if (!transfer)
      {
        fileRejected = enqueueFileFailureNL(clipboard, &record,
            LG_CLIPBOARD_FILE_ERROR_NO_MEMORY);
        dispatch = false;
        break;
      }
      fileRequest = (LG_ClipboardFileRequest)
      {
        .dataset   = record.clipboardGeneration,
        .request   = record.transfer,
        .node      = record.size,
        .offset    = record.offset,
        .length    = record.token == KVMFR_CLIPBOARD_FILE_OP_READ ?
          record.flags : 0,
        .operation = fromWireFileOperation(record.token),
      };
      transfer->request  = fileRequest;
      transfer->sizeHint = KVMFR_CLIPBOARD_SIZE_UNKNOWN;
      transfer->next     = clipboard->fileWrites;
      clipboard->fileWrites = transfer;
      break;
    }

    case KVMFR_CLIPBOARD_MESSAGE_FILE_CANCEL:
    {
      const bool fromHelper = kvmfrClipboardTransferFromHelper(
          record.transfer);
      struct FileTransfer ** transfers = fromHelper ?
        &clipboard->fileWrites : &clipboard->fileReads;
      struct FileTransfer * found = fileTransferFindNL(
          *transfers, record.transfer);
      struct FileTransfer * transfer = found &&
          found->request.dataset == record.clipboardGeneration ?
        fileTransferTakeNL(transfers, record.transfer) : NULL;
      struct FileAcquisition * acquisition = fileAcquisitionTakeNL(
          clipboard, record.clipboardGeneration, record.transfer,
          fromHelper);
      if (!transfer && !acquisition)
      {
        dispatch = false;
        break;
      }
      free(transfer);
      free(acquisition);
      break;
    }
  }
  LG_UNLOCK(clipboard->lock);

  if (!dispatch)
  {
    if (fileRejected)
      signalWorker(clipboard);
    else
      DEBUG_WARN("Ignoring stale LGMP clipboard control record");
    return true;
  }

  switch (record.type)
  {
    case KVMFR_CLIPBOARD_MESSAGE_OFFER:
      dispatchOffer(clipboard, record.clipboardGeneration, record.token);
      break;
    case KVMFR_CLIPBOARD_MESSAGE_CLEAR:
      dispatchRelease(clipboard);
      break;
    case KVMFR_CLIPBOARD_MESSAGE_REQUEST:
      if (!dispatchRequest(clipboard, &record))
      {
        LG_LOCK(clipboard->lock);
        KVMFRClipboardMessage cancel = { 0 };
        cancel.type                = KVMFR_CLIPBOARD_MESSAGE_CANCEL;
        cancel.clipboardGeneration = record.clipboardGeneration;
        cancel.transfer            = record.transfer;
        cancel.format              = record.format;
        cancel.token               = LG_CLIPBOARD_CANCEL_UNAVAILABLE;
        enqueueRecordNL(clipboard, cancel);
        clearWriteNL(clipboard);
        LG_UNLOCK(clipboard->lock);
        signalWorker(clipboard);
      }
      break;
    case KVMFR_CLIPBOARD_MESSAGE_CANCEL:
      if (cancelledRead != LG_CLIPBOARD_REQUEST_INVALID)
        dispatchDataCancel(clipboard, cancelledRead, record.token);
      else
        dispatchCancel(clipboard, &record);
      break;
    case KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRE:
      dispatchFileAcquire(clipboard, &record);
      break;
    case KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRED:
      dispatchFileAcquired(clipboard, &record);
      break;
    case KVMFR_CLIPBOARD_MESSAGE_FILE_RELEASE:
      dispatchFileRelease(clipboard, &record);
      break;
    case KVMFR_CLIPBOARD_MESSAGE_FILE_REQUEST:
      dispatchFileRequest(clipboard, &fileRequest);
      break;
    case KVMFR_CLIPBOARD_MESSAGE_FILE_CANCEL:
      dispatchFileCancel(clipboard, &record);
      break;
  }
  return true;
}

static int clipboardThread(void * opaque)
{
  LGMPClipboard * clipboard = opaque;
  LGMPStreamPollState streamPoll;
  const LGMP_STATUS pollStatus = lgmpStreamPollInit(&streamPoll,
      (struct LGMPStreamPollConfig)
      {
        .spinCount = 32U,
        .minWaitUs = 50U,
        .maxWaitUs = 1000U,
      });
  if (pollStatus != LGMP_OK)
  {
    DEBUG_ERROR("Failed to initialize LGMP clipboard polling: %s",
      lgmpStatusString(pollStatus));
    return -1;
  }

  while (!atomic_load_explicit(&clipboard->stop, memory_order_acquire))
  {
    bool     running = true;
    bool     streamProgress = false;
    unsigned drained = 0;
    for (; drained < CLIPBOARD_DRAIN_MAX; ++drained)
    {
      if (!processHeld(clipboard))
      {
        running = false;
        break;
      }
      dispatchRetiredFiles(clipboard);

      bool queueProcessed = false;
      if (!processMessage(clipboard, &queueProcessed))
      {
        running = false;
        break;
      }
      dispatchRetiredFiles(clipboard);
      bool streamProcessed = false;
      if (!processStream(clipboard, &streamProcessed))
      {
        running = false;
        break;
      }
      streamProgress |= streamProcessed;
      dispatchRetiredFiles(clipboard);
      if (!queueProcessed && !streamProcessed)
        break;
    }
    if (!running)
      break;

    LG_ClipboardRequest readyRequest = LG_CLIPBOARD_REQUEST_INVALID;
    uint64_t fileReady[CLIPBOARD_PENDING_MAX];
    size_t fileReadyCount = 0;
    LG_LOCK(clipboard->lock);
    flushPendingNL(clipboard);
    const uint64_t now = microtime();
    if (clipboard->connected && clipboard->claimed &&
        !clipboard->pendingCount &&
        now - clipboard->lastSend >= CLIPBOARD_KEEPALIVE_US)
      enqueueTypeNL(clipboard, KVMFR_CLIPBOARD_MESSAGE_KEEPALIVE);
    bool blockedFile = false;
    for (struct FileTransfer * transfer = clipboard->fileWrites;
        transfer; transfer = transfer->next)
      if (transfer->blocked)
      {
        blockedFile = true;
        break;
      }
    const bool writable =
      (clipboard->writeBlocked || blockedFile) &&
      dataWritableNL(clipboard);
    if (writable)
      streamProgress = true;
    if (clipboard->writeBlocked && writable)
    {
      readyRequest = clipboard->writeBlockedRequest;
      clipboard->writeBlocked = false;
    }
    if (writable)
      for (struct FileTransfer * transfer = clipboard->fileWrites;
          transfer && fileReadyCount < CLIPBOARD_PENDING_MAX;
          transfer = transfer->next)
        if (transfer->blocked)
        {
          transfer->blocked = false;
          fileReady[fileReadyCount++] = transfer->request.request;
        }
    const bool active = drained == CLIPBOARD_DRAIN_MAX ||
      clipboard->pendingCount || clipboard->held ||
      clipboard->writeBlocked || clipboard->fileReads ||
      clipboard->fileWrites;
    const bool streamActive = clipboard->connected &&
      (clipboard->claimed || clipboard->releasing);
    LG_UNLOCK(clipboard->lock);

    if (readyRequest != LG_CLIPBOARD_REQUEST_INVALID)
      dispatchReady(clipboard, readyRequest);
    for (size_t i = 0; i < fileReadyCount; ++i)
      dispatchFileReady(clipboard, fileReady[i]);

    if (atomic_load_explicit(&clipboard->stop, memory_order_acquire))
      break;
    if (drained == CLIPBOARD_DRAIN_MAX)
    {
      if (streamProgress)
        lgmpStreamPollActivity(&streamPoll);
      continue;
    }

    bool signaled;
    if (streamActive)
    {
      if (streamProgress)
        lgmpStreamPollActivity(&streamPoll);
      const uint32_t waitUs = lgmpStreamPollIdle(&streamPoll);
      if (!waitUs)
        continue;
      signaled = lgWaitEventNS(clipboard->event, waitUs * 1000U);
    }
    else
    {
      lgmpStreamPollActivity(&streamPoll);
      signaled = lgWaitEvent(clipboard->event,
        active ? CLIPBOARD_ACTIVE_POLL_MS : CLIPBOARD_IDLE_POLL_MS);
    }
    if (signaled)
      lgmpStreamPollActivity(&streamPoll);
  }
  dispatchRetiredFiles(clipboard);
  notifyStatus(clipboard);
  return 0;
}

bool lgmpClipboard_create(PLGMPClient client, LGMPClipboard ** result)
{
  if (!client || !result)
    return false;
  LGMPClipboard * clipboard = calloc(1, sizeof(*clipboard));
  if (!clipboard)
    return false;
  clipboard->client = client;
  uint64_t nonce = 0;
  if (!randomBytes(&nonce, sizeof(nonce)))
  {
    free(clipboard);
    return false;
  }
  nonce &= ~KVMFR_CLIPBOARD_TRANSFER_HELPER;
  if (!nonce)
    nonce = 1;
  clipboard->transferSerial = nonce;
  clipboard->localGenerationSerial =
    (nonce ^ UINT64_C(0x4c47434c49504244)) &
      ~KVMFR_CLIPBOARD_TRANSFER_HELPER;
  if (!clipboard->localGenerationSerial)
    clipboard->localGenerationSerial = 1;
  clipboard->providerGeneration = 1;
  LG_LOCK_INIT(clipboard->lock);
  LG_LOCK_INIT(clipboard->eventLock);
  LG_LOCK_INIT(clipboard->statusLock);
  atomic_init(&clipboard->stop, false);
  *result = clipboard;
  return true;
}

void lgmpClipboard_destroy(LGMPClipboard ** clipboard)
{
  if (!clipboard || !*clipboard)
    return;
  lgmpClipboard_disconnect(*clipboard);
  LG_LOCK_FREE((*clipboard)->statusLock);
  LG_LOCK_FREE((*clipboard)->eventLock);
  LG_LOCK_FREE((*clipboard)->lock);
  free(*clipboard);
  *clipboard = NULL;
}

bool lgmpClipboard_connect(LGMPClipboard * clipboard, uint32_t clientID)
{
  if (!clipboard || !clientID)
    return false;

  LG_LOCK(clipboard->lock);
  if (clipboard->connected)
  {
    const bool same = clipboard->clientID == clientID;
    LG_UNLOCK(clipboard->lock);
    return same;
  }
  if (clipboard->thread || clipboard->queue)
  {
    LG_UNLOCK(clipboard->lock);
    return false;
  }

  LGMP_STATUS status = lgmpClientSubscribe(
    clipboard->client, LGMP_Q_CLIPBOARD, &clipboard->queue);
  if (status != LGMP_OK)
  {
    LG_UNLOCK(clipboard->lock);
    DEBUG_WARN("Failed to subscribe to LGMP clipboard queue: %s",
      lgmpStatusString(status));
    return false;
  }
  clipboard->event = lgCreateEvent(true, 0);
  if (!clipboard->event)
  {
    lgmpClientUnsubscribe(&clipboard->queue);
    LG_UNLOCK(clipboard->lock);
    return false;
  }

  clipboard->connected             = true;
  clipboard->available             = false;
  clipboard->statusValid           = false;
  clipboard->claimed               = false;
  clipboard->ownerConfirmed        = false;
  clipboard->releasing             = false;
  clipboard->clientID              = clientID;
  clipboard->endpointGeneration    = 0;
  clipboard->statusSerial          = 0;
  clipboard->pendingHead           = 0;
  clipboard->pendingCount          = 0;
  clipboard->held                  = false;
  clipboard->heldReady             = false;
  clearWriteNL(clipboard);
  clipboard->remoteFormats         = 0;
  clipboard->remoteClipboardGeneration = 0;
  clearReadNL(clipboard);
  clipboard->publishedClaimGeneration = 0;
  atomic_store_explicit(&clipboard->stop, false, memory_order_release);

  LGThread * thread;
  if (lgCreateThread("lgmpClipboard", clipboardThread,
      clipboard, &thread))
  {
    clipboard->thread = thread;
    LG_UNLOCK(clipboard->lock);
    return true;
  }

  clipboard->connected = false;
  LGEvent * event = clipboard->event;
  PLGMPClientQueue queue = clipboard->queue;
  clipboard->event = NULL;
  clipboard->queue = NULL;
  LG_UNLOCK(clipboard->lock);
  lgmpClientUnsubscribe(&queue);
  lgFreeEvent(event);
  return false;
}

static void releaseOnDisconnect(LGMPClipboard * clipboard)
{
  if (!clipboard->queue || !clipboard->publishedClaimGeneration ||
      !lgmpClientSessionValid(clipboard->client))
    return;

  const KVMFRClipboardMessage release =
  {
    .version    = KVMFR_CLIPBOARD_VERSION,
    .type       = KVMFR_CLIPBOARD_MESSAGE_RELEASE,
    .generation = clipboard->publishedClaimGeneration,
  };
  const uint64_t deadline =
    microtime() + CLIPBOARD_RELEASE_TIMEOUT_US;
  uint32_t serial = 0;
  LGMP_STATUS status;
  do
  {
    status = lgmpClientTrySendData(clipboard->queue,
      &release, sizeof(release), &serial);
    if (status == LGMP_OK)
      break;
    if (status != LGMP_ERR_QUEUE_BUSY && status != LGMP_ERR_QUEUE_FULL)
      return;
    if (clipboard->event)
      lgWaitEvent(clipboard->event, CLIPBOARD_ACTIVE_POLL_MS);
  }
  while (microtime() < deadline);

  if (status != LGMP_OK)
  {
    DEBUG_WARN("Timed out releasing LGMP clipboard ownership");
    return;
  }

  do
  {
    uint32_t processed;
    status = lgmpClientGetSerial(clipboard->queue, &processed);
    if (status != LGMP_OK)
      return;
    if ((int32_t)(processed - serial) >= 0)
      return;
    if (clipboard->event)
      lgWaitEvent(clipboard->event, CLIPBOARD_ACTIVE_POLL_MS);
  }
  while (microtime() < deadline);
  DEBUG_WARN("Timed out waiting for LGMP clipboard release");
}

void lgmpClipboard_disconnect(LGMPClipboard * clipboard)
{
  if (!clipboard)
    return;

  LG_LOCK(clipboard->lock);
  if (!clipboard->thread && !clipboard->queue)
  {
    LG_UNLOCK(clipboard->lock);
    return;
  }
  if (clipboard->connected || clipboard->available)
    nextNonzero(&clipboard->providerGeneration);
  clipboard->connected = false;
  clipboard->available = false;
  clipboard->claimed   = false;
  atomic_store_explicit(&clipboard->stop, true, memory_order_release);
  LGThread * thread = clipboard->thread;
  LGEvent * event = clipboard->event;
  LG_UNLOCK(clipboard->lock);
  if (event)
    lgSignalEvent(event);
  if (thread)
    lgJoinThread(thread, NULL);

  LG_LOCK(clipboard->lock);
  if (clipboard->held)
  {
    const LGMP_STATUS status = lgmpClientStreamReadRelease(
      clipboard->hostToClientStream, &clipboard->heldStreamBuffer);
    if (status != LGMP_OK && status != LGMP_ERR_STREAM_STALE &&
        status != LGMP_ERR_STREAM_UNBOUND)
      DEBUG_WARN("Failed to release held clipboard data during "
        "disconnect: %s", lgmpStatusString(status));
  }
  releaseOnDisconnect(clipboard);
  PLGMPClientQueue queue = clipboard->queue;
  clipboard->queue  = NULL;
  clipboard->thread = NULL;
  clipboard->event  = NULL;
  clipboard->held   = false;
  memset(&clipboard->heldStreamBuffer, 0,
    sizeof(clipboard->heldStreamBuffer));
  detachStreamsNL(clipboard);
  clearProtocolNL(clipboard);
  LG_UNLOCK(clipboard->lock);
  dispatchRetiredFiles(clipboard);
  if (queue)
    lgmpClientUnsubscribe(&queue);
  if (event)
    lgFreeEvent(event);
}

static void setStatusListener(void * opaque,
    LG_ClipboardStatusFn callback, void * callbackOpaque)
{
  LGMPClipboard * clipboard = opaque;
  LG_LOCK(clipboard->statusLock);
  LG_LOCK(clipboard->lock);
  clipboard->statusCallback = callback;
  clipboard->statusOpaque   = callbackOpaque;
  LG_ClipboardStatus status =
  {
    .available  = clipboard->connected && clipboard->available,
    .generation = clipboard->providerGeneration,
  };
  LG_UNLOCK(clipboard->lock);
  if (callback)
    callback(callbackOpaque, &status);
  LG_UNLOCK(clipboard->statusLock);
}

static bool attach(void * opaque, const LG_ClipboardEventOps * events,
    void * eventOpaque)
{
  LGMPClipboard * clipboard = opaque;
  KVMFRClipboardFormatFlags formats;
  uint64_t dataset;
  LG_ClipboardData replay[LG_CLIPBOARD_DATA_NONE];
  bool available;
  LG_LOCK(clipboard->eventLock);
  LG_LOCK(clipboard->lock);
  available = clipboard->connected && clipboard->available && events;
  if (available)
  {
    clipboard->events      = events;
    clipboard->eventOpaque = eventOpaque;
    if (!ensureClaimNL(clipboard))
    {
      clipboard->events      = NULL;
      clipboard->eventOpaque = NULL;
      available = false;
    }
  }
  formats = clipboard->remoteFormats;
  dataset = clipboard->remoteClipboardGeneration;
  LG_UNLOCK(clipboard->lock);
  const unsigned replayCount = available ?
    formatsFromMask(formats, replay) : 0;
  if (replayCount)
  {
    if ((formats & KVMFR_CLIPBOARD_FORMAT_MASK_FILES) &&
        events->fileOffer)
      events->fileOffer(eventOpaque, dataset);
    if (events->notice)
      events->notice(eventOpaque, replay, replayCount);
  }
  LG_UNLOCK(clipboard->eventLock);
  if (available)
    signalWorker(clipboard);
  return available;
}

static void detach(void * opaque)
{
  LGMPClipboard * clipboard = opaque;
  bool releaseQueued = false;
  bool releaseRequired = false;
  LG_LOCK(clipboard->eventLock);
  LG_LOCK(clipboard->lock);
  clipboard->events      = NULL;
  clipboard->eventOpaque = NULL;
  if (clipboard->claimed)
  {
    releaseRequired = true;
    KVMFRClipboardMessage release = { 0 };
    release.type = KVMFR_CLIPBOARD_MESSAGE_RELEASE;
    releaseQueued = enqueueUrgentRecordNL(clipboard, release);
    clipboard->claimed   = false;
    clipboard->releasing = true;
    clipboard->available = false;
  }
  clearDataPlaneNL(clipboard);
  if (clipboard->held)
    clipboard->heldReady = true;
  LG_UNLOCK(clipboard->lock);
  LG_UNLOCK(clipboard->eventLock);
  if (!releaseQueued && releaseRequired)
    DEBUG_WARN("Failed to queue LGMP clipboard release; ownership will "
      "expire");
  signalWorker(clipboard);
}

static bool releaseClipboard(void * opaque)
{
  LGMPClipboard * clipboard = opaque;
  LG_LOCK(clipboard->lock);
  KVMFRClipboardMessage clear = { 0 };
  clear.type                = KVMFR_CLIPBOARD_MESSAGE_CLEAR;
  clear.clipboardGeneration = nextClientGeneration(clipboard);
  const unsigned required = clipboard->claimed ? 1U : 2U;
  const bool result =
    clipboard->pendingCount <= CLIPBOARD_PENDING_NORMAL_MAX - required &&
    ensureClaimNL(clipboard) && enqueueRecordNL(clipboard, clear);
  if (result)
  {
    clipboard->localClipboardGeneration = clear.clipboardGeneration;
    clipboard->localFormats             = 0;
  }
  LG_UNLOCK(clipboard->lock);
  if (result)
    signalWorker(clipboard);
  return result;
}

static bool notifyTypes(void * opaque, const LG_ClipboardData types[],
    size_t count)
{
  LGMPClipboard * clipboard = opaque;
  if (!types || !count || count > LG_CLIPBOARD_DATA_NONE)
    return false;
  for (size_t i = 0; i < count; ++i)
    if (!kvmfrClipboardRepresentationFormatValid(toWireFormat(types[i])))
      return false;
  const KVMFRClipboardFormatFlags formats = formatMask(types, count);

  LG_LOCK(clipboard->lock);
  KVMFRClipboardMessage offer = { 0 };
  offer.type                = KVMFR_CLIPBOARD_MESSAGE_OFFER;
  offer.token               = formats;
  offer.clipboardGeneration = nextClientGeneration(clipboard);
  const unsigned required = clipboard->claimed ? 1U : 2U;
  const bool result =
    clipboard->pendingCount <= CLIPBOARD_PENDING_NORMAL_MAX - required &&
    ensureClaimNL(clipboard) &&
    enqueueRecordNL(clipboard, offer);
  if (result)
  {
    clipboard->localClipboardGeneration = offer.clipboardGeneration;
    clipboard->localFormats             = formats;
  }
  LG_UNLOCK(clipboard->lock);
  if (result)
    signalWorker(clipboard);
  return result;
}

static LG_ClipboardResult dataBegin(void * opaque,
    LG_ClipboardRequest request, LG_ClipboardData type, uint64_t sizeHint)
{
  LGMPClipboard * clipboard = opaque;
  const KVMFRClipboardFormat format = toWireFormat(type);
  if (!kvmfrClipboardTransferFromHelper(request) ||
      !kvmfrClipboardRepresentationFormatValid(format))
    return LG_CLIPBOARD_RESULT_FAILED;

  LG_LOCK(clipboard->lock);
  if (!clipboard->connected || !clipboard->available ||
      !clipboard->claimed || clipboard->writeBegan ||
      clipboard->writeTransfer != request ||
      clipboard->writeFormat != format ||
      !clipboard->writeClipboardGeneration)
  {
    LG_UNLOCK(clipboard->lock);
    return LG_CLIPBOARD_RESULT_FAILED;
  }
  clipboard->writeSizeHint  = sizeHint;
  clipboard->writeOffset    = 0;
  clipboard->writeSequence  = 0;
  clipboard->writeBegan     = true;
  clipboard->writeWireBegan = false;
  clipboard->writeBlocked   = false;
  LG_UNLOCK(clipboard->lock);
  return LG_CLIPBOARD_RESULT_ACCEPTED;
}

static LG_ClipboardResult dataChunk(void * opaque,
    LG_ClipboardRequest request, uint64_t offset,
    const void * data, size_t size)
{
  LGMPClipboard * clipboard = opaque;
  if (!kvmfrClipboardTransferFromHelper(request) || !data || !size ||
      size > KVMFR_CLIPBOARD_DATA_BYTES || offset > UINT64_MAX - size)
    return LG_CLIPBOARD_RESULT_FAILED;

  LG_LOCK(clipboard->lock);
  if (!clipboard->connected || !clipboard->available ||
      !clipboard->claimed || !clipboard->writeBegan ||
      clipboard->writeTransfer != request ||
      clipboard->writeOffset != offset ||
      (clipboard->writeSizeHint != KVMFR_CLIPBOARD_SIZE_UNKNOWN &&
       offset + size > clipboard->writeSizeHint))
  {
    LG_UNLOCK(clipboard->lock);
    return LG_CLIPBOARD_RESULT_FAILED;
  }
  KVMFRClipboardMessage record = { 0 };
  record.type                = KVMFR_CLIPBOARD_MESSAGE_DATA;
  record.clipboardGeneration = clipboard->writeClipboardGeneration;
  record.sequence            = clipboard->writeSequence;
  record.transfer            = request;
  record.offset              = offset;
  record.size                = clipboard->writeWireBegan ?
    KVMFR_CLIPBOARD_SIZE_UNKNOWN : clipboard->writeSizeHint;
  record.format              = clipboard->writeFormat;
  record.flags               = clipboard->writeWireBegan ?
    0 : KVMFR_CLIPBOARD_FLAG_BEGIN;
  record.length              = (uint32_t)size;
  const bool result = enqueueDataNL(clipboard, record, data);
  if (result)
  {
    clipboard->writeOffset += size;
    ++clipboard->writeSequence;
    clipboard->writeWireBegan = true;
  }
  else
  {
    clipboard->writeBlocked = true;
    clipboard->writeBlockedRequest = request;
  }
  LG_UNLOCK(clipboard->lock);
  if (result)
    signalWorker(clipboard);
  return result ? LG_CLIPBOARD_RESULT_ACCEPTED :
    LG_CLIPBOARD_RESULT_BLOCKED;
}

static LG_ClipboardResult dataEnd(void * opaque,
    LG_ClipboardRequest request, uint64_t finalSize)
{
  LGMPClipboard * clipboard = opaque;
  if (!kvmfrClipboardTransferFromHelper(request))
    return LG_CLIPBOARD_RESULT_FAILED;
  LG_LOCK(clipboard->lock);
  if (!clipboard->connected || !clipboard->available ||
      !clipboard->claimed || !clipboard->writeBegan ||
      clipboard->writeTransfer != request ||
      clipboard->writeOffset != finalSize ||
      (clipboard->writeSizeHint != KVMFR_CLIPBOARD_SIZE_UNKNOWN &&
       clipboard->writeSizeHint != finalSize))
  {
    LG_UNLOCK(clipboard->lock);
    return LG_CLIPBOARD_RESULT_FAILED;
  }
  KVMFRClipboardMessage record = { 0 };
  record.type                = KVMFR_CLIPBOARD_MESSAGE_DATA;
  record.clipboardGeneration = clipboard->writeClipboardGeneration;
  record.transfer            = request;
  record.format              = clipboard->writeFormat;
  record.flags               = KVMFR_CLIPBOARD_FLAG_END |
    (clipboard->writeWireBegan ? 0 : KVMFR_CLIPBOARD_FLAG_BEGIN);
  record.size                 = finalSize;
  record.offset               = finalSize;
  record.sequence             = clipboard->writeSequence;
  const bool result = enqueueDataNL(clipboard, record, NULL);
  if (result)
    clearWriteNL(clipboard);
  else
  {
    clipboard->writeBlocked = true;
    clipboard->writeBlockedRequest = request;
  }
  LG_UNLOCK(clipboard->lock);
  if (result)
    signalWorker(clipboard);
  return result ? LG_CLIPBOARD_RESULT_ACCEPTED :
    LG_CLIPBOARD_RESULT_BLOCKED;
}

static bool dataCancel(void * opaque, LG_ClipboardRequest request,
    LG_ClipboardCancelReason reason)
{
  LGMPClipboard * clipboard = opaque;
  if (!kvmfrClipboardTransferFromHelper(request) ||
      (unsigned)reason > LG_CLIPBOARD_CANCEL_INVALID)
    return false;
  LG_LOCK(clipboard->lock);
  KVMFRClipboardMessage record = { 0 };
  record.type                = KVMFR_CLIPBOARD_MESSAGE_CANCEL;
  record.clipboardGeneration = clipboard->writeClipboardGeneration;
  record.transfer            = request;
  record.format              = clipboard->writeFormat;
  record.token               = reason;
  const bool result = clipboard->claimed &&
    clipboard->writeTransfer == request &&
    enqueueRecordNL(clipboard, record);
  if (clipboard->writeTransfer == request)
    clearWriteNL(clipboard);
  LG_UNLOCK(clipboard->lock);
  if (result)
    signalWorker(clipboard);
  return result;
}

static bool dataReady(void * opaque, LG_ClipboardRequest request)
{
  LGMPClipboard * clipboard = opaque;
  LG_LOCK(clipboard->lock);
  const bool result = clipboard->held &&
    clipboard->heldRecord.transfer == request;
  if (result)
    clipboard->heldReady = true;
  LG_UNLOCK(clipboard->lock);
  if (result)
    signalWorker(clipboard);
  return result;
}

static bool requestData(void * opaque, LG_ClipboardRequest request,
    LG_ClipboardData type)
{
  LGMPClipboard * clipboard = opaque;
  const KVMFRClipboardFormat format = toWireFormat(type);
  if (request == LG_CLIPBOARD_REQUEST_INVALID ||
      !kvmfrClipboardRepresentationFormatValid(format))
    return false;
  LG_LOCK(clipboard->lock);
  if (clipboard->readRequest || !clipboard->remoteClipboardGeneration ||
      !(clipboard->remoteFormats & kvmfrClipboardFormatFlag(format)))
  {
    LG_UNLOCK(clipboard->lock);
    return false;
  }
  KVMFRClipboardMessage record = { 0 };
  record.type       = KVMFR_CLIPBOARD_MESSAGE_REQUEST;
  record.transfer   = nextClientTransfer(clipboard);
  record.format     = format;
  record.clipboardGeneration = clipboard->remoteClipboardGeneration;
  const bool result = ensureClaimNL(clipboard) &&
    enqueueRecordNL(clipboard, record);
  if (result)
  {
    clipboard->readRequest             = request;
    clipboard->readTransfer            = record.transfer;
    clipboard->readClipboardGeneration =
      record.clipboardGeneration;
    clipboard->readSizeHint = 0;
    clipboard->readOffset   = 0;
    clipboard->readFormat   = format;
    clipboard->readSequence = 0;
    clipboard->readBegan    = false;
  }
  LG_UNLOCK(clipboard->lock);
  if (result)
    signalWorker(clipboard);
  return result;
}

static bool offerFiles(void * opaque, uint64_t dataset)
{
  LGMPClipboard * clipboard = opaque;
  if (!dataset || kvmfrClipboardTransferFromHelper(dataset))
    return false;
  KVMFRClipboardMessage offer = { 0 };
  offer.type                = KVMFR_CLIPBOARD_MESSAGE_OFFER;
  offer.clipboardGeneration = dataset;
  offer.token               = KVMFR_CLIPBOARD_FORMAT_MASK_FILES;
  LG_LOCK(clipboard->lock);
  const unsigned required = clipboard->claimed ? 1U : 2U;
  const bool result = clipboard->pendingCount <=
      CLIPBOARD_PENDING_NORMAL_MAX - required && ensureClaimNL(clipboard) &&
    enqueueRecordNL(clipboard, offer);
  if (result)
  {
    clipboard->localClipboardGeneration = dataset;
    clipboard->localFormats = KVMFR_CLIPBOARD_FORMAT_MASK_FILES;
  }
  LG_UNLOCK(clipboard->lock);
  if (result)
    signalWorker(clipboard);
  return result;
}

static bool fileAcquire(void * opaque, uint64_t dataset,
    uint64_t acquisitionId)
{
  LGMPClipboard * clipboard = opaque;
  if (!dataset || !kvmfrClipboardTransferFromClient(acquisitionId))
    return false;
  LG_LOCK(clipboard->lock);
  if (fileAcquisitionCountNL(clipboard) >=
      KVMFR_CLIPBOARD_FILE_MAX_ACQUISITIONS)
  {
    LG_UNLOCK(clipboard->lock);
    return false;
  }
  struct FileAcquisition * acquisition = calloc(1, sizeof(*acquisition));
  if (!acquisition)
  {
    LG_UNLOCK(clipboard->lock);
    return false;
  }
  KVMFRClipboardMessage record = { 0 };
  record.type                = KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRE;
  record.clipboardGeneration = dataset;
  record.transfer            = acquisitionId;
  record.format              = KVMFR_CLIPBOARD_FORMAT_FILES;
  const bool result = dataset == clipboard->remoteClipboardGeneration &&
    (clipboard->remoteFormats & KVMFR_CLIPBOARD_FORMAT_MASK_FILES) &&
    !fileAcquisitionFindNL(clipboard, dataset, acquisitionId, false) &&
    ensureClaimNL(clipboard) && enqueueRecordNL(clipboard, record);
  if (result)
  {
    *acquisition = (struct FileAcquisition)
    {
      .dataset     = dataset,
      .acquisition = acquisitionId,
      .next        = clipboard->fileAcquisitions,
    };
    clipboard->fileAcquisitions = acquisition;
  }
  LG_UNLOCK(clipboard->lock);
  if (!result)
    free(acquisition);
  else
    signalWorker(clipboard);
  return result;
}

static bool fileAcquired(void * opaque, uint64_t dataset,
    uint64_t acquisitionId, LG_ClipboardFileError error)
{
  LGMPClipboard * clipboard = opaque;
  if (!dataset || !kvmfrClipboardTransferFromHelper(acquisitionId) ||
      !validFileError(error))
    return false;
  LG_LOCK(clipboard->lock);
  struct FileAcquisition * acquisition = fileAcquisitionFindNL(
      clipboard, dataset, acquisitionId, true);
  KVMFRClipboardMessage record = { 0 };
  record.type                = KVMFR_CLIPBOARD_MESSAGE_FILE_ACQUIRED;
  record.clipboardGeneration = dataset;
  record.transfer            = acquisitionId;
  record.format              = KVMFR_CLIPBOARD_FORMAT_FILES;
  record.token               = toWireFileError(error);
  const bool result = acquisition && !acquisition->active &&
    enqueueUrgentRecordNL(clipboard, record);
  if (result && error == LG_CLIPBOARD_FILE_ERROR_NONE)
    acquisition->active = true;
  else if (result)
    free(fileAcquisitionTakeNL(
        clipboard, dataset, acquisitionId, true));
  LG_UNLOCK(clipboard->lock);
  if (result)
    signalWorker(clipboard);
  return result;
}

static bool fileRelease(void * opaque, uint64_t dataset,
    uint64_t acquisitionId)
{
  LGMPClipboard * clipboard = opaque;
  if (!dataset || !kvmfrClipboardTransferFromClient(acquisitionId))
    return false;
  LG_LOCK(clipboard->lock);
  struct FileAcquisition * acquisition = fileAcquisitionFindNL(
      clipboard, dataset, acquisitionId, false);
  KVMFRClipboardMessage record = { 0 };
  record.type                = KVMFR_CLIPBOARD_MESSAGE_FILE_RELEASE;
  record.clipboardGeneration = dataset;
  record.transfer            = acquisitionId;
  record.format              = KVMFR_CLIPBOARD_FORMAT_FILES;
  const bool result = acquisition && acquisition->active &&
    enqueueUrgentRecordNL(clipboard, record);
  if (result)
    free(fileAcquisitionTakeNL(
        clipboard, dataset, acquisitionId, false));
  LG_UNLOCK(clipboard->lock);
  if (result)
    signalWorker(clipboard);
  return result;
}

static bool fileRequest(void * opaque,
    const LG_ClipboardFileRequest * descriptor)
{
  LGMPClipboard * 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 > KVMFR_CLIPBOARD_FILE_READ_BYTES ||
        descriptor->offset > UINT64_MAX - descriptor->length)))
    return false;
  KVMFRClipboardMessage record = { 0 };
  record.type                = KVMFR_CLIPBOARD_MESSAGE_FILE_REQUEST;
  record.clipboardGeneration = descriptor->dataset;
  record.transfer            = descriptor->request;
  record.offset              = descriptor->offset;
  record.size                = descriptor->node;
  record.format              = KVMFR_CLIPBOARD_FORMAT_FILES;
  record.flags               = descriptor->operation ==
    LG_CLIPBOARD_FILE_READ ? descriptor->length : 0;
  record.token               = toWireFileOperation(descriptor->operation);
  LG_LOCK(clipboard->lock);
  if (fileTransferCountNL(clipboard) >=
      KVMFR_CLIPBOARD_FILE_MAX_REQUESTS)
  {
    LG_UNLOCK(clipboard->lock);
    return false;
  }
  struct FileTransfer * transfer = calloc(1, sizeof(*transfer));
  if (!transfer)
  {
    LG_UNLOCK(clipboard->lock);
    return false;
  }
  const bool result = fileDatasetAcquiredNL(
      clipboard, descriptor->dataset, false) &&
    !fileTransferFindNL(clipboard->fileReads, descriptor->request) &&
    enqueueRecordNL(clipboard, record);
  if (result)
  {
    transfer->request  = *descriptor;
    transfer->sizeHint = KVMFR_CLIPBOARD_SIZE_UNKNOWN;
    transfer->next     = clipboard->fileReads;
    clipboard->fileReads = transfer;
  }
  LG_UNLOCK(clipboard->lock);
  if (!result)
    free(transfer);
  else
    signalWorker(clipboard);
  return result;
}

static bool sameFileRequest(const LG_ClipboardFileRequest * a,
    const LG_ClipboardFileRequest * b)
{
  return a->dataset == b->dataset && a->request == b->request &&
    a->node == b->node && a->offset == b->offset &&
    a->length == b->length && a->operation == b->operation;
}

static LG_ClipboardResult fileDataBegin(void * opaque,
    const LG_ClipboardFileRequest * descriptor, uint64_t sizeHint)
{
  LGMPClipboard * clipboard = opaque;
  if (!descriptor || !kvmfrClipboardTransferFromHelper(descriptor->request))
    return LG_CLIPBOARD_RESULT_FAILED;
  LG_LOCK(clipboard->lock);
  struct FileTransfer * transfer = fileTransferFindNL(
      clipboard->fileWrites, descriptor->request);
  const bool valid = transfer && !transfer->began &&
    sameFileRequest(&transfer->request, descriptor) &&
    (descriptor->operation != LG_CLIPBOARD_FILE_READ ||
     sizeHint <= descriptor->length);
  if (valid)
  {
    transfer->began          = true;
    transfer->sizeHint       = sizeHint;
    transfer->responseOffset = 0;
    transfer->sequence       = 0;
    transfer->blocked        = false;
    transfer->wireEnded      = false;
  }
  LG_UNLOCK(clipboard->lock);
  return valid ? LG_CLIPBOARD_RESULT_ACCEPTED :
    LG_CLIPBOARD_RESULT_FAILED;
}

static LG_ClipboardResult fileDataChunk(void * opaque,
    const LG_ClipboardFileRequest * descriptor, uint64_t responseOffset,
    const void * data, size_t size, bool end)
{
  LGMPClipboard * clipboard = opaque;
  if (!descriptor || !data || !size ||
      size > KVMFR_CLIPBOARD_DATA_BYTES ||
      responseOffset > UINT64_MAX - size)
    return LG_CLIPBOARD_RESULT_FAILED;
  LG_LOCK(clipboard->lock);
  struct FileTransfer * transfer = fileTransferFindNL(
      clipboard->fileWrites, descriptor->request);
  if (!transfer || !transfer->began || transfer->wireEnded ||
      !sameFileRequest(&transfer->request, descriptor) ||
      transfer->responseOffset != responseOffset ||
      (transfer->sizeHint != KVMFR_CLIPBOARD_SIZE_UNKNOWN &&
       responseOffset + size > transfer->sizeHint) ||
      (end && transfer->sizeHint != KVMFR_CLIPBOARD_SIZE_UNKNOWN &&
       transfer->sizeHint != responseOffset + size) ||
      (descriptor->operation == LG_CLIPBOARD_FILE_READ &&
       responseOffset + size > descriptor->length))
  {
    LG_UNLOCK(clipboard->lock);
    return LG_CLIPBOARD_RESULT_FAILED;
  }
  KVMFRClipboardMessage record = { 0 };
  record.type                = KVMFR_CLIPBOARD_MESSAGE_FILE_DATA;
  record.clipboardGeneration = descriptor->dataset;
  record.transfer            = descriptor->request;
  record.offset              = responseOffset;
  record.sequence            = transfer->sequence;
  record.size                = end ? responseOffset + size :
    transfer->sequence ? KVMFR_CLIPBOARD_SIZE_UNKNOWN : transfer->sizeHint;
  record.format              = KVMFR_CLIPBOARD_FORMAT_FILES;
  record.flags               = (transfer->sequence ? 0 :
    KVMFR_CLIPBOARD_FLAG_BEGIN) |
    (end ? KVMFR_CLIPBOARD_FLAG_END : 0);
  record.token               = toWireFileOperation(descriptor->operation);
  record.length              = (uint32_t)size;
  const bool result = enqueueDataNL(clipboard, record, data);
  if (result)
  {
    transfer->responseOffset += size;
    ++transfer->sequence;
    transfer->wireEnded      = end;
  }
  else
    transfer->blocked = true;
  LG_UNLOCK(clipboard->lock);
  if (result)
    signalWorker(clipboard);
  return result ? LG_CLIPBOARD_RESULT_ACCEPTED :
    LG_CLIPBOARD_RESULT_BLOCKED;
}

static LG_ClipboardResult fileDataEnd(void * opaque,
    const LG_ClipboardFileRequest * descriptor, uint64_t finalSize)
{
  LGMPClipboard * clipboard = opaque;
  if (!descriptor)
    return LG_CLIPBOARD_RESULT_FAILED;
  LG_LOCK(clipboard->lock);
  struct FileTransfer * transfer = fileTransferFindNL(
      clipboard->fileWrites, descriptor->request);
  if (!transfer || !transfer->began ||
      !sameFileRequest(&transfer->request, descriptor) ||
      transfer->responseOffset != finalSize ||
      (transfer->sizeHint != KVMFR_CLIPBOARD_SIZE_UNKNOWN &&
       transfer->sizeHint != finalSize))
  {
    LG_UNLOCK(clipboard->lock);
    return LG_CLIPBOARD_RESULT_FAILED;
  }
  if (transfer->wireEnded)
  {
    free(fileTransferTakeNL(
        &clipboard->fileWrites, descriptor->request));
    LG_UNLOCK(clipboard->lock);
    return LG_CLIPBOARD_RESULT_ACCEPTED;
  }
  KVMFRClipboardMessage record = { 0 };
  record.type                = KVMFR_CLIPBOARD_MESSAGE_FILE_DATA;
  record.clipboardGeneration = descriptor->dataset;
  record.transfer            = descriptor->request;
  record.offset              = finalSize;
  record.sequence            = transfer->sequence;
  record.size                = finalSize;
  record.format              = KVMFR_CLIPBOARD_FORMAT_FILES;
  record.flags               = KVMFR_CLIPBOARD_FLAG_END |
    (transfer->sequence ? 0 : KVMFR_CLIPBOARD_FLAG_BEGIN);
  record.token               = toWireFileOperation(descriptor->operation);
  const bool result = enqueueDataNL(clipboard, record, NULL);
  if (result)
    free(fileTransferTakeNL(
        &clipboard->fileWrites, descriptor->request));
  else
    transfer->blocked = true;
  LG_UNLOCK(clipboard->lock);
  if (result)
    signalWorker(clipboard);
  return result ? LG_CLIPBOARD_RESULT_ACCEPTED :
    LG_CLIPBOARD_RESULT_BLOCKED;
}

static bool fileCancel(void * opaque, uint64_t dataset,
    uint64_t request, LG_ClipboardFileError reason)
{
  LGMPClipboard * clipboard = opaque;
  if (!dataset || !request || reason == LG_CLIPBOARD_FILE_ERROR_NONE ||
      !validFileError(reason))
    return false;
  LG_LOCK(clipboard->lock);
  struct FileTransfer * read = fileTransferFindNL(
      clipboard->fileReads, request);
  struct FileTransfer * write = fileTransferFindNL(
      clipboard->fileWrites, request);
  struct FileAcquisition * acquisition = fileAcquisitionFindNL(
      clipboard, dataset, request,
      kvmfrClipboardTransferFromHelper(request));
  KVMFRClipboardMessage record = { 0 };
  record.type                = KVMFR_CLIPBOARD_MESSAGE_FILE_CANCEL;
  record.clipboardGeneration = dataset;
  record.transfer            = request;
  record.format              = KVMFR_CLIPBOARD_FORMAT_FILES;
  record.token               = toWireFileError(reason);
  const bool matches = (read && read->request.dataset == dataset) ||
    (write && write->request.dataset == dataset) || acquisition;
  const bool result = matches && enqueueUrgentRecordNL(clipboard, record);
  if (result)
  {
    free(fileTransferTakeNL(&clipboard->fileReads, request));
    free(fileTransferTakeNL(&clipboard->fileWrites, request));
    if (acquisition)
      free(fileAcquisitionTakeNL(clipboard, dataset, request,
          kvmfrClipboardTransferFromHelper(request)));
  }
  LG_UNLOCK(clipboard->lock);
  if (result)
    signalWorker(clipboard);
  return result;
}

static const LG_ClipboardOps CLIPBOARD_OPS =
{
  .name              = "LGMP",
  .setStatusListener = setStatusListener,
  .attach            = attach,
  .detach            = detach,
  .release           = releaseClipboard,
  .notifyTypes       = notifyTypes,
  .offerFiles        = offerFiles,
  .dataBegin         = dataBegin,
  .dataChunk         = dataChunk,
  .dataEnd           = dataEnd,
  .dataCancel        = dataCancel,
  .dataReady         = dataReady,
  .request           = requestData,
  .fileAcquire       = fileAcquire,
  .fileAcquired      = fileAcquired,
  .fileRelease       = fileRelease,
  .fileRequest       = fileRequest,
  .fileDataBegin     = fileDataBegin,
  .fileDataChunk     = fileDataChunk,
  .fileDataEnd       = fileDataEnd,
  .fileCancel        = fileCancel,
};

const LG_ClipboardOps * lgmpClipboard_getOps(void)
{
  return &CLIPBOARD_OPS;
}
