mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-11-12 14:57:20 +00:00
[idd] network: add transport session foundation
This commit is contained in:
@@ -88,6 +88,9 @@
|
||||
<ClCompile Include="transport\lgmp\CLGMPHost.cpp" />
|
||||
<ClCompile Include="transport\lgmp\CLGMPInputTransport.cpp" />
|
||||
<ClCompile Include="transport\lgmp\CLGMPTransport.cpp" />
|
||||
<ClCompile Include="transport\network\CNetworkSession.cpp" />
|
||||
<ClCompile Include="transport\network\CNetworkTransport.cpp" />
|
||||
<ClCompile Include="transport\network\NetworkSettings.cpp" />
|
||||
<ClCompile Include="config\CSettings.cpp" />
|
||||
<ClCompile Include="platform\CPlatformInfo.cpp" />
|
||||
</ItemGroup>
|
||||
@@ -168,6 +171,9 @@
|
||||
<ClInclude Include="transport\lgmp\CLGMPHost.h" />
|
||||
<ClInclude Include="transport\lgmp\CLGMPInputTransport.h" />
|
||||
<ClInclude Include="transport\lgmp\CLGMPTransport.h" />
|
||||
<ClInclude Include="transport\network\CNetworkSession.h" />
|
||||
<ClInclude Include="transport\network\CNetworkTransport.h" />
|
||||
<ClInclude Include="transport\network\NetworkSettings.h" />
|
||||
<ClInclude Include="config\CSettings.h" />
|
||||
<ClInclude Include="platform\CPlatformInfo.h" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -35,6 +35,9 @@
|
||||
<Filter Include="Transport\LGMP">
|
||||
<UniqueIdentifier>{530EE339-4AD6-4AE7-9A74-09420E73D04B}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Transport\Network">
|
||||
<UniqueIdentifier>{9B627995-4F7D-4F7D-AE88-AD0AA785880D}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Configuration">
|
||||
<UniqueIdentifier>{82810F1C-E51C-4DBD-86B2-1CDE8C16A2A9}</UniqueIdentifier>
|
||||
</Filter>
|
||||
@@ -283,6 +286,15 @@
|
||||
<ClInclude Include="transport\lgmp\CLGMPTransport.h">
|
||||
<Filter>Transport\LGMP</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="transport\network\CNetworkSession.h">
|
||||
<Filter>Transport\Network</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="transport\network\CNetworkTransport.h">
|
||||
<Filter>Transport\Network</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="transport\network\NetworkSettings.h">
|
||||
<Filter>Transport\Network</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="config\CSettings.h">
|
||||
<Filter>Configuration</Filter>
|
||||
</ClInclude>
|
||||
@@ -459,6 +471,15 @@
|
||||
<ClCompile Include="transport\lgmp\CLGMPTransport.cpp">
|
||||
<Filter>Transport\LGMP</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="transport\network\CNetworkSession.cpp">
|
||||
<Filter>Transport\Network</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="transport\network\CNetworkTransport.cpp">
|
||||
<Filter>Transport\Network</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="transport\network\NetworkSettings.cpp">
|
||||
<Filter>Transport\Network</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="config\CSettings.cpp">
|
||||
<Filter>Configuration</Filter>
|
||||
</ClCompile>
|
||||
|
||||
@@ -649,7 +649,7 @@ bool CDeviceContext::SetupTransport(size_t alignSize)
|
||||
{
|
||||
// Frame buffers cannot be allocated until the GPU-specific alignment is
|
||||
// known. The swap-chain path may call this again after setup completed.
|
||||
if (!m_transport->Frames().GetMaxFrameSize())
|
||||
if (!m_transport->SetupReady())
|
||||
{
|
||||
if (!InitializeTransport() || !m_transport->Setup(alignSize))
|
||||
return false;
|
||||
|
||||
@@ -1256,6 +1256,44 @@ bool CTransportManager::Setup(size_t alignment)
|
||||
return success;
|
||||
}
|
||||
|
||||
bool CTransportManager::SetupReady() const
|
||||
{
|
||||
CTransportManager * manager = const_cast<CTransportManager *>(this);
|
||||
if (!manager->BeginPhase(Phase::ACCESS, true))
|
||||
return false;
|
||||
|
||||
Entry * entries[FRAME_MAX_SINKS] = {};
|
||||
Entry * primary = nullptr;
|
||||
unsigned count = 0;
|
||||
bool setup = false;
|
||||
{
|
||||
CSRWSharedLock managerLock(m_lock);
|
||||
primary = m_primary;
|
||||
setup = m_setup && !m_stopping && !m_stopped;
|
||||
count = m_entryCount;
|
||||
for (unsigned i = 0; i < count; ++i)
|
||||
entries[i] = m_entries[i].get();
|
||||
}
|
||||
bool primaryReady = false;
|
||||
bool ready = setup && primary != nullptr;
|
||||
for (unsigned i = 0; ready && i < count; ++i)
|
||||
{
|
||||
Entry& entry = *entries[i];
|
||||
CSRWSharedLock entryLock(entry.lock);
|
||||
if (entry.required &&
|
||||
(entry.state != State::READY || !entry.setupDone))
|
||||
{
|
||||
ready = false;
|
||||
break;
|
||||
}
|
||||
if (&entry == primary)
|
||||
primaryReady = entry.state == State::READY &&
|
||||
entry.setupDone && entry.frameAdded;
|
||||
}
|
||||
manager->EndPhase();
|
||||
return ready && primaryReady;
|
||||
}
|
||||
|
||||
CfgResult CTransportManager::Cfg(const GraphCfg& cfg,
|
||||
uint64_t revision, CFrameGraph& graph, ITexStage * activation)
|
||||
{
|
||||
|
||||
@@ -195,6 +195,9 @@ public:
|
||||
OpenResult Open();
|
||||
bool Initialize();
|
||||
bool Setup(size_t alignment);
|
||||
// Unlike the legacy frame-size sentinel, this also recognizes a primary
|
||||
// texture route and verifies every required transport completed setup.
|
||||
bool SetupReady() const;
|
||||
CfgResult Cfg(const GraphCfg& cfg, uint64_t revision,
|
||||
CFrameGraph& graph, ITexStage * activation = nullptr);
|
||||
ProcessResult Process(ITransportActions& actions);
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "config/CSettings.h"
|
||||
#include "transport/CTransportManager.h"
|
||||
#include "transport/lgmp/CLGMPTransport.h"
|
||||
#include "transport/network/CNetworkTransport.h"
|
||||
|
||||
#include <new>
|
||||
|
||||
@@ -41,14 +42,23 @@ namespace
|
||||
new (std::nothrow) CLGMPTransport(config));
|
||||
}
|
||||
|
||||
std::unique_ptr<ITransport> CreateNetwork(
|
||||
const TransportInstance& config)
|
||||
{
|
||||
return std::unique_ptr<ITransport>(
|
||||
new (std::nothrow) CNetworkTransport(config));
|
||||
}
|
||||
|
||||
const TransportKind KINDS[] =
|
||||
{
|
||||
{ L"LGMP", 1 },
|
||||
{ L"LGMP", 1 },
|
||||
{ L"NETWORK", 1 },
|
||||
};
|
||||
|
||||
const Provider PROVIDERS[] =
|
||||
{
|
||||
{ CreateLGMP },
|
||||
{ CreateLGMP },
|
||||
{ CreateNetwork },
|
||||
};
|
||||
|
||||
static_assert(ARRAYSIZE(KINDS) == ARRAYSIZE(PROVIDERS),
|
||||
|
||||
415
idd/LGIdd/transport/network/CNetworkSession.cpp
Normal file
415
idd/LGIdd/transport/network/CNetworkSession.cpp
Normal file
@@ -0,0 +1,415 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2026 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "transport/network/CNetworkSession.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
bool CNetworkEventQueue::Push(const NetworkEvent& event)
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (m_count == CAPACITY)
|
||||
return false;
|
||||
|
||||
const unsigned tail = (m_head + m_count) % CAPACITY;
|
||||
m_events[tail] = event;
|
||||
++m_count;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CNetworkEventQueue::Take(NetworkEvent& event)
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (!m_count)
|
||||
return false;
|
||||
|
||||
event = m_events[m_head];
|
||||
m_events[m_head] = {};
|
||||
m_head = (m_head + 1) % CAPACITY;
|
||||
--m_count;
|
||||
return true;
|
||||
}
|
||||
|
||||
void CNetworkEventQueue::Reset()
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
for (NetworkEvent& event : m_events)
|
||||
event = {};
|
||||
m_head = 0;
|
||||
m_count = 0;
|
||||
}
|
||||
|
||||
bool CNetworkInputSource::Start(IInputTarget& target)
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (m_target && m_target != &target)
|
||||
return false;
|
||||
m_target = ⌖
|
||||
return true;
|
||||
}
|
||||
|
||||
void CNetworkInputSource::Stop()
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
m_target = nullptr;
|
||||
}
|
||||
|
||||
InputTargetState CNetworkInputSource::GetState(
|
||||
const InputSourceId& source) const
|
||||
{
|
||||
CSRWSharedLock lock(m_lock);
|
||||
return m_target ? m_target->GetState(source) : InputTargetState {};
|
||||
}
|
||||
|
||||
InputResult CNetworkInputSource::Claim(const InputSourceId& source)
|
||||
{
|
||||
CSRWSharedLock lock(m_lock);
|
||||
return m_target ?
|
||||
m_target->Claim(source) : InputResult::UNAVAILABLE;
|
||||
}
|
||||
|
||||
InputResult CNetworkInputSource::Touch(const InputSourceId& source)
|
||||
{
|
||||
CSRWSharedLock lock(m_lock);
|
||||
return m_target ?
|
||||
m_target->Touch(source) : InputResult::UNAVAILABLE;
|
||||
}
|
||||
|
||||
InputResult CNetworkInputSource::Release(
|
||||
const InputSourceId& source, bool reset)
|
||||
{
|
||||
CSRWSharedLock lock(m_lock);
|
||||
return m_target ?
|
||||
m_target->Release(source, reset) : InputResult::UNAVAILABLE;
|
||||
}
|
||||
|
||||
InputResult CNetworkInputSource::SendMouseRelative(
|
||||
const InputSourceId& source, int32_t deltaX, int32_t deltaY,
|
||||
int32_t wheel, uint32_t buttons)
|
||||
{
|
||||
CSRWSharedLock lock(m_lock);
|
||||
return m_target ? m_target->SendMouseRelative(
|
||||
source, deltaX, deltaY, wheel, buttons) : InputResult::UNAVAILABLE;
|
||||
}
|
||||
|
||||
InputResult CNetworkInputSource::SendMouseAbsolute(
|
||||
const InputSourceId& source, uint16_t x, uint16_t y,
|
||||
int32_t wheel, uint32_t buttons)
|
||||
{
|
||||
CSRWSharedLock lock(m_lock);
|
||||
return m_target ? m_target->SendMouseAbsolute(
|
||||
source, x, y, wheel, buttons) : InputResult::UNAVAILABLE;
|
||||
}
|
||||
|
||||
InputResult CNetworkInputSource::SendKeyboard(
|
||||
const InputSourceId& source, uint8_t modifiers, const uint8_t * keys)
|
||||
{
|
||||
CSRWSharedLock lock(m_lock);
|
||||
return m_target ? m_target->SendKeyboard(
|
||||
source, modifiers, keys) : InputResult::UNAVAILABLE;
|
||||
}
|
||||
|
||||
InputResult CNetworkInputSource::Reset(const InputSourceId& source)
|
||||
{
|
||||
CSRWSharedLock lock(m_lock);
|
||||
return m_target ?
|
||||
m_target->Reset(source) : InputResult::UNAVAILABLE;
|
||||
}
|
||||
|
||||
void CNetworkControlSink::SetControlEvents(
|
||||
IControlEvents * events, const ControlToken& token)
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
m_events = events;
|
||||
m_token = events ? token : ControlToken {};
|
||||
}
|
||||
|
||||
ControlResult CNetworkControlSink::SendCursor(
|
||||
const IDARG_OUT_QUERY_HWCURSOR& info, const BYTE * data,
|
||||
size_t size, UINT sdrWhiteLevel)
|
||||
{
|
||||
const bool shape = info.CursorShapeInfo.CursorType !=
|
||||
IDDCX_CURSOR_SHAPE_TYPE_UNINITIALIZED;
|
||||
if (shape && info.CursorShapeInfo.Height &&
|
||||
info.CursorShapeInfo.Pitch >
|
||||
SIZE_MAX / info.CursorShapeInfo.Height)
|
||||
return ControlResult::FAILED;
|
||||
const size_t shapeSize = shape ? static_cast<size_t>(
|
||||
info.CursorShapeInfo.Height) * info.CursorShapeInfo.Pitch : 0;
|
||||
if (size != shapeSize || (size && !data))
|
||||
return ControlResult::FAILED;
|
||||
|
||||
std::vector<BYTE> cursorData;
|
||||
if (shapeSize)
|
||||
cursorData.assign(data, data + size);
|
||||
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (!m_events)
|
||||
return ControlResult::RETRY;
|
||||
m_state.cursor.IsCursorVisible = info.IsCursorVisible;
|
||||
m_state.cursor.X = info.X;
|
||||
m_state.cursor.Y = info.Y;
|
||||
m_state.sdrWhiteLevel = sdrWhiteLevel;
|
||||
m_state.cursorValid = true;
|
||||
if (shape)
|
||||
{
|
||||
m_state.cursor.IsCursorShapeUpdated = info.IsCursorShapeUpdated;
|
||||
m_state.cursor.CursorShapeInfo = info.CursorShapeInfo;
|
||||
m_state.cursorData = std::move(cursorData);
|
||||
m_state.shapeValid = true;
|
||||
}
|
||||
++m_state.revision;
|
||||
return ControlResult::APPLIED;
|
||||
}
|
||||
|
||||
ControlResult CNetworkControlSink::SetColorTransform(
|
||||
std::shared_ptr<const D12ColorTransform> transform)
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lock);
|
||||
if (!m_events)
|
||||
return ControlResult::RETRY;
|
||||
m_state.transform = std::move(transform);
|
||||
m_state.transformValid = true;
|
||||
++m_state.revision;
|
||||
return ControlResult::APPLIED;
|
||||
}
|
||||
|
||||
void CNetworkControlSink::RequestReplay()
|
||||
{
|
||||
CSRWSharedLock lock(m_lock);
|
||||
if (m_events)
|
||||
m_events->OnControlReplay(m_token);
|
||||
}
|
||||
|
||||
bool CNetworkControlSink::Snapshot(NetworkControlSnapshot& state) const
|
||||
{
|
||||
CSRWSharedLock lock(m_lock);
|
||||
if (!m_events)
|
||||
return false;
|
||||
state = m_state;
|
||||
return true;
|
||||
}
|
||||
|
||||
CNetworkSession::CNetworkSession(uint32_t client, uint32_t generation,
|
||||
CNetworkEventQueue& events, CNetworkInputSource& input) :
|
||||
m_events(events), m_input(input)
|
||||
{
|
||||
m_source.client = client;
|
||||
m_source.generation = generation;
|
||||
}
|
||||
|
||||
CNetworkSession::~CNetworkSession()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
SourceKey CNetworkSession::EventSource() const
|
||||
{
|
||||
SourceKey source;
|
||||
source.client = m_source.client;
|
||||
source.generation = m_source.generation;
|
||||
return source;
|
||||
}
|
||||
|
||||
bool CNetworkSession::IsOpen() const
|
||||
{
|
||||
CSRWSharedLock lock(m_lifecycleLock);
|
||||
return m_open;
|
||||
}
|
||||
|
||||
void CNetworkSession::Close()
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lifecycleLock);
|
||||
if (!m_open)
|
||||
return;
|
||||
m_open = false;
|
||||
m_input.Release(m_source, true);
|
||||
}
|
||||
|
||||
bool CNetworkSession::SetCursorPosition(int32_t x, int32_t y)
|
||||
{
|
||||
CSRWSharedLock lock(m_lifecycleLock);
|
||||
if (!m_open)
|
||||
return false;
|
||||
|
||||
NetworkEvent event;
|
||||
event.type = NetworkEventType::SET_CURSOR_POSITION;
|
||||
event.source = EventSource();
|
||||
event.x = x;
|
||||
event.y = y;
|
||||
return m_events.Push(event);
|
||||
}
|
||||
|
||||
bool CNetworkSession::SetResolution(uint32_t width, uint32_t height)
|
||||
{
|
||||
CSRWSharedLock lock(m_lifecycleLock);
|
||||
if (!m_open || !width || !height)
|
||||
return false;
|
||||
|
||||
NetworkEvent event;
|
||||
event.type = NetworkEventType::SET_RESOLUTION;
|
||||
event.source = EventSource();
|
||||
event.width = width;
|
||||
event.height = height;
|
||||
return m_events.Push(event);
|
||||
}
|
||||
|
||||
bool CNetworkSession::RequestRecovery(
|
||||
uint64_t session, uint32_t serial, bool active)
|
||||
{
|
||||
CSRWSharedLock lock(m_lifecycleLock);
|
||||
if (!m_open || !session || !serial)
|
||||
return false;
|
||||
|
||||
NetworkEvent event;
|
||||
event.type = NetworkEventType::RECOVERY_REQUEST;
|
||||
event.source = EventSource();
|
||||
event.session = session;
|
||||
event.serial = serial;
|
||||
event.active = active;
|
||||
return m_events.Push(event);
|
||||
}
|
||||
|
||||
void CNetworkSession::ProcessEvent(
|
||||
const NetworkEvent& event, ITransportEvents& events)
|
||||
{
|
||||
CSRWSharedLock lock(m_lifecycleLock);
|
||||
if (!m_open || event.source.client != m_source.client ||
|
||||
event.source.generation != m_source.generation)
|
||||
return;
|
||||
|
||||
switch (event.type)
|
||||
{
|
||||
case NetworkEventType::SET_CURSOR_POSITION:
|
||||
events.OnSetCursorPos(event.source, event.x, event.y);
|
||||
break;
|
||||
|
||||
case NetworkEventType::SET_RESOLUTION:
|
||||
events.OnSetResolution(event.source, event.width, event.height);
|
||||
break;
|
||||
|
||||
case NetworkEventType::RECOVERY_REQUEST:
|
||||
{
|
||||
const RecoveryAdmission admission = events.OnRecoveryRequest(
|
||||
event.source, event.session, event.serial, event.active);
|
||||
if (!admission.complete)
|
||||
break;
|
||||
|
||||
NetworkRecoveryStatus status;
|
||||
status.session = event.session;
|
||||
status.serial = event.serial;
|
||||
status.active = event.active;
|
||||
status.state = admission.state;
|
||||
status.error = admission.error;
|
||||
SetRecoveryStatus(status);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CNetworkSession::SetRecoveryStatus(
|
||||
const NetworkRecoveryStatus& status)
|
||||
{
|
||||
CSRWExclusiveLock lock(m_recoveryLock);
|
||||
if (m_recoveryCount == RECOVERY_CAPACITY)
|
||||
{
|
||||
m_recovery[m_recoveryHead] = {};
|
||||
m_recoveryHead =
|
||||
(m_recoveryHead + 1) % RECOVERY_CAPACITY;
|
||||
--m_recoveryCount;
|
||||
}
|
||||
|
||||
const unsigned tail =
|
||||
(m_recoveryHead + m_recoveryCount) % RECOVERY_CAPACITY;
|
||||
m_recovery[tail] = status;
|
||||
++m_recoveryCount;
|
||||
}
|
||||
|
||||
bool CNetworkSession::TakeRecoveryStatus(NetworkRecoveryStatus& status)
|
||||
{
|
||||
CSRWExclusiveLock lock(m_recoveryLock);
|
||||
if (!m_recoveryCount)
|
||||
return false;
|
||||
|
||||
status = m_recovery[m_recoveryHead];
|
||||
m_recovery[m_recoveryHead] = {};
|
||||
m_recoveryHead =
|
||||
(m_recoveryHead + 1) % RECOVERY_CAPACITY;
|
||||
--m_recoveryCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
InputTargetState CNetworkSession::InputState() const
|
||||
{
|
||||
CSRWSharedLock lock(m_lifecycleLock);
|
||||
return m_open ? m_input.GetState(m_source) : InputTargetState {};
|
||||
}
|
||||
|
||||
InputResult CNetworkSession::ClaimInput()
|
||||
{
|
||||
CSRWSharedLock lock(m_lifecycleLock);
|
||||
return m_open ?
|
||||
m_input.Claim(m_source) : InputResult::UNAVAILABLE;
|
||||
}
|
||||
|
||||
InputResult CNetworkSession::TouchInput()
|
||||
{
|
||||
CSRWSharedLock lock(m_lifecycleLock);
|
||||
return m_open ?
|
||||
m_input.Touch(m_source) : InputResult::UNAVAILABLE;
|
||||
}
|
||||
|
||||
InputResult CNetworkSession::ReleaseInput(bool reset)
|
||||
{
|
||||
CSRWSharedLock lock(m_lifecycleLock);
|
||||
return m_open ?
|
||||
m_input.Release(m_source, reset) : InputResult::UNAVAILABLE;
|
||||
}
|
||||
|
||||
InputResult CNetworkSession::SendMouseRelative(
|
||||
int32_t deltaX, int32_t deltaY, int32_t wheel, uint32_t buttons)
|
||||
{
|
||||
CSRWSharedLock lock(m_lifecycleLock);
|
||||
return m_open ? m_input.SendMouseRelative(
|
||||
m_source, deltaX, deltaY, wheel, buttons) : InputResult::UNAVAILABLE;
|
||||
}
|
||||
|
||||
InputResult CNetworkSession::SendMouseAbsolute(
|
||||
uint16_t x, uint16_t y, int32_t wheel, uint32_t buttons)
|
||||
{
|
||||
CSRWSharedLock lock(m_lifecycleLock);
|
||||
return m_open ? m_input.SendMouseAbsolute(
|
||||
m_source, x, y, wheel, buttons) : InputResult::UNAVAILABLE;
|
||||
}
|
||||
|
||||
InputResult CNetworkSession::SendKeyboard(
|
||||
uint8_t modifiers, const uint8_t * keys)
|
||||
{
|
||||
CSRWSharedLock lock(m_lifecycleLock);
|
||||
return m_open ? m_input.SendKeyboard(
|
||||
m_source, modifiers, keys) : InputResult::UNAVAILABLE;
|
||||
}
|
||||
|
||||
InputResult CNetworkSession::ResetInput()
|
||||
{
|
||||
CSRWSharedLock lock(m_lifecycleLock);
|
||||
return m_open ?
|
||||
m_input.Reset(m_source) : InputResult::UNAVAILABLE;
|
||||
}
|
||||
181
idd/LGIdd/transport/network/CNetworkSession.h
Normal file
181
idd/LGIdd/transport/network/CNetworkSession.h
Normal file
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2026 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CSRWLock.h"
|
||||
#include "transport/IControlSink.h"
|
||||
#include "transport/IInputSource.h"
|
||||
#include "transport/ITransport.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
|
||||
enum class NetworkEventType : uint8_t
|
||||
{
|
||||
SET_CURSOR_POSITION,
|
||||
SET_RESOLUTION,
|
||||
RECOVERY_REQUEST,
|
||||
};
|
||||
|
||||
struct NetworkEvent
|
||||
{
|
||||
NetworkEventType type = NetworkEventType::SET_CURSOR_POSITION;
|
||||
SourceKey source;
|
||||
int32_t x = 0;
|
||||
int32_t y = 0;
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
uint64_t session = 0;
|
||||
uint32_t serial = 0;
|
||||
bool active = false;
|
||||
};
|
||||
|
||||
struct NetworkRecoveryStatus
|
||||
{
|
||||
uint64_t session = 0;
|
||||
uint32_t serial = 0;
|
||||
bool active = false;
|
||||
RecoveryState state = RecoveryState::FAILED;
|
||||
uint32_t error = 0;
|
||||
};
|
||||
|
||||
class CNetworkEventQueue
|
||||
{
|
||||
private:
|
||||
static constexpr unsigned CAPACITY = 128;
|
||||
|
||||
CSRWLock m_lock;
|
||||
NetworkEvent m_events[CAPACITY] = {};
|
||||
unsigned m_head = 0;
|
||||
unsigned m_count = 0;
|
||||
|
||||
public:
|
||||
bool Push(const NetworkEvent& event);
|
||||
bool Take(NetworkEvent& event);
|
||||
void Reset();
|
||||
};
|
||||
|
||||
class CNetworkInputSource final : public IInputSource
|
||||
{
|
||||
private:
|
||||
mutable CSRWLock m_lock;
|
||||
IInputTarget * m_target = nullptr;
|
||||
|
||||
public:
|
||||
bool Start(IInputTarget& target) override;
|
||||
void Stop() override;
|
||||
|
||||
InputTargetState GetState(const InputSourceId& source) const;
|
||||
InputResult Claim(const InputSourceId& source);
|
||||
InputResult Touch(const InputSourceId& source);
|
||||
InputResult Release(const InputSourceId& source, bool reset);
|
||||
InputResult SendMouseRelative(const InputSourceId& source,
|
||||
int32_t deltaX, int32_t deltaY, int32_t wheel, uint32_t buttons);
|
||||
InputResult SendMouseAbsolute(const InputSourceId& source,
|
||||
uint16_t x, uint16_t y, int32_t wheel, uint32_t buttons);
|
||||
InputResult SendKeyboard(const InputSourceId& source,
|
||||
uint8_t modifiers, const uint8_t * keys);
|
||||
InputResult Reset(const InputSourceId& source);
|
||||
};
|
||||
|
||||
struct NetworkControlSnapshot
|
||||
{
|
||||
IDARG_OUT_QUERY_HWCURSOR cursor = {};
|
||||
std::vector<BYTE> cursorData;
|
||||
std::shared_ptr<const D12ColorTransform> transform;
|
||||
UINT sdrWhiteLevel = 0;
|
||||
uint64_t revision = 0;
|
||||
bool cursorValid = false;
|
||||
bool shapeValid = false;
|
||||
bool transformValid = false;
|
||||
};
|
||||
|
||||
class CNetworkControlSink final : public IControlSink
|
||||
{
|
||||
private:
|
||||
mutable CSRWLock m_lock;
|
||||
IControlEvents * m_events = nullptr;
|
||||
ControlToken m_token;
|
||||
NetworkControlSnapshot m_state;
|
||||
|
||||
public:
|
||||
void SetControlEvents(
|
||||
IControlEvents * events, const ControlToken& token) override;
|
||||
ControlResult SendCursor(const IDARG_OUT_QUERY_HWCURSOR& info,
|
||||
const BYTE * data, size_t size, UINT sdrWhiteLevel) override;
|
||||
ControlResult SetColorTransform(
|
||||
std::shared_ptr<const D12ColorTransform> transform) override;
|
||||
|
||||
void RequestReplay();
|
||||
bool Snapshot(NetworkControlSnapshot& state) const;
|
||||
};
|
||||
|
||||
// A session is transport-independent state owned by the eventual QUIC
|
||||
// provider. Decoded requests enter the bounded event queue or the existing
|
||||
// input hub; encoded replies are drained from the bounded status queue.
|
||||
class CNetworkSession final
|
||||
{
|
||||
private:
|
||||
static constexpr unsigned RECOVERY_CAPACITY = 16;
|
||||
|
||||
CNetworkEventQueue& m_events;
|
||||
CNetworkInputSource& m_input;
|
||||
InputSourceId m_source;
|
||||
mutable CSRWLock m_lifecycleLock;
|
||||
bool m_open = true;
|
||||
|
||||
CSRWLock m_recoveryLock;
|
||||
NetworkRecoveryStatus m_recovery[RECOVERY_CAPACITY] = {};
|
||||
unsigned m_recoveryHead = 0;
|
||||
unsigned m_recoveryCount = 0;
|
||||
|
||||
SourceKey EventSource() const;
|
||||
|
||||
public:
|
||||
CNetworkSession(uint32_t client, uint32_t generation,
|
||||
CNetworkEventQueue& events, CNetworkInputSource& input);
|
||||
~CNetworkSession();
|
||||
|
||||
CNetworkSession(const CNetworkSession&) = delete;
|
||||
CNetworkSession& operator=(const CNetworkSession&) = delete;
|
||||
|
||||
InputSourceId Source() const { return m_source; }
|
||||
bool IsOpen() const;
|
||||
void Close();
|
||||
|
||||
bool SetCursorPosition(int32_t x, int32_t y);
|
||||
bool SetResolution(uint32_t width, uint32_t height);
|
||||
bool RequestRecovery(uint64_t session, uint32_t serial, bool active);
|
||||
void ProcessEvent(const NetworkEvent& event, ITransportEvents& events);
|
||||
void SetRecoveryStatus(const NetworkRecoveryStatus& status);
|
||||
bool TakeRecoveryStatus(NetworkRecoveryStatus& status);
|
||||
|
||||
InputTargetState InputState() const;
|
||||
InputResult ClaimInput();
|
||||
InputResult TouchInput();
|
||||
InputResult ReleaseInput(bool reset);
|
||||
InputResult SendMouseRelative(
|
||||
int32_t deltaX, int32_t deltaY, int32_t wheel, uint32_t buttons);
|
||||
InputResult SendMouseAbsolute(
|
||||
uint16_t x, uint16_t y, int32_t wheel, uint32_t buttons);
|
||||
InputResult SendKeyboard(uint8_t modifiers, const uint8_t * keys);
|
||||
InputResult ResetInput();
|
||||
};
|
||||
273
idd/LGIdd/transport/network/CNetworkTransport.cpp
Normal file
273
idd/LGIdd/transport/network/CNetworkTransport.cpp
Normal file
@@ -0,0 +1,273 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2026 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "transport/network/CNetworkTransport.h"
|
||||
|
||||
#include "CDebug.h"
|
||||
|
||||
#include <new>
|
||||
#include <utility>
|
||||
|
||||
static constexpr unsigned EVENT_DRAIN_LIMIT = 64;
|
||||
|
||||
CNetworkTransport::CNetworkTransport(const TransportInstance& config) :
|
||||
m_config(config)
|
||||
{
|
||||
}
|
||||
|
||||
CNetworkTransport::~CNetworkTransport()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
ITransport::OpenResult CNetworkTransport::Open()
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lifecycleLock);
|
||||
if (m_state == State::STOPPED)
|
||||
return OpenResult::FAILURE;
|
||||
if (m_state != State::CONSTRUCTED)
|
||||
return OpenResult::SUCCESS;
|
||||
|
||||
NetworkSettings settings;
|
||||
std::wstring error;
|
||||
if (!ParseNetworkSettings(m_config.settings, settings, &error))
|
||||
{
|
||||
DEBUG_ERROR("Invalid network transport settings: %ls", error.c_str());
|
||||
return OpenResult::FAILURE;
|
||||
}
|
||||
|
||||
m_settings = std::move(settings);
|
||||
m_state = State::OPEN;
|
||||
DEBUG_INFO("Network transport configured on [%ls]:%u for up to %u "
|
||||
"clients (%ls authentication)",
|
||||
m_settings.bindAddress.c_str(),
|
||||
static_cast<unsigned>(m_settings.port),
|
||||
static_cast<unsigned>(m_settings.maxClients),
|
||||
m_settings.auth == LG_NET_AUTH_PASSWORD ? L"password" : L"none");
|
||||
return OpenResult::SUCCESS;
|
||||
}
|
||||
|
||||
bool CNetworkTransport::Initialize()
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lifecycleLock);
|
||||
if (m_state == State::INITIALIZED || m_state == State::READY)
|
||||
return true;
|
||||
if (m_state != State::OPEN)
|
||||
return false;
|
||||
|
||||
m_state = State::INITIALIZED;
|
||||
if (m_config.services & TRANSPORT_SERVICE_FRAME)
|
||||
DEBUG_WARN("Network video is unavailable until the GPU codec and "
|
||||
"texture sink are attached");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CNetworkTransport::Setup(size_t alignment)
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lifecycleLock);
|
||||
if (m_state == State::READY)
|
||||
return m_alignment == alignment;
|
||||
if (m_state != State::INITIALIZED)
|
||||
return false;
|
||||
|
||||
m_alignment = alignment;
|
||||
m_state = State::READY;
|
||||
return true;
|
||||
}
|
||||
|
||||
ITransport::ProcessResult CNetworkTransport::Process(
|
||||
ITransportEvents& events)
|
||||
{
|
||||
{
|
||||
CSRWSharedLock lock(m_lifecycleLock);
|
||||
if (m_state != State::INITIALIZED && m_state != State::READY)
|
||||
return m_state == State::STOPPED ?
|
||||
ProcessResult::FAILURE : ProcessResult::OK;
|
||||
}
|
||||
|
||||
NetworkEvent event;
|
||||
for (unsigned i = 0; i < EVENT_DRAIN_LIMIT && m_events.Take(event); ++i)
|
||||
{
|
||||
const SessionPtr session = FindSession(event.source);
|
||||
if (!session)
|
||||
continue;
|
||||
session->ProcessEvent(event, events);
|
||||
}
|
||||
return ProcessResult::OK;
|
||||
}
|
||||
|
||||
void CNetworkTransport::Stop()
|
||||
{
|
||||
{
|
||||
CSRWExclusiveLock lock(m_lifecycleLock);
|
||||
if (m_state == State::STOPPED)
|
||||
return;
|
||||
m_state = State::STOPPED;
|
||||
}
|
||||
|
||||
std::vector<SessionPtr> sessions;
|
||||
{
|
||||
CSRWExclusiveLock lock(m_sessionLock);
|
||||
sessions.swap(m_sessions);
|
||||
}
|
||||
for (const SessionPtr& session : sessions)
|
||||
session->Close();
|
||||
|
||||
m_input.Stop();
|
||||
m_control.SetControlEvents(nullptr, {});
|
||||
m_events.Reset();
|
||||
}
|
||||
|
||||
void CNetworkTransport::RecoveryStatus(const SourceKey& source,
|
||||
uint64_t session, uint32_t serial, bool active,
|
||||
Recovery state, uint32_t error)
|
||||
{
|
||||
const SessionPtr target = FindSession(source);
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
NetworkRecoveryStatus status;
|
||||
status.session = session;
|
||||
status.serial = serial;
|
||||
status.active = active;
|
||||
status.state = state;
|
||||
status.error = error;
|
||||
target->SetRecoveryStatus(status);
|
||||
}
|
||||
|
||||
const FrameProfile * CNetworkTransport::Profiles(unsigned& count) const
|
||||
{
|
||||
count = 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CfgResult CNetworkTransport::Probe(const FrameCfg& cfg) const
|
||||
{
|
||||
UNREFERENCED_PARAMETER(cfg);
|
||||
return CfgResult::REJECTED;
|
||||
}
|
||||
|
||||
CfgResult CNetworkTransport::Prepare(const FrameCfg& cfg)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(cfg);
|
||||
return CfgResult::REJECTED;
|
||||
}
|
||||
|
||||
void CNetworkTransport::Commit()
|
||||
{
|
||||
}
|
||||
|
||||
void CNetworkTransport::Abort()
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<const FrameCaps> CNetworkTransport::GetFrameCaps() const
|
||||
{
|
||||
return std::shared_ptr<const FrameCaps>();
|
||||
}
|
||||
|
||||
DirectFrameBufferMemory CNetworkTransport::GetDirectMemory() const
|
||||
{
|
||||
return DirectFrameBufferMemory {};
|
||||
}
|
||||
|
||||
IControlSink * CNetworkTransport::Control()
|
||||
{
|
||||
return (m_config.services & TRANSPORT_SERVICE_CONTROL) ?
|
||||
&m_control : nullptr;
|
||||
}
|
||||
|
||||
IInputSource * CNetworkTransport::Input()
|
||||
{
|
||||
return (m_config.services & TRANSPORT_SERVICE_INPUT) ? &m_input : nullptr;
|
||||
}
|
||||
|
||||
bool CNetworkTransport::ControlSnapshot(
|
||||
NetworkControlSnapshot& state) const
|
||||
{
|
||||
return m_control.Snapshot(state);
|
||||
}
|
||||
|
||||
std::shared_ptr<CNetworkSession> CNetworkTransport::CreateSession(
|
||||
uint32_t client, uint32_t generation)
|
||||
{
|
||||
if (!client || !generation)
|
||||
return std::shared_ptr<CNetworkSession>();
|
||||
|
||||
SessionPtr created;
|
||||
{
|
||||
CSRWSharedLock lifecycleLock(m_lifecycleLock);
|
||||
if (m_state != State::READY)
|
||||
return std::shared_ptr<CNetworkSession>();
|
||||
|
||||
CSRWExclusiveLock lock(m_sessionLock);
|
||||
if (m_sessions.size() >= m_settings.maxClients)
|
||||
return std::shared_ptr<CNetworkSession>();
|
||||
for (const SessionPtr& existing : m_sessions)
|
||||
{
|
||||
const InputSourceId source = existing->Source();
|
||||
if (source.client == client && source.generation == generation)
|
||||
return std::shared_ptr<CNetworkSession>();
|
||||
}
|
||||
|
||||
created.reset(
|
||||
new (std::nothrow) CNetworkSession(
|
||||
client, generation, m_events, m_input));
|
||||
if (!created)
|
||||
return std::shared_ptr<CNetworkSession>();
|
||||
m_sessions.push_back(created);
|
||||
}
|
||||
m_control.RequestReplay();
|
||||
return created;
|
||||
}
|
||||
|
||||
void CNetworkTransport::RemoveSession(
|
||||
uint32_t client, uint32_t generation)
|
||||
{
|
||||
SessionPtr removed;
|
||||
{
|
||||
CSRWExclusiveLock lock(m_sessionLock);
|
||||
for (auto it = m_sessions.begin(); it != m_sessions.end(); ++it)
|
||||
{
|
||||
const InputSourceId source = (*it)->Source();
|
||||
if (source.client != client || source.generation != generation)
|
||||
continue;
|
||||
removed = *it;
|
||||
m_sessions.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (removed)
|
||||
removed->Close();
|
||||
}
|
||||
|
||||
std::shared_ptr<CNetworkSession> CNetworkTransport::FindSession(
|
||||
const SourceKey& source) const
|
||||
{
|
||||
CSRWSharedLock lock(m_sessionLock);
|
||||
for (const SessionPtr& session : m_sessions)
|
||||
{
|
||||
const InputSourceId candidate = session->Source();
|
||||
if (candidate.client == source.client &&
|
||||
candidate.generation == source.generation)
|
||||
return session;
|
||||
}
|
||||
return std::shared_ptr<CNetworkSession>();
|
||||
}
|
||||
100
idd/LGIdd/transport/network/CNetworkTransport.h
Normal file
100
idd/LGIdd/transport/network/CNetworkTransport.h
Normal file
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2026 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CSRWLock.h"
|
||||
#include "transport/ITransport.h"
|
||||
#include "transport/network/CNetworkSession.h"
|
||||
#include "transport/network/NetworkSettings.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
// This provider owns transport-independent lifecycle and session state. It
|
||||
// deliberately advertises no frame capabilities and no texture sink until a
|
||||
// GPU codec backend is attached; it must not be mistaken for working media.
|
||||
class CNetworkTransport final : public ITransport
|
||||
{
|
||||
private:
|
||||
using SessionPtr = std::shared_ptr<CNetworkSession>;
|
||||
|
||||
enum class State
|
||||
{
|
||||
CONSTRUCTED,
|
||||
OPEN,
|
||||
INITIALIZED,
|
||||
READY,
|
||||
STOPPED,
|
||||
};
|
||||
|
||||
TransportInstance m_config;
|
||||
NetworkSettings m_settings;
|
||||
|
||||
mutable CSRWLock m_lifecycleLock;
|
||||
State m_state = State::CONSTRUCTED;
|
||||
size_t m_alignment = 0;
|
||||
|
||||
CNetworkEventQueue m_events;
|
||||
CNetworkInputSource m_input;
|
||||
CNetworkControlSink m_control;
|
||||
|
||||
mutable CSRWLock m_sessionLock;
|
||||
std::vector<SessionPtr> m_sessions;
|
||||
|
||||
std::shared_ptr<CNetworkSession> FindSession(
|
||||
const SourceKey& source) const;
|
||||
|
||||
public:
|
||||
explicit CNetworkTransport(const TransportInstance& config);
|
||||
~CNetworkTransport() override;
|
||||
|
||||
CNetworkTransport(const CNetworkTransport&) = delete;
|
||||
CNetworkTransport& operator=(const CNetworkTransport&) = delete;
|
||||
|
||||
OpenResult Open() override;
|
||||
bool Initialize() override;
|
||||
bool Setup(size_t alignment) override;
|
||||
ProcessResult Process(ITransportEvents& events) override;
|
||||
void Stop() override;
|
||||
void RecoveryStatus(const SourceKey& source,
|
||||
uint64_t session, uint32_t serial, bool active,
|
||||
Recovery state, uint32_t error) override;
|
||||
|
||||
const FrameProfile * Profiles(unsigned& count) const override;
|
||||
CfgResult Probe(const FrameCfg& cfg) const override;
|
||||
CfgResult Prepare(const FrameCfg& cfg) override;
|
||||
void Commit() override;
|
||||
void Abort() override;
|
||||
|
||||
std::shared_ptr<const FrameCaps> GetFrameCaps() const override;
|
||||
DirectFrameBufferMemory GetDirectMemory() const override;
|
||||
IControlSink * Control() override;
|
||||
IInputSource * Input() override;
|
||||
|
||||
const NetworkSettings& Settings() const { return m_settings; }
|
||||
bool ControlSnapshot(NetworkControlSnapshot& state) const;
|
||||
|
||||
// The QUIC provider calls these only after authenticating a peer. Keeping
|
||||
// the registry here makes Stop a barrier for session-originated callbacks.
|
||||
std::shared_ptr<CNetworkSession> CreateSession(
|
||||
uint32_t client, uint32_t generation);
|
||||
void RemoveSession(uint32_t client, uint32_t generation);
|
||||
};
|
||||
194
idd/LGIdd/transport/network/NetworkSettings.cpp
Normal file
194
idd/LGIdd/transport/network/NetworkSettings.cpp
Normal file
@@ -0,0 +1,194 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2026 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include "transport/network/NetworkSettings.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <utility>
|
||||
#include <wctype.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
enum Field : unsigned
|
||||
{
|
||||
FIELD_BIND = 1U << 0,
|
||||
FIELD_PORT = 1U << 1,
|
||||
FIELD_MAX_CLIENTS = 1U << 2,
|
||||
FIELD_AUTH = 1U << 3,
|
||||
FIELD_PASSWORD = 1U << 4,
|
||||
};
|
||||
|
||||
std::wstring Trim(const std::wstring& value)
|
||||
{
|
||||
size_t begin = 0;
|
||||
size_t end = value.size();
|
||||
while (begin < end && iswspace(value[begin]))
|
||||
++begin;
|
||||
while (end > begin && iswspace(value[end - 1]))
|
||||
--end;
|
||||
return value.substr(begin, end - begin);
|
||||
}
|
||||
|
||||
bool Equal(const std::wstring& left, const wchar_t * right)
|
||||
{
|
||||
if (!right)
|
||||
return false;
|
||||
|
||||
size_t index = 0;
|
||||
for (; index < left.size() && right[index]; ++index)
|
||||
if (towlower(left[index]) != towlower(right[index]))
|
||||
return false;
|
||||
return index == left.size() && !right[index];
|
||||
}
|
||||
|
||||
bool Fail(std::wstring * error, const wchar_t * message)
|
||||
{
|
||||
if (error)
|
||||
*error = message;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ValidText(const std::wstring& value, size_t maximum)
|
||||
{
|
||||
if (value.empty() || value.size() > maximum)
|
||||
return false;
|
||||
for (wchar_t character : value)
|
||||
if (iswcntrl(character))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseUnsigned(const std::wstring& value,
|
||||
unsigned long maximum, unsigned long& result)
|
||||
{
|
||||
if (value.empty() || value[0] == L'-')
|
||||
return false;
|
||||
|
||||
wchar_t * end = nullptr;
|
||||
errno = 0;
|
||||
const unsigned long parsed = wcstoul(value.c_str(), &end, 10);
|
||||
if (errno == ERANGE || !end || *end || !parsed || parsed > maximum)
|
||||
return false;
|
||||
result = parsed;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SetField(const std::wstring& key, const std::wstring& rawValue,
|
||||
NetworkSettings& settings, unsigned& fields,
|
||||
std::wstring * error)
|
||||
{
|
||||
const std::wstring value = Trim(rawValue);
|
||||
unsigned field = 0;
|
||||
if (Equal(key, L"bind"))
|
||||
{
|
||||
field = FIELD_BIND;
|
||||
if (!ValidText(value, 255))
|
||||
return Fail(error, L"invalid bind address");
|
||||
settings.bindAddress = value;
|
||||
}
|
||||
else if (Equal(key, L"port"))
|
||||
{
|
||||
field = FIELD_PORT;
|
||||
unsigned long parsed = 0;
|
||||
if (!ParseUnsigned(value, UINT16_MAX, parsed))
|
||||
return Fail(error, L"invalid port");
|
||||
settings.port = static_cast<uint16_t>(parsed);
|
||||
}
|
||||
else if (Equal(key, L"maxClients"))
|
||||
{
|
||||
field = FIELD_MAX_CLIENTS;
|
||||
unsigned long parsed = 0;
|
||||
if (!ParseUnsigned(value, NETWORK_MAX_CLIENTS, parsed))
|
||||
return Fail(error, L"invalid maximum client count");
|
||||
settings.maxClients = static_cast<uint16_t>(parsed);
|
||||
}
|
||||
else if (Equal(key, L"auth"))
|
||||
{
|
||||
field = FIELD_AUTH;
|
||||
if (Equal(value, L"none"))
|
||||
settings.auth = LG_NET_AUTH_NONE;
|
||||
else if (Equal(value, L"password"))
|
||||
settings.auth = LG_NET_AUTH_PASSWORD;
|
||||
else
|
||||
return Fail(error, L"invalid authentication mode");
|
||||
}
|
||||
else if (Equal(key, L"password"))
|
||||
{
|
||||
field = FIELD_PASSWORD;
|
||||
if (!ValidText(value, 255))
|
||||
return Fail(error, L"invalid password");
|
||||
settings.password = value;
|
||||
}
|
||||
else
|
||||
return Fail(error, L"unknown setting");
|
||||
|
||||
if (fields & field)
|
||||
return Fail(error, L"duplicate setting");
|
||||
fields |= field;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool ParseNetworkSettings(const std::wstring& source,
|
||||
NetworkSettings& settings, std::wstring * error)
|
||||
{
|
||||
NetworkSettings parsed;
|
||||
if (error)
|
||||
error->clear();
|
||||
|
||||
const std::wstring input = Trim(source);
|
||||
if (input.empty())
|
||||
{
|
||||
settings = std::move(parsed);
|
||||
return true;
|
||||
}
|
||||
if (input.size() > 4096)
|
||||
return Fail(error, L"settings are too long");
|
||||
|
||||
unsigned fields = 0;
|
||||
size_t begin = 0;
|
||||
while (begin < input.size())
|
||||
{
|
||||
const size_t separator = input.find(L';', begin);
|
||||
const size_t end = separator == std::wstring::npos ?
|
||||
input.size() : separator;
|
||||
const std::wstring field = input.substr(begin, end - begin);
|
||||
const size_t equals = field.find(L'=');
|
||||
if (equals == std::wstring::npos ||
|
||||
!SetField(Trim(field.substr(0, equals)),
|
||||
field.substr(equals + 1), parsed, fields, error))
|
||||
return false;
|
||||
|
||||
if (separator == std::wstring::npos)
|
||||
break;
|
||||
begin = separator + 1;
|
||||
if (begin == input.size())
|
||||
return Fail(error, L"empty setting");
|
||||
}
|
||||
|
||||
if (parsed.auth == LG_NET_AUTH_PASSWORD && parsed.password.empty())
|
||||
return Fail(error, L"password authentication requires a password");
|
||||
if (parsed.auth == LG_NET_AUTH_NONE && !parsed.password.empty())
|
||||
return Fail(error, L"password requires password authentication");
|
||||
|
||||
settings = std::move(parsed);
|
||||
return true;
|
||||
}
|
||||
45
idd/LGIdd/transport/network/NetworkSettings.h
Normal file
45
idd/LGIdd/transport/network/NetworkSettings.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Looking Glass
|
||||
* Copyright © 2017-2026 The Looking Glass Authors
|
||||
* https://looking-glass.io
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation; either version 2 of the License, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
||||
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/network/protocol.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
|
||||
static constexpr uint16_t NETWORK_DEFAULT_MAX_CLIENTS = 4;
|
||||
static constexpr uint16_t NETWORK_MAX_CLIENTS = 32;
|
||||
|
||||
struct NetworkSettings
|
||||
{
|
||||
std::wstring bindAddress = L"::";
|
||||
uint16_t port = static_cast<uint16_t>(LG_NET_DEFAULT_PORT);
|
||||
uint16_t maxClients = NETWORK_DEFAULT_MAX_CLIENTS;
|
||||
LGNetAuthMode auth = LG_NET_AUTH_MODE_DEFAULT;
|
||||
std::wstring password;
|
||||
};
|
||||
|
||||
// Settings use a semicolon-separated key=value list. Supported keys are
|
||||
// bind, port, maxClients, auth, and password. Password authentication requires
|
||||
// a non-empty password. The default address requests a dual-stack wildcard
|
||||
// listener from the eventual QUIC provider.
|
||||
bool ParseNetworkSettings(const std::wstring& source,
|
||||
NetworkSettings& settings, std::wstring * error = nullptr);
|
||||
Reference in New Issue
Block a user