Skip to main content

z85/
lib.rs

1//! Rust implementation of ZeroMQ's Z85 encoding mechanism.
2
3use std::error::Error;
4use std::fmt::{self, Display};
5
6mod internal;
7#[derive(Debug, Copy, Clone, PartialEq, Eq)]
8pub enum DecodeError {
9    InvalidByte(usize, u8),
10    InvalidChunk(usize),
11    InvalidLength(usize),
12    InvalidTail,
13}
14
15impl Display for DecodeError {
16    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
17        match self {
18            DecodeError::InvalidLength(length) => {
19                write!(f, "Z85 data length ({}) is not multiple of five", length)
20            }
21            DecodeError::InvalidByte(position, byte) => write!(
22                f,
23                "Z85 data has an invalid byte (0x{:02X}) at ({}) ",
24                byte, position
25            ),
26            DecodeError::InvalidChunk(position) => write!(
27                f,
28                "Z85 data has an invalid 5-bytes chunk at ({}) ",
29                position
30            ),
31            DecodeError::InvalidTail => write!(f, "Z85 data has an invalid padding chunk"),
32        }
33    }
34}
35
36impl Error for DecodeError {}
37
38/// Errors that can occur while decoding.
39impl DecodeError {
40    fn add_offset(&self, chunk_count: usize) -> Self {
41        use DecodeError::*;
42        let offset = chunk_count * 5;
43        match self {
44            InvalidByte(index, byte) => InvalidByte(index + offset, *byte),
45            InvalidChunk(index) => InvalidChunk(index + offset),
46            _ => *self,
47        }
48    }
49}
50
51/// Encode arbitrary octets as z85. Returns a String.
52pub fn encode<T: AsRef<[u8]>>(input: T) -> String {
53    let input = input.as_ref();
54    let length = input.len();
55    if length == 0 {
56        return String::with_capacity(0);
57    }
58    let tail_size = length % 4;
59    let chunked_size = length - tail_size;
60    let mut out = Vec::with_capacity(length / 4 * 5 + 5);
61    for chunk in input[..chunked_size].chunks(4) {
62        let z85_chunk = internal::encode_chunk(chunk);
63        out.extend_from_slice(&z85_chunk);
64    }
65    if tail_size > 0 {
66        let bintail = &input[chunked_size..];
67        let tail = internal::encode_tail(bintail);
68        out.extend_from_slice(&tail);
69    }
70
71    // all bytes in internal::LETTERS have their most significant
72    // bit set to zero. confirmed with unit test.
73    unsafe { String::from_utf8_unchecked(out) }
74}
75
76/// Decode from string reference as octets. Returns a Result containing a Vec.
77pub fn decode<T: AsRef<[u8]>>(input: T) -> Result<Vec<u8>, DecodeError> {
78    let input = input.as_ref();
79    let length = input.len();
80    if length == 0 {
81        return Ok(Vec::with_capacity(0));
82    }
83    if length % 5 != 0 {
84        return Err(DecodeError::InvalidLength(length));
85    }
86    let has_tail = input[length - 5] == b'#';
87    let chunked_size = if has_tail { length - 5 } else { length };
88    let mut out = Vec::with_capacity(length / 5 * 4);
89    for (chunk_count, chunk) in input[..chunked_size].chunks(5).enumerate() {
90        match internal::decode_chunk(chunk) {
91            Err(decode_error) => return Err(decode_error.add_offset(chunk_count)),
92            Ok(binchunk) => out.extend_from_slice(&binchunk),
93        }
94    }
95    if has_tail {
96        let last_chunk = &input[chunked_size..];
97        match internal::decode_tail(last_chunk) {
98            Err(decode_error) => return Err(decode_error.add_offset(length - 5)),
99            Ok(bintail) => bintail.append_to_vec(&mut out),
100        }
101    }
102    Ok(out)
103}