use std::io::Write;
use chrono::{DateTime, Utc};
use crc32fast::Hasher;
use enumset::EnumSet;
use crate::{
constants::{self, V2_SIGNATURE},
deserialize::Deserializable,
serialize::{Serializable, Writer},
Error, GenResult,
};
use super::{
eventfeatures::EventFeatures, file::File, number::SubunitNumber, teststatus::TestStatus,
timestamp::Timestamp,
};
macro_rules! safe_read {
($expr:expr) => {
match $expr {
::std::result::Result::Ok(val) => val,
::std::result::Result::Err(size) => {
return ::std::result::Result::Ok(size);
}
}
};
}
macro_rules! safe_de {
($expr:expr) => {
match $expr {
::std::result::Result::Ok(val) => val,
::std::result::Result::Err(_size) => {
return ::std::result::Result::Err($crate::Error::NotEnoughBytes.into());
}
}
};
}
pub struct EventBuilder(Event);
impl EventBuilder {
pub fn test_id(mut self, test_id: &str) -> Self {
self.0.test_id = Some(test_id.to_string());
self
}
pub fn datetime(mut self, datetime: DateTime<Utc>) -> GenResult<Self> {
self.0.timestamp = Some(datetime.try_into()?);
Ok(self)
}
pub fn tag(mut self, tag: &str) -> Self {
if self.0.tags.is_none() {
self.0.tags = Some(Vec::new());
}
self.0.tags.as_mut().unwrap().push(tag.to_string());
self
}
pub fn mime_type(mut self, mime_type: &str) -> Self {
self.0.file.mime_type = Some(mime_type.to_string());
self
}
pub fn file_content(mut self, name: &str, content: &[u8]) -> Self {
self.0.file.file = Some((name.to_string(), content.to_vec()));
self
}
pub fn end_of_file(mut self) -> Self {
self.0.file.eof = true;
self
}
pub fn runnable(mut self) -> Self {
self.0.runnable = true;
self
}
pub fn route_code(mut self, route_code: &str) -> Self {
self.0.route_code = Some(route_code.to_string());
self
}
pub fn build(self) -> Event {
self.0
}
}
impl EventBuilder {}
#[derive(Debug, Clone, PartialEq)]
pub struct Event {
pub status: TestStatus,
pub test_id: Option<String>,
pub timestamp: Option<Timestamp>,
pub file: File,
pub route_code: Option<String>,
pub tags: Option<Vec<String>>,
pub runnable: bool,
}
impl Event {
#[allow(clippy::new_ret_no_self)]
pub fn new(status: TestStatus) -> EventBuilder {
EventBuilder(Self {
status,
test_id: None,
timestamp: None,
file: File::default(),
route_code: None,
tags: None,
runnable: false,
})
}
pub fn to_vec(&self) -> GenResult<Vec<u8>> {
let size = self.wire_size()?.as_u32() as usize;
let mut buffer = Vec::with_capacity(size);
self.serialize(&mut buffer)?;
Ok(buffer)
}
fn make_flags(&self) -> u16 {
let mut flags = EnumSet::new();
if self.timestamp.is_some() {
flags |= EventFeatures::Timestamp;
}
if self.test_id.is_some() {
flags |= EventFeatures::TestId;
}
if self.tags.is_some() {
flags |= EventFeatures::Tags;
}
if self.file.mime_type.is_some() {
flags |= EventFeatures::FileMimeType;
}
if self.file.file.is_some() {
flags |= EventFeatures::FileContent;
}
if self.file.eof {
flags |= EventFeatures::EndOfFile;
}
if self.route_code.is_some() {
flags |= EventFeatures::RoutingCode;
}
if self.runnable {
flags |= EventFeatures::Runnable;
}
let version = 0x2000_u16;
version | flags.as_repr() | self.status as u16
}
fn packet_length(base_length: u32) -> GenResult<SubunitNumber> {
match base_length {
0..=62 => 1_u32 + base_length,
63..=16381 => 2_u32 + base_length,
16382..=4194300 => 3_u32 + base_length, _ => return Err(Error::TooLarge.into()),
}
.try_into()
}
}
impl Serializable for Event {
fn wire_size(&self) -> GenResult<SubunitNumber> {
let base_length = (V2_SIGNATURE.wire_size()?.as_u32()
+ 2 + self.timestamp.wire_size()? + self.test_id.wire_size()? + self.tags.wire_size()? + self.file.wire_size()? + self.route_code.wire_size()? + SubunitNumber::new(4_u32)?)?;
Self::packet_length(base_length.as_u32())
}
fn serialize<W: Write>(&self, out: &mut W) -> GenResult<()> {
let mut writer = Writer::new(out);
crate::constants::V2_SIGNATURE.serialize(&mut writer)?;
let flags = self.make_flags();
writer.write_all(&flags.to_be_bytes())?;
let packet_length = self.wire_size()?;
packet_length.serialize(&mut writer)?;
self.timestamp.serialize(&mut writer)?;
self.test_id.serialize(&mut writer)?;
self.tags.serialize(&mut writer)?;
self.file.serialize(&mut writer)?;
self.route_code.serialize(&mut writer)?;
let checksum = writer.finalize();
out.write_all(&checksum.to_be_bytes())?;
Ok(())
}
}
impl Deserializable for Event {
fn required_bytes(bytes: &[u8]) -> GenResult<usize> {
let mut reader = Reader::new(bytes);
let signature = safe_read!(reader.read::<u8>()?);
if signature != V2_SIGNATURE {
return Err(Error::InvalidSignature.into());
}
let flags = safe_read!(reader.read::<u16>()?);
if flags & 0xF000 != constants::VERSION2 {
return Err(Error::BadVersion(flags & 0xF00).into());
}
let _features = EnumSet::<EventFeatures>::from_repr_truncated(flags);
let packet_length = safe_read!(reader.read::<SubunitNumber>()?);
if packet_length.as_u32() > constants::MAX_PACKET_LENGTH {
return Err(Error::TooLarge.into());
}
if (packet_length.as_u32() as usize) < reader.bytes_read {
return Err(
Error::LengthTooSmall(packet_length.as_u32(), reader.bytes_read as u32).into(),
);
}
Ok(packet_length.as_u32() as usize)
}
fn deserialize(bytes: &[u8]) -> GenResult<(Self, usize)> {
let mut reader = Reader::new(bytes);
let signature = safe_de!(reader.read::<u8>()?);
if signature != V2_SIGNATURE {
return Err(Error::InvalidSignature.into());
}
let flags = safe_de!(reader.read::<u16>()?);
if flags & 0xF000 != constants::VERSION2 {
return Err(Error::BadVersion(flags & 0xF00).into());
}
let status = TestStatus::from(flags);
let features = EnumSet::<EventFeatures>::from_repr_truncated(flags);
let packet_length = safe_de!(reader.read::<SubunitNumber>()?).as_u32() as usize;
if packet_length > constants::MAX_PACKET_LENGTH as usize {
return Err(Error::TooLarge.into());
}
if packet_length < reader.bytes_read {
return Err(
Error::LengthTooSmall(packet_length as u32, reader.bytes_read as u32).into(),
);
}
reader.set_slice_end(packet_length)?;
let mut result = Event {
status,
test_id: None,
timestamp: None,
file: File::default(),
route_code: None,
tags: None,
runnable: false,
};
if features.contains(EventFeatures::Reserved) {
return Err(Error::Internal("Reserved feature".to_string()).into());
}
if features.contains(EventFeatures::Timestamp) {
let timestamp = safe_de!(reader.read_without_estimating::<Timestamp>()?);
result.timestamp = Some(timestamp);
}
if features.contains(EventFeatures::TestId) {
let test_id = safe_de!(reader.read_without_estimating::<String>()?);
result.test_id = Some(test_id);
}
if features.contains(EventFeatures::Tags) {
let tags = safe_de!(reader.read_without_estimating::<Vec<String>>()?);
result.tags = Some(tags);
}
if features.contains(EventFeatures::Runnable) {
result.runnable = true;
}
if features.contains(EventFeatures::EndOfFile) {
result.file.eof = true
}
if features.contains(EventFeatures::FileMimeType) {
let mime = safe_de!(reader.read_without_estimating::<String>()?);
result.file.mime_type = Some(mime);
}
if features.contains(EventFeatures::FileContent) {
let name = safe_de!(reader.read_without_estimating::<String>()?);
let content = safe_de!(reader.read_without_estimating::<Vec<u8>>()?);
result.file.file = Some((name, content));
}
if features.contains(EventFeatures::RoutingCode) {
let route_code = safe_de!(reader.read_without_estimating::<String>()?);
result.route_code = Some(route_code);
}
let packet_crc32 = u32::from_be_bytes(
reader.bytes[reader.bytes_read..reader.bytes_read + 4]
.try_into()
.unwrap(),
);
let measured_crc32 = reader.finalize();
if measured_crc32 != packet_crc32 {
return Err(Error::CRC32Mismatch(measured_crc32, packet_crc32).into());
}
Ok((result, packet_length))
}
}
struct Reader<'a> {
bytes: &'a [u8],
bytes_read: usize,
hasher: Hasher,
}
impl<'a> Reader<'a> {
fn new(bytes: &'a [u8]) -> Self {
Self {
bytes,
bytes_read: 0,
hasher: Hasher::new(),
}
}
fn read<T>(&mut self) -> GenResult<Result<T, usize>>
where
T: Deserializable,
{
let required = T::required_bytes(&self.bytes[self.bytes_read..])?;
if required > self.bytes.len() - self.bytes_read {
return Ok(Err(required + self.bytes_read));
}
let val = T::deserialize(&self.bytes[self.bytes_read..])?;
self.hasher
.update(&self.bytes[self.bytes_read..self.bytes_read + val.1]);
self.bytes_read += val.1;
Ok(Ok(val.0))
}
fn read_without_estimating<T>(&mut self) -> GenResult<Result<T, usize>>
where
T: Deserializable,
{
let val = T::deserialize(&self.bytes[self.bytes_read..])?;
self.hasher
.update(&self.bytes[self.bytes_read..self.bytes_read + val.1]);
self.bytes_read += val.1;
Ok(Ok(val.0))
}
fn finalize(self) -> u32 {
self.hasher.finalize()
}
fn set_slice_end(&mut self, length: usize) -> GenResult<()> {
if length > self.bytes.len() {
return Err(Error::NotEnoughBytes.into());
}
self.bytes = &self.bytes[..length];
Ok(())
}
}
#[cfg(test)]
mod tests {
use chrono::{DateTime, NaiveDate};
use crate::{
deserialize::Deserializable,
serialize::Serializable,
types::{event::Event, teststatus::TestStatus},
};
#[test]
fn test_write_event() {
let event = Event::new(TestStatus::InProgress)
.test_id("A_test_id")
.datetime(
NaiveDate::from_ymd_opt(2014, 7, 8)
.unwrap()
.and_hms_opt(9, 10, 11)
.unwrap()
.and_utc(),
)
.unwrap()
.tag("tag_a")
.tag("tag_b")
.build();
let buffer = event.to_vec().unwrap();
let out_event = Event::deserialize(&buffer).unwrap().0;
assert_eq!(event, out_event);
}
#[test]
fn test_write_full_test_event_with_file_content() {
let event = Event::new(TestStatus::InProgress)
.test_id("A_test_id")
.datetime(
NaiveDate::from_ymd_opt(2014, 7, 8)
.unwrap()
.and_hms_opt(9, 10, 11)
.unwrap()
.and_utc(),
)
.unwrap()
.tag("tag_a")
.tag("tag_b")
.mime_type("text/plain;charset=utf8")
.file_content("stdout:''", b"stdout content")
.build();
let event_a = Event::new(TestStatus::Failed)
.test_id("A_test_id")
.datetime(
NaiveDate::from_ymd_opt(2014, 7, 8)
.unwrap()
.and_hms_opt(9, 12, 1)
.unwrap()
.and_utc(),
)
.unwrap()
.tag("tag_a")
.tag("tag_b")
.build();
let mut buffer = event.to_vec().unwrap();
event_a.serialize(&mut buffer).unwrap();
let mut offset = 0;
for event in [event, event_a].iter() {
let (parsed_event, length) = Event::deserialize(&buffer[offset..]).unwrap();
assert_eq!(*event, parsed_event);
offset += length;
}
}
#[test]
fn test_reference_values() {
#[track_caller]
fn assert_reference(event: Event, buffer: &[u8]) {
let (parsed, length) = Event::deserialize(buffer).unwrap();
assert_eq!(length, buffer.len());
assert_eq!(parsed, event);
let serialized = event.to_vec().unwrap();
assert_eq!(serialized, buffer);
}
let enumerated: &[u8] = b"\xb3)\x01\x0c\x03foo\x08U_\x1b";
assert_reference(
Event::new(TestStatus::Enumeration)
.test_id("foo")
.runnable()
.build(),
enumerated,
);
let inprogress: &[u8] = b"\xb3)\x02\x0c\x03foo\x8e\xc1-\xb5";
assert_reference(
Event::new(TestStatus::InProgress)
.test_id("foo")
.runnable()
.build(),
inprogress,
);
let success: &[u8] = b"\xb3)\x03\x0c\x03fooE\x9d\xfe\x10";
assert_reference(
Event::new(TestStatus::Success)
.test_id("foo")
.runnable()
.build(),
success,
);
let uxsuccess: &[u8] = b"\xb3)\x04\x0c\x03fooX\x98\xce\xa8";
assert_reference(
Event::new(TestStatus::UnexpectedSuccess)
.test_id("foo")
.runnable()
.build(),
uxsuccess,
);
let skip: &[u8] = b"\xb3)\x05\x0c\x03foo\x93\xc4\x1d\r";
assert_reference(
Event::new(TestStatus::Skipped)
.test_id("foo")
.runnable()
.build(),
skip,
);
let fail: &[u8] = b"\xb3)\x06\x0c\x03foo\x15Po\xa3";
assert_reference(
Event::new(TestStatus::Failed)
.test_id("foo")
.runnable()
.build(),
fail,
);
let xfail: &[u8] = b"\xb3)\x07\x0c\x03foo\xde\x0c\xbc\x06";
assert_reference(
Event::new(TestStatus::ExpectedFailure)
.test_id("foo")
.runnable()
.build(),
xfail,
);
let eof: &[u8] = b"\xb3!\x10\x08S\x15\x88\xdc";
assert_reference(
Event::new(TestStatus::Undefined)
.end_of_file()
.runnable()
.build(),
eof,
);
let file_content: &[u8] = b"\xb3!@\x13\x06barney\x03wooA5\xe3\x8c";
assert_reference(
Event::new(TestStatus::Undefined)
.file_content("barney", b"woo")
.runnable()
.build(),
file_content,
);
let mime: &[u8] = b"\xb3! #\x1aapplication/foo; charset=1x3Q\x15";
assert_reference(
Event::new(TestStatus::Undefined)
.mime_type("application/foo; charset=1")
.runnable()
.build(),
mime,
);
let timestamp: &[u8] = b"\xb3+\x03\x13<\x17T\xcf\x80\xaf\xc8\x03barI\x96>-";
assert_reference(
Event::new(TestStatus::Success)
.test_id("bar")
.datetime(DateTime::from_timestamp(1008161999, 45000).unwrap())
.unwrap()
.runnable()
.build(),
timestamp,
);
let route_code: &[u8] = b"\xb3-\x03\x13\x03bar\x06source\x9cY9\x19";
assert_reference(
Event::new(TestStatus::Success)
.test_id("bar")
.route_code("source")
.runnable()
.build(),
route_code,
);
let runnable: &[u8] = b"\xb3(\x03\x0c\x03foo\xe3\xea\xf5\xa4";
assert_reference(
Event::new(TestStatus::Success).test_id("foo").build(),
runnable,
);
let tag1: &[u8] = b"\xb3)\x80\x15\x03bar\x02\x03foo\x03barTHn\xb4";
assert_reference(
Event::new(TestStatus::Undefined)
.test_id("bar")
.tag("foo")
.tag("bar")
.runnable()
.build(),
tag1,
);
let tag2: &[u8] = b"\xb3)\x80\x15\x03bar\x02\x03bar\x03foo\xf8\xf1\x91o";
assert_reference(
Event::new(TestStatus::Undefined)
.test_id("bar")
.tag("bar")
.tag("foo")
.runnable()
.build(),
tag2,
);
}
#[test]
fn packet_length() {
assert_eq!(12, Event::packet_length(11).unwrap().as_u32());
}
}