Skip to main content

phantom_protocol/transport/
device_profile.rs

1//! Device Profile System
2//!
3//! Three device tiers with adaptive transport parameters (buffer sizes, stream
4//! limits, coalescing, compression, MTU):
5//! - Constrained: ESP32, drones, IoT (~520 KB RAM, no HW AES)
6//! - Standard: smartphones, SBCs, Raspberry Pi (1-4 GB RAM)
7//! - Performance: servers, desktops (8+ GB RAM, HW AES)
8//!
9//! Post-quantum security is mandatory for EVERY tier.
10//!
11//! NOTE: this module is **descriptive / not yet wired**. `DeviceProfile` and its
12//! `PqKemLevel` / `PqSignLevel` fields are not imported by the handshake or crypto
13//! layers — the live handshake uses a single fixed hybrid suite (X25519 + ML-KEM-768
14//! KEM, Ed25519 + ML-DSA-65 signatures), not a per-tier-selectable Kyber512 /
15//! Dilithium2. The `PqKemLevel` / `PqSignLevel` enums (Kyber/Dilithium are the
16//! pre-standardization names for ML-KEM / ML-DSA) describe the *intended* tiering
17//! but do not currently steer any wire negotiation. The non-crypto knobs
18//! (`buffer_size`, `max_streams`, `coalescing`, …) are likewise advisory defaults.
19
20use crate::crypto::adaptive_crypto::{CipherSuite, HwCaps};
21
22/// Device tier classification
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum DeviceTier {
25    /// IoT, drones, ESP32, STM32, old mobile phones
26    /// ~520KB RAM, no HW AES, limited CPU
27    Constrained,
28    /// Smartphones, Raspberry Pi, SBCs
29    /// 1-4GB RAM, may or may not have HW AES
30    Standard,
31    /// Servers, desktops, modern laptops
32    /// 8+GB RAM, HW AES available
33    Performance,
34}
35
36/// PQ KEM security level
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum PqKemLevel {
39    /// Kyber512 — NIST Level 1 (~21KB RAM), for Constrained
40    Kyber512,
41    /// Kyber768 — NIST Level 3 (~29KB RAM), for Standard/Performance
42    Kyber768,
43}
44
45/// PQ Signature level
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum PqSignLevel {
48    /// Dilithium2 — NIST Level 2 (~40KB RAM)
49    Dilithium2,
50    /// Dilithium3 — NIST Level 3 (~70KB RAM)
51    Dilithium3,
52}
53
54/// Complete device profile — all transport parameters
55#[derive(Debug, Clone)]
56pub struct DeviceProfile {
57    /// Device classification
58    pub tier: DeviceTier,
59    /// Preferred cipher suite
60    pub cipher: CipherSuite,
61    /// PQ KEM level (always present — PQ is mandatory)
62    pub pq_kem: PqKemLevel,
63    /// PQ signature level (always present)
64    pub pq_sign: PqSignLevel,
65    /// Buffer size for I/O operations
66    pub buffer_size: usize,
67    /// Maximum concurrent streams
68    pub max_streams: u16,
69    /// Enable UDP packet coalescing
70    pub coalescing: bool,
71    /// Maximum coalesced datagram size
72    pub max_datagram_size: usize,
73    /// Enable compression
74    pub compression: bool,
75    /// Maximum packet payload size
76    pub max_payload: usize,
77}
78
79impl DeviceProfile {
80    /// Auto-detect the optimal profile for this device
81    pub fn auto_detect() -> Self {
82        let caps = HwCaps::detect();
83        let available_ram = Self::estimate_available_ram();
84
85        let tier = if available_ram < 1_048_576 {
86            // < 1MB
87            DeviceTier::Constrained
88        } else if available_ram < 512_000_000 {
89            // < 512MB
90            DeviceTier::Standard
91        } else {
92            DeviceTier::Performance
93        };
94
95        Self::for_tier(tier, &caps)
96    }
97
98    /// Create a profile for a specific tier with given HW caps
99    pub fn for_tier(tier: DeviceTier, caps: &HwCaps) -> Self {
100        match tier {
101            DeviceTier::Constrained => Self {
102                tier,
103                cipher: CipherSuite::ChaCha20Poly1305, // Always ChaCha20 for constrained
104                pq_kem: PqKemLevel::Kyber512,          // Light PQ — 21KB RAM
105                pq_sign: PqSignLevel::Dilithium2,      // Light PQ sig — 40KB RAM
106                buffer_size: 2 * 1024,                 // 2KB buffers
107                max_streams: 4,
108                coalescing: false,      // No coalescing overhead
109                max_datagram_size: 512, // Tiny datagrams
110                compression: false,     // CPU more precious than bandwidth
111                max_payload: 256,
112            },
113            DeviceTier::Standard => Self {
114                tier,
115                cipher: caps.recommended_cipher(), // Auto: AES if HW, else ChaCha
116                pq_kem: PqKemLevel::Kyber768,      // Full PQ — 29KB RAM
117                pq_sign: PqSignLevel::Dilithium3,  // Full PQ sig
118                buffer_size: 16 * 1024,            // 16KB buffers
119                max_streams: 64,
120                coalescing: true,
121                max_datagram_size: 4096,
122                compression: true, // Zstd-1
123                max_payload: 1400, // Standard MTU
124            },
125            DeviceTier::Performance => Self {
126                tier,
127                cipher: CipherSuite::Aes256Gcm, // Always AES-GCM (HW)
128                pq_kem: PqKemLevel::Kyber768,   // Full PQ
129                pq_sign: PqSignLevel::Dilithium3, // Full PQ sig
130                buffer_size: 64 * 1024,         // 64KB buffers
131                max_streams: 256,
132                coalescing: true,
133                max_datagram_size: 8192, // Jumbo-like
134                compression: true,       // LZ4 (ultra-fast)
135                max_payload: 8192,
136            },
137        }
138    }
139
140    /// Create a specific named profile
141    pub fn constrained() -> Self {
142        Self::for_tier(DeviceTier::Constrained, &HwCaps { has_hw_aes: false })
143    }
144
145    pub fn standard() -> Self {
146        Self::for_tier(DeviceTier::Standard, &HwCaps::detect())
147    }
148
149    pub fn performance() -> Self {
150        Self::for_tier(DeviceTier::Performance, &HwCaps { has_hw_aes: true })
151    }
152
153    /// Estimate available RAM (platform-specific)
154    fn estimate_available_ram() -> usize {
155        // On std targets, use sysinfo-like heuristics
156        // For now, use a simple approach based on pointer size
157        #[cfg(target_pointer_width = "64")]
158        {
159            8_000_000_000
160        } // 64-bit → assume Performance-class
161
162        #[cfg(target_pointer_width = "32")]
163        {
164            512_000
165        } // 32-bit → assume Constrained-class
166
167        #[cfg(not(any(target_pointer_width = "64", target_pointer_width = "32")))]
168        {
169            256_000
170        } // 16-bit → definitely Constrained
171    }
172
173    /// Whether this profile's *intended* PQ KEM level is the full Kyber768
174    /// (== ML-KEM-768). Advisory only — see the module note; the live handshake
175    /// always uses ML-KEM-768 regardless of this profile.
176    pub fn is_full_pq(&self) -> bool {
177        matches!(self.pq_kem, PqKemLevel::Kyber768)
178    }
179
180    /// Stable 1-byte tier discriminant (Constrained=0 / Standard=1 / Performance=2).
181    /// A convenience encoding for callers that want to serialize the tier; the live
182    /// wire protocol does not currently carry it.
183    pub fn tier_byte(&self) -> u8 {
184        match self.tier {
185            DeviceTier::Constrained => 0,
186            DeviceTier::Standard => 1,
187            DeviceTier::Performance => 2,
188        }
189    }
190}
191
192impl std::fmt::Display for DeviceProfile {
193    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194        write!(
195            f,
196            "{:?} [cipher={:?}, kem={:?}, sign={:?}, buf={}KB, streams={}]",
197            self.tier,
198            self.cipher,
199            self.pq_kem,
200            self.pq_sign,
201            self.buffer_size / 1024,
202            self.max_streams,
203        )
204    }
205}
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210
211    #[test]
212    fn constrained_profile() {
213        let p = DeviceProfile::constrained();
214        assert_eq!(p.tier, DeviceTier::Constrained);
215        assert_eq!(p.cipher, CipherSuite::ChaCha20Poly1305);
216        assert_eq!(p.pq_kem, PqKemLevel::Kyber512);
217        assert_eq!(p.pq_sign, PqSignLevel::Dilithium2);
218        assert_eq!(p.buffer_size, 2048);
219        assert!(!p.coalescing);
220        assert!(!p.compression);
221        eprintln!("Constrained: {}", p);
222    }
223
224    #[test]
225    fn standard_profile() {
226        let p = DeviceProfile::standard();
227        assert_eq!(p.tier, DeviceTier::Standard);
228        assert_eq!(p.pq_kem, PqKemLevel::Kyber768);
229        assert!(p.coalescing);
230        assert!(p.compression);
231        eprintln!("Standard: {}", p);
232    }
233
234    #[test]
235    fn performance_profile() {
236        let p = DeviceProfile::performance();
237        assert_eq!(p.tier, DeviceTier::Performance);
238        assert_eq!(p.cipher, CipherSuite::Aes256Gcm);
239        assert_eq!(p.pq_kem, PqKemLevel::Kyber768);
240        assert_eq!(p.buffer_size, 65536);
241        eprintln!("Performance: {}", p);
242    }
243
244    #[test]
245    fn auto_detect_profile() {
246        let p = DeviceProfile::auto_detect();
247        eprintln!("Auto: {}", p);
248        // On 64-bit host, should be Performance
249        #[cfg(target_pointer_width = "64")]
250        assert_eq!(p.tier, DeviceTier::Performance);
251    }
252
253    #[test]
254    fn all_tiers_have_pq() {
255        for tier in [
256            DeviceTier::Constrained,
257            DeviceTier::Standard,
258            DeviceTier::Performance,
259        ] {
260            let p = DeviceProfile::for_tier(tier, &HwCaps::detect());
261            // PQ KEM is always present
262            assert!(matches!(
263                p.pq_kem,
264                PqKemLevel::Kyber512 | PqKemLevel::Kyber768
265            ));
266            // PQ Signature is always present
267            assert!(matches!(
268                p.pq_sign,
269                PqSignLevel::Dilithium2 | PqSignLevel::Dilithium3
270            ));
271            eprintln!("{:?}: PQ KEM={:?}, PQ Sign={:?}", tier, p.pq_kem, p.pq_sign);
272        }
273    }
274}