rizin/librz/util/base16.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

147 lines
4.4 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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 Base16 encoding and decoding functions
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <rz_types_base.h>
#include <rz_util.h>
/** \internal
* \brief Calculate the length of \p src.
* \param[in] src The binary data to be Base16-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 (st64)real_len;
}
/**
* \brief Base-16-encode binary data.
* \param[out] dest Buffer to receive NUL-terminated encoded output.
* Must have at least \c (2 × n) + 1 bytes.
* \param[in] src Pointer to the binary input.
* \param[in] n Number of bytes in \p src.
* \return Number of characters written (excluding the NUL terminator),
* or \c 0 if parameters are invalid.
*
* This function converts each input byte to two lowercase hexadecimal
* characters using \c rz_hex_bin2str().
*/
RZ_API int rz_base16_encode(RZ_OUT RZ_NONNULL char *dest, RZ_NONNULL const ut8 *src, size_t n) {
rz_return_val_if_fail(src && dest, 0);
if (rz_hex_bin2str(src, (int)n, dest) == 0) {
return 0;
}
return n * 2;
}
/**
* \brief Dynamically allocate and fill a Base-16-encoded string.
* \param[in] src Pointer to the binary input.
* \param[in] n Number of bytes in \p src.
* \return Pointer to a NUL-terminated Base-16 string allocated with
* \c malloc, or \c NULL if allocation fails or \p src is \c NULL.
* Caller must \c free() the returned pointer.
*
* Allocates exactly \c (2 × n) + 1 bytes, encodes the input, and NUL-terminates.
*/
RZ_API RZ_OWN char *rz_base16_encode_dyn(RZ_NONNULL const ut8 *src, size_t n) {
rz_return_val_if_fail(src, NULL);
char *out = (char *)malloc(2 * n + 1);
if (!out) {
return NULL;
}
if (rz_base16_encode(out, src, n) == 0) {
return NULL;
}
return out;
}
/**
* \brief Decode a Base-16 string into binary form.
* \param[out] dest Output buffer for decoded bytes.
* Must have at least (\c strlen(src) / 2) + 1 bytes.
* \param[in] src NUL-terminated Base-16 string.
* \return Number of decoded bytes on success,
* or a negative value if an odd number of nibbles was parsed,
* or \c 0 if parameters are invalid.
*
* This is a thin wrapper around \c rz_hex_str2bin().
* Output is **not** automatically NUL-terminated unless you add it yourself.
*/
RZ_API int rz_base16_decode(RZ_OUT RZ_NONNULL ut8 *dest, RZ_NONNULL const char *src) {
rz_return_val_if_fail(src && dest, 0);
int out_len = rz_hex_str2bin(src, dest);
if (out_len < 0) {
// Odd number of nibbles — still terminate after absolute length
dest[-out_len] = '\0';
return out_len;
}
// NUL terminate after the decoded bytes
dest[out_len] = '\0';
return out_len;
}
/**
* \brief Dynamically decode a Base-16 string into binary form.
* \param[in] src NUL-terminated Base-16 string to decode.
* \param[in] len Length of \p src in characters, or \c -1 to use \c strlen().
* \return Pointer to a newly allocated buffer containing the decoded
* binary data followed by a NUL byte, or \c NULL on error.
*
* Accepts even or odd numbers of hex digits. For odd lengths, the final
* nibble is padded with zero.
*/
RZ_API RZ_OWN ut8 *rz_base16_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 = (len / 2) + 1;
ut8 *buf = (ut8 *)malloc((size_t)cap);
if (!buf) {
return NULL;
}
st64 out_len = rz_base16_decode(buf, src);
if (out_len == 0) { // truly invalid hex
free(buf);
return NULL;
}
if (out_len < 0) { // odd nibble count — still valid, pad added
out_len = -out_len;
}
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;
}