1use crate::load::ResolvedFlashRegion;
2use crate::sim::error::{ProjectError, SimError};
3use crate::sim::project_loader::{decode_owned_cstr, next_capacity, validate_written};
4use crate::sim::types::{
5 SignalMeta, SignalType, SignalValue, SimCanBusDesc, SimCanBusDescRaw, SimCanFrame,
6 SimCanFrameRaw, SimSharedDesc, SimSharedDescRaw, SimSharedSlot, SimSharedSlotRaw,
7 SimSignalDescRaw, SimValueRaw,
8};
9use crate::sim::validation::{
10 validate_can_metadata, validate_shared_metadata, validate_signal_metadata,
11};
12use libloading::Library;
13use std::collections::HashMap;
14use std::path::{Path, PathBuf};
15
16type SimInitFn = unsafe extern "C" fn() -> u32;
17type SimResetFn = unsafe extern "C" fn() -> u32;
18type SimTickFn = unsafe extern "C" fn() -> u32;
19type SimReadValFn = unsafe extern "C" fn(u32, *mut SimValueRaw) -> u32;
20type SimWriteValFn = unsafe extern "C" fn(u32, *const SimValueRaw) -> u32;
21type SimGetSignalCountFn = unsafe extern "C" fn(*mut u32) -> u32;
22type SimGetSignalsFn = unsafe extern "C" fn(*mut SimSignalDescRaw, u32, *mut u32) -> u32;
23type SimGetApiVersionFn = unsafe extern "C" fn(*mut u32, *mut u32) -> u32;
24type SimGetTickDurationUsFn = unsafe extern "C" fn(*mut u32) -> u32;
25type SimFlashWriteFn = unsafe extern "C" fn(u32, *const u8, u32) -> u32;
26type SimCanGetBusesFn = unsafe extern "C" fn(*mut SimCanBusDescRaw, u32, *mut u32) -> u32;
27type SimCanRxFn = unsafe extern "C" fn(u32, *const SimCanFrameRaw, u32) -> u32;
28type SimCanTxFn = unsafe extern "C" fn(u32, *mut SimCanFrameRaw, u32, *mut u32) -> u32;
29type SimSharedGetChannelsFn = unsafe extern "C" fn(*mut SimSharedDescRaw, u32, *mut u32) -> u32;
30type SimSharedReadFn =
31 unsafe extern "C" fn(u32, *const crate::sim::types::SimSharedSlotRaw, u32) -> u32;
32type SimSharedWriteFn =
33 unsafe extern "C" fn(u32, *mut crate::sim::types::SimSharedSlotRaw, u32, *mut u32) -> u32;
34
35const STATUS_OK: u32 = 0;
36const STATUS_NOT_INITIALIZED: u32 = 1;
37const STATUS_INVALID_SIGNAL: u32 = 3;
38const STATUS_TYPE_MISMATCH: u32 = 4;
39const STATUS_BUFFER_TOO_SMALL: u32 = 5;
40const SUPPORTED_API_VERSION_MAJOR: u32 = 2;
41const SUPPORTED_API_VERSION_MINOR: u32 = 0;
42
43struct ProjectCanApi {
44 sim_can_rx: SimCanRxFn,
45 sim_can_tx: SimCanTxFn,
46}
47
48struct ProjectSharedApi {
49 sim_shared_read: SimSharedReadFn,
50 sim_shared_write: SimSharedWriteFn,
51}
52
53pub struct Project {
54 pub libpath: PathBuf,
55 tick_duration_us: u32,
56 signals: Vec<SignalMeta>,
57 can_buses: Vec<SimCanBusDesc>,
58 shared_channels: Vec<SimSharedDesc>,
59 signal_name_to_id: HashMap<String, u32>,
60 signal_id_to_index: HashMap<u32, usize>,
61 sim_reset: SimResetFn,
62 sim_tick: SimTickFn,
63 sim_read_val: SimReadValFn,
64 sim_write_val: SimWriteValFn,
65 _sim_get_signal_count: SimGetSignalCountFn,
66 _sim_get_signals: SimGetSignalsFn,
67 _sim_get_tick_duration_us: SimGetTickDurationUsFn,
68 can_api: Option<ProjectCanApi>,
69 shared_api: Option<ProjectSharedApi>,
70 _library: Library,
71}
72
73impl Project {
74 pub fn load(
75 libpath: impl AsRef<Path>,
76 flash_regions: &[ResolvedFlashRegion],
77 ) -> Result<Self, ProjectError> {
78 let path = libpath.as_ref().to_path_buf();
79 let library =
80 unsafe { Library::new(&path) }.map_err(|e| ProjectError::LibraryLoad(e.to_string()))?;
81
82 let sim_init: SimInitFn = *unsafe { library.get::<SimInitFn>(b"sim_init\0") }
83 .map_err(|_| ProjectError::MissingSymbol("sim_init"))?;
84 let sim_reset: SimResetFn = *unsafe { library.get::<SimResetFn>(b"sim_reset\0") }
85 .map_err(|_| ProjectError::MissingSymbol("sim_reset"))?;
86 let sim_tick: SimTickFn = *unsafe { library.get::<SimTickFn>(b"sim_tick\0") }
87 .map_err(|_| ProjectError::MissingSymbol("sim_tick"))?;
88 let sim_read_val: SimReadValFn = *unsafe { library.get::<SimReadValFn>(b"sim_read_val\0") }
89 .map_err(|_| ProjectError::MissingSymbol("sim_read_val"))?;
90 let sim_write_val: SimWriteValFn =
91 *unsafe { library.get::<SimWriteValFn>(b"sim_write_val\0") }
92 .map_err(|_| ProjectError::MissingSymbol("sim_write_val"))?;
93 let sim_get_signal_count: SimGetSignalCountFn =
94 *unsafe { library.get::<SimGetSignalCountFn>(b"sim_get_signal_count\0") }
95 .map_err(|_| ProjectError::MissingSymbol("sim_get_signal_count"))?;
96 let sim_get_signals: SimGetSignalsFn =
97 *unsafe { library.get::<SimGetSignalsFn>(b"sim_get_signals\0") }
98 .map_err(|_| ProjectError::MissingSymbol("sim_get_signals"))?;
99 let sim_get_api_version: SimGetApiVersionFn =
100 *unsafe { library.get::<SimGetApiVersionFn>(b"sim_get_api_version\0") }
101 .map_err(|_| ProjectError::MissingSymbol("sim_get_api_version"))?;
102 let sim_get_tick_duration_us: SimGetTickDurationUsFn =
103 *unsafe { library.get::<SimGetTickDurationUsFn>(b"sim_get_tick_duration_us\0") }
104 .map_err(|_| ProjectError::MissingSymbol("sim_get_tick_duration_us"))?;
105 let sim_flash_write = unsafe { library.get::<SimFlashWriteFn>(b"sim_flash_write\0") }
106 .ok()
107 .map(|symbol| *symbol);
108 let sim_can_get_buses = unsafe { library.get::<SimCanGetBusesFn>(b"sim_can_get_buses\0") }
109 .ok()
110 .map(|symbol| *symbol);
111 let sim_can_rx = unsafe { library.get::<SimCanRxFn>(b"sim_can_rx\0") }
112 .ok()
113 .map(|symbol| *symbol);
114 let sim_can_tx = unsafe { library.get::<SimCanTxFn>(b"sim_can_tx\0") }
115 .ok()
116 .map(|symbol| *symbol);
117 let sim_shared_get_channels =
118 unsafe { library.get::<SimSharedGetChannelsFn>(b"sim_shared_get_channels\0") }
119 .ok()
120 .map(|symbol| *symbol);
121 let sim_shared_read = unsafe { library.get::<SimSharedReadFn>(b"sim_shared_read\0") }
122 .ok()
123 .map(|symbol| *symbol);
124 let sim_shared_write = unsafe { library.get::<SimSharedWriteFn>(b"sim_shared_write\0") }
125 .ok()
126 .map(|symbol| *symbol);
127
128 {
129 let mut major = 0_u32;
130 let mut minor = 0_u32;
131 let status =
132 unsafe { sim_get_api_version(&mut major as *mut u32, &mut minor as *mut u32) };
133 if status != STATUS_OK {
134 return Err(ProjectError::LibraryLoad(format!(
135 "sim_get_api_version failed with status {status}"
136 )));
137 }
138 if major != SUPPORTED_API_VERSION_MAJOR || minor != SUPPORTED_API_VERSION_MINOR {
139 return Err(ProjectError::LibraryLoad(format!(
140 "ABI version mismatch: project reports {major}.{minor}, runtime requires {}.{}",
141 SUPPORTED_API_VERSION_MAJOR, SUPPORTED_API_VERSION_MINOR
142 )));
143 }
144 }
145
146 let tick_duration_us = {
147 let mut value = 0_u32;
148 let status = unsafe { sim_get_tick_duration_us(&mut value as *mut u32) };
149 if status != STATUS_OK {
150 return Err(ProjectError::LibraryLoad(format!(
151 "sim_get_tick_duration_us failed with status {status}"
152 )));
153 }
154 value
155 };
156
157 let signals = {
158 let mut count = 0_u32;
159 let status = unsafe { sim_get_signal_count(&mut count as *mut u32) };
160 if status != STATUS_OK {
161 return Err(ProjectError::LibraryLoad(format!(
162 "sim_get_signal_count failed with status {status}"
163 )));
164 }
165
166 let mut capacity = count.max(1);
167 loop {
168 let mut raw = vec![
169 SimSignalDescRaw {
170 id: 0,
171 name: std::ptr::null(),
172 signal_type: 0,
173 units: std::ptr::null(),
174 };
175 capacity as usize
176 ];
177 let mut written = 0_u32;
178 let status = unsafe {
179 sim_get_signals(raw.as_mut_ptr(), capacity, &mut written as *mut u32)
180 };
181 if status == STATUS_BUFFER_TOO_SMALL {
182 capacity = next_capacity(capacity, "sim_get_signals")
183 .map_err(ProjectError::FfiContract)?;
184 continue;
185 }
186 if status != STATUS_OK {
187 return Err(ProjectError::LibraryLoad(format!(
188 "sim_get_signals failed with status {status}"
189 )));
190 }
191 raw.truncate(
192 validate_written(written, capacity, "sim_get_signals")
193 .map_err(ProjectError::FfiContract)?,
194 );
195 break raw
196 .into_iter()
197 .map(|entry| {
198 let name = decode_owned_cstr(entry.name, "signal name")
199 .map_err(ProjectError::InvalidSignalMetadata)?;
200 let units = if entry.units.is_null() {
201 None
202 } else {
203 Some(
204 decode_owned_cstr(entry.units, "signal units")
205 .map_err(ProjectError::InvalidSignalMetadata)?,
206 )
207 };
208 let signal_type =
209 SignalType::try_from(entry.signal_type).map_err(|_| {
210 ProjectError::InvalidSignalMetadata(format!(
211 "signal '{}' uses invalid type tag {}",
212 name, entry.signal_type
213 ))
214 })?;
215 Ok(SignalMeta {
216 id: entry.id,
217 name,
218 signal_type,
219 units,
220 })
221 })
222 .collect::<Result<Vec<_>, _>>()?;
223 }
224 };
225
226 if !flash_regions.is_empty() {
227 let flash_write = sim_flash_write.ok_or_else(|| {
228 ProjectError::Flash(
229 "flash regions were configured, but the project does not export sim_flash_write"
230 .to_string(),
231 )
232 })?;
233 for region in flash_regions {
234 let len = u32::try_from(region.data.len()).map_err(|_| {
235 ProjectError::Flash(format!(
236 "flash region at 0x{:08X} is too large ({} bytes)",
237 region.base_addr,
238 region.data.len()
239 ))
240 })?;
241 let status = unsafe { flash_write(region.base_addr, region.data.as_ptr(), len) };
242 if status != STATUS_OK {
243 return Err(ProjectError::Flash(format!(
244 "sim_flash_write failed for region 0x{:08X} ({} bytes) with status {}",
245 region.base_addr,
246 region.data.len(),
247 status
248 )));
249 }
250 }
251 }
252
253 let init_status = unsafe { sim_init() };
254 if init_status != STATUS_OK {
255 return Err(ProjectError::LibraryLoad(format!(
256 "sim_init failed with status {init_status}"
257 )));
258 }
259
260 let (can_api, can_buses) = match (sim_can_get_buses, sim_can_rx, sim_can_tx) {
261 (None, None, None) => (None, Vec::new()),
262 (Some(get_buses), Some(can_rx), Some(can_tx)) => (
263 Some(ProjectCanApi {
264 sim_can_rx: can_rx,
265 sim_can_tx: can_tx,
266 }),
267 Self::load_can_buses(get_buses)?,
268 ),
269 _ => {
270 return Err(ProjectError::InvalidCanExports(
271 "if any CAN symbol is exported, sim_can_get_buses/sim_can_rx/sim_can_tx must all be exported"
272 .to_string(),
273 ));
274 }
275 };
276
277 let (shared_api, shared_channels) = match (
278 sim_shared_get_channels,
279 sim_shared_read,
280 sim_shared_write,
281 ) {
282 (None, None, None) => (None, Vec::new()),
283 (Some(get_channels), Some(shared_read), Some(shared_write)) => (
284 Some(ProjectSharedApi {
285 sim_shared_read: shared_read,
286 sim_shared_write: shared_write,
287 }),
288 Self::load_shared_channels(get_channels)?,
289 ),
290 _ => {
291 return Err(ProjectError::InvalidSharedExports(
292 "if any shared-state symbol is exported, sim_shared_get_channels/sim_shared_read/sim_shared_write must all be exported"
293 .to_string(),
294 ));
295 }
296 };
297 validate_signal_metadata(&signals)?;
298 validate_can_metadata(&can_buses)?;
299 validate_shared_metadata(&shared_channels)?;
300
301 let signal_name_to_id = signals
302 .iter()
303 .map(|s| (s.name.clone(), s.id))
304 .collect::<HashMap<_, _>>();
305 let signal_id_to_index = signals
306 .iter()
307 .enumerate()
308 .map(|(idx, s)| (s.id, idx))
309 .collect::<HashMap<_, _>>();
310
311 Ok(Self {
312 libpath: path,
313 tick_duration_us,
314 signals,
315 can_buses,
316 shared_channels,
317 signal_name_to_id,
318 signal_id_to_index,
319 sim_reset,
320 sim_tick,
321 sim_read_val,
322 sim_write_val,
323 _sim_get_signal_count: sim_get_signal_count,
324 _sim_get_signals: sim_get_signals,
325 _sim_get_tick_duration_us: sim_get_tick_duration_us,
326 can_api,
327 shared_api,
328 _library: library,
329 })
330 }
331
332 pub fn tick_duration_us(&self) -> u32 {
333 self.tick_duration_us
334 }
335
336 pub fn signals(&self) -> &[SignalMeta] {
337 &self.signals
338 }
339
340 pub fn can_buses(&self) -> &[SimCanBusDesc] {
341 &self.can_buses
342 }
343
344 pub fn shared_channels(&self) -> &[SimSharedDesc] {
345 &self.shared_channels
346 }
347
348 pub fn signal_by_id(&self, id: u32) -> Option<&SignalMeta> {
349 self.signal_id_to_index
350 .get(&id)
351 .and_then(|idx| self.signals.get(*idx))
352 }
353
354 pub fn signal_id_by_name(&self, name: &str) -> Option<u32> {
355 self.signal_name_to_id.get(name).copied()
356 }
357
358 fn shared_channel_by_id(&self, channel_id: u32) -> Option<&SimSharedDesc> {
359 self.shared_channels
360 .iter()
361 .find(|channel| channel.id == channel_id)
362 }
363
364 pub(crate) fn reset(&self) -> Result<(), SimError> {
365 self.map_status(unsafe { (self.sim_reset)() }, None, None)
366 }
367
368 pub(crate) fn tick(&self) -> Result<(), SimError> {
369 self.map_status(unsafe { (self.sim_tick)() }, None, None)
370 }
371
372 pub(crate) fn can_rx(&self, bus_id: u32, frames: &[SimCanFrame]) -> Result<(), SimError> {
373 let Some(can_api) = &self.can_api else {
374 return Ok(());
375 };
376 if frames.is_empty() {
377 return Ok(());
378 }
379 let raw_frames = frames.iter().map(SimCanFrame::to_raw).collect::<Vec<_>>();
380 let status =
381 unsafe { (can_api.sim_can_rx)(bus_id, raw_frames.as_ptr(), raw_frames.len() as u32) };
382 self.map_status(status, None, None)
383 }
384
385 pub(crate) fn can_tx(&self, bus_id: u32) -> Result<Vec<SimCanFrame>, SimError> {
386 let Some(can_api) = &self.can_api else {
387 return Ok(Vec::new());
388 };
389 let mut out = Vec::new();
390 let mut capacity = 32_u32;
391 loop {
392 let mut raw_frames = vec![
393 SimCanFrameRaw {
394 arb_id: 0,
395 len: 0,
396 flags: 0,
397 _pad: [0, 0],
398 data: [0; 64],
399 };
400 capacity as usize
401 ];
402 let mut written = 0_u32;
403 let status = unsafe {
404 (can_api.sim_can_tx)(
405 bus_id,
406 raw_frames.as_mut_ptr(),
407 capacity,
408 &mut written as *mut u32,
409 )
410 };
411 if status != STATUS_OK && status != STATUS_BUFFER_TOO_SMALL {
412 self.map_status(status, None, None)?;
413 break;
414 }
415 raw_frames.truncate(
416 validate_written(written, capacity, "sim_can_tx").map_err(SimError::FfiContract)?,
417 );
418 out.extend(raw_frames.into_iter().map(SimCanFrame::from_raw));
419 if status == STATUS_BUFFER_TOO_SMALL {
420 capacity = next_capacity(capacity, "sim_can_tx").map_err(SimError::FfiContract)?;
421 continue;
422 }
423 break;
424 }
425 Ok(out)
426 }
427
428 pub(crate) fn shared_read(
429 &self,
430 channel_id: u32,
431 slots: &[SimSharedSlot],
432 ) -> Result<(), SimError> {
433 let Some(shared_api) = &self.shared_api else {
434 return Ok(());
435 };
436 let expected_slot_count = self
437 .shared_channel_by_id(channel_id)
438 .ok_or_else(|| {
439 SimError::FfiContract(format!("unknown shared channel id {channel_id}"))
440 })?
441 .slot_count as usize;
442 validate_dense_shared_snapshot(slots, expected_slot_count, "sim_shared_read")?;
443 let raw_slots = slots.iter().map(SimSharedSlot::to_raw).collect::<Vec<_>>();
444 let status = unsafe {
445 (shared_api.sim_shared_read)(channel_id, raw_slots.as_ptr(), raw_slots.len() as u32)
446 };
447 self.map_status(status, None, None)
448 }
449
450 pub(crate) fn shared_write(&self, channel_id: u32) -> Result<Vec<SimSharedSlot>, SimError> {
451 let Some(shared_api) = &self.shared_api else {
452 return Ok(Vec::new());
453 };
454 let expected_slot_count = self
455 .shared_channel_by_id(channel_id)
456 .ok_or_else(|| {
457 SimError::FfiContract(format!("unknown shared channel id {channel_id}"))
458 })?
459 .slot_count;
460 let capacity = expected_slot_count.max(1);
461 let mut raw_slots = vec![SimSharedSlotRaw::default(); capacity as usize];
462 let mut written = 0_u32;
463 let status = unsafe {
464 (shared_api.sim_shared_write)(
465 channel_id,
466 raw_slots.as_mut_ptr(),
467 capacity,
468 &mut written as *mut u32,
469 )
470 };
471 if status == STATUS_BUFFER_TOO_SMALL {
472 return Err(SimError::FfiContract(format!(
473 "sim_shared_write reported BUFFER_TOO_SMALL for channel {channel_id} with declared slot_count {expected_slot_count}"
474 )));
475 }
476 if status != STATUS_OK {
477 self.map_status(status, None, None)?;
478 }
479
480 raw_slots.truncate(
481 validate_written(written, capacity, "sim_shared_write")
482 .map_err(SimError::FfiContract)?,
483 );
484 let slots = raw_slots
485 .into_iter()
486 .map(|slot| {
487 SimSharedSlot::try_from_raw(slot)
488 .map_err(|err| SimError::FfiContract(format!("sim_shared_write: {err}")))
489 })
490 .collect::<Result<Vec<_>, _>>()?;
491 validate_dense_shared_snapshot(&slots, expected_slot_count as usize, "sim_shared_write")?;
492 Ok(slots)
493 }
494
495 pub(crate) fn read(&self, signal: &SignalMeta) -> Result<SignalValue, SimError> {
496 let mut raw = SimValueRaw {
497 signal_type: 0,
498 data: crate::sim::types::SimValueDataRaw { u32: 0 },
499 };
500 let status = unsafe { (self.sim_read_val)(signal.id, &mut raw as *mut SimValueRaw) };
501 self.map_status(status, Some(signal), None)?;
502 let value = unsafe { SignalValue::from_raw(raw) }
503 .ok_or_else(|| SimError::InvalidArg("bad read value".to_string()))?;
504 Ok(value)
505 }
506
507 pub(crate) fn write(&self, signal: &SignalMeta, value: &SignalValue) -> Result<(), SimError> {
508 let raw = value.to_raw();
509 let status = unsafe { (self.sim_write_val)(signal.id, &raw as *const SimValueRaw) };
510 self.map_status(status, Some(signal), Some(value.signal_type()))
511 }
512
513 fn map_status(
514 &self,
515 status: u32,
516 signal: Option<&SignalMeta>,
517 actual_type: Option<SignalType>,
518 ) -> Result<(), SimError> {
519 match status {
520 STATUS_OK => Ok(()),
521 STATUS_NOT_INITIALIZED => Err(SimError::NotInitialized),
522 2 => Err(SimError::InvalidArg("invalid ffi argument".to_string())),
523 STATUS_INVALID_SIGNAL => Err(SimError::InvalidSignal(
524 signal
525 .map(|s| s.name.clone())
526 .unwrap_or_else(|| "<unknown>".to_string()),
527 )),
528 STATUS_TYPE_MISMATCH => Err(SimError::TypeMismatch {
529 name: signal
530 .map(|s| s.name.clone())
531 .unwrap_or_else(|| "<unknown>".to_string()),
532 expected: signal.map(|s| s.signal_type).unwrap_or(SignalType::F64),
533 actual: actual_type.unwrap_or(SignalType::F64),
534 }),
535 STATUS_BUFFER_TOO_SMALL => Err(SimError::BufferTooSmall),
536 255 => Err(SimError::Internal),
537 _ => Err(SimError::UnknownStatus(status)),
538 }
539 }
540
541 fn load_can_buses(
542 sim_can_get_buses: SimCanGetBusesFn,
543 ) -> Result<Vec<SimCanBusDesc>, ProjectError> {
544 let mut capacity = 4_u32;
545 loop {
546 let mut raw = vec![
547 SimCanBusDescRaw {
548 id: 0,
549 name: std::ptr::null(),
550 bitrate: 0,
551 bitrate_data: 0,
552 flags: 0,
553 _pad: [0, 0, 0],
554 };
555 capacity as usize
556 ];
557 let mut written = 0_u32;
558 let status =
559 unsafe { sim_can_get_buses(raw.as_mut_ptr(), capacity, &mut written as *mut u32) };
560 if status == STATUS_BUFFER_TOO_SMALL {
561 capacity = next_capacity(capacity, "sim_can_get_buses")
562 .map_err(ProjectError::FfiContract)?;
563 continue;
564 }
565 if status != STATUS_OK {
566 return Err(ProjectError::LibraryLoad(format!(
567 "sim_can_get_buses failed with status {status}"
568 )));
569 }
570 raw.truncate(
571 validate_written(written, capacity, "sim_can_get_buses")
572 .map_err(ProjectError::FfiContract)?,
573 );
574 return raw
575 .into_iter()
576 .map(|entry| {
577 let name = decode_owned_cstr(entry.name, "CAN bus name")
578 .map_err(ProjectError::InvalidCanMetadata)?;
579 Ok(SimCanBusDesc {
580 id: entry.id,
581 name,
582 bitrate: entry.bitrate,
583 bitrate_data: entry.bitrate_data,
584 fd_capable: (entry.flags & 0x01) != 0,
585 })
586 })
587 .collect();
588 }
589 }
590
591 fn load_shared_channels(
592 sim_shared_get_channels: SimSharedGetChannelsFn,
593 ) -> Result<Vec<SimSharedDesc>, ProjectError> {
594 let mut capacity = 4_u32;
595 loop {
596 let mut raw = vec![
597 SimSharedDescRaw {
598 id: 0,
599 name: std::ptr::null(),
600 slot_count: 0,
601 };
602 capacity as usize
603 ];
604 let mut written = 0_u32;
605 let status = unsafe {
606 sim_shared_get_channels(raw.as_mut_ptr(), capacity, &mut written as *mut u32)
607 };
608 if status == STATUS_BUFFER_TOO_SMALL {
609 capacity = next_capacity(capacity, "sim_shared_get_channels")
610 .map_err(ProjectError::FfiContract)?;
611 continue;
612 }
613 if status != STATUS_OK {
614 return Err(ProjectError::LibraryLoad(format!(
615 "sim_shared_get_channels failed with status {status}"
616 )));
617 }
618 raw.truncate(
619 validate_written(written, capacity, "sim_shared_get_channels")
620 .map_err(ProjectError::FfiContract)?,
621 );
622 return raw
623 .into_iter()
624 .map(|entry| {
625 let name = decode_owned_cstr(entry.name, "shared channel name")
626 .map_err(ProjectError::InvalidSharedMetadata)?;
627 Ok(SimSharedDesc {
628 id: entry.id,
629 name,
630 slot_count: entry.slot_count,
631 })
632 })
633 .collect();
634 }
635 }
636}
637
638fn validate_dense_shared_snapshot(
639 slots: &[SimSharedSlot],
640 expected_slot_count: usize,
641 context: &str,
642) -> Result<(), SimError> {
643 if slots.len() != expected_slot_count {
644 return Err(SimError::FfiContract(format!(
645 "{context} returned {} slots, expected {expected_slot_count}",
646 slots.len()
647 )));
648 }
649
650 for (expected_slot_id, slot) in slots.iter().enumerate() {
651 if slot.slot_id as usize != expected_slot_id {
652 return Err(SimError::FfiContract(format!(
653 "{context} returned slot id {} at dense index {}; expected slot id {}",
654 slot.slot_id, expected_slot_id, expected_slot_id
655 )));
656 }
657 }
658
659 Ok(())
660}