use crate::error::{Error, Result};
const PARM_SCALETYPE: usize = 0;
const PARM_NELMTS: usize = 2;
const PARM_CLASS: usize = 3;
const PARM_SIZE: usize = 4;
const PARM_SIGN: usize = 5;
const PARM_ORDER: usize = 6;
const PARM_FILAVAIL: usize = 7;
const PARM_FILVAL: usize = 8;
const CLS_INTEGER: u32 = 0;
const CLS_FLOAT: u32 = 1;
const SIGN_UNSIGNED: u32 = 0;
const SIGN_TWOS: u32 = 1;
const ORDER_LE: u32 = 0;
const ORDER_BE: u32 = 1;
const HEADER_LEN: usize = 21;
const SCALE_FLOAT_DSCALE: u32 = 0;
const SCALE_INT: u32 = 2;
#[derive(Debug, Clone, Copy)]
struct Parms {
size: usize,
minbits: usize,
order: u32,
}
pub fn decompress_into(data: &[u8], client_data: &[u32], out: &mut Vec<u8>) -> Result<()> {
can_apply_scaleoffset(client_data)?;
if client_data.len() <= PARM_ORDER {
return Err(Error::InvalidFormat(
"scaleoffset filter missing datatype parameters".into(),
));
}
let scale_type = client_data[PARM_SCALETYPE];
let nelmts = scaleoffset_usize(client_data[PARM_NELMTS], "scaleoffset element count")?;
let class = client_data[PARM_CLASS];
let size = scaleoffset_usize(client_data[PARM_SIZE], "scaleoffset datatype size")?;
let sign = client_data[PARM_SIGN];
let order = client_data[PARM_ORDER];
if size == 0 {
return Err(Error::InvalidFormat(
"scaleoffset datatype size is zero".into(),
));
}
if size > 16 {
return Err(Error::Unsupported(format!(
"scaleoffset datatype size {size} exceeds 16-byte arithmetic support"
)));
}
if order != ORDER_LE && order != ORDER_BE {
return Err(Error::InvalidFormat(format!(
"invalid scaleoffset byte order {order}"
)));
}
validate_scaleoffset_datatype(class, sign, size, scale_type)?;
if data.len() < HEADER_LEN {
return Err(Error::InvalidFormat("scaleoffset data too short".into()));
}
let minbits = scaleoffset_usize(
read_u32_le_at(data, 0, "scaleoffset minimum bit count")?,
"scaleoffset minimum bit count",
)?;
validate_scaleoffset_parameters(class, sign, size, minbits)?;
let minval_size = usize::from(data[4]);
if minval_size > 16 {
return Err(Error::InvalidFormat(
"invalid scaleoffset minimum value header".into(),
));
}
let minval = read_le_u128(checked_window(
data,
5,
minval_size,
"scaleoffset minimum value header",
)?);
let out_len = nelmts
.checked_mul(size)
.ok_or_else(|| Error::InvalidFormat("scaleoffset output size overflow".into()))?;
let mut decoded = Vec::new();
decoded.try_reserve_exact(out_len).map_err(|err| {
Error::InvalidFormat(format!("scaleoffset output allocation failed: {err}"))
})?;
decoded.resize(out_len, 0);
let full_precision = minbits == size * 8;
if full_precision {
let raw_end = HEADER_LEN.checked_add(out_len).ok_or_else(|| {
Error::InvalidFormat("scaleoffset full-precision data too short".into())
})?;
let raw = data.get(HEADER_LEN..raw_end).ok_or_else(|| {
Error::InvalidFormat("scaleoffset full-precision data too short".into())
})?;
decoded.copy_from_slice(raw);
} else if minbits != 0 {
let parms = Parms {
size,
minbits,
order,
};
let mut stream = BitStream::new(&data[HEADER_LEN..]);
for idx in 0..nelmts {
let data_offset = idx
.checked_mul(size)
.ok_or_else(|| Error::InvalidFormat("scaleoffset output offset overflow".into()))?;
decompress_atomic(&mut decoded, data_offset, &mut stream, parms)?;
}
}
if !full_precision {
match class {
CLS_INTEGER => {
let fill = if client_data.get(PARM_FILAVAIL).copied().unwrap_or(0) != 0 {
Some(read_fill_value(client_data, size, order))
} else {
None
};
postprocess_integer(&mut decoded, size, sign, order, minbits, minval, fill)?
}
CLS_FLOAT if scale_type == 0 => {
postprocess_float(&mut decoded, size, order, minbits, minval, client_data)?
}
CLS_FLOAT => {
return Err(Error::Unsupported(format!(
"scaleoffset float scale type {scale_type}"
)));
}
other => {
return Err(Error::Unsupported(format!(
"scaleoffset datatype class {other}"
)));
}
}
}
out.try_reserve_exact(decoded.len()).map_err(|err| {
Error::InvalidFormat(format!("scaleoffset output allocation failed: {err}"))
})?;
out.extend_from_slice(&decoded);
Ok(())
}
pub fn can_apply_scaleoffset(client_data: &[u32]) -> Result<()> {
if client_data.len() <= PARM_ORDER {
return Err(Error::InvalidFormat(
"scaleoffset filter missing datatype parameters".into(),
));
}
let scale_type = client_data[PARM_SCALETYPE];
let class = client_data[PARM_CLASS];
let size = scaleoffset_usize(client_data[PARM_SIZE], "scaleoffset datatype size")?;
let sign = client_data[PARM_SIGN];
let order = client_data[PARM_ORDER];
if size == 0 {
return Err(Error::InvalidFormat(
"scaleoffset datatype size is zero".into(),
));
}
if size > 16 {
return Err(Error::Unsupported(format!(
"scaleoffset datatype size {size} exceeds 16-byte arithmetic support"
)));
}
if order != ORDER_LE && order != ORDER_BE {
return Err(Error::InvalidFormat(format!(
"invalid scaleoffset byte order {order}"
)));
}
validate_scaleoffset_datatype(class, sign, size, scale_type)?;
validate_scaleoffset_fill_value(client_data, size)?;
Ok(())
}
pub fn scaleoffset_convert(
data: &mut [u8],
element_size: usize,
from_order: u32,
to_order: u32,
) -> Result<()> {
if from_order != ORDER_LE && from_order != ORDER_BE {
return Err(Error::InvalidFormat(format!(
"invalid scaleoffset byte order {from_order}"
)));
}
if to_order != ORDER_LE && to_order != ORDER_BE {
return Err(Error::InvalidFormat(format!(
"invalid scaleoffset byte order {to_order}"
)));
}
if element_size == 0 {
return Err(Error::InvalidFormat(
"scaleoffset datatype size is zero".into(),
));
}
if from_order != to_order {
for chunk in data.chunks_exact_mut(element_size) {
chunk.reverse();
}
}
Ok(())
}
pub fn scaleoffset_log2(value: u64) -> Option<u32> {
(value != 0).then(|| u64::BITS - 1 - value.leading_zeros())
}
pub fn filter_scaleoffset_into(
data: &[u8],
client_data: &[u32],
reverse: bool,
out: &mut Vec<u8>,
) -> Result<()> {
if reverse {
decompress_into(data, client_data, out)
} else {
scaleoffset_compress_into(data, client_data, out)
}
}
pub fn scaleoffset_compress_into(
data: &[u8],
client_data: &[u32],
out: &mut Vec<u8>,
) -> Result<()> {
can_apply_scaleoffset(client_data)?;
let scale_type = client_data[PARM_SCALETYPE];
let nelmts = scaleoffset_usize(client_data[PARM_NELMTS], "scaleoffset element count")?;
let class = client_data[PARM_CLASS];
let size = scaleoffset_usize(client_data[PARM_SIZE], "scaleoffset datatype size")?;
let sign = client_data[PARM_SIGN];
let order = client_data[PARM_ORDER];
let expected = nelmts
.checked_mul(size)
.ok_or_else(|| Error::InvalidFormat("scaleoffset input size overflow".into()))?;
if data.len() < expected {
return Err(Error::InvalidFormat(
"scaleoffset input data too short".into(),
));
}
let mut packed = Vec::new();
packed.try_reserve_exact(expected).map_err(|err| {
Error::InvalidFormat(format!(
"scaleoffset packed buffer allocation failed: {err}"
))
})?;
let (minbits, minval) = match class {
CLS_INTEGER => scaleoffset_precompress_i_with_minbits_into(
&data[..expected],
size,
sign,
order,
Some(client_data[1]),
if client_data.get(PARM_FILAVAIL).copied().unwrap_or(0) != 0 {
Some(read_fill_value(client_data, size, order))
} else {
None
},
&mut packed,
)?,
CLS_FLOAT if scale_type == 0 => scaleoffset_precompress_fd_into(
&data[..expected],
size,
order,
client_data,
&mut packed,
)?,
CLS_FLOAT => {
return Err(Error::Unsupported(format!(
"scaleoffset float scale type {scale_type}"
)));
}
other => {
return Err(Error::Unsupported(format!(
"scaleoffset datatype class {other}"
)));
}
};
let mut encoded = Vec::new();
encoded.try_reserve_exact(HEADER_LEN).map_err(|err| {
Error::InvalidFormat(format!("scaleoffset output allocation failed: {err}"))
})?;
encoded.resize(HEADER_LEN, 0);
let header = &mut encoded[..HEADER_LEN];
let minbits_u32 = u32::try_from(minbits).map_err(|_| {
Error::InvalidFormat("scaleoffset minimum bit count does not fit in u32".into())
})?;
header[..4].copy_from_slice(&minbits_u32.to_le_bytes());
header[4] = size as u8;
write_uint(
checked_window_mut(header, 5, size, "scaleoffset minimum value header")?,
ORDER_LE,
minval,
);
if minbits == size * 8 {
encoded.extend_from_slice(&data[..expected]);
} else if minbits != 0 {
let mut writer = BitWriter::new(&mut encoded);
let parms = Parms {
size,
minbits,
order,
};
for idx in 0..nelmts {
let data_offset = idx
.checked_mul(size)
.ok_or_else(|| Error::InvalidFormat("scaleoffset packed offset overflow".into()))?;
scaleoffset_compress_one_atomic(&packed, data_offset, &mut writer, parms)?;
}
writer.finish();
}
out.try_reserve_exact(encoded.len()).map_err(|err| {
Error::InvalidFormat(format!("scaleoffset output allocation failed: {err}"))
})?;
out.extend_from_slice(&encoded);
Ok(())
}
pub fn scaleoffset_precompress_i_into(
data: &[u8],
size: usize,
sign: u32,
order: u32,
packed: &mut Vec<u8>,
) -> Result<(usize, u128)> {
scaleoffset_precompress_i_with_minbits_into(data, size, sign, order, None, None, packed)
}
fn scaleoffset_precompress_i_with_minbits_into(
data: &[u8],
size: usize,
sign: u32,
order: u32,
requested_minbits: Option<u32>,
fill: Option<u128>,
packed: &mut Vec<u8>,
) -> Result<(usize, u128)> {
validate_scaleoffset_parameters(CLS_INTEGER, sign, size, 0)?;
if size == 0 || data.len() % size != 0 {
return Err(Error::InvalidFormat(
"scaleoffset integer input size mismatch".into(),
));
}
if data.is_empty() {
return Ok((0, 0));
}
let requested_minbits = requested_minbits
.filter(|bits| *bits != 0)
.map(|bits| scaleoffset_usize(bits, "scaleoffset integer minimum bit count"))
.transpose()?
.map(|bits| bits.min(size * 8));
let (computed_minbits, minval) = if sign == SIGN_TWOS {
let fill = fill.map(|value| read_int_from_bits(value, size));
let minval = data
.chunks_exact(size)
.map(|chunk| read_int(chunk, order))
.filter(|value| Some(*value) != fill)
.min()
.unwrap_or(0);
let max_delta = data
.chunks_exact(size)
.map(|chunk| read_int(chunk, order))
.filter(|value| Some(*value) != fill)
.map(|value| signed_delta(value, minval))
.max()
.unwrap_or(0);
(
scaleoffset_integer_minbits(max_delta, fill.is_some(), size)?,
signed_to_uint_bits(minval, size),
)
} else {
let minval = data
.chunks_exact(size)
.map(|chunk| read_uint(chunk, order))
.filter(|value| Some(*value) != fill)
.min()
.unwrap_or(0);
let max_delta = data
.chunks_exact(size)
.map(|chunk| read_uint(chunk, order))
.filter(|value| Some(*value) != fill)
.map(|value| value.wrapping_sub(minval))
.max()
.unwrap_or(0);
(
scaleoffset_integer_minbits(max_delta, fill.is_some(), size)?,
minval,
)
};
let minbits = requested_minbits.unwrap_or(computed_minbits);
let fill_marker = fill
.filter(|_| minbits != size * 8)
.map(|_| fill_marker(minbits));
let start = packed.len();
packed.resize(
start
.checked_add(data.len())
.ok_or_else(|| Error::InvalidFormat("scaleoffset packed size overflow".into()))?,
0,
);
for (idx, chunk) in data.chunks_exact(size).enumerate() {
let offset =
start
.checked_add(idx.checked_mul(size).ok_or_else(|| {
Error::InvalidFormat("scaleoffset packed offset overflow".into())
})?)
.ok_or_else(|| Error::InvalidFormat("scaleoffset packed offset overflow".into()))?;
let raw = read_uint(chunk, order);
let value = if Some(raw) == fill {
fill_marker.unwrap_or(raw)
} else if sign == SIGN_TWOS {
signed_delta(
read_int_from_bits(raw, size),
read_int_from_bits(minval, size),
)
} else {
raw.wrapping_sub(minval)
};
write_uint(
checked_window_mut(packed, offset, size, "scaleoffset packed integer value")?,
order,
value,
);
}
Ok((minbits, minval))
}
pub fn scaleoffset_precompress_fd_into(
data: &[u8],
size: usize,
order: u32,
client_data: &[u32],
packed: &mut Vec<u8>,
) -> Result<(usize, u128)> {
if !matches!(size, 4 | 8) || data.len() % size != 0 {
return Err(Error::InvalidFormat(
"scaleoffset floating-point input size mismatch".into(),
));
}
if data.is_empty() {
return Ok((0, 0));
}
let scale = client_data
.get(1)
.copied()
.ok_or_else(|| Error::InvalidFormat("scaleoffset missing scale factor".into()))?
.to_ne_bytes();
let scale = i32::from_ne_bytes(scale);
let multiplier = 10f64.powi(scale);
let fill = if client_data.get(PARM_FILAVAIL).copied().unwrap_or(0) != 0 {
Some(read_fill_float(client_data, size, order))
} else {
None
};
let fill_epsilon = 10f64.powi(-scale);
let is_fill = |value: f64| {
fill.is_some_and(|fill| {
if value.is_nan() || fill.is_nan() {
value.to_bits() == fill.to_bits()
} else {
(value - fill).abs() < fill_epsilon
}
})
};
let mut values = data
.chunks_exact(size)
.map(|chunk| read_float(chunk, size, order))
.filter(|value| !is_fill(*value));
let first = values.next().unwrap_or(0.0);
let (min, max) = values.fold((first, first), |(min, max), value| {
(min.min(value), max.max(value))
});
let max_delta = data
.chunks_exact(size)
.map(|chunk| read_float(chunk, size, order))
.filter(|value| !is_fill(*value))
.map(|value| ((value - min) * multiplier).round().max(0.0) as u128)
.max()
.unwrap_or_else(|| ((max - min) * multiplier).round().max(0.0) as u128);
let minbits = if fill.is_some() {
scaleoffset_integer_minbits(max_delta, true, size)?
} else if max_delta == 0 {
0
} else {
usize::try_from(u128::BITS - max_delta.leading_zeros())
.map_err(|_| Error::InvalidFormat("scaleoffset bit count overflow".into()))?
}
.min(size * 8);
let fill_marker = fill
.filter(|_| minbits != size * 8)
.map(|_| fill_marker(minbits));
let start = packed.len();
packed.resize(
start
.checked_add(data.len())
.ok_or_else(|| Error::InvalidFormat("scaleoffset packed size overflow".into()))?,
0,
);
for (idx, chunk) in data.chunks_exact(size).enumerate() {
let value = read_float(chunk, size, order);
let delta = if is_fill(value) {
fill_marker.unwrap_or_else(|| read_uint(chunk, order))
} else {
((value - min) * multiplier).round().max(0.0) as u128
};
let offset =
start
.checked_add(idx.checked_mul(size).ok_or_else(|| {
Error::InvalidFormat("scaleoffset packed offset overflow".into())
})?)
.ok_or_else(|| Error::InvalidFormat("scaleoffset packed offset overflow".into()))?;
write_uint(
checked_window_mut(packed, offset, size, "scaleoffset packed float value")?,
order,
delta,
);
}
let minval = if size == 4 {
(min as f32).to_bits() as u128
} else {
min.to_bits() as u128
};
Ok((minbits, minval))
}
fn read_float(chunk: &[u8], size: usize, order: u32) -> f64 {
if size == 4 {
let mut raw = [0u8; 4];
raw.copy_from_slice(chunk);
if order == ORDER_LE {
f32::from_le_bytes(raw) as f64
} else {
f32::from_be_bytes(raw) as f64
}
} else {
let mut raw = [0u8; 8];
raw.copy_from_slice(chunk);
if order == ORDER_LE {
f64::from_le_bytes(raw)
} else {
f64::from_be_bytes(raw)
}
}
}
fn read_fill_float(client_data: &[u32], size: usize, order: u32) -> f64 {
let fill = read_fill_value(client_data, size, order);
if size == 4 {
f32::from_le_bytes(low_u32(fill).to_le_bytes()) as f64
} else {
f64::from_le_bytes(low_u64(fill).to_le_bytes())
}
}
fn validate_scaleoffset_datatype(
class: u32,
sign: u32,
size: usize,
scale_type: u32,
) -> Result<()> {
match class {
CLS_INTEGER => {
if scale_type != SCALE_INT {
return Err(Error::InvalidFormat(format!(
"invalid scaleoffset integer scale type {scale_type}"
)));
}
if sign != SIGN_UNSIGNED && sign != SIGN_TWOS {
return Err(Error::InvalidFormat(format!(
"invalid scaleoffset integer sign {sign}"
)));
}
}
CLS_FLOAT => {
if !matches!(size, 4 | 8) {
return Err(Error::Unsupported(format!(
"scaleoffset floating-point size {size}"
)));
}
if scale_type != SCALE_FLOAT_DSCALE {
return Err(Error::Unsupported(format!(
"scaleoffset float scale type {scale_type}"
)));
}
}
other => {
return Err(Error::Unsupported(format!(
"scaleoffset datatype class {other}"
)));
}
}
Ok(())
}
fn validate_scaleoffset_parameters(
class: u32,
sign: u32,
size: usize,
minbits: usize,
) -> Result<()> {
if minbits > size * 8 {
return Err(Error::InvalidFormat(
"invalid scaleoffset minimum bit count".into(),
));
}
if class == CLS_INTEGER && sign != SIGN_UNSIGNED && sign != SIGN_TWOS {
return Err(Error::InvalidFormat(format!(
"invalid scaleoffset integer sign {sign}"
)));
}
Ok(())
}
fn validate_scaleoffset_fill_value(client_data: &[u32], size: usize) -> Result<()> {
if client_data.get(PARM_FILAVAIL).copied().unwrap_or(0) == 0 {
return Ok(());
}
let fill_words = size
.checked_add(3)
.ok_or_else(|| Error::InvalidFormat("scaleoffset fill value size overflow".into()))?
/ 4;
let required = PARM_FILVAL
.checked_add(fill_words)
.ok_or_else(|| Error::InvalidFormat("scaleoffset fill value size overflow".into()))?;
if client_data.len() < required {
return Err(Error::InvalidFormat(
"scaleoffset fill value parameters are truncated".into(),
));
}
Ok(())
}
fn decompress_atomic(
out: &mut [u8],
data_offset: usize,
stream: &mut BitStream<'_>,
parms: Parms,
) -> Result<()> {
let dtype_bits = parms.size * 8;
if parms.minbits == 0 || parms.minbits > dtype_bits {
return Err(Error::InvalidFormat(
"invalid scaleoffset minimum bit count".into(),
));
}
if parms.order == ORDER_LE {
let begin = parms.size - 1 - (dtype_bits - parms.minbits) / 8;
for k in (0..=begin).rev() {
decompress_byte(out, data_offset, k, begin, stream, parms, dtype_bits)?;
}
} else {
let begin = (dtype_bits - parms.minbits) / 8;
for k in begin..parms.size {
decompress_byte(out, data_offset, k, begin, stream, parms, dtype_bits)?;
}
}
Ok(())
}
fn scaleoffset_compress_one_atomic(
input: &[u8],
data_offset: usize,
writer: &mut BitWriter<'_>,
parms: Parms,
) -> Result<()> {
let dtype_bits = parms.size * 8;
if parms.minbits == 0 || parms.minbits > dtype_bits {
return Err(Error::InvalidFormat(
"invalid scaleoffset minimum bit count".into(),
));
}
if parms.order == ORDER_LE {
let begin = parms.size - 1 - (dtype_bits - parms.minbits) / 8;
for k in (0..=begin).rev() {
scaleoffset_compress_one_byte(input, data_offset, k, begin, writer, parms, dtype_bits)?;
}
} else {
let begin = (dtype_bits - parms.minbits) / 8;
for k in begin..parms.size {
scaleoffset_compress_one_byte(input, data_offset, k, begin, writer, parms, dtype_bits)?;
}
}
Ok(())
}
fn decompress_byte(
out: &mut [u8],
data_offset: usize,
k: usize,
begin: usize,
stream: &mut BitStream<'_>,
parms: Parms,
dtype_bits: usize,
) -> Result<()> {
let mut bits_to_copy = if k == begin {
8 - (dtype_bits - parms.minbits) % 8
} else {
8
};
let out_idx = data_offset + k;
let mut val = u32::from(
*stream
.data
.get(stream.byte)
.ok_or_else(|| Error::InvalidFormat("scaleoffset data too short".into()))?,
);
if stream.bits_left > bits_to_copy {
out[out_idx] =
((val >> (stream.bits_left - bits_to_copy)) & !(u32::MAX << bits_to_copy)) as u8;
stream.bits_left -= bits_to_copy;
} else {
out[out_idx] =
((val & !(u32::MAX << stream.bits_left)) << (bits_to_copy - stream.bits_left)) as u8;
bits_to_copy -= stream.bits_left;
stream.next_byte();
if bits_to_copy == 0 {
return Ok(());
}
val = u32::from(
*stream
.data
.get(stream.byte)
.ok_or_else(|| Error::InvalidFormat("scaleoffset data too short".into()))?,
);
out[out_idx] |=
((val >> (stream.bits_left - bits_to_copy)) & !(u32::MAX << bits_to_copy)) as u8;
stream.bits_left -= bits_to_copy;
}
Ok(())
}
fn scaleoffset_compress_one_byte(
input: &[u8],
data_offset: usize,
k: usize,
begin: usize,
writer: &mut BitWriter<'_>,
parms: Parms,
dtype_bits: usize,
) -> Result<()> {
let bits_to_copy = if k == begin {
8 - (dtype_bits - parms.minbits) % 8
} else {
8
};
let idx = data_offset
.checked_add(k)
.ok_or_else(|| Error::InvalidFormat("scaleoffset input offset overflow".into()))?;
let byte = *input
.get(idx)
.ok_or_else(|| Error::InvalidFormat("scaleoffset input offset out of range".into()))?;
let mask = if bits_to_copy == 8 {
0xff
} else {
((1u16 << bits_to_copy) - 1) as u8
};
writer.write_bits(u16::from(byte & mask), bits_to_copy)
}
macro_rules! postprocess_integer_typed {
($prim:ty, $n:literal, $out:expr, $order:expr, $minbits:expr, $minval:expr, $fill:expr) => {{
let minv = $minval as $prim;
let width_bits: usize = $n * 8;
let marker: Option<$prim> = if $minbits > 0 && $minbits < width_bits {
Some(((1 as $prim) << $minbits).wrapping_sub(1))
} else {
None
};
let fillv: Option<$prim> = $fill.map(|f| f as $prim);
let le = $order == ORDER_LE;
for chunk in $out.chunks_exact_mut($n) {
let mut bytes = [0u8; $n];
bytes.copy_from_slice(chunk);
let value = if le {
<$prim>::from_le_bytes(bytes)
} else {
<$prim>::from_be_bytes(bytes)
};
let result = if let (Some(f), Some(m)) = (fillv, marker) {
if value == m {
f
} else {
minv.wrapping_add(value)
}
} else if $minbits == 0 {
minv
} else {
minv.wrapping_add(value)
};
let outb = if le {
result.to_le_bytes()
} else {
result.to_be_bytes()
};
chunk.copy_from_slice(&outb);
}
}};
}
fn postprocess_integer(
out: &mut [u8],
size: usize,
sign: u32,
order: u32,
minbits: usize,
minval: u128,
fill: Option<u128>,
) -> Result<()> {
debug_assert!(sign == SIGN_UNSIGNED || sign == SIGN_TWOS);
match size {
1 => postprocess_integer_typed!(u8, 1, out, order, minbits, minval, fill),
2 => postprocess_integer_typed!(u16, 2, out, order, minbits, minval, fill),
4 => postprocess_integer_typed!(u32, 4, out, order, minbits, minval, fill),
8 => postprocess_integer_typed!(u64, 8, out, order, minbits, minval, fill),
_ => postprocess_integer_wide(out, size, order, minbits, minval, fill),
}
Ok(())
}
fn postprocess_integer_wide(
out: &mut [u8],
size: usize,
order: u32,
minbits: usize,
minval: u128,
fill: Option<u128>,
) {
let fill_marker = if minbits > 0 && minbits < 128 {
Some((1u128 << minbits) - 1)
} else if minbits == 128 {
Some(u128::MAX)
} else {
None
};
for chunk in out.chunks_exact_mut(size) {
let value = read_uint(chunk, order);
let value = if let (Some(fill), Some(marker)) = (fill, fill_marker) {
if value == marker {
fill
} else {
minval.wrapping_add(value)
}
} else if minbits == 0 {
minval
} else {
minval.wrapping_add(value)
};
write_uint(chunk, order, value);
}
}
fn postprocess_float(
out: &mut [u8],
size: usize,
order: u32,
minbits: usize,
minval: u128,
client_data: &[u32],
) -> Result<()> {
let scale = client_data
.get(1)
.copied()
.ok_or_else(|| Error::InvalidFormat("scaleoffset missing scale factor".into()))?
.to_ne_bytes();
let scale = i32::from_ne_bytes(scale);
let divisor = 10f64.powi(scale);
let marker = if minbits > 0 && minbits < 128 {
Some((1u128 << minbits) - 1)
} else {
None
};
let fill = if client_data.get(PARM_FILAVAIL).copied().unwrap_or(0) != 0 {
Some(read_fill_value(client_data, size, order))
} else {
None
};
match size {
4 => {
let min = f64::from(f32::from_le_bytes(low_u32(minval).to_le_bytes()));
let fill = fill.map(|v| f32::from_le_bytes(low_u32(v).to_le_bytes()));
for chunk in out.chunks_exact_mut(size) {
let packed = read_uint(chunk, order);
let value = if let (Some(marker), Some(fill)) = (marker, fill) {
if packed == marker {
fill
} else {
(signed_low_i64(packed) as f64 / divisor + min) as f32
}
} else {
(signed_low_i64(packed) as f64 / divisor + min) as f32
};
write_float32(chunk, order, value);
}
}
8 => {
let min = f64::from_le_bytes(low_u64(minval).to_le_bytes());
let fill = fill.map(|v| f64::from_le_bytes(low_u64(v).to_le_bytes()));
for chunk in out.chunks_exact_mut(size) {
let packed = read_uint(chunk, order);
let value = if let (Some(marker), Some(fill)) = (marker, fill) {
if packed == marker {
fill
} else {
signed_low_i64(packed) as f64 / divisor + min
}
} else {
signed_low_i64(packed) as f64 / divisor + min
};
write_float64(chunk, order, value);
}
}
_ => {
return Err(Error::Unsupported(format!(
"scaleoffset floating-point size {size}"
)));
}
}
Ok(())
}
fn read_uint(bytes: &[u8], order: u32) -> u128 {
let mut value = 0u128;
if order == ORDER_LE {
for (idx, byte) in bytes.iter().take(16).enumerate() {
value |= u128::from(*byte) << (idx * 8);
}
} else {
for byte in bytes.iter().take(16) {
value = (value << 8) | u128::from(*byte);
}
}
value
}
fn read_int(bytes: &[u8], order: u32) -> i128 {
read_int_from_bits(read_uint(bytes, order), bytes.len())
}
fn read_int_from_bits(value: u128, size: usize) -> i128 {
let bits = size.saturating_mul(8).min(128);
if bits == 0 {
return 0;
}
let shift = 128 - bits;
((value << shift) as i128) >> shift
}
fn signed_to_uint_bits(value: i128, size: usize) -> u128 {
let bits = size.saturating_mul(8).min(128);
if bits == 128 {
value as u128
} else {
(value as u128) & ((1u128 << bits) - 1)
}
}
fn signed_abs(value: i128) -> u128 {
if value == i128::MIN {
1u128 << 127
} else {
value.unsigned_abs()
}
}
fn signed_delta(value: i128, minval: i128) -> u128 {
debug_assert!(value >= minval);
if minval >= 0 {
(value - minval) as u128
} else if value < 0 {
signed_abs(minval) - signed_abs(value)
} else {
signed_abs(minval) + value as u128
}
}
fn minbits_for_delta(delta: u128) -> Result<usize> {
if delta == 0 {
Ok(0)
} else {
usize::try_from(u128::BITS - delta.leading_zeros())
.map_err(|_| Error::InvalidFormat("scaleoffset bit count overflow".into()))
}
}
fn scaleoffset_integer_minbits(delta: u128, has_fill: bool, size: usize) -> Result<usize> {
let delta = if has_fill {
delta.saturating_add(1)
} else {
delta
};
Ok(minbits_for_delta(delta)?.min(size * 8))
}
fn fill_marker(minbits: usize) -> u128 {
if minbits >= 128 {
u128::MAX
} else {
(1u128 << minbits) - 1
}
}
fn low_u32(value: u128) -> u32 {
let bytes = value.to_le_bytes();
u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]])
}
fn low_u64(value: u128) -> u64 {
let bytes = value.to_le_bytes();
u64::from_le_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
])
}
fn signed_low_i64(value: u128) -> i64 {
i64::from_le_bytes(low_u64(value).to_le_bytes())
}
fn write_uint(bytes: &mut [u8], order: u32, value: u128) {
if order == ORDER_LE {
for (idx, byte) in bytes.iter_mut().take(16).enumerate() {
*byte = (value >> (idx * 8)) as u8;
}
} else {
let n = bytes.len().min(16);
for (idx, byte) in bytes.iter_mut().take(n).enumerate() {
*byte = (value >> ((n - idx - 1) * 8)) as u8;
}
}
}
fn read_le_u128(bytes: &[u8]) -> u128 {
let mut value = 0u128;
for (idx, byte) in bytes.iter().take(16).enumerate() {
value |= u128::from(*byte) << (idx * 8);
}
value
}
fn read_u32_le_at(data: &[u8], offset: usize, context: &str) -> Result<u32> {
let bytes = checked_window(data, offset, 4, context)?;
Ok(u32::from_le_bytes(bytes.try_into().map_err(|_| {
Error::InvalidFormat(format!("{context} is truncated"))
})?))
}
fn scaleoffset_usize(value: u32, context: &'static str) -> Result<usize> {
usize::try_from(value)
.map_err(|_| Error::InvalidFormat(format!("{context} does not fit in usize")))
}
fn checked_window<'a>(
data: &'a [u8],
offset: usize,
len: usize,
context: &str,
) -> Result<&'a [u8]> {
let end = offset
.checked_add(len)
.ok_or_else(|| Error::InvalidFormat(format!("{context} offset overflow")))?;
data.get(offset..end)
.ok_or_else(|| Error::InvalidFormat(format!("{context} is truncated")))
}
fn checked_window_mut<'a>(
data: &'a mut [u8],
offset: usize,
len: usize,
context: &str,
) -> Result<&'a mut [u8]> {
let end = offset
.checked_add(len)
.ok_or_else(|| Error::InvalidFormat(format!("{context} offset overflow")))?;
data.get_mut(offset..end)
.ok_or_else(|| Error::InvalidFormat(format!("{context} is truncated")))
}
fn read_fill_value(client_data: &[u32], size: usize, _order: u32) -> u128 {
let mut raw = [0u8; 16];
let mut pos = 0usize;
for value in client_data.iter().skip(PARM_FILVAL) {
let bytes = value.to_le_bytes();
for byte in bytes {
if pos < size {
raw[pos] = byte;
pos += 1;
}
}
}
read_uint(&raw[..size], ORDER_LE)
}
fn write_float32(bytes: &mut [u8], order: u32, value: f32) {
let raw = if order == ORDER_LE {
value.to_le_bytes()
} else {
value.to_be_bytes()
};
bytes[..4].copy_from_slice(&raw);
}
fn write_float64(bytes: &mut [u8], order: u32, value: f64) {
let raw = if order == ORDER_LE {
value.to_le_bytes()
} else {
value.to_be_bytes()
};
bytes[..8].copy_from_slice(&raw);
}
struct BitStream<'a> {
data: &'a [u8],
byte: usize,
bits_left: usize,
}
impl<'a> BitStream<'a> {
fn new(data: &'a [u8]) -> Self {
Self {
data,
byte: 0,
bits_left: 8,
}
}
fn next_byte(&mut self) {
self.byte += 1;
self.bits_left = 8;
}
}
struct BitWriter<'a> {
out: &'a mut Vec<u8>,
current: u8,
bits_used: usize,
}
impl<'a> BitWriter<'a> {
fn new(out: &'a mut Vec<u8>) -> Self {
Self {
out,
current: 0,
bits_used: 0,
}
}
fn next_byte(&mut self) {
self.out.push(self.current);
self.current = 0;
self.bits_used = 0;
}
fn write_bits(&mut self, value: u16, mut nbits: usize) -> Result<()> {
if nbits > 16 {
return Err(Error::InvalidFormat("scaleoffset bit run too long".into()));
}
while nbits > 0 {
let free = 8 - self.bits_used;
let take = free.min(nbits);
let shift = nbits - take;
let mask = if take == 16 {
u16::MAX
} else {
(1u16 << take) - 1
};
let bits = u8::try_from((value >> shift) & mask)
.map_err(|_| Error::InvalidFormat("scaleoffset bit run exceeds byte".into()))?;
self.current |= bits << (free - take);
self.bits_used += take;
nbits -= take;
if self.bits_used == 8 {
self.next_byte();
}
}
Ok(())
}
fn finish(mut self) {
if self.bits_used != 0 {
self.next_byte();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn header(minbits: u32, minval_size: u8) -> Vec<u8> {
let mut data = vec![0u8; HEADER_LEN];
data[..4].copy_from_slice(&minbits.to_le_bytes());
data[4] = minval_size;
data
}
fn decompress_err(data: &[u8], params: &[u32]) -> Error {
let mut out = Vec::new();
decompress_into(data, params, &mut out).unwrap_err()
}
fn integer_params(nelmts: u32, size: u32) -> Vec<u32> {
vec![2, 0, nelmts, CLS_INTEGER, size, SIGN_UNSIGNED, ORDER_LE]
}
#[test]
fn rejects_missing_client_data() {
let err = decompress_err(&[], &[0, 0, 1]);
assert!(
err.to_string()
.contains("scaleoffset filter missing datatype parameters"),
"unexpected error: {err}"
);
}
#[test]
fn rejects_invalid_float_scale_type() {
let params = vec![1, 2, 1, CLS_FLOAT, 4, SIGN_UNSIGNED, ORDER_LE];
let err = decompress_err(&header(0, 0), ¶ms);
assert!(
err.to_string().contains("scaleoffset float scale type 1"),
"unexpected error: {err}"
);
}
#[test]
fn rejects_full_precision_output_size_mismatch() {
let params = vec![2, 0, 2, CLS_INTEGER, 4, SIGN_UNSIGNED, ORDER_LE];
let mut data = header(32, 0);
data.extend_from_slice(&1u32.to_le_bytes());
let err = decompress_err(&data, ¶ms);
assert!(
err.to_string()
.contains("scaleoffset full-precision data too short"),
"unexpected error: {err}"
);
}
#[test]
fn rejects_invalid_integer_sign_even_for_empty_chunks() {
let params = vec![2, 0, 0, CLS_INTEGER, 4, 99, ORDER_LE];
let err = decompress_err(&header(0, 0), ¶ms);
assert!(
err.to_string()
.contains("invalid scaleoffset integer sign 99"),
"unexpected error: {err}"
);
}
#[test]
fn rejects_invalid_integer_scale_type_even_for_empty_chunks() {
let params = vec![0, 0, 0, CLS_INTEGER, 4, SIGN_UNSIGNED, ORDER_LE];
let err = decompress_err(&header(0, 0), ¶ms);
assert!(
err.to_string()
.contains("invalid scaleoffset integer scale type 0"),
"unexpected error: {err}"
);
}
#[test]
fn rejects_unsupported_datatype_class_before_chunk_header() {
let params = vec![2, 0, 0, 99, 4, SIGN_UNSIGNED, ORDER_LE];
let err = decompress_err(&[], ¶ms);
assert!(
err.to_string().contains("scaleoffset datatype class 99"),
"unexpected error: {err}"
);
}
#[test]
fn rejects_unsupported_float_size_before_chunk_header() {
let params = vec![0, 0, 0, CLS_FLOAT, 2, SIGN_UNSIGNED, ORDER_LE];
let err = decompress_err(&[], ¶ms);
assert!(
err.to_string()
.contains("scaleoffset floating-point size 2"),
"unexpected error: {err}"
);
}
#[test]
fn rejects_unsupported_float_scale_type_before_chunk_header() {
let params = vec![1, 0, 0, CLS_FLOAT, 4, SIGN_UNSIGNED, ORDER_LE];
let err = decompress_err(&[], ¶ms);
assert!(
err.to_string().contains("scaleoffset float scale type 1"),
"unexpected error: {err}"
);
}
#[test]
fn rejects_truncated_fill_value_parameters_before_chunk_header() {
let params = vec![2, 0, 0, CLS_INTEGER, 1, SIGN_UNSIGNED, ORDER_LE, 1];
let err = decompress_err(&[], ¶ms);
assert!(
err.to_string()
.contains("scaleoffset fill value parameters are truncated"),
"unexpected error: {err}"
);
}
#[test]
fn rejects_partial_multibyte_fill_value_parameters_before_chunk_header() {
let params = vec![2, 0, 0, CLS_INTEGER, 8, SIGN_UNSIGNED, ORDER_LE, 1, 0];
let err = decompress_err(&[], ¶ms);
assert!(
err.to_string()
.contains("scaleoffset fill value parameters are truncated"),
"unexpected error: {err}"
);
}
#[test]
fn rejects_minbits_larger_than_datatype_even_for_empty_chunks() {
let params = integer_params(0, 4);
let err = decompress_err(&header(33, 0), ¶ms);
assert!(
err.to_string()
.contains("invalid scaleoffset minimum bit count"),
"unexpected error: {err}"
);
}
#[test]
fn rejects_datatype_sizes_beyond_internal_integer_width() {
let params = vec![2, 0, 0, CLS_INTEGER, 17, SIGN_UNSIGNED, ORDER_LE];
let err = decompress_err(&header(0, 0), ¶ms);
assert!(
err.to_string()
.contains("scaleoffset datatype size 17 exceeds 16-byte arithmetic support"),
"unexpected error: {err}"
);
}
#[test]
fn rejects_truncated_chunk_header() {
let params = integer_params(1, 2);
let err = decompress_err(&header(8, 0)[..HEADER_LEN - 1], ¶ms);
assert!(
err.to_string().contains("scaleoffset data too short"),
"unexpected error: {err}"
);
}
#[test]
fn rejects_invalid_minimum_value_header_size() {
let params = integer_params(1, 2);
let err = decompress_err(&header(0, 17), ¶ms);
assert!(
err.to_string()
.contains("invalid scaleoffset minimum value header"),
"unexpected error: {err}"
);
}
#[test]
fn rejects_truncated_packed_payload() {
let params = integer_params(2, 2);
let mut data = header(9, 0);
data.push(0xff);
let err = decompress_err(&data, ¶ms);
assert!(
err.to_string().contains("scaleoffset data too short"),
"unexpected error: {err}"
);
}
#[test]
fn decompress_into_preserves_output_on_malformed_payloads() {
let params = integer_params(2, 2);
let mut data = header(9, 0);
data.push(0xff);
let mut out = b"stale".to_vec();
let err = decompress_into(&data, ¶ms, &mut out).unwrap_err();
assert!(
err.to_string().contains("scaleoffset data too short"),
"unexpected error: {err}"
);
assert_eq!(out, b"stale");
let mut full_precision = header(16, 0);
full_precision.extend_from_slice(&1u16.to_le_bytes());
let err = decompress_into(&full_precision, ¶ms, &mut out).unwrap_err();
assert!(
err.to_string()
.contains("scaleoffset full-precision data too short"),
"unexpected error: {err}"
);
assert_eq!(out, b"stale");
}
#[test]
fn rejects_truncated_full_precision_payload_generated_in_memory() {
let params = integer_params(2, 2);
let mut data = header(16, 0);
data.extend_from_slice(&1u16.to_le_bytes());
let err = decompress_err(&data, ¶ms);
assert!(
err.to_string()
.contains("scaleoffset full-precision data too short"),
"unexpected error: {err}"
);
}
#[test]
fn checked_window_rejects_offset_overflow() {
let err = checked_window(&[], usize::MAX, 1, "scaleoffset test window").unwrap_err();
assert!(
err.to_string()
.contains("scaleoffset test window offset overflow"),
"unexpected error: {err}"
);
}
#[test]
fn checked_window_mut_rejects_offset_overflow() {
let mut data = [];
let err = checked_window_mut(&mut data, usize::MAX, 1, "scaleoffset test mutable window")
.unwrap_err();
assert!(
err.to_string()
.contains("scaleoffset test mutable window offset overflow"),
"unexpected error: {err}"
);
}
#[test]
fn scaleoffset_integer_compress_roundtrips() {
let params = vec![2, 0, 4, CLS_INTEGER, 2, SIGN_UNSIGNED, ORDER_LE];
let input = [10u16, 11, 12, 16]
.into_iter()
.flat_map(u16::to_le_bytes)
.collect::<Vec<_>>();
let mut compressed = Vec::new();
scaleoffset_compress_into(&input, ¶ms, &mut compressed).unwrap();
let mut decoded = Vec::new();
decompress_into(&compressed, ¶ms, &mut decoded).unwrap();
assert_eq!(decoded, input);
}
#[test]
fn scaleoffset_compress_into_appends_and_preserves_output_on_errors() {
let params = vec![2, 0, 4, CLS_INTEGER, 2, SIGN_UNSIGNED, ORDER_LE];
let input = [10u16, 11, 12, 16]
.into_iter()
.flat_map(u16::to_le_bytes)
.collect::<Vec<_>>();
let mut out = b"prefix".to_vec();
scaleoffset_compress_into(&input, ¶ms, &mut out).unwrap();
assert_eq!(&out[..6], b"prefix");
let mut decoded = Vec::new();
decompress_into(&out[6..], ¶ms, &mut decoded).unwrap();
assert_eq!(decoded, input);
let stale = out.clone();
let err =
scaleoffset_compress_into(&input[..input.len() - 1], ¶ms, &mut out).unwrap_err();
assert!(
err.to_string().contains("scaleoffset input data too short"),
"unexpected error: {err}"
);
assert_eq!(out, stale);
}
#[test]
fn filter_scaleoffset_into_preserves_output_on_invalid_client_data() {
let params = vec![2, 0, 1, CLS_INTEGER, 2, SIGN_UNSIGNED, ORDER_LE, 1];
let mut out = b"stale".to_vec();
let err = filter_scaleoffset_into(&[10, 0], ¶ms, false, &mut out)
.expect_err("invalid ScaleOffset client data should fail before output mutation");
assert!(
err.to_string()
.contains("scaleoffset fill value parameters are truncated"),
"unexpected error: {err}"
);
assert_eq!(out, b"stale");
}
#[test]
fn filter_scaleoffset_into_decompress_appends_and_preserves_output_on_malformed_payload() {
let params = vec![2, 0, 4, CLS_INTEGER, 2, SIGN_UNSIGNED, ORDER_LE];
let input = [10u16, 11, 12, 16]
.into_iter()
.flat_map(u16::to_le_bytes)
.collect::<Vec<_>>();
let mut compressed = Vec::new();
scaleoffset_compress_into(&input, ¶ms, &mut compressed).unwrap();
let mut out = b"prefix".to_vec();
filter_scaleoffset_into(&compressed, ¶ms, true, &mut out).unwrap();
assert_eq!(&out[..6], b"prefix");
assert_eq!(&out[6..], input.as_slice());
let stale = out.clone();
let mut malformed = header(9, 0);
malformed.push(0xff);
let err = filter_scaleoffset_into(&malformed, ¶ms, true, &mut out).unwrap_err();
assert!(
err.to_string().contains("scaleoffset data too short"),
"unexpected error: {err}"
);
assert_eq!(out, stale);
}
#[test]
fn scaleoffset_u128_compress_roundtrips_wide_deltas() {
let params = vec![2, 0, 3, CLS_INTEGER, 16, SIGN_UNSIGNED, ORDER_LE];
let base = (1u128 << 80) + 5;
let input = [base, base + 1, base + (1u128 << 65) + 3]
.into_iter()
.flat_map(u128::to_le_bytes)
.collect::<Vec<_>>();
let mut compressed = Vec::new();
scaleoffset_compress_into(&input, ¶ms, &mut compressed).unwrap();
assert_eq!(&compressed[..4], &66u32.to_le_bytes());
assert_eq!(&compressed[5..21], &base.to_le_bytes());
let mut decoded = Vec::new();
decompress_into(&compressed, ¶ms, &mut decoded).unwrap();
assert_eq!(decoded, input);
}
#[test]
fn scaleoffset_integer_full_precision_payload_is_raw_libhdf5_passthrough() {
let params = vec![2, 0, 2, CLS_INTEGER, 1, SIGN_UNSIGNED, ORDER_LE];
let mut encoded = header(8, 1);
encoded[5] = 100;
encoded.extend_from_slice(&[100, 255]);
let mut decoded = Vec::new();
decompress_into(&encoded, ¶ms, &mut decoded).unwrap();
assert_eq!(decoded, [100, 255]);
let mut compressed = Vec::new();
scaleoffset_compress_into(&[100, 255], ¶ms, &mut compressed).unwrap();
assert_eq!(&compressed[HEADER_LEN..], &[100, 255]);
let mut roundtripped = Vec::new();
decompress_into(&compressed, ¶ms, &mut roundtripped).unwrap();
assert_eq!(roundtripped, [100, 255]);
}
#[test]
fn scaleoffset_integer_compress_respects_fixed_minbits() {
let params = vec![2, 2, 4, CLS_INTEGER, 2, SIGN_UNSIGNED, ORDER_LE];
let input = [10u16, 11, 12, 16]
.into_iter()
.flat_map(u16::to_le_bytes)
.collect::<Vec<_>>();
let mut compressed = Vec::new();
scaleoffset_compress_into(&input, ¶ms, &mut compressed).unwrap();
assert_eq!(&compressed[..4], &2u32.to_le_bytes());
assert_eq!(&compressed[5..7], &10u16.to_le_bytes());
let mut decoded = Vec::new();
decompress_into(&compressed, ¶ms, &mut decoded).unwrap();
let expected = [10u16, 11, 12, 12]
.into_iter()
.flat_map(u16::to_le_bytes)
.collect::<Vec<_>>();
assert_eq!(decoded, expected);
}
#[test]
fn scaleoffset_big_endian_integer_compress_respects_fixed_minbits() {
let params = vec![2, 3, 4, CLS_INTEGER, 2, SIGN_UNSIGNED, ORDER_BE];
let input = [0x0100u16, 0x0101, 0x0107, 0x0108]
.into_iter()
.flat_map(u16::to_be_bytes)
.collect::<Vec<_>>();
let mut compressed = Vec::new();
scaleoffset_compress_into(&input, ¶ms, &mut compressed).unwrap();
assert_eq!(&compressed[..4], &3u32.to_le_bytes());
assert_eq!(&compressed[5..7], &0x0100u16.to_le_bytes());
let mut decoded = Vec::new();
decompress_into(&compressed, ¶ms, &mut decoded).unwrap();
let expected = [0x0100u16, 0x0101, 0x0107, 0x0100]
.into_iter()
.flat_map(u16::to_be_bytes)
.collect::<Vec<_>>();
assert_eq!(decoded, expected);
}
#[test]
fn scaleoffset_integer_compress_caps_oversized_fixed_minbits() {
let params = vec![2, 100, 2, CLS_INTEGER, 2, SIGN_UNSIGNED, ORDER_LE];
let input = [1u16, 257]
.into_iter()
.flat_map(u16::to_le_bytes)
.collect::<Vec<_>>();
let mut compressed = Vec::new();
scaleoffset_compress_into(&input, ¶ms, &mut compressed).unwrap();
assert_eq!(&compressed[..4], &16u32.to_le_bytes());
let mut decoded = Vec::new();
decompress_into(&compressed, ¶ms, &mut decoded).unwrap();
assert_eq!(decoded, input);
}
#[test]
fn scaleoffset_integer_compress_reserves_fill_marker() {
let params = vec![2, 0, 4, CLS_INTEGER, 1, SIGN_UNSIGNED, ORDER_LE, 1, 250];
let input = [10, 250, 12, 13];
let mut compressed = Vec::new();
scaleoffset_compress_into(&input, ¶ms, &mut compressed).unwrap();
assert_eq!(&compressed[..4], &3u32.to_le_bytes());
assert_eq!(compressed[5], 10);
assert_eq!(&compressed[HEADER_LEN..], &[0x1d, 0x30]);
let mut decoded = Vec::new();
decompress_into(&compressed, ¶ms, &mut decoded).unwrap();
assert_eq!(decoded, input);
}
#[test]
fn scaleoffset_big_endian_integer_compress_reserves_fill_marker() {
let fill = 0x1234u16;
let fill_client_word = u32::from(fill);
let params = vec![
2,
0,
4,
CLS_INTEGER,
2,
SIGN_UNSIGNED,
ORDER_BE,
1,
fill_client_word,
];
let input = [0x0100u16, fill, 0x0103, 0x0104]
.into_iter()
.flat_map(u16::to_be_bytes)
.collect::<Vec<_>>();
let mut compressed = Vec::new();
scaleoffset_compress_into(&input, ¶ms, &mut compressed).unwrap();
assert_eq!(&compressed[..4], &3u32.to_le_bytes());
assert_eq!(&compressed[5..7], &0x0100u16.to_le_bytes());
let mut decoded = Vec::new();
decompress_into(&compressed, ¶ms, &mut decoded).unwrap();
assert_eq!(decoded, input);
}
#[test]
fn scaleoffset_signed_integer_compress_reserves_fill_marker() {
let params = vec![2, 0, 3, CLS_INTEGER, 1, SIGN_TWOS, ORDER_LE, 1, 0xf7];
let input = [0xfe, 0xf7, 0x01];
let mut compressed = Vec::new();
scaleoffset_compress_into(&input, ¶ms, &mut compressed).unwrap();
assert_eq!(&compressed[..4], &3u32.to_le_bytes());
assert_eq!(compressed[5], 0xfe);
let mut decoded = Vec::new();
decompress_into(&compressed, ¶ms, &mut decoded).unwrap();
assert_eq!(decoded, input);
}
#[test]
fn scaleoffset_i64_signed_integer_compress_reserves_fill_marker_for_both_endians() {
let fill = -9_223_372_036_854_775_000i64;
let fill_bytes = fill.to_le_bytes();
let fill_low = u32::from_le_bytes(fill_bytes[..4].try_into().unwrap());
let fill_high = u32::from_le_bytes(fill_bytes[4..].try_into().unwrap());
let little_params = vec![
2,
0,
4,
CLS_INTEGER,
8,
SIGN_TWOS,
ORDER_LE,
1,
fill_low,
fill_high,
];
let little_input = [fill + 10, fill, fill + 12, fill + 13]
.into_iter()
.flat_map(i64::to_le_bytes)
.collect::<Vec<_>>();
let mut little_compressed = Vec::new();
scaleoffset_compress_into(&little_input, &little_params, &mut little_compressed).unwrap();
let mut little_decoded = Vec::new();
decompress_into(&little_compressed, &little_params, &mut little_decoded).unwrap();
assert_eq!(little_decoded, little_input);
let big_params = vec![
2,
0,
4,
CLS_INTEGER,
8,
SIGN_TWOS,
ORDER_BE,
1,
fill_low,
fill_high,
];
let big_input = [fill + 10, fill, fill + 12, fill + 13]
.into_iter()
.flat_map(i64::to_be_bytes)
.collect::<Vec<_>>();
let mut big_compressed = Vec::new();
scaleoffset_compress_into(&big_input, &big_params, &mut big_compressed).unwrap();
let mut big_decoded = Vec::new();
decompress_into(&big_compressed, &big_params, &mut big_decoded).unwrap();
assert_eq!(big_decoded, big_input);
}
#[test]
fn scaleoffset_signed_integer_uses_signed_minimum() {
let params = vec![2, 0, 3, CLS_INTEGER, 1, SIGN_TWOS, ORDER_LE];
let input = [0xff, 0x00, 0x01];
let mut compressed = Vec::new();
scaleoffset_compress_into(&input, ¶ms, &mut compressed).unwrap();
assert_eq!(&compressed[..4], &2u32.to_le_bytes());
assert_eq!(compressed[4], 1);
assert_eq!(compressed[5], 0xff);
assert_eq!(compressed.len(), HEADER_LEN + 1);
let mut decoded = Vec::new();
decompress_into(&compressed, ¶ms, &mut decoded).unwrap();
assert_eq!(decoded, input);
}
#[test]
fn scaleoffset_signed_integer_compress_respects_fixed_minbits() {
let params = vec![2, 2, 5, CLS_INTEGER, 2, SIGN_TWOS, ORDER_LE];
let input = [-2i16, -1, 0, 1, 2]
.into_iter()
.flat_map(i16::to_le_bytes)
.collect::<Vec<_>>();
let mut compressed = Vec::new();
scaleoffset_compress_into(&input, ¶ms, &mut compressed).unwrap();
assert_eq!(&compressed[..4], &2u32.to_le_bytes());
assert_eq!(compressed[4], 2);
assert_eq!(&compressed[5..7], &(-2i16).to_le_bytes());
let mut decoded = Vec::new();
decompress_into(&compressed, ¶ms, &mut decoded).unwrap();
let expected = [-2i16, -1, 0, 1, -2]
.into_iter()
.flat_map(i16::to_le_bytes)
.collect::<Vec<_>>();
assert_eq!(decoded, expected);
}
#[test]
fn scaleoffset_signed_integer_constant_negative_values_omit_payload() {
let params = vec![2, 0, 3, CLS_INTEGER, 2, SIGN_TWOS, ORDER_LE];
let input = [-123i16, -123, -123]
.into_iter()
.flat_map(i16::to_le_bytes)
.collect::<Vec<_>>();
let mut compressed = Vec::new();
scaleoffset_compress_into(&input, ¶ms, &mut compressed).unwrap();
assert_eq!(&compressed[..4], &0u32.to_le_bytes());
assert_eq!(compressed[4], 2);
assert_eq!(&compressed[5..7], &(-123i16).to_le_bytes());
assert_eq!(compressed.len(), HEADER_LEN);
let mut decoded = Vec::new();
decompress_into(&compressed, ¶ms, &mut decoded).unwrap();
assert_eq!(decoded, input);
}
#[test]
fn scaleoffset_signed_integer_big_endian_constant_negative_values_omit_payload() {
let params = vec![2, 0, 3, CLS_INTEGER, 2, SIGN_TWOS, ORDER_BE];
let input = [-321i16, -321, -321]
.into_iter()
.flat_map(i16::to_be_bytes)
.collect::<Vec<_>>();
let mut compressed = Vec::new();
scaleoffset_compress_into(&input, ¶ms, &mut compressed).unwrap();
assert_eq!(&compressed[..4], &0u32.to_le_bytes());
assert_eq!(compressed[4], 2);
assert_eq!(&compressed[5..7], &(-321i16).to_le_bytes());
assert_eq!(compressed.len(), HEADER_LEN);
let mut decoded = Vec::new();
decompress_into(&compressed, ¶ms, &mut decoded).unwrap();
assert_eq!(decoded, input);
}
#[test]
fn scaleoffset_signed_integer_big_endian_roundtrips_crossing_zero() {
let params = vec![2, 0, 3, CLS_INTEGER, 2, SIGN_TWOS, ORDER_BE];
let input = [0xff, 0xfe, 0xff, 0xff, 0x00, 0x00];
let mut compressed = Vec::new();
scaleoffset_compress_into(&input, ¶ms, &mut compressed).unwrap();
assert_eq!(&compressed[..4], &2u32.to_le_bytes());
assert_eq!(compressed[4], 2);
assert_eq!(&compressed[5..7], &[0xfe, 0xff]);
let mut decoded = Vec::new();
decompress_into(&compressed, ¶ms, &mut decoded).unwrap();
assert_eq!(decoded, input);
}
#[test]
fn scaleoffset_float_compress_reserves_fill_marker() {
let fill = -999.0f32;
let mut params = vec![0, 1, 4, CLS_FLOAT, 4, SIGN_UNSIGNED, ORDER_LE, 1];
params.push(fill.to_bits());
let input = [1.0f32, fill, 1.2, 1.3]
.into_iter()
.flat_map(f32::to_le_bytes)
.collect::<Vec<_>>();
let mut compressed = Vec::new();
scaleoffset_compress_into(&input, ¶ms, &mut compressed).unwrap();
assert_eq!(&compressed[..4], &3u32.to_le_bytes());
assert_eq!(&compressed[5..9], &1.0f32.to_le_bytes());
let mut decoded = Vec::new();
decompress_into(&compressed, ¶ms, &mut decoded).unwrap();
let values = decoded
.chunks_exact(4)
.map(|chunk| f32::from_le_bytes(chunk.try_into().unwrap()))
.collect::<Vec<_>>();
assert_eq!(values, [1.0, fill, 1.2, 1.3]);
}
#[test]
fn scaleoffset_big_endian_float_compress_reserves_fill_marker() {
let fill = -999.0f32;
let fill_client_word = fill.to_bits();
let params = vec![
0,
1,
4,
CLS_FLOAT,
4,
SIGN_UNSIGNED,
ORDER_BE,
1,
fill_client_word,
];
let input = [1.0f32, fill, 1.2, 1.3]
.into_iter()
.flat_map(f32::to_be_bytes)
.collect::<Vec<_>>();
let mut compressed = Vec::new();
scaleoffset_compress_into(&input, ¶ms, &mut compressed).unwrap();
assert_eq!(&compressed[..4], &3u32.to_le_bytes());
assert_eq!(&compressed[5..9], &1.0f32.to_le_bytes());
let mut decoded = Vec::new();
decompress_into(&compressed, ¶ms, &mut decoded).unwrap();
let values = decoded
.chunks_exact(4)
.map(|chunk| f32::from_be_bytes(chunk.try_into().unwrap()))
.collect::<Vec<_>>();
assert_eq!(values, [1.0, fill, 1.2, 1.3]);
}
#[test]
fn scaleoffset_big_endian_f64_compress_reserves_fill_marker() {
let fill = -999.0f64;
let fill_bytes = fill.to_bits().to_le_bytes();
let fill_low = u32::from_le_bytes(fill_bytes[..4].try_into().unwrap());
let fill_high = u32::from_le_bytes(fill_bytes[4..].try_into().unwrap());
let params = vec![
0,
2,
4,
CLS_FLOAT,
8,
SIGN_UNSIGNED,
ORDER_BE,
1,
fill_low,
fill_high,
];
let input = [1.0f64, fill, 1.25, 1.5]
.into_iter()
.flat_map(f64::to_be_bytes)
.collect::<Vec<_>>();
let mut compressed = Vec::new();
scaleoffset_compress_into(&input, ¶ms, &mut compressed).unwrap();
assert_eq!(&compressed[..4], &6u32.to_le_bytes());
assert_eq!(&compressed[5..13], &1.0f64.to_le_bytes());
let mut decoded = Vec::new();
decompress_into(&compressed, ¶ms, &mut decoded).unwrap();
let values = decoded
.chunks_exact(8)
.map(|chunk| f64::from_be_bytes(chunk.try_into().unwrap()))
.collect::<Vec<_>>();
assert_eq!(values, [1.0, fill, 1.25, 1.5]);
}
#[test]
fn scaleoffset_big_endian_f32_constant_values_omit_payload() {
let params = vec![0, 2, 3, CLS_FLOAT, 4, SIGN_UNSIGNED, ORDER_BE];
let input = [3.5f32, 3.5, 3.5]
.into_iter()
.flat_map(f32::to_be_bytes)
.collect::<Vec<_>>();
let mut compressed = Vec::new();
scaleoffset_compress_into(&input, ¶ms, &mut compressed).unwrap();
assert_eq!(&compressed[..4], &0u32.to_le_bytes());
assert_eq!(&compressed[5..9], &3.5f32.to_le_bytes());
assert_eq!(compressed.len(), HEADER_LEN);
let mut decoded = Vec::new();
decompress_into(&compressed, ¶ms, &mut decoded).unwrap();
assert_eq!(decoded, input);
}
#[test]
fn scaleoffset_little_endian_f64_compress_roundtrips_negative_decimal_values() {
let params = vec![0, 2, 4, CLS_FLOAT, 8, SIGN_UNSIGNED, ORDER_LE];
let input = [-2.5f64, -1.25, 0.0, 3.75]
.into_iter()
.flat_map(f64::to_le_bytes)
.collect::<Vec<_>>();
let mut compressed = Vec::new();
scaleoffset_compress_into(&input, ¶ms, &mut compressed).unwrap();
assert_eq!(&compressed[..4], &10u32.to_le_bytes());
assert_eq!(&compressed[5..13], &(-2.5f64).to_le_bytes());
let mut decoded = Vec::new();
decompress_into(&compressed, ¶ms, &mut decoded).unwrap();
let values = decoded
.chunks_exact(8)
.map(|chunk| f64::from_le_bytes(chunk.try_into().unwrap()))
.collect::<Vec<_>>();
assert_eq!(values, [-2.5, -1.25, 0.0, 3.75]);
}
#[test]
fn scaleoffset_big_endian_f64_compress_roundtrips_negative_decimal_values() {
let params = vec![0, 2, 4, CLS_FLOAT, 8, SIGN_UNSIGNED, ORDER_BE];
let input = [-8.5f64, -0.25, 0.0, 4.75]
.into_iter()
.flat_map(f64::to_be_bytes)
.collect::<Vec<_>>();
let mut compressed = Vec::new();
scaleoffset_compress_into(&input, ¶ms, &mut compressed).unwrap();
assert_eq!(&compressed[..4], &11u32.to_le_bytes());
assert_eq!(&compressed[5..13], &(-8.5f64).to_le_bytes());
let mut decoded = Vec::new();
decompress_into(&compressed, ¶ms, &mut decoded).unwrap();
let values = decoded
.chunks_exact(8)
.map(|chunk| f64::from_be_bytes(chunk.try_into().unwrap()))
.collect::<Vec<_>>();
assert_eq!(values, [-8.5, -0.25, 0.0, 4.75]);
}
#[test]
fn scaleoffset_integer_all_fill_values_roundtrip() {
let fill = 0xffffu32;
let params = vec![2, 0, 3, CLS_INTEGER, 2, SIGN_UNSIGNED, ORDER_LE, 1, fill];
let input = [0xffffu16, 0xffff, 0xffff]
.into_iter()
.flat_map(u16::to_le_bytes)
.collect::<Vec<_>>();
let mut compressed = Vec::new();
scaleoffset_compress_into(&input, ¶ms, &mut compressed).unwrap();
assert_eq!(&compressed[..4], &1u32.to_le_bytes());
assert_eq!(&compressed[5..7], &0u16.to_le_bytes());
let mut decoded = Vec::new();
decompress_into(&compressed, ¶ms, &mut decoded).unwrap();
assert_eq!(decoded, input);
}
#[test]
fn scaleoffset_float_all_fill_values_roundtrip() {
let fill = -999.0f64;
let low = fill.to_bits() as u32;
let high = (fill.to_bits() >> 32) as u32;
let params = vec![0, 2, 2, CLS_FLOAT, 8, SIGN_UNSIGNED, ORDER_LE, 1, low, high];
let input = [fill, fill]
.into_iter()
.flat_map(f64::to_le_bytes)
.collect::<Vec<_>>();
let mut compressed = Vec::new();
scaleoffset_compress_into(&input, ¶ms, &mut compressed).unwrap();
assert_eq!(&compressed[..4], &1u32.to_le_bytes());
assert_eq!(&compressed[5..13], &0f64.to_le_bytes());
let mut decoded = Vec::new();
decompress_into(&compressed, ¶ms, &mut decoded).unwrap();
assert_eq!(decoded, input);
}
}