alpine/
control.rs

1use std::time::{SystemTime, UNIX_EPOCH};
2
3use crate::crypto::{compute_mac, verify_mac, SessionKeys};
4use crate::handshake::HandshakeError;
5use crate::messages::{Acknowledge, ControlEnvelope, ControlOp, MessageType};
6use crate::{handshake::transport::ReliableControlChannel, handshake::HandshakeTransport};
7use serde::{de::DeserializeOwned, Serialize};
8use uuid::Uuid;
9
10/// Signs and verifies control envelopes using the derived session keys.
11#[derive(Debug)]
12pub struct ControlCrypto {
13    keys: SessionKeys,
14}
15
16impl ControlCrypto {
17    pub fn new(keys: SessionKeys) -> Self {
18        Self { keys }
19    }
20
21    pub fn mac_for_payload(
22        &self,
23        seq: u64,
24        session_id: &str,
25        payload: &serde_json::Value,
26    ) -> Result<Vec<u8>, HandshakeError> {
27        let bytes = serde_cbor::to_vec(payload)
28            .map_err(|e| HandshakeError::Protocol(format!("payload encode: {}", e)))?;
29        compute_mac(&self.keys, seq, &bytes, session_id.as_bytes())
30            .map_err(|e| HandshakeError::Authentication(e.to_string()))
31    }
32
33    pub fn verify_mac(
34        &self,
35        seq: u64,
36        session_id: &str,
37        payload: &serde_json::Value,
38        mac: &[u8],
39    ) -> Result<(), HandshakeError> {
40        let bytes = serde_cbor::to_vec(payload)
41            .map_err(|e| HandshakeError::Protocol(format!("payload encode: {}", e)))?;
42        if verify_mac(&self.keys, seq, &bytes, session_id.as_bytes(), mac) {
43            Ok(())
44        } else {
45            Err(HandshakeError::Authentication(
46                "control MAC validation failed".into(),
47            ))
48        }
49    }
50
51    pub fn mac_for_ack(
52        &self,
53        seq: u64,
54        session_id: &str,
55        ok: bool,
56        detail: Option<&str>,
57        payload: Option<&[u8]>,
58    ) -> Result<Vec<u8>, HandshakeError> {
59        let record = AckMacRecord {
60            ok,
61            detail,
62            payload,
63        };
64        let bytes = serde_cbor::to_vec(&record)
65            .map_err(|e| HandshakeError::Protocol(format!("ack encode: {}", e)))?;
66        compute_mac(&self.keys, seq, &bytes, session_id.as_bytes())
67            .map_err(|e| HandshakeError::Authentication(e.to_string()))
68    }
69
70    pub fn decode_ack_payload<T>(payload: Option<&[u8]>) -> Result<Option<T>, HandshakeError>
71    where
72        T: DeserializeOwned,
73    {
74        if let Some(bytes) = payload {
75            serde_cbor::from_slice(bytes)
76                .map(Some)
77                .map_err(|e| HandshakeError::Protocol(format!("ack payload decode: {}", e)))
78        } else {
79            Ok(None)
80        }
81    }
82}
83
84#[derive(Serialize)]
85struct AckMacRecord<'a> {
86    ok: bool,
87    detail: Option<&'a str>,
88    #[serde(skip_serializing_if = "Option::is_none")]
89    payload: Option<&'a [u8]>,
90}
91
92/// Control-plane client helper to build authenticated envelopes and handle acks.
93#[derive(Debug)]
94pub struct ControlClient {
95    pub device_id: Uuid,
96    pub crypto: ControlCrypto,
97    pub session_id: String,
98}
99
100impl ControlClient {
101    pub fn new(device_id: Uuid, session_id: String, crypto: ControlCrypto) -> Self {
102        Self {
103            device_id,
104            crypto,
105            session_id,
106        }
107    }
108
109    pub fn envelope(
110        &self,
111        seq: u64,
112        op: ControlOp,
113        payload: serde_json::Value,
114    ) -> Result<ControlEnvelope, HandshakeError> {
115        let mac = self
116            .crypto
117            .mac_for_payload(seq, self.session_id.as_str(), &payload)?;
118        Ok(ControlEnvelope {
119            message_type: MessageType::AlpineControl,
120            session_id: self.session_id.clone(),
121            seq,
122            op,
123            payload,
124            mac,
125        })
126    }
127
128    pub async fn send<T: HandshakeTransport + Send>(
129        &self,
130        channel: &mut ReliableControlChannel<T>,
131        op: ControlOp,
132        payload: serde_json::Value,
133    ) -> Result<Acknowledge, HandshakeError> {
134        let seq = channel.next_seq();
135        let env = self.envelope(seq, op, payload)?;
136        channel.send_reliable(env).await
137    }
138
139    pub fn now_ms() -> u64 {
140        SystemTime::now()
141            .duration_since(UNIX_EPOCH)
142            .unwrap_or_default()
143            .as_millis() as u64
144    }
145}
146
147/// Control responder to validate envelopes and generate authenticated acks.
148pub struct ControlResponder {
149    pub crypto: ControlCrypto,
150    pub session_id: String,
151}
152
153impl ControlResponder {
154    pub fn new(session_id: String, crypto: ControlCrypto) -> Self {
155        Self { crypto, session_id }
156    }
157
158    pub fn verify(&self, env: &ControlEnvelope) -> Result<(), HandshakeError> {
159        if env.session_id != self.session_id {
160            return Err(HandshakeError::Protocol(
161                "session_id mismatch on control envelope".into(),
162            ));
163        }
164        self.crypto
165            .verify_mac(env.seq, env.session_id.as_str(), &env.payload, &env.mac)
166    }
167
168    pub fn ack(
169        &self,
170        seq: u64,
171        ok: bool,
172        detail: Option<String>,
173        payload: Option<Vec<u8>>,
174    ) -> Result<Acknowledge, HandshakeError> {
175        let mac = self.crypto.mac_for_ack(
176            seq,
177            self.session_id.as_str(),
178            ok,
179            detail.as_deref(),
180            payload.as_deref(),
181        )?;
182        Ok(Acknowledge {
183            message_type: MessageType::AlpineControlAck,
184            session_id: self.session_id.clone(),
185            seq,
186            ok,
187            detail,
188            payload,
189            mac,
190        })
191    }
192}