phantom-protocol 0.1.1

Post-quantum-secure L4/L6 universal transport framework — hybrid X25519+ML-KEM-768 / Ed25519+ML-DSA-65, multi-path, UniFFI bindings
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
//! FakeTLS Transport Leg
//!
//! Obfuscated transport that mimics TLS 1.3 traffic for DPI bypass.
//! Wraps actual payload in fake TLS records.

use crate::transport::legs::TransportLeg;

use async_trait::async_trait;
use bytes::{Bytes, BytesMut};
use rand::rngs::OsRng;
use ring::aead::{self, LessSafeKey, Nonce, UnboundKey};
use std::io;
use std::net::SocketAddr;
use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio::sync::Mutex;
use x25519_dalek::{PublicKey as X25519PublicKey, StaticSecret};

/// TLS record types
#[repr(u8)]
#[derive(Debug, Clone, Copy)]
enum TlsContentType {
    #[allow(dead_code)]
    ChangeCipherSpec = 20,
    #[allow(dead_code)]
    Alert = 21,
    Handshake = 22,
    ApplicationData = 23,
}

/// FakeTLS configuration
#[derive(Debug, Clone)]
pub struct FakeTlsConfig {
    /// Server Name Indication (SNI) to use in ClientHello
    pub sni: String,
    /// TLS version to advertise
    pub version: u16,
}

impl Default for FakeTlsConfig {
    fn default() -> Self {
        Self {
            sni: "www.google.com".to_string(),
            version: 0x0303, // TLS 1.2 (for compatibility)
        }
    }
}

/// State machine for FakeTLS Handshake
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum FakeTlsState {
    /// Initial state (0)
    Init = 0,
    /// Client: Sent ClientHello, waiting for ServerHello (1)
    /// Server: Received ClientHello, waiting to send ServerHello (1)
    ClientHelloDone = 1,
    /// Handshake completed, ready for Application Data (2)
    ServerHelloDone = 2,
    /// Ready for Phantom packets wrapped in ApplicationData (3)
    ApplicationData = 3,
}

/// FakeTLS transport leg
///
/// AEAD note: This is an *outer* DPI-obfuscation layer. Real confidentiality
/// and authentication come from the inner Phantom session (see
/// `crate::transport::session::Session`). The outer AEAD key is derived
/// deterministically from a public seed (SNI + version) so both peers reach
/// the same key without a Diffie-Hellman exchange; nonce uniqueness per record
/// is provided by an atomic counter (mirrors `AesSession::make_nonce`).
pub struct FakeTlsLeg {
    /// Configuration
    config: FakeTlsConfig,
    /// Underlying TCP stream
    stream: Mutex<Option<TcpStream>>,
    /// Remote address
    remote_addr: Option<SocketAddr>,
    /// Current RTT estimate (ms)
    rtt_ms: AtomicU32,
    /// Whether leg is available
    available: AtomicBool,
    /// Current connection state
    state: parking_lot::RwLock<FakeTlsState>,
    /// Read buffer
    read_buf: Mutex<BytesMut>,
    /// AES-256-GCM key used for sealing outbound records.
    send_key: LessSafeKey,
    /// AES-256-GCM key used for opening inbound records.
    recv_key: LessSafeKey,
    /// 4-byte nonce prefix; remaining 8 bytes are a per-direction counter.
    nonce_prefix: [u8; 4],
    /// Monotonically increasing counter for outbound records.
    send_counter: AtomicU64,
    /// Monotonically increasing counter for inbound records (TCP is in-order).
    recv_counter: AtomicU64,
    /// Is this leg acting as a server?
    #[allow(dead_code)]
    is_server: bool,
}

