Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/github-actions-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,7 @@ jobs:
uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: Install test dependencies
run: sudo apt-get update && sudo apt-get install -y zlib1g-dev
- name: Run tests
run: ./scripts/test.sh
4 changes: 2 additions & 2 deletions .github/workflows/test-each-commit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ jobs:
- name: Show commit
run: git log -1 --oneline

- name: Install clang-format
run: apt-get update && apt-get install -y clang-format
- name: Install check dependencies
run: apt-get update && apt-get install -y clang-format zlib1g-dev

- name: Configure git identity (required by rebase)
run: |
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ simulator/sim_data/
# Tests
components/bbqr/test/test_bbqr
components/bbqr/test/test_base32
components/bbqr/test/test_miniz
components/deflate_codec/test/test_deflate_codec
main/core/test/test_derivation_path
main/core/test/test_ss_whitelist_parse
main/core/test/test_ss_whitelist_is_whitelisted
Expand Down
3 changes: 2 additions & 1 deletion components/bbqr/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ idf_component_register(
SRCS
"src/bbqr.c"
"src/base32.c"
"src/miniz.c"
INCLUDE_DIRS
"src"
PRIV_REQUIRES
deflate_codec
)
85 changes: 62 additions & 23 deletions components/bbqr/src/base32.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,27 +26,27 @@ static const int8_t BASE32_DECODE_TABLE[128] = {
};

size_t base32_encoded_len(size_t input_len) {
// Each 5 bytes becomes 8 characters
return ((input_len + 4) / 5) * 8;
// BBQr omits RFC 4648 padding, so partial groups use 2, 4, 5, or 7 chars.
static const uint8_t chars_per_remainder[] = {0, 2, 4, 5, 7};
size_t full_groups = input_len / 5;
size_t remainder_chars = chars_per_remainder[input_len % 5];
if (full_groups > (SIZE_MAX - remainder_chars) / 8) {
return 0;
}
return full_groups * 8 + remainder_chars;
}

size_t base32_decoded_len(size_t input_len) {
// Each 8 characters becomes 5 bytes (maximum)
return (input_len * 5) / 8;
}

size_t base32_encode(const uint8_t *input, size_t input_len, char *output,
size_t output_size) {
if (!input || !output || input_len == 0) {
return 0;
}

size_t encoded_len = base32_encoded_len(input_len);
if (output_size < encoded_len + 1) {
return 0;
bool base32_encode_write(const uint8_t *input, size_t input_len,
base32_write_fn write, void *context) {
if (!input || !write || input_len == 0) {
return false;
}

size_t out_idx = 0;
size_t in_idx = 0;

// Process input in groups of 5 bytes -> 8 chars
Expand All @@ -60,28 +60,67 @@ size_t base32_encode(const uint8_t *input, size_t input_len, char *output,
bytes_in_group++;
}

// Calculate how many base32 chars we need for this group
// 1 byte (8 bits) -> 2 chars
// 2 bytes (16 bits) -> 4 chars
// 3 bytes (24 bits) -> 5 chars
// 4 bytes (32 bits) -> 7 chars
// 5 bytes (40 bits) -> 8 chars
static const int chars_per_bytes[] = {0, 2, 4, 5, 7, 8};
int num_chars = chars_per_bytes[bytes_in_group];
char encoded[8];

for (int i = 0; i < num_chars; i++) {
int idx = (buffer >> (35 - i * 5)) & 0x1F;
encoded[i] = BASE32_ALPHABET[idx];
}

// Extract 5-bit groups from high bits
for (int i = 0; i < 8; i++) {
if (i < num_chars) {
int idx = (buffer >> (35 - i * 5)) & 0x1F;
output[out_idx++] = BASE32_ALPHABET[idx];
} else {
output[out_idx++] = '=';
}
if (!write(context, encoded, (size_t)num_chars)) {
return false;
}
}

output[out_idx] = '\0';
return out_idx;
return true;
}

typedef struct {
char *output;
size_t output_size;
size_t output_len;
} base32_buffer_writer_t;

static bool write_to_buffer(void *context, const char *data, size_t len) {
base32_buffer_writer_t *writer = (base32_buffer_writer_t *)context;
if (len > writer->output_size - writer->output_len - 1) {
return false;
}

memcpy(writer->output + writer->output_len, data, len);
writer->output_len += len;
return true;
}

size_t base32_encode(const uint8_t *input, size_t input_len, char *output,
size_t output_size) {
if (!input || !output || input_len == 0) {
return 0;
}

size_t encoded_len = base32_encoded_len(input_len);
if (output_size < encoded_len + 1) {
return 0;
}

base32_buffer_writer_t writer = {
.output = output,
.output_size = output_size,
.output_len = 0,
};
if (!base32_encode_write(input, input_len, write_to_buffer, &writer)) {
return 0;
}

output[writer.output_len] = '\0';
return writer.output_len;
}

bool base32_decode(const char *input, size_t input_len, uint8_t *output,
Expand Down
18 changes: 17 additions & 1 deletion components/bbqr/src/base32.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
#include <stddef.h>
#include <stdint.h>

/**
* @brief Callback used by base32_encode_write().
*
* @return true to continue encoding, false to stop with failure.
*/
typedef bool (*base32_write_fn)(void *context, const char *data, size_t len);

/**
* @brief Calculate the encoded length for base32 encoding
*
Expand All @@ -25,7 +32,8 @@ size_t base32_decoded_len(size_t input_len);
* @brief Encode binary data to base32 (RFC 4648)
*
* Uses the standard base32 alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZ234567
* Output is uppercase and includes padding with '=' characters.
* Output is uppercase and omits padding as required by BBQr. The resulting
* characters are all valid in QR alphanumeric mode.
*
* @param input Binary data to encode
* @param input_len Length of input data in bytes
Expand All @@ -37,6 +45,14 @@ size_t base32_decoded_len(size_t input_len);
size_t base32_encode(const uint8_t *input, size_t input_len, char *output,
size_t output_size);

/**
* @brief Encode binary data to unpadded Base32 through a callback.
*
* The callback receives groups of at most eight QR-alphanumeric characters.
*/
bool base32_encode_write(const uint8_t *input, size_t input_len,
base32_write_fn write, void *context);

/**
* @brief Decode base32 string to binary data (RFC 4648)
*
Expand Down
Loading
Loading