Skip to content

C# — Server

Server API is in the Nnrp.Server namespace.

Import

csharp
using Nnrp.Server;
using Nnrp.Core;

ServerProfile

Server configuration (mutable class).

PropertyTypeDefaultDescription
MaxConcurrentFramesint1Max in-flight frames
EnableCachebooltrueEnable cache negotiation
MaxSectionsint16Max tensor sections per frame
MaxBodyBytesint33554432Max body bytes (32 MB)
ModelNamestring""Model name (sent in SERVER_HELLO_ACK)
csharp
public NnrpCapabilitySelection ToCapabilities();
public ServerHelloAckMessage CreateServerHelloAck(ClientHelloMessage hello, uint sessionId);
public bool TryValidate(ClientHelloMessage hello, out NnrpProtocolFailure? failure);

INnrpServerSession

Server session interface for receiving frames and pushing results.

csharp
public interface INnrpServerSession : IAsyncDisposable
{
    uint SessionId { get; }
    TransportId ActiveTransportId { get; }
    ClientHelloMessage ClientHello { get; }
    NnrpCapabilitySelection Capabilities { get; }

    Task<NnrpFrameSubmit> ReceiveSubmitAsync(CancellationToken ct = default);
    Task SendResultAsync(NnrpResult result, CancellationToken ct = default);
    Task SendResultDropAsync(uint frameId, CancellationToken ct = default);
    Task SendFlowUpdateAsync(FlowUpdateMessage update, CancellationToken ct = default);
    Task CloseAsync(CancellationToken ct = default);
}

Method Parameter Reference

MethodParametersReturnsDescription
ReceiveSubmitAsyncctNnrpFrameSubmitBlock until the next FRAME_SUBMIT arrives; throws NnrpConnectionClosedException on disconnect
SendResultAsyncresult: NnrpResult (requires FrameId, ResultClass); ctTaskPush inference result back to the client; populate either Sections or TypedPayloads
SendResultDropAsyncframeId: frame to drop; ctTaskNotify the client this frame won't return a result. Must be called when dropping a frame or SubmitAsync on the client side will block forever
SendFlowUpdateAsyncupdate: FlowUpdateMessage with Flags (FlowUpdateFlags), Credit (allowed in-flight count), RetryAfterMsTaskSend backpressure signal; Credit=0 pauses the client
CloseAsyncTaskSend CLOSE and gracefully terminate the connection

NnrpResult key fields: FrameId (required), ResultClass (ResultClass, required), Sections (output tensor sections), InferenceMs (inference latency), AppliedBudgetPolicy (actual policy used — required when Partial or Degraded).


NnrpServerSession

Default implementation of INnrpServerSession (sealed class).

Obtained via NnrpServer.AcceptAsync().


Data Types

NnrpFrameSubmit

Received and parsed frame submission.

PropertyTypeDescription
FrameIduintFrame ID
SessionIduintSession ID
InputProfileInputProfileInput data format
BudgetPolicyBudgetPolicyAllowed degradation
InferenceBudgetMsintClient budget (ms)
DeadlineMslongDeadline
SectionsIReadOnlyList<NnrpTensorSection>Tensor sections
TypedPayloadsIReadOnlyList<NnrpTypedPayload>Non-tensor payloads

NnrpResult

Result to push to client.

PropertyTypeDescription
FrameIduintFrame ID
ResultClassResultClassResult completeness class
ResultFlagsResultFlagsResult flags
AppliedBudgetPolicyBudgetPolicyActual degradation applied
InferenceMsintInference time (ms)
QueueMsintQueue wait time (ms)
ServerTotalMsintTotal server time (ms)
StatusCodeintStatus code
SectionsIReadOnlyList<NnrpTensorSection>Result tensor sections
TypedPayloadsIReadOnlyList<NnrpTypedPayload>Non-tensor payload frames

NnrpServer

Top-level server that accepts connections.

csharp
public sealed class NnrpServer : IAsyncDisposable
{
    public static Task<NnrpServer> BindAsync(
        string host, int port,
        ServerProfile profile,
        NnrpServerOptions? options = null,
        CancellationToken ct = default);

    public Task<INnrpServerSession> AcceptAsync(
        CancellationToken ct = default);

    public Task<INnrpServerSession> AcceptWithAuthAsync(
        Func<ClientHelloMessage, bool> authValidator,
        CancellationToken ct = default);

    public ValueTask DisposeAsync();
}

NnrpServerOptions

PropertyTypeDefaultDescription
UseQuicboolfalseUse QUIC instead of TCP (Preview3)
CertificatePathstring?nullTLS certificate path (for QUIC)
PrivateKeyPathstring?nullTLS private key path

Example

csharp
using Nnrp.Server;
using Nnrp.Core;

var profile = new ServerProfile { MaxConcurrentFrames = 4 };
await using var server = await NnrpServer.BindAsync("0.0.0.0", 4433, profile);

while (true)
{
    var session = await server.AcceptWithAuthAsync(hello =>
        ValidateAuthBlock(hello.AuthBlock.Span));

    _ = Task.Run(async () =>
    {
        try
        {
            while (true)
            {
                var submit = await session.ReceiveSubmitAsync();
                var output = RunInference(submit);
                await session.SendResultAsync(new NnrpResult
                {
                    FrameId = submit.FrameId,
                    ResultClass = ResultClass.Complete,
                    InferenceMs = output.InferenceMs,
                    Sections = output.Sections,
                });
            }
        }
        finally
        {
            await session.DisposeAsync();
        }
    });
}

Typical Use Cases

Authentication

csharp
var serverProfile = new NnrpServerProfile
{
    MaxConcurrentFrames = 8,
    AuthValidator = authBlock =>
    {
        if (authBlock.Length < 32) return false;
        var expected = Hmac.Sha256(SECRET_KEY, authBlock[32..]);
        return CryptographicOperations.FixedTimeEquals(
            authBlock[..32], expected);
    },
};

Backpressure and Degradation

csharp
async Task HandleAsync(INnrpServerSession session)
{
    while (true)
    {
        var submit = await session.ReceiveSubmitAsync();
        if (_queue > MaxQueue)
        {
            await session.SendResultDropAsync(submit.FrameId);
            await session.SendResultHintAsync(new NnrpResultHint
            {
                CongestionState = ResultHintCongestionState.Saturated,
            });
            continue;
        }
        var sections = await RunInferenceAsync(submit.Sections);
        await session.SendResultAsync(new NnrpResult
        {
            FrameId     = submit.FrameId,
            Sections    = sections,
            ResultClass = ResultClass.Complete,
        });
    }
}

Common Pitfalls

WARNING

  1. Never block inside await ReceiveSubmitAsync(). Wrap synchronous inference in Task.Run; blocking the I/O thread causes PING/PONG timeouts.

  2. Timed-out frames must get SendResultDropAsync. Silently skipping them leaves the client's SubmitAsync hanging forever.

  3. AuthValidator is a synchronous delegate. For async auth (database lookup), either cache tokens in memory or use AsyncAuthValidator overload.

  4. MaxConcurrentFrames is a soft limit. Implement SemaphoreSlim-based concurrency control at the application layer.

NNRP Documentation