ring/
test.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 AUTHORS DISCLAIM ALL WARRANTIES
8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS 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_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\test.rs: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
120#[cfg(feature = "alloc")]
121use alloc::{format, string::String, vec::Vec};
122
123#[cfg(feature = "alloc")]
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 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 fn compile_time_assert_copy<T: Copy>() {}
136
137/// `compile_time_assert_send::<T>();` fails to compile if `T` doesn't
138/// implement `Send`.
139pub fn compile_time_assert_send<T: Send>() {}
140
141/// `compile_time_assert_sync::<T>();` fails to compile if `T` doesn't
142/// implement `Sync`.
143pub fn compile_time_assert_sync<T: Sync>() {}
144
145/// `compile_time_assert_std_error_error::<T>();` fails to compile if `T`
146/// doesn't implement `std::error::Error`.
147#[cfg(feature = "std")]
148pub fn compile_time_assert_std_error_error<T: std::error::Error>() {}
149
150/// A test case. A test case consists of a set of named attributes. Every
151/// attribute in the test case must be consumed exactly once; this helps catch
152/// typos and omissions.
153///
154/// Requires the `alloc` default feature to be enabled.
155#[cfg(feature = "alloc")]
156#[derive(Debug)]
157pub struct TestCase {
158    attributes: Vec<(String, String, bool)>,
159}
160
161#[cfg(feature = "alloc")]
162impl TestCase {
163    /// Maps the string "true" to true and the string "false" to false.
164    pub fn consume_bool(&mut self, key: &str) -> bool {
165        match self.consume_string(key).as_ref() {
166            "true" => true,
167            "false" => false,
168            s => panic!("Invalid bool value: {}", s),
169        }
170    }
171
172    /// Maps the strings "SHA1", "SHA256", "SHA384", and "SHA512" to digest
173    /// algorithms, maps "SHA224" to `None`, and panics on other (erroneous)
174    /// inputs. "SHA224" is mapped to None because *ring* intentionally does
175    /// not support SHA224, but we need to consume test vectors from NIST that
176    /// have SHA224 vectors in them.
177    pub fn consume_digest_alg(&mut self, key: &str) -> Option<&'static digest::Algorithm> {
178        let name = self.consume_string(key);
179        match name.as_ref() {
180            "SHA1" => Some(&digest::SHA1_FOR_LEGACY_USE_ONLY),
181            "SHA224" => None, // We actively skip SHA-224 support.
182            "SHA256" => Some(&digest::SHA256),
183            "SHA384" => Some(&digest::SHA384),
184            "SHA512" => Some(&digest::SHA512),
185            "SHA512_256" => Some(&digest::SHA512_256),
186            _ => panic!("Unsupported digest algorithm: {}", name),
187        }
188    }
189
190    /// Returns the value of an attribute that is encoded as a sequence of an
191    /// even number of hex digits, or as a double-quoted UTF-8 string. The
192    /// empty (zero-length) value is represented as "".
193    pub fn consume_bytes(&mut self, key: &str) -> Vec<u8> {
194        let s = self.consume_string(key);
195        if s.starts_with('\"') {
196            // The value is a quoted UTF-8 string.
197
198            let mut bytes = Vec::with_capacity(s.as_bytes().len() - 2);
199            let mut s = s.as_bytes().iter().skip(1);
200            loop {
201                let b = match s.next() {
202                    Some(b'\\') => {
203                        match s.next() {
204                            // We don't allow all octal escape sequences, only "\0" for null.
205                            Some(b'0') => 0u8,
206                            Some(b't') => b'\t',
207                            Some(b'n') => b'\n',
208                            // "\xHH"
209                            Some(b'x') => {
210                                let hi = s.next().expect("Invalid hex escape sequence in string.");
211                                let lo = s.next().expect("Invalid hex escape sequence in string.");
212                                if let (Ok(hi), Ok(lo)) = (from_hex_digit(*hi), from_hex_digit(*lo))
213                                {
214                                    (hi << 4) | lo
215                                } else {
216                                    panic!("Invalid hex escape sequence in string.");
217                                }
218                            }
219                            _ => {
220                                panic!("Invalid hex escape sequence in string.");
221                            }
222                        }
223                    }
224                    Some(b'"') => {
225                        if s.next().is_some() {
226                            panic!("characters after the closing quote of a quoted string.");
227                        }
228                        break;
229                    }
230                    Some(b) => *b,
231                    None => panic!("Missing terminating '\"' in string literal."),
232                };
233                bytes.push(b);
234            }
235            bytes
236        } else {
237            // The value is hex encoded.
238            match from_hex(&s) {
239                Ok(s) => s,
240                Err(err_str) => {
241                    panic!("{} in {}", err_str, s);
242                }
243            }
244        }
245    }
246
247    /// Returns the value of an attribute that is an integer, in decimal
248    /// notation.
249    pub fn consume_usize(&mut self, key: &str) -> usize {
250        let s = self.consume_string(key);
251        s.parse::<usize>().unwrap()
252    }
253
254    /// Returns the value of an attribute that is an integer, in decimal
255    /// notation, as a bit length.
256    #[cfg(feature = "alloc")]
257    pub fn consume_usize_bits(&mut self, key: &str) -> bits::BitLength {
258        let s = self.consume_string(key);
259        let bits = s.parse::<usize>().unwrap();
260        bits::BitLength::from_usize_bits(bits)
261    }
262
263    /// Returns the raw value of an attribute, without any unquoting or
264    /// other interpretation.
265    pub fn consume_string(&mut self, key: &str) -> String {
266        self.consume_optional_string(key)
267            .unwrap_or_else(|| panic!("No attribute named \"{}\"", key))
268    }
269
270    /// Like `consume_string()` except it returns `None` if the test case
271    /// doesn't have the attribute.
272    pub fn consume_optional_string(&mut self, key: &str) -> Option<String> {
273        for (name, value, consumed) in &mut self.attributes {
274            if key == name {
275                if *consumed {
276                    panic!("Attribute {} was already consumed", key);
277                }
278                *consumed = true;
279                return Some(value.clone());
280            }
281        }
282        None
283    }
284}
285
286/// References a test input file.
287#[cfg(feature = "alloc")]
288#[macro_export]
289macro_rules! test_file {
290    ($file_name:expr) => {
291        crate::test::File {
292            file_name: $file_name,
293            contents: include_str!($file_name),
294        }
295    };
296}
297
298/// A test input file.
299#[cfg(feature = "alloc")]
300pub struct File<'a> {
301    /// The name (path) of the file.
302    pub file_name: &'a str,
303
304    /// The contents of the file.
305    pub contents: &'a str,
306}
307
308/// Parses test cases out of the given file, calling `f` on each vector until
309/// `f` fails or until all the test vectors have been read. `f` can indicate
310/// failure either by returning `Err()` or by panicking.
311///
312/// Requires the `alloc` default feature to be enabled
313#[cfg(feature = "alloc")]
314pub fn run<F>(test_file: File, mut f: F)
315where
316    F: FnMut(&str, &mut TestCase) -> Result<(), error::Unspecified>,
317{
318    let lines = &mut test_file.contents.lines();
319
320    let mut current_section = String::from("");
321    let mut failed = false;
322
323    while let Some(mut test_case) = parse_test_case(&mut current_section, lines) {
324        let result = match f(&current_section, &mut test_case) {
325            Ok(()) => {
326                if !test_case
327                    .attributes
328                    .iter()
329                    .any(|&(_, _, consumed)| !consumed)
330                {
331                    Ok(())
332                } else {
333                    failed = true;
334                    Err("Test didn't consume all attributes.")
335                }
336            }
337            Err(error::Unspecified) => Err("Test returned Err(error::Unspecified)."),
338        };
339
340        if result.is_err() {
341            failed = true;
342        }
343
344        #[cfg(feature = "test_logging")]
345        {
346            if let Err(msg) = result {
347                std::println!("{}: {}", test_file.file_name, msg);
348
349                for (name, value, consumed) in test_case.attributes {
350                    let consumed_str = if consumed { "" } else { " (unconsumed)" };
351                    std::println!("{}{} = {}", name, consumed_str, value);
352                }
353            };
354        }
355    }
356
357    if failed {
358        panic!("Test failed.")
359    }
360}
361
362/// Decode an string of hex digits into a sequence of bytes. The input must
363/// have an even number of digits.
364#[cfg(feature = "alloc")]
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
381#[cfg(feature = "alloc")]
382fn from_hex_digit(d: u8) -> Result<u8, String> {
383    use core::ops::RangeInclusive;
384    const DECIMAL: (u8, RangeInclusive<u8>) = (0, b'0'..=b'9');
385    const HEX_LOWER: (u8, RangeInclusive<u8>) = (10, b'a'..=b'f');
386    const HEX_UPPER: (u8, RangeInclusive<u8>) = (10, b'A'..=b'F');
387    for (offset, range) in &[DECIMAL, HEX_LOWER, HEX_UPPER] {
388        if range.contains(&d) {
389            return Ok(d - range.start() + offset);
390        }
391    }
392    Err(format!("Invalid hex digit '{}'", d as char))
393}
394
395#[cfg(feature = "alloc")]
396fn parse_test_case(
397    current_section: &mut String,
398    lines: &mut dyn Iterator<Item = &str>,
399) -> Option<TestCase> {
400    let mut attributes = Vec::new();
401
402    let mut is_first_line = true;
403    loop {
404        let line = lines.next();
405
406        #[cfg(feature = "test_logging")]
407        {
408            if let Some(text) = &line {
409                std::println!("Line: {}", text);
410            }
411        }
412
413        match line {
414            // If we get to EOF when we're not in the middle of a test case,
415            // then we're done.
416            None if is_first_line => {
417                return None;
418            }
419
420            // End of the file on a non-empty test cases ends the test case.
421            None => {
422                return Some(TestCase { attributes });
423            }
424
425            // A blank line ends a test case if the test case isn't empty.
426            Some(ref line) if line.is_empty() => {
427                if !is_first_line {
428                    return Some(TestCase { attributes });
429                }
430                // Ignore leading blank lines.
431            }
432
433            // Comments start with '#'; ignore them.
434            Some(ref line) if line.starts_with('#') => (),
435
436            Some(ref line) if line.starts_with('[') => {
437                assert!(is_first_line);
438                assert!(line.ends_with(']'));
439                current_section.truncate(0);
440                current_section.push_str(line);
441                let _ = current_section.pop();
442                let _ = current_section.remove(0);
443            }
444
445            Some(ref line) => {
446                is_first_line = false;
447
448                let parts: Vec<&str> = line.splitn(2, " = ").collect();
449                if parts.len() != 2 {
450                    panic!("Syntax error: Expected Key = Value.");
451                };
452
453                let key = parts[0].trim();
454                let value = parts[1].trim();
455
456                // Don't allow the value to be ommitted. An empty value can be
457                // represented as an empty quoted string.
458                assert_ne!(value.len(), 0);
459
460                // Checking is_none() ensures we don't accept duplicate keys.
461                attributes.push((String::from(key), String::from(value), false));
462            }
463        }
464    }
465}
466
467/// Deterministic implementations of `ring::rand::SecureRandom`.
468///
469/// These implementations are particularly useful for testing implementations
470/// of randomized algorithms & protocols using known-answer-tests where the
471/// test vectors contain the random seed to use. They are also especially
472/// useful for some types of fuzzing.
473#[doc(hidden)]
474pub mod rand {
475    use crate::{error, polyfill, rand};
476
477    /// An implementation of `SecureRandom` that always fills the output slice
478    /// with the given byte.
479    #[derive(Debug)]
480    pub struct FixedByteRandom {
481        pub byte: u8,
482    }
483
484    impl rand::sealed::SecureRandom for FixedByteRandom {
485        fn fill_impl(&self, dest: &mut [u8]) -> Result<(), error::Unspecified> {
486            polyfill::slice::fill(dest, self.byte);
487            Ok(())
488        }
489    }
490
491    /// An implementation of `SecureRandom` that always fills the output slice
492    /// with the slice in `bytes`. The length of the slice given to `slice`
493    /// must match exactly.
494    #[derive(Debug)]
495    pub struct FixedSliceRandom<'a> {
496        pub bytes: &'a [u8],
497    }
498
499    impl rand::sealed::SecureRandom for FixedSliceRandom<'_> {
500        fn fill_impl(&self, dest: &mut [u8]) -> Result<(), error::Unspecified> {
501            dest.copy_from_slice(self.bytes);
502            Ok(())
503        }
504    }
505
506    /// An implementation of `SecureRandom` where each slice in `bytes` is a
507    /// test vector for one call to `fill()`. *Not thread-safe.*
508    ///
509    /// The first slice in `bytes` is the output for the first call to
510    /// `fill()`, the second slice is the output for the second call to
511    /// `fill()`, etc. The output slice passed to `fill()` must have exactly
512    /// the length of the corresponding entry in `bytes`. `current` must be
513    /// initialized to zero. `fill()` must be called exactly once for each
514    /// entry in `bytes`.
515    #[derive(Debug)]
516    pub struct FixedSliceSequenceRandom<'a> {
517        /// The value.
518        pub bytes: &'a [&'a [u8]],
519        pub current: core::cell::UnsafeCell<usize>,
520    }
521
522    impl rand::sealed::SecureRandom for FixedSliceSequenceRandom<'_> {
523        fn fill_impl(&self, dest: &mut [u8]) -> Result<(), error::Unspecified> {
524            let current = unsafe { *self.current.get() };
525            let bytes = self.bytes[current];
526            dest.copy_from_slice(bytes);
527            // Remember that we returned this slice and prepare to return
528            // the next one, if any.
529            unsafe { *self.current.get() += 1 };
530            Ok(())
531        }
532    }
533
534    impl Drop for FixedSliceSequenceRandom<'_> {
535        fn drop(&mut self) {
536            // Ensure that `fill()` was called exactly the right number of
537            // times.
538            assert_eq!(unsafe { *self.current.get() }, self.bytes.len());
539        }
540    }
541}
542
543#[cfg(test)]
544mod tests {
545    use crate::{error, test};
546
547    #[test]
548    fn one_ok() {
549        test::run(test_file!("test_1_tests.txt"), |_, test_case| {
550            let _ = test_case.consume_string("Key");
551            Ok(())
552        });
553    }
554
555    #[test]
556    #[should_panic(expected = "Test failed.")]
557    fn one_err() {
558        test::run(test_file!("test_1_tests.txt"), |_, test_case| {
559            let _ = test_case.consume_string("Key");
560            Err(error::Unspecified)
561        });
562    }
563
564    #[test]
565    #[should_panic(expected = "Oh noes!")]
566    fn one_panics() {
567        test::run(test_file!("test_1_tests.txt"), |_, test_case| {
568            let _ = test_case.consume_string("Key");
569            panic!("Oh noes!");
570        });
571    }
572
573    #[test]
574    #[should_panic(expected = "Test failed.")]
575    fn first_err() {
576        err_one(0)
577    }
578
579    #[test]
580    #[should_panic(expected = "Test failed.")]
581    fn middle_err() {
582        err_one(1)
583    }
584
585    #[test]
586    #[should_panic(expected = "Test failed.")]
587    fn last_err() {
588        err_one(2)
589    }
590
591    fn err_one(test_to_fail: usize) {
592        let mut n = 0;
593        test::run(test_file!("test_3_tests.txt"), |_, test_case| {
594            let _ = test_case.consume_string("Key");
595            let result = if n != test_to_fail {
596                Ok(())
597            } else {
598                Err(error::Unspecified)
599            };
600            n += 1;
601            result
602        });
603    }
604
605    #[test]
606    #[should_panic(expected = "Oh Noes!")]
607    fn first_panic() {
608        panic_one(0)
609    }
610
611    #[test]
612    #[should_panic(expected = "Oh Noes!")]
613    fn middle_panic() {
614        panic_one(1)
615    }
616
617    #[test]
618    #[should_panic(expected = "Oh Noes!")]
619    fn last_panic() {
620        panic_one(2)
621    }
622
623    fn panic_one(test_to_fail: usize) {
624        let mut n = 0;
625        test::run(test_file!("test_3_tests.txt"), |_, test_case| {
626            let _ = test_case.consume_string("Key");
627            if n == test_to_fail {
628                panic!("Oh Noes!");
629            };
630            n += 1;
631            Ok(())
632        });
633    }
634
635    #[test]
636    #[should_panic(expected = "Syntax error: Expected Key = Value.")]
637    fn syntax_error() {
638        test::run(test_file!("test_1_syntax_error_tests.txt"), |_, _| Ok(()));
639    }
640}