feat: add encodings to boxutils, no dependencies! :D

This commit is contained in:
Teesh 2025-03-27 12:41:02 +02:00
parent d8bc6b620e
commit 60956e3831
5 changed files with 222 additions and 0 deletions

View file

@ -0,0 +1,52 @@
const BASE16_ALPHABET: &[u8] = b"0123456789ABCDEF";
fn encode(to_encode: String) -> String {
let bytes = to_encode.as_bytes();
let mut encoded = String::new();
for &byte in bytes {
encoded.push(BASE16_ALPHABET[(byte >> 4) as usize] as char);
encoded.push(BASE16_ALPHABET[(byte & 0x0F) as usize] as char);
}
encoded
}
fn decode(to_decode: String) -> String {
let bytes = to_decode.as_bytes();
let mut decoded = Vec::new();
for chunk in bytes.chunks(2) {
let high = BASE16_ALPHABET.iter().position(|&c| c == chunk[0]).unwrap() as u8;
let low = BASE16_ALPHABET.iter().position(|&c| c == chunk[1]).unwrap() as u8;
decoded.push((high << 4) | low);
}
String::from_utf8(decoded).unwrap()
}
mod tests {
use super::*;
#[test]
fn test_encode() {
let encoded = encode("Hello, world!".to_string());
assert_eq!(encoded, "48656C6C6F2C20776F726C6421");
}
#[test]
fn test_decode() {
let decoded = decode("48656C6C6F2C20776F726C6421".into());
assert_eq!(decoded, "Hello, world!");
}
#[test]
fn test_using_rfc() {
assert_eq!(encode("".into()), "");
assert_eq!(encode("f".into()), "66");
assert_eq!(encode("fo".into()), "666F");
assert_eq!(encode("foo".into()), "666F6F");
assert_eq!(encode("foob".into()), "666F6F62");
assert_eq!(encode("fooba".into()), "666F6F6261");
assert_eq!(encode("foobar".into()), "666F6F626172");
}
}

View file

@ -0,0 +1,75 @@
const BASE32_ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
fn encode(to_encode: String) -> String {
let bytes = to_encode.as_bytes();
let mut encoded = String::new();
let mut bits = 0u32;
let mut bit_count = 0;
for &byte in bytes {
bits = (bits << 8) | byte as u32;
bit_count += 8;
while bit_count >= 5 {
let index = (bits >> (bit_count - 5)) & 0b11111;
encoded.push(BASE32_ALPHABET[index as usize] as char);
bit_count -= 5;
}
}
if bit_count > 0 {
let index = (bits << (5 - bit_count)) & 0b11111;
encoded.push(BASE32_ALPHABET[index as usize] as char);
}
while encoded.len() % 8 != 0 {
encoded.push('=');
}
encoded
}
fn decode(to_decode: String) -> String {
let bytes = to_decode.as_bytes();
let mut decoded = Vec::new();
let mut bits = 0u32;
let mut bit_count = 0;
for &b in bytes.iter().filter(|&&b| b != b'=') {
if let Some(pos) = BASE32_ALPHABET.iter().position(|&c| c == b) {
bits = (bits << 5) | pos as u32;
bit_count += 5;
if bit_count >= 8 {
decoded.push((bits >> (bit_count - 8)) as u8);
bit_count -= 8;
}
}
}
String::from_utf8(decoded).unwrap()
}
mod tests {
use super::*;
#[test]
fn test_encode() {
let encoded = encode("Hello, world!".to_string());
assert_eq!(encoded, "JBSWY3DPFQQHO33SNRSCC===");
}
#[test]
fn test_decode() {
let decoded = decode("JBSWY3DPFQQHO33SNRSCC===".into());
assert_eq!(decoded, "Hello, world!");
}
#[test]
fn test_using_rfc() {
assert_eq!(encode("".into()), "");
assert_eq!(encode("f".into()), "MY======");
assert_eq!(encode("fo".into()), "MZXQ====");
assert_eq!(encode("foo".into()), "MZXW6===");
assert_eq!(encode("foob".into()), "MZXW6YQ=");
assert_eq!(encode("fooba".into()), "MZXW6YTB");
assert_eq!(encode("foobar".into()), "MZXW6YTBOI======");
}
}

