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.
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
52/// Control-plane client helper to build authenticated envelopes and handle acks.
53#[derive(Debug)]
54pub struct ControlClient {
55    pub device_id: Uuid,
56    pub crypto: ControlCrypto,
57    pub session_id: Uuid,
58}
59
60impl ControlClient {
61    pub fn new(device_id: Uuid, session_id: Uuid, crypto: ControlCrypto) -> Self {
62        Self {
63            device_id,
64            crypto,
65            session_id,
66        }
67    }
68
69    pub fn envelope(
70        &self,
71        seq: u64,
72        op: ControlOp,
73        payload: serde_json::Value,
74    ) -> Result<ControlEnvelope, HandshakeError> {
75        let mac = self
76            .crypto
77            .mac_for_payload(seq, &self.session_id, &payload)?;
78        Ok(ControlEnvelope {
79            message_type: MessageType::AlpineControl,
80            session_id: self.session_id,
81            seq,
82            op,
83            payload,
84            mac,
85        })
86    }
87
88    pub async fn send<T: HandshakeTransport + Send>(
89        &self,
90        channel: &mut ReliableControlChannel<T>,
91        op: ControlOp,
92        payload: serde_json::Value,
93    ) -> Result<Acknowledge, HandshakeError> {
94        let seq = channel.next_seq();
95        let env = self.envelope(seq, op, payload)?;
96        channel.send_reliable(env).await
97    }
98
99    pub fn now_ms() -> u64 {
100        SystemTime::now()
101            .duration_since(UNIX_EPOCH)
102            .unwrap_or_default()
103            .as_millis() as u64
104    }
105}
106
107/// Control responder to validate envelopes and generate authenticated acks.
108pub struct ControlResponder {
109    pub crypto: ControlCrypto,
110    pub session_id: Uuid,
111}
112
113impl ControlResponder {
114    pub fn new(session_id: Uuid, crypto: ControlCrypto) -> Self {
115        Self { crypto, session_id }
116    }
117
118    pub fn verify(&self, env: &ControlEnvelope) -> Result<(), HandshakeError> {
119        self.crypto
120            .verify_mac(env.seq, &env.session_id, &env.payload, &env.mac)
121    }
122
123    pub fn ack(
124        &self,
125        seq: u64,
126        ok: bool,
127        detail: Option<String>,
128    ) -> Result<Acknowledge, HandshakeError> {
129        let payload = json!({"ok": ok, "detail": detail});
130        let mac = self
131            .crypto
132            .mac_for_payload(seq, &self.session_id, &payload)?;
133        Ok(Acknowledge {
134            message_type: MessageType::AlpineControlAck,
135            session_id: self.session_id,
136            seq,
137            ok,
138            detail,
139            mac,
140        })
141    }
142}