TechTeaStudio.Protocols.Hyperion 0.3.0

dotnet add package TechTeaStudio.Protocols.Hyperion --version 0.3.0
                    
NuGet\Install-Package TechTeaStudio.Protocols.Hyperion -Version 0.3.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="TechTeaStudio.Protocols.Hyperion" Version="0.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="TechTeaStudio.Protocols.Hyperion" Version="0.3.0" />
                    
Directory.Packages.props
<PackageReference Include="TechTeaStudio.Protocols.Hyperion" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add TechTeaStudio.Protocols.Hyperion --version 0.3.0
                    
#r "nuget: TechTeaStudio.Protocols.Hyperion, 0.3.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package TechTeaStudio.Protocols.Hyperion@0.3.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=TechTeaStudio.Protocols.Hyperion&version=0.3.0
                    
Install as a Cake Addin
#tool nuget:?package=TechTeaStudio.Protocols.Hyperion&version=0.3.0
                    
Install as a Cake Tool

<p align="center"> <img src="https://raw.githubusercontent.com/TechTeaStudio/HyperionProtocol/main/src/TechTeaStudio.Protocols.Hyperion/icon2.png" alt="HyperionProtocol logo" width="160" /> </p>

<h1 align="center">HyperionProtocol</h1>

<p align="center"> Chunked TCP messaging for .NET. Pluggable serialization, adaptive framing, streaming receive, and <code>System.IO.Pipelines</code> support — without the weight of a full RPC framework. </p>

<p align="center"> <a href="https://www.nuget.org/packages/TechTeaStudio.Protocols.Hyperion"><img alt="NuGet" src="https://img.shields.io/nuget/v/TechTeaStudio.Protocols.Hyperion.svg?logo=nuget&label=NuGet" /></a> <a href="https://www.nuget.org/packages/TechTeaStudio.Protocols.Hyperion"><img alt="Downloads" src="https://img.shields.io/nuget/dt/TechTeaStudio.Protocols.Hyperion.svg?logo=nuget&label=Downloads" /></a> <img alt=".NET" src="https://img.shields.io/badge/.NET-6.0%20%7C%208.0%20%7C%209.0%20%7C%2010.0-512BD4?logo=dotnet&logoColor=white" /> <a href="https://github.com/TechTeaStudio/HyperionProtocol/actions/workflows/dotnet.yml"><img alt="Build" src="https://img.shields.io/github/actions/workflow/status/TechTeaStudio/HyperionProtocol/dotnet.yml?branch=main&logo=github&label=build" /></a> <a href="LICENSE"><img alt="License" src="https://img.shields.io/badge/license-MIT-blue.svg" /></a> </p>

Overview

HyperionProtocol solves three problems that every long-lived TCP service in .NET eventually re-implements: message framing, chunking of payloads that don't fit in one socket write, and a clean boundary between the wire format and your serializer of choice. It does this with a small, focused public surface, four target frameworks, and no opinion about what your messages contain.

The package powers the Hyperion Omni Client but is independent: any .NET app that talks over NetworkStream — or anything that exposes a Stream / PipeReader / PipeWriter — can adopt it as a one-line replacement for hand-rolled framing.

When to reach for it

You want this library when you need a point-to-point binary protocol over TCP and you'd rather not write the framing yourself. Concretely, that means:

  • A client and a server you own on both ends.
  • Messages that may be small (a few bytes) or large (multi-gigabyte file transfers).
  • A serializer you've already chosen — System.Text.Json, MessagePack, protobuf-net, MemoryPack, whatever — that you want to keep using as-is.
  • An async/await codebase that benefits from IAsyncEnumerable and System.IO.Pipelines.

You probably don't want this library when you need browser interop (use WebSocket or SignalR), when you need full method-call RPC semantics with code generation (use gRPC), or when you need a message broker for pub/sub (use NATS or MQTT).

How it compares

Library / Approach What it gives you Schema / codegen Native chunking Streaming receive Browser Dep footprint
HyperionProtocol Framing + adaptive chunking + pluggable serializer None Yes (default 1 MiB) IAsyncEnumerable<ReadOnlyMemory<byte>> No System.IO.Pipelines
gRPC (Grpc.Net.Client) Full RPC framework with services, methods, streaming .proto + codegen Via HTTP/2 framing Yes (server-streaming RPCs) Through grpc-web Large (Grpc.*, HTTP/2 stack)
SignalR client Real-time hub/RPC over WebSocket / SSE / long-poll None at wire level, attribute-based at API No (transport-level only) Streaming hub methods First-class Large (Microsoft.AspNetCore.SignalR.Client + transports)
System.Net.WebSockets Frame-based bidirectional messaging None No (you split & reassemble) Manual First-class In-box
StreamJsonRpc JSON-RPC 2.0 over any Stream None No No (response is one message) Through duplex pipe wrappers StreamJsonRpc
Raw TcpClient + custom framing Everything is yours None Whatever you write Whatever you write No None

