mirror of
https://github.com/kristoferssolo/cipher-workshop.git
synced 2025-12-20 11:04:38 +00:00
refactor(cli): change subcommand order
This commit is contained in:
parent
dc243a8731
commit
54ddcab377
@ -2,7 +2,6 @@ use clap::{Parser, Subcommand, ValueEnum};
|
|||||||
use std::{
|
use std::{
|
||||||
fmt::{Display, LowerHex, UpperHex},
|
fmt::{Display, LowerHex, UpperHex},
|
||||||
fs::read_to_string,
|
fs::read_to_string,
|
||||||
num::IntErrorKind,
|
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
};
|
};
|
||||||
@ -25,7 +24,7 @@ pub enum ValueError {
|
|||||||
#[error("Invalid number format: {0}")]
|
#[error("Invalid number format: {0}")]
|
||||||
InvalidFormat(String),
|
InvalidFormat(String),
|
||||||
|
|
||||||
#[error("Invalid byte string length: expected no more than, found {0}")]
|
#[error("Invalid byte string length: expected no more than 8, found {0}")]
|
||||||
InvalidByteStringLength(usize),
|
InvalidByteStringLength(usize),
|
||||||
|
|
||||||
#[error("String-to-u64 conversion error: {0}")]
|
#[error("String-to-u64 conversion error: {0}")]
|
||||||
@ -37,23 +36,30 @@ pub enum ValueError {
|
|||||||
pub struct Args {
|
pub struct Args {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
pub operation: Operation,
|
pub operation: Operation,
|
||||||
|
|
||||||
/// Key used to encrypt/decrypt data (64-bit number, string, or path to file)
|
|
||||||
#[arg(short, long, value_parser = Value::from_str, required = true)]
|
|
||||||
pub key: Value,
|
|
||||||
|
|
||||||
/// The text to encrypt/decrypt data (64-bit number, string, or path to file)
|
|
||||||
#[arg(value_name = "TEXT", value_parser = Value::from_str, required = true)]
|
|
||||||
pub text: Value,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Subcommand, Default)]
|
#[derive(Debug, Clone, Subcommand)]
|
||||||
pub enum Operation {
|
pub enum Operation {
|
||||||
/// Encrypt data
|
/// Encrypt data
|
||||||
#[default]
|
Encrypt {
|
||||||
Encrypt,
|
/// Key used to encrypt/decrypt data (64-bit number, string, or path to file)
|
||||||
|
#[arg(short, long, value_parser = Value::from_str, required = true)]
|
||||||
|
key: Value,
|
||||||
|
|
||||||
|
/// The text to encrypt/decrypt data (64-bit number, string, or path to file)
|
||||||
|
#[arg(value_name = "TEXT", value_parser = Value::from_str, required = true)]
|
||||||
|
text: Value,
|
||||||
|
},
|
||||||
/// Decrypt data
|
/// Decrypt data
|
||||||
Decrypt {
|
Decrypt {
|
||||||
|
/// Key used to encrypt/decrypt data (64-bit number, string, or path to file)
|
||||||
|
#[arg(short, long, value_parser = Value::from_str, required = true)]
|
||||||
|
key: Value,
|
||||||
|
|
||||||
|
/// The text to encrypt/decrypt data (64-bit number, string, or path to file)
|
||||||
|
#[arg(value_name = "TEXT", value_parser = Value::from_str, required = true)]
|
||||||
|
text: Value,
|
||||||
|
|
||||||
/// Output format for decrypted data
|
/// Output format for decrypted data
|
||||||
#[arg(short = 'f', long, value_enum)]
|
#[arg(short = 'f', long, value_enum)]
|
||||||
output_format: Option<OutputFormat>,
|
output_format: Option<OutputFormat>,
|
||||||
@ -131,55 +137,41 @@ fn parse_string_to_u64(s: &str) -> Result<u64, ValueError> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Hexadecimal with 0x/0X prefix
|
// Hexadecimal with 0x/0X prefix
|
||||||
if trimmed.starts_with("0x") || trimmed.starts_with("0X") {
|
if let Some(hex_str) = trimmed
|
||||||
let hex_str = &trimmed[2..].trim_start_matches('0');
|
.strip_prefix("0X")
|
||||||
if hex_str.is_empty() {
|
.or_else(|| trimmed.strip_prefix("0x"))
|
||||||
return Ok(0); // 0x000 ->
|
{
|
||||||
}
|
return parse_radix(hex_str, 16, "Hex");
|
||||||
return u64::from_str_radix(hex_str, 16)
|
|
||||||
.map_err(|e| ValueError::InvalidFormat(format!("Hex parsing failed: {e}")));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Binary with 0b/0B prefix
|
// Binary with 0b/0B prefix
|
||||||
if trimmed.starts_with("0b") || trimmed.starts_with("0B") {
|
if let Some(bin_str) = trimmed
|
||||||
let bin_str = &trimmed[2..].trim_start_matches('0');
|
.strip_prefix("0b")
|
||||||
if bin_str.is_empty() {
|
.or_else(|| trimmed.strip_prefix("0B"))
|
||||||
return Ok(0); // 0b000 -> 0
|
{
|
||||||
}
|
return parse_radix(bin_str, 2, "Binary");
|
||||||
if !bin_str.chars().all(|ch| ch == '0' || ch == '1') {
|
|
||||||
return Err(ValueError::InvalidFormat(
|
|
||||||
"Binary string contains invalid characters".into(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
return u64::from_str_radix(bin_str, 2)
|
|
||||||
.map_err(|e| ValueError::InvalidFormat(format!("Binary parsing failed: {e}")));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 8-character ASCII string conversion to u64
|
// 8-character ASCII string conversion to u64
|
||||||
if trimmed.len() <= 8 {
|
if trimmed.len() > 8 {
|
||||||
return ascii_string_to_u64(trimmed);
|
return Err(ValueError::InvalidByteStringLength(trimmed.len()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Regular decimal parsing
|
ascii_string_to_u64(trimmed)
|
||||||
trimmed.parse::<u64>().map_err(|e| {
|
}
|
||||||
ValueError::InvalidFormat(match e.kind() {
|
|
||||||
IntErrorKind::InvalidDigit => "contains invalid digits".into(),
|
fn parse_radix(s: &str, radix: u32, name: &str) -> Result<u64, ValueError> {
|
||||||
IntErrorKind::PosOverflow => "number too large for u64".into(),
|
let trimmed = s.trim_start_matches('0');
|
||||||
IntErrorKind::NegOverflow => "negative numbers not allowed".into(),
|
if trimmed.is_empty() {
|
||||||
IntErrorKind::Empty => "empty string".into(),
|
return Ok(0);
|
||||||
IntErrorKind::Zero => "invalid zero".into(),
|
}
|
||||||
_ => format!("parsing error: {e}"),
|
|
||||||
})
|
u64::from_str_radix(trimmed, radix)
|
||||||
})
|
.map_err(|e| ValueError::InvalidFormat(format!("{name} parsing failed: {e}")))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ascii_string_to_u64(s: &str) -> Result<u64, ValueError> {
|
fn ascii_string_to_u64(s: &str) -> Result<u64, ValueError> {
|
||||||
if s.len() > 8 {
|
if !s.is_ascii() {
|
||||||
return Err(ValueError::InvalidByteStringLength(s.len()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure all characters are valid ASCII (0-127)
|
|
||||||
if !s.bytes().all(|b| b <= 127) {
|
|
||||||
return Err(ValueError::ConversionError(
|
return Err(ValueError::ConversionError(
|
||||||
"String contains non-ASCII characters".into(),
|
"String contains non-ASCII characters".into(),
|
||||||
));
|
));
|
||||||
|
|||||||
@ -7,15 +7,20 @@ use des::Des;
|
|||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
let des = Des::new(args.key.as_64());
|
|
||||||
|
|
||||||
match args.operation {
|
match args.operation {
|
||||||
Operation::Encrypt => {
|
Operation::Encrypt { key, text } => {
|
||||||
let ciphertext = des.encrypt(&args.text.to_be_bytes())?;
|
let des = Des::new(key.as_64());
|
||||||
|
let ciphertext = des.encrypt(&text.to_be_bytes())?;
|
||||||
println!("{ciphertext:016X}");
|
println!("{ciphertext:016X}");
|
||||||
}
|
}
|
||||||
Operation::Decrypt { output_format } => {
|
Operation::Decrypt {
|
||||||
let plaintext = des.decrypt(&args.text.to_be_bytes())?;
|
key,
|
||||||
|
text,
|
||||||
|
output_format,
|
||||||
|
} => {
|
||||||
|
let des = Des::new(key.as_64());
|
||||||
|
let plaintext = des.decrypt(&text.to_be_bytes())?;
|
||||||
match output_format.unwrap_or_default() {
|
match output_format.unwrap_or_default() {
|
||||||
OutputFormat::Binary => println!("{plaintext:064b}"),
|
OutputFormat::Binary => println!("{plaintext:064b}"),
|
||||||
OutputFormat::Octal => println!("{plaintext:022o}"),
|
OutputFormat::Octal => println!("{plaintext:022o}"),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user