use crate::error::{Mf4Error, Result};
use crate::io::ByteSource;
use crate::model::SignalValues;
use super::{Mdf3Channel, Mdf3DataGroup};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Mdf3SampleKind {
Unsigned,
Signed,
Float,
VaxF,
VaxD,
VaxG,
String,
ByteArray,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Mdf3SampleFormat {
pub kind: Mdf3SampleKind,
pub big_endian: bool,
}
impl Mdf3SampleFormat {
pub fn from_code(code: u16, file_big_endian: bool) -> Result<Self> {
use Mdf3SampleKind::*;
let (kind, big_endian) = match code {
0 => (Unsigned, file_big_endian),
1 => (Signed, file_big_endian),
2 | 3 => (Float, file_big_endian),
4 => (VaxF, false),
5 => (VaxD, false),
6 => (VaxG, false),
7 => (String, false),
8 => (ByteArray, false),
9 => (Unsigned, true),
10 => (Signed, true),
11 | 12 => (Float, true),
13 => (Unsigned, false),
14 => (Signed, false),
15 | 16 => (Float, false),
other => {
return Err(Mf4Error::unsupported(
format!("MDF 3.x signal data type {other}"),
"this build decodes the integer, IEEE 754, VAX floating point, string and byte-array \
types; any unknown code is refused rather than guessed at",
))
}
};
Ok(Self { kind, big_endian })
}
}
#[derive(Debug, Clone, Copy)]
struct ChannelLayout {
format: Mdf3SampleFormat,
byte_offset: usize,
bit_offset: u32,
bit_count: u32,
byte_span: usize,
}
impl ChannelLayout {
fn build(channel: &Mdf3Channel, record_size: usize, file_big_endian: bool) -> Result<Self> {
let format = Mdf3SampleFormat::from_code(channel.data_type, file_big_endian)?;
let start_bit = channel.start_offset as usize + channel.additional_byte_offset as usize * 8;
let bit_count = channel.bit_count as usize;
let end_byte = (start_bit + bit_count).div_ceil(8);
if end_byte > record_size {
return Err(Mf4Error::InvalidDataBlock {
message: format!(
"channel {:?} claims bits {}..{} of a {}-byte record, which ends at bit {}",
channel.name,
start_bit,
start_bit + bit_count,
record_size,
record_size * 8
),
});
}
let byte_offset = start_bit / 8;
let bit_offset = (start_bit % 8) as u32;
match format.kind {
Mdf3SampleKind::String | Mdf3SampleKind::ByteArray => {
if bit_offset != 0 || !bit_count.is_multiple_of(8) {
return Err(Mf4Error::InvalidDataBlock {
message: format!(
"channel {:?} is a text or byte-array channel of {bit_count} bits \
at bit offset {bit_offset}; both must be whole bytes",
channel.name
),
});
}
}
Mdf3SampleKind::Float => {
if bit_count != 32 && bit_count != 64 {
return Err(Mf4Error::unsupported(
format!("a {bit_count}-bit IEEE 754 float"),
format!(
"channel {:?} declares a floating point type of a width \
IEEE 754 does not define",
channel.name
),
));
}
}
Mdf3SampleKind::VaxF => {
if bit_count != 32 {
return Err(Mf4Error::unsupported(
format!("a {bit_count}-bit VAX F float"),
format!(
"channel {:?} declares a VAX F floating point type of a width \
other than 32 bits",
channel.name
),
));
}
}
Mdf3SampleKind::VaxD => {
if bit_count != 64 {
return Err(Mf4Error::unsupported(
format!("a {bit_count}-bit VAX D float"),
format!(
"channel {:?} declares a VAX D floating point type of a width \
other than 64 bits",
channel.name
),
));
}
}
Mdf3SampleKind::VaxG => {
if bit_count != 64 {
return Err(Mf4Error::unsupported(
format!("a {bit_count}-bit VAX G float"),
format!(
"channel {:?} declares a VAX G floating point type of a width \
other than 64 bits",
channel.name
),
));
}
}
Mdf3SampleKind::Unsigned | Mdf3SampleKind::Signed => {
if bit_count > 64 {
return Err(Mf4Error::unsupported(
format!("a {bit_count}-bit integer"),
format!(
"channel {:?} is wider than the widest integer this build \
decodes; its bytes are readable but its value is not",
channel.name
),
));
}
}
}
Ok(Self {
format,
byte_offset,
bit_offset,
bit_count: bit_count as u32,
byte_span: (bit_offset as usize + bit_count).div_ceil(8),
})
}
fn bits(&self, record: &[u8]) -> u128 {
let bytes = &record[self.byte_offset..self.byte_offset + self.byte_span];
if let Some(raw) = self.aligned_bits(bytes) {
return raw;
}
let mut raw: u128 = 0;
if self.format.big_endian {
for &b in bytes {
raw = (raw << 8) | b as u128;
}
} else {
for (i, &b) in bytes.iter().enumerate() {
raw |= (b as u128) << (8 * i);
}
}
let shifted = raw >> self.bit_offset;
if self.bit_count >= 128 {
shifted
} else {
shifted & ((1u128 << self.bit_count) - 1)
}
}
fn aligned_bits(&self, bytes: &[u8]) -> Option<u128> {
if self.bit_offset != 0 || self.bit_count != 8 * self.byte_span as u32 {
return None;
}
let raw = match (self.byte_span, self.format.big_endian) {
(1, _) => bytes[0] as u128, (2, false) => u16::from_le_bytes(bytes.try_into().ok()?) as u128,
(2, true) => u16::from_be_bytes(bytes.try_into().ok()?) as u128,
(4, false) => u32::from_le_bytes(bytes.try_into().ok()?) as u128,
(4, true) => u32::from_be_bytes(bytes.try_into().ok()?) as u128,
(8, false) => u64::from_le_bytes(bytes.try_into().ok()?) as u128,
(8, true) => u64::from_be_bytes(bytes.try_into().ok()?) as u128,
_ => return None,
};
Some(raw)
}
}
enum Acc {
U8(Vec<u8>),
U16(Vec<u16>),
U32(Vec<u32>),
U64(Vec<u64>),
I8(Vec<i8>),
I16(Vec<i16>),
I32(Vec<i32>),
I64(Vec<i64>),
F32(Vec<f32>),
F64(Vec<f64>),
VaxF(Vec<f64>),
VaxD(Vec<f64>),
VaxG(Vec<f64>),
Str(Vec<String>),
Bytes { data: Vec<u8>, width: usize },
}
impl Acc {
fn for_layout(layout: &ChannelLayout, capacity: usize) -> Self {
let bytes = layout.bit_count.div_ceil(8);
match layout.format.kind {
Mdf3SampleKind::Unsigned => match bytes {
0..=1 => Acc::U8(Vec::with_capacity(capacity)),
2 => Acc::U16(Vec::with_capacity(capacity)),
3..=4 => Acc::U32(Vec::with_capacity(capacity)),
_ => Acc::U64(Vec::with_capacity(capacity)),
},
Mdf3SampleKind::Signed => match bytes {
0..=1 => Acc::I8(Vec::with_capacity(capacity)),
2 => Acc::I16(Vec::with_capacity(capacity)),
3..=4 => Acc::I32(Vec::with_capacity(capacity)),
_ => Acc::I64(Vec::with_capacity(capacity)),
},
Mdf3SampleKind::Float => {
if layout.bit_count == 32 {
Acc::F32(Vec::with_capacity(capacity))
} else {
Acc::F64(Vec::with_capacity(capacity))
}
}
Mdf3SampleKind::VaxF => Acc::VaxF(Vec::with_capacity(capacity)),
Mdf3SampleKind::VaxD => Acc::VaxD(Vec::with_capacity(capacity)),
Mdf3SampleKind::VaxG => Acc::VaxG(Vec::with_capacity(capacity)),
Mdf3SampleKind::String => Acc::Str(Vec::with_capacity(capacity)),
Mdf3SampleKind::ByteArray => Acc::Bytes {
data: Vec::with_capacity(capacity * (layout.bit_count as usize / 8)),
width: layout.bit_count as usize / 8,
},
}
}
fn push(&mut self, layout: &ChannelLayout, record: &[u8]) {
let bytes = &record[layout.byte_offset..layout.byte_offset + layout.byte_span];
let signed = |l: &ChannelLayout| sign_extend(l.bits(record), l.bit_count);
match self {
Acc::U8(v) => v.push(layout.bits(record) as u8),
Acc::U16(v) => v.push(layout.bits(record) as u16),
Acc::U32(v) => v.push(layout.bits(record) as u32),
Acc::U64(v) => v.push(layout.bits(record) as u64),
Acc::I8(v) => v.push(signed(layout) as i8),
Acc::I16(v) => v.push(signed(layout) as i16),
Acc::I32(v) => v.push(signed(layout) as i32),
Acc::I64(v) => v.push(signed(layout) as i64),
Acc::F32(v) => v.push(f32::from_bits(layout.bits(record) as u32)),
Acc::F64(v) => v.push(f64::from_bits(layout.bits(record) as u64)),
Acc::VaxF(v) => {
let b = (layout.bits(record) as u32).to_le_bytes();
v.push(decode_vax_f(b));
}
Acc::VaxD(v) => {
let b = (layout.bits(record) as u64).to_le_bytes();
v.push(decode_vax_d(b));
}
Acc::VaxG(v) => {
let b = (layout.bits(record) as u64).to_le_bytes();
v.push(decode_vax_g(b));
}
Acc::Str(v) => {
let cut = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len());
v.push(bytes[..cut].iter().map(|&b| b as char).collect());
}
Acc::Bytes { data, .. } => data.extend_from_slice(bytes),
}
}
fn into_values(self) -> SignalValues {
match self {
Acc::U8(v) => SignalValues::U8(v),
Acc::U16(v) => SignalValues::U16(v),
Acc::U32(v) => SignalValues::U32(v),
Acc::U64(v) => SignalValues::U64(v),
Acc::I8(v) => SignalValues::I8(v),
Acc::I16(v) => SignalValues::I16(v),
Acc::I32(v) => SignalValues::I32(v),
Acc::I64(v) => SignalValues::I64(v),
Acc::F32(v) => SignalValues::F32(v),
Acc::F64(v) | Acc::VaxF(v) | Acc::VaxD(v) | Acc::VaxG(v) => SignalValues::F64(v),
Acc::Str(v) => SignalValues::Str(v),
Acc::Bytes { data, width } => SignalValues::Bytes { data, width },
}
}
}
pub fn decode_vax_f(bytes: [u8; 4]) -> f64 {
let word0 = u16::from_le_bytes([bytes[0], bytes[1]]);
let word1 = u16::from_le_bytes([bytes[2], bytes[3]]);
let sign = (word0 >> 15) & 1;
let exponent = (word0 >> 7) & 0xFF;
let fraction = (((word0 & 0x7F) as u32) << 16) | (word1 as u32);
if exponent == 0 {
if sign == 0 {
0.0
} else {
f64::NAN
}
} else {
let sign_mul = if sign == 1 { -1.0 } else { 1.0 };
let mantissa = 1.0 + (fraction as f64) * 2.0f64.powi(-23);
sign_mul * mantissa * 2.0f64.powi(exponent as i32 - 129)
}
}
pub fn decode_vax_d(bytes: [u8; 8]) -> f64 {
let word0 = u16::from_le_bytes([bytes[0], bytes[1]]);
let word1 = u16::from_le_bytes([bytes[2], bytes[3]]);
let word2 = u16::from_le_bytes([bytes[4], bytes[5]]);
let word3 = u16::from_le_bytes([bytes[6], bytes[7]]);
let sign = (word0 >> 15) & 1;
let exponent = (word0 >> 7) & 0xFF;
let fraction = (((word0 & 0x7F) as u64) << 48)
| ((word1 as u64) << 32)
| ((word2 as u64) << 16)
| (word3 as u64);
if exponent == 0 {
if sign == 0 {
0.0
} else {
f64::NAN
}
} else {
let sign_mul = if sign == 1 { -1.0 } else { 1.0 };
let mantissa = 1.0 + (fraction as f64) * 2.0f64.powi(-55);
sign_mul * mantissa * 2.0f64.powi(exponent as i32 - 129)
}
}
pub fn decode_vax_g(bytes: [u8; 8]) -> f64 {
let word0 = u16::from_le_bytes([bytes[0], bytes[1]]);
let word1 = u16::from_le_bytes([bytes[2], bytes[3]]);
let word2 = u16::from_le_bytes([bytes[4], bytes[5]]);
let word3 = u16::from_le_bytes([bytes[6], bytes[7]]);
let sign = (word0 >> 15) & 1;
let exponent = (word0 >> 4) & 0x7FF;
let fraction = (((word0 & 0x0F) as u64) << 48)
| ((word1 as u64) << 32)
| ((word2 as u64) << 16)
| (word3 as u64);
if exponent == 0 {
if sign == 0 {
0.0
} else {
f64::NAN
}
} else {
let sign_mul = if sign == 1 { -1.0 } else { 1.0 };
let mantissa = 1.0 + (fraction as f64) * 2.0f64.powi(-52);
sign_mul * mantissa * 2.0f64.powi(exponent as i32 - 1025)
}
}
fn sign_extend(bits: u128, bit_count: u32) -> i128 {
if bit_count == 0 || bit_count >= 128 {
return bits as i128;
}
let sign = 1u128 << (bit_count - 1);
if bits & sign != 0 {
(bits | !((1u128 << bit_count) - 1)) as i128
} else {
bits as i128
}
}
struct RecordPlan {
id_count: u16,
sizes: Vec<Option<usize>>,
cycles: Vec<u64>,
total_size: usize,
}
impl RecordPlan {
fn build(dg: &Mdf3DataGroup) -> Result<Self> {
let id_count = dg.record_id_count;
if id_count == 0 && dg.channel_groups.len() > 1 {
return Err(Mf4Error::InvalidDataBlock {
message: format!(
"data group holds {} channel groups but writes no record identifier, \
so its records cannot be told apart",
dg.channel_groups.len()
),
});
}
let mut sizes: Vec<Option<usize>> = vec![None; 256];
let mut cycles: Vec<u64> = vec![0; 256];
let mut total_size: usize = 0;
for cg in &dg.channel_groups {
let id = if id_count == 0 { 0 } else { cg.record_id };
if id > u8::MAX as u16 {
return Err(Mf4Error::InvalidDataBlock {
message: format!(
"channel group declares record identifier {id}, but a v3 record \
identifier is a single byte"
),
});
}
let id = id as usize;
if sizes[id].is_some() {
return Err(Mf4Error::InvalidDataBlock {
message: format!(
"two channel groups in one data group share record identifier {id}"
),
});
}
sizes[id] = Some(cg.record_size as usize);
cycles[id] = cg.cycle_count as u64;
let per_record = cg.record_size as u64 + id_count as u64;
let bytes = per_record
.checked_mul(cg.cycle_count as u64)
.and_then(|b| b.checked_add(total_size as u64))
.ok_or_else(|| Mf4Error::InvalidDataBlock {
message: "data group declares more record bytes than can be addressed"
.to_string(),
})?;
total_size = usize::try_from(bytes).map_err(|_| Mf4Error::InvalidDataBlock {
message: "data group declares more record bytes than fit in memory".to_string(),
})?;
}
Ok(Self {
id_count,
sizes,
cycles,
total_size,
})
}
}
pub(super) fn validate_layout(
channel: &Mdf3Channel,
record_size: usize,
file_big_endian: bool,
) -> Result<()> {
ChannelLayout::build(channel, record_size, file_big_endian).map(|_| ())
}
pub(super) fn read_channel_group(
source: &dyn ByteSource,
file_big_endian: bool,
dg: &Mdf3DataGroup,
cg_index: usize,
) -> Result<Vec<Option<SignalValues>>> {
let cg = dg.channel_groups.get(cg_index).ok_or_else(|| {
Mf4Error::parse_error(format!("no channel group {cg_index} in this data group"))
})?;
let record_size = cg.record_size as usize;
let layouts: Vec<Option<ChannelLayout>> = cg
.channels
.iter()
.map(|ch| ChannelLayout::build(ch, record_size, file_big_endian).ok())
.collect();
let mut accs: Vec<Option<Acc>> = layouts
.iter()
.map(|l| {
l.as_ref()
.map(|l| Acc::for_layout(l, cg.cycle_count as usize))
})
.collect();
let plan = RecordPlan::build(dg)?;
let target_id = if plan.id_count == 0 { 0 } else { cg.record_id };
if plan.total_size == 0 {
return finish(accs);
}
if dg.data_block_addr == 0 {
return Err(Mf4Error::InvalidDataBlock {
message: format!(
"data group declares {} bytes of records but no data block address",
plan.total_size
),
});
}
let addr = dg.data_block_addr as u64;
let available = source.len().saturating_sub(addr);
if (plan.total_size as u64) > available {
return Err(Mf4Error::TruncatedFile {
offset: addr,
expected: plan.total_size,
actual: available as usize,
});
}
let block = source.read_bytes(addr, plan.total_size)?;
let trailing = if plan.id_count == 2 { 1 } else { 0 };
let mut counts: Vec<u64> = vec![0; 256];
let mut pos = 0usize;
while pos < block.len() {
let (id, start) = if plan.id_count == 0 {
(target_id, pos)
} else {
(block[pos] as u16, pos + 1)
};
let size = plan.sizes[id as usize].ok_or_else(|| Mf4Error::InvalidDataBlock {
message: format!(
"record at byte {pos} of the data block carries identifier {id}, which no \
channel group in this data group claims; its length is therefore unknown"
),
})?;
let end = start + size;
if end + trailing > block.len() {
return Err(Mf4Error::TruncatedFile {
offset: addr + start as u64,
expected: size + trailing,
actual: block.len().saturating_sub(start),
});
}
if trailing == 1 && block[end] as u16 != id {
return Err(Mf4Error::InvalidDataBlock {
message: format!(
"record at byte {pos} opens with identifier {id} and closes with {}; \
the trailing copy must repeat the leading one",
block[end]
),
});
}
if id == target_id {
let record = &block[start..end];
for (layout, acc) in layouts.iter().zip(&mut accs) {
if let (Some(layout), Some(acc)) = (layout, acc) {
acc.push(layout, record);
}
}
}
counts[id as usize] += 1;
pos = end + trailing;
}
for (id, &declared) in plan.cycles.iter().enumerate() {
if plan.sizes[id].is_some() && counts[id] != declared {
return Err(Mf4Error::InvalidDataBlock {
message: format!(
"channel group with record identifier {id} declares {declared} cycles \
but the data block holds {} records for it",
counts[id]
),
});
}
}
finish(accs)
}
fn finish(accs: Vec<Option<Acc>>) -> Result<Vec<Option<SignalValues>>> {
Ok(accs
.into_iter()
.map(|acc| acc.map(Acc::into_values))
.collect())
}
#[cfg(test)]
mod tests {
use super::*;
fn layout(start_bit: usize, bit_count: usize, code: u16) -> ChannelLayout {
ChannelLayout {
format: Mdf3SampleFormat::from_code(code, false).unwrap(),
byte_offset: start_bit / 8,
bit_offset: (start_bit % 8) as u32,
bit_count: bit_count as u32,
byte_span: ((start_bit % 8) + bit_count).div_ceil(8),
}
}
#[test]
fn a_little_endian_field_is_read_from_the_low_bits_up() {
let record = [0xF1u8, 0x2C];
assert_eq!(layout(4, 12, 13).bits(&record), 0x2CF);
}
#[test]
fn a_big_endian_field_is_read_most_significant_byte_first() {
let record = [0xF1u8, 0x2C];
assert_eq!(layout(4, 12, 9).bits(&record), 0xF12);
}
#[test]
fn a_field_spanning_three_bytes_keeps_its_high_bits() {
let record = [0x01u8, 0x02, 0x03];
assert_eq!(layout(6, 18, 13).bits(&record), 0x030201u128 >> 6);
}
#[test]
fn a_negative_value_narrower_than_its_type_sign_extends() {
assert_eq!(sign_extend(0b1101, 4), -3);
assert_eq!(sign_extend(0b0101, 4), 5);
assert_eq!(sign_extend(0xFFF, 12), -1);
assert_eq!(sign_extend(0x7FF, 12), 2047);
}
#[test]
fn a_data_type_code_resolves_its_own_byte_order_before_the_files() {
assert!(Mdf3SampleFormat::from_code(9, false).unwrap().big_endian);
assert!(!Mdf3SampleFormat::from_code(13, true).unwrap().big_endian);
assert!(Mdf3SampleFormat::from_code(0, true).unwrap().big_endian);
assert!(!Mdf3SampleFormat::from_code(0, false).unwrap().big_endian);
}
#[test]
fn vax_float_codes_resolve_to_vax_formats() {
assert_eq!(
Mdf3SampleFormat::from_code(4, false).unwrap(),
Mdf3SampleFormat {
kind: Mdf3SampleKind::VaxF,
big_endian: false,
}
);
assert_eq!(
Mdf3SampleFormat::from_code(5, false).unwrap(),
Mdf3SampleFormat {
kind: Mdf3SampleKind::VaxD,
big_endian: false,
}
);
assert_eq!(
Mdf3SampleFormat::from_code(6, false).unwrap(),
Mdf3SampleFormat {
kind: Mdf3SampleKind::VaxG,
big_endian: false,
}
);
}
#[test]
fn an_unknown_code_is_refused_by_name() {
for code in [99u16, 100] {
assert!(matches!(
Mdf3SampleFormat::from_code(code, false),
Err(Mf4Error::Unsupported { .. })
));
}
}
#[test]
fn vax_f_decoding_hand_computed_values() {
let bytes_1_0 = [0x80u8, 0x40, 0x00, 0x00];
let vax_1_0 = decode_vax_f(bytes_1_0);
assert_eq!(vax_1_0, 1.0);
let ieee_1_0 = f32::from_le_bytes(bytes_1_0);
assert_ne!(vax_1_0, ieee_1_0 as f64);
let bytes_0_5 = [0x00u8, 0x40, 0x00, 0x00];
let vax_0_5 = decode_vax_f(bytes_0_5);
assert_eq!(vax_0_5, 0.5);
let ieee_0_5 = f32::from_le_bytes(bytes_0_5);
assert_ne!(vax_0_5, ieee_0_5 as f64);
let bytes_neg_2_5 = [0x20u8, 0xC1, 0x00, 0x00];
let vax_neg_2_5 = decode_vax_f(bytes_neg_2_5);
assert_eq!(vax_neg_2_5, -2.5);
let ieee_neg_2_5 = f32::from_le_bytes(bytes_neg_2_5);
assert_ne!(vax_neg_2_5, ieee_neg_2_5 as f64);
let bytes_zero = [0x00u8, 0x00, 0x00, 0x00];
assert_eq!(decode_vax_f(bytes_zero), 0.0);
let bytes_reserved = [0x00u8, 0x80, 0x00, 0x00];
assert!(decode_vax_f(bytes_reserved).is_nan());
let bytes_1_5 = [0xC0u8, 0x40, 0x00, 0x00];
assert_eq!(decode_vax_f(bytes_1_5), 1.5);
}
#[test]
fn vax_d_decoding_hand_computed_values() {
let bytes_1_0 = [0x80u8, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
let vax_1_0 = decode_vax_d(bytes_1_0);
assert_eq!(vax_1_0, 1.0);
let ieee_1_0 = f64::from_le_bytes(bytes_1_0);
assert_ne!(vax_1_0, ieee_1_0);
let bytes_0_5 = [0x00u8, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
let vax_0_5 = decode_vax_d(bytes_0_5);
assert_eq!(vax_0_5, 0.5);
let ieee_0_5 = f64::from_le_bytes(bytes_0_5);
assert_ne!(vax_0_5, ieee_0_5);
let bytes_neg_2_5 = [0x20u8, 0xC1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
let vax_neg_2_5 = decode_vax_d(bytes_neg_2_5);
assert_eq!(vax_neg_2_5, -2.5);
let ieee_neg_2_5 = f64::from_le_bytes(bytes_neg_2_5);
assert_ne!(vax_neg_2_5, ieee_neg_2_5);
let bytes_zero = [0x00u8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
assert_eq!(decode_vax_d(bytes_zero), 0.0);
let bytes_reserved = [0x00u8, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
assert!(decode_vax_d(bytes_reserved).is_nan());
let bytes_1_5 = [0xC0u8, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
assert_eq!(decode_vax_d(bytes_1_5), 1.5);
}
#[test]
fn vax_g_decoding_hand_computed_values() {
let bytes_1_0 = [0x10u8, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
let vax_1_0 = decode_vax_g(bytes_1_0);
assert_eq!(vax_1_0, 1.0);
let ieee_1_0 = f64::from_le_bytes(bytes_1_0);
assert_ne!(vax_1_0, ieee_1_0);
let bytes_0_5 = [0x00u8, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
let vax_0_5 = decode_vax_g(bytes_0_5);
assert_eq!(vax_0_5, 0.5);
let ieee_0_5 = f64::from_le_bytes(bytes_0_5);
assert_ne!(vax_0_5, ieee_0_5);
let bytes_neg_2_5 = [0x24u8, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
let vax_neg_2_5 = decode_vax_g(bytes_neg_2_5);
assert_eq!(vax_neg_2_5, -2.5);
let ieee_neg_2_5 = f64::from_le_bytes(bytes_neg_2_5);
assert_ne!(vax_neg_2_5, ieee_neg_2_5);
let bytes_zero = [0x00u8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
assert_eq!(decode_vax_g(bytes_zero), 0.0);
let bytes_reserved = [0x00u8, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
assert!(decode_vax_g(bytes_reserved).is_nan());
let bytes_1_5 = [0x18u8, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
assert_eq!(decode_vax_g(bytes_1_5), 1.5);
}
#[test]
fn vax_channel_decodes_into_signal_values_f64() {
let l_f = layout(0, 32, 4);
let mut acc_f = Acc::for_layout(&l_f, 3);
acc_f.push(&l_f, &[0x80, 0x40, 0x00, 0x00]); acc_f.push(&l_f, &[0x00, 0x40, 0x00, 0x00]); acc_f.push(&l_f, &[0x20, 0xC1, 0x00, 0x00]); assert_eq!(acc_f.into_values(), SignalValues::F64(vec![1.0, 0.5, -2.5]));
let l_d = layout(0, 64, 5);
let mut acc_d = Acc::for_layout(&l_d, 2);
acc_d.push(&l_d, &[0x80, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); acc_d.push(&l_d, &[0xC0, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); assert_eq!(acc_d.into_values(), SignalValues::F64(vec![1.0, 1.5]));
let l_g = layout(0, 64, 6);
let mut acc_g = Acc::for_layout(&l_g, 2);
acc_g.push(&l_g, &[0x10, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); acc_g.push(&l_g, &[0x24, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); assert_eq!(acc_g.into_values(), SignalValues::F64(vec![1.0, -2.5]));
}
}