blob: 98b295d1c5270d79fecfcb456111d62de3979254 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Implements the Zedmon USB protocol, defined by
//! https://fuchsia.googlesource.com/zedmon/+/HEAD/docs/usb_proto.md.
use {
byteorder::{LittleEndian as LE, ReadBytesExt},
num::FromPrimitive,
num_derive::FromPrimitive,
std::io::{BufRead, Cursor},
std::str,
};
pub const MAX_PACKET_SIZE: usize = 64;
/// Types of packets that may be sent to or received from Zedmon.
#[repr(u8)]
#[derive(Debug, FromPrimitive, PartialEq)]
pub enum PacketType {
QueryReportFormat = 0x0,
QueryTime = 0x01,
QueryParameter = 0x2,
EnableReporting = 0x10,
DisableReporting = 0x11,
SetOutput = 0x20,
ReportFormat = 0x80,
Report = 0x81,
Timestamp = 0x82,
ParameterValue = 0x83,
}
/// Types for scalars sent over the network.
#[repr(u8)]
#[derive(Copy, Clone, Debug, FromPrimitive, PartialEq)]
pub enum ScalarType {
U8 = 0x0,
U16 = 0x1,
U32 = 0x3,
U64 = 0x4,
I8 = 0x10,
I16 = 0x11,
I32 = 0x13,
I64 = 0x14,
Bool = 0x20,
F32 = 0x40,
}
/// Possible units for scalar vales.
#[repr(u8)]
#[derive(Clone, Debug, FromPrimitive, PartialEq)]
pub enum Unit {
Amperes = 0x0,
Volts = 0x1,
}
/// Allowable types of a reported value.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Value {
U8(u8),
U16(u16),
U32(u32),
U64(u64),
I8(i8),
I16(i16),
I32(i32),
I64(i64),
Bool(bool),
F32(f32),
}
/// A fixed parameter of the Zedmon device.
#[derive(Debug, PartialEq)]
pub struct ParameterValue {
pub name: String,
pub value: Value,
}
/// Format of a field in a Report packet.
#[derive(Clone, Debug, PartialEq)]
pub struct ReportFormat {
pub index: u8,
pub field_type: ScalarType,
pub unit: Unit,
pub scale: f32,
pub name: String,
}
pub const REPORT_FORMAT_INDEX_END: u8 = 0xff;
/// Report of measured data at a timepoint.
#[derive(Clone, Debug, PartialEq)]
pub struct Report {
/// Timestamp, in microseconds, in Zedmon's clock domain.
///
/// At time of writing the timestamp is zero when Zedmon boots, but that is not a guarantee.
// TODO(fxbug.dev/60030): Use one of power_manager's newtypes, or similar.
pub timestamp_micros: u64,
/// Reported values for this sample.
pub values: Vec<Value>,
}
// Typical Zedmons only support the relay output.
#[repr(u8)]
pub enum Output {
Relay = 0x00,
}
#[derive(Debug, thiserror::Error, PartialEq)]
pub enum Error {
#[error("Short data: required {required}, got {received}")]
ShortData { required: usize, received: usize },
#[error("Invalid string")]
InvalidString,
#[error("Wrong packet type; expected {expected:?}, got {received:?}")]
WrongPacketType { expected: PacketType, received: PacketType },
#[error("Unknown primitive value {primitive} for enum type {enum_name}")]
UnknownEnumValue { primitive: u8, enum_name: &'static str },
#[error("{name} has ScalarType {scalar_type:?}, which cannot be scaled")]
InvalidScaledType { name: String, scalar_type: ScalarType },
#[error("Invalid ReportFormats: {reason}")]
InvalidReportFormats { reason: String },
#[error("std::io::Error received: {io_error}")]
IoError { io_error: String },
}
impl From<std::io::Error> for Error {
fn from(error: std::io::Error) -> Self {
Error::IoError { io_error: error.to_string() }
}
}
/// Result alias for fallible calls in this crate.
type Result<T> = std::result::Result<T, Error>;
/// Extra reader methods for a Cursor into a byte slice.
trait CursorExtras {
/// Retrieves a byte slice of the specified length, returning `Error::ShortData` if
/// insufficient data is available. *Does not* advance the cursor position.
fn get_byte_slice(&mut self, len: usize) -> Result<&[u8]>;
/// Reads a UTF-8 string of the specifed `len`. Forwards errors from `get_byte_slice`,
/// and returns `Error::InvalidString` if invalid UTF-8 characters are detected.
fn read_string(&mut self, len: usize) -> Result<String>;
/// Reads a byte and casts it to `EnumType`. Forwards errors from `get_byte_slice`,
/// and returns `Error::UnknownEnumValue` if the cast fails.
fn read_enum<EnumType: FromPrimitive>(&mut self) -> Result<EnumType>;
}
impl<T: AsRef<[u8]>> CursorExtras for Cursor<T> {
fn get_byte_slice(&mut self, len: usize) -> Result<&[u8]> {
// fill_buf() is not fallible for Cursor<T: AsRef<[u8]>>, so unwrap() is safe.
let full_slice = &self.fill_buf().unwrap();
if full_slice.len() < len {
return Err(Error::ShortData { required: len, received: full_slice.len() });
}
Ok(&full_slice[..len])
}
fn read_string(&mut self, len: usize) -> Result<String> {
let string = {
let raw = self.get_byte_slice(len)?;
let null_pos = raw.iter().position(|c| *c == b'\0').ok_or(Error::InvalidString)?;
let slice = str::from_utf8(&raw[..null_pos]).map_err(|_| Error::InvalidString)?;
slice.trim().to_string()
};
self.consume(len);
Ok(string)
}
fn read_enum<EnumType: FromPrimitive>(&mut self) -> Result<EnumType> {
// Use `get_byte_slice` rather than `read_u8` to clarify possible errors.
let primitive = self.get_byte_slice(1)?[0];
self.consume(1);
EnumType::from_u8(primitive).ok_or(Error::UnknownEnumValue {
primitive,
enum_name: std::any::type_name::<EnumType>(),
})
}
}
pub fn encode_disable_reporting() -> [u8; 1] {
[PacketType::DisableReporting as u8]
}
pub fn encode_enable_reporting() -> [u8; 1] {
[PacketType::EnableReporting as u8]
}
pub fn encode_query_report_format(index: u8) -> [u8; 2] {
[PacketType::QueryReportFormat as u8, index]
}
pub fn encode_query_parameter(index: u8) -> [u8; 2] {
[PacketType::QueryParameter as u8, index]
}
pub fn encode_query_time() -> [u8; 1] {
[PacketType::QueryTime as u8]
}
pub fn encode_set_output(index: u8, value: bool) -> [u8; 3] {
[PacketType::SetOutput as u8, index, value as u8]
}
/// Validates that `packet` has the required length and `PacketType`. Returns a cursor to the
/// remainder of the packet's data.
// NOTE: The `?Sized` relaxation in this function and several others below is needed to support
// T=[u8].
fn validate_packet<T: AsRef<[u8]> + ?Sized>(
packet: &T,
len: usize,
expected_type: PacketType,
) -> Result<Cursor<&T>> {
let mut cursor = Cursor::new(packet);
let packet_type = cursor.read_enum::<PacketType>()?;
if packet_type != expected_type {
return Err(Error::WrongPacketType { expected: expected_type, received: packet_type });
}
// Check for expected length after the packet type, as WrongPacketType is more useful to a
// caller.
let packet_len = packet.as_ref().len();
if packet_len < len {
return Err(Error::ShortData { required: len, received: packet_len });
}
Ok(cursor)
}
const PARAMETER_VALUE_NAME_LENGTH: usize = 54;
/// Parses a `PacketType::ParameterValue` packet.
pub fn parse_parameter_value<T: AsRef<[u8]>>(packet: &T) -> Result<ParameterValue> {
let mut cursor = validate_packet(packet, 64, PacketType::ParameterValue)?;
let name = cursor.read_string(PARAMETER_VALUE_NAME_LENGTH)?;
let field_type = cursor.read_enum::<ScalarType>()?;
let value = match field_type {
ScalarType::U8 => Value::U8(cursor.read_u8().unwrap()),
ScalarType::U16 => Value::U16(cursor.read_u16::<LE>().unwrap()),
ScalarType::U32 => Value::U32(cursor.read_u32::<LE>().unwrap()),
ScalarType::U64 => Value::U64(cursor.read_u64::<LE>().unwrap()),
ScalarType::I8 => Value::I8(cursor.read_i8().unwrap()),
ScalarType::I16 => Value::I16(cursor.read_i16::<LE>().unwrap()),
ScalarType::I32 => Value::I32(cursor.read_i32::<LE>().unwrap()),
ScalarType::I64 => Value::I64(cursor.read_i64::<LE>().unwrap()),
ScalarType::Bool => Value::Bool(cursor.read_u8().unwrap() != 0x00),
ScalarType::F32 => Value::F32(cursor.read_f32::<LE>().unwrap()),
};
Ok(ParameterValue { name, value })
}
const REPORT_FIELD_NAME_LENGTH: usize = 56;
/// Parses a `PacketType::ReportFormat` packet.
pub fn parse_report_format<T: AsRef<[u8]> + ?Sized>(packet: &T) -> Result<ReportFormat> {
let mut cursor = validate_packet(packet, 64, PacketType::ReportFormat)?;
let index = cursor.read_u8().unwrap();
let field_type = cursor.read_enum::<ScalarType>()?;
let unit = cursor.read_enum::<Unit>()?;
let scale = cursor.read_f32::<LE>().unwrap();
let name = cursor.read_string(REPORT_FIELD_NAME_LENGTH)?;
Ok(ReportFormat { index, field_type, unit, scale, name })
}
pub fn parse_timestamp_micros<T: AsRef<[u8]> + ?Sized>(packet: &T) -> Result<u64> {
let mut cursor = validate_packet(packet, 9, PacketType::Timestamp)?;
Ok(cursor.read_u64::<LE>()?)
}
/// Calculates the length of a serialized Report from its field types.
fn calc_report_length(field_types: &Vec<ScalarType>) -> usize {
let mut len = 8; // u64 timestamp
for t in field_types {
len += match t {
ScalarType::U8 => 1,
ScalarType::U16 => 2,
ScalarType::U32 => 4,
ScalarType::U64 => 8,
ScalarType::I8 => 1,
ScalarType::I16 => 2,
ScalarType::I32 => 4,
ScalarType::I64 => 8,
ScalarType::Bool => 1,
ScalarType::F32 => 4,
};
}
len
}
/// ReportParser's internal way of representing the type of a reported value. A field with
/// ScalarType of U8, U16, I8, or I16 may be coupled with a nonzero scale, in which case
/// ReportParser will parse it into an F32.
///
/// For now, only the integer types that may be losslessly converted to f32 are considered scalable.
/// Should the need arise, it would be straightforward to support scalable u32 and i32 values as
/// well, with parsing into f64.
#[derive(Debug)]
enum ReportedValueType {
Unscaled(ScalarType),
ScaledU8(f32),
ScaledU16(f32),
ScaledI8(f32),
ScaledI16(f32),
}
/// Helper struct for parsing reports.
///
/// This is used instead of a bare function to avoid repeatedly re-calculating report length based
/// on the fixed value types.
#[derive(Debug)]
pub struct ReportParser {
report_length: usize,
value_types: Vec<ReportedValueType>,
}
impl ReportParser {
pub fn new(format: &Vec<ReportFormat>) -> Result<ReportParser> {
// Sort the provided field formats by index, and make sure that the resulting indices
// are 0..format.len().
let format = {
let mut sorted = format.clone();
let comparator = |a: &ReportFormat, b: &ReportFormat| {
if a.index < b.index {
std::cmp::Ordering::Less
} else if a.index == b.index {
std::cmp::Ordering::Equal
} else {
std::cmp::Ordering::Greater
}
};
sorted.sort_by(comparator);
let indices: Vec<u8> = sorted.iter().map(|f| f.index).collect();
let expected: Vec<u8> = (0..sorted.len()).map(|x| x as u8).collect();
if indices != expected {
return Err(Error::InvalidReportFormats {
reason: format!(
"ReportFormat indices {:?} must sort to {:?}",
indices, expected
),
});
}
sorted
};
// Determine the ScalarType and ReportedValueType of each field.
let mut scalar_types = Vec::new();
let mut reported_types = Vec::new();
for field_format in format {
scalar_types.push(field_format.field_type);
let scale = field_format.scale;
let reported_type = if scale == 0.0 {
ReportedValueType::Unscaled(field_format.field_type)
} else {
match field_format.field_type {
ScalarType::U8 => ReportedValueType::ScaledU8(scale),
ScalarType::U16 => ReportedValueType::ScaledU16(scale),
ScalarType::I8 => ReportedValueType::ScaledI8(scale),
ScalarType::I16 => ReportedValueType::ScaledI16(scale),
_ => {
return Err(Error::InvalidScaledType {
name: field_format.name.clone(),
scalar_type: field_format.field_type,
})
}
}
};
reported_types.push(reported_type);
}
Ok(ReportParser {
report_length: calc_report_length(&scalar_types),
value_types: reported_types,
})
}
/// Parses a Report packet to a Vec<Report>.
pub fn parse_reports<T: AsRef<[u8]> + ?Sized>(&self, packet: &T) -> Result<Vec<Report>> {
let num_bytes = packet.as_ref().len();
// Determine how many reports follow the leading PacketType byte.
let num_reports = (num_bytes - 1) / self.report_length;
let mut cursor = validate_packet(packet, num_bytes, PacketType::Report)?;
let mut reports = Vec::with_capacity(num_reports);
for _ in 0..num_reports {
let timestamp = cursor.read_u64::<LE>()?;
let mut values = Vec::new();
for t in &self.value_types {
let value = match *t {
ReportedValueType::Unscaled(st) => match st {
ScalarType::U8 => Value::U8(cursor.read_u8()?),
ScalarType::U16 => Value::U16(cursor.read_u16::<LE>()?),
ScalarType::U32 => Value::U32(cursor.read_u32::<LE>()?),
ScalarType::U64 => Value::U64(cursor.read_u64::<LE>()?),
ScalarType::I8 => Value::I8(cursor.read_i8()?),
ScalarType::I16 => Value::I16(cursor.read_i16::<LE>()?),
ScalarType::I32 => Value::I32(cursor.read_i32::<LE>()?),
ScalarType::I64 => Value::I64(cursor.read_i64::<LE>()?),
ScalarType::Bool => Value::Bool(cursor.read_u8()? != 0),
ScalarType::F32 => Value::F32(cursor.read_f32::<LE>()?),
},
ReportedValueType::ScaledU8(scale) => {
Value::F32(cursor.read_u8()? as f32 * scale)
}
ReportedValueType::ScaledU16(scale) => {
Value::F32(cursor.read_u16::<LE>()? as f32 * scale)
}
ReportedValueType::ScaledI8(scale) => {
Value::F32(cursor.read_i8()? as f32 * scale)
}
ReportedValueType::ScaledI16(scale) => {
Value::F32(cursor.read_i16::<LE>()? as f32 * scale)
}
};
values.push(value);
}
reports.push(Report { timestamp_micros: timestamp, values });
}
Ok(reports)
}
}
/// This module is public to expose serialization functions to tests in other modules.
#[cfg(test)]
pub mod tests {
use {super::*, byteorder::WriteBytesExt, matches::assert_matches, std::io::Write};
fn get_scalar_type(value: &Value) -> ScalarType {
match *value {
Value::U8(_) => ScalarType::U8,
Value::U16(_) => ScalarType::U16,
Value::U32(_) => ScalarType::U32,
Value::U64(_) => ScalarType::U64,
Value::I8(_) => ScalarType::I8,
Value::I16(_) => ScalarType::I16,
Value::I32(_) => ScalarType::I32,
Value::I64(_) => ScalarType::I64,
Value::Bool(_) => ScalarType::Bool,
Value::F32(_) => ScalarType::F32,
}
}
/// Serializes a ReportFormat into a buffer, returning the number of bytes written.
pub fn serialize_report_format(format: ReportFormat, buffer: &mut [u8]) -> usize {
let mut buffer = &mut buffer[..MAX_PACKET_SIZE];
buffer
.write(&[
PacketType::ReportFormat as u8,
format.index,
format.field_type as u8,
format.unit as u8,
])
.unwrap();
buffer.write_f32::<LE>(format.scale).unwrap();
// Make sure the name leaves room for a null terminator, write it, then zero the rest of the
// buffer.
let name = &format.name;
assert!(name.len() < buffer.len());
buffer.write(name.as_bytes()).unwrap();
for i in 0..buffer.len() {
buffer[i] = 0;
}
MAX_PACKET_SIZE
}
/// Serializes a ParameterValue into a buffer, returning the number of bytes written.
pub fn serialize_parameter_value(parameter: ParameterValue, buffer: &mut [u8]) -> usize {
let buffer = &mut buffer[..MAX_PACKET_SIZE];
buffer[0] = PacketType::ParameterValue as u8;
// Write the parameter name, making sure there's room for a null terminator.
let name_length = parameter.name.len();
assert!(name_length < PARAMETER_VALUE_NAME_LENGTH);
buffer[1..name_length + 1].copy_from_slice(parameter.name.as_bytes());
// Zero out the remainder of the name region, and the value region as well, so we don't have
// to worry about the number of bytes to zero for each ScalarType.
for i in name_length + 1..MAX_PACKET_SIZE {
buffer[i] = 0;
}
// Write the value.
let mut value_buffer = &mut buffer[1 + PARAMETER_VALUE_NAME_LENGTH..];
value_buffer.write_u8(get_scalar_type(&parameter.value) as u8).unwrap();
match parameter.value {
Value::U8(v) => value_buffer.write_u8(v).unwrap(),
Value::U16(v) => value_buffer.write_u16::<LE>(v).unwrap(),
Value::U32(v) => value_buffer.write_u32::<LE>(v).unwrap(),
Value::U64(v) => value_buffer.write_u64::<LE>(v).unwrap(),
Value::I8(v) => value_buffer.write_i8(v).unwrap(),
Value::I16(v) => value_buffer.write_i16::<LE>(v).unwrap(),
Value::I32(v) => value_buffer.write_i32::<LE>(v).unwrap(),
Value::I64(v) => value_buffer.write_i64::<LE>(v).unwrap(),
Value::Bool(v) => value_buffer.write_u8(v as u8).unwrap(),
Value::F32(v) => value_buffer.write_f32::<LE>(v).unwrap(),
}
MAX_PACKET_SIZE
}
/// Serializes a Vec<Report> into a buffer, returning the number of bytes written.
///
/// If the Values in a given Report are to be scaled, they should be sent to this function in
/// their *unscaled* form. Note that serialize_reports is not the inverse of ReportParser.parse
/// for scaled Values.
pub fn serialize_reports(reports: &Vec<Report>, mut buffer: &mut [u8]) -> usize {
let field_types = reports[0].values.iter().map(|v| get_scalar_type(v)).collect();
// Make sure the buffer has enough space for the reports. If this fails, the test probably isn't
// configured correctly.
let num_bytes = calc_report_length(&field_types) * reports.len() + 1;
assert!(num_bytes <= buffer.len());
buffer.write_u8(PacketType::Report as u8).unwrap();
for report in reports {
buffer.write_u64::<LE>(report.timestamp_micros).unwrap();
for (value, field_type) in report.values.iter().zip(field_types.iter()) {
assert_eq!(
get_scalar_type(value),
*field_type,
"Value {:?} has wrong type (need {:?})",
*value,
*field_type
);
match *value {
Value::U8(v) => buffer.write_u8(v).unwrap(),
Value::U16(v) => buffer.write_u16::<LE>(v).unwrap(),
Value::U32(v) => buffer.write_u32::<LE>(v).unwrap(),
Value::U64(v) => buffer.write_u64::<LE>(v).unwrap(),
Value::I8(v) => buffer.write_i8(v).unwrap(),
Value::I16(v) => buffer.write_i16::<LE>(v).unwrap(),
Value::I32(v) => buffer.write_i32::<LE>(v).unwrap(),
Value::I64(v) => buffer.write_i64::<LE>(v).unwrap(),
Value::Bool(v) => buffer.write_u8(v as u8).unwrap(),
Value::F32(v) => buffer.write_f32::<LE>(v).unwrap(),
}
}
}
num_bytes
}
/// Serializes a timestamp into a buffer, returning the number of bytes written.
pub fn serialize_timestamp_micros(timestamp: u64, mut buffer: &mut [u8]) -> usize {
buffer.write_u8(PacketType::Timestamp as u8).unwrap();
buffer.write_u64::<LE>(timestamp).unwrap();
9
}
#[test]
fn test_validate_packet() {
// Successful validation
assert!(validate_packet(
&[PacketType::ParameterValue as u8, 0x00],
2,
PacketType::ParameterValue
)
.is_ok());
// Bad PacketType
assert_matches!(
validate_packet(
&[0xff, 0x00], 2, PacketType::ParameterValue),
Err(Error::UnknownEnumValue {
primitive: 0xff,
enum_name
}) if enum_name.contains("PacketType")
);
// Wrong packet length
assert_eq!(
validate_packet(
&[PacketType::ParameterValue as u8, 0x00],
3,
PacketType::ParameterValue
),
Err(Error::ShortData { required: 3, received: 2 })
);
// Wrong PacketType
assert_eq!(
validate_packet(&[PacketType::ReportFormat as u8, 0x00], 2, PacketType::ParameterValue),
Err(Error::WrongPacketType {
expected: PacketType::ParameterValue,
received: PacketType::ReportFormat
})
);
}
#[test]
fn test_parse_report_format() {
let mut base_packet = [0; MAX_PACKET_SIZE];
serialize_report_format(
ReportFormat {
index: 1,
field_type: ScalarType::F32,
unit: Unit::Volts,
scale: 0.00125,
name: "v_bus".to_string(),
},
&mut base_packet,
);
// Base packet parses successfully
assert_eq!(
parse_report_format(&base_packet),
Ok(ReportFormat {
index: 1,
field_type: ScalarType::F32,
unit: Unit::Volts,
scale: 0.00125,
name: "v_bus".to_string(),
})
);
// Wrong packet type
let mut packet = base_packet.clone();
packet[0] = PacketType::ParameterValue as u8;
assert_eq!(
parse_report_format(&packet),
Err(Error::WrongPacketType {
expected: PacketType::ReportFormat,
received: PacketType::ParameterValue,
})
);
// Bad field type
let mut packet = base_packet.clone();
packet[2] = 0xff; // Invalid ScalarType
assert_matches!(
parse_report_format(&packet),
Err(Error::UnknownEnumValue {
primitive: 0xff,
enum_name
}) if enum_name.contains("ScalarType")
);
// Bad unit
let mut packet = base_packet.clone();
packet[3] = 0xff; // Invalid Unit
assert_matches!(
parse_report_format(&packet),
Err(Error::UnknownEnumValue {
primitive: 0xff,
enum_name
}) if enum_name.contains("Unit")
);
// Invalid string
let mut packet = base_packet.clone();
packet[8] = 0xff; // Invalid UTF-8 character
assert_eq!(parse_report_format(&packet), Err(Error::InvalidString));
}
#[test]
fn test_parse_parameter_value() {
let mut base_packet = [0; MAX_PACKET_SIZE];
serialize_parameter_value(
ParameterValue { name: "shunt_resistance".to_string(), value: Value::F32(0.01) },
&mut base_packet,
);
// Base packet parses successfully
assert_eq!(
parse_parameter_value(&base_packet),
Ok(ParameterValue { name: "shunt_resistance".to_string(), value: Value::F32(0.01) })
);
// Wrong packet type
let mut packet = base_packet.clone();
packet[0] = PacketType::ReportFormat as u8;
assert_eq!(
parse_parameter_value(&packet),
Err(Error::WrongPacketType {
expected: PacketType::ParameterValue,
received: PacketType::ReportFormat,
})
);
// Invalid string
let mut packet = base_packet.clone();
packet[1] = 0xff; // Invalid UTF-8 character
assert_eq!(parse_parameter_value(&packet), Err(Error::InvalidString));
// Bad field type
let mut packet = base_packet.clone();
packet[1 + PARAMETER_VALUE_NAME_LENGTH] = 0xff; // Invalid ScalarType
assert_matches!(
parse_parameter_value(&packet),
Err(Error::UnknownEnumValue {
primitive: 0xff,
enum_name
}) if enum_name.contains("ScalarType")
);
}
#[test]
fn test_parse_timestamp_micros() {
let mut base_packet = [0; 9];
serialize_timestamp_micros(1234567, &mut base_packet);
// Base packet parses successfully
assert_eq!(parse_timestamp_micros(&base_packet), Ok(1234567));
let mut packet = base_packet.clone();
packet[0] = PacketType::ParameterValue as u8;
assert_eq!(
parse_timestamp_micros(&packet),
Err(Error::WrongPacketType {
expected: PacketType::Timestamp,
received: PacketType::ParameterValue,
})
);
}
#[test]
fn test_report_parser() -> Result<()> {
/// Makes a Vec<ReportFormat> from an array or Vec of tuples.
fn make_format<V>(field_formats: V) -> Vec<ReportFormat>
where
for<'a> &'a V: IntoIterator<Item = &'a (u8, ScalarType, f32)>,
{
field_formats
.into_iter()
.map(|v| ReportFormat {
index: v.0,
field_type: v.1,
unit: Unit::Volts,
scale: v.2,
name: "foo".to_string(),
})
.collect()
}
fn serialize_and_parse(
reports_in: &Vec<Report>,
parser: &ReportParser,
) -> Result<Vec<Report>> {
let mut packet = [0; MAX_PACKET_SIZE];
let bytes_used = serialize_reports(&reports_in, &mut packet[..]);
parser.parse_reports(&packet[..bytes_used])
}
// Test all unscaled types.
let format = make_format([
(0, ScalarType::U8, 0.0),
(1, ScalarType::U16, 0.0),
(2, ScalarType::U32, 0.0),
(3, ScalarType::U64, 0.0),
(4, ScalarType::I8, 0.0),
(5, ScalarType::I16, 0.0),
(6, ScalarType::I32, 0.0),
(7, ScalarType::I64, 0.0),
(8, ScalarType::Bool, 0.0),
(9, ScalarType::F32, 0.0),
]);
let parser = ReportParser::new(&format)?;
let reports_in = vec![Report {
timestamp_micros: 1000,
values: vec![
Value::U8(u8::MAX),
Value::U16(u16::MAX),
Value::U32(u32::MAX),
Value::U64(u64::MAX),
Value::I8(i8::MIN),
Value::I16(i16::MIN),
Value::I32(i32::MIN),
Value::I64(i64::MIN),
Value::Bool(true),
Value::F32(f32::MAX),
],
}];
let reports_out = serialize_and_parse(&reports_in, &parser)?;
assert_eq!(reports_in, reports_out);
// Test all scaled types.
let format = make_format([
(0, ScalarType::U8, 0.1),
(1, ScalarType::U16, 0.2),
(2, ScalarType::I8, 0.3),
(3, ScalarType::I16, 0.4),
]);
let parser = ReportParser::new(&format)?;
let reports_in = vec![Report {
timestamp_micros: 1000,
values: vec![
Value::U8(u8::MAX),
Value::U16(u16::MAX),
Value::I8(i8::MIN),
Value::I16(i16::MIN),
],
}];
let reports_out = serialize_and_parse(&reports_in, &parser)?;
let expected = vec![Report {
timestamp_micros: 1000,
values: vec![
Value::F32(u8::MAX as f32 * 0.1),
Value::F32(u16::MAX as f32 * 0.2),
Value::F32(i8::MIN as f32 * 0.3),
Value::F32(i16::MIN as f32 * 0.4),
],
}];
assert_eq!(reports_out, expected);
// Test multiple reports.
let format = make_format([(0, ScalarType::U8, 0.0), (1, ScalarType::I16, 0.0)]);
let parser = ReportParser::new(&format)?;
let reports_in = vec![
Report { timestamp_micros: 1000, values: vec![Value::U8(1), Value::I16(-1)] },
Report { timestamp_micros: 2000, values: vec![Value::U8(2), Value::I16(-2)] },
Report { timestamp_micros: 3000, values: vec![Value::U8(3), Value::I16(-3)] },
Report { timestamp_micros: 4000, values: vec![Value::U8(4), Value::I16(-4)] },
];
let reports_out = serialize_and_parse(&reports_in, &parser)?;
assert_eq!(reports_in, reports_out);
// Test sorting of field formats by index
let format = make_format([
(2, ScalarType::F32, 0.0),
(0, ScalarType::U8, 0.0),
(1, ScalarType::I16, 0.0),
]);
let parser = ReportParser::new(&format)?;
let reports_in = vec![Report {
timestamp_micros: 1000,
values: vec![Value::U8(u8::MAX), Value::I16(i16::MIN), Value::F32(f32::MIN)],
}];
let reports_out = serialize_and_parse(&reports_in, &parser)?;
assert_eq!(reports_in, reports_out);
// Test error on invalid ReportFormat indices
let format = make_format([
(0, ScalarType::U8, 0.0),
(1, ScalarType::I16, 0.0),
(3, ScalarType::F32, 0.0),
]);
let result = ReportParser::new(&format);
assert_matches!(result, Err(Error::InvalidReportFormats { reason: _ }));
// Test error on invalid scaled type
let format = make_format([(0, ScalarType::Bool, 0.1)]);
let result = ReportParser::new(&format);
assert_matches!(result, Err(Error::InvalidScaledType { name: _, scalar_type: _ }));
Ok(())
}
}