/// Derive the outer AEAD key material for both peers from a publicly-known seed.
///
/// Because the seed is public this layer provides no real confidentiality — it
/// exists solely to produce pseudo-random ciphertext that defeats DPI
/// fingerprinting. The inner Phantom session provides actual auth/conf.
fn derive_outer_keys(
    sni: &str,
    version: u16,
    is_server: bool,
) -> io::Result<(LessSafeKey, LessSafeKey, [u8; 4])> {
    let mut seed = Vec::with_capacity(64);
    seed.extend_from_slice(b"phantom-faketls-outer-v1");
    seed.extend_from_slice(&version.to_be_bytes());
    seed.extend_from_slice(sni.as_bytes());

    // `crypto::kdf::derive_key_32` dispatches to `blake3::derive_key`
    // by default and HKDF-SHA256 under `--features fips`. FakeTLS is
    // anti-DPI obfuscation only (the inner Phantom session provides the
    // real confidentiality + auth), but consistency with the rest of
    // the codebase under fips removes a non-approved primitive from the
    // FIPS surface.
    let key_c2s = crate::crypto::kdf::derive_key_32("phantom-faketls-c2s-v1", &seed);
    let key_s2c = crate::crypto::kdf::derive_key_32("phantom-faketls-s2c-v1", &seed);
    let pfx = crate::crypto::kdf::derive_key_32("phantom-faketls-pfx-v1", &seed);

    let mut nonce_prefix = [0u8; 4];
    nonce_prefix.copy_from_slice(&pfx[..4]);

    let (send_bytes, recv_bytes) = if is_server {
        (key_s2c, key_c2s)
    } else {
        (key_c2s, key_s2c)
    };
    // `UnboundKey::new(&AES_256_GCM, key)` only fails if the slice length
    // is wrong — `derive_key_32` always emits exactly 32 bytes and
    // `AES_256_GCM.key_len()` is 32, so this is structurally infallible.
    // Surface it as a typed error anyway so the function stays total.
    let send_unbound = UnboundKey::new(&aead::AES_256_GCM, &send_bytes)
        .map_err(|e| io::Error::other(format!("AES key init (send): {}", e)))?;
    let recv_unbound = UnboundKey::new(&aead::AES_256_GCM, &recv_bytes)
        .map_err(|e| io::Error::other(format!("AES key init (recv): {}", e)))?;
    Ok((
        LessSafeKey::new(send_unbound),
        LessSafeKey::new(recv_unbound),
        nonce_prefix,
    ))
}

impl FakeTlsLeg {
    /// Create a new FakeTLS leg with default config. Returns an error only
    /// in the structurally-impossible case that AES-256-GCM key initialization
    /// fails (preserved as a `Result` to keep the API panic-free).
    pub fn new() -> io::Result<Self> {
        Self::with_config(FakeTlsConfig::default())
    }

    /// Create with custom config (client role; not yet connected). See
    /// [`FakeTlsLeg::new`] for the error contract.
    pub fn with_config(config: FakeTlsConfig) -> io::Result<Self> {
        let (send_key, recv_key, nonce_prefix) =
            derive_outer_keys(&config.sni, config.version, false)?;

        Ok(Self {
            config,
            stream: Mutex::new(None),
            remote_addr: None,
            rtt_ms: AtomicU32::new(150),
            available: AtomicBool::new(false),
            state: parking_lot::RwLock::new(FakeTlsState::Init),
            read_buf: Mutex::new(BytesMut::with_capacity(16384)),
            send_key,
            recv_key,
            nonce_prefix,
            send_counter: AtomicU64::new(0),
            recv_counter: AtomicU64::new(0),
            is_server: false,
        })
    }

    /// Connect as Client and perform fake TLS handshake
    pub async fn connect(addr: SocketAddr, config: FakeTlsConfig) -> io::Result<Self> {
        let start = std::time::Instant::now();
        let stream = TcpStream::connect(addr).await?;
        let rtt = start.elapsed().as_millis() as u32;

        stream.set_nodelay(true)?;

        let (send_key, recv_key, nonce_prefix) =
            derive_outer_keys(&config.sni, config.version, false)?;

        let leg = Self {
            config,
            stream: Mutex::new(Some(stream)),
            remote_addr: Some(addr),
            rtt_ms: AtomicU32::new(rtt),
            available: AtomicBool::new(true),
            state: parking_lot::RwLock::new(FakeTlsState::Init),
            read_buf: Mutex::new(BytesMut::with_capacity(16384)),
            send_key,
            recv_key,
            nonce_prefix,
            send_counter: AtomicU64::new(0),
            recv_counter: AtomicU64::new(0),
            is_server: false,
        };

        leg.do_client_handshake().await?;

        Ok(leg)
    }

    /// Accept connection as Server and perform fake TLS handshake
    pub async fn accept(stream: TcpStream, config: FakeTlsConfig) -> io::Result<Self> {
        stream.set_nodelay(true)?;
        let remote_addr = stream.peer_addr().ok();

        let (send_key, recv_key, nonce_prefix) =
            derive_outer_keys(&config.sni, config.version, true)?;

        let leg = Self {
            config,
            stream: Mutex::new(Some(stream)),
            remote_addr,
            rtt_ms: AtomicU32::new(150),
            available: AtomicBool::new(true),
            state: parking_lot::RwLock::new(FakeTlsState::Init),
            read_buf: Mutex::new(BytesMut::with_capacity(16384)),
            send_key,
            recv_key,
            nonce_prefix,
            send_counter: AtomicU64::new(0),
            recv_counter: AtomicU64::new(0),
            is_server: true,
        };

        leg.do_server_handshake().await?;

        Ok(leg)
    }

