use std::cmp::Ordering;
use std::io::{Read, Write};
use crate::traits::{self, Cipher, Error, GeneratedKey};
struct DotWriter<W: Write> {
inner: W,
buffer: Vec<u8>,
}
impl<W: Write> DotWriter<W> {
fn new(writer: W) -> Self {
Self {
inner: writer,
buffer: Vec::new(),
}
}
}
impl<W: Write> Write for DotWriter<W> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
if let Some(last_dot) = buf.iter().rposition(|&c| c == b'.') {
self.inner.write_all(&self.buffer)?;
self.buffer.clear();
let (to_write, to_buffer) = buf.split_at(last_dot + 1);
self.inner.write_all(to_write)?;
self.buffer.extend(to_buffer);
} else {
self.buffer.extend(buf);
}
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
if let Some(last_dot) = self.buffer.iter().rposition(|&c| c == b'.') {
self.inner.write_all(&self.buffer[..=last_dot])?;
}
self.inner.flush()
}
}
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 {
assert!(N > 0, "column width must be greater than zero");
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> {
let mut written = 0;
while written < buf.len() {
let remaining_columns = N - self.line_length;
let chunk_length = remaining_columns.min(buf.len() - written);
let chunk = &buf[written..written + chunk_length];
if let Some(newline) = chunk.iter().position(|&c| c == b'\n') {
let end = written + newline + 1;
self.inner.write_all(&buf[written..end])?;
written = end;
self.line_length = 0;
continue;
}
self.inner.write_all(chunk)?;
written += chunk.len();
self.line_length += chunk.len();
if self.line_length == N {
self.line_length = 0;
self.inner.write_all(b"\n")?;
}
}
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
if self.line_length > 0 {
self.inner.write_all(b"\n")?;
self.line_length = 0;
}
self.inner.flush()
}
}
#[inline]
fn instruction_run_length(program: &[u8], instruction: usize) -> usize {
let operator = program[instruction];
program[instruction..]
.iter()
.take_while(|&&candidate| candidate == operator)
.count()
}
#[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)]
struct Clear(u8);
impl Clear {
#[inline]
fn for_char(character: u8) -> Self {
Self(character)
}
#[inline]
fn len(self) -> usize {
3 + usize::from(self.0) }
#[inline]
fn write_to(self, buf: &mut Vec<u8>) {
buf.extend(b"[-]");
for _ in 0..self.0 {
buf.push(b'+');
}
}
}
#[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], shift_balance: &mut i32) -> 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());
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
}
}
pub struct Brainfuck;
impl Cipher for Brainfuck {
fn generate_key(&self) -> GeneratedKey {
GeneratedKey::None
}
fn terminates_output(&self) -> bool {
true
}
fn encrypt_stream(
&self,
_: &[u8],
reader: &mut dyn Read,
writer: &mut dyn Write,
) -> traits::Result<()> {
let writer = ColWriter::<_, 72>::new(writer);
let mut writer = DotWriter::new(writer);
let mut previous_char = 97;
let mut shift_balance = 0;
let initialization = Opti::remove_redundant_shifts(
Opti::registers_initialization().as_bytes(),
&mut shift_balance,
);
writer
.write_all(&initialization)
.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 clear = Clear::for_char(c);
let opti = Opti::for_char(c);
if opti.len() < delta.len() {
opti.write_to(&mut output);
} else {
if clear.len() < delta.len() {
clear.write_to(&mut output);
} else {
delta.write_to(&mut output);
}
previous_char = c;
output.push(b'.');
}
}
let output = Opti::remove_redundant_shifts(&output, &mut shift_balance);
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::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()))?;
program.retain(|&instruction| instruction != b'\n');
let mut memory = vec![0u8; 8]; let mut ptr: usize = 0;
let mut instruction = 0;
let mut loop_stack = Vec::new();
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;
match program[instruction] {
b'>' => {
ptr = ptr
.checked_add(1)
.expect("memory allocation will fail first");
if ptr == memory.len() {
memory.extend([0u8; 4096]);
}
}
b'<' => {
ptr = ptr.checked_sub(1).ok_or_else(|| {
Error::Other(format!(
"\
Pointer underflow.
Attempting to shift data pointer below 0: {pos} (<).",
))
})?;
}
b'+' => {
let run_length = instruction_run_length(&program, instruction);
let delta = u8::try_from(run_length % 256).expect("remainder is at most 255");
memory[ptr] = memory[ptr].wrapping_add(delta);
instruction += run_length;
continue;
}
b'-' => {
let run_length = instruction_run_length(&program, instruction);
let delta = u8::try_from(run_length % 256).expect("remainder is at most 255");
memory[ptr] = memory[ptr].wrapping_sub(delta);
instruction += run_length;
continue;
}
b'.' => writer
.write_all(&[memory[ptr]])
.map_err(|e| Error::Write(e.to_string()))?,
b',' => {
memory[ptr] = 0;
}
b'[' if program.get(instruction..instruction + 3) == Some(&b"[-]"[..]) => {
memory[ptr] = 0;
instruction += 3;
continue;
}
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()`");
}
}
_ => (),
}
instruction += 1;
}
Ok(())
}
}
#[cfg(test)]
pub mod tests {
use super::*;
#[derive(Default)]
struct CountingWriter {
bytes: Vec<u8>,
writes: usize,
}
impl Write for CountingWriter {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.bytes.extend_from_slice(buf);
self.writes += 1;
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
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_col_writer_batches_inner_writes() {
let mut inner = CountingWriter::default();
{
let mut writer = ColWriter::<_, 4>::new(&mut inner);
writer.write_all(b"abcdef").unwrap();
}
assert_eq!(inner.bytes, b"abcd\nef");
assert_eq!(inner.writes, 3);
}
#[test]
fn brainfuck_col_writer_terminates_partial_last_line() {
let mut inner = CountingWriter::default();
{
let mut writer = ColWriter::<_, 4>::new(&mut inner);
writer.write_all(b"abcdef").unwrap();
writer.flush().unwrap();
}
assert_eq!(inner.bytes, b"abcd\nef\n");
}
#[test]
fn brainfuck_col_writer_does_not_terminate_full_last_line_twice() {
let mut inner = CountingWriter::default();
{
let mut writer = ColWriter::<_, 4>::new(&mut inner);
writer.write_all(b"abcd").unwrap();
writer.flush().unwrap();
}
assert_eq!(inner.bytes, b"abcd\n");
}
#[test]
fn brainfuck_encrypt_length() {
let plaintext = TEXT.as_bytes();
let encrypted = Brainfuck.encrypt(&[], plaintext).unwrap();
dbg!(String::from_utf8_lossy(&encrypted));
assert_eq!(encrypted.len(), 7702);
}
#[test]
fn brainfuck_encrypt_clears_cell_for_large_downward_delta() {
let encrypted = Brainfuck.encrypt(&[], b"\0").unwrap();
let program: Vec<_> = encrypted
.into_iter()
.filter(|&instruction| instruction != b'\n')
.collect();
assert!(program.ends_with(b"[-]."));
}
#[test]
fn brainfuck_encrypt_cancels_shifts_across_chunks() {
let plaintext = b"e".repeat(4097);
let encrypted = Brainfuck.encrypt(&[], &plaintext).unwrap();
let program: Vec<_> = encrypted
.into_iter()
.filter(|&instruction| instruction != b'\n')
.collect();
assert!(
!program
.windows(2)
.any(|instructions| matches!(instructions, b"<>" | b"><"))
);
}
#[test]
fn brainfuck_round_trip() {
let plaintext = TEXT.as_bytes();
let encrypted = Brainfuck.encrypt(&[], plaintext).unwrap();
dbg!(String::from_utf8_lossy(&encrypted));
let decrypted = Brainfuck.decrypt(&[], &encrypted).unwrap();
dbg!(String::from_utf8_lossy(&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_wraps() {
let ciphertext = b"+++++[>++++++++++<-]>+[<+++++>-]<+.";
let decrypted = Brainfuck.decrypt(&[], ciphertext).unwrap();
assert_eq!(decrypted, b"\0");
}
#[test]
fn brainfuck_decrypt_cell_underflow_wraps() {
let ciphertext = b"-.";
let decrypted = Brainfuck.decrypt(&[], ciphertext).unwrap();
assert_eq!(decrypted, b"\xff");
}
#[test]
fn brainfuck_decrypt_cell_run_wraps() {
let mut ciphertext = b"+".repeat(256);
ciphertext.push(b'.');
let decrypted = Brainfuck.decrypt(&[], &ciphertext).unwrap();
assert_eq!(decrypted, b"\0");
}
#[test]
fn brainfuck_decrypt_input_sets_cell_to_zero() {
let ciphertext = b"+++,+++++++++++++++++++++++++++++++++.";
let decrypted = Brainfuck.decrypt(&[], ciphertext).unwrap();
dbg!(String::from_utf8_lossy(&decrypted));
assert_eq!(decrypted, b"!");
}
#[test]
fn brainfuck_decrypt_clear_cell_loop() {
let ciphertext = b"+++++++++++++++++++++++++++++++++[-]+++++++++++++++++++++++++++++++++.";
let decrypted = Brainfuck.decrypt(&[], ciphertext).unwrap();
assert_eq!(decrypted, b"!");
}
#[test]
fn brainfuck_decrypt_wraps_across_newlines() {
let ciphertext = b"-\n+.";
let decrypted = Brainfuck.decrypt(&[], ciphertext).unwrap();
assert_eq!(decrypted, b"\0");
}
#[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_grows_at_initial_boundary() {
let ciphertext = b">>>>>>>>+.";
let decrypted = Brainfuck.decrypt(&[], ciphertext).unwrap();
assert_eq!(decrypted, b"\x01");
}
#[test]
fn brainfuck_decrypt_memory_length() {
let ciphertext = b"\
++++[>++++++<-]>[>+++++>+++++++<<-]>>++++<[[>[[>>+<<-]<]>>>-]>-[>+>+<<-]>]
+++++[>+++++++<<++>-]>.<<.
";
let decrypted = Brainfuck.decrypt(&[], ciphertext).unwrap();
dbg!(String::from_utf8_lossy(&decrypted));
assert_eq!(decrypted, b"#\n");
}
#[test]
fn brainfuck_decrypt_obscure_problems() {
let ciphertext = br#"[]++++++++++[>>+>+>++++++[<<+<+++>>>-]<<<<-]
"A*$";?@![#>>+<<]>[>>]<<<<[>++<[-]]>.>.
"#;
let decrypted = Brainfuck.decrypt(&[], ciphertext).unwrap();
dbg!(String::from_utf8_lossy(&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()
)
);
}
}