Skip to main content

peat_protocol/qos/
classification.rs

1//! Data type classification for QoS (ADR-019)
2//!
3//! Maps Peat data types to their default QoS classes, enabling
4//! automatic priority assignment based on data semantics.
5
6use super::{QoSClass, QoSPolicy};
7use serde::{Deserialize, Serialize};
8use std::fmt;
9
10/// Peat Protocol data types for QoS classification
11///
12/// Represents the semantic categories of data flowing through the mesh.
13/// Each type has a default QoS class and policy.
14#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
15pub enum DataType {
16    // ========================================================================
17    // P1: Critical - Mission-critical, immediate sync
18    // ========================================================================
19    /// Enemy/target contact report requiring immediate commander awareness
20    ContactReport,
21    /// Emergency alert (medical, safety, system failure)
22    EmergencyAlert,
23    /// Abort/halt command requiring immediate execution
24    AbortCommand,
25    /// Rules of engagement update (safety-critical)
26    RoeUpdate,
27
28    // ========================================================================
29    // P2: High - Important, sync within seconds
30    // ========================================================================
31    /// Target imagery for analysis
32    TargetImage,
33    /// Audio intercept for intelligence
34    AudioIntercept,
35    /// Mission retasking directive
36    MissionRetasking,
37    /// Formation change command
38    FormationChange,
39
40    // ========================================================================
41    // P3: Normal - Standard operational data
42    // ========================================================================
43    /// Node health status (battery, sensors, comms)
44    HealthStatus,
45    /// Capability advertisement change
46    CapabilityChange,
47    /// Cell formation update
48    FormationUpdate,
49    /// Task assignment
50    TaskAssignment,
51
52    // ========================================================================
53    // P4: Low - Routine telemetry
54    // ========================================================================
55    /// Periodic position update
56    PositionUpdate,
57    /// Heartbeat/keepalive
58    Heartbeat,
59    /// Sensor telemetry (non-critical)
60    SensorTelemetry,
61    /// Environment data (weather, terrain)
62    EnvironmentData,
63
64    // ========================================================================
65    // P5: Bulk - Archival/historical data
66    // ========================================================================
67    /// AI model update distribution
68    ModelUpdate,
69    /// Debug/diagnostic logs
70    DebugLog,
71    /// Historical track data
72    HistoricalTrack,
73    /// Training data for on-device ML
74    TrainingData,
75
76    // ========================================================================
77    // Dynamic/Custom
78    // ========================================================================
79    /// Custom data type with explicit QoS class
80    Custom {
81        /// User-defined type name
82        name: String,
83        /// Explicitly assigned QoS class
84        qos_class: QoSClass,
85    },
86}
87
88impl DataType {
89    /// Get the default QoS class for this data type
90    pub fn default_class(&self) -> QoSClass {
91        match self {
92            // P1: Critical
93            Self::ContactReport | Self::EmergencyAlert | Self::AbortCommand | Self::RoeUpdate => {
94                QoSClass::Critical
95            }
96
97            // P2: High
98            Self::TargetImage
99            | Self::AudioIntercept
100            | Self::MissionRetasking
101            | Self::FormationChange => QoSClass::High,
102
103            // P3: Normal
104            Self::HealthStatus
105            | Self::CapabilityChange
106            | Self::FormationUpdate
107            | Self::TaskAssignment => QoSClass::Normal,
108
109            // P4: Low
110            Self::PositionUpdate
111            | Self::Heartbeat
112            | Self::SensorTelemetry
113            | Self::EnvironmentData => QoSClass::Low,
114
115            // P5: Bulk
116            Self::ModelUpdate | Self::DebugLog | Self::HistoricalTrack | Self::TrainingData => {
117                QoSClass::Bulk
118            }
119
120            // Custom uses its explicit class
121            Self::Custom { qos_class, .. } => *qos_class,
122        }
123    }
124
125    /// Get the default QoS policy for this data type
126    pub fn default_policy(&self) -> QoSPolicy {
127        match self {
128            // P1: Critical - strict latency, no TTL, non-preemptable
129            Self::ContactReport => QoSPolicy {
130                base_class: QoSClass::Critical,
131                max_latency_ms: Some(500),
132                max_size_bytes: Some(32 * 1024), // 32KB
133                ttl_seconds: None,               // Never expire
134                retention_priority: 5,
135                preemptable: false,
136            },
137            Self::EmergencyAlert => QoSPolicy {
138                base_class: QoSClass::Critical,
139                max_latency_ms: Some(500),
140                max_size_bytes: Some(8 * 1024), // 8KB
141                ttl_seconds: None,
142                retention_priority: 5,
143                preemptable: false,
144            },
145            Self::AbortCommand => QoSPolicy {
146                base_class: QoSClass::Critical,
147                max_latency_ms: Some(500),
148                max_size_bytes: Some(1024), // 1KB - commands are small
149                ttl_seconds: None,
150                retention_priority: 5,
151                preemptable: false,
152            },
153            Self::RoeUpdate => QoSPolicy {
154                base_class: QoSClass::Critical,
155                max_latency_ms: Some(500),
156                max_size_bytes: Some(64 * 1024), // 64KB for ROE docs
157                ttl_seconds: None,
158                retention_priority: 5,
159                preemptable: false,
160            },
161
162            // P2: High - moderate latency, longer TTL
163            Self::TargetImage => QoSPolicy {
164                base_class: QoSClass::High,
165                max_latency_ms: Some(5_000),
166                max_size_bytes: Some(10 * 1024 * 1024), // 10MB
167                ttl_seconds: Some(3600),                // 1 hour
168                retention_priority: 4,
169                preemptable: true,
170            },
171            Self::AudioIntercept => QoSPolicy {
172                base_class: QoSClass::High,
173                max_latency_ms: Some(5_000),
174                max_size_bytes: Some(5 * 1024 * 1024), // 5MB
175                ttl_seconds: Some(3600),
176                retention_priority: 4,
177                preemptable: true,
178            },
179            Self::MissionRetasking => QoSPolicy {
180                base_class: QoSClass::High,
181                max_latency_ms: Some(2_000), // 2s - commands need faster delivery
182                max_size_bytes: Some(64 * 1024),
183                ttl_seconds: Some(7200), // 2 hours
184                retention_priority: 4,
185                preemptable: false, // Commands should complete
186            },
187            Self::FormationChange => QoSPolicy {
188                base_class: QoSClass::High,
189                max_latency_ms: Some(5_000),
190                max_size_bytes: Some(16 * 1024),
191                ttl_seconds: Some(3600),
192                retention_priority: 4,
193                preemptable: true,
194            },
195
196            // P3: Normal - relaxed latency
197            Self::HealthStatus => QoSPolicy {
198                base_class: QoSClass::Normal,
199                max_latency_ms: Some(60_000), // 1 minute
200                max_size_bytes: Some(8 * 1024),
201                ttl_seconds: Some(86400), // 24 hours
202                retention_priority: 3,
203                preemptable: true,
204            },
205            Self::CapabilityChange => QoSPolicy {
206                base_class: QoSClass::Normal,
207                max_latency_ms: Some(60_000),
208                max_size_bytes: Some(16 * 1024),
209                ttl_seconds: Some(86400),
210                retention_priority: 3,
211                preemptable: true,
212            },
213            Self::FormationUpdate => QoSPolicy {
214                base_class: QoSClass::Normal,
215                max_latency_ms: Some(60_000),
216                max_size_bytes: Some(32 * 1024),
217                ttl_seconds: Some(43200), // 12 hours
218                retention_priority: 3,
219                preemptable: true,
220            },
221            Self::TaskAssignment => QoSPolicy {
222                base_class: QoSClass::Normal,
223                max_latency_ms: Some(30_000), // 30s - tasks need moderate priority
224                max_size_bytes: Some(16 * 1024),
225                ttl_seconds: Some(86400),
226                retention_priority: 3,
227                preemptable: true,
228            },
229
230            // P4: Low - can be delayed
231            Self::PositionUpdate => QoSPolicy {
232                base_class: QoSClass::Low,
233                max_latency_ms: Some(300_000), // 5 minutes
234                max_size_bytes: Some(1024),    // Position data is small
235                ttl_seconds: Some(86400),      // 24 hours
236                retention_priority: 2,
237                preemptable: true,
238            },
239            Self::Heartbeat => QoSPolicy {
240                base_class: QoSClass::Low,
241                max_latency_ms: Some(300_000),
242                max_size_bytes: Some(256), // Heartbeats are tiny
243                ttl_seconds: Some(3600),   // 1 hour
244                retention_priority: 1,     // Evict first
245                preemptable: true,
246            },
247            Self::SensorTelemetry => QoSPolicy {
248                base_class: QoSClass::Low,
249                max_latency_ms: Some(300_000),
250                max_size_bytes: Some(64 * 1024),
251                ttl_seconds: Some(43200), // 12 hours
252                retention_priority: 2,
253                preemptable: true,
254            },
255            Self::EnvironmentData => QoSPolicy {
256                base_class: QoSClass::Low,
257                max_latency_ms: Some(600_000), // 10 minutes
258                max_size_bytes: Some(128 * 1024),
259                ttl_seconds: Some(86400),
260                retention_priority: 2,
261                preemptable: true,
262            },
263
264            // P5: Bulk - background transfer
265            Self::ModelUpdate => QoSPolicy {
266                base_class: QoSClass::Bulk,
267                max_latency_ms: None, // No latency requirement
268                max_size_bytes: Some(500 * 1024 * 1024), // 500MB for ML models
269                ttl_seconds: Some(604800), // 1 week
270                retention_priority: 2, // Keep models longer
271                preemptable: true,
272            },
273            Self::DebugLog => QoSPolicy {
274                base_class: QoSClass::Bulk,
275                max_latency_ms: None,
276                max_size_bytes: Some(10 * 1024 * 1024), // 10MB
277                ttl_seconds: Some(259200),              // 3 days
278                retention_priority: 1,                  // Evict first
279                preemptable: true,
280            },
281            Self::HistoricalTrack => QoSPolicy {
282                base_class: QoSClass::Bulk,
283                max_latency_ms: None,
284                max_size_bytes: Some(100 * 1024 * 1024), // 100MB
285                ttl_seconds: Some(604800),               // 1 week
286                retention_priority: 2,
287                preemptable: true,
288            },
289            Self::TrainingData => QoSPolicy {
290                base_class: QoSClass::Bulk,
291                max_latency_ms: None,
292                max_size_bytes: Some(1024 * 1024 * 1024), // 1GB
293                ttl_seconds: Some(2592000),               // 30 days
294                retention_priority: 2,
295                preemptable: true,
296            },
297
298            // Custom types get default policy for their class
299            Self::Custom { qos_class, .. } => match qos_class {
300                QoSClass::Critical => QoSPolicy::critical(),
301                QoSClass::High => QoSPolicy::high(),
302                QoSClass::Normal => QoSPolicy::default(),
303                QoSClass::Low => QoSPolicy::low(),
304                QoSClass::Bulk => QoSPolicy::bulk(),
305            },
306        }
307    }
308
309    /// Check if this data type is mission-critical
310    pub fn is_critical(&self) -> bool {
311        self.default_class() == QoSClass::Critical
312    }
313
314    /// Check if this data type can be preempted
315    pub fn is_preemptable(&self) -> bool {
316        self.default_policy().preemptable
317    }
318
319    /// Get all predefined data types (excluding Custom)
320    pub fn all_predefined() -> &'static [DataType] {
321        &[
322            // P1
323            DataType::ContactReport,
324            DataType::EmergencyAlert,
325            DataType::AbortCommand,
326            DataType::RoeUpdate,
327            // P2
328            DataType::TargetImage,
329            DataType::AudioIntercept,
330            DataType::MissionRetasking,
331            DataType::FormationChange,
332            // P3
333            DataType::HealthStatus,
334            DataType::CapabilityChange,
335            DataType::FormationUpdate,
336            DataType::TaskAssignment,
337            // P4
338            DataType::PositionUpdate,
339            DataType::Heartbeat,
340            DataType::SensorTelemetry,
341            DataType::EnvironmentData,
342            // P5
343            DataType::ModelUpdate,
344            DataType::DebugLog,
345            DataType::HistoricalTrack,
346            DataType::TrainingData,
347        ]
348    }
349}
350
351impl fmt::Display for DataType {
352    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
353        match self {
354            Self::ContactReport => write!(f, "ContactReport"),
355            Self::EmergencyAlert => write!(f, "EmergencyAlert"),
356            Self::AbortCommand => write!(f, "AbortCommand"),
357            Self::RoeUpdate => write!(f, "RoeUpdate"),
358            Self::TargetImage => write!(f, "TargetImage"),
359            Self::AudioIntercept => write!(f, "AudioIntercept"),
360            Self::MissionRetasking => write!(f, "MissionRetasking"),
361            Self::FormationChange => write!(f, "FormationChange"),
362            Self::HealthStatus => write!(f, "HealthStatus"),
363            Self::CapabilityChange => write!(f, "CapabilityChange"),
364            Self::FormationUpdate => write!(f, "FormationUpdate"),
365            Self::TaskAssignment => write!(f, "TaskAssignment"),
366            Self::PositionUpdate => write!(f, "PositionUpdate"),
367            Self::Heartbeat => write!(f, "Heartbeat"),
368            Self::SensorTelemetry => write!(f, "SensorTelemetry"),
369            Self::EnvironmentData => write!(f, "EnvironmentData"),
370            Self::ModelUpdate => write!(f, "ModelUpdate"),
371            Self::DebugLog => write!(f, "DebugLog"),
372            Self::HistoricalTrack => write!(f, "HistoricalTrack"),
373            Self::TrainingData => write!(f, "TrainingData"),
374            Self::Custom { name, .. } => write!(f, "Custom({})", name),
375        }
376    }
377}
378
379#[cfg(test)]
380mod tests {
381    use super::*;
382
383    #[test]
384    fn test_data_type_default_class() {
385        // P1 Critical
386        assert_eq!(DataType::ContactReport.default_class(), QoSClass::Critical);
387        assert_eq!(DataType::EmergencyAlert.default_class(), QoSClass::Critical);
388        assert_eq!(DataType::AbortCommand.default_class(), QoSClass::Critical);
389
390        // P2 High
391        assert_eq!(DataType::TargetImage.default_class(), QoSClass::High);
392        assert_eq!(DataType::MissionRetasking.default_class(), QoSClass::High);
393
394        // P3 Normal
395        assert_eq!(DataType::HealthStatus.default_class(), QoSClass::Normal);
396        assert_eq!(DataType::CapabilityChange.default_class(), QoSClass::Normal);
397
398        // P4 Low
399        assert_eq!(DataType::PositionUpdate.default_class(), QoSClass::Low);
400        assert_eq!(DataType::Heartbeat.default_class(), QoSClass::Low);
401
402        // P5 Bulk
403        assert_eq!(DataType::ModelUpdate.default_class(), QoSClass::Bulk);
404        assert_eq!(DataType::DebugLog.default_class(), QoSClass::Bulk);
405    }
406
407    #[test]
408    fn test_data_type_default_policy() {
409        let policy = DataType::ContactReport.default_policy();
410        assert_eq!(policy.base_class, QoSClass::Critical);
411        assert_eq!(policy.max_latency_ms, Some(500));
412        assert!(!policy.preemptable);
413
414        let policy = DataType::PositionUpdate.default_policy();
415        assert_eq!(policy.base_class, QoSClass::Low);
416        assert_eq!(policy.max_latency_ms, Some(300_000));
417        assert!(policy.preemptable);
418    }
419
420    #[test]
421    fn test_data_type_is_critical() {
422        assert!(DataType::ContactReport.is_critical());
423        assert!(DataType::EmergencyAlert.is_critical());
424        assert!(!DataType::HealthStatus.is_critical());
425        assert!(!DataType::DebugLog.is_critical());
426    }
427
428    #[test]
429    fn test_data_type_is_preemptable() {
430        assert!(!DataType::ContactReport.is_preemptable());
431        assert!(!DataType::AbortCommand.is_preemptable());
432        assert!(!DataType::MissionRetasking.is_preemptable()); // Commands complete
433        assert!(DataType::TargetImage.is_preemptable());
434        assert!(DataType::HealthStatus.is_preemptable());
435    }
436
437    #[test]
438    fn test_custom_data_type() {
439        let custom = DataType::Custom {
440            name: "MyType".to_string(),
441            qos_class: QoSClass::High,
442        };
443        assert_eq!(custom.default_class(), QoSClass::High);
444        assert_eq!(custom.to_string(), "Custom(MyType)");
445    }
446
447    #[test]
448    fn test_all_predefined_data_types() {
449        let all = DataType::all_predefined();
450        assert_eq!(all.len(), 20);
451
452        // Verify all types have valid policies
453        for dt in all {
454            let policy = dt.default_policy();
455            assert!(policy.retention_priority >= 1 && policy.retention_priority <= 5);
456        }
457    }
458
459    #[test]
460    fn test_data_type_serialization() {
461        let dt = DataType::ContactReport;
462        let json = serde_json::to_string(&dt).unwrap();
463        assert_eq!(json, "\"ContactReport\"");
464
465        let deserialized: DataType = serde_json::from_str(&json).unwrap();
466        assert_eq!(deserialized, DataType::ContactReport);
467    }
468
469    #[test]
470    fn test_model_update_large_size() {
471        // ML models can be large
472        let policy = DataType::ModelUpdate.default_policy();
473        assert_eq!(policy.max_size_bytes, Some(500 * 1024 * 1024)); // 500MB
474    }
475}