multi_base/lib.rs
1// SPDX-License-Identifier: MIT
2
3//! # multibase
4//!
5//! Implementation of [multibase](https://github.com/multiformats/multibase) in Rust.
6
7#![deny(missing_docs)]
8#![cfg_attr(not(feature = "std"), no_std)]
9
10#[cfg(not(feature = "std"))]
11extern crate alloc;
12
13#[cfg(not(feature = "std"))]
14use alloc::{string::String, vec::Vec};
15
16mod base;
17pub mod encoded;
18mod encoding;
19mod error;
20mod impls;
21
22pub use self::base::Base;
23pub use self::encoded::EncodedString;
24pub use self::error::{Error, Result};
25
26/// Decode the base string.
27///
28/// # Examples
29///
30/// ```
31/// use multi_base::{Base, decode};
32///
33/// assert_eq!(
34/// decode("zCn8eVZg", true).unwrap(),
35/// (Base::Base58Btc, b"hello".to_vec())
36/// );
37/// ```
38pub fn decode<T: AsRef<str>>(input: T, strict: bool) -> Result<(Base, Vec<u8>)> {
39 let input = input.as_ref();
40 let code = input.chars().next().ok_or(Error::EmptyInput)?;
41 let base = Base::from_code(code)?;
42 let decoded = base.decode(&input[code.len_utf8()..], strict)?;
43 Ok((base, decoded))
44}
45
46/// Encode with the given byte slice to base string.
47///
48/// # Examples
49///
50/// ```
51/// use multi_base::{Base, encode};
52///
53/// assert_eq!(encode(Base::Base58Btc, b"hello"), "zCn8eVZg");
54/// ```
55///
56/// # Performance
57///
58/// This function pre-allocates the exact capacity needed and constructs the
59/// result string efficiently by prepending the base code without requiring
60/// reallocation or memory moves.
61pub fn encode<T: AsRef<[u8]>>(base: Base, input: T) -> String {
62 let input = input.as_ref();
63 let encoded = base.encode(input);
64 let code = base.code();
65
66 // Pre-allocate exact size needed: code length + encoded length
67 // This is much faster than insert(0) which requires moving all bytes
68 let mut result = String::with_capacity(code.len_utf8() + encoded.len());
69 result.push(code);
70 result.push_str(&encoded);
71 result
72}
73
74/// Encode with the given byte slice to base string, writing into an existing buffer.
75///
76/// This is a zero-copy variant of [`encode`] that reuses an existing String buffer,
77/// avoiding allocations when encoding multiple values in a loop.
78///
79/// The buffer will be cleared before encoding, then filled with the base code prefix
80/// followed by the encoded data.
81///
82/// # Examples
83///
84/// ```
85/// use multi_base::{Base, encode_into};
86///
87/// let mut buffer = String::new();
88///
89/// // Encode multiple values reusing the same buffer
90/// encode_into(Base::Base58Btc, b"hello", &mut buffer);
91/// assert_eq!(buffer, "zCn8eVZg");
92///
93/// encode_into(Base::Base64, b"world", &mut buffer);
94/// assert_eq!(buffer, "md29ybGQ");
95/// ```
96///
97/// # Performance
98///
99/// When encoding many values, this function can be significantly faster than
100/// [`encode`] as it reuses the allocated buffer instead of allocating a new
101/// String for each encoding operation.
102pub fn encode_into<T: AsRef<[u8]>>(base: Base, input: T, buffer: &mut String) {
103 let input = input.as_ref();
104 let encoded = base.encode(input);
105 let code = base.code();
106
107 // Clear and reserve exact capacity needed
108 buffer.clear();
109 buffer.reserve(code.len_utf8() + encoded.len());
110 buffer.push(code);
111 buffer.push_str(&encoded);
112}
113
114/// Decode the base string, writing the result into an existing buffer.
115///
116/// This is a zero-copy variant of [`decode`] that reuses an existing `Vec<u8>` buffer,
117/// avoiding allocations when decoding multiple values in a loop.
118///
119/// The buffer will be cleared before decoding, then filled with the decoded bytes.
120/// Returns the base encoding that was detected from the prefix.
121///
122/// # Examples
123///
124/// ```
125/// use multi_base::{Base, decode_into};
126///
127/// let mut buffer = Vec::new();
128///
129/// // Decode multiple values reusing the same buffer
130/// let base = decode_into("zCn8eVZg", true, &mut buffer).unwrap();
131/// assert_eq!(base, Base::Base58Btc);
132/// assert_eq!(buffer, b"hello");
133///
134/// let base = decode_into("md29ybGQ", true, &mut buffer).unwrap();
135/// assert_eq!(base, Base::Base64);
136/// assert_eq!(buffer, b"world");
137/// ```
138///
139/// # Performance
140///
141/// When decoding many values, this function can be significantly faster than
142/// [`decode`] as it reuses the allocated buffer instead of allocating a new
143/// `Vec<u8>` for each decoding operation.
144///
145/// # Errors
146///
147/// Returns an error if:
148/// - The input string is empty
149/// - The base code prefix is unknown
150/// - The encoded data is invalid for the specified base
151pub fn decode_into<T: AsRef<str>>(input: T, strict: bool, buffer: &mut Vec<u8>) -> Result<Base> {
152 let input = input.as_ref();
153 let code = input.chars().next().ok_or(Error::EmptyInput)?;
154 let base = Base::from_code(code)?;
155 base.decode_into(&input[code.len_utf8()..], strict, buffer)?;
156 Ok(base)
157}
158
159/// Encode with the given byte slice and return a validated `EncodedString`.
160///
161/// This is a convenience function that combines [`encode`] with [`EncodedString::new`],
162/// providing type-level guarantees that the returned string is a valid multibase encoding.
163///
164/// # Examples
165///
166/// ```
167/// use multi_base::{Base, encode_to_validated};
168///
169/// let encoded = encode_to_validated(Base::Base58Btc, b"hello");
170/// assert_eq!(encoded.base(), Base::Base58Btc);
171/// assert_eq!(encoded.as_str(), "zCn8eVZg");
172///
173/// // Decode directly from the validated string
174/// let decoded = encoded.decode().unwrap();
175/// assert_eq!(decoded, b"hello");
176/// ```
177///
178/// # Performance
179///
180/// This function has the same performance characteristics as [`encode`].
181/// The validation overhead is negligible (just checking the base code).
182pub fn encode_to_validated<T: AsRef<[u8]>>(base: Base, input: T) -> EncodedString {
183 let encoded_str = encode(base, input);
184 // SAFETY: We just encoded this with a valid base, so it must be valid
185 // This unwrap is safe and will never panic
186 EncodedString::new(encoded_str).expect("freshly encoded string must be valid")
187}
188
189/// Parse a multibase string into a validated `EncodedString`.
190///
191/// This is equivalent to [`EncodedString::new`] but provides a standalone
192/// function for consistency with other API functions.
193///
194/// # Examples
195///
196/// ```
197/// use multi_base::{Base, parse_encoded};
198///
199/// let encoded = parse_encoded("zCn8eVZg").unwrap();
200/// assert_eq!(encoded.base(), Base::Base58Btc);
201/// ```
202///
203/// # Errors
204///
205/// Returns an error if:
206/// - The input string is empty
207/// - The base code prefix is unknown
208pub fn parse_encoded<T: AsRef<str>>(input: T) -> Result<EncodedString> {
209 EncodedString::new(input.as_ref())
210}