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
This commit is contained in:
Ahmed Mohamed Ibrahim 2025-08-17 10:26:25 +03:00 committed by GitHub
parent 3efdc9a318
commit 38a530dbe3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 1828 additions and 128 deletions

View file

@ -38,7 +38,11 @@ static const struct {
RzCryptoSelector bit;
} codec_name_bytes[] = {
{ "all", UT64_MAX },
{ "base16", RZ_CODEC_B16 },
{ "base32", RZ_CODEC_B32 },
{ "base36", RZ_CODEC_B36 },
{ "base64", RZ_CODEC_B64 },
{ "base85", RZ_CODEC_B85 },
{ "base91", RZ_CODEC_B91 },
{ "punycode", RZ_CODEC_PUNYCODE },
};

View file

@ -1,7 +1,11 @@
crypto_plugins_list = [
'aes',
'aes_cbc',
'base16',
'base32',
'base36',
'base64',
'base85',
'base91',
'blowfish',
'cps2',
@ -29,7 +33,11 @@ rz_crypto_sources = [
'crypto.c',
'p/crypto_aes.c',
'p/crypto_aes_cbc.c',
'p/crypto_base16.c',
'p/crypto_base32.c',
'p/crypto_base36.c',
'p/crypto_base64.c',
'p/crypto_base85.c',
'p/crypto_base91.c',
'p/crypto_blowfish.c',
'p/crypto_cps2.c',

View file

@ -0,0 +1,84 @@
// SPDX-FileCopyrightText: 2025 Ahmed Ibrahim <a.ibrahim8686@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_lib.h>
#include <rz_crypto.h>
#include <rz_util.h>
static bool base16_set_key(RzCrypto *cry, const ut8 *key, int keylen, int mode, int direction) {
cry->dir = direction;
return true;
}
static int base16_get_key_size(RzCrypto *cry) {
return 0;
}
static bool base16_use(const char *algo) {
return !strcmp(algo, "base16");
}
static bool update(RzCrypto *cry, const ut8 *buf, int len) {
if (!cry || !buf || len <= 0) {
return false;
}
if (cry->dir == RZ_CRYPTO_DIR_ENCRYPT) {
size_t outlen = 1 + 2 * len;
char *encoded = malloc(outlen);
if (!encoded) {
return false;
}
rz_base16_encode(encoded, buf, len);
rz_crypto_append(cry, (const ut8 *)encoded, strlen(encoded));
free(encoded);
} else {
size_t outlen = (size_t)(len / 2 + 1);
ut8 *decoded = malloc(outlen);
if (!decoded) {
return false;
}
st64 decoded_len = rz_base16_decode(decoded, (const char *)buf);
if (decoded_len == 0) { // truly invalid
free(decoded);
return false;
}
if (decoded_len < 0) { // odd nibbles handled, still valid
decoded_len = -decoded_len;
}
rz_crypto_append(cry, decoded, (size_t)decoded_len);
free(decoded);
}
return true;
}
static bool final(RzCrypto *cry, const ut8 *buf, int len) {
if (!buf) {
return true;
}
return update(cry, buf, len);
}
RzCryptoPlugin rz_crypto_plugin_base16 = {
.name = "base16",
.author = "Ahmed Ibrahim",
.license = "LGPL-3",
.description = "Base16 encoder/decoder",
.set_key = base16_set_key,
.get_key_size = base16_get_key_size,
.use = base16_use,
.update = update,
.final = final
};
#ifndef RZ_PLUGIN_INCORE
RZ_API RzLibStruct rizin_plugin = {
.type = RZ_LIB_TYPE_CRYPTO,
.data = &rz_crypto_plugin_base16,
.version = RZ_VERSION
};
#endif

View file

@ -0,0 +1,79 @@
// SPDX-FileCopyrightText: 2025 Ahmed Ibrahim <a.ibrahim8686@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_lib.h>
#include <rz_crypto.h>
#include <rz_util.h>
static bool base32_set_key(RzCrypto *cry, const ut8 *key, int keylen, int mode, int direction) {
cry->dir = direction;
return true;
}
static int base32_get_key_size(RzCrypto *cry) {
return 0;
}
static bool base32_use(const char *algo) {
return !strcmp(algo, "base32");
}
static bool update(RzCrypto *cry, const ut8 *buf, int len) {
if (!cry || !buf || len <= 0) {
return false;
}
if (cry->dir == RZ_CRYPTO_DIR_ENCRYPT) {
size_t outlen = 1 + 8 * ((len + 4) / 5);
char *encoded = malloc(outlen);
if (!encoded) {
return false;
}
rz_base32_encode(encoded, buf, len);
rz_crypto_append(cry, (const ut8 *)encoded, strlen(encoded));
free(encoded);
} else {
size_t outlen = 1 + 5 * ((len + 7) / 8);
ut8 *decoded = malloc(outlen);
if (!decoded) {
return false;
}
st64 decoded_len = rz_base32_decode(decoded, (const char *)buf, len);
if (decoded_len == -1) {
free(decoded);
return false;
}
rz_crypto_append(cry, decoded, decoded_len);
free(decoded);
}
return true;
}
static bool final(RzCrypto *cry, const ut8 *buf, int len) {
if (!buf) {
return true;
}
return update(cry, buf, len);
}
RzCryptoPlugin rz_crypto_plugin_base32 = {
.name = "base32",
.author = "Ahmed Ibrahim",
.license = "LGPL-3",
.description = "Base32 encoder/decoder",
.set_key = base32_set_key,
.get_key_size = base32_get_key_size,
.use = base32_use,
.update = update,
.final = final
};
#ifndef RZ_PLUGIN_INCORE
RZ_API RzLibStruct rizin_plugin = {
.type = RZ_LIB_TYPE_CRYPTO,
.data = &rz_crypto_plugin_base32,
.version = RZ_VERSION
};
#endif

View file

@ -0,0 +1,105 @@
// SPDX-FileCopyrightText: 2025 Ahmed Ibrahim <a.ibrahim8686@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_lib.h>
#include <rz_crypto.h>
#include <rz_util.h>
static bool base36_set_key(RzCrypto *cry, const ut8 *key, int keylen, int mode, int direction) {
cry->dir = direction;
return true;
}
static int base36_get_key_size(RzCrypto *cry) {
return 0;
}
static bool base36_use(const char *algo) {
return !strcmp(algo, "base36");
}
static bool update(RzCrypto *cry, const ut8 *buf, int len) {
if (!cry || !buf || len <= 0) {
return false;
}
ut8 *out = NULL;
size_t out_len = 0;
if (cry->dir == RZ_CRYPTO_DIR_ENCRYPT) {
if (len > 8) { /* > 64bit wont fit in our encoder */
RZ_LOG_ERROR("base36: encoder supports up to 64bit values (%d > 8 bytes)\n", len);
return false;
}
ut64 val = 0;
for (int i = 0; i < len; i++) {
val = (val << 8) | buf[i];
}
char *enc = rz_base36_encode_dyn(val);
if (!enc) {
return false;
}
out = (ut8 *)enc;
out_len = strlen(enc);
} else if (cry->dir == RZ_CRYPTO_DIR_DECRYPT) {
ut64 val = 0;
st64 ret = rz_base36_decode(&val, (const char *)buf, (size_t)len);
/* Here, rz_base36_decode returns 0 either for the value 0 or on error.
* We consider empty input an error. */
if (ret < 0) {
return false;
}
// convert val → bigendian byte array
ut8 tmp[8];
int idx = 8;
if (val == 0) {
tmp[--idx] = 0;
} else {
while (val > 0) {
tmp[--idx] = (ut8)(val & 0xff);
val >>= 8;
}
}
out_len = 8 - idx;
out = (ut8 *)malloc(out_len);
if (!out) {
return false;
}
memcpy(out, tmp + idx, out_len);
} else {
return false;
}
rz_crypto_append(cry, out, (int)out_len);
free(out);
return true;
}
static bool final(RzCrypto *cry, const ut8 *buf, int len) {
if (!buf || len == 0) {
return true;
}
return update(cry, buf, len);
}
RzCryptoPlugin rz_crypto_plugin_base36 = {
.name = "base36",
.author = "abcSup",
.license = "LGPL-3",
.description = "Base36 encoder/decoder",
.set_key = base36_set_key,
.get_key_size = base36_get_key_size,
.use = base36_use,
.update = update,
.final = final
};
#ifndef RZ_PLUGIN_INCORE
RZ_API RzLibStruct rizin_plugin = {
.type = RZ_LIB_TYPE_CRYPTO,
.data = &rz_crypto_plugin_base36,
.version = RZ_VERSION
};
#endif

View file

@ -0,0 +1,72 @@
// SPDX-FileCopyrightText: 2025 Ahmed Ibrahim <a.ibrahim8686@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_lib.h>
#include <rz_crypto.h>
#include <rz_util.h>
static bool base85_set_key(RzCrypto *cry, const ut8 *key, int keylen, int mode, int direction) {
cry->dir = direction;
return true;
}
static int base85_get_key_size(RzCrypto *cry) {
return 0;
}
static bool base85_use(const char *algo) {
return !strcmp(algo, "base85");
}
static bool update(RzCrypto *cry, const ut8 *buf, int len) {
if (!cry || !buf || len <= 0) {
return false;
}
ut8 *out = NULL;
size_t out_len = 0;
if (cry->dir == RZ_CRYPTO_DIR_ENCRYPT) {
char *enc = rz_base85_encode_dyn((char *)buf, (size_t)len, 0, 0, 1);
if (!enc) {
return false;
}
out = (ut8 *)enc;
out_len = strlen(enc);
} else if (cry->dir == RZ_CRYPTO_DIR_DECRYPT) {
out = (ut8 *)rz_base85_decode_dyn((const char *)buf, (st64)len, 0, 0, &out_len);
if (!out) {
return false;
}
} else {
return false;
}
rz_crypto_append(cry, out, (int)out_len);
free(out);
return true;
}
static bool final(RzCrypto *cry, const ut8 *buf, int len) {
if (!buf || len == 0) {
return true;
}
return update(cry, buf, len);
}
RzCryptoPlugin rz_crypto_plugin_base85 = {
.name = "base85",
.author = "Ahmed Ibrahim",
.license = "LGPL-3",
.description = "Base85 encoder/decoder",
.set_key = base85_set_key,
.get_key_size = base85_get_key_size,
.use = base85_use,
.update = update,
.final = final
};
#ifndef RZ_PLUGIN_INCORE
RZ_API RzLibStruct rizin_plugin = {
.type = RZ_LIB_TYPE_CRYPTO,
.data = &rz_crypto_plugin_base85,
.version = RZ_VERSION
};
#endif

View file

@ -65,7 +65,11 @@ rz_util_files = [
'rz_util/rz_asn1.h',
'rz_util/rz_assert.h',
'rz_util/rz_axml.h',
'rz_util/rz_base16.h',
'rz_util/rz_base32.h',
'rz_util/rz_base36.h',
'rz_util/rz_base64.h',
'rz_util/rz_base85.h',
'rz_util/rz_base91.h',
'rz_util/rz_big.h',
'rz_util/rz_bits.h',

View file

@ -108,6 +108,10 @@ RZ_API RZ_BORROW const RzCryptoPlugin *rz_crypto_plugin_by_index(RZ_NONNULL RzCr
#define RZ_CODEC_B64 1ULL
#define RZ_CODEC_B91 1ULL << 1
#define RZ_CODEC_PUNYCODE 1ULL << 2
#define RZ_CODEC_B85 1ULL << 3
#define RZ_CODEC_B36 1ULL << 4
#define RZ_CODEC_B32 1ULL << 5
#define RZ_CODEC_B16 1ULL << 6
#define RZ_CODEC_ALL 0xFFFF
#ifdef __cplusplus

View file

@ -29,7 +29,11 @@
#include "rz_util/rz_rbtree.h"
#include "rz_util/rz_intervaltree.h"
#include "rz_util/rz_big.h"
#include "rz_util/rz_base16.h"
#include "rz_util/rz_base32.h"
#include "rz_util/rz_base36.h"
#include "rz_util/rz_base64.h"
#include "rz_util/rz_base85.h"
#include "rz_util/rz_base91.h"
#include "rz_util/rz_buf.h"
#include "rz_util/rz_bits.h"

View file

@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: 2025 Ahmed Ibrahim <a.ibrahim8686@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#ifndef RZ_BASE16_H
#define RZ_BASE16_H
#include <rz_types.h>
#ifdef __cplusplus
extern "C" {
#endif
RZ_API int rz_base16_encode(RZ_OUT RZ_NONNULL char *bout, RZ_NONNULL const ut8 *bin, size_t sz);
RZ_API int rz_base16_decode(RZ_OUT RZ_NONNULL ut8 *bout, RZ_NONNULL const char *bin);
RZ_API RZ_OWN char *rz_base16_encode_dyn(RZ_NONNULL const ut8 *bin, size_t sz);
RZ_API RZ_OWN ut8 *rz_base16_decode_dyn(RZ_NONNULL const char *in, st64 len);
#ifdef __cplusplus
}
#endif
#endif // RZ_BASE16_H

View file

@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: 2025 Ahmed Ibrahim <a.ibrahim8686@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#ifndef RZ_BASE32_H
#define RZ_BASE32_H
#include <rz_types.h>
#ifdef __cplusplus
extern "C" {
#endif
RZ_API size_t rz_base32_encode(RZ_OUT RZ_NONNULL char *bout, RZ_NONNULL const ut8 *bin, size_t sz);
RZ_API st64 rz_base32_decode(RZ_OUT RZ_NONNULL ut8 *bout, RZ_NONNULL const char *bin, st64 len);
RZ_API RZ_OWN char *rz_base32_encode_dyn(RZ_NONNULL const ut8 *bin, size_t sz);
RZ_API RZ_OWN ut8 *rz_base32_decode_dyn(RZ_NONNULL const char *in, st64 len);
#ifdef __cplusplus
}
#endif
#endif // RZ_BASE32_H

View file

@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: 2025 Ahmed Ibrahim <a.ibrahim8686@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#ifndef RZ_BASE36_H
#define RZ_BASE36_H
#include <rz_types.h>
#ifdef __cplusplus
extern "C" {
#endif
RZ_API size_t rz_base36_encode(RZ_OUT RZ_NONNULL char *bout, ut64 val);
RZ_API RZ_OWN char *rz_base36_encode_dyn(ut64 val);
RZ_API st64 rz_base36_decode(RZ_OUT RZ_NONNULL ut64 *bout, RZ_NONNULL const char *bin, st64 len);
RZ_API RZ_OWN ut64 *rz_base36_decode_dyn(RZ_NONNULL const char *bin, const size_t len);
#ifdef __cplusplus
}
#endif
#endif // RZ_BASE36_H

View file

@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: 2025 Ahmed Ibrahim <a.ibrahim8686@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#ifndef RZ_BASE85_H
#define RZ_BASE85_H
#include <rz_types.h>
#ifdef __cplusplus
extern "C" {
#endif
RZ_API int rz_base85_encode(RZ_OUT RZ_NONNULL char *dest, RZ_NONNULL const char *src, size_t n, int delims, int wrap, int y_abbr);
RZ_API st64 rz_base85_decode(RZ_OUT RZ_NONNULL char *dest, RZ_NONNULL const char *src, st64 len, int delims, int ignore_garbage);
RZ_API RZ_OWN char *rz_base85_encode_dyn(RZ_NONNULL const char *src, size_t n, int delims, int wrap, int y_abbr);
RZ_API RZ_OWN char *rz_base85_decode_dyn(RZ_NONNULL const char *src, st64 len, int delims, int ignore_garbage, size_t *out_len);
#ifdef __cplusplus
}
#endif
#endif // RZ_BASE85_H

147
librz/util/base16.c Normal file
View file

@ -0,0 +1,147 @@
// 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;
}

354
librz/util/base32.c Normal file
View file

@ -0,0 +1,354 @@
// 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 NULallocates 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;
}

149
librz/util/base36.c Normal file
View file

@ -0,0 +1,149 @@
// 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;
}

View file

@ -1,12 +1,14 @@
// SPDX-FileCopyrightText: 2011 Remy Oukaour
// SPDX-FileCopyrightText: 2017 pancake
// SPDX-FileCopyrightText: 2025 Ahmed Ibrahim
// SPDX-License-Identifier: MIT
/*
* ascii85 - Ascii85 encode/decode data and print to standard output
* ascii85 - Ascii85 encode/decode data
*
* Copyright (C) 2011 Remy Oukaour
* Updated by pancake in 2017
* Updated by Ahmed Ibrahim in 2025
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -30,142 +32,398 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <rz_types.h>
#include <rz_util.h>
static int getc_nospace(FILE *f) {
int c;
while (isspace(c = getc(f))) {
;
/** \internal
* \brief Return the next nonwhitespace character from an inmemory buffer.
*
* \param src Pointer to the start of the Ascii85 text.
* \param len Total length of the buffer in bytes.
* \param pos Pointer to the current cursor index; on return it is advanced
* past the character that was delivered.
*
* \return The first nonspace character at or after \p *pos, or <tt>EOF</tt>
* when the cursor reaches \p len. Whitespace is determined by the C librarys \c isspace() predicate.
*
*/
static int getc_nospace_buf(const char *src, st64 len, st64 *pos) {
while (*pos < len && isspace((unsigned char)src[*pos])) {
(*pos)++;
}
return c;
if (*pos >= len) {
return EOF;
}
return (unsigned char)src[(*pos)++];
}
static void putc_wrap(char c, int wrap, int *len) {
if (wrap && *len >= wrap) {
putchar('\n');
*len = 0;
/** \internal
* \brief Append a character to an output buffer, inserting a newline when the wrap width is reached.
*
* \param d Pointer to the current write cursor inside the destination buffer.
* The pointer is advanced as characters are written.
* \param len Pointer to a running count of bytes already written; incremented by this function.
* \param wrap Maximum printablecharacter column before a automatic newline is inserted.
* A value of \c 0 disables wrapping entirely.
* \param col Pointer to the column counter (characters since the last newline); reset when a wrap occurs.
* \param c The character to append.
*
* If \p wrap is nonzero and the column counter referenced by \p col is
* greater than or equal to \p wrap, the function first appends a newline
* to the buffer and resets \p *col to 0. It then appends \p c, advancing \p *d, and increments both \p *len
* and \p *col to reflect the newly written character.
*/
static void putc_wrap_buf(char **d, size_t *len, int wrap, int *col, char c) {
if (wrap && *col >= wrap) {
*(*d)++ = '\n';
(*len)++;
*col = 0;
}
putchar(c);
*(*d)++ = c;
(*len)++;
(*col)++;
}
static void encode_tuple(unsigned long tuple, int count, int wrap, int *plen, int y_abbr) {
int i, lim;
char out[5];
/** \internal
* \brief Encoding up to four input bytes and append their Ascii85 representation to a buffer.
*
* \param tuple A bigendian 32bit value holding the pending input bytes.
* \param count Number of meaningful bytes inside \p tuple (14).
* A value of4 represents a full 32bit group; smaller values
* occur only for the final, partial block at endofinput.
* \param wrap Maximum printablecolumn width before a newline is inserted;
* a value of0 disables automatic wrapping.
* \param d Pointer to the current write cursor inside the destination
* buffer. The cursor is advanced as characters are written.
* \param len Pointer to a running total of bytes written so far; incremented
* by this function.
* \param col Pointer to the current column counter (characters since the
* last newline); reset to0 whenever a wrap occurs.
* \param y_abbr When nonzero, emit the nonstandard abbreviation
* <tt>'y'</tt> for four space characters (<tt>0x20202020</tt>).
*
* **Abbreviations**
* - A tuple of 0 (<tt>count==4</tt>) is emitted as a single <tt>'z'</tt>.
* - With \p y_abbr 0, the tuple 0x20202020 (<tt>count==4</tt>) is emitted
* as a single <tt>'y'</tt> (Adobe extension).
*
* **General case**
* Otherwise the 32bit value is converted to five base85 digits.
* For partial final blocks only the leading <tt>count+1</tt> digits are
* appended, exactly matching the Adobe/ RFC 1924 padding rule.
*/
static void encode_tuple_buf(unsigned long tuple, int count, int wrap, char **d, size_t *len, int *col, int y_abbr) {
if (tuple == 0 && count == 4) {
putc_wrap('z', wrap, plen);
putc_wrap_buf(d, len, wrap, col, 'z');
} else if (tuple == 0x20202020 && count == 4 && y_abbr) {
putc_wrap('y', wrap, plen);
putc_wrap_buf(d, len, wrap, col, 'y');
} else {
for (i = 0; i < 5; i++) {
out[i] = tuple % 85 + '!';
char out[5];
for (int i = 0; i < 5; i++) {
out[i] = (tuple % 85) + '!';
tuple /= 85;
}
lim = 4 - count;
for (i = 4; i >= lim; i--) {
putc_wrap(out[i], wrap, plen);
int lim = 4 - count;
for (int i = 4; i >= lim; i--) {
putc_wrap_buf(d, len, wrap, col, out[i]);
}
}
}
RZ_API void rz_base85_decode_tuple(unsigned long tuple, int count) {
int i;
for (i = 1; i < count; i++) {
putchar(tuple >> ((4 - i) * 8));
/** \internal
* \brief Calculate the buffer size required for an Ascii85 encoding.
*
* \param n The length of the binary input in bytes.
* \param delims Nonzero if the encoding will be wrapped in the Adobe
* delimiters <code>"<~"</code> and <code>"~>"</code>.
* \param wrap Linewrap column width. A value of0 disables wrapping.
* \return The number of bytes needed to store the encoded text
* *including* the terminating NUL.
*
* The formula derives from:
*
* * **Expansion** Each full 4byte block produces 5 digits.
* A partial final block of <i>r</i>{1,2,3} bytes produces <i>r+1</i> digits.
* * **Delimiters** When \p delims 0, exactly four characters
* (<tt><~</tt> and <tt>~></tt>) are added.
* * **Line breaks** If \p wrap &gt; 0, the encoder inserts a newline
* before every byte whose position would otherwise push the current
* column counter beyond \p wrap. The number of newline characters is
* therefore <code>(total_chars ? (total_chars 1) / wrap : 0)</code>.
*
* Finally one extra byte is added for the NUL terminator.
*/
static size_t rz_base85_enc_buflen(size_t n, int delims, int wrap) {
size_t full = n / 4;
size_t rem = n % 4;
size_t digits = 5 * full + (rem ? rem + 1 : 0);
size_t chars = digits + (delims ? 4 : 0);
if (wrap > 0 && chars > 0) {
size_t newlines = (chars - 1) / (size_t)wrap;
chars += newlines;
}
return chars + 1;
}
/** \internal
* \brief Append up to four bytes decoded from an Ascii85 tuple to a buffer.
*
* \param tuple The 32bit value obtained by accumulating five base85 digits.
* \param count Number of digits that were present in the source group
* (25). A full group (5digits) produces four bytes,
* while a partial final group produces \p count1 bytes.
* \param d Pointer to the current write cursor inside the destination
* buffer; advanced as bytes are written.
* \param len Pointer to a running total of bytes written so far; incremented
* by this function.
*
* The function starts with the mostsignificant decoded byte and appends up to
* four bytes to the buffer referenced by \p d.
*/
static void decode_tuple_buf(unsigned long tuple, size_t count, char **d, size_t *len) {
for (size_t i = 1; i < count; i++) {
*(*d)++ = (char)(tuple >> ((4 - i) * 8));
(*len)++;
}
}
RZ_API void rz_base85_encode(FILE *fp, int delims, int wrap, int y_abbr) {
int c, count = 0, len = 0;
unsigned long tuple = 0;
if (delims) {
putc_wrap('<', wrap, &len);
putc_wrap('~', wrap, &len);
/** \internal
* \brief Compute the buffer size required to hold the decoded bytes.
*
* \param enc_len The length, in characters, of the Ascii85 text that will be
* passed to the decoder <b>after</b> stripping any
* delimiters, linebreaks, or whitespace.
*
* \return A value large enough to store every possible decoding of a string
* of length \p enc_len, <em>plus</em> one extra byte for a NUL
* terminator.
*/
static size_t rz_base85_dec_buflen(size_t enc_len) {
/* Upper bound: each Ascii85 char can produce up to 4 bytes, plus NUL */
return 1 + 4 * enc_len;
}
/**
* \brief Base-encode a memory buffer to Ascii 85.
*
* \param dest Destination buffer that receives the encoded text.
* \param src Pointer to the binary data to encode.
* \param n The length of \p src in bytes.
* \param delims When non-zero, wrap the output between literal
* <code>"<~"</code> and <code>"~>"</code>.
* \param wrap Column width for automatic line wrapping.
* A value of 0 disables wrapping.
* \param y_abbr When non-zero, enable the non-standard abbreviation
* <code>'y'</code> for the pattern 0x20 0x20 0x20 0x20.
* \return The number of characters written to \p dest (excluding the
* terminating NUL), or \c 0 if either \p dest or \p src is <code>NULL</code>.
*
* \attention The caller must allocate at least \c 1 + 5 × ((n + 3)/4) bytes,
* plus room for optional delimiters and line-wrap newlines.
*
* The function processes the input four bytes at a time, converts each group
* into five base-85 digits, and appends the result to \p dest. Special-case
* abbreviations are emitted where possible:
* - <code>'z'</code> for four zero bytes,
* - <code>'y'</code> for four space bytes when \p y_abbr is true.
*/
RZ_API int rz_base85_encode(RZ_OUT RZ_NONNULL char *dest, RZ_NONNULL const char *src, size_t n, int delims, int wrap, int y_abbr) {
if (!dest || !src) {
return 0;
}
for (;;) {
c = getc(fp);
if (c != EOF) {
tuple |= c << ((3 - count++) * 8);
if (count < 4) {
char *d = dest;
size_t out = 0;
int col = 0;
if (delims) {
putc_wrap_buf(&d, &out, wrap, &col, '<');
putc_wrap_buf(&d, &out, wrap, &col, '~');
}
unsigned long tuple = 0;
int count = 0;
for (size_t i = 0; i < n || count > 0;) {
if (i < n) {
tuple |= (unsigned long)src[i++] << ((3 - count) * 8);
if (++count < 4) {
continue;
}
} else if (count == 0) {
break;
}
encode_tuple(tuple, count, wrap, &len, y_abbr);
if (c == EOF) {
break;
}
encode_tuple_buf(tuple, count, wrap, &d, &out, &col, y_abbr);
tuple = 0;
count = 0;
}
if (delims) {
putc_wrap('~', wrap, &len);
putc_wrap('>', wrap, &len);
putc_wrap_buf(&d, &out, wrap, &col, '~');
putc_wrap_buf(&d, &out, wrap, &col, '>');
}
*d = '\0'; /* NULterminate the Cstring */
return (int)out;
}
RZ_API bool rz_base85_decode(FILE *fp, int delims, int ignore_garbage) {
int c, count = 0, end = 0;
unsigned long tuple = 0, pows[] = { 85 * 85 * 85 * 85, 85 * 85 * 85, 85 * 85, 85, 1 };
while (delims) {
c = getc_nospace(fp);
if (c == '<') {
c = getc_nospace(fp);
if (c == '~') {
break;
}
ungetc(c, fp);
} else if (c == EOF) {
eprintf("ascii85: missing <~");
return false;
/**
* \brief Dynamically allocate and return an Ascii85 encoding.
*
* \param src Pointer to the binary data to encode.
* \param n The length of \p src in bytes.
* \param delims Nonzero to surround the payload with <tt>"<~"</tt> <tt>"~>"</tt>.
* \param wrap Column width for automatic line wrapping (0=none).
* \param y_abbr Nonzero to enable the <tt>'y'</tt> abbreviation for four spaces.
*
* \return Pointer to a freshlyallocated, NULterminated string containing
* the Ascii85 representation, or <code>NULL</code> if either
* <code>src == NULL</code> or a memory allocation fails.
*/
RZ_API RZ_OWN char *rz_base85_encode_dyn(RZ_NONNULL const char *src, size_t n, int delims, int wrap, int y_abbr) {
rz_return_val_if_fail(src, NULL);
char *ret = (char *)malloc(rz_base85_enc_buflen(n, delims, wrap));
if (!ret) {
return NULL;
}
rz_base85_encode(ret, src, n, delims, wrap, y_abbr);
return ret;
}
/**
* \brief Decode an Ascii85 string held in memory.
*
* \param[out] dest Destination buffer that receives the decoded bytes.
* \param[in] src Pointer to the Ascii85 text. Need not be NULterminated.
* \param len Number of characters in \p src; pass 1 to decode up to
* the first <code>'\0'</code>.
* \param delims When nonzero, require the Adobe delimiters
* <tt>"<~"</tt>  <tt>"~>"</tt>.
* \param ignore_garbage When nonzero, silently skip bytes outside the legal
* digit range; otherwise such bytes trigger an error.
*
* \return The number of decoded bytes on success, or 1 on error
* (malformed input or delimiter mismatch).
*
* \attention The caller must allocate \p dest large enough. A safe upper bound is
* \c 1 + 3×((len + 1)/4) bytes.
*/
RZ_API st64 rz_base85_decode(RZ_OUT RZ_NONNULL char *dest, RZ_NONNULL const char *src, st64 len, int delims, int ignore_garbage) {
rz_return_val_if_fail(dest, -1);
rz_return_val_if_fail(src, -1);
if (len < 0) {
len = (st64)strlen(src);
}
st64 pos = 0;
int count = 0;
int have_end = 0;
size_t out = 0;
unsigned long tuple = 0;
const unsigned long pows[5] = {
85u * 85u * 85u * 85u,
85u * 85u * 85u,
85u * 85u,
85u,
1u
};
if (delims) {
int c = getc_nospace_buf(src, len, &pos);
if (c != '<' || getc_nospace_buf(src, len, &pos) != '~') {
RZ_LOG_ERROR("ascii85: missing <~\n");
return -1;
}
}
for (;;) {
c = getc_nospace(fp);
int c = getc_nospace_buf(src, len, &pos);
if (c == 'z' && count == 0) {
rz_base85_decode_tuple(0, 5);
decode_tuple_buf(0, 5, &dest, &out);
continue;
}
if (c == 'y' && count == 0) {
rz_base85_decode_tuple(0x20202020, 5);
decode_tuple_buf(0x20202020, 5, &dest, &out);
continue;
}
if (c == '~' && delims) {
c = getc_nospace(fp);
if (c != '>') {
eprintf("ascii85: ~ without >\n");
return false;
if (getc_nospace_buf(src, len, &pos) != '>') {
RZ_LOG_ERROR("ascii85: '~' not followed by '>'\n");
return -1;
}
c = EOF;
end = 1;
have_end = 1;
break;
}
if (c == EOF) {
if (delims && !end) {
eprintf("ascii85: missing ~>");
return false;
}
if (count > 0) {
tuple += pows[count - 1];
rz_base85_decode_tuple(tuple, count);
}
break;
}
if (c < '!' || c > 'u') {
if (ignore_garbage) {
continue;
}
eprintf("ascii85: invalid character '%c'\n", c);
return false;
RZ_LOG_ERROR("ascii85: invalid character '%c'\n", c);
return -1;
}
tuple += (c - '!') * pows[count++];
tuple += (unsigned long)(c - '!') * pows[count++];
if (count == 5) {
rz_base85_decode_tuple(tuple, count);
decode_tuple_buf(tuple, count, &dest, &out);
tuple = 0;
count = 0;
}
}
return true;
if (delims && !have_end) {
RZ_LOG_ERROR("ascii85: missing ~>\n");
return -1;
}
if (count > 0) { /* partial final group */
tuple += pows[count - 1]; /* implicit 'u' padding */
decode_tuple_buf(tuple, count, &dest, &out);
}
return (st64)out;
}
/**
* \brief Decode an Ascii85 string, allocating the output buffer automatically.
*
* \param src Ascii85 text to decode (may contain NULbytes).
* \param len The length of \p src in bytes, or 1 to use strlen().
* \param delims Expect <tt>"<~"</tt> <tt>"~>"</tt> delimiters.
* \param ignore_garbage Skip vs. error on invalid characters.
* \param[out] out_len Optional pointer that receives the decoded size.
*
* \return Pointer to a newly allocated buffer containing the raw bytes,
* or \c NULL on error. The buffer is NULterminated for convenience.
*/
RZ_API RZ_OWN char *rz_base85_decode_dyn(RZ_NONNULL const char *src, st64 len, int delims, int ignore_garbage, size_t *out_len) {
rz_return_val_if_fail(src, NULL);
if (len < 0) {
len = (st64)strlen(src);
}
size_t cap = rz_base85_dec_buflen(len);
char *buf = (char *)malloc(cap);
if (!buf) {
return NULL;
}
st64 written = rz_base85_decode(buf, src, len, delims, ignore_garbage);
if (written < 0) {
free(buf);
return NULL;
}
/* shrinktofit and NULterminate */
if (written + 1 < (st64)cap) {
char *tmp = (char *)realloc(buf, (size_t)written + 1);
if (tmp) {
buf = tmp;
}
}
buf[written] = '\0';
if (out_len) {
*out_len = (size_t)written;
}
return buf;
}

View file

@ -456,6 +456,19 @@ RZ_API int rz_hex_pair2bin(const char *arg) {
return (int)c;
}
/**
* \brief Convert binary data to a lowercase hexadecimal string.
* \param[in] in Pointer to the binary input.
* \param[in] len Number of bytes in \p in. Must be non-negative.
* \param[out] out Buffer to receive NUL-terminated hexadecimal output.
* Must have at least \c (2 × len) + 1 bytes.
* \return Number of bytes processed (same as \p len), or \c 0 if \p len
* is negative.
*
* This function encodes each input byte as two lowercase hexadecimal
* characters (e.g., \c 0xAF becomes "af") using \c snprintf() and writes
* them sequentially to the output buffer, followed by a NUL terminator.
*/
RZ_API int rz_hex_bin2str(const ut8 *in, int len, char *out) {
int i, idx;
char tmp[8];

View file

@ -8,8 +8,6 @@ rz_util_common_sources = [
'assert.c',
'astr.c',
'axml.c',
'base85.c',
'base91.c',
'bitvector.c',
'buf.c',
'calc.c',
@ -73,7 +71,12 @@ rz_util_common_sources = [
'thread_types.c',
'time.c',
'tree.c',
'ubase64.c',
'base16.c',
'base32.c',
'base36.c',
'base64.c',
'base85.c',
'base91.c',
'uleb128.c',
'unum.c',
'utf16.c',

View file

@ -39,48 +39,6 @@ typedef struct iobnet_t {
RzCrypto *crypto;
} iobnet_t;
// 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 };
static ut64 base36_decode(const char *str) {
ut64 ret = 0;
size_t i;
size_t len = strlen(str);
// 64-bit base36 str has at most 13 characters
if (len > 13) {
eprintf("Error: base36_decode supports up to 64-bit values only\n");
return 0;
}
for (i = 0; i < len; i++) {
char c = str[len - i - 1];
// "01234567890abcdefghijklmnopqrstuvwxyz"
if (c < '0' || c > 'z' || ('9' < c && c < 'a')) {
eprintf("Error: %s is not a valid base36 encoded string\n", str);
return 0;
}
ut8 v = d32[c - '0'];
// Character does not exist in base36 encoding
if (v == '$') {
eprintf("Error: %s is not a valid base36 encoded string\n", str);
return 0;
}
v -= 91;
// Check for overflow
if (i == 12) {
if (v > 3 || UT64_ADD_OVFCHK(ret, v * pow36[i])) {
printf("Error: base36_decode supports up to 64-bit values only\n");
return 0;
}
}
ret += v * pow36[i];
}
return ret;
}
/*
* @brief Initialize the key for enc/decrypting KDNet packet with the type Data.
*
@ -147,7 +105,11 @@ static void *iob_net_open(const char *path) {
if (nkey) {
*nkey++ = 0;
}
rz_write_le64(obj->key + i * 8, base36_decode(key));
ut64 decoded_val = 0;
st64 ret = rz_base36_decode(&decoded_val, key, strlen(key));
decoded_val = ret >= 0 ? decoded_val : 0;
rz_write_le64(obj->key + i * 8, (ut64)decoded_val);
}
// HMAC Key is the negation of AES-256 Control Key bytes

View file

@ -282,16 +282,20 @@ ED____ rc4 LGPL-3 pancake RC4 symmetric-ke
ED____ rc6 LGPL-3 rakholiyajenish.07 RC6 symmetric-key block cipher
_D____ ror LGPL-3 pancake Rotate Right symmetric-key block cipher
__ed__ base64 LGPL-3 rakholiyajenish.07 Base64 encoder/decoder
ED____ xor LGPL-3 pancake XOR symmetric-key block cipher
__ed__ base32 LGPL-3 Ahmed Ibrahim Base32 encoder/decoder
ED____ rc2 LGPL-3 lionaneesh RC2 symmetric-key block cipher
ED____ xor LGPL-3 pancake XOR symmetric-key block cipher
ED____ cps2 LGPL-3 pancake,esanfelix,pof Capcom Play System 2 (CPS-2) symmetric-key block cipher
ED____ rot LGPL-3 pancake Caesar symmetric-key cipher
__ed__ punycode LGPL-3 pancake Punycode encoder/decoder
ED____ serpent-ecb LGPL-3 NicsTr Serpent symmetric-key block cipher (ECB block mode)
ED____ sm4-ecb LGPL-3 0xSh4dy ShangMi 4 symmetric-key block cipher (ECB block mode)
__ed__ base36 LGPL-3 abcSup Base36 encoder/decoder
__ed__ base85 LGPL-3 Ahmed Ibrahim Base85 encoder/decoder
ED____ aes-cbc LGPL-3 rakholiyajenish.07 AES symmetric-key block cipher (CBC block mode)
E_____ rol LGPL-3 pancake Rotate Left symmetric-key block cipher
ED____ aes-ecb LGPL3 Nettle project,pancake AES symmetric-key block cipher (ECB block mode)
__ed__ base16 LGPL-3 Ahmed Ibrahim Base16 encoder/decoder
ED____ des-ecb LGPL-3 deroad DES symmetric-key block cipher (ECB block mode)
ED____ blowfish LGPL3 kishorbhat Blowfish symmetric-key block cipher
__ed__ base91 LGPL-3 rakholiyajenish.07 Base91 encoder/decoder

View file

@ -23,7 +23,11 @@ if get_option('enable_tests')
'analysis_var',
'analysis_xrefs',
'annotated_code',
'base16',
'base32',
'base36',
'base64',
'base85',
'big',
'bin_lines',
'bin_mach0',

64
test/unit/test_base16.c Normal file
View file

@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: 2025 Ahmed Ibrahim <a.ibrahim8686@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_util.h>
#include "minunit.h"
bool test_rz_base16_decode_dyn(void) {
char *hello = (char *)rz_base16_decode_dyn("68656c6c6f", -1);
mu_assert_streq(hello, "hello", "base16_decode_dyn");
free(hello);
mu_end;
}
bool test_rz_base16_decode_even(void) {
ut8 buf[16];
int status = rz_base16_decode(buf, "68656c6c6f");
mu_assert_eq(status, 5, "even-length hex decoded length mismatch");
mu_assert_memeq(buf, (const ut8 *)"hello", 5, "even-length hex decoded bytes mismatch");
mu_assert_eq(buf[5], '\0', "missing NUL terminator");
mu_end;
}
bool test_rz_base16_decode_odd(void) {
ut8 buf[16];
int status = rz_base16_decode(buf, "68 65 6c 6c 6f 6");
mu_assert_eq(status, -6, "odd-length hex did not return negative length");
mu_assert_memeq(buf, (const ut8 *)"hello\x60", 6, "odd-length hex decoded bytes mismatch");
mu_assert_eq(buf[6], '\0', "missing NUL terminator");
mu_end;
}
bool test_rz_base16_decode_invalid(void) {
ut8 buf[16];
int status = rz_base16_decode(buf, "68656cZZ");
mu_assert_eq(status, 0, "invalid char should return 0");
mu_end;
}
bool test_rz_base16_encode_dyn(void) {
char *enc = rz_base16_encode_dyn((const ut8 *)"hello", 5);
mu_assert_streq(enc, "68656c6c6f", "encode_dyn mismatch");
free(enc);
mu_end;
}
bool test_rz_base16_encode(void) {
char enc[16];
rz_base16_encode(enc, (const ut8 *)"hello", 5);
mu_assert_streq(enc, "68656c6c6f", "encode mismatch");
mu_end;
}
int all_tests() {
mu_run_test(test_rz_base16_decode_dyn);
mu_run_test(test_rz_base16_decode_even);
mu_run_test(test_rz_base16_decode_odd);
mu_run_test(test_rz_base16_decode_invalid);
mu_run_test(test_rz_base16_encode_dyn);
mu_run_test(test_rz_base16_encode);
return tests_passed != tests_run;
}
mu_main(all_tests)

75
test/unit/test_base32.c Normal file
View file

@ -0,0 +1,75 @@
// SPDX-FileCopyrightText: 2025 Ahmed Ibrahim <a.ibrahim8686@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_util.h>
#include "minunit.h"
bool test_rz_base32_decode_dyn(void) {
char *hello = (char *)rz_base32_decode_dyn("NBSWY3DP", -1);
mu_assert_streq(hello, "hello", "base32_decode_dyn");
free(hello);
mu_end;
}
bool test_rz_base32_decode(void) {
ut8 *hello = malloc(50);
int status = rz_base32_decode(hello, "NBSWY3DP", -1);
mu_assert_eq(status, (int)strlen("hello"), "valid base32 decoding");
mu_assert_streq((char *)hello, "hello", "base32 decoding");
free(hello);
mu_end;
}
bool test_rz_base32_decode_invalid(void) {
ut8 buf[16];
mu_assert_eq(rz_base32_decode(buf, "MZXW@===", -1), -1, "decoder accepted invalid char");
mu_end;
}
bool test_rz_base32_encode_dyn(void) {
static const struct {
const char *in;
const char *exp;
} vec[] = {
{ "hello", "NBSWY3DP" },
{ "hello1", "NBSWY3DPGE======" },
{ "hello12", "NBSWY3DPGEZA====" },
{ "hello123", "NBSWY3DPGEZDG===" }
};
for (size_t i = 0; i < RZ_ARRAY_SIZE(vec); i++) {
char *enc = rz_base32_encode_dyn((const ut8 *)vec[i].in, strlen(vec[i].in));
mu_assert_streq(enc, vec[i].exp, "encode_dyn mismatch");
free(enc);
}
mu_end;
}
bool test_rz_base32_encode(void) {
char enc[32];
rz_base32_encode(enc, (const ut8 *)"hello", 5);
mu_assert_streq(enc, "NBSWY3DP", "encode mismatch");
mu_end;
}
bool test_rz_base32_decode_offby1(void) {
unsigned char msg[4] = { 'A', 0, 'B', 0 };
char enc[32] = { 0 };
rz_base32_encode(enc, msg, 1);
rz_base32_decode(msg, enc, strlen(enc));
mu_assert_eq(msg[2], 'B', "decoder wrote past end");
mu_end;
}
int all_tests() {
mu_run_test(test_rz_base32_decode_dyn);
mu_run_test(test_rz_base32_decode);
mu_run_test(test_rz_base32_decode_invalid);
mu_run_test(test_rz_base32_encode_dyn);
mu_run_test(test_rz_base32_encode);
mu_run_test(test_rz_base32_decode_offby1);
return tests_passed != tests_run;
}
mu_main(all_tests)

92
test/unit/test_base36.c Normal file
View file

@ -0,0 +1,92 @@
// SPDX-FileCopyrightText: 2025 Ahmed Ibrahim <a.ibrahim8686@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#include <limits.h>
#include <rz_util.h>
#include "minunit.h"
static const struct {
ut64 value;
const char *str;
} vectors[] = {
{ 0ULL, "0" },
{ 1ULL, "1" },
{ 35ULL, "z" },
{ 36ULL, "10" },
{ 125ULL, "3h" },
{ 123456789ULL, "21i3v9" },
{ UINT64_MAX, "3w5e11264sgsf" }
};
bool test_rz_base36_encode_dyn_basic(void) {
for (size_t i = 0; i < RZ_ARRAY_SIZE(vectors); i++) {
char *s = rz_base36_encode_dyn(vectors[i].value);
mu_assert_notnull(s, "encode returned NULL");
mu_assert_streq(s, vectors[i].str, "unexpected base36 encoding");
free(s);
}
mu_end;
}
bool test_rz_base36_decode_basic(void) {
for (size_t i = 0; i < RZ_ARRAY_SIZE(vectors); i++) {
ut64 out = 0;
ut64 ret = rz_base36_decode(&out, vectors[i].str, strlen(vectors[i].str));
mu_assert_neq(ret, -1, "decode failed unexpectedly");
mu_assert_eq(out, vectors[i].value, "base36 decode mismatch");
}
mu_end;
}
bool test_rz_base36_roundtrip_large(void) {
const ut64 x = 0xDEADBEEFCAFEBABEULL; /* arbitrary 64bit value */
char *enc = rz_base36_encode_dyn(x);
mu_assert_notnull(enc, "encode_dyn returned NULL");
ut64 out = 0;
st64 ret = rz_base36_decode(&out, enc, strlen(enc));
free(enc);
mu_assert_neq(ret, -1, "decode failed unexpectedly");
mu_assert_eq(out, x, "roundtrip encode/decode failed");
mu_end;
}
bool test_rz_base36_decode_invalid_char(void) {
/* '@' is not legal in [09AZ] */
const char bad[] = "1a@";
ut64 out = 0;
st64 ret = rz_base36_decode(&out, bad, strlen(bad));
mu_assert_eq(ret, -1, "decoder did not flag invalid char");
mu_end;
}
bool test_rz_base36_decode_overflow(void) {
/* "zzzzzzzzzzzzzz" (14×'z') = 36^14-1 > 2^64-1 */
const char huge[] = "zzzzzzzzzzzzzz";
ut64 out = 0;
ut64 ret = rz_base36_decode(&out, huge, strlen(huge));
mu_assert_eq(ret, -1, "decoder did not flag overflow");
mu_end;
}
bool test_rz_base36_decode_dyn(void) {
ut64 *out = rz_base36_decode_dyn("21i3v9", 6);
mu_assert_notnull(out, "decode_dyn returned NULL");
mu_assert_eq(*out, 123456789ULL, "decode_dyn mismatch");
free(out);
mu_end;
}
int all_tests(void) {
mu_run_test(test_rz_base36_encode_dyn_basic);
mu_run_test(test_rz_base36_decode_basic);
mu_run_test(test_rz_base36_roundtrip_large);
mu_run_test(test_rz_base36_decode_invalid_char);
mu_run_test(test_rz_base36_decode_overflow);
mu_run_test(test_rz_base36_decode_dyn);
return tests_passed != tests_run;
}
mu_main(all_tests)

119
test/unit/test_base85.c Normal file
View file

@ -0,0 +1,119 @@
// SPDX-FileCopyrightText: 2025 Ahmed Ibrahim <a.ibrahim8686@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_util.h>
#include "minunit.h"
bool test_rz_base85_encode_dyn_nodelims(void) {
const char *src = "hello, ascii85";
char *dest = rz_base85_encode_dyn(src, strlen(src), 0, 0, 1);
mu_assert_streq(dest, "BOu!rD_*#>F(8ou3&L", "ascii85 encode mismatch");
free(dest);
mu_end;
}
bool test_rz_base85_encode_dyn_delims(void) {
const char *src = "hello, ascii85";
char *dest = rz_base85_encode_dyn(src, strlen(src), 1, 0, 1);
mu_assert_streq(dest, "<~BOu!rD_*#>F(8ou3&L~>", "ascii85 encode mismatch");
free(dest);
mu_end;
}
bool test_rz_base85_decode_dyn_nodelims(void) {
const char *src = "BOu!rD_*#>F(8ou3&L";
char *dest = rz_base85_decode_dyn(src, strlen(src), 0, 0, NULL);
mu_assert_streq(dest, "hello, ascii85", "decoder output mismatch");
free(dest);
mu_end;
}
bool test_rz_base85_decode_dyn_delims(void) {
const char *src = "<~BOu!rD_*#>F(8ou3&L~>";
char *dest = rz_base85_decode_dyn(src, strlen(src), 1, 0, NULL);
mu_assert_streq(dest, "hello, ascii85", "ascii85 decode with delimiters mismatch");
free(dest);
mu_end;
}
bool test_rz_base85_encode_decode_wrap10(void) {
char src[51];
memset(src, 'A', 50);
src[50] = '\0';
char *encoded = rz_base85_encode_dyn(src, strlen(src), 0, 10, 0);
int col = 0, newlines = 0;
for (size_t i = 0; encoded[i]; i++) {
if (encoded[i] == '\n') {
mu_assert_eq(col, 10, "line length before \\n not 10");
col = 0;
newlines++;
} else {
col++;
}
}
mu_assert_true(col <= 10, "final line exceeds wrap length");
mu_assert_true(newlines > 0, "no line breaks were inserted");
size_t decoded_len;
char *decoded = rz_base85_decode_dyn(encoded, strlen(encoded), 0, 0, &decoded_len);
mu_assert_eq(decoded_len, 50U, "decoded length mismatch");
mu_assert_true(memcmp(decoded, src, 50) == 0, "decoded data mismatch");
free(encoded);
free(decoded);
mu_end;
}
bool test_rz_base85_encode_decode_z_abbrev(void) {
const ut8 zeros[4] = { 0, 0, 0, 0 };
char *enc = rz_base85_encode_dyn((const char *)zeros, 4, 0, 0, 0);
mu_assert_notnull(enc, "rz_base85_encode_dyn returned NULL");
mu_assert_streq(enc, "z", "encode zeros should be 'z'");
size_t decoded_len;
char *decoded = rz_base85_decode_dyn("zz", -1, 0, 0, &decoded_len);
mu_assert_notnull(decoded, "rz_base85_decode_dyn returned NULL");
mu_assert_eq(decoded_len, 8U, "decoded length != 8");
for (size_t i = 0; i < decoded_len; i++) {
mu_assert_eq(decoded[i], '\0', "decoded byte nonzero");
}
free(enc);
free(decoded);
mu_end;
}
bool test_rz_base85_decode_invalid_strict_vs_lenient(void) {
const char bad[] = "FCfN8v";
size_t decoded_len;
char *strict_out = rz_base85_decode_dyn(bad, -1, 0, 0, &decoded_len);
mu_assert_true(strict_out == NULL, "decoder accepted garbage in strict mode");
char *lenient_out = rz_base85_decode_dyn(bad, -1, 0, 1, &decoded_len);
mu_assert_notnull(lenient_out, "lenient decode returned NULL");
mu_assert_eq(decoded_len, 4U, "decoded length != 4");
mu_assert_true(memcmp(lenient_out, "test", 4) == 0, "lenient decode produced wrong bytes");
free(lenient_out);
mu_end;
}
int all_tests(void) {
mu_run_test(test_rz_base85_encode_dyn_nodelims);
mu_run_test(test_rz_base85_decode_dyn_nodelims);
mu_run_test(test_rz_base85_encode_dyn_delims);
mu_run_test(test_rz_base85_decode_dyn_delims);
mu_run_test(test_rz_base85_encode_decode_wrap10);
mu_run_test(test_rz_base85_encode_decode_z_abbrev);
mu_run_test(test_rz_base85_decode_invalid_strict_vs_lenient);
return tests_passed != tests_run;
}
mu_main(all_tests)