1extern crate alloc;
80
81use alloc::string::{String, ToString};
82use alloc::vec::Vec;
83use core::fmt;
84
85const DAF_RECORD_BYTES: usize = 1024;
86const DAF_ID_BYTES: usize = 8;
87const DAF_INTERNAL_NAME_BYTES: usize = 60;
88const DAF_BINARY_FORMAT_OFFSET: usize = 88;
89const DAF_BINARY_FORMAT_BYTES: usize = 8;
90const DAF_FILE_RECORD_BYTES: usize = DAF_RECORD_BYTES;
91const SUMMARY_CONTROL_WORDS: usize = 3;
92const SPK_ND: i32 = 2;
93const SPK_NI: i32 = 6;
94const SPK_TYPE_2: i32 = 2;
95const SPK_TYPE_3: i32 = 3;
96const SPK_TYPE_21: i32 = 21;
97const SPK_TYPE_21_MAX_TABLE_DIM: usize = 25;
100const SPK_TYPE_21_DIRECTORY_STRIDE: usize = 100;
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub enum DafByteOrder {
106 LittleEndian,
108 BigEndian,
110}
111
112impl DafByteOrder {
113 fn read_i32(self, bytes: &[u8], offset: usize, field: &'static str) -> Result<i32, SpkError> {
114 let data = bytes.get(offset..offset + 4).ok_or(SpkError::Truncated {
115 context: field,
116 needed: offset + 4,
117 actual: bytes.len(),
118 })?;
119 let mut word = [0u8; 4];
120 word.copy_from_slice(data);
121 Ok(match self {
122 DafByteOrder::LittleEndian => i32::from_le_bytes(word),
123 DafByteOrder::BigEndian => i32::from_be_bytes(word),
124 })
125 }
126
127 fn read_f64(self, bytes: &[u8], offset: usize, field: &'static str) -> Result<f64, SpkError> {
128 let data = bytes.get(offset..offset + 8).ok_or(SpkError::Truncated {
129 context: field,
130 needed: offset + 8,
131 actual: bytes.len(),
132 })?;
133 let mut word = [0u8; 8];
134 word.copy_from_slice(data);
135 Ok(match self {
136 DafByteOrder::LittleEndian => f64::from_le_bytes(word),
137 DafByteOrder::BigEndian => f64::from_be_bytes(word),
138 })
139 }
140}
141
142#[derive(Debug, Clone, PartialEq)]
144pub struct DafFileRecord {
145 pub id_word: String,
147 pub file_type: String,
149 pub double_components: i32,
151 pub integer_components: i32,
153 pub internal_name: String,
155 pub forward_record: i32,
157 pub backward_record: i32,
159 pub free_address: i32,
161 pub byte_order: DafByteOrder,
163 pub binary_format: String,
165}
166
167#[derive(Debug, Clone, PartialEq)]
169pub struct SpkSegmentDescriptor {
170 pub name: String,
172 pub start_et: f64,
174 pub stop_et: f64,
176 pub target: i32,
178 pub center: i32,
180 pub frame: i32,
182 pub data_type: i32,
184 pub start_address: i32,
186 pub end_address: i32,
188}
189
190#[derive(Debug, Clone, PartialEq)]
192pub struct DafSpk {
193 pub file_record: DafFileRecord,
195 pub segments: Vec<SpkSegmentDescriptor>,
197}
198
199#[derive(Debug, Clone, PartialEq)]
201pub struct Spk {
202 bytes: Vec<u8>,
203 directory: DafSpk,
204}
205
206#[derive(Debug, Clone, Copy, PartialEq)]
208pub struct SpkStateVector {
209 pub position_km: [f64; 3],
211 pub velocity_km_s: [f64; 3],
213}
214
215#[derive(Debug, Clone, Copy, PartialEq)]
217pub struct SpkState {
218 pub target: i32,
220 pub center: i32,
222 pub position_km: [f64; 3],
224 pub velocity_km_s: Option<[f64; 3]>,
229 pub frame: i32,
231}
232
233#[derive(Debug, Clone, PartialEq)]
235pub enum SpkError {
236 Io {
238 path: String,
240 message: String,
242 },
243 Truncated {
245 context: &'static str,
247 needed: usize,
249 actual: usize,
251 },
252 UnsupportedDafId {
254 id_word: String,
256 },
257 UnsupportedBinaryFormat {
259 binary_format: String,
261 },
262 UnsupportedSummaryShape {
264 nd: i32,
266 ni: i32,
268 },
269 InvalidField {
271 field: &'static str,
273 value: i32,
275 },
276 InvalidDoubleField {
278 field: &'static str,
280 value: f64,
282 },
283 OutOfCoverage {
285 et: f64,
287 start_et: f64,
289 stop_et: f64,
291 },
292 UnsupportedSegmentType {
294 expected: i32,
296 actual: i32,
298 },
299 InvalidSegmentLayout {
301 context: &'static str,
303 },
304 UnknownBody {
306 body: i32,
308 },
309 NoSegmentPath {
311 target: i32,
313 center: i32,
315 },
316 CoverageGap {
318 target: i32,
320 center: i32,
322 et: f64,
324 },
325 UnsupportedStateSegmentType {
327 data_type: i32,
329 },
330 FrameMismatch {
332 first: i32,
334 second: i32,
336 },
337}
338
339impl fmt::Display for SpkError {
340 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
341 match self {
342 SpkError::Io { path, message } => {
343 write!(f, "failed to read SPK kernel {path}: {message}")
344 }
345 SpkError::Truncated {
346 context,
347 needed,
348 actual,
349 } => write!(
350 f,
351 "truncated SPK/DAF input while reading {context}: need {needed} bytes, have {actual}"
352 ),
353 SpkError::UnsupportedDafId { id_word } => {
354 write!(f, "unsupported DAF identification word {id_word:?}")
355 }
356 SpkError::UnsupportedBinaryFormat { binary_format } => {
357 write!(f, "unsupported DAF binary format {binary_format:?}")
358 }
359 SpkError::UnsupportedSummaryShape { nd, ni } => {
360 write!(f, "unsupported SPK summary shape ND={nd}, NI={ni}")
361 }
362 SpkError::InvalidField { field, value } => {
363 write!(f, "invalid SPK/DAF field {field}: {value}")
364 }
365 SpkError::InvalidDoubleField { field, value } => {
366 write!(f, "invalid SPK field {field}: {value}")
367 }
368 SpkError::OutOfCoverage {
369 et,
370 start_et,
371 stop_et,
372 } => write!(
373 f,
374 "ET {et} is outside SPK segment coverage [{start_et}, {stop_et}]"
375 ),
376 SpkError::UnsupportedSegmentType { expected, actual } => write!(
377 f,
378 "unsupported SPK segment type {actual}; expected type {expected}"
379 ),
380 SpkError::InvalidSegmentLayout { context } => {
381 write!(f, "invalid SPK segment layout: {context}")
382 }
383 SpkError::UnknownBody { body } => {
384 write!(f, "unknown SPK body {body}")
385 }
386 SpkError::NoSegmentPath { target, center } => {
387 write!(f, "no SPK segment path from target {target} to center {center}")
388 }
389 SpkError::CoverageGap { target, center, et } => write!(
390 f,
391 "no SPK segment path from target {target} to center {center} covers ET {et}"
392 ),
393 SpkError::UnsupportedStateSegmentType { data_type } => {
394 write!(f, "unsupported SPK state segment type {data_type}")
395 }
396 SpkError::FrameMismatch { first, second } => write!(
397 f,
398 "cannot chain SPK states across frame ids {first} and {second}"
399 ),
400 }
401 }
402}
403
404#[cfg(feature = "std")]
405impl std::error::Error for SpkError {}
406
407impl Spk {
408 pub fn from_bytes(bytes: &[u8]) -> Result<Self, SpkError> {
410 Self::from_vec(bytes.to_vec())
411 }
412
413 #[cfg(feature = "std")]
415 pub fn load(path: impl AsRef<std::path::Path>) -> Result<Self, SpkError> {
416 let path = path.as_ref();
417 let bytes = std::fs::read(path).map_err(|error| SpkError::Io {
418 path: path.display().to_string(),
419 message: error.to_string(),
420 })?;
421 Self::from_vec(bytes)
422 }
423
424 pub fn file_record(&self) -> &DafFileRecord {
426 &self.directory.file_record
427 }
428
429 pub fn segments(&self) -> &[SpkSegmentDescriptor] {
431 &self.directory.segments
432 }
433
434 pub fn spk_state(&self, target: i32, center: i32, et: f64) -> Result<SpkState, SpkError> {
436 if !et.is_finite() {
437 return Err(SpkError::InvalidDoubleField {
438 field: "ET",
439 value: et,
440 });
441 }
442
443 if !self.body_is_known(target) {
444 return Err(SpkError::UnknownBody { body: target });
445 }
446 if !self.body_is_known(center) {
447 return Err(SpkError::UnknownBody { body: center });
448 }
449 if target == center {
450 if !self.body_has_coverage_at(target, et) {
451 return Err(SpkError::CoverageGap { target, center, et });
452 }
453 return Ok(SpkState {
454 target,
455 center,
456 position_km: [0.0; 3],
457 velocity_km_s: Some([0.0; 3]),
458 frame: 0,
459 });
460 }
461 if !self.has_segment_path(target, center) {
462 return Err(SpkError::NoSegmentPath { target, center });
463 }
464
465 self.covering_state_path(target, center, et)
466 .ok_or(SpkError::CoverageGap { target, center, et })?
467 }
468
469 fn from_vec(bytes: Vec<u8>) -> Result<Self, SpkError> {
470 let directory = parse_daf_spk(&bytes)?;
471 Ok(Self { bytes, directory })
472 }
473
474 fn body_is_known(&self, body: i32) -> bool {
475 self.directory
476 .segments
477 .iter()
478 .any(|segment| segment.target == body || segment.center == body)
479 }
480
481 fn body_has_coverage_at(&self, body: i32, et: f64) -> bool {
482 self.directory.segments.iter().any(|segment| {
483 (segment.target == body || segment.center == body)
484 && et >= segment.start_et
485 && et <= segment.stop_et
486 })
487 }
488
489 fn has_segment_path(&self, target: i32, center: i32) -> bool {
490 let mut visited = Vec::new();
491 let mut queue = Vec::new();
492 visited.push(target);
493 queue.push(target);
494
495 let mut cursor = 0;
496 while cursor < queue.len() {
497 let body = queue[cursor];
498 cursor += 1;
499
500 if body == center {
501 return true;
502 }
503
504 for segment in self.directory.segments.iter().rev() {
505 let next = if segment.target == body {
506 segment.center
507 } else if segment.center == body {
508 segment.target
509 } else {
510 continue;
511 };
512
513 if visited.contains(&next) {
514 continue;
515 }
516 if next == center {
517 return true;
518 }
519 visited.push(next);
520 queue.push(next);
521 }
522 }
523
524 false
525 }
526
527 fn covering_state_path(
528 &self,
529 target: i32,
530 center: i32,
531 et: f64,
532 ) -> Option<Result<SpkState, SpkError>> {
533 let root = StateSearchNode {
534 body: target,
535 state: AccumulatedSpkState {
536 position_km: [0.0; 3],
537 velocity_km_s: Some([0.0; 3]),
538 frame: None,
539 },
540 };
541
542 let mut search = StatePathSearch::new(target);
543
544 self.covering_state_path_from(root, center, et, &mut search)
545 .or_else(|| search.fallback())
546 }
547
548 fn covering_state_path_from(
549 &self,
550 node: StateSearchNode,
551 center: i32,
552 et: f64,
553 search: &mut StatePathSearch,
554 ) -> Option<Result<SpkState, SpkError>> {
555 for segment in self.directory.segments.iter().rev() {
556 let (next, sign) = if segment.target == node.body {
557 (segment.center, 1.0)
558 } else if segment.center == node.body {
559 (segment.target, -1.0)
560 } else {
561 continue;
562 };
563
564 if et < segment.start_et || et > segment.stop_et {
565 continue;
566 }
567
568 let leg = match self.evaluate_segment_state(segment, et) {
569 Ok(leg) => leg,
570 Err(SpkError::OutOfCoverage { .. }) => continue,
571 Err(error @ SpkError::UnsupportedStateSegmentType { .. }) => {
572 if next == center && search.is_root() {
573 return Some(Err(error));
574 }
575 if search.first_unsupported.is_none() {
576 search.first_unsupported = Some(error);
577 }
578 continue;
579 }
580 Err(error) => return Some(Err(error)),
581 };
582 let state = match node.state.extend(leg, sign) {
583 Ok(state) => state,
584 Err(error @ SpkError::FrameMismatch { .. }) => {
585 if search.first_frame_mismatch.is_none() {
586 search.first_frame_mismatch = Some(error);
587 }
588 continue;
589 }
590 Err(error) => return Some(Err(error)),
591 };
592
593 if next == center {
594 let state = state.into_state(search.target, center);
595 if state.velocity_km_s.is_some() || search.is_root() {
596 return Some(Ok(state));
597 }
598 if search.first_position_only_state.is_none() {
599 search.first_position_only_state = Some(state);
600 }
601 continue;
602 }
603
604 if search.visited.contains(&next) {
605 continue;
606 }
607 search.visited.push(next);
608 if let Some(result) = self.covering_state_path_from(
609 StateSearchNode { body: next, state },
610 center,
611 et,
612 search,
613 ) {
614 return Some(result);
615 }
616 search.visited.pop();
617 }
618
619 None
620 }
621
622 fn evaluate_segment_state(
623 &self,
624 segment: &SpkSegmentDescriptor,
625 et: f64,
626 ) -> Result<SpkState, SpkError> {
627 match segment.data_type {
628 SPK_TYPE_2 => Ok(SpkState {
629 target: segment.target,
630 center: segment.center,
631 position_km: evaluate_type2_position(
632 &self.bytes,
633 self.directory.file_record.byte_order,
634 segment,
635 et,
636 )?,
637 velocity_km_s: None,
638 frame: segment.frame,
639 }),
640 SPK_TYPE_3 => {
641 let state = evaluate_type3_state(
642 &self.bytes,
643 self.directory.file_record.byte_order,
644 segment,
645 et,
646 )?;
647 Ok(SpkState {
648 target: segment.target,
649 center: segment.center,
650 position_km: state.position_km,
651 velocity_km_s: Some(state.velocity_km_s),
652 frame: segment.frame,
653 })
654 }
655 SPK_TYPE_21 => {
656 let state = evaluate_type21_state(
657 &self.bytes,
658 self.directory.file_record.byte_order,
659 segment,
660 et,
661 )?;
662 Ok(SpkState {
663 target: segment.target,
664 center: segment.center,
665 position_km: state.position_km,
666 velocity_km_s: Some(state.velocity_km_s),
667 frame: segment.frame,
668 })
669 }
670 data_type => Err(SpkError::UnsupportedStateSegmentType { data_type }),
671 }
672 }
673}
674
675#[derive(Debug, Clone, Copy)]
676struct StateSearchNode {
677 body: i32,
678 state: AccumulatedSpkState,
679}
680
681#[derive(Debug)]
682struct StatePathSearch {
683 target: i32,
684 visited: Vec<i32>,
685 first_unsupported: Option<SpkError>,
686 first_frame_mismatch: Option<SpkError>,
687 first_position_only_state: Option<SpkState>,
688}
689
690impl StatePathSearch {
691 fn new(target: i32) -> Self {
692 Self {
693 target,
694 visited: vec![target],
695 first_unsupported: None,
696 first_frame_mismatch: None,
697 first_position_only_state: None,
698 }
699 }
700
701 fn fallback(self) -> Option<Result<SpkState, SpkError>> {
702 self.first_position_only_state.map(Ok).or_else(|| {
703 self.first_frame_mismatch
704 .map(Err)
705 .or_else(|| self.first_unsupported.map(Err))
706 })
707 }
708
709 fn is_root(&self) -> bool {
710 self.visited.len() == 1
711 }
712}
713
714#[derive(Debug, Clone, Copy)]
715struct AccumulatedSpkState {
716 position_km: [f64; 3],
717 velocity_km_s: Option<[f64; 3]>,
718 frame: Option<i32>,
719}
720
721impl AccumulatedSpkState {
722 fn extend(self, leg: SpkState, sign: f64) -> Result<Self, SpkError> {
723 let frame = match self.frame {
724 Some(frame) if frame != leg.frame => {
725 return Err(SpkError::FrameMismatch {
726 first: frame,
727 second: leg.frame,
728 });
729 }
730 Some(frame) => Some(frame),
731 None => Some(leg.frame),
732 };
733
734 Ok(Self {
735 position_km: add_scaled(self.position_km, leg.position_km, sign),
736 velocity_km_s: match (self.velocity_km_s, leg.velocity_km_s) {
737 (Some(accumulated), Some(leg)) => Some(add_scaled(accumulated, leg, sign)),
738 _ => None,
739 },
740 frame,
741 })
742 }
743
744 fn into_state(self, target: i32, center: i32) -> SpkState {
745 SpkState {
746 target,
747 center,
748 position_km: self.position_km,
749 velocity_km_s: self.velocity_km_s,
750 frame: self.frame.unwrap_or(0),
751 }
752 }
753}
754
755pub fn spk_state(spk: &Spk, target: i32, center: i32, et: f64) -> Result<SpkState, SpkError> {
757 spk.spk_state(target, center, et)
758}
759
760fn add_scaled(lhs: [f64; 3], rhs: [f64; 3], sign: f64) -> [f64; 3] {
761 [
762 lhs[0] + sign * rhs[0],
763 lhs[1] + sign * rhs[1],
764 lhs[2] + sign * rhs[2],
765 ]
766}
767
768pub fn parse_daf_spk(bytes: &[u8]) -> Result<DafSpk, SpkError> {
770 let file_record = parse_file_record(bytes)?;
771 if file_record.double_components != SPK_ND || file_record.integer_components != SPK_NI {
772 return Err(SpkError::UnsupportedSummaryShape {
773 nd: file_record.double_components,
774 ni: file_record.integer_components,
775 });
776 }
777
778 let mut segments = Vec::new();
779 let summary_words = summary_word_count(
780 file_record.double_components,
781 file_record.integer_components,
782 )?;
783 let summary_bytes = summary_words * 8;
784 let name_bytes = summary_words * 8;
785
786 let mut record = file_record.forward_record;
787 validate_summary_record_pointer(record, "FWARD")?;
788 let mut visited_records = Vec::new();
789 while record != 0 {
790 if visited_records.contains(&record) {
791 return Err(SpkError::InvalidField {
792 field: "summary record chain",
793 value: record,
794 });
795 }
796 visited_records.push(record);
797
798 let summary_offset = record_offset(record, bytes.len(), "summary record")?;
799 let name_record = paired_name_record(record, "name record")?;
800 let name_offset = record_offset(name_record, bytes.len(), "name record")?;
801
802 let next = read_summary_control_i32(
803 file_record.byte_order,
804 bytes,
805 summary_offset,
806 0,
807 "next summary record",
808 )?;
809 validate_summary_record_pointer(next, "next summary record")?;
810 let count = read_summary_control_i32(
811 file_record.byte_order,
812 bytes,
813 summary_offset,
814 2,
815 "summary count",
816 )?;
817 if count < 0 {
818 return Err(SpkError::InvalidField {
819 field: "summary count",
820 value: count,
821 });
822 }
823
824 let count = usize::try_from(count).map_err(|_| SpkError::InvalidField {
825 field: "summary count",
826 value: count,
827 })?;
828 let summaries_start = summary_offset + SUMMARY_CONTROL_WORDS * 8;
829 let summaries_end = summaries_start + count * summary_bytes;
830 if summaries_end > summary_offset + DAF_RECORD_BYTES {
831 return Err(SpkError::Truncated {
832 context: "summary record entries",
833 needed: summaries_end,
834 actual: summary_offset + DAF_RECORD_BYTES,
835 });
836 }
837 let names_end = name_offset + count * name_bytes;
838 if names_end > name_offset + DAF_RECORD_BYTES {
839 return Err(SpkError::Truncated {
840 context: "name record entries",
841 needed: names_end,
842 actual: name_offset + DAF_RECORD_BYTES,
843 });
844 }
845
846 for index in 0..count {
847 let entry_offset = summaries_start + index * summary_bytes;
848 let name_start = name_offset + index * name_bytes;
849 let name = trim_ascii(&bytes[name_start..name_start + name_bytes]);
850
851 let start_et =
852 file_record
853 .byte_order
854 .read_f64(bytes, entry_offset, "segment start ET")?;
855 let stop_et =
856 file_record
857 .byte_order
858 .read_f64(bytes, entry_offset + 8, "segment stop ET")?;
859 let ints_offset = entry_offset + 16;
860 let target = file_record
861 .byte_order
862 .read_i32(bytes, ints_offset, "segment target")?;
863 let center =
864 file_record
865 .byte_order
866 .read_i32(bytes, ints_offset + 4, "segment center")?;
867 let frame = file_record
868 .byte_order
869 .read_i32(bytes, ints_offset + 8, "segment frame")?;
870 let data_type =
871 file_record
872 .byte_order
873 .read_i32(bytes, ints_offset + 12, "segment data type")?;
874 let start_address = file_record.byte_order.read_i32(
875 bytes,
876 ints_offset + 16,
877 "segment start address",
878 )?;
879 let end_address =
880 file_record
881 .byte_order
882 .read_i32(bytes, ints_offset + 20, "segment end address")?;
883
884 segments.push(SpkSegmentDescriptor {
885 name,
886 start_et,
887 stop_et,
888 target,
889 center,
890 frame,
891 data_type,
892 start_address,
893 end_address,
894 });
895 }
896
897 record = next;
898 }
899
900 Ok(DafSpk {
901 file_record,
902 segments,
903 })
904}
905
906pub fn evaluate_type2_position(
908 bytes: &[u8],
909 byte_order: DafByteOrder,
910 segment: &SpkSegmentDescriptor,
911 et: f64,
912) -> Result<[f64; 3], SpkError> {
913 if segment.data_type != SPK_TYPE_2 {
914 return Err(SpkError::UnsupportedSegmentType {
915 expected: SPK_TYPE_2,
916 actual: segment.data_type,
917 });
918 }
919
920 let directory = read_type2_directory(bytes, byte_order, segment)?;
921 let record_index =
922 chebyshev_record_index(segment, directory.init, directory.intlen, directory.n, et)?;
923 let record_start = checked_address_add(
924 segment_start_address(segment)?,
925 record_index
926 .checked_mul(directory.rsize)
927 .ok_or(SpkError::InvalidSegmentLayout {
928 context: "record offset overflow",
929 })?,
930 "type-2 record address",
931 )?;
932
933 let mid = read_daf_f64(bytes, byte_order, record_start, "type-2 record midpoint")?;
934 let radius = read_daf_f64(bytes, byte_order, record_start + 1, "type-2 record radius")?;
935 if !radius.is_finite() || radius <= 0.0 {
936 return Err(SpkError::InvalidDoubleField {
937 field: "type-2 record radius",
938 value: radius,
939 });
940 }
941
942 let tau = (et - mid) / radius;
943 let coeff_count = (directory.rsize - 2) / 3;
944 let coeff_start = record_start + 2;
945 let x = evaluate_chebyshev_component(bytes, byte_order, coeff_start, coeff_count, tau)?;
946 let y = evaluate_chebyshev_component(
947 bytes,
948 byte_order,
949 coeff_start + coeff_count,
950 coeff_count,
951 tau,
952 )?;
953 let z = evaluate_chebyshev_component(
954 bytes,
955 byte_order,
956 coeff_start + 2 * coeff_count,
957 coeff_count,
958 tau,
959 )?;
960 Ok([x, y, z])
961}
962
963pub fn evaluate_type3_state(
965 bytes: &[u8],
966 byte_order: DafByteOrder,
967 segment: &SpkSegmentDescriptor,
968 et: f64,
969) -> Result<SpkStateVector, SpkError> {
970 if segment.data_type != SPK_TYPE_3 {
971 return Err(SpkError::UnsupportedSegmentType {
972 expected: SPK_TYPE_3,
973 actual: segment.data_type,
974 });
975 }
976
977 let directory = read_type3_directory(bytes, byte_order, segment)?;
978 let record_index =
979 chebyshev_record_index(segment, directory.init, directory.intlen, directory.n, et)?;
980 let record_start = checked_address_add(
981 segment_start_address(segment)?,
982 record_index
983 .checked_mul(directory.rsize)
984 .ok_or(SpkError::InvalidSegmentLayout {
985 context: "record offset overflow",
986 })?,
987 "type-3 record address",
988 )?;
989
990 let mid = read_daf_f64(bytes, byte_order, record_start, "type-3 record midpoint")?;
991 let radius = read_daf_f64(bytes, byte_order, record_start + 1, "type-3 record radius")?;
992 if !radius.is_finite() || radius <= 0.0 {
993 return Err(SpkError::InvalidDoubleField {
994 field: "type-3 record radius",
995 value: radius,
996 });
997 }
998
999 let tau = (et - mid) / radius;
1000 let coeff_count = (directory.rsize - 2) / 6;
1001 let coeff_start = record_start + 2;
1002 let x = evaluate_chebyshev_component(bytes, byte_order, coeff_start, coeff_count, tau)?;
1003 let y = evaluate_chebyshev_component(
1004 bytes,
1005 byte_order,
1006 coeff_start + coeff_count,
1007 coeff_count,
1008 tau,
1009 )?;
1010 let z = evaluate_chebyshev_component(
1011 bytes,
1012 byte_order,
1013 coeff_start + 2 * coeff_count,
1014 coeff_count,
1015 tau,
1016 )?;
1017 let vx = evaluate_chebyshev_component(
1018 bytes,
1019 byte_order,
1020 coeff_start + 3 * coeff_count,
1021 coeff_count,
1022 tau,
1023 )?;
1024 let vy = evaluate_chebyshev_component(
1025 bytes,
1026 byte_order,
1027 coeff_start + 4 * coeff_count,
1028 coeff_count,
1029 tau,
1030 )?;
1031 let vz = evaluate_chebyshev_component(
1032 bytes,
1033 byte_order,
1034 coeff_start + 5 * coeff_count,
1035 coeff_count,
1036 tau,
1037 )?;
1038
1039 Ok(SpkStateVector {
1040 position_km: [x, y, z],
1041 velocity_km_s: [vx, vy, vz],
1042 })
1043}
1044
1045pub fn evaluate_type21_state(
1055 bytes: &[u8],
1056 byte_order: DafByteOrder,
1057 segment: &SpkSegmentDescriptor,
1058 et: f64,
1059) -> Result<SpkStateVector, SpkError> {
1060 if segment.data_type != SPK_TYPE_21 {
1061 return Err(SpkError::UnsupportedSegmentType {
1062 expected: SPK_TYPE_21,
1063 actual: segment.data_type,
1064 });
1065 }
1066
1067 let directory = read_type21_directory(bytes, byte_order, segment)?;
1068 let record_address = type21_record_address(bytes, byte_order, segment, &directory, et)?;
1069
1070 let dlsize = 4 * directory.maxdim + 11;
1073 let mut record = [0.0f64; 4 * SPK_TYPE_21_MAX_TABLE_DIM + 12];
1074 record[0] = directory.maxdim as f64;
1075 for (offset, slot) in record[1..=dlsize].iter_mut().enumerate() {
1076 *slot = read_daf_f64(
1077 bytes,
1078 byte_order,
1079 record_address + offset,
1080 "type-21 difference line",
1081 )?;
1082 }
1083
1084 evaluate_type21_record(&record, directory.maxdim, et)
1085}
1086
1087#[derive(Debug, Clone, Copy)]
1088struct Type21Directory {
1089 begin_address: usize,
1091 end_address: usize,
1093 maxdim: usize,
1095 record_count: usize,
1097 directory_count: usize,
1099}
1100
1101fn read_type21_directory(
1102 bytes: &[u8],
1103 byte_order: DafByteOrder,
1104 segment: &SpkSegmentDescriptor,
1105) -> Result<Type21Directory, SpkError> {
1106 let begin = segment_start_address(segment)?;
1107 let end = segment_end_address(segment)?;
1108 if end < begin + 1 {
1109 return Err(SpkError::InvalidSegmentLayout {
1110 context: "segment is shorter than the type-21 trailer",
1111 });
1112 }
1113
1114 let maxdim = read_daf_f64(bytes, byte_order, end - 1, "type-21 MAXDIM")?;
1116 let record_count = read_daf_f64(bytes, byte_order, end, "type-21 record count")?;
1117 let maxdim = f64_to_usize(maxdim, "type-21 MAXDIM")?;
1118 let record_count = f64_to_usize(record_count, "type-21 record count")?;
1119
1120 if maxdim == 0 || maxdim > SPK_TYPE_21_MAX_TABLE_DIM {
1121 return Err(SpkError::InvalidSegmentLayout {
1122 context: "type-21 difference table dimension is out of range",
1123 });
1124 }
1125 if record_count == 0 {
1126 return Err(SpkError::InvalidSegmentLayout {
1127 context: "type-21 segment has zero records",
1128 });
1129 }
1130
1131 let dlsize = 4 * maxdim + 11;
1132 let directory_count = record_count / SPK_TYPE_21_DIRECTORY_STRIDE;
1133
1134 let required_words = record_count
1137 .checked_mul(dlsize)
1138 .and_then(|words| words.checked_add(record_count))
1139 .and_then(|words| words.checked_add(directory_count))
1140 .and_then(|words| words.checked_add(2))
1141 .ok_or(SpkError::InvalidSegmentLayout {
1142 context: "type-21 segment word count overflow",
1143 })?;
1144 let segment_words = end - begin + 1;
1145 if required_words > segment_words {
1146 return Err(SpkError::InvalidSegmentLayout {
1147 context: "type-21 records exceed segment address range",
1148 });
1149 }
1150
1151 Ok(Type21Directory {
1152 begin_address: begin,
1153 end_address: end,
1154 maxdim,
1155 record_count,
1156 directory_count,
1157 })
1158}
1159
1160fn type21_record_address(
1162 bytes: &[u8],
1163 byte_order: DafByteOrder,
1164 segment: &SpkSegmentDescriptor,
1165 directory: &Type21Directory,
1166 et: f64,
1167) -> Result<usize, SpkError> {
1168 if !et.is_finite() {
1169 return Err(SpkError::InvalidDoubleField {
1170 field: "type-21 ET",
1171 value: et,
1172 });
1173 }
1174 if et < segment.start_et || et > segment.stop_et {
1175 return Err(SpkError::OutOfCoverage {
1176 et,
1177 start_et: segment.start_et,
1178 stop_et: segment.stop_et,
1179 });
1180 }
1181
1182 let first_epoch_address = directory
1185 .end_address
1186 .checked_sub(directory.directory_count + 2 + directory.record_count)
1187 .ok_or(SpkError::InvalidSegmentLayout {
1188 context: "type-21 epoch list underflows segment",
1189 })?
1190 + 1;
1191
1192 let mut earlier = 0usize;
1196 for index in 0..directory.record_count {
1197 let epoch = read_daf_f64(
1198 bytes,
1199 byte_order,
1200 first_epoch_address + index,
1201 "type-21 epoch",
1202 )?;
1203 if epoch < et {
1204 earlier += 1;
1205 } else {
1206 break;
1207 }
1208 }
1209 let record_index = earlier.min(directory.record_count - 1);
1210
1211 let dlsize = 4 * directory.maxdim + 11;
1212 checked_address_add(
1213 directory.begin_address,
1214 record_index
1215 .checked_mul(dlsize)
1216 .ok_or(SpkError::InvalidSegmentLayout {
1217 context: "type-21 record offset overflow",
1218 })?,
1219 "type-21 record address",
1220 )
1221}
1222
1223fn evaluate_type21_record(
1227 record: &[f64],
1228 maxdim: usize,
1229 et: f64,
1230) -> Result<SpkStateVector, SpkError> {
1231 const DIM: usize = SPK_TYPE_21_MAX_TABLE_DIM;
1232 let mut fc = [0.0f64; DIM];
1233 fc[0] = 1.0;
1234 let mut wc = [0.0f64; DIM - 1];
1235 let mut w = [0.0f64; DIM + 2];
1236 let mut g = [0.0f64; DIM];
1237
1238 let tl = record[1];
1239 g[..maxdim].copy_from_slice(&record[2..2 + maxdim]);
1240
1241 let refpos = [record[maxdim + 2], record[maxdim + 4], record[maxdim + 6]];
1242 let refvel = [record[maxdim + 3], record[maxdim + 5], record[maxdim + 7]];
1243
1244 let mut dt = [[0.0f64; 3]; DIM];
1246 for component in 0..3 {
1247 let base = (component + 1) * maxdim + 8;
1248 for (row, dt_row) in dt.iter_mut().enumerate().take(maxdim) {
1249 dt_row[component] = record[base + row];
1250 }
1251 }
1252
1253 let kqmax1_u = f64_to_usize(record[4 * maxdim + 8], "type-21 KQMAX1")?;
1259 if kqmax1_u < 1 || kqmax1_u > maxdim + 1 {
1260 return Err(SpkError::InvalidSegmentLayout {
1261 context: "type-21 KQMAX1 is out of range",
1262 });
1263 }
1264 let kq_u = [
1265 f64_to_usize(record[4 * maxdim + 9], "type-21 KQ(1)")?,
1266 f64_to_usize(record[4 * maxdim + 10], "type-21 KQ(2)")?,
1267 f64_to_usize(record[4 * maxdim + 11], "type-21 KQ(3)")?,
1268 ];
1269 for &kqq in &kq_u {
1270 if kqq >= kqmax1_u {
1271 return Err(SpkError::InvalidSegmentLayout {
1272 context: "type-21 KQ order exceeds KQMAX1",
1273 });
1274 }
1275 }
1276 let kqmax1 = kqmax1_u as i64;
1277 let kq = [kq_u[0] as i64, kq_u[1] as i64, kq_u[2] as i64];
1278
1279 let delta = et - tl;
1280 let mut tp = delta;
1281 let mq2 = kqmax1 - 2;
1282 let mut ks = kqmax1 - 1;
1283
1284 let mut j = 1i64;
1286 while j <= mq2 {
1287 let gj = g[(j - 1) as usize];
1288 if gj == 0.0 {
1289 return Err(SpkError::InvalidDoubleField {
1290 field: "type-21 stepsize vector",
1291 value: 0.0,
1292 });
1293 }
1294 fc[j as usize] = tp / gj;
1295 wc[(j - 1) as usize] = delta / gj;
1296 tp = delta + gj;
1297 j += 1;
1298 }
1299
1300 let mut j = 1i64;
1302 while j <= kqmax1 {
1303 w[(j - 1) as usize] = 1.0 / j as f64;
1304 j += 1;
1305 }
1306
1307 let mut jx = 0i64;
1309 let mut ks1 = ks - 1;
1310 while ks >= 2 {
1311 jx += 1;
1312 let mut j = 1i64;
1313 while j <= jx {
1314 let term = fc[j as usize] * w[(j + ks1 - 1) as usize]
1315 - wc[(j - 1) as usize] * w[(j + ks - 1) as usize];
1316 w[(j + ks - 1) as usize] = term;
1317 j += 1;
1318 }
1319 ks = ks1;
1320 ks1 -= 1;
1321 }
1322
1323 let mut state = [0.0f64; 6];
1324 for component in 0..3 {
1325 let kqq = kq[component];
1326 let mut sum = 0.0;
1327 let mut j = kqq;
1328 while j >= 1 {
1329 sum += dt[(j - 1) as usize][component] * w[(j + ks - 1) as usize];
1330 j -= 1;
1331 }
1332 state[component] = refpos[component] + delta * (refvel[component] + delta * sum);
1333 }
1334
1335 let mut j = 1i64;
1337 while j <= jx {
1338 let term = fc[j as usize] * w[(j + ks1 - 1) as usize]
1339 - wc[(j - 1) as usize] * w[(j + ks - 1) as usize];
1340 w[(j + ks - 1) as usize] = term;
1341 j += 1;
1342 }
1343 ks -= 1;
1344
1345 for component in 0..3 {
1346 let kqq = kq[component];
1347 let mut sum = 0.0;
1348 let mut j = kqq;
1349 while j >= 1 {
1350 sum += dt[(j - 1) as usize][component] * w[(j + ks - 1) as usize];
1351 j -= 1;
1352 }
1353 state[component + 3] = refvel[component] + delta * sum;
1354 }
1355
1356 Ok(SpkStateVector {
1357 position_km: [state[0], state[1], state[2]],
1358 velocity_km_s: [state[3], state[4], state[5]],
1359 })
1360}
1361
1362fn parse_file_record(bytes: &[u8]) -> Result<DafFileRecord, SpkError> {
1363 if bytes.len() < DAF_FILE_RECORD_BYTES {
1364 return Err(SpkError::Truncated {
1365 context: "DAF file record",
1366 needed: DAF_FILE_RECORD_BYTES,
1367 actual: bytes.len(),
1368 });
1369 }
1370
1371 let id_word = trim_ascii(&bytes[0..DAF_ID_BYTES]);
1372 let file_type = id_word
1373 .split_once('/')
1374 .map(|(_, file_type)| file_type.trim().to_string())
1375 .unwrap_or_default();
1376 if id_word != "DAF/SPK" {
1377 return Err(SpkError::UnsupportedDafId { id_word });
1378 }
1379
1380 let binary_format = trim_ascii(
1381 &bytes[DAF_BINARY_FORMAT_OFFSET..DAF_BINARY_FORMAT_OFFSET + DAF_BINARY_FORMAT_BYTES],
1382 );
1383 let byte_order = match binary_format.as_str() {
1384 "LTL-IEEE" => DafByteOrder::LittleEndian,
1385 "BIG-IEEE" => DafByteOrder::BigEndian,
1386 _ => {
1387 return Err(SpkError::UnsupportedBinaryFormat { binary_format });
1388 }
1389 };
1390
1391 let double_components = byte_order.read_i32(bytes, 8, "ND")?;
1392 let integer_components = byte_order.read_i32(bytes, 12, "NI")?;
1393 let internal_name = trim_ascii(&bytes[16..16 + DAF_INTERNAL_NAME_BYTES]);
1394 let forward_record = byte_order.read_i32(bytes, 76, "FWARD")?;
1395 let backward_record = byte_order.read_i32(bytes, 80, "BWARD")?;
1396 let free_address = byte_order.read_i32(bytes, 84, "FREE")?;
1397
1398 if forward_record < 0 {
1399 return Err(SpkError::InvalidField {
1400 field: "FWARD",
1401 value: forward_record,
1402 });
1403 }
1404 if backward_record < 0 {
1405 return Err(SpkError::InvalidField {
1406 field: "BWARD",
1407 value: backward_record,
1408 });
1409 }
1410
1411 Ok(DafFileRecord {
1412 id_word,
1413 file_type,
1414 double_components,
1415 integer_components,
1416 internal_name,
1417 forward_record,
1418 backward_record,
1419 free_address,
1420 byte_order,
1421 binary_format,
1422 })
1423}
1424
1425#[derive(Debug, Clone, Copy)]
1426struct ChebyshevDirectory {
1427 init: f64,
1428 intlen: f64,
1429 rsize: usize,
1430 n: usize,
1431}
1432
1433fn read_type2_directory(
1434 bytes: &[u8],
1435 byte_order: DafByteOrder,
1436 segment: &SpkSegmentDescriptor,
1437) -> Result<ChebyshevDirectory, SpkError> {
1438 let start = segment_start_address(segment)?;
1439 let end = segment_end_address(segment)?;
1440 if end < start + 3 {
1441 return Err(SpkError::InvalidSegmentLayout {
1442 context: "segment is shorter than the type-2 directory",
1443 });
1444 }
1445
1446 let init = read_daf_f64(bytes, byte_order, end - 3, "type-2 INIT")?;
1447 let intlen = read_daf_f64(bytes, byte_order, end - 2, "type-2 INTLEN")?;
1448 let rsize = read_daf_f64(bytes, byte_order, end - 1, "type-2 RSIZE")?;
1449 let n = read_daf_f64(bytes, byte_order, end, "type-2 N")?;
1450
1451 if !init.is_finite() {
1452 return Err(SpkError::InvalidDoubleField {
1453 field: "type-2 INIT",
1454 value: init,
1455 });
1456 }
1457 if !intlen.is_finite() || intlen <= 0.0 {
1458 return Err(SpkError::InvalidDoubleField {
1459 field: "type-2 INTLEN",
1460 value: intlen,
1461 });
1462 }
1463
1464 let rsize = f64_to_usize(rsize, "type-2 RSIZE")?;
1465 let n = f64_to_usize(n, "type-2 N")?;
1466 if n == 0 {
1467 return Err(SpkError::InvalidSegmentLayout {
1468 context: "type-2 segment has zero records",
1469 });
1470 }
1471 if rsize < 5 || (rsize - 2) % 3 != 0 {
1472 return Err(SpkError::InvalidSegmentLayout {
1473 context: "type-2 RSIZE does not match three position components",
1474 });
1475 }
1476
1477 let segment_words = end - start + 1;
1478 let required_words = n
1479 .checked_mul(rsize)
1480 .and_then(|words| words.checked_add(4))
1481 .ok_or(SpkError::InvalidSegmentLayout {
1482 context: "type-2 segment word count overflow",
1483 })?;
1484 if required_words > segment_words {
1485 return Err(SpkError::InvalidSegmentLayout {
1486 context: "type-2 records exceed segment address range",
1487 });
1488 }
1489
1490 Ok(ChebyshevDirectory {
1491 init,
1492 intlen,
1493 rsize,
1494 n,
1495 })
1496}
1497
1498fn read_type3_directory(
1499 bytes: &[u8],
1500 byte_order: DafByteOrder,
1501 segment: &SpkSegmentDescriptor,
1502) -> Result<ChebyshevDirectory, SpkError> {
1503 let start = segment_start_address(segment)?;
1504 let end = segment_end_address(segment)?;
1505 if end < start + 3 {
1506 return Err(SpkError::InvalidSegmentLayout {
1507 context: "segment is shorter than the type-3 directory",
1508 });
1509 }
1510
1511 let init = read_daf_f64(bytes, byte_order, end - 3, "type-3 INIT")?;
1512 let intlen = read_daf_f64(bytes, byte_order, end - 2, "type-3 INTLEN")?;
1513 let rsize = read_daf_f64(bytes, byte_order, end - 1, "type-3 RSIZE")?;
1514 let n = read_daf_f64(bytes, byte_order, end, "type-3 N")?;
1515
1516 if !init.is_finite() {
1517 return Err(SpkError::InvalidDoubleField {
1518 field: "type-3 INIT",
1519 value: init,
1520 });
1521 }
1522 if !intlen.is_finite() || intlen <= 0.0 {
1523 return Err(SpkError::InvalidDoubleField {
1524 field: "type-3 INTLEN",
1525 value: intlen,
1526 });
1527 }
1528
1529 let rsize = f64_to_usize(rsize, "type-3 RSIZE")?;
1530 let n = f64_to_usize(n, "type-3 N")?;
1531 if n == 0 {
1532 return Err(SpkError::InvalidSegmentLayout {
1533 context: "type-3 segment has zero records",
1534 });
1535 }
1536 if rsize < 8 || (rsize - 2) % 6 != 0 {
1537 return Err(SpkError::InvalidSegmentLayout {
1538 context: "type-3 RSIZE does not match six state components",
1539 });
1540 }
1541
1542 let segment_words = end - start + 1;
1543 let required_words = n
1544 .checked_mul(rsize)
1545 .and_then(|words| words.checked_add(4))
1546 .ok_or(SpkError::InvalidSegmentLayout {
1547 context: "type-3 segment word count overflow",
1548 })?;
1549 if required_words > segment_words {
1550 return Err(SpkError::InvalidSegmentLayout {
1551 context: "type-3 records exceed segment address range",
1552 });
1553 }
1554
1555 Ok(ChebyshevDirectory {
1556 init,
1557 intlen,
1558 rsize,
1559 n,
1560 })
1561}
1562
1563fn chebyshev_record_index(
1564 segment: &SpkSegmentDescriptor,
1565 init: f64,
1566 intlen: f64,
1567 n: usize,
1568 et: f64,
1569) -> Result<usize, SpkError> {
1570 if et < segment.start_et || et > segment.stop_et {
1571 return Err(SpkError::OutOfCoverage {
1572 et,
1573 start_et: segment.start_et,
1574 stop_et: segment.stop_et,
1575 });
1576 }
1577
1578 let directory_stop = init + intlen * n as f64;
1579 if et < init || et > directory_stop {
1580 return Err(SpkError::OutOfCoverage {
1581 et,
1582 start_et: init,
1583 stop_et: directory_stop,
1584 });
1585 }
1586
1587 let record = ((et - init) / intlen).floor();
1588 if !record.is_finite() || record < 0.0 {
1589 return Err(SpkError::OutOfCoverage {
1590 et,
1591 start_et: init,
1592 stop_et: directory_stop,
1593 });
1594 }
1595
1596 let record = record as usize;
1597 if record < n {
1598 Ok(record)
1599 } else if record == n && et <= directory_stop {
1600 Ok(n - 1)
1601 } else {
1602 Err(SpkError::OutOfCoverage {
1603 et,
1604 start_et: init,
1605 stop_et: directory_stop,
1606 })
1607 }
1608}
1609
1610fn evaluate_chebyshev_component(
1611 bytes: &[u8],
1612 byte_order: DafByteOrder,
1613 coeff_start: usize,
1614 coeff_count: usize,
1615 tau: f64,
1616) -> Result<f64, SpkError> {
1617 let mut sum = read_daf_f64(bytes, byte_order, coeff_start, "Chebyshev coefficient")?;
1618 if coeff_count == 1 {
1619 return Ok(sum);
1620 }
1621
1622 let mut previous = 1.0;
1623 let mut current = tau;
1624 sum += read_daf_f64(bytes, byte_order, coeff_start + 1, "Chebyshev coefficient")? * current;
1625
1626 for index in 2..coeff_count {
1627 let next = 2.0 * tau * current - previous;
1628 sum += read_daf_f64(
1629 bytes,
1630 byte_order,
1631 coeff_start + index,
1632 "Chebyshev coefficient",
1633 )? * next;
1634 previous = current;
1635 current = next;
1636 }
1637
1638 Ok(sum)
1639}
1640
1641fn f64_to_usize(value: f64, field: &'static str) -> Result<usize, SpkError> {
1642 if !value.is_finite() || value.fract() != 0.0 || value < 0.0 || value > usize::MAX as f64 {
1643 return Err(SpkError::InvalidDoubleField { field, value });
1644 }
1645 Ok(value as usize)
1646}
1647
1648fn segment_start_address(segment: &SpkSegmentDescriptor) -> Result<usize, SpkError> {
1649 segment_address(segment.start_address, "segment start address")
1650}
1651
1652fn segment_end_address(segment: &SpkSegmentDescriptor) -> Result<usize, SpkError> {
1653 let start = segment_start_address(segment)?;
1654 let end = segment_address(segment.end_address, "segment end address")?;
1655 if end < start {
1656 return Err(SpkError::InvalidSegmentLayout {
1657 context: "segment end address precedes start address",
1658 });
1659 }
1660 Ok(end)
1661}
1662
1663fn segment_address(address: i32, field: &'static str) -> Result<usize, SpkError> {
1664 if address <= 0 {
1665 return Err(SpkError::InvalidField {
1666 field,
1667 value: address,
1668 });
1669 }
1670 usize::try_from(address).map_err(|_| SpkError::InvalidField {
1671 field,
1672 value: address,
1673 })
1674}
1675
1676fn checked_address_add(
1677 address: usize,
1678 offset_words: usize,
1679 context: &'static str,
1680) -> Result<usize, SpkError> {
1681 address
1682 .checked_add(offset_words)
1683 .ok_or(SpkError::InvalidSegmentLayout { context })
1684}
1685
1686fn read_daf_f64(
1687 bytes: &[u8],
1688 byte_order: DafByteOrder,
1689 address: usize,
1690 field: &'static str,
1691) -> Result<f64, SpkError> {
1692 if address == 0 {
1693 return Err(SpkError::InvalidSegmentLayout {
1694 context: "DAF addresses are one-based",
1695 });
1696 }
1697 let offset = (address - 1)
1698 .checked_mul(8)
1699 .ok_or(SpkError::InvalidSegmentLayout {
1700 context: "DAF address byte offset overflow",
1701 })?;
1702 byte_order.read_f64(bytes, offset, field)
1703}
1704
1705fn read_summary_control_i32(
1706 byte_order: DafByteOrder,
1707 bytes: &[u8],
1708 summary_offset: usize,
1709 word_index: usize,
1710 field: &'static str,
1711) -> Result<i32, SpkError> {
1712 let value = byte_order.read_f64(bytes, summary_offset + word_index * 8, field)?;
1713 if !value.is_finite()
1714 || value.fract() != 0.0
1715 || value < i32::MIN as f64
1716 || value > i32::MAX as f64
1717 {
1718 return Err(SpkError::InvalidField { field, value: 0 });
1719 }
1720 Ok(value as i32)
1721}
1722
1723fn summary_word_count(nd: i32, ni: i32) -> Result<usize, SpkError> {
1724 if nd < 0 {
1725 return Err(SpkError::InvalidField {
1726 field: "ND",
1727 value: nd,
1728 });
1729 }
1730 if ni < 0 {
1731 return Err(SpkError::InvalidField {
1732 field: "NI",
1733 value: ni,
1734 });
1735 }
1736
1737 let nd = usize::try_from(nd).map_err(|_| SpkError::InvalidField {
1738 field: "ND",
1739 value: nd,
1740 })?;
1741 let ni = usize::try_from(ni).map_err(|_| SpkError::InvalidField {
1742 field: "NI",
1743 value: ni,
1744 })?;
1745 Ok(nd + ni.div_ceil(2))
1746}
1747
1748fn validate_summary_record_pointer(record: i32, field: &'static str) -> Result<(), SpkError> {
1749 paired_name_record(record, field).map(|_| ())
1750}
1751
1752fn paired_name_record(record: i32, field: &'static str) -> Result<i32, SpkError> {
1753 record.checked_add(1).ok_or(SpkError::InvalidField {
1754 field,
1755 value: record,
1756 })
1757}
1758
1759fn record_offset(record: i32, len: usize, context: &'static str) -> Result<usize, SpkError> {
1760 if record <= 0 {
1761 return Err(SpkError::InvalidField {
1762 field: context,
1763 value: record,
1764 });
1765 }
1766 let record = usize::try_from(record).map_err(|_| SpkError::InvalidField {
1767 field: context,
1768 value: record,
1769 })?;
1770 let offset = (record - 1)
1771 .checked_mul(DAF_RECORD_BYTES)
1772 .ok_or(SpkError::InvalidField {
1773 field: context,
1774 value: i32::MAX,
1775 })?;
1776 let needed = offset + DAF_RECORD_BYTES;
1777 if needed > len {
1778 return Err(SpkError::Truncated {
1779 context,
1780 needed,
1781 actual: len,
1782 });
1783 }
1784 Ok(offset)
1785}
1786
1787fn trim_ascii(bytes: &[u8]) -> String {
1788 let end = bytes
1789 .iter()
1790 .rposition(|byte| *byte != b' ' && *byte != 0)
1791 .map(|index| index + 1)
1792 .unwrap_or(0);
1793 let trimmed = &bytes[..end];
1794 String::from_utf8_lossy(trimmed).trim().to_string()
1795}
1796
1797#[cfg(test)]
1798mod tests {
1799 use super::*;
1800
1801 #[test]
1802 fn decodes_little_endian_daf_summaries() {
1803 let bytes = build_daf(DafByteOrder::LittleEndian);
1804
1805 let parsed = parse_daf_spk(&bytes).unwrap();
1806
1807 assert_eq!(parsed.file_record.id_word, "DAF/SPK");
1808 assert_eq!(parsed.file_record.file_type, "SPK");
1809 assert_eq!(parsed.file_record.double_components, 2);
1810 assert_eq!(parsed.file_record.integer_components, 6);
1811 assert_eq!(parsed.file_record.internal_name, "SYNTHETIC SPK");
1812 assert_eq!(parsed.file_record.forward_record, 3);
1813 assert_eq!(parsed.file_record.backward_record, 3);
1814 assert_eq!(parsed.file_record.free_address, 901);
1815 assert_eq!(parsed.file_record.byte_order, DafByteOrder::LittleEndian);
1816 assert_eq!(parsed.file_record.binary_format, "LTL-IEEE");
1817 assert_eq!(parsed.segments, expected_segments());
1818 }
1819
1820 #[test]
1821 fn decodes_big_endian_daf_summaries() {
1822 let bytes = build_daf(DafByteOrder::BigEndian);
1823
1824 let parsed = parse_daf_spk(&bytes).unwrap();
1825
1826 assert_eq!(parsed.file_record.byte_order, DafByteOrder::BigEndian);
1827 assert_eq!(parsed.file_record.binary_format, "BIG-IEEE");
1828 assert_eq!(parsed.segments, expected_segments());
1829 }
1830
1831 #[test]
1832 fn decodes_linear_daf_summary_record_chain() {
1833 let bytes = build_chained_daf(DafByteOrder::LittleEndian, 0.0);
1834
1835 let parsed = parse_daf_spk(&bytes).unwrap();
1836
1837 assert_eq!(parsed.file_record.forward_record, 3);
1838 assert_eq!(parsed.file_record.backward_record, 5);
1839 assert_eq!(parsed.segments, expected_segments());
1840 }
1841
1842 #[test]
1843 fn cyclic_daf_summary_record_chain_returns_typed_error() {
1844 let bytes = build_chained_daf(DafByteOrder::LittleEndian, 3.0);
1845
1846 let err = parse_daf_spk(&bytes).unwrap_err();
1847
1848 assert_eq!(
1849 err,
1850 SpkError::InvalidField {
1851 field: "summary record chain",
1852 value: 3,
1853 }
1854 );
1855 }
1856
1857 #[test]
1858 fn max_forward_summary_record_returns_typed_error() {
1859 let byte_order = DafByteOrder::LittleEndian;
1860 let mut bytes = build_daf(byte_order);
1861 write_i32(byte_order, &mut bytes, 76, i32::MAX);
1862
1863 let err = parse_daf_spk(&bytes).unwrap_err();
1864
1865 assert_eq!(
1866 err,
1867 SpkError::InvalidField {
1868 field: "FWARD",
1869 value: i32::MAX,
1870 }
1871 );
1872 }
1873
1874 #[test]
1875 fn max_next_summary_record_returns_typed_error() {
1876 let byte_order = DafByteOrder::LittleEndian;
1877 let mut bytes = build_daf(byte_order);
1878 write_f64(
1879 byte_order,
1880 &mut bytes,
1881 DAF_RECORD_BYTES * 2,
1882 f64::from(i32::MAX),
1883 );
1884
1885 let err = parse_daf_spk(&bytes).unwrap_err();
1886
1887 assert_eq!(
1888 err,
1889 SpkError::InvalidField {
1890 field: "next summary record",
1891 value: i32::MAX,
1892 }
1893 );
1894 }
1895
1896 #[test]
1897 fn truncated_header_returns_typed_error() {
1898 let err = parse_daf_spk(&[0u8; 16]).unwrap_err();
1899
1900 assert_eq!(
1901 err,
1902 SpkError::Truncated {
1903 context: "DAF file record",
1904 needed: 1024,
1905 actual: 16,
1906 }
1907 );
1908 }
1909
1910 #[test]
1911 fn evaluates_type2_position_records_and_boundaries() {
1912 let (bytes, segment) = build_type2_segment(DafByteOrder::LittleEndian);
1913
1914 assert_position_close(
1915 evaluate_type2_position(&bytes, DafByteOrder::LittleEndian, &segment, 0.0).unwrap(),
1916 [2.0, -5.5, 7.0],
1917 );
1918 assert_position_close(
1919 evaluate_type2_position(&bytes, DafByteOrder::LittleEndian, &segment, 5.0).unwrap(),
1920 [-2.0, -3.0, 7.0],
1921 );
1922 assert_position_close(
1923 evaluate_type2_position(&bytes, DafByteOrder::LittleEndian, &segment, 10.0).unwrap(),
1924 [11.0, 19.0, -9.0],
1925 );
1926 assert_position_close(
1927 evaluate_type2_position(&bytes, DafByteOrder::LittleEndian, &segment, 20.0).unwrap(),
1928 [9.0, 23.0, -1.0],
1929 );
1930 }
1931
1932 #[test]
1933 fn evaluates_big_endian_type2_position() {
1934 let (bytes, segment) = build_type2_segment(DafByteOrder::BigEndian);
1935
1936 assert_position_close(
1937 evaluate_type2_position(&bytes, DafByteOrder::BigEndian, &segment, 15.0).unwrap(),
1938 [10.0, 19.0, -1.0],
1939 );
1940 }
1941
1942 #[test]
1943 fn type2_out_of_coverage_returns_typed_error() {
1944 let (bytes, segment) = build_type2_segment(DafByteOrder::LittleEndian);
1945
1946 let err = evaluate_type2_position(&bytes, DafByteOrder::LittleEndian, &segment, -0.001)
1947 .unwrap_err();
1948
1949 assert_eq!(
1950 err,
1951 SpkError::OutOfCoverage {
1952 et: -0.001,
1953 start_et: 0.0,
1954 stop_et: 20.0,
1955 }
1956 );
1957 }
1958
1959 #[test]
1960 fn evaluates_type3_state_records_and_boundaries() {
1961 let (bytes, segment) = build_type3_segment(DafByteOrder::LittleEndian);
1962
1963 assert_state_close(
1964 evaluate_type3_state(&bytes, DafByteOrder::LittleEndian, &segment, 5.0).unwrap(),
1965 [1.0, 3.0, 5.0],
1966 [0.1, -0.3, 1.0],
1967 );
1968 assert_state_close(
1969 evaluate_type3_state(&bytes, DafByteOrder::LittleEndian, &segment, 10.0).unwrap(),
1970 [9.0, 22.0, -8.0],
1971 [0.0, 5.0, 3.75],
1972 );
1973 assert_state_close(
1974 evaluate_type3_state(&bytes, DafByteOrder::LittleEndian, &segment, 20.0).unwrap(),
1975 [11.0, 18.0, -2.0],
1976 [2.0, -1.0, 4.25],
1977 );
1978 }
1979
1980 #[test]
1981 fn type3_out_of_coverage_returns_typed_error() {
1982 let (bytes, segment) = build_type3_segment(DafByteOrder::LittleEndian);
1983
1984 let err =
1985 evaluate_type3_state(&bytes, DafByteOrder::LittleEndian, &segment, 20.001).unwrap_err();
1986
1987 assert_eq!(
1988 err,
1989 SpkError::OutOfCoverage {
1990 et: 20.001,
1991 start_et: 0.0,
1992 stop_et: 20.0,
1993 }
1994 );
1995 }
1996
1997 #[test]
1998 fn evaluates_type21_synthetic_records_and_boundaries() {
1999 let (bytes, segment) = build_type21_segment(DafByteOrder::LittleEndian);
2000
2001 assert_state_close(
2005 evaluate_type21_state(&bytes, DafByteOrder::LittleEndian, &segment, 0.0).unwrap(),
2006 [100.0, 200.0, 300.0],
2007 [1.0, 2.0, 3.0],
2008 );
2009 assert_state_close(
2010 evaluate_type21_state(&bytes, DafByteOrder::LittleEndian, &segment, 5.0).unwrap(),
2011 [105.0, 210.0, 315.0],
2012 [1.0, 2.0, 3.0],
2013 );
2014 assert_state_close(
2016 evaluate_type21_state(&bytes, DafByteOrder::LittleEndian, &segment, 10.0).unwrap(),
2017 [110.0, 220.0, 330.0],
2018 [1.0, 2.0, 3.0],
2019 );
2020 assert_state_close(
2022 evaluate_type21_state(&bytes, DafByteOrder::LittleEndian, &segment, 15.0).unwrap(),
2023 [1050.0, 2100.0, 3150.0],
2024 [10.0, 20.0, 30.0],
2025 );
2026 assert_state_close(
2027 evaluate_type21_state(&bytes, DafByteOrder::LittleEndian, &segment, 20.0).unwrap(),
2028 [1100.0, 2200.0, 3300.0],
2029 [10.0, 20.0, 30.0],
2030 );
2031 }
2032
2033 #[test]
2034 fn evaluates_big_endian_type21_state() {
2035 let (bytes, segment) = build_type21_segment(DafByteOrder::BigEndian);
2036
2037 assert_state_close(
2038 evaluate_type21_state(&bytes, DafByteOrder::BigEndian, &segment, 5.0).unwrap(),
2039 [105.0, 210.0, 315.0],
2040 [1.0, 2.0, 3.0],
2041 );
2042 }
2043
2044 #[test]
2045 fn type21_out_of_coverage_returns_typed_error() {
2046 let (bytes, segment) = build_type21_segment(DafByteOrder::LittleEndian);
2047
2048 let err = evaluate_type21_state(&bytes, DafByteOrder::LittleEndian, &segment, 20.001)
2049 .unwrap_err();
2050
2051 assert_eq!(
2052 err,
2053 SpkError::OutOfCoverage {
2054 et: 20.001,
2055 start_et: 0.0,
2056 stop_et: 20.0,
2057 }
2058 );
2059 }
2060
2061 #[test]
2062 fn type21_nonfinite_et_returns_typed_error() {
2063 let (bytes, segment) = build_type21_segment(DafByteOrder::LittleEndian);
2064
2065 for et in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
2066 let err = evaluate_type21_state(&bytes, DafByteOrder::LittleEndian, &segment, et)
2067 .unwrap_err();
2068 assert!(
2069 matches!(
2070 err,
2071 SpkError::InvalidDoubleField {
2072 field: "type-21 ET",
2073 ..
2074 }
2075 ),
2076 "non-finite et {et} should be rejected, got {err:?}"
2077 );
2078 }
2079 }
2080
2081 #[test]
2082 fn type21_malformed_order_words_return_typed_error() {
2083 let maxdim = 3usize;
2087 let kqmax1_addr = 1 + 4 * maxdim + 8;
2088 let kq1_addr = 1 + 4 * maxdim + 9;
2089
2090 let (mut bytes, segment) = build_type21_segment(DafByteOrder::LittleEndian);
2092 write_f64_address(DafByteOrder::LittleEndian, &mut bytes, kqmax1_addr, 99.0);
2093 let err =
2094 evaluate_type21_state(&bytes, DafByteOrder::LittleEndian, &segment, 5.0).unwrap_err();
2095 assert!(
2096 matches!(err, SpkError::InvalidSegmentLayout { .. }),
2097 "oversized KQMAX1 should be rejected, got {err:?}"
2098 );
2099
2100 let (mut bytes, segment) = build_type21_segment(DafByteOrder::LittleEndian);
2102 write_f64_address(DafByteOrder::LittleEndian, &mut bytes, kq1_addr, 5.0);
2103 let err =
2104 evaluate_type21_state(&bytes, DafByteOrder::LittleEndian, &segment, 5.0).unwrap_err();
2105 assert!(
2106 matches!(err, SpkError::InvalidSegmentLayout { .. }),
2107 "out-of-range KQ should be rejected, got {err:?}"
2108 );
2109
2110 let (mut bytes, segment) = build_type21_segment(DafByteOrder::LittleEndian);
2112 write_f64_address(
2113 DafByteOrder::LittleEndian,
2114 &mut bytes,
2115 kqmax1_addr,
2116 f64::NAN,
2117 );
2118 let err =
2119 evaluate_type21_state(&bytes, DafByteOrder::LittleEndian, &segment, 5.0).unwrap_err();
2120 assert!(
2121 matches!(
2122 err,
2123 SpkError::InvalidDoubleField { .. } | SpkError::InvalidSegmentLayout { .. }
2124 ),
2125 "non-integer KQMAX1 should be rejected, got {err:?}"
2126 );
2127 }
2128
2129 #[test]
2136 fn real_type21_kernel_matches_cspice_reference() {
2137 const KERNEL: &[u8] = include_bytes!("../../tests/fixtures/spk/horizons_eros_type21.bsp");
2138
2139 const REFERENCE: &[(f64, [f64; 6])] = &[
2141 (
2142 757339200.0,
2143 [
2144 198083634.33689928,
2145 56306354.00566181,
2146 67761020.0290685,
2147 -14.136880898003753,
2148 18.729945253375007,
2149 8.080580941541488,
2150 ],
2151 ),
2152 (
2153 757655424.0,
2154 [
2155 193484166.17007136,
2156 62190955.161292516,
2157 70271250.61392583,
2158 -14.95317475425177,
2159 18.482891309963502,
2160 7.792787505001918,
2161 ],
2162 ),
2163 (
2164 760501440.0,
2165 [
2166 140599517.39824444,
2167 110142414.48840125,
2168 87942357.2561364,
2169 -22.110500498220798,
2170 14.728648269072185,
2171 4.367688325339683,
2172 ],
2173 ),
2174 (
2175 765244800.0,
2176 [
2177 14324682.473833444,
2178 151855494.96957216,
2179 88809564.6055465,
2180 -29.543141519840074,
2181 1.384349579197926,
2182 -4.552338928064369,
2183 ],
2184 ),
2185 (
2186 767879989.4592,
2187 [
2188 -62463976.26374265,
2189 142278295.29334122,
2190 69496198.60194506,
2191 -27.83899206786184,
2192 -8.728214407471189,
2193 -9.98623557339431,
2194 ],
2195 ),
2196 (
2197 773150400.0,
2198 [
2199 -170058326.9714746,
2200 51931259.239147335,
2201 -1243746.8623651236,
2202 -10.894277182103927,
2203 -23.170448141059893,
2204 -15.1244115495765,
2205 ],
2206 ),
2207 (
2208 778420810.5408,
2209 [
2210 -175392526.76953015,
2211 -73836504.48334283,
2212 -73616410.40702733,
2213 7.685028379798697,
2214 -22.45642797493469,
2215 -11.361635361252944,
2216 ],
2217 ),
2218 (
2219 781056000.0,
2220 [
2221 -146671840.75331673,
2222 -128352102.87455015,
2223 -99379387.41255096,
2224 13.724782826077085,
2225 -18.71213758130164,
2226 -8.14425619120024,
2227 ],
2228 ),
2229 (
2230 785799360.0,
2231 [
2232 -65781054.32577276,
2233 -197470134.64271438,
2234 -124005727.09542452,
2235 19.398793883808853,
2236 -10.268325580683918,
2237 -2.324610823885138,
2238 ],
2239 ),
2240 (
2241 788645376.0,
2242 [
2243 -8859755.122267516,
2244 -219270459.75263178,
2245 -126097226.6160046,
2246 20.34548822205983,
2247 -5.074720269573395,
2248 0.7953511794172388,
2249 ],
2250 ),
2251 (
2252 788961600.0,
2253 [
2254 -2423286.488811064,
2255 -220785626.12491044,
2256 -125794359.14041424,
2257 20.360009383792537,
2258 -4.508637229520069,
2259 1.1193915696949732,
2260 ],
2261 ),
2262 ];
2263
2264 let spk = Spk::from_bytes(KERNEL).unwrap();
2265
2266 let segments = spk.segments();
2267 assert_eq!(segments.len(), 1);
2268 assert_eq!(segments[0].data_type, SPK_TYPE_21);
2269 assert_eq!(segments[0].target, 20000433);
2270 assert_eq!(segments[0].center, 10);
2271
2272 let mut max_position_error = 0.0f64;
2273 let mut max_velocity_error = 0.0f64;
2274 for &(et, expected) in REFERENCE {
2275 let state = spk.spk_state(20000433, 10, et).unwrap();
2276 let velocity = state.velocity_km_s.expect("type-21 yields velocity");
2277 for axis in 0..3 {
2278 max_position_error =
2279 max_position_error.max((state.position_km[axis] - expected[axis]).abs());
2280 max_velocity_error =
2281 max_velocity_error.max((velocity[axis] - expected[axis + 3]).abs());
2282 }
2283 }
2284
2285 assert!(
2290 max_position_error < 5e-8,
2291 "type-21 position drift {max_position_error:e} km exceeds CSPICE parity gate"
2292 );
2293 assert!(
2294 max_velocity_error < 1e-14,
2295 "type-21 velocity drift {max_velocity_error:e} km/s exceeds CSPICE parity gate"
2296 );
2297 }
2298
2299 #[test]
2300 fn spk_state_returns_direct_segment_state() {
2301 let bytes = build_query_spk();
2302 let spk = Spk::from_bytes(&bytes).unwrap();
2303
2304 let state = spk.spk_state(301, 3, 5.0).unwrap();
2305
2306 assert_eq!(spk.file_record().internal_name, "QUERY SPK");
2307 assert_eq!(spk.segments().len(), 4);
2308 assert_eq!(state.target, 301);
2309 assert_eq!(state.center, 3);
2310 assert_query_state_close(state, [100.0, 200.0, 300.0], Some([1.0, 2.0, 3.0]), 1);
2311 }
2312
2313 #[test]
2314 fn spk_state_chains_through_common_center() {
2315 let bytes = build_query_spk();
2316 let spk = Spk::from_bytes(&bytes).unwrap();
2317
2318 let state = spk_state(&spk, 399, 3, 5.0).unwrap();
2319
2320 assert_eq!(state.target, 399);
2321 assert_eq!(state.center, 3);
2322 assert_query_state_close(state, [950.0, -5.0, 5.0], Some([9.5, -0.25, 0.5]), 1);
2323 }
2324
2325 #[test]
2326 fn spk_state_prefers_later_overlapping_segments() {
2327 let bytes = build_priority_spk();
2328 let spk = Spk::from_bytes(&bytes).unwrap();
2329
2330 let direct = spk.spk_state(301, 3, 5.0).unwrap();
2331 let chained = spk.spk_state(399, 3, 5.0).unwrap();
2332
2333 assert_eq!(spk.segments().len(), 5);
2334 assert_query_state_close(direct, [900.0, 800.0, 700.0], Some([9.0, 8.0, 7.0]), 1);
2335 assert_query_state_close(chained, [1950.0, 5.0, 25.0], Some([19.5, 0.25, 1.5]), 1);
2336 }
2337
2338 #[test]
2339 fn spk_state_prefers_later_position_only_segment() {
2340 let bytes = build_position_only_priority_spk();
2341 let spk = Spk::from_bytes(&bytes).unwrap();
2342
2343 let state = spk.spk_state(301, 3, 5.0).unwrap();
2344
2345 assert_eq!(spk.segments().len(), 2);
2346 assert_query_state_close(state, [700.0, 800.0, 900.0], None, 1);
2347 }
2348
2349 #[test]
2350 fn spk_state_errors_on_later_unsupported_segment() {
2351 let bytes = build_unsupported_priority_spk();
2352 let spk = Spk::from_bytes(&bytes).unwrap();
2353
2354 let err = spk.spk_state(301, 3, 5.0).unwrap_err();
2355 let supported = spk.spk_state(302, 3, 5.0).unwrap();
2356
2357 assert_eq!(err, SpkError::UnsupportedStateSegmentType { data_type: 99 });
2358 assert_query_state_close(supported, [400.0, 500.0, 600.0], Some([4.0, 5.0, 6.0]), 1);
2359 }
2360
2361 #[test]
2362 fn spk_state_prefers_later_segment_when_center_changes() {
2363 let bytes = build_changed_center_priority_spk();
2364 let spk = Spk::from_bytes(&bytes).unwrap();
2365
2366 let state = spk.spk_state(301, 3, 5.0).unwrap();
2367
2368 assert_eq!(spk.segments().len(), 3);
2369 assert_query_state_close(state, [1000.0, 80.0, 12.0], Some([100.0, 8.0, 1.2]), 1);
2370 }
2371
2372 #[test]
2373 fn spk_state_prefers_later_reversed_segment() {
2374 let bytes = build_reversed_priority_spk();
2375 let spk = Spk::from_bytes(&bytes).unwrap();
2376
2377 let state = spk.spk_state(301, 3, 5.0).unwrap();
2378
2379 assert_eq!(spk.segments().len(), 3);
2380 assert_query_state_close(state, [-800.0, -80.0, -2.0], Some([-80.0, -8.0, -0.2]), 1);
2381 }
2382
2383 #[test]
2384 fn spk_state_preserves_velocity_bearing_chain() {
2385 let bytes = build_velocity_retention_spk();
2386 let spk = Spk::from_bytes(&bytes).unwrap();
2387
2388 let state = spk.spk_state(800, 3, 5.0).unwrap();
2389
2390 assert_query_state_close(state, [120.0, 3.0, 4.0], Some([12.0, 0.3, 0.4]), 1);
2391 }
2392
2393 #[test]
2394 fn spk_state_tries_alternate_chain_after_frame_mismatch() {
2395 let bytes = build_frame_mismatch_spk();
2396 let spk = Spk::from_bytes(&bytes).unwrap();
2397
2398 let state = spk.spk_state(700, 3, 5.0).unwrap();
2399
2400 assert_query_state_close(state, [120.0, 3.0, 4.0], Some([12.0, 0.3, 0.4]), 1);
2401 }
2402
2403 #[test]
2404 fn spk_state_returns_frame_mismatch_when_no_chain_is_compatible() {
2405 let bytes = build_frame_mismatch_spk();
2406 let spk = Spk::from_bytes(&bytes).unwrap();
2407
2408 let err = spk.spk_state(701, 3, 5.0).unwrap_err();
2409
2410 assert_eq!(
2411 err,
2412 SpkError::FrameMismatch {
2413 first: 1,
2414 second: 2,
2415 }
2416 );
2417 }
2418
2419 #[test]
2420 fn spk_state_returns_none_velocity_for_type2_segment() {
2421 let bytes = build_query_spk();
2422 let spk = Spk::from_bytes(&bytes).unwrap();
2423
2424 let state = spk.spk_state(302, 3, 5.0).unwrap();
2425
2426 assert_query_state_close(state, [7.0, 8.0, 9.0], None, 1);
2427 }
2428
2429 #[test]
2430 fn spk_state_known_self_query_returns_zero_state() {
2431 let bytes = build_query_spk();
2432 let spk = Spk::from_bytes(&bytes).unwrap();
2433
2434 let state = spk.spk_state(301, 301, 5.0).unwrap();
2435
2436 assert_query_state_close(state, [0.0; 3], Some([0.0; 3]), 0);
2437 }
2438
2439 #[test]
2440 fn spk_state_unknown_self_query_returns_typed_error() {
2441 let bytes = build_query_spk();
2442 let spk = Spk::from_bytes(&bytes).unwrap();
2443
2444 let err = spk.spk_state(999, 999, 5.0).unwrap_err();
2445
2446 assert_eq!(err, SpkError::UnknownBody { body: 999 });
2447 }
2448
2449 #[test]
2450 fn spk_state_self_query_out_of_coverage_returns_typed_error() {
2451 let bytes = build_query_spk();
2452 let spk = Spk::from_bytes(&bytes).unwrap();
2453
2454 let err = spk.spk_state(301, 301, 20.0).unwrap_err();
2455
2456 assert_eq!(
2457 err,
2458 SpkError::CoverageGap {
2459 target: 301,
2460 center: 301,
2461 et: 20.0,
2462 }
2463 );
2464 }
2465
2466 #[test]
2467 fn spk_state_unknown_target_returns_typed_error() {
2468 let bytes = build_query_spk();
2469 let spk = Spk::from_bytes(&bytes).unwrap();
2470
2471 let err = spk.spk_state(999, 0, 5.0).unwrap_err();
2472
2473 assert_eq!(err, SpkError::UnknownBody { body: 999 });
2474 }
2475
2476 #[test]
2477 fn spk_state_out_of_coverage_returns_typed_error() {
2478 let bytes = build_query_spk();
2479 let spk = Spk::from_bytes(&bytes).unwrap();
2480
2481 let err = spk.spk_state(301, 3, 20.0).unwrap_err();
2482
2483 assert_eq!(
2484 err,
2485 SpkError::CoverageGap {
2486 target: 301,
2487 center: 3,
2488 et: 20.0,
2489 }
2490 );
2491 }
2492
2493 #[cfg(feature = "std")]
2494 #[test]
2495 fn std_load_reads_spk_file() {
2496 let bytes = build_query_spk();
2497 let mut path = std::env::temp_dir();
2498 let nonce = std::time::SystemTime::now()
2499 .duration_since(std::time::UNIX_EPOCH)
2500 .unwrap()
2501 .as_nanos();
2502 path.push(format!(
2503 "sidereon-spk-load-{}-{nonce}.bsp",
2504 std::process::id()
2505 ));
2506
2507 std::fs::write(&path, &bytes).unwrap();
2508
2509 let spk = Spk::load(&path).unwrap();
2510 std::fs::remove_file(&path).unwrap();
2511
2512 let state = spk.spk_state(301, 3, 5.0).unwrap();
2513 assert_query_state_close(state, [100.0, 200.0, 300.0], Some([1.0, 2.0, 3.0]), 1);
2514
2515 let err = Spk::load(&path).unwrap_err();
2516 match err {
2517 SpkError::Io {
2518 path: err_path,
2519 message,
2520 } => {
2521 assert_eq!(err_path, path.display().to_string());
2522 assert!(!message.is_empty());
2523 }
2524 other => panic!("expected IO error from missing SPK path, got {other:?}"),
2525 }
2526 }
2527
2528 fn expected_segments() -> Vec<SpkSegmentDescriptor> {
2529 vec![
2530 SpkSegmentDescriptor {
2531 name: "MERCURY BARYCENTER".to_string(),
2532 start_et: -100.0,
2533 stop_et: 100.0,
2534 target: 1,
2535 center: 0,
2536 frame: 1,
2537 data_type: 2,
2538 start_address: 513,
2539 end_address: 700,
2540 },
2541 SpkSegmentDescriptor {
2542 name: "EARTH".to_string(),
2543 start_et: 200.0,
2544 stop_et: 400.0,
2545 target: 399,
2546 center: 3,
2547 frame: 17,
2548 data_type: 3,
2549 start_address: 701,
2550 end_address: 900,
2551 },
2552 ]
2553 }
2554
2555 fn build_daf(byte_order: DafByteOrder) -> Vec<u8> {
2556 let mut bytes = vec![0u8; DAF_RECORD_BYTES * 4];
2557
2558 bytes[0..8].copy_from_slice(b"DAF/SPK ");
2559 write_i32(byte_order, &mut bytes, 8, 2);
2560 write_i32(byte_order, &mut bytes, 12, 6);
2561 write_ascii(&mut bytes, 16, DAF_INTERNAL_NAME_BYTES, "SYNTHETIC SPK");
2562 write_i32(byte_order, &mut bytes, 76, 3);
2563 write_i32(byte_order, &mut bytes, 80, 3);
2564 write_i32(byte_order, &mut bytes, 84, 901);
2565 match byte_order {
2566 DafByteOrder::LittleEndian => bytes
2567 [DAF_BINARY_FORMAT_OFFSET..DAF_BINARY_FORMAT_OFFSET + 8]
2568 .copy_from_slice(b"LTL-IEEE"),
2569 DafByteOrder::BigEndian => bytes
2570 [DAF_BINARY_FORMAT_OFFSET..DAF_BINARY_FORMAT_OFFSET + 8]
2571 .copy_from_slice(b"BIG-IEEE"),
2572 }
2573
2574 let summary_offset = DAF_RECORD_BYTES * 2;
2575 let name_offset = DAF_RECORD_BYTES * 3;
2576 write_f64(byte_order, &mut bytes, summary_offset, 0.0);
2577 write_f64(byte_order, &mut bytes, summary_offset + 8, 0.0);
2578 write_f64(byte_order, &mut bytes, summary_offset + 16, 2.0);
2579
2580 write_summary(
2581 byte_order,
2582 &mut bytes,
2583 summary_offset + 24,
2584 -100.0,
2585 100.0,
2586 [1, 0, 1, 2, 513, 700],
2587 );
2588 write_summary(
2589 byte_order,
2590 &mut bytes,
2591 summary_offset + 64,
2592 200.0,
2593 400.0,
2594 [399, 3, 17, 3, 701, 900],
2595 );
2596 write_ascii(&mut bytes, name_offset, 40, "MERCURY BARYCENTER");
2597 write_ascii(&mut bytes, name_offset + 40, 40, "EARTH");
2598
2599 bytes
2600 }
2601
2602 fn build_chained_daf(byte_order: DafByteOrder, final_next_record: f64) -> Vec<u8> {
2603 let mut bytes = vec![0u8; DAF_RECORD_BYTES * 6];
2604
2605 bytes[0..8].copy_from_slice(b"DAF/SPK ");
2606 write_i32(byte_order, &mut bytes, 8, 2);
2607 write_i32(byte_order, &mut bytes, 12, 6);
2608 write_ascii(&mut bytes, 16, DAF_INTERNAL_NAME_BYTES, "CHAINED SPK");
2609 write_i32(byte_order, &mut bytes, 76, 3);
2610 write_i32(byte_order, &mut bytes, 80, 5);
2611 write_i32(byte_order, &mut bytes, 84, 901);
2612 match byte_order {
2613 DafByteOrder::LittleEndian => bytes
2614 [DAF_BINARY_FORMAT_OFFSET..DAF_BINARY_FORMAT_OFFSET + 8]
2615 .copy_from_slice(b"LTL-IEEE"),
2616 DafByteOrder::BigEndian => bytes
2617 [DAF_BINARY_FORMAT_OFFSET..DAF_BINARY_FORMAT_OFFSET + 8]
2618 .copy_from_slice(b"BIG-IEEE"),
2619 }
2620
2621 let first_summary_offset = DAF_RECORD_BYTES * 2;
2622 let first_name_offset = DAF_RECORD_BYTES * 3;
2623 write_f64(byte_order, &mut bytes, first_summary_offset, 5.0);
2624 write_f64(byte_order, &mut bytes, first_summary_offset + 8, 0.0);
2625 write_f64(byte_order, &mut bytes, first_summary_offset + 16, 1.0);
2626 write_summary(
2627 byte_order,
2628 &mut bytes,
2629 first_summary_offset + 24,
2630 -100.0,
2631 100.0,
2632 [1, 0, 1, 2, 513, 700],
2633 );
2634 write_ascii(&mut bytes, first_name_offset, 40, "MERCURY BARYCENTER");
2635
2636 let second_summary_offset = DAF_RECORD_BYTES * 4;
2637 let second_name_offset = DAF_RECORD_BYTES * 5;
2638 write_f64(
2639 byte_order,
2640 &mut bytes,
2641 second_summary_offset,
2642 final_next_record,
2643 );
2644 write_f64(byte_order, &mut bytes, second_summary_offset + 8, 3.0);
2645 write_f64(byte_order, &mut bytes, second_summary_offset + 16, 1.0);
2646 write_summary(
2647 byte_order,
2648 &mut bytes,
2649 second_summary_offset + 24,
2650 200.0,
2651 400.0,
2652 [399, 3, 17, 3, 701, 900],
2653 );
2654 write_ascii(&mut bytes, second_name_offset, 40, "EARTH");
2655
2656 bytes
2657 }
2658
2659 fn build_query_spk() -> Vec<u8> {
2660 let byte_order = DafByteOrder::LittleEndian;
2661 let direct_start = 513usize;
2662 let target_to_ssb_start = 525usize;
2663 let center_to_ssb_start = 537usize;
2664 let type2_start = 549usize;
2665 let type2_end = type2_start + 8;
2666 let mut bytes = vec![0u8; type2_end * 8];
2667
2668 bytes[0..8].copy_from_slice(b"DAF/SPK ");
2669 write_i32(byte_order, &mut bytes, 8, 2);
2670 write_i32(byte_order, &mut bytes, 12, 6);
2671 write_ascii(&mut bytes, 16, DAF_INTERNAL_NAME_BYTES, "QUERY SPK");
2672 write_i32(byte_order, &mut bytes, 76, 3);
2673 write_i32(byte_order, &mut bytes, 80, 3);
2674 write_i32(byte_order, &mut bytes, 84, (type2_end + 1) as i32);
2675 bytes[DAF_BINARY_FORMAT_OFFSET..DAF_BINARY_FORMAT_OFFSET + 8].copy_from_slice(b"LTL-IEEE");
2676
2677 let summary_offset = DAF_RECORD_BYTES * 2;
2678 let name_offset = DAF_RECORD_BYTES * 3;
2679 write_f64(byte_order, &mut bytes, summary_offset, 0.0);
2680 write_f64(byte_order, &mut bytes, summary_offset + 8, 0.0);
2681 write_f64(byte_order, &mut bytes, summary_offset + 16, 4.0);
2682
2683 write_summary(
2684 byte_order,
2685 &mut bytes,
2686 summary_offset + 24,
2687 0.0,
2688 10.0,
2689 [301, 3, 1, 3, direct_start as i32, direct_start as i32 + 11],
2690 );
2691 write_summary(
2692 byte_order,
2693 &mut bytes,
2694 summary_offset + 64,
2695 0.0,
2696 10.0,
2697 [
2698 399,
2699 0,
2700 1,
2701 3,
2702 target_to_ssb_start as i32,
2703 target_to_ssb_start as i32 + 11,
2704 ],
2705 );
2706 write_summary(
2707 byte_order,
2708 &mut bytes,
2709 summary_offset + 104,
2710 0.0,
2711 10.0,
2712 [
2713 3,
2714 0,
2715 1,
2716 3,
2717 center_to_ssb_start as i32,
2718 center_to_ssb_start as i32 + 11,
2719 ],
2720 );
2721 write_summary(
2722 byte_order,
2723 &mut bytes,
2724 summary_offset + 144,
2725 0.0,
2726 10.0,
2727 [302, 3, 1, 2, type2_start as i32, type2_end as i32],
2728 );
2729
2730 write_ascii(&mut bytes, name_offset, 40, "BODY 301 TO CENTER 3");
2731 write_ascii(&mut bytes, name_offset + 40, 40, "BODY 399 TO SSB");
2732 write_ascii(&mut bytes, name_offset + 80, 40, "CENTER 3 TO SSB");
2733 write_ascii(
2734 &mut bytes,
2735 name_offset + 120,
2736 40,
2737 "TYPE2 BODY 302 TO CENTER 3",
2738 );
2739
2740 write_type3_constant_segment(
2741 byte_order,
2742 &mut bytes,
2743 direct_start,
2744 [100.0, 200.0, 300.0],
2745 [1.0, 2.0, 3.0],
2746 );
2747 write_type3_constant_segment(
2748 byte_order,
2749 &mut bytes,
2750 target_to_ssb_start,
2751 [1000.0, 0.0, 0.0],
2752 [10.0, 0.0, 0.0],
2753 );
2754 write_type3_constant_segment(
2755 byte_order,
2756 &mut bytes,
2757 center_to_ssb_start,
2758 [50.0, 5.0, -5.0],
2759 [0.5, 0.25, -0.5],
2760 );
2761 write_type2_constant_segment(byte_order, &mut bytes, type2_start, [7.0, 8.0, 9.0]);
2762
2763 bytes
2764 }
2765
2766 fn build_priority_spk() -> Vec<u8> {
2767 let byte_order = DafByteOrder::LittleEndian;
2768 let direct_early_start = 513usize;
2769 let direct_priority_start = 525usize;
2770 let target_early_start = 537usize;
2771 let center_start = 549usize;
2772 let target_priority_start = 561usize;
2773 let target_priority_end = target_priority_start + 11;
2774 let mut bytes = vec![0u8; target_priority_end * 8];
2775
2776 bytes[0..8].copy_from_slice(b"DAF/SPK ");
2777 write_i32(byte_order, &mut bytes, 8, 2);
2778 write_i32(byte_order, &mut bytes, 12, 6);
2779 write_ascii(&mut bytes, 16, DAF_INTERNAL_NAME_BYTES, "PRIORITY SPK");
2780 write_i32(byte_order, &mut bytes, 76, 3);
2781 write_i32(byte_order, &mut bytes, 80, 3);
2782 write_i32(byte_order, &mut bytes, 84, (target_priority_end + 1) as i32);
2783 bytes[DAF_BINARY_FORMAT_OFFSET..DAF_BINARY_FORMAT_OFFSET + 8].copy_from_slice(b"LTL-IEEE");
2784
2785 let summary_offset = DAF_RECORD_BYTES * 2;
2786 let name_offset = DAF_RECORD_BYTES * 3;
2787 write_f64(byte_order, &mut bytes, summary_offset, 0.0);
2788 write_f64(byte_order, &mut bytes, summary_offset + 8, 0.0);
2789 write_f64(byte_order, &mut bytes, summary_offset + 16, 5.0);
2790
2791 write_summary(
2792 byte_order,
2793 &mut bytes,
2794 summary_offset + 24,
2795 0.0,
2796 10.0,
2797 [
2798 301,
2799 3,
2800 1,
2801 3,
2802 direct_early_start as i32,
2803 direct_early_start as i32 + 11,
2804 ],
2805 );
2806 write_summary(
2807 byte_order,
2808 &mut bytes,
2809 summary_offset + 64,
2810 0.0,
2811 10.0,
2812 [
2813 301,
2814 3,
2815 1,
2816 3,
2817 direct_priority_start as i32,
2818 direct_priority_start as i32 + 11,
2819 ],
2820 );
2821 write_summary(
2822 byte_order,
2823 &mut bytes,
2824 summary_offset + 104,
2825 0.0,
2826 10.0,
2827 [
2828 399,
2829 0,
2830 1,
2831 3,
2832 target_early_start as i32,
2833 target_early_start as i32 + 11,
2834 ],
2835 );
2836 write_summary(
2837 byte_order,
2838 &mut bytes,
2839 summary_offset + 144,
2840 0.0,
2841 10.0,
2842 [3, 0, 1, 3, center_start as i32, center_start as i32 + 11],
2843 );
2844 write_summary(
2845 byte_order,
2846 &mut bytes,
2847 summary_offset + 184,
2848 0.0,
2849 10.0,
2850 [
2851 399,
2852 0,
2853 1,
2854 3,
2855 target_priority_start as i32,
2856 target_priority_start as i32 + 11,
2857 ],
2858 );
2859
2860 write_ascii(&mut bytes, name_offset, 40, "EARLY BODY 301 TO CENTER 3");
2861 write_ascii(
2862 &mut bytes,
2863 name_offset + 40,
2864 40,
2865 "PRIORITY BODY 301 TO CENTER 3",
2866 );
2867 write_ascii(&mut bytes, name_offset + 80, 40, "EARLY BODY 399 TO SSB");
2868 write_ascii(&mut bytes, name_offset + 120, 40, "CENTER 3 TO SSB");
2869 write_ascii(
2870 &mut bytes,
2871 name_offset + 160,
2872 40,
2873 "PRIORITY BODY 399 TO SSB",
2874 );
2875
2876 write_type3_constant_segment(
2877 byte_order,
2878 &mut bytes,
2879 direct_early_start,
2880 [100.0, 200.0, 300.0],
2881 [1.0, 2.0, 3.0],
2882 );
2883 write_type3_constant_segment(
2884 byte_order,
2885 &mut bytes,
2886 direct_priority_start,
2887 [900.0, 800.0, 700.0],
2888 [9.0, 8.0, 7.0],
2889 );
2890 write_type3_constant_segment(
2891 byte_order,
2892 &mut bytes,
2893 target_early_start,
2894 [1000.0, 0.0, 0.0],
2895 [10.0, 0.0, 0.0],
2896 );
2897 write_type3_constant_segment(
2898 byte_order,
2899 &mut bytes,
2900 center_start,
2901 [50.0, 5.0, -5.0],
2902 [0.5, 0.25, -0.5],
2903 );
2904 write_type3_constant_segment(
2905 byte_order,
2906 &mut bytes,
2907 target_priority_start,
2908 [2000.0, 10.0, 20.0],
2909 [20.0, 0.5, 1.0],
2910 );
2911
2912 bytes
2913 }
2914
2915 fn build_position_only_priority_spk() -> Vec<u8> {
2916 let byte_order = DafByteOrder::LittleEndian;
2917 let direct_early_start = 513usize;
2918 let direct_priority_start = 525usize;
2919 let direct_priority_end = direct_priority_start + 8;
2920 let mut bytes = vec![0u8; direct_priority_end * 8];
2921
2922 bytes[0..8].copy_from_slice(b"DAF/SPK ");
2923 write_i32(byte_order, &mut bytes, 8, 2);
2924 write_i32(byte_order, &mut bytes, 12, 6);
2925 write_ascii(
2926 &mut bytes,
2927 16,
2928 DAF_INTERNAL_NAME_BYTES,
2929 "POSITION ONLY PRIORITY SPK",
2930 );
2931 write_i32(byte_order, &mut bytes, 76, 3);
2932 write_i32(byte_order, &mut bytes, 80, 3);
2933 write_i32(byte_order, &mut bytes, 84, (direct_priority_end + 1) as i32);
2934 bytes[DAF_BINARY_FORMAT_OFFSET..DAF_BINARY_FORMAT_OFFSET + 8].copy_from_slice(b"LTL-IEEE");
2935
2936 let summary_offset = DAF_RECORD_BYTES * 2;
2937 let name_offset = DAF_RECORD_BYTES * 3;
2938 write_f64(byte_order, &mut bytes, summary_offset, 0.0);
2939 write_f64(byte_order, &mut bytes, summary_offset + 8, 0.0);
2940 write_f64(byte_order, &mut bytes, summary_offset + 16, 2.0);
2941
2942 write_summary(
2943 byte_order,
2944 &mut bytes,
2945 summary_offset + 24,
2946 0.0,
2947 10.0,
2948 [
2949 301,
2950 3,
2951 1,
2952 3,
2953 direct_early_start as i32,
2954 direct_early_start as i32 + 11,
2955 ],
2956 );
2957 write_summary(
2958 byte_order,
2959 &mut bytes,
2960 summary_offset + 64,
2961 0.0,
2962 10.0,
2963 [
2964 301,
2965 3,
2966 1,
2967 2,
2968 direct_priority_start as i32,
2969 direct_priority_end as i32,
2970 ],
2971 );
2972
2973 write_ascii(
2974 &mut bytes,
2975 name_offset,
2976 40,
2977 "EARLY TYPE3 BODY 301 TO CENTER 3",
2978 );
2979 write_ascii(
2980 &mut bytes,
2981 name_offset + 40,
2982 40,
2983 "LATE TYPE2 BODY 301 TO CENTER 3",
2984 );
2985
2986 write_type3_constant_segment(
2987 byte_order,
2988 &mut bytes,
2989 direct_early_start,
2990 [100.0, 200.0, 300.0],
2991 [1.0, 2.0, 3.0],
2992 );
2993 write_type2_constant_segment(
2994 byte_order,
2995 &mut bytes,
2996 direct_priority_start,
2997 [700.0, 800.0, 900.0],
2998 );
2999
3000 bytes
3001 }
3002
3003 fn build_unsupported_priority_spk() -> Vec<u8> {
3004 let byte_order = DafByteOrder::LittleEndian;
3005 let direct_early_start = 513usize;
3006 let unsupported_start = 525usize;
3007 let supported_start = 537usize;
3008 let supported_end = supported_start + 11;
3009 let mut bytes = vec![0u8; supported_end * 8];
3010
3011 bytes[0..8].copy_from_slice(b"DAF/SPK ");
3012 write_i32(byte_order, &mut bytes, 8, 2);
3013 write_i32(byte_order, &mut bytes, 12, 6);
3014 write_ascii(
3015 &mut bytes,
3016 16,
3017 DAF_INTERNAL_NAME_BYTES,
3018 "UNSUPPORTED PRIORITY SPK",
3019 );
3020 write_i32(byte_order, &mut bytes, 76, 3);
3021 write_i32(byte_order, &mut bytes, 80, 3);
3022 write_i32(byte_order, &mut bytes, 84, (supported_end + 1) as i32);
3023 bytes[DAF_BINARY_FORMAT_OFFSET..DAF_BINARY_FORMAT_OFFSET + 8].copy_from_slice(b"LTL-IEEE");
3024
3025 let summary_offset = DAF_RECORD_BYTES * 2;
3026 let name_offset = DAF_RECORD_BYTES * 3;
3027 write_f64(byte_order, &mut bytes, summary_offset, 0.0);
3028 write_f64(byte_order, &mut bytes, summary_offset + 8, 0.0);
3029 write_f64(byte_order, &mut bytes, summary_offset + 16, 3.0);
3030
3031 write_summary(
3032 byte_order,
3033 &mut bytes,
3034 summary_offset + 24,
3035 0.0,
3036 10.0,
3037 [
3038 301,
3039 3,
3040 1,
3041 3,
3042 direct_early_start as i32,
3043 direct_early_start as i32 + 11,
3044 ],
3045 );
3046 write_summary(
3047 byte_order,
3048 &mut bytes,
3049 summary_offset + 64,
3050 0.0,
3051 10.0,
3052 [
3053 301,
3054 3,
3055 1,
3056 99,
3057 unsupported_start as i32,
3058 unsupported_start as i32 + 11,
3059 ],
3060 );
3061 write_summary(
3062 byte_order,
3063 &mut bytes,
3064 summary_offset + 104,
3065 0.0,
3066 10.0,
3067 [
3068 302,
3069 3,
3070 1,
3071 3,
3072 supported_start as i32,
3073 supported_start as i32 + 11,
3074 ],
3075 );
3076
3077 write_ascii(
3078 &mut bytes,
3079 name_offset,
3080 40,
3081 "EARLY TYPE3 BODY 301 TO CENTER 3",
3082 );
3083 write_ascii(
3084 &mut bytes,
3085 name_offset + 40,
3086 40,
3087 "LATE UNSUPPORTED BODY 301 TO 3",
3088 );
3089 write_ascii(
3090 &mut bytes,
3091 name_offset + 80,
3092 40,
3093 "SUPPORTED BODY 302 TO CENTER 3",
3094 );
3095
3096 write_type3_constant_segment(
3097 byte_order,
3098 &mut bytes,
3099 direct_early_start,
3100 [100.0, 200.0, 300.0],
3101 [1.0, 2.0, 3.0],
3102 );
3103 write_type3_constant_segment(
3104 byte_order,
3105 &mut bytes,
3106 unsupported_start,
3107 [900.0, 900.0, 900.0],
3108 [9.0, 9.0, 9.0],
3109 );
3110 write_type3_constant_segment(
3111 byte_order,
3112 &mut bytes,
3113 supported_start,
3114 [400.0, 500.0, 600.0],
3115 [4.0, 5.0, 6.0],
3116 );
3117
3118 bytes
3119 }
3120
3121 fn build_changed_center_priority_spk() -> Vec<u8> {
3122 let byte_order = DafByteOrder::LittleEndian;
3123 let direct_early_start = 513usize;
3124 let center_start = 525usize;
3125 let target_priority_start = 537usize;
3126 let target_priority_end = target_priority_start + 11;
3127 let mut bytes = vec![0u8; target_priority_end * 8];
3128
3129 bytes[0..8].copy_from_slice(b"DAF/SPK ");
3130 write_i32(byte_order, &mut bytes, 8, 2);
3131 write_i32(byte_order, &mut bytes, 12, 6);
3132 write_ascii(
3133 &mut bytes,
3134 16,
3135 DAF_INTERNAL_NAME_BYTES,
3136 "CHANGED CENTER PRIORITY SPK",
3137 );
3138 write_i32(byte_order, &mut bytes, 76, 3);
3139 write_i32(byte_order, &mut bytes, 80, 3);
3140 write_i32(byte_order, &mut bytes, 84, (target_priority_end + 1) as i32);
3141 bytes[DAF_BINARY_FORMAT_OFFSET..DAF_BINARY_FORMAT_OFFSET + 8].copy_from_slice(b"LTL-IEEE");
3142
3143 let summary_offset = DAF_RECORD_BYTES * 2;
3144 let name_offset = DAF_RECORD_BYTES * 3;
3145 write_f64(byte_order, &mut bytes, summary_offset, 0.0);
3146 write_f64(byte_order, &mut bytes, summary_offset + 8, 0.0);
3147 write_f64(byte_order, &mut bytes, summary_offset + 16, 3.0);
3148
3149 write_summary(
3150 byte_order,
3151 &mut bytes,
3152 summary_offset + 24,
3153 0.0,
3154 10.0,
3155 [
3156 301,
3157 3,
3158 1,
3159 3,
3160 direct_early_start as i32,
3161 direct_early_start as i32 + 11,
3162 ],
3163 );
3164 write_summary(
3165 byte_order,
3166 &mut bytes,
3167 summary_offset + 64,
3168 0.0,
3169 10.0,
3170 [20, 3, 1, 3, center_start as i32, center_start as i32 + 11],
3171 );
3172 write_summary(
3173 byte_order,
3174 &mut bytes,
3175 summary_offset + 104,
3176 0.0,
3177 10.0,
3178 [
3179 301,
3180 20,
3181 1,
3182 3,
3183 target_priority_start as i32,
3184 target_priority_start as i32 + 11,
3185 ],
3186 );
3187
3188 write_ascii(&mut bytes, name_offset, 40, "EARLY BODY 301 TO CENTER 3");
3189 write_ascii(&mut bytes, name_offset + 40, 40, "CENTER 20 TO 3");
3190 write_ascii(
3191 &mut bytes,
3192 name_offset + 80,
3193 40,
3194 "PRIORITY BODY 301 TO CENTER 20",
3195 );
3196
3197 write_type3_constant_segment(
3198 byte_order,
3199 &mut bytes,
3200 direct_early_start,
3201 [10.0, 20.0, 30.0],
3202 [1.0, 2.0, 3.0],
3203 );
3204 write_type3_constant_segment(
3205 byte_order,
3206 &mut bytes,
3207 center_start,
3208 [100.0, 0.0, 5.0],
3209 [10.0, 0.0, 0.5],
3210 );
3211 write_type3_constant_segment(
3212 byte_order,
3213 &mut bytes,
3214 target_priority_start,
3215 [900.0, 80.0, 7.0],
3216 [90.0, 8.0, 0.7],
3217 );
3218
3219 bytes
3220 }
3221
3222 fn build_reversed_priority_spk() -> Vec<u8> {
3223 let byte_order = DafByteOrder::LittleEndian;
3224 let forward_start = 513usize;
3225 let center_start = 525usize;
3226 let reversed_priority_start = 537usize;
3227 let reversed_priority_end = reversed_priority_start + 11;
3228 let mut bytes = vec![0u8; reversed_priority_end * 8];
3229
3230 bytes[0..8].copy_from_slice(b"DAF/SPK ");
3231 write_i32(byte_order, &mut bytes, 8, 2);
3232 write_i32(byte_order, &mut bytes, 12, 6);
3233 write_ascii(
3234 &mut bytes,
3235 16,
3236 DAF_INTERNAL_NAME_BYTES,
3237 "REVERSED PRIORITY SPK",
3238 );
3239 write_i32(byte_order, &mut bytes, 76, 3);
3240 write_i32(byte_order, &mut bytes, 80, 3);
3241 write_i32(
3242 byte_order,
3243 &mut bytes,
3244 84,
3245 (reversed_priority_end + 1) as i32,
3246 );
3247 bytes[DAF_BINARY_FORMAT_OFFSET..DAF_BINARY_FORMAT_OFFSET + 8].copy_from_slice(b"LTL-IEEE");
3248
3249 let summary_offset = DAF_RECORD_BYTES * 2;
3250 let name_offset = DAF_RECORD_BYTES * 3;
3251 write_f64(byte_order, &mut bytes, summary_offset, 0.0);
3252 write_f64(byte_order, &mut bytes, summary_offset + 8, 0.0);
3253 write_f64(byte_order, &mut bytes, summary_offset + 16, 3.0);
3254
3255 write_summary(
3256 byte_order,
3257 &mut bytes,
3258 summary_offset + 24,
3259 0.0,
3260 10.0,
3261 [
3262 301,
3263 20,
3264 1,
3265 3,
3266 forward_start as i32,
3267 forward_start as i32 + 11,
3268 ],
3269 );
3270 write_summary(
3271 byte_order,
3272 &mut bytes,
3273 summary_offset + 64,
3274 0.0,
3275 10.0,
3276 [20, 3, 1, 3, center_start as i32, center_start as i32 + 11],
3277 );
3278 write_summary(
3279 byte_order,
3280 &mut bytes,
3281 summary_offset + 104,
3282 0.0,
3283 10.0,
3284 [
3285 20,
3286 301,
3287 1,
3288 3,
3289 reversed_priority_start as i32,
3290 reversed_priority_start as i32 + 11,
3291 ],
3292 );
3293
3294 write_ascii(&mut bytes, name_offset, 40, "EARLY BODY 301 TO CENTER 20");
3295 write_ascii(&mut bytes, name_offset + 40, 40, "CENTER 20 TO 3");
3296 write_ascii(&mut bytes, name_offset + 80, 40, "PRIORITY BODY 20 TO 301");
3297
3298 write_type3_constant_segment(
3299 byte_order,
3300 &mut bytes,
3301 forward_start,
3302 [10.0, 20.0, 30.0],
3303 [1.0, 2.0, 3.0],
3304 );
3305 write_type3_constant_segment(
3306 byte_order,
3307 &mut bytes,
3308 center_start,
3309 [100.0, 0.0, 5.0],
3310 [10.0, 0.0, 0.5],
3311 );
3312 write_type3_constant_segment(
3313 byte_order,
3314 &mut bytes,
3315 reversed_priority_start,
3316 [900.0, 80.0, 7.0],
3317 [90.0, 8.0, 0.7],
3318 );
3319
3320 bytes
3321 }
3322
3323 fn build_velocity_retention_spk() -> Vec<u8> {
3324 let byte_order = DafByteOrder::LittleEndian;
3325 let center_start = 513usize;
3326 let velocity_target_start = 525usize;
3327 let position_target_start = 537usize;
3328 let position_target_end = position_target_start + 8;
3329 let mut bytes = vec![0u8; position_target_end * 8];
3330
3331 bytes[0..8].copy_from_slice(b"DAF/SPK ");
3332 write_i32(byte_order, &mut bytes, 8, 2);
3333 write_i32(byte_order, &mut bytes, 12, 6);
3334 write_ascii(
3335 &mut bytes,
3336 16,
3337 DAF_INTERNAL_NAME_BYTES,
3338 "VELOCITY RETENTION SPK",
3339 );
3340 write_i32(byte_order, &mut bytes, 76, 3);
3341 write_i32(byte_order, &mut bytes, 80, 3);
3342 write_i32(byte_order, &mut bytes, 84, (position_target_end + 1) as i32);
3343 bytes[DAF_BINARY_FORMAT_OFFSET..DAF_BINARY_FORMAT_OFFSET + 8].copy_from_slice(b"LTL-IEEE");
3344
3345 let summary_offset = DAF_RECORD_BYTES * 2;
3346 let name_offset = DAF_RECORD_BYTES * 3;
3347 write_f64(byte_order, &mut bytes, summary_offset, 0.0);
3348 write_f64(byte_order, &mut bytes, summary_offset + 8, 0.0);
3349 write_f64(byte_order, &mut bytes, summary_offset + 16, 3.0);
3350
3351 write_summary(
3352 byte_order,
3353 &mut bytes,
3354 summary_offset + 24,
3355 0.0,
3356 10.0,
3357 [20, 3, 1, 3, center_start as i32, center_start as i32 + 11],
3358 );
3359 write_summary(
3360 byte_order,
3361 &mut bytes,
3362 summary_offset + 64,
3363 0.0,
3364 10.0,
3365 [
3366 800,
3367 20,
3368 1,
3369 3,
3370 velocity_target_start as i32,
3371 velocity_target_start as i32 + 11,
3372 ],
3373 );
3374 write_summary(
3375 byte_order,
3376 &mut bytes,
3377 summary_offset + 104,
3378 0.0,
3379 10.0,
3380 [
3381 800,
3382 20,
3383 1,
3384 2,
3385 position_target_start as i32,
3386 position_target_end as i32,
3387 ],
3388 );
3389
3390 write_ascii(&mut bytes, name_offset, 40, "CENTER 20 TO 3");
3391 write_ascii(
3392 &mut bytes,
3393 name_offset + 40,
3394 40,
3395 "VELOCITY TARGET 800 TO 20",
3396 );
3397 write_ascii(
3398 &mut bytes,
3399 name_offset + 80,
3400 40,
3401 "POSITION TARGET 800 TO 20",
3402 );
3403
3404 write_type3_constant_segment(
3405 byte_order,
3406 &mut bytes,
3407 center_start,
3408 [20.0, 3.0, 4.0],
3409 [2.0, 0.3, 0.4],
3410 );
3411 write_type3_constant_segment(
3412 byte_order,
3413 &mut bytes,
3414 velocity_target_start,
3415 [100.0, 0.0, 0.0],
3416 [10.0, 0.0, 0.0],
3417 );
3418 write_type2_constant_segment(
3419 byte_order,
3420 &mut bytes,
3421 position_target_start,
3422 [900.0, 0.0, 0.0],
3423 );
3424
3425 bytes
3426 }
3427
3428 fn build_frame_mismatch_spk() -> Vec<u8> {
3429 let byte_order = DafByteOrder::LittleEndian;
3430 let good_center_start = 513usize;
3431 let good_target_start = 525usize;
3432 let bad_center_start = 537usize;
3433 let bad_target_start = 549usize;
3434 let unresolvable_center_start = 561usize;
3435 let unresolvable_target_start = 573usize;
3436 let unresolvable_target_end = unresolvable_target_start + 11;
3437 let mut bytes = vec![0u8; unresolvable_target_end * 8];
3438
3439 bytes[0..8].copy_from_slice(b"DAF/SPK ");
3440 write_i32(byte_order, &mut bytes, 8, 2);
3441 write_i32(byte_order, &mut bytes, 12, 6);
3442 write_ascii(
3443 &mut bytes,
3444 16,
3445 DAF_INTERNAL_NAME_BYTES,
3446 "FRAME MISMATCH SPK",
3447 );
3448 write_i32(byte_order, &mut bytes, 76, 3);
3449 write_i32(byte_order, &mut bytes, 80, 3);
3450 write_i32(
3451 byte_order,
3452 &mut bytes,
3453 84,
3454 (unresolvable_target_end + 1) as i32,
3455 );
3456 bytes[DAF_BINARY_FORMAT_OFFSET..DAF_BINARY_FORMAT_OFFSET + 8].copy_from_slice(b"LTL-IEEE");
3457
3458 let summary_offset = DAF_RECORD_BYTES * 2;
3459 let name_offset = DAF_RECORD_BYTES * 3;
3460 write_f64(byte_order, &mut bytes, summary_offset, 0.0);
3461 write_f64(byte_order, &mut bytes, summary_offset + 8, 0.0);
3462 write_f64(byte_order, &mut bytes, summary_offset + 16, 6.0);
3463
3464 write_summary(
3465 byte_order,
3466 &mut bytes,
3467 summary_offset + 24,
3468 0.0,
3469 10.0,
3470 [
3471 20,
3472 3,
3473 1,
3474 3,
3475 good_center_start as i32,
3476 good_center_start as i32 + 11,
3477 ],
3478 );
3479 write_summary(
3480 byte_order,
3481 &mut bytes,
3482 summary_offset + 64,
3483 0.0,
3484 10.0,
3485 [
3486 700,
3487 20,
3488 1,
3489 3,
3490 good_target_start as i32,
3491 good_target_start as i32 + 11,
3492 ],
3493 );
3494 write_summary(
3495 byte_order,
3496 &mut bytes,
3497 summary_offset + 104,
3498 0.0,
3499 10.0,
3500 [
3501 10,
3502 3,
3503 2,
3504 3,
3505 bad_center_start as i32,
3506 bad_center_start as i32 + 11,
3507 ],
3508 );
3509 write_summary(
3510 byte_order,
3511 &mut bytes,
3512 summary_offset + 144,
3513 0.0,
3514 10.0,
3515 [
3516 700,
3517 10,
3518 1,
3519 3,
3520 bad_target_start as i32,
3521 bad_target_start as i32 + 11,
3522 ],
3523 );
3524 write_summary(
3525 byte_order,
3526 &mut bytes,
3527 summary_offset + 184,
3528 0.0,
3529 10.0,
3530 [
3531 30,
3532 3,
3533 2,
3534 3,
3535 unresolvable_center_start as i32,
3536 unresolvable_center_start as i32 + 11,
3537 ],
3538 );
3539 write_summary(
3540 byte_order,
3541 &mut bytes,
3542 summary_offset + 224,
3543 0.0,
3544 10.0,
3545 [
3546 701,
3547 30,
3548 1,
3549 3,
3550 unresolvable_target_start as i32,
3551 unresolvable_target_start as i32 + 11,
3552 ],
3553 );
3554
3555 write_ascii(&mut bytes, name_offset, 40, "GOOD CENTER 20 TO 3");
3556 write_ascii(&mut bytes, name_offset + 40, 40, "GOOD TARGET 700 TO 20");
3557 write_ascii(&mut bytes, name_offset + 80, 40, "BAD CENTER 10 TO 3");
3558 write_ascii(&mut bytes, name_offset + 120, 40, "BAD TARGET 700 TO 10");
3559 write_ascii(&mut bytes, name_offset + 160, 40, "BAD CENTER 30 TO 3");
3560 write_ascii(
3561 &mut bytes,
3562 name_offset + 200,
3563 40,
3564 "UNRESOLVABLE TARGET 701 TO 30",
3565 );
3566
3567 write_type3_constant_segment(
3568 byte_order,
3569 &mut bytes,
3570 good_center_start,
3571 [20.0, 3.0, 4.0],
3572 [2.0, 0.3, 0.4],
3573 );
3574 write_type3_constant_segment(
3575 byte_order,
3576 &mut bytes,
3577 good_target_start,
3578 [100.0, 0.0, 0.0],
3579 [10.0, 0.0, 0.0],
3580 );
3581 write_type3_constant_segment(
3582 byte_order,
3583 &mut bytes,
3584 bad_center_start,
3585 [900.0, 0.0, 0.0],
3586 [90.0, 0.0, 0.0],
3587 );
3588 write_type3_constant_segment(
3589 byte_order,
3590 &mut bytes,
3591 bad_target_start,
3592 [1.0, 0.0, 0.0],
3593 [0.1, 0.0, 0.0],
3594 );
3595 write_type3_constant_segment(
3596 byte_order,
3597 &mut bytes,
3598 unresolvable_center_start,
3599 [30.0, 0.0, 0.0],
3600 [3.0, 0.0, 0.0],
3601 );
3602 write_type3_constant_segment(
3603 byte_order,
3604 &mut bytes,
3605 unresolvable_target_start,
3606 [701.0, 0.0, 0.0],
3607 [70.1, 0.0, 0.0],
3608 );
3609
3610 bytes
3611 }
3612
3613 fn build_type2_segment(byte_order: DafByteOrder) -> (Vec<u8>, SpkSegmentDescriptor) {
3614 let rsize = 11usize;
3615 let n = 2usize;
3616 let end_address = rsize * n + 4;
3617 let mut bytes = vec![0u8; end_address * 8];
3618
3619 write_type2_record(
3620 byte_order,
3621 &mut bytes,
3622 1,
3623 5.0,
3624 5.0,
3625 [[1.0, 2.0, 3.0], [-4.0, 0.5, -1.0], [7.0, 0.0, 0.0]],
3626 );
3627 write_type2_record(
3628 byte_order,
3629 &mut bytes,
3630 12,
3631 15.0,
3632 5.0,
3633 [[10.0, -1.0, 0.0], [20.0, 2.0, 1.0], [-3.0, 4.0, -2.0]],
3634 );
3635 write_f64_address(byte_order, &mut bytes, end_address - 3, 0.0);
3636 write_f64_address(byte_order, &mut bytes, end_address - 2, 10.0);
3637 write_f64_address(byte_order, &mut bytes, end_address - 1, rsize as f64);
3638 write_f64_address(byte_order, &mut bytes, end_address, n as f64);
3639
3640 (
3641 bytes,
3642 SpkSegmentDescriptor {
3643 name: "TYPE 2 TEST".to_string(),
3644 start_et: 0.0,
3645 stop_et: 20.0,
3646 target: 301,
3647 center: 3,
3648 frame: 1,
3649 data_type: 2,
3650 start_address: 1,
3651 end_address: end_address as i32,
3652 },
3653 )
3654 }
3655
3656 fn build_type3_segment(byte_order: DafByteOrder) -> (Vec<u8>, SpkSegmentDescriptor) {
3657 let rsize = 14usize;
3658 let n = 2usize;
3659 let end_address = rsize * n + 4;
3660 let mut bytes = vec![0u8; end_address * 8];
3661
3662 write_type3_record(
3663 byte_order,
3664 &mut bytes,
3665 1,
3666 5.0,
3667 5.0,
3668 [
3669 [1.0, 2.0],
3670 [3.0, -4.0],
3671 [5.0, 0.5],
3672 [0.1, 0.2],
3673 [-0.3, 0.0],
3674 [1.0, -1.0],
3675 ],
3676 );
3677 write_type3_record(
3678 byte_order,
3679 &mut bytes,
3680 15,
3681 15.0,
3682 5.0,
3683 [
3684 [10.0, 1.0],
3685 [20.0, -2.0],
3686 [-5.0, 3.0],
3687 [1.0, 1.0],
3688 [2.0, -3.0],
3689 [4.0, 0.25],
3690 ],
3691 );
3692 write_f64_address(byte_order, &mut bytes, end_address - 3, 0.0);
3693 write_f64_address(byte_order, &mut bytes, end_address - 2, 10.0);
3694 write_f64_address(byte_order, &mut bytes, end_address - 1, rsize as f64);
3695 write_f64_address(byte_order, &mut bytes, end_address, n as f64);
3696
3697 (
3698 bytes,
3699 SpkSegmentDescriptor {
3700 name: "TYPE 3 TEST".to_string(),
3701 start_et: 0.0,
3702 stop_et: 20.0,
3703 target: 499,
3704 center: 4,
3705 frame: 1,
3706 data_type: 3,
3707 start_address: 1,
3708 end_address: end_address as i32,
3709 },
3710 )
3711 }
3712
3713 fn build_type21_segment(byte_order: DafByteOrder) -> (Vec<u8>, SpkSegmentDescriptor) {
3714 let maxdim = 3usize;
3715 let n = 2usize;
3716 let dlsize = 4 * maxdim + 11;
3717 let end_address = n * dlsize + n + 2;
3720 let mut bytes = vec![0u8; end_address * 8];
3721
3722 write_type21_record(
3724 byte_order,
3725 &mut bytes,
3726 1,
3727 maxdim,
3728 0.0,
3729 [100.0, 200.0, 300.0],
3730 [1.0, 2.0, 3.0],
3731 );
3732 write_type21_record(
3734 byte_order,
3735 &mut bytes,
3736 1 + dlsize,
3737 maxdim,
3738 10.0,
3739 [1000.0, 2000.0, 3000.0],
3740 [10.0, 20.0, 30.0],
3741 );
3742
3743 let first_epoch = n * dlsize + 1;
3745 write_f64_address(byte_order, &mut bytes, first_epoch, 10.0);
3746 write_f64_address(byte_order, &mut bytes, first_epoch + 1, 20.0);
3747
3748 write_f64_address(byte_order, &mut bytes, end_address - 1, maxdim as f64);
3750 write_f64_address(byte_order, &mut bytes, end_address, n as f64);
3751
3752 (
3753 bytes,
3754 SpkSegmentDescriptor {
3755 name: "TYPE 21 TEST".to_string(),
3756 start_et: 0.0,
3757 stop_et: 20.0,
3758 target: 2000001,
3759 center: 10,
3760 frame: 1,
3761 data_type: 21,
3762 start_address: 1,
3763 end_address: end_address as i32,
3764 },
3765 )
3766 }
3767
3768 fn write_type21_record(
3771 byte_order: DafByteOrder,
3772 bytes: &mut [u8],
3773 start_address: usize,
3774 maxdim: usize,
3775 tl: f64,
3776 refpos: [f64; 3],
3777 refvel: [f64; 3],
3778 ) {
3779 write_f64_address(byte_order, bytes, start_address, tl);
3782 for offset in 0..maxdim {
3783 write_f64_address(byte_order, bytes, start_address + 1 + offset, 1.0);
3785 }
3786 write_f64_address(byte_order, bytes, start_address + maxdim + 1, refpos[0]);
3788 write_f64_address(byte_order, bytes, start_address + maxdim + 2, refvel[0]);
3789 write_f64_address(byte_order, bytes, start_address + maxdim + 3, refpos[1]);
3790 write_f64_address(byte_order, bytes, start_address + maxdim + 4, refvel[1]);
3791 write_f64_address(byte_order, bytes, start_address + maxdim + 5, refpos[2]);
3792 write_f64_address(byte_order, bytes, start_address + maxdim + 6, refvel[2]);
3793 write_f64_address(byte_order, bytes, start_address + 4 * maxdim + 7, 2.0);
3796 write_f64_address(byte_order, bytes, start_address + 4 * maxdim + 8, 1.0);
3797 write_f64_address(byte_order, bytes, start_address + 4 * maxdim + 9, 1.0);
3798 write_f64_address(byte_order, bytes, start_address + 4 * maxdim + 10, 1.0);
3799 }
3800
3801 fn write_type3_constant_segment(
3802 byte_order: DafByteOrder,
3803 bytes: &mut [u8],
3804 start_address: usize,
3805 position_km: [f64; 3],
3806 velocity_km_s: [f64; 3],
3807 ) {
3808 let rsize = 8usize;
3809 let end_address = start_address + rsize + 3;
3810 write_f64_address(byte_order, bytes, start_address, 5.0);
3811 write_f64_address(byte_order, bytes, start_address + 1, 5.0);
3812 write_f64_address(byte_order, bytes, start_address + 2, position_km[0]);
3813 write_f64_address(byte_order, bytes, start_address + 3, position_km[1]);
3814 write_f64_address(byte_order, bytes, start_address + 4, position_km[2]);
3815 write_f64_address(byte_order, bytes, start_address + 5, velocity_km_s[0]);
3816 write_f64_address(byte_order, bytes, start_address + 6, velocity_km_s[1]);
3817 write_f64_address(byte_order, bytes, start_address + 7, velocity_km_s[2]);
3818 write_f64_address(byte_order, bytes, end_address - 3, 0.0);
3819 write_f64_address(byte_order, bytes, end_address - 2, 10.0);
3820 write_f64_address(byte_order, bytes, end_address - 1, rsize as f64);
3821 write_f64_address(byte_order, bytes, end_address, 1.0);
3822 }
3823
3824 fn write_type2_constant_segment(
3825 byte_order: DafByteOrder,
3826 bytes: &mut [u8],
3827 start_address: usize,
3828 position_km: [f64; 3],
3829 ) {
3830 let rsize = 5usize;
3831 let end_address = start_address + rsize + 3;
3832 write_f64_address(byte_order, bytes, start_address, 5.0);
3833 write_f64_address(byte_order, bytes, start_address + 1, 5.0);
3834 write_f64_address(byte_order, bytes, start_address + 2, position_km[0]);
3835 write_f64_address(byte_order, bytes, start_address + 3, position_km[1]);
3836 write_f64_address(byte_order, bytes, start_address + 4, position_km[2]);
3837 write_f64_address(byte_order, bytes, end_address - 3, 0.0);
3838 write_f64_address(byte_order, bytes, end_address - 2, 10.0);
3839 write_f64_address(byte_order, bytes, end_address - 1, rsize as f64);
3840 write_f64_address(byte_order, bytes, end_address, 1.0);
3841 }
3842
3843 fn write_type3_record(
3844 byte_order: DafByteOrder,
3845 bytes: &mut [u8],
3846 start_address: usize,
3847 mid: f64,
3848 radius: f64,
3849 coeffs: [[f64; 2]; 6],
3850 ) {
3851 write_f64_address(byte_order, bytes, start_address, mid);
3852 write_f64_address(byte_order, bytes, start_address + 1, radius);
3853 let mut address = start_address + 2;
3854 for component in coeffs {
3855 for coeff in component {
3856 write_f64_address(byte_order, bytes, address, coeff);
3857 address += 1;
3858 }
3859 }
3860 }
3861
3862 fn write_type2_record(
3863 byte_order: DafByteOrder,
3864 bytes: &mut [u8],
3865 start_address: usize,
3866 mid: f64,
3867 radius: f64,
3868 coeffs: [[f64; 3]; 3],
3869 ) {
3870 write_f64_address(byte_order, bytes, start_address, mid);
3871 write_f64_address(byte_order, bytes, start_address + 1, radius);
3872 let mut address = start_address + 2;
3873 for component in coeffs {
3874 for coeff in component {
3875 write_f64_address(byte_order, bytes, address, coeff);
3876 address += 1;
3877 }
3878 }
3879 }
3880
3881 fn write_f64_address(byte_order: DafByteOrder, bytes: &mut [u8], address: usize, value: f64) {
3882 write_f64(byte_order, bytes, (address - 1) * 8, value);
3883 }
3884
3885 fn assert_position_close(actual: [f64; 3], expected: [f64; 3]) {
3886 for axis in 0..3 {
3887 assert!(
3888 (actual[axis] - expected[axis]).abs() < 1e-12,
3889 "axis {axis}: actual {:?}, expected {:?}",
3890 actual,
3891 expected
3892 );
3893 }
3894 }
3895
3896 fn assert_state_close(
3897 actual: SpkStateVector,
3898 expected_position: [f64; 3],
3899 expected_velocity: [f64; 3],
3900 ) {
3901 assert_position_close(actual.position_km, expected_position);
3902 assert_position_close(actual.velocity_km_s, expected_velocity);
3903 }
3904
3905 fn assert_query_state_close(
3906 actual: SpkState,
3907 expected_position: [f64; 3],
3908 expected_velocity: Option<[f64; 3]>,
3909 expected_frame: i32,
3910 ) {
3911 assert_position_close(actual.position_km, expected_position);
3912 match (actual.velocity_km_s, expected_velocity) {
3913 (Some(actual), Some(expected)) => assert_position_close(actual, expected),
3914 (None, None) => {}
3915 _ => panic!(
3916 "velocity mismatch: actual {:?}, expected {:?}",
3917 actual.velocity_km_s, expected_velocity
3918 ),
3919 }
3920 assert_eq!(actual.frame, expected_frame);
3921 }
3922
3923 fn write_summary(
3924 byte_order: DafByteOrder,
3925 bytes: &mut [u8],
3926 offset: usize,
3927 start_et: f64,
3928 stop_et: f64,
3929 ints: [i32; 6],
3930 ) {
3931 write_f64(byte_order, bytes, offset, start_et);
3932 write_f64(byte_order, bytes, offset + 8, stop_et);
3933 for (index, value) in ints.into_iter().enumerate() {
3934 write_i32(byte_order, bytes, offset + 16 + index * 4, value);
3935 }
3936 }
3937
3938 fn write_ascii(bytes: &mut [u8], offset: usize, len: usize, text: &str) {
3939 bytes[offset..offset + len].fill(b' ');
3940 bytes[offset..offset + text.len()].copy_from_slice(text.as_bytes());
3941 }
3942
3943 fn write_i32(byte_order: DafByteOrder, bytes: &mut [u8], offset: usize, value: i32) {
3944 let word = match byte_order {
3945 DafByteOrder::LittleEndian => value.to_le_bytes(),
3946 DafByteOrder::BigEndian => value.to_be_bytes(),
3947 };
3948 bytes[offset..offset + 4].copy_from_slice(&word);
3949 }
3950
3951 fn write_f64(byte_order: DafByteOrder, bytes: &mut [u8], offset: usize, value: f64) {
3952 let word = match byte_order {
3953 DafByteOrder::LittleEndian => value.to_le_bytes(),
3954 DafByteOrder::BigEndian => value.to_be_bytes(),
3955 };
3956 bytes[offset..offset + 8].copy_from_slice(&word);
3957 }
3958}