[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) : CClipboardHub::CClipboardHub(CClipboardChannel& channel) :
m_channel(channel) m_channel(channel)
{ {
for (Source& source : m_sources)
source.owner = this;
m_channel.SetHandler(this); m_channel.SetHandler(this);
} }
@@ -68,27 +70,107 @@ CClipboardHub::~CClipboardHub()
Stop(); 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); CSRWExclusiveLock lock(m_lock);
if (m_source != &source || (!m_active && !m_reserved)) if (!BindingPresentNL(source) && !source.reserved)
return false; return false;
const bool changed = !m_failed; const bool changed = !source.failed;
m_failed = true; source.failed = true;
m_failurePending = true; source.failurePending = true;
return changed; 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( ClipboardChannelResult CClipboardHub::HoldOrDiscard(
const KVMFRClipboardMessage& record) 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 // Transfer-scoped records belong to the source which admitted them and
// must never cross a transport generation. // must never cross a transport generation.
if (record.type == KVMFR_CLIPBOARD_MESSAGE_OFFER || if (record.type == KVMFR_CLIPBOARD_MESSAGE_OFFER ||
@@ -139,47 +221,58 @@ bool CClipboardHub::Bind(
if (!backend || !epoch) if (!backend || !epoch)
return false; return false;
bool available; Source * selected = nullptr;
uint32_t generation; bool available = false;
uint64_t channelEpoch; uint32_t generation = 0;
{ {
CSRWExclusiveLock lock(m_lock); CSRWExclusiveLock lock(m_lock);
if (m_stopped || m_source || m_active || m_reserved) if (m_stopped)
return false; return false;
AdvanceGenerationNL(); for (Source& slot : m_sources)
m_source = &source; if ((slot.active || slot.failed || slot.reserved) &&
m_backend = backend; slot.backend == backend && slot.epoch == epoch)
m_epoch = epoch; return false;
m_active = true; for (Source& slot : m_sources)
m_reserved = true; if (!slot.active && !slot.failed && !slot.reserved)
m_failed = false; {
m_failurePending = false; slot.backend = backend;
available = m_available; slot.epoch = epoch;
generation = m_generation; slot.generation = NextGenerationNL();
channelEpoch = m_channelEpoch; 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); CSRWExclusiveLock callbackLock(m_callbackLock);
const bool started = source.Start(*this); const bool started = source.Start(*selected);
bool attached = false; bool attached = false;
{ {
CSRWExclusiveLock lock(m_lock); CSRWExclusiveLock lock(m_lock);
if (started && m_source == &source && m_backend == backend && if (started && selected->endpoint == &source &&
m_epoch == epoch && m_active && m_reserved && !m_failed && selected->backend == backend && selected->epoch == epoch &&
selected->active && selected->reserved && !selected->failed &&
!m_stopped) !m_stopped)
{ {
m_running = true; selected->running = true;
m_reserved = false; selected->reserved = false;
available = m_available; available = m_available && !m_resetting;
generation = m_generation; generation = selected->generation;
channelEpoch = m_channelEpoch; attached = true;
attached = true;
} }
else else
{ {
m_active = false; selected->active = false;
m_running = false; selected->running = false;
} }
} }
@@ -187,99 +280,133 @@ bool CClipboardHub::Bind(
{ {
source.Stop(); source.Stop();
CSRWExclusiveLock lock(m_lock); 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; selected->backend = 0;
m_backend = 0; selected->epoch = 0;
m_epoch = 0; selected->generation = 0;
m_active = false; selected->endpoint = nullptr;
m_running = false; selected->running = false;
m_reserved = false; selected->active = false;
m_failed = false; selected->reserved = false;
m_failurePending = false; selected->failed = false;
selected->failurePending = false;
} }
return false; return false;
} }
source.ClipboardState(available, generation); source.ClipboardState(available, generation);
callbackLock.Unlock();
if (available && channelEpoch)
m_channel.Kick(channelEpoch);
return true; return true;
} }
void CClipboardHub::Unbind(BackendId backend, uint32_t epoch) void CClipboardHub::Unbind(BackendId backend, uint32_t epoch)
{ {
CSRWExclusiveLock lifecycleLock(m_lifecycleLock); CSRWExclusiveLock lifecycleLock(m_lifecycleLock);
IClipboardSource * source = nullptr; Source * selected = nullptr;
bool stop = false; IClipboardSource * source = nullptr;
bool stop = false;
bool resetChannel = false;
uint64_t channelEpoch = 0;
{ {
CSRWExclusiveLock lock(m_lock); CSRWExclusiveLock lock(m_lock);
if ((!m_active && !m_failed && !m_reserved) || for (Source& slot : m_sources)
m_backend != backend || m_epoch != epoch || !m_source) if ((slot.active || slot.failed || slot.reserved) &&
return; slot.backend == backend && slot.epoch == epoch && slot.endpoint)
{
source = m_source; selected = &slot;
stop = m_running || m_reserved; source = slot.endpoint;
m_active = false; stop = slot.running || slot.reserved;
m_running = false; resetChannel = m_owner == &slot;
m_reserved = true; channelEpoch = m_channelEpoch;
m_failed = false; slot.active = false;
m_failurePending = false; slot.running = false;
AdvanceGenerationNL(); 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); CSRWExclusiveLock callbackLock(m_callbackLock);
if (stop) if (stop)
source->Stop(); source->Stop();
CSRWExclusiveLock lock(m_lock); 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; selected->backend = 0;
m_backend = 0; selected->epoch = 0;
m_epoch = 0; selected->generation = 0;
m_active = false; selected->endpoint = nullptr;
m_running = false; selected->running = false;
m_reserved = false; selected->active = false;
m_failed = false; selected->reserved = false;
m_failurePending = 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) bool CClipboardHub::TakeFailure(SourceKey& source)
{ {
CSRWSharedLock lifecycleLock(m_lifecycleLock); CSRWSharedLock lifecycleLock(m_lifecycleLock);
CSRWExclusiveLock lock(m_lock); CSRWExclusiveLock lock(m_lock);
if (!m_failurePending) for (Source& slot : m_sources)
return false; {
if (!slot.failurePending)
source = {}; continue;
source.backend = m_backend; source = {};
source.epoch = m_epoch; source.backend = slot.backend;
m_failurePending = false; source.epoch = slot.epoch;
return true; slot.failurePending = false;
return true;
}
return false;
} }
void CClipboardHub::Stop() void CClipboardHub::Stop()
{ {
CSRWExclusiveLock lifecycleLock(m_lifecycleLock); CSRWExclusiveLock lifecycleLock(m_lifecycleLock);
IClipboardSource * source = nullptr; Source * sources[MAX_SOURCES] = {};
bool stop = false; unsigned count = 0;
{ {
CSRWExclusiveLock lock(m_lock); CSRWExclusiveLock lock(m_lock);
if (m_stopped) if (m_stopped)
return; return;
m_stopped = true; m_stopped = true;
source = m_source; m_owner = nullptr;
stop = source && (m_running || m_reserved); m_cachedSource = nullptr;
m_active = false; m_ownerId = {};
m_running = false; m_cachedClipboard = {};
m_reserved = source != nullptr; m_cachedValid = false;
m_failed = false; m_resetting = false;
m_failurePending = false; for (Source& source : m_sources)
AdvanceGenerationNL(); 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 // Clearing the channel handler is a callback-quiescence barrier. It must
@@ -288,41 +415,114 @@ void CClipboardHub::Stop()
m_channel.ClearHandler(this); m_channel.ClearHandler(this);
CSRWExclusiveLock callbackLock(m_callbackLock); CSRWExclusiveLock callbackLock(m_callbackLock);
if (stop) for (unsigned i = count; i > 0; --i)
source->Stop(); sources[i - 1]->endpoint->Stop();
CSRWExclusiveLock lock(m_lock); CSRWExclusiveLock lock(m_lock);
m_source = nullptr; for (Source& source : m_sources)
m_backend = 0; {
m_epoch = 0; source.backend = 0;
m_active = false; source.epoch = 0;
m_running = false; source.generation = 0;
m_reserved = false; source.endpoint = nullptr;
m_failed = false; source.running = false;
m_failurePending = 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) void CClipboardHub::ClipboardState(bool available, uint64_t epoch)
{ {
CSRWExclusiveLock callbackLock(m_callbackLock); CSRWExclusiveLock callbackLock(m_callbackLock);
IClipboardSource * source = nullptr; Source * sources[MAX_SOURCES] = {};
uint32_t generation; uint32_t generations[MAX_SOURCES] = {};
unsigned count = 0;
{ {
CSRWExclusiveLock lock(m_lock); CSRWExclusiveLock lock(m_lock);
if (m_stopped) if (m_stopped)
return; return;
if (m_available != available || m_channelEpoch != epoch) const bool changed = m_available != available ||
AdvanceGenerationNL(); m_channelEpoch != epoch || m_resetting;
m_available = available; m_available = available;
m_channelEpoch = epoch; m_channelEpoch = epoch;
generation = m_generation; m_resetting = false;
if (m_active && m_running && !m_failed) if (!changed)
source = m_source; 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) for (unsigned i = 0; i < count; ++i)
source->ClipboardState(available, generation); sources[i]->endpoint->ClipboardState(available, generations[i]);
} }
ClipboardChannelResult CClipboardHub::ClipboardRecord( ClipboardChannelResult CClipboardHub::ClipboardRecord(
@@ -332,24 +532,39 @@ ClipboardChannelResult CClipboardHub::ClipboardRecord(
return ClipboardChannelResult::FAILED; return ClipboardChannelResult::FAILED;
CSRWExclusiveLock callbackLock(m_callbackLock); CSRWExclusiveLock callbackLock(m_callbackLock);
IClipboardSource * source = nullptr; Source * source = nullptr;
uint32_t generation; IClipboardSource * endpoint = nullptr;
ClipboardSourceId ownerId = {};
uint32_t generation = 0;
{ {
CSRWSharedLock lock(m_lock); CSRWSharedLock lock(m_lock);
if (m_stopped || !m_available) if (m_stopped || !m_available)
return ClipboardChannelResult::ACCEPTED; return ClipboardChannelResult::ACCEPTED;
if (m_failed || !m_active || !m_running || !m_source) source = m_owner;
if (!source || !BindingValidNL(*source))
return HoldOrDiscard(record); return HoldOrDiscard(record);
source = m_source; endpoint = source->endpoint;
generation = m_generation; ownerId = m_ownerId;
generation = source->generation;
} }
// Helper records are transport-agnostic. Stamp them only after selecting
// the active transport binding.
KVMFRClipboardMessage stamped = record; KVMFRClipboardMessage stamped = record;
stamped.generation = generation; stamped.generation = generation;
const ClipboardChannelResult result = const ClipboardChannelResult result =
source->SendClipboard(stamped, endpoint->SendClipboard(stamped,
data.empty() ? nullptr : data.data()); 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; return result;
if (MarkFailed(*source)) if (MarkFailed(*source))
@@ -364,59 +579,78 @@ ClipboardChannelResult CClipboardHub::ClipboardRecord(
void CClipboardHub::ClipboardReset(uint64_t epoch, uint32_t reason) void CClipboardHub::ClipboardReset(uint64_t epoch, uint32_t reason)
{ {
CSRWExclusiveLock callbackLock(m_callbackLock); CSRWExclusiveLock callbackLock(m_callbackLock);
IClipboardSource * source = nullptr; Source * sources[MAX_SOURCES] = {};
uint32_t generation; uint32_t generations[MAX_SOURCES] = {};
unsigned count = 0;
{ {
CSRWExclusiveLock lock(m_lock); CSRWExclusiveLock lock(m_lock);
if (m_stopped || m_channelEpoch != epoch) if (m_stopped || m_channelEpoch != epoch)
return; return;
AdvanceGenerationNL(); m_owner = nullptr;
generation = m_generation; m_cachedSource = nullptr;
if (m_active && m_running && !m_failed) m_ownerId = {};
source = m_source; 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) for (unsigned i = 0; i < count; ++i)
source->ClipboardReset(generation, reason); sources[i]->endpoint->ClipboardReset(generations[i], reason);
} }
ClipboardChannelResult CClipboardHub::SendClipboard( ClipboardChannelResult CClipboardHub::SendClipboard(Source& source,
const KVMFRClipboardMessage& record, const uint8_t * data) const KVMFRClipboardMessage& record, const uint8_t * data)
{ {
const ClipboardChannelWrite write = { record, data }; const ClipboardChannelWrite write = { record, data };
size_t accepted = 0; size_t accepted = 0;
return SendClipboardBatch(&write, 1, accepted); return SendClipboardBatch(source, &write, 1, accepted);
} }
ClipboardChannelResult CClipboardHub::SendClipboardBatch( ClipboardChannelResult CClipboardHub::SendClipboardBatch(Source& source,
const ClipboardChannelWrite * records, size_t count, size_t& accepted) const ClipboardChannelWrite * records, size_t count, size_t& accepted)
{ {
accepted = 0; accepted = 0;
if (!records || !count || count > KVMFR_CLIPBOARD_SLOT_COUNT) if (!records || !count || count > KVMFR_CLIPBOARD_SLOT_COUNT)
return ClipboardChannelResult::FAILED; 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. // generation after validation but before the records enter the channel.
CSRWSharedLock lock(m_lock); CSRWExclusiveLock lock(m_lock);
if (m_stopped || !m_available || !m_active || !m_running || m_failed || if (m_stopped || !BindingValidNL(source) || m_owner != &source)
!m_source)
return ClipboardChannelResult::FAILED; return ClipboardChannelResult::FAILED;
if (!m_available || m_resetting || !m_channel.Available())
return ClipboardChannelResult::BUSY;
for (size_t i = 0; i < count; ++i) for (size_t i = 0; i < count; ++i)
if (!ValidClientDirection(records[i].record) || if (!ValidClientDirection(records[i].record) ||
records[i].record.generation != m_generation) records[i].record.generation != source.generation)
return ClipboardChannelResult::FAILED; 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); CSRWSharedLock lock(m_lock);
if (m_stopped || !m_available || !m_active || m_failed || if (m_stopped || !m_available || !BindingValidNL(source) ||
!m_running) m_owner != &source)
return; return;
} }
@@ -425,14 +659,9 @@ void CClipboardHub::ClipboardReceiveReady()
m_channel.Kick(epoch); m_channel.Kick(epoch);
} }
void CClipboardHub::ClipboardFailed() void CClipboardHub::ClipboardFailed(Source& source)
{ {
IClipboardSource * source; if (!MarkFailed(source))
{
CSRWSharedLock lock(m_lock);
source = m_source;
}
if (!source || !MarkFailed(*source))
return; return;
const uint64_t epoch = m_channel.Epoch(); const uint64_t epoch = m_channel.Epoch();

View File

@@ -25,33 +25,77 @@
#include "transport/IClipboardSource.h" #include "transport/IClipboardSource.h"
#include "transport/ITransport.h" #include "transport/ITransport.h"
class CClipboardHub final : public IClipboardTarget, class CClipboardHub final : private IClipboardChannelHandler
private IClipboardChannelHandler
{ {
private: 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_lifecycleLock;
CSRWLock m_callbackLock; CSRWLock m_callbackLock;
CSRWLock m_lock; CSRWLock m_lock;
IClipboardSource * m_source = nullptr; Source m_sources[MAX_SOURCES];
BackendId m_backend = 0; Source * m_owner = nullptr;
uint32_t m_epoch = 0; Source * m_cachedSource = nullptr;
uint64_t m_channelEpoch = 0; ClipboardSourceId m_ownerId;
uint32_t m_generation = 1; KVMFRClipboardMessage m_cachedClipboard = {};
bool m_available = false; uint64_t m_channelEpoch = 0;
bool m_active = false; uint32_t m_generation = 1;
bool m_running = false; bool m_cachedValid = false;
bool m_reserved = false; bool m_available = false;
bool m_failed = false; bool m_resetting = false;
bool m_failurePending = false; bool m_stopped = false;
bool m_stopped = false;
void AdvanceGenerationNL(); uint32_t NextGenerationNL();
bool MarkFailed(IClipboardSource& source); 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( ClipboardChannelResult HoldOrDiscard(
const KVMFRClipboardMessage& record); 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; void ClipboardState(bool available, uint64_t epoch) override;
ClipboardChannelResult ClipboardRecord( ClipboardChannelResult ClipboardRecord(
const KVMFRClipboardMessage& record, const KVMFRClipboardMessage& record,
@@ -69,13 +113,4 @@ public:
void Unbind(BackendId backend, uint32_t epoch); void Unbind(BackendId backend, uint32_t epoch);
bool TakeFailure(SourceKey& source); bool TakeFailure(SourceKey& source);
void Stop(); 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> #include <stdint.h>
struct ClipboardSourceId
{
uint64_t client = 0;
uint64_t generation = 0;
};
enum class ClipboardClaimResult
{
ACCEPTED,
BUSY,
UNAVAILABLE,
STALE,
};
class IClipboardTarget class IClipboardTarget
{ {
public: public:
virtual ~IClipboardTarget() = default; 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 // Sends one client-to-Helper record. record.generation must be the binding
// generation most recently supplied by ClipboardState; transports which // generation most recently supplied by ClipboardState; transports which
// expose their own endpoint generation validate and replace it at this // 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); CSRWExclusiveLock lock(m_lock);
m_target = &target; m_target = &target;
m_failed = false; m_hubOwner = {};
m_statusDirty = true; m_hubReleasePending = false;
m_failed = false;
m_statusDirty = true;
} }
m_thread = CreateThread(nullptr, 0, ThreadProc, this, 0, nullptr); m_thread = CreateThread(nullptr, 0, ThreadProc, this, 0, nullptr);
if (!m_thread) if (!m_thread)
@@ -446,11 +448,13 @@ void CLGMPClipboardTransport::Stop()
CSRWExclusiveLock lock(m_lock); CSRWExclusiveLock lock(m_lock);
DropPendingTarget(); DropPendingTarget();
ResetProtocol(false); ResetProtocol(false);
m_available = false; m_available = false;
m_endpointGeneration = 0; m_endpointGeneration = 0;
m_target = nullptr; m_target = nullptr;
m_failed = false; m_hubOwner = {};
m_statusDirty = true; m_hubReleasePending = false;
m_failed = false;
m_statusDirty = true;
} }
bool CLGMPClipboardTransport::IsOwner( bool CLGMPClipboardTransport::IsOwner(
@@ -462,15 +466,20 @@ bool CLGMPClipboardTransport::IsOwner(
bool CLGMPClipboardTransport::OwnerSubscribed() const bool CLGMPClipboardTransport::OwnerSubscribed() const
{ {
if (!m_ownerClientID) return ClientSubscribed(m_ownerClientID);
}
bool CLGMPClipboardTransport::ClientSubscribed(uint32_t clientID) const
{
if (!clientID || !m_queue)
return false; return false;
uint32_t clients[LGMP_MAX_CLIENTS] = {}; uint32_t clients[LGMP_MAX_CLIENTS] = {};
unsigned count = 0; unsigned count = 0;
if (lgmpHostGetClientIDs(m_queue, clients, &count) != LGMP_OK) if (lgmpHostGetClientIDs(m_queue, clients, &count) != LGMP_OK)
return false; return false;
for (unsigned i = 0; i < count; ++i) for (unsigned i = 0; i < count; ++i)
if (clients[i] == m_ownerClientID) if (clients[i] == clientID)
return true; return true;
return false; return false;
} }
@@ -938,28 +947,28 @@ void CLGMPClipboardTransport::FinishOwnerRelease()
if (!m_ownerClientID) if (!m_ownerClientID)
return; return;
const uint32_t clientID = m_ownerClientID; const uint32_t clientID = m_ownerClientID;
const uint32_t generation = m_ownerGeneration; const uint32_t generation = m_ownerGeneration;
const char * const reason = m_ownerReleaseReason ? const char * const reason = m_ownerReleaseReason ?
m_ownerReleaseReason : "unspecified"; m_ownerReleaseReason : "unspecified";
const Transfer clientToHelper = m_clientToHelper; const Transfer clientToHelper = m_clientToHelper;
const Transfer helperToClient = m_helperToClient; const Transfer helperToClient = m_helperToClient;
const bool clearHelper = m_releaseClearHelper; const bool clearHelper = m_releaseClearHelper;
m_ownerClientID = 0; m_ownerClientID = 0;
m_ownerGeneration = 0; m_ownerGeneration = 0;
m_ownerReleasing = false; m_ownerReleasing = false;
m_releaseClearHelper = false; m_releaseClearHelper = false;
m_ownerReleaseReason = nullptr; m_ownerReleaseReason = nullptr;
m_ownerDeadline = 0; m_ownerDeadline = 0;
m_ownerDrainDeadline = 0; m_ownerDrainDeadline = 0;
m_replayPending = false; m_replayPending = false;
ClearQueuedStreamTargets(); ClearQueuedStreamTargets();
m_discardClientToHelper = 0; m_discardClientToHelper = 0;
m_clientToHelper.Clear(); m_clientToHelper.Clear();
m_helperToClient.Clear(); m_helperToClient.Clear();
if (helperToClient.Active()) if (helperToClient.Active())
m_discardHelperToClient = helperToClient.transfer; m_discardHelperToClient = helperToClient.transfer;
m_statusDirty = true; m_statusDirty = true;
if (clearHelper) if (clearHelper)
{ {
@@ -972,8 +981,9 @@ void CLGMPClipboardTransport::FinishOwnerRelease()
{ {
m_files.Reset(); m_files.Reset();
m_clientClipboardGeneration = 0; m_clientClipboardGeneration = 0;
m_clientFormats = 0; m_clientFormats = 0;
} }
m_hubReleasePending = m_hubOwner.client != 0;
DEBUG_INFO("Clipboard owner %u generation %u released (%s)", DEBUG_INFO("Clipboard owner %u generation %u released (%s)",
clientID, generation, reason); clientID, generation, reason);
@@ -1002,8 +1012,27 @@ bool CLGMPClipboardTransport::RetryOwnerRelease()
return !m_failed; 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) void CLGMPClipboardTransport::ResetProtocol(bool keepClipboard)
{ {
DropPendingClaim();
DropPendingTarget(); DropPendingTarget();
if (m_ownerClientID) if (m_ownerClientID)
ReleaseOwner("endpoint reset", false); 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( bool CLGMPClipboardTransport::ProcessMessage(
uint32_t clientID, const KVMFRClipboardMessage& message) uint32_t clientID, const KVMFRClipboardMessage& message)
{ {
@@ -1532,24 +1659,14 @@ bool CLGMPClipboardTransport::ProcessMessage(
RenewLease(); RenewLease();
return true; return true;
} }
if (m_pendingClaim.valid)
if (!BindStreams(clientID))
{
DeInitStreams();
m_failed = true;
if (m_stopEvent)
SetEvent(m_stopEvent);
return true; return true;
}
m_ownerClientID = clientID; m_pendingClaim.valid = true;
m_ownerGeneration = message.generation; m_pendingClaim.clientID = clientID;
RenewLease(); m_pendingClaim.record = message;
m_statusDirty = true; (void)RetryClaim();
m_replayPending = m_cachedValid; return false;
DEBUG_INFO("Clipboard owner %u generation %u acquired",
m_ownerClientID, m_ownerGeneration);
return true;
} }
if (!IsOwner(clientID, message.generation)) if (!IsOwner(clientID, message.generation))
@@ -1625,12 +1742,14 @@ bool CLGMPClipboardTransport::ProcessMessage(
bool CLGMPClipboardTransport::DrainMessage() 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] = {}; uint8_t data[LGMP_MSGS_SIZE] = {};
size_t size = 0;
uint32_t clientID = 0; size_t size = 0;
const LGMP_STATUS status = lgmpHostReadDataWithSource( uint32_t clientID = 0;
const LGMP_STATUS status = lgmpHostReadDataWithSource(
m_queue, data, &size, &clientID); m_queue, data, &size, &clientID);
if (status == LGMP_ERR_QUEUE_EMPTY) if (status == LGMP_ERR_QUEUE_EMPTY)
return true; return true;
@@ -1663,7 +1782,7 @@ bool CLGMPClipboardTransport::DrainMessage()
} }
} }
} }
return true; return !m_failed;
} }
ClipboardChannelResult CLGMPClipboardTransport::SendControl( ClipboardChannelResult CLGMPClipboardTransport::SendControl(
@@ -1927,6 +2046,7 @@ void CLGMPClipboardTransport::ClipboardState(
if (generation != m_endpointGeneration || available != m_available) if (generation != m_endpointGeneration || available != m_available)
{ {
DropPendingClaim();
DropPendingTarget(); DropPendingTarget();
ClearStreamTargets(); ClearStreamTargets();
m_internalTargetCount = 0; m_internalTargetCount = 0;
@@ -1958,6 +2078,7 @@ void CLGMPClipboardTransport::ClipboardReset(
CSRWExclusiveLock lock(m_lock); CSRWExclusiveLock lock(m_lock);
if (!generation) if (!generation)
return; return;
DropPendingClaim();
DropPendingTarget(); DropPendingTarget();
ClearStreamTargets(); ClearStreamTargets();
m_internalTargetCount = 0; m_internalTargetCount = 0;
@@ -2009,10 +2130,10 @@ void CLGMPClipboardTransport::Thread()
bool notifyFailed = false; bool notifyFailed = false;
for (;;) for (;;)
{ {
uint32_t waitUs = IDLE_POLL_MS * 1000U; uint32_t waitUs = IDLE_POLL_MS * 1000U;
bool streamReceived = false; bool streamReceived = false;
bool notifyReady = false; bool notifyReady = false;
IClipboardTarget * readyTarget = nullptr; IClipboardTarget * readyTarget = nullptr;
{ {
CSRWExclusiveLock lock(m_lock); CSRWExclusiveLock lock(m_lock);
if (m_failed) if (m_failed)
@@ -2033,7 +2154,7 @@ void CLGMPClipboardTransport::Thread()
// A restarted client can reuse dataset and transfer IDs. Deliver every // A restarted client can reuse dataset and transfer IDs. Deliver every
// old-owner cleanup record before admitting messages from a new owner. // old-owner cleanup record before admitting messages from a new owner.
if (!RetryTarget() || !RetryStreamTargets() || if (!RetryClaim() || !RetryTarget() || !RetryStreamTargets() ||
!PumpInternalTarget() || !PumpInternalTarget() ||
(!m_streamTargetCount && m_internalTargetCount == 0 && (!m_streamTargetCount && m_internalTargetCount == 0 &&
!DrainMessage()) || !DrainMessage()) ||
@@ -2045,14 +2166,16 @@ void CLGMPClipboardTransport::Thread()
notifyFailed = true; notifyFailed = true;
break; break;
} }
ReleaseHubOwner();
if (m_outboundBlocked) if (m_outboundBlocked)
{ {
notifyReady = m_target != nullptr; notifyReady = m_target != nullptr;
readyTarget = m_target; readyTarget = m_target;
} }
if (m_pendingTarget.valid || m_internalTargetCount || if (m_pendingClaim.valid || m_pendingTarget.valid ||
m_streamTargetCount || m_ownerClientID) m_internalTargetCount ||
m_streamTargetCount || m_ownerClientID || m_hubReleasePending)
waitUs = ACTIVE_POLL_MS * 1000U; waitUs = ACTIVE_POLL_MS * 1000U;
if (m_ownerClientID) if (m_ownerClientID)
{ {
@@ -2115,10 +2238,14 @@ void CLGMPClipboardTransport::Thread()
IClipboardTarget * target = nullptr; IClipboardTarget * target = nullptr;
{ {
CSRWExclusiveLock lock(m_lock); CSRWExclusiveLock lock(m_lock);
DropPendingClaim();
DropPendingTarget(); DropPendingTarget();
ClearStreamTargets(); ClearStreamTargets();
if (m_ownerClientID) if (m_ownerClientID)
ReleaseOwner("transport stopped", false, true); ReleaseOwner("transport stopped", false, true);
notifyFailed = notifyFailed || m_failed;
if (!notifyFailed)
ReleaseHubOwner(true);
target = m_target; target = m_target;
} }
if (notifyFailed && target) if (notifyFailed && target)

View File

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

View File

@@ -58,6 +58,16 @@ namespace
return left.client == right.client && left.generation == right.generation; 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) uint64_t Next64(uint64_t value)
{ {
++value; ++value;
@@ -269,6 +279,8 @@ struct CNetworkClipboard::Impl
std::vector<PeerState> m_peers = {}; std::vector<PeerState> m_peers = {};
NetworkClipboardPeer m_owner = {}; NetworkClipboardPeer m_owner = {};
ClipboardSourceId m_hubOwner = {};
bool m_hubReleasePending = false;
uint64_t m_ownerID = 0; uint64_t m_ownerID = 0;
uint64_t m_claimEpoch = 0; uint64_t m_claimEpoch = 0;
uint64_t m_claimSerial = 0; uint64_t m_claimSerial = 0;
@@ -396,6 +408,7 @@ struct CNetworkClipboard::Impl
void PumpSends(); void PumpSends();
void PumpTargets(); void PumpTargets();
void ReleaseHubOwner(bool force = false);
void NotifyTargetReady(); void NotifyTargetReady();
void NotifyFailure(); void NotifyFailure();
void DropOwnerNL(bool clearHelper); void DropOwnerNL(bool clearHelper);
@@ -420,9 +433,11 @@ struct CNetworkClipboard::Impl
std::vector<SendItem>& batch, const KVMFRClipboardMessage& record); std::vector<SendItem>& batch, const KVMFRClipboardMessage& record);
NetworkClipboardPacketResult HandleClaimNL( NetworkClipboardPacketResult HandleClaimNL(
PeerState& peer, const LGNetPacketView& packet, uint64_t nowMs); PeerState& peer, const LGNetPacketView& packet, uint64_t nowMs,
CSRWExclusiveLock& stateLock);
NetworkClipboardPacketResult HandleClipboardNL( 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( NetworkClipboardPacketResult HandleFileNL(
PeerState& peer, const LGNetPacketView& packet, bool bulk); PeerState& peer, const LGNetPacketView& packet, bool bulk);
@@ -704,6 +719,7 @@ void CNetworkClipboard::Impl::PumpSends()
void CNetworkClipboard::Impl::PumpTargets() void CNetworkClipboard::Impl::PumpTargets()
{ {
ReleaseHubOwner();
for (size_t count = 0; count < PROCESS_MAX; ++count) for (size_t count = 0; count < PROCESS_MAX; ++count)
{ {
TargetItem action; TargetItem action;
@@ -758,22 +774,60 @@ void CNetworkClipboard::Impl::PumpTargets()
m_targetBytes -= sizeof(item.record) + item.data.size(); m_targetBytes -= sizeof(item.record) + item.data.size();
m_targets.pop_front(); 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() void CNetworkClipboard::Impl::NotifyTargetReady()
{ {
{
CSRWSharedLock lock(m_lock);
if (m_targetCallbackActive)
return;
}
CSRWExclusiveLock callbackLock(m_targetCallbackLock); CSRWExclusiveLock callbackLock(m_targetCallbackLock);
IClipboardTarget * target = nullptr; IClipboardTarget * target = nullptr;
{ {
CSRWExclusiveLock lock(m_lock); CSRWExclusiveLock lock(m_lock);
if (m_targetCallbackActive || !m_receiveReadyPending) if (!m_receiveReadyPending)
return; return;
m_receiveReadyPending = false; m_receiveReadyPending = false;
target = m_started ? m_target : nullptr; target = m_started ? m_target : nullptr;
@@ -789,33 +843,17 @@ void CNetworkClipboard::Impl::NotifyTargetReady()
void CNetworkClipboard::Impl::NotifyFailure() void CNetworkClipboard::Impl::NotifyFailure()
{ {
{ CSRWExclusiveLock callbackLock(m_targetCallbackLock);
CSRWSharedLock lock(m_lock); IClipboardTarget * target = nullptr;
if (m_targetCallbackActive)
return;
}
bool notify = false;
{ {
CSRWExclusiveLock lock(m_lock); CSRWExclusiveLock lock(m_lock);
if (!m_targetCallbackActive && m_failed && !m_failureNotified && m_target) if (!m_failed || m_failureNotified || !m_target)
{ return;
m_failureNotified = true; m_failureNotified = true;
m_targetCallbackActive = true; m_targetCallbackActive = true;
notify = true; target = m_target;
}
} }
if (!notify) target->ClipboardFailed();
return;
CSRWExclusiveLock callbackLock(m_targetCallbackLock);
IClipboardTarget * target;
{
CSRWSharedLock lock(m_lock);
target = m_target;
}
if (target)
target->ClipboardFailed();
{ {
CSRWExclusiveLock lock(m_lock); CSRWExclusiveLock lock(m_lock);
m_targetCallbackActive = false; m_targetCallbackActive = false;
@@ -1101,13 +1139,15 @@ void CNetworkClipboard::Impl::DropOwnerNL(bool clearHelper)
if (!m_claimEpoch) if (!m_claimEpoch)
return; return;
bool cleanupQueued = true;
if (clearHelper && m_remoteOffer.published && m_remoteOffer.offerID) if (clearHelper && m_remoteOffer.published && m_remoteOffer.offerID)
{ {
TargetItem item; TargetItem item;
item.record.version = KVMFR_CLIPBOARD_VERSION; item.record.version = KVMFR_CLIPBOARD_VERSION;
item.record.type = KVMFR_CLIPBOARD_MESSAGE_CLEAR; item.record.type = KVMFR_CLIPBOARD_MESSAGE_CLEAR;
item.record.clipboardGeneration = m_remoteOffer.offerID; item.record.clipboardGeneration = m_remoteOffer.offerID;
QueueTargetNL(std::move(item)); if (!QueueTargetNL(std::move(item)))
cleanupQueued = false;
} }
if (clearHelper) if (clearHelper)
@@ -1120,7 +1160,8 @@ void CNetworkClipboard::Impl::DropOwnerNL(bool clearHelper)
item.record.clipboardGeneration = transfer.offerID; item.record.clipboardGeneration = transfer.offerID;
item.record.transfer = transfer.localID; item.record.transfer = transfer.localID;
item.record.format = transfer.format; item.record.format = transfer.format;
QueueTargetNL(std::move(item)); if (!QueueTargetNL(std::move(item)))
cleanupQueued = false;
} }
for (const FileTransfer& transfer : m_fileTransfers) for (const FileTransfer& transfer : m_fileTransfers)
@@ -1132,7 +1173,8 @@ void CNetworkClipboard::Impl::DropOwnerNL(bool clearHelper)
item.record.transfer = transfer.localID; item.record.transfer = transfer.localID;
item.record.format = KVMFR_CLIPBOARD_FORMAT_FILES; item.record.format = KVMFR_CLIPBOARD_FORMAT_FILES;
item.record.token = KVMFR_CLIPBOARD_FILE_ERROR_DISCONNECTED; 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) 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.type = KVMFR_CLIPBOARD_MESSAGE_FILE_CANCEL;
item.record.token = KVMFR_CLIPBOARD_FILE_ERROR_DISCONNECTED; 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()); m_sends.end());
ResetTransfersNL(); ResetTransfersNL();
if (!cleanupQueued)
m_failed = true;
m_hubReleasePending = m_hubOwner.client != 0;
m_remoteOffer.Clear(); m_remoteOffer.Clear();
m_owner = {}; m_owner = {};
m_ownerID = 0; m_ownerID = 0;
@@ -1184,7 +1230,8 @@ void CNetworkClipboard::Impl::DropOwnerNL(bool clearHelper)
} }
NetworkClipboardPacketResult CNetworkClipboard::Impl::HandleClaimNL( NetworkClipboardPacketResult CNetworkClipboard::Impl::HandleClaimNL(
PeerState& peer, const LGNetPacketView& packet, uint64_t nowMs) PeerState& peer, const LGNetPacketView& packet, uint64_t nowMs,
CSRWExclusiveLock& stateLock)
{ {
if (!DeliveryFlags( if (!DeliveryFlags(
packet.envelope, LG_NET_ENVELOPE_ACK_REQUIRED, false) || packet.envelope, LG_NET_ENVELOPE_ACK_REQUIRED, false) ||
@@ -1225,8 +1272,10 @@ NetworkClipboardPacketResult CNetworkClipboard::Impl::HandleClaimNL(
NetworkClipboardPacketResult::INVALID; 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; peer.lastClaimEpoch = claim.claimEpoch;
return QueueClaimStatusNL(peer.peer, claim.claimEpoch, return QueueClaimStatusNL(peer.peer, claim.claimEpoch,
LG_NET_CLIPBOARD_CLAIM_REJECTED, 0, 0, 2) ? LG_NET_CLIPBOARD_CLAIM_REJECTED, 0, 0, 2) ?
@@ -1234,8 +1283,45 @@ NetworkClipboardPacketResult CNetworkClipboard::Impl::HandleClaimNL(
NetworkClipboardPacketResult::INVALID; NetworkClipboardPacketResult::INVALID;
} }
if (m_claimEpoch) const ClipboardSourceId source =
DropOwnerNL(true); 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_owner = peer.peer;
m_ownerID = claim.ownerID; m_ownerID = claim.ownerID;
@@ -1264,14 +1350,15 @@ NetworkClipboardPacketResult CNetworkClipboard::Impl::HandleClaimNL(
} }
NetworkClipboardPacketResult CNetworkClipboard::Impl::HandleClipboardNL( 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; const LGNetEnvelope& envelope = packet.envelope;
if (envelope.messageType == LG_NET_CLIPBOARD_MESSAGE_CLAIM) if (envelope.messageType == LG_NET_CLIPBOARD_MESSAGE_CLAIM)
{ {
if (bulk) if (bulk)
return NetworkClipboardPacketResult::INVALID; return NetworkClipboardPacketResult::INVALID;
return HandleClaimNL(peer, packet, nowMs); return HandleClaimNL(peer, packet, nowMs, stateLock);
} }
const bool transferData = const bool transferData =
@@ -3176,15 +3263,21 @@ void CNetworkClipboard::DetachSender()
{ {
CSRWExclusiveLock lifecycleLock(m_impl->m_lifecycleLock); CSRWExclusiveLock lifecycleLock(m_impl->m_lifecycleLock);
CSRWExclusiveLock processLock(m_impl->m_processLock); CSRWExclusiveLock processLock(m_impl->m_processLock);
CSRWExclusiveLock callbackLock(m_impl->m_senderCallbackLock); {
CSRWExclusiveLock lock(m_impl->m_lock); CSRWExclusiveLock callbackLock(m_impl->m_senderCallbackLock);
if (!m_impl->m_sender) CSRWExclusiveLock lock(m_impl->m_lock);
return; if (!m_impl->m_sender)
m_impl->DropOwnerNL(true); return;
m_impl->m_sender = nullptr; m_impl->DropOwnerNL(true);
m_impl->m_peers.clear(); m_impl->m_sender = nullptr;
m_impl->m_sends.clear(); m_impl->m_peers.clear();
m_impl->m_sendBytes = 0; 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) bool CNetworkClipboard::PeerConnected(const NetworkClipboardPeer& peer)
@@ -3280,6 +3373,7 @@ NetworkClipboardPacketResult CNetworkClipboard::HandlePacket(
CSRWExclusiveLock processLock(m_impl->m_processLock); CSRWExclusiveLock processLock(m_impl->m_processLock);
NetworkClipboardPacketResult result; NetworkClipboardPacketResult result;
{ {
CSRWExclusiveLock callbackLock(m_impl->m_targetCallbackLock);
CSRWExclusiveLock lock(m_impl->m_lock); CSRWExclusiveLock lock(m_impl->m_lock);
Impl::PeerState * state = m_impl->FindPeerNL(peer); Impl::PeerState * state = m_impl->FindPeerNL(peer);
if (!state) if (!state)
@@ -3304,7 +3398,8 @@ NetworkClipboardPacketResult CNetworkClipboard::HandlePacket(
return NetworkClipboardPacketResult::INVALID; return NetworkClipboardPacketResult::INVALID;
result = packet.envelope.service == LG_NET_SERVICE_CLIPBOARD ? 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); m_impl->HandleFileNL(*state, packet, bulk);
} }
@@ -3373,10 +3468,12 @@ bool CNetworkClipboard::Start(IClipboardTarget& target)
CSRWExclusiveLock lock(m_impl->m_lock); CSRWExclusiveLock lock(m_impl->m_lock);
if (m_impl->m_started || m_impl->m_target) if (m_impl->m_started || m_impl->m_target)
return false; return false;
m_impl->m_target = &target; m_impl->m_target = &target;
m_impl->m_started = true; m_impl->m_started = true;
m_impl->m_failed = false; m_impl->m_failed = false;
m_impl->m_failureNotified = false; m_impl->m_failureNotified = false;
m_impl->m_hubOwner = {};
m_impl->m_hubReleasePending = false;
return true; return true;
} }
@@ -3385,23 +3482,52 @@ void CNetworkClipboard::Stop()
CSRWExclusiveLock lifecycleLock(m_impl->m_lifecycleLock); CSRWExclusiveLock lifecycleLock(m_impl->m_lifecycleLock);
CSRWExclusiveLock processLock(m_impl->m_processLock); CSRWExclusiveLock processLock(m_impl->m_processLock);
CSRWExclusiveLock callbackLock(m_impl->m_targetCallbackLock); CSRWExclusiveLock callbackLock(m_impl->m_targetCallbackLock);
CSRWExclusiveLock lock(m_impl->m_lock); IClipboardTarget * target = nullptr;
m_impl->m_target = nullptr; ClipboardSourceId source = {};
m_impl->m_started = false; bool release = false;
m_impl->m_stateKnown = false; {
m_impl->m_available = false; CSRWExclusiveLock lock(m_impl->m_lock);
m_impl->m_generation = 0; if (m_impl->m_claimEpoch)
m_impl->m_failed = false; m_impl->DropOwnerNL(false);
m_impl->m_failureNotified = false; target = m_impl->m_target;
m_impl->m_targetCallbackActive = false; source = m_impl->m_hubOwner;
m_impl->m_targets.clear(); release = target && source.client &&
m_impl->m_targetBytes = 0; source.generation;
m_impl->ResetTransfersNL(); m_impl->m_targetCallbackActive = release;
m_impl->m_remoteOffer.published = false; }
m_impl->m_sourceBlocked = false;
m_impl->m_sourceBlockedService = 0; if (release)
m_impl->m_sourceBlockedTransfer = 0; target->Release(source);
m_impl->m_receiveReadyPending = false;
{
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) void CNetworkClipboard::ClipboardState(bool available, uint32_t generation)