Skip to main content

gpp_sync/
transport.rs

1//! Noise_XX transport over TCP.
2//!
3//! Handshake: `Noise_XX_25519_ChaChaPoly_BLAKE2s` (mutual static-key auth,
4//! like WireGuard). After the 3-message XX handshake both peers know each
5//! other's static public key (used for TOFU). Application messages are
6//! length-delimited and chunked so payloads larger than a Noise message
7//! (64 KiB) transfer transparently.
8
9use std::io::{Read, Write};
10use std::net::TcpStream;
11
12use crate::error::{Error, Result};
13
14const NOISE_PARAMS: &str = "Noise_XX_25519_ChaChaPoly_BLAKE2s";
15/// Max plaintext per Noise message (64 KiB − 16-byte tag), rounded down.
16const CHUNK: usize = 60_000;
17
18/// Generate a persistent X25519 static keypair `(private, public)`.
19pub fn generate_keypair() -> Result<(Vec<u8>, Vec<u8>)> {
20    let b = snow::Builder::new(NOISE_PARAMS.parse().map_err(noise)?);
21    let kp = b.generate_keypair().map_err(noise)?;
22    Ok((kp.private, kp.public))
23}
24
25fn noise<E: std::fmt::Display>(e: E) -> Error {
26    Error::Noise(e.to_string())
27}
28
29fn write_frame(s: &mut TcpStream, data: &[u8]) -> Result<()> {
30    let len: u16 = data
31        .len()
32        .try_into()
33        .map_err(|_| Error::Protocol("noise frame too large".into()))?;
34    s.write_all(&len.to_be_bytes())?;
35    s.write_all(data)?;
36    Ok(())
37}
38
39fn read_frame(s: &mut TcpStream) -> Result<Vec<u8>> {
40    let mut len = [0u8; 2];
41    s.read_exact(&mut len)?;
42    let mut buf = vec![0u8; u16::from_be_bytes(len) as usize];
43    s.read_exact(&mut buf)?;
44    Ok(buf)
45}
46
47/// An established, encrypted session.
48pub struct Transport {
49    stream: TcpStream,
50    noise: snow::TransportState,
51    /// Peer's static public key (from the XX handshake).
52    pub remote_static: Vec<u8>,
53}
54
55impl Transport {
56    /// Run the XX handshake as the connecting peer.
57    pub fn initiate(stream: TcpStream, static_priv: &[u8]) -> Result<Self> {
58        Self::handshake(stream, static_priv, true)
59    }
60
61    /// Run the XX handshake as the listening peer.
62    pub fn respond(stream: TcpStream, static_priv: &[u8]) -> Result<Self> {
63        Self::handshake(stream, static_priv, false)
64    }
65
66    fn handshake(mut stream: TcpStream, static_priv: &[u8], initiator: bool) -> Result<Self> {
67        let builder =
68            snow::Builder::new(NOISE_PARAMS.parse().map_err(noise)?).local_private_key(static_priv);
69        let mut hs = if initiator {
70            builder.build_initiator().map_err(noise)?
71        } else {
72            builder.build_responder().map_err(noise)?
73        };
74
75        let mut buf = vec![0u8; 65535];
76        let mut my_turn = initiator;
77        while !hs.is_handshake_finished() {
78            if my_turn {
79                let n = hs.write_message(&[], &mut buf).map_err(noise)?;
80                write_frame(&mut stream, &buf[..n])?;
81            } else {
82                let msg = read_frame(&mut stream)?;
83                hs.read_message(&msg, &mut buf).map_err(noise)?;
84            }
85            my_turn = !my_turn;
86        }
87        let remote_static = hs
88            .get_remote_static()
89            .map(|s| s.to_vec())
90            .ok_or_else(|| Error::Protocol("peer did not present a static key".into()))?;
91        let noise = hs.into_transport_mode().map_err(noise)?;
92        Ok(Self {
93            stream,
94            noise,
95            remote_static,
96        })
97    }
98
99    /// Send one logical message (chunked + Noise-encrypted).
100    pub fn send(&mut self, plaintext: &[u8]) -> Result<()> {
101        let mut out = vec![0u8; 65535];
102        for chunk in plaintext.chunks(CHUNK).chain(if plaintext.is_empty() {
103            // still send a single empty chunk so recv() terminates
104            Some([].as_slice())
105        } else {
106            None
107        }) {
108            let n = self.noise.write_message(chunk, &mut out).map_err(noise)?;
109            write_frame(&mut self.stream, &out[..n])?;
110        }
111        // Zero-length terminator frame marks end of logical message.
112        write_frame(&mut self.stream, &[])?;
113        Ok(())
114    }
115
116    /// Receive one logical message.
117    pub fn recv(&mut self) -> Result<Vec<u8>> {
118        let mut out = vec![0u8; 65535];
119        let mut msg = Vec::new();
120        loop {
121            let frame = read_frame(&mut self.stream)?;
122            if frame.is_empty() {
123                break;
124            }
125            let n = self.noise.read_message(&frame, &mut out).map_err(noise)?;
126            msg.extend_from_slice(&out[..n]);
127        }
128        Ok(msg)
129    }
130}