use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use std::io::{self, Cursor};
#[cfg(unix)]
use tracing::debug;
#[cfg(not(unix))]
macro_rules! debug {
($($args:tt)*) => {};
}
type CoordinateData = (Vec<u16>, Vec<u16>, Vec<i16>);
const MAX_BUFFER_SIZE: usize = 65535;
#[derive(Debug, Clone, Copy)]
pub struct EncodingFlags {
pub delta_timestamps: bool,
pub packed_coordinates: bool,
pub masked_x_coords: bool,
}
impl EncodingFlags {
#[allow(dead_code)]
fn to_u32(self) -> u32 {
let mut flags = 0u32;
if self.delta_timestamps {
flags |= 0x1;
}
if self.packed_coordinates {
flags |= 0x2;
}
if self.masked_x_coords {
flags |= 0x4;
}
flags
}
}
#[derive(Debug, Clone, Copy)]
pub struct EventCD {
pub x: u16,
pub y: u16,
pub p: i16,
pub t: i64,
}
pub struct ECFDecoder {
debug: bool,
}
impl Default for ECFDecoder {
fn default() -> Self {
Self::new()
}
}
impl ECFDecoder {
pub fn new() -> Self {
Self { debug: false }
}
pub fn with_debug(mut self, debug: bool) -> Self {
self.debug = debug;
self
}
pub fn decode(&self, compressed_data: &[u8]) -> Result<Vec<EventCD>, io::Error> {
if compressed_data.len() < 4 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"ECF chunk too small for header",
));
}
let mut cursor = Cursor::new(compressed_data);
let header = cursor.read_u32::<LittleEndian>()?;
let num_events = (header >> 3) as usize; let delta_timestamps = (header >> 2) & 1 != 0; let ys_xs_and_ps_packed = (header >> 1) & 1 != 0; let xs_and_ps_packed = header & 1 != 0;
let encoding_flags = EncodingFlags {
delta_timestamps,
packed_coordinates: ys_xs_and_ps_packed || xs_and_ps_packed,
masked_x_coords: false, };
if self.debug {
debug!(header = %format!("0x{:08x}", header), num_events, delta_timestamps, ys_xs_and_ps_packed, xs_and_ps_packed, "ECF header");
}
if num_events == 0 {
return Ok(Vec::new());
}
if num_events > MAX_BUFFER_SIZE {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!(
"ECF chunk too large: {} events (max {})",
num_events, MAX_BUFFER_SIZE
),
));
}
if encoding_flags.delta_timestamps {
self.decode_delta_timestamps(&mut cursor, num_events, encoding_flags)
} else {
self.decode_raw_events(&mut cursor, num_events)
}
}
fn decode_delta_timestamps(
&self,
cursor: &mut Cursor<&[u8]>,
num_events: usize,
flags: EncodingFlags,
) -> Result<Vec<EventCD>, io::Error> {
let base_timestamp = cursor.read_i64::<LittleEndian>()?;
if self.debug {
debug!(base_timestamp, "ECF base timestamp");
}
let coords_and_pols = if flags.packed_coordinates {
self.decode_packed_coordinates(cursor, num_events)?
} else {
self.decode_raw_coordinates(cursor, num_events)?
};
let timestamps = self.decode_timestamp_deltas(cursor, num_events, base_timestamp)?;
let events = coords_and_pols
.0
.into_iter()
.zip(coords_and_pols.1)
.zip(coords_and_pols.2)
.zip(timestamps)
.map(|(((x, y), p), t)| EventCD { x, y, p, t })
.collect();
Ok(events)
}
fn decode_packed_coordinates(
&self,
cursor: &mut Cursor<&[u8]>,
num_events: usize,
) -> Result<CoordinateData, io::Error> {
let mut x_coords = Vec::with_capacity(num_events);
let mut y_coords = Vec::with_capacity(num_events);
let mut polarities = Vec::with_capacity(num_events);
for _ in 0..num_events {
let x = cursor.read_u16::<LittleEndian>()?;
let y = cursor.read_u16::<LittleEndian>()?;
let p = cursor.read_i16::<LittleEndian>()?;
x_coords.push(x);
y_coords.push(y);
polarities.push(p);
}
Ok((x_coords, y_coords, polarities))
}
fn decode_raw_coordinates(
&self,
cursor: &mut Cursor<&[u8]>,
num_events: usize,
) -> Result<CoordinateData, io::Error> {
self.decode_packed_coordinates(cursor, num_events)
}
fn decode_timestamp_deltas(
&self,
cursor: &mut Cursor<&[u8]>,
num_events: usize,
base_timestamp: i64,
) -> Result<Vec<i64>, io::Error> {
let mut timestamps = Vec::with_capacity(num_events);
let mut current_timestamp = base_timestamp;
for i in 0..num_events {
if cursor.position() >= cursor.get_ref().len() as u64 {
for _ in i..num_events {
timestamps.push(current_timestamp);
}
break;
}
let delta = cursor.read_u32::<LittleEndian>()? as i64;
current_timestamp += delta;
timestamps.push(current_timestamp);
}
Ok(timestamps)
}
fn decode_raw_events(
&self,
cursor: &mut Cursor<&[u8]>,
num_events: usize,
) -> Result<Vec<EventCD>, io::Error> {
let mut events = Vec::with_capacity(num_events);
for _ in 0..num_events {
if cursor.position() + 14 > cursor.get_ref().len() as u64 {
break;
}
let x = cursor.read_u16::<LittleEndian>()?;
let y = cursor.read_u16::<LittleEndian>()?;
let p = cursor.read_i16::<LittleEndian>()?;
let t = cursor.read_i64::<LittleEndian>()?;
events.push(EventCD { x, y, p, t });
}
Ok(events)
}
}
pub struct ECFEncoder {
debug: bool,
}
impl Default for ECFEncoder {
fn default() -> Self {
Self::new()
}
}
impl ECFEncoder {
pub fn new() -> Self {
Self { debug: false }
}
pub fn with_debug(mut self, debug: bool) -> Self {
self.debug = debug;
self
}
pub fn encode(&self, events: &[EventCD]) -> Result<Vec<u8>, io::Error> {
if events.is_empty() {
let mut output = Vec::new();
let mut cursor = Cursor::new(&mut output);
let header = 0u32; cursor.write_u32::<LittleEndian>(header)?;
return Ok(output);
}
if events.len() > MAX_BUFFER_SIZE {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!(
"Too many events: {} (max {})",
events.len(),
MAX_BUFFER_SIZE
),
));
}
let mut output = Vec::new();
let mut cursor = Cursor::new(&mut output);
let flags = self.determine_encoding_flags(events);
let header = ((events.len() as u32) << 3)
| ((flags.delta_timestamps as u32) << 2)
| ((flags.packed_coordinates as u32) << 1)
| (flags.packed_coordinates as u32);
cursor.write_u32::<LittleEndian>(header)?;
if flags.delta_timestamps {
self.encode_with_delta_timestamps(&mut cursor, events, flags)?;
} else {
self.encode_raw_events(&mut cursor, events)?;
}
Ok(output)
}
fn determine_encoding_flags(&self, events: &[EventCD]) -> EncodingFlags {
let use_delta = events.len() > 10; let use_packed = true;
EncodingFlags {
delta_timestamps: use_delta,
packed_coordinates: use_packed,
masked_x_coords: false, }
}
fn encode_with_delta_timestamps(
&self,
cursor: &mut Cursor<&mut Vec<u8>>,
events: &[EventCD],
flags: EncodingFlags,
) -> Result<(), io::Error> {
let base_timestamp = events[0].t;
cursor.write_i64::<LittleEndian>(base_timestamp)?;
if flags.packed_coordinates {
self.encode_packed_coordinates(cursor, events)?;
} else {
self.encode_raw_coordinates(cursor, events)?;
}
self.encode_timestamp_deltas(cursor, events, base_timestamp)?;
Ok(())
}
fn encode_packed_coordinates(
&self,
cursor: &mut Cursor<&mut Vec<u8>>,
events: &[EventCD],
) -> Result<(), io::Error> {
for event in events {
cursor.write_u16::<LittleEndian>(event.x)?;
cursor.write_u16::<LittleEndian>(event.y)?;
cursor.write_i16::<LittleEndian>(event.p)?;
}
Ok(())
}
fn encode_raw_coordinates(
&self,
cursor: &mut Cursor<&mut Vec<u8>>,
events: &[EventCD],
) -> Result<(), io::Error> {
self.encode_packed_coordinates(cursor, events)
}
fn encode_timestamp_deltas(
&self,
cursor: &mut Cursor<&mut Vec<u8>>,
events: &[EventCD],
base_timestamp: i64,
) -> Result<(), io::Error> {
let mut previous_timestamp = base_timestamp;
for event in events {
let delta = (event.t - previous_timestamp) as u32;
cursor.write_u32::<LittleEndian>(delta)?;
previous_timestamp = event.t;
}
Ok(())
}
fn encode_raw_events(
&self,
cursor: &mut Cursor<&mut Vec<u8>>,
events: &[EventCD],
) -> Result<(), io::Error> {
for event in events {
cursor.write_u16::<LittleEndian>(event.x)?;
cursor.write_u16::<LittleEndian>(event.y)?;
cursor.write_i16::<LittleEndian>(event.p)?;
cursor.write_i64::<LittleEndian>(event.t)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ecf_roundtrip() {
let events = vec![
EventCD {
x: 100,
y: 150,
p: 1,
t: 1000,
},
EventCD {
x: 101,
y: 151,
p: -1,
t: 1010,
},
EventCD {
x: 102,
y: 152,
p: 1,
t: 1020,
},
];
let encoder = ECFEncoder::new().with_debug(true);
let decoder = ECFDecoder::new().with_debug(true);
let compressed = encoder.encode(&events).unwrap();
debug!(
events = events.len(),
bytes = compressed.len(),
"Compressed events"
);
let decoded = decoder.decode(&compressed).unwrap();
assert_eq!(events.len(), decoded.len());
for (original, decoded) in events.iter().zip(decoded.iter()) {
assert_eq!(original.x, decoded.x);
assert_eq!(original.y, decoded.y);
assert_eq!(original.p, decoded.p);
assert_eq!(original.t, decoded.t);
}
}
#[test]
fn test_empty_events() {
let encoder = ECFEncoder::new();
let decoder = ECFDecoder::new();
let compressed = encoder.encode(&[]).unwrap();
let decoded = decoder.decode(&compressed).unwrap();
assert!(decoded.is_empty());
}
}