1use crate::transport::legs::TransportLeg;
7
8use async_trait::async_trait;
9use bytes::{Bytes, BytesMut};
10use rand::rngs::OsRng;
11use ring::aead::{self, LessSafeKey, Nonce, UnboundKey};
12use std::io;
13use std::net::SocketAddr;
14use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};
15use tokio::io::{AsyncReadExt, AsyncWriteExt};
16use tokio::net::TcpStream;
17use tokio::sync::Mutex;
18use x25519_dalek::{PublicKey as X25519PublicKey, StaticSecret};
19
20#[repr(u8)]
22#[derive(Debug, Clone, Copy)]
23enum TlsContentType {
24 #[allow(dead_code)]
25 ChangeCipherSpec = 20,
26 #[allow(dead_code)]
27 Alert = 21,
28 Handshake = 22,
29 ApplicationData = 23,
30}
31
32#[derive(Debug, Clone)]
34pub struct FakeTlsConfig {
35 pub sni: String,
37 pub version: u16,
39}
40
41impl Default for FakeTlsConfig {
42 fn default() -> Self {
43 Self {
44 sni: "www.google.com".to_string(),
45 version: 0x0303, }
47 }
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52#[repr(u8)]
53pub enum FakeTlsState {
54 Init = 0,
56 ClientHelloDone = 1,
59 ServerHelloDone = 2,
61 ApplicationData = 3,
63}
64
65pub struct FakeTlsLeg {
74 config: FakeTlsConfig,
76 stream: Mutex<Option<TcpStream>>,
78 remote_addr: Option<SocketAddr>,
80 rtt_ms: AtomicU32,
82 available: AtomicBool,
84 state: parking_lot::RwLock<FakeTlsState>,
86 read_buf: Mutex<BytesMut>,
88 send_key: LessSafeKey,
90 recv_key: LessSafeKey,
92 nonce_prefix: [u8; 4],
94 send_counter: AtomicU64,
96 recv_counter: AtomicU64,
98 #[allow(dead_code)]
100 is_server: bool,
101}
102
103fn derive_outer_keys(
109 sni: &str,
110 version: u16,
111 is_server: bool,
112) -> io::Result<(LessSafeKey, LessSafeKey, [u8; 4])> {
113 let mut seed = Vec::with_capacity(64);
114 seed.extend_from_slice(b"phantom-faketls-outer-v1");
115 seed.extend_from_slice(&version.to_be_bytes());
116 seed.extend_from_slice(sni.as_bytes());
117
118 let key_c2s = crate::crypto::kdf::derive_key_32("phantom-faketls-c2s-v1", &seed);
125 let key_s2c = crate::crypto::kdf::derive_key_32("phantom-faketls-s2c-v1", &seed);
126 let pfx = crate::crypto::kdf::derive_key_32("phantom-faketls-pfx-v1", &seed);
127
128 let mut nonce_prefix = [0u8; 4];
129 nonce_prefix.copy_from_slice(&pfx[..4]);
130
131 let (send_bytes, recv_bytes) = if is_server {
132 (key_s2c, key_c2s)
133 } else {
134 (key_c2s, key_s2c)
135 };
136 let send_unbound = UnboundKey::new(&aead::AES_256_GCM, &send_bytes)
141 .map_err(|e| io::Error::other(format!("AES key init (send): {}", e)))?;
142 let recv_unbound = UnboundKey::new(&aead::AES_256_GCM, &recv_bytes)
143 .map_err(|e| io::Error::other(format!("AES key init (recv): {}", e)))?;
144 Ok((
145 LessSafeKey::new(send_unbound),
146 LessSafeKey::new(recv_unbound),
147 nonce_prefix,
148 ))
149}
150
151impl FakeTlsLeg {
152 pub fn new() -> io::Result<Self> {
156 Self::with_config(FakeTlsConfig::default())
157 }
158
159 pub fn with_config(config: FakeTlsConfig) -> io::Result<Self> {
162 let (send_key, recv_key, nonce_prefix) =
163 derive_outer_keys(&config.sni, config.version, false)?;
164
165 Ok(Self {
166 config,
167 stream: Mutex::new(None),
168 remote_addr: None,
169 rtt_ms: AtomicU32::new(150),
170 available: AtomicBool::new(false),
171 state: parking_lot::RwLock::new(FakeTlsState::Init),
172 read_buf: Mutex::new(BytesMut::with_capacity(16384)),
173 send_key,
174 recv_key,
175 nonce_prefix,
176 send_counter: AtomicU64::new(0),
177 recv_counter: AtomicU64::new(0),
178 is_server: false,
179 })
180 }
181
182 pub async fn connect(addr: SocketAddr, config: FakeTlsConfig) -> io::Result<Self> {
184 let start = std::time::Instant::now();
185 let stream = TcpStream::connect(addr).await?;
186 let rtt = start.elapsed().as_millis() as u32;
187
188 stream.set_nodelay(true)?;
189
190 let (send_key, recv_key, nonce_prefix) =
191 derive_outer_keys(&config.sni, config.version, false)?;
192
193 let leg = Self {
194 config,
195 stream: Mutex::new(Some(stream)),
196 remote_addr: Some(addr),
197 rtt_ms: AtomicU32::new(rtt),
198 available: AtomicBool::new(true),
199 state: parking_lot::RwLock::new(FakeTlsState::Init),
200 read_buf: Mutex::new(BytesMut::with_capacity(16384)),
201 send_key,
202 recv_key,
203 nonce_prefix,
204 send_counter: AtomicU64::new(0),
205 recv_counter: AtomicU64::new(0),
206 is_server: false,
207 };
208
209 leg.do_client_handshake().await?;
210
211 Ok(leg)
212 }
213
214 pub async fn accept(stream: TcpStream, config: FakeTlsConfig) -> io::Result<Self> {
216 stream.set_nodelay(true)?;
217 let remote_addr = stream.peer_addr().ok();
218
219 let (send_key, recv_key, nonce_prefix) =
220 derive_outer_keys(&config.sni, config.version, true)?;
221
222 let leg = Self {
223 config,
224 stream: Mutex::new(Some(stream)),
225 remote_addr,
226 rtt_ms: AtomicU32::new(150),
227 available: AtomicBool::new(true),
228 state: parking_lot::RwLock::new(FakeTlsState::Init),
229 read_buf: Mutex::new(BytesMut::with_capacity(16384)),
230 send_key,
231 recv_key,
232 nonce_prefix,
233 send_counter: AtomicU64::new(0),
234 recv_counter: AtomicU64::new(0),
235 is_server: true,
236 };
237
238 leg.do_server_handshake().await?;
239
240 Ok(leg)
241 }
242
243 #[inline]
245 fn make_nonce(&self, counter: u64) -> Nonce {
246 let mut n = [0u8; 12];
247 n[..4].copy_from_slice(&self.nonce_prefix);
248 n[4..12].copy_from_slice(&counter.to_be_bytes());
249 Nonce::assume_unique_for_key(n)
250 }
251
252 async fn do_client_handshake(&self) -> io::Result<()> {
254 let mut stream_guard = self.stream.lock().await;
255 let stream = stream_guard
256 .as_mut()
257 .ok_or_else(|| io::Error::new(io::ErrorKind::NotConnected, "Not connected"))?;
258
259 let client_hello = self.build_fake_client_hello();
261 stream.write_all(&client_hello).await?;
262 *self.state.write() = FakeTlsState::ClientHelloDone;
263
264 let mut buf = [0u8; 4096];
266 let n = tokio::time::timeout(std::time::Duration::from_secs(5), stream.read(&mut buf))
267 .await
268 .map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "ServerHello timeout"))??;
269
270 if n == 0 {
271 return Err(io::Error::new(
272 io::ErrorKind::ConnectionAborted,
273 "Connection closed by server",
274 ));
275 }
276
277 *self.state.write() = FakeTlsState::ServerHelloDone;
279
280 *self.state.write() = FakeTlsState::ApplicationData;
282 Ok(())
283 }
284
285 async fn do_server_handshake(&self) -> io::Result<()> {
287 let mut stream_guard = self.stream.lock().await;
288 let stream = stream_guard
289 .as_mut()
290 .ok_or_else(|| io::Error::new(io::ErrorKind::NotConnected, "Not connected"))?;
291
292 let mut buf = [0u8; 4096];
294 let n = tokio::time::timeout(std::time::Duration::from_secs(5), stream.read(&mut buf))
295 .await
296 .map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "ClientHello timeout"))??;
297
298 if n == 0 {
299 return Err(io::Error::new(
300 io::ErrorKind::ConnectionAborted,
301 "Connection closed by client",
302 ));
303 }
304
305 *self.state.write() = FakeTlsState::ClientHelloDone;
306
307 let server_hello = self.build_fake_server_hello();
309 stream.write_all(&server_hello).await?;
310 *self.state.write() = FakeTlsState::ServerHelloDone;
311
312 *self.state.write() = FakeTlsState::ApplicationData;
314 Ok(())
315 }
316
317 fn build_fake_server_hello(&self) -> Vec<u8> {
319 let mut record = Vec::with_capacity(128);
320 record.push(TlsContentType::Handshake as u8);
321 record.extend_from_slice(&self.config.version.to_be_bytes()); let mut hs = Vec::new();
324 hs.push(0x02); hs.extend_from_slice(&[0, 0, 0]);
327 hs.extend_from_slice(&0x0303u16.to_be_bytes());
329
330 let mut random = [0u8; 32];
332 if getrandom::getrandom(&mut random).is_err() {
333 use rand::RngCore;
334 rand::thread_rng().fill_bytes(&mut random);
335 }
336 hs.extend_from_slice(&random);
337
338 hs.push(0);
340
341 hs.extend_from_slice(&0x1302u16.to_be_bytes());
343 hs.push(0);
345
346 hs.extend_from_slice(&[0, 8]); hs.extend_from_slice(&51u16.to_be_bytes());
350 hs.extend_from_slice(&4u16.to_be_bytes()); hs.extend_from_slice(&0x001du16.to_be_bytes()); hs.extend_from_slice(&0u16.to_be_bytes()); let hs_len = (hs.len() - 4) as u32;
355 hs[1] = ((hs_len >> 16) & 0xFF) as u8;
356 hs[2] = ((hs_len >> 8) & 0xFF) as u8;
357 hs[3] = (hs_len & 0xFF) as u8;
358
359 record.extend_from_slice(&(hs.len() as u16).to_be_bytes());
360 record.extend_from_slice(&hs);
361
362 record
363 }
364
365 fn build_fake_client_hello(&self) -> Vec<u8> {
367 let mut record = Vec::with_capacity(512);
368 let mut rng = OsRng;
369
370 record.push(TlsContentType::Handshake as u8);
372 record.extend_from_slice(&self.config.version.to_be_bytes());
373
374 let length_pos = record.len();
376 record.extend_from_slice(&[0u8; 2]);
377
378 record.push(0x01); let hs_length_pos = record.len();
382 record.extend_from_slice(&[0u8; 3]);
383
384 record.extend_from_slice(&0x0303u16.to_be_bytes());
386
387 let mut random = [0u8; 32];
389 if getrandom::getrandom(&mut random).is_err() {
390 rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut random);
391 }
392 record.extend_from_slice(&random);
393
394 record.push(32);
396 let mut session_id = [0u8; 32];
397 if getrandom::getrandom(&mut session_id).is_err() {
398 rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut session_id);
399 }
400 record.extend_from_slice(&session_id);
401
402 let grease = (rng.gen::<u8>() & 0xf0) as u16 + 0x0a0a;
405
406 let mut suites = vec![
407 0x1301, 0x1302, 0x1303, 0xc02b, 0xc02c, 0xc02f, 0xc030, 0xcca9, 0xcca8, ];
417
418 use rand::seq::SliceRandom;
420 use rand::Rng; suites.shuffle(&mut rng);
423 let num_suites = rng.gen_range(3..=suites.len());
424 suites.truncate(num_suites);
425
426 suites.insert(rng.gen_range(0..=suites.len()), grease);
428
429 record.extend_from_slice(&((suites.len() * 2) as u16).to_be_bytes());
430 for s in suites {
431 record.extend_from_slice(&s.to_be_bytes());
432 }
433
434 record.push(1);
436 record.push(0); let extensions_start = record.len();
440 record.extend_from_slice(&[0u8; 2]); let supported_groups = self.make_supported_groups_body(&mut rng);
446 let key_share = self.make_key_share_body(&mut rng);
447 let supported_versions = self.make_supported_versions_body(&mut rng);
448 let mut exts: Vec<(u16, Vec<u8>)> = vec![
449 (0u16, self.make_sni_extension_body()),
451 (10u16, supported_groups),
453 (11u16, vec![1, 0]),
455 (13u16, self.make_signature_algorithms_body()),
457 (51u16, key_share),
459 (43u16, supported_versions),
461 ];
462
463 if !exts.is_empty() {
465 let sni = exts.remove(0); exts.shuffle(&mut rng);
467 exts.insert(0, sni);
468 }
469
470 exts.push((21u16, vec![0u8; rng.gen_range(50..200)]));
473
474 for (etype, body) in exts {
475 record.extend_from_slice(&etype.to_be_bytes());
476 record.extend_from_slice(&(body.len() as u16).to_be_bytes());
477 record.extend_from_slice(&body);
478 }
479
480 let extensions_len = (record.len() - extensions_start - 2) as u16;
482 record[extensions_start..extensions_start + 2]
483 .copy_from_slice(&extensions_len.to_be_bytes());
484
485 let hs_len = (record.len() - hs_length_pos - 3) as u32;
487 record[hs_length_pos] = ((hs_len >> 16) & 0xFF) as u8;
488 record[hs_length_pos + 1] = ((hs_len >> 8) & 0xFF) as u8;
489 record[hs_length_pos + 2] = (hs_len & 0xFF) as u8;
490
491 let record_len = (record.len() - 5) as u16;
493 record[length_pos..length_pos + 2].copy_from_slice(&record_len.to_be_bytes());
494
495 record
496 }
497
498 fn make_sni_extension_body(&self) -> Vec<u8> {
499 let mut body = Vec::new();
500 let sni_bytes = self.config.sni.as_bytes();
501 body.extend_from_slice(&((3 + sni_bytes.len()) as u16).to_be_bytes()); body.push(0); body.extend_from_slice(&(sni_bytes.len() as u16).to_be_bytes());
504 body.extend_from_slice(sni_bytes);
505 body
506 }
507
508 fn make_supported_groups_body(&self, rng: &mut impl rand::Rng) -> Vec<u8> {
509 let mut body = Vec::new();
510 let grease = (rng.gen::<u8>() & 0xf0) as u16 + 0x0a0a;
511 let groups = [
512 grease, 0x001d, 0x0017, ];
515 let len = (groups.len() * 2) as u16;
516 body.extend_from_slice(&len.to_be_bytes());
517 for g in groups {
518 body.extend_from_slice(&g.to_be_bytes());
519 }
520 body
521 }
522
523 fn make_signature_algorithms_body(&self) -> Vec<u8> {
524 let mut body = Vec::new();
525 let algos: [u16; 8] = [
526 0x0403, 0x0804, 0x0401, 0x0503, 0x0805, 0x0501, 0x0806, 0x0601, ];
535 let len = (algos.len() * 2) as u16;
536 body.extend_from_slice(&len.to_be_bytes());
537 for a in algos {
538 body.extend_from_slice(&a.to_be_bytes());
539 }
540 body
541 }
542
543 fn make_key_share_body(&self, rng: &mut (impl rand::Rng + rand::CryptoRng)) -> Vec<u8> {
544 let mut body = Vec::new();
545 let grease = (rng.gen::<u8>() & 0xf0) as u16 + 0x0a0a;
546
547 let secret = StaticSecret::random_from_rng(rng);
549 let public = X25519PublicKey::from(&secret);
550 let x25519_share = public.as_bytes();
551
552 let client_shares_len = 4 + (2 + 32); body.extend_from_slice(&(client_shares_len as u16).to_be_bytes());
554
555 body.extend_from_slice(&grease.to_be_bytes());
557 body.extend_from_slice(&1u16.to_be_bytes()); body.push(0);
559
560 body.extend_from_slice(&0x001du16.to_be_bytes());
562 body.extend_from_slice(&(x25519_share.len() as u16).to_be_bytes());
563 body.extend_from_slice(x25519_share);
564
565 body
566 }
567
568 fn make_supported_versions_body(&self, rng: &mut impl rand::Rng) -> Vec<u8> {
569 let mut body = Vec::new();
570 let grease = (rng.gen::<u8>() & 0xf0) as u16 + 0x0a0a;
571 let versions = [
572 grease, 0x0304, 0x0303, ];
575 let len = (versions.len() * 2) as u16;
576 body.push((len & 0xFF) as u8); for v in versions {
578 body.extend_from_slice(&v.to_be_bytes());
579 }
580 body
581 }
582
583 fn wrap_as_tls_record(&self, data: &[u8]) -> io::Result<Vec<u8>> {
589 let tag_len = self.send_key.algorithm().tag_len();
597 let framed_len = data.len().saturating_add(1).saturating_add(tag_len);
598 if framed_len > u16::MAX as usize {
599 return Err(io::Error::new(
600 io::ErrorKind::InvalidData,
601 format!(
602 "faketls record body would be {framed_len} bytes, exceeding the {}-byte u16 length field",
603 u16::MAX
604 ),
605 ));
606 }
607
608 let mut inner_plaintext = Vec::with_capacity(data.len() + 1);
610 inner_plaintext.extend_from_slice(data);
611 inner_plaintext.push(TlsContentType::ApplicationData as u8); let mut in_out = inner_plaintext;
615 let counter = self.send_counter.fetch_add(1, Ordering::Relaxed);
616 let nonce = self.make_nonce(counter);
617
618 let aad = aead::Aad::empty();
619 self.send_key
622 .seal_in_place_append_tag(nonce, aad, &mut in_out)
623 .map_err(|_| io::Error::other("faketls AEAD seal failed"))?;
624
625 let mut record = Vec::with_capacity(5 + in_out.len());
626
627 record.push(TlsContentType::ApplicationData as u8);
630 record.extend_from_slice(&self.config.version.to_be_bytes());
631 record.extend_from_slice(&(in_out.len() as u16).to_be_bytes());
632 record.extend_from_slice(&in_out);
633
634 Ok(record)
635 }
636
637 fn unwrap_tls_record<'a>(&self, record: &'a mut [u8]) -> io::Result<&'a [u8]> {
644 if record.len() < 5 {
645 return Err(io::Error::new(
646 io::ErrorKind::InvalidData,
647 "Record too short",
648 ));
649 }
650
651 let payload = &mut record[5..];
652
653 let counter = self.recv_counter.fetch_add(1, Ordering::Relaxed);
654 let nonce = self.make_nonce(counter);
655 let aad = aead::Aad::empty();
656
657 let decrypted = self
658 .recv_key
659 .open_in_place(nonce, aad, payload)
660 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "AEAD decryption failed"))?;
661
662 if decrypted.is_empty() {
664 return Err(io::Error::new(
665 io::ErrorKind::InvalidData,
666 "Empty inner plaintext",
667 ));
668 }
669
670 let inner_len = decrypted.len() - 1;
671 Ok(&decrypted[..inner_len])
672 }
673}
674
675impl Default for FakeTlsLeg {
676 fn default() -> Self {
680 #[allow(clippy::expect_used)]
681 Self::new().expect("FakeTlsLeg::default: AES key init invariant violated")
682 }
683}
684
685#[async_trait]
686impl TransportLeg for FakeTlsLeg {
687 async fn send(&self, data: Bytes) -> io::Result<()> {
688 if !self.is_available() {
689 return Err(io::Error::new(
690 io::ErrorKind::NotConnected,
691 "FakeTLS not connected",
692 ));
693 }
694
695 if *self.state.read() != FakeTlsState::ApplicationData {
696 return Err(io::Error::other("Handshake not finished"));
697 }
698
699 let record = self.wrap_as_tls_record(&data)?;
700
701 let mut stream_guard = self.stream.lock().await;
702 let stream = stream_guard
703 .as_mut()
704 .ok_or_else(|| io::Error::new(io::ErrorKind::NotConnected, "Not connected"))?;
705
706 stream.write_all(&record).await?;
707 stream.flush().await
708 }
709
710 async fn recv(&self) -> io::Result<Bytes> {
711 if !self.is_available() {
712 return Err(io::Error::new(
713 io::ErrorKind::NotConnected,
714 "FakeTLS not connected",
715 ));
716 }
717
718 if *self.state.read() != FakeTlsState::ApplicationData {
719 return Err(io::Error::other("Handshake not finished"));
720 }
721
722 let mut stream_guard = self.stream.lock().await;
723 let stream = stream_guard
724 .as_mut()
725 .ok_or_else(|| io::Error::new(io::ErrorKind::NotConnected, "Not connected"))?;
726
727 let mut read_buf = self.read_buf.lock().await;
728
729 while read_buf.len() < 5 {
731 let mut temp = [0u8; 4096];
732 let n = stream.read(&mut temp).await?;
733 if n == 0 {
734 return Err(io::Error::new(
735 io::ErrorKind::UnexpectedEof,
736 "Connection closed",
737 ));
738 }
739 read_buf.extend_from_slice(&temp[..n]);
740 }
741
742 let record_len = u16::from_be_bytes([read_buf[3], read_buf[4]]) as usize;
744
745 while read_buf.len() < 5 + record_len {
747 let mut temp = [0u8; 4096];
748 let n = stream.read(&mut temp).await?;
749 if n == 0 {
750 return Err(io::Error::new(
751 io::ErrorKind::UnexpectedEof,
752 "Connection closed",
753 ));
754 }
755 read_buf.extend_from_slice(&temp[..n]);
756 }
757
758 let mut record = read_buf.split_to(5 + record_len);
760
761 let payload_len = self.unwrap_tls_record(&mut record)?.len();
765
766 let mut out = BytesMut::with_capacity(payload_len);
768 out.extend_from_slice(&record[5..5 + payload_len]);
769
770 Ok(out.freeze())
771 }
772
773 fn is_available(&self) -> bool {
774 self.available.load(Ordering::Relaxed)
775 && *self.state.read() == FakeTlsState::ApplicationData
776 }
777
778 fn rtt_ms(&self) -> u32 {
779 self.rtt_ms.load(Ordering::Relaxed)
780 }
781
782 fn loss_percent(&self) -> u8 {
783 0 }
785
786 fn remote_addr(&self) -> Option<SocketAddr> {
787 self.remote_addr
788 }
789
790 async fn close(&self) -> io::Result<()> {
791 self.available.store(false, Ordering::Relaxed);
792
793 if let Some(stream) = self.stream.lock().await.take() {
794 drop(stream);
795 }
796
797 log::info!("FakeTLS closed");
798 Ok(())
799 }
800}
801
802impl std::fmt::Debug for FakeTlsLeg {
803 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
804 f.debug_struct("FakeTlsLeg")
805 .field("sni", &self.config.sni)
806 .field("remote", &self.remote_addr)
807 .field("rtt_ms", &self.rtt_ms.load(Ordering::Relaxed))
808 .field("available", &self.is_available())
809 .finish()
810 }
811}
812
813#[cfg(test)]
814mod tests {
815 use super::*;
816
817 #[test]
818 fn test_faketls_leg_creation() {
819 let leg = FakeTlsLeg::new().expect("FakeTlsLeg::new");
820 assert!(!leg.is_available());
821 assert_eq!(leg.config.sni, "www.google.com");
822 }
823
824 #[test]
825 fn test_fake_client_hello() {
826 let leg = FakeTlsLeg::new().expect("FakeTlsLeg::new");
827 let hello = leg.build_fake_client_hello();
828
829 assert_eq!(hello[0], TlsContentType::Handshake as u8);
831 assert!(hello.len() > 100); }
833
834 #[test]
835 fn test_wrap_tls_record() {
836 let leg = FakeTlsLeg::new().expect("FakeTlsLeg::new");
837 let data = b"test payload";
838 let record = leg.wrap_as_tls_record(data).expect("wrap_as_tls_record");
839
840 assert_eq!(record[0], TlsContentType::ApplicationData as u8);
841 assert_eq!(record.len(), 5 + data.len() + 1 + 16);
843 }
844
845 #[test]
849 fn oversized_record_payload_is_rejected_not_truncated() {
850 let leg = FakeTlsLeg::new().expect("FakeTlsLeg::new");
851 let ok = leg
854 .wrap_as_tls_record(&vec![0u8; 65_518])
855 .expect("boundary payload fits");
856 assert_eq!(ok.len(), 5 + u16::MAX as usize);
857 let err = leg
858 .wrap_as_tls_record(&vec![0u8; 65_519])
859 .expect_err("oversized payload must be rejected, not truncated");
860 assert_eq!(err.kind(), std::io::ErrorKind::InvalidData);
861 }
862
863 #[test]
864 fn test_ja3_randomization() {
865 let leg = FakeTlsLeg::new().expect("FakeTlsLeg::new");
866 let hello1_ = leg.build_fake_client_hello();
867 let hello2 = leg.build_fake_client_hello();
868
869 assert_ne!(hello1_, hello2);
871
872 assert_eq!(hello1_[9], 0x03);
875 assert_eq!(hello1_[10], 0x03);
876 }
877
878 #[test]
881 fn test_wrap_unwrap_roundtrip_across_peers() {
882 let client =
884 FakeTlsLeg::with_config(FakeTlsConfig::default()).expect("FakeTlsLeg::with_config");
885
886 let cfg = FakeTlsConfig::default();
889 let (send_key, recv_key, nonce_prefix) =
890 derive_outer_keys(&cfg.sni, cfg.version, true).expect("derive_outer_keys");
891 let server = FakeTlsLeg {
892 config: cfg,
893 stream: Mutex::new(None),
894 remote_addr: None,
895 rtt_ms: AtomicU32::new(0),
896 available: AtomicBool::new(false),
897 state: parking_lot::RwLock::new(FakeTlsState::ApplicationData),
898 read_buf: Mutex::new(BytesMut::new()),
899 send_key,
900 recv_key,
901 nonce_prefix,
902 send_counter: AtomicU64::new(0),
903 recv_counter: AtomicU64::new(0),
904 is_server: true,
905 };
906
907 let plaintexts: &[&[u8]] = &[
908 b"first record",
909 b"second record",
910 b"third record (a bit longer to vary length)",
911 ];
912 for pt in plaintexts {
913 let mut record = client.wrap_as_tls_record(pt).expect("wrap_as_tls_record");
914 let recovered_len = server.unwrap_tls_record(&mut record).unwrap().len();
915 assert_eq!(&record[5..5 + recovered_len], *pt);
916 }
917 }
918
919 #[test]
922 fn test_nonce_advances_per_record() {
923 let leg =
924 FakeTlsLeg::with_config(FakeTlsConfig::default()).expect("FakeTlsLeg::with_config");
925 let r1 = leg.wrap_as_tls_record(b"identical").expect("wrap r1");
926 let r2 = leg.wrap_as_tls_record(b"identical").expect("wrap r2");
927 assert_ne!(
928 r1, r2,
929 "counter must advance — identical plaintext must not produce identical ciphertext"
930 );
931 }
932
933 #[test]
934 fn test_client_hello_structure() {
935 let leg = FakeTlsLeg::new().expect("FakeTlsLeg::new");
936 let hello = leg.build_fake_client_hello();
937
938 assert_eq!(
940 hello[0],
941 TlsContentType::Handshake as u8,
942 "First byte should be Handshake (22)"
943 );
944
945 let record_version = u16::from_be_bytes([hello[1], hello[2]]);
947 assert_eq!(
948 record_version, 0x0303,
949 "Record version should be TLS 1.2 (0x0303)"
950 );
951
952 let record_len = u16::from_be_bytes([hello[3], hello[4]]) as usize;
954 assert_eq!(
955 hello.len(),
956 5 + record_len,
957 "Record length should match payload"
958 );
959
960 assert_eq!(hello[5], 0x01, "Handshake type should be ClientHello");
962
963 assert_eq!(hello[9], 0x03, "Legacy version major");
965 assert_eq!(hello[10], 0x03, "Legacy version minor");
966
967 let random = &hello[11..43];
969 assert!(
970 !random.iter().all(|&b| b == 0),
971 "Random should not be all zeros"
972 );
973
974 assert_eq!(hello[43], 32, "Session ID length should be 32");
976 }
977}