carry transport proto end-to-end through OPEN/OPEN_ACK
OPEN and OPEN_ACK now carry proto:1 alongside upstream_id:1. The session is tagged with its transport proto and reliability semantics switch on the specific proto: - TCP (proto=1): full L2 reliability (seq, ack, retransmit, in-order) - UDP (proto=2, reserved): best-effort (no retransmit, no ordering, no pure ACKs — seq/ack_seq fields present but ignored) This is architecturally correct: instead of a generic 'reliable:bool' flag, each proto gets the semantics it needs. Today only TCP exists so every session is reliable, but the extension point is clean for when stateless protos are added. Updated: PROTOCOL.md, Rust frame.rs/session.rs/main.rs, C# Frame.cs/SessionManager.cs.
This commit is contained in:
+6
-6
@@ -45,8 +45,8 @@ abstract record Frame
|
||||
{
|
||||
internal record Discover : 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 Open(byte UpstreamId, byte Proto) : Frame;
|
||||
internal record OpenAck(uint SessionId, byte UpstreamId, byte Proto) : Frame;
|
||||
internal record OpenNak(byte UpstreamId, byte Reason) : Frame;
|
||||
internal record Data(uint SessionId, uint Seq, uint AckSeq, byte[] Payload) : Frame;
|
||||
internal record Close(uint SessionId, byte? Reason) : Frame;
|
||||
@@ -106,9 +106,9 @@ static class FrameCodec
|
||||
Frame.Manifest manifest =>
|
||||
Build(Proto.TypeManifest, 0, BuildManifestPayload(manifest.Hostname, manifest.Entries)),
|
||||
Frame.Open open =>
|
||||
Build(Proto.TypeOpen, 0, [open.UpstreamId]),
|
||||
Build(Proto.TypeOpen, 0, [open.UpstreamId, open.Proto]),
|
||||
Frame.OpenAck ack =>
|
||||
Build(Proto.TypeOpenAck, ack.SessionId, [ack.UpstreamId]),
|
||||
Build(Proto.TypeOpenAck, ack.SessionId, [ack.UpstreamId, ack.Proto]),
|
||||
Frame.OpenNak nak =>
|
||||
Build(Proto.TypeOpenNak, 0, [nak.UpstreamId, nak.Reason]),
|
||||
Frame.Data data =>
|
||||
@@ -177,8 +177,8 @@ static class FrameCodec
|
||||
return type switch
|
||||
{
|
||||
Proto.TypeManifest => ParseManifest(payload2),
|
||||
Proto.TypeOpenAck when payload2.Length == 1 =>
|
||||
new Frame.OpenAck(sessionId, payload2[0]),
|
||||
Proto.TypeOpenAck when payload2.Length == 2 =>
|
||||
new Frame.OpenAck(sessionId, payload2[0], payload2[1]),
|
||||
Proto.TypeOpenNak when payload2.Length == 2 =>
|
||||
new Frame.OpenNak(payload2[0], payload2[1]),
|
||||
Proto.TypeClose when payload2.Length is 0 or 1 =>
|
||||
|
||||
@@ -180,22 +180,22 @@ sealed class SessionManager : IDisposable
|
||||
client = await state.Listener.AcceptTcpClientAsync();
|
||||
}
|
||||
catch { break; }
|
||||
EnqueueOpen(client, state.Upstream.Id);
|
||||
EnqueueOpen(client, state.Upstream.Id, state.Upstream.Protocol);
|
||||
}
|
||||
}
|
||||
|
||||
void EnqueueOpen(TcpClient client, byte upstreamId)
|
||||
void EnqueueOpen(TcpClient client, byte upstreamId, byte proto)
|
||||
{
|
||||
lock (_openLock)
|
||||
{
|
||||
if (_pending == null)
|
||||
{
|
||||
_pending = new PendingOpen(client, upstreamId);
|
||||
_pending = new PendingOpen(client, upstreamId, proto);
|
||||
SendOpen(_pending);
|
||||
}
|
||||
else
|
||||
{
|
||||
_openQueue.Enqueue(new PendingOpen(client, upstreamId));
|
||||
_openQueue.Enqueue(new PendingOpen(client, upstreamId, proto));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -208,7 +208,7 @@ sealed class SessionManager : IDisposable
|
||||
po.Client.Dispose();
|
||||
return;
|
||||
}
|
||||
_link.SendTo(_serverMac, new Frame.Open(po.UpstreamId));
|
||||
_link.SendTo(_serverMac, new Frame.Open(po.UpstreamId, po.Proto));
|
||||
}
|
||||
|
||||
void ProcessQueue()
|
||||
@@ -240,7 +240,7 @@ sealed class SessionManager : IDisposable
|
||||
}
|
||||
|
||||
var session = new Session(
|
||||
ack.SessionId, po.Client, srcMac, _link!,
|
||||
ack.SessionId, ack.Proto, po.Client, srcMac, _link!,
|
||||
() => _sessions.TryRemove(ack.SessionId, out _),
|
||||
msg => Log?.Invoke(msg));
|
||||
_sessions[ack.SessionId] = session;
|
||||
@@ -273,10 +273,11 @@ sealed class ListenerState(TcpListener listener, UpstreamEntry upstream)
|
||||
public UpstreamEntry Upstream { get; } = upstream;
|
||||
}
|
||||
|
||||
sealed class PendingOpen(TcpClient client, byte upstreamId)
|
||||
sealed class PendingOpen(TcpClient client, byte upstreamId, byte proto)
|
||||
{
|
||||
public TcpClient Client { get; } = client;
|
||||
public byte UpstreamId { get; } = upstreamId;
|
||||
public byte Proto { get; } = proto;
|
||||
}
|
||||
|
||||
/// L2 reliability: sender-side state.
|
||||
@@ -380,6 +381,7 @@ class RecvState
|
||||
|
||||
sealed class Session(
|
||||
uint sessionId,
|
||||
byte proto,
|
||||
TcpClient client,
|
||||
byte[] serverMac,
|
||||
TunnelLink link,
|
||||
@@ -387,6 +389,7 @@ sealed class Session(
|
||||
Action<string>? log) : IDisposable
|
||||
{
|
||||
readonly CancellationTokenSource _cts = new();
|
||||
readonly bool _isTcp = proto == Proto.ProtoTcp;
|
||||
readonly SendState _send = new();
|
||||
readonly RecvState _recv = new();
|
||||
readonly object _sendLock = new();
|
||||
@@ -396,13 +399,22 @@ sealed class Session(
|
||||
{
|
||||
_ = PumpSocketToTunnel();
|
||||
_ = PumpTunnelToSocket();
|
||||
_ = RetransmitTimer();
|
||||
if (_isTcp)
|
||||
_ = RetransmitTimer();
|
||||
}
|
||||
|
||||
/// Handle a DATA frame from the tunnel: process ack, deliver in-order.
|
||||
/// Handle a DATA frame from the tunnel.
|
||||
public void HandleData(Frame.Data data)
|
||||
{
|
||||
// Process ack_seq to advance send window.
|
||||
if (!_isTcp)
|
||||
{
|
||||
// Best-effort: deliver directly, ignore seq/ack.
|
||||
if (data.Payload.Length > 0)
|
||||
_deliverChannel.Writer.TryWrite(data.Payload);
|
||||
return;
|
||||
}
|
||||
|
||||
// TCP: process ack_seq to advance send window.
|
||||
lock (_sendLock)
|
||||
_send.ProcessAck(data.AckSeq);
|
||||
|
||||
@@ -438,20 +450,27 @@ sealed class Session(
|
||||
var n = await stream.ReadAsync(buf, _cts.Token);
|
||||
if (n == 0) break;
|
||||
|
||||
uint seq;
|
||||
uint ackSeq;
|
||||
lock (_sendLock)
|
||||
uint seq = 0, ackSeq = 0;
|
||||
byte[]? frameBytes = null;
|
||||
|
||||
if (_isTcp)
|
||||
{
|
||||
seq = _send.NextSeq();
|
||||
lock (_recvLock)
|
||||
ackSeq = _recv.CurrentAckSeq;
|
||||
lock (_sendLock)
|
||||
{
|
||||
seq = _send.NextSeq();
|
||||
lock (_recvLock)
|
||||
ackSeq = _recv.CurrentAckSeq;
|
||||
}
|
||||
}
|
||||
|
||||
var frame = new Frame.Data(sessionId, seq, ackSeq, buf[..n]);
|
||||
var frameBytes = FrameCodec.Encode(frame);
|
||||
|
||||
lock (_sendLock)
|
||||
_send.RecordSent(seq, frameBytes);
|
||||
if (_isTcp)
|
||||
{
|
||||
frameBytes = FrameCodec.Encode(frame);
|
||||
lock (_sendLock)
|
||||
_send.RecordSent(seq, frameBytes);
|
||||
}
|
||||
|
||||
link.SendTo(serverMac, frame);
|
||||
}
|
||||
@@ -508,6 +527,7 @@ sealed class Session(
|
||||
|
||||
void SendPureAck()
|
||||
{
|
||||
if (!_isTcp) return;
|
||||
uint seq, ackSeq;
|
||||
lock (_sendLock)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user