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