The honest pitch: HyperionProtocol sits in the gap between "raw TCP with handmade length-prefixed framing" and "a full RPC framework". If you're about to write your fifth [length:4][payload] loop and then realise you also need to chunk multi-megabyte payloads and stream them straight to disk, this is the package that already did all of that.

Install

dotnet add package TechTeaStudio.Protocols.Hyperion

Or pin a specific version in .csproj:

<PackageReference Include="TechTeaStudio.Protocols.Hyperion" Version="0.3.0" />

Quick start

Server

using System.Net;
using System.Net.Sockets;
using TechTeaStudio.Protocols.Hyperion;
using TechTeaStudio.Protocols.Hyperion.Protocols;

var listener = new TcpListener(IPAddress.Any, 8080);
listener.Start();

while (true)
{
    var client = await listener.AcceptTcpClientAsync();
    _ = HandleClientAsync(client);
}

async Task HandleClientAsync(TcpClient client)
{
    using (client)
    using (var stream = client.GetStream())
    {
        var protocol = new SmartHyperionProtocol(new DefaultSerializer());

        var message = await protocol.ReceiveAsync<string>(stream);
        await protocol.SendAsync($"Echo: {message}", stream);
    }
}

Client

using var client = new TcpClient();
await client.ConnectAsync("localhost", 8080);
using var stream = client.GetStream();

var protocol = new SmartHyperionProtocol(new DefaultSerializer());

await protocol.SendAsync("Hello HyperionProtocol!", stream);
var response = await protocol.ReceiveAsync<string>(stream);

Custom serializer

Implement ISerializer to plug in MessagePack, protobuf, MemoryPack, or anything else:

public sealed class MessagePackSerializer : ISerializer
{
    public byte[] Serialize<T>(T obj)   => MessagePack.MessagePackSerializer.Serialize(obj);
    public T Deserialize<T>(byte[] data) => MessagePack.MessagePackSerializer.Deserialize<T>(data);
}

var protocol = new SmartHyperionProtocol(new MessagePackSerializer());

The default DefaultSerializer already short-circuits string and byte[] to avoid round-tripping them through JSON.

Working with large payloads

Anything bigger than 64 KiB falls onto the chunked path (default chunk size: 1 MiB). For receivers that don't want to buffer the full message in memory, use the streaming variant — it yields each chunk as it arrives.

await using var file = File.Create("incoming.bin");
await foreach (var chunk in protocol.ReceiveStreamingAsync(stream))
{
    await file.WriteAsync(chunk);
}

Validation (magic, version, chunk order, end-flag, packet-id continuity) runs before every yield, so corrupt or out-of-order traffic surfaces as a HyperionProtocolException immediately, not after the whole payload has been read.

System.IO.Pipelines

For high-throughput services, use the Pipelines overloads for zero-copy writes and natural backpressure:

var writer = PipeWriter.Create(networkStream);
var reader = PipeReader.Create(networkStream);

await protocol.SendAsync(message, writer);
var response = await protocol.ReceiveAsync<MyType>(reader);

Adaptive framing

SmartHyperionProtocol picks the cheapest layout per message. You don't tune anything — the sender decides based on payload size:

Payload Wire format Overhead
< 1 KiB [magic:1 = 0xFF][length:2 BE][data] 3 bytes
< 64 KiB [magic:1 = 0xFE][length:4 BE][data] 5 bytes
anything else chunked frames with a JSON PacketHeader (same as base HyperionProtocol) ~120 bytes per chunk

The receiver reads one byte to disambiguate: 0xFF/0xFE pick lightweight/direct, anything else is taken as the high byte of the chunked header-length prefix (a JSON header is always small enough that this byte is 0x00).

You can observe what the picker chose:

var stats = protocol.GetStatsSnapshot();
Console.WriteLine(stats);
// Protocol Stats:
// Lightweight: 42 (84.0%)
// Direct:       6 (12.0%)
// Chunked:      2  (4.0%)
// Bytes saved: 6,612

