//! Protocol constants and datacenter addresses #![allow(dead_code)] use std::net::{IpAddr, Ipv4Addr}; use crate::crypto::SecureRandom; use std::sync::LazyLock; // ============= Telegram Datacenters ============= pub const TG_DATACENTER_PORT: u16 = 443; pub static TG_DATACENTERS_V4: LazyLock> = LazyLock::new(|| { vec![ IpAddr::V4(Ipv4Addr::new(149, 154, 175, 50)), IpAddr::V4(Ipv4Addr::new(149, 154, 167, 51)), IpAddr::V4(Ipv4Addr::new(149, 154, 175, 100)), IpAddr::V4(Ipv4Addr::new(149, 154, 167, 91)), IpAddr::V4(Ipv4Addr::new(149, 154, 171, 5)), ] }); pub static TG_DATACENTERS_V6: LazyLock> = LazyLock::new(|| { vec![ IpAddr::V6("2001:b28:f23d:f001::a".parse().unwrap()), IpAddr::V6("2001:67c:04e8:f002::a".parse().unwrap()), IpAddr::V6("2001:b28:f23d:f003::a".parse().unwrap()), IpAddr::V6("2001:67c:04e8:f004::a".parse().unwrap()), IpAddr::V6("2001:b28:f23f:f005::a".parse().unwrap()), ] }); // ============= Middle Proxies (for advertising) ============= pub static TG_MIDDLE_PROXIES_V4: LazyLock>> = LazyLock::new(|| { let mut m = std::collections::HashMap::new(); m.insert( 1, vec![(IpAddr::V4(Ipv4Addr::new(149, 154, 175, 50)), 8888)], ); m.insert( -1, vec![(IpAddr::V4(Ipv4Addr::new(149, 154, 175, 50)), 8888)], ); m.insert( 2, vec![(IpAddr::V4(Ipv4Addr::new(149, 154, 161, 144)), 8888)], ); m.insert( -2, vec![(IpAddr::V4(Ipv4Addr::new(149, 154, 161, 144)), 8888)], ); m.insert( 3, vec![(IpAddr::V4(Ipv4Addr::new(149, 154, 175, 100)), 8888)], ); m.insert( -3, vec![(IpAddr::V4(Ipv4Addr::new(149, 154, 175, 100)), 8888)], ); m.insert(4, vec![(IpAddr::V4(Ipv4Addr::new(91, 108, 4, 136)), 8888)]); m.insert( -4, vec![(IpAddr::V4(Ipv4Addr::new(149, 154, 165, 109)), 8888)], ); m.insert(5, vec![(IpAddr::V4(Ipv4Addr::new(91, 108, 56, 183)), 8888)]); m.insert( -5, vec![(IpAddr::V4(Ipv4Addr::new(91, 108, 56, 183)), 8888)], ); m }); pub static TG_MIDDLE_PROXIES_V6: LazyLock>> = LazyLock::new(|| { let mut m = std::collections::HashMap::new(); m.insert( 1, vec![(IpAddr::V6("2001:b28:f23d:f001::d".parse().unwrap()), 8888)], ); m.insert( -1, vec![(IpAddr::V6("2001:b28:f23d:f001::d".parse().unwrap()), 8888)], ); m.insert( 2, vec![(IpAddr::V6("2001:67c:04e8:f002::d".parse().unwrap()), 80)], ); m.insert( -2, vec![(IpAddr::V6("2001:67c:04e8:f002::d".parse().unwrap()), 80)], ); m.insert( 3, vec![(IpAddr::V6("2001:b28:f23d:f003::d".parse().unwrap()), 8888)], ); m.insert( -3, vec![(IpAddr::V6("2001:b28:f23d:f003::d".parse().unwrap()), 8888)], ); m.insert( 4, vec![(IpAddr::V6("2001:67c:04e8:f004::d".parse().unwrap()), 8888)], ); m.insert( -4, vec![(IpAddr::V6("2001:67c:04e8:f004::d".parse().unwrap()), 8888)], ); m.insert( 5, vec![(IpAddr::V6("2001:b28:f23f:f005::d".parse().unwrap()), 8888)], ); m.insert( -5, vec![(IpAddr::V6("2001:b28:f23f:f005::d".parse().unwrap()), 8888)], ); m }); // ============= Protocol Tags ============= /// MTProto transport protocol variants #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[repr(u32)] pub enum ProtoTag { /// Abridged protocol - compact framing Abridged = 0xefefefef, /// Intermediate protocol - simple 4-byte length prefix Intermediate = 0xeeeeeeee, /// Secure intermediate - with random padding Secure = 0xdddddddd, } impl ProtoTag { /// Parse protocol tag from 4 bytes pub fn from_bytes(bytes: [u8; 4]) -> Option { match u32::from_le_bytes(bytes) { 0xefefefef => Some(ProtoTag::Abridged), 0xeeeeeeee => Some(ProtoTag::Intermediate), 0xdddddddd => Some(ProtoTag::Secure), _ => None, } } /// Convert to 4 bytes (little-endian) pub fn to_bytes(self) -> [u8; 4] { (self as u32).to_le_bytes() } /// Get protocol tag as bytes slice pub fn as_bytes(&self) -> &'static [u8; 4] { match self { ProtoTag::Abridged => &PROTO_TAG_ABRIDGED, ProtoTag::Intermediate => &PROTO_TAG_INTERMEDIATE, ProtoTag::Secure => &PROTO_TAG_SECURE, } } } /// Protocol tag bytes pub const PROTO_TAG_ABRIDGED: [u8; 4] = [0xef, 0xef, 0xef, 0xef]; pub const PROTO_TAG_INTERMEDIATE: [u8; 4] = [0xee, 0xee, 0xee, 0xee]; pub const PROTO_TAG_SECURE: [u8; 4] = [0xdd, 0xdd, 0xdd, 0xdd]; // ============= Handshake Layout ============= /// Bytes to skip at the start of handshake pub const SKIP_LEN: usize = 8; /// Pre-key length (before hashing with secret) pub const PREKEY_LEN: usize = 32; /// AES key length pub const KEY_LEN: usize = 32; /// AES IV length pub const IV_LEN: usize = 16; /// Total handshake length pub const HANDSHAKE_LEN: usize = 64; /// Position of protocol tag in decrypted handshake pub const PROTO_TAG_POS: usize = 56; /// Position of datacenter index pub const DC_IDX_POS: usize = 60; // ============= Message Limits ============= /// Minimum message length pub const MIN_MSG_LEN: usize = 12; /// Maximum message length (16 MB) pub const MAX_MSG_LEN: usize = 1 << 24; /// CBC block padding size pub const CBC_PADDING: usize = 16; /// Padding filler bytes pub const PADDING_FILLER: [u8; 4] = [0x04, 0x00, 0x00, 0x00]; // ============= TLS Constants ============= /// Minimum certificate length for detection pub const MIN_CERT_LEN: usize = 1024; /// TLS 1.3 version bytes pub const TLS_VERSION: [u8; 2] = [0x03, 0x03]; /// TLS record type: Handshake pub const TLS_RECORD_HANDSHAKE: u8 = 0x16; /// TLS record type: Change Cipher Spec pub const TLS_RECORD_CHANGE_CIPHER: u8 = 0x14; /// TLS record type: Application Data pub const TLS_RECORD_APPLICATION: u8 = 0x17; /// TLS record type: Alert pub const TLS_RECORD_ALERT: u8 = 0x15; /// Maximum TLS plaintext record payload size. /// RFC 8446 §5.1: "The length MUST NOT exceed 2^14 bytes." /// Use this for validating incoming unencrypted records /// (ClientHello, ChangeCipherSpec, unprotected Handshake messages). pub const MAX_TLS_PLAINTEXT_SIZE: usize = 16_384; /// Structural minimum for a valid TLS 1.3 ClientHello with SNI. /// Derived from RFC 8446 §4.1.2 field layout + Appendix D.4 compat mode. /// Deliberately conservative (below any real client) to avoid false /// positives on legitimate connections with compact extension sets. pub const MIN_TLS_CLIENT_HELLO_SIZE: usize = 100; /// Maximum TLS ciphertext record payload size. /// RFC 8446 §5.2: "The length MUST NOT exceed 2^14 + 256 bytes." /// The +256 accounts for maximum AEAD expansion overhead. /// Use this for validating or sizing buffers for encrypted records. pub const MAX_TLS_CIPHERTEXT_SIZE: usize = 16_384 + 256; #[deprecated(note = "use MAX_TLS_PLAINTEXT_SIZE")] pub const MAX_TLS_RECORD_SIZE: usize = MAX_TLS_PLAINTEXT_SIZE; #[deprecated(note = "use MAX_TLS_CIPHERTEXT_SIZE")] pub const MAX_TLS_CHUNK_SIZE: usize = MAX_TLS_CIPHERTEXT_SIZE; /// Secure Intermediate payload is expected to be 4-byte aligned. pub fn is_valid_secure_payload_len(data_len: usize) -> bool { data_len.is_multiple_of(4) } /// Compute Secure Intermediate payload length from wire length. /// Secure mode strips up to 3 random tail bytes by truncating to 4-byte boundary. pub fn secure_payload_len_from_wire_len(wire_len: usize) -> Option { if wire_len < 4 { return None; } Some(wire_len - (wire_len % 4)) } /// Generate padding length for Secure Intermediate protocol. /// Data must be 4-byte aligned; padding is 1..=3 so total is never divisible by 4. pub fn secure_padding_len(data_len: usize, rng: &SecureRandom) -> usize { debug_assert!( is_valid_secure_payload_len(data_len), "Secure payload must be 4-byte aligned, got {data_len}" ); rng.range(3) + 1 } // ============= Timeouts ============= /// Default handshake timeout in seconds pub const DEFAULT_HANDSHAKE_TIMEOUT_SECS: u64 = 10; /// Default connect timeout in seconds pub const DEFAULT_CONNECT_TIMEOUT_SECS: u64 = 10; /// Default keepalive interval in seconds pub const DEFAULT_KEEPALIVE_SECS: u64 = 600; /// Default ACK timeout in seconds pub const DEFAULT_ACK_TIMEOUT_SECS: u64 = 300; // ============= Buffer Sizes ============= /// Default buffer size pub const DEFAULT_BUFFER_SIZE: usize = 16384; /// Small buffer size for bad client handling pub const SMALL_BUFFER_SIZE: usize = 8192; // ============= Statistics ============= /// Duration buckets for histogram metrics pub static DURATION_BUCKETS: &[f64] = &[0.1, 0.5, 1.0, 2.0, 5.0, 15.0, 60.0, 300.0, 600.0, 1800.0]; // ============= Reserved Nonce Patterns ============= /// Reserved first bytes of nonce (must avoid) pub static RESERVED_NONCE_FIRST_BYTES: &[u8] = &[0xef]; /// Reserved 4-byte beginnings of nonce pub static RESERVED_NONCE_BEGINNINGS: &[[u8; 4]] = &[ [0x48, 0x45, 0x41, 0x44], // HEAD [0x50, 0x4F, 0x53, 0x54], // POST [0x47, 0x45, 0x54, 0x20], // GET [0xee, 0xee, 0xee, 0xee], // Intermediate [0xdd, 0xdd, 0xdd, 0xdd], // Secure [0x16, 0x03, 0x01, 0x02], // TLS ]; /// Reserved continuation bytes (bytes 4-7) pub static RESERVED_NONCE_CONTINUES: &[[u8; 4]] = &[[0x00, 0x00, 0x00, 0x00]]; // ============= RPC Constants (for Middle Proxy) ============= /// RPC Proxy Request /// RPC Flags (from Erlang mtp_rpc.erl) pub const RPC_FLAG_NOT_ENCRYPTED: u32 = 0x2; pub const RPC_FLAG_HAS_AD_TAG: u32 = 0x8; pub const RPC_FLAG_MAGIC: u32 = 0x1000; pub const RPC_FLAG_EXTMODE2: u32 = 0x20000; pub const RPC_FLAG_PAD: u32 = 0x8000000; pub const RPC_FLAG_INTERMEDIATE: u32 = 0x20000000; pub const RPC_FLAG_ABRIDGED: u32 = 0x40000000; pub const RPC_FLAG_QUICKACK: u32 = 0x80000000; pub const RPC_PROXY_REQ: [u8; 4] = [0xee, 0xf1, 0xce, 0x36]; /// RPC Proxy Answer pub const RPC_PROXY_ANS: [u8; 4] = [0x0d, 0xda, 0x03, 0x44]; /// RPC Close Extended pub const RPC_CLOSE_EXT: [u8; 4] = [0xa2, 0x34, 0xb6, 0x5e]; /// RPC Simple ACK pub const RPC_SIMPLE_ACK: [u8; 4] = [0x9b, 0x40, 0xac, 0x3b]; /// RPC Unknown pub const RPC_UNKNOWN: [u8; 4] = [0xdf, 0xa2, 0x30, 0x57]; /// RPC Handshake pub const RPC_HANDSHAKE: [u8; 4] = [0xf5, 0xee, 0x82, 0x76]; /// RPC Nonce pub const RPC_NONCE: [u8; 4] = [0xaa, 0x87, 0xcb, 0x7a]; /// RPC Flags pub mod rpc_flags { pub const FLAG_NOT_ENCRYPTED: u32 = 0x2; pub const FLAG_HAS_AD_TAG: u32 = 0x8; pub const FLAG_MAGIC: u32 = 0x1000; pub const FLAG_EXTMODE2: u32 = 0x20000; pub const FLAG_PAD: u32 = 0x8000000; pub const FLAG_INTERMEDIATE: u32 = 0x20000000; pub const FLAG_ABRIDGED: u32 = 0x40000000; pub const FLAG_QUICKACK: u32 = 0x80000000; } // ============= Middle-End Proxy Servers ============= pub const ME_PROXY_PORT: u16 = 8888; pub static TG_MIDDLE_PROXIES_FLAT_V4: LazyLock> = LazyLock::new(|| { vec![ (IpAddr::V4(Ipv4Addr::new(149, 154, 175, 50)), 8888), (IpAddr::V4(Ipv4Addr::new(149, 154, 161, 144)), 8888), (IpAddr::V4(Ipv4Addr::new(149, 154, 175, 100)), 8888), (IpAddr::V4(Ipv4Addr::new(91, 108, 4, 136)), 8888), (IpAddr::V4(Ipv4Addr::new(91, 108, 56, 183)), 8888), ] }); // ============= RPC Constants (u32 native endian) ============= // From mtproto-common.h + net-tcp-rpc-common.h + mtproto-proxy.c pub const RPC_NONCE_U32: u32 = 0x7acb87aa; pub const RPC_HANDSHAKE_U32: u32 = 0x7682eef5; pub const RPC_HANDSHAKE_ERROR_U32: u32 = 0x6a27beda; pub const TL_PROXY_TAG_U32: u32 = 0xdb1e26ae; // mtproto-proxy.c:121 // mtproto-common.h pub const RPC_PROXY_REQ_U32: u32 = 0x36cef1ee; pub const RPC_PROXY_ANS_U32: u32 = 0x4403da0d; pub const RPC_CLOSE_CONN_U32: u32 = 0x1fcf425d; pub const RPC_CLOSE_EXT_U32: u32 = 0x5eb634a2; pub const RPC_SIMPLE_ACK_U32: u32 = 0x3bac409b; pub const RPC_PING_U32: u32 = 0x5730a2df; pub const RPC_PONG_U32: u32 = 0x8430eaa7; pub const RPC_CRYPTO_NONE_U32: u32 = 0; pub const RPC_CRYPTO_AES_U32: u32 = 1; pub mod proxy_flags { pub const FLAG_HAS_AD_TAG: u32 = 1; pub const FLAG_NOT_ENCRYPTED: u32 = 0x2; pub const FLAG_HAS_AD_TAG2: u32 = 0x8; pub const FLAG_MAGIC: u32 = 0x1000; pub const FLAG_EXTMODE2: u32 = 0x20000; pub const FLAG_PAD: u32 = 0x8000000; pub const FLAG_INTERMEDIATE: u32 = 0x20000000; pub const FLAG_ABRIDGED: u32 = 0x40000000; pub const FLAG_QUICKACK: u32 = 0x80000000; } pub mod rpc_crypto_flags { pub const USE_CRC32C: u32 = 0x800; } pub const ME_CONNECT_TIMEOUT_SECS: u64 = 5; pub const ME_HANDSHAKE_TIMEOUT_SECS: u64 = 10; #[cfg(test)] #[path = "tests/tls_size_constants_security_tests.rs"] mod tls_size_constants_security_tests; #[cfg(test)] mod tests { use super::*; #[test] fn test_proto_tag_roundtrip() { for tag in [ProtoTag::Abridged, ProtoTag::Intermediate, ProtoTag::Secure] { let bytes = tag.to_bytes(); let parsed = ProtoTag::from_bytes(bytes).unwrap(); assert_eq!(tag, parsed); } } #[test] fn test_proto_tag_values() { assert_eq!(ProtoTag::Abridged.to_bytes(), PROTO_TAG_ABRIDGED); assert_eq!(ProtoTag::Intermediate.to_bytes(), PROTO_TAG_INTERMEDIATE); assert_eq!(ProtoTag::Secure.to_bytes(), PROTO_TAG_SECURE); } #[test] fn test_invalid_proto_tag() { assert!(ProtoTag::from_bytes([0, 0, 0, 0]).is_none()); assert!(ProtoTag::from_bytes([0xff, 0xff, 0xff, 0xff]).is_none()); } #[test] fn test_datacenters_count() { assert_eq!(TG_DATACENTERS_V4.len(), 5); assert_eq!(TG_DATACENTERS_V6.len(), 5); } #[test] fn secure_padding_never_produces_aligned_total() { let rng = SecureRandom::new(); for data_len in (0..1000).step_by(4) { for _ in 0..100 { let padding = secure_padding_len(data_len, &rng); assert!( padding <= 3, "padding out of range: data_len={data_len}, padding={padding}" ); assert_ne!( (data_len + padding) % 4, 0, "invariant violated: data_len={data_len}, padding={padding}, total={}", data_len + padding ); } } } #[test] fn secure_wire_len_roundtrip_for_aligned_payload() { for payload_len in (4..4096).step_by(4) { for padding in 0..=3usize { let wire_len = payload_len + padding; let recovered = secure_payload_len_from_wire_len(wire_len); assert_eq!(recovered, Some(payload_len)); } } } #[test] fn secure_wire_len_rejects_too_short_frames() { assert_eq!(secure_payload_len_from_wire_len(0), None); assert_eq!(secure_payload_len_from_wire_len(1), None); assert_eq!(secure_payload_len_from_wire_len(2), None); assert_eq!(secure_payload_len_from_wire_len(3), None); } }