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}