Protocol version negotiation

PacketHeader.Version is part of every chunked frame; the validator accepts versions in [MinSupportedProtocolVersion .. ProtocolVersion] (currently 0..1, with 0 treated as legacy v1 from 0.2.x senders). An optional handshake helper exchanges a magic preamble plus version on connection and returns the minimum both sides agree to speak:

int version = await HyperionProtocol.HandshakeAsync(stream);
if (version < HyperionProtocol.ProtocolVersion)
{
    // adapt: fall back to features the older peer understands
}

Wire format

Chunked frame (HyperionProtocol & SmartHyperionProtocol fallback):

  ┌──────────────────┬────────────────────┬─────────────────┐
  │ Header Length    │ JSON Header        │ Chunk Payload   │
  │ 4 bytes BE       │ N bytes (≤ 64 KiB) │ ≤ 1 MiB         │
  └──────────────────┴────────────────────┴─────────────────┘

Header (JSON):
  { "Version":1, "Magic":"TTS", "PacketId":"<guid>",
    "ChunkNumber":0, "TotalChunks":5, "DataLength":1048576, "Flags":0 }

Smart-mode framing for small messages skips the JSON header entirely:

  Lightweight (<1 KiB):  [0xFF][len:2 BE][data]
  Direct      (<64 KiB): [0xFE][len:4 BE][data]

Both ends of a connection must agree on ChunkSize and MaxHeaderLength; the receiver rejects oversized chunks and headers. The defaults are 1 MiB and 64 KiB respectively, configurable via HyperionProtocolOptions.

Public API

public class HyperionProtocol
{
    public const int ProtocolVersion = 1;
    public const int MinSupportedProtocolVersion = 0;

    public HyperionProtocol(ISerializer serializer);
    public HyperionProtocol(ISerializer serializer, HyperionProtocolOptions options);

    public int ChunkSize { get; }
    public int MaxHeaderLength { get; }

    // NetworkStream API
    public virtual Task SendAsync<T>(T message, NetworkStream stream, CancellationToken ct = default);
    public virtual Task<T> ReceiveAsync<T>(NetworkStream stream, CancellationToken ct = default);
    public virtual IAsyncEnumerable<ReadOnlyMemory<byte>> ReceiveStreamingAsync(NetworkStream stream, CancellationToken ct = default);

    // Pipelines API
    public virtual Task SendAsync<T>(T message, PipeWriter writer, CancellationToken ct = default);
    public virtual Task<T> ReceiveAsync<T>(PipeReader reader, CancellationToken ct = default);

    // Optional handshake
    public static Task<int> HandshakeAsync(NetworkStream stream, int? localVersion = null, CancellationToken ct = default);
}

public class SmartHyperionProtocol : HyperionProtocol
{
    public ProtocolStats Stats { get; }
    public ProtocolStats GetStatsSnapshot();
    public void ResetStats();
}

public interface ISerializer
{
    byte[] Serialize<T>(T obj);
    T Deserialize<T>(byte[] data);
    void Serialize<T>(IBufferWriter<byte> writer, T obj);
    T Deserialize<T>(ReadOnlySpan<byte> data);
}

public sealed class HyperionProtocolOptions
{
    public const int DefaultChunkSize = 1024 * 1024;
    public const int DefaultMaxHeaderLength = 64 * 1024;

    public int ChunkSize { get; init; } = DefaultChunkSize;
    public int MaxHeaderLength { get; init; } = DefaultMaxHeaderLength;
}

Exceptions that escape the receive/send paths are wrapped in HyperionProtocolException — bad magic, bad chunk order, header out of range, mismatched packet id, premature EOF.

Performance

Numbers from a typical dev box over loopback (Ryzen-class CPU, single TCP pair):

Test Result
One-way 100 MiB — chunked HyperionProtocol ~290–340 MiB/sec
One-way 100 MiB — Pipelines path ~250–280 MiB/sec
One-way 100 MiB — ReceiveStreamingAsync ~430–500 MiB/sec (no buffering of the full payload)
SmartHyperionProtocol lightweight round-trip ~13 000–14 000 ops/sec, ~0.07 ms/op
100 concurrent clients, single round-trip each ~40 ms total

These come straight from the [Category("Performance")] NUnit tests in this repo. Run them yourself:

dotnet test src/TechTeaStudio.Protocols.Hyperion/TechTeaStudio.Protocols.Hyperion.sln -c Release \
    --filter "TestCategory=Performance|TestCategory=Stress" \
    --logger "console;verbosity=normal"

