rizin/librz/util/base32.c
Ahmed Mohamed Ibrahim 38a530dbe3
librz/util: base64, base85, base36, base32, base16 support and refactor (#5216) (#5271)
* Rename librz/util/ubase64.c into librz/util/base64.c for consistency #5216

* Add Doxygen documentation to function in librz/util/base85.c #5216

* Add unit-tests for librz/util/base85.c #5216

* Refactor base85 API and internal functions #5216

base85 was using a FILE as an input and it used to print its result to
STDOUT, which was very bad for having unit-testing or to use it to
extend rz-hash functionality, since the solution I really had was
through using pipes which isn't ideal as much as the refactoring one.

* Extend rz-hash with base85 #5126

* Fix formatting #5216

* Extract base36 decoding function from subprojects/rzwinkd/iob_net.c to librz/util/base36.c #5216

- Also, added Doxygen docs to rz_base36_decode function

* Added rz_base36_encode_dyn #5216

- this function encode a u64 value to it's char[13+1] base36 counterpart
- later, it will be helpful in expanding the funcitionality of rz-hash
- added its Doxygen docs

* Expand rz-hash with base36 encoding/decoding #5216

* Refactor rz_base36_decode to return a suitable error code like -1 #5216

- that should help testing it later for valid and invalid decodings
- keeping the original behaviour as it was in
subprojects/rzwinkd/iob_net.c

* Add unit-tests for base36 encoding & decoding functions #5216

* Fix formatting #5216

* Fix base36.h header file guards #5216

* Add base32 encoding & decoding #5216

added their Doxygen Docs as well

* Add base32 encoding and decoding unti-tests #5216

* Expand rz-hash with base32 encoding/decoding functionality #5216

* Fix formatting #5216

* Fix add base32 to codec_name_bytes #5216

* Fix base32 docs #5216

* Add base16 encoding/decoding functions #5216

- added their Doxygen docs as well.

* Add base16 unit-tests #5216

* Fix test_base85 conversions warnings #5216

* Expand rz-hash with base16 encoding/decoding functionality #5216

* Fix formatting #5216

* Fix doxygen docs #5216

* Restore subprojects/rizin-shell-parser/parser.c to match origin/dev

* Fix the order by baseXX

* Fix base16 - invert the logic in calculate_src_length

* Fix base16 - intialize variables in rz_base16_encode

* Fix base16 - null terminate the output buffer of rz_base16_encode_dyn

* Fix base16 - get rid of unnecessary else in rz_base16_decode

* Fix base16 - use `len & 1` instead of `(len % 2) != 0`

* Fix base32 - invert the logic in calculate_src_length

* Fix base32 - compress two return statments by using `rz_return_val_if_fail(src && dest, 0);`

* Fix base32 - null terminate the output of rz_base32_encode

* Fix base32 - get rid of unnecessary else in rz_base32_decode

add more parentheses to split addition from mult for better clarity

* Fix base32 - intialize variables in rz_base32_encode_dyn

* Fix base36 - intialize `tmp` variable in rz_base36_encode_dyn

* Fix base36 - use RZ_LOG_ERROR instead of eprintf

* Fix base36 - use RZ_NULLABLE for the API interface

* Fix base85 - use RZ_OUT & RZ_NULLABLE for the API interface

* Fix base85 - use RZ_LOG_ERROR instead of eprintf

* Fix base85 - use size_t instead of int for decode_tuple_buf

* Fix base85 - remove unused varaible, `out_len`, in rz_base85_encode_dyn

* Fix base85 - correct decode buffer size calculation

The previous rz_base85_dec_buflen() underestimated the worst‑case output
size (3 bytes per 4 input chars), causing overflows when using ‘z’/’y’
abbreviations. Update it to allocate 4 bytes per input character plus
one for the NUL terminator, eliminating heap-buffer-overflow errors.

* Fix formatting

* Fix base85-test - use `newlines` variable to make sure line breaks were inserted as expected

* Fix base85-test - remove unnecessary includes

* Fix rz-hash test - updated rz-hash -L expected result

* Fix crypto_base36 - use RZ_LOG_ERROR instead of eprintf

* Fix base16 - compress rz_return_val_if_fail statements and move before locals

* Update base16 - use hex.c existing encoding/decoding logic

- base16.c encoding/decoding functions are now wrappers for hex.c
  `rz_hex_bin2str` and `rz_hex_str2bin` functions
- updated related base16 unit testing and documentation
- updated the integraion with rz-hash binary (or crypto_base16)

* Fix formatting

* Add documentation to rz_hex_bin2str

* Fix base85 - add missing `reutrn` docs to rz_base85_encode function

* Fix base85 - use RZ_NONNULL for dest and src function instead of RZ_NULLABLE

* Fix base36 - use RZ_NONNULL for dest and src function instead of RZ_NULLABLE

* Fix base16 - use RZ_NONNULL for dest and src function instead of RZ_NULLABLE

* Fix base32 - use RZ_NONNULL for dest and src function instead of RZ_NULLABLE

* Fix base16 - add missig checks for invalid inputs in rz_base16_encode & rz_base16_encode_dyn

* Fix base32 - add missing checks for bad encoding

* Fix - add missing SPDX

* Fix Formatting

* Add rz_base36_encode & rz_base36_decode_dyn functions to base36 API
2025-08-17 15:26:25 +08:00

354 lines
11 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// SPDX-FileCopyrightText: 2025 Ahmed Ibrahim <a.ibrahim8686@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
/**
* \file
* \brief Base32 encoding and decoding functions
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <rz_types_base.h>
#include <rz_util.h>
/** \internal
* \brief Table for mapping a 5bit value (031) to its Base32 symbol.
*/
static const char cb32[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
/** \internal
* \brief Map a Base32 symbol to its 5bit index (031).
*/
static size_t cd32(int c) {
if (c >= 'A' && c <= 'Z') {
return (size_t)(c - 'A');
}
if (c >= 'a' && c <= 'z') {
return (size_t)(c - 'a');
}
if (c >= '2' && c <= '7') {
return (size_t)(26 + (c - '2'));
}
return SIZE_MAX;
}
/** \internal
* \brief Base32 encode one 40bit (5byte) block.
* \param[in] src Five input bytes.
* \param[out] dest Eightcharacter output (no padding chars).
*
* The caller must pad any partial final block with zeros *before*
* invoking this function; the encoder later converts the surplus
* characters to = if RFC4648 padding is desired.
*/
static void pack_to5(ut8 dest[8], const ut8 src[5]) {
/* Each line extracts a 5bit group from the 40bit stream */
dest[0] = cb32[(src[0] >> 3) & 0x1F];
dest[1] = cb32[((src[0] << 2) | (src[1] >> 6)) & 0x1F];
dest[2] = cb32[(src[1] >> 1) & 0x1F];
dest[3] = cb32[((src[1] << 4) | (src[2] >> 4)) & 0x1F];
dest[4] = cb32[((src[2] << 1) | (src[3] >> 7)) & 0x1F];
dest[5] = cb32[(src[3] >> 2) & 0x1F];
dest[6] = cb32[((src[3] << 3) | (src[4] >> 5)) & 0x1F];
dest[7] = cb32[src[4] & 0x1F];
}
/** \internal
* \brief Decode a single base32-output group.
* \param[in] src The encoded group of 8 base32 characters.
* \param[out] dest The decoded group of 5 binary bytes.
*
* This function operates on a group of exactly 8 base32 symbols and
* reconstructs the original 5 input bytes (8 × 5 = 40 bits = 5 × 8).
*/
static void unpack_from5(ut8 dest[5], const ut8 src[8]) {
ut8 idx[8];
size_t i;
for (i = 0; i < 8; i++) {
idx[i] = cd32(src[i]);
}
dest[0] = (idx[0] << 3) | (idx[1] >> 2);
dest[1] = (idx[1] << 6) | (idx[2] << 1) | (idx[3] >> 4);
dest[2] = (idx[3] << 4) | (idx[4] >> 1);
dest[3] = (idx[4] << 7) | (idx[5] << 2) | (idx[6] >> 3);
dest[4] = (idx[6] << 5) | (idx[7] >> 0);
}
/** \internal
* \brief Validate a Base32 character.
* \param c The character to examine (promoted to \c int by the caller).
*
* The function returns true in case:
* - an uppercase letter in the range <code>'A''Z'</code>, or
* - a lowercase letter in the range <code>'a''z'</code> (accepted for
* leniency), or
* - a digit in the range <code>'2''7'</code>.
*
* Any other value yields \c false.
*/
static inline bool is_base32(int c) {
return (c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') || /* allow lowercase */
(c >= '2' && c <= '7');
}
/** \internal
* \brief Calculate the length of \p src.
* \param[in] src The binary data to be Base32-encoded later.
* \param len The length of the binary data in bytes.
*
* This function returns \p len as it is unless it is negative, in
* which case, it returns the string length of \p src. A possibility
* of a string size larger than \c ST64_MAX requires us to make a
* bounds check, and error if overflow is possible. This function is
* provided in lieu of modifying the decoding API parameter list.
*/
static st64 calculate_src_length(const char *src, st64 len) {
if (len >= 0)
return len;
size_t real_len = strlen(src);
if (ST64_MAX < real_len) {
return -1;
}
return real_len;
}
/** \internal
* \brief Calculate the length, in bytes, of the Base32 encoded result.
* \param len the length of the binary input in bytes.
*
* Base32 processes the data in 5byte blocks and produces an 8character
* output block for each full 40bit chunk. When the input is not an exact
* multiple of five bytes, the final (partial) block is still encoded as a
* *full* 8character group, with the surplus characters subsequently replaced
* by the \c = padding symbol to reach the next 8byte boundary.
*/
static size_t calculate_dest_length(size_t len) {
return 8 * ((len + 4) / 5);
}
/**
* \brief Base32encode binary data.
* \param[out] dest The encoded output.
* \param[in] src The binary input data.
* \param n The length of the binary data in bytes.
* \return The length of the encoded output, excluding the NUL byte.
* \attention The \p dest parameter must have enough space to hold the
* full encoded output, including a terminating NUL byte. In particular,
* it should reserve at least \c 1+8×((n+4)/5) bytes.
*
* This function implements canonical Base32 encoding as per RFC4648.
* The input is padded with zero bits to a full 40bit block (5 bytes)
* if needed. The output is a sequence of 8character blocks, padded
* using the \c = character to fill any final incomplete group.
*
* If \p dest or \p src is \c NULL, nothing is written and \c 0 is returned.
*
* # Example
* \code{.c}
* const ut8 msg[] = "Hi!";
* size_t msg_len = strlen((const char *)msg);
* size_t enc_len = 8 * ((msg_len + 4) / 5);
* char *enc = malloc(enc_len + 1); // +1 for NUL byte
* if (!enc) goto memory_error;
* rz_base32_encode(enc, msg, msg_len);
* assert(strcmp(enc, "JBUSC===") == 0);
* free(enc);
* \endcode
*/
RZ_API size_t rz_base32_encode(RZ_OUT RZ_NONNULL char *dest, RZ_NONNULL const ut8 *src, size_t n) {
rz_return_val_if_fail(src && dest, 0);
ut8 final_group[5] = { 0 };
size_t ret = calculate_dest_length(n);
while (n >= 5) {
pack_to5((ut8 *)dest, src);
src += 5;
dest += 8;
n -= 5;
}
if (n > 0) {
memcpy(final_group, src, n);
pack_to5((ut8 *)dest, final_group);
static const ut8 pad_count[5] = { 6, 4, 3, 1 };
size_t pad = pad_count[n - 1];
memset(dest + (8 - pad), '=', pad);
dest += 8;
}
dest[0] = '\0';
return ret;
}
/**
* \brief Base32encode binary data and return the result in a newly allocated buffer.
* \param[in] src Pointer to the binary data to encode.
* \param n the length of the binary data in bytes.
* \return A pointer to a NULterminated Base32 string allocated with
* \c malloc, or \c NULL if \p src is \c NULL or a memoryallocation
* failure occurs. The caller is responsible for \c free()ing the
* returned buffer.
*
* This function is the Base32 analogue of \c rz_base64_encode_dyn. It first
* computes the exact output size—\c 1+8×((n+4)/5) bytes to accommodate
* complete 8character blocks plus a terminating NUL—allocates that much
* memory, and then invokes \c rz_base32_encode to fill the buffer.
*
* # Example
* \code{.c}
* const ut8 plain[] = { 0x48, 0x69 }; // "Hi"
* char *enc = rz_base32_encode_dyn(plain, sizeof plain);
* if (!enc) { goto memory_error; }
* assert(strcmp(enc, "JBUSC===") == 0);
* free(enc);
* \endcode
*/
RZ_API RZ_OWN char *rz_base32_encode_dyn(RZ_NONNULL const ut8 *src, size_t n) {
rz_return_val_if_fail(src, NULL);
size_t buf_sz = calculate_dest_length(n) + 1;
char *out = (char *)malloc(buf_sz);
if (!out) {
return NULL;
}
if (rz_base32_encode(out, src, n) == 0) {
return NULL;
}
out[buf_sz - 1] = '\0';
return out;
}
/**
* \brief Decode a Base32-encoded message.
* \param[out] dest The decoded output.
* \param[in] src The Base32-encoded message.
* \param n The length of the encoded message.
* \return The length of the decoded message, excluding the NUL byte.
* \attention The \p dest parameter should have sufficient space to
* accomodate the decoded output, including the NUL byte. In
* particular, it should have at least \c 1+(5*(n+4))/8 bytes
* available.
*
* Decode a Base32-encoded message. The \p n parameter may be
* negative, in which case \p src is treated as a C string and its
* string length is calculated via strlen. The decoded output is stored in \p
* dest, and will be NUL byte terminated.
*
* If either \p dest or \p src is \c NULL, nothing is done and the
* value \c 0 is returned.
*
* The return value is \c -1 in the following cases:
* - Characters in \p src are rejected as invalid.
* - The parameter \p n was \c -1 and the string length exceeds \c ST64_MAX.
*
* # Example
*
* \code{.c}
* const char *b32 = "MZXW6==="; // "foo"
* ut8 buf[16];
* st64 len = rz_base32_decode(buf, b32, -1);
* assert(len == 3);
* assert(memcmp(buf, "foo", 3) == 0);
* \endcode
*/
RZ_API st64 rz_base32_decode(RZ_OUT RZ_NONNULL ut8 *dest, RZ_NONNULL const char *src, st64 n) {
rz_return_val_if_fail(src && dest, 0);
char buf[8] = { 0 }, tmp[5] = { 0 };
int c = 0;
size_t i = 0, j = 0;
st64 ret = 0;
n = calculate_src_length(src, n);
if (n == -1) {
return -1;
}
for (i = j = 0; i < (size_t)n; i++) {
c = src[i];
if (is_base32(c)) {
buf[j++] = c;
if (j == 8) {
j = 0;
ret += 5;
unpack_from5((ut8 *)dest, (const ut8 *)buf);
dest += 5;
}
} else if (c == '=') {
continue;
} else {
return -1;
}
}
if (j == 0) {
dest[0] = '\0';
return ret;
} else if (j <= 1) {
return -1;
}
size_t rem = j;
for (; j < 8; j++) {
buf[j] = 'A'; // zero padding for partial group
}
unpack_from5((ut8 *)tmp, (const ut8 *)buf);
// decide how many of the unpacked bytes are valid based on how many base32 symbols you originally had.
static const int rem2bytes[8] = { 0, 0, 1, 0, 2, 3, 0, 4 };
int bytes = rem2bytes[rem];
if (bytes == 0) {
return -1;
}
memcpy(dest, tmp, bytes);
dest[bytes] = '\0';
return ret + bytes;
}
/**
* \brief Dynamically decode a Base32encoded string.
* \param[in] src Pointer to the Base32 text.
* \param len The length of \p src; pass \c -1 to use \c strlen(src).
* \return A newly allocated buffer holding the decoded binary data and
* terminated with a NUL byte, or \c NULL on error.
*
* Memory is obtained with \c malloc and must be released with \c free
* by the caller. The function returns \c NULL when:
* - \p src is \c NULL,
* - a length overflow would exceed \c ST64_MAX,
* - a memory allocation fails, or
* - the underlying decoder reports malformed input.
*
* The worstcase decoded size for \p len bytes of Base32 is
* \c 1+5×((len+7)/8) bytes (the final “\c 1” is for the NUL terminator).
* This bound is used to preallocate the buffer and then \c realloc
* trims it to the exact number of bytes actually produced.
*/
RZ_API RZ_OWN ut8 *rz_base32_decode_dyn(RZ_NONNULL const char *src, st64 len) {
rz_return_val_if_fail(src, NULL);
len = calculate_src_length(src, len);
if (len < 0) {
return NULL;
}
st64 cap = 1 + (5 * ((len + 7) / 8));
ut8 *buf = (ut8 *)malloc((size_t)cap);
if (!buf) {
return NULL;
}
st64 out_len = rz_base32_decode(buf, src, len);
if (out_len < 0) {
free(buf);
return NULL;
}
/* NULterminate and shrink to fit. */
buf[out_len] = '\0';
if (out_len + 1 < cap) {
ut8 *tmp = (ut8 *)realloc(buf, (size_t)out_len + 1);
if (tmp) {
buf = tmp;
}
}
return buf;
}