[idd] clipboard: arbitrate ownership by active client

This commit is contained in:
Geoffrey McRae
2026-10-01 18:36:51 +10:00
parent 450d20c8d8
commit cbb7835158
6 changed files with 918 additions and 355 deletions

View File

@@ -60,6 +60,8 @@ namespace
CClipboardHub::CClipboardHub(CClipboardChannel& channel) :
m_channel(channel)
{
for (Source& source : m_sources)
source.owner = this;
m_channel.SetHandler(this);
}
@@ -68,27 +70,107 @@ CClipboardHub::~CClipboardHub()
Stop();
}
void CClipboardHub::AdvanceGenerationNL()
ClipboardClaimResult CClipboardHub::Source::Claim(
const ClipboardSourceId& source, KVMFRClipboardMessage& cached)
{
Seq::Inc(m_generation);
return owner->Claim(*this, source, cached);
}
bool CClipboardHub::MarkFailed(IClipboardSource& source)
void CClipboardHub::Source::Release(const ClipboardSourceId& source)
{
owner->Release(*this, source);
}
ClipboardChannelResult CClipboardHub::Source::SendClipboard(
const KVMFRClipboardMessage& record, const uint8_t * data)
{
return owner->SendClipboard(*this, record, data);
}
ClipboardChannelResult CClipboardHub::Source::SendClipboardBatch(
const ClipboardChannelWrite * records, size_t count, size_t& accepted)
{
return owner->SendClipboardBatch(*this, records, count, accepted);
}
void CClipboardHub::Source::ClipboardReceiveReady()
{
owner->ClipboardReceiveReady(*this);
}
void CClipboardHub::Source::ClipboardFailed()
{
owner->ClipboardFailed(*this);
}
uint32_t CClipboardHub::NextGenerationNL()
{
Seq::Inc(m_generation);
return m_generation;
}
bool CClipboardHub::BindingPresentNL(const Source& source) const
{
return source.active && source.endpoint && source.backend && source.epoch;
}
bool CClipboardHub::BindingValidNL(const Source& source) const
{
return BindingPresentNL(source) && source.running && !source.reserved &&
!source.failed && source.generation;
}
bool CClipboardHub::OwnerIsNL(
const Source& source, const ClipboardSourceId& id) const
{
return m_owner == &source && m_ownerId.client == id.client &&
m_ownerId.generation == id.generation;
}
bool CClipboardHub::MarkFailed(Source& source)
{
CSRWExclusiveLock lock(m_lock);
if (m_source != &source || (!m_active && !m_reserved))
if (!BindingPresentNL(source) && !source.reserved)
return false;
const bool changed = !m_failed;
m_failed = true;
m_failurePending = true;
const bool changed = !source.failed;
source.failed = true;
source.failurePending = true;
return changed;
}
void CClipboardHub::CachePublicationNL(
const KVMFRClipboardMessage& record, Source * source)
{
if (record.type != KVMFR_CLIPBOARD_MESSAGE_OFFER &&
record.type != KVMFR_CLIPBOARD_MESSAGE_CLEAR)
return;
m_cachedClipboard = record;
m_cachedSource = source;
m_cachedValid = true;
}
void CClipboardHub::InvalidateSourceCacheNL(const Source& source)
{
if (m_cachedSource != &source)
return;
if (m_cachedValid &&
m_cachedClipboard.type == KVMFR_CLIPBOARD_MESSAGE_CLEAR)
{
m_cachedSource = nullptr;
return;
}
m_cachedClipboard = {};
m_cachedSource = nullptr;
m_cachedValid = false;
}
ClipboardChannelResult CClipboardHub::HoldOrDiscard(
const KVMFRClipboardMessage& record)
{
// Preserve the pending clipboard publication until a source is rebound.
// Preserve the pending clipboard publication until a client claims it.
// Transfer-scoped records belong to the source which admitted them and
// must never cross a transport generation.
if (record.type == KVMFR_CLIPBOARD_MESSAGE_OFFER ||
@@ -139,47 +221,58 @@ bool CClipboardHub::Bind(
if (!backend || !epoch)
return false;
bool available;
uint32_t generation;
uint64_t channelEpoch;
Source * selected = nullptr;
bool available = false;
uint32_t generation = 0;
{
CSRWExclusiveLock lock(m_lock);
if (m_stopped || m_source || m_active || m_reserved)
if (m_stopped)
return false;
AdvanceGenerationNL();
m_source = &source;
m_backend = backend;
m_epoch = epoch;
m_active = true;
m_reserved = true;
m_failed = false;
m_failurePending = false;
available = m_available;
generation = m_generation;
channelEpoch = m_channelEpoch;
for (Source& slot : m_sources)
if ((slot.active || slot.failed || slot.reserved) &&
slot.backend == backend && slot.epoch == epoch)
return false;
for (Source& slot : m_sources)
if (!slot.active && !slot.failed && !slot.reserved)
{
slot.backend = backend;
slot.epoch = epoch;
slot.generation = NextGenerationNL();
slot.endpoint = &source;
slot.running = false;
slot.active = true;
slot.reserved = true;
slot.failurePending = false;
selected = &slot;
available = m_available && !m_resetting;
generation = slot.generation;
break;
}
}
if (!selected)
return false;
CSRWExclusiveLock callbackLock(m_callbackLock);
const bool started = source.Start(*this);
bool attached = false;
const bool started = source.Start(*selected);
bool attached = false;
{
CSRWExclusiveLock lock(m_lock);
if (started && m_source == &source && m_backend == backend &&
m_epoch == epoch && m_active && m_reserved && !m_failed &&
if (started && selected->endpoint == &source &&
selected->backend == backend && selected->epoch == epoch &&
selected->active && selected->reserved && !selected->failed &&
!m_stopped)
{
m_running = true;
m_reserved = false;
available = m_available;
generation = m_generation;
channelEpoch = m_channelEpoch;
attached = true;
selected->running = true;
selected->reserved = false;
available = m_available && !m_resetting;
generation = selected->generation;
attached = true;
}
else
{
m_active = false;
m_running = false;
selected->active = false;
selected->running = false;
}
}
@@ -187,99 +280,133 @@ bool CClipboardHub::Bind(
{
source.Stop();
CSRWExclusiveLock lock(m_lock);
if (m_source == &source && m_backend == backend && m_epoch == epoch)
if (selected->endpoint == &source && selected->backend == backend &&
selected->epoch == epoch)
{
m_source = nullptr;
m_backend = 0;
m_epoch = 0;
m_active = false;
m_running = false;
m_reserved = false;
m_failed = false;
m_failurePending = false;
selected->backend = 0;
selected->epoch = 0;
selected->generation = 0;
selected->endpoint = nullptr;
selected->running = false;
selected->active = false;
selected->reserved = false;
selected->failed = false;
selected->failurePending = false;
}
return false;
}
source.ClipboardState(available, generation);
callbackLock.Unlock();
if (available && channelEpoch)
m_channel.Kick(channelEpoch);
return true;
}
void CClipboardHub::Unbind(BackendId backend, uint32_t epoch)
{
CSRWExclusiveLock lifecycleLock(m_lifecycleLock);
IClipboardSource * source = nullptr;
bool stop = false;
Source * selected = nullptr;
IClipboardSource * source = nullptr;
bool stop = false;
bool resetChannel = false;
uint64_t channelEpoch = 0;
{
CSRWExclusiveLock lock(m_lock);
if ((!m_active && !m_failed && !m_reserved) ||
m_backend != backend || m_epoch != epoch || !m_source)
return;
source = m_source;
stop = m_running || m_reserved;
m_active = false;
m_running = false;
m_reserved = true;
m_failed = false;
m_failurePending = false;
AdvanceGenerationNL();
for (Source& slot : m_sources)
if ((slot.active || slot.failed || slot.reserved) &&
slot.backend == backend && slot.epoch == epoch && slot.endpoint)
{
selected = &slot;
source = slot.endpoint;
stop = slot.running || slot.reserved;
resetChannel = m_owner == &slot;
channelEpoch = m_channelEpoch;
slot.active = false;
slot.running = false;
slot.reserved = true;
slot.failed = false;
slot.failurePending = false;
InvalidateSourceCacheNL(slot);
if (resetChannel)
{
m_owner = nullptr;
m_ownerId = {};
m_resetting = true;
}
break;
}
}
if (!selected)
return;
CSRWExclusiveLock callbackLock(m_callbackLock);
if (stop)
source->Stop();
CSRWExclusiveLock lock(m_lock);
if (m_source == source && m_backend == backend && m_epoch == epoch)
if (selected->endpoint == source && selected->backend == backend &&
selected->epoch == epoch)
{
m_source = nullptr;
m_backend = 0;
m_epoch = 0;
m_active = false;
m_running = false;
m_reserved = false;
m_failed = false;
m_failurePending = false;
selected->backend = 0;
selected->epoch = 0;
selected->generation = 0;
selected->endpoint = nullptr;
selected->running = false;
selected->active = false;
selected->reserved = false;
selected->failed = false;
selected->failurePending = false;
}
lock.Unlock();
callbackLock.Unlock();
lifecycleLock.Unlock();
if (resetChannel && channelEpoch)
m_channel.Reset(channelEpoch, ERROR_DEVICE_NOT_CONNECTED);
}
bool CClipboardHub::TakeFailure(SourceKey& source)
{
CSRWSharedLock lifecycleLock(m_lifecycleLock);
CSRWExclusiveLock lock(m_lock);
if (!m_failurePending)
return false;
source = {};
source.backend = m_backend;
source.epoch = m_epoch;
m_failurePending = false;
return true;
for (Source& slot : m_sources)
{
if (!slot.failurePending)
continue;
source = {};
source.backend = slot.backend;
source.epoch = slot.epoch;
slot.failurePending = false;
return true;
}
return false;
}
void CClipboardHub::Stop()
{
CSRWExclusiveLock lifecycleLock(m_lifecycleLock);
IClipboardSource * source = nullptr;
bool stop = false;
Source * sources[MAX_SOURCES] = {};
unsigned count = 0;
{
CSRWExclusiveLock lock(m_lock);
if (m_stopped)
return;
m_stopped = true;
source = m_source;
stop = source && (m_running || m_reserved);
m_active = false;
m_running = false;
m_reserved = source != nullptr;
m_failed = false;
m_failurePending = false;
AdvanceGenerationNL();
m_stopped = true;
m_owner = nullptr;
m_cachedSource = nullptr;
m_ownerId = {};
m_cachedClipboard = {};
m_cachedValid = false;
m_resetting = false;
for (Source& source : m_sources)
if (source.endpoint)
{
source.active = false;
source.running = false;
source.reserved = true;
source.failed = false;
source.failurePending = false;
sources[count++] = &source;
}
}
// Clearing the channel handler is a callback-quiescence barrier. It must
@@ -288,41 +415,114 @@ void CClipboardHub::Stop()
m_channel.ClearHandler(this);
CSRWExclusiveLock callbackLock(m_callbackLock);
if (stop)
source->Stop();
for (unsigned i = count; i > 0; --i)
sources[i - 1]->endpoint->Stop();
CSRWExclusiveLock lock(m_lock);
m_source = nullptr;
m_backend = 0;
m_epoch = 0;
m_active = false;
m_running = false;
m_reserved = false;
m_failed = false;
m_failurePending = false;
for (Source& source : m_sources)
{
source.backend = 0;
source.epoch = 0;
source.generation = 0;
source.endpoint = nullptr;
source.running = false;
source.active = false;
source.reserved = false;
source.failed = false;
source.failurePending = false;
}
}
ClipboardClaimResult CClipboardHub::Claim(Source& source,
const ClipboardSourceId& id, KVMFRClipboardMessage& cached)
{
cached = {};
if (!id.client || !id.generation)
return ClipboardClaimResult::STALE;
uint64_t channelEpoch = 0;
{
CSRWExclusiveLock lock(m_lock);
if (!BindingValidNL(source))
return ClipboardClaimResult::STALE;
if (!m_available || m_resetting || !m_channelEpoch ||
!m_channel.Available())
return ClipboardClaimResult::UNAVAILABLE;
if (m_owner)
return OwnerIsNL(source, id) ?
ClipboardClaimResult::ACCEPTED : ClipboardClaimResult::BUSY;
m_owner = &source;
m_ownerId = id;
// A client-originated OFFER cannot be served by a replacement client.
// Its matching cleanup CLEAR is safe to replay, as is any
// Helper-originated publication.
if (m_cachedValid && (!m_cachedSource ||
m_cachedClipboard.type == KVMFR_CLIPBOARD_MESSAGE_CLEAR))
{
cached = m_cachedClipboard;
cached.generation = source.generation;
}
channelEpoch = m_channelEpoch;
}
m_channel.Kick(channelEpoch);
return ClipboardClaimResult::ACCEPTED;
}
void CClipboardHub::Release(
Source& source, const ClipboardSourceId& id)
{
uint64_t channelEpoch = 0;
{
CSRWExclusiveLock lock(m_lock);
if (!OwnerIsNL(source, id))
return;
m_owner = nullptr;
m_ownerId = {};
channelEpoch = m_channelEpoch;
}
if (channelEpoch)
m_channel.Kick(channelEpoch);
}
void CClipboardHub::ClipboardState(bool available, uint64_t epoch)
{
CSRWExclusiveLock callbackLock(m_callbackLock);
IClipboardSource * source = nullptr;
uint32_t generation;
Source * sources[MAX_SOURCES] = {};
uint32_t generations[MAX_SOURCES] = {};
unsigned count = 0;
{
CSRWExclusiveLock lock(m_lock);
if (m_stopped)
return;
if (m_available != available || m_channelEpoch != epoch)
AdvanceGenerationNL();
m_available = available;
m_channelEpoch = epoch;
generation = m_generation;
if (m_active && m_running && !m_failed)
source = m_source;
const bool changed = m_available != available ||
m_channelEpoch != epoch || m_resetting;
m_available = available;
m_channelEpoch = epoch;
m_resetting = false;
if (!changed)
return;
m_owner = nullptr;
m_cachedSource = nullptr;
m_ownerId = {};
m_cachedClipboard = {};
m_cachedValid = false;
for (Source& source : m_sources)
if (BindingValidNL(source))
{
source.generation = NextGenerationNL();
sources[count] = &source;
generations[count] = source.generation;
++count;
}
}
if (source)
source->ClipboardState(available, generation);
for (unsigned i = 0; i < count; ++i)
sources[i]->endpoint->ClipboardState(available, generations[i]);
}
ClipboardChannelResult CClipboardHub::ClipboardRecord(
@@ -332,24 +532,39 @@ ClipboardChannelResult CClipboardHub::ClipboardRecord(
return ClipboardChannelResult::FAILED;
CSRWExclusiveLock callbackLock(m_callbackLock);
IClipboardSource * source = nullptr;
uint32_t generation;
Source * source = nullptr;
IClipboardSource * endpoint = nullptr;
ClipboardSourceId ownerId = {};
uint32_t generation = 0;
{
CSRWSharedLock lock(m_lock);
if (m_stopped || !m_available)
return ClipboardChannelResult::ACCEPTED;
if (m_failed || !m_active || !m_running || !m_source)
source = m_owner;
if (!source || !BindingValidNL(*source))
return HoldOrDiscard(record);
source = m_source;
generation = m_generation;
endpoint = source->endpoint;
ownerId = m_ownerId;
generation = source->generation;
}
// Helper records are transport-agnostic. Stamp them only after selecting
// the active transport binding.
KVMFRClipboardMessage stamped = record;
stamped.generation = generation;
const ClipboardChannelResult result =
source->SendClipboard(stamped,
endpoint->SendClipboard(stamped,
data.empty() ? nullptr : data.data());
if (result != ClipboardChannelResult::FAILED)
if (result == ClipboardChannelResult::ACCEPTED)
{
CSRWExclusiveLock lock(m_lock);
CachePublicationNL(stamped, nullptr);
const bool publication = stamped.type == KVMFR_CLIPBOARD_MESSAGE_OFFER ||
stamped.type == KVMFR_CLIPBOARD_MESSAGE_CLEAR;
return publication && m_owner && !OwnerIsNL(*source, ownerId) ?
ClipboardChannelResult::BUSY : result;
}
if (result == ClipboardChannelResult::BUSY)
return result;
if (MarkFailed(*source))
@@ -364,59 +579,78 @@ ClipboardChannelResult CClipboardHub::ClipboardRecord(
void CClipboardHub::ClipboardReset(uint64_t epoch, uint32_t reason)
{
CSRWExclusiveLock callbackLock(m_callbackLock);
IClipboardSource * source = nullptr;
uint32_t generation;
Source * sources[MAX_SOURCES] = {};
uint32_t generations[MAX_SOURCES] = {};
unsigned count = 0;
{
CSRWExclusiveLock lock(m_lock);
if (m_stopped || m_channelEpoch != epoch)
return;
AdvanceGenerationNL();
generation = m_generation;
if (m_active && m_running && !m_failed)
source = m_source;
m_owner = nullptr;
m_cachedSource = nullptr;
m_ownerId = {};
m_cachedClipboard = {};
m_cachedValid = false;
m_resetting = true;
for (Source& source : m_sources)
if (BindingValidNL(source))
{
source.generation = NextGenerationNL();
sources[count] = &source;
generations[count] = source.generation;
++count;
}
}
if (source)
source->ClipboardReset(generation, reason);
for (unsigned i = 0; i < count; ++i)
sources[i]->endpoint->ClipboardReset(generations[i], reason);
}
ClipboardChannelResult CClipboardHub::SendClipboard(
ClipboardChannelResult CClipboardHub::SendClipboard(Source& source,
const KVMFRClipboardMessage& record, const uint8_t * data)
{
const ClipboardChannelWrite write = { record, data };
size_t accepted = 0;
return SendClipboardBatch(&write, 1, accepted);
const ClipboardChannelWrite write = { record, data };
size_t accepted = 0;
return SendClipboardBatch(source, &write, 1, accepted);
}
ClipboardChannelResult CClipboardHub::SendClipboardBatch(
ClipboardChannelResult CClipboardHub::SendClipboardBatch(Source& source,
const ClipboardChannelWrite * records, size_t count, size_t& accepted)
{
accepted = 0;
if (!records || !count || count > KVMFR_CLIPBOARD_SLOT_COUNT)
return ClipboardChannelResult::FAILED;
// Keep the shared lock through Send so Unbind cannot advance the binding
// Keep the lock through Send so Unbind cannot advance the binding
// generation after validation but before the records enter the channel.
CSRWSharedLock lock(m_lock);
if (m_stopped || !m_available || !m_active || !m_running || m_failed ||
!m_source)
CSRWExclusiveLock lock(m_lock);
if (m_stopped || !BindingValidNL(source) || m_owner != &source)
return ClipboardChannelResult::FAILED;
if (!m_available || m_resetting || !m_channel.Available())
return ClipboardChannelResult::BUSY;
for (size_t i = 0; i < count; ++i)
if (!ValidClientDirection(records[i].record) ||
records[i].record.generation != m_generation)
records[i].record.generation != source.generation)
return ClipboardChannelResult::FAILED;
return m_channel.SendBatch(records, count, accepted);
const ClipboardChannelResult result =
m_channel.SendBatch(records, count, accepted);
for (size_t i = 0; i < accepted; ++i)
CachePublicationNL(records[i].record, &source);
if (result == ClipboardChannelResult::FAILED &&
(m_resetting || !m_channel.Available()))
return ClipboardChannelResult::BUSY;
return result;
}
void CClipboardHub::ClipboardReceiveReady()
void CClipboardHub::ClipboardReceiveReady(Source& source)
{
{
CSRWSharedLock lock(m_lock);
if (m_stopped || !m_available || !m_active || m_failed ||
!m_running)
if (m_stopped || !m_available || !BindingValidNL(source) ||
m_owner != &source)
return;
}
@@ -425,14 +659,9 @@ void CClipboardHub::ClipboardReceiveReady()
m_channel.Kick(epoch);
}
void CClipboardHub::ClipboardFailed()
void CClipboardHub::ClipboardFailed(Source& source)
{
IClipboardSource * source;
{
CSRWSharedLock lock(m_lock);
source = m_source;
}
if (!source || !MarkFailed(*source))
if (!MarkFailed(source))
return;
const uint64_t epoch = m_channel.Epoch();

View File

@@ -25,33 +25,77 @@
#include "transport/IClipboardSource.h"
#include "transport/ITransport.h"
class CClipboardHub final : public IClipboardTarget,
private IClipboardChannelHandler
class CClipboardHub final : private IClipboardChannelHandler
{
private:
CClipboardChannel& m_channel;
static constexpr unsigned MAX_SOURCES = TRANSPORT_MAX_INSTANCES;
struct Source final : public IClipboardTarget
{
CClipboardHub * owner = nullptr;
BackendId backend = 0;
uint32_t epoch = 0;
uint32_t generation = 0;
IClipboardSource * endpoint = nullptr;
bool running = false;
bool active = false;
bool reserved = false;
bool failed = false;
bool failurePending = false;
ClipboardClaimResult Claim(const ClipboardSourceId& source,
KVMFRClipboardMessage& cached) override;
void Release(const ClipboardSourceId& source) override;
ClipboardChannelResult SendClipboard(
const KVMFRClipboardMessage& record,
const uint8_t * data) override;
ClipboardChannelResult SendClipboardBatch(
const ClipboardChannelWrite * records,
size_t count, size_t& accepted) override;
void ClipboardReceiveReady() override;
void ClipboardFailed() override;
};
CClipboardChannel& m_channel;
CSRWLock m_lifecycleLock;
CSRWLock m_callbackLock;
CSRWLock m_lock;
IClipboardSource * m_source = nullptr;
BackendId m_backend = 0;
uint32_t m_epoch = 0;
uint64_t m_channelEpoch = 0;
uint32_t m_generation = 1;
bool m_available = false;
bool m_active = false;
bool m_running = false;
bool m_reserved = false;
bool m_failed = false;
bool m_failurePending = false;
bool m_stopped = false;
Source m_sources[MAX_SOURCES];
Source * m_owner = nullptr;
Source * m_cachedSource = nullptr;
ClipboardSourceId m_ownerId;
KVMFRClipboardMessage m_cachedClipboard = {};
uint64_t m_channelEpoch = 0;
uint32_t m_generation = 1;
bool m_cachedValid = false;
bool m_available = false;
bool m_resetting = false;
bool m_stopped = false;
void AdvanceGenerationNL();
bool MarkFailed(IClipboardSource& source);
uint32_t NextGenerationNL();
bool BindingPresentNL(const Source& source) const;
bool BindingValidNL(const Source& source) const;
bool OwnerIsNL(
const Source& source, const ClipboardSourceId& id) const;
bool MarkFailed(Source& source);
void CachePublicationNL(
const KVMFRClipboardMessage& record, Source * source);
void InvalidateSourceCacheNL(const Source& source);
ClipboardChannelResult HoldOrDiscard(
const KVMFRClipboardMessage& record);
ClipboardClaimResult Claim(Source& source,
const ClipboardSourceId& id, KVMFRClipboardMessage& cached);
void Release(Source& source, const ClipboardSourceId& id);
ClipboardChannelResult SendClipboard(Source& source,
const KVMFRClipboardMessage& record, const uint8_t * data);
ClipboardChannelResult SendClipboardBatch(Source& source,
const ClipboardChannelWrite * records,
size_t count, size_t& accepted);
void ClipboardReceiveReady(Source& source);
void ClipboardFailed(Source& source);
void ClipboardState(bool available, uint64_t epoch) override;
ClipboardChannelResult ClipboardRecord(
const KVMFRClipboardMessage& record,
@@ -69,13 +113,4 @@ public:
void Unbind(BackendId backend, uint32_t epoch);
bool TakeFailure(SourceKey& source);
void Stop();
ClipboardChannelResult SendClipboard(
const KVMFRClipboardMessage& record,
const uint8_t * data) override;
ClipboardChannelResult SendClipboardBatch(
const ClipboardChannelWrite * records,
size_t count, size_t& accepted) override;
void ClipboardReceiveReady() override;
void ClipboardFailed() override;
};

View File

@@ -25,11 +25,32 @@
#include <stdint.h>
struct ClipboardSourceId
{
uint64_t client = 0;
uint64_t generation = 0;
};
enum class ClipboardClaimResult
{
ACCEPTED,
BUSY,
UNAVAILABLE,
STALE,
};
class IClipboardTarget
{
public:
virtual ~IClipboardTarget() = default;
// Claim arbitrates clipboard ownership across every transport. The first
// active client is accepted; cached receives the latest publication for
// replay by the new owner, or an empty record when none is available.
virtual ClipboardClaimResult Claim(const ClipboardSourceId& source,
KVMFRClipboardMessage& cached) = 0;
virtual void Release(const ClipboardSourceId& source) = 0;
// Sends one client-to-Helper record. record.generation must be the binding
// generation most recently supplied by ClipboardState; transports which
// expose their own endpoint generation validate and replace it at this

View File

@@ -398,9 +398,11 @@ bool CLGMPClipboardTransport::Start(IClipboardTarget& target)
{
CSRWExclusiveLock lock(m_lock);
m_target = &target;
m_failed = false;
m_statusDirty = true;
m_target = &target;
m_hubOwner = {};
m_hubReleasePending = false;
m_failed = false;
m_statusDirty = true;
}
m_thread = CreateThread(nullptr, 0, ThreadProc, this, 0, nullptr);
if (!m_thread)
@@ -446,11 +448,13 @@ void CLGMPClipboardTransport::Stop()
CSRWExclusiveLock lock(m_lock);
DropPendingTarget();
ResetProtocol(false);
m_available = false;
m_available = false;
m_endpointGeneration = 0;
m_target = nullptr;
m_failed = false;
m_statusDirty = true;
m_target = nullptr;
m_hubOwner = {};
m_hubReleasePending = false;
m_failed = false;
m_statusDirty = true;
}
bool CLGMPClipboardTransport::IsOwner(
@@ -462,15 +466,20 @@ bool CLGMPClipboardTransport::IsOwner(
bool CLGMPClipboardTransport::OwnerSubscribed() const
{
if (!m_ownerClientID)
return ClientSubscribed(m_ownerClientID);
}
bool CLGMPClipboardTransport::ClientSubscribed(uint32_t clientID) const
{
if (!clientID || !m_queue)
return false;
uint32_t clients[LGMP_MAX_CLIENTS] = {};
unsigned count = 0;
unsigned count = 0;
if (lgmpHostGetClientIDs(m_queue, clients, &count) != LGMP_OK)
return false;
for (unsigned i = 0; i < count; ++i)
if (clients[i] == m_ownerClientID)
if (clients[i] == clientID)
return true;
return false;
}
@@ -938,28 +947,28 @@ void CLGMPClipboardTransport::FinishOwnerRelease()
if (!m_ownerClientID)
return;
const uint32_t clientID = m_ownerClientID;
const uint32_t generation = m_ownerGeneration;
const char * const reason = m_ownerReleaseReason ?
const uint32_t clientID = m_ownerClientID;
const uint32_t generation = m_ownerGeneration;
const char * const reason = m_ownerReleaseReason ?
m_ownerReleaseReason : "unspecified";
const Transfer clientToHelper = m_clientToHelper;
const Transfer helperToClient = m_helperToClient;
const bool clearHelper = m_releaseClearHelper;
m_ownerClientID = 0;
m_ownerGeneration = 0;
m_ownerReleasing = false;
m_releaseClearHelper = false;
m_ownerReleaseReason = nullptr;
m_ownerDeadline = 0;
m_ownerDrainDeadline = 0;
m_replayPending = false;
const Transfer clientToHelper = m_clientToHelper;
const Transfer helperToClient = m_helperToClient;
const bool clearHelper = m_releaseClearHelper;
m_ownerClientID = 0;
m_ownerGeneration = 0;
m_ownerReleasing = false;
m_releaseClearHelper = false;
m_ownerReleaseReason = nullptr;
m_ownerDeadline = 0;
m_ownerDrainDeadline = 0;
m_replayPending = false;
ClearQueuedStreamTargets();
m_discardClientToHelper = 0;
m_discardClientToHelper = 0;
m_clientToHelper.Clear();
m_helperToClient.Clear();
if (helperToClient.Active())
m_discardHelperToClient = helperToClient.transfer;
m_statusDirty = true;
m_statusDirty = true;
if (clearHelper)
{
@@ -972,8 +981,9 @@ void CLGMPClipboardTransport::FinishOwnerRelease()
{
m_files.Reset();
m_clientClipboardGeneration = 0;
m_clientFormats = 0;
m_clientFormats = 0;
}
m_hubReleasePending = m_hubOwner.client != 0;
DEBUG_INFO("Clipboard owner %u generation %u released (%s)",
clientID, generation, reason);
@@ -1002,8 +1012,27 @@ bool CLGMPClipboardTransport::RetryOwnerRelease()
return !m_failed;
}
void CLGMPClipboardTransport::ReleaseHubOwner(bool force)
{
if (!m_hubOwner.client)
{
m_hubReleasePending = false;
return;
}
if (!force && (m_failed || !m_hubReleasePending || m_ownerClientID ||
m_ownerReleasing || m_pendingTarget.valid || m_streamTargetCount ||
m_internalTargetCount))
return;
if (m_target)
m_target->Release(m_hubOwner);
m_hubOwner = {};
m_hubReleasePending = false;
}
void CLGMPClipboardTransport::ResetProtocol(bool keepClipboard)
{
DropPendingClaim();
DropPendingTarget();
if (m_ownerClientID)
ReleaseOwner("endpoint reset", false);
@@ -1518,6 +1547,104 @@ void CLGMPClipboardTransport::DropPendingTarget()
}
}
void CLGMPClipboardTransport::DropPendingClaim()
{
if (!m_pendingClaim.valid)
return;
m_pendingClaim.Clear();
if (m_queue)
{
const LGMP_STATUS status = lgmpHostAckData(m_queue);
if (status != LGMP_OK)
Fail("lgmpHostAckData", status);
}
}
bool CLGMPClipboardTransport::RetryClaim()
{
if (!m_pendingClaim.valid)
return true;
const uint32_t clientID = m_pendingClaim.clientID;
const KVMFRClipboardMessage message = m_pendingClaim.record;
if (!ClientSubscribed(clientID) || !m_available ||
!ValidateClaim(message))
{
DropPendingClaim();
return !m_failed;
}
if (m_ownerClientID)
{
if (!m_ownerReleasing && IsOwner(clientID, message.generation))
RenewLease();
DropPendingClaim();
return !m_failed;
}
if (m_hubReleasePending)
return true;
const ClipboardSourceId source =
{
clientID,
message.generation,
};
KVMFRClipboardMessage cached = {};
const ClipboardClaimResult claim = m_target ?
m_target->Claim(source, cached) : ClipboardClaimResult::UNAVAILABLE;
if (claim == ClipboardClaimResult::BUSY ||
claim == ClipboardClaimResult::UNAVAILABLE)
return true;
if (claim != ClipboardClaimResult::ACCEPTED)
{
DropPendingClaim();
return !m_failed;
}
m_hubOwner = source;
if (!BindStreams(clientID))
{
m_target->Release(m_hubOwner);
m_hubOwner = {};
DeInitStreams();
DropPendingClaim();
m_failed = true;
if (m_stopEvent)
SetEvent(m_stopEvent);
return false;
}
m_ownerClientID = clientID;
m_ownerGeneration = message.generation;
m_cachedClipboard = {};
m_cachedValid = false;
m_helperFormats = 0;
if (cached.version)
{
if ((cached.type != KVMFR_CLIPBOARD_MESSAGE_OFFER &&
cached.type != KVMFR_CLIPBOARD_MESSAGE_CLEAR) ||
!ValidateOutboundRecord(cached))
{
ReleaseOwner("invalid hub clipboard replay", false, true);
DropPendingClaim();
m_failed = true;
return false;
}
ApplyOutbound(cached);
}
RenewLease();
m_statusDirty = true;
m_replayPending = m_cachedValid;
DropPendingClaim();
if (m_failed)
return false;
DEBUG_INFO("Clipboard owner %u generation %u acquired",
m_ownerClientID, m_ownerGeneration);
return true;
}
bool CLGMPClipboardTransport::ProcessMessage(
uint32_t clientID, const KVMFRClipboardMessage& message)
{
@@ -1532,24 +1659,14 @@ bool CLGMPClipboardTransport::ProcessMessage(
RenewLease();
return true;
}
if (!BindStreams(clientID))
{
DeInitStreams();
m_failed = true;
if (m_stopEvent)
SetEvent(m_stopEvent);
if (m_pendingClaim.valid)
return true;
}
m_ownerClientID = clientID;
m_ownerGeneration = message.generation;
RenewLease();
m_statusDirty = true;
m_replayPending = m_cachedValid;
DEBUG_INFO("Clipboard owner %u generation %u acquired",
m_ownerClientID, m_ownerGeneration);
return true;
m_pendingClaim.valid = true;
m_pendingClaim.clientID = clientID;
m_pendingClaim.record = message;
(void)RetryClaim();
return false;
}
if (!IsOwner(clientID, message.generation))
@@ -1625,12 +1742,14 @@ bool CLGMPClipboardTransport::ProcessMessage(
bool CLGMPClipboardTransport::DrainMessage()
{
for (unsigned count = 0; count < 64 && !m_pendingTarget.valid; ++count)
for (unsigned count = 0; count < 64 && !m_pendingClaim.valid &&
!m_pendingTarget.valid && !m_failed; ++count)
{
uint8_t data[LGMP_MSGS_SIZE] = {};
size_t size = 0;
uint32_t clientID = 0;
const LGMP_STATUS status = lgmpHostReadDataWithSource(
size_t size = 0;
uint32_t clientID = 0;
const LGMP_STATUS status = lgmpHostReadDataWithSource(
m_queue, data, &size, &clientID);
if (status == LGMP_ERR_QUEUE_EMPTY)
return true;
@@ -1663,7 +1782,7 @@ bool CLGMPClipboardTransport::DrainMessage()
}
}
}
return true;
return !m_failed;
}
ClipboardChannelResult CLGMPClipboardTransport::SendControl(
@@ -1927,6 +2046,7 @@ void CLGMPClipboardTransport::ClipboardState(
if (generation != m_endpointGeneration || available != m_available)
{
DropPendingClaim();
DropPendingTarget();
ClearStreamTargets();
m_internalTargetCount = 0;
@@ -1958,6 +2078,7 @@ void CLGMPClipboardTransport::ClipboardReset(
CSRWExclusiveLock lock(m_lock);
if (!generation)
return;
DropPendingClaim();
DropPendingTarget();
ClearStreamTargets();
m_internalTargetCount = 0;
@@ -2009,10 +2130,10 @@ void CLGMPClipboardTransport::Thread()
bool notifyFailed = false;
for (;;)
{
uint32_t waitUs = IDLE_POLL_MS * 1000U;
bool streamReceived = false;
bool notifyReady = false;
IClipboardTarget * readyTarget = nullptr;
uint32_t waitUs = IDLE_POLL_MS * 1000U;
bool streamReceived = false;
bool notifyReady = false;
IClipboardTarget * readyTarget = nullptr;
{
CSRWExclusiveLock lock(m_lock);
if (m_failed)
@@ -2033,7 +2154,7 @@ void CLGMPClipboardTransport::Thread()
// A restarted client can reuse dataset and transfer IDs. Deliver every
// old-owner cleanup record before admitting messages from a new owner.
if (!RetryTarget() || !RetryStreamTargets() ||
if (!RetryClaim() || !RetryTarget() || !RetryStreamTargets() ||
!PumpInternalTarget() ||
(!m_streamTargetCount && m_internalTargetCount == 0 &&
!DrainMessage()) ||
@@ -2045,14 +2166,16 @@ void CLGMPClipboardTransport::Thread()
notifyFailed = true;
break;
}
ReleaseHubOwner();
if (m_outboundBlocked)
{
notifyReady = m_target != nullptr;
readyTarget = m_target;
}
if (m_pendingTarget.valid || m_internalTargetCount ||
m_streamTargetCount || m_ownerClientID)
if (m_pendingClaim.valid || m_pendingTarget.valid ||
m_internalTargetCount ||
m_streamTargetCount || m_ownerClientID || m_hubReleasePending)
waitUs = ACTIVE_POLL_MS * 1000U;
if (m_ownerClientID)
{
@@ -2115,10 +2238,14 @@ void CLGMPClipboardTransport::Thread()
IClipboardTarget * target = nullptr;
{
CSRWExclusiveLock lock(m_lock);
DropPendingClaim();
DropPendingTarget();
ClearStreamTargets();
if (m_ownerClientID)
ReleaseOwner("transport stopped", false, true);
notifyFailed = notifyFailed || m_failed;
if (!notifyFailed)
ReleaseHubOwner(true);
target = m_target;
}
if (notifyFailed && target)

View File

@@ -88,9 +88,9 @@ private:
struct PendingTarget
{
bool valid = false;
bool acknowledge = false;
KVMFRClipboardMessage record = {};
bool valid = false;
bool acknowledge = false;
KVMFRClipboardMessage record = {};
void Clear()
{
@@ -100,67 +100,88 @@ private:
}
};
struct PendingClaim
{
bool valid = false;
uint32_t clientID = 0;
KVMFRClipboardMessage record = {};
void Clear()
{
valid = false;
clientID = 0;
record = {};
}
};
struct StreamTarget
{
KVMFRClipboardMessage record = {};
uint8_t data[KVMFR_CLIPBOARD_DATA_BYTES] = {};
KVMFRClipboardMessage record = {};
uint8_t data[KVMFR_CLIPBOARD_DATA_BYTES] = {};
void Clear() { record = {}; }
};
CLGMPHost& m_host;
PLGMPHostQueue m_queue = nullptr;
PLGMPHostStream m_hostToClientStream = nullptr;
PLGMPHostStream m_clientToHostStream = nullptr;
PLGMPMemory m_statusMemory [MEMORY_COUNT] = {};
PLGMPMemory m_messageMemory[MEMORY_COUNT] = {};
PLGMPHostQueue m_queue = nullptr;
PLGMPHostStream m_hostToClientStream = nullptr;
PLGMPHostStream m_clientToHostStream = nullptr;
CSRWLock m_lifecycleLock;
CSRWLock m_lock;
IClipboardTarget * m_target = nullptr;
HANDLE m_stopEvent = nullptr;
HANDLE m_wakeEvent = nullptr;
HANDLE m_pollTimer = nullptr;
HANDLE m_thread = nullptr;
PLGMPMemory m_statusMemory [MEMORY_COUNT] = {};
PLGMPMemory m_messageMemory[MEMORY_COUNT] = {};
bool m_available = false;
bool m_statusDirty = true;
bool m_cachedValid = false;
bool m_replayPending = false;
bool m_outboundBlocked = false;
bool m_ownerReleasing = false;
bool m_releaseClearHelper = false;
bool m_failed = false;
uint32_t m_endpointGeneration = 0;
uint32_t m_ownerClientID = 0;
uint32_t m_ownerGeneration = 0;
uint32_t m_statusSerial = 0;
uint32_t m_messageSerial = 0;
uint32_t m_helperFormats = 0;
uint32_t m_clientFormats = 0;
uint32_t m_blockedOwnerClientID = 0;
uint32_t m_blockedOwnerGeneration = 0;
uint64_t m_ownerDeadline = 0;
uint64_t m_ownerDrainDeadline = 0;
uint64_t m_clientClipboardGeneration = 0;
uint64_t m_discardClientToHelper = 0;
uint64_t m_discardHelperToClient = 0;
const char * m_ownerReleaseReason = nullptr;
KVMFRClipboardMessage m_cachedClipboard = {};
KVMFRClipboardMessage m_blockedOutbound = {};
KVMFRStreamDescriptor m_hostToClientDescriptor = {};
KVMFRStreamDescriptor m_clientToHostDescriptor = {};
Transfer m_clientToHelper;
Transfer m_helperToClient;
PendingTarget m_pendingTarget;
StreamTarget m_streamTarget[STREAM_TARGET_COUNT];
unsigned m_streamTargetHead = 0;
unsigned m_streamTargetCount = 0;
unsigned m_streamTargetPrepared = 0;
KVMFRClipboardMessage m_internalTarget[INTERNAL_TARGET_COUNT] = {};
unsigned m_internalTargetCount = 0;
CLGMPClipboardFiles m_files;
CSRWLock m_lifecycleLock;
CSRWLock m_lock;
IClipboardTarget * m_target = nullptr;
ClipboardSourceId m_hubOwner;
HANDLE m_stopEvent = nullptr;
HANDLE m_wakeEvent = nullptr;
HANDLE m_pollTimer = nullptr;
HANDLE m_thread = nullptr;
bool m_available = false;
bool m_statusDirty = true;
bool m_cachedValid = false;
bool m_replayPending = false;
bool m_outboundBlocked = false;
bool m_ownerReleasing = false;
bool m_releaseClearHelper = false;
bool m_hubReleasePending = false;
bool m_failed = false;
uint32_t m_endpointGeneration = 0;
uint32_t m_ownerClientID = 0;
uint32_t m_ownerGeneration = 0;
uint32_t m_statusSerial = 0;
uint32_t m_messageSerial = 0;
uint32_t m_helperFormats = 0;
uint32_t m_clientFormats = 0;
uint32_t m_blockedOwnerClientID = 0;
uint32_t m_blockedOwnerGeneration = 0;
uint64_t m_ownerDeadline = 0;
uint64_t m_ownerDrainDeadline = 0;
uint64_t m_clientClipboardGeneration = 0;
uint64_t m_discardClientToHelper = 0;
uint64_t m_discardHelperToClient = 0;
const char * m_ownerReleaseReason = nullptr;
KVMFRClipboardMessage m_cachedClipboard = {};
KVMFRClipboardMessage m_blockedOutbound = {};
KVMFRStreamDescriptor m_hostToClientDescriptor = {};
KVMFRStreamDescriptor m_clientToHostDescriptor = {};
Transfer m_clientToHelper;
Transfer m_helperToClient;
PendingClaim m_pendingClaim;
PendingTarget m_pendingTarget;
StreamTarget m_streamTarget[STREAM_TARGET_COUNT];
unsigned m_streamTargetHead = 0;
unsigned m_streamTargetCount = 0;
unsigned m_streamTargetPrepared = 0;
KVMFRClipboardMessage m_internalTarget[INTERNAL_TARGET_COUNT] = {};
unsigned m_internalTargetCount = 0;
CLGMPClipboardFiles m_files;
bool Initialize();
void DeInit();
@@ -175,6 +196,7 @@ private:
void Wake() const;
bool IsOwner(uint32_t clientID, uint32_t generation) const;
bool ClientSubscribed(uint32_t clientID) const;
bool OwnerSubscribed() const;
void RenewLease();
void BlockOutbound(const KVMFRClipboardMessage& record,
@@ -185,6 +207,7 @@ private:
bool force = false);
void FinishOwnerRelease();
bool RetryOwnerRelease();
void ReleaseHubOwner(bool force = false);
void ResetProtocol(bool keepClipboard);
void QueueHelperClear();
void QueueTransferCancel(const Transfer& transfer);
@@ -214,6 +237,8 @@ private:
bool DrainMessage();
bool ProcessMessage(uint32_t clientID,
const KVMFRClipboardMessage& message);
bool RetryClaim();
void DropPendingClaim();
bool RetryTarget();
void FinishTarget(bool accepted);
void DropPendingTarget();

View File

@@ -58,6 +58,16 @@ namespace
return left.client == right.client && left.generation == right.generation;
}
ClipboardSourceId SourceId(
const NetworkClipboardPeer& peer, uint64_t claimEpoch)
{
return
{
(static_cast<uint64_t>(peer.generation) << 32U) | peer.client,
claimEpoch,
};
}
uint64_t Next64(uint64_t value)
{
++value;
@@ -269,6 +279,8 @@ struct CNetworkClipboard::Impl
std::vector<PeerState> m_peers = {};
NetworkClipboardPeer m_owner = {};
ClipboardSourceId m_hubOwner = {};
bool m_hubReleasePending = false;
uint64_t m_ownerID = 0;
uint64_t m_claimEpoch = 0;
uint64_t m_claimSerial = 0;
@@ -396,6 +408,7 @@ struct CNetworkClipboard::Impl
void PumpSends();
void PumpTargets();
void ReleaseHubOwner(bool force = false);
void NotifyTargetReady();
void NotifyFailure();
void DropOwnerNL(bool clearHelper);
@@ -420,9 +433,11 @@ struct CNetworkClipboard::Impl
std::vector<SendItem>& batch, const KVMFRClipboardMessage& record);
NetworkClipboardPacketResult HandleClaimNL(
PeerState& peer, const LGNetPacketView& packet, uint64_t nowMs);
PeerState& peer, const LGNetPacketView& packet, uint64_t nowMs,
CSRWExclusiveLock& stateLock);
NetworkClipboardPacketResult HandleClipboardNL(
PeerState& peer, const LGNetPacketView& packet, bool bulk, uint64_t nowMs);
PeerState& peer, const LGNetPacketView& packet, bool bulk, uint64_t nowMs,
CSRWExclusiveLock& stateLock);
NetworkClipboardPacketResult HandleFileNL(
PeerState& peer, const LGNetPacketView& packet, bool bulk);
@@ -704,6 +719,7 @@ void CNetworkClipboard::Impl::PumpSends()
void CNetworkClipboard::Impl::PumpTargets()
{
ReleaseHubOwner();
for (size_t count = 0; count < PROCESS_MAX; ++count)
{
TargetItem action;
@@ -758,22 +774,60 @@ void CNetworkClipboard::Impl::PumpTargets()
m_targetBytes -= sizeof(item.record) + item.data.size();
m_targets.pop_front();
}
ReleaseHubOwner();
}
}
void CNetworkClipboard::Impl::ReleaseHubOwner(bool force)
{
CSRWExclusiveLock callbackLock(m_targetCallbackLock);
ClipboardSourceId source = {};
IClipboardTarget * target = nullptr;
{
CSRWExclusiveLock lock(m_lock);
if (!m_hubReleasePending)
return;
// A terminal failure retains ownership until CClipboardHub::Unbind resets
// the Helper channel, preventing partial transfers reaching a new owner.
if (!force && (m_failed || !m_targets.empty()))
return;
source = m_hubOwner;
target = m_target;
if (!source.client || !source.generation || !target)
{
if (force)
{
m_hubOwner = {};
m_hubReleasePending = false;
}
return;
}
m_targetCallbackActive = true;
}
target->Release(source);
{
CSRWExclusiveLock lock(m_lock);
m_targetCallbackActive = false;
if (m_hubReleasePending &&
source.client == m_hubOwner.client &&
source.generation == m_hubOwner.generation)
{
m_hubOwner = {};
m_hubReleasePending = false;
}
}
}
void CNetworkClipboard::Impl::NotifyTargetReady()
{
{
CSRWSharedLock lock(m_lock);
if (m_targetCallbackActive)
return;
}
CSRWExclusiveLock callbackLock(m_targetCallbackLock);
IClipboardTarget * target = nullptr;
{
CSRWExclusiveLock lock(m_lock);
if (m_targetCallbackActive || !m_receiveReadyPending)
if (!m_receiveReadyPending)
return;
m_receiveReadyPending = false;
target = m_started ? m_target : nullptr;
@@ -789,33 +843,17 @@ void CNetworkClipboard::Impl::NotifyTargetReady()
void CNetworkClipboard::Impl::NotifyFailure()
{
{
CSRWSharedLock lock(m_lock);
if (m_targetCallbackActive)
return;
}
bool notify = false;
CSRWExclusiveLock callbackLock(m_targetCallbackLock);
IClipboardTarget * target = nullptr;
{
CSRWExclusiveLock lock(m_lock);
if (!m_targetCallbackActive && m_failed && !m_failureNotified && m_target)
{
m_failureNotified = true;
m_targetCallbackActive = true;
notify = true;
}
if (!m_failed || m_failureNotified || !m_target)
return;
m_failureNotified = true;
m_targetCallbackActive = true;
target = m_target;
}
if (!notify)
return;
CSRWExclusiveLock callbackLock(m_targetCallbackLock);
IClipboardTarget * target;
{
CSRWSharedLock lock(m_lock);
target = m_target;
}
if (target)
target->ClipboardFailed();
target->ClipboardFailed();
{
CSRWExclusiveLock lock(m_lock);
m_targetCallbackActive = false;
@@ -1101,13 +1139,15 @@ void CNetworkClipboard::Impl::DropOwnerNL(bool clearHelper)
if (!m_claimEpoch)
return;
bool cleanupQueued = true;
if (clearHelper && m_remoteOffer.published && m_remoteOffer.offerID)
{
TargetItem item;
item.record.version = KVMFR_CLIPBOARD_VERSION;
item.record.type = KVMFR_CLIPBOARD_MESSAGE_CLEAR;
item.record.clipboardGeneration = m_remoteOffer.offerID;
QueueTargetNL(std::move(item));
if (!QueueTargetNL(std::move(item)))
cleanupQueued = false;
}
if (clearHelper)
@@ -1120,7 +1160,8 @@ void CNetworkClipboard::Impl::DropOwnerNL(bool clearHelper)
item.record.clipboardGeneration = transfer.offerID;
item.record.transfer = transfer.localID;
item.record.format = transfer.format;
QueueTargetNL(std::move(item));
if (!QueueTargetNL(std::move(item)))
cleanupQueued = false;
}
for (const FileTransfer& transfer : m_fileTransfers)
@@ -1132,7 +1173,8 @@ void CNetworkClipboard::Impl::DropOwnerNL(bool clearHelper)
item.record.transfer = transfer.localID;
item.record.format = KVMFR_CLIPBOARD_FORMAT_FILES;
item.record.token = KVMFR_CLIPBOARD_FILE_ERROR_DISCONNECTED;
QueueTargetNL(std::move(item));
if (!QueueTargetNL(std::move(item)))
cleanupQueued = false;
}
for (const FileAcquisition& acquisition : m_fileAcquisitions)
@@ -1158,7 +1200,8 @@ void CNetworkClipboard::Impl::DropOwnerNL(bool clearHelper)
item.record.type = KVMFR_CLIPBOARD_MESSAGE_FILE_CANCEL;
item.record.token = KVMFR_CLIPBOARD_FILE_ERROR_DISCONNECTED;
}
QueueTargetNL(std::move(item));
if (!QueueTargetNL(std::move(item)))
cleanupQueued = false;
}
}
@@ -1172,6 +1215,9 @@ void CNetworkClipboard::Impl::DropOwnerNL(bool clearHelper)
}),
m_sends.end());
ResetTransfersNL();
if (!cleanupQueued)
m_failed = true;
m_hubReleasePending = m_hubOwner.client != 0;
m_remoteOffer.Clear();
m_owner = {};
m_ownerID = 0;
@@ -1184,7 +1230,8 @@ void CNetworkClipboard::Impl::DropOwnerNL(bool clearHelper)
}
NetworkClipboardPacketResult CNetworkClipboard::Impl::HandleClaimNL(
PeerState& peer, const LGNetPacketView& packet, uint64_t nowMs)
PeerState& peer, const LGNetPacketView& packet, uint64_t nowMs,
CSRWExclusiveLock& stateLock)
{
if (!DeliveryFlags(
packet.envelope, LG_NET_ENVELOPE_ACK_REQUIRED, false) ||
@@ -1225,8 +1272,10 @@ NetworkClipboardPacketResult CNetworkClipboard::Impl::HandleClaimNL(
NetworkClipboardPacketResult::INVALID;
}
if (m_claimEpoch && !SamePeer(m_owner, peer.peer))
if (m_claimEpoch || m_hubReleasePending)
{
if (m_claimEpoch && SamePeer(m_owner, peer.peer))
DropOwnerNL(true);
peer.lastClaimEpoch = claim.claimEpoch;
return QueueClaimStatusNL(peer.peer, claim.claimEpoch,
LG_NET_CLIPBOARD_CLAIM_REJECTED, 0, 0, 2) ?
@@ -1234,8 +1283,45 @@ NetworkClipboardPacketResult CNetworkClipboard::Impl::HandleClaimNL(
NetworkClipboardPacketResult::INVALID;
}
if (m_claimEpoch)
DropOwnerNL(true);
const ClipboardSourceId source =
SourceId(peer.peer, claim.claimEpoch);
KVMFRClipboardMessage cached = {};
IClipboardTarget * target = m_target;
m_targetCallbackActive = target != nullptr;
stateLock.Unlock();
const ClipboardClaimResult claimResult = target ?
target->Claim(source, cached) : ClipboardClaimResult::UNAVAILABLE;
CSRWExclusiveLock lock(m_lock);
m_targetCallbackActive = false;
if (claimResult != ClipboardClaimResult::ACCEPTED)
{
const uint32_t detail = claimResult == ClipboardClaimResult::STALE ? 1 :
claimResult == ClipboardClaimResult::BUSY ? 2 : 3;
peer.lastClaimEpoch = claim.claimEpoch;
return QueueClaimStatusNL(peer.peer, claim.claimEpoch,
LG_NET_CLIPBOARD_CLAIM_REJECTED, 0, 0, detail) ?
NetworkClipboardPacketResult::HANDLED :
NetworkClipboardPacketResult::INVALID;
}
m_hubOwner = source;
m_hubReleasePending = false;
m_cachedRecord = {};
m_cachedValid = false;
if (cached.version)
{
const bool valid = cached.generation == m_generation &&
((cached.type == KVMFR_CLIPBOARD_MESSAGE_OFFER && ValidOffer(cached)) ||
(cached.type == KVMFR_CLIPBOARD_MESSAGE_CLEAR && ValidClear(cached)));
if (!valid)
{
m_hubReleasePending = true;
peer.lastClaimEpoch = claim.claimEpoch;
return NetworkClipboardPacketResult::INVALID;
}
m_cachedRecord = cached;
m_cachedValid = true;
}
m_owner = peer.peer;
m_ownerID = claim.ownerID;
@@ -1264,14 +1350,15 @@ NetworkClipboardPacketResult CNetworkClipboard::Impl::HandleClaimNL(
}
NetworkClipboardPacketResult CNetworkClipboard::Impl::HandleClipboardNL(
PeerState& peer, const LGNetPacketView& packet, bool bulk, uint64_t nowMs)
PeerState& peer, const LGNetPacketView& packet, bool bulk, uint64_t nowMs,
CSRWExclusiveLock& stateLock)
{
const LGNetEnvelope& envelope = packet.envelope;
if (envelope.messageType == LG_NET_CLIPBOARD_MESSAGE_CLAIM)
{
if (bulk)
return NetworkClipboardPacketResult::INVALID;
return HandleClaimNL(peer, packet, nowMs);
return HandleClaimNL(peer, packet, nowMs, stateLock);
}
const bool transferData =
@@ -3176,15 +3263,21 @@ void CNetworkClipboard::DetachSender()
{
CSRWExclusiveLock lifecycleLock(m_impl->m_lifecycleLock);
CSRWExclusiveLock processLock(m_impl->m_processLock);
CSRWExclusiveLock callbackLock(m_impl->m_senderCallbackLock);
CSRWExclusiveLock lock(m_impl->m_lock);
if (!m_impl->m_sender)
return;
m_impl->DropOwnerNL(true);
m_impl->m_sender = nullptr;
m_impl->m_peers.clear();
m_impl->m_sends.clear();
m_impl->m_sendBytes = 0;
{
CSRWExclusiveLock callbackLock(m_impl->m_senderCallbackLock);
CSRWExclusiveLock lock(m_impl->m_lock);
if (!m_impl->m_sender)
return;
m_impl->DropOwnerNL(true);
m_impl->m_sender = nullptr;
m_impl->m_peers.clear();
m_impl->m_sends.clear();
m_impl->m_sendBytes = 0;
}
m_impl->PumpTargets();
processLock.Unlock();
m_impl->NotifyTargetReady();
m_impl->NotifyFailure();
}
bool CNetworkClipboard::PeerConnected(const NetworkClipboardPeer& peer)
@@ -3280,6 +3373,7 @@ NetworkClipboardPacketResult CNetworkClipboard::HandlePacket(
CSRWExclusiveLock processLock(m_impl->m_processLock);
NetworkClipboardPacketResult result;
{
CSRWExclusiveLock callbackLock(m_impl->m_targetCallbackLock);
CSRWExclusiveLock lock(m_impl->m_lock);
Impl::PeerState * state = m_impl->FindPeerNL(peer);
if (!state)
@@ -3304,7 +3398,8 @@ NetworkClipboardPacketResult CNetworkClipboard::HandlePacket(
return NetworkClipboardPacketResult::INVALID;
result = packet.envelope.service == LG_NET_SERVICE_CLIPBOARD ?
m_impl->HandleClipboardNL(*state, packet, bulk, GetTickCount64()) :
m_impl->HandleClipboardNL(
*state, packet, bulk, GetTickCount64(), lock) :
m_impl->HandleFileNL(*state, packet, bulk);
}
@@ -3373,10 +3468,12 @@ bool CNetworkClipboard::Start(IClipboardTarget& target)
CSRWExclusiveLock lock(m_impl->m_lock);
if (m_impl->m_started || m_impl->m_target)
return false;
m_impl->m_target = &target;
m_impl->m_started = true;
m_impl->m_failed = false;
m_impl->m_failureNotified = false;
m_impl->m_target = &target;
m_impl->m_started = true;
m_impl->m_failed = false;
m_impl->m_failureNotified = false;
m_impl->m_hubOwner = {};
m_impl->m_hubReleasePending = false;
return true;
}
@@ -3385,23 +3482,52 @@ void CNetworkClipboard::Stop()
CSRWExclusiveLock lifecycleLock(m_impl->m_lifecycleLock);
CSRWExclusiveLock processLock(m_impl->m_processLock);
CSRWExclusiveLock callbackLock(m_impl->m_targetCallbackLock);
CSRWExclusiveLock lock(m_impl->m_lock);
m_impl->m_target = nullptr;
m_impl->m_started = false;
m_impl->m_stateKnown = false;
m_impl->m_available = false;
m_impl->m_generation = 0;
m_impl->m_failed = false;
m_impl->m_failureNotified = false;
m_impl->m_targetCallbackActive = false;
m_impl->m_targets.clear();
m_impl->m_targetBytes = 0;
m_impl->ResetTransfersNL();
m_impl->m_remoteOffer.published = false;
m_impl->m_sourceBlocked = false;
m_impl->m_sourceBlockedService = 0;
m_impl->m_sourceBlockedTransfer = 0;
m_impl->m_receiveReadyPending = false;
IClipboardTarget * target = nullptr;
ClipboardSourceId source = {};
bool release = false;
{
CSRWExclusiveLock lock(m_impl->m_lock);
if (m_impl->m_claimEpoch)
m_impl->DropOwnerNL(false);
target = m_impl->m_target;
source = m_impl->m_hubOwner;
release = target && source.client &&
source.generation;
m_impl->m_targetCallbackActive = release;
}
if (release)
target->Release(source);
{
CSRWExclusiveLock lock(m_impl->m_lock);
m_impl->m_target = nullptr;
m_impl->m_started = false;
m_impl->m_stateKnown = false;
m_impl->m_available = false;
m_impl->m_generation = 0;
m_impl->m_failed = false;
m_impl->m_failureNotified = false;
m_impl->m_targetCallbackActive = false;
m_impl->m_hubOwner = {};
m_impl->m_hubReleasePending = false;
m_impl->m_owner = {};
m_impl->m_ownerID = 0;
m_impl->m_claimEpoch = 0;
m_impl->m_claimSerial = 0;
m_impl->m_remoteClaimSerial = 0;
m_impl->m_remoteOfferSerial = 0;
m_impl->m_claimDeadline = 0;
m_impl->m_claimLeaseMs = 0;
m_impl->m_targetBytes = 0;
m_impl->m_remoteOffer.published = false;
m_impl->m_sourceBlocked = false;
m_impl->m_sourceBlockedService = 0;
m_impl->m_sourceBlockedTransfer = 0;
m_impl->m_receiveReadyPending = false;
m_impl->m_targets.clear();
m_impl->ResetTransfersNL();
}
}
void CNetworkClipboard::ClipboardState(bool available, uint32_t generation)