1#![allow(clippy::cargo)]
14#![allow(clippy::unreadable_literal)]
15
16extern crate byteorder;
17extern crate chrono;
18extern crate crc32fast;
19
20use std::collections::HashSet;
21use std::error::Error;
22use std::fmt;
23use std::io::Cursor;
24use std::io::Read;
25use std::io::Write;
26
27use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
28use chrono::prelude::*;
29
30#[derive(Debug, Clone)]
31pub struct SizeError;
32#[derive(Debug, Clone)]
33pub struct InvalidFlag;
34#[derive(Debug, Clone)]
35pub struct InvalidMask;
36
37type GenError = Box<dyn Error>;
38type GenResult<T> = Result<T, GenError>;
39
40const SIGNATURE: u8 = 0xb3;
41
42impl fmt::Display for SizeError {
43 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
44 write!(f, "Value is too large to encode")
45 }
46}
47
48impl Error for SizeError {
49 fn description(&self) -> &str {
50 "Value is too large to encode"
51 }
52
53 fn cause(&self) -> Option<&dyn Error> {
54 None
55 }
56}
57
58impl fmt::Display for InvalidMask {
59 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
60 write!(f, "Mask code is invalid")
61 }
62}
63
64impl Error for InvalidMask {
65 fn description(&self) -> &str {
66 "Mask code is valid"
67 }
68
69 fn cause(&self) -> Option<&dyn Error> {
70 None
71 }
72}
73
74impl fmt::Display for InvalidFlag {
75 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
76 write!(f, "Flag code is invalid")
77 }
78}
79
80impl Error for InvalidFlag {
81 fn description(&self) -> &str {
82 "Flag code is invalid"
83 }
84
85 fn cause(&self) -> Option<&dyn Error> {
86 None
87 }
88}
89
90fn flag_to_status(flag: u8) -> Result<String, InvalidFlag> {
91 match flag {
92 0x0 => Result::Ok("".to_string()),
93 0x1 => Result::Ok("exists".to_string()),
94 0x2 => Result::Ok("inprogress".to_string()),
95 0x3 => Result::Ok("success".to_string()),
96 0x4 => Result::Ok("uxsuccess".to_string()),
97 0x5 => Result::Ok("skip".to_string()),
98 0x6 => Result::Ok("fail".to_string()),
99 0x7 => Result::Ok("xfail".to_string()),
100 _ => Result::Err(InvalidFlag),
101 }
102}
103
104fn status_to_flag(status: &str) -> Result<u16, InvalidFlag> {
105 if status.is_empty() {
106 Result::Ok(0x0)
107 } else if status == "exists" {
108 Result::Ok(0x1)
109 } else if status == "inprogress" {
110 Result::Ok(0x2)
111 } else if status == "success" {
112 Result::Ok(0x3)
113 } else if status == "uxsuccess" {
114 Result::Ok(0x4)
115 } else if status == "skip" {
116 Result::Ok(0x5)
117 } else if status == "fail" {
118 Result::Ok(0x6)
119 } else if status == "xfail" {
120 Result::Ok(0x7)
121 } else {
122 Result::Err(InvalidFlag)
123 }
124}
125
126fn flag_masks(masks: &str) -> Result<u16, InvalidMask> {
127 match masks {
128 "testId" => Result::Ok(0x0800),
129 "routeCode" => Result::Ok(0x0400),
130 "timestamp" => Result::Ok(0x0200),
131 "runnable" => Result::Ok(0x0100),
132 "tags" => Result::Ok(0x0080),
133 "mimeType" => Result::Ok(0x0020),
134 "eof" => Result::Ok(0x0010),
135 "fileContent" => Result::Ok(0x0040),
136 _ => Result::Err(InvalidMask),
137 }
138}
139
140fn flags_to_masks(flags: u16) -> GenResult<HashSet<String>> {
141 let static_flags: [u16; 8] = [
142 0x0800, 0x0400, 0x0200, 0x0100, 0x0080, 0x0020, 0x0010, 0x0040,
143 ];
144 let mut masks: HashSet<String> = HashSet::new();
145 for flag in static_flags.iter() {
146 if flags & *flag != 0 {
147 if *flag == 0x0800 {
148 masks.insert("testId".to_string());
149 } else if *flag == 0x0400 {
150 masks.insert("routeCode".to_string());
151 } else if *flag == 0x0200 {
152 masks.insert("timestamp".to_string());
153 } else if *flag == 0x0100 {
154 masks.insert("runnable".to_string());
155 } else if *flag == 0x0080 {
156 masks.insert("tags".to_string());
157 } else if *flag == 0x0020 {
158 masks.insert("mimeType".to_string());
159 } else if *flag == 0x0010 {
160 masks.insert("eof".to_string());
161 } else if *flag == 0x0040 {
162 masks.insert("fileContent".to_string());
163 }
164 }
165 }
166 Result::Ok(masks)
167}
168
169fn write_number<T: Write>(value: u32, mut ret: T) -> GenResult<T> {
170 if value < 64 {
178 ret.write_u8(value as u8)?
180 } else if value < 16384 {
182 ret.write_u16::<BigEndian>(value as u16 | 0x4000)?
185 } else if value < 4194304 {
187 ret.write_u8(((value >> 16) | 0x80) as u8)?;
190 ret.write_u16::<BigEndian>(value as u16)?;
191 } else if value < 1073741824 {
193 ret.write_u32::<BigEndian>(value | 0xC0000000)?;
196 } else {
197 return Result::Err(Box::new(SizeError));
198 }
199 Result::Ok(ret)
200}
201
202fn write_utf8<T: Write>(string: &str, mut out: T) -> GenResult<T> {
203 out = write_number(string.len() as u32, out)?;
204 out.write_all(string.as_bytes())?;
205 Result::Ok(out)
206}
207
208pub fn read_number(reader: &mut Cursor<Vec<u8>>) -> GenResult<u32> {
209 let first = reader.read_u8()?;
210 let number_type = first & 0xc0;
212 let mut value = u32::from(first) & 0x3f;
214 if number_type == 0x00 {
216 Result::Ok(value as u32)
217 } else if number_type == 0x40 {
219 let suffix = reader.read_u8()?;
220 value = (value << 8) | u32::from(suffix);
221 Result::Ok(value as u32)
222 } else if number_type == 0x80 {
224 let suffix = reader.read_u16::<BigEndian>()?;
225 value = (value << 16) | u32::from(suffix);
226 Result::Ok(value as u32)
227 } else {
229 let suffix = reader.read_u32::<BigEndian>()?;
230 value = (value << 24) | suffix;
231 Result::Ok(value as u32)
232 }
233}
234
235fn read_utf8(reader: &mut Cursor<Vec<u8>>) -> GenResult<String> {
236 let length = read_number(reader)?;
237 let mut bytes: Vec<u8> = Vec::new();
238 for _i in 0..length {
239 let byte = reader.read_u8()?;
240 bytes.push(byte)
241 }
242 let output = String::from_utf8(bytes)?;
243 Result::Ok(output)
244}
245
246fn read_packet(cursor: &mut Cursor<Vec<u8>>) -> GenResult<Event> {
247 let start_position = cursor.position();
248 let sig = cursor.read_u8()?;
249 let flags = cursor.read_u16::<BigEndian>()?;
250 let packet_length = read_number(cursor)?;
251 if sig != SIGNATURE {
252 panic!("Invalid signature");
253 }
254 let status = flag_to_status((flags & 0x0007) as u8)?;
255 let masks = flags_to_masks(flags)?;
256
257 let timestamp = if masks.contains("timestamp") {
258 let seconds = cursor.read_u32::<BigEndian>()?;
259 let nanos = read_number(cursor)?;
260 Some(Utc.timestamp(i64::from(seconds), nanos))
261 } else {
262 None
263 };
264 let test_id = if masks.contains("testId") {
265 let id = read_utf8(cursor)?;
266 Some(id)
267 } else {
268 None
269 };
270 let tags = if masks.contains("tags") {
271 let count = read_number(cursor)?;
272 let mut tags_vec: Vec<String> = Vec::new();
273 for _i in 0..count {
274 let tag = read_utf8(cursor)?;
275 tags_vec.push(tag);
276 }
277 Some(tags_vec)
278 } else {
279 None
280 };
281 let mime_type = if masks.contains("mimeType") {
282 let mime = read_utf8(cursor)?;
283 Some(mime)
284 } else {
285 None
286 };
287 let file_content;
288 let file_name;
289 if masks.contains("fileContent") {
290 let name = read_utf8(cursor)?;
291 file_name = Some(name);
292 let file_length = read_number(cursor)?;
293 let mut content: Vec<u8> = Vec::new();
294 for _i in 0..file_length {
295 let byte = cursor.read_u8()?;
296 content.push(byte);
297 }
298 file_content = Some(content);
299 } else {
300 file_content = None;
301 file_name = None;
302 }
303
304 let route_code = if masks.contains("routeCode") {
305 let code = read_utf8(cursor)?;
306 Some(code)
307 } else {
308 None
309 };
310 let _crc32 = cursor.read_u32::<BigEndian>()?;
311 let end_position = cursor.position();
312 if u64::from(packet_length) != (end_position - start_position) {
313 panic!("Packet length doesn't match");
314 }
315
316 let event = Event {
317 status: Some(status),
318 test_id,
319 timestamp,
320 tags,
321 file_content,
322 file_name,
323 mime_type,
324 route_code,
325 };
326 Result::Ok(event)
327}
328
329pub fn parse_subunit<T: Read>(mut reader: T) -> GenResult<Vec<Event>> {
330 let mut output: Vec<Event> = Vec::new();
331 let mut contents: Vec<u8> = Vec::new();
332 reader.read_to_end(&mut contents)?;
333 let stream_length = contents.len() as u64;
334 let cursor = &mut Cursor::new(contents);
335 while cursor.position() < stream_length {
336 let packet = read_packet(cursor)?;
337 output.push(packet);
338 }
339 Result::Ok(output)
340}
341
342pub struct Event {
343 pub status: Option<String>,
344 pub test_id: Option<String>,
345 pub timestamp: Option<DateTime<Utc>>,
346 pub file_name: Option<String>,
347 pub file_content: Option<Vec<u8>>,
348 pub mime_type: Option<String>,
349 pub route_code: Option<String>,
350 pub tags: Option<Vec<String>>,
351}
352
353impl Event {
354 pub fn write<T: Write>(&mut self, mut writer: T) -> GenResult<T> {
355 let flags = self.make_flags()?;
358 let timestamp = self.make_timestamp()?;
359 let test_id = self.make_test_id()?;
360 let tags = self.make_tags()?;
361 let mime_type = self.make_mime_type()?;
362 let file_content = self.make_file_content()?;
363 let routing_code = self.make_routing_code()?;
364
365 let mut buffer: Vec<u8> = Vec::new();
366 let mut body_length = timestamp.len() + test_id.len() + tags.len();
367 body_length += mime_type.len() + file_content.len();
368 body_length += routing_code.len();
369 let base_length = 3 + body_length + 4;
371 let length;
374 if base_length <= 62 {
375 length = base_length + 1;
376 } else if base_length <= 16381 {
378 length = base_length + 2;
379 } else if base_length <= 4194300 {
381 length = base_length + 3;
382 } else {
383 panic!("The packet is too large");
384 }
385
386 buffer.write_u8(SIGNATURE)?;
388 buffer.write_u16::<BigEndian>(flags)?;
389 buffer = write_number(length as u32, buffer)?;
390
391 buffer.write_all(×tamp)?;
392 buffer.write_all(&test_id)?;
393 buffer.write_all(&tags)?;
394 buffer.write_all(&mime_type)?;
395 buffer.write_all(&file_content)?;
396 buffer.write_all(&routing_code)?;
397 let checksum = crc32fast::hash(&buffer);
399 writer.write_all(&buffer)?;
400 writer.write_u32::<BigEndian>(checksum)?;
401 Result::Ok(writer)
402 }
403 fn make_routing_code(&self) -> GenResult<Vec<u8>> {
404 let mut routing_code: Vec<u8> = Vec::new();
405 if self.route_code.is_some() {
406 routing_code = write_utf8(self.route_code.as_ref().unwrap(), routing_code)?;
407 }
408 Result::Ok(routing_code)
409 }
410
411 fn make_file_content(&self) -> GenResult<Vec<u8>> {
412 let mut file_content: Vec<u8> = Vec::new();
413 if self.file_name.is_some() {
414 match self.file_content {
415 Option::Some(ref body) => {
416 file_content = write_utf8(self.file_name.as_ref().unwrap(), file_content)?;
417 let len = self.file_content.as_ref().unwrap().len();
418 file_content = write_number(len as u32, file_content)?;
419 file_content.write_all(body)?;
420 }
421 Option::None => (), }
423 }
424 Result::Ok(file_content)
425 }
426
427 fn make_mime_type(&self) -> GenResult<Vec<u8>> {
428 let mut mime_type: Vec<u8> = Vec::new();
429 if self.mime_type.is_some() {
430 mime_type = write_utf8(self.mime_type.as_ref().unwrap(), mime_type)?;
431 }
432 Result::Ok(mime_type)
433 }
434
435 fn make_tags(&self) -> GenResult<Vec<u8>> {
436 let mut tags: Vec<u8> = Vec::new();
437 if self.tags.is_some() {
438 let len = self.tags.as_ref().unwrap().len();
439 tags = write_number(len as u32, tags)?;
440 for tag in self.tags.as_ref().unwrap() {
441 tags = write_utf8(tag, tags)?;
442 }
443 }
444 Result::Ok(tags)
445 }
446
447 fn make_test_id(&self) -> GenResult<Vec<u8>> {
448 let mut test_id: Vec<u8> = Vec::new();
449 if self.test_id.is_some() {
450 let raw_id = self.test_id.as_ref().unwrap();
451 test_id = write_utf8(raw_id, test_id)?;
452 }
453 Result::Ok(test_id)
454 }
455
456 fn make_timestamp(&self) -> GenResult<Vec<u8>> {
457 let mut timestamp: Vec<u8> = Vec::new();
458 if self.timestamp.is_some() {
459 let secs = self.timestamp.unwrap().timestamp() as u32;
460 timestamp.write_u32::<BigEndian>(secs)?;
461 let subsec_nanos = self.timestamp.unwrap().timestamp_subsec_nanos();
462 timestamp = write_number(subsec_nanos, timestamp)?;
463 }
464 Result::Ok(timestamp)
465 }
466
467 fn make_flags(&self) -> GenResult<u16> {
468 let mut flags = 0x2000_u16; if self.status.is_some() {
470 flags |= status_to_flag(self.status.as_ref().unwrap())?;
471 }
472
473 if self.timestamp.is_some() {
474 flags |= flag_masks("timestamp")?;
475 }
476 if self.test_id.is_some() {
477 flags |= flag_masks("testId")?;
478 }
479 if self.tags.is_some() {
480 flags |= flag_masks("tags")?;
481 }
482 if self.mime_type.is_some() {
483 flags |= flag_masks("mimeType")?;
484 }
485 if self.file_name.is_some() && self.file_content.is_some() {
486 flags |= flag_masks("fileContent")?;
487 }
488 if self.route_code.is_some() {
489 flags |= flag_masks("routeCode")?;
490 }
491 Result::Ok(flags)
492 }
493}
494
495#[cfg(test)]
496mod tests {
497 use super::*;
498
499 #[test]
500 fn test_write_event() {
501 let mut event = Event {
502 status: Some("inprogress".to_string()),
503 test_id: Some("A_test_id".to_string()),
504 timestamp: Some(Utc.ymd(2014, 7, 8).and_hms(9, 10, 11)),
505 tags: Some(vec!["tag_a".to_string(), "tag_b".to_string()]),
506 file_content: None,
507 file_name: None,
508 mime_type: None,
509 route_code: None,
510 };
511 let mut buffer: Vec<u8> = Vec::new();
512 buffer = match event.write(buffer) {
516 Result::Ok(buffer) => buffer,
517 Result::Err(err) => panic!("Error while generating subunit {}", err),
518 };
519 let cursor = Cursor::new(buffer);
520 let out_events = parse_subunit(cursor);
521 let out_event = out_events.unwrap().pop().unwrap();
522 assert_eq!(event.test_id, out_event.test_id);
523 assert_eq!(event.status, out_event.status);
524 assert_eq!(event.timestamp, out_event.timestamp);
525 assert_eq!(event.tags, out_event.tags);
526 assert_eq!(event.file_content, out_event.file_content);
527 assert_eq!(event.file_name, out_event.file_name);
528 assert_eq!(event.mime_type, out_event.mime_type);
529 assert_eq!(event.route_code, out_event.route_code);
530 }
531 #[test]
532 fn test_write_full_test_event_with_file_content() {
533 let mut event = Event {
534 status: Some("inprogress".to_string()),
535 test_id: Some("A_test_id".to_string()),
536 timestamp: Some(Utc.ymd(2014, 7, 8).and_hms(9, 10, 11)),
537 tags: Some(vec!["tag_a".to_string(), "tag_b".to_string()]),
538 file_content: Some("stdout content".to_string().into_bytes()),
539 file_name: Some("stdout:''".to_string()),
540 mime_type: Some("text/plain;charset=utf8".to_string()),
541 route_code: None,
542 };
543 let mut event_a = Event {
544 status: Some("fail".to_string()),
545 test_id: Some("A_test_id".to_string()),
546 timestamp: Some(Utc.ymd(2014, 7, 8).and_hms(9, 12, 1)),
547 tags: Some(vec!["tag_a".to_string(), "tag_b".to_string()]),
548 file_content: None,
549 file_name: None,
550 mime_type: None,
551 route_code: None,
552 };
553 let mut buffer: Vec<u8> = Vec::new();
554 buffer = match event.write(buffer) {
558 Result::Ok(buffer) => buffer,
559 Result::Err(err) => panic!("Error while generating subunit {}", err),
560 };
561 buffer = match event_a.write(buffer) {
562 Result::Ok(buffer) => buffer,
563 Result::Err(err) => panic!("Error while generating subunit {}", err),
564 };
565 let cursor = Cursor::new(buffer);
566 let mut out_events = parse_subunit(cursor).unwrap();
567 let out_event_a = out_events.pop().unwrap();
569 assert_eq!(event_a.test_id, out_event_a.test_id);
570 assert_eq!(event_a.status, out_event_a.status);
571 assert_eq!(event_a.timestamp, out_event_a.timestamp);
572 assert_eq!(event_a.tags, out_event_a.tags);
573 assert_eq!(event_a.file_content, out_event_a.file_content);
574 assert_eq!(event_a.file_name, out_event_a.file_name);
575 assert_eq!(event_a.mime_type, out_event_a.mime_type);
576 assert_eq!(event_a.route_code, out_event_a.route_code);
577 let out_event = out_events.pop().unwrap();
579 assert_eq!(event.test_id, out_event.test_id);
580 assert_eq!(event.status, out_event.status);
581 assert_eq!(event.timestamp, out_event.timestamp);
582 assert_eq!(event.tags, out_event.tags);
583 assert_eq!(event.file_content, out_event.file_content);
584 assert_eq!(event.file_name, out_event.file_name);
585 assert_eq!(event.mime_type, out_event.mime_type);
586 assert_eq!(event.route_code, out_event.route_code);
587 }
588}