Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/uu/base32/locales/en-US.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ basenc-help-base2msbf = bit string with most significant bit (msb) first
basenc-help-z85 = ascii85-like encoding;
when encoding, input length must be a multiple of 4;
when decoding, input length must be a multiple of 5
basenc-help-base58 = visually unambiguous base58 encoding

# Error messages
basenc-error-missing-encoding-type = missing encoding type
Expand Down
1 change: 1 addition & 0 deletions src/uu/base32/locales/fr-FR.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ basenc-help-base2msbf = chaîne de bits avec le bit de poids fort (msb) en premi
basenc-help-z85 = encodage de type ascii85 ;
lors de l'encodage, la longueur d'entrée doit être un multiple de 4 ;
lors du décodage, la longueur d'entrée doit être un multiple de 5
basenc-help-base58 = encodage base58 visuellement non ambigu

# Messages d'erreur
basenc-error-missing-encoding-type = type d'encodage manquant
Expand Down
5 changes: 3 additions & 2 deletions src/uu/base32/src/base_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ use std::io::{self, ErrorKind, Read, Seek};
use std::path::{Path, PathBuf};
use uucore::display::Quotable;
use uucore::encoding::{
BASE2LSBF, BASE2MSBF, Base64SimdWrapper, EncodingWrapper, Format, SupportsFastDecodeAndEncode,
Z85Wrapper,
BASE2LSBF, BASE2MSBF, Base58Wrapper, Base64SimdWrapper, EncodingWrapper, Format,
SupportsFastDecodeAndEncode, Z85Wrapper,
for_base_common::{BASE32, BASE32HEX, BASE64URL, HEXUPPER_PERMISSIVE},
};
use uucore::error::{FromIo, UResult, USimpleError, UUsageError};
Expand Down Expand Up @@ -285,6 +285,7 @@ pub fn get_supports_fast_decode_and_encode(
b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789=_-",
)),
Format::Z85 => Box::from(Z85Wrapper {}),
Format::Base58 => Box::from(Base58Wrapper {}),
}
}

Expand Down
1 change: 1 addition & 0 deletions src/uu/basenc/src/basenc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ fn get_encodings() -> Vec<(&'static str, Format, String)> {
translate!("basenc-help-base2msbf"),
),
("z85", Format::Z85, translate!("basenc-help-z85")),
("base58", Format::Base58, translate!("basenc-help-base58")),
]
}

Expand Down
140 changes: 140 additions & 0 deletions src/uucore/src/lib/features/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

// spell-checker:ignore (encodings) lsbf msbf
// spell-checker:ignore unpadded
// spell-checker:ignore ABCDEFGHJKLMNPQRSTUVWXY Zabcdefghijkmnopqrstuvwxyz

use crate::error::{UResult, USimpleError};
use base64_simd;
Expand Down Expand Up @@ -105,6 +106,7 @@ pub enum Format {
Base2Lsbf,
Base2Msbf,
Z85,
Base58,
}

