use crate::consts::VALID_ENTR_LENGTHS;
use crate::error::{Error, Result};
use crate::tag::Tag;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum PayloadKind {
Entr,
Mnem,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Payload {
Entr(Vec<u8>),
Mnem {
language: u8,
entropy: Vec<u8>,
},
}
impl Payload {
pub fn validate(&self) -> Result<()> {
match self {
Payload::Entr(data) => {
if !VALID_ENTR_LENGTHS.contains(&data.len()) {
return Err(Error::PayloadLengthMismatch {
tag: *Tag::ENTR.as_bytes(),
expected: VALID_ENTR_LENGTHS,
got: data.len(),
});
}
Ok(())
}
Payload::Mnem { language, entropy } => {
if *language >= 10 {
return Err(Error::MnemUnknownLanguage(*language));
}
if !VALID_ENTR_LENGTHS.contains(&entropy.len()) {
return Err(Error::PayloadLengthMismatch {
tag: *Tag::ENTR.as_bytes(),
expected: VALID_ENTR_LENGTHS,
got: entropy.len(),
});
}
Ok(())
}
}
}
pub fn kind(&self) -> PayloadKind {
match self {
Payload::Entr(_) => PayloadKind::Entr,
Payload::Mnem { .. } => PayloadKind::Mnem,
}
}
pub fn as_bytes(&self) -> &[u8] {
match self {
Payload::Entr(data) => data,
Payload::Mnem { entropy, .. } => entropy,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mnem_valid_language_and_entropy_accepts() {
let p = Payload::Mnem { language: 1, entropy: vec![0u8; 16] };
assert!(matches!(p.validate(), Ok(())));
}
#[test]
fn mnem_language_10_rejects() {
let p = Payload::Mnem { language: 10, entropy: vec![0u8; 16] };
assert!(matches!(p.validate(), Err(Error::MnemUnknownLanguage(10))));
}
#[test]
fn mnem_language_0x10_rejects() {
let p = Payload::Mnem { language: 0x10, entropy: vec![0u8; 16] };
assert!(matches!(p.validate(), Err(Error::MnemUnknownLanguage(0x10))));
}
#[test]
fn mnem_bad_entropy_length_rejects() {
let p = Payload::Mnem { language: 0, entropy: vec![0u8; 17] };
assert!(matches!(p.validate(), Err(Error::PayloadLengthMismatch { .. })));
}
#[test]
fn mnem_kind_returns_mnem() {
let p = Payload::Mnem { language: 0, entropy: vec![0u8; 16] };
assert_eq!(p.kind(), PayloadKind::Mnem);
}
#[test]
fn entr_accepts_all_bip39_lengths() {
for len in [16usize, 20, 24, 28, 32] {
let p = Payload::Entr(vec![0u8; len]);
p.validate()
.unwrap_or_else(|e| panic!("expected ok for len {}, got {:?}", len, e));
}
}
#[test]
fn entr_rejects_off_by_one_lengths() {
for len in [15usize, 17, 19, 21, 23, 25, 31, 33] {
let p = Payload::Entr(vec![0u8; len]);
assert!(
matches!(p.validate(), Err(Error::PayloadLengthMismatch { .. })),
"expected reject for len {}",
len
);
}
}
#[test]
fn entr_rejects_zero_length() {
let p = Payload::Entr(vec![]);
assert!(matches!(
p.validate(),
Err(Error::PayloadLengthMismatch { .. })
));
}
#[test]
fn kind_returns_entr() {
assert_eq!(Payload::Entr(vec![0u8; 16]).kind(), PayloadKind::Entr);
}
}