refactor(proto): unify request codecs
Replace the three per-kind request encoders and decoders with one directional pair, ahead of introducing irtt-server. Sender side: encode_request(RequestToEncode, hmac_key) constructs OPEN, CLOSE and ECHO requests from borrowed values. Params and the ECHO payload are borrowed rather than owned, removing a Params clone and a payload Vec from the client's hot paths. Wire output is byte-identical; all captured vectors still pass unchanged. Receiver side: decode_request(packet) structurally classifies an inbound request with no HMAC key, no negotiated Params, no session state and no allocation. A server must extract a token before it knows which key applies, so authentication is a separate step. Classification follows the verified server spec: reserved bits and FLAG_REPLY are rejected, OPEN takes precedence over CLOSE and over an echo-shaped body, and only the structural minimums (4/12/16, plus 16 with FLAG_HMAC) are enforced. Trailing bytes are tolerated, no negotiated length ceiling is applied, and a zero token is structurally valid. OPEN parameters stay encoded bytes so an authenticating receiver can verify before decoding untrusted data, and everything after an ECHO sequence number is exposed as an opaque tail. Add verify_packet_hmac(key, packet), which locates the standard field itself and rejects a packet lacking FLAG_HMAC. hmac_present reports packet layout, never authentication. The offset-based verify_hmac stays public as the low-level primitive for hand-built and captured wire vectors, matching the existing compute_hmac pair. Refactor the private envelope into structural header decoding, codec flag rules, HMAC presence policy and verification as separate steps. The reply codecs compose the same steps in the same order, so decode_open_reply and decode_echo_reply keep their exact error ordering and the client's fatal-versus-ignore classification is unchanged. Reply decoding stays state-specific: the client already knows which reply to expect, and the two need different semantic context. Migrate the production client and every fake peer to the new API; the fake peers now admit requests the way a server does, instead of parsing packets by hand.
This commit is contained in:
18 files changed
+1296
-820
No files matched your search
@@ -1,79 +0,0 @@
|
||||
use crate::{
|
||||
envelope::{self, FlagRule},
|
||||
flags::{has, FLAG_CLOSE, FLAG_OPEN, FLAG_REPLY},
|
||||
layout::PacketLayout,
|
||||
ProtoError, Result, TOKEN_SIZE,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CloseRequest {
|
||||
pub token: u64,
|
||||
}
|
||||
|
||||
pub fn encode_close_request(request: &CloseRequest, hmac_key: Option<&[u8]>) -> Result<Vec<u8>> {
|
||||
let mut out = envelope::begin(
|
||||
FLAG_CLOSE,
|
||||
hmac_key,
|
||||
&[
|
||||
FlagRule::Require(FLAG_CLOSE),
|
||||
FlagRule::Reject(FLAG_OPEN),
|
||||
FlagRule::Reject(FLAG_REPLY),
|
||||
],
|
||||
PacketLayout::close_request(hmac_key.is_some()).header_len(),
|
||||
)?;
|
||||
out.extend_from_slice(&request.token.to_le_bytes());
|
||||
envelope::finish(out, hmac_key)
|
||||
}
|
||||
|
||||
pub fn decode_close_request(packet: &[u8], hmac_key: Option<&[u8]>) -> Result<CloseRequest> {
|
||||
let envelope = envelope::decode(
|
||||
packet,
|
||||
hmac_key,
|
||||
&[
|
||||
FlagRule::Require(FLAG_CLOSE),
|
||||
FlagRule::Reject(FLAG_OPEN),
|
||||
FlagRule::Reject(FLAG_REPLY),
|
||||
],
|
||||
)?;
|
||||
let expected_len =
|
||||
PacketLayout::close_request(has(envelope.flags, crate::FLAG_HMAC)).header_len();
|
||||
if packet.len() != expected_len {
|
||||
return Err(ProtoError::PacketLengthMismatch {
|
||||
expected: expected_len,
|
||||
actual: packet.len(),
|
||||
});
|
||||
}
|
||||
envelope::verify(packet, hmac_key)?;
|
||||
|
||||
Ok(CloseRequest {
|
||||
token: u64::from_le_bytes(
|
||||
packet[envelope.body_offset..envelope.body_offset + TOKEN_SIZE]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{hmac, FLAG_HMAC, HMAC_SIZE};
|
||||
|
||||
#[test]
|
||||
fn hmac_close_request_places_token_after_hmac() {
|
||||
let packet = encode_close_request(
|
||||
&CloseRequest {
|
||||
token: 0x7896_b6ab_8771_5213,
|
||||
},
|
||||
Some(b"testkey"),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(packet[3], FLAG_CLOSE | FLAG_HMAC);
|
||||
assert_eq!(
|
||||
&packet[4 + HMAC_SIZE..],
|
||||
&0x7896_b6ab_8771_5213u64.to_le_bytes()
|
||||
);
|
||||
hmac::verify_hmac(b"testkey", &packet, hmac::hmac_offset()).unwrap();
|
||||
}
|
||||
}
|
||||
+19
-197
@@ -1,9 +1,9 @@
|
||||
use crate::{
|
||||
envelope::{self, FlagRule},
|
||||
flags::{FLAG_CLOSE, FLAG_OPEN, FLAG_REPLY},
|
||||
flags::{FLAG_OPEN, FLAG_REPLY},
|
||||
layout::{echo_packet_len, PacketLayout},
|
||||
params::{Clock, Params, StampAt},
|
||||
ProtoError, Result, RECV_COUNT_SIZE, RECV_WINDOW_SIZE, SEQ_SIZE, TIMESTAMP_SIZE, TOKEN_SIZE,
|
||||
ProtoError, Result, SEQ_SIZE, TIMESTAMP_SIZE, TOKEN_SIZE,
|
||||
};
|
||||
|
||||
/// Upstream irtt 0.9.1 always emits both midpoint timestamp fields (wall,
|
||||
@@ -34,13 +34,6 @@ fn midpoint_compat_packet_len(layout: PacketLayout, normal_len: usize) -> usize
|
||||
(layout.header_len() + MIDPOINT_COMPAT_EXTRA).max(normal_len)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct EchoRequest {
|
||||
pub token: u64,
|
||||
pub sequence: u32,
|
||||
pub payload: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct EchoReply {
|
||||
pub flags: u8,
|
||||
@@ -62,69 +55,6 @@ pub struct TimestampFields {
|
||||
pub send_mono: Option<i64>,
|
||||
}
|
||||
|
||||
pub fn encode_echo_request(
|
||||
request: &EchoRequest,
|
||||
params: &Params,
|
||||
hmac_key: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>> {
|
||||
let layout = PacketLayout::echo(hmac_key.is_some(), params);
|
||||
let len = echo_packet_len(hmac_key.is_some(), params)?;
|
||||
let payload_offset = layout.header_len();
|
||||
let available_payload_len = len.saturating_sub(payload_offset);
|
||||
if request.payload.len() > available_payload_len {
|
||||
return Err(ProtoError::PayloadTooLarge {
|
||||
available: available_payload_len,
|
||||
provided: request.payload.len(),
|
||||
});
|
||||
}
|
||||
let mut out = envelope::begin(
|
||||
0,
|
||||
hmac_key,
|
||||
&[
|
||||
FlagRule::Reject(FLAG_OPEN),
|
||||
FlagRule::Reject(FLAG_REPLY),
|
||||
FlagRule::Reject(FLAG_CLOSE),
|
||||
],
|
||||
len,
|
||||
)?;
|
||||
out.extend_from_slice(&request.token.to_le_bytes());
|
||||
out.extend_from_slice(&request.sequence.to_le_bytes());
|
||||
push_zeroed_layout_tail(layout, &mut out);
|
||||
out.resize(len, 0);
|
||||
out[payload_offset..payload_offset + request.payload.len()].copy_from_slice(&request.payload);
|
||||
|
||||
envelope::finish(out, hmac_key)
|
||||
}
|
||||
|
||||
pub fn decode_echo_request(
|
||||
packet: &[u8],
|
||||
params: &Params,
|
||||
hmac_key: Option<&[u8]>,
|
||||
) -> Result<EchoRequest> {
|
||||
let envelope = envelope::decode(
|
||||
packet,
|
||||
hmac_key,
|
||||
&[
|
||||
FlagRule::Reject(FLAG_OPEN),
|
||||
FlagRule::Reject(FLAG_REPLY),
|
||||
FlagRule::Reject(FLAG_CLOSE),
|
||||
],
|
||||
)?;
|
||||
let layout = PacketLayout::echo(hmac_key.is_some(), params);
|
||||
validate_echo_length(packet, params, layout, false)?;
|
||||
envelope::verify(packet, hmac_key)?;
|
||||
|
||||
let mut pos = envelope.body_offset;
|
||||
let token = read_u64(packet, &mut pos);
|
||||
let sequence = read_u32(packet, &mut pos);
|
||||
|
||||
Ok(EchoRequest {
|
||||
token,
|
||||
sequence,
|
||||
payload: packet[layout.header_len()..].to_vec(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn encode_echo_reply(
|
||||
reply: &EchoReply,
|
||||
params: &Params,
|
||||
@@ -141,7 +71,7 @@ pub fn encode_echo_reply(
|
||||
});
|
||||
}
|
||||
|
||||
let mut out = envelope::begin(
|
||||
let mut out = envelope::begin_checked(
|
||||
reply.flags,
|
||||
hmac_key,
|
||||
&[FlagRule::Reject(FLAG_OPEN), FlagRule::Require(FLAG_REPLY)],
|
||||
@@ -183,7 +113,7 @@ pub fn decode_echo_reply(
|
||||
&[FlagRule::Reject(FLAG_OPEN), FlagRule::Require(FLAG_REPLY)],
|
||||
)?;
|
||||
let layout = PacketLayout::echo(hmac_key.is_some(), params);
|
||||
let midpoint_compat = validate_echo_length(packet, params, layout, true)?;
|
||||
let midpoint_compat = validate_echo_length(packet, params, layout)?;
|
||||
envelope::verify(packet, hmac_key)?;
|
||||
|
||||
let header_len = layout.header_len()
|
||||
@@ -238,18 +168,12 @@ pub fn decode_echo_reply(
|
||||
|
||||
/// Validates the packet length against the negotiated layout. Returns
|
||||
/// `Ok(true)` when the packet matched the upstream midpoint compatibility
|
||||
/// length rather than the exact negotiated length (only possible when
|
||||
/// `allow_midpoint_compat` is set).
|
||||
/// length rather than the exact negotiated length.
|
||||
///
|
||||
/// The negotiated length is always checked first, so when the compatibility
|
||||
/// form is not longer than the negotiated form the two are indistinguishable
|
||||
/// by length and normal negotiated parsing wins. See [`decode_echo_reply`].
|
||||
fn validate_echo_length(
|
||||
packet: &[u8],
|
||||
params: &Params,
|
||||
layout: PacketLayout,
|
||||
allow_midpoint_compat: bool,
|
||||
) -> Result<bool> {
|
||||
fn validate_echo_length(packet: &[u8], params: &Params, layout: PacketLayout) -> Result<bool> {
|
||||
let header_len = layout.header_len();
|
||||
if packet.len() < header_len {
|
||||
return Err(ProtoError::PacketTooShort {
|
||||
@@ -261,7 +185,7 @@ fn validate_echo_length(
|
||||
if packet.len() == expected_len {
|
||||
return Ok(false);
|
||||
}
|
||||
if allow_midpoint_compat && is_midpoint_single_clock(params) {
|
||||
if is_midpoint_single_clock(params) {
|
||||
let compat_len = midpoint_compat_packet_len(layout, expected_len);
|
||||
if compat_len > expected_len && packet.len() == compat_len {
|
||||
return Ok(true);
|
||||
@@ -273,18 +197,6 @@ fn validate_echo_length(
|
||||
})
|
||||
}
|
||||
|
||||
fn push_zeroed_layout_tail(layout: PacketLayout, out: &mut Vec<u8>) {
|
||||
if layout.recv_count {
|
||||
out.extend_from_slice(&[0; RECV_COUNT_SIZE]);
|
||||
}
|
||||
if layout.recv_window {
|
||||
out.extend_from_slice(&[0; RECV_WINDOW_SIZE]);
|
||||
}
|
||||
for _ in 0..layout.timestamp_count() {
|
||||
out.extend_from_slice(&[0; TIMESTAMP_SIZE]);
|
||||
}
|
||||
}
|
||||
|
||||
fn push_echo_reply_tail(reply: &EchoReply, layout: PacketLayout, out: &mut Vec<u8>) -> Result<()> {
|
||||
if let Some(value) = field_value(layout.recv_count, reply.recv_count, "recv_count")? {
|
||||
out.extend_from_slice(&value.to_le_bytes());
|
||||
@@ -350,7 +262,7 @@ mod tests {
|
||||
use crate::{
|
||||
hmac,
|
||||
params::{Clock, ReceivedStats, StampAt},
|
||||
write_header, FLAG_HMAC, HMAC_SIZE,
|
||||
write_header, FLAG_HMAC, HMAC_SIZE, RECV_COUNT_SIZE, RECV_WINDOW_SIZE,
|
||||
};
|
||||
|
||||
fn default_params() -> Params {
|
||||
@@ -370,108 +282,18 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn echo_request_with_payload(payload: Vec<u8>) -> EchoRequest {
|
||||
EchoRequest {
|
||||
token: 0x7896_b6ab_8771_5213,
|
||||
sequence: 9,
|
||||
payload,
|
||||
/// Zero-fills every negotiated reply field, matching what a peer that
|
||||
/// reports no optional values would put on the wire.
|
||||
fn push_zeroed_layout_tail(layout: PacketLayout, out: &mut Vec<u8>) {
|
||||
if layout.recv_count {
|
||||
out.extend_from_slice(&[0; RECV_COUNT_SIZE]);
|
||||
}
|
||||
if layout.recv_window {
|
||||
out.extend_from_slice(&[0; RECV_WINDOW_SIZE]);
|
||||
}
|
||||
for _ in 0..layout.timestamp_count() {
|
||||
out.extend_from_slice(&[0; TIMESTAMP_SIZE]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn echo_request_encodes_default_placeholders() {
|
||||
let params = default_params();
|
||||
let packet = encode_echo_request(
|
||||
&EchoRequest {
|
||||
token: 0x7896_b6ab_8771_5213,
|
||||
sequence: 0,
|
||||
payload: Vec::new(),
|
||||
},
|
||||
¶ms,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(packet.len(), 60);
|
||||
assert_eq!(&packet[..4], &[0x14, 0xa7, 0x5b, 0x00]);
|
||||
assert_eq!(&packet[4..12], &0x7896_b6ab_8771_5213u64.to_le_bytes());
|
||||
assert!(packet[12..].iter().all(|byte| *byte == 0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn echo_request_encodes_exact_fit_payload() {
|
||||
let params = params_with_payload_space(4);
|
||||
let request = echo_request_with_payload(vec![1, 2, 3, 4]);
|
||||
let packet = encode_echo_request(&request, ¶ms, None).unwrap();
|
||||
let payload_offset = PacketLayout::echo(false, ¶ms).header_len();
|
||||
|
||||
assert_eq!(&packet[payload_offset..], &[1, 2, 3, 4]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn echo_request_encodes_shorter_payload_and_zero_fills_remainder() {
|
||||
let params = params_with_payload_space(4);
|
||||
let request = echo_request_with_payload(vec![1, 2]);
|
||||
let packet = encode_echo_request(&request, ¶ms, None).unwrap();
|
||||
let payload_offset = PacketLayout::echo(false, ¶ms).header_len();
|
||||
|
||||
assert_eq!(&packet[payload_offset..], &[1, 2, 0, 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn echo_request_rejects_oversized_payload() {
|
||||
let params = params_with_payload_space(4);
|
||||
let request = echo_request_with_payload(vec![1, 2, 3, 4, 5]);
|
||||
let original = request.clone();
|
||||
|
||||
assert_eq!(
|
||||
encode_echo_request(&request, ¶ms, None),
|
||||
Err(ProtoError::PayloadTooLarge {
|
||||
available: 4,
|
||||
provided: 5,
|
||||
})
|
||||
);
|
||||
assert_eq!(request, original);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn echo_request_rejects_negative_requested_length() {
|
||||
let request = echo_request_with_payload(Vec::new());
|
||||
let params = Params {
|
||||
length: -1,
|
||||
..Params::default()
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
encode_echo_request(&request, ¶ms, None),
|
||||
Err(ProtoError::NegativePacketLength { length: -1 })
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hmac_echo_request_places_token_and_sequence_after_hmac() {
|
||||
let params = default_params();
|
||||
let packet = encode_echo_request(
|
||||
&EchoRequest {
|
||||
token: 0x7896_b6ab_8771_5213,
|
||||
sequence: 9,
|
||||
payload: Vec::new(),
|
||||
},
|
||||
¶ms,
|
||||
Some(b"testkey"),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(packet.len(), 76);
|
||||
assert_eq!(&packet[..4], &[0x14, 0xa7, 0x5b, FLAG_HMAC]);
|
||||
assert_eq!(
|
||||
&packet[4 + HMAC_SIZE..4 + HMAC_SIZE + TOKEN_SIZE],
|
||||
&0x7896_b6ab_8771_5213u64.to_le_bytes()
|
||||
);
|
||||
assert_eq!(
|
||||
&packet[4 + HMAC_SIZE + TOKEN_SIZE..4 + HMAC_SIZE + TOKEN_SIZE + SEQ_SIZE],
|
||||
&9_u32.to_le_bytes()
|
||||
);
|
||||
hmac::verify_hmac(b"testkey", &packet, hmac::hmac_offset()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
//! Shared structural envelope handling.
|
||||
//!
|
||||
//! Layering, from packet bytes upwards:
|
||||
//!
|
||||
//! 1. [`decode_structural`] — magic, reserved flag bits, and the HMAC-dependent
|
||||
//! body offset. No key, no packet-type semantics.
|
||||
//! 2. [`require_flags`] — codec-specific semantic flag rules.
|
||||
//! 3. [`check_hmac_presence`] — HMAC presence *policy* derived from a key.
|
||||
//! 4. [`verify`] — cryptographic verification.
|
||||
//!
|
||||
//! [`decode`] composes 1–3 for the reply codecs, which know the packet type
|
||||
//! they expect and hold the applicable key. Request decoding uses only step 1,
|
||||
//! because a server must extract a token before it knows which key applies.
|
||||
|
||||
use crate::{
|
||||
flags::{self, has, FLAG_HMAC},
|
||||
hmac, validate_header, write_header, ProtoError, Result, HEADER_SIZE, HMAC_SIZE,
|
||||
@@ -9,28 +23,52 @@ pub(crate) enum FlagRule {
|
||||
Reject(u8),
|
||||
}
|
||||
|
||||
/// Structural result of parsing the fixed 4-byte protocol header.
|
||||
///
|
||||
/// `hmac_present` reports only that `FLAG_HMAC` was set, and therefore that the
|
||||
/// packet is laid out with an authentication field before its body. It carries
|
||||
/// no claim about the contents of that field.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) struct Envelope {
|
||||
pub(crate) flags: u8,
|
||||
pub(crate) hmac_present: bool,
|
||||
pub(crate) body_offset: usize,
|
||||
}
|
||||
|
||||
/// The single source of truth for protocol header structure: minimum length,
|
||||
/// magic, reserved flag bits, and the HMAC-dependent body offset.
|
||||
pub(crate) fn decode_structural(packet: &[u8]) -> Result<Envelope> {
|
||||
let flags = validate_header(packet)?;
|
||||
let hmac_present = has(flags, FLAG_HMAC);
|
||||
Ok(Envelope {
|
||||
flags,
|
||||
hmac_present,
|
||||
body_offset: HEADER_SIZE + if hmac_present { HMAC_SIZE } else { 0 },
|
||||
})
|
||||
}
|
||||
|
||||
/// Structural decode plus the packet-type and HMAC-presence checks a codec that
|
||||
/// already knows both the expected packet type and the applicable key performs.
|
||||
pub(crate) fn decode(
|
||||
packet: &[u8],
|
||||
hmac_key: Option<&[u8]>,
|
||||
rules: &[FlagRule],
|
||||
) -> Result<Envelope> {
|
||||
let flags = validate_header(packet)?;
|
||||
validate_packet_type(flags, rules)?;
|
||||
check_hmac_presence(flags, hmac_key)?;
|
||||
|
||||
Ok(Envelope {
|
||||
flags,
|
||||
body_offset: HEADER_SIZE + if has(flags, FLAG_HMAC) { HMAC_SIZE } else { 0 },
|
||||
})
|
||||
let envelope = decode_structural(packet)?;
|
||||
require_flags(envelope.flags, rules)?;
|
||||
check_hmac_presence(envelope.flags, hmac_key)?;
|
||||
Ok(envelope)
|
||||
}
|
||||
|
||||
pub(crate) fn begin(
|
||||
/// Begins a packet whose flags the encoder derived itself, so no packet-type
|
||||
/// rule can fail. The key is authoritative for `FLAG_HMAC`.
|
||||
pub(crate) fn begin(flags: u8, hmac_key: Option<&[u8]>, capacity: usize) -> Result<Vec<u8>> {
|
||||
begin_checked(flags, hmac_key, &[], capacity)
|
||||
}
|
||||
|
||||
/// Begins a packet from caller-supplied flags, which must satisfy the codec's
|
||||
/// packet-type rules.
|
||||
pub(crate) fn begin_checked(
|
||||
flags: u8,
|
||||
hmac_key: Option<&[u8]>,
|
||||
rules: &[FlagRule],
|
||||
@@ -40,7 +78,7 @@ pub(crate) fn begin(
|
||||
// while unauthenticated encoders clear any caller-supplied FLAG_HMAC.
|
||||
let flags = with_hmac_flag(flags, hmac_key.is_some());
|
||||
flags::validate_flags(flags)?;
|
||||
validate_packet_type(flags, rules)?;
|
||||
require_flags(flags, rules)?;
|
||||
|
||||
let minimum_capacity = HEADER_SIZE + if hmac_key.is_some() { HMAC_SIZE } else { 0 };
|
||||
let mut out = Vec::with_capacity(capacity.max(minimum_capacity));
|
||||
@@ -65,7 +103,7 @@ pub(crate) fn finish(mut packet: Vec<u8>, hmac_key: Option<&[u8]>) -> Result<Vec
|
||||
Ok(packet)
|
||||
}
|
||||
|
||||
fn validate_packet_type(flags: u8, rules: &[FlagRule]) -> Result<()> {
|
||||
pub(crate) fn require_flags(flags: u8, rules: &[FlagRule]) -> Result<()> {
|
||||
for rule in rules {
|
||||
match *rule {
|
||||
FlagRule::Require(flag) if !has(flags, flag) => {
|
||||
@@ -80,7 +118,10 @@ fn validate_packet_type(flags: u8, rules: &[FlagRule]) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_hmac_presence(flags: u8, hmac_key: Option<&[u8]>) -> Result<()> {
|
||||
/// Checks structural HMAC presence against the caller's expectation. This is
|
||||
/// policy, not authentication: it only compares `FLAG_HMAC` with whether a key
|
||||
/// was supplied.
|
||||
pub(crate) fn check_hmac_presence(flags: u8, hmac_key: Option<&[u8]>) -> Result<()> {
|
||||
if has(flags, FLAG_HMAC) == hmac_key.is_some() {
|
||||
Ok(())
|
||||
} else {
|
||||
@@ -152,9 +193,23 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn structural_decode_reports_hmac_presence_without_a_key() {
|
||||
let mut authenticated = header(FLAG_OPEN | FLAG_HMAC);
|
||||
authenticated.extend_from_slice(&[0xff; HMAC_SIZE]);
|
||||
|
||||
let envelope = decode_structural(&authenticated).unwrap();
|
||||
assert!(envelope.hmac_present);
|
||||
assert_eq!(envelope.body_offset, HEADER_SIZE + HMAC_SIZE);
|
||||
|
||||
let envelope = decode_structural(&header(FLAG_OPEN)).unwrap();
|
||||
assert!(!envelope.hmac_present);
|
||||
assert_eq!(envelope.body_offset, HEADER_SIZE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_rejects_bad_hmac() {
|
||||
let mut packet = begin(FLAG_OPEN, Some(KEY), OPEN_REQUEST_RULES, 22).unwrap();
|
||||
let mut packet = begin_checked(FLAG_OPEN, Some(KEY), OPEN_REQUEST_RULES, 22).unwrap();
|
||||
packet.extend_from_slice(&[1, 2]);
|
||||
let mut packet = finish(packet, Some(KEY)).unwrap();
|
||||
*packet.last_mut().unwrap() ^= 0x80;
|
||||
@@ -165,10 +220,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn encoder_key_authoritatively_normalizes_hmac_flag() {
|
||||
let plain = begin(FLAG_OPEN | FLAG_HMAC, None, OPEN_REQUEST_RULES, HEADER_SIZE).unwrap();
|
||||
let plain =
|
||||
begin_checked(FLAG_OPEN | FLAG_HMAC, None, OPEN_REQUEST_RULES, HEADER_SIZE).unwrap();
|
||||
assert_eq!(plain[3], FLAG_OPEN);
|
||||
|
||||
let authenticated = begin(
|
||||
let authenticated = begin_checked(
|
||||
FLAG_OPEN,
|
||||
Some(KEY),
|
||||
OPEN_REQUEST_RULES,
|
||||
|
||||
@@ -2,7 +2,7 @@ use hmac::{Hmac, KeyInit, Mac};
|
||||
use md5::Md5;
|
||||
use subtle::ConstantTimeEq;
|
||||
|
||||
use crate::{ProtoError, Result, HEADER_SIZE, HMAC_SIZE};
|
||||
use crate::{envelope, flags::FLAG_HMAC, ProtoError, Result, HEADER_SIZE, HMAC_SIZE};
|
||||
|
||||
// HMAC-MD5 is used for compatibility with the IRTT wire protocol, not as a
|
||||
// new cryptographic recommendation. Verification uses HMAC with constant-time
|
||||
@@ -34,6 +34,12 @@ pub fn compute_hmac_in_place(key: &[u8], packet: &mut [u8], hmac_offset: usize)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Verifies an HMAC field at an explicit offset.
|
||||
///
|
||||
/// This is the low-level primitive, kept for hand-built and captured wire
|
||||
/// vectors that name their own field position. Code handling real packets
|
||||
/// should use [`verify_packet_hmac`], which locates the field itself and
|
||||
/// rejects a packet that does not carry `FLAG_HMAC`.
|
||||
pub fn verify_hmac(key: &[u8], packet: &[u8], hmac_offset: usize) -> Result<()> {
|
||||
if packet.len().saturating_sub(hmac_offset) < HMAC_SIZE {
|
||||
return Err(ProtoError::InvalidHmacOffset);
|
||||
@@ -47,6 +53,29 @@ pub fn verify_hmac(key: &[u8], packet: &[u8], hmac_offset: usize) -> Result<()>
|
||||
}
|
||||
}
|
||||
|
||||
/// Verifies the authentication field of a whole IRTT packet.
|
||||
///
|
||||
/// The field is the standard 16 bytes immediately after the 4-byte header, so
|
||||
/// callers never compute an offset themselves. This is cryptographic
|
||||
/// verification only: it says nothing about which key *should* apply to the
|
||||
/// packet, nor about session state. Choosing the key is the caller's policy.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`ProtoError::PacketTooShort`], [`ProtoError::BadMagic`] or
|
||||
/// [`ProtoError::ReservedFlags`] for a structurally invalid header,
|
||||
/// [`ProtoError::MissingFlag`] when the packet does not carry `FLAG_HMAC`,
|
||||
/// [`ProtoError::InvalidHmacOffset`] when the field is truncated, and
|
||||
/// [`ProtoError::BadHmac`] when the MAC does not match. The packet is never
|
||||
/// modified.
|
||||
pub fn verify_packet_hmac(key: &[u8], packet: &[u8]) -> Result<()> {
|
||||
let envelope = envelope::decode_structural(packet)?;
|
||||
if !envelope.hmac_present {
|
||||
return Err(ProtoError::MissingFlag(FLAG_HMAC));
|
||||
}
|
||||
verify_hmac(key, packet, hmac_offset())
|
||||
}
|
||||
|
||||
pub(crate) fn hmac_offset() -> usize {
|
||||
HEADER_SIZE
|
||||
}
|
||||
|
||||
@@ -12,15 +12,28 @@
|
||||
//! performs no additional validation, so callers that build `Params` manually
|
||||
//! are responsible for validating those values before sending them.
|
||||
//!
|
||||
//! The crate also provides packet layout calculation, open/echo/close packet
|
||||
//! encoding and decoding, and optional HMAC placement, computation, and
|
||||
//! verification helpers.
|
||||
//! The crate also provides packet layout calculation, packet encoding and
|
||||
//! decoding, and optional HMAC placement, computation, and verification
|
||||
//! helpers.
|
||||
//! Encoder `hmac_key` arguments are authoritative: `Some(key)` adds
|
||||
//! `FLAG_HMAC`, while `None` removes it from caller-supplied reply flags.
|
||||
//!
|
||||
//! # Directional model
|
||||
//!
|
||||
//! Requests use one encoder and one decoder for all three request kinds:
|
||||
//! [`encode_request`] builds a request from borrowed sender-side values, and
|
||||
//! [`decode_request`] structurally classifies an inbound request without a key,
|
||||
//! negotiated [`Params`], or any session state. [`verify_packet_hmac`]
|
||||
//! authenticates a packet separately, once the applicable key is known —
|
||||
//! `FLAG_HMAC` being present is a statement about layout, not about validity.
|
||||
//!
|
||||
//! Replies keep type-specific decoders ([`decode_open_reply`],
|
||||
//! [`decode_echo_reply`]) because a client already knows from its session state
|
||||
//! which reply kind to expect, and the two need genuinely different semantic
|
||||
//! context.
|
||||
//!
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
pub mod close;
|
||||
pub mod echo;
|
||||
mod envelope;
|
||||
pub mod error;
|
||||
@@ -29,22 +42,19 @@ pub mod hmac;
|
||||
pub mod layout;
|
||||
pub mod open;
|
||||
pub mod params;
|
||||
pub mod request;
|
||||
pub mod varint;
|
||||
|
||||
pub use close::{decode_close_request, encode_close_request, CloseRequest};
|
||||
pub use echo::{
|
||||
decode_echo_reply, decode_echo_request, encode_echo_reply, encode_echo_request, EchoReply,
|
||||
EchoRequest, TimestampFields,
|
||||
};
|
||||
pub use echo::{decode_echo_reply, encode_echo_reply, EchoReply, TimestampFields};
|
||||
pub use error::{ProtoError, Result};
|
||||
pub use flags::*;
|
||||
pub use hmac::{compute_hmac, compute_hmac_in_place, verify_hmac};
|
||||
pub use hmac::{compute_hmac, compute_hmac_in_place, verify_hmac, verify_packet_hmac};
|
||||
pub use layout::{echo_header_len, echo_packet_len, PacketLayout};
|
||||
pub use open::{
|
||||
decode_open_reply, decode_open_request, encode_open_reply, encode_open_request, OpenReply,
|
||||
OpenRequest,
|
||||
};
|
||||
pub use open::{decode_open_reply, encode_open_reply, OpenReply};
|
||||
pub use params::{Clock, Params, ReceivedStats, ServerFill, StampAt, MAX_SERVER_FILL_BYTES};
|
||||
pub use request::{
|
||||
decode_request, encode_request, DecodedRequest, DecodedRequestKind, RequestToEncode,
|
||||
};
|
||||
|
||||
pub const MAGIC: [u8; 3] = [0x14, 0xA7, 0x5B];
|
||||
pub const PROTOCOL_VERSION: i64 = 1;
|
||||
|
||||
@@ -6,12 +6,6 @@ use crate::{
|
||||
ProtoError, Result, 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,
|
||||
@@ -19,39 +13,9 @@ pub struct OpenReply {
|
||||
pub params: Params,
|
||||
}
|
||||
|
||||
pub fn encode_open_request(request: &OpenRequest, hmac_key: Option<&[u8]>) -> Result<Vec<u8>> {
|
||||
let mut flags = FLAG_OPEN;
|
||||
if request.close {
|
||||
flags |= FLAG_CLOSE;
|
||||
}
|
||||
let params = request.params.encode();
|
||||
let mut out = envelope::begin(
|
||||
flags,
|
||||
hmac_key,
|
||||
&[FlagRule::Require(FLAG_OPEN), FlagRule::Reject(FLAG_REPLY)],
|
||||
PacketLayout::open_request(hmac_key.is_some()).header_len() + params.len(),
|
||||
)?;
|
||||
out.extend_from_slice(¶ms);
|
||||
envelope::finish(out, hmac_key)
|
||||
}
|
||||
|
||||
pub fn decode_open_request(packet: &[u8], hmac_key: Option<&[u8]>) -> Result<OpenRequest> {
|
||||
let envelope = envelope::decode(
|
||||
packet,
|
||||
hmac_key,
|
||||
&[FlagRule::Require(FLAG_OPEN), FlagRule::Reject(FLAG_REPLY)],
|
||||
)?;
|
||||
envelope::verify(packet, hmac_key)?;
|
||||
|
||||
Ok(OpenRequest {
|
||||
params: Params::decode(&packet[envelope.body_offset..])?,
|
||||
close: has(envelope.flags, FLAG_CLOSE),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn encode_open_reply(reply: &OpenReply, hmac_key: Option<&[u8]>) -> Result<Vec<u8>> {
|
||||
let params = reply.params.encode();
|
||||
let mut out = envelope::begin(
|
||||
let mut out = envelope::begin_checked(
|
||||
reply.flags,
|
||||
hmac_key,
|
||||
&[FlagRule::Require(FLAG_OPEN), FlagRule::Require(FLAG_REPLY)],
|
||||
@@ -116,42 +80,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[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];
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
//! Directional request codecs: one sender-side encoder and one receiver-side
|
||||
//! structural decoder.
|
||||
//!
|
||||
//! The two directions are deliberately asymmetric. Encoding an ECHO request
|
||||
//! needs the negotiated [`Params`] because the sender constructs a
|
||||
//! negotiated-length datagram; decoding one does not, because a receiver reads
|
||||
//! only the token and sequence number and treats the rest as opaque.
|
||||
|
||||
use crate::{
|
||||
envelope,
|
||||
flags::{has, FLAG_CLOSE, FLAG_OPEN, FLAG_REPLY},
|
||||
layout::{echo_packet_len, PacketLayout},
|
||||
params::Params,
|
||||
ProtoError, Result, SEQ_SIZE, TOKEN_SIZE,
|
||||
};
|
||||
|
||||
/// A request to construct, described in sender-side semantic terms.
|
||||
///
|
||||
/// Every variant borrows what it needs; the encoder owns nothing.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum RequestToEncode<'a> {
|
||||
/// Session-opening request. `no_test` selects the *no-test* form, which
|
||||
/// adds `FLAG_CLOSE` and asks the peer to answer without creating a
|
||||
/// session.
|
||||
Open { params: &'a Params, no_test: bool },
|
||||
/// Session teardown request.
|
||||
Close { token: u64 },
|
||||
/// Round-trip probe. `params` supplies the negotiated packet layout and
|
||||
/// length, and `payload` is placed at the negotiated payload offset.
|
||||
Echo {
|
||||
token: u64,
|
||||
sequence: u32,
|
||||
params: &'a Params,
|
||||
payload: &'a [u8],
|
||||
},
|
||||
}
|
||||
|
||||
/// A structurally decoded inbound request, borrowed from the packet.
|
||||
///
|
||||
/// `hmac_present` reports only that `FLAG_HMAC` was set and that the packet is
|
||||
/// laid out with an authentication field. It is **not** an authentication
|
||||
/// result: the field may hold arbitrary bytes. Use [`verify_packet_hmac`] to
|
||||
/// check the MAC once the applicable key is known.
|
||||
///
|
||||
/// [`verify_packet_hmac`]: crate::verify_packet_hmac
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct DecodedRequest<'a> {
|
||||
pub hmac_present: bool,
|
||||
pub kind: DecodedRequestKind<'a>,
|
||||
}
|
||||
|
||||
/// The request kinds a receiver can distinguish from the packet alone.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum DecodedRequestKind<'a> {
|
||||
/// `FLAG_OPEN` was set. `params` is the still-encoded parameter payload;
|
||||
/// see [`decode_request`] for why it is not decoded here.
|
||||
Open { no_test: bool, params: &'a [u8] },
|
||||
/// `FLAG_CLOSE` was set without `FLAG_OPEN`. Trailing bytes after the token
|
||||
/// are accepted and not exposed, because they carry no meaning.
|
||||
Close { token: u64 },
|
||||
/// Neither `FLAG_OPEN` nor `FLAG_CLOSE` was set. `tail` is everything after
|
||||
/// the sequence number, which is opaque to a receiver.
|
||||
Echo {
|
||||
token: u64,
|
||||
sequence: u32,
|
||||
tail: &'a [u8],
|
||||
},
|
||||
}
|
||||
|
||||
/// Encodes an outbound request.
|
||||
///
|
||||
/// This is sender-side semantic construction: the variant determines the flags,
|
||||
/// so callers never assemble flag bits themselves.
|
||||
///
|
||||
/// `hmac_key` is authoritative. `Some(key)` sets `FLAG_HMAC`, reserves the
|
||||
/// 16-byte authentication field after the header, and signs the finished
|
||||
/// datagram; `None` produces an unauthenticated packet.
|
||||
///
|
||||
/// [`RequestToEncode::Echo`] requires the negotiated [`Params`] because the
|
||||
/// sender must build a datagram of the negotiated length with the payload at
|
||||
/// the negotiated offset. The receiving side needs no `Params` — see
|
||||
/// [`decode_request`].
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`ProtoError::PayloadTooLarge`] when an ECHO payload does not fit
|
||||
/// the negotiated length, and [`ProtoError::NegativePacketLength`] when the
|
||||
/// negotiated length is negative.
|
||||
pub fn encode_request(request: RequestToEncode<'_>, hmac_key: Option<&[u8]>) -> Result<Vec<u8>> {
|
||||
match request {
|
||||
RequestToEncode::Open { params, no_test } => encode_open(params, no_test, hmac_key),
|
||||
RequestToEncode::Close { token } => encode_close(token, hmac_key),
|
||||
RequestToEncode::Echo {
|
||||
token,
|
||||
sequence,
|
||||
params,
|
||||
payload,
|
||||
} => encode_echo(token, sequence, params, payload, hmac_key),
|
||||
}
|
||||
}
|
||||
|
||||
/// Structurally decodes an inbound request.
|
||||
///
|
||||
/// This is packet structure only — it is **not** admission control. It performs
|
||||
/// no token or session lookup, applies no server policy, and needs no HMAC key,
|
||||
/// precisely so that a receiver can extract a token *before* it knows which
|
||||
/// session and key apply.
|
||||
///
|
||||
/// Classification, for a packet with valid magic and no reserved flag bits set:
|
||||
///
|
||||
/// - `FLAG_REPLY` set — rejected; a reply is never an inbound request.
|
||||
/// - `FLAG_OPEN` set — [`DecodedRequestKind::Open`], with `no_test` from
|
||||
/// `FLAG_CLOSE`. **Open takes precedence over Close and over an echo-shaped
|
||||
/// body**: an `OPEN | CLOSE` packet is a no-test open, and bytes that look
|
||||
/// like a token and sequence number are parameter data.
|
||||
/// - otherwise `FLAG_CLOSE` set — [`DecodedRequestKind::Close`].
|
||||
/// - otherwise — [`DecodedRequestKind::Echo`].
|
||||
///
|
||||
/// `FLAG_HMAC` is orthogonal to all of the above and is reported as
|
||||
/// [`DecodedRequest::hmac_present`], which indicates field presence rather than
|
||||
/// valid authentication.
|
||||
///
|
||||
/// Open parameters are returned as encoded bytes rather than a decoded
|
||||
/// [`Params`], so that an authenticating receiver can verify a packet before
|
||||
/// spending effort on untrusted parameter data. A packet with malformed
|
||||
/// parameters decodes structurally and fails later at [`Params::decode`].
|
||||
///
|
||||
/// Only structural minimum lengths are enforced — 4/12/16 bytes for
|
||||
/// Open/Close/Echo, plus 16 with `FLAG_HMAC`. Trailing bytes beyond a kind's
|
||||
/// fields are tolerated, no negotiated length ceiling is applied, and a zero
|
||||
/// token is accepted; discarding stale, unknown, or oversized requests is the
|
||||
/// receiving layer's policy.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`ProtoError::PacketTooShort`], [`ProtoError::BadMagic`],
|
||||
/// [`ProtoError::ReservedFlags`], or [`ProtoError::UnexpectedFlag`] for
|
||||
/// `FLAG_REPLY`.
|
||||
pub fn decode_request(packet: &[u8]) -> Result<DecodedRequest<'_>> {
|
||||
let envelope = envelope::decode_structural(packet)?;
|
||||
if has(envelope.flags, FLAG_REPLY) {
|
||||
return Err(ProtoError::UnexpectedFlag(FLAG_REPLY));
|
||||
}
|
||||
|
||||
let body = envelope.body_offset;
|
||||
let kind = if has(envelope.flags, FLAG_OPEN) {
|
||||
// Open wins over Close and over an echo-shaped body.
|
||||
require_len(packet, body)?;
|
||||
DecodedRequestKind::Open {
|
||||
no_test: has(envelope.flags, FLAG_CLOSE),
|
||||
params: &packet[body..],
|
||||
}
|
||||
} else if has(envelope.flags, FLAG_CLOSE) {
|
||||
require_len(packet, body + TOKEN_SIZE)?;
|
||||
DecodedRequestKind::Close {
|
||||
token: read_token(packet, body),
|
||||
}
|
||||
} else {
|
||||
let tail_offset = body + TOKEN_SIZE + SEQ_SIZE;
|
||||
require_len(packet, tail_offset)?;
|
||||
DecodedRequestKind::Echo {
|
||||
token: read_token(packet, body),
|
||||
sequence: read_sequence(packet, body + TOKEN_SIZE),
|
||||
tail: &packet[tail_offset..],
|
||||
}
|
||||
};
|
||||
|
||||
Ok(DecodedRequest {
|
||||
hmac_present: envelope.hmac_present,
|
||||
kind,
|
||||
})
|
||||
}
|
||||
|
||||
fn encode_open(params: &Params, no_test: bool, hmac_key: Option<&[u8]>) -> Result<Vec<u8>> {
|
||||
let flags = if no_test {
|
||||
FLAG_OPEN | FLAG_CLOSE
|
||||
} else {
|
||||
FLAG_OPEN
|
||||
};
|
||||
let encoded = params.encode();
|
||||
let mut out = envelope::begin(
|
||||
flags,
|
||||
hmac_key,
|
||||
PacketLayout::open_request(hmac_key.is_some()).header_len() + encoded.len(),
|
||||
)?;
|
||||
out.extend_from_slice(&encoded);
|
||||
envelope::finish(out, hmac_key)
|
||||
}
|
||||
|
||||
fn encode_close(token: u64, hmac_key: Option<&[u8]>) -> Result<Vec<u8>> {
|
||||
let mut out = envelope::begin(
|
||||
FLAG_CLOSE,
|
||||
hmac_key,
|
||||
PacketLayout::close_request(hmac_key.is_some()).header_len(),
|
||||
)?;
|
||||
out.extend_from_slice(&token.to_le_bytes());
|
||||
envelope::finish(out, hmac_key)
|
||||
}
|
||||
|
||||
fn encode_echo(
|
||||
token: u64,
|
||||
sequence: u32,
|
||||
params: &Params,
|
||||
payload: &[u8],
|
||||
hmac_key: Option<&[u8]>,
|
||||
) -> Result<Vec<u8>> {
|
||||
let layout = PacketLayout::echo(hmac_key.is_some(), params);
|
||||
let len = echo_packet_len(hmac_key.is_some(), params)?;
|
||||
let payload_offset = layout.header_len();
|
||||
let available_payload_len = len.saturating_sub(payload_offset);
|
||||
if payload.len() > available_payload_len {
|
||||
return Err(ProtoError::PayloadTooLarge {
|
||||
available: available_payload_len,
|
||||
provided: payload.len(),
|
||||
});
|
||||
}
|
||||
|
||||
let mut out = envelope::begin(0, hmac_key, len)?;
|
||||
out.extend_from_slice(&token.to_le_bytes());
|
||||
out.extend_from_slice(&sequence.to_le_bytes());
|
||||
// Everything from here to the negotiated length is zeroed: the reply-field
|
||||
// positions the sender does not fill in, then the payload region.
|
||||
out.resize(len, 0);
|
||||
out[payload_offset..payload_offset + payload.len()].copy_from_slice(payload);
|
||||
|
||||
envelope::finish(out, hmac_key)
|
||||
}
|
||||
|
||||
fn require_len(packet: &[u8], needed: usize) -> Result<()> {
|
||||
if packet.len() < needed {
|
||||
return Err(ProtoError::PacketTooShort {
|
||||
needed,
|
||||
actual: packet.len(),
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_token(packet: &[u8], offset: usize) -> u64 {
|
||||
u64::from_le_bytes(packet[offset..offset + TOKEN_SIZE].try_into().unwrap())
|
||||
}
|
||||
|
||||
fn read_sequence(packet: &[u8], offset: usize) -> u32 {
|
||||
u32::from_le_bytes(packet[offset..offset + SEQ_SIZE].try_into().unwrap())
|
||||
}
|
||||
Reference in new issue
Block a user