sleep_parser/
lib.rs

1#![forbid(unsafe_code, missing_debug_implementations, missing_docs)]
2#![cfg_attr(test, deny(warnings))]
3
4//! ## Example
5//! ```rust
6//! extern crate sleep_parser as sleep_parser;
7//!
8//! use sleep_parser::{FileType, HashType, Header};
9//! use std::fs::File;
10//! use std::io::{BufRead, BufReader};
11//!
12//! let file = File::open("tests/fixtures/content.bitfield").unwrap();
13//! let mut reader = BufReader::with_capacity(32, file);
14//! let buffer = reader.fill_buf().unwrap();
15//! let header = Header::from_vec(&buffer).unwrap();
16//! assert!(header.is_bitfield());
17//! ```
18
19mod file_type;
20mod hash_type;
21mod protocol_version;
22
23use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
24use failure::{bail, ensure, format_err, Error};
25use std::io::Cursor;
26
27pub use crate::file_type::FileType;
28pub use crate::hash_type::HashType;
29pub use crate::protocol_version::ProtocolVersion;
30
31const HEADER_LENGTH: usize = 32;
32const MAX_ALGORITHM_NAME_LENGTH: usize = HEADER_LENGTH - 8;
33
34/// Structural representation of 32 byte SLEEP headers.
35#[derive(Debug)]
36pub struct Header {
37  /// Type of file.
38  pub file_type: FileType,
39  /// Version of the SLEEP protocol.
40  pub protocol_version: ProtocolVersion,
41  /// Size of each piece of data in the file body.
42  pub entry_size: u16,
43  /// Algorithm used for hashing the content.
44  pub hash_type: HashType,
45}
46
47impl Header {
48  /// Create a new `Header`.
49  #[inline]
50  pub fn new(
51    file_type: FileType,
52    entry_size: u16,
53    hash_type: HashType,
54  ) -> Self {
55    Header {
56      file_type,
57      entry_size,
58      hash_type,
59      protocol_version: ProtocolVersion::V0,
60    }
61  }
62
63  /// Parse a 32 byte buffer slice into a valid Header.
64  #[inline]
65  pub fn from_vec(buffer: &[u8]) -> Result<Header, Error> {
66    ensure!(buffer.len() == 32, "buffer should be 32 bytes");
67
68    let mut rdr = Cursor::new(buffer);
69    let byte = rdr.read_u8().unwrap();
70    ensure!(
71      byte == 5,
72      "The first byte of a SLEEP header should be '5', found {}",
73      byte
74    );
75
76    let byte = rdr.read_u8().unwrap();
77    ensure!(
78      byte == 2,
79      "The second byte of a SLEEP header should be '2', found {}",
80      byte
81    );
82
83    let byte = rdr.read_u8().unwrap();
84    ensure!(
85      byte == 87,
86      "The third byte of a SLEEP header should be '87', found {}",
87      byte
88    );
89
90    let file_type = match rdr.read_u8().unwrap() {
91      0 => FileType::BitField,
92      1 => FileType::Signatures,
93      2 => FileType::Tree,
94      num => bail!(
95        "The fourth byte '{}' does not belong to any known SLEEP file type",
96        num
97      ),
98    };
99
100    let protocol_version = match rdr.read_u8().unwrap() {
101      0 => ProtocolVersion::V0,
102      num => bail!(
103        "The fifth byte '{}' does not belong to any known SLEEP protocol protocol_version",
104        num
105      ),
106    };
107
108    // Read entry size which will inform how many bytes to read next.
109    let entry_size = rdr.read_u16::<BigEndian>().unwrap();
110
111    // Read out the "entry_size" bytes into a string.
112    // NOTE(yw): there should be a more concise way of doing this.
113    let hash_name_len = rdr.read_u8().unwrap() as usize;
114    let current = rdr.position() as usize;
115
116    ensure!(
117      hash_name_len <= MAX_ALGORITHM_NAME_LENGTH,
118      "Algorithm name is too long: {} (max: {})",
119      hash_name_len,
120      MAX_ALGORITHM_NAME_LENGTH
121    );
122
123    let hash_name_upper = current + hash_name_len;
124    ensure!(
125      buffer.len() >= hash_name_upper,
126      "Broken parser: algorithm name is out of bounds: {} {}",
127      hash_name_upper,
128      buffer.len()
129    );
130
131    let buf_slice = &buffer[current..hash_name_upper];
132    rdr.set_position(hash_name_upper as u64 + 1);
133    let algo = ::std::str::from_utf8(buf_slice).map_err(|e| {
134      format_err!("The algorithm string was invalid utf8 encoded: {:?}", e)
135    })?;
136
137    let hash_type = match algo {
138      "BLAKE2b" => HashType::BLAKE2b,
139      "Ed25519" => HashType::Ed25519,
140      "" => HashType::None,
141      name => bail!("Unexpected algorithm name: {}", name),
142    };
143
144    Ok(Header {
145      protocol_version,
146      entry_size,
147      file_type,
148      hash_type,
149    })
150  }
151
152  /// Convert a `Header` into a `Vec<u8>`. Use this to persist a header back to
153  /// disk.
154  #[inline]
155  pub fn to_vec(&self) -> Vec<u8> {
156    let mut wtr = Vec::with_capacity(32);
157
158    wtr.extend_from_slice(&[5u8, 2, 87]);
159
160    let file_type = match self.file_type {
161      FileType::BitField => 0,
162      FileType::Signatures => 1,
163      FileType::Tree => 2,
164    };
165    wtr.write_u8(file_type).unwrap();
166
167    let protocol_version = match self.protocol_version {
168      ProtocolVersion::V0 => 0,
169    };
170    wtr.write_u8(protocol_version).unwrap();
171
172    wtr.write_u16::<BigEndian>(self.entry_size).unwrap();
173
174    let hash_type = match self.hash_type {
175      HashType::BLAKE2b => "BLAKE2b",
176      HashType::Ed25519 => "Ed25519",
177      HashType::None => "",
178    };
179    let hash_type = hash_type.as_bytes();
180    wtr.write_u8(hash_type.len() as u8).unwrap();
181    wtr.extend_from_slice(hash_type);
182
183    for _ in wtr.len()..wtr.capacity() {
184      wtr.write_u8(0).unwrap();
185    }
186    wtr
187  }
188
189  /// Check whether the header is formatted as a `.bitfield`.
190  #[inline]
191  pub fn is_bitfield(&self) -> bool {
192    self.entry_size == 3328
193      && self.file_type.is_bitfield()
194      && self.hash_type.is_none()
195  }
196
197  /// Check whether the header is formatted as a `.signatures`.
198  #[inline]
199  pub fn is_signatures(&self) -> bool {
200    self.entry_size == 64
201      && self.file_type.is_signatures()
202      && self.hash_type.is_ed25519()
203  }
204
205  /// Check whether the header is formatted as a `.tree`.
206  #[inline]
207  pub fn is_tree(&self) -> bool {
208    self.entry_size == 40
209      && self.file_type.is_tree()
210      && self.hash_type.is_blake2b()
211  }
212}
213
214/// Create a new `Header` in the `Bitfield` configuration.
215#[inline]
216pub fn create_bitfield() -> Header {
217  Header::new(FileType::BitField, 3328, HashType::None)
218}
219
220/// Create a new `Header` in the `Signatures` configuration.
221#[inline]
222pub fn create_signatures() -> Header {
223  Header::new(FileType::Signatures, 64, HashType::Ed25519)
224}
225
226/// Create a new `Header` in the `Tree` configuration.
227#[inline]
228pub fn create_tree() -> Header {
229  Header::new(FileType::Tree, 40, HashType::BLAKE2b)
230}