    /// Construct nonce: 4-byte prefix || 8-byte big-endian counter.
    #[inline]
    fn make_nonce(&self, counter: u64) -> Nonce {
        let mut n = [0u8; 12];
        n[..4].copy_from_slice(&self.nonce_prefix);
        n[4..12].copy_from_slice(&counter.to_be_bytes());
        Nonce::assume_unique_for_key(n)
    }

    /// Perform client-side fake handshake
    async fn do_client_handshake(&self) -> io::Result<()> {
        let mut stream_guard = self.stream.lock().await;
        let stream = stream_guard
            .as_mut()
            .ok_or_else(|| io::Error::new(io::ErrorKind::NotConnected, "Not connected"))?;

        // State 0 -> 1: Send ClientHello
        let client_hello = self.build_fake_client_hello();
        stream.write_all(&client_hello).await?;
        *self.state.write() = FakeTlsState::ClientHelloDone;

        // State 1 -> 2: Read ServerHello
        let mut buf = [0u8; 4096];
        let n = tokio::time::timeout(std::time::Duration::from_secs(5), stream.read(&mut buf))
            .await
            .map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "ServerHello timeout"))??;

        if n == 0 {
            return Err(io::Error::new(
                io::ErrorKind::ConnectionAborted,
                "Connection closed by server",
            ));
        }

        // We received dummy ServerHello
        *self.state.write() = FakeTlsState::ServerHelloDone;

        // State 2 -> 3: Application Data (Ready)
        *self.state.write() = FakeTlsState::ApplicationData;
        Ok(())
    }

    /// Perform server-side fake handshake
    async fn do_server_handshake(&self) -> io::Result<()> {
        let mut stream_guard = self.stream.lock().await;
        let stream = stream_guard
            .as_mut()
            .ok_or_else(|| io::Error::new(io::ErrorKind::NotConnected, "Not connected"))?;

        // State 0 -> 1: Read ClientHello
        let mut buf = [0u8; 4096];
        let n = tokio::time::timeout(std::time::Duration::from_secs(5), stream.read(&mut buf))
            .await
            .map_err(|_| io::Error::new(io::ErrorKind::TimedOut, "ClientHello timeout"))??;

        if n == 0 {
            return Err(io::Error::new(
                io::ErrorKind::ConnectionAborted,
                "Connection closed by client",
            ));
        }

        *self.state.write() = FakeTlsState::ClientHelloDone;

        // State 1 -> 2: Send ServerHello
        let server_hello = self.build_fake_server_hello();
        stream.write_all(&server_hello).await?;
        *self.state.write() = FakeTlsState::ServerHelloDone;

        // State 2 -> 3: Application Data (Ready)
        *self.state.write() = FakeTlsState::ApplicationData;
        Ok(())
    }

    /// Build a fake TLS ServerHello
    fn build_fake_server_hello(&self) -> Vec<u8> {
        let mut record = Vec::with_capacity(128);
        record.push(TlsContentType::Handshake as u8);
        record.extend_from_slice(&self.config.version.to_be_bytes()); // e.g. TLS 1.2

        let mut hs = Vec::new();
        hs.push(0x02); // ServerHello
                       // length placeholder
        hs.extend_from_slice(&[0, 0, 0]);
        // Server version
        hs.extend_from_slice(&0x0303u16.to_be_bytes());

        // Server Random
        let mut random = [0u8; 32];
        if getrandom::getrandom(&mut random).is_err() {
            use rand::RngCore;
            rand::thread_rng().fill_bytes(&mut random);
        }
        hs.extend_from_slice(&random);

        // Session ID length (0)
        hs.push(0);

        // Cipher suite TLS_AES_256_GCM_SHA384
        hs.extend_from_slice(&0x1302u16.to_be_bytes());
        // Compression method null
        hs.push(0);

        // Extensions
        hs.extend_from_slice(&[0, 8]); // Extensions len: 8
                                       // Key share (Server)
        hs.extend_from_slice(&51u16.to_be_bytes());
        hs.extend_from_slice(&4u16.to_be_bytes()); // len 4
        hs.extend_from_slice(&0x001du16.to_be_bytes()); // X25519
        hs.extend_from_slice(&0u16.to_be_bytes()); // dummy payload

        let hs_len = (hs.len() - 4) as u32;
        hs[1] = ((hs_len >> 16) & 0xFF) as u8;
        hs[2] = ((hs_len >> 8) & 0xFF) as u8;
        hs[3] = (hs_len & 0xFF) as u8;

        record.extend_from_slice(&(hs.len() as u16).to_be_bytes());
        record.extend_from_slice(&hs);

        record
    }

    /// Build a fake TLS ClientHello that looks legitimate and randomized (JA3)
    fn build_fake_client_hello(&self) -> Vec<u8> {
        let mut record = Vec::with_capacity(512);
        let mut rng = OsRng;

        // TLS record header
        record.push(TlsContentType::Handshake as u8);
        record.extend_from_slice(&self.config.version.to_be_bytes());

        // Placeholder for length (will fill in later)
        let length_pos = record.len();
        record.extend_from_slice(&[0u8; 2]);

        // Handshake header
        record.push(0x01); // ClientHello
                           // Placeholder for handshake length
        let hs_length_pos = record.len();
        record.extend_from_slice(&[0u8; 3]);

        // Client version (Legacy TLS 1.2)
        record.extend_from_slice(&0x0303u16.to_be_bytes());

        // Random (32 bytes)
        let mut random = [0u8; 32];
        if getrandom::getrandom(&mut random).is_err() {
            rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut random);
        }
        record.extend_from_slice(&random);

        // Session ID (32 bytes)
        record.push(32);
        let mut session_id = [0u8; 32];
        if getrandom::getrandom(&mut session_id).is_err() {
            rand::RngCore::fill_bytes(&mut rand::thread_rng(), &mut session_id);
        }
        record.extend_from_slice(&session_id);

        // Cipher suites (Randomized order and selection)
        // Grease ciphers: 0x0a0a, 0x1a1a, etc.
        let grease = (rng.gen::<u8>() & 0xf0) as u16 + 0x0a0a;

        let mut suites = vec![
            0x1301, // TLS_AES_128_GCM_SHA256
            0x1302, // TLS_AES_256_GCM_SHA384
            0x1303, // TLS_CHACHA20_POLY1305_SHA256
            0xc02b, // TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
            0xc02c, // TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
            0xc02f, // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
            0xc030, // TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
            0xcca9, // TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
            0xcca8, // TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
        ];

        // Shuffle and take random subset (at least 3)
        use rand::seq::SliceRandom;
        use rand::Rng; // Add this import if not present, but better to use fully qualified or assume scope

        suites.shuffle(&mut rng);
        let num_suites = rng.gen_range(3..=suites.len());
        suites.truncate(num_suites);

        // Insert GREASE at random position
        suites.insert(rng.gen_range(0..=suites.len()), grease);

        record.extend_from_slice(&((suites.len() * 2) as u16).to_be_bytes());
        for s in suites {
            record.extend_from_slice(&s.to_be_bytes());
        }

        // Compression methods (Unused in TLS 1.3 but required for legacy)
        record.push(1);
        record.push(0); // null compression

        // Extensions
        let extensions_start = record.len();
        record.extend_from_slice(&[0u8; 2]); // Extensions length placeholder

        // Generate extensions. The `make_*` helpers that take `&mut rng` must
        // be called in sequence, so we build the vec eagerly then push the
        // rng-dependent entries afterwards.
        let supported_groups = self.make_supported_groups_body(&mut rng);
        let key_share = self.make_key_share_body(&mut rng);
        let supported_versions = self.make_supported_versions_body(&mut rng);
        let mut exts: Vec<(u16, Vec<u8>)> = vec![
            // SNI (0)
            (0u16, self.make_sni_extension_body()),
            // Supported Groups (10) - X25519, P-256, P-384 + GREASE
            (10u16, supported_groups),
            // EC Point Formats (11)
            (11u16, vec![1, 0]),
            // Signature Algorithms (13)
            (13u16, self.make_signature_algorithms_body()),
            // Key Share (51)
            (51u16, key_share),
            // Supported Versions (43) - TLS 1.3, TLS 1.2 + GREASE
            (43u16, supported_versions),
        ];

        // Shuffle extensions (except SNI which usually comes first)
        if !exts.is_empty() {
            let sni = exts.remove(0); // Assuming we pushed SNI first
            exts.shuffle(&mut rng);
            exts.insert(0, sni);
        }

        // Padding (21) - MUST be at the end for JA3 fingerprinting evasion
        // Add random size padding to randomize the ClientHello length
        exts.push((21u16, vec![0u8; rng.gen_range(50..200)]));

        for (etype, body) in exts {
            record.extend_from_slice(&etype.to_be_bytes());
            record.extend_from_slice(&(body.len() as u16).to_be_bytes());
            record.extend_from_slice(&body);
        }

        // Fill in extensions length
        let extensions_len = (record.len() - extensions_start - 2) as u16;
        record[extensions_start..extensions_start + 2]
            .copy_from_slice(&extensions_len.to_be_bytes());

        // Fill in handshake length
        let hs_len = (record.len() - hs_length_pos - 3) as u32;
        record[hs_length_pos] = ((hs_len >> 16) & 0xFF) as u8;
        record[hs_length_pos + 1] = ((hs_len >> 8) & 0xFF) as u8;
        record[hs_length_pos + 2] = (hs_len & 0xFF) as u8;

        // Fill in record length
        let record_len = (record.len() - 5) as u16;
        record[length_pos..length_pos + 2].copy_from_slice(&record_len.to_be_bytes());

        record
    }

    fn make_sni_extension_body(&self) -> Vec<u8> {
        let mut body = Vec::new();
        let sni_bytes = self.config.sni.as_bytes();
        body.extend_from_slice(&((3 + sni_bytes.len()) as u16).to_be_bytes()); // Server name list length
        body.push(0); // Name type: host_name
        body.extend_from_slice(&(sni_bytes.len() as u16).to_be_bytes());
        body.extend_from_slice(sni_bytes);
        body
    }

    fn make_supported_groups_body(&self, rng: &mut impl rand::Rng) -> Vec<u8> {
        let mut body = Vec::new();
        let grease = (rng.gen::<u8>() & 0xf0) as u16 + 0x0a0a;
        let groups = [
            grease, 0x001d, // X25519
            0x0017, // secp256r1
        ];
        let len = (groups.len() * 2) as u16;
        body.extend_from_slice(&len.to_be_bytes());
        for g in groups {
            body.extend_from_slice(&g.to_be_bytes());
        }
        body
    }

    fn make_signature_algorithms_body(&self) -> Vec<u8> {
        let mut body = Vec::new();
        let algos: [u16; 8] = [
            0x0403, // ecdsa_secp256r1_sha256
            0x0804, // rsa_pss_rsae_sha256
            0x0401, // rsa_pkcs1_sha256
            0x0503, // ecdsa_secp384r1_sha384
            0x0805, // rsa_pss_rsae_sha384
            0x0501, // rsa_pkcs1_sha384
            0x0806, // rsa_pss_rsae_sha512
            0x0601, // rsa_pkcs1_sha512
        ];
        let len = (algos.len() * 2) as u16;
        body.extend_from_slice(&len.to_be_bytes());
        for a in algos {
            body.extend_from_slice(&a.to_be_bytes());
        }
        body
    }

    fn make_key_share_body(&self, rng: &mut (impl rand::Rng + rand::CryptoRng)) -> Vec<u8> {
        let mut body = Vec::new();
        let grease = (rng.gen::<u8>() & 0xf0) as u16 + 0x0a0a;

        // Real X25519 key share
        let secret = StaticSecret::random_from_rng(rng);
        let public = X25519PublicKey::from(&secret);
        let x25519_share = public.as_bytes();

        let client_shares_len = 4 + (2 + 32); // Grease (4) + X25519 (2+32)
        body.extend_from_slice(&(client_shares_len as u16).to_be_bytes());

        // GREASE share
        body.extend_from_slice(&grease.to_be_bytes());
        body.extend_from_slice(&1u16.to_be_bytes()); // Len 1
        body.push(0);

        // X25519 share
        body.extend_from_slice(&0x001du16.to_be_bytes());
        body.extend_from_slice(&(x25519_share.len() as u16).to_be_bytes());
        body.extend_from_slice(x25519_share);

        body
    }

    fn make_supported_versions_body(&self, rng: &mut impl rand::Rng) -> Vec<u8> {
        let mut body = Vec::new();
        let grease = (rng.gen::<u8>() & 0xf0) as u16 + 0x0a0a;
        let versions = [
            grease, 0x0304, // TLS 1.3
            0x0303, // TLS 1.2
        ];
        let len = (versions.len() * 2) as u16;
        body.push((len & 0xFF) as u8); // Supported versions length is 1 byte in this extension body
        for v in versions {
            body.extend_from_slice(&v.to_be_bytes());
        }
        body
    }

    /// Wrap data as TLS Application Data record using ring AEAD.
    ///
    /// Each call increments `send_counter` so the (key, nonce) pair is unique
    /// per record — the previous implementation reused `[0u8; 12]` and was
    /// vulnerable to the Forbidden Attack on AES-GCM.
    fn wrap_as_tls_record(&self, data: &[u8]) -> io::Result<Vec<u8>> {
        // faketls-2: the outer TLS record carries its body length in a u16
        // field. The sealed body is `data + 1` (inner content-type byte) plus
        // the AEAD tag. Reject anything whose sealed length would exceed
        // `u16::MAX` BEFORE sealing, so an oversized payload can never silently
        // truncate the length field into a corrupt record (and we skip a wasted
        // seal). Invariant 3 is untouched — the per-record `send_counter` nonce
        // and direction-keyed `send_key` below are exactly as before.
        let tag_len = self.send_key.algorithm().tag_len();
        let framed_len = data.len().saturating_add(1).saturating_add(tag_len);
        if framed_len > u16::MAX as usize {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!(
                    "faketls record body would be {framed_len} bytes, exceeding the {}-byte u16 length field",
                    u16::MAX
                ),
            ));
        }

        // TLS 1.3 framing: inner plaintext + TLS1.3 AppData type (0x17)
        let mut inner_plaintext = Vec::with_capacity(data.len() + 1);
        inner_plaintext.extend_from_slice(data);
        inner_plaintext.push(TlsContentType::ApplicationData as u8); // inner content type

        // Encrypt with ring using a counter-based nonce.
        let mut in_out = inner_plaintext;
        let counter = self.send_counter.fetch_add(1, Ordering::Relaxed);
        let nonce = self.make_nonce(counter);

        let aad = aead::Aad::empty();
        // A seal failure here means ring rejected the input (e.g. the AEAD
        // invocation ceiling); surface it rather than panicking.
        self.send_key
            .seal_in_place_append_tag(nonce, aad, &mut in_out)
            .map_err(|_| io::Error::other("faketls AEAD seal failed"))?;

        let mut record = Vec::with_capacity(5 + in_out.len());

        // Outer TLS record header (Legacy Application Data). `in_out.len()` is
        // ≤ u16::MAX by the guard above, so the cast cannot truncate.
        record.push(TlsContentType::ApplicationData as u8);
        record.extend_from_slice(&self.config.version.to_be_bytes());
        record.extend_from_slice(&(in_out.len() as u16).to_be_bytes());
        record.extend_from_slice(&in_out);

        Ok(record)
    }

    /// Unwrap TLS Application Data record.
    ///
    /// Each call increments `recv_counter` in lockstep with the peer's
    /// `send_counter` (TCP guarantees in-order delivery). If a record is
    /// corrupted or the counters drift, decryption fails and the leg should
    /// be closed.
    fn unwrap_tls_record<'a>(&self, record: &'a mut [u8]) -> io::Result<&'a [u8]> {
        if record.len() < 5 {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                "Record too short",
            ));
        }

        let payload = &mut record[5..];

        let counter = self.recv_counter.fetch_add(1, Ordering::Relaxed);
        let nonce = self.make_nonce(counter);
        let aad = aead::Aad::empty();

        let decrypted = self
            .recv_key
            .open_in_place(nonce, aad, payload)
            .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "AEAD decryption failed"))?;

        // Remove inner content type (last byte)
        if decrypted.is_empty() {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                "Empty inner plaintext",
            ));
        }

        let inner_len = decrypted.len() - 1;
        Ok(&decrypted[..inner_len])
    }
}

