Esiur Protocol (EP) Specification
1. Overview
The Esiur Protocol (EP) is a compact binary application-layer protocol for distributed object computing. It enables first-class sharing of object instances across a TCP or WebSocket connection: property values synchronize in real time, remote methods are called as if local, and events propagate transparently to every subscriber.
EP is fully self-describing: a client can discover the type schema
of any resource at runtime (a TypeDef query) and generate
strongly-typed proxies without out-of-band API documents. Actively maintained
implementations exist for .NET and
TypeScript, sharing wire concepts, packet
layout, and command names byte-for-byte.
Key design properties:
- Minimal wire overhead β a 1-byte header is a complete, valid notification packet; request/reply packets add a 4-byte callback ID.
- Correlation-safe β every request carries a callback ID matched to its asynchronous reply.
- Streamable β chunked transfers, progress reports, and pausable pull/push streams for long-running operations.
- Authenticated by construction β every connection completes an authentication phase (possibly a no-op, see Β§4) before any session packet is exchanged.
2. Transport & URI scheme
TCP
EP runs natively over TCP. EpServer defaults to TCP port
10518 in both reference implementations; a bare
ep://host URL (no explicit port) resolves to the same default.
The server accepts incoming connections and immediately begins the
Authentication Phase (Β§4).
WebSocket
EP also runs as a WebSocket sub-protocol, identifier EP
(uppercase, compared case-sensitively/ordinally by both implementations).
After the WebSocket handshake, the binary framing is identical to the
TCP wire format: each WebSocket message carries exactly one EP packet.
URI scheme
Resources are addressed with an ep:// URL:
ep://<host>[:port]/<store>/<resource-path>
Examples
ep://server.example.com/sys/thermostat
ep://localhost:10518/sys/counter
ep://iot-hub.local/sensors/temperature
The path maps directly to a resource in the server's store hierarchy,
analogous to a filesystem path. Omitting the port uses 10518.
A bare ep://host[:port] with no path resolves to the connection itself.
eps:// to
wss:// (TLS-protected WebSocket) β this is implemented and
working. The .NET client's URL resolver does not currently register a
distinct handler for an eps/EPS scheme, and
neither implementation currently wraps raw TCP connections in
TLS automatically. Confidentiality on a raw TCP connection today
relies entirely on the application-layer session encryption negotiated
during the auth handshake (Β§4.3), not on transport-layer TLS. See
Β§8 and Β§9.
3. Connection lifecycle
Client Server
β β
βββββββββββ TCP / WebSocket connect ββββββββββββΆβ
β β
βββββββββ Auth: Initialize ββββββββββββββββββββββ server sends first
β β
βββββββββ Auth: Action ββββββββββββββββββββββββββΆ zero or more rounds,
βββββββββ Auth: Acknowledge βββββββββββββββββββββ protocol-dependent
β β
βββββββββ Auth: SessionEstablished ββββββββββββββ
β β
ββββββββββββββ EP session packets ββββββββββββββΆβ
β Request / Reply / Notification β
β β
βββββββββββ connection close βββββββββββββββββββΆβ
Once the session is established, the connection is fully bidirectional β either peer may send Requests, Replies, or Notifications at any time. A resource attachment stays live until the connection closes or the client explicitly detaches; on reconnect, a client can reattach and resynchronize rather than re-fetch full state from scratch.
4. Authentication phase
Before any EP session packet is transmitted, both sides exchange one or
more auth packets, a distinct binary format from session
packets. The server always sends the first packet (Initialize).
Even a connection configured for anonymous access completes this phase β
it just negotiates AuthenticationMode.None with
EncryptionMode.None in a single round trip.
4.1 Auth packet header byte
| Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
|---|---|---|---|---|---|---|---|
| Command2 bits | hasTDU1 bit | reserved1 bit | AuthMode2 bits β Initialize only | EncMode2 bits β Initialize only | |||
For every command other than Initialize, bits 4β0
(with bit 5 masked off) are instead read as a single EpAuthPacketMethod
byte (Β§4.3) β the low 5 bits given above only apply to the very first,
server-sent Initialize packet. hasTDU (bit 5)
β when set, a TDU payload (Β§5) follows, encoding a keyβvalue map of
handshake parameters (Β§4.4).
4.2 Auth commands (bits 7β6)
| Value | Name | Direction | Description |
|---|---|---|---|
| 0x0 | Initialize | Server β Client | Opens authentication; announces supported modes and algorithms |
| 0x1 | Acknowledge | Both | Accept, deny, or redirect an authentication step |
| 0x2 | Action | Client β Server | A handshake step β credentials or key material |
| 0x3 | Event | Server β Client | Session established, an error, or an indication |
AuthenticationMode (bits 3β2 of an Initialize packet):
| Value | Name | Description |
|---|---|---|
| 0x0 | None | No identity required from either party |
| 0x1 | InitializerIdentity | The connecting client must authenticate |
| 0x2 | ResponderIdentity | The server must present its identity to the client |
| 0x3 | DualIdentity | Mutual authentication β both parties must prove identity |
EncryptionMode (bits 1β0 of an Initialize packet):
| Value | Name | Description |
|---|---|---|
| 0x0 | None | Session data transmitted without application-layer encryption |
| 0x1 | EncryptWithSessionKey | Symmetric session key (AES-256-GCM) derived during the handshake |
| 0x2 | EncryptWithSessionKeyAndAddress | Session key additionally bound to the connection's source address |
4.3 EpAuthPacketMethod (Acknowledge / Action / Event byte)
For any command other than Initialize, the byte identifies the specific handshake step:
| Value | Name | Command | Description |
|---|---|---|---|
| 0x40 | Denied | Acknowledge | Auth denied β terminate connection |
| 0x41 | NotSupported | Acknowledge | Requested method not supported β terminate |
| 0x42 | TrySupported | Acknowledge | Try an alternative, supported method |
| 0x43 | Retry | Acknowledge | Retry with different parameters |
| 0x44 | ProceedToHandshake | Acknowledge | Proceed to the key-exchange step |
| 0x45 | ProceedToFinalHandshake | Acknowledge | Proceed to the final handshake step |
| 0x46 | ProceedToEstablishSession | Acknowledge | Derive session keys |
| 0x47 | SessionEstablished | Acknowledge | Session ready β switch to EP session packets |
| 0x80 | Handshake | Action | Client sends key material / credentials |
| 0x81 | FinalHandshake | Action | Client sends final confirmation |
| 0x82 | KeyRotation | Action | Request mid-session encryption-key rotation (.NET only β see Β§8) |
| 0xC0 | Established | Event | Auth flow complete |
| 0xC1 | ErrorTerminate | Event | Fatal auth error β connection will close |
| 0xC2 | ErrorMustEncrypt | Event | Encryption is required for this resource |
| 0xC3 | ErrorRetry | Event | Transient error β client may retry |
| 0xC8 | IndicationEstablished | Event | Informational: (re-)established indication |
| 0xC9 | KeyRotationEstablished | Event | Key rotation completed (.NET only β see Β§8) |
| 0xD0 | IAuthPlain | Event | Identity assertion, unhashed |
| 0xD1 | IAuthHashed | Event | Identity assertion, hashed |
| 0xD2 | IAuthEncrypted | Event | Identity assertion, encrypted |
4.4 Auth TDU header keys
The TDU payload of an auth packet is a keyed map. Integer keys 0β17 are defined:
| # | Key | # | Key |
|---|---|---|---|
| 0 | Version | 9 | SoftwareIdentity |
| 1 | Domain | 10 | Referrer |
| 2 | SupportedAuthentications | 11 | Time |
| 3 | SupportedHashAlgorithms | 12 | IPAddress |
| 4 | SupportedCiphers | 13 | Identity |
| 5 | SupportedCompression | 14 | AuthenticationProtocol |
| 6 | SupportedMultiFactorAuthentications | 15 | AuthenticationData |
| 7 | CipherType | 16 | ErrorMessage |
| 8 | CipherKey | 17 | CipherNonce |
5. TDU (Transmission Data Unit) encoding
Every value carried inside a session or auth packet β arguments, return values, property contents, handshake parameters β is encoded as a self-describing TDU. Decoders never need out-of-band schema information: each TDU carries its own type tag and, for variable-length types, its own length.
5.1 Identifier byte
| Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
|---|---|---|---|---|---|---|---|
| Class2 bits | Fixed: exponent / Var-length: size-field count3 bits | Sub-type3 bits | |||||
| Bits 7β6 | Class | Bits 5β3 meaning |
|---|---|---|
00 | Fixed | Exponent β payload width = 0 if exponent is 0, else 1 << (exponentβ1) bytes. |
01 | Dynamic | Length-field byte count (0β7, see Β§5.3) β how many big-endian length bytes follow the identifier. |
10 | Typed | Same length-field encoding as Dynamic. Carries a type reference (record/enum/tuple). |
11 | Extension | Same length-field encoding as Dynamic. Protocol-level markers. |
For Dynamic, Typed, and Extension classes, the identifier recovered by a
parser is headerByte & 0xC7 (class bits + sub-type bits
only β the length-field-count bits are stripped once the length has been
read). The length bytes themselves are big-endian
(most-significant byte first):
| Bits 5β3 | OR-mask | Length-field size | Max payload |
|---|---|---|---|
000 | 0x00 | none β zero-length value | 0 B |
001 | 0x08 | 1 byte | 255 B |
010 | 0x10 | 2 bytes | 64 KB |
011 | 0x18 | 3 bytes | 16 MB |
100 | 0x20 | 4 bytes | 4 GB |
101β111 | 0x28β0x38 | 5β7 bytes | up to 72 PB |
5.2 Fixed class (0x00β0x2F)
| Width | Identifiers |
|---|---|
| 0 bytes | 0x00 Null, 0x01 False, 0x02 True, 0x03 NotModified, 0x04 Infinity |
| 1 byte | 0x08 UInt8, 0x09 Int8, 0x0A Char8, 0x0B LocalResource8, 0x0C RemoteResource8, 0x0D LocalProcedure8, 0x0E RemoteProcedure8 |
| 2 bytes | 0x10 UInt16, 0x11 Int16, 0x12 Char16, 0x13 LocalResource16, 0x14 RemoteResource16, 0x15 LocalProcedure16, 0x16 RemoteProcedure16 |
| 4 bytes | 0x18 UInt32, 0x19 Int32, 0x1A Float32, 0x1B LocalResource32, 0x1C RemoteResource32, 0x1D LocalProcedure32, 0x1E RemoteProcedure32 |
| 8 bytes | 0x20 UInt64, 0x21 Int64, 0x22 Float64, 0x23 DateTime (ticks, signed 64-bit) |
| 16 bytes | 0x28 UInt128, 0x29 Int128, 0x2A Decimal128, 0x2B UUID (RFC 4122, 16 raw bytes) |
Multi-byte Fixed-class payload contents (integers, floats, DateTime ticks) are little-endian β see Β§5.5 for verified examples. 128-bit integers are two little-endian 64-bit halves, low word first.
5.3 Dynamic class (0x40β0x47)
| Base ID | Name | Description |
|---|---|---|
| 0x40 | RawData | Opaque byte array |
| 0x41 | String | UTF-8 text |
| 0x42 | List | Ordered list of heterogeneous TDUs |
| 0x43 | ResourceList | List of resource references |
| 0x44 | RecordList | List of typed records |
| 0x45 | ResourceLink | Resource path, as a string |
| 0x46 | Map | Keyβvalue map |
| 0x47 | MapList | Ordered list of maps |
5.4 Typed & Extension classes
| Identifier | Class | Name | Description |
|---|---|---|---|
| 0x80 | Typed | Typed | Record/enum/tuple value; embeds a type-reference (TRU) block before its content |
| 0x81 | Typed | TypeDef | A full type definition, returned in answer to a TypeDef query |
| 0x82 | Typed | TRU | A standalone type-reference unit |
| 0xC0 | Extension | TypeContinuation | Multi-part type reference β continuation fragment |
| 0xC1 | Extension | TypeOfTarget | Use the enclosing resource's own type as the type reference |
5.5 Verified wire examples
Each of these is a complete TDU (identifier byte, any length bytes, payload), taken from byte-for-byte matching test vectors shared between the .NET and TypeScript test suites:
The Int32/Int64 examples above intentionally show a multi-byte payload: note the least-significant byte comes first. This is the opposite byte order from the TDU length-prefix field (Β§5.1), which is big-endian β the two fields are independent and use different conventions.
6. EP packet format
After the authentication phase completes, all traffic uses EP session packets. Every packet begins with a single header byte; Request and Reply packets additionally carry a 4-byte callback ID. Payloads, when present, are TDU-encoded (Β§5).
| Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 |
|---|---|---|---|---|---|---|---|
| Method2 bits | hasTDU1 bit | Command5 bits | |||||
| Bits 7β6 | Method | Callback ID? | Notes |
|---|---|---|---|
00 | Notification | No β 1-byte minimum packet | Unsolicited; no reply expected |
01 | Request | Yes β 4-byte little-endian uint32 | 5-byte minimum packet |
10 | Reply | Yes β echoes the Request's callback ID | 5-byte minimum packet |
11 | Extension | No | Reserved β parsed by both implementations, emitted by neither (see Β§8) |
Verified example β a KeepAlive request (no payload) with callback ID 12:
7. Commands reference
Request commands
| Value | Name | Group | Description |
|---|---|---|---|
| 0x00 | InvokeFunction | Invoke | Call a method on a resource instance by index |
| 0x01 | SetProperty | Invoke | Set a resource property by index |
| 0x02 | Subscribe | Invoke | Subscribe to property/event change notifications |
| 0x03 | Unsubscribe | Invoke | Cancel a subscription |
| 0x08 | TypeDefIdsByNames | Inquire | Resolve numeric type IDs from class name strings |
| 0x09 | TypeDefById | Inquire | Fetch a full type definition by numeric ID |
| 0x0A | TypeDefByResourceId | Inquire | Fetch the type definition for an attached resource |
| 0x0B | Query | Inquire | Search resources by path pattern |
| 0x0C | LinkTypeDefs | Inquire | Get all type definitions reachable from a resource link |
| 0x0D | Token | Inquire | Request an authentication token |
| 0x0E | GetResourceIdByLink | Inquire | Resolve a resource path to its server-assigned numeric ID |
| 0x10 | AttachResource | Manage | Attach to a resource; server returns its full current state |
| 0x11 | ReattachResource | Manage | Re-sync after reconnection |
| 0x12 | DetachResource | Manage | Stop receiving updates for this resource |
| 0x13 | CreateResource | Manage | Instantiate a new resource on the server |
| 0x14 | DeleteResource | Manage | Destroy a server-side resource |
| 0x15 | MoveResource | Manage | Rename or relocate a resource |
| 0x18 | KeepAlive | Static | Connection heartbeat β no payload |
| 0x19 | ProcedureCall | Static | Call a named server-side procedure |
| 0x1A | StaticCall | Static | Call a static/class-level method β no instance required |
| 0x1B | IndirectCall | Static | Indirect invocation via a callable reference. Declared, not yet dispatched by the reference server β see Β§8. |
| 0x1C | PullStream | Static | Pull the next chunk from a server-side stream |
| 0x1D | TerminateExecution | Static | Abort an in-progress execution |
| 0x1E | HaltExecution | Static | Pause an in-progress (pausable) execution |
| 0x1F | ResumeExecution | Static | Resume a halted execution |
Reply commands carry the Request's callback ID
| Value | Name | Group | Description |
|---|---|---|---|
| 0x00 | Completed | Success | Operation completed; payload is the return value, if any |
| 0x01 | Propagated | Success | Result delivered to a downstream subscriber |
| 0x02 | Stream | Success | Operation returned a stream handle |
| 0x04 | PermissionError | Error | Request denied β insufficient permissions |
| 0x05 | ExecutionError | Error | Exception thrown during method execution |
| 0x08 | Progress | Partial | Intermediate progress update; request still pending |
| 0x09 | Chunk | Partial | One chunk of a streamed response; more may follow |
| 0x0A | Warning | Partial | Non-fatal warning; operation continues |
Notification commands no callback ID β unsolicited
| Value | Name | Group | Description |
|---|---|---|---|
| 0x00 | PropertyModified | Invoke | A subscribed property value changed |
| 0x01 | EventOccurred | Invoke | A subscribed event was raised |
| 0x08 | ResourceDestroyed | Manage | An attached resource was deleted on the server |
| 0x09 | ResourceReassigned | Manage | An attached resource was assigned a new server ID |
| 0x0A | ResourceMoved | Manage | An attached resource was renamed or relocated |
| 0x0B | SystemFailure | Manage | System-level failure affecting the connection |
8. Implementation notes
EP is specified so that independent implementations interoperate over the wire without a translation layer, and the two reference implementations agree on every packet, command, and TDU value covered above. As of this writing, a few areas are implemented in one reference implementation but not (yet) the other:
| Area | .NET | TypeScript |
|---|---|---|
| WebSocket subprotocol enforcement on accept | Required β rejects the upgrade with HTTP 400 if the client didn't request EP | Not currently validated β EpServer accepts the upgrade regardless of the requested subprotocol |
eps:// URI scheme | Not currently registered as a distinct handler | Maps to wss:// (TLS-protected WebSocket) |
Mid-session key rotation (KeyRotation / KeyRotationEstablished, Β§4.3) | Implemented, used by the PPAP authentication provider | Not yet implemented |
| Untrusted-input TDU length guards | Configurable maximum payload/allocation limits (WarehouseConfiguration) | No equivalent limit yet at the TDU-parsing layer |
EpPacketMethod.Extension (session packets, Β§6) and IndirectCall (Β§7) | Parsed if received; not currently emitted or dispatched | Not currently emitted |
None of this affects wire compatibility for the features both implementations do support today β it's listed here so implementers building a third party don't assume behavior that isn't universal yet.
9. Security considerations
EncryptionMode.None, a connection has no
identity verification and no application-layer confidentiality at all.
-
For any internet-facing deployment, use
InitializerIdentityorDualIdentityauthentication together withEncryptWithSessionKey(orEncryptWithSessionKeyAndAddress) encryption β or terminate the connection inside a TLS tunnel (awss://reverse proxy, or a TLS-wrapping proxy in front of raw TCP), since neither reference implementation currently wraps raw TCP in transport-layer TLS automatically (Β§8). - Implementations parsing TDUs from an untrusted peer should enforce a maximum declared length before allocating a buffer for the length prefix described in Β§5.1 β an attacker-controlled length field is an obvious resource-exhaustion vector otherwise.
-
A conforming WebSocket-hosting server should require and verify the
negotiated subprotocol is exactly
EP(ordinal, case-sensitive) before treating a connection as an EP session, even though not every implementation enforces this yet (Β§8). -
Stored password verifiers should be treated as sensitive as a plaintext
password: under the
password-sha3-v1protocol, possessing the stored verifier is sufficient to authenticate. -
ppap-mlkem768-v1is available where post-quantum resistance against key material is a requirement β see the PPAP paper [Zamil & Jasim, 2025] in Β§10.
10. References
- A. K. Zamil and A. D. Jasim, "A Multi-Factor Quantum-Resistant and Privacy-Preserving Authentication Protocol for Decentralized Systems," Mesopotamian Journal of CyberSecurity, vol. 5, no. 3, pp. 1272β1291, 2025. Accessed: Jul. 22, 2026. [Online]. Available: mesopotamian.press/journals/index.php/CyberSecurity/article/view/989 β the PPAP protocol behind
ppap-mlkem768-v1(Β§9) - RFC 6455 β The WebSocket Protocol
- RFC 4122 β UUID layout, referenced by the TDU
UUIDtype - github.com/esiur/esiur-dotnet β reference implementation, .NET
- github.com/esiur/esiur-ts β reference implementation, TypeScript