use super::error::SpiceError;
use crate::formats::spice::daf::{Daf, Summary};
#[derive(Debug, Clone)]
pub enum SpkSegment {
Type2(ChebSegment),
Type3(ChebSegment),
Type9(DiscreteStatesSegment),
Type13(DiscreteStatesSegment),
}
impl SpkSegment {
pub fn start_tdb_seconds(&self) -> f64 {
match self {
SpkSegment::Type2(c) | SpkSegment::Type3(c) => c.init,
SpkSegment::Type9(s) | SpkSegment::Type13(s) => s.start_tdb_seconds(),
}
}
pub fn end_tdb_seconds(&self) -> f64 {
match self {
SpkSegment::Type2(c) | SpkSegment::Type3(c) => c.init + c.intlen * c.n_records as f64,
SpkSegment::Type9(s) | SpkSegment::Type13(s) => s.end_tdb_seconds(),
}
}
pub fn cheb(&self) -> &ChebSegment {
match self {
SpkSegment::Type2(c) | SpkSegment::Type3(c) => c,
SpkSegment::Type9(_) | SpkSegment::Type13(_) => {
panic!("SPK segment is not a Chebyshev Type 2/3 segment")
}
}
}
pub fn evaluate(&self, et: f64) -> Result<[f64; 6], SpiceError> {
match self {
SpkSegment::Type2(c) => c.evaluate_type2(et),
SpkSegment::Type3(c) => c.evaluate_type3(et),
SpkSegment::Type9(s) => s.evaluate_lagrange(et),
SpkSegment::Type13(s) => s.evaluate_hermite(et),
}
}
}
#[derive(Debug, Clone)]
pub struct ChebSegment {
pub init: f64,
pub intlen: f64,
pub rsize: usize,
pub ncoeff: usize,
pub n_records: usize,
pub components: u8,
pub records: Vec<f64>,
}
impl ChebSegment {
fn locate(&self, et: f64) -> Result<(usize, f64, f64), SpiceError> {
if !et.is_finite() {
return Err(SpiceError::Corrupted {
message: format!("epoch is not finite: {et}"),
});
}
let end = self.init + self.intlen * self.n_records as f64;
if et < self.init || et > end {
return Err(SpiceError::OutOfCoverage {
target: 0,
center: 0,
epoch_tdb_seconds: et,
start_tdb_seconds: self.init,
end_tdb_seconds: end,
});
}
let mut idx = ((et - self.init) / self.intlen).floor() as i64;
if idx < 0 {
idx = 0;
}
if idx as usize >= self.n_records {
idx = self.n_records as i64 - 1;
}
let off = (idx as usize) * self.rsize;
let mid = self.records[off];
let radius = self.records[off + 1];
if !radius.is_finite() || radius <= 0.0 {
return Err(SpiceError::Corrupted {
message: format!("record {idx} has non-positive radius {radius} (mid={mid})"),
});
}
Ok((idx as usize, mid, radius))
}
pub fn evaluate_type2(&self, et: f64) -> Result<[f64; 6], SpiceError> {
debug_assert_eq!(self.components, 3);
let (idx, mid, radius) = self.locate(et)?;
let off = idx * self.rsize + 2;
let tau = (et - mid) / radius;
let n = self.ncoeff;
let mut out = [0.0_f64; 6];
for i in 0..3 {
let coeffs = &self.records[off + i * n..off + (i + 1) * n];
let (p, dp) = cheby::core::evaluate_both(coeffs, tau);
out[i] = p;
out[i + 3] = dp / radius;
}
Ok(out)
}
pub fn evaluate_type3(&self, et: f64) -> Result<[f64; 6], SpiceError> {
debug_assert_eq!(self.components, 6);
let (idx, mid, radius) = self.locate(et)?;
let off = idx * self.rsize + 2;
let tau = (et - mid) / radius;
let n = self.ncoeff;
let mut out = [0.0_f64; 6];
for (i, slot) in out.iter_mut().enumerate() {
let coeffs = &self.records[off + i * n..off + (i + 1) * n];
*slot = cheby::core::evaluate(coeffs, tau);
}
Ok(out)
}
}
#[derive(Debug, Clone)]
pub struct DiscreteStatesSegment {
pub data_type: i32,
pub interpolation_size: usize,
pub n_records: usize,
pub states: Vec<f64>,
pub epochs: Vec<f64>,
pub epoch_directory: Vec<f64>,
}
impl DiscreteStatesSegment {
pub fn start_tdb_seconds(&self) -> f64 {
self.epochs.first().copied().unwrap_or(f64::NAN)
}
pub fn end_tdb_seconds(&self) -> f64 {
self.epochs.last().copied().unwrap_or(f64::NAN)
}
fn state_record(&self, idx: usize) -> Result<[f64; 6], SpiceError> {
let start = idx.checked_mul(6).ok_or_else(|| SpiceError::Parse {
message: format!("state index overflow for record {idx}"),
})?;
let slice = self
.states
.get(start..start + 6)
.ok_or_else(|| SpiceError::Parse {
message: format!("state record {idx} outside Type {} data", self.data_type),
})?;
Ok([slice[0], slice[1], slice[2], slice[3], slice[4], slice[5]])
}
fn locate_or_window(&self, et: f64, count: usize) -> Result<WindowSelection, SpiceError> {
if !et.is_finite() {
return Err(SpiceError::Corrupted {
message: format!("epoch is not finite: {et}"),
});
}
let start = self.start_tdb_seconds();
let end = self.end_tdb_seconds();
if self.n_records == 0 || et < start || et > end {
return Err(SpiceError::OutOfCoverage {
target: 0,
center: 0,
epoch_tdb_seconds: et,
start_tdb_seconds: start,
end_tdb_seconds: end,
});
}
match self
.epochs
.binary_search_by(|epoch| epoch.partial_cmp(&et).unwrap_or(core::cmp::Ordering::Less))
{
Ok(idx) => Ok(WindowSelection::Exact(idx)),
Err(insertion) => {
if count == 0 || count > self.n_records {
return Err(SpiceError::Parse {
message: format!(
"Type {} interpolation window {count} invalid for {} records",
self.data_type, self.n_records
),
});
}
let left = count / 2;
let mut first = insertion.saturating_sub(left);
if first + count > self.n_records {
first = self.n_records - count;
}
Ok(WindowSelection::Window { first, count })
}
}
}
fn evaluate_lagrange(&self, et: f64) -> Result<[f64; 6], SpiceError> {
let count = self.interpolation_size + 1;
match self.locate_or_window(et, count)? {
WindowSelection::Exact(idx) => self.state_record(idx),
WindowSelection::Window { first, count } => {
let epochs = &self.epochs[first..first + count];
let mut out = [0.0_f64; 6];
for (component, output) in out.iter_mut().enumerate() {
let mut values = Vec::with_capacity(count);
for idx in first..first + count {
values.push(self.states[idx * 6 + component]);
}
*output = lagrange_value(epochs, &values, et)?;
}
Ok(out)
}
}
}
fn evaluate_hermite(&self, et: f64) -> Result<[f64; 6], SpiceError> {
let count = self.interpolation_size;
match self.locate_or_window(et, count)? {
WindowSelection::Exact(idx) => self.state_record(idx),
WindowSelection::Window { first, count } => {
let epochs = &self.epochs[first..first + count];
let mut out = [0.0_f64; 6];
for axis in 0..3 {
let mut positions = Vec::with_capacity(count);
let mut velocities = Vec::with_capacity(count);
for idx in first..first + count {
positions.push(self.states[idx * 6 + axis]);
velocities.push(self.states[idx * 6 + axis + 3]);
}
let (position, velocity) =
hermite_value_and_derivative(epochs, &positions, &velocities, et)?;
out[axis] = position;
out[axis + 3] = velocity;
}
Ok(out)
}
}
}
}
enum WindowSelection {
Exact(usize),
Window { first: usize, count: usize },
}
pub fn segment_for_summary(
file_data: &[u8],
daf: &Daf,
summary: &Summary,
) -> Result<SpkSegment, SpiceError> {
match summary.data_type {
2 => Ok(SpkSegment::Type2(read_chebyshev(
file_data, daf, summary, 3,
)?)),
3 => Ok(SpkSegment::Type3(read_chebyshev(
file_data, daf, summary, 6,
)?)),
9 => Ok(SpkSegment::Type9(read_discrete_states(
file_data, daf, summary, 9,
)?)),
13 => Ok(SpkSegment::Type13(read_discrete_states(
file_data, daf, summary, 13,
)?)),
other => Err(SpiceError::UnsupportedDataType { data_type: other }),
}
}
fn read_chebyshev(
file_data: &[u8],
daf: &Daf,
summary: &Summary,
components: u8,
) -> Result<ChebSegment, SpiceError> {
let end = summary.end_word;
if end < 4 {
return Err(SpiceError::Parse {
message: format!("segment trailer at end_word={end} is too small"),
});
}
let n_records = daf.read_f64_at_word(file_data, end) as usize;
let rsize = daf.read_f64_at_word(file_data, end - 1) as usize;
let intlen = daf.read_f64_at_word(file_data, end - 2);
let init = daf.read_f64_at_word(file_data, end - 3);
if !(5..=4096).contains(&rsize) {
return Err(SpiceError::Parse {
message: format!("implausible rsize={rsize} for SPK Chebyshev segment"),
});
}
let comp = components as usize;
if !(rsize - 2).is_multiple_of(comp) {
return Err(SpiceError::Parse {
message: format!("rsize={rsize} is not 2 + {comp}*k for any k (components={comp})"),
});
}
let ncoeff = (rsize - 2) / comp;
if n_records == 0 || n_records > 10_000_000 {
return Err(SpiceError::Parse {
message: format!("implausible n_records={n_records}"),
});
}
if !(intlen.is_finite() && intlen > 0.0) {
return Err(SpiceError::Parse {
message: format!("non-positive intlen={intlen}"),
});
}
if !init.is_finite() {
return Err(SpiceError::Parse {
message: format!("non-finite init={init}"),
});
}
let total = n_records
.checked_mul(rsize)
.ok_or_else(|| SpiceError::Parse {
message: format!("segment record count overflow: n_records={n_records} rsize={rsize}"),
})?;
let data_start_word = summary.start_word;
if data_start_word + total - 1 > summary.end_word {
return Err(SpiceError::Parse {
message: format!(
"segment data window [{}..{}] does not fit in summary [{}..{}]",
data_start_word,
data_start_word + total - 1,
summary.start_word,
summary.end_word
),
});
}
let mut records = Vec::with_capacity(total);
for i in 0..total {
records.push(daf.read_f64_at_word(file_data, data_start_word + i));
}
Ok(ChebSegment {
init,
intlen,
rsize,
ncoeff,
n_records,
components,
records,
})
}
fn read_discrete_states(
file_data: &[u8],
daf: &Daf,
summary: &Summary,
data_type: i32,
) -> Result<DiscreteStatesSegment, SpiceError> {
let n_words = summary
.end_word
.checked_sub(summary.start_word)
.and_then(|delta| delta.checked_add(1))
.ok_or_else(|| SpiceError::Parse {
message: format!(
"invalid SPK Type {data_type} word range [{}..{}]",
summary.start_word, summary.end_word
),
})?;
if n_words < 9 {
return Err(SpiceError::Parse {
message: format!("SPK Type {data_type} segment too short: {n_words} words"),
});
}
let mut words = Vec::with_capacity(n_words);
for i in 0..n_words {
words.push(daf.read_f64_at_word(file_data, summary.start_word + i));
}
let n_records_f = words[n_words - 1];
let descriptor_f = words[n_words - 2];
if !n_records_f.is_finite() || n_records_f < 1.0 {
return Err(SpiceError::Parse {
message: format!("SPK Type {data_type} invalid record count {n_records_f}"),
});
}
if !descriptor_f.is_finite() || descriptor_f < 0.0 {
return Err(SpiceError::Parse {
message: format!(
"SPK Type {data_type} invalid interpolation descriptor {descriptor_f}"
),
});
}
let n_records = n_records_f as usize;
let descriptor = descriptor_f as usize;
let state_len = n_records.checked_mul(6).ok_or_else(|| SpiceError::Parse {
message: format!("SPK Type {data_type} state count overflow: {n_records}"),
})?;
let epoch_start = state_len;
let epoch_end = epoch_start + n_records;
if epoch_end + 2 > words.len() {
return Err(SpiceError::Parse {
message: format!("SPK Type {data_type} payload too short for {n_records} records"),
});
}
let states = words[..state_len].to_vec();
let epochs = words[epoch_start..epoch_end].to_vec();
let epoch_directory = words[epoch_end..words.len() - 2].to_vec();
if states
.iter()
.chain(epochs.iter())
.chain(epoch_directory.iter())
.any(|v| !v.is_finite())
{
return Err(SpiceError::Corrupted {
message: format!("SPK Type {data_type} contains non-finite state or epoch data"),
});
}
if !epochs.windows(2).all(|w| w[0] < w[1]) {
return Err(SpiceError::Parse {
message: format!("SPK Type {data_type} epochs are not strictly increasing"),
});
}
let interpolation_size = match data_type {
9 => {
let degree = descriptor;
if degree == 0 || degree + 1 > n_records || degree > 31 {
return Err(SpiceError::Parse {
message: format!(
"SPK Type 9 invalid Lagrange degree {degree} for {n_records} records"
),
});
}
degree
}
13 => {
let samples = descriptor + 1;
if samples < 2 || samples > n_records || samples > 16 {
return Err(SpiceError::Parse {
message: format!(
"SPK Type 13 invalid Hermite sample count {samples} for {n_records} records"
),
});
}
samples
}
_ => unreachable!(),
};
Ok(DiscreteStatesSegment {
data_type,
interpolation_size,
n_records,
states,
epochs,
epoch_directory,
})
}
fn lagrange_value(xs: &[f64], ys: &[f64], x: f64) -> Result<f64, SpiceError> {
if xs.len() != ys.len() || xs.is_empty() {
return Err(SpiceError::Parse {
message: "Lagrange interpolation requires matching non-empty inputs".into(),
});
}
let mut value = 0.0_f64;
for i in 0..xs.len() {
let mut basis = 1.0_f64;
for j in 0..xs.len() {
if i == j {
continue;
}
let denom = xs[i] - xs[j];
if denom.abs() <= f64::EPSILON {
return Err(SpiceError::Corrupted {
message: "duplicate epochs in Lagrange interpolation window".into(),
});
}
basis *= (x - xs[j]) / denom;
}
value += ys[i] * basis;
}
Ok(value)
}
fn hermite_value_and_derivative(
xs: &[f64],
ys: &[f64],
dydx: &[f64],
x: f64,
) -> Result<(f64, f64), SpiceError> {
if xs.len() != ys.len() || xs.len() != dydx.len() || xs.is_empty() {
return Err(SpiceError::Parse {
message: "Hermite interpolation requires matching non-empty inputs".into(),
});
}
let n = xs.len();
let order = 2 * n;
let mut z = vec![0.0_f64; order];
let mut table = vec![vec![0.0_f64; order]; order];
for i in 0..n {
let even = 2 * i;
let odd = even + 1;
z[even] = xs[i];
z[odd] = xs[i];
table[even][0] = ys[i];
table[odd][0] = ys[i];
table[odd][1] = dydx[i];
if i == 0 {
table[even][1] = dydx[i];
} else {
let denom = z[even] - z[even - 1];
if denom.abs() <= f64::EPSILON {
return Err(SpiceError::Corrupted {
message: "duplicate epochs in Hermite interpolation window".into(),
});
}
table[even][1] = (table[even][0] - table[even - 1][0]) / denom;
}
}
for i in 2..order {
for j in 2..=i {
let denom = z[i] - z[i - j];
if denom.abs() <= f64::EPSILON {
return Err(SpiceError::Corrupted {
message: "duplicate epochs in Hermite divided differences".into(),
});
}
table[i][j] = (table[i][j - 1] - table[i - 1][j - 1]) / denom;
}
}
let coeffs: Vec<f64> = (0..order).map(|i| table[i][i]).collect();
let mut value = coeffs[order - 1];
let mut derivative = 0.0_f64;
for i in (0..order - 1).rev() {
derivative = derivative * (x - z[i]) + value;
value = value * (x - z[i]) + coeffs[i];
}
Ok((value, derivative))
}
#[cfg(test)]
mod tests {
use super::*;
fn build_const_type2_segment(x: f64, y: f64, z: f64) -> (Vec<u8>, Daf, Summary) {
let ncoeff = 1;
let rsize = 2 + 3 * ncoeff;
let n_records = 1;
let n_words = rsize * n_records + 4; let mut buf = vec![0u8; n_words * 8];
let write = |word: usize, v: f64, b: &mut [u8]| {
b[(word - 1) * 8..word * 8].copy_from_slice(&v.to_le_bytes());
};
write(1, 0.5, &mut buf); write(2, 0.5, &mut buf); write(3, x, &mut buf);
write(4, y, &mut buf);
write(5, z, &mut buf);
write(6, 0.0, &mut buf); write(7, 1.0, &mut buf); write(8, rsize as f64, &mut buf);
write(9, n_records as f64, &mut buf);
let daf = Daf {
nd: 2,
ni: 6,
summaries: vec![],
};
let summary = Summary {
start_et: 0.0,
end_et: 1.0,
target_id: 399,
center_id: 0,
frame_id: 1,
data_type: 2,
start_word: 1,
end_word: 9,
};
(buf, daf, summary)
}
#[test]
fn type2_constant_polynomial_recovers_position() {
let (buf, daf, summary) = build_const_type2_segment(1.0, 2.0, 3.0);
let seg = segment_for_summary(&buf, &daf, &summary).unwrap();
let state = seg.evaluate(0.5).unwrap();
assert_eq!(state[0], 1.0);
assert_eq!(state[1], 2.0);
assert_eq!(state[2], 3.0);
assert_eq!(state[3], 0.0);
assert_eq!(state[4], 0.0);
assert_eq!(state[5], 0.0);
}
#[test]
fn type2_linear_polynomial_recovers_velocity() {
let ncoeff = 2;
let rsize = 2 + 3 * ncoeff; let n_records = 1;
let n_words = rsize * n_records + 4; let mut buf = vec![0u8; n_words * 8];
let write = |word: usize, v: f64, b: &mut [u8]| {
b[(word - 1) * 8..word * 8].copy_from_slice(&v.to_le_bytes());
};
let radius = 2.0_f64;
write(1, 1.0, &mut buf); write(2, radius, &mut buf); write(3, 10.0, &mut buf);
write(4, 4.0, &mut buf);
write(5, 0.0, &mut buf);
write(6, 0.0, &mut buf);
write(7, -1.0, &mut buf);
write(8, 5.0, &mut buf);
write(9, 0.0, &mut buf); write(10, 4.0, &mut buf); write(11, rsize as f64, &mut buf);
write(12, n_records as f64, &mut buf);
let daf = Daf {
nd: 2,
ni: 6,
summaries: vec![],
};
let summary = Summary {
start_et: 0.0,
end_et: 4.0,
target_id: 399,
center_id: 0,
frame_id: 1,
data_type: 2,
start_word: 1,
end_word: 12,
};
let seg = segment_for_summary(&buf, &daf, &summary).unwrap();
let s = seg.evaluate(1.0).unwrap();
assert!((s[0] - 10.0).abs() < 1e-12);
assert!((s[1]).abs() < 1e-12);
assert!((s[2] + 1.0).abs() < 1e-12);
assert!((s[3] - 4.0 / radius).abs() < 1e-12);
assert!((s[4]).abs() < 1e-12);
assert!((s[5] - 5.0 / radius).abs() < 1e-12);
}
#[test]
fn type3_recovers_independent_velocity_polynomial() {
let ncoeff = 1;
let rsize = 2 + 6 * ncoeff; let n_records = 1;
let n_words = rsize * n_records + 4;
let mut buf = vec![0u8; n_words * 8];
let write = |word: usize, v: f64, b: &mut [u8]| {
b[(word - 1) * 8..word * 8].copy_from_slice(&v.to_le_bytes());
};
write(1, 0.5, &mut buf); write(2, 0.5, &mut buf); write(3, 10.0, &mut buf);
write(4, 20.0, &mut buf);
write(5, 30.0, &mut buf);
write(6, 1.0, &mut buf);
write(7, 2.0, &mut buf);
write(8, 3.0, &mut buf);
write(9, 0.0, &mut buf); write(10, 1.0, &mut buf); write(11, rsize as f64, &mut buf);
write(12, n_records as f64, &mut buf);
let daf = Daf {
nd: 2,
ni: 6,
summaries: vec![],
};
let summary = Summary {
start_et: 0.0,
end_et: 1.0,
target_id: 399,
center_id: 0,
frame_id: 1,
data_type: 3,
start_word: 1,
end_word: 12,
};
let seg = segment_for_summary(&buf, &daf, &summary).unwrap();
let s = seg.evaluate(0.5).unwrap();
assert_eq!(s, [10.0, 20.0, 30.0, 1.0, 2.0, 3.0]);
}
#[test]
fn out_of_coverage_returned_for_epoch_outside_range() {
let (buf, daf, summary) = build_const_type2_segment(1.0, 2.0, 3.0);
let seg = segment_for_summary(&buf, &daf, &summary).unwrap();
let err = seg.evaluate(2.0).unwrap_err();
assert!(matches!(err, SpiceError::OutOfCoverage { .. }));
}
#[test]
fn unsupported_data_type_rejected() {
let (buf, daf, mut summary) = build_const_type2_segment(1.0, 2.0, 3.0);
summary.data_type = 21;
let err = segment_for_summary(&buf, &daf, &summary).unwrap_err();
assert!(matches!(
err,
SpiceError::UnsupportedDataType { data_type: 21 }
));
}
fn build_discrete_segment(data_type: i32, descriptor: f64) -> (Vec<u8>, Daf, Summary) {
let n_records = 4usize;
let n_words = n_records * 6 + n_records + 2;
let mut buf = vec![0u8; n_words * 8];
let write = |word: usize, v: f64, b: &mut [u8]| {
b[(word - 1) * 8..word * 8].copy_from_slice(&v.to_le_bytes());
};
for i in 0..n_records {
let t = i as f64;
let base = i * 6 + 1;
write(base, t, &mut buf);
write(base + 1, 2.0 * t, &mut buf);
write(base + 2, 3.0 * t, &mut buf);
write(base + 3, 1.0, &mut buf);
write(base + 4, 2.0, &mut buf);
write(base + 5, 3.0, &mut buf);
}
let epoch_start = n_records * 6 + 1;
for i in 0..n_records {
write(epoch_start + i, i as f64, &mut buf);
}
write(n_words - 1, descriptor, &mut buf);
write(n_words, n_records as f64, &mut buf);
let daf = Daf {
nd: 2,
ni: 6,
summaries: vec![],
};
let summary = Summary {
start_et: 0.0,
end_et: 3.0,
target_id: 399,
center_id: 0,
frame_id: 1,
data_type,
start_word: 1,
end_word: n_words,
};
(buf, daf, summary)
}
#[test]
fn type9_lagrange_interpolates_state_records() {
let (buf, daf, summary) = build_discrete_segment(9, 1.0);
let seg = segment_for_summary(&buf, &daf, &summary).unwrap();
let s = seg.evaluate(1.5).unwrap();
assert!((s[0] - 1.5).abs() < 1e-12);
assert!((s[1] - 3.0).abs() < 1e-12);
assert!((s[2] - 4.5).abs() < 1e-12);
assert!((s[3] - 1.0).abs() < 1e-12);
assert!((s[4] - 2.0).abs() < 1e-12);
assert!((s[5] - 3.0).abs() < 1e-12);
}
#[test]
fn type13_hermite_interpolates_position_and_velocity() {
let (buf, daf, summary) = build_discrete_segment(13, 1.0);
let seg = segment_for_summary(&buf, &daf, &summary).unwrap();
let s = seg.evaluate(1.5).unwrap();
assert!((s[0] - 1.5).abs() < 1e-12);
assert!((s[1] - 3.0).abs() < 1e-12);
assert!((s[2] - 4.5).abs() < 1e-12);
assert!((s[3] - 1.0).abs() < 1e-12);
assert!((s[4] - 2.0).abs() < 1e-12);
assert!((s[5] - 3.0).abs() < 1e-12);
}
#[test]
fn rsize_not_divisible_by_components_rejected() {
let n_words = 10;
let mut buf = vec![0u8; n_words * 8];
let write = |word: usize, v: f64, b: &mut [u8]| {
b[(word - 1) * 8..word * 8].copy_from_slice(&v.to_le_bytes());
};
write(7, 0.0, &mut buf); write(8, 1.0, &mut buf); write(9, 6.0, &mut buf); write(10, 1.0, &mut buf); let daf = Daf {
nd: 2,
ni: 6,
summaries: vec![],
};
let summary = Summary {
start_et: 0.0,
end_et: 1.0,
target_id: 0,
center_id: 0,
frame_id: 1,
data_type: 2,
start_word: 1,
end_word: 10,
};
let err = segment_for_summary(&buf, &daf, &summary).unwrap_err();
assert!(matches!(err, SpiceError::Parse { .. }));
}
}