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