mirror of
https://github.com/kristoferssolo/cipher-workshop.git
synced 2025-12-31 13:52:29 +00:00
refactor(cipher-core): extract shared block parsing logic
Add generic BlockInt trait and parse_block_int<T>() function to
cipher-core, eliminating duplicate parsing code in aes and des crates.
- BlockInt trait abstracts over u64/u128 integer types
- Supports hex (0x), binary (0b), and ASCII string formats
- Improved BlockError::InvalidByteStringLength with max/actual fields
This commit is contained in:
parent
656e112d9f
commit
451986d702
@ -2,7 +2,7 @@ use crate::{
|
||||
block::{Block32, secret_block},
|
||||
sbox::SboxLookup,
|
||||
};
|
||||
use cipher_core::{BlockError, InputBlock};
|
||||
use cipher_core::{parse_block_int, BlockError, InputBlock};
|
||||
use std::{
|
||||
ops::BitXor,
|
||||
slice::{from_raw_parts, from_raw_parts_mut},
|
||||
@ -68,63 +68,10 @@ impl Block128 {
|
||||
impl FromStr for Block128 {
|
||||
type Err = BlockError;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self(parse_string_to_u128(s)?))
|
||||
Ok(Self(parse_block_int(s)?))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_string_to_u128(s: &str) -> Result<u128, BlockError> {
|
||||
let trimmed = s.trim();
|
||||
|
||||
if trimmed.is_empty() {
|
||||
return Err(BlockError::EmptyBlock);
|
||||
}
|
||||
|
||||
// Hexadecimal with 0x/0X prefix
|
||||
if let Some(hex_str) = trimmed
|
||||
.strip_prefix("0x")
|
||||
.or_else(|| trimmed.strip_prefix("0X"))
|
||||
{
|
||||
return parse_radix(hex_str, 16);
|
||||
}
|
||||
// Binary with 0b/0B prefix
|
||||
if let Some(bin_str) = trimmed
|
||||
.strip_prefix("0b")
|
||||
.or_else(|| trimmed.strip_prefix("0B"))
|
||||
{
|
||||
return parse_radix(bin_str, 2);
|
||||
}
|
||||
|
||||
ascii_string_to_u128(trimmed)
|
||||
}
|
||||
|
||||
fn parse_radix(s: &str, radix: u32) -> Result<u128, BlockError> {
|
||||
let trimmed = s.trim_start_matches('0');
|
||||
if trimmed.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
u128::from_str_radix(trimmed, radix).map_err(BlockError::from)
|
||||
}
|
||||
|
||||
fn ascii_string_to_u128(s: &str) -> Result<u128, BlockError> {
|
||||
if s.len() > 16 {
|
||||
return Err(BlockError::InvalidByteStringLength(s.len()));
|
||||
}
|
||||
|
||||
if !s.is_ascii() {
|
||||
return Err(BlockError::conversion_error(
|
||||
"u64",
|
||||
"String contains non-ASCII characters",
|
||||
));
|
||||
}
|
||||
|
||||
let mut bytes = [0u8; 16];
|
||||
let offset = 16 - s.len();
|
||||
bytes[offset..].copy_from_slice(s.as_bytes());
|
||||
|
||||
Ok(u128::from_be_bytes(bytes))
|
||||
}
|
||||
|
||||
impl From<[u8; 16]> for Block128 {
|
||||
fn from(bytes: [u8; 16]) -> Self {
|
||||
Self::from_be_bytes(bytes)
|
||||
|
||||
@ -44,8 +44,8 @@ pub enum BlockError {
|
||||
ParseError(#[from] ParseIntError),
|
||||
|
||||
/// Byte size length
|
||||
#[error("Invalid byte string length: expected no more than 8, found {0}")]
|
||||
InvalidByteStringLength(usize),
|
||||
#[error("Invalid byte string length: expected no more than {max}, found {actual}")]
|
||||
InvalidByteStringLength { max: usize, actual: usize },
|
||||
|
||||
/// String to int conversion error
|
||||
#[error("String-to-{typ} conversion error: {err}")]
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
mod error;
|
||||
mod parsing;
|
||||
mod traits;
|
||||
mod types;
|
||||
|
||||
pub use {
|
||||
error::{BlockError, CipherError, CipherResult},
|
||||
parsing::{parse_block_int, BlockInt},
|
||||
traits::{BlockCipher, BlockParser, InputBlock},
|
||||
types::{CipherAction, Output},
|
||||
};
|
||||
|
||||
112
cipher-core/src/parsing.rs
Normal file
112
cipher-core/src/parsing.rs
Normal file
@ -0,0 +1,112 @@
|
||||
use crate::BlockError;
|
||||
use std::{any::type_name, num::ParseIntError};
|
||||
|
||||
/// Trait for integer types that can be parsed from block string formats.
|
||||
///
|
||||
/// Implemented for `u64` and `u128` to support DES (64-bit) and AES (128-bit) block parsing.
|
||||
pub trait BlockInt: Sized + Copy {
|
||||
/// Number of bytes this integer type represents.
|
||||
const BYTE_SIZE: usize;
|
||||
|
||||
/// Parse from string with given radix.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns `ParseIntError` if the string contains invalid digits for the radix.
|
||||
fn from_str_radix(s: &str, radix: u32) -> Result<Self, ParseIntError>;
|
||||
|
||||
/// Construct from big-endian bytes (zero-padded on the left).
|
||||
fn from_be_bytes_padded(bytes: &[u8]) -> Self;
|
||||
}
|
||||
|
||||
impl BlockInt for u64 {
|
||||
const BYTE_SIZE: usize = 8;
|
||||
|
||||
fn from_str_radix(s: &str, radix: u32) -> Result<Self, ParseIntError> {
|
||||
Self::from_str_radix(s, radix)
|
||||
}
|
||||
|
||||
fn from_be_bytes_padded(bytes: &[u8]) -> Self {
|
||||
let mut arr = [0u8; 8];
|
||||
let offset = 8 - bytes.len();
|
||||
arr[offset..].copy_from_slice(bytes);
|
||||
Self::from_be_bytes(arr)
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockInt for u128 {
|
||||
const BYTE_SIZE: usize = 16;
|
||||
|
||||
fn from_str_radix(s: &str, radix: u32) -> Result<Self, ParseIntError> {
|
||||
Self::from_str_radix(s, radix)
|
||||
}
|
||||
|
||||
fn from_be_bytes_padded(bytes: &[u8]) -> Self {
|
||||
let mut arr = [0u8; 16];
|
||||
let offset = 16 - bytes.len();
|
||||
arr[offset..].copy_from_slice(bytes);
|
||||
Self::from_be_bytes(arr)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a string into a block integer, supporting hex (0x), binary (0b), and ASCII formats.
|
||||
///
|
||||
/// # Formats
|
||||
/// - `0x...` or `0X...`: Hexadecimal
|
||||
/// - `0b...` or `0B...`: Binary
|
||||
/// - Otherwise: ASCII string (right-aligned, zero-padded)
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns `BlockError` if the string is empty, contains invalid characters,
|
||||
/// or exceeds the maximum byte length for the target type.
|
||||
pub fn parse_block_int<T: BlockInt>(s: &str) -> Result<T, BlockError> {
|
||||
let trimmed = s.trim();
|
||||
|
||||
if trimmed.is_empty() {
|
||||
return Err(BlockError::EmptyBlock);
|
||||
}
|
||||
|
||||
// Hexadecimal with 0x/0X prefix
|
||||
if let Some(hex_str) = trimmed
|
||||
.strip_prefix("0x")
|
||||
.or_else(|| trimmed.strip_prefix("0X"))
|
||||
{
|
||||
return parse_radix::<T>(hex_str, 16);
|
||||
}
|
||||
|
||||
// Binary with 0b/0B prefix
|
||||
if let Some(bin_str) = trimmed
|
||||
.strip_prefix("0b")
|
||||
.or_else(|| trimmed.strip_prefix("0B"))
|
||||
{
|
||||
return parse_radix::<T>(bin_str, 2);
|
||||
}
|
||||
|
||||
parse_ascii::<T>(trimmed)
|
||||
}
|
||||
|
||||
fn parse_radix<T: BlockInt>(s: &str, radix: u32) -> Result<T, BlockError> {
|
||||
let trimmed = s.trim_start_matches('0');
|
||||
if trimmed.is_empty() {
|
||||
return Ok(T::from_be_bytes_padded(&[]));
|
||||
}
|
||||
|
||||
T::from_str_radix(trimmed, radix).map_err(BlockError::from)
|
||||
}
|
||||
|
||||
fn parse_ascii<T: BlockInt>(s: &str) -> Result<T, BlockError> {
|
||||
if s.len() > T::BYTE_SIZE {
|
||||
return Err(BlockError::InvalidByteStringLength {
|
||||
max: T::BYTE_SIZE,
|
||||
actual: s.len(),
|
||||
});
|
||||
}
|
||||
|
||||
if !s.is_ascii() {
|
||||
return Err(BlockError::conversion_error(
|
||||
type_name::<T>(),
|
||||
"String contains non-ASCII characters",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(T::from_be_bytes_padded(s.as_bytes()))
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
use crate::block::{lr::LR, secret_block};
|
||||
use cipher_core::{BlockError, InputBlock};
|
||||
use cipher_core::{parse_block_int, BlockError, InputBlock};
|
||||
use std::{
|
||||
slice::{from_raw_parts, from_raw_parts_mut},
|
||||
str::FromStr,
|
||||
@ -48,63 +48,10 @@ impl Block64 {
|
||||
impl FromStr for Block64 {
|
||||
type Err = BlockError;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self(parse_string_to_u64(s)?))
|
||||
Ok(Self(parse_block_int(s)?))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_string_to_u64(s: &str) -> Result<u64, BlockError> {
|
||||
let trimmed = s.trim();
|
||||
|
||||
if trimmed.is_empty() {
|
||||
return Err(BlockError::EmptyBlock);
|
||||
}
|
||||
|
||||
// Hexadecimal with 0x/0X prefix
|
||||
if let Some(hex_str) = trimmed
|
||||
.strip_prefix("0x")
|
||||
.or_else(|| trimmed.strip_prefix("0X"))
|
||||
{
|
||||
return parse_radix(hex_str, 16);
|
||||
}
|
||||
// Binary with 0b/0B prefix
|
||||
if let Some(bin_str) = trimmed
|
||||
.strip_prefix("0b")
|
||||
.or_else(|| trimmed.strip_prefix("0B"))
|
||||
{
|
||||
return parse_radix(bin_str, 2);
|
||||
}
|
||||
|
||||
ascii_string_to_u64(trimmed)
|
||||
}
|
||||
|
||||
fn parse_radix(s: &str, radix: u32) -> Result<u64, BlockError> {
|
||||
let trimmed = s.trim_start_matches('0');
|
||||
if trimmed.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
u64::from_str_radix(trimmed, radix).map_err(BlockError::from)
|
||||
}
|
||||
|
||||
fn ascii_string_to_u64(s: &str) -> Result<u64, BlockError> {
|
||||
if s.len() > 8 {
|
||||
return Err(BlockError::InvalidByteStringLength(s.len()));
|
||||
}
|
||||
|
||||
if !s.is_ascii() {
|
||||
return Err(BlockError::conversion_error(
|
||||
"u64",
|
||||
"String contains non-ASCII characters",
|
||||
));
|
||||
}
|
||||
|
||||
let mut bytes = [0u8; 8];
|
||||
let offset = 8 - s.len();
|
||||
bytes[offset..].copy_from_slice(s.as_bytes());
|
||||
|
||||
Ok(u64::from_be_bytes(bytes))
|
||||
}
|
||||
|
||||
impl From<[u8; 8]> for Block64 {
|
||||
fn from(bytes: [u8; 8]) -> Self {
|
||||
Self::from_be_bytes(bytes)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user