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: &Uuid,
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: &Uuid,
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: &Uuid,
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: Uuid,
98}
99
100impl ControlClient {
101    pub fn new(device_id: Uuid, session_id: Uuid, 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, &payload)?;
118        Ok(ControlEnvelope {
119            message_type: MessageType::AlpineControl,
120            session_id: self.session_id,
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: Uuid,
151}
152
153impl ControlResponder {
154    pub fn new(session_id: Uuid, crypto: ControlCrypto) -> Self {
155        Self { crypto, session_id }
156    }
157
158    pub fn verify(&self, env: &ControlEnvelope) -> Result<(), HandshakeError> {
159        self.crypto
160            .verify_mac(env.seq, &env.session_id, &env.payload, &env.mac)
161    }
162
163    pub fn ack(
164        &self,
165        seq: u64,
166        ok: bool,
167        detail: Option<String>,
168        payload: Option<Vec<u8>>,
169    ) -> Result<Acknowledge, HandshakeError> {
170        let mac = self.crypto.mac_for_ack(
171            seq,
172            &self.session_id,
173            ok,
174            detail.as_deref(),
175            payload.as_deref(),
176        )?;
177        Ok(Acknowledge {
178            message_type: MessageType::AlpineControlAck,
179            session_id: self.session_id,
180            seq,
181            ok,
182            detail,
183            payload,
184            mac,
185        })
186    }
187}