frameassembly 1.2.2

Declarative Rust DSL and compiler to generate deterministic .pcap/.pcapng packet captures across 81 L2-L7 protocols for Wireshark, IDS & CI testing.
Documentation
//! Transport Layer Security (TLS 1.2 / TLS 1.3) (RFC 8446 (TLS 1.3) / RFC 5246 (TLS 1.2) / RFC 6066 (SNI) / RFC 7301 (ALPN)) Layer 7 wire packet builder.
//!
//! This module synthesizes deterministic on-the-wire Transport Layer Security (TLS 1.2 / TLS 1.3) frames over TCP port 443.
//! The wire encoder constructs a 5-byte TLS Record Layer header (8-bit ContentType: `21` Alert, `22` Handshake, `23` ApplicationData; 16-bit Legacy Record Version e.g. `0x0303`; 16-bit Length) encapsulating a 4-byte Handshake header (8-bit HandshakeType: `1` ClientHello, `2` ServerHello, `11` Certificate, `20` Finished; 24-bit Length), 32-byte Random nonce, CipherSuite list (`0x1301` TLS_AES_128_GCM_SHA256, `0x1302` TLS_AES_256_GCM_SHA384), and SNI (`0x0000`) / ALPN (`0x0010`) extensions. When optional fields in [`TlsSpec`]
//! are omitted in the `.fasm` source, RFC-compliant default values are automatically populated.
//!
//! Canonical `.fasm` syntax:
//! `a -> b TLS [CLIENT_HELLO | SERVER_HELLO | CERTIFICATE | FINISHED | APP_DATA | ALERT] [RECORD_VERSION <u16>] [HANDSHAKE_VERSION <u16>] [SNI <str>] [CIPHERSUITE <u16>] [ALPN <str>]`

use crate::ast::*;
use crate::builders::WireContext;
use alloc::vec::Vec;

