alpine/messages/
mod.rs

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