1 | //! All binary files generated by measureme have a simple file header that |
2 | //! consists of a 4 byte file magic string and a 4 byte little-endian version |
3 | //! number. |
4 | use std::convert::TryInto; |
5 | use std::error::Error; |
6 | use std::path::Path; |
7 | |
8 | pub const CURRENT_FILE_FORMAT_VERSION: u32 = 9; |
9 | |
10 | pub const FILE_MAGIC_TOP_LEVEL: &[u8; 4] = b"MMPD" ; |
11 | pub const FILE_MAGIC_EVENT_STREAM: &[u8; 4] = b"MMES" ; |
12 | pub const FILE_MAGIC_STRINGTABLE_DATA: &[u8; 4] = b"MMSD" ; |
13 | pub const FILE_MAGIC_STRINGTABLE_INDEX: &[u8; 4] = b"MMSI" ; |
14 | |
15 | pub const FILE_EXTENSION: &str = "mm_profdata" ; |
16 | |
17 | /// The size of the file header in bytes. Note that functions in this module |
18 | /// rely on this size to be `8`. |
19 | pub const FILE_HEADER_SIZE: usize = 8; |
20 | |
21 | pub fn write_file_header( |
22 | s: &mut dyn std::io::Write, |
23 | file_magic: &[u8; 4], |
24 | ) -> Result<(), Box<dyn Error + Send + Sync>> { |
25 | // The implementation here relies on FILE_HEADER_SIZE to have the value 8. |
26 | // Let's make sure this assumption cannot be violated without being noticed. |
27 | assert_eq!(FILE_HEADER_SIZE, 8); |
28 | |
29 | s.write_all(file_magic).map_err(op:Box::new)?; |
30 | s.write_all(&CURRENT_FILE_FORMAT_VERSION.to_le_bytes()) |
31 | .map_err(op:Box::new)?; |
32 | |
33 | Ok(()) |
34 | } |
35 | |
36 | #[must_use ] |
37 | pub fn verify_file_header( |
38 | bytes: &[u8], |
39 | expected_magic: &[u8; 4], |
40 | diagnostic_file_path: Option<&Path>, |
41 | stream_tag: &str, |
42 | ) -> Result<(), Box<dyn Error + Send + Sync>> { |
43 | // The implementation here relies on FILE_HEADER_SIZE to have the value 8. |
44 | // Let's make sure this assumption cannot be violated without being noticed. |
45 | assert_eq!(FILE_HEADER_SIZE, 8); |
46 | |
47 | let diagnostic_file_path = diagnostic_file_path.unwrap_or(Path::new("<in-memory>" )); |
48 | |
49 | if bytes.len() < FILE_HEADER_SIZE { |
50 | let msg = format!( |
51 | "Error reading {} stream in file ` {}`: Expected file to contain at least ` {:?}` bytes but found ` {:?}` bytes" , |
52 | stream_tag, |
53 | diagnostic_file_path.display(), |
54 | FILE_HEADER_SIZE, |
55 | bytes.len() |
56 | ); |
57 | |
58 | return Err(From::from(msg)); |
59 | } |
60 | |
61 | let actual_magic = &bytes[0..4]; |
62 | |
63 | if actual_magic != expected_magic { |
64 | let msg = format!( |
65 | "Error reading {} stream in file ` {}`: Expected file magic ` {:?}` but found ` {:?}`" , |
66 | stream_tag, |
67 | diagnostic_file_path.display(), |
68 | expected_magic, |
69 | actual_magic |
70 | ); |
71 | |
72 | return Err(From::from(msg)); |
73 | } |
74 | |
75 | let file_format_version = u32::from_le_bytes(bytes[4..8].try_into().unwrap()); |
76 | |
77 | if file_format_version != CURRENT_FILE_FORMAT_VERSION { |
78 | let msg = format!( |
79 | "Error reading {} stream in file ` {}`: Expected file format version {} but found ` {}`" , |
80 | stream_tag, |
81 | diagnostic_file_path.display(), |
82 | CURRENT_FILE_FORMAT_VERSION, |
83 | file_format_version |
84 | ); |
85 | |
86 | return Err(From::from(msg)); |
87 | } |
88 | |
89 | Ok(()) |
90 | } |
91 | |
92 | pub fn strip_file_header(data: &[u8]) -> &[u8] { |
93 | &data[FILE_HEADER_SIZE..] |
94 | } |
95 | |
96 | #[cfg (test)] |
97 | mod tests { |
98 | use super::*; |
99 | use crate::{PageTag, SerializationSinkBuilder}; |
100 | |
101 | #[test ] |
102 | fn roundtrip() { |
103 | let data_sink = SerializationSinkBuilder::new_in_memory().new_sink(PageTag::Events); |
104 | |
105 | write_file_header(&mut data_sink.as_std_write(), FILE_MAGIC_EVENT_STREAM).unwrap(); |
106 | |
107 | let data = data_sink.into_bytes(); |
108 | |
109 | verify_file_header(&data, FILE_MAGIC_EVENT_STREAM, None, "test" ).unwrap(); |
110 | } |
111 | |
112 | #[test ] |
113 | fn invalid_magic() { |
114 | let data_sink = SerializationSinkBuilder::new_in_memory().new_sink(PageTag::Events); |
115 | write_file_header(&mut data_sink.as_std_write(), FILE_MAGIC_STRINGTABLE_DATA).unwrap(); |
116 | let mut data = data_sink.into_bytes(); |
117 | |
118 | // Invalidate the filemagic |
119 | data[2] = 0; |
120 | assert!(verify_file_header(&data, FILE_MAGIC_STRINGTABLE_DATA, None, "test" ).is_err()); |
121 | } |
122 | |
123 | #[test ] |
124 | fn other_version() { |
125 | let data_sink = SerializationSinkBuilder::new_in_memory().new_sink(PageTag::Events); |
126 | |
127 | write_file_header(&mut data_sink.as_std_write(), FILE_MAGIC_STRINGTABLE_INDEX).unwrap(); |
128 | |
129 | let mut data = data_sink.into_bytes(); |
130 | |
131 | // Change version |
132 | data[4] = 0xFF; |
133 | data[5] = 0xFF; |
134 | data[6] = 0xFF; |
135 | data[7] = 0xFF; |
136 | assert!(verify_file_header(&data, FILE_MAGIC_STRINGTABLE_INDEX, None, "test" ).is_err()); |
137 | } |
138 | |
139 | #[test ] |
140 | fn empty_file() { |
141 | let data: [u8; 0] = []; |
142 | |
143 | assert!(verify_file_header(&data, FILE_MAGIC_STRINGTABLE_DATA, None, "test" ).is_err()); |
144 | } |
145 | } |
146 | |