impl Default for FakeTlsLeg {
    /// `Default` panics on key-init failure — see [`FakeTlsLeg::new`] for the
    /// structurally-impossible error contract. Prefer the explicit
    /// `FakeTlsLeg::new()` in code that cares about the error.
    fn default() -> Self {
        #[allow(clippy::expect_used)]
        Self::new().expect("FakeTlsLeg::default: AES key init invariant violated")
    }
}

#[async_trait]
impl TransportLeg for FakeTlsLeg {
    async fn send(&self, data: Bytes) -> io::Result<()> {
        if !self.is_available() {
            return Err(io::Error::new(
                io::ErrorKind::NotConnected,
                "FakeTLS not connected",
            ));
        }

        if *self.state.read() != FakeTlsState::ApplicationData {
            return Err(io::Error::other("Handshake not finished"));
        }

        let record = self.wrap_as_tls_record(&data)?;

        let mut stream_guard = self.stream.lock().await;
        let stream = stream_guard
            .as_mut()
            .ok_or_else(|| io::Error::new(io::ErrorKind::NotConnected, "Not connected"))?;

        stream.write_all(&record).await?;
        stream.flush().await
    }

    async fn recv(&self) -> io::Result<Bytes> {
        if !self.is_available() {
            return Err(io::Error::new(
                io::ErrorKind::NotConnected,
                "FakeTLS not connected",
            ));
        }

        if *self.state.read() != FakeTlsState::ApplicationData {
            return Err(io::Error::other("Handshake not finished"));
        }

        let mut stream_guard = self.stream.lock().await;
        let stream = stream_guard
            .as_mut()
            .ok_or_else(|| io::Error::new(io::ErrorKind::NotConnected, "Not connected"))?;

        let mut read_buf = self.read_buf.lock().await;

        // Read TLS record header (5 bytes)
        while read_buf.len() < 5 {
            let mut temp = [0u8; 4096];
            let n = stream.read(&mut temp).await?;
            if n == 0 {
                return Err(io::Error::new(
                    io::ErrorKind::UnexpectedEof,
                    "Connection closed",
                ));
            }
            read_buf.extend_from_slice(&temp[..n]);
        }

        // Parse record length
        let record_len = u16::from_be_bytes([read_buf[3], read_buf[4]]) as usize;

        // Read full record
        while read_buf.len() < 5 + record_len {
            let mut temp = [0u8; 4096];
            let n = stream.read(&mut temp).await?;
            if n == 0 {
                return Err(io::Error::new(
                    io::ErrorKind::UnexpectedEof,
                    "Connection closed",
                ));
            }
            read_buf.extend_from_slice(&temp[..n]);
        }

        // Extract record
        let mut record = read_buf.split_to(5 + record_len);

        // Unwrap and decrypt payload
        // We have to copy it to a new Bytes because unwrap_tls_record mutates in place
        // and returns a reference. Or we can just use `unwrap_tls_record(&mut record)`.
        let payload_len = self.unwrap_tls_record(&mut record)?.len();

        // The decrypted payload is at `&record[5..5+payload_len]`
        let mut out = BytesMut::with_capacity(payload_len);
        out.extend_from_slice(&record[5..5 + payload_len]);

        Ok(out.freeze())
    }

