proto-socket/PROTOCOL.md
toki 240239682c Initial commit: Dart implementation of toki-socket protocol
- Protobuf-based binary TCP socket library
- ProtobufClient / ProtobufServer abstract classes with heartbeat
- Type-based message routing via Communicator
- 4-byte big-endian length-prefixed framing protocol
- 8 integration tests (all passing)
- PROTOCOL.md wire format specification
- VSCode launch.json for test runner

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 13:44:04 +09:00

4 KiB

Toki Socket Protocol

Binary TCP protocol using Protocol Buffers for message framing and type-based routing. Designed for bidirectional, heterogeneous communication across languages and platforms.


Wire Format

┌──────────────────────────────────────────┐
│  Header  (4 bytes, big-endian int32)     │  ← PacketBase payload length
├──────────────────────────────────────────┤
│  PacketBase  (protobuf, N bytes)         │  ← typeName + nonce + data
└──────────────────────────────────────────┘

Header

  • 4 bytes, big-endian signed int32
  • Value: byte length of the following PacketBase protobuf payload
  • Value of 0: reserved / no-op, receiver clears buffer

PacketBase

message PacketBase {
  string typeName = 1;   // fully-qualified protobuf message name
  int32  nonce    = 2;   // monotonically increasing per-connection counter
  bytes  data     = 3;   // serialized inner message bytes
}
Field Description
typeName GeneratedMessage.info_.qualifiedMessageName — language-agnostic routing key
nonce Starts at 1, increments by 1 per send() call per connection
data Protobuf-serialized bytes of the inner message

Built-in Messages

HeartBeat

message HeartBeat {}

Automatically handled by the framework. Applications do not need to register or handle this manually.


Heartbeat Lifecycle

Side A                                    Side B
  │                                          │
  │── (no activity for intervalTime) ──────▶│
  │        send(HeartBeat)                   │
  │                                          │── onHeartBeat() called
  │                                          │   send(HeartBeat) [response]
  │◀─────────────────────────────────────────│
  │  onHeartBeat() called                    │
  │  _waitingHeartbeatResponse = false       │
  │  timer reset                             │
  • After any received message → heartbeat interval timer resets
  • If no data received within heartbeatIntervalTime seconds → send HeartBeat
  • If no response within heartbeatWaitTime seconds → onDisconnected()dispose()
  • Only the initiating side responds to HeartBeat echo (ping-pong prevention via _waitingHeartbeatResponse flag)

typeName Convention

Uses GeneratedMessage.info_.qualifiedMessageName on the send side. On the receive side, use the same value as the registration key.

Language Registration key
Dart T.toString() (equals qualified name for top-level proto messages)
C# typeof(T).Name — verify matches proto qualified name
Kotlin T::class.simpleName — verify matches
Swift String(describing: T.self) — verify matches

Important: Verify typeName consistency across languages before connecting heterogeneous clients.


nonce

  • Starts at 1 per connection
  • Increments by 1 on every send() call
  • Currently used as a message ID; request-response correlation can be built on top

Example Packet (hex)

Sending HeartBeat {}:

00 00 00 05          ← header: PacketBase length = 5 bytes
0A 09 48 65 61 72 74 42 65 61 74  ← PacketBase { typeName: "HeartBeat" }

Multi-language Implementations

Language Status Path
Dart Available dart/
C# (Unity) Planned csharp/
Kotlin Planned kotlin/
Swift Planned swift/

Proto Source

dart/lib/src/packets/message_common.proto is the canonical packet definition. All language implementations must generate bindings from this file.