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_json::json;
8use uuid::Uuid;
9
10/// Signs and verifies control envelopes using the derived session keys.
11pub struct ControlCrypto {
12    keys: SessionKeys,
13}
14
15impl ControlCrypto {
16    pub fn new(keys: SessionKeys) -> Self {
17        Self { keys }
18    }
19
20    pub fn mac_for_payload(
21        &self,
22        seq: u64,
23        session_id: &Uuid,
24        payload: &serde_json::Value,
25    ) -> Result<Vec<u8>, HandshakeError> {
26        let bytes = serde_cbor::to_vec(payload)
27            .map_err(|e| HandshakeError::Protocol(format!("payload encode: {}", e)))?;
28        compute_mac(&self.keys, seq, &bytes, session_id.as_bytes())
29            .map_err(|e| HandshakeError::Authentication(e.to_string()))
30    }
31
32    pub fn verify_mac(
33        &self,
34        seq: u64,
35        session_id: &Uuid,
36        payload: &serde_json::Value,
37        mac: &[u8],
38    ) -> Result<(), HandshakeError> {
39        let bytes = serde_cbor::to_vec(payload)
40            .map_err(|e| HandshakeError::Protocol(format!("payload encode: {}", e)))?;
41        if verify_mac(&self.keys, seq, &bytes, session_id.as_bytes(), mac) {
42            Ok(())
43        } else {
44            Err(HandshakeError::Authentication(
45                "control MAC validation failed".into(),
46            ))
47        }
48    }
49}
50
51/// Control-plane client helper to build authenticated envelopes and handle acks.
52pub struct ControlClient {
53    pub device_id: Uuid,
54    pub crypto: ControlCrypto,
55    pub session_id: Uuid,
56}
57
58impl ControlClient {
59    pub fn new(device_id: Uuid, session_id: Uuid, crypto: ControlCrypto) -> Self {
60        Self {
61            device_id,
62            crypto,
63            session_id,
64        }
65    }
66
67    pub fn envelope(
68        &self,
69        seq: u64,
70        op: ControlOp,
71        payload: serde_json::Value,
72    ) -> Result<ControlEnvelope, HandshakeError> {
73        let mac = self
74            .crypto
75            .mac_for_payload(seq, &self.session_id, &payload)?;
76        Ok(ControlEnvelope {
77            message_type: MessageType::AlpineControl,
78            session_id: self.session_id,
79            seq,
80            op,
81            payload,
82            mac,
83        })
84    }
85
86    pub async fn send<T: HandshakeTransport + Send>(
87        &self,
88        channel: &mut ReliableControlChannel<T>,
89        op: ControlOp,
90        payload: serde_json::Value,
91    ) -> Result<Acknowledge, HandshakeError> {
92        let seq = channel.next_seq();
93        let env = self.envelope(seq, op, payload)?;
94        channel.send_reliable(env).await
95    }
96
97    pub fn now_ms() -> u64 {
98        SystemTime::now()
99            .duration_since(UNIX_EPOCH)
100            .unwrap_or_default()
101            .as_millis() as u64
102    }
103}
104
105/// Control responder to validate envelopes and generate authenticated acks.
106pub struct ControlResponder {
107    pub crypto: ControlCrypto,
108    pub session_id: Uuid,
109}
110
111impl ControlResponder {
112    pub fn new(session_id: Uuid, crypto: ControlCrypto) -> Self {
113        Self { crypto, session_id }
114    }
115
116    pub fn verify(&self, env: &ControlEnvelope) -> Result<(), HandshakeError> {
117        self.crypto
118            .verify_mac(env.seq, &env.session_id, &env.payload, &env.mac)
119    }
120
121    pub fn ack(
122        &self,
123        seq: u64,
124        ok: bool,
125        detail: Option<String>,
126    ) -> Result<Acknowledge, HandshakeError> {
127        let payload = json!({"ok": ok, "detail": detail});
128        let mac = self
129            .crypto
130            .mac_for_payload(seq, &self.session_id, &payload)?;
131        Ok(Acknowledge {
132            message_type: MessageType::AlpineControlAck,
133            session_id: self.session_id,
134            seq,
135            ok,
136            detail,
137            mac,
138        })
139    }
140}