SRO.Net.BIDHASP 1.0.0

SRO.Net.BIDHASP Framework

SRO.Net.BIDHASP is a .NET Framework library that implements a Bidirectional Hub-and-Spoke Protocol (BIDHASP) for inter-process communication. It provides a structured, event-driven messaging architecture where a central hub server manages connections from multiple spoke clients, with full support for raw sockets, plain TCP streams, and TLS-encrypted streams.

Table of Contents


Architecture Overview

BIDHASP follows a hub-and-spoke topology:

  [Spoke A] ──┐
  [Spoke B] ──┼──► [Hub]
  [Spoke C] ──┘
  • The Hub is a TCP server that listens for incoming connections, assigns each connected spoke a unique integer ID, and can broadcast messages to all connected spokes.
  • A Spoke is a TCP client that connects to the hub, registers itself with a hostname-derived name, and can send and receive typed data packets.

All data is exchanged as TransactedData packets — fixed-header, variable-payload frames that carry a BasicDataType (String or Binary), a DetailedDataType (e.g., PlainString, JSON, Int32), an application-defined MessageType, and the payload bytes.


Projects

Project Description
SRO.Net.BIDHASP.Core Core protocol classes, base communication units, configuration manager, and the BidiDataPipe transport abstraction.
SRO.Net.BIDHASP.Hub HubManager — the server-side hub that accepts spoke connections and broadcasts messages.
SRO.Net.BIDHASP.Spoke SpokeManager — the client-side spoke manager that connects to the hub and routes messages.
SRO.TestHarness.POCO Plain-object test types (Person, Employee, Address) used by the test harnesses.
BIDHASPHubTestHarness WinForms test application for the hub side.
BIDHASPSpokeTestHarness WinForms test application for the spoke side.

Getting Started

Configuration

Before creating a HubManager or SpokeManager, initialize the APIConfigurationManager. The configuration is read from a JSON file named {appname}.config.json.

Quick-start (development only — no TLS):

// Uses defaults: port 4800, localhost, plain Stream channel.
// Creates bidhasp.config.json in the user profile folder if it does not exist.
APIConfigurationManager.Initialize();

Named application config from user profile directory:

APIConfigurationManager.Initialize("myapp");
// Loads %USERPROFILE%\myapp.config.json

Named application config from an explicit directory:

APIConfigurationManager.Initialize(@"C:\myapp\config", "myapp");
// Loads C:\myapp\config\myapp.config.json

Configuration file structure (myapp.config.json):

{
  "HubHost": "localhost",
  "Port": 4800,
  "CommunicationChannelType": "Stream",
  "CertificateFilePath": "bidhasp_cert.pfx",
  "KeyFilePath": "bidhasp_key.pem",
  "Thumbprint": "",
  "SubjectName": "",
  "PathIsRelativeToProfileDirectory": true,
  "PrivateKeyPassword": ""
}
Field Default Description
HubHost "localhost" Hostname or IP of the hub (used by spokes to connect).
Port 4800 TCP port for hub/spoke communication.
CommunicationChannelType "Stream" Transport type: Socket, Stream, or TLSStream.
CertificateFilePath "bidhasp_cert.pfx" Path to the TLS certificate (PFX or PEM).
KeyFilePath "bidhasp_key.pem" Path to the PEM private-key file (used when the certificate is not a PFX).
Thumbprint "" Certificate store thumbprint — takes lookup priority over files.
SubjectName "" Certificate store subject name — second lookup priority.
PathIsRelativeToProfileDirectory true When true, relative cert paths are resolved against %USERPROFILE%.
PrivateKeyPassword "" Password for the PEM private-key file when it is encrypted. Omit or leave empty if the key is not password-protected.

Hub Setup

using SRO.Net.BIDHASP.Core;
using SRO.Net.BIDHASP.Hub;

// 1. Initialize configuration.
APIConfigurationManager.Initialize();

