From 7aed2098a265e5853ba9372931b84cecff3e09d1 Mon Sep 17 00:00:00 2001 From: 0xghost42 Date: Thu, 21 May 2026 13:39:52 +0530 Subject: [PATCH] refactor(dpi/quic): single-allocation connection_id_to_hex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `connection_id_to_hex` was rendering a QUIC connection ID through the worst-case allocation pattern: `format!("{:02x}", b)` allocated a 2-byte `String` for every input byte, the iterator collected them into a `Vec` (one more allocation), and `join(":")` then walked the vector to build the final `String`. For an 8-byte short-form DCID that is 8 + 1 + 1 = 10 heap allocations per render. Replace with a single `String::with_capacity(id.len() * 3 - 1)` (exact final size: 2 hex chars per byte + N-1 colon separators) and a per-byte `write!`. Writing into a pre-sized `String` does not reallocate, so the helper now performs exactly one heap allocation per call regardless of input length, except for the empty-slice case which returns an empty `String` (no allocation). The lowercase, colon-separated output is unchanged. Adds four regression tests covering: - An 8-byte representative DCID — locks the lowercase + colon-separated contract. - A mix of single-digit bytes (0x00..=0x0f) — locks the zero-padding contract that `{:02x}` provides. - A single-byte input — locks that no trailing separator is emitted. - The empty-slice base case. Closes #305 --- src/network/dpi/quic.rs | 49 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/src/network/dpi/quic.rs b/src/network/dpi/quic.rs index cfdd858a..1eef34ab 100644 --- a/src/network/dpi/quic.rs +++ b/src/network/dpi/quic.rs @@ -6,6 +6,7 @@ use aes::cipher::{BlockCipherEncrypt, KeyInit}; use log::{debug, warn}; use ring::aead::{Aad, LessSafeKey, Nonce, UnboundKey}; use ring::{aead, hkdf}; +use std::fmt::Write as _; // QUIC v1 Initial salt (from RFC 9001) const INITIAL_SALT_V1: &[u8] = &[ @@ -1877,12 +1878,23 @@ fn aes_ecb_encrypt(key: &[u8], block: &[u8]) -> Option<[u8; 16]> { Some(output.into()) } -/// Convert connection ID to hex string +/// Convert connection ID to a lowercase colon-separated hex string. fn connection_id_to_hex(id: &[u8]) -> String { - id.iter() - .map(|b| format!("{:02x}", b)) - .collect::>() - .join(":") + if id.is_empty() { + return String::new(); + } + // 2 hex chars per byte + a separator between every pair of bytes. + let mut out = String::with_capacity(id.len() * 3 - 1); + let mut first = true; + for b in id { + if !first { + out.push(':'); + } + // write! to a String never fails. + let _ = write!(out, "{b:02x}"); + first = false; + } + out } /// Check if a packet is likely a QUIC packet @@ -2573,4 +2585,31 @@ mod tests { "unparseable token length should cause us to treat the rest of the datagram as consumed" ); } + + #[test] + fn test_connection_id_to_hex_8_byte_short_dcid() { + // Locks the lowercase + colon-separated contract on a representative + // 8-byte short-form DCID. + let dcid: [u8; 8] = [0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0]; + assert_eq!(connection_id_to_hex(&dcid), "12:34:56:78:9a:bc:de:f0"); + } + + #[test] + fn test_connection_id_to_hex_pads_single_digit_bytes() { + // Locks the `{:02x}` zero-padding contract. + assert_eq!( + connection_id_to_hex(&[0x00, 0x0a, 0x0f, 0x10]), + "00:0a:0f:10" + ); + } + + #[test] + fn test_connection_id_to_hex_single_byte_no_separator() { + assert_eq!(connection_id_to_hex(&[0xab]), "ab"); + } + + #[test] + fn test_connection_id_to_hex_empty_returns_empty() { + assert_eq!(connection_id_to_hex(&[]), ""); + } }