proto-socket/PROTOCOL.md
toki 7fe90e4ea7 docs: add Python and Rust to planned implementations
- README.md, PROTOCOL.md: Python, Rust 추가
- .gitignore: python/, rust/ 빌드 아티팩트 규칙 추가

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

4.2 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
Python descriptor.name from MessageClass.DESCRIPTOR — verify matches
Rust M::default().descriptor_dyn().name().to_string() (protobuf crate) — 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/
Python Planned python/
Rust Planned rust/

Proto Source

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