use std::convert::TryInto;
use std::error::Error;
use std::fmt;
use crate::pb;
const SUPPORTED_VERSION: u8 = 1;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DecodeError {
UnsupportedVersion(u8),
Truncated,
}
impl fmt::Display for DecodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DecodeError::UnsupportedVersion(v) => {
write!(
f,
"unsupported record batch version {v} (expected {SUPPORTED_VERSION})"
)
}
DecodeError::Truncated => write!(f, "record batch blob is truncated"),
}
}
}
impl Error for DecodeError {}
struct Reader<'a> {
buf: &'a [u8],
off: usize,
}
impl<'a> Reader<'a> {
fn new(buf: &'a [u8]) -> Self {
Reader { buf, off: 0 }
}
fn take(&mut self, n: usize) -> Result<&'a [u8], DecodeError> {
let end = self.off.checked_add(n).ok_or(DecodeError::Truncated)?;
let slice = self.buf.get(self.off..end).ok_or(DecodeError::Truncated)?;
self.off = end;
Ok(slice)
}
fn u8(&mut self) -> Result<u8, DecodeError> {
Ok(self.take(1)?[0])
}
fn u16(&mut self) -> Result<u16, DecodeError> {
let b = self.take(2)?;
Ok(u16::from_le_bytes(b.try_into().unwrap()))
}
fn i16(&mut self) -> Result<i16, DecodeError> {
let b = self.take(2)?;
Ok(i16::from_le_bytes(b.try_into().unwrap()))
}
fn u32(&mut self) -> Result<u32, DecodeError> {
let b = self.take(4)?;
Ok(u32::from_le_bytes(b.try_into().unwrap()))
}
}
fn row_slice(off: &[usize], vals: &[u32], row: usize) -> Result<Vec<i64>, DecodeError> {
let start = if row == 0 { 0 } else { off[row - 1] };
let end = off[row];
let slice = vals.get(start..end).ok_or(DecodeError::Truncated)?;
Ok(slice.iter().map(|&c| c as i64).collect())
}
fn signal_string(code: u8) -> String {
match code {
1 => "r",
2 => "y",
3 => "g",
4 => "G",
5 => "s",
6 => "u",
7 => "o",
8 => "O",
_ => "undefined",
}
.to_string()
}
pub fn decode_record_batch(columns: &[u8]) -> Result<Vec<pb::SessionStepResponse>, DecodeError> {
let mut rd = Reader::new(columns);
let version = rd.u8()?;
if version != SUPPORTED_VERSION {
return Err(DecodeError::UnsupportedVersion(version));
}
let tick_start = rd.u32()?;
let tick_count = rd.u32()? as usize;
let total_rows = rd.u32()? as usize;
let mut rows_per_tick = Vec::with_capacity(tick_count);
for _ in 0..tick_count {
rows_per_tick.push(rd.u32()? as usize);
}
let mut veh_id = Vec::with_capacity(total_rows);
for _ in 0..total_rows {
veh_id.push(rd.u32()?);
}
let mut cell = Vec::with_capacity(total_rows);
for _ in 0..total_rows {
cell.push(rd.u32()?);
}
let agent_type = rd.take(total_rows)?.to_vec();
let mut angle = Vec::with_capacity(total_rows);
for _ in 0..total_rows {
angle.push(rd.u16()?);
}
let mut speed = Vec::with_capacity(total_rows);
for _ in 0..total_rows {
speed.push(rd.i16()?);
}
let mut trip = Vec::with_capacity(total_rows);
for _ in 0..total_rows {
trip.push(rd.u32()?);
}
let mut ic_off = Vec::with_capacity(total_rows);
for _ in 0..total_rows {
ic_off.push(rd.u32()? as usize);
}
let ic_total = ic_off.last().copied().unwrap_or(0);
let mut ic_vals = Vec::with_capacity(ic_total);
for _ in 0..ic_total {
ic_vals.push(rd.u32()?);
}
let mut tail_off = Vec::with_capacity(total_rows);
for _ in 0..total_rows {
tail_off.push(rd.u32()? as usize);
}
let tail_total = tail_off.last().copied().unwrap_or(0);
let mut tail_vals = Vec::with_capacity(tail_total);
for _ in 0..tail_total {
tail_vals.push(rd.u32()?);
}
let g_count = rd.u32()? as usize;
let mut tl_keys = Vec::with_capacity(g_count);
for _ in 0..g_count {
let tl_id = rd.u32()?;
let group_id = rd.u32()?;
tl_keys.push((tl_id, group_id));
}
let tl_signals = rd.take(tick_count.saturating_mul(g_count))?.to_vec();
let mut responses = Vec::with_capacity(tick_count);
let mut row = 0usize;
for (t, &n) in rows_per_tick.iter().enumerate() {
let timestamp = tick_start as i64 + t as i64;
let mut vehicle_data = Vec::with_capacity(n);
for _ in 0..n {
if row >= total_rows {
return Err(DecodeError::Truncated);
}
vehicle_data.push(pb::VehicleState {
vehicle_id: veh_id[row] as i64,
vehicle_type: agent_type[row] as i32,
bearing: angle[row] as f64 / 100.0,
speed: speed[row] as i64,
cell: cell[row] as i64,
intermediate_cells: row_slice(&ic_off, &ic_vals, row)?,
travel_time: -1,
trip_id: trip[row] as i64,
tail_cells: row_slice(&tail_off, &tail_vals, row)?,
});
row += 1;
}
let mut tls_data: Vec<pb::TlsState> = Vec::new();
for (gi, &(tl_id, group_id)) in tl_keys.iter().enumerate() {
let code = tl_signals[t * g_count + gi];
let group = pb::TlGroup {
id: group_id as i64,
signal: signal_string(code),
};
match tls_data.last_mut() {
Some(last) if last.id == tl_id as i64 => last.groups.push(group),
_ => tls_data.push(pb::TlsState {
id: tl_id as i64,
groups: vec![group],
}),
}
}
responses.push(pb::SessionStepResponse {
code: 0,
text: String::new(),
timestamp,
vehicle_data,
tls_data,
});
}
Ok(responses)
}
#[cfg(test)]
mod tests {
use super::*;
fn build_blob() -> Vec<u8> {
let mut b = Vec::new();
b.push(1u8);
b.extend_from_slice(&100u32.to_le_bytes());
b.extend_from_slice(&2u32.to_le_bytes());
b.extend_from_slice(&3u32.to_le_bytes());
b.extend_from_slice(&2u32.to_le_bytes());
b.extend_from_slice(&1u32.to_le_bytes());
for v in [10u32, 11, 12] {
b.extend_from_slice(&v.to_le_bytes());
}
for v in [5u32, 6, 7] {
b.extend_from_slice(&v.to_le_bytes());
}
b.extend_from_slice(&[1u8, 2, 5]);
for v in [0u16, 9000, 35999] {
b.extend_from_slice(&v.to_le_bytes());
}
for v in [3i16, 0, -1] {
b.extend_from_slice(&v.to_le_bytes());
}
for v in [1u32, 1, 2] {
b.extend_from_slice(&v.to_le_bytes());
}
for v in [2u32, 2, 3] {
b.extend_from_slice(&v.to_le_bytes());
}
for v in [100u32, 101, 102] {
b.extend_from_slice(&v.to_le_bytes());
}
for v in [0u32, 1, 3] {
b.extend_from_slice(&v.to_le_bytes());
}
for v in [200u32, 201, 202] {
b.extend_from_slice(&v.to_le_bytes());
}
b.extend_from_slice(&2u32.to_le_bytes());
b.extend_from_slice(&1u32.to_le_bytes());
b.extend_from_slice(&100u32.to_le_bytes());
b.extend_from_slice(&1u32.to_le_bytes());
b.extend_from_slice(&200u32.to_le_bytes());
b.extend_from_slice(&[3u8, 1, 1, 3]);
b
}
#[test]
fn round_trip_blob() {
let blob = build_blob();
let responses = decode_record_batch(&blob).expect("decode");
assert_eq!(responses.len(), 2);
let r0 = &responses[0];
assert_eq!(r0.code, 0);
assert_eq!(r0.text, "");
assert_eq!(r0.timestamp, 100);
assert_eq!(r0.vehicle_data.len(), 2);
let v0 = &r0.vehicle_data[0];
assert_eq!(v0.vehicle_id, 10);
assert_eq!(v0.cell, 5);
assert_eq!(v0.vehicle_type, pb::AgentType::Car as i32);
assert!((v0.bearing - 0.0).abs() < 1e-9);
assert_eq!(v0.speed, 3);
assert_eq!(v0.trip_id, 1);
assert_eq!(v0.travel_time, -1);
assert_eq!(v0.intermediate_cells, vec![100i64, 101]);
assert_eq!(v0.tail_cells, Vec::<i64>::new());
let v1 = &r0.vehicle_data[1];
assert_eq!(v1.vehicle_id, 11);
assert_eq!(v1.vehicle_type, pb::AgentType::Bus as i32);
assert!((v1.bearing - 90.0).abs() < 1e-9);
assert_eq!(v1.speed, 0);
assert_eq!(v1.intermediate_cells, Vec::<i64>::new());
assert_eq!(v1.tail_cells, vec![200i64]);
assert_eq!(r0.tls_data.len(), 1);
assert_eq!(r0.tls_data[0].id, 1);
assert_eq!(r0.tls_data[0].groups.len(), 2);
assert_eq!(r0.tls_data[0].groups[0].id, 100);
assert_eq!(r0.tls_data[0].groups[0].signal, "g");
assert_eq!(r0.tls_data[0].groups[1].id, 200);
assert_eq!(r0.tls_data[0].groups[1].signal, "r");
let r1 = &responses[1];
assert_eq!(r1.timestamp, 101);
assert_eq!(r1.vehicle_data.len(), 1);
let v2 = &r1.vehicle_data[0];
assert_eq!(v2.vehicle_id, 12);
assert_eq!(v2.vehicle_type, pb::AgentType::Truck as i32);
assert!((v2.bearing - 359.99).abs() < 1e-9);
assert_eq!(v2.speed, -1);
assert_eq!(v2.trip_id, 2);
assert_eq!(v2.intermediate_cells, vec![102i64]);
assert_eq!(v2.tail_cells, vec![201i64, 202]);
assert_eq!(r1.tls_data.len(), 1);
assert_eq!(r1.tls_data[0].id, 1);
assert_eq!(r1.tls_data[0].groups[0].signal, "r");
assert_eq!(r1.tls_data[0].groups[1].signal, "g");
}
#[test]
fn rejects_bad_version() {
let mut blob = build_blob();
blob[0] = 2;
match decode_record_batch(&blob) {
Err(DecodeError::UnsupportedVersion(2)) => {}
other => panic!("expected UnsupportedVersion(2), got {other:?}"),
}
}
#[test]
fn rejects_truncated() {
let blob = build_blob();
match decode_record_batch(&blob[..5]) {
Err(DecodeError::Truncated) => {}
other => panic!("expected Truncated, got {other:?}"),
}
}
#[test]
fn empty_batch_no_panic() {
let mut b = Vec::new();
b.push(1u8);
b.extend_from_slice(&0u32.to_le_bytes());
b.extend_from_slice(&0u32.to_le_bytes());
b.extend_from_slice(&0u32.to_le_bytes());
b.extend_from_slice(&0u32.to_le_bytes());
let responses = decode_record_batch(&b).expect("decode empty");
assert!(responses.is_empty());
}
}