View file

@ -0,0 +1,91 @@
const BASE64_ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
fn encode(to_encode: String) -> String {
let bytes = to_encode.as_bytes();
let mut encoded = String::new();
let mut chunks = bytes.chunks_exact(3);
for chunk in &mut chunks {
let b1 = chunk[0] as u32;
let b2 = chunk[1] as u32;
let b3 = chunk[2] as u32;
encoded.push(BASE64_ALPHABET[(b1 >> 2) as usize] as char);
encoded.push(BASE64_ALPHABET[((b1 & 0b11) << 4 | (b2 >> 4)) as usize] as char);
encoded.push(BASE64_ALPHABET[((b2 & 0b1111) << 2 | (b3 >> 6)) as usize] as char);
encoded.push(BASE64_ALPHABET[(b3 & 0b111111) as usize] as char);
}
let remainder = chunks.remainder();
if !remainder.is_empty() {
let b1 = remainder[0] as u32;
encoded.push(BASE64_ALPHABET[(b1 >> 2) as usize] as char);
if remainder.len() == 2 {
let b2 = remainder[1] as u32;
encoded.push(BASE64_ALPHABET[((b1 & 0b11) << 4 | (b2 >> 4)) as usize] as char);
encoded.push(BASE64_ALPHABET[((b2 & 0b1111) << 2) as usize] as char);
encoded.push('=');
} else {
encoded.push(BASE64_ALPHABET[((b1 & 0b11) << 4) as usize] as char);
encoded.push_str("==");
}
}
encoded
}
fn decode(to_decode: String) -> String {
let bytes = to_decode.as_bytes();
let mut decoded = Vec::new();
let mut buffer = [0u8; 4];
let mut index = 0;
for &b in bytes.iter().filter(|&&b| b != b'=') {
if let Some(pos) = BASE64_ALPHABET.iter().position(|&c| c == b) {
buffer[index] = pos as u8;
index += 1;
if index == 4 {
decoded.push(buffer[0] << 2 | buffer[1] >> 4);
decoded.push(buffer[1] << 4 | buffer[2] >> 2);
decoded.push(buffer[2] << 6 | buffer[3]);
index = 0;
}
}
}
if index > 1 {
decoded.push(buffer[0] << 2 | buffer[1] >> 4);
}
if index > 2 {
decoded.push(buffer[1] << 4 | buffer[2] >> 2);
}
String::from_utf8(decoded).unwrap()
}
mod tests {
use super::*;
#[test]
fn test_encode() {
let encoded = encode("Hello, world!".to_string());
assert_eq!(encoded, "SGVsbG8sIHdvcmxkIQ==");
}
#[test]
fn test_decode() {
let decoded = decode("SGVsbG8sIHdvcmxkIQ==".into());
assert_eq!(decoded, "Hello, world!");
}
#[test]
fn test_using_rfc() {
assert_eq!(encode("".into()), "");
assert_eq!(encode("f".into()), "Zg==");
assert_eq!(encode("fo".into()), "Zm8=");
assert_eq!(encode("foo".into()), "Zm9v");
assert_eq!(encode("foob".into()), "Zm9vYg==");
assert_eq!(encode("fooba".into()), "Zm9vYmE=");
assert_eq!(encode("foobar".into()), "Zm9vYmFy");
}
}

View file

@ -0,0 +1,3 @@
pub mod base64;
pub mod base32;
pub mod base16;

View file

@ -1,3 +1,4 @@
pub mod args; pub mod args;
pub mod commands; pub mod commands;
pub mod registry; pub mod registry;
pub mod encoding;