fn mix(seed: u64, n: u64) -> u64 {
let mut z = seed
.wrapping_add(n.wrapping_mul(0x9E37_79B9_7F4A_7C15))
.wrapping_add(0x1234_5678);
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
const RATE: u64 = 40;
pub(crate) fn corrupt(source: &str, seed: u64) -> String {
let chars: Vec<char> = source.chars().collect();
let mut out = String::with_capacity(source.len() + 8);
let mut i = 0;
while i < chars.len() {
let c = chars[i];
if c.is_alphabetic() && mix(seed, i as u64) % RATE == 0 {
match mix(seed, (i as u64) ^ 0x5bd1_e995) % 4 {
0 if i + 1 < chars.len() && chars[i + 1].is_alphabetic() => {
out.push(chars[i + 1]);
out.push(c);
i += 2;
continue;
}
1 => {}
2 => {
out.push(c);
out.push(c);
}
_ => out.push(misread(c, seed, i)),
}
} else {
out.push(c);
}
i += 1;
}
out
}
fn misread(c: char, seed: u64, i: usize) -> char {
let shift = (mix(seed, i as u64 + 7) % 5 + 1) as u8;
if c.is_ascii_lowercase() {
(b'a' + (c as u8 - b'a' + shift) % 26) as char
} else if c.is_ascii_uppercase() {
(b'A' + (c as u8 - b'A' + shift) % 26) as char
} else {
c }
}
#[cfg(test)]
mod tests {
use super::*;
const TEXT: &str =
"fn main() {\n let greeting = \"hello world\";\n println!(\"{greeting}\");\n}\n";
#[test]
fn corruption_is_deterministic() {
assert_eq!(corrupt(TEXT, 7), corrupt(TEXT, 7));
assert_ne!(corrupt(TEXT, 1), corrupt(TEXT, 2));
}
#[test]
fn corruption_actually_changes_the_text() {
assert_ne!(corrupt(TEXT, 3), TEXT);
}
#[test]
fn structure_survives_letters_break() {
let out = corrupt(TEXT, 5);
assert_eq!(out.matches('\n').count(), TEXT.matches('\n').count());
for ch in ['{', '}', '(', ')', ';', '"'] {
assert_eq!(
out.matches(ch).count(),
TEXT.matches(ch).count(),
"punctuation {ch:?} should be untouched"
);
}
}
}