    fn is_available(&self) -> bool {
        self.available.load(Ordering::Relaxed)
            && *self.state.read() == FakeTlsState::ApplicationData
    }

    fn rtt_ms(&self) -> u32 {
        self.rtt_ms.load(Ordering::Relaxed)
    }

    fn loss_percent(&self) -> u8 {
        0 // Based on TCP
    }

    fn remote_addr(&self) -> Option<SocketAddr> {
        self.remote_addr
    }

    async fn close(&self) -> io::Result<()> {
        self.available.store(false, Ordering::Relaxed);

        if let Some(stream) = self.stream.lock().await.take() {
            drop(stream);
        }

        log::info!("FakeTLS closed");
        Ok(())
    }
}

impl std::fmt::Debug for FakeTlsLeg {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("FakeTlsLeg")
            .field("sni", &self.config.sni)
            .field("remote", &self.remote_addr)
            .field("rtt_ms", &self.rtt_ms.load(Ordering::Relaxed))
            .field("available", &self.is_available())
            .finish()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_faketls_leg_creation() {
        let leg = FakeTlsLeg::new().expect("FakeTlsLeg::new");
        assert!(!leg.is_available());
        assert_eq!(leg.config.sni, "www.google.com");
    }

    #[test]
    fn test_fake_client_hello() {
        let leg = FakeTlsLeg::new().expect("FakeTlsLeg::new");
        let hello = leg.build_fake_client_hello();

        // Should start with TLS handshake record
        assert_eq!(hello[0], TlsContentType::Handshake as u8);
        assert!(hello.len() > 100); // Should be substantial
    }

