use std::cmp::Ordering;
use std::io::{Read, Write};
use crate::pipeline::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 new() -> Self
where
Self: Sized,
{
Self
}
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(())
}
fn decrypt_stream(&self, _: &[u8], _: &mut dyn Read, _: &mut dyn Write) -> traits::Result<()> {
eprintln!("Brainfuck interpreting is not supported.");
std::process::exit(1);
}
}
#[cfg(test)]
pub mod tests {
use super::*;
#[test]
fn brainfuck_encrypt_length() {
let plaintext = 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.)
"#
.as_bytes();
let encrypted = Brainfuck::new().encrypt(&[], plaintext).unwrap();
dbg!(&encrypted);
assert_eq!(encrypted.len(), 7706 - 1);
}
}