1use super::loose::{GnssFixMeasurement, GnssFixStatus, InertialFilter};
4use super::state::{
5 validate_covariance_matrix, validate_finite_slice, ErrorStateLayout, ErrorStateVector,
6 InsFilterState,
7};
8use super::tight::{
9 augmented_dimension, TightCarrierPhaseObservation, TightFilterSnapshot, TightGnssEpoch,
10 TightGnssObservation, TightRangeRateObservation,
11};
12use super::timesync::{
13 InertialFilterSnapshot, RateEndpoint, StationarityDetectorSnapshotSample, StoredCheckpoint,
14 StoredGnssMeasurement, StoredImuSample, TimeSyncHistoryConfig, TimeSyncHistorySnapshot,
15};
16use crate::inertial::{ImuSample, ImuSampleKind, NavState};
17use crate::{GnssSatelliteId, GnssSystem};
18
19const FUSION_STATE_MAGIC: [u8; 8] = *b"FUSSTAT\0";
20const FNV_OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
21const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
22
23pub const FUSION_STATE_CODEC_VERSION: u16 = 4;
25const MIN_SUPPORTED_FUSION_STATE_CODEC_VERSION: u16 = 1;
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
29#[serde(transparent)]
30pub struct F64Bits {
31 pub bits: u64,
33}
34
35impl F64Bits {
36 pub const fn from_f64(value: f64) -> Self {
38 Self {
39 bits: value.to_bits(),
40 }
41 }
42
43 pub const fn to_f64(self) -> f64 {
45 f64::from_bits(self.bits)
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
51pub enum SerializableErrorStateLayout {
52 Fifteen,
54 TwentyOne,
56}
57
58impl SerializableErrorStateLayout {
59 pub const fn from_native(layout: ErrorStateLayout) -> Self {
61 match layout {
62 ErrorStateLayout::Fifteen => Self::Fifteen,
63 ErrorStateLayout::TwentyOne => Self::TwentyOne,
64 }
65 }
66
67 pub const fn to_native(self) -> ErrorStateLayout {
69 match self {
70 Self::Fifteen => ErrorStateLayout::Fifteen,
71 Self::TwentyOne => ErrorStateLayout::TwentyOne,
72 }
73 }
74}
75
76#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
78pub struct SerializableNavState {
79 pub t_j2000_s: F64Bits,
81 pub position_ecef_m: [F64Bits; 3],
83 pub velocity_ecef_mps: [F64Bits; 3],
85 pub attitude_body_to_ecef: [[F64Bits; 3]; 3],
87 pub accel_bias_mps2: [F64Bits; 3],
89 pub gyro_bias_rps: [F64Bits; 3],
91}
92
93impl SerializableNavState {
94 pub fn from_native(state: &NavState) -> Self {
96 Self {
97 t_j2000_s: F64Bits::from_f64(state.t_j2000_s),
98 position_ecef_m: bits3(state.position_ecef_m),
99 velocity_ecef_mps: bits3(state.velocity_ecef_mps),
100 attitude_body_to_ecef: bits3x3(state.attitude_body_to_ecef),
101 accel_bias_mps2: bits3(state.accel_bias_mps2),
102 gyro_bias_rps: bits3(state.gyro_bias_rps),
103 }
104 }
105
106 pub fn to_native(&self) -> Result<NavState, FusionStateCodecError> {
108 let state = NavState {
109 t_j2000_s: self.t_j2000_s.to_f64(),
110 position_ecef_m: f643(self.position_ecef_m),
111 velocity_ecef_mps: f643(self.velocity_ecef_mps),
112 attitude_body_to_ecef: f643x3(self.attitude_body_to_ecef),
113 accel_bias_mps2: f643(self.accel_bias_mps2),
114 gyro_bias_rps: f643(self.gyro_bias_rps),
115 };
116 state.validate().map_err(invalid_state)?;
117 Ok(state)
118 }
119}
120
121#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
123pub struct SerializableInsFilterState {
124 pub layout: SerializableErrorStateLayout,
126 pub nominal: SerializableNavState,
128 pub error_state: Vec<F64Bits>,
130 pub covariance: Vec<Vec<F64Bits>>,
132 pub accel_scale_factor: [F64Bits; 3],
134 pub gyro_scale_factor: [F64Bits; 3],
136}
137
138impl SerializableInsFilterState {
139 pub fn from_native(state: &InsFilterState) -> Self {
141 Self {
142 layout: SerializableErrorStateLayout::from_native(state.layout()),
143 nominal: SerializableNavState::from_native(&state.nominal),
144 error_state: bits_slice(state.error_state.as_slice()),
145 covariance: bits_matrix(&state.covariance),
146 accel_scale_factor: bits3(state.accel_scale_factor),
147 gyro_scale_factor: bits3(state.gyro_scale_factor),
148 }
149 }
150
151 pub fn to_native(&self) -> Result<InsFilterState, FusionStateCodecError> {
153 let layout = self.layout.to_native();
154 let nominal = self.nominal.to_native()?;
155 let error_state = ErrorStateVector::from_vec(layout, f64_vec(&self.error_state))
156 .map_err(invalid_state)?;
157 let state = InsFilterState {
158 nominal,
159 error_state,
160 covariance: f64_matrix(&self.covariance),
161 accel_scale_factor: f643(self.accel_scale_factor),
162 gyro_scale_factor: f643(self.gyro_scale_factor),
163 };
164 state.validate().map_err(invalid_state)?;
165 Ok(state)
166 }
167}
168
169#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
171pub struct SerializableTightFilterState {
172 pub clock_bias_m: F64Bits,
174 pub clock_drift_m_s: F64Bits,
176 pub augmented_covariance: Vec<Vec<F64Bits>>,
178}
179
180impl SerializableTightFilterState {
181 pub fn from_native(snapshot: &TightFilterSnapshot) -> Self {
183 Self {
184 clock_bias_m: F64Bits::from_f64(snapshot.clock_bias_m),
185 clock_drift_m_s: F64Bits::from_f64(snapshot.clock_drift_m_s),
186 augmented_covariance: bits_matrix(&snapshot.augmented_covariance),
187 }
188 }
189
190 pub fn to_native(
192 &self,
193 base_dimension: usize,
194 ) -> Result<TightFilterSnapshot, FusionStateCodecError> {
195 let snapshot = TightFilterSnapshot {
196 clock_bias_m: self.clock_bias_m.to_f64(),
197 clock_drift_m_s: self.clock_drift_m_s.to_f64(),
198 augmented_covariance: f64_matrix(&self.augmented_covariance),
199 };
200 validate_finite_slice(
201 &[snapshot.clock_bias_m, snapshot.clock_drift_m_s],
202 "tight_clock",
203 )
204 .map_err(invalid_state)?;
205 validate_covariance_matrix(
206 &snapshot.augmented_covariance,
207 augmented_dimension(base_dimension),
208 "tight_augmented_covariance",
209 )
210 .map_err(invalid_state)?;
211 Ok(snapshot)
212 }
213}
214
215#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
217pub struct SerializableTimeSyncHistoryConfig {
218 pub imu_capacity: u32,
220 pub checkpoint_capacity: u32,
222}
223
224impl SerializableTimeSyncHistoryConfig {
225 pub fn from_native(config: TimeSyncHistoryConfig) -> Result<Self, FusionStateCodecError> {
227 Ok(Self {
228 imu_capacity: checked_u32(config.imu_capacity)?,
229 checkpoint_capacity: checked_u32(config.checkpoint_capacity)?,
230 })
231 }
232
233 pub fn to_native(self) -> Result<TimeSyncHistoryConfig, FusionStateCodecError> {
235 let config = TimeSyncHistoryConfig::new(
236 self.imu_capacity as usize,
237 self.checkpoint_capacity as usize,
238 );
239 config.validate().map_err(invalid_state)?;
240 Ok(config)
241 }
242}
243
244#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
246pub struct SerializableSatelliteId {
247 pub system: GnssSystem,
249 pub prn: u8,
251}
252
253impl SerializableSatelliteId {
254 pub const fn from_native(id: GnssSatelliteId) -> Self {
256 Self {
257 system: id.system,
258 prn: id.prn,
259 }
260 }
261
262 pub fn to_native(self) -> Result<GnssSatelliteId, FusionStateCodecError> {
264 GnssSatelliteId::new(self.system, self.prn).map_err(invalid_state)
265 }
266}
267
268#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
270pub struct SerializableRateEndpoint {
271 pub t_j2000_s: F64Bits,
273 pub specific_force_mps2: [F64Bits; 3],
275 pub angular_rate_rps: [F64Bits; 3],
277}
278
279impl SerializableRateEndpoint {
280 fn from_native(endpoint: RateEndpoint) -> Self {
282 Self {
283 t_j2000_s: F64Bits::from_f64(endpoint.t_j2000_s),
284 specific_force_mps2: bits3(endpoint.specific_force_mps2),
285 angular_rate_rps: bits3(endpoint.angular_rate_rps),
286 }
287 }
288
289 fn to_native(self) -> RateEndpoint {
291 RateEndpoint {
292 t_j2000_s: self.t_j2000_s.to_f64(),
293 specific_force_mps2: f643(self.specific_force_mps2),
294 angular_rate_rps: f643(self.angular_rate_rps),
295 }
296 }
297}
298
299#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
301pub struct SerializableStationarityDetectorSample {
302 pub specific_force_norm_error_mps2: F64Bits,
304 pub body_rate_wrt_ecef_norm_rps: F64Bits,
306}
307
308impl SerializableStationarityDetectorSample {
309 fn from_native(sample: StationarityDetectorSnapshotSample) -> Self {
310 Self {
311 specific_force_norm_error_mps2: F64Bits::from_f64(
312 sample.specific_force_norm_error_mps2,
313 ),
314 body_rate_wrt_ecef_norm_rps: F64Bits::from_f64(sample.body_rate_wrt_ecef_norm_rps),
315 }
316 }
317
318 fn to_native(self) -> StationarityDetectorSnapshotSample {
319 StationarityDetectorSnapshotSample {
320 specific_force_norm_error_mps2: self.specific_force_norm_error_mps2.to_f64(),
321 body_rate_wrt_ecef_norm_rps: self.body_rate_wrt_ecef_norm_rps.to_f64(),
322 }
323 }
324}
325
326#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
328pub enum SerializableImuSampleKind {
329 Rate {
331 specific_force_mps2: [F64Bits; 3],
333 angular_rate_rps: [F64Bits; 3],
335 },
336 Increment {
338 delta_velocity_mps: [F64Bits; 3],
340 delta_theta_rad: [F64Bits; 3],
342 dt_s: F64Bits,
344 },
345}
346
347impl SerializableImuSampleKind {
348 pub fn from_native(kind: ImuSampleKind) -> Self {
350 match kind {
351 ImuSampleKind::Rate {
352 specific_force_mps2,
353 angular_rate_rps,
354 } => Self::Rate {
355 specific_force_mps2: bits3(specific_force_mps2),
356 angular_rate_rps: bits3(angular_rate_rps),
357 },
358 ImuSampleKind::Increment {
359 delta_velocity_mps,
360 delta_theta_rad,
361 dt_s,
362 } => Self::Increment {
363 delta_velocity_mps: bits3(delta_velocity_mps),
364 delta_theta_rad: bits3(delta_theta_rad),
365 dt_s: F64Bits::from_f64(dt_s),
366 },
367 }
368 }
369
370 pub fn to_native(self) -> ImuSampleKind {
372 match self {
373 Self::Rate {
374 specific_force_mps2,
375 angular_rate_rps,
376 } => ImuSampleKind::Rate {
377 specific_force_mps2: f643(specific_force_mps2),
378 angular_rate_rps: f643(angular_rate_rps),
379 },
380 Self::Increment {
381 delta_velocity_mps,
382 delta_theta_rad,
383 dt_s,
384 } => ImuSampleKind::Increment {
385 delta_velocity_mps: f643(delta_velocity_mps),
386 delta_theta_rad: f643(delta_theta_rad),
387 dt_s: dt_s.to_f64(),
388 },
389 }
390 }
391}
392
393#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
395pub struct SerializableImuSample {
396 pub t_j2000_s: F64Bits,
398 pub kind: SerializableImuSampleKind,
400}
401
402impl SerializableImuSample {
403 pub fn from_native(sample: ImuSample) -> Self {
405 Self {
406 t_j2000_s: F64Bits::from_f64(sample.t_j2000_s),
407 kind: SerializableImuSampleKind::from_native(sample.kind),
408 }
409 }
410
411 pub fn to_native(self) -> ImuSample {
413 ImuSample {
414 t_j2000_s: self.t_j2000_s.to_f64(),
415 kind: self.kind.to_native(),
416 }
417 }
418}
419
420#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
422pub struct SerializableStoredImuSample {
423 pub previous_t_j2000_s: F64Bits,
425 pub sample: SerializableImuSample,
427 pub previous_rate: Option<SerializableRateEndpoint>,
429}
430
431impl SerializableStoredImuSample {
432 fn from_native(sample: StoredImuSample) -> Self {
434 Self {
435 previous_t_j2000_s: F64Bits::from_f64(sample.previous_t_j2000_s),
436 sample: SerializableImuSample::from_native(sample.sample),
437 previous_rate: sample
438 .previous_rate
439 .map(SerializableRateEndpoint::from_native),
440 }
441 }
442
443 fn to_native(self) -> StoredImuSample {
445 StoredImuSample {
446 previous_t_j2000_s: self.previous_t_j2000_s.to_f64(),
447 sample: self.sample.to_native(),
448 previous_rate: self.previous_rate.map(SerializableRateEndpoint::to_native),
449 }
450 }
451}
452
453#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
455pub struct SerializableLooseMeasurement {
456 pub t_j2000_s: F64Bits,
458 pub position_ecef_m: [F64Bits; 3],
460 pub velocity_ecef_mps: Option<[F64Bits; 3]>,
462 pub covariance: Vec<Vec<F64Bits>>,
464 pub satellites_used: u32,
466 pub solution_valid: bool,
468 #[serde(default = "default_serializable_fix_status")]
470 pub fix_status: SerializableGnssFixStatus,
471}
472
473#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
475pub enum SerializableGnssFixStatus {
476 Single,
478 Float,
480 Fixed,
482}
483
484fn default_serializable_fix_status() -> SerializableGnssFixStatus {
485 SerializableGnssFixStatus::Single
486}
487
488impl SerializableGnssFixStatus {
489 fn from_native(status: GnssFixStatus) -> Self {
490 match status {
491 GnssFixStatus::Single => Self::Single,
492 GnssFixStatus::Float => Self::Float,
493 GnssFixStatus::Fixed => Self::Fixed,
494 }
495 }
496
497 const fn to_native(self) -> GnssFixStatus {
498 match self {
499 Self::Single => GnssFixStatus::Single,
500 Self::Float => GnssFixStatus::Float,
501 Self::Fixed => GnssFixStatus::Fixed,
502 }
503 }
504}
505
506impl SerializableLooseMeasurement {
507 pub fn from_native(measurement: &GnssFixMeasurement) -> Result<Self, FusionStateCodecError> {
509 Ok(Self {
510 t_j2000_s: F64Bits::from_f64(measurement.t_j2000_s),
511 position_ecef_m: bits3(measurement.position_ecef_m),
512 velocity_ecef_mps: measurement.velocity_ecef_mps.map(bits3),
513 covariance: bits_matrix(&measurement.covariance),
514 satellites_used: checked_u32(measurement.satellites_used)?,
515 solution_valid: measurement.solution_valid,
516 fix_status: SerializableGnssFixStatus::from_native(measurement.fix_status),
517 })
518 }
519
520 pub fn to_native(&self) -> Result<GnssFixMeasurement, FusionStateCodecError> {
522 let measurement = GnssFixMeasurement {
523 t_j2000_s: self.t_j2000_s.to_f64(),
524 position_ecef_m: f643(self.position_ecef_m),
525 velocity_ecef_mps: self.velocity_ecef_mps.map(f643),
526 covariance: f64_matrix(&self.covariance),
527 satellites_used: self.satellites_used as usize,
528 solution_valid: self.solution_valid,
529 fix_status: self.fix_status.to_native(),
530 };
531 measurement.validate().map_err(invalid_state)?;
532 Ok(measurement)
533 }
534}
535
536#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
538pub struct SerializableTightRangeRateObservation {
539 pub measured_range_rate_m_s: F64Bits,
541 pub sigma_m_s: F64Bits,
543 pub satellite_clock_drift_m_s: F64Bits,
545}
546
547impl SerializableTightRangeRateObservation {
548 pub fn from_native(observation: TightRangeRateObservation) -> Self {
550 Self {
551 measured_range_rate_m_s: F64Bits::from_f64(observation.measured_range_rate_m_s),
552 sigma_m_s: F64Bits::from_f64(observation.sigma_m_s),
553 satellite_clock_drift_m_s: F64Bits::from_f64(observation.satellite_clock_drift_m_s),
554 }
555 }
556
557 pub fn to_native(self) -> TightRangeRateObservation {
559 TightRangeRateObservation {
560 measured_range_rate_m_s: self.measured_range_rate_m_s.to_f64(),
561 sigma_m_s: self.sigma_m_s.to_f64(),
562 satellite_clock_drift_m_s: self.satellite_clock_drift_m_s.to_f64(),
563 }
564 }
565}
566
567#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
569pub struct SerializableTightCarrierPhaseObservation {
570 pub phase_range_m: F64Bits,
572 pub sigma_m: F64Bits,
574 pub float_ambiguity_m: F64Bits,
576}
577
578impl SerializableTightCarrierPhaseObservation {
579 pub fn from_native(observation: TightCarrierPhaseObservation) -> Self {
581 Self {
582 phase_range_m: F64Bits::from_f64(observation.phase_range_m),
583 sigma_m: F64Bits::from_f64(observation.sigma_m),
584 float_ambiguity_m: F64Bits::from_f64(observation.float_ambiguity_m),
585 }
586 }
587
588 pub fn to_native(self) -> TightCarrierPhaseObservation {
590 TightCarrierPhaseObservation {
591 phase_range_m: self.phase_range_m.to_f64(),
592 sigma_m: self.sigma_m.to_f64(),
593 float_ambiguity_m: self.float_ambiguity_m.to_f64(),
594 }
595 }
596}
597
598#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
600pub struct SerializableTightGnssObservation {
601 pub satellite_id: SerializableSatelliteId,
603 pub pseudorange_m: F64Bits,
605 pub pseudorange_sigma_m: F64Bits,
607 pub range_rate: Option<SerializableTightRangeRateObservation>,
609 pub carrier_phase: Option<SerializableTightCarrierPhaseObservation>,
611 pub ionosphere_delay_m: F64Bits,
613 pub troposphere_delay_m: F64Bits,
615}
616
617impl SerializableTightGnssObservation {
618 pub fn from_native(observation: TightGnssObservation) -> Self {
620 Self {
621 satellite_id: SerializableSatelliteId::from_native(observation.satellite_id),
622 pseudorange_m: F64Bits::from_f64(observation.pseudorange_m),
623 pseudorange_sigma_m: F64Bits::from_f64(observation.pseudorange_sigma_m),
624 range_rate: observation
625 .range_rate
626 .map(SerializableTightRangeRateObservation::from_native),
627 carrier_phase: observation
628 .carrier_phase
629 .map(SerializableTightCarrierPhaseObservation::from_native),
630 ionosphere_delay_m: F64Bits::from_f64(observation.ionosphere_delay_m),
631 troposphere_delay_m: F64Bits::from_f64(observation.troposphere_delay_m),
632 }
633 }
634
635 pub fn to_native(self) -> Result<TightGnssObservation, FusionStateCodecError> {
637 let observation = TightGnssObservation {
638 satellite_id: self.satellite_id.to_native()?,
639 pseudorange_m: self.pseudorange_m.to_f64(),
640 pseudorange_sigma_m: self.pseudorange_sigma_m.to_f64(),
641 range_rate: self
642 .range_rate
643 .map(SerializableTightRangeRateObservation::to_native),
644 carrier_phase: self
645 .carrier_phase
646 .map(SerializableTightCarrierPhaseObservation::to_native),
647 ionosphere_delay_m: self.ionosphere_delay_m.to_f64(),
648 troposphere_delay_m: self.troposphere_delay_m.to_f64(),
649 };
650 observation.validate().map_err(invalid_state)?;
651 Ok(observation)
652 }
653}
654
655#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
657pub struct SerializableTightGnssEpoch {
658 pub t_j2000_s: F64Bits,
660 pub observations: Vec<SerializableTightGnssObservation>,
662}
663
664impl SerializableTightGnssEpoch {
665 pub fn from_native(epoch: &TightGnssEpoch) -> Self {
667 Self {
668 t_j2000_s: F64Bits::from_f64(epoch.t_j2000_s),
669 observations: epoch
670 .observations
671 .iter()
672 .copied()
673 .map(SerializableTightGnssObservation::from_native)
674 .collect(),
675 }
676 }
677
678 pub fn to_native(&self) -> Result<TightGnssEpoch, FusionStateCodecError> {
680 let observations = self
681 .observations
682 .iter()
683 .copied()
684 .map(SerializableTightGnssObservation::to_native)
685 .collect::<Result<Vec<_>, _>>()?;
686 let epoch = TightGnssEpoch {
687 t_j2000_s: self.t_j2000_s.to_f64(),
688 observations,
689 };
690 epoch.validate().map_err(invalid_state)?;
691 Ok(epoch)
692 }
693}
694
695#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
697pub enum SerializableStoredGnssMeasurement {
698 Loose(SerializableLooseMeasurement),
700 Tight(SerializableTightGnssEpoch),
702}
703
704impl SerializableStoredGnssMeasurement {
705 fn from_native(measurement: &StoredGnssMeasurement) -> Result<Self, FusionStateCodecError> {
707 match measurement {
708 StoredGnssMeasurement::Loose(measurement) => Ok(Self::Loose(
709 SerializableLooseMeasurement::from_native(measurement)?,
710 )),
711 StoredGnssMeasurement::Tight(epoch) => {
712 Ok(Self::Tight(SerializableTightGnssEpoch::from_native(epoch)))
713 }
714 }
715 }
716
717 fn to_native(&self) -> Result<StoredGnssMeasurement, FusionStateCodecError> {
719 match self {
720 Self::Loose(measurement) => Ok(StoredGnssMeasurement::Loose(measurement.to_native()?)),
721 Self::Tight(epoch) => Ok(StoredGnssMeasurement::Tight(epoch.to_native()?)),
722 }
723 }
724}
725
726#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
728pub struct SerializableStoredCheckpoint {
729 pub t_j2000_s: F64Bits,
731 pub snapshot: Box<SerializableFusionSnapshot>,
733}
734
735impl SerializableStoredCheckpoint {
736 fn from_native(checkpoint: &StoredCheckpoint) -> Self {
738 Self {
739 t_j2000_s: F64Bits::from_f64(checkpoint.t_j2000_s),
740 snapshot: Box::new(SerializableFusionSnapshot::from_snapshot(
741 &checkpoint.snapshot,
742 )),
743 }
744 }
745
746 fn to_native(&self) -> Result<StoredCheckpoint, FusionStateCodecError> {
748 let snapshot = self.snapshot.to_snapshot()?;
749 let checkpoint = StoredCheckpoint {
750 t_j2000_s: self.t_j2000_s.to_f64(),
751 snapshot,
752 };
753 if checkpoint.t_j2000_s != checkpoint.snapshot.state.nominal.t_j2000_s {
754 return Err(FusionStateCodecError::InvalidState {
755 reason: "checkpoint epoch must match snapshot".to_string(),
756 });
757 }
758 Ok(checkpoint)
759 }
760}
761
762#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
764pub struct SerializableFusionSnapshot {
765 pub state: SerializableInsFilterState,
767 pub last_body_rate_wrt_ecef_rps: [F64Bits; 3],
769 #[serde(default)]
771 pub stationarity_window: Vec<SerializableStationarityDetectorSample>,
772 #[serde(default)]
774 pub last_stationary_update_t_j2000_s: Option<F64Bits>,
775 #[serde(default)]
777 pub last_non_holonomic_update_t_j2000_s: Option<F64Bits>,
778 pub tight: SerializableTightFilterState,
780}
781
782impl SerializableFusionSnapshot {
783 pub fn from_snapshot(snapshot: &InertialFilterSnapshot) -> Self {
785 Self {
786 state: SerializableInsFilterState::from_native(&snapshot.state),
787 last_body_rate_wrt_ecef_rps: bits3(snapshot.last_body_rate_wrt_ecef_rps),
788 stationarity_window: snapshot
789 .stationarity_window
790 .iter()
791 .copied()
792 .map(SerializableStationarityDetectorSample::from_native)
793 .collect(),
794 last_stationary_update_t_j2000_s: snapshot
795 .last_stationary_update_t_j2000_s
796 .map(F64Bits::from_f64),
797 last_non_holonomic_update_t_j2000_s: snapshot
798 .last_non_holonomic_update_t_j2000_s
799 .map(F64Bits::from_f64),
800 tight: SerializableTightFilterState::from_native(&snapshot.tight),
801 }
802 }
803
804 pub fn to_snapshot(&self) -> Result<InertialFilterSnapshot, FusionStateCodecError> {
806 let state = self.state.to_native()?;
807 let last_body_rate_wrt_ecef_rps = f643(self.last_body_rate_wrt_ecef_rps);
808 validate_finite_slice(&last_body_rate_wrt_ecef_rps, "last_body_rate_wrt_ecef_rps")
809 .map_err(invalid_state)?;
810 let stationarity_window = stationarity_window_to_native(&self.stationarity_window)?;
811 let last_stationary_update_t_j2000_s = optional_epoch_to_native(
812 self.last_stationary_update_t_j2000_s,
813 "last_stationary_update_t_j2000_s",
814 )?;
815 let last_non_holonomic_update_t_j2000_s = optional_epoch_to_native(
816 self.last_non_holonomic_update_t_j2000_s,
817 "last_non_holonomic_update_t_j2000_s",
818 )?;
819 let tight = self.tight.to_native(state.dimension())?;
820 Ok(InertialFilterSnapshot {
821 state,
822 last_body_rate_wrt_ecef_rps,
823 stationarity_window,
824 last_stationary_update_t_j2000_s,
825 last_non_holonomic_update_t_j2000_s,
826 tight,
827 })
828 }
829}
830
831fn stationarity_window_to_native(
832 samples: &[SerializableStationarityDetectorSample],
833) -> Result<Vec<StationarityDetectorSnapshotSample>, FusionStateCodecError> {
834 let out = samples
835 .iter()
836 .copied()
837 .map(SerializableStationarityDetectorSample::to_native)
838 .collect::<Vec<_>>();
839 for sample in &out {
840 validate_finite_slice(
841 &[
842 sample.specific_force_norm_error_mps2,
843 sample.body_rate_wrt_ecef_norm_rps,
844 ],
845 "stationarity_window",
846 )
847 .map_err(invalid_state)?;
848 }
849 Ok(out)
850}
851
852fn optional_epoch_to_native(
853 value: Option<F64Bits>,
854 field: &'static str,
855) -> Result<Option<f64>, FusionStateCodecError> {
856 let Some(value) = value else {
857 return Ok(None);
858 };
859 let value = value.to_f64();
860 validate_finite_slice(&[value], field).map_err(invalid_state)?;
861 Ok(Some(value))
862}
863
864#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
866pub struct SerializableTimeSyncHistory {
867 pub config: SerializableTimeSyncHistoryConfig,
869 pub imu_samples: Vec<SerializableStoredImuSample>,
871 pub checkpoints: Vec<SerializableStoredCheckpoint>,
873 pub measurements: Vec<SerializableStoredGnssMeasurement>,
875}
876
877impl SerializableTimeSyncHistory {
878 fn from_native(history: &TimeSyncHistorySnapshot) -> Result<Self, FusionStateCodecError> {
880 Ok(Self {
881 config: SerializableTimeSyncHistoryConfig::from_native(history.config)?,
882 imu_samples: history
883 .imu_samples
884 .iter()
885 .copied()
886 .map(SerializableStoredImuSample::from_native)
887 .collect(),
888 checkpoints: history
889 .checkpoints
890 .iter()
891 .map(SerializableStoredCheckpoint::from_native)
892 .collect(),
893 measurements: history
894 .measurements
895 .iter()
896 .map(SerializableStoredGnssMeasurement::from_native)
897 .collect::<Result<Vec<_>, _>>()?,
898 })
899 }
900
901 fn to_native(&self) -> Result<TimeSyncHistorySnapshot, FusionStateCodecError> {
903 let snapshot = TimeSyncHistorySnapshot {
904 config: self.config.to_native()?,
905 imu_samples: self
906 .imu_samples
907 .iter()
908 .copied()
909 .map(SerializableStoredImuSample::to_native)
910 .collect(),
911 checkpoints: self
912 .checkpoints
913 .iter()
914 .map(SerializableStoredCheckpoint::to_native)
915 .collect::<Result<Vec<_>, _>>()?,
916 measurements: self
917 .measurements
918 .iter()
919 .map(SerializableStoredGnssMeasurement::to_native)
920 .collect::<Result<Vec<_>, _>>()?,
921 };
922 validate_history_by_restore(snapshot)
923 }
924}
925
926#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
928pub struct SerializableFusionState {
929 pub version: u16,
931 pub state: SerializableInsFilterState,
933 pub last_body_rate_wrt_ecef_rps: [F64Bits; 3],
935 #[serde(default)]
937 pub stationarity_window: Vec<SerializableStationarityDetectorSample>,
938 #[serde(default)]
940 pub last_stationary_update_t_j2000_s: Option<F64Bits>,
941 #[serde(default)]
943 pub last_non_holonomic_update_t_j2000_s: Option<F64Bits>,
944 pub tight: SerializableTightFilterState,
946 pub time_sync: SerializableTimeSyncHistory,
948}
949
950impl SerializableFusionState {
951 pub fn from_snapshot(snapshot: &InertialFilterSnapshot) -> Self {
953 let history = TimeSyncHistorySnapshot::from_filter_snapshot(snapshot.clone());
954 let time_sync =
955 SerializableTimeSyncHistory::from_native(&history).expect("default history encodes");
956 Self {
957 version: FUSION_STATE_CODEC_VERSION,
958 state: SerializableInsFilterState::from_native(&snapshot.state),
959 last_body_rate_wrt_ecef_rps: bits3(snapshot.last_body_rate_wrt_ecef_rps),
960 stationarity_window: snapshot
961 .stationarity_window
962 .iter()
963 .copied()
964 .map(SerializableStationarityDetectorSample::from_native)
965 .collect(),
966 last_stationary_update_t_j2000_s: snapshot
967 .last_stationary_update_t_j2000_s
968 .map(F64Bits::from_f64),
969 last_non_holonomic_update_t_j2000_s: snapshot
970 .last_non_holonomic_update_t_j2000_s
971 .map(F64Bits::from_f64),
972 tight: SerializableTightFilterState::from_native(&snapshot.tight),
973 time_sync,
974 }
975 }
976
977 pub fn from_filter(filter: &InertialFilter) -> Result<Self, FusionStateCodecError> {
979 let snapshot = filter.snapshot();
980 Ok(Self {
981 version: FUSION_STATE_CODEC_VERSION,
982 state: SerializableInsFilterState::from_native(&snapshot.state),
983 last_body_rate_wrt_ecef_rps: bits3(snapshot.last_body_rate_wrt_ecef_rps),
984 stationarity_window: snapshot
985 .stationarity_window
986 .iter()
987 .copied()
988 .map(SerializableStationarityDetectorSample::from_native)
989 .collect(),
990 last_stationary_update_t_j2000_s: snapshot
991 .last_stationary_update_t_j2000_s
992 .map(F64Bits::from_f64),
993 last_non_holonomic_update_t_j2000_s: snapshot
994 .last_non_holonomic_update_t_j2000_s
995 .map(F64Bits::from_f64),
996 tight: SerializableTightFilterState::from_native(&snapshot.tight),
997 time_sync: SerializableTimeSyncHistory::from_native(
998 &filter.time_sync.snapshot_history(),
999 )?,
1000 })
1001 }
1002
1003 pub fn to_snapshot(&self) -> Result<InertialFilterSnapshot, FusionStateCodecError> {
1005 self.validate_version()?;
1006 let state = self.state.to_native()?;
1007 let last_body_rate_wrt_ecef_rps = f643(self.last_body_rate_wrt_ecef_rps);
1008 validate_finite_slice(&last_body_rate_wrt_ecef_rps, "last_body_rate_wrt_ecef_rps")
1009 .map_err(invalid_state)?;
1010 let stationarity_window = stationarity_window_to_native(&self.stationarity_window)?;
1011 let last_stationary_update_t_j2000_s = optional_epoch_to_native(
1012 self.last_stationary_update_t_j2000_s,
1013 "last_stationary_update_t_j2000_s",
1014 )?;
1015 let last_non_holonomic_update_t_j2000_s = optional_epoch_to_native(
1016 self.last_non_holonomic_update_t_j2000_s,
1017 "last_non_holonomic_update_t_j2000_s",
1018 )?;
1019 let tight = self.tight.to_native(state.dimension())?;
1020 Ok(InertialFilterSnapshot {
1021 state,
1022 last_body_rate_wrt_ecef_rps,
1023 stationarity_window,
1024 last_stationary_update_t_j2000_s,
1025 last_non_holonomic_update_t_j2000_s,
1026 tight,
1027 })
1028 }
1029
1030 fn to_time_sync_history(&self) -> Result<TimeSyncHistorySnapshot, FusionStateCodecError> {
1032 self.validate_version()?;
1033 self.time_sync.to_native()
1034 }
1035
1036 pub fn encode_versioned(&self) -> Result<Vec<u8>, FusionStateCodecError> {
1038 self.validate_current_version()?;
1039 let mut bytes = Vec::new();
1040 bytes.extend_from_slice(&FUSION_STATE_MAGIC);
1041 write_u16(&mut bytes, self.version);
1042 write_layout(&mut bytes, self.state.layout);
1043 write_nav(&mut bytes, &self.state.nominal);
1044 write_f64_vec(&mut bytes, &self.state.error_state)?;
1045 write_f64_matrix(&mut bytes, &self.state.covariance)?;
1046 write_f64_array(&mut bytes, &self.state.accel_scale_factor);
1047 write_f64_array(&mut bytes, &self.state.gyro_scale_factor);
1048 write_f64_array(&mut bytes, &self.last_body_rate_wrt_ecef_rps);
1049 write_stationarity_window(&mut bytes, &self.stationarity_window)?;
1050 write_optional_f64(&mut bytes, self.last_stationary_update_t_j2000_s);
1051 write_optional_f64(&mut bytes, self.last_non_holonomic_update_t_j2000_s);
1052 write_f64(&mut bytes, self.tight.clock_bias_m);
1053 write_f64(&mut bytes, self.tight.clock_drift_m_s);
1054 write_f64_matrix(&mut bytes, &self.tight.augmented_covariance)?;
1055 write_time_sync_history(&mut bytes, &self.time_sync)?;
1056 let checksum = fnv1a64(&bytes);
1057 write_u64(&mut bytes, checksum);
1058 Ok(bytes)
1059 }
1060
1061 pub fn decode_versioned(bytes: &[u8]) -> Result<Self, FusionStateCodecError> {
1063 let minimum = FUSION_STATE_MAGIC.len() + 2 + 8;
1064 if bytes.len() < minimum {
1065 return Err(FusionStateCodecError::Truncated {
1066 offset: 0,
1067 needed: minimum,
1068 actual: bytes.len(),
1069 });
1070 }
1071 if bytes[..FUSION_STATE_MAGIC.len()] != FUSION_STATE_MAGIC {
1072 return Err(FusionStateCodecError::InvalidMagic);
1073 }
1074 let checksum_offset = bytes.len() - 8;
1075 let expected = read_u64_at(bytes, checksum_offset)?;
1076 let found = fnv1a64(&bytes[..checksum_offset]);
1077 if expected != found {
1078 return Err(FusionStateCodecError::Checksum { expected, found });
1079 }
1080
1081 let mut cursor = FUSION_STATE_MAGIC.len();
1082 let version = read_u16(bytes, &mut cursor, checksum_offset)?;
1083 if !is_supported_fusion_state_codec_version(version) {
1084 return Err(FusionStateCodecError::UnsupportedVersion { version });
1085 }
1086 let layout = read_layout(bytes, &mut cursor, checksum_offset)?;
1087 let nominal = read_nav(bytes, &mut cursor, checksum_offset)?;
1088 let error_state = read_f64_vec(bytes, &mut cursor, checksum_offset)?;
1089 let covariance = read_f64_matrix(bytes, &mut cursor, checksum_offset)?;
1090 let accel_scale_factor = read_f64_array(bytes, &mut cursor, checksum_offset)?;
1091 let gyro_scale_factor = read_f64_array(bytes, &mut cursor, checksum_offset)?;
1092 let last_body_rate_wrt_ecef_rps = read_f64_array(bytes, &mut cursor, checksum_offset)?;
1093 let stationarity_window = if version >= 3 {
1094 read_stationarity_window(bytes, &mut cursor, checksum_offset)?
1095 } else {
1096 Vec::new()
1097 };
1098 let (last_stationary_update_t_j2000_s, last_non_holonomic_update_t_j2000_s) =
1099 if version >= 4 {
1100 (
1101 read_optional_f64(bytes, &mut cursor, checksum_offset)?,
1102 read_optional_f64(bytes, &mut cursor, checksum_offset)?,
1103 )
1104 } else {
1105 (None, None)
1106 };
1107 let clock_bias_m = read_f64(bytes, &mut cursor, checksum_offset)?;
1108 let clock_drift_m_s = read_f64(bytes, &mut cursor, checksum_offset)?;
1109 let augmented_covariance = read_f64_matrix(bytes, &mut cursor, checksum_offset)?;
1110 let time_sync = read_time_sync_history(bytes, &mut cursor, checksum_offset, version)?;
1111 if cursor != checksum_offset {
1112 return Err(FusionStateCodecError::TrailingBytes {
1113 remaining: checksum_offset - cursor,
1114 });
1115 }
1116 let state = Self {
1117 version,
1118 state: SerializableInsFilterState {
1119 layout,
1120 nominal,
1121 error_state,
1122 covariance,
1123 accel_scale_factor,
1124 gyro_scale_factor,
1125 },
1126 last_body_rate_wrt_ecef_rps,
1127 stationarity_window,
1128 last_stationary_update_t_j2000_s,
1129 last_non_holonomic_update_t_j2000_s,
1130 tight: SerializableTightFilterState {
1131 clock_bias_m,
1132 clock_drift_m_s,
1133 augmented_covariance,
1134 },
1135 time_sync,
1136 };
1137 state.to_snapshot()?;
1138 state.to_time_sync_history()?;
1139 Ok(state)
1140 }
1141
1142 pub fn to_json_string(&self) -> Result<String, FusionStateCodecError> {
1144 self.validate_current_version()?;
1145 serde_json::to_string(self).map_err(|error| FusionStateCodecError::Json {
1146 message: error.to_string(),
1147 })
1148 }
1149
1150 pub fn from_json_str(text: &str) -> Result<Self, FusionStateCodecError> {
1152 let state: Self =
1153 serde_json::from_str(text).map_err(|error| FusionStateCodecError::Json {
1154 message: error.to_string(),
1155 })?;
1156 state.to_snapshot()?;
1157 state.to_time_sync_history()?;
1158 Ok(state)
1159 }
1160
1161 fn validate_version(&self) -> Result<(), FusionStateCodecError> {
1162 if is_supported_fusion_state_codec_version(self.version) {
1163 Ok(())
1164 } else {
1165 Err(FusionStateCodecError::UnsupportedVersion {
1166 version: self.version,
1167 })
1168 }
1169 }
1170
1171 fn validate_current_version(&self) -> Result<(), FusionStateCodecError> {
1172 if self.version == FUSION_STATE_CODEC_VERSION {
1173 Ok(())
1174 } else {
1175 Err(FusionStateCodecError::UnsupportedVersion {
1176 version: self.version,
1177 })
1178 }
1179 }
1180}
1181
1182fn is_supported_fusion_state_codec_version(version: u16) -> bool {
1183 (MIN_SUPPORTED_FUSION_STATE_CODEC_VERSION..=FUSION_STATE_CODEC_VERSION).contains(&version)
1184}
1185
1186impl InertialFilterSnapshot {
1187 pub fn to_serializable_fusion_state(&self) -> SerializableFusionState {
1189 SerializableFusionState::from_snapshot(self)
1190 }
1191
1192 pub fn encode_fusion_state(&self) -> Result<Vec<u8>, FusionStateCodecError> {
1194 self.to_serializable_fusion_state().encode_versioned()
1195 }
1196
1197 pub fn decode_fusion_state(bytes: &[u8]) -> Result<Self, FusionStateCodecError> {
1199 SerializableFusionState::decode_versioned(bytes)?.to_snapshot()
1200 }
1201}
1202
1203impl InertialFilter {
1204 pub fn serializable_state(&self) -> Result<SerializableFusionState, FusionStateCodecError> {
1206 SerializableFusionState::from_filter(self)
1207 }
1208
1209 pub fn encode_state(&self) -> Result<Vec<u8>, FusionStateCodecError> {
1211 SerializableFusionState::from_filter(self)?.encode_versioned()
1212 }
1213
1214 pub fn restore_serializable_state(
1216 &mut self,
1217 state: &SerializableFusionState,
1218 ) -> Result<(), FusionStateCodecError> {
1219 let snapshot = state.to_snapshot()?;
1220 let history = state.to_time_sync_history()?;
1221 self.restore_snapshot(&snapshot).map_err(invalid_state)?;
1222 self.time_sync
1223 .restore_history(history)
1224 .map_err(invalid_state)
1225 }
1226
1227 pub fn restore_encoded_state(&mut self, bytes: &[u8]) -> Result<(), FusionStateCodecError> {
1229 let state = SerializableFusionState::decode_versioned(bytes)?;
1230 self.restore_serializable_state(&state)
1231 }
1232}
1233
1234#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
1236pub enum FusionStateCodecError {
1237 #[error("fusion state payload has invalid magic")]
1239 InvalidMagic,
1240 #[error("fusion state version {version} is not supported")]
1242 UnsupportedVersion {
1243 version: u16,
1245 },
1246 #[error("fusion state payload truncated at {offset}, needed {needed} bytes, got {actual}")]
1248 Truncated {
1249 offset: usize,
1251 needed: usize,
1253 actual: usize,
1255 },
1256 #[error("fusion state checksum expected {expected:#x} but found {found:#x}")]
1258 Checksum {
1259 expected: u64,
1261 found: u64,
1263 },
1264 #[error("fusion state payload has {remaining} trailing bytes")]
1266 TrailingBytes {
1267 remaining: usize,
1269 },
1270 #[error("invalid fusion state payload: {reason}")]
1272 InvalidState {
1273 reason: String,
1275 },
1276 #[error("fusion state JSON error: {message}")]
1278 Json {
1279 message: String,
1281 },
1282}
1283
1284fn invalid_state(error: impl core::fmt::Display) -> FusionStateCodecError {
1285 FusionStateCodecError::InvalidState {
1286 reason: error.to_string(),
1287 }
1288}
1289
1290fn checked_u32(value: usize) -> Result<u32, FusionStateCodecError> {
1291 u32::try_from(value).map_err(|_| FusionStateCodecError::InvalidState {
1292 reason: "length exceeds u32".to_string(),
1293 })
1294}
1295
1296fn validate_history_by_restore(
1297 snapshot: TimeSyncHistorySnapshot,
1298) -> Result<TimeSyncHistorySnapshot, FusionStateCodecError> {
1299 snapshot.validate().map_err(invalid_state)?;
1300 Ok(snapshot)
1301}
1302
1303fn bits3(values: [f64; 3]) -> [F64Bits; 3] {
1304 values.map(F64Bits::from_f64)
1305}
1306
1307fn f643(values: [F64Bits; 3]) -> [f64; 3] {
1308 values.map(F64Bits::to_f64)
1309}
1310
1311fn bits3x3(values: [[f64; 3]; 3]) -> [[F64Bits; 3]; 3] {
1312 values.map(bits3)
1313}
1314
1315fn f643x3(values: [[F64Bits; 3]; 3]) -> [[f64; 3]; 3] {
1316 values.map(f643)
1317}
1318
1319fn bits_slice(values: &[f64]) -> Vec<F64Bits> {
1320 values.iter().copied().map(F64Bits::from_f64).collect()
1321}
1322
1323fn f64_vec(values: &[F64Bits]) -> Vec<f64> {
1324 values.iter().copied().map(F64Bits::to_f64).collect()
1325}
1326
1327fn bits_matrix(values: &[Vec<f64>]) -> Vec<Vec<F64Bits>> {
1328 values.iter().map(|row| bits_slice(row)).collect()
1329}
1330
1331fn f64_matrix(values: &[Vec<F64Bits>]) -> Vec<Vec<f64>> {
1332 values.iter().map(|row| f64_vec(row)).collect()
1333}
1334
1335fn write_nav(bytes: &mut Vec<u8>, state: &SerializableNavState) {
1336 write_f64(bytes, state.t_j2000_s);
1337 write_f64_array(bytes, &state.position_ecef_m);
1338 write_f64_array(bytes, &state.velocity_ecef_mps);
1339 for row in &state.attitude_body_to_ecef {
1340 write_f64_array(bytes, row);
1341 }
1342 write_f64_array(bytes, &state.accel_bias_mps2);
1343 write_f64_array(bytes, &state.gyro_bias_rps);
1344}
1345
1346fn read_nav(
1347 bytes: &[u8],
1348 cursor: &mut usize,
1349 limit: usize,
1350) -> Result<SerializableNavState, FusionStateCodecError> {
1351 Ok(SerializableNavState {
1352 t_j2000_s: read_f64(bytes, cursor, limit)?,
1353 position_ecef_m: read_f64_array(bytes, cursor, limit)?,
1354 velocity_ecef_mps: read_f64_array(bytes, cursor, limit)?,
1355 attitude_body_to_ecef: [
1356 read_f64_array(bytes, cursor, limit)?,
1357 read_f64_array(bytes, cursor, limit)?,
1358 read_f64_array(bytes, cursor, limit)?,
1359 ],
1360 accel_bias_mps2: read_f64_array(bytes, cursor, limit)?,
1361 gyro_bias_rps: read_f64_array(bytes, cursor, limit)?,
1362 })
1363}
1364
1365fn write_layout(bytes: &mut Vec<u8>, layout: SerializableErrorStateLayout) {
1366 bytes.push(match layout {
1367 SerializableErrorStateLayout::Fifteen => 15,
1368 SerializableErrorStateLayout::TwentyOne => 21,
1369 });
1370}
1371
1372fn read_layout(
1373 bytes: &[u8],
1374 cursor: &mut usize,
1375 limit: usize,
1376) -> Result<SerializableErrorStateLayout, FusionStateCodecError> {
1377 match read_u8(bytes, cursor, limit)? {
1378 15 => Ok(SerializableErrorStateLayout::Fifteen),
1379 21 => Ok(SerializableErrorStateLayout::TwentyOne),
1380 _ => Err(FusionStateCodecError::InvalidState {
1381 reason: "invalid error-state layout tag".to_string(),
1382 }),
1383 }
1384}
1385
1386fn write_time_sync_history(
1387 bytes: &mut Vec<u8>,
1388 history: &SerializableTimeSyncHistory,
1389) -> Result<(), FusionStateCodecError> {
1390 write_u32_checked(bytes, history.config.imu_capacity as usize)?;
1391 write_u32_checked(bytes, history.config.checkpoint_capacity as usize)?;
1392 write_u32_checked(bytes, history.imu_samples.len())?;
1393 for sample in &history.imu_samples {
1394 write_stored_imu_sample(bytes, sample);
1395 }
1396 write_u32_checked(bytes, history.checkpoints.len())?;
1397 for checkpoint in &history.checkpoints {
1398 write_stored_checkpoint(bytes, checkpoint)?;
1399 }
1400 write_u32_checked(bytes, history.measurements.len())?;
1401 for measurement in &history.measurements {
1402 write_stored_measurement(bytes, measurement)?;
1403 }
1404 Ok(())
1405}
1406
1407fn read_time_sync_history(
1408 bytes: &[u8],
1409 cursor: &mut usize,
1410 limit: usize,
1411 version: u16,
1412) -> Result<SerializableTimeSyncHistory, FusionStateCodecError> {
1413 let config = SerializableTimeSyncHistoryConfig {
1414 imu_capacity: read_u32(bytes, cursor, limit)?,
1415 checkpoint_capacity: read_u32(bytes, cursor, limit)?,
1416 };
1417 let imu_len = read_len(bytes, cursor, limit, 1, "imu_samples")?;
1418 let mut imu_samples = Vec::with_capacity(imu_len);
1419 for _ in 0..imu_len {
1420 imu_samples.push(read_stored_imu_sample(bytes, cursor, limit)?);
1421 }
1422 let checkpoint_len = read_len(bytes, cursor, limit, 1, "checkpoints")?;
1423 let mut checkpoints = Vec::with_capacity(checkpoint_len);
1424 for _ in 0..checkpoint_len {
1425 checkpoints.push(read_stored_checkpoint(bytes, cursor, limit, version)?);
1426 }
1427 let measurement_len = read_len(bytes, cursor, limit, 1, "gnss_measurements")?;
1428 let mut measurements = Vec::with_capacity(measurement_len);
1429 for _ in 0..measurement_len {
1430 measurements.push(read_stored_measurement(bytes, cursor, limit)?);
1431 }
1432 let history = SerializableTimeSyncHistory {
1433 config,
1434 imu_samples,
1435 checkpoints,
1436 measurements,
1437 };
1438 history.to_native()?;
1439 Ok(history)
1440}
1441
1442fn write_stored_imu_sample(bytes: &mut Vec<u8>, sample: &SerializableStoredImuSample) {
1443 write_f64(bytes, sample.previous_t_j2000_s);
1444 write_imu_sample(bytes, &sample.sample);
1445 match sample.previous_rate {
1446 Some(endpoint) => {
1447 write_bool(bytes, true);
1448 write_rate_endpoint(bytes, endpoint);
1449 }
1450 None => write_bool(bytes, false),
1451 }
1452}
1453
1454fn read_stored_imu_sample(
1455 bytes: &[u8],
1456 cursor: &mut usize,
1457 limit: usize,
1458) -> Result<SerializableStoredImuSample, FusionStateCodecError> {
1459 let previous_t_j2000_s = read_f64(bytes, cursor, limit)?;
1460 let sample = read_imu_sample(bytes, cursor, limit)?;
1461 let previous_rate = if read_bool(bytes, cursor, limit)? {
1462 Some(read_rate_endpoint(bytes, cursor, limit)?)
1463 } else {
1464 None
1465 };
1466 Ok(SerializableStoredImuSample {
1467 previous_t_j2000_s,
1468 sample,
1469 previous_rate,
1470 })
1471}
1472
1473fn write_imu_sample(bytes: &mut Vec<u8>, sample: &SerializableImuSample) {
1474 write_f64(bytes, sample.t_j2000_s);
1475 match sample.kind {
1476 SerializableImuSampleKind::Rate {
1477 specific_force_mps2,
1478 angular_rate_rps,
1479 } => {
1480 bytes.push(0);
1481 write_f64_array(bytes, &specific_force_mps2);
1482 write_f64_array(bytes, &angular_rate_rps);
1483 }
1484 SerializableImuSampleKind::Increment {
1485 delta_velocity_mps,
1486 delta_theta_rad,
1487 dt_s,
1488 } => {
1489 bytes.push(1);
1490 write_f64_array(bytes, &delta_velocity_mps);
1491 write_f64_array(bytes, &delta_theta_rad);
1492 write_f64(bytes, dt_s);
1493 }
1494 }
1495}
1496
1497fn read_imu_sample(
1498 bytes: &[u8],
1499 cursor: &mut usize,
1500 limit: usize,
1501) -> Result<SerializableImuSample, FusionStateCodecError> {
1502 let t_j2000_s = read_f64(bytes, cursor, limit)?;
1503 let kind = match read_u8(bytes, cursor, limit)? {
1504 0 => SerializableImuSampleKind::Rate {
1505 specific_force_mps2: read_f64_array(bytes, cursor, limit)?,
1506 angular_rate_rps: read_f64_array(bytes, cursor, limit)?,
1507 },
1508 1 => SerializableImuSampleKind::Increment {
1509 delta_velocity_mps: read_f64_array(bytes, cursor, limit)?,
1510 delta_theta_rad: read_f64_array(bytes, cursor, limit)?,
1511 dt_s: read_f64(bytes, cursor, limit)?,
1512 },
1513 _ => {
1514 return Err(FusionStateCodecError::InvalidState {
1515 reason: "invalid IMU sample kind tag".to_string(),
1516 });
1517 }
1518 };
1519 Ok(SerializableImuSample { t_j2000_s, kind })
1520}
1521
1522fn write_rate_endpoint(bytes: &mut Vec<u8>, endpoint: SerializableRateEndpoint) {
1523 write_f64(bytes, endpoint.t_j2000_s);
1524 write_f64_array(bytes, &endpoint.specific_force_mps2);
1525 write_f64_array(bytes, &endpoint.angular_rate_rps);
1526}
1527
1528fn read_rate_endpoint(
1529 bytes: &[u8],
1530 cursor: &mut usize,
1531 limit: usize,
1532) -> Result<SerializableRateEndpoint, FusionStateCodecError> {
1533 Ok(SerializableRateEndpoint {
1534 t_j2000_s: read_f64(bytes, cursor, limit)?,
1535 specific_force_mps2: read_f64_array(bytes, cursor, limit)?,
1536 angular_rate_rps: read_f64_array(bytes, cursor, limit)?,
1537 })
1538}
1539
1540fn write_stored_checkpoint(
1541 bytes: &mut Vec<u8>,
1542 checkpoint: &SerializableStoredCheckpoint,
1543) -> Result<(), FusionStateCodecError> {
1544 write_f64(bytes, checkpoint.t_j2000_s);
1545 write_fusion_snapshot(bytes, checkpoint.snapshot.as_ref())
1546}
1547
1548fn read_stored_checkpoint(
1549 bytes: &[u8],
1550 cursor: &mut usize,
1551 limit: usize,
1552 version: u16,
1553) -> Result<SerializableStoredCheckpoint, FusionStateCodecError> {
1554 Ok(SerializableStoredCheckpoint {
1555 t_j2000_s: read_f64(bytes, cursor, limit)?,
1556 snapshot: Box::new(read_fusion_snapshot(bytes, cursor, limit, version)?),
1557 })
1558}
1559
1560fn write_fusion_snapshot(
1561 bytes: &mut Vec<u8>,
1562 snapshot: &SerializableFusionSnapshot,
1563) -> Result<(), FusionStateCodecError> {
1564 write_layout(bytes, snapshot.state.layout);
1565 write_nav(bytes, &snapshot.state.nominal);
1566 write_f64_vec(bytes, &snapshot.state.error_state)?;
1567 write_f64_matrix(bytes, &snapshot.state.covariance)?;
1568 write_f64_array(bytes, &snapshot.state.accel_scale_factor);
1569 write_f64_array(bytes, &snapshot.state.gyro_scale_factor);
1570 write_f64_array(bytes, &snapshot.last_body_rate_wrt_ecef_rps);
1571 write_stationarity_window(bytes, &snapshot.stationarity_window)?;
1572 write_optional_f64(bytes, snapshot.last_stationary_update_t_j2000_s);
1573 write_optional_f64(bytes, snapshot.last_non_holonomic_update_t_j2000_s);
1574 write_f64(bytes, snapshot.tight.clock_bias_m);
1575 write_f64(bytes, snapshot.tight.clock_drift_m_s);
1576 write_f64_matrix(bytes, &snapshot.tight.augmented_covariance)
1577}
1578
1579fn read_fusion_snapshot(
1580 bytes: &[u8],
1581 cursor: &mut usize,
1582 limit: usize,
1583 version: u16,
1584) -> Result<SerializableFusionSnapshot, FusionStateCodecError> {
1585 let state = SerializableInsFilterState {
1586 layout: read_layout(bytes, cursor, limit)?,
1587 nominal: read_nav(bytes, cursor, limit)?,
1588 error_state: read_f64_vec(bytes, cursor, limit)?,
1589 covariance: read_f64_matrix(bytes, cursor, limit)?,
1590 accel_scale_factor: read_f64_array(bytes, cursor, limit)?,
1591 gyro_scale_factor: read_f64_array(bytes, cursor, limit)?,
1592 };
1593 let last_body_rate_wrt_ecef_rps = read_f64_array(bytes, cursor, limit)?;
1594 let stationarity_window = if version >= 3 {
1595 read_stationarity_window(bytes, cursor, limit)?
1596 } else {
1597 Vec::new()
1598 };
1599 let (last_stationary_update_t_j2000_s, last_non_holonomic_update_t_j2000_s) = if version >= 4 {
1600 (
1601 read_optional_f64(bytes, cursor, limit)?,
1602 read_optional_f64(bytes, cursor, limit)?,
1603 )
1604 } else {
1605 (None, None)
1606 };
1607 Ok(SerializableFusionSnapshot {
1608 state,
1609 last_body_rate_wrt_ecef_rps,
1610 stationarity_window,
1611 last_stationary_update_t_j2000_s,
1612 last_non_holonomic_update_t_j2000_s,
1613 tight: SerializableTightFilterState {
1614 clock_bias_m: read_f64(bytes, cursor, limit)?,
1615 clock_drift_m_s: read_f64(bytes, cursor, limit)?,
1616 augmented_covariance: read_f64_matrix(bytes, cursor, limit)?,
1617 },
1618 })
1619}
1620
1621fn write_stationarity_window(
1622 bytes: &mut Vec<u8>,
1623 samples: &[SerializableStationarityDetectorSample],
1624) -> Result<(), FusionStateCodecError> {
1625 write_u32_checked(bytes, samples.len())?;
1626 for sample in samples {
1627 write_f64(bytes, sample.specific_force_norm_error_mps2);
1628 write_f64(bytes, sample.body_rate_wrt_ecef_norm_rps);
1629 }
1630 Ok(())
1631}
1632
1633fn read_stationarity_window(
1634 bytes: &[u8],
1635 cursor: &mut usize,
1636 limit: usize,
1637) -> Result<Vec<SerializableStationarityDetectorSample>, FusionStateCodecError> {
1638 let len = read_len(bytes, cursor, limit, 16, "stationarity_window")?;
1639 let mut samples = Vec::with_capacity(len);
1640 for _ in 0..len {
1641 samples.push(SerializableStationarityDetectorSample {
1642 specific_force_norm_error_mps2: read_f64(bytes, cursor, limit)?,
1643 body_rate_wrt_ecef_norm_rps: read_f64(bytes, cursor, limit)?,
1644 });
1645 }
1646 Ok(samples)
1647}
1648
1649fn write_optional_f64(bytes: &mut Vec<u8>, value: Option<F64Bits>) {
1650 match value {
1651 Some(value) => {
1652 write_bool(bytes, true);
1653 write_f64(bytes, value);
1654 }
1655 None => write_bool(bytes, false),
1656 }
1657}
1658
1659fn read_optional_f64(
1660 bytes: &[u8],
1661 cursor: &mut usize,
1662 limit: usize,
1663) -> Result<Option<F64Bits>, FusionStateCodecError> {
1664 if read_bool(bytes, cursor, limit)? {
1665 Ok(Some(read_f64(bytes, cursor, limit)?))
1666 } else {
1667 Ok(None)
1668 }
1669}
1670
1671fn write_stored_measurement(
1672 bytes: &mut Vec<u8>,
1673 measurement: &SerializableStoredGnssMeasurement,
1674) -> Result<(), FusionStateCodecError> {
1675 match measurement {
1676 SerializableStoredGnssMeasurement::Loose(measurement) => {
1677 bytes.push(2);
1678 write_loose_measurement(bytes, measurement)
1679 }
1680 SerializableStoredGnssMeasurement::Tight(epoch) => {
1681 bytes.push(1);
1682 write_tight_epoch(bytes, epoch)
1683 }
1684 }
1685}
1686
1687fn read_stored_measurement(
1688 bytes: &[u8],
1689 cursor: &mut usize,
1690 limit: usize,
1691) -> Result<SerializableStoredGnssMeasurement, FusionStateCodecError> {
1692 match read_u8(bytes, cursor, limit)? {
1693 0 => Ok(SerializableStoredGnssMeasurement::Loose(
1694 read_loose_measurement_with_default_status(bytes, cursor, limit)?,
1695 )),
1696 1 => Ok(SerializableStoredGnssMeasurement::Tight(read_tight_epoch(
1697 bytes, cursor, limit,
1698 )?)),
1699 2 => Ok(SerializableStoredGnssMeasurement::Loose(
1700 read_loose_measurement(bytes, cursor, limit)?,
1701 )),
1702 _ => Err(FusionStateCodecError::InvalidState {
1703 reason: "invalid GNSS measurement tag".to_string(),
1704 }),
1705 }
1706}
1707
1708fn write_loose_measurement(
1709 bytes: &mut Vec<u8>,
1710 measurement: &SerializableLooseMeasurement,
1711) -> Result<(), FusionStateCodecError> {
1712 write_f64(bytes, measurement.t_j2000_s);
1713 write_f64_array(bytes, &measurement.position_ecef_m);
1714 match measurement.velocity_ecef_mps {
1715 Some(velocity) => {
1716 write_bool(bytes, true);
1717 write_f64_array(bytes, &velocity);
1718 }
1719 None => write_bool(bytes, false),
1720 }
1721 write_f64_matrix(bytes, &measurement.covariance)?;
1722 write_u32_checked(bytes, measurement.satellites_used as usize)?;
1723 write_bool(bytes, measurement.solution_valid);
1724 write_fix_status(bytes, measurement.fix_status);
1725 Ok(())
1726}
1727
1728fn read_loose_measurement(
1729 bytes: &[u8],
1730 cursor: &mut usize,
1731 limit: usize,
1732) -> Result<SerializableLooseMeasurement, FusionStateCodecError> {
1733 read_loose_measurement_core(bytes, cursor, limit, true)
1734}
1735
1736fn read_loose_measurement_with_default_status(
1737 bytes: &[u8],
1738 cursor: &mut usize,
1739 limit: usize,
1740) -> Result<SerializableLooseMeasurement, FusionStateCodecError> {
1741 read_loose_measurement_core(bytes, cursor, limit, false)
1742}
1743
1744fn read_loose_measurement_core(
1745 bytes: &[u8],
1746 cursor: &mut usize,
1747 limit: usize,
1748 read_status: bool,
1749) -> Result<SerializableLooseMeasurement, FusionStateCodecError> {
1750 let t_j2000_s = read_f64(bytes, cursor, limit)?;
1751 let position_ecef_m = read_f64_array(bytes, cursor, limit)?;
1752 let velocity_ecef_mps = if read_bool(bytes, cursor, limit)? {
1753 Some(read_f64_array(bytes, cursor, limit)?)
1754 } else {
1755 None
1756 };
1757 let covariance = read_f64_matrix(bytes, cursor, limit)?;
1758 let satellites_used = read_u32(bytes, cursor, limit)?;
1759 let solution_valid = read_bool(bytes, cursor, limit)?;
1760 let fix_status = if read_status {
1761 read_fix_status(bytes, cursor, limit)?
1762 } else {
1763 SerializableGnssFixStatus::Single
1764 };
1765 Ok(SerializableLooseMeasurement {
1766 t_j2000_s,
1767 position_ecef_m,
1768 velocity_ecef_mps,
1769 covariance,
1770 satellites_used,
1771 solution_valid,
1772 fix_status,
1773 })
1774}
1775
1776fn write_fix_status(bytes: &mut Vec<u8>, status: SerializableGnssFixStatus) {
1777 bytes.push(match status {
1778 SerializableGnssFixStatus::Single => 0,
1779 SerializableGnssFixStatus::Float => 1,
1780 SerializableGnssFixStatus::Fixed => 2,
1781 });
1782}
1783
1784fn read_fix_status(
1785 bytes: &[u8],
1786 cursor: &mut usize,
1787 limit: usize,
1788) -> Result<SerializableGnssFixStatus, FusionStateCodecError> {
1789 match read_u8(bytes, cursor, limit)? {
1790 0 => Ok(SerializableGnssFixStatus::Single),
1791 1 => Ok(SerializableGnssFixStatus::Float),
1792 2 => Ok(SerializableGnssFixStatus::Fixed),
1793 _ => Err(FusionStateCodecError::InvalidState {
1794 reason: "invalid loose fix status".to_string(),
1795 }),
1796 }
1797}
1798
1799fn write_tight_epoch(
1800 bytes: &mut Vec<u8>,
1801 epoch: &SerializableTightGnssEpoch,
1802) -> Result<(), FusionStateCodecError> {
1803 write_f64(bytes, epoch.t_j2000_s);
1804 write_u32_checked(bytes, epoch.observations.len())?;
1805 for observation in &epoch.observations {
1806 write_tight_observation(bytes, observation);
1807 }
1808 Ok(())
1809}
1810
1811fn read_tight_epoch(
1812 bytes: &[u8],
1813 cursor: &mut usize,
1814 limit: usize,
1815) -> Result<SerializableTightGnssEpoch, FusionStateCodecError> {
1816 let t_j2000_s = read_f64(bytes, cursor, limit)?;
1817 let len = read_len(bytes, cursor, limit, 1, "tight_observations")?;
1818 let mut observations = Vec::with_capacity(len);
1819 for _ in 0..len {
1820 observations.push(read_tight_observation(bytes, cursor, limit)?);
1821 }
1822 Ok(SerializableTightGnssEpoch {
1823 t_j2000_s,
1824 observations,
1825 })
1826}
1827
1828fn write_tight_observation(bytes: &mut Vec<u8>, observation: &SerializableTightGnssObservation) {
1829 write_satellite_id(bytes, observation.satellite_id);
1830 write_f64(bytes, observation.pseudorange_m);
1831 write_f64(bytes, observation.pseudorange_sigma_m);
1832 match observation.range_rate {
1833 Some(range_rate) => {
1834 write_bool(bytes, true);
1835 write_range_rate_observation(bytes, range_rate);
1836 }
1837 None => write_bool(bytes, false),
1838 }
1839 match observation.carrier_phase {
1840 Some(carrier_phase) => {
1841 write_bool(bytes, true);
1842 write_carrier_phase_observation(bytes, carrier_phase);
1843 }
1844 None => write_bool(bytes, false),
1845 }
1846 write_f64(bytes, observation.ionosphere_delay_m);
1847 write_f64(bytes, observation.troposphere_delay_m);
1848}
1849
1850fn read_tight_observation(
1851 bytes: &[u8],
1852 cursor: &mut usize,
1853 limit: usize,
1854) -> Result<SerializableTightGnssObservation, FusionStateCodecError> {
1855 let satellite_id = read_satellite_id(bytes, cursor, limit)?;
1856 let pseudorange_m = read_f64(bytes, cursor, limit)?;
1857 let pseudorange_sigma_m = read_f64(bytes, cursor, limit)?;
1858 let range_rate = if read_bool(bytes, cursor, limit)? {
1859 Some(read_range_rate_observation(bytes, cursor, limit)?)
1860 } else {
1861 None
1862 };
1863 let carrier_phase = if read_bool(bytes, cursor, limit)? {
1864 Some(read_carrier_phase_observation(bytes, cursor, limit)?)
1865 } else {
1866 None
1867 };
1868 Ok(SerializableTightGnssObservation {
1869 satellite_id,
1870 pseudorange_m,
1871 pseudorange_sigma_m,
1872 range_rate,
1873 carrier_phase,
1874 ionosphere_delay_m: read_f64(bytes, cursor, limit)?,
1875 troposphere_delay_m: read_f64(bytes, cursor, limit)?,
1876 })
1877}
1878
1879fn write_range_rate_observation(
1880 bytes: &mut Vec<u8>,
1881 observation: SerializableTightRangeRateObservation,
1882) {
1883 write_f64(bytes, observation.measured_range_rate_m_s);
1884 write_f64(bytes, observation.sigma_m_s);
1885 write_f64(bytes, observation.satellite_clock_drift_m_s);
1886}
1887
1888fn read_range_rate_observation(
1889 bytes: &[u8],
1890 cursor: &mut usize,
1891 limit: usize,
1892) -> Result<SerializableTightRangeRateObservation, FusionStateCodecError> {
1893 Ok(SerializableTightRangeRateObservation {
1894 measured_range_rate_m_s: read_f64(bytes, cursor, limit)?,
1895 sigma_m_s: read_f64(bytes, cursor, limit)?,
1896 satellite_clock_drift_m_s: read_f64(bytes, cursor, limit)?,
1897 })
1898}
1899
1900fn write_carrier_phase_observation(
1901 bytes: &mut Vec<u8>,
1902 observation: SerializableTightCarrierPhaseObservation,
1903) {
1904 write_f64(bytes, observation.phase_range_m);
1905 write_f64(bytes, observation.sigma_m);
1906 write_f64(bytes, observation.float_ambiguity_m);
1907}
1908
1909fn read_carrier_phase_observation(
1910 bytes: &[u8],
1911 cursor: &mut usize,
1912 limit: usize,
1913) -> Result<SerializableTightCarrierPhaseObservation, FusionStateCodecError> {
1914 Ok(SerializableTightCarrierPhaseObservation {
1915 phase_range_m: read_f64(bytes, cursor, limit)?,
1916 sigma_m: read_f64(bytes, cursor, limit)?,
1917 float_ambiguity_m: read_f64(bytes, cursor, limit)?,
1918 })
1919}
1920
1921fn write_satellite_id(bytes: &mut Vec<u8>, id: SerializableSatelliteId) {
1922 bytes.push(match id.system {
1923 GnssSystem::Gps => 0,
1924 GnssSystem::Glonass => 1,
1925 GnssSystem::Galileo => 2,
1926 GnssSystem::BeiDou => 3,
1927 GnssSystem::Qzss => 4,
1928 GnssSystem::Navic => 5,
1929 GnssSystem::Sbas => 6,
1930 });
1931 bytes.push(id.prn);
1932}
1933
1934fn read_satellite_id(
1935 bytes: &[u8],
1936 cursor: &mut usize,
1937 limit: usize,
1938) -> Result<SerializableSatelliteId, FusionStateCodecError> {
1939 let system = match read_u8(bytes, cursor, limit)? {
1940 0 => GnssSystem::Gps,
1941 1 => GnssSystem::Glonass,
1942 2 => GnssSystem::Galileo,
1943 3 => GnssSystem::BeiDou,
1944 4 => GnssSystem::Qzss,
1945 5 => GnssSystem::Navic,
1946 6 => GnssSystem::Sbas,
1947 _ => {
1948 return Err(FusionStateCodecError::InvalidState {
1949 reason: "invalid GNSS system tag".to_string(),
1950 });
1951 }
1952 };
1953 Ok(SerializableSatelliteId {
1954 system,
1955 prn: read_u8(bytes, cursor, limit)?,
1956 })
1957}
1958
1959fn write_f64_matrix(
1960 bytes: &mut Vec<u8>,
1961 matrix: &[Vec<F64Bits>],
1962) -> Result<(), FusionStateCodecError> {
1963 write_u32_checked(bytes, matrix.len())?;
1964 let cols = matrix.first().map_or(0, Vec::len);
1965 write_u32_checked(bytes, cols)?;
1966 for row in matrix {
1967 if row.len() != cols {
1968 return Err(FusionStateCodecError::InvalidState {
1969 reason: "ragged matrix cannot be encoded".to_string(),
1970 });
1971 }
1972 write_f64_vec_body(bytes, row);
1973 }
1974 Ok(())
1975}
1976
1977fn read_f64_matrix(
1978 bytes: &[u8],
1979 cursor: &mut usize,
1980 limit: usize,
1981) -> Result<Vec<Vec<F64Bits>>, FusionStateCodecError> {
1982 let rows = read_u32(bytes, cursor, limit)? as usize;
1983 let cols = read_u32(bytes, cursor, limit)? as usize;
1984 if rows == 0 || cols == 0 {
1985 return Err(FusionStateCodecError::InvalidState {
1986 reason: "matrix dimensions must be positive".to_string(),
1987 });
1988 }
1989 let count = rows
1990 .checked_mul(cols)
1991 .ok_or_else(|| FusionStateCodecError::InvalidState {
1992 reason: "matrix dimensions overflow usize".to_string(),
1993 })?;
1994 let needed = count
1995 .checked_mul(8)
1996 .ok_or_else(|| FusionStateCodecError::InvalidState {
1997 reason: "matrix byte length overflows usize".to_string(),
1998 })?;
1999 ensure_available(*cursor, needed, limit)?;
2000 let mut matrix = Vec::with_capacity(rows);
2001 for _ in 0..rows {
2002 let mut row = Vec::with_capacity(cols);
2003 for _ in 0..cols {
2004 row.push(read_f64(bytes, cursor, limit)?);
2005 }
2006 matrix.push(row);
2007 }
2008 Ok(matrix)
2009}
2010
2011fn write_f64_vec(bytes: &mut Vec<u8>, values: &[F64Bits]) -> Result<(), FusionStateCodecError> {
2012 write_u32_checked(bytes, values.len())?;
2013 write_f64_vec_body(bytes, values);
2014 Ok(())
2015}
2016
2017fn write_f64_vec_body(bytes: &mut Vec<u8>, values: &[F64Bits]) {
2018 for value in values {
2019 write_f64(bytes, *value);
2020 }
2021}
2022
2023fn read_f64_vec(
2024 bytes: &[u8],
2025 cursor: &mut usize,
2026 limit: usize,
2027) -> Result<Vec<F64Bits>, FusionStateCodecError> {
2028 let len = read_u32(bytes, cursor, limit)? as usize;
2029 let needed = len
2030 .checked_mul(8)
2031 .ok_or_else(|| FusionStateCodecError::InvalidState {
2032 reason: "vector byte length overflows usize".to_string(),
2033 })?;
2034 ensure_available(*cursor, needed, limit)?;
2035 let mut values = Vec::with_capacity(len);
2036 for _ in 0..len {
2037 values.push(read_f64(bytes, cursor, limit)?);
2038 }
2039 Ok(values)
2040}
2041
2042fn write_f64_array<const N: usize>(bytes: &mut Vec<u8>, values: &[F64Bits; N]) {
2043 for value in values {
2044 write_f64(bytes, *value);
2045 }
2046}
2047
2048fn read_f64_array<const N: usize>(
2049 bytes: &[u8],
2050 cursor: &mut usize,
2051 limit: usize,
2052) -> Result<[F64Bits; N], FusionStateCodecError> {
2053 let mut out = [F64Bits { bits: 0 }; N];
2054 for value in &mut out {
2055 *value = read_f64(bytes, cursor, limit)?;
2056 }
2057 Ok(out)
2058}
2059
2060fn write_f64(bytes: &mut Vec<u8>, value: F64Bits) {
2061 write_u64(bytes, value.bits);
2062}
2063
2064fn read_f64(
2065 bytes: &[u8],
2066 cursor: &mut usize,
2067 limit: usize,
2068) -> Result<F64Bits, FusionStateCodecError> {
2069 Ok(F64Bits {
2070 bits: read_u64(bytes, cursor, limit)?,
2071 })
2072}
2073
2074fn write_u16(bytes: &mut Vec<u8>, value: u16) {
2075 bytes.extend_from_slice(&value.to_le_bytes());
2076}
2077
2078fn write_u32_checked(bytes: &mut Vec<u8>, value: usize) -> Result<(), FusionStateCodecError> {
2079 let value = u32::try_from(value).map_err(|_| FusionStateCodecError::InvalidState {
2080 reason: "length exceeds u32".to_string(),
2081 })?;
2082 bytes.extend_from_slice(&value.to_le_bytes());
2083 Ok(())
2084}
2085
2086fn write_u64(bytes: &mut Vec<u8>, value: u64) {
2087 bytes.extend_from_slice(&value.to_le_bytes());
2088}
2089
2090fn write_bool(bytes: &mut Vec<u8>, value: bool) {
2091 bytes.push(u8::from(value));
2092}
2093
2094fn read_bool(
2095 bytes: &[u8],
2096 cursor: &mut usize,
2097 limit: usize,
2098) -> Result<bool, FusionStateCodecError> {
2099 match read_u8(bytes, cursor, limit)? {
2100 0 => Ok(false),
2101 1 => Ok(true),
2102 _ => Err(FusionStateCodecError::InvalidState {
2103 reason: "invalid boolean tag".to_string(),
2104 }),
2105 }
2106}
2107
2108fn read_u8(bytes: &[u8], cursor: &mut usize, limit: usize) -> Result<u8, FusionStateCodecError> {
2109 ensure_available(*cursor, 1, limit)?;
2110 let value = bytes[*cursor];
2111 *cursor += 1;
2112 Ok(value)
2113}
2114
2115fn read_u16(bytes: &[u8], cursor: &mut usize, limit: usize) -> Result<u16, FusionStateCodecError> {
2116 let data = read_array::<2>(bytes, *cursor, limit)?;
2117 *cursor += 2;
2118 Ok(u16::from_le_bytes(data))
2119}
2120
2121fn read_u32(bytes: &[u8], cursor: &mut usize, limit: usize) -> Result<u32, FusionStateCodecError> {
2122 let data = read_array::<4>(bytes, *cursor, limit)?;
2123 *cursor += 4;
2124 Ok(u32::from_le_bytes(data))
2125}
2126
2127fn read_u64(bytes: &[u8], cursor: &mut usize, limit: usize) -> Result<u64, FusionStateCodecError> {
2128 let data = read_array::<8>(bytes, *cursor, limit)?;
2129 *cursor += 8;
2130 Ok(u64::from_le_bytes(data))
2131}
2132
2133fn read_u64_at(bytes: &[u8], offset: usize) -> Result<u64, FusionStateCodecError> {
2134 Ok(u64::from_le_bytes(read_array::<8>(
2135 bytes,
2136 offset,
2137 bytes.len(),
2138 )?))
2139}
2140
2141fn read_len(
2142 bytes: &[u8],
2143 cursor: &mut usize,
2144 limit: usize,
2145 min_element_bytes: usize,
2146 field: &'static str,
2147) -> Result<usize, FusionStateCodecError> {
2148 let len = read_u32(bytes, cursor, limit)? as usize;
2149 let needed =
2150 len.checked_mul(min_element_bytes)
2151 .ok_or_else(|| FusionStateCodecError::InvalidState {
2152 reason: format!("{field} byte length overflows usize"),
2153 })?;
2154 ensure_available(*cursor, needed, limit)?;
2155 Ok(len)
2156}
2157
2158fn read_array<const N: usize>(
2159 bytes: &[u8],
2160 offset: usize,
2161 limit: usize,
2162) -> Result<[u8; N], FusionStateCodecError> {
2163 ensure_available(offset, N, limit)?;
2164 let end = offset + N;
2165 let mut out = [0u8; N];
2166 out.copy_from_slice(&bytes[offset..end]);
2167 Ok(out)
2168}
2169
2170fn ensure_available(
2171 offset: usize,
2172 needed: usize,
2173 limit: usize,
2174) -> Result<(), FusionStateCodecError> {
2175 let end = offset
2176 .checked_add(needed)
2177 .ok_or(FusionStateCodecError::Truncated {
2178 offset,
2179 needed,
2180 actual: limit.saturating_sub(offset),
2181 })?;
2182 if end <= limit {
2183 Ok(())
2184 } else {
2185 Err(FusionStateCodecError::Truncated {
2186 offset,
2187 needed,
2188 actual: limit.saturating_sub(offset),
2189 })
2190 }
2191}
2192
2193fn fnv1a64(bytes: &[u8]) -> u64 {
2194 bytes.iter().fold(FNV_OFFSET_BASIS, |hash, byte| {
2195 (hash ^ u64::from(*byte)).wrapping_mul(FNV_PRIME)
2196 })
2197}
2198
2199#[cfg(test)]
2200mod tests {
2201 use super::*;
2207 use crate::astro::constants::earth::WGS84_A_M;
2208 use crate::fusion::state::{ErrorStateLayout, ERROR_STATE_DIMENSION_15};
2209 use crate::fusion::TimeSyncHistoryConfig;
2210 use crate::inertial::config::RANDOM_WALK_BIAS_TAU_S;
2211 use crate::inertial::state::mat3_identity;
2212 use crate::inertial::{ImuSample, ImuSpec, NavState};
2213
2214 fn test_filter() -> InertialFilter {
2215 let nominal = NavState::new(
2216 12.5,
2217 [WGS84_A_M, -0.0, 3.25],
2218 [0.5, -0.25, 0.125],
2219 mat3_identity(),
2220 )
2221 .expect("nominal")
2222 .with_biases([0.01, -0.02, 0.03], [-0.001, 0.002, -0.003])
2223 .expect("biases");
2224 let mut diagonal = vec![1.0; ERROR_STATE_DIMENSION_15];
2225 diagonal[0] = 4.0;
2226 diagonal[1] = 9.0;
2227 let state = InsFilterState::from_diagonal(nominal, ErrorStateLayout::Fifteen, &diagonal)
2228 .expect("state");
2229 let spec = ImuSpec::datasheet(
2230 0.0,
2231 0.0,
2232 0.0,
2233 0.0,
2234 RANDOM_WALK_BIAS_TAU_S,
2235 RANDOM_WALK_BIAS_TAU_S,
2236 None,
2237 None,
2238 );
2239 InertialFilter::new(state, spec).expect("filter")
2240 }
2241
2242 fn increment(t_j2000_s: f64, dt_s: f64) -> ImuSample {
2243 ImuSample::increment(
2244 t_j2000_s,
2245 [0.015625 * dt_s, -0.0078125 * dt_s, 0.00390625 * dt_s],
2246 [
2247 0.0009765625 * dt_s,
2248 -0.00048828125 * dt_s,
2249 0.000244140625 * dt_s,
2250 ],
2251 dt_s,
2252 )
2253 }
2254
2255 fn measurement_at(t_j2000_s: f64, position_ecef_m: [f64; 3]) -> GnssFixMeasurement {
2256 GnssFixMeasurement::position(
2257 t_j2000_s,
2258 position_ecef_m,
2259 [[4.0, 0.0, 0.0], [0.0, 5.0, 0.0], [0.0, 0.0, 6.0]],
2260 8,
2261 )
2262 .expect("measurement")
2263 }
2264
2265 #[test]
2266 fn binary_stored_loose_measurement_preserves_fix_status() {
2267 let measurement = SerializableLooseMeasurement::from_native(
2268 &measurement_at(13.0, [WGS84_A_M + 0.25, -0.125, 3.5])
2269 .with_fix_status(GnssFixStatus::Float),
2270 )
2271 .expect("serial measurement");
2272 let stored = SerializableStoredGnssMeasurement::Loose(measurement.clone());
2273 let mut encoded = Vec::new();
2274 write_stored_measurement(&mut encoded, &stored).expect("write measurement");
2275
2276 let mut cursor = 0usize;
2277 let decoded = read_stored_measurement(&encoded, &mut cursor, encoded.len()).expect("read");
2278
2279 assert_eq!(decoded, stored);
2280 assert_eq!(cursor, encoded.len());
2281 }
2282
2283 #[test]
2284 fn binary_stored_loose_measurement_old_tag_defaults_fix_status() {
2285 let measurement = SerializableLooseMeasurement::from_native(
2286 &measurement_at(13.0, [WGS84_A_M + 0.25, -0.125, 3.5])
2287 .with_fix_status(GnssFixStatus::Fixed),
2288 )
2289 .expect("serial measurement");
2290 let mut encoded = Vec::new();
2291 encoded.push(0);
2292 write_f64(&mut encoded, measurement.t_j2000_s);
2293 write_f64_array(&mut encoded, &measurement.position_ecef_m);
2294 if let Some(velocity) = measurement.velocity_ecef_mps {
2295 write_bool(&mut encoded, true);
2296 write_f64_array(&mut encoded, &velocity);
2297 } else {
2298 write_bool(&mut encoded, false);
2299 }
2300 write_f64_matrix(&mut encoded, &measurement.covariance).expect("covariance");
2301 write_u32_checked(&mut encoded, measurement.satellites_used as usize).expect("satellites");
2302 write_bool(&mut encoded, measurement.solution_valid);
2303
2304 let mut cursor = 0usize;
2305 let decoded = read_stored_measurement(&encoded, &mut cursor, encoded.len()).expect("read");
2306
2307 let SerializableStoredGnssMeasurement::Loose(decoded) = decoded else {
2308 panic!("expected loose measurement");
2309 };
2310 assert_eq!(decoded.fix_status, SerializableGnssFixStatus::Single);
2311 assert_eq!(decoded.t_j2000_s, measurement.t_j2000_s);
2312 assert_eq!(decoded.position_ecef_m, measurement.position_ecef_m);
2313 assert_eq!(cursor, encoded.len());
2314 }
2315
2316 #[test]
2317 fn binary_and_json_round_trip_preserve_bits() {
2318 let filter = test_filter();
2319 let serial = filter.serializable_state().expect("serial state");
2320 let encoded = serial.encode_versioned().expect("encode");
2321 let decoded = SerializableFusionState::decode_versioned(&encoded).expect("decode");
2322 assert_eq!(decoded, serial);
2323 assert_snapshot_bits(
2324 &decoded.to_snapshot().expect("snapshot"),
2325 &filter.snapshot(),
2326 );
2327
2328 let json = serial.to_json_string().expect("json");
2329 let decoded_json = SerializableFusionState::from_json_str(&json).expect("json decode");
2330 assert_eq!(decoded_json, serial);
2331 assert_snapshot_bits(
2332 &decoded_json.to_snapshot().expect("json snapshot"),
2333 &filter.snapshot(),
2334 );
2335 }
2336
2337 #[test]
2338 fn binary_decoder_accepts_legacy_v2_without_stationarity_window() {
2339 let filter = test_filter();
2340 let serial = filter.serializable_state().expect("serial state");
2341 let encoded = encode_legacy_without_stationarity(&serial, 2).expect("legacy encode");
2342
2343 let decoded = SerializableFusionState::decode_versioned(&encoded).expect("decode v2");
2344 assert_eq!(decoded.version, 2);
2345 let mut expected = filter.snapshot();
2346 expected.stationarity_window.clear();
2347 expected.last_stationary_update_t_j2000_s = None;
2348 expected.last_non_holonomic_update_t_j2000_s = None;
2349 assert_snapshot_bits(&decoded.to_snapshot().expect("snapshot"), &expected);
2350 }
2351
2352 #[test]
2353 fn truncated_and_corrupted_payloads_are_typed_errors() {
2354 let serial = test_filter().serializable_state().expect("serial state");
2355 let encoded = serial.encode_versioned().expect("encode");
2356 let truncated = &encoded[..encoded.len() - 3];
2357 assert!(matches!(
2358 SerializableFusionState::decode_versioned(truncated),
2359 Err(FusionStateCodecError::Checksum { .. })
2360 ));
2361
2362 let mut corrupted = encoded;
2363 let idx = corrupted.len() / 2;
2364 corrupted[idx] ^= 0x55;
2365 assert!(matches!(
2366 SerializableFusionState::decode_versioned(&corrupted),
2367 Err(FusionStateCodecError::Checksum { .. })
2368 ));
2369
2370 let too_short = [0u8; 5];
2371 assert!(matches!(
2372 SerializableFusionState::decode_versioned(&too_short),
2373 Err(FusionStateCodecError::Truncated { .. })
2374 ));
2375 }
2376
2377 #[test]
2378 fn malformed_matrix_dimensions_are_typed_errors() {
2379 let mut bytes = Vec::new();
2380 bytes.extend_from_slice(&u32::MAX.to_le_bytes());
2381 bytes.extend_from_slice(&0u32.to_le_bytes());
2382 let mut cursor = 0usize;
2383 assert!(matches!(
2384 read_f64_matrix(&bytes, &mut cursor, bytes.len()),
2385 Err(FusionStateCodecError::InvalidState { .. })
2386 ));
2387 }
2388
2389 #[test]
2390 fn restored_encoded_state_retains_time_sync_history_for_late_replay_bits() {
2391 let first = measurement_at(13.0, [WGS84_A_M + 0.25, -0.125, 3.5]);
2392 let late = measurement_at(13.25, [WGS84_A_M - 0.0625, 0.1875, 3.0]);
2393 let final_fix = measurement_at(13.5, [WGS84_A_M + 0.03125, -0.25, 3.125]);
2394
2395 let mut direct = test_filter();
2396 direct
2397 .configure_time_sync_history(TimeSyncHistoryConfig::new(8, 8))
2398 .expect("history");
2399 direct.propagate(increment(13.0, 0.5)).expect("imu");
2400 direct.update_loose(&first).expect("first");
2401 direct.propagate(increment(13.25, 0.25)).expect("imu");
2402 direct.update_loose(&late).expect("late in order");
2403 direct.propagate(increment(13.5, 0.25)).expect("imu");
2404 direct.update_loose(&final_fix).expect("final");
2405 direct.propagate(increment(14.0, 0.5)).expect("imu");
2406
2407 let mut delayed = test_filter();
2408 delayed
2409 .configure_time_sync_history(TimeSyncHistoryConfig::new(8, 8))
2410 .expect("history");
2411 delayed.propagate(increment(13.0, 0.5)).expect("imu");
2412 delayed.update_loose(&first).expect("first");
2413 delayed.propagate(increment(13.5, 0.5)).expect("imu");
2414 delayed.update_loose(&final_fix).expect("final");
2415 delayed.propagate(increment(14.0, 0.5)).expect("imu");
2416 let encoded = delayed.encode_state().expect("encode");
2417
2418 let mut restored = test_filter();
2419 restored.restore_encoded_state(&encoded).expect("restore");
2420 let update = restored
2421 .update_loose_time_sync(&late)
2422 .expect("late replay after restore");
2423
2424 assert!(update.late_measurement);
2425 assert_snapshot_bits(&restored.snapshot(), &direct.snapshot());
2426 }
2427
2428 fn assert_snapshot_bits(actual: &InertialFilterSnapshot, expected: &InertialFilterSnapshot) {
2429 assert_eq!(
2430 actual.state.nominal.t_j2000_s.to_bits(),
2431 expected.state.nominal.t_j2000_s.to_bits()
2432 );
2433 for axis in 0..3 {
2434 assert_eq!(
2435 actual.state.nominal.position_ecef_m[axis].to_bits(),
2436 expected.state.nominal.position_ecef_m[axis].to_bits()
2437 );
2438 assert_eq!(
2439 actual.last_body_rate_wrt_ecef_rps[axis].to_bits(),
2440 expected.last_body_rate_wrt_ecef_rps[axis].to_bits()
2441 );
2442 }
2443 assert_eq!(
2444 actual.stationarity_window.len(),
2445 expected.stationarity_window.len()
2446 );
2447 for (actual_sample, expected_sample) in actual
2448 .stationarity_window
2449 .iter()
2450 .zip(expected.stationarity_window.iter())
2451 {
2452 assert_eq!(
2453 actual_sample.specific_force_norm_error_mps2.to_bits(),
2454 expected_sample.specific_force_norm_error_mps2.to_bits()
2455 );
2456 assert_eq!(
2457 actual_sample.body_rate_wrt_ecef_norm_rps.to_bits(),
2458 expected_sample.body_rate_wrt_ecef_norm_rps.to_bits()
2459 );
2460 }
2461 assert_eq!(
2462 actual.last_stationary_update_t_j2000_s.map(f64::to_bits),
2463 expected.last_stationary_update_t_j2000_s.map(f64::to_bits)
2464 );
2465 assert_eq!(
2466 actual.last_non_holonomic_update_t_j2000_s.map(f64::to_bits),
2467 expected
2468 .last_non_holonomic_update_t_j2000_s
2469 .map(f64::to_bits)
2470 );
2471 for row in 0..actual.state.covariance.len() {
2472 for col in 0..actual.state.covariance[row].len() {
2473 assert_eq!(
2474 actual.state.covariance[row][col].to_bits(),
2475 expected.state.covariance[row][col].to_bits()
2476 );
2477 }
2478 }
2479 assert_eq!(
2480 actual.tight.clock_bias_m.to_bits(),
2481 expected.tight.clock_bias_m.to_bits()
2482 );
2483 assert_eq!(
2484 actual.tight.clock_drift_m_s.to_bits(),
2485 expected.tight.clock_drift_m_s.to_bits()
2486 );
2487 for row in 0..actual.tight.augmented_covariance.len() {
2488 for col in 0..actual.tight.augmented_covariance[row].len() {
2489 assert_eq!(
2490 actual.tight.augmented_covariance[row][col].to_bits(),
2491 expected.tight.augmented_covariance[row][col].to_bits()
2492 );
2493 }
2494 }
2495 }
2496
2497 fn encode_legacy_without_stationarity(
2498 serial: &SerializableFusionState,
2499 version: u16,
2500 ) -> Result<Vec<u8>, FusionStateCodecError> {
2501 let mut bytes = Vec::new();
2502 bytes.extend_from_slice(&FUSION_STATE_MAGIC);
2503 write_u16(&mut bytes, version);
2504 write_layout(&mut bytes, serial.state.layout);
2505 write_nav(&mut bytes, &serial.state.nominal);
2506 write_f64_vec(&mut bytes, &serial.state.error_state)?;
2507 write_f64_matrix(&mut bytes, &serial.state.covariance)?;
2508 write_f64_array(&mut bytes, &serial.state.accel_scale_factor);
2509 write_f64_array(&mut bytes, &serial.state.gyro_scale_factor);
2510 write_f64_array(&mut bytes, &serial.last_body_rate_wrt_ecef_rps);
2511 write_f64(&mut bytes, serial.tight.clock_bias_m);
2512 write_f64(&mut bytes, serial.tight.clock_drift_m_s);
2513 write_f64_matrix(&mut bytes, &serial.tight.augmented_covariance)?;
2514 write_time_sync_history_legacy_without_stationarity(&mut bytes, &serial.time_sync)?;
2515 let checksum = fnv1a64(&bytes);
2516 write_u64(&mut bytes, checksum);
2517 Ok(bytes)
2518 }
2519
2520 fn write_time_sync_history_legacy_without_stationarity(
2521 bytes: &mut Vec<u8>,
2522 history: &SerializableTimeSyncHistory,
2523 ) -> Result<(), FusionStateCodecError> {
2524 write_u32_checked(bytes, history.config.imu_capacity as usize)?;
2525 write_u32_checked(bytes, history.config.checkpoint_capacity as usize)?;
2526 write_u32_checked(bytes, history.imu_samples.len())?;
2527 for sample in &history.imu_samples {
2528 write_stored_imu_sample(bytes, sample);
2529 }
2530 write_u32_checked(bytes, history.checkpoints.len())?;
2531 for checkpoint in &history.checkpoints {
2532 write_f64(bytes, checkpoint.t_j2000_s);
2533 write_fusion_snapshot_legacy_without_stationarity(bytes, checkpoint.snapshot.as_ref())?;
2534 }
2535 write_u32_checked(bytes, history.measurements.len())?;
2536 for measurement in &history.measurements {
2537 write_stored_measurement(bytes, measurement)?;
2538 }
2539 Ok(())
2540 }
2541
2542 fn write_fusion_snapshot_legacy_without_stationarity(
2543 bytes: &mut Vec<u8>,
2544 snapshot: &SerializableFusionSnapshot,
2545 ) -> Result<(), FusionStateCodecError> {
2546 write_layout(bytes, snapshot.state.layout);
2547 write_nav(bytes, &snapshot.state.nominal);
2548 write_f64_vec(bytes, &snapshot.state.error_state)?;
2549 write_f64_matrix(bytes, &snapshot.state.covariance)?;
2550 write_f64_array(bytes, &snapshot.state.accel_scale_factor);
2551 write_f64_array(bytes, &snapshot.state.gyro_scale_factor);
2552 write_f64_array(bytes, &snapshot.last_body_rate_wrt_ecef_rps);
2553 write_f64(bytes, snapshot.tight.clock_bias_m);
2554 write_f64(bytes, snapshot.tight.clock_drift_m_s);
2555 write_f64_matrix(bytes, &snapshot.tight.augmented_covariance)
2556 }
2557}