base94/lib.rs
1//! # Base94 Encoding Library
2//!
3//! This crate provides functions for encoding and decoding data using Base94 encoding.
4//! Base94 encoding is a method of converting binary data into a text-based format using a larger
5//! character set than Base64. The encoding and decoding functions in this crate allow you to
6//! convert data between its original binary form and a Base94-encoded string representation.
7//!
8//! ## Usage
9//!
10//! To use this library, you can include it as a dependency in your `Cargo.toml`:
11//!
12//! ```toml
13//! [dependencies]
14//! base94 = {current_version}
15//! ```
16//!
17//! Then, you can use the provided functions in your Rust code:
18//!
19//! ```rust
20//! use base94::{encode, decode};
21//!
22//! let data = b"Hello, World!";
23//! let base = 94;
24//! let encoded = encode(data, base);
25//! let decoded = decode(&encoded, base).unwrap();
26//!
27//! assert_eq!(decoded, data);
28//! ```
29//!
30//! ## Supported Bases
31//!
32//! The encoding and decoding functions support various bases within the range of 2 to 94.
33//! The specified base must be consistent between encoding and decoding operations.
34//!
35//! ## Examples
36//!
37//! ```
38//! use base94::{encode, decode};
39//!
40//! let data = b"Example data for encoding.";
41//! let base = 50;
42//!
43//! let encoded = encode(data, base);
44//! let decoded = decode(&encoded, base).unwrap();
45//!
46//! assert_eq!(decoded, data);
47//! ```
48//!
49
50use num::BigUint;
51use num::Integer;
52use num::ToPrimitive;
53use thiserror::Error;
54
55pub static CHARACTERS: &[u8; 94] = include_bytes!("characters.txt");
56
57/// Encodes a slice of bytes into a Base94-encoded string using the specified base.
58///
59/// Base94 encoding is a method of converting binary data into a text-based format using
60/// a larger character set than Base64. The provided base specifies the number of unique
61/// characters used in the encoding, ranging from 2 to 94. The encoded string can later be
62/// decoded back to the original data using the `decode` function.
63///
64/// # Arguments
65///
66/// * `data` - A slice of bytes representing the data to be encoded.
67/// * `base` - The base used for encoding. Must be between 2 and 94 (inclusive).
68///
69/// # Panics
70///
71/// This function panics if the specified base is outside the valid range (2 to 94).
72///
73/// # Returns
74///
75/// A Base94-encoded string representation of the input data.
76///
77/// # Examples
78///
79/// ```
80/// use base94::encode;
81///
82/// let data = b"Hello, World!";
83/// let base = 94;
84/// let encoded = encode(data, base);
85/// println!("Encoded: {}", encoded);
86/// ```
87pub fn encode(data: &[u8], base: u8) -> String {
88 assert!(base <= 94, "Base must be less than or equal to 94");
89 assert!(base >= 2, "Base must be greater than or equal to 2");
90
91 let mut num = BigUint::from_bytes_le(data);
92 let mut out = String::new();
93
94 while num > BigUint::from(0u8) {
95 let (div, rem) = num.div_rem(&BigUint::from(base));
96 num = div;
97 out.push(CHARACTERS[rem.to_usize().unwrap()] as char);
98 }
99
100 out
101}
102
103#[derive(Error, Debug)]
104pub enum DecodeError {
105 #[error("Invalid character '{c}' at position {position}")]
106 InvalidCharacter { c: u8, position: usize },
107}
108
109/// Decodes a Base94-encoded string back to its original byte representation using the specified base.
110///
111/// Base94 encoding is a method of converting binary data into a text-based format using
112/// a larger character set than Base64. The provided base specifies the number of unique
113/// characters used in the encoding, ranging from 2 to 94. The encoded string should have
114/// been generated using the `encode` function with the same base.
115///
116/// # Arguments
117///
118/// * `encoded` - A Base94-encoded string to be decoded.
119/// * `base` - The base used for decoding. Must match the base used for encoding.
120///
121/// # Returns
122///
123/// An optional vector of bytes representing the decoded original data. Returns `None`
124/// if the decoding process encounters invalid characters.
125///
126/// # Examples
127///
128/// ```
129/// use base94::decode;
130///
131/// let encoded = "A@#D9e@D9n9RRb6^";
132/// let base = 94;
133/// let decoded = decode(encoded, base).unwrap();
134/// println!("Decoded: {:?}", decoded);
135/// ```
136pub fn decode(encoded: &str, base: u8) -> Result<Vec<u8>, DecodeError> {
137 let mut num = BigUint::from(0u8);
138 let mut power = BigUint::from(1u8);
139
140 for (i, c) in encoded.chars().enumerate() {
141 let index =
142 CHARACTERS
143 .iter()
144 .position(|&x| x == c as u8)
145 .ok_or(DecodeError::InvalidCharacter {
146 c: c as u8,
147 position: i,
148 })?;
149 num += BigUint::from(index) * &power;
150 power *= BigUint::from(base);
151 }
152
153 let out = num.to_bytes_le();
154 Ok(out)
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 const MAX_BASE: u8 = 94;
162
163 #[test]
164 fn test_encode_decode_empty() {
165 for base in 2..=MAX_BASE {
166 let data = [0];
167 let encoded = encode(&data, base);
168 let decoded = decode(&encoded, base).unwrap();
169 assert_eq!(decoded, data);
170 }
171 }
172
173 #[test]
174 fn test_encode_decode_single_byte() {
175 for base in 2..=MAX_BASE {
176 let data = [65]; // ASCII value of 'A'
177 let encoded = encode(&data, base);
178 let decoded = decode(&encoded, base).unwrap();
179 assert_eq!(decoded, data);
180 }
181 }
182
183 #[test]
184 fn test_encode_decode_hello_world() {
185 for base in 2..=MAX_BASE {
186 let data = b"Hello, World!";
187 let encoded = encode(data, base);
188 let decoded = decode(&encoded, base).unwrap();
189 assert_eq!(decoded, data);
190 }
191 }
192
193 #[test]
194 fn test_encode_decode_max_value() {
195 for base in 2..=MAX_BASE {
196 let data = [255, 255, 255]; // Three bytes of value 255
197 let encoded = encode(&data, base);
198 let decoded = decode(&encoded, base).unwrap();
199 assert_eq!(decoded, data);
200 }
201 }
202
203 #[test]
204 fn test_encode_decode_large_data() {
205 for base in 2..=MAX_BASE {
206 let data = vec![42; 1000]; // 1000 bytes of value 42 ('*')
207 let encoded = encode(&data, base);
208 let decoded = decode(&encoded, base).unwrap();
209 assert_eq!(decoded, data);
210 }
211 }
212}