Skip to main content

macula_rust/frame/
session.rs

1//! Handshake v5 (macula 13.2, DESIGN_NEIGHBOUR_CHANNEL_BINDING section 3): the
2//! session proofs authenticate the neighbour once, so after HELLO no frame
3//! carries a neighbour signature, and QUIC's AEAD authenticates every frame.
4//! The liveness probe is liveness_ping, answered with liveness_pong and the
5//! same nonce by the peer's connection itself: unsigned, never handed on, and
6//! on a v5 connection only.
7
8use crate::cbor::Value;
9
10use super::neighbour::control_header;
11use super::{base, entry, FrameError};
12
13/// The bytes of a liveness frame's nonce.
14pub const LIVENESS_NONCE_SIZE: usize = 16;
15
16/// A received frame on a v5 connection: one that carries a neighbour
17/// signature is [`FrameError::Malformed`], and any other comes back as it is.
18pub fn verify_session_frame(frame: &Value) -> Result<Value, FrameError> {
19    let Value::Map(pairs) = frame else {
20        return Err(FrameError::Malformed);
21    };
22    match control_header(pairs) {
23        (_, _, true) => Err(FrameError::Malformed),
24        _ => Ok(frame.clone()),
25    }
26}
27
28/// macula 13.2's liveness_ping with a 16-byte nonce.
29pub fn liveness_ping_frame(nonce: &[u8; LIVENESS_NONCE_SIZE]) -> Value {
30    liveness_frame("liveness_ping", nonce)
31}
32
33/// macula 13.2's liveness_pong, answering the liveness_ping of the same
34/// nonce.
35pub fn liveness_pong_frame(nonce: &[u8; LIVENESS_NONCE_SIZE]) -> Value {
36    liveness_frame("liveness_pong", nonce)
37}
38
39fn liveness_frame(frame_type: &str, nonce: &[u8; LIVENESS_NONCE_SIZE]) -> Value {
40    let mut fields = base(frame_type);
41    fields.push(entry("nonce", Value::Bytes(nonce.to_vec())));
42    Value::Map(fields)
43}
44
45/// Whether a liveness frame is the peer's probe or its answer.
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum Liveness {
48    Ping,
49    Pong,
50}
51
52/// A liveness frame's kind and nonce, or `None` for any other frame, or one
53/// whose nonce is not 16 bytes.
54pub fn liveness_nonce(frame: &Value) -> Option<(Liveness, [u8; LIVENESS_NONCE_SIZE])> {
55    let Value::Map(pairs) = frame else {
56        return None;
57    };
58    let kind = match control_header(pairs).0.as_str() {
59        "liveness_ping" => Liveness::Ping,
60        "liveness_pong" => Liveness::Pong,
61        _ => return None,
62    };
63    match frame.get("nonce") {
64        Some(Value::Bytes(nonce)) => Some((kind, nonce.as_slice().try_into().ok()?)),
65        _ => None,
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72    use crate::frame::{goodbye_frame, neighbour_signed};
73    use crate::profile::Profile;
74
75    #[test]
76    fn a_v5_connection_reads_frames_without_a_neighbour_signature_and_refuses_one_that_has_it() {
77        let ping = liveness_ping_frame(&[7; 16]);
78        assert_eq!(verify_session_frame(&ping).unwrap(), ping);
79        let Value::Map(mut goodbye) = goodbye_frame("bye", None).unwrap() else {
80            panic!("a goodbye is a map");
81        };
82        goodbye.push(entry("neighbour", Value::Map(Vec::new())));
83        assert_eq!(
84            verify_session_frame(&Value::Map(goodbye)),
85            Err(FrameError::Malformed)
86        );
87    }
88
89    #[test]
90    fn liveness_frames_carry_their_nonce_and_are_never_neighbour_signed() {
91        let nonce = [1; 16];
92        for (kind, frame, name) in [
93            (Liveness::Ping, liveness_ping_frame(&nonce), "liveness_ping"),
94            (Liveness::Pong, liveness_pong_frame(&nonce), "liveness_pong"),
95        ] {
96            assert_eq!(liveness_nonce(&frame), Some((kind, nonce)), "{name}");
97            assert!(!neighbour_signed(Profile::PqHybrid, name), "{name}");
98            let wire = crate::frame::encode(&frame).unwrap();
99            let crate::frame::Decoded::Complete { frame: decoded, .. } =
100                crate::frame::decode(&wire).unwrap()
101            else {
102                panic!("{name}: not whole");
103            };
104            assert_eq!(liveness_nonce(&decoded), Some((kind, nonce)), "{name}");
105        }
106        let mut short = base("liveness_ping");
107        short.push(entry("nonce", Value::Bytes(vec![1, 2])));
108        assert_eq!(liveness_nonce(&Value::Map(short)), None);
109        assert_eq!(liveness_nonce(&goodbye_frame("bye", None).unwrap()), None);
110    }
111}