Each performance test asserts a generous lower throughput floor (30 MiB/sec) to catch regressions without flaking on slow CI.

For accurate, statistically-meaningful measurements there's a separate BenchmarkDotNet project:

dotnet run -c Release \
  --project src/TechTeaStudio.Protocols.Hyperion/TechTeaStudio.Protocols.Hyperion.Benchmarks \
  --framework net9.0 -- --filter "*"

It includes OneWayThroughputBenchmarks (NetworkStream vs Pipelines vs streaming receive across 64 KiB–64 MiB payloads with [MemoryDiagnoser]), SmartProtocolBenchmarks (round-trip latency across the lightweight / direct / chunked thresholds), and ConcurrentClientsBenchmarks (aggregate throughput at 10 / 50 / 100 clients).

Project layout

HyperionProtocol/
├── src/TechTeaStudio.Protocols.Hyperion/
│   ├── TechTeaStudio.Protocols.Hyperion.sln
│   ├── TechTeaStudio.Protocols.Hyperion/             <- NuGet package source
│   │   ├── Protocols/HyperionProtocol.cs             <- chunked protocol (base)
│   │   ├── Protocols/SmartHyperionProtocol.cs        <- adaptive framing
│   │   ├── Protocols/ChunkData.cs
│   │   ├── ISerializer.cs / DefaultSerializer.cs
│   │   ├── PacketHeader.cs / ProtocolStats.cs
│   │   ├── HyperionProtocolOptions.cs
│   │   └── HyperionProtocolException.cs
│   ├── TechTeaStudio.Protocols.Hyperion.Tests/       <- NUnit (correctness + perf + stress)
│   ├── TechTeaStudio.Protocols.Hyperion.Benchmarks/  <- BenchmarkDotNet console app
│   └── ConsoleTestApp/                                <- smoke test
├── .github/workflows/dotnet.yml                       <- CI publish (shared TTS workflow)
├── CHANGELOG.md
├── LICENSE
└── README.md

Build & test

dotnet build src/TechTeaStudio.Protocols.Hyperion/TechTeaStudio.Protocols.Hyperion.sln
dotnet test  src/TechTeaStudio.Protocols.Hyperion/TechTeaStudio.Protocols.Hyperion.sln

The library multi-targets net6.0;net8.0;net9.0;net10.0. net7.0 is intentionally skipped (EOL). The test and benchmark projects target net8.0;net9.0;net10.0 only — the NUnit3 test platform is unstable on net6.0.

Versioning & release

Version lives in TechTeaStudio.Protocols.Hyperion.csproj as a 3-part <Version>X.Y.Z</Version>. Bump rules:

  • Bug fix → Z + 1
  • New feature, source-compatible → Y + 1, reset Z = 0
  • Breaking change in public API or wire format → X + 1 (after 1.0), reset Y = Z = 0

Commit format is vX.Y.Z <short description>. Push to main triggers the shared TechTeaStudio NuGet publish workflow, which packs and pushes to nuget.org with --skip-duplicate. Never push to nuget.org manually.

See CHANGELOG.md for the full release history.

License

Licensed under the MIT License. Copyright © Tech Tea Studio.

<p align="center"> Built as part of the Hyperion Ecosystem by <a href="https://techteastudio.cc">TechTeaStudio</a>. </p>

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.3.0 102 5/12/2026
0.1.3 130 1/15/2026
0.1.2 266 11/16/2025
0.1.1 240 9/2/2025
0.1.0 231 8/31/2025

v0.3.0 — Streaming receive, Pipelines support, protocol versioning.

Added:
 * ReceiveStreamingAsync(NetworkStream) returning IAsyncEnumerable<ReadOnlyMemory<byte>> — stream chunks straight to disk without buffering the full payload.
 * SendAsync(T, PipeWriter) and ReceiveAsync<T>(PipeReader) — first-class System.IO.Pipelines path for natural backpressure and zero-copy writes.
 * Protocol-version negotiation: PacketHeader.Version field, ProtocolVersion / MinSupportedProtocolVersion constants, and HandshakeAsync(NetworkStream, localVersion?) helper that exchanges "TTSH" + version with the peer and returns the negotiated minimum.

Wire format: backward compatible — receivers tolerate headers without the new Version field (v0.2.x senders) and treat them as v1.

Full changelog: https://github.com/TechTeaStudio/HyperionProtocol/blob/main/CHANGELOG.md