use crate::{ flags::{has, FLAG_CLOSE, FLAG_HMAC, FLAG_OPEN, FLAG_REPLY}, hmac, params::Params, validate_header, write_header, ProtoError, Result, HEADER_SIZE, HMAC_SIZE, TOKEN_SIZE, }; #[derive(Debug, Clone, PartialEq, Eq)] pub struct OpenRequest { pub params: Params, pub close: bool, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct OpenReply { pub flags: u8, pub token: u64, pub params: Params, } pub fn encode_open_request(request: &OpenRequest, hmac_key: Option<&[u8]>) -> Result> { let mut flags = FLAG_OPEN; if request.close { flags |= FLAG_CLOSE; } if hmac_key.is_some() { flags |= FLAG_HMAC; } let mut out = Vec::new(); write_header(&mut out, flags); if hmac_key.is_some() { out.extend_from_slice(&[0; HMAC_SIZE]); } out.extend_from_slice(&request.params.encode()); if let Some(key) = hmac_key { hmac::compute_hmac_in_place(key, &mut out, hmac::hmac_offset())?; } Ok(out) } pub fn decode_open_reply(packet: &[u8], hmac_key: Option<&[u8]>) -> Result { let flags = validate_header(packet)?; require(flags, FLAG_OPEN)?; require(flags, FLAG_REPLY)?; check_hmac_presence(flags, hmac_key)?; if let Some(key) = hmac_key { hmac::verify_hmac(key, packet, hmac::hmac_offset())?; } let mut pos = HEADER_SIZE; if has(flags, FLAG_HMAC) { pos += HMAC_SIZE; } let needed = pos + TOKEN_SIZE; if packet.len() < needed { return Err(ProtoError::PacketTooShort { needed, actual: packet.len(), }); } let token = u64::from_le_bytes(packet[pos..pos + TOKEN_SIZE].try_into().unwrap()); pos += TOKEN_SIZE; if token == 0 && !has(flags, FLAG_CLOSE) { return Err(ProtoError::ZeroToken); } Ok(OpenReply { flags, token, params: Params::decode(&packet[pos..])?, }) } pub(crate) fn require(flags: u8, flag: u8) -> Result<()> { if has(flags, flag) { Ok(()) } else { Err(ProtoError::MissingFlag(flag)) } } pub(crate) fn reject(flags: u8, flag: u8) -> Result<()> { if has(flags, flag) { Err(ProtoError::UnexpectedFlag(flag)) } else { Ok(()) } } pub(crate) fn check_hmac_presence(flags: u8, hmac_key: Option<&[u8]>) -> Result<()> { if has(flags, FLAG_HMAC) == hmac_key.is_some() { Ok(()) } else { Err(ProtoError::HmacPresenceMismatch) } } #[cfg(test)] mod tests { use super::*; use crate::params::{Clock, ReceivedStats, StampAt}; fn default_params() -> Params { Params { protocol_version: 1, duration_ns: 3_000_000_000, interval_ns: 1_000_000_000, received_stats: ReceivedStats::Both, stamp_at: StampAt::Both, clock: Clock::Both, ..Params::default() } } #[test] fn open_request_has_no_token() { let packet = encode_open_request( &OpenRequest { params: default_params(), close: false, }, None, ) .unwrap(); assert_eq!(packet.len(), 24); assert_eq!(&packet[..4], &[0x14, 0xa7, 0x5b, 0x01]); assert_eq!(&packet[4..], &default_params().encode()); } #[test] fn hmac_open_request_inserts_hmac_before_params() { let packet = encode_open_request( &OpenRequest { params: default_params(), close: false, }, Some(b"testkey"), ) .unwrap(); assert_eq!( packet.len(), 4 + HMAC_SIZE + default_params().encode().len() ); assert_eq!(&packet[..4], &[0x14, 0xa7, 0x5b, FLAG_OPEN | FLAG_HMAC]); assert_eq!(&packet[4 + HMAC_SIZE..], &default_params().encode()); hmac::verify_hmac(b"testkey", &packet, hmac::hmac_offset()).unwrap(); } #[test] fn open_reply_decodes_token() { let mut packet = vec![0x14, 0xa7, 0x5b, 0x03]; packet.extend_from_slice(&0x7896_b6ab_8771_5213u64.to_le_bytes()); packet.extend_from_slice(&default_params().encode()); let reply = decode_open_reply(&packet, None).unwrap(); assert_eq!(reply.token, 0x7896_b6ab_8771_5213); assert_eq!(reply.params, default_params()); } #[test] fn hmac_open_reply_decodes_token_after_hmac_field() { let mut packet = vec![0x14, 0xa7, 0x5b, FLAG_OPEN | FLAG_REPLY | FLAG_HMAC]; packet.extend_from_slice(&[0; HMAC_SIZE]); packet.extend_from_slice(&0x7896_b6ab_8771_5213u64.to_le_bytes()); packet.extend_from_slice(&default_params().encode()); hmac::compute_hmac_in_place(b"testkey", &mut packet, hmac::hmac_offset()).unwrap(); assert_eq!( packet.len(), 4 + HMAC_SIZE + TOKEN_SIZE + default_params().encode().len() ); let reply = decode_open_reply(&packet, Some(b"testkey")).unwrap(); assert_eq!(reply.token, 0x7896_b6ab_8771_5213); assert_eq!(reply.params, default_params()); } }