alpine/messages/
mod.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use uuid::Uuid;
4
5pub const ALPINE_VERSION: &str = "1.0";
6
7/// Common envelope type identifiers used across CBOR payloads.
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
9#[serde(rename_all = "snake_case")]
10pub enum MessageType {
11    AlpineDiscover,
12    AlpineDiscoverReply,
13    SessionInit,
14    SessionAck,
15    SessionReady,
16    SessionComplete,
17    AlpineControl,
18    AlpineControlAck,
19    AlpineFrame,
20    Keepalive,
21}
22
23/// Discovery request broadcast by controllers.
24#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
25pub struct DiscoveryRequest {
26    #[serde(rename = "type")]
27    pub message_type: MessageType,
28    pub version: String,
29    pub client_nonce: Vec<u8>,
30    pub requested: Vec<String>,
31}
32
33impl DiscoveryRequest {
34    pub fn new(requested: Vec<String>, client_nonce: Vec<u8>) -> Self {
35        Self {
36            message_type: MessageType::AlpineDiscover,
37            version: ALPINE_VERSION.to_string(),
38            client_nonce,
39            requested,
40        }
41    }
42}
43
44/// Discovery reply signed by the device.
45#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
46pub struct DiscoveryReply {
47    #[serde(rename = "type")]
48    pub message_type: MessageType,
49    pub alpine_version: String,
50    pub device_id: String,
51    pub manufacturer_id: String,
52    pub model_id: String,
53    pub hardware_rev: String,
54    pub firmware_rev: String,
55    pub mac: String,
56    pub server_nonce: Vec<u8>,
57    pub capabilities: CapabilitySet,
58    pub signature: Vec<u8>,
59}
60
61impl DiscoveryReply {
62    pub fn new(
63        identity: &DeviceIdentity,
64        mac: String,
65        server_nonce: Vec<u8>,
66        capabilities: CapabilitySet,
67        signature: Vec<u8>,
68    ) -> Self {
69        Self {
70            message_type: MessageType::AlpineDiscoverReply,
71            alpine_version: ALPINE_VERSION.to_string(),
72            device_id: identity.device_id.clone(),
73            manufacturer_id: identity.manufacturer_id.clone(),
74            model_id: identity.model_id.clone(),
75            hardware_rev: identity.hardware_rev.clone(),
76            firmware_rev: identity.firmware_rev.clone(),
77            mac,
78            server_nonce,
79            capabilities,
80            signature,
81        }
82    }
83}
84
85/// Device identity tuple exchanged during discovery and handshake.
86#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
87pub struct DeviceIdentity {
88    pub device_id: String,
89    pub manufacturer_id: String,
90    pub model_id: String,
91    pub hardware_rev: String,
92    pub firmware_rev: String,
93}
94
95/// Declared capabilities as defined by the spec.
96#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
97pub struct CapabilitySet {
98    pub channel_formats: Vec<ChannelFormat>,
99    pub max_channels: u32,
100    pub grouping_supported: bool,
101    pub streaming_supported: bool,
102    pub encryption_supported: bool,
103    pub vendor_extensions: Option<HashMap<String, serde_json::Value>>,
104}
105
106impl Default for CapabilitySet {
107    fn default() -> Self {
108        Self {
109            channel_formats: vec![ChannelFormat::U8],
110            max_channels: 512,
111            grouping_supported: false,
112            streaming_supported: true,
113            encryption_supported: true,
114            vendor_extensions: None,
115        }
116    }
117}
118
119/// Supported channel encodings for frames.
120#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
121#[serde(rename_all = "lowercase")]
122pub enum ChannelFormat {
123    U8,
124    U16,
125}
126
127/// Handshake session_init payload.
128#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
129pub struct SessionInit {
130    #[serde(rename = "type")]
131    pub message_type: MessageType,
132    pub controller_nonce: Vec<u8>,
133    pub controller_pubkey: Vec<u8>,
134    pub requested: CapabilitySet,
135    pub session_id: Uuid,
136}
137
138/// Handshake session_ack payload.
139#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
140pub struct SessionAck {
141    #[serde(rename = "type")]
142    pub message_type: MessageType,
143    pub device_nonce: Vec<u8>,
144    pub device_pubkey: Vec<u8>,
145    pub device_identity: DeviceIdentity,
146    pub capabilities: CapabilitySet,
147    pub signature: Vec<u8>,
148    pub session_id: Uuid,
149}
150
151/// Controller readiness marker after keys are derived.
152#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
153pub struct SessionReady {
154    #[serde(rename = "type")]
155    pub message_type: MessageType,
156    pub session_id: Uuid,
157    pub mac: Vec<u8>,
158}
159
160/// Device completion acknowledgement.
161#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
162pub struct SessionComplete {
163    #[serde(rename = "type")]
164    pub message_type: MessageType,
165    pub session_id: Uuid,
166    pub ok: bool,
167    pub error: Option<ErrorCode>,
168}
169
170/// Internal representation of an established session derived from the handshake.
171#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
172pub struct SessionEstablished {
173    pub session_id: Uuid,
174    pub controller_nonce: Vec<u8>,
175    pub device_nonce: Vec<u8>,
176    pub capabilities: CapabilitySet,
177    pub device_identity: DeviceIdentity,
178}
179
180/// Control-plane envelope with authenticated payload.
181#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
182pub struct ControlEnvelope {
183    #[serde(rename = "type")]
184    pub message_type: MessageType,
185    pub session_id: Uuid,
186    pub seq: u64,
187    pub op: ControlOp,
188    pub payload: serde_json::Value,
189    pub mac: Vec<u8>,
190}
191
192/// Ack for control-plane operations.
193#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
194pub struct Acknowledge {
195    #[serde(rename = "type")]
196    pub message_type: MessageType,
197    pub session_id: Uuid,
198    pub seq: u64,
199    pub ok: bool,
200    pub detail: Option<String>,
201    pub mac: Vec<u8>,
202}
203
204/// Control operations enumerated by the spec.
205#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
206#[serde(rename_all = "snake_case")]
207pub enum ControlOp {
208    GetInfo,
209    GetCaps,
210    Identify,
211    Restart,
212    GetStatus,
213    SetConfig,
214    SetMode,
215    TimeSync,
216    Vendor,
217}
218
219/// Real-time frame envelope.
220#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
221pub struct FrameEnvelope {
222    #[serde(rename = "type")]
223    pub message_type: MessageType,
224    pub session_id: Uuid,
225    pub timestamp_us: u64,
226    pub priority: u8,
227    pub channel_format: ChannelFormat,
228    pub channels: Vec<u16>,
229    pub groups: Option<HashMap<String, Vec<u16>>>,
230    pub metadata: Option<HashMap<String, serde_json::Value>>,
231}
232
233/// Control-plane keepalive frame to detect dead sessions.
234#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
235pub struct Keepalive {
236    #[serde(rename = "type")]
237    pub message_type: MessageType,
238    pub session_id: Uuid,
239    pub tick_ms: u64,
240}
241
242/// Standard error codes from docs/errors.md.
243#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
244#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
245pub enum ErrorCode {
246    DiscoveryInvalidSignature,
247    DiscoveryNonceMismatch,
248    DiscoveryUnsupportedVersion,
249    HandshakeSignatureInvalid,
250    HandshakeKeyDerivationFailed,
251    HandshakeTimeout,
252    HandshakeReplay,
253    SessionExpired,
254    SessionInvalidToken,
255    SessionMacMismatch,
256    ControlUnknownOp,
257    ControlPayloadInvalid,
258    ControlUnauthorized,
259    StreamBadFormat,
260    StreamTooLarge,
261    StreamUnsupportedChannelMode,
262}