Skip to main content

agent_sim/sim/
types.rs

1use serde::{Deserialize, Serialize};
2use std::ffi::c_char;
3use thiserror::Error;
4
5#[repr(u32)]
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum SimStatusRaw {
8    Ok = 0,
9    NotInitialized = 1,
10    InvalidArg = 2,
11    InvalidSignal = 3,
12    TypeMismatch = 4,
13    BufferTooSmall = 5,
14    Internal = 255,
15}
16
17impl TryFrom<u32> for SimStatusRaw {
18    type Error = ();
19
20    fn try_from(value: u32) -> Result<Self, Self::Error> {
21        match value {
22            0 => Ok(Self::Ok),
23            1 => Ok(Self::NotInitialized),
24            2 => Ok(Self::InvalidArg),
25            3 => Ok(Self::InvalidSignal),
26            4 => Ok(Self::TypeMismatch),
27            5 => Ok(Self::BufferTooSmall),
28            255 => Ok(Self::Internal),
29            _ => Err(()),
30        }
31    }
32}
33
34#[repr(u32)]
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum SimTypeRaw {
37    Bool = 0,
38    U32 = 1,
39    I32 = 2,
40    F32 = 3,
41    F64 = 4,
42}
43
44impl TryFrom<u32> for SimTypeRaw {
45    type Error = ();
46
47    fn try_from(value: u32) -> Result<Self, Self::Error> {
48        match value {
49            0 => Ok(Self::Bool),
50            1 => Ok(Self::U32),
51            2 => Ok(Self::I32),
52            3 => Ok(Self::F32),
53            4 => Ok(Self::F64),
54            _ => Err(()),
55        }
56    }
57}
58
59#[repr(C)]
60#[derive(Clone, Copy)]
61pub union SimValueDataRaw {
62    pub b: bool,
63    pub u32: u32,
64    pub i32: i32,
65    pub f32: f32,
66    pub f64: f64,
67}
68
69#[repr(C)]
70#[derive(Clone, Copy)]
71pub struct SimValueRaw {
72    pub signal_type: u32,
73    pub data: SimValueDataRaw,
74}
75
76#[repr(C)]
77#[derive(Clone, Copy)]
78pub struct SimSignalDescRaw {
79    pub id: u32,
80    pub name: *const c_char,
81    pub signal_type: u32,
82    pub units: *const c_char,
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
86#[serde(rename_all = "snake_case")]
87pub enum SignalType {
88    Bool,
89    U32,
90    I32,
91    F32,
92    F64,
93}
94
95impl SignalType {
96    pub fn parse(value: &str) -> Option<Self> {
97        match value {
98            "bool" => Some(Self::Bool),
99            "u32" => Some(Self::U32),
100            "i32" => Some(Self::I32),
101            "f32" => Some(Self::F32),
102            "f64" => Some(Self::F64),
103            _ => None,
104        }
105    }
106}
107
108impl std::fmt::Display for SignalType {
109    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110        let value = match self {
111            Self::Bool => "bool",
112            Self::U32 => "u32",
113            Self::I32 => "i32",
114            Self::F32 => "f32",
115            Self::F64 => "f64",
116        };
117        write!(f, "{value}")
118    }
119}
120
121impl TryFrom<u32> for SignalType {
122    type Error = ();
123
124    fn try_from(value: u32) -> Result<Self, Self::Error> {
125        Ok(match SimTypeRaw::try_from(value)? {
126            SimTypeRaw::Bool => SignalType::Bool,
127            SimTypeRaw::U32 => SignalType::U32,
128            SimTypeRaw::I32 => SignalType::I32,
129            SimTypeRaw::F32 => SignalType::F32,
130            SimTypeRaw::F64 => SignalType::F64,
131        })
132    }
133}
134
135#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
136#[serde(untagged)]
137pub enum SignalValue {
138    Bool(bool),
139    U32(u32),
140    I32(i32),
141    F32(f32),
142    F64(f64),
143}
144
145impl SignalValue {
146    pub fn signal_type(&self) -> SignalType {
147        match self {
148            Self::Bool(_) => SignalType::Bool,
149            Self::U32(_) => SignalType::U32,
150            Self::I32(_) => SignalType::I32,
151            Self::F32(_) => SignalType::F32,
152            Self::F64(_) => SignalType::F64,
153        }
154    }
155
156    pub fn to_raw(&self) -> SimValueRaw {
157        match self {
158            Self::Bool(v) => SimValueRaw {
159                signal_type: SimTypeRaw::Bool as u32,
160                data: SimValueDataRaw { b: *v },
161            },
162            Self::U32(v) => SimValueRaw {
163                signal_type: SimTypeRaw::U32 as u32,
164                data: SimValueDataRaw { u32: *v },
165            },
166            Self::I32(v) => SimValueRaw {
167                signal_type: SimTypeRaw::I32 as u32,
168                data: SimValueDataRaw { i32: *v },
169            },
170            Self::F32(v) => SimValueRaw {
171                signal_type: SimTypeRaw::F32 as u32,
172                data: SimValueDataRaw { f32: *v },
173            },
174            Self::F64(v) => SimValueRaw {
175                signal_type: SimTypeRaw::F64 as u32,
176                data: SimValueDataRaw { f64: *v },
177            },
178        }
179    }
180
181    /// # Safety
182    ///
183    /// The caller must ensure `raw` originated from a valid `SimValue`
184    /// representation using the `sim_api.h` ABI contract. If `signal_type`
185    /// does not match the active union field, behavior is undefined.
186    pub unsafe fn from_raw(raw: SimValueRaw) -> Option<Self> {
187        match SimTypeRaw::try_from(raw.signal_type).ok()? {
188            SimTypeRaw::Bool => Some(Self::Bool(unsafe { raw.data.b })),
189            SimTypeRaw::U32 => Some(Self::U32(unsafe { raw.data.u32 })),
190            SimTypeRaw::I32 => Some(Self::I32(unsafe { raw.data.i32 })),
191            SimTypeRaw::F32 => Some(Self::F32(unsafe { raw.data.f32 })),
192            SimTypeRaw::F64 => Some(Self::F64(unsafe { raw.data.f64 })),
193        }
194    }
195}
196
197#[derive(Debug, Clone)]
198pub struct SignalMeta {
199    pub id: u32,
200    pub name: String,
201    pub signal_type: SignalType,
202    pub units: Option<String>,
203}
204
205#[repr(C)]
206#[derive(Clone, Copy, Debug)]
207pub struct SimCanFrameRaw {
208    pub arb_id: u32,
209    pub len: u8,
210    pub flags: u8,
211    pub _pad: [u8; 2],
212    pub data: [u8; 64],
213}
214
215#[repr(C)]
216#[derive(Clone, Copy, Debug)]
217pub struct SimCanBusDescRaw {
218    pub id: u32,
219    pub name: *const c_char,
220    pub bitrate: u32,
221    pub bitrate_data: u32,
222    pub flags: u8,
223    pub _pad: [u8; 3],
224}
225
226pub const CAN_FLAG_EXTENDED: u8 = 1 << 0;
227pub const CAN_FLAG_FD: u8 = 1 << 1;
228pub const CAN_FLAG_BRS: u8 = 1 << 2;
229pub const CAN_FLAG_ESI: u8 = 1 << 3;
230pub const CAN_FLAG_RTR: u8 = 1 << 4;
231pub const CAN_FLAG_RESERVED_MASK: u8 = 0b1110_0000;
232
233#[derive(Debug, Clone, PartialEq, Eq)]
234pub struct SimCanFrame {
235    pub arb_id: u32,
236    pub len: u8,
237    pub flags: u8,
238    pub data: [u8; 64],
239}
240
241impl SimCanFrame {
242    pub fn to_raw(&self) -> SimCanFrameRaw {
243        SimCanFrameRaw {
244            arb_id: self.arb_id,
245            len: self.len,
246            flags: self.flags,
247            _pad: [0, 0],
248            data: self.data,
249        }
250    }
251
252    pub fn from_raw(raw: SimCanFrameRaw) -> Self {
253        Self {
254            arb_id: raw.arb_id,
255            len: raw.len,
256            flags: raw.flags,
257            data: raw.data,
258        }
259    }
260
261    pub fn payload(&self) -> &[u8] {
262        let len = usize::from(self.len.min(64));
263        &self.data[..len]
264    }
265}
266
267#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
268pub struct SimCanBusDesc {
269    pub id: u32,
270    pub name: String,
271    pub bitrate: u32,
272    pub bitrate_data: u32,
273    pub fd_capable: bool,
274}
275
276#[repr(C)]
277#[derive(Clone, Copy, Debug)]
278pub struct SimSharedDescRaw {
279    pub id: u32,
280    pub name: *const c_char,
281    pub slot_count: u32,
282}
283
284#[repr(C)]
285#[derive(Clone, Copy)]
286pub struct SimSharedSlotRaw {
287    pub slot_id: u32,
288    pub signal_type: u32,
289    pub value: SimValueRaw,
290}
291
292impl Default for SimSharedSlotRaw {
293    fn default() -> Self {
294        let value = SignalValue::F64(0.0).to_raw();
295        Self {
296            slot_id: 0,
297            signal_type: SimTypeRaw::F64 as u32,
298            value,
299        }
300    }
301}
302
303#[derive(Debug, Clone, PartialEq, Eq)]
304pub struct SimSharedDesc {
305    pub id: u32,
306    pub name: String,
307    pub slot_count: u32,
308}
309
310#[derive(Debug, Clone, PartialEq)]
311pub struct SimSharedSlot {
312    pub slot_id: u32,
313    pub value: SignalValue,
314}
315
316#[derive(Debug, Clone, PartialEq, Eq, Error)]
317pub enum SharedSlotDecodeError {
318    #[error("shared slot {slot_id} uses invalid type tag {signal_type}")]
319    InvalidTypeTag { slot_id: u32, signal_type: u32 },
320    #[error("shared slot {slot_id} has mismatched type tags: slot={slot_type} value={value_type}")]
321    MismatchedTypeTags {
322        slot_id: u32,
323        slot_type: u32,
324        value_type: u32,
325    },
326}
327
328impl SimSharedSlot {
329    pub fn to_raw(&self) -> SimSharedSlotRaw {
330        let raw = self.value.to_raw();
331        SimSharedSlotRaw {
332            slot_id: self.slot_id,
333            signal_type: raw.signal_type,
334            value: raw,
335        }
336    }
337
338    pub fn try_from_raw(raw: SimSharedSlotRaw) -> Result<Self, SharedSlotDecodeError> {
339        let slot_type = SimTypeRaw::try_from(raw.signal_type).map_err(|_| {
340            SharedSlotDecodeError::InvalidTypeTag {
341                slot_id: raw.slot_id,
342                signal_type: raw.signal_type,
343            }
344        })?;
345        let value_type = SimTypeRaw::try_from(raw.value.signal_type).map_err(|_| {
346            SharedSlotDecodeError::InvalidTypeTag {
347                slot_id: raw.slot_id,
348                signal_type: raw.value.signal_type,
349            }
350        })?;
351
352        if slot_type != value_type {
353            return Err(SharedSlotDecodeError::MismatchedTypeTags {
354                slot_id: raw.slot_id,
355                slot_type: raw.signal_type,
356                value_type: raw.value.signal_type,
357            });
358        }
359
360        Ok(Self {
361            slot_id: raw.slot_id,
362            value: unsafe { SignalValue::from_raw(raw.value) }.expect("validated type tag"),
363        })
364    }
365}
366
367#[cfg(test)]
368mod tests {
369    use super::{SharedSlotDecodeError, SignalValue, SimSharedSlot, SimSharedSlotRaw, SimTypeRaw};
370
371    #[test]
372    fn shared_slot_decoding_rejects_mismatched_type_tags() {
373        let raw = SimSharedSlotRaw {
374            slot_id: 1,
375            signal_type: SimTypeRaw::Bool as u32,
376            value: SignalValue::F32(1.5).to_raw(),
377        };
378
379        let err = SimSharedSlot::try_from_raw(raw).expect_err("mismatched tags must fail");
380        assert_eq!(
381            err,
382            SharedSlotDecodeError::MismatchedTypeTags {
383                slot_id: 1,
384                slot_type: SimTypeRaw::Bool as u32,
385                value_type: SimTypeRaw::F32 as u32,
386            }
387        );
388    }
389
390    #[test]
391    fn shared_slot_decoding_accepts_matching_tags() {
392        let raw = SimSharedSlot {
393            slot_id: 3,
394            value: SignalValue::U32(42),
395        }
396        .to_raw();
397
398        let slot = SimSharedSlot::try_from_raw(raw).expect("matching tags must decode");
399        assert_eq!(slot.slot_id, 3);
400        assert_eq!(slot.value, SignalValue::U32(42));
401    }
402}