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    #[serde(skip_serializing_if = "Option::is_none")]
202    pub payload: Option<Vec<u8>>,
203    pub mac: Vec<u8>,
204}
205
206/// Control operations enumerated by the spec.
207#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
208#[serde(rename_all = "snake_case")]
209pub enum ControlOp {
210    GetInfo,
211    GetCaps,
212    Identify,
213    Restart,
214    GetStatus,
215    SetConfig,
216    SetMode,
217    TimeSync,
218    Vendor,
219}
220
221/// Real-time frame envelope.
222#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
223pub struct FrameEnvelope {
224    #[serde(rename = "type")]
225    pub message_type: MessageType,
226    pub session_id: Uuid,
227    pub timestamp_us: u64,
228    pub priority: u8,
229    pub channel_format: ChannelFormat,
230    pub channels: Vec<u16>,
231    pub groups: Option<HashMap<String, Vec<u16>>>,
232    pub metadata: Option<HashMap<String, serde_json::Value>>,
233}
234
235/// Control-plane keepalive frame to detect dead sessions.
236#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
237pub struct Keepalive {
238    #[serde(rename = "type")]
239    pub message_type: MessageType,
240    pub session_id: Uuid,
241    pub tick_ms: u64,
242}
243
244/// Standard error codes from docs/errors.md.
245#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
246#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
247pub enum ErrorCode {
248    DiscoveryInvalidSignature,
249    DiscoveryNonceMismatch,
250    DiscoveryUnsupportedVersion,
251    HandshakeSignatureInvalid,
252    HandshakeKeyDerivationFailed,
253    HandshakeTimeout,
254    HandshakeReplay,
255    SessionExpired,
256    SessionInvalidToken,
257    SessionMacMismatch,
258    ControlUnknownOp,
259    ControlPayloadInvalid,
260    ControlUnauthorized,
261    StreamBadFormat,
262    StreamTooLarge,
263    StreamUnsupportedChannelMode,
264}