subunit_rust/
lib.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License at
4//
5//     http://www.apache.org/licenses/LICENSE-2.0
6//
7// Unless required by applicable law or agreed to in writing, software
8// distributed under the License is distributed on an "AS IS" BASIS,
9// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10// See the License for the specific language governing permissions and
11// limitations under the License.
12
13#![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    // The first two bits encode the size:
171    // 00 = 1 byte
172    // 01 = 2 bytes
173    // 10 = 3 bytes
174    // 11 = 4 bytes
175
176    // 2^(8-2)
177    if value < 64 {
178        // Fits in one byte.
179        ret.write_u8(value as u8)?
180    // 2^(16-2):
181    } else if value < 16384 {
182        // Fits in two bytes.
183        // Set the size to 01.
184        ret.write_u16::<BigEndian>(value as u16 | 0x4000)?
185    // 2^(24-2):
186    } else if value < 4194304 {
187        // Fits in three bytes.
188        // Drop the two least significant bytes and set the size to 10.
189        ret.write_u8(((value >> 16) | 0x80) as u8)?;
190        ret.write_u16::<BigEndian>(value as u16)?;
191    // 2^(32-2):
192    } else if value < 1073741824 {
193        // Fits in four bytes.
194        // Set the size to 11.
195        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    // Get 2 first bits for prefix
211    let number_type = first & 0xc0;
212    // Get last 6 bits for first octet
213    let mut value = u32::from(first) & 0x3f;
214    // 0b00, 1 octet
215    if number_type == 0x00 {
216        Result::Ok(value as u32)
217    // 0b01, 2octets
218    } 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    // 0b10, 3 octets
223    } 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    // 0b11, 4 octets
228    } 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        //  PACKET = SIGNATURE FLAGS PACKET_LENGTH TIMESTAMP? TESTID? TAGS?
356        //           MIME? FILECONTENT? ROUTING_CODE? CRC32
357        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        // baseLength = header (minus variant length) + body + crc32
370        let base_length = 3 + body_length + 4;
371        // length of length depends on baseLength and its own length
372        // 63 - 1
373        let length;
374        if base_length <= 62 {
375            length = base_length + 1;
376        // 16383 - 2
377        } else if base_length <= 16381 {
378            length = base_length + 2;
379        // 4194303 - 3
380        } else if base_length <= 4194300 {
381            length = base_length + 3;
382        } else {
383            panic!("The packet is too large");
384        }
385
386        // Write event to stream
387        buffer.write_u8(SIGNATURE)?;
388        buffer.write_u16::<BigEndian>(flags)?;
389        buffer = write_number(length as u32, buffer)?;
390
391        buffer.write_all(&timestamp)?;
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        // Flush buffer into output and digest to calculate crc32
398        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 => (), // missing body is ok ?
422            }
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; // version 0x2
469        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        //        use std::fs::File;
513        //        let mut buffer = File::create("/tmp/test.subunit").unwrap();
514
515        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        //        use std::fs::File;
555        //        let mut buffer = File::create("/tmp/test2.subunit").unwrap();
556
557        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        // Parse last packet
568        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        // Parse first packet
578        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}