#![cfg(feature = "codec")]
use core::fmt;
use std::collections::BTreeMap;
use secure_types::{EncodeError, FORMAT_VERSION, SecureBytes, SecureVec};
use serde::Serialize;
use serde::ser::{self, SerializeMap as _, SerializeSeq as _, SerializeStruct as _};
#[test]
fn test_scalars_are_little_endian_and_fixed_width() {
assert_encodes_to(&true, &[FORMAT_VERSION, 0x01]);
assert_encodes_to(&false, &[FORMAT_VERSION, 0x00]);
assert_encodes_to(&1u8, &[FORMAT_VERSION, 0x01]);
assert_encodes_to(&0x0102u16, &[FORMAT_VERSION, 0x02, 0x01]);
assert_encodes_to(&1u32, &[FORMAT_VERSION, 0x01, 0x00, 0x00, 0x00]);
assert_encodes_to(
&1u64,
&[FORMAT_VERSION, 0x01, 0, 0, 0, 0, 0, 0, 0],
);
assert_encodes_to(
&1u128,
&[
FORMAT_VERSION,
0x01,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
],
);
assert_encodes_to(&(-1i8), &[FORMAT_VERSION, 0xFF]);
assert_encodes_to(&(-2i16), &[FORMAT_VERSION, 0xFE, 0xFF]);
assert_encodes_to(
&(-1i32),
&[FORMAT_VERSION, 0xFF, 0xFF, 0xFF, 0xFF],
);
assert_encodes_to(
&(-1i64),
&[
FORMAT_VERSION,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
],
);
assert_encodes_to(
&(-1i128),
&[
FORMAT_VERSION,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
],
);
assert_encodes_to(&1.5f32, &[FORMAT_VERSION, 0x00, 0x00, 0xC0, 0x3F]);
assert_encodes_to(
&1.5f64,
&[
FORMAT_VERSION,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0xF8,
0x3F,
],
);
assert_encodes_to(&'A', &[FORMAT_VERSION, 0x41, 0x00, 0x00, 0x00]);
}
#[test]
fn test_str_is_length_prefixed_raw_utf8() {
assert_encodes_to(&"", &[FORMAT_VERSION, 0x00]);
assert_encodes_to(&"ab", &[FORMAT_VERSION, 0x02, b'a', b'b']);
assert_encodes_to(&"é", &[FORMAT_VERSION, 0x02, 0xC3, 0xA9]);
}
#[test]
fn test_bytes_are_a_single_length_prefixed_blob() {
let secret = SecureVec::from_slice(&[1u8, 2, 3]).unwrap();
assert_encodes_to(&secret, &[FORMAT_VERSION, 0x03, 0x01, 0x02, 0x03]);
}
#[test]
fn test_option_unit_and_newtype() {
assert_encodes_to(&Option::<u8>::None, &[FORMAT_VERSION, 0x00]);
assert_encodes_to(&Some(7u8), &[FORMAT_VERSION, 0x01, 0x07]);
assert_encodes_to(&(), &[FORMAT_VERSION]);
assert_encodes_to(&UnitStruct, &[FORMAT_VERSION]);
assert_encodes_to(&NewtypeStruct(9), &[FORMAT_VERSION, 0x09]);
}
#[test]
fn test_sequences_and_tuples_carry_their_length() {
assert_encodes_to(
&[1u8, 2, 3],
&[FORMAT_VERSION, 0x03, 0x01, 0x02, 0x03],
);
assert_encodes_to(
&[1u8, 2, 3][..],
&[FORMAT_VERSION, 0x03, 0x01, 0x02, 0x03],
);
assert_encodes_to(
&(1u8, 2u16),
&[FORMAT_VERSION, 0x02, 0x01, 0x02, 0x00],
);
let long = [0u8; 200];
encoded(&long[..]).unlock_slice(|bytes| {
assert_eq!(&bytes[..3], &[FORMAT_VERSION, 0xC8, 0x01]);
assert_eq!(bytes.len(), 1 + 2 + 200);
});
}
#[test]
fn test_maps() {
let mut map = BTreeMap::new();
map.insert("a", 1u8);
assert_encodes_to(&map, &[FORMAT_VERSION, 0x01, 0x01, b'a', 0x01]);
let mut pair = BTreeMap::new();
pair.insert("a", 1u8);
pair.insert("b", 2u8);
assert_encodes_to(
&pair,
&[FORMAT_VERSION, 0x02, 0x01, b'a', 0x01, 0x01, b'b', 0x02],
);
}
#[test]
fn test_struct_field_layout() {
assert_encodes_to(
&Point { x: 1, y: 300 },
&[
FORMAT_VERSION,
0x02,
0x01,
b'x',
0x01,
0x00,
0x00,
0x00,
0x01,
0x01,
b'y',
0x02,
0x00,
0x00,
0x00,
0x2C,
0x01,
],
);
}
#[test]
fn test_nested_struct_frames_close_only_their_own_scope() {
assert_encodes_to(
&Outer {
name: "ab",
point: Point { x: 1, y: 300 },
},
&[
FORMAT_VERSION,
0x02,
0x04,
b'n',
b'a',
b'm',
b'e',
0x03,
0x00,
0x00,
0x00,
0x02,
b'a',
b'b',
0x05,
b'p',
b'o',
b'i',
b'n',
b't',
0x10,
0x00,
0x00,
0x00,
0x02,
0x01,
b'x',
0x01,
0x00,
0x00,
0x00,
0x01,
0x01,
b'y',
0x02,
0x00,
0x00,
0x00,
0x2C,
0x01,
],
);
}
#[test]
fn test_empty_and_fully_skipped_structs_emit_a_zero_count() {
assert_encodes_to(&EmptyStruct {}, &[FORMAT_VERSION, 0x00]);
}
#[test]
fn test_skipped_fields_shift_the_count_and_nothing_else() {
assert_encodes_to(
&MidSkip {
first: 1,
middle: None,
last: 2,
},
&[
FORMAT_VERSION,
0x02,
0x05,
b'f',
b'i',
b'r',
b's',
b't',
0x01,
0x00,
0x00,
0x00,
0x01,
0x04,
b'l',
b'a',
b's',
b't',
0x01,
0x00,
0x00,
0x00,
0x02,
],
);
assert_encodes_to(
&MidSkip {
first: 1,
middle: Some(9),
last: 2,
},
&[
FORMAT_VERSION,
0x03,
0x05,
b'f',
b'i',
b'r',
b's',
b't',
0x01,
0x00,
0x00,
0x00,
0x01,
0x06,
b'm',
b'i',
b'd',
b'd',
b'l',
b'e',
0x02,
0x00,
0x00,
0x00,
0x01,
0x09,
0x04,
b'l',
b'a',
b's',
b't',
0x01,
0x00,
0x00,
0x00,
0x02,
],
);
}
#[test]
fn test_skip_serializing_removes_the_field_entirely() {
assert_encodes_to(
&WithNever { kept: 1, never: 9 },
&[
FORMAT_VERSION,
0x01,
0x04,
b'k',
b'e',
b'p',
b't',
0x01,
0x00,
0x00,
0x00,
0x01,
],
);
}
#[test]
fn test_enum_variants_are_name_tagged() {
assert_encodes_to(
&Shape::Unit,
&[FORMAT_VERSION, 0x04, b'U', b'n', b'i', b't'],
);
assert_encodes_to(
&Shape::New(7),
&[FORMAT_VERSION, 0x03, b'N', b'e', b'w', 0x07],
);
assert_encodes_to(
&Shape::Tup(1, 300),
&[
FORMAT_VERSION,
0x03,
b'T',
b'u',
b'p',
0x02,
0x01,
0x2C,
0x01,
],
);
assert_encodes_to(
&Shape::Named { a: 5 },
&[
FORMAT_VERSION,
0x05,
b'N',
b'a',
b'm',
b'e',
b'd',
0x01,
0x01,
b'a',
0x01,
0x00,
0x00,
0x00,
0x05,
],
);
}
#[test]
fn test_unknown_length_seq_is_buffered_in_locked_memory() {
assert_encodes_to(
&UnknownLengthSeq,
&[FORMAT_VERSION, 0x03, 0x01, 0x02, 0x03],
);
}
#[test]
fn test_unknown_length_map_counts_entries_not_values() {
assert_encodes_to(
&UnknownLengthMap,
&[FORMAT_VERSION, 0x02, 0x01, b'a', 0x01, 0x01, b'b', 0x02],
);
}
#[test]
fn test_element_count_mismatches_are_rejected() {
assert!(matches!(
secure_types::encode(&DeclaresTooMany),
Err(EncodeError::ElementCountMismatch)
));
assert!(matches!(
secure_types::encode(&WritesMoreThanDeclared),
Err(EncodeError::ElementCountMismatch)
));
assert!(matches!(
secure_types::encode(&StructDeclaresTooMany),
Err(EncodeError::ElementCountMismatch)
));
}
#[test]
fn test_collect_str_encodes_as_a_length_prefixed_str() {
assert_encodes_to(
&Displays(7),
&[FORMAT_VERSION, 0x04, b'i', b'd', b'-', b'7'],
);
}
#[test]
fn test_is_not_human_readable() {
struct RecordsReadability;
impl Serialize for RecordsReadability {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
let readable = serializer.is_human_readable();
serializer.serialize_bool(readable)
}
}
assert_encodes_to(&RecordsReadability, &[FORMAT_VERSION, 0x00]);
}
fn encoded<T>(value: &T) -> SecureBytes
where
T: ?Sized + Serialize,
{
secure_types::encode(value).unwrap()
}
fn assert_encodes_to<T>(value: &T, expected: &[u8])
where
T: ?Sized + Serialize,
{
encoded(value).unlock_slice(|bytes| {
assert_eq!(
bytes, expected,
"\n actual: {bytes:02X?}\n expected: {expected:02X?}"
)
});
}
#[derive(Serialize)]
struct EmptyStruct {}
#[derive(Serialize)]
struct UnitStruct;
#[derive(Serialize)]
struct NewtypeStruct(u8);
#[derive(Serialize)]
struct Point {
x: u8,
y: u16,
}
#[derive(Serialize)]
struct Outer {
name: &'static str,
point: Point,
}
#[derive(Serialize)]
struct MidSkip {
first: u8,
#[serde(skip_serializing_if = "Option::is_none")]
middle: Option<u8>,
last: u8,
}
#[derive(Serialize)]
struct WithNever {
kept: u8,
#[serde(skip_serializing)]
#[allow(dead_code)]
never: u8,
}
#[derive(Serialize)]
enum Shape {
Unit,
New(u8),
Tup(u8, u16),
Named { a: u8 },
}
struct UnknownLengthSeq;
impl Serialize for UnknownLengthSeq {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
let mut seq = serializer.serialize_seq(None)?;
seq.serialize_element(&1u8)?;
seq.serialize_element(&2u8)?;
seq.serialize_element(&3u8)?;
seq.end()
}
}
struct UnknownLengthMap;
impl Serialize for UnknownLengthMap {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
let mut map = serializer.serialize_map(None)?;
map.serialize_entry("a", &1u8)?;
map.serialize_entry("b", &2u8)?;
map.end()
}
}
struct DeclaresTooMany;
impl Serialize for DeclaresTooMany {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
let mut seq = serializer.serialize_seq(Some(3))?;
seq.serialize_element(&1u8)?;
seq.serialize_element(&2u8)?;
seq.end()
}
}
struct WritesMoreThanDeclared;
impl Serialize for WritesMoreThanDeclared {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
let mut seq = serializer.serialize_seq(Some(1))?;
seq.serialize_element(&1u8)?;
seq.serialize_element(&2u8)?;
seq.end()
}
}
struct StructDeclaresTooMany;
impl Serialize for StructDeclaresTooMany {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
let mut structure = serializer.serialize_struct("Short", 3)?;
structure.serialize_field("a", &1u8)?;
structure.end()
}
}
struct Displays(u8);
impl fmt::Display for Displays {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "id-{}", self.0)
}
}
impl Serialize for Displays {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
serializer.collect_str(self)
}
}
#[test]
fn test_encode_errors_never_echo_payload_bytes() {
const MARKER: &str = "SEED-PHRASE-MARKER-ENCODE";
struct FailsAfterWritingTheSecret;
impl Serialize for FailsAfterWritingTheSecret {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
let mut seq = serializer.serialize_seq(Some(2))?;
seq.serialize_element(MARKER)?;
seq.end()
}
}
let error = match secure_types::encode(&FailsAfterWritingTheSecret) {
Ok(_) => panic!("encoding a mismatched element count should have failed"),
Err(error) => error,
};
assert!(
matches!(&error, EncodeError::ElementCountMismatch),
"expected the count mismatch to fail the encoding, got {error:?}"
);
for form in [error.to_string(), format!("{error:?}")] {
assert!(
!form.contains(MARKER),
"an encode error echoed payload bytes: {form}"
);
}
}
#[test]
fn test_a_serialize_impls_rejection_message_never_reaches_the_error() {
const MARKER: &str = "SEED-PHRASE-MARKER-ENCODE-CUSTOM";
struct RejectsWithTheSecret;
impl Serialize for RejectsWithTheSecret {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
let _ = serializer;
Err(ser::Error::custom(format!(
"refusing to encode {MARKER}"
)))
}
}
let error = match secure_types::encode(&RejectsWithTheSecret) {
Ok(_) => panic!("a rejected value should not encode"),
Err(error) => error,
};
assert!(
matches!(&error, EncodeError::Custom),
"expected the message-free `Custom` variant, got {error:?}"
);
for form in [error.to_string(), format!("{error:?}")] {
assert!(
!form.contains(MARKER),
"the encode error echoed the impl's message: {form}"
);
}
}
fn locked_bytes<T>(value: &T) -> Vec<u8>
where
T: ?Sized + Serialize,
{
secure_types::encode(value)
.unwrap()
.unlock_slice(<[u8]>::to_vec)
}
fn assert_vec_encoding_matches<T>(value: &T)
where
T: ?Sized + Serialize,
{
let expected = locked_bytes(value);
let plain = secure_types::encode_to_vec(value).unwrap();
assert_eq!(
plain, expected,
"\n encode_to_vec: {plain:02X?}\n encode: {expected:02X?}"
);
assert_eq!(plain.first(), Some(&FORMAT_VERSION));
for capacity in [0, 1, expected.len()] {
let sized = secure_types::encode_to_vec_with_capacity(value, capacity).unwrap();
assert_eq!(
sized, expected,
"encode_to_vec_with_capacity({capacity}) disagreed with encode"
);
}
}
#[test]
fn test_encode_to_vec_matches_encode() {
assert_vec_encoding_matches(&true);
assert_vec_encoding_matches(&(-1i64));
assert_vec_encoding_matches(&u64::MAX);
assert_vec_encoding_matches(&'x');
assert_vec_encoding_matches(&"a string");
assert_vec_encoding_matches(&Some(7u8));
assert_vec_encoding_matches(&Option::<u8>::None);
assert_vec_encoding_matches(&vec![1u8, 2, 3]);
assert_vec_encoding_matches(&BTreeMap::from([("k", 1u8)]));
assert_vec_encoding_matches(&EmptyStruct {});
assert_vec_encoding_matches(&UnitStruct);
assert_vec_encoding_matches(&NewtypeStruct(7));
assert_vec_encoding_matches(&Point { x: 1, y: 0x0102 });
assert_vec_encoding_matches(&Outer {
name: "vault",
point: Point { x: 3, y: 4 },
});
assert_vec_encoding_matches(&MidSkip {
first: 1,
middle: None,
last: 2,
});
assert_vec_encoding_matches(&WithNever { kept: 1, never: 2 });
assert_vec_encoding_matches(&Shape::Unit);
assert_vec_encoding_matches(&Shape::New(1));
assert_vec_encoding_matches(&Shape::Tup(1, 2));
assert_vec_encoding_matches(&Shape::Named { a: 1 });
assert_vec_encoding_matches(&UnknownLengthSeq);
assert_vec_encoding_matches(&UnknownLengthMap);
assert_vec_encoding_matches(&Displays(7));
}
#[test]
fn test_encode_into_vec_appends_after_a_prefix() {
const VAULT_PAYLOAD_CODEC: u8 = 0x07;
let value = Outer {
name: "vault",
point: Point { x: 1, y: 2 },
};
let document = locked_bytes(&value);
let mut buffer = Vec::new();
buffer.push(VAULT_PAYLOAD_CODEC);
secure_types::encode_into_vec(&mut buffer, &value).unwrap();
assert_eq!(buffer[0], VAULT_PAYLOAD_CODEC);
assert_eq!(
&buffer[1..],
document.as_slice(),
"the document must be appended after the prefix, unchanged"
);
}
#[test]
fn test_encode_into_vec_erases_its_partial_document_on_failure() {
const PREFIX: &[u8] = b"tag";
let mut buffer = PREFIX.to_vec();
let error = secure_types::encode_into_vec(&mut buffer, &DeclaresTooMany)
.expect_err("a mismatched element count should fail the encoding");
assert!(matches!(error, EncodeError::ElementCountMismatch));
assert_eq!(
buffer, PREFIX,
"the failed encoding left bytes behind in the caller's buffer"
);
}
fn assert_encoded_len_matches<T>(value: &T)
where
T: ?Sized + Serialize,
{
let document = locked_bytes(value);
assert_eq!(
secure_types::encoded_len(value).unwrap(),
document.len(),
"encoded_len disagreed with the encoded document"
);
}
#[test]
fn test_encoded_len_matches_the_document() {
assert_encoded_len_matches(&EmptyStruct {});
assert_encoded_len_matches(&UnitStruct);
assert_encoded_len_matches(&NewtypeStruct(7));
assert_encoded_len_matches(&true);
assert_encoded_len_matches(&"a string");
assert_encoded_len_matches(&Some(7u8));
assert_encoded_len_matches(&Option::<u8>::None);
assert_encoded_len_matches(&vec![1u8, 2, 3]);
assert_encoded_len_matches(&BTreeMap::from([("k", 1u8)]));
assert_encoded_len_matches(&Point { x: 1, y: 0x0102 });
assert_encoded_len_matches(&Outer {
name: "vault",
point: Point { x: 3, y: 4 },
});
assert_encoded_len_matches(&MidSkip {
first: 1,
middle: None,
last: 2,
});
assert_encoded_len_matches(&WithNever { kept: 1, never: 2 });
assert_encoded_len_matches(&Shape::Unit);
assert_encoded_len_matches(&Shape::New(1));
assert_encoded_len_matches(&Shape::Tup(1, 2));
assert_encoded_len_matches(&Shape::Named { a: 1 });
assert_encoded_len_matches(&"x".repeat(127));
assert_encoded_len_matches(&"x".repeat(128));
assert_encoded_len_matches(&UnknownLengthSeq);
assert_encoded_len_matches(&UnknownLengthMap);
assert_encoded_len_matches(&Displays(7));
}
#[test]
fn test_encoded_len_fails_where_encode_fails() {
let error = secure_types::encoded_len(&DeclaresTooMany)
.expect_err("a mismatched element count should fail the measurement");
assert!(matches!(error, EncodeError::ElementCountMismatch));
}