// 2. Create the hub manager.
HubManager hub = new HubManager();        // uses the configured port (default: 4800)
// — or —
HubManager hub = new HubManager(5000);   // explicit port override

// 3. Wire up logging.
hub.MessagePosted += (sender, e) =>
{
    Console.WriteLine($"[{e.Level}] {e.Message}");
    if (e.Cause != null) Console.WriteLine(e.Cause);
};

// 4. Handle spoke lifecycle events.
hub.SpokeWorkflowEvent += (sender, e) =>
{
    if (e.EventType == SpokeWorkflowEventType.ASSIGNED_ID)
    {
        Console.WriteLine($"Spoke connected — ID: {e.Spoke.Id}, Name: {e.Spoke.Name}");

        // Subscribe to per-spoke data events.
        e.Spoke.ReceiveStringData += (s, args) =>
            Console.WriteLine($"String from spoke {args.Id} (type {args.MessageType}): {args.Data}");

        e.Spoke.ReceiveBinaryData += (s, args) =>
            Console.WriteLine($"Binary from spoke {args.Id} (type {args.MessageType}): {args.Data.Length} bytes");
    }
};

// 5. Start the hub — binds the socket and begins accepting connections.
hub.Start();

// 6. Broadcast to all connected spokes.
hub.Send(messageType: 1, data: "Hello, all spokes!");
hub.Send(messageType: 2, data: new byte[] { 0x01, 0x02, 0x03 });

// 7. Send to a specific spoke by ID.
Spoke target = hub.SpokeUnits.FirstOrDefault(s => s.Id == 3);
target?.Send(messageType: 1, stringInfo: "Hello, spoke 3 only!");

// 8. Stop the hub — closes all connections.
hub.Stop();

Spoke Setup

using SRO.Net.BIDHASP.Core;
using SRO.Net.BIDHASP.Spoke;

// 1. Initialize configuration.
APIConfigurationManager.Initialize();

// 2. Create the spoke manager.
SpokeManager spokeManager = new SpokeManager();
// — or —
SpokeManager spokeManager = new SpokeManager(hubHostname: "192.168.1.10", port: 4800);

// 3. Wire up logging.
spokeManager.MessagePosted += (sender, e) =>
    Console.WriteLine($"[{e.Level}] {e.Message}");

// 4. Subscribe to data events on the hub facade.
spokeManager.Hub.ReceiveStringData += (sender, e) =>
    Console.WriteLine($"Received string from hub (type {e.MessageType}): {e.Data}");

spokeManager.Hub.ReceiveBinaryData += (sender, e) =>
    Console.WriteLine($"Received binary from hub (type {e.MessageType}): {e.Data.Length} bytes");

// 5. Subscribe to the ID-assignment event (fired once, shortly after Connect).
spokeManager.Hub.AssignedIdReceived += (sender, e) =>
    Console.WriteLine($"Hub assigned this spoke ID: {e.AssignedId}");

// 6. Connect to the hub (synchronous — includes a brief settlement delay).
spokeManager.Hub.Connect();

// 7. Send data to the hub.
spokeManager.Hub.Send(messageType: 1, stringInfo: "Hello, hub!");
spokeManager.Hub.Send(messageType: 2, bytes: new byte[] { 0xAA, 0xBB });

Wire Protocol

Every message is framed as a TransactedData packet with a fixed 12-byte header followed by a variable-length payload:

 Offset  0       4       6    7       8          12     12+N
         +-------+-------+----+-------+----------+------+
         |  Id   |MsgType|BDT | DDT   |PayloadSz |  ... |
         | int32 |uint16 |byte| byte  |  uint32  |  N   |
         +-------+-------+----+-------+----------+------+
         |<------------- 12-byte header -------->|<pay->|
