Low-Level Session APIs
On this page
- 1. Raw Message Hooks (Zero-Allocation Paths)#
- Purely Synchronous Parsing (OnMessageReceived)#
- Purely Asynchronous Queuing (OnMessageAsync)#
- 2. Granular Handshake and Resumption Controls#
- 3. Raw Buffer Transmission (Bypassing Serialization)#
- 4. Manual Encryption Overrides (Per-Packet)#
- Related Information Paths#
The primary Client Session Guide focuses on high-level workflows like strongly-typed packet subscriptions and reconnect helpers. This page covers the lower-level transport surface exposed by TransportSession, TcpSession, and UdpSession.
1. Raw Message Hooks (Zero-Allocation Paths)
Instead of using session.On<MyPacket>(...), which dynamically deserializes classes using the framework’s catalog memory footprint, you can subscribe raw memory handlers.
Purely Synchronous Parsing (OnMessageReceived)
You are provided with an IBufferLease for each decoded frame. In the current SDK implementation, TcpSession invokes OnMessageReceived from HandleReceiveMessage(...) after FrameReader finishes a frame, while UdpSession invokes it from its receive loop after inbound transforms complete. In both cases the SDK keeps ownership of the lease and automatically disposes it after the callback path finishes unless you explicitly call Retain().
// WARNING: Do NOT execute heavy CPU loops or blocking await tasks here.
// You block the entire network stream from receiving subsequent frames.
session.OnMessageReceived += (sender, lease) =>
{
// Inspect underlying memory safely without throwing classes on the Garbage Collector
ReadOnlySpan<byte> rawData = lease.Span;
if (rawData[..2].SequenceEqual(MyMagicNumber)) {
Console.WriteLine("Matched Magic Bytes");
}
// lease is disposed inherently UNLESS you call lease.Retain() to hold ownership.
// If you retain it, YOU must manually call `lease.Dispose()` later to prevent memory leaks!
};
Purely Asynchronous Queuing (OnMessageAsync)
Provides an asynchronous receive pipeline (event Func<ReadOnlyMemory<byte>, Task>? declared on concrete sessions TcpSession, UdpSession, and WebSocketSession). Retains the underlying lease automatically (lease.Retain()) for the duration of the returned Task and disposes it upon completion (TcpSession and WebSocketSession observe completion via continuation, while UdpSession awaits the handler directly before reading the next datagram). Note that TransportOptions.AsyncQueueCapacity is currently reserved and not consumed.
session.OnMessageAsync += async (payload) =>
{
// payload is a ReadOnlyMemory<byte> safely detached from the hot path
await Database.StreamChangesAsync(payload);
};
2. Granular Handshake and Resumption Controls
When orchestrating connections manually using ConnectAsync(), it is extremely important to understand the difference between establishing a brand-new connection and recovering from a network disconnect:
- Resume (
ResumeSessionAsync): Used exclusively when a client was disconnected but still retains theSessionTokenand symmetricSecretin memory. It bypasses asymmetric math completely. - Handshake (
HandshakeAsync): Used to derive a brand new set of cryptographic keys. It is only necessary for brand new secure sessions (or if your server explicitly mandates it; for raw plaintext local servers, you might completely skip both).
// 1. Establish raw TCP stream
await session.ConnectAsync();
// 2. Check if this is a Reconnection (We still have the old Identity State)
if (session.State.SessionToken != 0 && !session.State.Secret.IsZero)
{
// Fast path: Tell the server we dropped connection but still have our keys.
ProtocolReason resumeResult = await session.ResumeSessionAsync();
if (resumeResult == ProtocolReason.NONE)
{
Console.WriteLine("Dropped session recovered. No handshake needed!");
return; // We can securely transmit data immediately.
}
// If resume failed (e.g. server evicted the session), we usually must
// drop the socket, re-ConnectAsync(), and fallback to Handshake.
}
// 3. Brand New Connection (Or Fallback)
// Negotiates entirely new encryption keys dynamically using X25519.
// NOTE: Not necessary if your server purely runs in unencrypted Dev mode without Handshake handlers.
await session.HandshakeAsync();
3. Raw Buffer Transmission (Bypassing Serialization)
For systems like proxies or relays, you don’t want to deserialize a packet just to reserialize it. The Nalix SDK exposes a SendAsync overload that accepts ReadOnlyMemory<byte> directly. This skips the PacketRegistry and writes bytes directly to the underlying FrameSender.
byte[] precomputedLoginPacket = ComputeStaticLogin();
// Bypasses struct instantiation, interface boxing, and serialization entirely.
// Memory is grabbed, compressed, encrypted, and fired instantly.
await session.SendAsync(precomputedLoginPacket.AsMemory());
4. Manual Encryption Overrides (Per-Packet)
While session.State.EncryptionEnabled strictly governs global cryptographic enforcement across the TransportSession, there are scenarios where a highly specific internal frame must bypass the global rules (e.g. sending a SessionInit packet before encryption keys are actually finalized).
If you are working directly with an ITransportSession, you can override encryption per send:
ITransportSession session = myTcpSession;
// Example: force this one packet to travel in plaintext even if the
// session would normally encrypt payloads.
await session.SendAsync(new SessionInit(), encrypt: false);