diff --git a/idd/LGIdd/transport/CClipboardHub.cpp b/idd/LGIdd/transport/CClipboardHub.cpp index 9fcc6a7a..4531634b 100644 --- a/idd/LGIdd/transport/CClipboardHub.cpp +++ b/idd/LGIdd/transport/CClipboardHub.cpp @@ -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(); diff --git a/idd/LGIdd/transport/CClipboardHub.h b/idd/LGIdd/transport/CClipboardHub.h index 49d8c15c..076ae542 100644 --- a/idd/LGIdd/transport/CClipboardHub.h +++ b/idd/LGIdd/transport/CClipboardHub.h @@ -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; }; diff --git a/idd/LGIdd/transport/IClipboardSource.h b/idd/LGIdd/transport/IClipboardSource.h index 533b58c1..460dd3e6 100644 --- a/idd/LGIdd/transport/IClipboardSource.h +++ b/idd/LGIdd/transport/IClipboardSource.h @@ -25,11 +25,32 @@ #include +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 diff --git a/idd/LGIdd/transport/lgmp/CLGMPClipboardTransport.cpp b/idd/LGIdd/transport/lgmp/CLGMPClipboardTransport.cpp index b7eecb9a..41d9f14f 100644 --- a/idd/LGIdd/transport/lgmp/CLGMPClipboardTransport.cpp +++ b/idd/LGIdd/transport/lgmp/CLGMPClipboardTransport.cpp @@ -398,9 +398,11 @@ bool CLGMPClipboardTransport::Start(IClipboardTarget& target) { CSRWExclusiveLock lock(m_lock); - m_target = ⌖ - m_failed = false; - m_statusDirty = true; + m_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) diff --git a/idd/LGIdd/transport/lgmp/CLGMPClipboardTransport.h b/idd/LGIdd/transport/lgmp/CLGMPClipboardTransport.h index 16515478..8c94fcaf 100644 --- a/idd/LGIdd/transport/lgmp/CLGMPClipboardTransport.h +++ b/idd/LGIdd/transport/lgmp/CLGMPClipboardTransport.h @@ -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(); diff --git a/idd/LGIdd/transport/network/CNetworkClipboard.cpp b/idd/LGIdd/transport/network/CNetworkClipboard.cpp index 45c18bfe..a6510e7c 100644 --- a/idd/LGIdd/transport/network/CNetworkClipboard.cpp +++ b/idd/LGIdd/transport/network/CNetworkClipboard.cpp @@ -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(peer.generation) << 32U) | peer.client, + claimEpoch, + }; + } + uint64_t Next64(uint64_t value) { ++value; @@ -269,6 +279,8 @@ struct CNetworkClipboard::Impl std::vector 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& 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 = ⌖ - m_impl->m_started = true; - m_impl->m_failed = false; - m_impl->m_failureNotified = false; + m_impl->m_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)