Field Type Description
Id int32 Sender's unit ID. Hub = 0, unregistered spoke = -2, registered spoke = positive integer.
MessageType uint16 Application-defined discriminator identifying the message kind.
BasicDataType byte Major data category: String (1) or Binary (2).
DetailedDataType byte Sub-type such as PlainString, JSONSerialization, Int32 (see enum below).
PacketSize uint32 Payload length in bytes.
Payload byte[] The actual data bytes.

TransactedData can be serialized with ToByteArray() and deserialized with FromByteArray(byte[]). The payload size can be extracted from the header alone using GetHeaderPayloadSize(byte[]).


Sending and Receiving Data

Sending

Both HubManager and BIDCommunicationsUnitBase (the base for Spoke and the spoke-side Hub facade) expose the same family of send methods:

Method Description
Send(ushort messageType, byte[] bytes) Sends a raw byte array with DetailedDataType.ByteArray.
Send(ushort messageType, string stringInfo) Sends a UTF-8 string with DetailedDataType.PlainString.
BasicBinarySend(DetailedDataType ddt, ushort messageType, byte[] bytes) Sends bytes with an explicit DetailedDataType.
BasicStringSend(DetailedDataType ddt, ushort messageType, string stringInfo) Sends a string with an explicit DetailedDataType.
CoreSend(BasicDataType bdt, DetailedDataType ddt, ushort messageType, byte[] bytes) Low-level send with full type control.

Note: HubManager.Send() broadcasts to all connected spokes. To target a specific spoke, call Send() directly on the Spoke instance retrieved from HubManager.SpokeUnits.

Receiving

Subscribe to the ReceiveStringData or ReceiveBinaryData events on the relevant object:

unit.ReceiveStringData += (sender, e) =>
{
    // e.Id              — sender's unit ID
    // e.MessageType     — application-defined message type
    // e.DetailedDataType — e.g. PlainString, JSONSerialization
    // e.Data            — the string payload
};

unit.ReceiveBinaryData += (sender, e) =>
{
    // e.Id              — sender's unit ID
    // e.MessageType     — application-defined message type
    // e.DetailedDataType — e.g. ByteArray, Int32, Double …
    // e.Data            — the raw byte array
};

Events

HubManager Events

Event Args Type Description
MessagePosted BIDHASPMessageEventArgs Diagnostic/logging messages from the hub and its spokes.
ReceiveBinaryData ReceiveBinaryEventArgs Fired when any spoke sends binary data to the hub.
ReceiveStringData ReceiveStringEventArgs Fired when any spoke sends string data to the hub.
SpokeWorkflowEvent SpokeWorkflowEventArgs Spoke lifecycle events (see SpokeWorkflowEventType).

SpokeWorkflowEventType Values

Value Description
NEW A new incoming socket connection has been accepted.
CONNECTED The spoke's communication channel has been fully established.
ASSIGNED_ID The spoke has been assigned a unique integer ID — safe to begin sending per-spoke data.
DISCONNECTED The spoke has disconnected or its socket has closed.

BIDCommunicationsUnitBase / Spoke / Hub Events

Event Args Type Description
MessagePosted BIDHASPMessageEventArgs Diagnostic/logging messages from this unit.
ReceiveBinaryData ReceiveBinaryEventArgs Fired when binary data arrives from the partner.
ReceiveStringData ReceiveStringEventArgs Fired when string data arrives from the partner.

Hub (Spoke-side facade) Additional Event

Event Args Type Description
AssignedIdReceived AssignedIdEventArgs Fired once after Connect() when the hub assigns the spoke its unique ID.

Communication Channel Types

Type Enum Value Description
Direct Socket CommunicationChannelType.Socket Raw TCP socket with no stream wrapper.
Network Stream CommunicationChannelType.Stream NetworkStream wrapping the socket. Recommended for development.
TLS Stream CommunicationChannelType.TLSStream TLS 1.2/1.3 encrypted SslStream over a NetworkStream. Required for production.

TLS Configuration

