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