use std::cmp::Ordering;
use std::io::{Read, Write};
use crate::traits::{self, Cipher, Error, GeneratedKey};
struct ColWriter<W: Write, const N: usize> {
inner: W,
line_length: usize,
}
impl<W: Write, const N: usize> ColWriter<W, { N }> {
fn new(writer: W) -> Self {
Self {
inner: writer,
line_length: 0,
}
}
}
impl<W: Write, const N: usize> Write for ColWriter<W, { N }> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
for &c in buf {
self.inner.write(&[c])?;
self.line_length += 1;
if c == b'\n' {
self.line_length = 0;
} else if self.line_length == N {
self.line_length = 0;
self.inner.write(b"\n")?;
}
}
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
self.inner.flush()
}
}
#[derive(Copy, Clone, Debug)]
enum Delta {
Positive(u8),
Negative(u8),
Neutral,
}
impl Delta {
#[inline]
fn between(previous: u8, new: u8) -> Self {
match new.cmp(&previous) {
Ordering::Greater => Self::Positive(new - previous),
Ordering::Less => Self::Negative(previous - new),
Ordering::Equal => Self::Neutral,
}
}
#[inline]
fn len(self) -> usize {
match self {
Self::Positive(n) | Self::Negative(n) => usize::from(n),
Self::Neutral => 0,
}
}
#[inline]
fn write_to(self, buf: &mut Vec<u8>) {
let (operator, n) = match self {
Self::Positive(n) => (b'+', n), Self::Negative(n) => (b'-', n), Self::Neutral => return, };
for _ in 0..n {
buf.push(operator);
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct Opti(u8);
impl Opti {
#[inline]
fn for_char(character: u8) -> Self {
Self(character)
}
#[inline]
fn len(self) -> usize {
debug_assert!(usize::from(u8::MAX) < usize::MAX);
self.get().map_or(usize::MAX, str::len)
}
#[inline]
fn get(self) -> Option<&'static str> {
let chars = match self.0 {
b'e' => "<.>",
b' ' => ">.<",
b',' => ">>.<<",
b'.' => ">>>.<<<",
b'\n' => ">>>>.<<<<",
b'(' => ">>----.++++<<", b')' => ">>---.+++<<", b'a' => "<----.++++>", b'!' => ">+.-<", b'?' => ">>>+++++++++++++++++.-----------------<<<", b':' => ">>>++++++++++++.------------<<<", b';' => ">>>+++++++++++++.-------------<<<", b'"' => ">++.--<", b'\'' => ">>-----.+++++<<", b'-' => ">>+.-<<", _ => return None,
};
Some(chars)
}
fn registers_initialization() -> &'static str {
"\
>\
++++++++[>++++++++++++<-]>+\
<<++++++++++[>++++++++++<-]>+>\
>++++[>+++++++++++<-]\
++++++++++++++++++++++++++++++++\
>[>+>+<<-]>>[<<+>>-]<++\
>++++++++++\
<<<<\
"
}
#[inline]
fn write_to(self, buf: &mut Vec<u8>) {
let Some(chars) = self.get() else {
return;
};
buf.extend(chars.as_bytes());
}
fn remove_redundant_shifts(output: &[u8]) -> Vec<u8> {
fn tally(shift_balance: i32) -> impl Iterator<Item = u8> {
let shift_tally =
usize::try_from(shift_balance.unsigned_abs()).expect("platform not supported");
let character = if shift_balance.is_positive() {
b'>'
} else if shift_balance.is_negative() {
b'<'
} else {
0u8
};
std::iter::repeat_n(character, shift_tally)
}
let mut result = Vec::with_capacity(output.len());
let mut shift_balance: i32 = 0;
for &c in output {
match c {
b'>' => shift_balance += 1,
b'<' => shift_balance -= 1,
_ => {
result.extend(tally(shift_balance));
shift_balance = 0;
result.push(c);
}
}
}
result.extend(tally(shift_balance));
result
}
}
pub struct Brainfuck;
impl Cipher for Brainfuck {
fn generate_key(&self) -> GeneratedKey {
GeneratedKey::None
}
fn encrypt_stream(
&self,
_: &[u8],
reader: &mut dyn Read,
writer: &mut dyn Write,
) -> traits::Result<()> {
let mut writer = ColWriter::<_, 72>::new(writer);
let mut previous_char = 97;
writer
.write_all(Opti::registers_initialization().as_bytes())
.map_err(|e| Error::Write(e.to_string()))?;
let mut buffer = [0u8; 4096];
let mut output: Vec<u8> = Vec::new();
loop {
let n = match reader.read(&mut buffer) {
Ok(n) => n,
Err(reason) => return Err(Error::Read(reason.to_string())),
};
if n == 0 {
break;
}
output.clear();
for &c in &buffer[..n] {
let delta = Delta::between(previous_char, c);
let opti = Opti::for_char(c);
if opti.len() < delta.len() {
opti.write_to(&mut output);
} else {
delta.write_to(&mut output);
previous_char = c;
output.push(b'.');
}
}
let output = Opti::remove_redundant_shifts(&output);
writer
.write_all(&output)
.map_err(|e| Error::Write(e.to_string()))?;
}
writer.flush().map_err(|e| Error::Write(e.to_string()))?;
Ok(())
}
#[allow(
clippy::match_on_vec_items,
clippy::needless_range_loop,
clippy::redundant_else,
clippy::too_many_lines
)]
fn decrypt_stream(
&self,
_: &[u8],
reader: &mut dyn Read,
writer: &mut dyn Write,
) -> traits::Result<()> {
let mut program = Vec::new();
reader
.read_to_end(&mut program)
.map_err(|e| Error::Read(e.to_string()))?;
let mut memory = vec![0u8; 8]; let mut ptr: usize = 0;
let mut instruction = 0;
let mut loop_stack = Vec::new();
let mut line = 1;
loop {
if instruction == program.len() {
if let Some(opening_bracket) = loop_stack.last() {
return Err(Error::Other(format!(
"\
Unbalanced loop brackets.
Opening bracket is missing its pair: {} ([).",
opening_bracket + 1
)));
}
break;
}
let pos = (instruction + 1) - (line - 1);
match program[instruction] {
b'>' => {
if memory.len() == ptr {
memory.extend([0u8; 4096]);
}
ptr = ptr
.checked_add(1)
.expect("memory allocation will fail first");
}
b'<' => {
ptr = ptr.checked_sub(1).ok_or_else(|| {
Error::Other(format!(
"\
Pointer underflow.
Attempting to shift data pointer below 0: {pos} (<).",
))
})?;
}
b'+' => {
memory[ptr] = memory[ptr].checked_add(1).ok_or_else(|| {
Error::Other(format!(
"\
Cell overflow.
Attempting to increment cell {ptr} above 255: {pos} (+).",
))
})?;
}
b'-' => {
memory[ptr] = memory[ptr].checked_sub(1).ok_or_else(|| {
Error::Other(format!(
"\
Cell underflow.
Attempting to decrement cell {ptr} below 0: {pos} (-).",
))
})?;
}
b'.' => writer
.write_all(&[memory[ptr]])
.map_err(|e| Error::Write(e.to_string()))?,
b',' => {
memory[ptr] = 0;
}
b'[' => {
if memory[ptr] == 0 {
let mut depth = 1;
let mut did_find_matching_bracket = false;
for i in (instruction + 1)..program.len() {
match program[i] {
b'[' => depth += 1,
b']' => {
depth -= 1;
if depth == 0 {
did_find_matching_bracket = true;
instruction = i + 1;
break;
}
}
_ => (),
}
}
if !did_find_matching_bracket {
return Err(Error::Other(format!(
"\
Unbalanced loop brackets.
Opening bracket is missing its pair: {pos} ([).",
)));
}
continue;
} else {
loop_stack.push(instruction);
}
}
b']' => {
let Some(opening_bracket) = loop_stack.last() else {
return Err(Error::Other(format!(
"\
Unbalanced loop brackets.
Closing bracket is missing its pair: {pos} (]).",
)));
};
if memory[ptr] != 0 {
instruction = opening_bracket + 1;
continue;
} else {
loop_stack.pop().expect("there is a `last()`");
}
}
b'\n' => line += 1,
_ => (),
}
instruction += 1;
}
Ok(())
}
}
#[cfg(test)]
pub mod tests {
use super::*;
const TEXT: &str = r#"The quick brown fox jumps over the lazy dog. This sentence contains
every letter of the alphabet, making it useful for testing font
rendering and keyboard layouts.
Meanwhile, in a quiet village nestled between rolling hills, an old
clock tower struck midnight. The sound echoed through empty streets,
marking the end of another ordinary day.
"Nothing ever happens here," she whispered, staring out the window. Yet
in the silence, something had already begun to stir.
"Are you serious?" she asked (again); her tone carried both surprise and
irritation: he had, after all, forgotten — once more — to lock the door!
It wasn't the first time, nor would it be the last. Still, she couldn’t
help but wonder: what was going on in his head?
He paused... then smiled. "Relax. Everything’s fine."
(But it wasn’t.)
"#;
#[test]
fn brainfuck_encrypt_length() {
let plaintext = TEXT.as_bytes();
let encrypted = Brainfuck.encrypt(&[], plaintext).unwrap();
dbg!(&encrypted);
assert_eq!(encrypted.len(), 7706 - 1);
}
#[test]
fn brainfuck_round_trip() {
let plaintext = TEXT.as_bytes();
let encrypted = Brainfuck.encrypt(&[], plaintext).unwrap();
let decrypted = Brainfuck.decrypt(&[], &encrypted).unwrap();
dbg!(&decrypted);
assert_eq!(plaintext, decrypted);
}
#[test]
fn brainfuck_decrypt_pointer_underflow() {
let ciphertext = b"<";
let error = Brainfuck.decrypt(&[], ciphertext).unwrap_err();
dbg!(&error);
assert_eq!(
error,
Error::Other(
"\
Pointer underflow.
Attempting to shift data pointer below 0: 1 (<)."
.to_string()
)
);
}
#[test]
fn brainfuck_decrypt_cell_overflow() {
let ciphertext = b"+++++[>++++++++++<-]>+[<+++++>-]<+";
let error = Brainfuck.decrypt(&[], ciphertext).unwrap_err();
dbg!(&error);
assert_eq!(
error,
Error::Other(
"\
Cell overflow.
Attempting to increment cell 0 above 255: 34 (+)."
.to_string()
)
);
}
#[test]
fn brainfuck_decrypt_cell_underflow() {
let ciphertext = b"-";
let error = Brainfuck.decrypt(&[], ciphertext).unwrap_err();
dbg!(&error);
assert_eq!(
error,
Error::Other(
"\
Cell underflow.
Attempting to decrement cell 0 below 0: 1 (-)."
.to_string()
)
);
}
#[test]
fn brainfuck_decrypt_input_sets_cell_to_zero() {
let ciphertext = b"+++,+++++++++++++++++++++++++++++++++.";
let decrypted = Brainfuck.decrypt(&[], ciphertext).unwrap();
dbg!(&decrypted);
assert_eq!(decrypted, b"!");
}
#[test]
fn brainfuck_decrypt_unbalanced_left_bracket() {
let ciphertext = b"[[]++";
let error = Brainfuck.decrypt(&[], ciphertext).unwrap_err();
dbg!(&error);
assert_eq!(
error,
Error::Other(
"\
Unbalanced loop brackets.
Opening bracket is missing its pair: 1 ([)."
.to_string()
)
);
}
#[test]
fn brainfuck_decrypt_unbalanced_right_bracket_at_end() {
let ciphertext = b"+++]";
let error = Brainfuck.decrypt(&[], ciphertext).unwrap_err();
dbg!(&error);
assert_eq!(
error,
Error::Other(
"\
Unbalanced loop brackets.
Closing bracket is missing its pair: 4 (])."
.to_string()
)
);
}
#[test]
fn brainfuck_decrypt_memory_length() {
let ciphertext = b"\
++++[>++++++<-]>[>+++++>+++++++<<-]>>++++<[[>[[>>+<<-]<]>>>-]>-[>+>+<<-]>]
+++++[>+++++++<<++>-]>.<<.
";
let decrypted = Brainfuck.decrypt(&[], ciphertext).unwrap();
dbg!(&decrypted);
assert_eq!(decrypted, b"#\n");
}
#[test]
fn brainfuck_decrypt_obscure_problems() {
let ciphertext = br#"[]++++++++++[>>+>+>++++++[<<+<+++>>>-]<<<<-]
"A*$";?@![#>>+<<]>[>>]<<<<[>++<[-]]>.>.
"#;
let decrypted = Brainfuck.decrypt(&[], ciphertext).unwrap();
dbg!(&decrypted);
assert_eq!(decrypted, b"H\n");
}
#[test]
fn brainfuck_decrypt_unbalanced_left_bracket_at_end() {
let ciphertext = b"+++++[>+++++++>++<<-]>.>.[";
let error = Brainfuck.decrypt(&[], ciphertext).unwrap_err();
dbg!(&error);
assert_eq!(
error,
Error::Other(
"\
Unbalanced loop brackets.
Opening bracket is missing its pair: 26 ([)."
.to_string()
)
);
}
#[test]
fn brainfuck_decrypt_unbalanced_right_bracket() {
let ciphertext = b"+++++[>+++++++>++<<-]>.>.][";
let error = Brainfuck.decrypt(&[], ciphertext).unwrap_err();
dbg!(&error);
assert_eq!(
error,
Error::Other(
"\
Unbalanced loop brackets.
Closing bracket is missing its pair: 26 (])."
.to_string()
)
);
}
}