use crate::data::simple::{Attribute, ElGamalEncryptable, Pseudonym};
use std::io::{Error, ErrorKind};
pub trait Padded: ElGamalEncryptable {
fn from_bytes_padded(data: &[u8]) -> Result<Self, Error>
where
Self: Sized,
{
if data.len() > 15 {
return Err(Error::new(
ErrorKind::InvalidInput,
format!("Data too long: {} bytes (max 15)", data.len()),
));
}
let padding_byte = (16 - data.len()) as u8;
let mut block = [padding_byte; 16];
block[..data.len()].copy_from_slice(data);
Ok(Self::from_lizard(&block))
}
fn from_string_padded(text: &str) -> Result<Self, Error>
where
Self: Sized,
{
Self::from_bytes_padded(text.as_bytes())
}
fn to_string_padded(&self) -> Result<String, Error> {
let bytes = self.to_bytes_padded()?;
String::from_utf8(bytes).map_err(|e| Error::new(ErrorKind::InvalidData, e.to_string()))
}
fn to_bytes_padded(&self) -> Result<Vec<u8>, Error> {
let block = self.to_lizard().ok_or(Error::new(
ErrorKind::InvalidData,
"Value is not a valid padded value",
))?;
let padding_byte = block[15];
if padding_byte == 0 || padding_byte > 16 {
return Err(Error::new(ErrorKind::InvalidData, "Invalid padding"));
}
if block[16 - padding_byte as usize..]
.iter()
.any(|&b| b != padding_byte)
{
return Err(Error::new(ErrorKind::InvalidData, "Inconsistent padding"));
}
let data_bytes = 16 - padding_byte as usize;
Ok(block[..data_bytes].to_vec())
}
}
impl Padded for Pseudonym {}
impl Padded for Attribute {}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
use std::io::ErrorKind;
fn test_from_bytes_padded_roundtrip<T: Padded>() {
let test_cases = [
b"" as &[u8],
b"a",
b"hello",
b"Hello, world!",
b"123456789012345", ];
for data in test_cases {
let value = T::from_bytes_padded(data).unwrap();
let decoded = value.to_bytes_padded().unwrap();
assert_eq!(data, decoded.as_slice(), "Failed for input: {:?}", data);
}
}
fn test_from_string_padded_roundtrip<T: Padded>() {
let test_cases = ["", "a", "hello", "Hello, world!", "123456789012345"];
for text in test_cases {
let value = T::from_string_padded(text).unwrap();
let decoded = value.to_string_padded().unwrap();
assert_eq!(text, decoded.as_str(), "Failed for input: {:?}", text);
}
}
fn test_too_long<T: Padded + std::fmt::Debug>() {
let data = b"This is 16 bytes"; let result = T::from_bytes_padded(data);
assert!(result.is_err());
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidInput);
let data = b"This is way more than 15 bytes!";
let result = T::from_bytes_padded(data);
assert!(result.is_err());
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidInput);
let text = "This is way more than 15 bytes!";
let result = T::from_string_padded(text);
assert!(result.is_err());
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidInput);
}
fn test_padding_correctness<T: Padded + ElGamalEncryptable>() {
let value = T::from_bytes_padded(b"").unwrap();
let bytes = value.to_lizard().unwrap();
assert_eq!([16u8; 16], bytes);
let value = T::from_bytes_padded(b"X").unwrap();
let bytes = value.to_lizard().unwrap();
assert_eq!(b'X', bytes[0]);
for byte in bytes.iter().skip(1) {
assert_eq!(15, *byte);
}
let data = b"123456789012345";
let value = T::from_bytes_padded(data).unwrap();
let bytes = value.to_lizard().unwrap();
assert_eq!(data, &bytes[..15]);
assert_eq!(1, bytes[15]);
}
fn test_invalid_padding_decode<T: Padded + ElGamalEncryptable>() {
let invalid_block = [0u8; 16];
let value = T::from_lizard(&invalid_block);
let result = value.to_bytes_padded();
assert!(result.is_err());
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidData);
let mut inconsistent_block = [5u8; 16];
inconsistent_block[15] = 6; let value = T::from_lizard(&inconsistent_block);
let result = value.to_bytes_padded();
assert!(result.is_err());
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidData);
let mut invalid_block = [17u8; 16];
invalid_block[0] = b'X'; let value = T::from_lizard(&invalid_block);
let result = value.to_bytes_padded();
assert!(result.is_err());
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidData);
}
fn test_roundtrip_all_sizes<T: Padded>() {
for size in 0..=15 {
let data = vec![b'X'; size];
let value = T::from_bytes_padded(&data).unwrap();
let decoded = value.to_bytes_padded().unwrap();
assert_eq!(data, decoded, "Failed for size {}", size);
}
}
#[test]
fn pseudonym_from_bytes_padded() {
test_from_bytes_padded_roundtrip::<Pseudonym>();
}
#[test]
fn pseudonym_from_string_padded() {
test_from_string_padded_roundtrip::<Pseudonym>();
}
#[test]
fn pseudonym_too_long() {
test_too_long::<Pseudonym>();
}
#[test]
fn pseudonym_padding_correctness() {
test_padding_correctness::<Pseudonym>();
}
#[test]
fn pseudonym_invalid_padding_decode() {
test_invalid_padding_decode::<Pseudonym>();
}
#[test]
fn pseudonym_roundtrip_all_sizes() {
test_roundtrip_all_sizes::<Pseudonym>();
}
#[test]
fn attribute_from_bytes_padded() {
test_from_bytes_padded_roundtrip::<Attribute>();
}
#[test]
fn attribute_from_string_padded() {
test_from_string_padded_roundtrip::<Attribute>();
}
#[test]
fn attribute_too_long() {
test_too_long::<Attribute>();
}
#[test]
fn attribute_padding_correctness() {
test_padding_correctness::<Attribute>();
}
#[test]
fn attribute_invalid_padding_decode() {
test_invalid_padding_decode::<Attribute>();
}
#[test]
fn attribute_roundtrip_all_sizes() {
test_roundtrip_all_sizes::<Attribute>();
}
#[test]
fn attribute_unicode() {
let test_cases = [
"café", "你好", "🎉", ];
for text in test_cases {
let attr = Attribute::from_string_padded(text).unwrap();
let decoded = attr.to_string_padded().unwrap();
assert_eq!(text, decoded.as_str(), "Failed for input: {:?}", text);
}
}
#[test]
fn attribute_unicode_too_long() {
let text = "你好世界!"; let result = Attribute::from_string_padded(text);
assert!(result.is_ok());
let text = "你好世界!!"; let result = Attribute::from_string_padded(text);
assert!(result.is_err()); assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidInput);
}
}