use std::io;
use std::net::SocketAddr;
use std::path::Path;
use std::time::Instant;
pub const MAX_DATAGRAM_BYTES: usize = 1200;
pub const MAX_APP_PAYLOAD_BYTES: usize = 1176;
pub const SESSION_ID_LEN: usize = 8;
#[inline]
pub fn has_reserved_tncf_prefix(session_id: &[u8; SESSION_ID_LEN]) -> bool {
session_id[..4] == TNCF_MAGIC
}
pub const TNCF_MAGIC: [u8; 4] = [0x54, 0x4E, 0x43, 0x46];
pub const TNCF_VERSION_1: u8 = 1;
pub const TNCF_HEADER_LEN: usize = 8;
pub mod tncf_type {
pub const KEEPALIVE: u8 = 0x01;
pub const HANDSHAKE_MSG1: u8 = 0x10;
pub const HANDSHAKE_MSG2: u8 = 0x11;
pub const HANDSHAKE_MSG3: u8 = 0x12;
pub const OBSERVE_REQ: u8 = 0x20;
pub const COOKIE_CHALLENGE: u8 = 0x21;
pub const OBSERVE_RESP: u8 = 0x22;
}
#[cfg(feature = "noise")]
pub enum UdpSessionState {
Handshaking {
handshake: Box<snow::HandshakeState>,
step: u8,
},
Established {
transport: Box<snow::TransportState>,
remote_static_fingerprint: [u8; 32],
},
}
#[cfg(not(feature = "noise"))]
pub enum UdpSessionState {
Handshaking { step: u8 },
Established { remote_static_fingerprint: [u8; 32] },
}
pub struct NoiseUdpSession {
pub session_id: [u8; SESSION_ID_LEN],
pub node_id: Option<String>,
pub peer_addr: SocketAddr,
state: Option<UdpSessionState>,
pub last_seen: Instant,
pub created_at: Instant,
}
impl NoiseUdpSession {
pub fn is_established(&self) -> bool {
matches!(self.state, Some(UdpSessionState::Established { .. }))
}
pub fn remote_static_fingerprint(&self) -> Option<[u8; 32]> {
match &self.state {
Some(UdpSessionState::Established {
remote_static_fingerprint,
..
}) => Some(*remote_static_fingerprint),
_ => None,
}
}
pub fn state(&self) -> Option<&UdpSessionState> {
self.state.as_ref()
}
}
#[cfg(feature = "noise")]
impl NoiseUdpSession {
const NOISE_PARAMS: &'static str = "Noise_XX_25519_ChaChaPoly_BLAKE2s";
pub fn new_initiator(
peer_addr: SocketAddr,
static_private: &[u8],
) -> Result<(Self, Vec<u8>), snow::Error> {
use rand::RngCore;
let mut session_id = [0u8; SESSION_ID_LEN];
let mut rng = rand::thread_rng();
loop {
rng.fill_bytes(&mut session_id);
if !has_reserved_tncf_prefix(&session_id) {
break;
}
}
let builder = snow::Builder::new(Self::NOISE_PARAMS.parse()?);
let mut handshake = builder
.local_private_key(static_private)
.build_initiator()?;
let mut msg = vec![0u8; MAX_DATAGRAM_BYTES];
let written = handshake.write_message(&[], &mut msg)?;
msg.truncate(written);
let session = Self {
session_id,
node_id: None,
peer_addr,
state: Some(UdpSessionState::Handshaking {
handshake: Box::new(handshake),
step: 1,
}),
last_seen: Instant::now(),
created_at: Instant::now(),
};
Ok((session, msg))
}
pub fn new_responder(
session_id: [u8; SESSION_ID_LEN],
peer_addr: SocketAddr,
msg1: &[u8],
static_private: &[u8],
local_node_id: &str,
) -> Result<(Self, Vec<u8>), snow::Error> {
let builder = snow::Builder::new(Self::NOISE_PARAMS.parse()?);
let mut handshake = builder
.local_private_key(static_private)
.build_responder()?;
let mut scratch = vec![0u8; MAX_DATAGRAM_BYTES];
handshake.read_message(msg1, &mut scratch)?;
let payload = local_node_id.as_bytes();
let mut msg2 = vec![0u8; MAX_DATAGRAM_BYTES];
let written = handshake.write_message(payload, &mut msg2)?;
msg2.truncate(written);
let session = Self {
session_id,
node_id: None,
peer_addr,
state: Some(UdpSessionState::Handshaking {
handshake: Box::new(handshake),
step: 2,
}),
last_seen: Instant::now(),
created_at: Instant::now(),
};
Ok((session, msg2))
}
pub fn advance_handshake(
&mut self,
payload: &[u8],
local_node_id: Option<&str>,
) -> Result<Option<Vec<u8>>, snow::Error> {
let state = self.state.take().expect("NoiseUdpSession: state is None");
match state {
UdpSessionState::Handshaking {
mut handshake,
step,
} => {
let mut scratch = vec![0u8; MAX_DATAGRAM_BYTES];
let n = handshake.read_message(payload, &mut scratch)?;
self.last_seen = Instant::now();
if n > 0 {
if let Ok(id) = std::str::from_utf8(&scratch[..n]) {
if !id.is_empty() {
self.node_id = Some(id.to_owned());
}
}
}
if handshake.is_handshake_finished() {
let remote_static = Self::extract_remote_static(&handshake);
let transport = handshake.into_transport_mode()?;
self.state = Some(UdpSessionState::Established {
transport: Box::new(transport),
remote_static_fingerprint: remote_static,
});
Ok(None)
} else {
let id_payload = local_node_id.unwrap_or("").as_bytes();
let mut msg = vec![0u8; MAX_DATAGRAM_BYTES];
let written = handshake.write_message(id_payload, &mut msg)?;
msg.truncate(written);
if handshake.is_handshake_finished() {
let remote_static = Self::extract_remote_static(&handshake);
let transport = handshake.into_transport_mode()?;
self.state = Some(UdpSessionState::Established {
transport: Box::new(transport),
remote_static_fingerprint: remote_static,
});
} else {
self.state = Some(UdpSessionState::Handshaking {
handshake,
step: step + 1,
});
}
Ok(Some(msg))
}
}
other => {
self.state = Some(other);
Err(snow::Error::State(
snow::error::StateProblem::HandshakeAlreadyFinished,
))
}
}
}
pub fn encrypt(
&mut self,
plaintext: &[u8],
ciphertext: &mut [u8],
) -> Result<usize, snow::Error> {
match &mut self.state {
Some(UdpSessionState::Established { transport, .. }) => {
transport.write_message(plaintext, ciphertext)
}
_ => Err(snow::Error::State(
snow::error::StateProblem::HandshakeNotFinished,
)),
}
}
pub fn decrypt(
&mut self,
ciphertext: &[u8],
plaintext: &mut [u8],
src: SocketAddr,
) -> Result<usize, snow::Error> {
match &mut self.state {
Some(UdpSessionState::Established { transport, .. }) => {
let n = transport.read_message(ciphertext, plaintext)?;
self.peer_addr = src;
self.last_seen = Instant::now();
Ok(n)
}
_ => Err(snow::Error::State(
snow::error::StateProblem::HandshakeNotFinished,
)),
}
}
fn extract_remote_static(handshake: &snow::HandshakeState) -> [u8; 32] {
handshake
.get_remote_static()
.map(|s| {
let mut fp = [0u8; 32];
let len = s.len().min(32);
fp[..len].copy_from_slice(&s[..len]);
fp
})
.unwrap_or([0u8; 32])
}
}
#[cfg(feature = "noise")]
pub fn load_or_generate_static_keypair(key_path: &Path) -> io::Result<(Vec<u8>, Vec<u8>)> {
if key_path.exists() {
let data = std::fs::read(key_path)?;
if data.len() == 64 {
return Ok((data[..32].to_vec(), data[32..].to_vec()));
}
}
let builder = snow::Builder::new(
"Noise_XX_25519_ChaChaPoly_BLAKE2s"
.parse()
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("{e}")))?,
);
let keypair = builder
.generate_keypair()
.map_err(|e| io::Error::other(format!("{e}")))?;
let mut file_bytes = Vec::with_capacity(64);
file_bytes.extend_from_slice(&keypair.private);
file_bytes.extend_from_slice(&keypair.public);
if let Some(parent) = key_path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(key_path, &file_bytes)?;
Ok((keypair.private, keypair.public))
}
#[cfg(not(feature = "noise"))]
pub fn load_or_generate_static_keypair(_key_path: &Path) -> io::Result<(Vec<u8>, Vec<u8>)> {
Ok((vec![], vec![]))
}
#[inline]
pub fn is_tncf_frame(buf: &[u8]) -> bool {
buf.len() >= TNCF_HEADER_LEN && buf[..4] == TNCF_MAGIC
}
pub fn build_tncf_frame(frame_type: u8, body: &[u8]) -> Vec<u8> {
let mut frame = Vec::with_capacity(TNCF_HEADER_LEN + body.len());
frame.extend_from_slice(&TNCF_MAGIC);
frame.push(TNCF_VERSION_1);
frame.push(frame_type);
frame.push(0x00); frame.push(0x00); frame.extend_from_slice(body);
frame
}
pub fn parse_tncf_frame(buf: &[u8]) -> Option<(u8, &[u8])> {
if buf.len() < TNCF_HEADER_LEN {
return None;
}
let frame_type = buf[5];
let body = &buf[TNCF_HEADER_LEN..];
Some((frame_type, body))
}
pub fn build_session_frame(session_id: &[u8; SESSION_ID_LEN], noise_msg: &[u8]) -> Vec<u8> {
let mut frame = Vec::with_capacity(SESSION_ID_LEN + noise_msg.len());
frame.extend_from_slice(session_id);
frame.extend_from_slice(noise_msg);
frame
}