1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2026-01-20 12:11:08 +00:00

Fix two bugs. Add property testing.

This commit is contained in:
Andrew Liebenow 2024-09-24 19:25:29 -05:00
parent 4f19c0e9ff
commit ea7a543bfe
7 changed files with 608 additions and 128 deletions

View file

@ -20,6 +20,9 @@ path = "src/base32.rs"
clap = { workspace = true }
uucore = { workspace = true, features = ["encoding"] }
[dev-dependencies]
proptest = "1.5.0"
[[bin]]
name = "base32"
path = "src/main.rs"

View file

@ -7,7 +7,7 @@
use clap::{crate_version, Arg, ArgAction, Command};
use std::fs::File;
use std::io::{ErrorKind, Read, Stdin};
use std::io::{self, ErrorKind, Read, Stdin};
use std::path::Path;
use uucore::display::Quotable;
use uucore::encoding::{
@ -25,7 +25,7 @@ pub const BASE_CMD_PARSE_ERROR: i32 = 1;
/// Other implementations default to 76
///
/// This default is only used if no "-w"/"--wrap" argument is passed
const WRAP_DEFAULT: usize = 76;
pub const WRAP_DEFAULT: usize = 76;
pub struct Config {
pub decode: bool,
@ -158,6 +158,28 @@ pub fn handle_input<R: Read>(
ignore_garbage: bool,
decode: bool,
) -> UResult<()> {
let supports_fast_decode_and_encode = get_supports_fast_decode_and_encode(format);
let mut stdout_lock = io::stdout().lock();
if decode {
fast_decode::fast_decode(
input,
&mut stdout_lock,
supports_fast_decode_and_encode.as_ref(),
ignore_garbage,
)
} else {
fast_encode::fast_encode(
input,
&mut stdout_lock,
supports_fast_decode_and_encode.as_ref(),
wrap,
)
}
}
pub fn get_supports_fast_decode_and_encode(format: Format) -> Box<dyn SupportsFastDecodeAndEncode> {
const BASE16_VALID_DECODING_MULTIPLE: usize = 2;
const BASE2_VALID_DECODING_MULTIPLE: usize = 8;
const BASE32_VALID_DECODING_MULTIPLE: usize = 8;
@ -168,7 +190,7 @@ pub fn handle_input<R: Read>(
const BASE32_UNPADDED_MULTIPLE: usize = 5;
const BASE64_UNPADDED_MULTIPLE: usize = 3;
let supports_fast_decode_and_encode: Box<dyn SupportsFastDecodeAndEncode> = match format {
match format {
Format::Base16 => Box::from(EncodingWrapper::new(
HEXUPPER,
BASE16_VALID_DECODING_MULTIPLE,
@ -219,26 +241,14 @@ pub fn handle_input<R: Read>(
b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789=_-",
)),
Format::Z85 => Box::from(Z85Wrapper {}),
};
if decode {
fast_decode::fast_decode(
input,
supports_fast_decode_and_encode.as_ref(),
ignore_garbage,
)?;
} else {
fast_encode::fast_encode(input, supports_fast_decode_and_encode.as_ref(), wrap)?;
}
Ok(())
}
mod fast_encode {
pub mod fast_encode {
use crate::base_common::{format_read_error, WRAP_DEFAULT};
use std::{
collections::VecDeque,
io::{self, ErrorKind, Read, StdoutLock, Write},
io::{self, ErrorKind, Read, Write},
num::NonZeroUsize,
};
use uucore::{
@ -299,17 +309,17 @@ mod fast_encode {
fn write_without_line_breaks(
encoded_buffer: &mut VecDeque<u8>,
stdout_lock: &mut StdoutLock,
output: &mut dyn Write,
is_cleanup: bool,
) -> io::Result<()> {
// TODO
// `encoded_buffer` only has to be a VecDeque if line wrapping is enabled
// (`make_contiguous` should be a no-op here)
// Refactoring could avoid this call
stdout_lock.write_all(encoded_buffer.make_contiguous())?;
output.write_all(encoded_buffer.make_contiguous())?;
if is_cleanup {
stdout_lock.write_all(b"\n")?;
output.write_all(b"\n")?;
} else {
encoded_buffer.clear();
}
@ -323,7 +333,7 @@ mod fast_encode {
ref mut print_buffer,
}: &mut LineWrapping,
encoded_buffer: &mut VecDeque<u8>,
stdout_lock: &mut StdoutLock,
output: &mut dyn Write,
is_cleanup: bool,
) -> io::Result<()> {
let line_length = line_length.get();
@ -341,7 +351,7 @@ mod fast_encode {
print_buffer.push(b'\n');
}
stdout_lock.write_all(print_buffer)?;
output.write_all(print_buffer)?;
// Remove the bytes that were just printed from `encoded_buffer`
drop(encoded_buffer.drain(..bytes_added_to_print_buffer));
@ -351,8 +361,8 @@ mod fast_encode {
// Do not write a newline in this case, because two trailing newlines should never be printed
} else {
// Print the partial line, since this is cleanup and no more data is coming
stdout_lock.write_all(encoded_buffer.make_contiguous())?;
stdout_lock.write_all(b"\n")?;
output.write_all(encoded_buffer.make_contiguous())?;
output.write_all(b"\n")?;
}
} else {
print_buffer.clear();
@ -361,27 +371,28 @@ mod fast_encode {
Ok(())
}
fn write_to_stdout(
fn write_to_output(
line_wrapping_option: &mut Option<LineWrapping>,
encoded_buffer: &mut VecDeque<u8>,
stdout_lock: &mut StdoutLock,
output: &mut dyn Write,
is_cleanup: bool,
) -> io::Result<()> {
// Write all data in `encoded_buffer` to stdout
// Write all data in `encoded_buffer` to `output`
if let &mut Some(ref mut li) = line_wrapping_option {
write_with_line_breaks(li, encoded_buffer, stdout_lock, is_cleanup)?;
write_with_line_breaks(li, encoded_buffer, output, is_cleanup)?;
} else {
write_without_line_breaks(encoded_buffer, stdout_lock, is_cleanup)?;
write_without_line_breaks(encoded_buffer, output, is_cleanup)?;
}
Ok(())
}
// End of helper functions
pub fn fast_encode<R: Read>(
pub fn fast_encode<R: Read, W: Write>(
input: &mut R,
mut output: W,
supports_fast_decode_and_encode: &dyn SupportsFastDecodeAndEncode,
line_wrap: Option<usize>,
wrap: Option<usize>,
) -> UResult<()> {
// Based on performance testing
const INPUT_BUFFER_SIZE: usize = 32 * 1_024;
@ -393,10 +404,10 @@ mod fast_encode {
assert!(encode_in_chunks_of_size > 0);
// "data-encoding" supports line wrapping, but not arbitrary line wrapping, only certain widths, so line
// wrapping must be implemented here.
// The "data-encoding" crate supports line wrapping, but not arbitrary line wrapping, only certain widths, so
// line wrapping must be handled here.
// https://github.com/ia0/data-encoding/blob/4f42ad7ef242f6d243e4de90cd1b46a57690d00e/lib/src/lib.rs#L1710
let mut line_wrapping_option = match line_wrap {
let mut line_wrapping = match wrap {
// Line wrapping is disabled because "-w"/"--wrap" was passed with "0"
Some(0) => None,
// A custom line wrapping value was passed
@ -420,12 +431,10 @@ mod fast_encode {
// Data that was read from stdin but has not been encoded yet
let mut leftover_buffer = VecDeque::<u8>::new();
// Encoded data that needs to be written to stdout
// Encoded data that needs to be written to output
let mut encoded_buffer = VecDeque::<u8>::new();
// End of buffers
let mut stdout_lock = io::stdout().lock();
loop {
match input.read(&mut input_buffer) {
Ok(bytes_read_from_input) => {
@ -443,6 +452,8 @@ mod fast_encode {
// Do not have enough data to encode a chunk, so copy data to `leftover_buffer` and read more
leftover_buffer.extend(read_buffer);
assert!(leftover_buffer.len() < encode_in_chunks_of_size);
continue;
}
@ -456,13 +467,10 @@ mod fast_encode {
&mut leftover_buffer,
)?;
// Write all data in `encoded_buffer` to stdout
write_to_stdout(
&mut line_wrapping_option,
&mut encoded_buffer,
&mut stdout_lock,
false,
)?;
assert!(leftover_buffer.len() < encode_in_chunks_of_size);
// Write all data in `encoded_buffer` to output
write_to_output(&mut line_wrapping, &mut encoded_buffer, &mut output, false)?;
}
Err(er) => {
let kind = er.kind();
@ -484,23 +492,18 @@ mod fast_encode {
supports_fast_decode_and_encode
.encode_to_vec_deque(leftover_buffer.make_contiguous(), &mut encoded_buffer)?;
// Write all data in `encoded_buffer` to stdout
// Write all data in `encoded_buffer` to output
// `is_cleanup` triggers special cleanup-only logic
write_to_stdout(
&mut line_wrapping_option,
&mut encoded_buffer,
&mut stdout_lock,
true,
)?;
write_to_output(&mut line_wrapping, &mut encoded_buffer, &mut output, true)?;
}
Ok(())
}
}
mod fast_decode {
pub mod fast_decode {
use crate::base_common::format_read_error;
use std::io::{self, ErrorKind, Read, StdoutLock, Write};
use std::io::{self, ErrorKind, Read, Write};
use uucore::{
encoding::SupportsFastDecodeAndEncode,
error::{UResult, USimpleError},
@ -588,12 +591,9 @@ mod fast_decode {
Ok(())
}
fn write_to_stdout(
decoded_buffer: &mut Vec<u8>,
stdout_lock: &mut StdoutLock,
) -> io::Result<()> {
// Write all data in `decoded_buffer` to stdout
stdout_lock.write_all(decoded_buffer.as_slice())?;
fn write_to_output(decoded_buffer: &mut Vec<u8>, output: &mut dyn Write) -> io::Result<()> {
// Write all data in `decoded_buffer` to `output`
output.write_all(decoded_buffer.as_slice())?;
decoded_buffer.clear();
@ -601,10 +601,9 @@ mod fast_decode {
}
// End of helper functions
/// `encoding`, `decode_in_chunks_of_size`, and `alphabet` are passed in a tuple to indicate that they are
/// logically tied
pub fn fast_decode<R: Read>(
pub fn fast_decode<R: Read, W: Write>(
input: &mut R,
mut output: &mut W,
supports_fast_decode_and_encode: &dyn SupportsFastDecodeAndEncode,
ignore_garbage: bool,
) -> UResult<()> {
@ -640,7 +639,7 @@ mod fast_decode {
// Data that was read from stdin but has not been decoded yet
let mut leftover_buffer = Vec::<u8>::new();
// Decoded data that needs to be written to stdout
// Decoded data that needs to be written to `output`
let mut decoded_buffer = Vec::<u8>::new();
// Buffer that will be used when "ignore_garbage" is true, and the chunk read from "input" contains garbage
@ -648,8 +647,6 @@ mod fast_decode {
let mut non_garbage_buffer = Vec::<u8>::new();
// End of buffers
let mut stdout_lock = io::stdout().lock();
loop {
match input.read(&mut input_buffer) {
Ok(bytes_read_from_input) => {
@ -687,10 +684,12 @@ mod fast_decode {
// How many bytes to steal from `read_buffer` to get `leftover_buffer` to the right size
let bytes_to_steal = decode_in_chunks_of_size - leftover_buffer.len();
if bytes_to_steal > bytes_read_from_input {
if bytes_to_steal > read_buffer_filtered.len() {
// Do not have enough data to decode a chunk, so copy data to `leftover_buffer` and read more
leftover_buffer.extend(read_buffer_filtered);
assert!(leftover_buffer.len() < decode_in_chunks_of_size);
continue;
}
@ -700,12 +699,14 @@ mod fast_decode {
decode_in_chunks_of_size,
bytes_to_steal,
read_buffer_filtered,
&mut leftover_buffer,
&mut decoded_buffer,
&mut leftover_buffer,
)?;
// Write all data in `decoded_buffer` to stdout
write_to_stdout(&mut decoded_buffer, &mut stdout_lock)?;
assert!(leftover_buffer.len() < decode_in_chunks_of_size);
// Write all data in `decoded_buffer` to `output`
write_to_output(&mut decoded_buffer, &mut output)?;
}
Err(er) => {
let kind = er.kind();
@ -727,8 +728,8 @@ mod fast_decode {
supports_fast_decode_and_encode
.decode_into_vec(&leftover_buffer, &mut decoded_buffer)?;
// Write all data in `decoded_buffer` to stdout
write_to_stdout(&mut decoded_buffer, &mut stdout_lock)?;
// Write all data in `decoded_buffer` to `output`
write_to_output(&mut decoded_buffer, &mut output)?;
}
Ok(())
@ -739,7 +740,7 @@ fn format_read_error(kind: ErrorKind) -> String {
let kind_string = kind.to_string();
// e.g. "is a directory" -> "Is a directory"
let kind_string_uncapitalized = kind_string
let kind_string_capitalized = kind_string
.char_indices()
.map(|(index, character)| {
if index == 0 {
@ -750,5 +751,5 @@ fn format_read_error(kind: ErrorKind) -> String {
})
.collect::<String>();
format!("read error: {kind_string_uncapitalized}")
format!("read error: {kind_string_capitalized}")
}

View file

@ -0,0 +1,430 @@
// spell-checker:ignore lsbf msbf proptest
use proptest::{prelude::TestCaseError, prop_assert, prop_assert_eq, test_runner::TestRunner};
use std::io::Cursor;
use uu_base32::base_common::{fast_decode, fast_encode, get_supports_fast_decode_and_encode};
use uucore::encoding::{Format, SupportsFastDecodeAndEncode};
const CASES: u32 = {
#[cfg(debug_assertions)]
{
32
}
#[cfg(not(debug_assertions))]
{
128
}
};
const NORMAL_INPUT_SIZE_LIMIT: usize = {
#[cfg(debug_assertions)]
{
// 256 kibibytes
256 * 1024
}
#[cfg(not(debug_assertions))]
{
// 4 mebibytes
4 * 1024 * 1024
}
};
const LARGE_INPUT_SIZE_LIMIT: usize = 4 * NORMAL_INPUT_SIZE_LIMIT;
// Note that `TestRunner`s cannot be reused
fn get_test_runner() -> TestRunner {
TestRunner::new(proptest::test_runner::Config {
cases: CASES,
failure_persistence: None,
..proptest::test_runner::Config::default()
})
}
fn generic_round_trip(format: Format) {
let supports_fast_decode_and_encode = get_supports_fast_decode_and_encode(format);
let supports_fast_decode_and_encode_ref = supports_fast_decode_and_encode.as_ref();
// Make sure empty inputs round trip
{
get_test_runner()
.run(
&(
proptest::bool::ANY,
proptest::bool::ANY,
proptest::option::of(0_usize..512_usize),
),
|(ignore_garbage, line_wrap_zero, line_wrap)| {
configurable_round_trip(
format,
supports_fast_decode_and_encode_ref,
ignore_garbage,
line_wrap_zero,
line_wrap,
// Do not add garbage
Vec::<(usize, u8)>::new(),
// Empty input
Vec::<u8>::new(),
)
},
)
.unwrap();
}
// Unusually large line wrapping settings
{
get_test_runner()
.run(
&(
proptest::bool::ANY,
proptest::bool::ANY,
proptest::option::of(512_usize..65_535_usize),
proptest::collection::vec(proptest::num::u8::ANY, 0..NORMAL_INPUT_SIZE_LIMIT),
),
|(ignore_garbage, line_wrap_zero, line_wrap, input)| {
configurable_round_trip(
format,
supports_fast_decode_and_encode_ref,
ignore_garbage,
line_wrap_zero,
line_wrap,
// Do not add garbage
Vec::<(usize, u8)>::new(),
input,
)
},
)
.unwrap();
}
// Spend more time on sane line wrapping settings
{
get_test_runner()
.run(
&(
proptest::bool::ANY,
proptest::bool::ANY,
proptest::option::of(0_usize..512_usize),
proptest::collection::vec(proptest::num::u8::ANY, 0..NORMAL_INPUT_SIZE_LIMIT),
),
|(ignore_garbage, line_wrap_zero, line_wrap, input)| {
configurable_round_trip(
format,
supports_fast_decode_and_encode_ref,
ignore_garbage,
line_wrap_zero,
line_wrap,
// Do not add garbage
Vec::<(usize, u8)>::new(),
input,
)
},
)
.unwrap();
}
// Test with garbage data
{
get_test_runner()
.run(
&(
proptest::bool::ANY,
proptest::bool::ANY,
proptest::option::of(0_usize..512_usize),
// Garbage data to insert
proptest::collection::vec(
(
// Random index
proptest::num::usize::ANY,
// In all of the encodings being tested, non-ASCII bytes are garbage
128_u8..=u8::MAX,
),
0..4_096,
),
proptest::collection::vec(proptest::num::u8::ANY, 0..NORMAL_INPUT_SIZE_LIMIT),
),
|(ignore_garbage, line_wrap_zero, line_wrap, garbage_data, input)| {
configurable_round_trip(
format,
supports_fast_decode_and_encode_ref,
ignore_garbage,
line_wrap_zero,
line_wrap,
garbage_data,
input,
)
},
)
.unwrap();
}
// Test small inputs
{
get_test_runner()
.run(
&(
proptest::bool::ANY,
proptest::bool::ANY,
proptest::option::of(0_usize..512_usize),
proptest::collection::vec(proptest::num::u8::ANY, 0..1_024),
),
|(ignore_garbage, line_wrap_zero, line_wrap, input)| {
configurable_round_trip(
format,
supports_fast_decode_and_encode_ref,
ignore_garbage,
line_wrap_zero,
line_wrap,
// Do not add garbage
Vec::<(usize, u8)>::new(),
input,
)
},
)
.unwrap();
}
// Test small inputs with garbage data
{
get_test_runner()
.run(
&(
proptest::bool::ANY,
proptest::bool::ANY,
proptest::option::of(0_usize..512_usize),
// Garbage data to insert
proptest::collection::vec(
(
// Random index
proptest::num::usize::ANY,
// In all of the encodings being tested, non-ASCII bytes are garbage
128_u8..=u8::MAX,
),
0..1_024,
),
proptest::collection::vec(proptest::num::u8::ANY, 0..1_024),
),
|(ignore_garbage, line_wrap_zero, line_wrap, garbage_data, input)| {
configurable_round_trip(
format,
supports_fast_decode_and_encode_ref,
ignore_garbage,
line_wrap_zero,
line_wrap,
garbage_data,
input,
)
},
)
.unwrap();
}
// Test large inputs
{
get_test_runner()
.run(
&(
proptest::bool::ANY,
proptest::bool::ANY,
proptest::option::of(0_usize..512_usize),
proptest::collection::vec(proptest::num::u8::ANY, 0..LARGE_INPUT_SIZE_LIMIT),
),
|(ignore_garbage, line_wrap_zero, line_wrap, input)| {
configurable_round_trip(
format,
supports_fast_decode_and_encode_ref,
ignore_garbage,
line_wrap_zero,
line_wrap,
// Do not add garbage
Vec::<(usize, u8)>::new(),
input,
)
},
)
.unwrap();
}
}
fn configurable_round_trip(
format: Format,
supports_fast_decode_and_encode: &dyn SupportsFastDecodeAndEncode,
ignore_garbage: bool,
line_wrap_zero: bool,
line_wrap: Option<usize>,
garbage_data: Vec<(usize, u8)>,
mut input: Vec<u8>,
) -> Result<(), TestCaseError> {
// Z85 only accepts inputs with lengths divisible by 4
if let Format::Z85 = format {
// Reduce length of "input" until it is divisible by 4
input.truncate((input.len() / 4) * 4);
assert!((input.len() % 4) == 0);
}
let line_wrap_to_use = if line_wrap_zero { Some(0) } else { line_wrap };
let input_len = input.len();
let garbage_data_len = garbage_data.len();
let garbage_data_is_empty = garbage_data_len == 0;
let (input, encoded) = {
let mut output = Vec::with_capacity(input_len * 8);
let mut cursor = Cursor::new(input);
fast_encode::fast_encode(
&mut cursor,
&mut output,
supports_fast_decode_and_encode,
line_wrap_to_use,
)
.unwrap();
(cursor.into_inner(), output)
};
let encoded_or_encoded_with_garbage = if garbage_data_is_empty {
encoded
} else {
let encoded_len = encoded.len();
let encoded_highest_index = match encoded_len.checked_sub(1) {
Some(0) | None => None,
Some(x) => Some(x),
};
let mut garbage_data_indexed = vec![Option::<u8>::None; encoded_len];
let mut encoded_with_garbage = Vec::<u8>::with_capacity(encoded_len + garbage_data_len);
for (index, garbage_byte) in garbage_data {
if let Some(x) = encoded_highest_index {
let index_to_use = index % x;
garbage_data_indexed[index_to_use] = Some(garbage_byte);
} else {
encoded_with_garbage.push(garbage_byte);
}
}
for (index, encoded_byte) in encoded.into_iter().enumerate() {
encoded_with_garbage.push(encoded_byte);
if let Some(garbage_byte) = garbage_data_indexed[index] {
encoded_with_garbage.push(garbage_byte);
}
}
encoded_with_garbage
};
match line_wrap_to_use {
Some(0) => {
let line_endings_count = encoded_or_encoded_with_garbage
.iter()
.filter(|byte| **byte == b'\n')
.count();
// If line wrapping is disabled, there should only be one '\n' character (at the very end of the output)
prop_assert_eq!(line_endings_count, 1);
}
_ => {
// TODO
// Validate other line wrapping settings
}
}
let decoded_or_error = {
let mut output = Vec::with_capacity(input_len);
let mut cursor = Cursor::new(encoded_or_encoded_with_garbage);
match fast_decode::fast_decode(
&mut cursor,
&mut output,
supports_fast_decode_and_encode,
ignore_garbage,
) {
Ok(()) => Ok(output),
Err(er) => Err(er),
}
};
let made_round_trip = match decoded_or_error {
Ok(ve) => input.as_slice() == ve.as_slice(),
Err(_) => false,
};
let result_was_correct = if garbage_data_is_empty || ignore_garbage {
// If there was no garbage data added, or if "ignore_garbage" was enabled, expect the round trip to succeed
made_round_trip
} else {
// If garbage data was added, and "ignore_garbage" was disabled, expect the round trip to fail
!made_round_trip
};
if !result_was_correct {
eprintln!(
"\
(configurable_round_trip) FAILURE
format: {format:?}
ignore_garbage: {ignore_garbage}
line_wrap_to_use: {line_wrap_to_use:?}
garbage_data_len: {garbage_data_len}
input_len: {input_len}
",
);
}
prop_assert!(result_was_correct);
Ok(())
}
#[test]
fn base16_round_trip() {
generic_round_trip(Format::Base16);
}
#[test]
fn base2lsbf_round_trip() {
generic_round_trip(Format::Base2Lsbf);
}
#[test]
fn base2msbf_round_trip() {
generic_round_trip(Format::Base2Msbf);
}
#[test]
fn base32_round_trip() {
generic_round_trip(Format::Base32);
}
#[test]
fn base32hex_round_trip() {
generic_round_trip(Format::Base32Hex);
}
#[test]
fn base64_round_trip() {
generic_round_trip(Format::Base64);
}
#[test]
fn base64url_round_trip() {
generic_round_trip(Format::Base64Url);
}
#[test]
fn z85_round_trip() {
generic_round_trip(Format::Z85);
}

View file

@ -26,9 +26,6 @@ uucore = { workspace = true }
num-bigint = { workspace = true }
num-prime = { workspace = true }
[dev-dependencies]
quickcheck = "1.0.3"
[[bin]]
name = "factor"
path = "src/main.rs"

View file

@ -19,7 +19,7 @@ pub mod for_cksum {
pub use data_encoding::BASE64;
}
#[derive(Clone, Copy)]
#[derive(Clone, Copy, Debug)]
pub enum Format {
Base64,
Base64Url,
@ -57,6 +57,12 @@ impl EncodingWrapper {
unpadded_multiple: usize,
alphabet: &'static [u8],
) -> Self {
assert!(valid_decoding_multiple > 0);
assert!(unpadded_multiple > 0);
assert!(!alphabet.is_empty());
Self {
alphabet,
encoding,
@ -110,15 +116,6 @@ impl SupportsFastDecodeAndEncode for Z85Wrapper {
return Err(USimpleError::new(1, "error: invalid input".to_owned()));
}
// According to the spec we should not accept inputs whose len is not a multiple of 4.
// However, the z85 crate implements a padded encoding and accepts such inputs. We have to manually check for them.
if input.len() % 4 != 0 {
return Err(USimpleError::new(
1,
"error: invalid input (length must be multiple of 4 characters)".to_owned(),
));
};
let decode_result = match z85::decode(input) {
Ok(ve) => ve,
Err(_de) => {