    #[test]
    fn test_wrap_tls_record() {
        let leg = FakeTlsLeg::new().expect("FakeTlsLeg::new");
        let data = b"test payload";
        let record = leg.wrap_as_tls_record(data).expect("wrap_as_tls_record");

        assert_eq!(record[0], TlsContentType::ApplicationData as u8);
        // data len + 1 for inner content type + 16 for auth tag
        assert_eq!(record.len(), 5 + data.len() + 1 + 16);
    }

    /// faketls-2: a payload whose sealed length would overflow the u16 TLS
    /// record-length field must be rejected with `InvalidData`, never silently
    /// truncated into a corrupt record.
    #[test]
    fn oversized_record_payload_is_rejected_not_truncated() {
        let leg = FakeTlsLeg::new().expect("FakeTlsLeg::new");
        // sealed = data + 1 (inner content-type) + 16 (AES-GCM tag). The largest
        // payload that still fits u16::MAX (65_535) is 65_518; one more overflows.
        let ok = leg
            .wrap_as_tls_record(&vec![0u8; 65_518])
            .expect("boundary payload fits");
        assert_eq!(ok.len(), 5 + u16::MAX as usize);
        let err = leg
            .wrap_as_tls_record(&vec![0u8; 65_519])
            .expect_err("oversized payload must be rejected, not truncated");
        assert_eq!(err.kind(), std::io::ErrorKind::InvalidData);
    }

