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

149 lines
4.5 KiB
C
Raw Permalink 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: 2014-2020 abcSup <zifan.tan@gmail.com>
// SPDX-FileCopyrightText: 2025 Ahmed Ibrahim <a.ibrahim8686@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
/**
* \file
* \brief Base36 encoding and decoding functions.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <rz_types_base.h>
#include <rz_util.h>
#define RZ_BASE36_BUFSZ 13
// Constants to convert ASCII to its base36 value
static const char d32[] = "[\\]^_`abcd$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$efghijklmnopqrstuvwxyz{|}~";
// The powers of 36 up to the 13th for 64-bit values
static const ut64 pow36[] = { 1, 36, 1296, 46656, 1679616, 60466176, 2176782336,
78364164096, 2821109907456, 101559956668416, 3656158440062976,
131621703842267136, 4738381338321616896 };
/**
* \brief Encode a 64-bit unsigned integer as a Base 36 digit string.
*
* \param[out] bout Buffer to receive the NUL-terminated encoded output.
* Must have at least \c RZ_BASE36_BUFSZ bytes (\c 13 characters plus the terminator).
* \param val The unsigned 64-bit integer to encode.
* \return Number of characters written (excluding the NUL terminator), or \c 0 if \p bout is \c NULL.
*
* This function converts \p val to its lowercase Base 36 representation,
* writing the result to \p bout followed by a NUL terminator.
* The most-significant digit appears first in the output.
*/
RZ_API size_t rz_base36_encode(RZ_OUT RZ_NONNULL char *bout, ut64 val) {
rz_return_val_if_fail(bout, 0);
static const char alphabet[] = "0123456789abcdefghijklmnopqrstuvwxyz";
char tmp[RZ_BASE36_BUFSZ];
size_t n = 0;
if (val == 0) {
tmp[n++] = '0';
} else {
while (val && n < RZ_BASE36_BUFSZ) {
tmp[n++] = alphabet[val % 36];
val /= 36;
}
}
// reverse into output buffer
for (size_t i = 0; i < n; i++) {
bout[i] = tmp[n - 1 - i];
}
bout[n] = '\0';
return n;
}
/**
* \brief Dynamically allocate and return the Base36 representation of a 64bit value.
*
* \param val The unsigned 64bit integer to encode.
* \return Pointer to a freshly allocated, NULterminated digit string,
* or \c NULL if memory allocation fails.
*/
RZ_API RZ_OWN char *rz_base36_encode_dyn(ut64 val) {
char *out = (char *)malloc(RZ_BASE36_BUFSZ + 1);
if (!out) {
return NULL;
}
if (rz_base36_encode(out, val) == 0) {
free(out);
return NULL;
}
return out;
}
/**
* \brief Decode a Base36 string into a 64-bit unsigned integer.
*
* \param[out] bout Pointer to a ut64 that will receive the decoded value.
* \param[in] bin Input Base36-encoded string (not necessarily NUL-terminated).
* \param[in] len Length of the Base36 string.
* \return Number of characters consumed on success, or -1 on error.
*
* This function decodes the given Base36 string \p bin of length \p len into
* a 64-bit unsigned integer. The maximum supported length is
* \c RZ_BASE36_BUFSZ (13 characters). Input is case-sensitive and only
* lowercase 'a''z' and '0''9' are accepted.
*/
RZ_API st64 rz_base36_decode(RZ_OUT RZ_NONNULL ut64 *bout, RZ_NONNULL const char *bin, st64 len) {
rz_return_val_if_fail(bin && bout, -1);
size_t i;
// 64-bit base36 str has at most 13 characters
if (len > RZ_BASE36_BUFSZ) {
RZ_LOG_ERROR("base36_decode supports up to 64-bit values only\n");
return -1;
}
for (i = 0; i < len; i++) {
char c = bin[len - i - 1];
// "01234567890abcdefghijklmnopqrstuvwxyz"
if (c < '0' || c > 'z' || ('9' < c && c < 'a')) {
RZ_LOG_ERROR("%s is not a valid base36 encoded string\n", bin);
return -1;
}
ut8 v = d32[c - '0'];
// Character does not exist in base36 encoding
if (v == '$') {
RZ_LOG_ERROR("Error: %s is not a valid base36 encoded string\n", bin);
return -1;
}
v -= 91;
// Check for overflow
if (i == 12) {
if (v > 3 || UT64_ADD_OVFCHK(*bout, v * pow36[i])) {
RZ_LOG_ERROR("Error: base36_decode supports up to 64-bit values only\n");
return -1;
}
}
*bout += v * pow36[i];
}
return i;
}
/**
* \brief Decode a Base36 string into a newly allocated 64-bit integer.
*
* \param[in] bin Base36-encoded string (not necessarily NUL-terminated).
* \param[in] len Length of the string.
* \return A newly allocated \c ut64* holding the decoded value,
* or \c NULL on error.
*/
RZ_API RZ_OWN ut64 *rz_base36_decode_dyn(RZ_NONNULL const char *bin, const size_t len) {
rz_return_val_if_fail(bin, NULL);
ut64 *out = RZ_NEW0(ut64);
if (!out) {
return NULL;
}
if (rz_base36_decode(out, bin, (st64)len) < 0) {
free(out);
return NULL;
}
return out;
}