use crate::{BlockCipherPadding, PaddingError};
use core::fmt::{Display, Formatter};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct TbcPadding;
impl TbcPadding {
pub const fn new() -> Self {
Self
}
}
impl BlockCipherPadding for TbcPadding {
type Error = PaddingError;
fn add_padding(&mut self, block: &mut [u8], position: usize) -> Result<usize, Self::Error> {
let count = block
.len()
.checked_sub(position)
.ok_or(PaddingError::PositionOutOfRange)?;
if count == 0 {
return Err(PaddingError::BlockFull);
}
let last = if position > 0 {
block[position - 1]
} else {
block[block.len() - 1]
};
let code = (last & 0x01).wrapping_sub(1);
block[position..].fill(code);
Ok(count)
}
fn pad_count(&self, block: &[u8]) -> Result<usize, Self::Error> {
let code = *block.last().ok_or(PaddingError::CorruptPadding)?;
let mut count = 0;
let mut still_run = 1;
for &byte in block.iter().rev() {
let matches = ((byte ^ code) as usize).wrapping_sub(1) >> (usize::BITS - 1);
still_run &= matches;
count += still_run;
}
Ok(count)
}
}
impl Display for TbcPadding {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
f.write_str("TBC")
}
}
#[cfg(test)]
mod tests {
extern crate std;
use std::string::ToString;
use super::TbcPadding;
use crate::{BlockCipherPadding, PaddingError};
#[test]
fn a_message_ending_in_a_zero_bit_is_padded_with_ones() {
let mut padding = TbcPadding::new();
let mut block = [0x11_u8; 8];
block[2] = 0x1e;
assert_eq!(padding.add_padding(&mut block, 3), Ok(5));
assert_eq!(block, [0x11, 0x11, 0x1e, 0xff, 0xff, 0xff, 0xff, 0xff]);
assert_eq!(padding.pad_count(&block), Ok(5));
}
#[test]
fn a_message_ending_in_a_one_bit_is_padded_with_zeros() {
let mut padding = TbcPadding::new();
let mut block = [0x11_u8; 8];
block[2] = 0x1f;
assert_eq!(padding.add_padding(&mut block, 3), Ok(5));
assert_eq!(block, [0x11, 0x11, 0x1f, 0, 0, 0, 0, 0]);
assert_eq!(padding.pad_count(&block), Ok(5));
}
#[test]
fn the_run_never_reaches_into_the_message() {
let mut padding = TbcPadding::new();
let mut block = [0xfe_u8; 8];
assert_eq!(padding.add_padding(&mut block, 4), Ok(4));
assert_eq!(block, [0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff]);
assert_eq!(padding.pad_count(&block), Ok(4));
}
#[test]
fn a_single_padding_byte_is_recovered() {
let mut padding = TbcPadding::new();
let mut block = [0x1f_u8; 8];
assert_eq!(padding.add_padding(&mut block, 7), Ok(1));
assert_eq!(block, [0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0]);
assert_eq!(padding.pad_count(&block), Ok(1));
}
#[test]
fn a_whole_block_of_padding_uses_the_blocks_own_last_byte() {
let mut padding = TbcPadding::new();
let mut block = [0x1e_u8; 8];
assert_eq!(padding.add_padding(&mut block, 0), Ok(8));
assert_eq!(block, [0xff; 8]);
assert_eq!(padding.pad_count(&block), Ok(8));
}
#[test]
fn a_full_block_has_no_room_for_padding() {
let mut padding = TbcPadding::new();
assert_eq!(
padding.add_padding(&mut [0xff_u8; 8], 8),
Err(PaddingError::BlockFull)
);
}
#[test]
fn rejects_a_position_past_the_end_of_the_block() {
let mut padding = TbcPadding::new();
assert_eq!(
padding.add_padding(&mut [0xff_u8; 8], 9),
Err(PaddingError::PositionOutOfRange)
);
}
#[test]
fn rejects_an_empty_block() {
let padding = TbcPadding::new();
assert_eq!(padding.pad_count(&[]), Err(PaddingError::CorruptPadding));
}
#[test]
fn padding_round_trips_for_every_message_length() {
let mut padding = TbcPadding::new();
for used in 1..8 {
let mut block = [0xa5_u8; 8];
let added = padding.add_padding(&mut block, used).unwrap();
assert_eq!(added, 8 - used);
assert_eq!(padding.pad_count(&block), Ok(8 - used));
}
}
#[test]
fn reports_its_algorithm_name() {
assert_eq!(TbcPadding::new().to_string(), "TBC");
}
}