    #[test]
    fn test_ja3_randomization() {
        let leg = FakeTlsLeg::new().expect("FakeTlsLeg::new");
        let hello1_ = leg.build_fake_client_hello();
        let hello2 = leg.build_fake_client_hello();

        // They should be different due to random session ID, random, and shuffling
        assert_ne!(hello1_, hello2);

        // Check standard parts presence
        // TLS 1.2 Version (0x0303) at offset 9
        assert_eq!(hello1_[9], 0x03);
        assert_eq!(hello1_[10], 0x03);
    }

    /// Round-trip test: a client-side leg encrypts, a server-side leg decrypts.
    /// Proves that derived-key + counter-nonce works end-to-end.
    #[test]
    fn test_wrap_unwrap_roundtrip_across_peers() {
        // Build a "client" leg.
        let client =
            FakeTlsLeg::with_config(FakeTlsConfig::default()).expect("FakeTlsLeg::with_config");

        // Build a "server" leg from scratch: same config but is_server = true.
        // We construct it directly since `accept` requires a real TcpStream.
        let cfg = FakeTlsConfig::default();
        let (send_key, recv_key, nonce_prefix) =
            derive_outer_keys(&cfg.sni, cfg.version, true).expect("derive_outer_keys");
        let server = FakeTlsLeg {
            config: cfg,
            stream: Mutex::new(None),
            remote_addr: None,
            rtt_ms: AtomicU32::new(0),
            available: AtomicBool::new(false),
            state: parking_lot::RwLock::new(FakeTlsState::ApplicationData),
            read_buf: Mutex::new(BytesMut::new()),
            send_key,
            recv_key,
            nonce_prefix,
            send_counter: AtomicU64::new(0),
            recv_counter: AtomicU64::new(0),
            is_server: true,
        };

        let plaintexts: &[&[u8]] = &[
            b"first record",
            b"second record",
            b"third record (a bit longer to vary length)",
        ];
        for pt in plaintexts {
            let mut record = client.wrap_as_tls_record(pt).expect("wrap_as_tls_record");
            let recovered_len = server.unwrap_tls_record(&mut record).unwrap().len();
            assert_eq!(&record[5..5 + recovered_len], *pt);
        }
    }

