[idd] network: add transport session foundation

This commit is contained in:
Geoffrey McRae
2026-09-30 14:29:33 +10:00
parent 5ba1bda0c8
commit 5dcfad9bb0
12 changed files with 1289 additions and 3 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;

View File

@@ -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)
{

View File

@@ -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);

View File

@@ -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),

View 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 = &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;
}

View 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();
};

View 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>();
}

View 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);
};

View 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;
}

View 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);