pub const BASE2LSBF: Encoding = new_encoding! {
Expand All @@ -119,6 +121,8 @@ pub const BASE2MSBF: Encoding = new_encoding! {

pub struct Z85Wrapper {}

pub struct Base58Wrapper {}

pub struct EncodingWrapper {
pub alphabet: &'static [u8],
pub encoding: Encoding,
Expand Down Expand Up @@ -181,6 +185,142 @@ pub trait SupportsFastDecodeAndEncode {
fn valid_decoding_multiple(&self) -> usize;
}

impl SupportsFastDecodeAndEncode for Base58Wrapper {
fn alphabet(&self) -> &'static [u8] {
// Base58 alphabet
b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
}

fn decode_into_vec(&self, input: &[u8], output: &mut Vec<u8>) -> UResult<()> {
if input.is_empty() {
return Ok(());
}

// Count leading zeros (will become leading 1s in base58)
let leading_ones = input.iter().take_while(|&&b| b == b'1').count();

// Skip leading 1s for conversion
let input_trimmed = &input[leading_ones..];
if input_trimmed.is_empty() {
output.resize(output.len() + leading_ones, 0);
return Ok(());
}

// Convert base58 to big integer
let mut num: Vec<u32> = vec![0];
let alphabet = self.alphabet();

for &byte in input_trimmed {
// Find position in alphabet
let digit = alphabet
.iter()
.position(|&b| b == byte)
.ok_or_else(|| USimpleError::new(1, "error: invalid input".to_owned()))?;

// Multiply by 58 and add digit
let mut carry = digit as u32;
for n in &mut num {
let tmp = (*n as u64) * 58 + carry as u64;
*n = tmp as u32;
carry = (tmp >> 32) as u32;
}
if carry > 0 {
num.push(carry);
}
}

// Convert to bytes (little endian, then reverse)
let mut result = Vec::new();
for &n in &num {
result.extend_from_slice(&n.to_le_bytes());
}

// Remove trailing zeros and reverse to get big endian
while result.last() == Some(&0) && result.len() > 1 {
result.pop();
}
result.reverse();

// Add leading zeros for leading 1s in input
let mut final_result = vec![0; leading_ones];
final_result.extend_from_slice(&result);

output.extend_from_slice(&final_result);
Ok(())
}

fn encode_to_vec_deque(&self, input: &[u8], output: &mut VecDeque<u8>) -> UResult<()> {
if input.is_empty() {
return Ok(());
}

// Count leading zeros
let leading_zeros = input.iter().take_while(|&&b| b == 0).count();

// Skip leading zeros
let input_trimmed = &input[leading_zeros..];
if input_trimmed.is_empty() {
for _ in 0..leading_zeros {
output.push_back(b'1');
}
return Ok(());
}

// Convert bytes to big integer
let mut num: Vec<u32> = Vec::new();
for &byte in input_trimmed {
let mut carry = byte as u32;
for n in &mut num {
let tmp = (*n as u64) * 256 + carry as u64;
*n = tmp as u32;
carry = (tmp >> 32) as u32;
}
if carry > 0 {
num.push(carry);
}
}

// Convert to base58
let mut result = Vec::new();
let alphabet = self.alphabet();

while !num.is_empty() && num.iter().any(|&n| n != 0) {
let mut carry = 0u64;
for n in num.iter_mut().rev() {
let tmp = carry * (1u64 << 32) + *n as u64;
*n = (tmp / 58) as u32;
carry = tmp % 58;
}
result.push(alphabet[carry as usize]);

// Remove leading zeros
while num.last() == Some(&0) && num.len() > 1 {
num.pop();
}
}

// Add leading 1s for leading zeros in input
for _ in 0..leading_zeros {
output.push_back(b'1');
}

// Add result (reversed because we built it backwards)
for byte in result.into_iter().rev() {
output.push_back(byte);
}

Ok(())
}

fn unpadded_multiple(&self) -> usize {
1 // Base58 doesn't use padding
}

fn valid_decoding_multiple(&self) -> usize {
1 // Any length is valid for Base58
}
}

impl SupportsFastDecodeAndEncode for Z85Wrapper {
fn alphabet(&self) -> &'static [u8] {
// Z85 alphabet
Expand Down
44 changes: 31 additions & 13 deletions tests/by-util/test_basenc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,21 +185,30 @@ fn test_base2lsbf_decode() {
}

#[test]
fn test_choose_last_encoding_z85() {
fn test_z85_decode() {
new_ucmd!()
.args(&[
"--base2lsbf",
"--base2msbf",
"--base16",
"--base32hex",
"--base64url",
"--base32",
"--base64",
"--z85",
])
.pipe_in("Hello, World")
.args(&["--z85", "-d"])
.pipe_in("nm=QNz.92jz/PV8")
.succeeds()
.stdout_only("Hello, World");
}

#[test]
fn test_base58() {
new_ucmd!()
.arg("--base58")
.pipe_in("Hello, World!")
.succeeds()
.stdout_only("72k1xXWG59fYdzSNoA\n");
}

#[test]
fn test_base58_decode() {
new_ucmd!()
.args(&["--base58", "-d"])
.pipe_in("72k1xXWG59fYdzSNoA")
.succeeds()
.stdout_only("nm=QNz.92jz/PV8\n");
.stdout_only("Hello, World!");
}

#[test]
Expand Down Expand Up @@ -238,6 +247,15 @@ fn test_choose_last_encoding_base2lsbf() {
.stdout_only("00110110110011100100011001100110\n");
}

#[test]
fn test_choose_last_encoding_base58() {
new_ucmd!()
.args(&["--base64", "--base32", "--base16", "--z85", "--base58"])
.pipe_in("Hello!")
.succeeds()
.stdout_only("d3yC1LKr\n");
}

#[test]
fn test_base32_decode_repeated() {
new_ucmd!()
Expand Down
Loading