    /// Encrypting identical plaintexts twice must produce different ciphertexts
    /// because the per-record counter advances.
    #[test]
    fn test_nonce_advances_per_record() {
        let leg =
            FakeTlsLeg::with_config(FakeTlsConfig::default()).expect("FakeTlsLeg::with_config");
        let r1 = leg.wrap_as_tls_record(b"identical").expect("wrap r1");
        let r2 = leg.wrap_as_tls_record(b"identical").expect("wrap r2");
        assert_ne!(
            r1, r2,
            "counter must advance — identical plaintext must not produce identical ciphertext"
        );
    }

    #[test]
    fn test_client_hello_structure() {
        let leg = FakeTlsLeg::new().expect("FakeTlsLeg::new");
        let hello = leg.build_fake_client_hello();

        // Verify TLS record header
        assert_eq!(
            hello[0],
            TlsContentType::Handshake as u8,
            "First byte should be Handshake (22)"
        );

        // TLS record version (bytes 1-2)
        let record_version = u16::from_be_bytes([hello[1], hello[2]]);
        assert_eq!(
            record_version, 0x0303,
            "Record version should be TLS 1.2 (0x0303)"
        );

        // Record length (bytes 3-4)
        let record_len = u16::from_be_bytes([hello[3], hello[4]]) as usize;
        assert_eq!(
            hello.len(),
            5 + record_len,
            "Record length should match payload"
        );

        // Handshake type (byte 5) should be ClientHello (0x01)
        assert_eq!(hello[5], 0x01, "Handshake type should be ClientHello");

        // Client version at bytes 9-10 (legacy TLS 1.2)
        assert_eq!(hello[9], 0x03, "Legacy version major");
        assert_eq!(hello[10], 0x03, "Legacy version minor");

        // Random (32 bytes starting at byte 11) should not be all zeros
        let random = &hello[11..43];
        assert!(
            !random.iter().all(|&b| b == 0),
            "Random should not be all zeros"
        );

        // Session ID length at byte 43 should be 32
        assert_eq!(hello[43], 32, "Session ID length should be 32");
    }
}