announce server hostname in MANIFEST, display in client
Server reads its hostname via gethostname(2) and includes it as a length-prefixed UTF-8 string at the start of the MANIFEST payload. Client displays 'Server: <hostname> — <MAC>' in a label above the upstream list. Both sides updated for the new MANIFEST format: [hostname_len:1][hostname:N][entries...] Protocol version unchanged (still 1); MANIFEST payload layout change is backward-incompatible but both sides ship together.
This commit is contained in:
+27
-12
@@ -40,7 +40,7 @@ readonly record struct UpstreamEntry(
|
||||
abstract record Frame
|
||||
{
|
||||
internal record Discover : Frame;
|
||||
internal record Manifest(UpstreamEntry[] Entries) : Frame;
|
||||
internal record Manifest(string Hostname, UpstreamEntry[] Entries) : Frame;
|
||||
internal record Open(byte UpstreamId) : Frame;
|
||||
internal record OpenAck(uint SessionId, byte UpstreamId) : Frame;
|
||||
internal record OpenNak(byte UpstreamId, byte Reason) : Frame;
|
||||
@@ -74,7 +74,7 @@ static class FrameCodec
|
||||
Frame.Discover =>
|
||||
Build(Proto.TypeDiscover, 0, []),
|
||||
Frame.Manifest manifest =>
|
||||
Build(Proto.TypeManifest, 0, BuildManifestPayload(manifest.Entries)),
|
||||
Build(Proto.TypeManifest, 0, BuildManifestPayload(manifest.Hostname, manifest.Entries)),
|
||||
Frame.Open open =>
|
||||
Build(Proto.TypeOpen, 0, [open.UpstreamId]),
|
||||
Frame.OpenAck ack =>
|
||||
@@ -90,9 +90,14 @@ static class FrameCodec
|
||||
};
|
||||
}
|
||||
|
||||
static byte[] BuildManifestPayload(UpstreamEntry[] entries)
|
||||
static byte[] BuildManifestPayload(string hostname, UpstreamEntry[] entries)
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
var hnBytes = Encoding.UTF8.GetBytes(hostname);
|
||||
var hnLen = (byte)Math.Min(hnBytes.Length, 255);
|
||||
ms.WriteByte(hnLen);
|
||||
if (hnLen > 0)
|
||||
ms.Write(hnBytes, 0, hnLen);
|
||||
foreach (var e in entries)
|
||||
{
|
||||
var labelBytes = Encoding.UTF8.GetBytes(e.Label ?? "");
|
||||
@@ -141,25 +146,35 @@ static class FrameCodec
|
||||
|
||||
static Frame.Manifest? ParseManifest(ReadOnlySpan<byte> payload)
|
||||
{
|
||||
if (payload.Length < 1)
|
||||
return null;
|
||||
var hnLen = payload[0];
|
||||
if (1 + hnLen > payload.Length)
|
||||
return null;
|
||||
var hostname = hnLen == 0
|
||||
? ""
|
||||
: Encoding.UTF8.GetString(payload[1..(1 + hnLen)]);
|
||||
var rest = payload[(1 + hnLen)..];
|
||||
|
||||
var entries = new List<UpstreamEntry>();
|
||||
int i = 0;
|
||||
while (i < payload.Length)
|
||||
while (i < rest.Length)
|
||||
{
|
||||
if (i + 5 > payload.Length)
|
||||
if (i + 5 > rest.Length)
|
||||
return null;
|
||||
var id = payload[i];
|
||||
var proto = payload[i + 1];
|
||||
var port = (ushort)(payload[i + 2] << 8 | payload[i + 3]);
|
||||
var labelLen = payload[i + 4];
|
||||
var id = rest[i];
|
||||
var proto = rest[i + 1];
|
||||
var port = (ushort)(rest[i + 2] << 8 | rest[i + 3]);
|
||||
var labelLen = rest[i + 4];
|
||||
i += 5;
|
||||
if (i + labelLen > payload.Length)
|
||||
if (i + labelLen > rest.Length)
|
||||
return null;
|
||||
string? label = labelLen == 0
|
||||
? null
|
||||
: Encoding.UTF8.GetString(payload[i..(i + labelLen)]);
|
||||
: Encoding.UTF8.GetString(rest[i..(i + labelLen)]);
|
||||
i += labelLen;
|
||||
entries.Add(new UpstreamEntry(id, proto, port, label));
|
||||
}
|
||||
return new Frame.Manifest(entries.ToArray());
|
||||
return new Frame.Manifest(hostname, entries.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ public partial class MainForm : Form
|
||||
readonly SessionManager _sessions = new();
|
||||
readonly ComboBox _deviceBox = new();
|
||||
readonly Button _discoverBtn = new();
|
||||
readonly Label _serverLabel = new();
|
||||
readonly ListView _listView = new();
|
||||
readonly Label _statusLabel = new();
|
||||
TunnelLink? _link;
|
||||
@@ -23,7 +24,8 @@ public partial class MainForm : Form
|
||||
InitializeComponents();
|
||||
|
||||
_sessions.Log += msg => this.Invoke(() => _statusLabel.Text = msg);
|
||||
_sessions.ManifestReceived += entries => this.Invoke(() => PopulateList(entries));
|
||||
_sessions.ManifestReceived += (hostname, mac, entries) =>
|
||||
this.Invoke(() => PopulateList(hostname, mac, entries));
|
||||
|
||||
foreach (var d in TunnelLink.ListDevices())
|
||||
{
|
||||
@@ -54,9 +56,15 @@ public partial class MainForm : Form
|
||||
_discoverBtn.Click += OnDiscover;
|
||||
Controls.Add(_discoverBtn);
|
||||
|
||||
_listView.Left = pad; _listView.Top = _discoverBtn.Bottom + 8;
|
||||
_serverLabel.Left = pad; _serverLabel.Top = _discoverBtn.Bottom + 8;
|
||||
_serverLabel.Width = ClientSize.Width - pad * 2;
|
||||
_serverLabel.AutoEllipsis = true;
|
||||
_serverLabel.Text = "Server: not connected";
|
||||
Controls.Add(_serverLabel);
|
||||
|
||||
_listView.Left = pad; _listView.Top = _serverLabel.Bottom + 8;
|
||||
_listView.Width = ClientSize.Width - pad * 2;
|
||||
_listView.Height = 220;
|
||||
_listView.Height = 200;
|
||||
_listView.View = View.Details;
|
||||
_listView.FullRowSelect = true;
|
||||
_listView.CheckBoxes = true;
|
||||
@@ -96,11 +104,15 @@ public partial class MainForm : Form
|
||||
_sessions.AttachLink(_link);
|
||||
_link.Open();
|
||||
_sessions.Discover();
|
||||
_serverLabel.Text = "Server: discovering...";
|
||||
_statusLabel.Text = "discovering...";
|
||||
}
|
||||
|
||||
void PopulateList(UpstreamEntry[] entries)
|
||||
void PopulateList(string hostname, byte[] mac, UpstreamEntry[] entries)
|
||||
{
|
||||
var macStr = string.Join(":", mac.Select(b => b.ToString("X2")));
|
||||
_serverLabel.Text = $"Server: {hostname} — {macStr}";
|
||||
|
||||
_listView.BeginUpdate();
|
||||
_listView.Items.Clear();
|
||||
foreach (var up in entries)
|
||||
|
||||
@@ -12,6 +12,7 @@ sealed class SessionManager : IDisposable
|
||||
readonly ConcurrentDictionary<int, ListenerState> _listeners = new();
|
||||
|
||||
byte[]? _serverMac;
|
||||
string _serverHostname = "";
|
||||
UpstreamEntry[] _upstreams = [];
|
||||
|
||||
// Serialized OPEN: only one outstanding at a time.
|
||||
@@ -20,10 +21,11 @@ sealed class SessionManager : IDisposable
|
||||
readonly Queue<PendingOpen> _openQueue = new();
|
||||
|
||||
public event Action<string>? Log;
|
||||
public event Action<UpstreamEntry[]>? ManifestReceived;
|
||||
public event Action<string, byte[], UpstreamEntry[]>? ManifestReceived;
|
||||
|
||||
public UpstreamEntry[] Upstreams => _upstreams;
|
||||
public byte[]? ServerMac => _serverMac;
|
||||
public string ServerHostname => _serverHostname;
|
||||
public TunnelLink? Link => _link;
|
||||
|
||||
public void AttachLink(TunnelLink link)
|
||||
@@ -51,9 +53,10 @@ sealed class SessionManager : IDisposable
|
||||
{
|
||||
case Frame.Manifest manifest:
|
||||
_serverMac = srcMac;
|
||||
_serverHostname = manifest.Hostname;
|
||||
_upstreams = manifest.Entries;
|
||||
Log?.Invoke($"manifest: {manifest.Entries.Length} upstreams from {BitConverter.ToString(srcMac)}");
|
||||
ManifestReceived?.Invoke(manifest.Entries);
|
||||
Log?.Invoke($"manifest: {manifest.Entries.Length} upstreams from {manifest.Hostname} ({BitConverter.ToString(srcMac)})");
|
||||
ManifestReceived?.Invoke(manifest.Hostname, srcMac, manifest.Entries);
|
||||
break;
|
||||
|
||||
case Frame.OpenAck ack:
|
||||
|
||||
Reference in New Issue
Block a user