When CommunicationChannelType is TLSStream, the certificate is resolved in the following priority order:

  1. Thumbprint — Searches the CurrentUser\My and LocalMachine\My certificate stores.
  2. Subject Name — Searches the same stores by subject name.
  3. File-based:
    • .pfx / .p12 — PKCS#12 bundle containing the certificate and private key.
    • PEM certificate paired with a separate PEM private-key file (KeyFilePath). If the private key is encrypted, set PrivateKeyPassword in the configuration file.

The hub authenticates as the TLS server using the loaded certificate; spokes authenticate as TLS clients against the configured HubHost. Both TLS 1.2 and TLS 1.3 are accepted.


Message Levels

BIDHASPMessageLevel categorizes messages surfaced by the MessagePosted event:

Level Value Meaning
Verbose 0 Detailed trace output; safe to suppress in production.
Information 1 Normal operational status messages.
Warning 2 Something unexpected occurred but the system continues.
Error 3 A recoverable error that should be investigated.
Fatal 4 An unrecoverable error requiring immediate attention.

API Reference

APIConfigurationManager (static — SRO.Net.BIDHASP.Core)

Member Type Description
Initialize() Method Loads or creates the default bidhasp.config.json in %USERPROFILE%.
Initialize(appname) Method Loads {appname}.config.json from %USERPROFILE%.
Initialize(folder, appname) Method Loads {appname}.config.json from an explicit absolute folder path.
LoadConfiguration(folder, appname) Method Deserializes an existing configuration file into memory.
SaveConfiguration(folder, appname) Method Serializes the current in-memory configuration to disk.
HubHost string Configured hub hostname (default: "localhost").
Port int Configured port (default: 4800).
CommunicationChannelType CommunicationChannelType Active transport type (Socket, Stream, or TLSStream).
CertificateFilePath string Path to the TLS certificate file.
KeyFilePath string Path to the PEM private-key file.
Thumbprint string Certificate thumbprint for store-based lookup.
SubjectName string Certificate subject name for store-based lookup.
PathIsRelativeToProfileDirectory bool Whether relative cert paths are resolved against %USERPROFILE%.
PrivateKeyPassword string Password for the encrypted PEM private-key file; null if the key is not password-protected.
DefaultPort const int 4800
DefaultAppName const string "bidhasp"
DefaultHubHost const string "localhost"

HubManager (SRO.Net.BIDHASP.Hub)

Member Type Description
HubManager() Constructor Creates a hub using the port from APIConfigurationManager.
HubManager(int port) Constructor Creates a hub listening on the specified port.
Start() Method Binds the TCP socket and begins accepting spoke connections.
Stop() Method Closes all active spoke connections and the listening socket.
Send(ushort, byte[]) Method Broadcasts a byte array to all connected spokes.
Send(ushort, string) Method Broadcasts a UTF-8 string to all connected spokes.
BasicBinarySend(DetailedDataType, ushort, byte[]) Method Broadcasts bytes with an explicit DetailedDataType.
BasicStringSend(DetailedDataType, ushort, string) Method Broadcasts a string with an explicit DetailedDataType.
CoreSend(BasicDataType, DetailedDataType, ushort, byte[]) Method Low-level broadcast with full type control.
PostMessage(string, BIDHASPMessageLevel, Exception) Method Posts a diagnostic message (raises MessagePosted).
Port int The port this hub is listening on.
SpokeUnits List<Spoke> Live list of currently connected spoke units.
Name string Always "#HUB#".
Id int Always 0.
State CommunicationsUnitState Current operational state of the hub.
LastException Exception Last exception encountered by the hub, if any.
UnitType CommunicationUnitType Always HubServer.
MessagePosted Event Diagnostic/logging messages.
ReceiveBinaryData Event Aggregated binary data received from all spokes.
ReceiveStringData Event Aggregated string data received from all spokes.
SpokeWorkflowEvent Event Spoke lifecycle notifications.