/// Builds an RFC 8446 TLS 1.3 handshake record payload.
/// Syntax mapping: `TLS [CLIENT_HELLO | SERVER_HELLO | CERTIFICATE | FINISHED | APP_DATA | ALERT] [RECORD_VERSION <u16>] [HANDSHAKE_VERSION <u16>] [SNI <str>] [CIPHERSUITE <u16>] [ALPN <str>]`
#[doc(alias = "TLS")]
#[doc(alias = "TLS13")]
#[doc(alias = "RFC8446")]
#[doc(alias = "SNI")]
#[doc(alias = "ALPN")]
#[doc(alias = "CIPHERSUITE")]
pub fn build_tls_payload(
    _ctx: &WireContext,
    spec: &TlsSpec,
    payload_bytes: &[u8],
) -> Vec<u8> {
    let rec_version = spec.record_version.unwrap_or(0x0303); // TLS 1.2 compatibility layer
    let hs_version = spec
        .handshake_version
        .or(spec.record_version)
        .unwrap_or(0x0303);
    let cs = spec.ciphersuite.unwrap_or(0x1301); // TLS_AES_128_GCM_SHA256

    if spec.is_alert {
        let body: &[u8] = if !payload_bytes.is_empty() {
            payload_bytes
        } else {
            &[2, 40] // Level 2 (Fatal), Description 40 (handshake_failure)
        };
        let mut rec = Vec::with_capacity(5 + body.len());
        rec.push(21); // ContentType: Alert (21)
        rec.extend_from_slice(&rec_version.to_be_bytes());
        rec.extend_from_slice(&(body.len() as u16).to_be_bytes());
        rec.extend_from_slice(body);
        return rec;
    }

    if spec.is_app_data {
        let body: &[u8] = if !payload_bytes.is_empty() {
            payload_bytes
        } else {
            b"\x00\x01\x02\x03\x04\x05\x06\x07"
        };
        let mut rec = Vec::with_capacity(5 + body.len());
        rec.push(23); // ContentType: ApplicationData (23)
        rec.extend_from_slice(&rec_version.to_be_bytes());
        rec.extend_from_slice(&(body.len() as u16).to_be_bytes());
        rec.extend_from_slice(body);
        return rec;
    }

    // Build shared TLS Extensions (RFC 6066 SNI 0x0000 and RFC 7301 ALPN 0x0010)
    let mut exts = Vec::new();
    if let Some(sni) = spec.sni {
        let sni_bytes = sni.as_bytes();
        exts.extend_from_slice(&0u16.to_be_bytes()); // Extension Type 0: server_name
        let ext_len = (sni_bytes.len() + 5) as u16;
        exts.extend_from_slice(&ext_len.to_be_bytes());
        let list_len = (sni_bytes.len() + 3) as u16;
        exts.extend_from_slice(&list_len.to_be_bytes());
        exts.push(0); // HostName Type 0
        exts.extend_from_slice(&(sni_bytes.len() as u16).to_be_bytes());
        exts.extend_from_slice(sni_bytes);
    }
    if let Some(alpn) = spec.alpn {
        let alpn_bytes = alpn.as_bytes();
        exts.extend_from_slice(&0x0010u16.to_be_bytes()); // Extension Type 16: application_layer_protocol_negotiation
        let ext_len = (alpn_bytes.len() + 3) as u16;
        exts.extend_from_slice(&ext_len.to_be_bytes());
        let list_len = (alpn_bytes.len() + 1) as u16;
        exts.extend_from_slice(&list_len.to_be_bytes());
        exts.push(alpn_bytes.len() as u8);
        exts.extend_from_slice(alpn_bytes);
    }

    let hs_type: u8 = if spec.is_client_hello {
        1 // ClientHello
    } else if spec.is_server_hello {
        2 // ServerHello
    } else if spec.is_certificate {
        11 // Certificate
    } else if spec.is_finished {
        20 // Finished
    } else {
        1 // ClientHello (default)
    };

    let mut hs_body = Vec::new();
    if hs_type == 2 {
        // ServerHello Handshake
        hs_body.extend_from_slice(&hs_version.to_be_bytes());
        hs_body.extend_from_slice(&[0xaa; 32]); // Server Random
        hs_body.push(0); // Session ID length: 0
        hs_body.extend_from_slice(&cs.to_be_bytes());
        hs_body.push(0); // Compression Method 0 (null)
        hs_body.extend_from_slice(&(exts.len() as u16).to_be_bytes());
        hs_body.extend_from_slice(&exts);
    } else if hs_type == 11 {
        // Certificate Handshake
        if !payload_bytes.is_empty() {
            hs_body.extend_from_slice(payload_bytes);
        } else {
            hs_body.extend_from_slice(&[0x00, 0x00, 0x00]); // Empty certificate_list
        }
    } else if hs_type == 20 {
        // Finished Handshake
        if !payload_bytes.is_empty() {
            hs_body.extend_from_slice(payload_bytes);
        } else {
            hs_body.extend_from_slice(&[0xdd; 32]); // 32-byte verify_data
        }
    } else {
        // ClientHello Handshake (hs_type == 1)
        hs_body.extend_from_slice(&hs_version.to_be_bytes());
        hs_body.extend_from_slice(&[0x11; 32]); // Client Random
        hs_body.push(0); // Session ID length: 0

        // Cipher Suites (4 bytes: primary + fallback)
        hs_body.extend_from_slice(&4u16.to_be_bytes());
        hs_body.extend_from_slice(&cs.to_be_bytes());
        hs_body.extend_from_slice(&0x1302u16.to_be_bytes()); // TLS_AES_256_GCM_SHA384

        // Compression Methods
        hs_body.push(1); // 1 method
        hs_body.push(0); // null

        hs_body.extend_from_slice(&(exts.len() as u16).to_be_bytes());
        hs_body.extend_from_slice(&exts);
    }

    let hs_len = hs_body.len() as u32;
    let mut hs = alloc::vec![
        hs_type,
        (hs_len >> 16) as u8,
        (hs_len >> 8) as u8,
        hs_len as u8,
    ];
    hs.extend_from_slice(&hs_body);

    if (hs_type == 1 || hs_type == 2) && !payload_bytes.is_empty() {
        hs.extend_from_slice(payload_bytes);
    }

    let rec_len = hs.len() as u16;
    let mut rec = Vec::with_capacity(5 + hs.len());
    rec.push(22); // ContentType: Handshake (22)
    rec.extend_from_slice(&rec_version.to_be_bytes());
    rec.extend_from_slice(&rec_len.to_be_bytes());
    rec.extend_from_slice(&hs);
    rec
}