Protocol
Version: 1.0Date: January 2026Status: Draft
Table of Contents
Section titled “Table of Contents”- Introduction
- Protocol Architecture
- Discovery Mechanism
- Session Lifecycle
- Timecode Streaming
- Control Commands
- Sync Protocol
- Device Types
- Security
- Error Handling
- Appendix
1. Introduction
Section titled “1. Introduction”1.1 Purpose
Section titled “1.1 Purpose”The Helm Protocol (SLP) is a communication protocol designed for real-time synchronization in live production environments. It enables ultra-low latency timecode distribution, show data synchronization, and device management across the Helm product family.
1.2 Design Goals
Section titled “1.2 Design Goals”| Goal | Target | Rationale |
|---|---|---|
| Timecode Latency | <1ms | Required for frame-accurate sync |
| Reliability | 99.99% uptime | Live production critical |
| Scalability | 100+ devices | Large production requirements |
| Security | Session-based auth | Prevent unauthorized control |
| Discoverability | Zero-config | Ease of setup |
1.3 Scope
Section titled “1.3 Scope”This specification covers:
- Wire protocol format
- Transport layer requirements
- Session management
- Device discovery
- Timecode streaming
- Show data synchronization
Out of scope:
- VOIP/talkback audio (future extension)
- Video streaming
- Cloud connectivity
2. Protocol Architecture
Section titled “2. Protocol Architecture”2.1 Transport Layer
Section titled “2.1 Transport Layer”SLP uses QUIC (RFC 9000) as its transport protocol, providing:
- Connection migration
- Multiplexed streams
- Built-in TLS 1.3
- Unreliable datagrams (RFC 9221)
Implementation: quinn crate v0.11+
2.2 Channel Types
Section titled “2.2 Channel Types”| Channel | QUIC Feature | Reliability | Use Case |
|---|---|---|---|
| Timecode | Datagram | Unreliable | Ultra-low latency TC |
| Control | Unidirectional Stream | Reliable | Commands, config |
| Sync | Bidirectional Stream | Reliable | Show data |
| Heartbeat | Datagram | Unreliable | Keep-alive |
2.3 Message Format
Section titled “2.3 Message Format”All messages follow this binary format:
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| Magic (0x534C) | Version | Type |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+| Flags | Payload Length | |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| |+ Payload (MessagePack) +| |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+Header Fields (7 bytes):
| Field | Offset | Size | Description |
|---|---|---|---|
| Magic | 0 | 2 | 0x534C (“SL”) |
| Version | 2 | 1 | Protocol version (currently 0x01) |
| Type | 3 | 1 | Message type enum |
| Flags | 4 | 1 | Message flags |
| Length | 5 | 2 | Payload length (big-endian) |
Maximum payload size: 65,536 bytes
2.4 Message Types
Section titled “2.4 Message Types”| Value | Type | Direction | Channel |
|---|---|---|---|
| 0x01 | Timecode | Both | Datagram |
| 0x02 | Heartbeat | Client→Master | Datagram |
| 0x03 | HeartbeatAck | Master→Client | Datagram |
| 0x10 | JoinRequest | Client→Master | Stream |
| 0x11 | JoinResponse | Master→Client | Stream |
| 0x12 | Leave | Both | Stream |
| 0x20 | Control | Master→Client | Stream |
| 0x21 | ControlResponse | Client→Master | Stream |
| 0x30 | ShowSync | Master→Client | Stream |
| 0x31 | TrackUpdate | Master→Client | Stream |
| 0x32 | CueUpdate | Master→Client | Stream |
| 0x33 | ActiveTrackChange | Both | Stream |
| 0x34 | PlaybackStateChange | Master→Client | Stream |
| 0x40 | PermissionUpdate | Master→Client | Stream |
| 0x50 | DiscoveryBroadcast | Any | UDP |
| 0x51 | DiscoveryResponse | Any | UDP |
| 0xFF | Error | Both | Stream |
2.5 Message Flags
Section titled “2.5 Message Flags”| Bit | Flag | Description |
|---|---|---|
| 0 | COMPRESSED | Payload is zstd compressed |
| 1 | PRIORITY | High priority, skip queue |
| 2 | REQUIRES_ACK | Sender expects acknowledgment |
| 3 | IS_ACK | This is an acknowledgment |
| 4-7 | Reserved | Must be zero |
2.6 Payload Encoding
Section titled “2.6 Payload Encoding”Payloads are encoded using MessagePack for compact binary serialization. This provides:
- Smaller size than JSON
- Faster parsing
- Schema flexibility
- Cross-language support
3. Discovery Mechanism
Section titled “3. Discovery Mechanism”3.1 mDNS (Primary)
Section titled “3.1 mDNS (Primary)”SLP uses mDNS/DNS-SD for zero-configuration discovery.
Service Type: _showlink._udp.local.
TXT Records:
| Key | Value | Description |
|---|---|---|
| device_id | UUID | Unique device identifier |
| device_type | String | Device type enum name |
| name | String | Human-readable name |
| clients | Integer | Connected client count |
| version | String | SLP version |
Example:
_showlink._udp.local. PTR cuemaster-abc123._showlink._udp.local.cuemaster-abc123._showlink._udp.local. SRV 0 0 5353 cuemaster-abc123.local.cuemaster-abc123._showlink._udp.local. TXT "device_id=abc123..." "device_type=CueMaster" "name=Main Console" "clients=5"3.2 UDP Broadcast (Fallback)
Section titled “3.2 UDP Broadcast (Fallback)”For networks where mDNS doesn’t work well:
Port: 5354
Broadcast Packet:
struct DiscoveryBroadcast { device_id: UUID, device_type: DeviceType, device_name: String, is_hosting: bool, session_name: Option<String>, client_count: Option<u32>,}Interval: Every 5 seconds while hosting
3.3 Manual Connection
Section titled “3.3 Manual Connection”Devices may also connect via direct IP address input, bypassing discovery.
4. Session Lifecycle
Section titled “4. Session Lifecycle”4.1 Session Model
Section titled “4.1 Session Model”- One device acts as Master (Helm Cues or CueMaster)
- Other devices connect as Clients
- Master controls session state and permissions
- Master distributes show data and timecode
4.2 Join Flow
Section titled “4.2 Join Flow”Client Master | | |------- JoinRequest --------------->| | | (validate) |<------ JoinResponse ---------------| | | |<------ ShowSync -------------------| | | |-------- Heartbeat ---------------->| |<------- HeartbeatAck --------------| | ... |4.3 JoinRequest
Section titled “4.3 JoinRequest”struct JoinRequest { device_id: UUID, device_type: DeviceType, device_name: String, version: String, requested_role: Role,}4.4 JoinResponse
Section titled “4.4 JoinResponse”struct JoinResponse { accepted: bool, session_token: Option<String>, // 32-char random string assigned_role: Role, session_id: UUID, master_name: String, rejection_reason: Option<String>,}4.5 Session Token
Section titled “4.5 Session Token”- 32 alphanumeric characters
- Included in all subsequent requests
- Validated by master on every message
- Revoked on disconnect or kick
4.6 Heartbeat
Section titled “4.6 Heartbeat”Interval: Every 5 seconds
Timeout: 15 seconds (3 missed heartbeats)
struct Heartbeat { sequence: u32,}
struct HeartbeatAck { sequence: u32,}4.7 Leave
Section titled “4.7 Leave”struct Leave { reason: String,}5. Timecode Streaming
Section titled “5. Timecode Streaming”5.1 Requirements
Section titled “5.1 Requirements”| Requirement | Target |
|---|---|
| End-to-end latency | <1ms |
| Packet rate | Frame rate (e.g., 30Hz) |
| Packet loss tolerance | Up to 10% |
| Jitter tolerance | ±5ms |
5.2 Timecode Packet
Section titled “5.2 Timecode Packet”Optimized for minimal size (26 bytes):
struct TimecodePacket { timestamp_us: u64, // Sender timestamp (µs) position_frames: u64, // Absolute frame position frame_rate: u8, // FrameRate enum state: u8, // PlaybackState enum source: u8, // TimecodeSource enum reserved: u8, // Must be zero track_id: u32, // Active track ID sequence: u16, // Packet sequence number}Binary Layout:
Offset | Size | Field-------|------|----------------0 | 8 | timestamp_us (BE)8 | 8 | position_frames (BE)16 | 1 | frame_rate17 | 1 | state18 | 1 | source19 | 1 | reserved20 | 4 | track_id (BE)24 | 2 | sequence (BE)5.3 Frame Rate Encoding
Section titled “5.3 Frame Rate Encoding”| Value | Frame Rate |
|---|---|
| 0 | 23.976 fps |
| 1 | 24 fps |
| 2 | 25 fps |
| 3 | 29.97 drop-frame |
| 4 | 29.97 non-drop |
| 5 | 30 fps |
| 6 | 50 fps |
| 7 | 59.94 fps |
| 8 | 60 fps |
5.4 Playback State
Section titled “5.4 Playback State”| Value | State |
|---|---|
| 0 | Stopped |
| 1 | Playing |
| 2 | Paused |
| 3 | Seeking |
| 4 | Cueing |
5.5 Timecode Source
Section titled “5.5 Timecode Source”| Value | Source |
|---|---|
| 0 | Internal (master generated) |
| 1 | LTC Input |
| 2 | MTC Input |
| 3 | Network |
5.6 Bidirectional Flow
Section titled “5.6 Bidirectional Flow”Master → Clients: Broadcast current playback position
Client → Master: TimeKeeper/CueMaster can inject external TC:
- LTC received from audio input
- MTC received from MIDI input
- Requires
InjectTimecodepermission
5.7 Latency Measurement
Section titled “5.7 Latency Measurement”Clients calculate round-trip time using:
RTT = current_time_us - packet.timestamp_usFor accurate sync, clients should compensate for half RTT.
6. Control Commands
Section titled “6. Control Commands”6.1 Overview
Section titled “6.1 Overview”Control commands are reliable messages sent over QUIC streams. They require appropriate permissions.
6.2 Command Types
Section titled “6.2 Command Types”Identify
Section titled “Identify”Flash LED or show message on device screen.
Identify { device_id: UUID, message: Option<String>, duration_ms: u32,}SetTimecodeSource
Section titled “SetTimecodeSource”Configure device timecode source.
SetTimecodeSource { device_id: UUID, config: TimecodeSourceConfig,}
struct TimecodeSourceConfig { primary: TimecodeInputSource, fallback: Option<TimecodeInputSource>, passthrough_output: bool, frame_rate: u8,}SetDisplayMode (CueKeeper)
Section titled “SetDisplayMode (CueKeeper)”Configure display mode.
SetDisplayMode { device_id: UUID, mode: DisplayMode,}
enum DisplayMode { CueList { show_upcoming: u8, show_previous: u8 }, Countdown { show_cue_name: bool }, TimecodeOnly { show_track_name: bool }, Message { headline: String, body: Option<String> }, Split { position: SplitPosition, percent: u8 }, Off,}GetStatus
Section titled “GetStatus”Request device status.
GetStatus { device_id: UUID }Response:
Status { device_id: UUID, uptime_secs: u64, cpu_percent: u8, memory_percent: u8, temperature_c: Option<f32>, network_status: NetworkStatus, timecode_source: TimecodeInputSource, timecode_valid: bool, firmware_version: String, errors: Vec<String>,}Measure round-trip latency.
Ping { device_id: UUID, timestamp_us: u64 }
Pong { device_id: UUID, original_timestamp_us: u64, response_timestamp_us: u64,}Reboot
Section titled “Reboot”Restart device.
Reboot { device_id: UUID, delay_ms: u32 }7. Sync Protocol
Section titled “7. Sync Protocol”7.1 Initial Sync
Section titled “7.1 Initial Sync”On join, master sends complete show state:
struct ShowSync { show_id: UUID, show_name: String, version: u64, frame_rate: String, tracks: Vec<SyncTrack>, active_track_id: Option<UUID>, metadata: ShowMetadata,}
struct SyncTrack { id: UUID, name: String, order: u32, duration_seconds: f64, offset_seconds: f64, cues: Vec<SyncCue>, cue_stacks: Vec<SyncCueStack>, enabled: bool,}
struct SyncCue { id: UUID, number: String, name: String, time_seconds: f64, cue_type: String, sector: String, notes: Option<String>, outputs: Vec<SyncCueOutput>,}7.2 Delta Updates
Section titled “7.2 Delta Updates”After initial sync, only changes are sent:
struct TrackUpdate { track_id: UUID, operation: UpdateOperation, track: Option<SyncTrack>, version: u64,}
struct CueUpdate { track_id: UUID, cue_id: UUID, operation: UpdateOperation, cue: Option<SyncCue>, version: u64,}
enum UpdateOperation { Add, Modify, Delete, Reorder,}7.3 Versioning
Section titled “7.3 Versioning”Each update increments the show version. Clients track version to detect missed updates. If version gap detected, client requests full resync.
7.4 Conflict Resolution
Section titled “7.4 Conflict Resolution”Master wins. Client changes are proposals that master may accept or reject.
8. Device Types
Section titled “8. Device Types”8.1 Helm Cues (CuesSoftware)
Section titled “8.1 Helm Cues (CuesSoftware)”Desktop application acting as session master.
Capabilities:
- Host sessions
- Full show editing
- Timecode generation (LTC/MTC)
- All protocol outputs
8.2 Helm: CueMaster
Section titled “8.2 Helm: CueMaster”Standalone hardware device that can host sessions.
Capabilities:
- Host sessions without PC
- Timecode I/O
- Audio playback
- Display for cue lists
8.3 Helm: TimeKeeper
Section titled “8.3 Helm: TimeKeeper”MIF4-style timecode device.
Capabilities:
- Timecode display
- LTC/MTC input/output
- Audio monitoring
- Jam sync
8.4 Helm: CueKeeper
Section titled “8.4 Helm: CueKeeper”Cue display device.
Capabilities:
- High-resolution display
- Countdown timers
- Cue list display
- Configurable modes
9. Security
Section titled “9. Security”9.1 Transport Security
Section titled “9.1 Transport Security”All QUIC connections use TLS 1.3 with:
- Self-signed certificates (auto-generated)
- ALPN:
slp/1
9.2 Session Authentication
Section titled “9.2 Session Authentication”Token-based authentication:
- 32-character random token issued on join
- Included in message headers
- Validated by master
9.3 Role-Based Access Control
Section titled “9.3 Role-Based Access Control”| Permission | Observer | Operator | Editor | Admin | Hardware | Master |
|---|---|---|---|---|---|---|
| ReadShow | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| EditShow | ✓ | ✓ | ✓ | |||
| ControlPlayback | ✓ | ✓ | ✓ | ✓ | ||
| ChangeTrack | ✓ | ✓ | ✓ | ✓ | ||
| ViewDevices | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| ConfigureDevices | ✓ | ✓ | ||||
| ManageMembers | ✓ | ✓ | ||||
| InjectTimecode | ✓ | ✓ | ||||
| Admin | ✓ | ✓ |
10. Error Handling
Section titled “10. Error Handling”10.1 Error Message
Section titled “10.1 Error Message”struct SlpError { code: u16, message: String, related_sequence: Option<u32>,}10.2 Error Codes
Section titled “10.2 Error Codes”| Code | Name | Description |
|---|---|---|
| 1 | INVALID_MESSAGE | Malformed message |
| 2 | PERMISSION_DENIED | Insufficient permissions |
| 3 | SESSION_FULL | Maximum clients reached |
| 4 | SESSION_NOT_FOUND | No active session |
| 5 | DEVICE_NOT_FOUND | Unknown device ID |
| 6 | INVALID_TOKEN | Session token invalid |
| 7 | VERSION_MISMATCH | Protocol version incompatible |
| 100 | INTERNAL_ERROR | Server-side error |
11. Appendix
Section titled “11. Appendix”11.1 Default Ports
Section titled “11.1 Default Ports”| Port | Protocol | Purpose |
|---|---|---|
| 5353 | QUIC/UDP | SLP connections |
| 5354 | UDP | Discovery broadcast |
11.2 Test Vectors
Section titled “11.2 Test Vectors”Timecode Packet
Section titled “Timecode Packet”Input:
timestamp_us: 1704067200000000 (2024-01-01 00:00:00 UTC)position_frames: 108000 (1 hour at 30fps)frame_rate: 5 (30fps)state: 1 (Playing)source: 0 (Internal)track_id: 42sequence: 100Expected bytes (hex):
00 06 0F 4B F4 D9 80 00 <- timestamp_us00 00 00 00 00 01 A5 E0 <- position_frames05 01 00 00 <- frame_rate, state, source, reserved00 00 00 2A <- track_id00 64 <- sequenceHeader
Section titled “Header”Input:
magic: 0x534Cversion: 1type: Timecode (0x01)flags: PRIORITY (0x02)length: 26Expected bytes (hex):
53 4C 01 01 02 00 1A11.3 References
Section titled “11.3 References”- RFC 9000: QUIC Transport Protocol
- RFC 9221: QUIC Unreliable Datagrams
- MessagePack Specification: https://msgpack.org/
- SMPTE ST 12-1:2014 (Timecode)