use crate::{
DecodeError, Grib2GpvUnpack,
decoder::{
Grib2SubmessageDecoder,
simple::*,
stream::{FixedValueIterator, NBitwiseIterator},
},
};
pub(crate) struct Ccsds<'d>(
pub(crate) &'d Grib2SubmessageDecoder,
pub(crate) &'d crate::def::grib2::template::Template5_42,
);
impl<'d> Grib2GpvUnpack for Ccsds<'d> {
type Iter<'a>
= SimplePackingDecoder<NBitwiseIterator<Vec<u8>>>
where
Self: 'a;
fn iter<'a>(&'a self) -> Result<Self::Iter<'a>, DecodeError> {
let Self(target, template) = self;
super::orig_field_type_is_supported(template.orig_field_type)?;
let decoder = if template.simple.num_bits == 0 {
SimplePackingDecoder::ZeroLength(FixedValueIterator::new(
template.simple.zero_bit_reference_value(),
target.num_encoded_points(),
))
} else {
let element_size_in_bytes = super::helpers::num_octets(template.simple.num_bits);
let size = element_size_in_bytes * target.num_encoded_points();
let mut decoded = vec![0; size];
let mut stream = backend::Stream::new(
template.simple.num_bits.into(),
template.block_size.into(),
template.ref_sample_interval.into(),
template.mask.into(),
);
stream.set_output_samples(target.num_encoded_points());
stream
.decode(target.sect7_payload(), &mut decoded)
.map_err(DecodeError::from)?;
let decoder = NBitwiseIterator::new(decoded, element_size_in_bytes * 8);
let decoder = NonZeroSimplePackingDecoder::new(decoder, &template.simple);
SimplePackingDecoder::NonZeroLength(decoder)
};
Ok(decoder)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(all(
feature = "ccsds-unpack-with-libaec",
feature = "ccsds-unpack-with-rust-aec"
))]
use crate::def::grib2::{DataRepresentationTemplate, template::Template5_42};
use crate::test_utils::decompress_to_vec;
#[cfg(all(
feature = "ccsds-unpack-with-libaec",
feature = "ccsds-unpack-with-rust-aec"
))]
fn decoded_size(target: &Grib2SubmessageDecoder, template: &Template5_42) -> usize {
let element_size_in_bytes = super::super::helpers::num_octets(template.simple.num_bits);
element_size_in_bytes * target.num_encoded_points()
}
#[cfg(all(
feature = "ccsds-unpack-with-libaec",
feature = "ccsds-unpack-with-rust-aec"
))]
fn decode_with_libaec(
target: &Grib2SubmessageDecoder,
template: &Template5_42,
) -> Result<Vec<u8>, DecodeError> {
let mut decoded = vec![0; decoded_size(target, template)];
let mut stream = libaec::Stream::new(
template.simple.num_bits.into(),
template.block_size.into(),
template.ref_sample_interval.into(),
template.mask.into(),
);
stream.set_output_samples(target.num_encoded_points());
stream
.decode(target.sect7_payload(), &mut decoded)
.map_err(DecodeError::from)?;
Ok(decoded)
}
#[cfg(all(
feature = "ccsds-unpack-with-libaec",
feature = "ccsds-unpack-with-rust-aec"
))]
fn decode_with_rust_aec(
target: &Grib2SubmessageDecoder,
template: &Template5_42,
) -> Result<Vec<u8>, DecodeError> {
let mut decoded = vec![0; decoded_size(target, template)];
let mut stream = rust_aec::Stream::new(
template.simple.num_bits.into(),
template.block_size.into(),
template.ref_sample_interval.into(),
template.mask.into(),
);
stream.set_output_samples(target.num_encoded_points());
stream
.decode(target.sect7_payload(), &mut decoded)
.map_err(DecodeError::from)?;
Ok(decoded)
}
#[cfg(all(
feature = "ccsds-unpack-with-libaec",
feature = "ccsds-unpack-with-rust-aec"
))]
fn first_ccsds_decoder_with_nonzero_bits(
path: &str,
) -> Result<Grib2SubmessageDecoder, Box<dyn std::error::Error>> {
let buf = decompress_to_vec(path)?;
let cursor = std::io::Cursor::new(buf);
let grib2 = crate::from_reader(cursor)?;
for (_index, submessage) in grib2.iter() {
let decoder = Grib2SubmessageDecoder::from(submessage)?;
if let DataRepresentationTemplate::_5_42(template) =
&decoder.section5().payload.template
{
if template.simple.num_bits > 0 {
return Ok(decoder);
}
}
}
Err("non-zero-bit CCSDS submessage not found".into())
}
#[test]
fn decode_ccsds_compression_when_nbit_is_zero() -> Result<(), Box<dyn std::error::Error>> {
let buf = decompress_to_vec(crate::test_utils::data::grib2::ECMWF_REALTIME_OPER_FC_0)?;
let decoder = Grib2SubmessageDecoder::new(
405900,
buf[0x0006870b..0x00068724].to_vec(),
buf[0x00068724..0x0006872a].to_vec(),
buf[0x0006872a..0x0006872f].to_vec(),
)?;
let actual = decoder.dispatch()?.collect::<Vec<_>>();
let expected = vec![0f32; 0x0006318c];
assert_eq!(actual, expected);
Ok(())
}
#[test]
#[cfg(all(
feature = "ccsds-unpack-with-libaec",
feature = "ccsds-unpack-with-rust-aec"
))]
fn rust_aec_matches_libaec_for_ecmwf_ccsds_payload() -> Result<(), Box<dyn std::error::Error>> {
let decoder = first_ccsds_decoder_with_nonzero_bits(
crate::test_utils::data::grib2::ECMWF_REALTIME_OPER_FC_89,
)?;
let DataRepresentationTemplate::_5_42(template) = &decoder.section5().payload.template
else {
return Err("expected CCSDS template".into());
};
let libaec = decode_with_libaec(&decoder, template).map_err(|err| format!("{err:?}"))?;
let rust_aec =
decode_with_rust_aec(&decoder, template).map_err(|err| format!("{err:?}"))?;
assert_eq!(rust_aec, libaec);
Ok(())
}
#[test]
#[cfg(all(
feature = "ccsds-unpack-with-libaec",
feature = "ccsds-unpack-with-rust-aec"
))]
fn rust_aec_backend_takes_priority_when_both_features_are_enabled() {
let stream = backend::Stream::new(8, 8, 128, 0);
fn assert_rust_aec_stream(_: &rust_aec::Stream) {}
assert_rust_aec_stream(&stream);
}
}
#[cfg_attr(feature = "ccsds-unpack-with-rust-aec", allow(dead_code))]
#[cfg(feature = "ccsds-unpack-with-libaec")]
mod libaec;
#[cfg(feature = "ccsds-unpack-with-rust-aec")]
mod rust_aec;
#[cfg(all(
feature = "ccsds-unpack-with-libaec",
not(feature = "ccsds-unpack-with-rust-aec")
))]
use libaec as backend;
#[cfg(feature = "ccsds-unpack-with-rust-aec")]
use rust_aec as backend;