ring/testutil.rs
1// Copyright 2015-2016 Brian Smith.
2//
3// Permission to use, copy, modify, and/or distribute this software for any
4// purpose with or without fee is hereby granted, provided that the above
5// copyright notice and this permission notice appear in all copies.
6//
7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
10// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
12// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
13// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
15//! Testing framework.
16//!
17//! Unlike the rest of *ring*, this testing framework uses panics pretty
18//! liberally. It was originally designed for internal use--it drives most of
19//! *ring*'s internal tests, and so it is optimized for getting *ring*'s tests
20//! written quickly at the expense of some usability. The documentation is
21//! lacking. The best way to learn it is to look at some examples. The digest
22//! tests are the most complicated because they use named sections. Other tests
23//! avoid named sections and so are easier to understand.
24//!
25//! # Examples
26//!
27//! ## Writing Tests
28//!
29//! Input files look like this:
30//!
31//! ```text
32//! # This is a comment.
33//!
34//! HMAC = SHA1
35//! Input = "My test data"
36//! Key = ""
37//! Output = 61afdecb95429ef494d61fdee15990cabf0826fc
38//!
39//! HMAC = SHA256
40//! Input = "Sample message for keylen<blocklen"
41//! Key = 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F
42//! Output = A28CF43130EE696A98F14A37678B56BCFCBDD9E5CF69717FECF5480F0EBDF790
43//! ```
44//!
45//! Test cases are separated with blank lines. Note how the bytes of the `Key`
46//! attribute are specified as a quoted string in the first test case and as
47//! hex in the second test case; you can use whichever form is more convenient
48//! and you can mix and match within the same file. The empty sequence of bytes
49//! can only be represented with the quoted string form (`""`).
50//!
51//! Here's how you would consume the test data:
52//!
53//! ```ignore
54//! use ring::test;
55//!
56//! test::run(test::test_vector_file!("hmac_tests.txt"), |section, test_case| {
57//! assert_eq!(section, ""); // This test doesn't use named sections.
58//!
59//! let digest_alg = test_case.consume_digest_alg("HMAC");
60//! let input = test_case.consume_bytes("Input");
61//! let key = test_case.consume_bytes("Key");
62//! let output = test_case.consume_bytes("Output");
63//!
64//! // Do the actual testing here
65//! });
66//! ```
67//!
68//! Note that `consume_digest_alg` automatically maps the string "SHA1" to a
69//! reference to `digest::SHA1_FOR_LEGACY_USE_ONLY`, "SHA256" to
70//! `digest::SHA256`, etc.
71//!
72//! ## Output When a Test Fails
73//!
74//! When a test case fails, the framework automatically prints out the test
75//! case. If the test case failed with a panic, then the backtrace of the panic
76//! will be printed too. For example, let's say the failing test case looks
77//! like this:
78//!
79//! ```text
80//! Curve = P-256
81//! a = 2b11cb945c8cf152ffa4c9c2b1c965b019b35d0b7626919ef0ae6cb9d232f8af
82//! b = 18905f76a53755c679fb732b7762251075ba95fc5fedb60179e730d418a9143c
83//! r = 18905f76a53755c679fb732b7762251075ba95fc5fedb60179e730d418a9143c
84//! ```
85//! If the test fails, this will be printed (if `$RUST_BACKTRACE` is `1`):
86//!
87//! ```text
88//! src/example_tests.txt: Test panicked.
89//! Curve = P-256
90//! a = 2b11cb945c8cf152ffa4c9c2b1c965b019b35d0b7626919ef0ae6cb9d232f8af
91//! b = 18905f76a53755c679fb732b7762251075ba95fc5fedb60179e730d418a9143c
92//! r = 18905f76a53755c679fb732b7762251075ba95fc5fedb60179e730d418a9143c
93//! thread 'example_test' panicked at 'Test failed.', src\testutil:206
94//! stack backtrace:
95//! 0: 0x7ff654a05c7c - std::rt::lang_start::h61f4934e780b4dfc
96//! 1: 0x7ff654a04f32 - std::rt::lang_start::h61f4934e780b4dfc
97//! 2: 0x7ff6549f505d - std::panicking::rust_panic_with_hook::hfe203e3083c2b544
98//! 3: 0x7ff654a0825b - rust_begin_unwind
99//! 4: 0x7ff6549f63af - std::panicking::begin_panic_fmt::h484cd47786497f03
100//! 5: 0x7ff654a07e9b - rust_begin_unwind
101//! 6: 0x7ff654a0ae95 - core::panicking::panic_fmt::h257ceb0aa351d801
102//! 7: 0x7ff654a0b190 - core::panicking::panic::h4bb1497076d04ab9
103//! 8: 0x7ff65496dc41 - from_file<closure>
104//! at C:\Users\Example\example\<core macros>:4
105//! 9: 0x7ff65496d49c - example_test
106//! at C:\Users\Example\example\src\example.rs:652
107//! 10: 0x7ff6549d192a - test::stats::Summary::new::ha139494ed2e4e01f
108//! 11: 0x7ff6549d51a2 - test::stats::Summary::new::ha139494ed2e4e01f
109//! 12: 0x7ff654a0a911 - _rust_maybe_catch_panic
110//! 13: 0x7ff6549d56dd - test::stats::Summary::new::ha139494ed2e4e01f
111//! 14: 0x7ff654a03783 - std::sys::thread::Thread::new::h2b08da6cd2517f79
112//! 15: 0x7ff968518101 - BaseThreadInitThunk
113//! ```
114//!
115//! Notice that the output shows the name of the data file
116//! (`src/example_tests.txt`), the test inputs that led to the failure, and the
117//! stack trace to the line in the test code that panicked: entry 9 in the
118//! stack trace pointing to line 652 of the file `example.rs`.
119
120extern crate alloc;
121
122use alloc::{format, string::String, vec::Vec};
123
124use crate::{bits, digest, error};
125
126#[cfg(any(feature = "std", feature = "test_logging"))]
127extern crate std;
128
129/// `compile_time_assert_clone::<T>();` fails to compile if `T` doesn't
130/// implement `Clone`.
131pub const fn compile_time_assert_clone<T: Clone>() {}
132
133/// `compile_time_assert_copy::<T>();` fails to compile if `T` doesn't
134/// implement `Copy`.
135pub const fn compile_time_assert_copy<T: Copy>() {}
136
137/// `compile_time_assert_eq::<T>();` fails to compile if `T` doesn't
138/// implement `Eq`.
139pub const fn compile_time_assert_eq<T: Eq>() {}
140
141/// `compile_time_assert_send::<T>();` fails to compile if `T` doesn't
142/// implement `Send`.
143pub const fn compile_time_assert_send<T: Send>() {}
144
145/// `compile_time_assert_sync::<T>();` fails to compile if `T` doesn't
146/// implement `Sync`.
147pub const fn compile_time_assert_sync<T: Sync>() {}
148
149/// `compile_time_assert_std_error_error::<T>();` fails to compile if `T`
150/// doesn't implement `std::error::Error`.
151#[cfg(feature = "std")]
152pub const fn compile_time_assert_std_error_error<T: std::error::Error>() {}
153
154/// A test case. A test case consists of a set of named attributes. Every
155/// attribute in the test case must be consumed exactly once; this helps catch
156/// typos and omissions.
157///
158/// Requires the `alloc` default feature to be enabled.
159#[derive(Debug)]
160pub struct TestCase {
161 attributes: Vec<(String, String, bool)>,
162}
163
164impl TestCase {
165 /// Maps the string "true" to true and the string "false" to false.
166 pub fn consume_bool(&mut self, key: &str) -> bool {
167 match self.consume_string(key).as_ref() {
168 "true" => true,
169 "false" => false,
170 s => panic!("Invalid bool value: {}", s),
171 }
172 }
173
174 /// Maps the strings "SHA1", "SHA256", "SHA384", and "SHA512" to digest
175 /// algorithms, maps "SHA224" to `None`, and panics on other (erroneous)
176 /// inputs. "SHA224" is mapped to None because *ring* intentionally does
177 /// not support SHA224, but we need to consume test vectors from NIST that
178 /// have SHA224 vectors in them.
179 pub fn consume_digest_alg(&mut self, key: &str) -> Option<&'static digest::Algorithm> {
180 let name = self.consume_string(key);
181 match name.as_ref() {
182 "SHA1" => Some(&digest::SHA1_FOR_LEGACY_USE_ONLY),
183 "SHA224" => None, // We actively skip SHA-224 support.
184 "SHA256" => Some(&digest::SHA256),
185 "SHA384" => Some(&digest::SHA384),
186 "SHA512" => Some(&digest::SHA512),
187 "SHA512_256" => Some(&digest::SHA512_256),
188 _ => panic!("Unsupported digest algorithm: {}", name),
189 }
190 }
191
192 /// Returns the value of an attribute that is encoded as a sequence of an
193 /// even number of hex digits, or as a double-quoted UTF-8 string. The
194 /// empty (zero-length) value is represented as "".
195 pub fn consume_bytes(&mut self, key: &str) -> Vec<u8> {
196 self.consume_optional_bytes(key)
197 .unwrap_or_else(|| panic!("No attribute named \"{}\"", key))
198 }
199
200 /// Like `consume_bytes()` except it returns `None` if the test case
201 /// doesn't have the attribute.
202 pub fn consume_optional_bytes(&mut self, key: &str) -> Option<Vec<u8>> {
203 let s = self.consume_optional_string(key)?;
204 let result = if let [b'\"', s @ ..] = s.as_bytes() {
205 // The value is a quoted UTF-8 string.
206 let mut s = s.iter();
207 let mut bytes = Vec::with_capacity(s.len() - 1);
208 loop {
209 let b = match s.next() {
210 Some(b'\\') => {
211 match s.next() {
212 // We don't allow all octal escape sequences, only "\0" for null.
213 Some(b'0') => 0u8,
214 Some(b't') => b'\t',
215 Some(b'n') => b'\n',
216 // "\xHH"
217 Some(b'x') => {
218 let hi = s.next().expect("Invalid hex escape sequence in string.");
219 let lo = s.next().expect("Invalid hex escape sequence in string.");
220 if let (Ok(hi), Ok(lo)) = (from_hex_digit(*hi), from_hex_digit(*lo))
221 {
222 (hi << 4) | lo
223 } else {
224 panic!("Invalid hex escape sequence in string.");
225 }
226 }
227 _ => {
228 panic!("Invalid hex escape sequence in string.");
229 }
230 }
231 }
232 Some(b'"') => {
233 if s.next().is_some() {
234 panic!("characters after the closing quote of a quoted string.");
235 }
236 break;
237 }
238 Some(b) => *b,
239 None => panic!("Missing terminating '\"' in string literal."),
240 };
241 bytes.push(b);
242 }
243 bytes
244 } else {
245 // The value is hex encoded.
246 match from_hex(&s) {
247 Ok(s) => s,
248 Err(err_str) => {
249 panic!("{} in {}", err_str, s);
250 }
251 }
252 };
253 Some(result)
254 }
255
256 /// Returns the value of an attribute that is an integer, in decimal
257 /// notation.
258 pub fn consume_usize(&mut self, key: &str) -> usize {
259 let s = self.consume_string(key);
260 s.parse::<usize>().unwrap()
261 }
262
263 /// Returns the value of an attribute that is an integer, in decimal
264 /// notation, as a bit length.
265 pub fn consume_usize_bits(&mut self, key: &str) -> bits::BitLength {
266 let s = self.consume_string(key);
267 let bits = s.parse::<usize>().unwrap();
268 bits::BitLength::from_bits(bits)
269 }
270
271 /// Returns the raw value of an attribute, without any unquoting or
272 /// other interpretation.
273 pub fn consume_string(&mut self, key: &str) -> String {
274 self.consume_optional_string(key)
275 .unwrap_or_else(|| panic!("No attribute named \"{}\"", key))
276 }
277
278 /// Like `consume_string()` except it returns `None` if the test case
279 /// doesn't have the attribute.
280 pub fn consume_optional_string(&mut self, key: &str) -> Option<String> {
281 for (name, value, consumed) in &mut self.attributes {
282 if key == name {
283 if *consumed {
284 panic!("Attribute {} was already consumed", key);
285 }
286 *consumed = true;
287 return Some(value.clone());
288 }
289 }
290 None
291 }
292}
293
294/// References a test input file.
295#[cfg(test)]
296macro_rules! test_vector_file {
297 ($file_name:expr) => {
298 $crate::testutil::File {
299 file_name: $file_name,
300 contents: include_str!($file_name),
301 }
302 };
303}
304
305/// A test input file.
306pub struct File<'a> {
307 /// The name (path) of the file.
308 pub file_name: &'a str,
309
310 /// The contents of the file.
311 pub contents: &'a str,
312}
313
314/// Parses test cases out of the given file, calling `f` on each vector until
315/// `f` fails or until all the test vectors have been read. `f` can indicate
316/// failure either by returning `Err()` or by panicking.
317pub fn run<F>(test_file: File, mut f: F)
318where
319 F: FnMut(&str, &mut TestCase) -> Result<(), error::Unspecified>,
320{
321 let lines = &mut test_file.contents.lines();
322
323 let mut current_section = String::from("");
324 let mut failed = false;
325
326 while let Some(mut test_case) = parse_test_case(&mut current_section, lines) {
327 let result = match f(¤t_section, &mut test_case) {
328 Ok(()) => {
329 if !test_case
330 .attributes
331 .iter()
332 .any(|&(_, _, consumed)| !consumed)
333 {
334 Ok(())
335 } else {
336 failed = true;
337 Err("Test didn't consume all attributes.")
338 }
339 }
340 Err(error::Unspecified) => Err("Test returned Err(error::Unspecified)."),
341 };
342
343 if result.is_err() {
344 failed = true;
345 }
346
347 #[cfg(feature = "test_logging")]
348 if let Err(msg) = result {
349 std::println!("{}: {}", test_file.file_name, msg);
350
351 for (name, value, consumed) in test_case.attributes {
352 let consumed_str = if consumed { "" } else { " (unconsumed)" };
353 std::println!("{}{} = {}", name, consumed_str, value);
354 }
355 };
356 }
357
358 if failed {
359 panic!("Test failed.")
360 }
361}
362
363/// Decode an string of hex digits into a sequence of bytes. The input must
364/// have an even number of digits.
365pub fn from_hex(hex_str: &str) -> Result<Vec<u8>, String> {
366 if hex_str.len() % 2 != 0 {
367 return Err(String::from(
368 "Hex string does not have an even number of digits",
369 ));
370 }
371
372 let mut result = Vec::with_capacity(hex_str.len() / 2);
373 for digits in hex_str.as_bytes().chunks(2) {
374 let hi = from_hex_digit(digits[0])?;
375 let lo = from_hex_digit(digits[1])?;
376 result.push((hi * 0x10) | lo);
377 }
378 Ok(result)
379}
380
381fn from_hex_digit(d: u8) -> Result<u8, String> {
382 use core::ops::RangeInclusive;
383 const DECIMAL: (u8, RangeInclusive<u8>) = (0, b'0'..=b'9');
384 const HEX_LOWER: (u8, RangeInclusive<u8>) = (10, b'a'..=b'f');
385 const HEX_UPPER: (u8, RangeInclusive<u8>) = (10, b'A'..=b'F');
386 for (offset, range) in &[DECIMAL, HEX_LOWER, HEX_UPPER] {
387 if range.contains(&d) {
388 return Ok(d - range.start() + offset);
389 }
390 }
391 Err(format!("Invalid hex digit '{}'", d as char))
392}
393
394fn parse_test_case(
395 current_section: &mut String,
396 lines: &mut dyn Iterator<Item = &str>,
397) -> Option<TestCase> {
398 let mut attributes = Vec::new();
399
400 let mut is_first_line = true;
401 loop {
402 let line = lines.next();
403
404 #[cfg(feature = "test_logging")]
405 if let Some(text) = &line {
406 std::println!("Line: {}", text);
407 }
408
409 match line {
410 // If we get to EOF when we're not in the middle of a test case,
411 // then we're done.
412 None if is_first_line => {
413 return None;
414 }
415
416 // End of the file on a non-empty test cases ends the test case.
417 None => {
418 return Some(TestCase { attributes });
419 }
420
421 // A blank line ends a test case if the test case isn't empty.
422 Some("") => {
423 if !is_first_line {
424 return Some(TestCase { attributes });
425 }
426 // Ignore leading blank lines.
427 }
428
429 // Comments start with '#'; ignore them.
430 Some(line) if line.starts_with('#') => (),
431
432 Some(line) if line.starts_with('[') => {
433 assert!(is_first_line);
434 assert!(line.ends_with(']'));
435 current_section.truncate(0);
436 current_section.push_str(line);
437 let _ = current_section.pop();
438 let _ = current_section.remove(0);
439 }
440
441 Some(line) => {
442 is_first_line = false;
443
444 let parts: Vec<&str> = line.splitn(2, " = ").collect();
445 if parts.len() != 2 {
446 panic!("Syntax error: Expected Key = Value.");
447 };
448
449 let key = parts[0].trim();
450 let value = parts[1].trim();
451
452 // Don't allow the value to be omitted. An empty value can be
453 // represented as an empty quoted string.
454 assert_ne!(value.len(), 0);
455
456 // Checking is_none() ensures we don't accept duplicate keys.
457 attributes.push((String::from(key), String::from(value), false));
458 }
459 }
460 }
461}
462
463/// Deterministic implementations of `ring::rand::SecureRandom`.
464///
465/// These implementations are particularly useful for testing implementations
466/// of randomized algorithms & protocols using known-answer-tests where the
467/// test vectors contain the random seed to use. They are also especially
468/// useful for some types of fuzzing.
469#[doc(hidden)]
470pub mod rand {
471 use crate::{error, rand};
472
473 /// An implementation of `SecureRandom` that always fills the output slice
474 /// with the given byte.
475 #[derive(Debug)]
476 pub struct FixedByteRandom {
477 pub byte: u8,
478 }
479
480 impl rand::sealed::SecureRandom for FixedByteRandom {
481 fn fill_impl(&self, dest: &mut [u8]) -> Result<(), error::Unspecified> {
482 dest.fill(self.byte);
483 Ok(())
484 }
485 }
486
487 /// An implementation of `SecureRandom` that always fills the output slice
488 /// with the slice in `bytes`. The length of the slice given to `slice`
489 /// must match exactly.
490 #[derive(Debug)]
491 pub struct FixedSliceRandom<'a> {
492 pub bytes: &'a [u8],
493 }
494
495 impl rand::sealed::SecureRandom for FixedSliceRandom<'_> {
496 fn fill_impl(&self, dest: &mut [u8]) -> Result<(), error::Unspecified> {
497 dest.copy_from_slice(self.bytes);
498 Ok(())
499 }
500 }
501
502 /// An implementation of `SecureRandom` where each slice in `bytes` is a
503 /// test vector for one call to `fill()`. *Not thread-safe.*
504 ///
505 /// The first slice in `bytes` is the output for the first call to
506 /// `fill()`, the second slice is the output for the second call to
507 /// `fill()`, etc. The output slice passed to `fill()` must have exactly
508 /// the length of the corresponding entry in `bytes`. `current` must be
509 /// initialized to zero. `fill()` must be called exactly once for each
510 /// entry in `bytes`.
511 #[derive(Debug)]
512 pub struct FixedSliceSequenceRandom<'a> {
513 /// The value.
514 pub bytes: &'a [&'a [u8]],
515 pub current: core::cell::UnsafeCell<usize>,
516 }
517
518 impl rand::sealed::SecureRandom for FixedSliceSequenceRandom<'_> {
519 fn fill_impl(&self, dest: &mut [u8]) -> Result<(), error::Unspecified> {
520 let current = unsafe { *self.current.get() };
521 let bytes = self.bytes[current];
522 dest.copy_from_slice(bytes);
523 // Remember that we returned this slice and prepare to return
524 // the next one, if any.
525 unsafe { *self.current.get() += 1 };
526 Ok(())
527 }
528 }
529
530 impl Drop for FixedSliceSequenceRandom<'_> {
531 fn drop(&mut self) {
532 // Ensure that `fill()` was called exactly the right number of
533 // times.
534 assert_eq!(unsafe { *self.current.get() }, self.bytes.len());
535 }
536 }
537}
538
539#[cfg(test)]
540mod tests {
541 use crate::error;
542 use crate::testutil as test;
543
544 #[test]
545 fn one_ok() {
546 test::run(test_vector_file!("test_1_tests.txt"), |_, test_case| {
547 let _ = test_case.consume_string("Key");
548 Ok(())
549 });
550 }
551
552 #[test]
553 #[should_panic(expected = "Test failed.")]
554 fn one_err() {
555 test::run(test_vector_file!("test_1_tests.txt"), |_, test_case| {
556 let _ = test_case.consume_string("Key");
557 Err(error::Unspecified)
558 });
559 }
560
561 #[test]
562 #[should_panic(expected = "Oh noes!")]
563 fn one_panics() {
564 test::run(test_vector_file!("test_1_tests.txt"), |_, test_case| {
565 let _ = test_case.consume_string("Key");
566 panic!("Oh noes!");
567 });
568 }
569
570 #[test]
571 #[should_panic(expected = "Test failed.")]
572 fn first_err() {
573 err_one(0)
574 }
575
576 #[test]
577 #[should_panic(expected = "Test failed.")]
578 fn middle_err() {
579 err_one(1)
580 }
581
582 #[test]
583 #[should_panic(expected = "Test failed.")]
584 fn last_err() {
585 err_one(2)
586 }
587
588 fn err_one(test_to_fail: usize) {
589 let mut n = 0;
590 test::run(test_vector_file!("test_3_tests.txt"), |_, test_case| {
591 let _ = test_case.consume_string("Key");
592 let result = if n != test_to_fail {
593 Ok(())
594 } else {
595 Err(error::Unspecified)
596 };
597 n += 1;
598 result
599 });
600 }
601
602 #[test]
603 #[should_panic(expected = "Oh Noes!")]
604 fn first_panic() {
605 panic_one(0)
606 }
607
608 #[test]
609 #[should_panic(expected = "Oh Noes!")]
610 fn middle_panic() {
611 panic_one(1)
612 }
613
614 #[test]
615 #[should_panic(expected = "Oh Noes!")]
616 fn last_panic() {
617 panic_one(2)
618 }
619
620 fn panic_one(test_to_fail: usize) {
621 let mut n = 0;
622 test::run(test_vector_file!("test_3_tests.txt"), |_, test_case| {
623 let _ = test_case.consume_string("Key");
624 if n == test_to_fail {
625 panic!("Oh Noes!");
626 };
627 n += 1;
628 Ok(())
629 });
630 }
631
632 #[test]
633 #[should_panic(expected = "Syntax error: Expected Key = Value.")]
634 fn syntax_error() {
635 test::run(
636 test_vector_file!("test_1_syntax_error_tests.txt"),
637 |_, _| Ok(()),
638 );
639 }
640}