SpokeManager (SRO.Net.BIDHASP.Spoke)

Member Type Description
SpokeManager() Constructor Creates a spoke manager using configured hub hostname and port.
SpokeManager(string hubHostname, int port) Constructor Creates a spoke manager with explicit hub address.
Hub Hub The hub facade; call Connect() and Send() through this property.
HubHostname string The hub's hostname.
Port int The hub's port.
PostMessage(string, BIDHASPMessageLevel, Exception) Method Posts a diagnostic message (raises MessagePosted).
MessagePosted Event Diagnostic/logging messages from the spoke and its hub channel.

Hub — Spoke-side facade (SRO.Net.BIDHASP.Spoke)

Member Type Description
Connect() Method Resolves the hub's IP, opens a TCP connection, and completes the name-assignment handshake.
Send(ushort, byte[]) Method Sends a byte array to the hub.
Send(ushort, string) Method Sends a UTF-8 string to the hub.
BasicBinarySend(DetailedDataType, ushort, byte[]) Method Sends bytes with an explicit DetailedDataType.
BasicStringSend(DetailedDataType, ushort, string) Method Sends a string with an explicit DetailedDataType.
Manager SpokeManager The owning SpokeManager.
HubHostname string The hub's hostname.
Port int The hub's port.
Id int This spoke's hub-assigned ID (available after AssignedIdReceived fires).
State CommunicationsUnitState Current connection state.
AssignedIdReceived Event Fires once after Connect() when the hub assigns the spoke its ID.
ReceiveBinaryData Event Fired when binary data arrives from the hub.
ReceiveStringData Event Fired when string data arrives from the hub.
MessagePosted Event Diagnostic/logging messages.

DetailedDataType Enum (SRO.Net.BIDHASP.Core)

Value Description
Undefined (0) Unspecified — plain byte array or string with no further sub-typing.
RequestName (1) Control: spoke sends its hostname to the hub to request a name assignment.
AssignId (2) Control: hub sends the spoke's assigned integer ID (int32 payload).
ByteArray (3) Generic raw byte array.
PlainString (4) Plain UTF-8 text.
JSONSerialization (5) JSON-encoded string.
Int8 (6) Signed 8-bit integer.
UInt8 (7) Unsigned 8-bit integer.
Int16 (8) Signed 16-bit integer.
UInt16 (9) Unsigned 16-bit integer.
Int32 (10) Signed 32-bit integer.
UInt32 (11) Unsigned 32-bit integer.
Int64 (12) Signed 64-bit integer.
UInt64 (13) Unsigned 64-bit integer.
Float (14) 32-bit IEEE floating-point.
Double (15) 64-bit IEEE floating-point.
Boolean (16) Boolean value.
Character (17) Single character.
Decimal (18) 128-bit decimal value.
DateTime (19) DateTime.Ticks encoded as a signed 64-bit integer.
BinarySerialization (20) Custom plug-in binary serialization (see ExternalBinarySerializationManager).

CommunicationsUnitState Enum (SRO.Net.BIDHASP.Core)

Value Description
Undefined (-1) Not yet initialized.
Disconnected (0) Not connected to a partner (spoke/client state).
Connected (1) TCP connection established and channel active (spoke/client state).
Active (2) Hub is running and accepting connections (hub state).
Inactive (3) Hub has been stopped (hub state).
ExceptionDetected (4) A socket or stream error occurred; check LastException.

BIDCommunicationsUnitBase Constants (SRO.Net.BIDHASP.Core)

Constant Value Description
HUB_ID 0 Reserved ID for the hub.
UNDEFINED_ID -1 Default ID before a unit is registered.
CONTROL_MSG_ID -2 Temporary ID used during the connection handshake.

No packages depend on SRO.Net.BIDHASP.

.NET Framework 4.8

.NET 10.0

.NET 8.0

.NET 9.0

Version Downloads Last updated
1.0.0 4 09/02/2026