Skip to main content

elara_core/
class.rs

1//! Packet and event class definitions
2//!
3//! ELARA uses a class-based system for packet prioritization and handling:
4//! - Core: Identity, presence, session - never dropped
5//! - Perceptual: Voice, typing - loss tolerant, predictive
6//! - Enhancement: HD layers - opportunistic
7//! - Cosmetic: Reactions - discardable
8//! - Repair: State sync - delayed OK
9
10/// Packet class determines network and crypto behavior
11#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
12#[repr(u8)]
13pub enum PacketClass {
14    /// Core state: identity, presence, session, crypto
15    /// Network: redundant, never dropped
16    /// Crypto: strongest ratchet
17    #[default]
18    Core = 0x00,
19
20    /// Perceptual state: voice, typing, live updates
21    /// Network: predictive, loss tolerant
22    /// Crypto: fast ratchet
23    Perceptual = 0x01,
24
25    /// Enhancement state: HD layers, quality upgrades
26    /// Network: opportunistic, drop first
27    /// Crypto: standard ratchet
28    Enhancement = 0x02,
29
30    /// Cosmetic state: reactions, filters, UI hints
31    /// Network: discardable
32    /// Crypto: light ratchet
33    Cosmetic = 0x03,
34
35    /// Repair state: state summaries, resync
36    /// Network: bursty, delayed OK
37    /// Crypto: strong ratchet
38    Repair = 0x04,
39}
40
41impl PacketClass {
42    /// Parse from wire byte
43    pub fn from_byte(b: u8) -> Option<Self> {
44        match b {
45            0x00 => Some(PacketClass::Core),
46            0x01 => Some(PacketClass::Perceptual),
47            0x02 => Some(PacketClass::Enhancement),
48            0x03 => Some(PacketClass::Cosmetic),
49            0x04 => Some(PacketClass::Repair),
50            _ => None,
51        }
52    }
53
54    /// Convert to wire byte
55    #[inline]
56    pub fn to_byte(self) -> u8 {
57        self as u8
58    }
59
60    /// Get redundancy level (how many times to send)
61    pub fn redundancy(self) -> u8 {
62        match self {
63            PacketClass::Core => 3,
64            PacketClass::Perceptual => 1,
65            PacketClass::Enhancement => 1,
66            PacketClass::Cosmetic => 1,
67            PacketClass::Repair => 2,
68        }
69    }
70
71    /// Can this class be dropped under congestion?
72    pub fn is_droppable(self) -> bool {
73        match self {
74            PacketClass::Core => false,
75            PacketClass::Perceptual => false, // Predict instead
76            PacketClass::Enhancement => true,
77            PacketClass::Cosmetic => true,
78            PacketClass::Repair => false,
79        }
80    }
81
82    /// Priority for scheduling (lower = higher priority)
83    pub fn priority(self) -> u8 {
84        match self {
85            PacketClass::Core => 0,
86            PacketClass::Perceptual => 1,
87            PacketClass::Repair => 2,
88            PacketClass::Enhancement => 3,
89            PacketClass::Cosmetic => 4,
90        }
91    }
92
93    /// Ratchet frequency (messages between ratchet advances)
94    pub fn ratchet_frequency(self) -> u32 {
95        match self {
96            PacketClass::Core => 100,
97            PacketClass::Perceptual => 1000,
98            PacketClass::Enhancement => 500,
99            PacketClass::Cosmetic => 2000,
100            PacketClass::Repair => 50,
101        }
102    }
103
104    /// Replay window size
105    pub fn replay_window_size(self) -> u16 {
106        match self {
107            PacketClass::Core => 64,
108            PacketClass::Perceptual => 256, // Larger for reorder tolerance
109            PacketClass::Enhancement => 128,
110            PacketClass::Cosmetic => 32,
111            PacketClass::Repair => 32,
112        }
113    }
114}
115
116/// Representation profile hint
117#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
118#[repr(u8)]
119pub enum RepresentationProfile {
120    /// Text-based communication
121    #[default]
122    Textual = 0x00,
123
124    /// Minimal voice (MSP baseline)
125    VoiceMinimal = 0x10,
126    /// Standard voice quality
127    VoiceStandard = 0x11,
128    /// High quality voice
129    VoiceHigh = 0x12,
130
131    /// Low quality video
132    VideoLow = 0x20,
133    /// Standard video
134    VideoStandard = 0x21,
135    /// High quality video
136    VideoHigh = 0x22,
137
138    /// Asymmetric streaming (livestream)
139    StreamAsymmetric = 0x30,
140
141    /// AI agent presence
142    Agent = 0x40,
143
144    /// Unknown/custom profile
145    Custom = 0xFF,
146}
147
148impl RepresentationProfile {
149    pub fn from_byte(b: u8) -> Self {
150        match b {
151            0x00 => RepresentationProfile::Textual,
152            0x10 => RepresentationProfile::VoiceMinimal,
153            0x11 => RepresentationProfile::VoiceStandard,
154            0x12 => RepresentationProfile::VoiceHigh,
155            0x20 => RepresentationProfile::VideoLow,
156            0x21 => RepresentationProfile::VideoStandard,
157            0x22 => RepresentationProfile::VideoHigh,
158            0x30 => RepresentationProfile::StreamAsymmetric,
159            0x40 => RepresentationProfile::Agent,
160            _ => RepresentationProfile::Custom,
161        }
162    }
163
164    #[inline]
165    pub fn to_byte(self) -> u8 {
166        self as u8
167    }
168
169    /// Is this profile supported in MSP (Minimal Survival Profile)?
170    pub fn is_msp_supported(self) -> bool {
171        matches!(
172            self,
173            RepresentationProfile::Textual | RepresentationProfile::VoiceMinimal
174        )
175    }
176}
177
178/// State type classification
179#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
180pub enum StateType {
181    /// Core state: never lost, strongest guarantees
182    Core,
183    /// Perceptual state: loss tolerant, predictable
184    Perceptual,
185    /// Enhancement state: opportunistic delivery
186    Enhancement,
187    /// Cosmetic state: freely discardable
188    Cosmetic,
189}
190
191impl StateType {
192    /// Convert to packet class for transmission
193    pub fn to_packet_class(self) -> PacketClass {
194        match self {
195            StateType::Core => PacketClass::Core,
196            StateType::Perceptual => PacketClass::Perceptual,
197            StateType::Enhancement => PacketClass::Enhancement,
198            StateType::Cosmetic => PacketClass::Cosmetic,
199        }
200    }
201
202    /// Importance weight for divergence control
203    pub fn importance(self) -> f64 {
204        match self {
205            StateType::Core => 1.0,
206            StateType::Perceptual => 0.7,
207            StateType::Enhancement => 0.3,
208            StateType::Cosmetic => 0.1,
209        }
210    }
211}
212
213#[cfg(test)]
214mod tests {
215    use super::*;
216
217    #[test]
218    fn test_packet_class_roundtrip() {
219        for class in [
220            PacketClass::Core,
221            PacketClass::Perceptual,
222            PacketClass::Enhancement,
223            PacketClass::Cosmetic,
224            PacketClass::Repair,
225        ] {
226            let byte = class.to_byte();
227            let recovered = PacketClass::from_byte(byte).unwrap();
228            assert_eq!(class, recovered);
229        }
230    }
231
232    #[test]
233    fn test_profile_msp_support() {
234        assert!(RepresentationProfile::Textual.is_msp_supported());
235        assert!(RepresentationProfile::VoiceMinimal.is_msp_supported());
236        assert!(!RepresentationProfile::VideoStandard.is_msp_supported());
237    }
238
239    #[test]
240    fn test_class_priority_ordering() {
241        assert!(PacketClass::Core.priority() < PacketClass::Perceptual.priority());
242        assert!(PacketClass::Perceptual.priority() < PacketClass::Enhancement.priority());
243        assert!(PacketClass::Enhancement.priority() < PacketClass::Cosmetic.priority());
244    }
245}