* [PATCH v3 0/6] lib/base64: add generic encoder/decoder, migrate users
@ 2025-09-26 6:52 Guan-Chun Wu
2025-09-26 6:55 ` [PATCH v3 1/6] lib/base64: Add support for multiple variants Guan-Chun Wu
` (5 more replies)
0 siblings, 6 replies; 31+ messages in thread
From: Guan-Chun Wu @ 2025-09-26 6:52 UTC (permalink / raw)
To: akpm, ebiggers, tytso, jaegeuk, xiubli, idryomov, kbusch, axboe,
hch, sagi
Cc: visitorckw, 409411716, home7438072, linux-nvme, linux-fscrypt,
ceph-devel, linux-kernel
This series introduces a generic Base64 encoder/decoder to the kernel
library, eliminating duplicated implementations and delivering significant
performance improvements.
The Base64 API has been extended to support multiple variants (Standard,
URL-safe, and IMAP) as defined in RFC 4648 and RFC 3501. The API now takes
a variant parameter and an option to control padding. As part of this
series, users are migrated to the new interface while preserving their
specific formats: fscrypt now uses BASE64_URLSAFE, Ceph uses BASE64_IMAP,
and NVMe is updated to BASE64_STD.
On the encoder side, the implementation processes input in 3-byte blocks,
mapping 24 bits directly to 4 output symbols. This avoids bit-by-bit
streaming and reduces loop overhead, achieving about a 2.7x speedup compared
to previous implementations.
On the decoder side, replace strchr() lookups with per-variant reverse tables
and process input in 4-character groups. Each group is mapped to numeric values
and combined into 3 bytes. Padded and unpadded forms are validated explicitly,
rejecting invalid '=' usage and enforcing tail rules. This improves throughput
by ~23-28x.
Thanks,
Guan-Chun Wu
---
v2 -> v3:
- lib/base64: introduce enum base64_variant { BASE64_STD, BASE64_URLSAFE,
BASE64_IMAP } and change the API to take this enum instead of a
caller-supplied 64-character table.
- lib/base64: add per-variant reverse lookup tables and update the decoder
to use table lookups instead of strchr().
- tests: add URLSAFE/IMAP checks (no '=' allowed) and keep STD as the
main corpus.
- users: update call sites to explicit variants (fscrypt=BASE64_URLSAFE,
ceph=BASE64_IMAP, nvme-auth=BASE64_STD) while preserving formats.
---
Guan-Chun Wu (4):
lib/base64: rework encode/decode for speed and stricter validation
lib: add KUnit tests for base64 encoding/decoding
fscrypt: replace local base64url helpers with lib/base64
ceph: replace local base64 helpers with lib/base64
Kuan-Wei Chiu (2):
lib/base64: Add support for multiple variants
lib/base64: Optimize base64_decode() with reverse lookup tables
drivers/nvme/common/auth.c | 4 +-
fs/ceph/crypto.c | 60 +-------
fs/ceph/crypto.h | 6 +-
fs/ceph/dir.c | 5 +-
fs/ceph/inode.c | 2 +-
fs/crypto/fname.c | 89 +----------
include/linux/base64.h | 10 +-
lib/Kconfig.debug | 19 ++-
lib/base64.c | 241 +++++++++++++++++++++++-------
lib/tests/Makefile | 1 +
lib/tests/base64_kunit.c | 294 +++++++++++++++++++++++++++++++++++++
11 files changed, 525 insertions(+), 206 deletions(-)
create mode 100644 lib/tests/base64_kunit.c
--
2.34.1
^ permalink raw reply [flat|nested] 31+ messages in thread
* [PATCH v3 1/6] lib/base64: Add support for multiple variants
2025-09-26 6:52 [PATCH v3 0/6] lib/base64: add generic encoder/decoder, migrate users Guan-Chun Wu
@ 2025-09-26 6:55 ` Guan-Chun Wu
2025-09-30 23:56 ` Caleb Sander Mateos
2025-09-26 6:55 ` [PATCH v3 2/6] lib/base64: Optimize base64_decode() with reverse lookup tables Guan-Chun Wu
` (4 subsequent siblings)
5 siblings, 1 reply; 31+ messages in thread
From: Guan-Chun Wu @ 2025-09-26 6:55 UTC (permalink / raw)
To: 409411716
Cc: akpm, axboe, ceph-devel, ebiggers, hch, home7438072, idryomov,
jaegeuk, kbusch, linux-fscrypt, linux-kernel, linux-nvme, sagi,
tytso, visitorckw, xiubli
From: Kuan-Wei Chiu <visitorckw@gmail.com>
Extend the base64 API to support multiple variants (standard, URL-safe,
and IMAP) as defined in RFC 4648 and RFC 3501. The API now takes a
variant parameter and an option to control padding. Update NVMe auth
code to use the new interface with BASE64_STD.
Signed-off-by: Kuan-Wei Chiu <visitorckw@gmail.com>
Co-developed-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
Signed-off-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
---
drivers/nvme/common/auth.c | 4 ++--
include/linux/base64.h | 10 ++++++++--
lib/base64.c | 39 ++++++++++++++++++++++----------------
3 files changed, 33 insertions(+), 20 deletions(-)
diff --git a/drivers/nvme/common/auth.c b/drivers/nvme/common/auth.c
index 91e273b89..5fecb53cb 100644
--- a/drivers/nvme/common/auth.c
+++ b/drivers/nvme/common/auth.c
@@ -178,7 +178,7 @@ struct nvme_dhchap_key *nvme_auth_extract_key(unsigned char *secret,
if (!key)
return ERR_PTR(-ENOMEM);
- key_len = base64_decode(secret, allocated_len, key->key);
+ key_len = base64_decode(secret, allocated_len, key->key, true, BASE64_STD);
if (key_len < 0) {
pr_debug("base64 key decoding error %d\n",
key_len);
@@ -663,7 +663,7 @@ int nvme_auth_generate_digest(u8 hmac_id, u8 *psk, size_t psk_len,
if (ret)
goto out_free_digest;
- ret = base64_encode(digest, digest_len, enc);
+ ret = base64_encode(digest, digest_len, enc, true, BASE64_STD);
if (ret < hmac_len) {
ret = -ENOKEY;
goto out_free_digest;
diff --git a/include/linux/base64.h b/include/linux/base64.h
index 660d4cb1e..a2c6c9222 100644
--- a/include/linux/base64.h
+++ b/include/linux/base64.h
@@ -8,9 +8,15 @@
#include <linux/types.h>
+enum base64_variant {
+ BASE64_STD, /* RFC 4648 (standard) */
+ BASE64_URLSAFE, /* RFC 4648 (base64url) */
+ BASE64_IMAP, /* RFC 3501 */
+};
+
#define BASE64_CHARS(nbytes) DIV_ROUND_UP((nbytes) * 4, 3)
-int base64_encode(const u8 *src, int len, char *dst);
-int base64_decode(const char *src, int len, u8 *dst);
+int base64_encode(const u8 *src, int len, char *dst, bool padding, enum base64_variant variant);
+int base64_decode(const char *src, int len, u8 *dst, bool padding, enum base64_variant variant);
#endif /* _LINUX_BASE64_H */
diff --git a/lib/base64.c b/lib/base64.c
index b736a7a43..1af557785 100644
--- a/lib/base64.c
+++ b/lib/base64.c
@@ -1,12 +1,12 @@
// SPDX-License-Identifier: GPL-2.0
/*
- * base64.c - RFC4648-compliant base64 encoding
+ * base64.c - Base64 with support for multiple variants
*
* Copyright (c) 2020 Hannes Reinecke, SUSE
*
* Based on the base64url routines from fs/crypto/fname.c
- * (which are using the URL-safe base64 encoding),
- * modified to use the standard coding table from RFC4648 section 4.
+ * (which are using the URL-safe Base64 encoding),
+ * modified to support multiple Base64 variants.
*/
#include <linux/kernel.h>
@@ -15,26 +15,31 @@
#include <linux/string.h>
#include <linux/base64.h>
-static const char base64_table[65] =
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+static const char base64_tables[][65] = {
+ [BASE64_STD] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
+ [BASE64_URLSAFE] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",
+ [BASE64_IMAP] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,",
+};
/**
- * base64_encode() - base64-encode some binary data
+ * base64_encode() - Base64-encode some binary data
* @src: the binary data to encode
* @srclen: the length of @src in bytes
- * @dst: (output) the base64-encoded string. Not NUL-terminated.
+ * @dst: (output) the Base64-encoded string. Not NUL-terminated.
+ * @padding: whether to append '=' padding characters
+ * @variant: which base64 variant to use
*
- * Encodes data using base64 encoding, i.e. the "Base 64 Encoding" specified
- * by RFC 4648, including the '='-padding.
+ * Encodes data using the selected Base64 variant.
*
- * Return: the length of the resulting base64-encoded string in bytes.
+ * Return: the length of the resulting Base64-encoded string in bytes.
*/
-int base64_encode(const u8 *src, int srclen, char *dst)
+int base64_encode(const u8 *src, int srclen, char *dst, bool padding, enum base64_variant variant)
{
u32 ac = 0;
int bits = 0;
int i;
char *cp = dst;
+ const char *base64_table = base64_tables[variant];
for (i = 0; i < srclen; i++) {
ac = (ac << 8) | src[i];
@@ -57,25 +62,27 @@ int base64_encode(const u8 *src, int srclen, char *dst)
EXPORT_SYMBOL_GPL(base64_encode);
/**
- * base64_decode() - base64-decode a string
+ * base64_decode() - Base64-decode a string
* @src: the string to decode. Doesn't need to be NUL-terminated.
* @srclen: the length of @src in bytes
* @dst: (output) the decoded binary data
+ * @padding: whether to append '=' padding characters
+ * @variant: which base64 variant to use
*
- * Decodes a string using base64 encoding, i.e. the "Base 64 Encoding"
- * specified by RFC 4648, including the '='-padding.
+ * Decodes a string using the selected Base64 variant.
*
* This implementation hasn't been optimized for performance.
*
* Return: the length of the resulting decoded binary data in bytes,
- * or -1 if the string isn't a valid base64 string.
+ * or -1 if the string isn't a valid Base64 string.
*/
-int base64_decode(const char *src, int srclen, u8 *dst)
+int base64_decode(const char *src, int srclen, u8 *dst, bool padding, enum base64_variant variant)
{
u32 ac = 0;
int bits = 0;
int i;
u8 *bp = dst;
+ const char *base64_table = base64_tables[variant];
for (i = 0; i < srclen; i++) {
const char *p = strchr(base64_table, src[i]);
--
2.34.1
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH v3 2/6] lib/base64: Optimize base64_decode() with reverse lookup tables
2025-09-26 6:52 [PATCH v3 0/6] lib/base64: add generic encoder/decoder, migrate users Guan-Chun Wu
2025-09-26 6:55 ` [PATCH v3 1/6] lib/base64: Add support for multiple variants Guan-Chun Wu
@ 2025-09-26 6:55 ` Guan-Chun Wu
2025-09-26 23:33 ` Caleb Sander Mateos
2025-09-28 18:57 ` David Laight
2025-09-26 6:56 ` [PATCH v3 3/6] lib/base64: rework encode/decode for speed and stricter validation Guan-Chun Wu
` (3 subsequent siblings)
5 siblings, 2 replies; 31+ messages in thread
From: Guan-Chun Wu @ 2025-09-26 6:55 UTC (permalink / raw)
To: 409411716
Cc: akpm, axboe, ceph-devel, ebiggers, hch, home7438072, idryomov,
jaegeuk, kbusch, linux-fscrypt, linux-kernel, linux-nvme, sagi,
tytso, visitorckw, xiubli
From: Kuan-Wei Chiu <visitorckw@gmail.com>
Replace the use of strchr() in base64_decode() with precomputed reverse
lookup tables for each variant. This avoids repeated string scans and
improves performance. Use -1 in the tables to mark invalid characters.
Decode:
64B ~1530ns -> ~75ns (~20.4x)
1KB ~27726ns -> ~1165ns (~23.8x)
Signed-off-by: Kuan-Wei Chiu <visitorckw@gmail.com>
Co-developed-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
Signed-off-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
---
lib/base64.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 61 insertions(+), 5 deletions(-)
diff --git a/lib/base64.c b/lib/base64.c
index 1af557785..b20fdf168 100644
--- a/lib/base64.c
+++ b/lib/base64.c
@@ -21,6 +21,63 @@ static const char base64_tables[][65] = {
[BASE64_IMAP] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,",
};
+static const s8 base64_rev_tables[][256] = {
+ [BASE64_STD] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ },
+ [BASE64_URLSAFE] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63,
+ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ },
+ [BASE64_IMAP] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, 63, -1, -1, -1,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ },
+};
+
/**
* base64_encode() - Base64-encode some binary data
* @src: the binary data to encode
@@ -82,11 +139,9 @@ int base64_decode(const char *src, int srclen, u8 *dst, bool padding, enum base6
int bits = 0;
int i;
u8 *bp = dst;
- const char *base64_table = base64_tables[variant];
+ s8 ch;
for (i = 0; i < srclen; i++) {
- const char *p = strchr(base64_table, src[i]);
-
if (src[i] == '=') {
ac = (ac << 6);
bits += 6;
@@ -94,9 +149,10 @@ int base64_decode(const char *src, int srclen, u8 *dst, bool padding, enum base6
bits -= 8;
continue;
}
- if (p == NULL || src[i] == 0)
+ ch = base64_rev_tables[variant][(u8)src[i]];
+ if (ch == -1)
return -1;
- ac = (ac << 6) | (p - base64_table);
+ ac = (ac << 6) | ch;
bits += 6;
if (bits >= 8) {
bits -= 8;
--
2.34.1
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH v3 3/6] lib/base64: rework encode/decode for speed and stricter validation
2025-09-26 6:52 [PATCH v3 0/6] lib/base64: add generic encoder/decoder, migrate users Guan-Chun Wu
2025-09-26 6:55 ` [PATCH v3 1/6] lib/base64: Add support for multiple variants Guan-Chun Wu
2025-09-26 6:55 ` [PATCH v3 2/6] lib/base64: Optimize base64_decode() with reverse lookup tables Guan-Chun Wu
@ 2025-09-26 6:56 ` Guan-Chun Wu
2025-10-01 0:11 ` Caleb Sander Mateos
2025-09-26 6:56 ` [PATCH v3 4/6] lib: add KUnit tests for base64 encoding/decoding Guan-Chun Wu
` (2 subsequent siblings)
5 siblings, 1 reply; 31+ messages in thread
From: Guan-Chun Wu @ 2025-09-26 6:56 UTC (permalink / raw)
To: 409411716
Cc: akpm, axboe, ceph-devel, ebiggers, hch, home7438072, idryomov,
jaegeuk, kbusch, linux-fscrypt, linux-kernel, linux-nvme, sagi,
tytso, visitorckw, xiubli
The old base64 implementation relied on a bit-accumulator loop, which was
slow for larger inputs and too permissive in validation. It would accept
extra '=', missing '=', or even '=' appearing in the middle of the input,
allowing malformed strings to pass. This patch reworks the internals to
improve performance and enforce stricter validation.
Changes:
- Encoder:
* Process input in 3-byte blocks, mapping 24 bits into four 6-bit
symbols, avoiding bit-by-bit shifting and reducing loop iterations.
* Handle the final 1-2 leftover bytes explicitly and emit '=' only when
requested.
- Decoder:
* Based on the reverse lookup tables from the previous patch, decode
input in 4-character groups.
* Each group is looked up directly, converted into numeric values, and
combined into 3 output bytes.
* Explicitly handle padded and unpadded forms:
- With padding: input length must be a multiple of 4, and '=' is
allowed only in the last two positions. Reject stray or early '='.
- Without padding: validate tail lengths (2 or 3 chars) and require
unused low bits to be zero.
* Removed the bit-accumulator style loop to reduce loop iterations.
Performance (x86_64, Intel Core i7-10700 @ 2.90GHz, avg over 1000 runs,
KUnit):
Encode:
64B ~90ns -> ~32ns (~2.8x)
1KB ~1332ns -> ~510ns (~2.6x)
Decode:
64B ~1530ns -> ~64ns (~23.9x)
1KB ~27726ns -> ~982ns (~28.3x)
Co-developed-by: Kuan-Wei Chiu <visitorckw@gmail.com>
Signed-off-by: Kuan-Wei Chiu <visitorckw@gmail.com>
Co-developed-by: Yu-Sheng Huang <home7438072@gmail.com>
Signed-off-by: Yu-Sheng Huang <home7438072@gmail.com>
Signed-off-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
---
lib/base64.c | 150 +++++++++++++++++++++++++++++++++++++--------------
1 file changed, 110 insertions(+), 40 deletions(-)
diff --git a/lib/base64.c b/lib/base64.c
index b20fdf168..fd1db4611 100644
--- a/lib/base64.c
+++ b/lib/base64.c
@@ -93,26 +93,43 @@ static const s8 base64_rev_tables[][256] = {
int base64_encode(const u8 *src, int srclen, char *dst, bool padding, enum base64_variant variant)
{
u32 ac = 0;
- int bits = 0;
- int i;
char *cp = dst;
const char *base64_table = base64_tables[variant];
- for (i = 0; i < srclen; i++) {
- ac = (ac << 8) | src[i];
- bits += 8;
- do {
- bits -= 6;
- *cp++ = base64_table[(ac >> bits) & 0x3f];
- } while (bits >= 6);
- }
- if (bits) {
- *cp++ = base64_table[(ac << (6 - bits)) & 0x3f];
- bits -= 6;
+ while (srclen >= 3) {
+ ac = ((u32)src[0] << 16) |
+ ((u32)src[1] << 8) |
+ (u32)src[2];
+
+ *cp++ = base64_table[ac >> 18];
+ *cp++ = base64_table[(ac >> 12) & 0x3f];
+ *cp++ = base64_table[(ac >> 6) & 0x3f];
+ *cp++ = base64_table[ac & 0x3f];
+
+ src += 3;
+ srclen -= 3;
}
- while (bits < 0) {
- *cp++ = '=';
- bits += 2;
+
+ switch (srclen) {
+ case 2:
+ ac = ((u32)src[0] << 16) |
+ ((u32)src[1] << 8);
+
+ *cp++ = base64_table[ac >> 18];
+ *cp++ = base64_table[(ac >> 12) & 0x3f];
+ *cp++ = base64_table[(ac >> 6) & 0x3f];
+ if (padding)
+ *cp++ = '=';
+ break;
+ case 1:
+ ac = ((u32)src[0] << 16);
+ *cp++ = base64_table[ac >> 18];
+ *cp++ = base64_table[(ac >> 12) & 0x3f];
+ if (padding) {
+ *cp++ = '=';
+ *cp++ = '=';
+ }
+ break;
}
return cp - dst;
}
@@ -128,39 +145,92 @@ EXPORT_SYMBOL_GPL(base64_encode);
*
* Decodes a string using the selected Base64 variant.
*
- * This implementation hasn't been optimized for performance.
- *
* Return: the length of the resulting decoded binary data in bytes,
* or -1 if the string isn't a valid Base64 string.
*/
int base64_decode(const char *src, int srclen, u8 *dst, bool padding, enum base64_variant variant)
{
- u32 ac = 0;
- int bits = 0;
- int i;
u8 *bp = dst;
- s8 ch;
-
- for (i = 0; i < srclen; i++) {
- if (src[i] == '=') {
- ac = (ac << 6);
- bits += 6;
- if (bits >= 8)
- bits -= 8;
- continue;
- }
- ch = base64_rev_tables[variant][(u8)src[i]];
- if (ch == -1)
+ s8 input1, input2, input3, input4;
+ u32 val;
+
+ if (srclen == 0)
+ return 0;
+
+ /* Validate the input length for padding */
+ if (unlikely(padding && (srclen & 0x03) != 0))
+ return -1;
+
+ while (srclen >= 4) {
+ /* Decode the next 4 characters */
+ input1 = base64_rev_tables[variant][(u8)src[0]];
+ input2 = base64_rev_tables[variant][(u8)src[1]];
+ input3 = base64_rev_tables[variant][(u8)src[2]];
+ input4 = base64_rev_tables[variant][(u8)src[3]];
+
+ /* Return error if any Base64 character is invalid */
+ if (unlikely(input1 < 0 || input2 < 0 || (!padding && (input3 < 0 || input4 < 0))))
+ return -1;
+
+ /* Handle padding */
+ if (unlikely(padding && ((input3 < 0 && input4 >= 0) ||
+ (input3 < 0 && src[2] != '=') ||
+ (input4 < 0 && src[3] != '=') ||
+ (srclen > 4 && (input3 < 0 || input4 < 0)))))
+ return -1;
+ val = ((u32)input1 << 18) |
+ ((u32)input2 << 12) |
+ ((u32)((input3 < 0) ? 0 : input3) << 6) |
+ (u32)((input4 < 0) ? 0 : input4);
+
+ *bp++ = (u8)(val >> 16);
+
+ if (input3 >= 0)
+ *bp++ = (u8)(val >> 8);
+ if (input4 >= 0)
+ *bp++ = (u8)val;
+
+ src += 4;
+ srclen -= 4;
+ }
+
+ /* Handle leftover characters when padding is not used */
+ if (!padding && srclen > 0) {
+ switch (srclen) {
+ case 2:
+ input1 = base64_rev_tables[variant][(u8)src[0]];
+ input2 = base64_rev_tables[variant][(u8)src[1]];
+ if (unlikely(input1 < 0 || input2 < 0))
+ return -1;
+
+ val = ((u32)input1 << 6) | (u32)input2; /* 12 bits */
+ if (unlikely(val & 0x0F))
+ return -1; /* low 4 bits must be zero */
+
+ *bp++ = (u8)(val >> 4);
+ break;
+ case 3:
+ input1 = base64_rev_tables[variant][(u8)src[0]];
+ input2 = base64_rev_tables[variant][(u8)src[1]];
+ input3 = base64_rev_tables[variant][(u8)src[2]];
+ if (unlikely(input1 < 0 || input2 < 0 || input3 < 0))
+ return -1;
+
+ val = ((u32)input1 << 12) |
+ ((u32)input2 << 6) |
+ (u32)input3; /* 18 bits */
+
+ if (unlikely(val & 0x03))
+ return -1; /* low 2 bits must be zero */
+
+ *bp++ = (u8)(val >> 10);
+ *bp++ = (u8)((val >> 2) & 0xFF);
+ break;
+ default:
return -1;
- ac = (ac << 6) | ch;
- bits += 6;
- if (bits >= 8) {
- bits -= 8;
- *bp++ = (u8)(ac >> bits);
}
}
- if (ac & ((1 << bits) - 1))
- return -1;
+
return bp - dst;
}
EXPORT_SYMBOL_GPL(base64_decode);
--
2.34.1
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH v3 4/6] lib: add KUnit tests for base64 encoding/decoding
2025-09-26 6:52 [PATCH v3 0/6] lib/base64: add generic encoder/decoder, migrate users Guan-Chun Wu
` (2 preceding siblings ...)
2025-09-26 6:56 ` [PATCH v3 3/6] lib/base64: rework encode/decode for speed and stricter validation Guan-Chun Wu
@ 2025-09-26 6:56 ` Guan-Chun Wu
2025-09-26 6:56 ` [PATCH v3 5/6] fscrypt: replace local base64url helpers with lib/base64 Guan-Chun Wu
2025-09-26 6:57 ` [PATCH v3 6/6] ceph: replace local base64 " Guan-Chun Wu
5 siblings, 0 replies; 31+ messages in thread
From: Guan-Chun Wu @ 2025-09-26 6:56 UTC (permalink / raw)
To: 409411716
Cc: akpm, axboe, ceph-devel, ebiggers, hch, home7438072, idryomov,
jaegeuk, kbusch, linux-fscrypt, linux-kernel, linux-nvme, sagi,
tytso, visitorckw, xiubli
Add a KUnit test suite to validate the base64 helpers. The tests cover
both encoding and decoding, including padded and unpadded forms as defined
by RFC 4648 (standard base64), and add negative cases for malformed inputs
and padding errors.
The test suite also validates other variants (URLSAFE, IMAP) to ensure
their correctness.
In addition to functional checks, the suite includes simple microbenchmarks
which report average encode/decode latency for small (64B) and larger
(1KB) inputs. These numbers are informational only and do not gate the
tests.
Kconfig (BASE64_KUNIT) and lib/tests/Makefile are updated accordingly.
Sample KUnit output:
KTAP version 1
# Subtest: base64
# module: base64_kunit
1..4
# base64_performance_tests: [64B] encode run : 32ns
# base64_performance_tests: [64B] decode run : 64ns
# base64_performance_tests: [1KB] encode run : 510ns
# base64_performance_tests: [1KB] decode run : 980ns
ok 1 base64_performance_tests
ok 2 base64_std_encode_tests
ok 3 base64_std_decode_tests
ok 4 base64_variant_tests
# base64: pass:4 fail:0 skip:0 total:4
# Totals: pass:4 fail:0 skip:0 total:4
Signed-off-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
Reviewed-by: Kuan-Wei Chiu <visitorckw@gmail.com>
---
lib/Kconfig.debug | 19 ++-
lib/tests/Makefile | 1 +
lib/tests/base64_kunit.c | 294 +++++++++++++++++++++++++++++++++++++++
3 files changed, 313 insertions(+), 1 deletion(-)
create mode 100644 lib/tests/base64_kunit.c
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index dc0e0c6ed..1cfb12d02 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2794,8 +2794,25 @@ config CMDLINE_KUNIT_TEST
If unsure, say N.
+config BASE64_KUNIT
+ tristate "KUnit test for base64 decoding and encoding" if !KUNIT_ALL_TESTS
+ depends on KUNIT
+ default KUNIT_ALL_TESTS
+ help
+ This builds the base64 unit tests.
+
+ The tests cover the encoding and decoding logic of Base64 functions
+ in the kernel.
+ In addition to correctness checks, simple performance benchmarks
+ for both encoding and decoding are also included.
+
+ For more information on KUnit and unit tests in general please refer
+ to the KUnit documentation in Documentation/dev-tools/kunit/.
+
+ If unsure, say N.
+
config BITS_TEST
- tristate "KUnit test for bits.h" if !KUNIT_ALL_TESTS
+ tristate "KUnit test for bit functions and macros" if !KUNIT_ALL_TESTS
depends on KUNIT
default KUNIT_ALL_TESTS
help
diff --git a/lib/tests/Makefile b/lib/tests/Makefile
index fa6d728a8..6593a2873 100644
--- a/lib/tests/Makefile
+++ b/lib/tests/Makefile
@@ -4,6 +4,7 @@
# KUnit tests
CFLAGS_bitfield_kunit.o := $(DISABLE_STRUCTLEAK_PLUGIN)
+obj-$(CONFIG_BASE64_KUNIT) += base64_kunit.o
obj-$(CONFIG_BITFIELD_KUNIT) += bitfield_kunit.o
obj-$(CONFIG_BITS_TEST) += test_bits.o
obj-$(CONFIG_BLACKHOLE_DEV_KUNIT_TEST) += blackhole_dev_kunit.o
diff --git a/lib/tests/base64_kunit.c b/lib/tests/base64_kunit.c
new file mode 100644
index 000000000..f7252070c
--- /dev/null
+++ b/lib/tests/base64_kunit.c
@@ -0,0 +1,294 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * base64_kunit_test.c - KUnit tests for base64 encoding and decoding functions
+ *
+ * Copyright (c) 2025, Guan-Chun Wu <409411716@gms.tku.edu.tw>
+ */
+
+#include <kunit/test.h>
+#include <linux/base64.h>
+
+/* ---------- Benchmark helpers ---------- */
+static u64 bench_encode_ns(const u8 *data, int len, char *dst, int reps,
+ enum base64_variant variant)
+{
+ u64 t0, t1;
+
+ t0 = ktime_get_ns();
+ for (int i = 0; i < reps; i++)
+ base64_encode(data, len, dst, true, variant);
+ t1 = ktime_get_ns();
+
+ return div64_u64(t1 - t0, (u64)reps);
+}
+
+static u64 bench_decode_ns(const char *data, int len, u8 *dst, int reps,
+ enum base64_variant variant)
+{
+ u64 t0, t1;
+
+ t0 = ktime_get_ns();
+ for (int i = 0; i < reps; i++)
+ base64_decode(data, len, dst, true, variant);
+ t1 = ktime_get_ns();
+
+ return div64_u64(t1 - t0, (u64)reps);
+}
+
+static void run_perf_and_check(struct kunit *test, const char *label, int size,
+ enum base64_variant variant)
+{
+ const int reps = 1000;
+ size_t outlen = DIV_ROUND_UP(size, 3) * 4;
+ u8 *in = kmalloc(size, GFP_KERNEL);
+ char *enc = kmalloc(outlen, GFP_KERNEL);
+ u8 *decoded = kmalloc(size, GFP_KERNEL);
+
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, in);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, enc);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, decoded);
+
+ get_random_bytes(in, size);
+ int enc_len = base64_encode(in, size, enc, true, variant);
+ int dec_len = base64_decode(enc, enc_len, decoded, true, variant);
+
+ /* correctness sanity check */
+ KUNIT_EXPECT_EQ(test, dec_len, size);
+ KUNIT_EXPECT_MEMEQ(test, decoded, in, size);
+
+ /* benchmark encode */
+
+ u64 t1 = bench_encode_ns(in, size, enc, reps, variant);
+
+ kunit_info(test, "[%s] encode run : %lluns", label, t1);
+
+ u64 t2 = bench_decode_ns(enc, enc_len, decoded, reps, variant);
+
+ kunit_info(test, "[%s] decode run : %lluns", label, t2);
+
+ kfree(in);
+ kfree(enc);
+ kfree(decoded);
+}
+
+static void base64_performance_tests(struct kunit *test)
+{
+ /* run on STD variant only */
+ run_perf_and_check(test, "64B", 64, BASE64_STD);
+ run_perf_and_check(test, "1KB", 1024, BASE64_STD);
+}
+
+/* ---------- Helpers for encode ---------- */
+static void expect_encode_ok(struct kunit *test, const u8 *src, int srclen,
+ const char *expected, bool padding,
+ enum base64_variant variant)
+{
+ char buf[128];
+ int encoded_len = base64_encode(src, srclen, buf, padding, variant);
+
+ buf[encoded_len] = '\0';
+
+ KUNIT_EXPECT_EQ(test, encoded_len, strlen(expected));
+ KUNIT_EXPECT_STREQ(test, buf, expected);
+}
+
+/* ---------- Helpers for decode ---------- */
+static void expect_decode_ok(struct kunit *test, const char *src,
+ const u8 *expected, int expected_len, bool padding,
+ enum base64_variant variant)
+{
+ u8 buf[128];
+ int decoded_len = base64_decode(src, strlen(src), buf, padding, variant);
+
+ KUNIT_EXPECT_EQ(test, decoded_len, expected_len);
+ KUNIT_EXPECT_MEMEQ(test, buf, expected, expected_len);
+}
+
+static void expect_decode_err(struct kunit *test, const char *src,
+ int srclen, bool padding,
+ enum base64_variant variant)
+{
+ u8 buf[64];
+ int decoded_len = base64_decode(src, srclen, buf, padding, variant);
+
+ KUNIT_EXPECT_EQ(test, decoded_len, -1);
+}
+
+/* ---------- Encode Tests ---------- */
+static void base64_std_encode_tests(struct kunit *test)
+{
+ /* With padding */
+ expect_encode_ok(test, (const u8 *)"", 0, "", true, BASE64_STD);
+ expect_encode_ok(test, (const u8 *)"f", 1, "Zg==", true, BASE64_STD);
+ expect_encode_ok(test, (const u8 *)"fo", 2, "Zm8=", true, BASE64_STD);
+ expect_encode_ok(test, (const u8 *)"foo", 3, "Zm9v", true, BASE64_STD);
+ expect_encode_ok(test, (const u8 *)"foob", 4, "Zm9vYg==", true, BASE64_STD);
+ expect_encode_ok(test, (const u8 *)"fooba", 5, "Zm9vYmE=", true, BASE64_STD);
+ expect_encode_ok(test, (const u8 *)"foobar", 6, "Zm9vYmFy", true, BASE64_STD);
+
+ /* Extra cases with padding */
+ expect_encode_ok(test, (const u8 *)"Hello, world!", 13, "SGVsbG8sIHdvcmxkIQ==",
+ true, BASE64_STD);
+ expect_encode_ok(test, (const u8 *)"ABCDEFGHIJKLMNOPQRSTUVWXYZ", 26,
+ "QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo=", true, BASE64_STD);
+ expect_encode_ok(test, (const u8 *)"abcdefghijklmnopqrstuvwxyz", 26,
+ "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=", true, BASE64_STD);
+ expect_encode_ok(test, (const u8 *)"0123456789+/", 12, "MDEyMzQ1Njc4OSsv",
+ true, BASE64_STD);
+
+ /* Without padding */
+ expect_encode_ok(test, (const u8 *)"", 0, "", false, BASE64_STD);
+ expect_encode_ok(test, (const u8 *)"f", 1, "Zg", false, BASE64_STD);
+ expect_encode_ok(test, (const u8 *)"fo", 2, "Zm8", false, BASE64_STD);
+ expect_encode_ok(test, (const u8 *)"foo", 3, "Zm9v", false, BASE64_STD);
+ expect_encode_ok(test, (const u8 *)"foob", 4, "Zm9vYg", false, BASE64_STD);
+ expect_encode_ok(test, (const u8 *)"fooba", 5, "Zm9vYmE", false, BASE64_STD);
+ expect_encode_ok(test, (const u8 *)"foobar", 6, "Zm9vYmFy", false, BASE64_STD);
+
+ /* Extra cases without padding */
+ expect_encode_ok(test, (const u8 *)"Hello, world!", 13, "SGVsbG8sIHdvcmxkIQ",
+ false, BASE64_STD);
+ expect_encode_ok(test, (const u8 *)"ABCDEFGHIJKLMNOPQRSTUVWXYZ", 26,
+ "QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo", false, BASE64_STD);
+ expect_encode_ok(test, (const u8 *)"abcdefghijklmnopqrstuvwxyz", 26,
+ "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo", false, BASE64_STD);
+ expect_encode_ok(test, (const u8 *)"0123456789+/", 12, "MDEyMzQ1Njc4OSsv",
+ false, BASE64_STD);
+}
+
+/* ---------- Decode Tests ---------- */
+static void base64_std_decode_tests(struct kunit *test)
+{
+ /* -------- With padding --------*/
+ expect_decode_ok(test, "", (const u8 *)"", 0, true, BASE64_STD);
+ expect_decode_ok(test, "Zg==", (const u8 *)"f", 1, true, BASE64_STD);
+ expect_decode_ok(test, "Zm8=", (const u8 *)"fo", 2, true, BASE64_STD);
+ expect_decode_ok(test, "Zm9v", (const u8 *)"foo", 3, true, BASE64_STD);
+ expect_decode_ok(test, "Zm9vYg==", (const u8 *)"foob", 4, true, BASE64_STD);
+ expect_decode_ok(test, "Zm9vYmE=", (const u8 *)"fooba", 5, true, BASE64_STD);
+ expect_decode_ok(test, "Zm9vYmFy", (const u8 *)"foobar", 6, true, BASE64_STD);
+ expect_decode_ok(test, "SGVsbG8sIHdvcmxkIQ==", (const u8 *)"Hello, world!", 13,
+ true, BASE64_STD);
+ expect_decode_ok(test, "QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo=",
+ (const u8 *)"ABCDEFGHIJKLMNOPQRSTUVWXYZ", 26, true, BASE64_STD);
+ expect_decode_ok(test, "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=",
+ (const u8 *)"abcdefghijklmnopqrstuvwxyz", 26, true, BASE64_STD);
+
+ /* Error cases */
+ expect_decode_err(test, "Zg=!", 4, true, BASE64_STD);
+ expect_decode_err(test, "Zm$=", 4, true, BASE64_STD);
+ expect_decode_err(test, "Z===", 4, true, BASE64_STD);
+ expect_decode_err(test, "Zg", 2, true, BASE64_STD);
+ expect_decode_err(test, "Zm9v====", 8, true, BASE64_STD);
+ expect_decode_err(test, "Zm==A", 5, true, BASE64_STD);
+
+ {
+ char with_nul[4] = { 'Z', 'g', '\0', '=' };
+
+ expect_decode_err(test, with_nul, 4, true, BASE64_STD);
+ }
+
+ /* -------- Without padding --------*/
+ expect_decode_ok(test, "", (const u8 *)"", 0, false, BASE64_STD);
+ expect_decode_ok(test, "Zg", (const u8 *)"f", 1, false, BASE64_STD);
+ expect_decode_ok(test, "Zm8", (const u8 *)"fo", 2, false, BASE64_STD);
+ expect_decode_ok(test, "Zm9v", (const u8 *)"foo", 3, false, BASE64_STD);
+ expect_decode_ok(test, "Zm9vYg", (const u8 *)"foob", 4, false, BASE64_STD);
+ expect_decode_ok(test, "Zm9vYmE", (const u8 *)"fooba", 5, false, BASE64_STD);
+ expect_decode_ok(test, "Zm9vYmFy", (const u8 *)"foobar", 6, false, BASE64_STD);
+ expect_decode_ok(test, "TWFu", (const u8 *)"Man", 3, false, BASE64_STD);
+ expect_decode_ok(test, "SGVsbG8sIHdvcmxkIQ", (const u8 *)"Hello, world!", 13,
+ false, BASE64_STD);
+ expect_decode_ok(test, "QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo",
+ (const u8 *)"ABCDEFGHIJKLMNOPQRSTUVWXYZ", 26, false, BASE64_STD);
+ expect_decode_ok(test, "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo",
+ (const u8 *)"abcdefghijklmnopqrstuvwxyz", 26, false, BASE64_STD);
+ expect_decode_ok(test, "MDEyMzQ1Njc4OSsv", (const u8 *)"0123456789+/", 12,
+ false, BASE64_STD);
+
+ /* Error cases */
+ expect_decode_err(test, "Zg=!", 4, false, BASE64_STD);
+ expect_decode_err(test, "Zm$=", 4, false, BASE64_STD);
+ expect_decode_err(test, "Z===", 4, false, BASE64_STD);
+ expect_decode_err(test, "Zg=", 3, false, BASE64_STD);
+ expect_decode_err(test, "Zm9v====", 8, false, BASE64_STD);
+ expect_decode_err(test, "Zm==v", 4, false, BASE64_STD);
+
+ {
+ char with_nul[4] = { 'Z', 'g', '\0', '=' };
+
+ expect_decode_err(test, with_nul, 4, false, BASE64_STD);
+ }
+}
+
+/* ---------- Variant tests (URLSAFE / IMAP) ---------- */
+static void base64_variant_tests(struct kunit *test)
+{
+ const u8 sample1[] = { 0x00, 0xfb, 0xff, 0x7f, 0x80 };
+ char std_buf[128], url_buf[128], imap_buf[128];
+ u8 back[128];
+ int n_std, n_url, n_imap, m;
+ int i;
+
+ n_std = base64_encode(sample1, sizeof(sample1), std_buf, false, BASE64_STD);
+ n_url = base64_encode(sample1, sizeof(sample1), url_buf, false, BASE64_URLSAFE);
+ std_buf[n_std] = '\0';
+ url_buf[n_url] = '\0';
+
+ for (i = 0; i < n_std; i++) {
+ if (std_buf[i] == '+')
+ std_buf[i] = '-';
+ else if (std_buf[i] == '/')
+ std_buf[i] = '_';
+ }
+ KUNIT_EXPECT_STREQ(test, std_buf, url_buf);
+
+ m = base64_decode(url_buf, n_url, back, false, BASE64_URLSAFE);
+ KUNIT_EXPECT_EQ(test, m, (int)sizeof(sample1));
+ KUNIT_EXPECT_MEMEQ(test, back, sample1, sizeof(sample1));
+
+ n_std = base64_encode(sample1, sizeof(sample1), std_buf, false, BASE64_STD);
+ n_imap = base64_encode(sample1, sizeof(sample1), imap_buf, false, BASE64_IMAP);
+ std_buf[n_std] = '\0';
+ imap_buf[n_imap] = '\0';
+
+ for (i = 0; i < n_std; i++)
+ if (std_buf[i] == '/')
+ std_buf[i] = ',';
+ KUNIT_EXPECT_STREQ(test, std_buf, imap_buf);
+
+ m = base64_decode(imap_buf, n_imap, back, false, BASE64_IMAP);
+ KUNIT_EXPECT_EQ(test, m, (int)sizeof(sample1));
+ KUNIT_EXPECT_MEMEQ(test, back, sample1, sizeof(sample1));
+
+ {
+ const char *bad = "Zg==";
+ u8 tmp[8];
+
+ m = base64_decode(bad, strlen(bad), tmp, false, BASE64_URLSAFE);
+ KUNIT_EXPECT_EQ(test, m, -1);
+
+ m = base64_decode(bad, strlen(bad), tmp, false, BASE64_IMAP);
+ KUNIT_EXPECT_EQ(test, m, -1);
+ }
+}
+
+/* ---------- Test registration ---------- */
+static struct kunit_case base64_test_cases[] = {
+ KUNIT_CASE(base64_performance_tests),
+ KUNIT_CASE(base64_std_encode_tests),
+ KUNIT_CASE(base64_std_decode_tests),
+ KUNIT_CASE(base64_variant_tests),
+ {}
+};
+
+static struct kunit_suite base64_test_suite = {
+ .name = "base64",
+ .test_cases = base64_test_cases,
+};
+
+kunit_test_suite(base64_test_suite);
+
+MODULE_AUTHOR("Guan-Chun Wu <409411716@gms.tku.edu.tw>");
+MODULE_DESCRIPTION("KUnit tests for Base64 encoding/decoding, including performance checks");
+MODULE_LICENSE("GPL");
--
2.34.1
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH v3 5/6] fscrypt: replace local base64url helpers with lib/base64
2025-09-26 6:52 [PATCH v3 0/6] lib/base64: add generic encoder/decoder, migrate users Guan-Chun Wu
` (3 preceding siblings ...)
2025-09-26 6:56 ` [PATCH v3 4/6] lib: add KUnit tests for base64 encoding/decoding Guan-Chun Wu
@ 2025-09-26 6:56 ` Guan-Chun Wu
2025-09-26 6:57 ` [PATCH v3 6/6] ceph: replace local base64 " Guan-Chun Wu
5 siblings, 0 replies; 31+ messages in thread
From: Guan-Chun Wu @ 2025-09-26 6:56 UTC (permalink / raw)
To: 409411716
Cc: akpm, axboe, ceph-devel, ebiggers, hch, home7438072, idryomov,
jaegeuk, kbusch, linux-fscrypt, linux-kernel, linux-nvme, sagi,
tytso, visitorckw, xiubli
Replace the base64url encoding and decoding functions in fscrypt with the
generic base64_encode() and base64_decode() helpers from lib/base64.
This removes the custom implementation in fscrypt, reduces code
duplication, and relies on the shared Base64 implementation in lib.
The helpers preserve RFC 4648-compliant URL-safe Base64 encoding without
padding, so there are no functional changes.
This change also improves performance: encoding is about 2.7x faster and
decoding achieves 23-28x speedups compared to the previous implementation.
Signed-off-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
Reviewed-by: Kuan-Wei Chiu <visitorckw@gmail.com>
---
fs/crypto/fname.c | 89 ++++-------------------------------------------
1 file changed, 6 insertions(+), 83 deletions(-)
diff --git a/fs/crypto/fname.c b/fs/crypto/fname.c
index f9f6713e1..dcf7cff70 100644
--- a/fs/crypto/fname.c
+++ b/fs/crypto/fname.c
@@ -17,6 +17,7 @@
#include <linux/export.h>
#include <linux/namei.h>
#include <linux/scatterlist.h>
+#include <linux/base64.h>
#include "fscrypt_private.h"
@@ -72,7 +73,7 @@ struct fscrypt_nokey_name {
/* Encoded size of max-size no-key name */
#define FSCRYPT_NOKEY_NAME_MAX_ENCODED \
- FSCRYPT_BASE64URL_CHARS(FSCRYPT_NOKEY_NAME_MAX)
+ BASE64_CHARS(FSCRYPT_NOKEY_NAME_MAX)
static inline bool fscrypt_is_dot_dotdot(const struct qstr *str)
{
@@ -163,84 +164,6 @@ static int fname_decrypt(const struct inode *inode,
return 0;
}
-static const char base64url_table[65] =
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
-
-#define FSCRYPT_BASE64URL_CHARS(nbytes) DIV_ROUND_UP((nbytes) * 4, 3)
-
-/**
- * fscrypt_base64url_encode() - base64url-encode some binary data
- * @src: the binary data to encode
- * @srclen: the length of @src in bytes
- * @dst: (output) the base64url-encoded string. Not NUL-terminated.
- *
- * Encodes data using base64url encoding, i.e. the "Base 64 Encoding with URL
- * and Filename Safe Alphabet" specified by RFC 4648. '='-padding isn't used,
- * as it's unneeded and not required by the RFC. base64url is used instead of
- * base64 to avoid the '/' character, which isn't allowed in filenames.
- *
- * Return: the length of the resulting base64url-encoded string in bytes.
- * This will be equal to FSCRYPT_BASE64URL_CHARS(srclen).
- */
-static int fscrypt_base64url_encode(const u8 *src, int srclen, char *dst)
-{
- u32 ac = 0;
- int bits = 0;
- int i;
- char *cp = dst;
-
- for (i = 0; i < srclen; i++) {
- ac = (ac << 8) | src[i];
- bits += 8;
- do {
- bits -= 6;
- *cp++ = base64url_table[(ac >> bits) & 0x3f];
- } while (bits >= 6);
- }
- if (bits)
- *cp++ = base64url_table[(ac << (6 - bits)) & 0x3f];
- return cp - dst;
-}
-
-/**
- * fscrypt_base64url_decode() - base64url-decode a string
- * @src: the string to decode. Doesn't need to be NUL-terminated.
- * @srclen: the length of @src in bytes
- * @dst: (output) the decoded binary data
- *
- * Decodes a string using base64url encoding, i.e. the "Base 64 Encoding with
- * URL and Filename Safe Alphabet" specified by RFC 4648. '='-padding isn't
- * accepted, nor are non-encoding characters such as whitespace.
- *
- * This implementation hasn't been optimized for performance.
- *
- * Return: the length of the resulting decoded binary data in bytes,
- * or -1 if the string isn't a valid base64url string.
- */
-static int fscrypt_base64url_decode(const char *src, int srclen, u8 *dst)
-{
- u32 ac = 0;
- int bits = 0;
- int i;
- u8 *bp = dst;
-
- for (i = 0; i < srclen; i++) {
- const char *p = strchr(base64url_table, src[i]);
-
- if (p == NULL || src[i] == 0)
- return -1;
- ac = (ac << 6) | (p - base64url_table);
- bits += 6;
- if (bits >= 8) {
- bits -= 8;
- *bp++ = (u8)(ac >> bits);
- }
- }
- if (ac & ((1 << bits) - 1))
- return -1;
- return bp - dst;
-}
-
bool __fscrypt_fname_encrypted_size(const union fscrypt_policy *policy,
u32 orig_len, u32 max_len,
u32 *encrypted_len_ret)
@@ -387,8 +310,8 @@ int fscrypt_fname_disk_to_usr(const struct inode *inode,
nokey_name.sha256);
size = FSCRYPT_NOKEY_NAME_MAX;
}
- oname->len = fscrypt_base64url_encode((const u8 *)&nokey_name, size,
- oname->name);
+ oname->len = base64_encode((const u8 *)&nokey_name, size,
+ oname->name, false, BASE64_URLSAFE);
return 0;
}
EXPORT_SYMBOL(fscrypt_fname_disk_to_usr);
@@ -467,8 +390,8 @@ int fscrypt_setup_filename(struct inode *dir, const struct qstr *iname,
if (fname->crypto_buf.name == NULL)
return -ENOMEM;
- ret = fscrypt_base64url_decode(iname->name, iname->len,
- fname->crypto_buf.name);
+ ret = base64_decode(iname->name, iname->len,
+ fname->crypto_buf.name, false, BASE64_URLSAFE);
if (ret < (int)offsetof(struct fscrypt_nokey_name, bytes[1]) ||
(ret > offsetof(struct fscrypt_nokey_name, sha256) &&
ret != FSCRYPT_NOKEY_NAME_MAX)) {
--
2.34.1
^ permalink raw reply related [flat|nested] 31+ messages in thread
* [PATCH v3 6/6] ceph: replace local base64 helpers with lib/base64
2025-09-26 6:52 [PATCH v3 0/6] lib/base64: add generic encoder/decoder, migrate users Guan-Chun Wu
` (4 preceding siblings ...)
2025-09-26 6:56 ` [PATCH v3 5/6] fscrypt: replace local base64url helpers with lib/base64 Guan-Chun Wu
@ 2025-09-26 6:57 ` Guan-Chun Wu
5 siblings, 0 replies; 31+ messages in thread
From: Guan-Chun Wu @ 2025-09-26 6:57 UTC (permalink / raw)
To: 409411716
Cc: akpm, axboe, ceph-devel, ebiggers, hch, home7438072, idryomov,
jaegeuk, kbusch, linux-fscrypt, linux-kernel, linux-nvme, sagi,
tytso, visitorckw, xiubli
Remove the ceph_base64_encode() and ceph_base64_decode() functions and
replace their usage with the generic base64_encode() and base64_decode()
helpers from lib/base64.
This eliminates the custom implementation in Ceph, reduces code
duplication, and relies on the shared Base64 code in lib.
The helpers preserve RFC 3501-compliant Base64 encoding without padding,
so there are no functional changes.
This change also improves performance: encoding is about 2.7x faster and
decoding achieves 23-28x speedups compared to the previous local
implementation.
Signed-off-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
Reviewed-by: Kuan-Wei Chiu <visitorckw@gmail.com>
---
fs/ceph/crypto.c | 60 ++++--------------------------------------------
fs/ceph/crypto.h | 6 +----
fs/ceph/dir.c | 5 ++--
fs/ceph/inode.c | 2 +-
4 files changed, 9 insertions(+), 64 deletions(-)
diff --git a/fs/ceph/crypto.c b/fs/ceph/crypto.c
index cab722619..9bb0f320b 100644
--- a/fs/ceph/crypto.c
+++ b/fs/ceph/crypto.c
@@ -15,59 +15,6 @@
#include "mds_client.h"
#include "crypto.h"
-/*
- * The base64url encoding used by fscrypt includes the '_' character, which may
- * cause problems in snapshot names (which can not start with '_'). Thus, we
- * used the base64 encoding defined for IMAP mailbox names (RFC 3501) instead,
- * which replaces '-' and '_' by '+' and ','.
- */
-static const char base64_table[65] =
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,";
-
-int ceph_base64_encode(const u8 *src, int srclen, char *dst)
-{
- u32 ac = 0;
- int bits = 0;
- int i;
- char *cp = dst;
-
- for (i = 0; i < srclen; i++) {
- ac = (ac << 8) | src[i];
- bits += 8;
- do {
- bits -= 6;
- *cp++ = base64_table[(ac >> bits) & 0x3f];
- } while (bits >= 6);
- }
- if (bits)
- *cp++ = base64_table[(ac << (6 - bits)) & 0x3f];
- return cp - dst;
-}
-
-int ceph_base64_decode(const char *src, int srclen, u8 *dst)
-{
- u32 ac = 0;
- int bits = 0;
- int i;
- u8 *bp = dst;
-
- for (i = 0; i < srclen; i++) {
- const char *p = strchr(base64_table, src[i]);
-
- if (p == NULL || src[i] == 0)
- return -1;
- ac = (ac << 6) | (p - base64_table);
- bits += 6;
- if (bits >= 8) {
- bits -= 8;
- *bp++ = (u8)(ac >> bits);
- }
- }
- if (ac & ((1 << bits) - 1))
- return -1;
- return bp - dst;
-}
-
static int ceph_crypt_get_context(struct inode *inode, void *ctx, size_t len)
{
struct ceph_inode_info *ci = ceph_inode(inode);
@@ -316,7 +263,7 @@ int ceph_encode_encrypted_dname(struct inode *parent, char *buf, int elen)
}
/* base64 encode the encrypted name */
- elen = ceph_base64_encode(cryptbuf, len, p);
+ elen = base64_encode(cryptbuf, len, p, false, BASE64_IMAP);
doutc(cl, "base64-encoded ciphertext name = %.*s\n", elen, p);
/* To understand the 240 limit, see CEPH_NOHASH_NAME_MAX comments */
@@ -410,7 +357,8 @@ int ceph_fname_to_usr(const struct ceph_fname *fname, struct fscrypt_str *tname,
tname = &_tname;
}
- declen = ceph_base64_decode(name, name_len, tname->name);
+ declen = base64_decode(name, name_len,
+ tname->name, false, BASE64_IMAP);
if (declen <= 0) {
ret = -EIO;
goto out;
@@ -424,7 +372,7 @@ int ceph_fname_to_usr(const struct ceph_fname *fname, struct fscrypt_str *tname,
ret = fscrypt_fname_disk_to_usr(dir, 0, 0, &iname, oname);
if (!ret && (dir != fname->dir)) {
- char tmp_buf[CEPH_BASE64_CHARS(NAME_MAX)];
+ char tmp_buf[BASE64_CHARS(NAME_MAX)];
name_len = snprintf(tmp_buf, sizeof(tmp_buf), "_%.*s_%ld",
oname->len, oname->name, dir->i_ino);
diff --git a/fs/ceph/crypto.h b/fs/ceph/crypto.h
index 23612b2e9..b748e2060 100644
--- a/fs/ceph/crypto.h
+++ b/fs/ceph/crypto.h
@@ -8,6 +8,7 @@
#include <crypto/sha2.h>
#include <linux/fscrypt.h>
+#include <linux/base64.h>
#define CEPH_FSCRYPT_BLOCK_SHIFT 12
#define CEPH_FSCRYPT_BLOCK_SIZE (_AC(1, UL) << CEPH_FSCRYPT_BLOCK_SHIFT)
@@ -89,11 +90,6 @@ static inline u32 ceph_fscrypt_auth_len(struct ceph_fscrypt_auth *fa)
*/
#define CEPH_NOHASH_NAME_MAX (180 - SHA256_DIGEST_SIZE)
-#define CEPH_BASE64_CHARS(nbytes) DIV_ROUND_UP((nbytes) * 4, 3)
-
-int ceph_base64_encode(const u8 *src, int srclen, char *dst);
-int ceph_base64_decode(const char *src, int srclen, u8 *dst);
-
void ceph_fscrypt_set_ops(struct super_block *sb);
void ceph_fscrypt_free_dummy_policy(struct ceph_fs_client *fsc);
diff --git a/fs/ceph/dir.c b/fs/ceph/dir.c
index 8478e7e75..25045d817 100644
--- a/fs/ceph/dir.c
+++ b/fs/ceph/dir.c
@@ -998,13 +998,14 @@ static int prep_encrypted_symlink_target(struct ceph_mds_request *req,
if (err)
goto out;
- req->r_path2 = kmalloc(CEPH_BASE64_CHARS(osd_link.len) + 1, GFP_KERNEL);
+ req->r_path2 = kmalloc(BASE64_CHARS(osd_link.len) + 1, GFP_KERNEL);
if (!req->r_path2) {
err = -ENOMEM;
goto out;
}
- len = ceph_base64_encode(osd_link.name, osd_link.len, req->r_path2);
+ len = base64_encode(osd_link.name, osd_link.len,
+ req->r_path2, false, BASE64_IMAP);
req->r_path2[len] = '\0';
out:
fscrypt_fname_free_buffer(&osd_link);
diff --git a/fs/ceph/inode.c b/fs/ceph/inode.c
index fc543075b..d06fb76fc 100644
--- a/fs/ceph/inode.c
+++ b/fs/ceph/inode.c
@@ -911,7 +911,7 @@ static int decode_encrypted_symlink(struct ceph_mds_client *mdsc,
if (!sym)
return -ENOMEM;
- declen = ceph_base64_decode(encsym, enclen, sym);
+ declen = base64_decode(encsym, enclen, sym, false, BASE64_IMAP);
if (declen < 0) {
pr_err_client(cl,
"can't decode symlink (%d). Content: %.*s\n",
--
2.34.1
^ permalink raw reply related [flat|nested] 31+ messages in thread
* Re: [PATCH v3 2/6] lib/base64: Optimize base64_decode() with reverse lookup tables
2025-09-26 6:55 ` [PATCH v3 2/6] lib/base64: Optimize base64_decode() with reverse lookup tables Guan-Chun Wu
@ 2025-09-26 23:33 ` Caleb Sander Mateos
2025-09-28 6:37 ` Kuan-Wei Chiu
2025-10-01 10:18 ` Guan-Chun Wu
2025-09-28 18:57 ` David Laight
1 sibling, 2 replies; 31+ messages in thread
From: Caleb Sander Mateos @ 2025-09-26 23:33 UTC (permalink / raw)
To: Guan-Chun Wu
Cc: akpm, axboe, ceph-devel, ebiggers, hch, home7438072, idryomov,
jaegeuk, kbusch, linux-fscrypt, linux-kernel, linux-nvme, sagi,
tytso, visitorckw, xiubli
On Thu, Sep 25, 2025 at 11:59 PM Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
>
> From: Kuan-Wei Chiu <visitorckw@gmail.com>
>
> Replace the use of strchr() in base64_decode() with precomputed reverse
> lookup tables for each variant. This avoids repeated string scans and
> improves performance. Use -1 in the tables to mark invalid characters.
>
> Decode:
> 64B ~1530ns -> ~75ns (~20.4x)
> 1KB ~27726ns -> ~1165ns (~23.8x)
>
> Signed-off-by: Kuan-Wei Chiu <visitorckw@gmail.com>
> Co-developed-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
> Signed-off-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
> ---
> lib/base64.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++----
> 1 file changed, 61 insertions(+), 5 deletions(-)
>
> diff --git a/lib/base64.c b/lib/base64.c
> index 1af557785..b20fdf168 100644
> --- a/lib/base64.c
> +++ b/lib/base64.c
> @@ -21,6 +21,63 @@ static const char base64_tables[][65] = {
> [BASE64_IMAP] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,",
> };
>
> +static const s8 base64_rev_tables[][256] = {
> + [BASE64_STD] = {
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
> + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
> + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
> + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
> + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
> + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + },
> + [BASE64_URLSAFE] = {
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1,
> + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
> + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
> + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63,
> + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
> + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + },
> + [BASE64_IMAP] = {
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, 63, -1, -1, -1,
> + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
> + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
> + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
> + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
> + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + },
Do we actually need 3 separate lookup tables? It looks like all 3
variants agree on the value of any characters they have in common. So
we could combine them into a single lookup table that would work for a
valid base64 string of any variant. The only downside I can see is
that base64 strings which are invalid in some variants might no longer
be rejected by base64_decode().
> +};
> +
> /**
> * base64_encode() - Base64-encode some binary data
> * @src: the binary data to encode
> @@ -82,11 +139,9 @@ int base64_decode(const char *src, int srclen, u8 *dst, bool padding, enum base6
> int bits = 0;
> int i;
> u8 *bp = dst;
> - const char *base64_table = base64_tables[variant];
> + s8 ch;
>
> for (i = 0; i < srclen; i++) {
> - const char *p = strchr(base64_table, src[i]);
> -
> if (src[i] == '=') {
> ac = (ac << 6);
> bits += 6;
> @@ -94,9 +149,10 @@ int base64_decode(const char *src, int srclen, u8 *dst, bool padding, enum base6
> bits -= 8;
> continue;
> }
> - if (p == NULL || src[i] == 0)
> + ch = base64_rev_tables[variant][(u8)src[i]];
> + if (ch == -1)
Checking for < 0 can save an additional comparison here.
Best,
Caleb
> return -1;
> - ac = (ac << 6) | (p - base64_table);
> + ac = (ac << 6) | ch;
> bits += 6;
> if (bits >= 8) {
> bits -= 8;
> --
> 2.34.1
>
>
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v3 2/6] lib/base64: Optimize base64_decode() with reverse lookup tables
2025-09-26 23:33 ` Caleb Sander Mateos
@ 2025-09-28 6:37 ` Kuan-Wei Chiu
2025-10-01 10:18 ` Guan-Chun Wu
1 sibling, 0 replies; 31+ messages in thread
From: Kuan-Wei Chiu @ 2025-09-28 6:37 UTC (permalink / raw)
To: Caleb Sander Mateos
Cc: Guan-Chun Wu, akpm, axboe, ceph-devel, ebiggers, hch, home7438072,
idryomov, jaegeuk, kbusch, linux-fscrypt, linux-kernel,
linux-nvme, sagi, tytso, xiubli
On Fri, Sep 26, 2025 at 04:33:12PM -0700, Caleb Sander Mateos wrote:
> On Thu, Sep 25, 2025 at 11:59 PM Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
> >
> > From: Kuan-Wei Chiu <visitorckw@gmail.com>
> >
> > Replace the use of strchr() in base64_decode() with precomputed reverse
> > lookup tables for each variant. This avoids repeated string scans and
> > improves performance. Use -1 in the tables to mark invalid characters.
> >
> > Decode:
> > 64B ~1530ns -> ~75ns (~20.4x)
> > 1KB ~27726ns -> ~1165ns (~23.8x)
> >
> > Signed-off-by: Kuan-Wei Chiu <visitorckw@gmail.com>
> > Co-developed-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
> > Signed-off-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
> > ---
> > lib/base64.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++----
> > 1 file changed, 61 insertions(+), 5 deletions(-)
> >
> > diff --git a/lib/base64.c b/lib/base64.c
> > index 1af557785..b20fdf168 100644
> > --- a/lib/base64.c
> > +++ b/lib/base64.c
> > @@ -21,6 +21,63 @@ static const char base64_tables[][65] = {
> > [BASE64_IMAP] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,",
> > };
> >
> > +static const s8 base64_rev_tables[][256] = {
> > + [BASE64_STD] = {
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
> > + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
> > + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
> > + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
> > + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
> > + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + },
> > + [BASE64_URLSAFE] = {
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1,
> > + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
> > + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
> > + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63,
> > + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
> > + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + },
> > + [BASE64_IMAP] = {
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, 63, -1, -1, -1,
> > + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
> > + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
> > + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
> > + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
> > + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + },
>
> Do we actually need 3 separate lookup tables? It looks like all 3
> variants agree on the value of any characters they have in common. So
> we could combine them into a single lookup table that would work for a
> valid base64 string of any variant. The only downside I can see is
> that base64 strings which are invalid in some variants might no longer
> be rejected by base64_decode().
Ah, David also mentioned this earlier, but I forgot about it while
writing the code. Sorry for that. I'll rectify it.
Regards,
Kuan-Wei
>
> > +};
> > +
> > /**
> > * base64_encode() - Base64-encode some binary data
> > * @src: the binary data to encode
> > @@ -82,11 +139,9 @@ int base64_decode(const char *src, int srclen, u8 *dst, bool padding, enum base6
> > int bits = 0;
> > int i;
> > u8 *bp = dst;
> > - const char *base64_table = base64_tables[variant];
> > + s8 ch;
> >
> > for (i = 0; i < srclen; i++) {
> > - const char *p = strchr(base64_table, src[i]);
> > -
> > if (src[i] == '=') {
> > ac = (ac << 6);
> > bits += 6;
> > @@ -94,9 +149,10 @@ int base64_decode(const char *src, int srclen, u8 *dst, bool padding, enum base6
> > bits -= 8;
> > continue;
> > }
> > - if (p == NULL || src[i] == 0)
> > + ch = base64_rev_tables[variant][(u8)src[i]];
> > + if (ch == -1)
>
> Checking for < 0 can save an additional comparison here.
>
> Best,
> Caleb
>
> > return -1;
> > - ac = (ac << 6) | (p - base64_table);
> > + ac = (ac << 6) | ch;
> > bits += 6;
> > if (bits >= 8) {
> > bits -= 8;
> > --
> > 2.34.1
> >
> >
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v3 2/6] lib/base64: Optimize base64_decode() with reverse lookup tables
2025-09-26 6:55 ` [PATCH v3 2/6] lib/base64: Optimize base64_decode() with reverse lookup tables Guan-Chun Wu
2025-09-26 23:33 ` Caleb Sander Mateos
@ 2025-09-28 18:57 ` David Laight
1 sibling, 0 replies; 31+ messages in thread
From: David Laight @ 2025-09-28 18:57 UTC (permalink / raw)
To: Guan-Chun Wu
Cc: akpm, axboe, ceph-devel, ebiggers, hch, home7438072, idryomov,
jaegeuk, kbusch, linux-fscrypt, linux-kernel, linux-nvme, sagi,
tytso, visitorckw, xiubli
On Fri, 26 Sep 2025 14:55:56 +0800
Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
> From: Kuan-Wei Chiu <visitorckw@gmail.com>
>
> Replace the use of strchr() in base64_decode() with precomputed reverse
> lookup tables for each variant. This avoids repeated string scans and
> improves performance. Use -1 in the tables to mark invalid characters.
>
> Decode:
> 64B ~1530ns -> ~75ns (~20.4x)
> 1KB ~27726ns -> ~1165ns (~23.8x)
>
> Signed-off-by: Kuan-Wei Chiu <visitorckw@gmail.com>
> Co-developed-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
> Signed-off-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
> ---
> lib/base64.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++----
> 1 file changed, 61 insertions(+), 5 deletions(-)
>
> diff --git a/lib/base64.c b/lib/base64.c
> index 1af557785..b20fdf168 100644
> --- a/lib/base64.c
> +++ b/lib/base64.c
> @@ -21,6 +21,63 @@ static const char base64_tables[][65] = {
> [BASE64_IMAP] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,",
> };
>
> +static const s8 base64_rev_tables[][256] = {
> + [BASE64_STD] = {
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
> + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
> + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
> + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
> + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
> + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + },
Using:
[BASE64_STD] = {
[0 ... 255] = -1,
['A'] = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
['a'] = 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 48, 50, 51,
['0'] = 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
['+'] = 62,
['/'] = 63};
would be more readable.
(Assuming no one has turned on a warning that stops you defaulting the entries to -1.)
The is also definitely scope for a #define to common things up.
Even if it has to have the values for all the 5 special characters (-1 if not used)
rather than the characters for 62 and 63.
David
> + [BASE64_URLSAFE] = {
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1,
> + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
> + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
> + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63,
> + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
> + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + },
> + [BASE64_IMAP] = {
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, 63, -1, -1, -1,
> + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
> + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
> + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
> + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
> + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> + },
> +};
> +
> /**
> * base64_encode() - Base64-encode some binary data
> * @src: the binary data to encode
> @@ -82,11 +139,9 @@ int base64_decode(const char *src, int srclen, u8 *dst, bool padding, enum base6
> int bits = 0;
> int i;
> u8 *bp = dst;
> - const char *base64_table = base64_tables[variant];
> + s8 ch;
>
> for (i = 0; i < srclen; i++) {
> - const char *p = strchr(base64_table, src[i]);
> -
> if (src[i] == '=') {
> ac = (ac << 6);
> bits += 6;
> @@ -94,9 +149,10 @@ int base64_decode(const char *src, int srclen, u8 *dst, bool padding, enum base6
> bits -= 8;
> continue;
> }
> - if (p == NULL || src[i] == 0)
> + ch = base64_rev_tables[variant][(u8)src[i]];
> + if (ch == -1)
> return -1;
> - ac = (ac << 6) | (p - base64_table);
> + ac = (ac << 6) | ch;
> bits += 6;
> if (bits >= 8) {
> bits -= 8;
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v3 1/6] lib/base64: Add support for multiple variants
2025-09-26 6:55 ` [PATCH v3 1/6] lib/base64: Add support for multiple variants Guan-Chun Wu
@ 2025-09-30 23:56 ` Caleb Sander Mateos
2025-10-01 14:09 ` Guan-Chun Wu
0 siblings, 1 reply; 31+ messages in thread
From: Caleb Sander Mateos @ 2025-09-30 23:56 UTC (permalink / raw)
To: Guan-Chun Wu
Cc: akpm, axboe, ceph-devel, ebiggers, hch, home7438072, idryomov,
jaegeuk, kbusch, linux-fscrypt, linux-kernel, linux-nvme, sagi,
tytso, visitorckw, xiubli
On Thu, Sep 25, 2025 at 11:59 PM Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
>
> From: Kuan-Wei Chiu <visitorckw@gmail.com>
>
> Extend the base64 API to support multiple variants (standard, URL-safe,
> and IMAP) as defined in RFC 4648 and RFC 3501. The API now takes a
> variant parameter and an option to control padding. Update NVMe auth
> code to use the new interface with BASE64_STD.
>
> Signed-off-by: Kuan-Wei Chiu <visitorckw@gmail.com>
> Co-developed-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
> Signed-off-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
> ---
> drivers/nvme/common/auth.c | 4 ++--
> include/linux/base64.h | 10 ++++++++--
> lib/base64.c | 39 ++++++++++++++++++++++----------------
> 3 files changed, 33 insertions(+), 20 deletions(-)
>
> diff --git a/drivers/nvme/common/auth.c b/drivers/nvme/common/auth.c
> index 91e273b89..5fecb53cb 100644
> --- a/drivers/nvme/common/auth.c
> +++ b/drivers/nvme/common/auth.c
> @@ -178,7 +178,7 @@ struct nvme_dhchap_key *nvme_auth_extract_key(unsigned char *secret,
> if (!key)
> return ERR_PTR(-ENOMEM);
>
> - key_len = base64_decode(secret, allocated_len, key->key);
> + key_len = base64_decode(secret, allocated_len, key->key, true, BASE64_STD);
> if (key_len < 0) {
> pr_debug("base64 key decoding error %d\n",
> key_len);
> @@ -663,7 +663,7 @@ int nvme_auth_generate_digest(u8 hmac_id, u8 *psk, size_t psk_len,
> if (ret)
> goto out_free_digest;
>
> - ret = base64_encode(digest, digest_len, enc);
> + ret = base64_encode(digest, digest_len, enc, true, BASE64_STD);
> if (ret < hmac_len) {
> ret = -ENOKEY;
> goto out_free_digest;
> diff --git a/include/linux/base64.h b/include/linux/base64.h
> index 660d4cb1e..a2c6c9222 100644
> --- a/include/linux/base64.h
> +++ b/include/linux/base64.h
> @@ -8,9 +8,15 @@
>
> #include <linux/types.h>
>
> +enum base64_variant {
> + BASE64_STD, /* RFC 4648 (standard) */
> + BASE64_URLSAFE, /* RFC 4648 (base64url) */
> + BASE64_IMAP, /* RFC 3501 */
> +};
> +
> #define BASE64_CHARS(nbytes) DIV_ROUND_UP((nbytes) * 4, 3)
>
> -int base64_encode(const u8 *src, int len, char *dst);
> -int base64_decode(const char *src, int len, u8 *dst);
> +int base64_encode(const u8 *src, int len, char *dst, bool padding, enum base64_variant variant);
> +int base64_decode(const char *src, int len, u8 *dst, bool padding, enum base64_variant variant);
>
> #endif /* _LINUX_BASE64_H */
> diff --git a/lib/base64.c b/lib/base64.c
> index b736a7a43..1af557785 100644
> --- a/lib/base64.c
> +++ b/lib/base64.c
> @@ -1,12 +1,12 @@
> // SPDX-License-Identifier: GPL-2.0
> /*
> - * base64.c - RFC4648-compliant base64 encoding
> + * base64.c - Base64 with support for multiple variants
> *
> * Copyright (c) 2020 Hannes Reinecke, SUSE
> *
> * Based on the base64url routines from fs/crypto/fname.c
> - * (which are using the URL-safe base64 encoding),
> - * modified to use the standard coding table from RFC4648 section 4.
> + * (which are using the URL-safe Base64 encoding),
> + * modified to support multiple Base64 variants.
> */
>
> #include <linux/kernel.h>
> @@ -15,26 +15,31 @@
> #include <linux/string.h>
> #include <linux/base64.h>
>
> -static const char base64_table[65] =
> - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
> +static const char base64_tables[][65] = {
> + [BASE64_STD] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
> + [BASE64_URLSAFE] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",
> + [BASE64_IMAP] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,",
> +};
>
> /**
> - * base64_encode() - base64-encode some binary data
> + * base64_encode() - Base64-encode some binary data
> * @src: the binary data to encode
> * @srclen: the length of @src in bytes
> - * @dst: (output) the base64-encoded string. Not NUL-terminated.
> + * @dst: (output) the Base64-encoded string. Not NUL-terminated.
> + * @padding: whether to append '=' padding characters
> + * @variant: which base64 variant to use
> *
> - * Encodes data using base64 encoding, i.e. the "Base 64 Encoding" specified
> - * by RFC 4648, including the '='-padding.
> + * Encodes data using the selected Base64 variant.
> *
> - * Return: the length of the resulting base64-encoded string in bytes.
> + * Return: the length of the resulting Base64-encoded string in bytes.
> */
> -int base64_encode(const u8 *src, int srclen, char *dst)
> +int base64_encode(const u8 *src, int srclen, char *dst, bool padding, enum base64_variant variant)
Padding isn't actually implemented in this commit? That seems a bit
confusing. I think it would ideally be implemented in the same commit
that adds it. That could be before or after the commit that optimizes
the encode/decode implementations.
Best,
Caleb
> {
> u32 ac = 0;
> int bits = 0;
> int i;
> char *cp = dst;
> + const char *base64_table = base64_tables[variant];
>
> for (i = 0; i < srclen; i++) {
> ac = (ac << 8) | src[i];
> @@ -57,25 +62,27 @@ int base64_encode(const u8 *src, int srclen, char *dst)
> EXPORT_SYMBOL_GPL(base64_encode);
>
> /**
> - * base64_decode() - base64-decode a string
> + * base64_decode() - Base64-decode a string
> * @src: the string to decode. Doesn't need to be NUL-terminated.
> * @srclen: the length of @src in bytes
> * @dst: (output) the decoded binary data
> + * @padding: whether to append '=' padding characters
> + * @variant: which base64 variant to use
> *
> - * Decodes a string using base64 encoding, i.e. the "Base 64 Encoding"
> - * specified by RFC 4648, including the '='-padding.
> + * Decodes a string using the selected Base64 variant.
> *
> * This implementation hasn't been optimized for performance.
> *
> * Return: the length of the resulting decoded binary data in bytes,
> - * or -1 if the string isn't a valid base64 string.
> + * or -1 if the string isn't a valid Base64 string.
> */
> -int base64_decode(const char *src, int srclen, u8 *dst)
> +int base64_decode(const char *src, int srclen, u8 *dst, bool padding, enum base64_variant variant)
> {
> u32 ac = 0;
> int bits = 0;
> int i;
> u8 *bp = dst;
> + const char *base64_table = base64_tables[variant];
>
> for (i = 0; i < srclen; i++) {
> const char *p = strchr(base64_table, src[i]);
> --
> 2.34.1
>
>
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v3 3/6] lib/base64: rework encode/decode for speed and stricter validation
2025-09-26 6:56 ` [PATCH v3 3/6] lib/base64: rework encode/decode for speed and stricter validation Guan-Chun Wu
@ 2025-10-01 0:11 ` Caleb Sander Mateos
2025-10-01 9:39 ` Guan-Chun Wu
0 siblings, 1 reply; 31+ messages in thread
From: Caleb Sander Mateos @ 2025-10-01 0:11 UTC (permalink / raw)
To: Guan-Chun Wu
Cc: akpm, axboe, ceph-devel, ebiggers, hch, home7438072, idryomov,
jaegeuk, kbusch, linux-fscrypt, linux-kernel, linux-nvme, sagi,
tytso, visitorckw, xiubli
On Fri, Sep 26, 2025 at 12:01 AM Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
>
> The old base64 implementation relied on a bit-accumulator loop, which was
> slow for larger inputs and too permissive in validation. It would accept
> extra '=', missing '=', or even '=' appearing in the middle of the input,
> allowing malformed strings to pass. This patch reworks the internals to
> improve performance and enforce stricter validation.
>
> Changes:
> - Encoder:
> * Process input in 3-byte blocks, mapping 24 bits into four 6-bit
> symbols, avoiding bit-by-bit shifting and reducing loop iterations.
> * Handle the final 1-2 leftover bytes explicitly and emit '=' only when
> requested.
> - Decoder:
> * Based on the reverse lookup tables from the previous patch, decode
> input in 4-character groups.
> * Each group is looked up directly, converted into numeric values, and
> combined into 3 output bytes.
> * Explicitly handle padded and unpadded forms:
> - With padding: input length must be a multiple of 4, and '=' is
> allowed only in the last two positions. Reject stray or early '='.
> - Without padding: validate tail lengths (2 or 3 chars) and require
> unused low bits to be zero.
> * Removed the bit-accumulator style loop to reduce loop iterations.
>
> Performance (x86_64, Intel Core i7-10700 @ 2.90GHz, avg over 1000 runs,
> KUnit):
>
> Encode:
> 64B ~90ns -> ~32ns (~2.8x)
> 1KB ~1332ns -> ~510ns (~2.6x)
>
> Decode:
> 64B ~1530ns -> ~64ns (~23.9x)
> 1KB ~27726ns -> ~982ns (~28.3x)
>
> Co-developed-by: Kuan-Wei Chiu <visitorckw@gmail.com>
> Signed-off-by: Kuan-Wei Chiu <visitorckw@gmail.com>
> Co-developed-by: Yu-Sheng Huang <home7438072@gmail.com>
> Signed-off-by: Yu-Sheng Huang <home7438072@gmail.com>
> Signed-off-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
> ---
> lib/base64.c | 150 +++++++++++++++++++++++++++++++++++++--------------
> 1 file changed, 110 insertions(+), 40 deletions(-)
>
> diff --git a/lib/base64.c b/lib/base64.c
> index b20fdf168..fd1db4611 100644
> --- a/lib/base64.c
> +++ b/lib/base64.c
> @@ -93,26 +93,43 @@ static const s8 base64_rev_tables[][256] = {
> int base64_encode(const u8 *src, int srclen, char *dst, bool padding, enum base64_variant variant)
> {
> u32 ac = 0;
> - int bits = 0;
> - int i;
> char *cp = dst;
> const char *base64_table = base64_tables[variant];
>
> - for (i = 0; i < srclen; i++) {
> - ac = (ac << 8) | src[i];
> - bits += 8;
> - do {
> - bits -= 6;
> - *cp++ = base64_table[(ac >> bits) & 0x3f];
> - } while (bits >= 6);
> - }
> - if (bits) {
> - *cp++ = base64_table[(ac << (6 - bits)) & 0x3f];
> - bits -= 6;
> + while (srclen >= 3) {
> + ac = ((u32)src[0] << 16) |
> + ((u32)src[1] << 8) |
> + (u32)src[2];
> +
> + *cp++ = base64_table[ac >> 18];
> + *cp++ = base64_table[(ac >> 12) & 0x3f];
> + *cp++ = base64_table[(ac >> 6) & 0x3f];
> + *cp++ = base64_table[ac & 0x3f];
> +
> + src += 3;
> + srclen -= 3;
> }
> - while (bits < 0) {
> - *cp++ = '=';
> - bits += 2;
> +
> + switch (srclen) {
> + case 2:
> + ac = ((u32)src[0] << 16) |
> + ((u32)src[1] << 8);
> +
> + *cp++ = base64_table[ac >> 18];
> + *cp++ = base64_table[(ac >> 12) & 0x3f];
> + *cp++ = base64_table[(ac >> 6) & 0x3f];
> + if (padding)
> + *cp++ = '=';
> + break;
> + case 1:
> + ac = ((u32)src[0] << 16);
> + *cp++ = base64_table[ac >> 18];
> + *cp++ = base64_table[(ac >> 12) & 0x3f];
> + if (padding) {
> + *cp++ = '=';
> + *cp++ = '=';
> + }
> + break;
> }
> return cp - dst;
> }
> @@ -128,39 +145,92 @@ EXPORT_SYMBOL_GPL(base64_encode);
> *
> * Decodes a string using the selected Base64 variant.
> *
> - * This implementation hasn't been optimized for performance.
> - *
> * Return: the length of the resulting decoded binary data in bytes,
> * or -1 if the string isn't a valid Base64 string.
> */
> int base64_decode(const char *src, int srclen, u8 *dst, bool padding, enum base64_variant variant)
> {
> - u32 ac = 0;
> - int bits = 0;
> - int i;
> u8 *bp = dst;
> - s8 ch;
> -
> - for (i = 0; i < srclen; i++) {
> - if (src[i] == '=') {
> - ac = (ac << 6);
> - bits += 6;
> - if (bits >= 8)
> - bits -= 8;
> - continue;
> - }
> - ch = base64_rev_tables[variant][(u8)src[i]];
> - if (ch == -1)
> + s8 input1, input2, input3, input4;
> + u32 val;
> +
> + if (srclen == 0)
> + return 0;
Doesn't look like this special case is necessary; all the if and while
conditions below are false if srclen == 0, so the function will just
end up returning 0 in that case anyways. It would be nice to avoid
this branch, especially as it seems like an uncommon case.
> +
> + /* Validate the input length for padding */
> + if (unlikely(padding && (srclen & 0x03) != 0))
> + return -1;
> +
> + while (srclen >= 4) {
> + /* Decode the next 4 characters */
> + input1 = base64_rev_tables[variant][(u8)src[0]];
> + input2 = base64_rev_tables[variant][(u8)src[1]];
> + input3 = base64_rev_tables[variant][(u8)src[2]];
> + input4 = base64_rev_tables[variant][(u8)src[3]];
> +
> + /* Return error if any Base64 character is invalid */
> + if (unlikely(input1 < 0 || input2 < 0 || (!padding && (input3 < 0 || input4 < 0))))
> + return -1;
> +
> + /* Handle padding */
> + if (unlikely(padding && ((input3 < 0 && input4 >= 0) ||
> + (input3 < 0 && src[2] != '=') ||
> + (input4 < 0 && src[3] != '=') ||
> + (srclen > 4 && (input3 < 0 || input4 < 0)))))
Would be preferable to check and strip the padding (i.e. decrease
srclen) before this main loop. That way we could avoid several
branches in this hot loop that are only necessary to handle the
padding chars.
> + return -1;
> + val = ((u32)input1 << 18) |
> + ((u32)input2 << 12) |
> + ((u32)((input3 < 0) ? 0 : input3) << 6) |
> + (u32)((input4 < 0) ? 0 : input4);
> +
> + *bp++ = (u8)(val >> 16);
> +
> + if (input3 >= 0)
> + *bp++ = (u8)(val >> 8);
> + if (input4 >= 0)
> + *bp++ = (u8)val;
> +
> + src += 4;
> + srclen -= 4;
> + }
> +
> + /* Handle leftover characters when padding is not used */
> + if (!padding && srclen > 0) {
> + switch (srclen) {
> + case 2:
> + input1 = base64_rev_tables[variant][(u8)src[0]];
> + input2 = base64_rev_tables[variant][(u8)src[1]];
> + if (unlikely(input1 < 0 || input2 < 0))
> + return -1;
> +
> + val = ((u32)input1 << 6) | (u32)input2; /* 12 bits */
> + if (unlikely(val & 0x0F))
> + return -1; /* low 4 bits must be zero */
> +
> + *bp++ = (u8)(val >> 4);
> + break;
> + case 3:
> + input1 = base64_rev_tables[variant][(u8)src[0]];
> + input2 = base64_rev_tables[variant][(u8)src[1]];
> + input3 = base64_rev_tables[variant][(u8)src[2]];
> + if (unlikely(input1 < 0 || input2 < 0 || input3 < 0))
> + return -1;
> +
> + val = ((u32)input1 << 12) |
> + ((u32)input2 << 6) |
> + (u32)input3; /* 18 bits */
> +
> + if (unlikely(val & 0x03))
> + return -1; /* low 2 bits must be zero */
> +
> + *bp++ = (u8)(val >> 10);
> + *bp++ = (u8)((val >> 2) & 0xFF);
"& 0xFF" is redundant with the cast to u8.
Best,
Caleb
> + break;
> + default:
> return -1;
> - ac = (ac << 6) | ch;
> - bits += 6;
> - if (bits >= 8) {
> - bits -= 8;
> - *bp++ = (u8)(ac >> bits);
> }
> }
> - if (ac & ((1 << bits) - 1))
> - return -1;
> +
> return bp - dst;
> }
> EXPORT_SYMBOL_GPL(base64_decode);
> --
> 2.34.1
>
>
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v3 3/6] lib/base64: rework encode/decode for speed and stricter validation
2025-10-01 0:11 ` Caleb Sander Mateos
@ 2025-10-01 9:39 ` Guan-Chun Wu
2025-10-06 20:52 ` David Laight
0 siblings, 1 reply; 31+ messages in thread
From: Guan-Chun Wu @ 2025-10-01 9:39 UTC (permalink / raw)
To: Caleb Sander Mateos
Cc: akpm, axboe, ceph-devel, ebiggers, hch, home7438072, idryomov,
jaegeuk, kbusch, linux-fscrypt, linux-kernel, linux-nvme, sagi,
tytso, visitorckw, xiubli
On Tue, Sep 30, 2025 at 05:11:12PM -0700, Caleb Sander Mateos wrote:
> On Fri, Sep 26, 2025 at 12:01 AM Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
> >
> > The old base64 implementation relied on a bit-accumulator loop, which was
> > slow for larger inputs and too permissive in validation. It would accept
> > extra '=', missing '=', or even '=' appearing in the middle of the input,
> > allowing malformed strings to pass. This patch reworks the internals to
> > improve performance and enforce stricter validation.
> >
> > Changes:
> > - Encoder:
> > * Process input in 3-byte blocks, mapping 24 bits into four 6-bit
> > symbols, avoiding bit-by-bit shifting and reducing loop iterations.
> > * Handle the final 1-2 leftover bytes explicitly and emit '=' only when
> > requested.
> > - Decoder:
> > * Based on the reverse lookup tables from the previous patch, decode
> > input in 4-character groups.
> > * Each group is looked up directly, converted into numeric values, and
> > combined into 3 output bytes.
> > * Explicitly handle padded and unpadded forms:
> > - With padding: input length must be a multiple of 4, and '=' is
> > allowed only in the last two positions. Reject stray or early '='.
> > - Without padding: validate tail lengths (2 or 3 chars) and require
> > unused low bits to be zero.
> > * Removed the bit-accumulator style loop to reduce loop iterations.
> >
> > Performance (x86_64, Intel Core i7-10700 @ 2.90GHz, avg over 1000 runs,
> > KUnit):
> >
> > Encode:
> > 64B ~90ns -> ~32ns (~2.8x)
> > 1KB ~1332ns -> ~510ns (~2.6x)
> >
> > Decode:
> > 64B ~1530ns -> ~64ns (~23.9x)
> > 1KB ~27726ns -> ~982ns (~28.3x)
> >
> > Co-developed-by: Kuan-Wei Chiu <visitorckw@gmail.com>
> > Signed-off-by: Kuan-Wei Chiu <visitorckw@gmail.com>
> > Co-developed-by: Yu-Sheng Huang <home7438072@gmail.com>
> > Signed-off-by: Yu-Sheng Huang <home7438072@gmail.com>
> > Signed-off-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
> > ---
> > lib/base64.c | 150 +++++++++++++++++++++++++++++++++++++--------------
> > 1 file changed, 110 insertions(+), 40 deletions(-)
> >
> > diff --git a/lib/base64.c b/lib/base64.c
> > index b20fdf168..fd1db4611 100644
> > --- a/lib/base64.c
> > +++ b/lib/base64.c
> > @@ -93,26 +93,43 @@ static const s8 base64_rev_tables[][256] = {
> > int base64_encode(const u8 *src, int srclen, char *dst, bool padding, enum base64_variant variant)
> > {
> > u32 ac = 0;
> > - int bits = 0;
> > - int i;
> > char *cp = dst;
> > const char *base64_table = base64_tables[variant];
> >
> > - for (i = 0; i < srclen; i++) {
> > - ac = (ac << 8) | src[i];
> > - bits += 8;
> > - do {
> > - bits -= 6;
> > - *cp++ = base64_table[(ac >> bits) & 0x3f];
> > - } while (bits >= 6);
> > - }
> > - if (bits) {
> > - *cp++ = base64_table[(ac << (6 - bits)) & 0x3f];
> > - bits -= 6;
> > + while (srclen >= 3) {
> > + ac = ((u32)src[0] << 16) |
> > + ((u32)src[1] << 8) |
> > + (u32)src[2];
> > +
> > + *cp++ = base64_table[ac >> 18];
> > + *cp++ = base64_table[(ac >> 12) & 0x3f];
> > + *cp++ = base64_table[(ac >> 6) & 0x3f];
> > + *cp++ = base64_table[ac & 0x3f];
> > +
> > + src += 3;
> > + srclen -= 3;
> > }
> > - while (bits < 0) {
> > - *cp++ = '=';
> > - bits += 2;
> > +
> > + switch (srclen) {
> > + case 2:
> > + ac = ((u32)src[0] << 16) |
> > + ((u32)src[1] << 8);
> > +
> > + *cp++ = base64_table[ac >> 18];
> > + *cp++ = base64_table[(ac >> 12) & 0x3f];
> > + *cp++ = base64_table[(ac >> 6) & 0x3f];
> > + if (padding)
> > + *cp++ = '=';
> > + break;
> > + case 1:
> > + ac = ((u32)src[0] << 16);
> > + *cp++ = base64_table[ac >> 18];
> > + *cp++ = base64_table[(ac >> 12) & 0x3f];
> > + if (padding) {
> > + *cp++ = '=';
> > + *cp++ = '=';
> > + }
> > + break;
> > }
> > return cp - dst;
> > }
> > @@ -128,39 +145,92 @@ EXPORT_SYMBOL_GPL(base64_encode);
> > *
> > * Decodes a string using the selected Base64 variant.
> > *
> > - * This implementation hasn't been optimized for performance.
> > - *
> > * Return: the length of the resulting decoded binary data in bytes,
> > * or -1 if the string isn't a valid Base64 string.
> > */
> > int base64_decode(const char *src, int srclen, u8 *dst, bool padding, enum base64_variant variant)
> > {
> > - u32 ac = 0;
> > - int bits = 0;
> > - int i;
> > u8 *bp = dst;
> > - s8 ch;
> > -
> > - for (i = 0; i < srclen; i++) {
> > - if (src[i] == '=') {
> > - ac = (ac << 6);
> > - bits += 6;
> > - if (bits >= 8)
> > - bits -= 8;
> > - continue;
> > - }
> > - ch = base64_rev_tables[variant][(u8)src[i]];
> > - if (ch == -1)
> > + s8 input1, input2, input3, input4;
> > + u32 val;
> > +
> > + if (srclen == 0)
> > + return 0;
>
> Doesn't look like this special case is necessary; all the if and while
> conditions below are false if srclen == 0, so the function will just
> end up returning 0 in that case anyways. It would be nice to avoid
> this branch, especially as it seems like an uncommon case.
>
You're right. I'll remove it. Thanks.
> > +
> > + /* Validate the input length for padding */
> > + if (unlikely(padding && (srclen & 0x03) != 0))
> > + return -1;
> > +
> > + while (srclen >= 4) {
> > + /* Decode the next 4 characters */
> > + input1 = base64_rev_tables[variant][(u8)src[0]];
> > + input2 = base64_rev_tables[variant][(u8)src[1]];
> > + input3 = base64_rev_tables[variant][(u8)src[2]];
> > + input4 = base64_rev_tables[variant][(u8)src[3]];
> > +
> > + /* Return error if any Base64 character is invalid */
> > + if (unlikely(input1 < 0 || input2 < 0 || (!padding && (input3 < 0 || input4 < 0))))
> > + return -1;
> > +
> > + /* Handle padding */
> > + if (unlikely(padding && ((input3 < 0 && input4 >= 0) ||
> > + (input3 < 0 && src[2] != '=') ||
> > + (input4 < 0 && src[3] != '=') ||
> > + (srclen > 4 && (input3 < 0 || input4 < 0)))))
>
> Would be preferable to check and strip the padding (i.e. decrease
> srclen) before this main loop. That way we could avoid several
> branches in this hot loop that are only necessary to handle the
> padding chars.
>
You're right. As long as we check and strip the padding first, the
behavior with or without padding can be the same, and it could also
reduce some unnecessary branches. I'll make the change.
Best regards,
Guan-Chun
> > + return -1;
> > + val = ((u32)input1 << 18) |
> > + ((u32)input2 << 12) |
> > + ((u32)((input3 < 0) ? 0 : input3) << 6) |
> > + (u32)((input4 < 0) ? 0 : input4);
> > +
> > + *bp++ = (u8)(val >> 16);
> > +
> > + if (input3 >= 0)
> > + *bp++ = (u8)(val >> 8);
> > + if (input4 >= 0)
> > + *bp++ = (u8)val;
> > +
> > + src += 4;
> > + srclen -= 4;
> > + }
> > +
> > + /* Handle leftover characters when padding is not used */
> > + if (!padding && srclen > 0) {
> > + switch (srclen) {
> > + case 2:
> > + input1 = base64_rev_tables[variant][(u8)src[0]];
> > + input2 = base64_rev_tables[variant][(u8)src[1]];
> > + if (unlikely(input1 < 0 || input2 < 0))
> > + return -1;
> > +
> > + val = ((u32)input1 << 6) | (u32)input2; /* 12 bits */
> > + if (unlikely(val & 0x0F))
> > + return -1; /* low 4 bits must be zero */
> > +
> > + *bp++ = (u8)(val >> 4);
> > + break;
> > + case 3:
> > + input1 = base64_rev_tables[variant][(u8)src[0]];
> > + input2 = base64_rev_tables[variant][(u8)src[1]];
> > + input3 = base64_rev_tables[variant][(u8)src[2]];
> > + if (unlikely(input1 < 0 || input2 < 0 || input3 < 0))
> > + return -1;
> > +
> > + val = ((u32)input1 << 12) |
> > + ((u32)input2 << 6) |
> > + (u32)input3; /* 18 bits */
> > +
> > + if (unlikely(val & 0x03))
> > + return -1; /* low 2 bits must be zero */
> > +
> > + *bp++ = (u8)(val >> 10);
> > + *bp++ = (u8)((val >> 2) & 0xFF);
>
> "& 0xFF" is redundant with the cast to u8.
>
> Best,
> Caleb
>
> > + break;
> > + default:
> > return -1;
> > - ac = (ac << 6) | ch;
> > - bits += 6;
> > - if (bits >= 8) {
> > - bits -= 8;
> > - *bp++ = (u8)(ac >> bits);
> > }
> > }
> > - if (ac & ((1 << bits) - 1))
> > - return -1;
> > +
> > return bp - dst;
> > }
> > EXPORT_SYMBOL_GPL(base64_decode);
> > --
> > 2.34.1
> >
> >
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v3 2/6] lib/base64: Optimize base64_decode() with reverse lookup tables
2025-09-26 23:33 ` Caleb Sander Mateos
2025-09-28 6:37 ` Kuan-Wei Chiu
@ 2025-10-01 10:18 ` Guan-Chun Wu
2025-10-01 16:20 ` Caleb Sander Mateos
1 sibling, 1 reply; 31+ messages in thread
From: Guan-Chun Wu @ 2025-10-01 10:18 UTC (permalink / raw)
To: Caleb Sander Mateos
Cc: akpm, axboe, ceph-devel, ebiggers, hch, home7438072, idryomov,
jaegeuk, kbusch, linux-fscrypt, linux-kernel, linux-nvme, sagi,
tytso, visitorckw, xiubli
On Fri, Sep 26, 2025 at 04:33:12PM -0700, Caleb Sander Mateos wrote:
> On Thu, Sep 25, 2025 at 11:59 PM Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
> >
> > From: Kuan-Wei Chiu <visitorckw@gmail.com>
> >
> > Replace the use of strchr() in base64_decode() with precomputed reverse
> > lookup tables for each variant. This avoids repeated string scans and
> > improves performance. Use -1 in the tables to mark invalid characters.
> >
> > Decode:
> > 64B ~1530ns -> ~75ns (~20.4x)
> > 1KB ~27726ns -> ~1165ns (~23.8x)
> >
> > Signed-off-by: Kuan-Wei Chiu <visitorckw@gmail.com>
> > Co-developed-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
> > Signed-off-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
> > ---
> > lib/base64.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++----
> > 1 file changed, 61 insertions(+), 5 deletions(-)
> >
> > diff --git a/lib/base64.c b/lib/base64.c
> > index 1af557785..b20fdf168 100644
> > --- a/lib/base64.c
> > +++ b/lib/base64.c
> > @@ -21,6 +21,63 @@ static const char base64_tables[][65] = {
> > [BASE64_IMAP] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,",
> > };
> >
> > +static const s8 base64_rev_tables[][256] = {
> > + [BASE64_STD] = {
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
> > + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
> > + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
> > + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
> > + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
> > + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + },
> > + [BASE64_URLSAFE] = {
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1,
> > + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
> > + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
> > + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63,
> > + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
> > + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + },
> > + [BASE64_IMAP] = {
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, 63, -1, -1, -1,
> > + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
> > + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
> > + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
> > + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
> > + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > + },
>
> Do we actually need 3 separate lookup tables? It looks like all 3
> variants agree on the value of any characters they have in common. So
> we could combine them into a single lookup table that would work for a
> valid base64 string of any variant. The only downside I can see is
> that base64 strings which are invalid in some variants might no longer
> be rejected by base64_decode().
>
In addition to the approach David mentioned, maybe we can use a common
lookup table for A–Z, a–z, and 0–9, and then handle the variant-specific
symbols with a switch.
For example:
static const s8 base64_rev_common[256] = {
[0 ... 255] = -1,
['A'] = 0, ['B'] = 1, /* ... */, ['Z'] = 25,
['a'] = 26, /* ... */, ['z'] = 51,
['0'] = 52, /* ... */, ['9'] = 61,
};
static inline int base64_rev_lookup(u8 c, enum base64_variant variant) {
s8 v = base64_rev_common[c];
if (v != -1)
return v;
switch (variant) {
case BASE64_STD:
if (c == '+') return 62;
if (c == '/') return 63;
break;
case BASE64_IMAP:
if (c == '+') return 62;
if (c == ',') return 63;
break;
case BASE64_URLSAFE:
if (c == '-') return 62;
if (c == '_') return 63;
break;
}
return -1;
}
What do you think?
Best regards,
Guan-Chun
> > +};
> > +
> > /**
> > * base64_encode() - Base64-encode some binary data
> > * @src: the binary data to encode
> > @@ -82,11 +139,9 @@ int base64_decode(const char *src, int srclen, u8 *dst, bool padding, enum base6
> > int bits = 0;
> > int i;
> > u8 *bp = dst;
> > - const char *base64_table = base64_tables[variant];
> > + s8 ch;
> >
> > for (i = 0; i < srclen; i++) {
> > - const char *p = strchr(base64_table, src[i]);
> > -
> > if (src[i] == '=') {
> > ac = (ac << 6);
> > bits += 6;
> > @@ -94,9 +149,10 @@ int base64_decode(const char *src, int srclen, u8 *dst, bool padding, enum base6
> > bits -= 8;
> > continue;
> > }
> > - if (p == NULL || src[i] == 0)
> > + ch = base64_rev_tables[variant][(u8)src[i]];
> > + if (ch == -1)
>
> Checking for < 0 can save an additional comparison here.
>
> Best,
> Caleb
>
> > return -1;
> > - ac = (ac << 6) | (p - base64_table);
> > + ac = (ac << 6) | ch;
> > bits += 6;
> > if (bits >= 8) {
> > bits -= 8;
> > --
> > 2.34.1
> >
> >
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v3 1/6] lib/base64: Add support for multiple variants
2025-09-30 23:56 ` Caleb Sander Mateos
@ 2025-10-01 14:09 ` Guan-Chun Wu
0 siblings, 0 replies; 31+ messages in thread
From: Guan-Chun Wu @ 2025-10-01 14:09 UTC (permalink / raw)
To: Caleb Sander Mateos
Cc: akpm, axboe, ceph-devel, ebiggers, hch, home7438072, idryomov,
jaegeuk, kbusch, linux-fscrypt, linux-kernel, linux-nvme, sagi,
tytso, visitorckw, xiubli
On Tue, Sep 30, 2025 at 04:56:17PM -0700, Caleb Sander Mateos wrote:
> On Thu, Sep 25, 2025 at 11:59 PM Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
> >
> > From: Kuan-Wei Chiu <visitorckw@gmail.com>
> >
> > Extend the base64 API to support multiple variants (standard, URL-safe,
> > and IMAP) as defined in RFC 4648 and RFC 3501. The API now takes a
> > variant parameter and an option to control padding. Update NVMe auth
> > code to use the new interface with BASE64_STD.
> >
> > Signed-off-by: Kuan-Wei Chiu <visitorckw@gmail.com>
> > Co-developed-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
> > Signed-off-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
> > ---
> > drivers/nvme/common/auth.c | 4 ++--
> > include/linux/base64.h | 10 ++++++++--
> > lib/base64.c | 39 ++++++++++++++++++++++----------------
> > 3 files changed, 33 insertions(+), 20 deletions(-)
> >
> > diff --git a/drivers/nvme/common/auth.c b/drivers/nvme/common/auth.c
> > index 91e273b89..5fecb53cb 100644
> > --- a/drivers/nvme/common/auth.c
> > +++ b/drivers/nvme/common/auth.c
> > @@ -178,7 +178,7 @@ struct nvme_dhchap_key *nvme_auth_extract_key(unsigned char *secret,
> > if (!key)
> > return ERR_PTR(-ENOMEM);
> >
> > - key_len = base64_decode(secret, allocated_len, key->key);
> > + key_len = base64_decode(secret, allocated_len, key->key, true, BASE64_STD);
> > if (key_len < 0) {
> > pr_debug("base64 key decoding error %d\n",
> > key_len);
> > @@ -663,7 +663,7 @@ int nvme_auth_generate_digest(u8 hmac_id, u8 *psk, size_t psk_len,
> > if (ret)
> > goto out_free_digest;
> >
> > - ret = base64_encode(digest, digest_len, enc);
> > + ret = base64_encode(digest, digest_len, enc, true, BASE64_STD);
> > if (ret < hmac_len) {
> > ret = -ENOKEY;
> > goto out_free_digest;
> > diff --git a/include/linux/base64.h b/include/linux/base64.h
> > index 660d4cb1e..a2c6c9222 100644
> > --- a/include/linux/base64.h
> > +++ b/include/linux/base64.h
> > @@ -8,9 +8,15 @@
> >
> > #include <linux/types.h>
> >
> > +enum base64_variant {
> > + BASE64_STD, /* RFC 4648 (standard) */
> > + BASE64_URLSAFE, /* RFC 4648 (base64url) */
> > + BASE64_IMAP, /* RFC 3501 */
> > +};
> > +
> > #define BASE64_CHARS(nbytes) DIV_ROUND_UP((nbytes) * 4, 3)
> >
> > -int base64_encode(const u8 *src, int len, char *dst);
> > -int base64_decode(const char *src, int len, u8 *dst);
> > +int base64_encode(const u8 *src, int len, char *dst, bool padding, enum base64_variant variant);
> > +int base64_decode(const char *src, int len, u8 *dst, bool padding, enum base64_variant variant);
> >
> > #endif /* _LINUX_BASE64_H */
> > diff --git a/lib/base64.c b/lib/base64.c
> > index b736a7a43..1af557785 100644
> > --- a/lib/base64.c
> > +++ b/lib/base64.c
> > @@ -1,12 +1,12 @@
> > // SPDX-License-Identifier: GPL-2.0
> > /*
> > - * base64.c - RFC4648-compliant base64 encoding
> > + * base64.c - Base64 with support for multiple variants
> > *
> > * Copyright (c) 2020 Hannes Reinecke, SUSE
> > *
> > * Based on the base64url routines from fs/crypto/fname.c
> > - * (which are using the URL-safe base64 encoding),
> > - * modified to use the standard coding table from RFC4648 section 4.
> > + * (which are using the URL-safe Base64 encoding),
> > + * modified to support multiple Base64 variants.
> > */
> >
> > #include <linux/kernel.h>
> > @@ -15,26 +15,31 @@
> > #include <linux/string.h>
> > #include <linux/base64.h>
> >
> > -static const char base64_table[65] =
> > - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
> > +static const char base64_tables[][65] = {
> > + [BASE64_STD] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
> > + [BASE64_URLSAFE] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",
> > + [BASE64_IMAP] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,",
> > +};
> >
> > /**
> > - * base64_encode() - base64-encode some binary data
> > + * base64_encode() - Base64-encode some binary data
> > * @src: the binary data to encode
> > * @srclen: the length of @src in bytes
> > - * @dst: (output) the base64-encoded string. Not NUL-terminated.
> > + * @dst: (output) the Base64-encoded string. Not NUL-terminated.
> > + * @padding: whether to append '=' padding characters
> > + * @variant: which base64 variant to use
> > *
> > - * Encodes data using base64 encoding, i.e. the "Base 64 Encoding" specified
> > - * by RFC 4648, including the '='-padding.
> > + * Encodes data using the selected Base64 variant.
> > *
> > - * Return: the length of the resulting base64-encoded string in bytes.
> > + * Return: the length of the resulting Base64-encoded string in bytes.
> > */
> > -int base64_encode(const u8 *src, int srclen, char *dst)
> > +int base64_encode(const u8 *src, int srclen, char *dst, bool padding, enum base64_variant variant)
>
> Padding isn't actually implemented in this commit? That seems a bit
> confusing. I think it would ideally be implemented in the same commit
> that adds it. That could be before or after the commit that optimizes
> the encode/decode implementations.
>
> Best,
> Caleb
>
Got it, thanks for pointing that out. We'll address it in the next version.
Best regards,
Guan-Chun
> > {
> > u32 ac = 0;
> > int bits = 0;
> > int i;
> > char *cp = dst;
> > + const char *base64_table = base64_tables[variant];
> >
> > for (i = 0; i < srclen; i++) {
> > ac = (ac << 8) | src[i];
> > @@ -57,25 +62,27 @@ int base64_encode(const u8 *src, int srclen, char *dst)
> > EXPORT_SYMBOL_GPL(base64_encode);
> >
> > /**
> > - * base64_decode() - base64-decode a string
> > + * base64_decode() - Base64-decode a string
> > * @src: the string to decode. Doesn't need to be NUL-terminated.
> > * @srclen: the length of @src in bytes
> > * @dst: (output) the decoded binary data
> > + * @padding: whether to append '=' padding characters
> > + * @variant: which base64 variant to use
> > *
> > - * Decodes a string using base64 encoding, i.e. the "Base 64 Encoding"
> > - * specified by RFC 4648, including the '='-padding.
> > + * Decodes a string using the selected Base64 variant.
> > *
> > * This implementation hasn't been optimized for performance.
> > *
> > * Return: the length of the resulting decoded binary data in bytes,
> > - * or -1 if the string isn't a valid base64 string.
> > + * or -1 if the string isn't a valid Base64 string.
> > */
> > -int base64_decode(const char *src, int srclen, u8 *dst)
> > +int base64_decode(const char *src, int srclen, u8 *dst, bool padding, enum base64_variant variant)
> > {
> > u32 ac = 0;
> > int bits = 0;
> > int i;
> > u8 *bp = dst;
> > + const char *base64_table = base64_tables[variant];
> >
> > for (i = 0; i < srclen; i++) {
> > const char *p = strchr(base64_table, src[i]);
> > --
> > 2.34.1
> >
> >
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v3 2/6] lib/base64: Optimize base64_decode() with reverse lookup tables
2025-10-01 10:18 ` Guan-Chun Wu
@ 2025-10-01 16:20 ` Caleb Sander Mateos
2025-10-05 17:18 ` David Laight
0 siblings, 1 reply; 31+ messages in thread
From: Caleb Sander Mateos @ 2025-10-01 16:20 UTC (permalink / raw)
To: Guan-Chun Wu
Cc: akpm, axboe, ceph-devel, ebiggers, hch, home7438072, idryomov,
jaegeuk, kbusch, linux-fscrypt, linux-kernel, linux-nvme, sagi,
tytso, visitorckw, xiubli
On Wed, Oct 1, 2025 at 3:18 AM Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
>
> On Fri, Sep 26, 2025 at 04:33:12PM -0700, Caleb Sander Mateos wrote:
> > On Thu, Sep 25, 2025 at 11:59 PM Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
> > >
> > > From: Kuan-Wei Chiu <visitorckw@gmail.com>
> > >
> > > Replace the use of strchr() in base64_decode() with precomputed reverse
> > > lookup tables for each variant. This avoids repeated string scans and
> > > improves performance. Use -1 in the tables to mark invalid characters.
> > >
> > > Decode:
> > > 64B ~1530ns -> ~75ns (~20.4x)
> > > 1KB ~27726ns -> ~1165ns (~23.8x)
> > >
> > > Signed-off-by: Kuan-Wei Chiu <visitorckw@gmail.com>
> > > Co-developed-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
> > > Signed-off-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
> > > ---
> > > lib/base64.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++----
> > > 1 file changed, 61 insertions(+), 5 deletions(-)
> > >
> > > diff --git a/lib/base64.c b/lib/base64.c
> > > index 1af557785..b20fdf168 100644
> > > --- a/lib/base64.c
> > > +++ b/lib/base64.c
> > > @@ -21,6 +21,63 @@ static const char base64_tables[][65] = {
> > > [BASE64_IMAP] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,",
> > > };
> > >
> > > +static const s8 base64_rev_tables[][256] = {
> > > + [BASE64_STD] = {
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
> > > + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
> > > + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
> > > + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
> > > + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
> > > + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > + },
> > > + [BASE64_URLSAFE] = {
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1,
> > > + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
> > > + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
> > > + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63,
> > > + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
> > > + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > + },
> > > + [BASE64_IMAP] = {
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, 63, -1, -1, -1,
> > > + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
> > > + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
> > > + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
> > > + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
> > > + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > + },
> >
> > Do we actually need 3 separate lookup tables? It looks like all 3
> > variants agree on the value of any characters they have in common. So
> > we could combine them into a single lookup table that would work for a
> > valid base64 string of any variant. The only downside I can see is
> > that base64 strings which are invalid in some variants might no longer
> > be rejected by base64_decode().
> >
>
> In addition to the approach David mentioned, maybe we can use a common
> lookup table for A–Z, a–z, and 0–9, and then handle the variant-specific
> symbols with a switch.
>
> For example:
>
> static const s8 base64_rev_common[256] = {
> [0 ... 255] = -1,
> ['A'] = 0, ['B'] = 1, /* ... */, ['Z'] = 25,
> ['a'] = 26, /* ... */, ['z'] = 51,
> ['0'] = 52, /* ... */, ['9'] = 61,
> };
>
> static inline int base64_rev_lookup(u8 c, enum base64_variant variant) {
> s8 v = base64_rev_common[c];
> if (v != -1)
> return v;
>
> switch (variant) {
> case BASE64_STD:
> if (c == '+') return 62;
> if (c == '/') return 63;
> break;
> case BASE64_IMAP:
> if (c == '+') return 62;
> if (c == ',') return 63;
> break;
> case BASE64_URLSAFE:
> if (c == '-') return 62;
> if (c == '_') return 63;
> break;
> }
> return -1;
> }
>
> What do you think?
That adds several branches in the hot loop, at least 2 of which are
unpredictable for valid base64 input of a given variant (v != -1 as
well as the first c check in the applicable switch case). That seems
like it would hurt performance, no? I think having 3 separate tables
would be preferable to making the hot loop more branchy.
Best,
Caleb
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v3 2/6] lib/base64: Optimize base64_decode() with reverse lookup tables
2025-10-01 16:20 ` Caleb Sander Mateos
@ 2025-10-05 17:18 ` David Laight
2025-10-07 8:28 ` Guan-Chun Wu
0 siblings, 1 reply; 31+ messages in thread
From: David Laight @ 2025-10-05 17:18 UTC (permalink / raw)
To: Caleb Sander Mateos
Cc: Guan-Chun Wu, akpm, axboe, ceph-devel, ebiggers, hch, home7438072,
idryomov, jaegeuk, kbusch, linux-fscrypt, linux-kernel,
linux-nvme, sagi, tytso, visitorckw, xiubli
On Wed, 1 Oct 2025 09:20:27 -0700
Caleb Sander Mateos <csander@purestorage.com> wrote:
> On Wed, Oct 1, 2025 at 3:18 AM Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
> >
> > On Fri, Sep 26, 2025 at 04:33:12PM -0700, Caleb Sander Mateos wrote:
> > > On Thu, Sep 25, 2025 at 11:59 PM Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
> > > >
> > > > From: Kuan-Wei Chiu <visitorckw@gmail.com>
> > > >
> > > > Replace the use of strchr() in base64_decode() with precomputed reverse
> > > > lookup tables for each variant. This avoids repeated string scans and
> > > > improves performance. Use -1 in the tables to mark invalid characters.
> > > >
> > > > Decode:
> > > > 64B ~1530ns -> ~75ns (~20.4x)
> > > > 1KB ~27726ns -> ~1165ns (~23.8x)
> > > >
> > > > Signed-off-by: Kuan-Wei Chiu <visitorckw@gmail.com>
> > > > Co-developed-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
> > > > Signed-off-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
> > > > ---
> > > > lib/base64.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++----
> > > > 1 file changed, 61 insertions(+), 5 deletions(-)
> > > >
> > > > diff --git a/lib/base64.c b/lib/base64.c
> > > > index 1af557785..b20fdf168 100644
> > > > --- a/lib/base64.c
> > > > +++ b/lib/base64.c
> > > > @@ -21,6 +21,63 @@ static const char base64_tables[][65] = {
> > > > [BASE64_IMAP] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,",
> > > > };
> > > >
> > > > +static const s8 base64_rev_tables[][256] = {
> > > > + [BASE64_STD] = {
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
> > > > + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
> > > > + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
> > > > + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
> > > > + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
> > > > + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > + },
> > > > + [BASE64_URLSAFE] = {
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1,
> > > > + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
> > > > + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
> > > > + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63,
> > > > + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
> > > > + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > + },
> > > > + [BASE64_IMAP] = {
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, 63, -1, -1, -1,
> > > > + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
> > > > + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
> > > > + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
> > > > + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
> > > > + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > + },
> > >
> > > Do we actually need 3 separate lookup tables? It looks like all 3
> > > variants agree on the value of any characters they have in common. So
> > > we could combine them into a single lookup table that would work for a
> > > valid base64 string of any variant. The only downside I can see is
> > > that base64 strings which are invalid in some variants might no longer
> > > be rejected by base64_decode().
> > >
> >
> > In addition to the approach David mentioned, maybe we can use a common
> > lookup table for A–Z, a–z, and 0–9, and then handle the variant-specific
> > symbols with a switch.
It is certainly possible to generate the initialiser from a #define to
avoid all the replicated source.
> >
> > For example:
> >
> > static const s8 base64_rev_common[256] = {
> > [0 ... 255] = -1,
> > ['A'] = 0, ['B'] = 1, /* ... */, ['Z'] = 25,
If you assume ASCII (I doubt Linux runs on any EBCDIC systems) you
can assume the characters are sequential and miss ['B'] = etc to
reduce the the line lengths.
(Even EBCDIC has A-I J-R S-Z and 0-9 as adjacent values)
> > ['a'] = 26, /* ... */, ['z'] = 51,
> > ['0'] = 52, /* ... */, ['9'] = 61,
> > };
> >
> > static inline int base64_rev_lookup(u8 c, enum base64_variant variant) {
> > s8 v = base64_rev_common[c];
> > if (v != -1)
> > return v;
> >
> > switch (variant) {
> > case BASE64_STD:
> > if (c == '+') return 62;
> > if (c == '/') return 63;
> > break;
> > case BASE64_IMAP:
> > if (c == '+') return 62;
> > if (c == ',') return 63;
> > break;
> > case BASE64_URLSAFE:
> > if (c == '-') return 62;
> > if (c == '_') return 63;
> > break;
> > }
> > return -1;
> > }
> >
> > What do you think?
>
> That adds several branches in the hot loop, at least 2 of which are
> unpredictable for valid base64 input of a given variant (v != -1 as
> well as the first c check in the applicable switch case).
I'd certainly pass in the character values for 62 and 63 so they are
determined well outside the inner loop.
Possibly even going as far as #define BASE64_STD ('+' << 8 | '/').
> That seems like it would hurt performance, no?
> I think having 3 separate tables
> would be preferable to making the hot loop more branchy.
Depends how common you think 62 and 63 are...
I guess 63 comes from 0xff bytes - so might be quite common.
One thing I think you've missed is that the decode converts 4 characters
into 24 bits - which then need carefully writing into the output buffer.
There is no need to check whether each character is valid.
After:
val_24 = t[b[0]] | t[b[1]] << 6 | t[b[2]] << 12 | t[b[3]] << 18;
val_24 will be negative iff one of b[0..3] is invalid.
So you only need to check every 4 input characters, not for every one.
That does require separate tables.
(Or have a decoder that always maps "+-" to 62 and "/,_" to 63.)
David
>
> Best,
> Caleb
>
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v3 3/6] lib/base64: rework encode/decode for speed and stricter validation
2025-10-01 9:39 ` Guan-Chun Wu
@ 2025-10-06 20:52 ` David Laight
2025-10-07 8:34 ` Guan-Chun Wu
0 siblings, 1 reply; 31+ messages in thread
From: David Laight @ 2025-10-06 20:52 UTC (permalink / raw)
To: Guan-Chun Wu
Cc: Caleb Sander Mateos, akpm, axboe, ceph-devel, ebiggers, hch,
home7438072, idryomov, jaegeuk, kbusch, linux-fscrypt,
linux-kernel, linux-nvme, sagi, tytso, visitorckw, xiubli
On Wed, 1 Oct 2025 17:39:32 +0800
Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
> On Tue, Sep 30, 2025 at 05:11:12PM -0700, Caleb Sander Mateos wrote:
> > On Fri, Sep 26, 2025 at 12:01 AM Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
> > >
> > > The old base64 implementation relied on a bit-accumulator loop, which was
> > > slow for larger inputs and too permissive in validation. It would accept
> > > extra '=', missing '=', or even '=' appearing in the middle of the input,
> > > allowing malformed strings to pass. This patch reworks the internals to
> > > improve performance and enforce stricter validation.
> > >
> > > Changes:
> > > - Encoder:
> > > * Process input in 3-byte blocks, mapping 24 bits into four 6-bit
> > > symbols, avoiding bit-by-bit shifting and reducing loop iterations.
> > > * Handle the final 1-2 leftover bytes explicitly and emit '=' only when
> > > requested.
> > > - Decoder:
> > > * Based on the reverse lookup tables from the previous patch, decode
> > > input in 4-character groups.
> > > * Each group is looked up directly, converted into numeric values, and
> > > combined into 3 output bytes.
> > > * Explicitly handle padded and unpadded forms:
> > > - With padding: input length must be a multiple of 4, and '=' is
> > > allowed only in the last two positions. Reject stray or early '='.
> > > - Without padding: validate tail lengths (2 or 3 chars) and require
> > > unused low bits to be zero.
> > > * Removed the bit-accumulator style loop to reduce loop iterations.
> > >
> > > Performance (x86_64, Intel Core i7-10700 @ 2.90GHz, avg over 1000 runs,
> > > KUnit):
> > >
> > > Encode:
> > > 64B ~90ns -> ~32ns (~2.8x)
> > > 1KB ~1332ns -> ~510ns (~2.6x)
> > >
> > > Decode:
> > > 64B ~1530ns -> ~64ns (~23.9x)
> > > 1KB ~27726ns -> ~982ns (~28.3x)
> > >
> > > Co-developed-by: Kuan-Wei Chiu <visitorckw@gmail.com>
> > > Signed-off-by: Kuan-Wei Chiu <visitorckw@gmail.com>
> > > Co-developed-by: Yu-Sheng Huang <home7438072@gmail.com>
> > > Signed-off-by: Yu-Sheng Huang <home7438072@gmail.com>
> > > Signed-off-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
> > > ---
> > > lib/base64.c | 150 +++++++++++++++++++++++++++++++++++++--------------
> > > 1 file changed, 110 insertions(+), 40 deletions(-)
> > >
> > > diff --git a/lib/base64.c b/lib/base64.c
> > > index b20fdf168..fd1db4611 100644
> > > --- a/lib/base64.c
> > > +++ b/lib/base64.c
> > > @@ -93,26 +93,43 @@ static const s8 base64_rev_tables[][256] = {
> > > int base64_encode(const u8 *src, int srclen, char *dst, bool padding, enum base64_variant variant)
> > > {
> > > u32 ac = 0;
> > > - int bits = 0;
> > > - int i;
> > > char *cp = dst;
> > > const char *base64_table = base64_tables[variant];
> > >
> > > - for (i = 0; i < srclen; i++) {
> > > - ac = (ac << 8) | src[i];
> > > - bits += 8;
> > > - do {
> > > - bits -= 6;
> > > - *cp++ = base64_table[(ac >> bits) & 0x3f];
> > > - } while (bits >= 6);
> > > - }
> > > - if (bits) {
> > > - *cp++ = base64_table[(ac << (6 - bits)) & 0x3f];
> > > - bits -= 6;
> > > + while (srclen >= 3) {
> > > + ac = ((u32)src[0] << 16) |
> > > + ((u32)src[1] << 8) |
> > > + (u32)src[2];
> > > +
> > > + *cp++ = base64_table[ac >> 18];
> > > + *cp++ = base64_table[(ac >> 12) & 0x3f];
> > > + *cp++ = base64_table[(ac >> 6) & 0x3f];
> > > + *cp++ = base64_table[ac & 0x3f];
> > > +
> > > + src += 3;
> > > + srclen -= 3;
> > > }
> > > - while (bits < 0) {
> > > - *cp++ = '=';
> > > - bits += 2;
> > > +
> > > + switch (srclen) {
> > > + case 2:
> > > + ac = ((u32)src[0] << 16) |
> > > + ((u32)src[1] << 8);
> > > +
> > > + *cp++ = base64_table[ac >> 18];
> > > + *cp++ = base64_table[(ac >> 12) & 0x3f];
> > > + *cp++ = base64_table[(ac >> 6) & 0x3f];
> > > + if (padding)
> > > + *cp++ = '=';
> > > + break;
> > > + case 1:
> > > + ac = ((u32)src[0] << 16);
> > > + *cp++ = base64_table[ac >> 18];
> > > + *cp++ = base64_table[(ac >> 12) & 0x3f];
> > > + if (padding) {
> > > + *cp++ = '=';
> > > + *cp++ = '=';
> > > + }
> > > + break;
> > > }
> > > return cp - dst;
> > > }
> > > @@ -128,39 +145,92 @@ EXPORT_SYMBOL_GPL(base64_encode);
> > > *
> > > * Decodes a string using the selected Base64 variant.
> > > *
> > > - * This implementation hasn't been optimized for performance.
> > > - *
> > > * Return: the length of the resulting decoded binary data in bytes,
> > > * or -1 if the string isn't a valid Base64 string.
> > > */
> > > int base64_decode(const char *src, int srclen, u8 *dst, bool padding, enum base64_variant variant)
> > > {
> > > - u32 ac = 0;
> > > - int bits = 0;
> > > - int i;
> > > u8 *bp = dst;
> > > - s8 ch;
> > > -
> > > - for (i = 0; i < srclen; i++) {
> > > - if (src[i] == '=') {
> > > - ac = (ac << 6);
> > > - bits += 6;
> > > - if (bits >= 8)
> > > - bits -= 8;
> > > - continue;
> > > - }
> > > - ch = base64_rev_tables[variant][(u8)src[i]];
> > > - if (ch == -1)
> > > + s8 input1, input2, input3, input4;
> > > + u32 val;
> > > +
> > > + if (srclen == 0)
> > > + return 0;
> >
> > Doesn't look like this special case is necessary; all the if and while
> > conditions below are false if srclen == 0, so the function will just
> > end up returning 0 in that case anyways. It would be nice to avoid
> > this branch, especially as it seems like an uncommon case.
> >
>
> You're right. I'll remove it. Thanks.
>
> > > +
> > > + /* Validate the input length for padding */
> > > + if (unlikely(padding && (srclen & 0x03) != 0))
> > > + return -1;
> > > +
> > > + while (srclen >= 4) {
> > > + /* Decode the next 4 characters */
> > > + input1 = base64_rev_tables[variant][(u8)src[0]];
> > > + input2 = base64_rev_tables[variant][(u8)src[1]];
> > > + input3 = base64_rev_tables[variant][(u8)src[2]];
> > > + input4 = base64_rev_tables[variant][(u8)src[3]];
> > > +
> > > + /* Return error if any Base64 character is invalid */
> > > + if (unlikely(input1 < 0 || input2 < 0 || (!padding && (input3 < 0 || input4 < 0))))
> > > + return -1;
> > > +
> > > + /* Handle padding */
> > > + if (unlikely(padding && ((input3 < 0 && input4 >= 0) ||
> > > + (input3 < 0 && src[2] != '=') ||
> > > + (input4 < 0 && src[3] != '=') ||
> > > + (srclen > 4 && (input3 < 0 || input4 < 0)))))
> >
> > Would be preferable to check and strip the padding (i.e. decrease
> > srclen) before this main loop. That way we could avoid several
> > branches in this hot loop that are only necessary to handle the
> > padding chars.
> >
>
> You're right. As long as we check and strip the padding first, the
> behavior with or without padding can be the same, and it could also
> reduce some unnecessary branches. I'll make the change.
As I said earlier.
Calculate 'val' first using signed arithmetic.
If it is non-negative there are three bytes to write.
If negative then check for src[2] and src[3] being '=' (etc) before erroring out.
That way there is only one check in the normal path.
David
>
> Best regards,
> Guan-Chun
>
> > > + return -1;
> > > + val = ((u32)input1 << 18) |
> > > + ((u32)input2 << 12) |
> > > + ((u32)((input3 < 0) ? 0 : input3) << 6) |
> > > + (u32)((input4 < 0) ? 0 : input4);
> > > +
> > > + *bp++ = (u8)(val >> 16);
> > > +
> > > + if (input3 >= 0)
> > > + *bp++ = (u8)(val >> 8);
> > > + if (input4 >= 0)
> > > + *bp++ = (u8)val;
> > > +
> > > + src += 4;
> > > + srclen -= 4;
> > > + }
> > > +
> > > + /* Handle leftover characters when padding is not used */
> > > + if (!padding && srclen > 0) {
> > > + switch (srclen) {
> > > + case 2:
> > > + input1 = base64_rev_tables[variant][(u8)src[0]];
> > > + input2 = base64_rev_tables[variant][(u8)src[1]];
> > > + if (unlikely(input1 < 0 || input2 < 0))
> > > + return -1;
> > > +
> > > + val = ((u32)input1 << 6) | (u32)input2; /* 12 bits */
> > > + if (unlikely(val & 0x0F))
> > > + return -1; /* low 4 bits must be zero */
> > > +
> > > + *bp++ = (u8)(val >> 4);
> > > + break;
> > > + case 3:
> > > + input1 = base64_rev_tables[variant][(u8)src[0]];
> > > + input2 = base64_rev_tables[variant][(u8)src[1]];
> > > + input3 = base64_rev_tables[variant][(u8)src[2]];
> > > + if (unlikely(input1 < 0 || input2 < 0 || input3 < 0))
> > > + return -1;
> > > +
> > > + val = ((u32)input1 << 12) |
> > > + ((u32)input2 << 6) |
> > > + (u32)input3; /* 18 bits */
> > > +
> > > + if (unlikely(val & 0x03))
> > > + return -1; /* low 2 bits must be zero */
> > > +
> > > + *bp++ = (u8)(val >> 10);
> > > + *bp++ = (u8)((val >> 2) & 0xFF);
> >
> > "& 0xFF" is redundant with the cast to u8.
> >
> > Best,
> > Caleb
> >
> > > + break;
> > > + default:
> > > return -1;
> > > - ac = (ac << 6) | ch;
> > > - bits += 6;
> > > - if (bits >= 8) {
> > > - bits -= 8;
> > > - *bp++ = (u8)(ac >> bits);
> > > }
> > > }
> > > - if (ac & ((1 << bits) - 1))
> > > - return -1;
> > > +
> > > return bp - dst;
> > > }
> > > EXPORT_SYMBOL_GPL(base64_decode);
> > > --
> > > 2.34.1
> > >
> > >
>
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v3 2/6] lib/base64: Optimize base64_decode() with reverse lookup tables
2025-10-05 17:18 ` David Laight
@ 2025-10-07 8:28 ` Guan-Chun Wu
2025-10-07 14:57 ` Caleb Sander Mateos
0 siblings, 1 reply; 31+ messages in thread
From: Guan-Chun Wu @ 2025-10-07 8:28 UTC (permalink / raw)
To: David Laight
Cc: Caleb Sander Mateos, akpm, axboe, ceph-devel, ebiggers, hch,
home7438072, idryomov, jaegeuk, kbusch, linux-fscrypt,
linux-kernel, linux-nvme, sagi, tytso, visitorckw, xiubli
On Sun, Oct 05, 2025 at 06:18:03PM +0100, David Laight wrote:
> On Wed, 1 Oct 2025 09:20:27 -0700
> Caleb Sander Mateos <csander@purestorage.com> wrote:
>
> > On Wed, Oct 1, 2025 at 3:18 AM Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
> > >
> > > On Fri, Sep 26, 2025 at 04:33:12PM -0700, Caleb Sander Mateos wrote:
> > > > On Thu, Sep 25, 2025 at 11:59 PM Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
> > > > >
> > > > > From: Kuan-Wei Chiu <visitorckw@gmail.com>
> > > > >
> > > > > Replace the use of strchr() in base64_decode() with precomputed reverse
> > > > > lookup tables for each variant. This avoids repeated string scans and
> > > > > improves performance. Use -1 in the tables to mark invalid characters.
> > > > >
> > > > > Decode:
> > > > > 64B ~1530ns -> ~75ns (~20.4x)
> > > > > 1KB ~27726ns -> ~1165ns (~23.8x)
> > > > >
> > > > > Signed-off-by: Kuan-Wei Chiu <visitorckw@gmail.com>
> > > > > Co-developed-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
> > > > > Signed-off-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
> > > > > ---
> > > > > lib/base64.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++----
> > > > > 1 file changed, 61 insertions(+), 5 deletions(-)
> > > > >
> > > > > diff --git a/lib/base64.c b/lib/base64.c
> > > > > index 1af557785..b20fdf168 100644
> > > > > --- a/lib/base64.c
> > > > > +++ b/lib/base64.c
> > > > > @@ -21,6 +21,63 @@ static const char base64_tables[][65] = {
> > > > > [BASE64_IMAP] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,",
> > > > > };
> > > > >
> > > > > +static const s8 base64_rev_tables[][256] = {
> > > > > + [BASE64_STD] = {
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
> > > > > + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
> > > > > + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
> > > > > + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
> > > > > + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
> > > > > + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > + },
> > > > > + [BASE64_URLSAFE] = {
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1,
> > > > > + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
> > > > > + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
> > > > > + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63,
> > > > > + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
> > > > > + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > + },
> > > > > + [BASE64_IMAP] = {
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, 63, -1, -1, -1,
> > > > > + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
> > > > > + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
> > > > > + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
> > > > > + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
> > > > > + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > + },
> > > >
> > > > Do we actually need 3 separate lookup tables? It looks like all 3
> > > > variants agree on the value of any characters they have in common. So
> > > > we could combine them into a single lookup table that would work for a
> > > > valid base64 string of any variant. The only downside I can see is
> > > > that base64 strings which are invalid in some variants might no longer
> > > > be rejected by base64_decode().
> > > >
> > >
> > > In addition to the approach David mentioned, maybe we can use a common
> > > lookup table for A–Z, a–z, and 0–9, and then handle the variant-specific
> > > symbols with a switch.
>
> It is certainly possible to generate the initialiser from a #define to
> avoid all the replicated source.
>
> > >
> > > For example:
> > >
> > > static const s8 base64_rev_common[256] = {
> > > [0 ... 255] = -1,
> > > ['A'] = 0, ['B'] = 1, /* ... */, ['Z'] = 25,
>
> If you assume ASCII (I doubt Linux runs on any EBCDIC systems) you
> can assume the characters are sequential and miss ['B'] = etc to
> reduce the the line lengths.
> (Even EBCDIC has A-I J-R S-Z and 0-9 as adjacent values)
>
> > > ['a'] = 26, /* ... */, ['z'] = 51,
> > > ['0'] = 52, /* ... */, ['9'] = 61,
> > > };
> > >
> > > static inline int base64_rev_lookup(u8 c, enum base64_variant variant) {
> > > s8 v = base64_rev_common[c];
> > > if (v != -1)
> > > return v;
> > >
> > > switch (variant) {
> > > case BASE64_STD:
> > > if (c == '+') return 62;
> > > if (c == '/') return 63;
> > > break;
> > > case BASE64_IMAP:
> > > if (c == '+') return 62;
> > > if (c == ',') return 63;
> > > break;
> > > case BASE64_URLSAFE:
> > > if (c == '-') return 62;
> > > if (c == '_') return 63;
> > > break;
> > > }
> > > return -1;
> > > }
> > >
> > > What do you think?
> >
> > That adds several branches in the hot loop, at least 2 of which are
> > unpredictable for valid base64 input of a given variant (v != -1 as
> > well as the first c check in the applicable switch case).
>
> I'd certainly pass in the character values for 62 and 63 so they are
> determined well outside the inner loop.
> Possibly even going as far as #define BASE64_STD ('+' << 8 | '/').
>
> > That seems like it would hurt performance, no?
> > I think having 3 separate tables
> > would be preferable to making the hot loop more branchy.
>
> Depends how common you think 62 and 63 are...
> I guess 63 comes from 0xff bytes - so might be quite common.
>
> One thing I think you've missed is that the decode converts 4 characters
> into 24 bits - which then need carefully writing into the output buffer.
> There is no need to check whether each character is valid.
> After:
> val_24 = t[b[0]] | t[b[1]] << 6 | t[b[2]] << 12 | t[b[3]] << 18;
> val_24 will be negative iff one of b[0..3] is invalid.
> So you only need to check every 4 input characters, not for every one.
> That does require separate tables.
> (Or have a decoder that always maps "+-" to 62 and "/,_" to 63.)
>
> David
>
Thanks for the feedback.
For the next revision, we’ll use a single lookup table that maps both +
and - to 62, and /, _, and , to 63.
Does this approach sound good to everyone?
Best regards,
Guan-Chun
> >
> > Best,
> > Caleb
> >
>
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v3 3/6] lib/base64: rework encode/decode for speed and stricter validation
2025-10-06 20:52 ` David Laight
@ 2025-10-07 8:34 ` Guan-Chun Wu
0 siblings, 0 replies; 31+ messages in thread
From: Guan-Chun Wu @ 2025-10-07 8:34 UTC (permalink / raw)
To: David Laight
Cc: Caleb Sander Mateos, akpm, axboe, ceph-devel, ebiggers, hch,
home7438072, idryomov, jaegeuk, kbusch, linux-fscrypt,
linux-kernel, linux-nvme, sagi, tytso, visitorckw, xiubli
On Mon, Oct 06, 2025 at 09:52:12PM +0100, David Laight wrote:
> On Wed, 1 Oct 2025 17:39:32 +0800
> Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
>
> > On Tue, Sep 30, 2025 at 05:11:12PM -0700, Caleb Sander Mateos wrote:
> > > On Fri, Sep 26, 2025 at 12:01 AM Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
> > > >
> > > > The old base64 implementation relied on a bit-accumulator loop, which was
> > > > slow for larger inputs and too permissive in validation. It would accept
> > > > extra '=', missing '=', or even '=' appearing in the middle of the input,
> > > > allowing malformed strings to pass. This patch reworks the internals to
> > > > improve performance and enforce stricter validation.
> > > >
> > > > Changes:
> > > > - Encoder:
> > > > * Process input in 3-byte blocks, mapping 24 bits into four 6-bit
> > > > symbols, avoiding bit-by-bit shifting and reducing loop iterations.
> > > > * Handle the final 1-2 leftover bytes explicitly and emit '=' only when
> > > > requested.
> > > > - Decoder:
> > > > * Based on the reverse lookup tables from the previous patch, decode
> > > > input in 4-character groups.
> > > > * Each group is looked up directly, converted into numeric values, and
> > > > combined into 3 output bytes.
> > > > * Explicitly handle padded and unpadded forms:
> > > > - With padding: input length must be a multiple of 4, and '=' is
> > > > allowed only in the last two positions. Reject stray or early '='.
> > > > - Without padding: validate tail lengths (2 or 3 chars) and require
> > > > unused low bits to be zero.
> > > > * Removed the bit-accumulator style loop to reduce loop iterations.
> > > >
> > > > Performance (x86_64, Intel Core i7-10700 @ 2.90GHz, avg over 1000 runs,
> > > > KUnit):
> > > >
> > > > Encode:
> > > > 64B ~90ns -> ~32ns (~2.8x)
> > > > 1KB ~1332ns -> ~510ns (~2.6x)
> > > >
> > > > Decode:
> > > > 64B ~1530ns -> ~64ns (~23.9x)
> > > > 1KB ~27726ns -> ~982ns (~28.3x)
> > > >
> > > > Co-developed-by: Kuan-Wei Chiu <visitorckw@gmail.com>
> > > > Signed-off-by: Kuan-Wei Chiu <visitorckw@gmail.com>
> > > > Co-developed-by: Yu-Sheng Huang <home7438072@gmail.com>
> > > > Signed-off-by: Yu-Sheng Huang <home7438072@gmail.com>
> > > > Signed-off-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
> > > > ---
> > > > lib/base64.c | 150 +++++++++++++++++++++++++++++++++++++--------------
> > > > 1 file changed, 110 insertions(+), 40 deletions(-)
> > > >
> > > > diff --git a/lib/base64.c b/lib/base64.c
> > > > index b20fdf168..fd1db4611 100644
> > > > --- a/lib/base64.c
> > > > +++ b/lib/base64.c
> > > > @@ -93,26 +93,43 @@ static const s8 base64_rev_tables[][256] = {
> > > > int base64_encode(const u8 *src, int srclen, char *dst, bool padding, enum base64_variant variant)
> > > > {
> > > > u32 ac = 0;
> > > > - int bits = 0;
> > > > - int i;
> > > > char *cp = dst;
> > > > const char *base64_table = base64_tables[variant];
> > > >
> > > > - for (i = 0; i < srclen; i++) {
> > > > - ac = (ac << 8) | src[i];
> > > > - bits += 8;
> > > > - do {
> > > > - bits -= 6;
> > > > - *cp++ = base64_table[(ac >> bits) & 0x3f];
> > > > - } while (bits >= 6);
> > > > - }
> > > > - if (bits) {
> > > > - *cp++ = base64_table[(ac << (6 - bits)) & 0x3f];
> > > > - bits -= 6;
> > > > + while (srclen >= 3) {
> > > > + ac = ((u32)src[0] << 16) |
> > > > + ((u32)src[1] << 8) |
> > > > + (u32)src[2];
> > > > +
> > > > + *cp++ = base64_table[ac >> 18];
> > > > + *cp++ = base64_table[(ac >> 12) & 0x3f];
> > > > + *cp++ = base64_table[(ac >> 6) & 0x3f];
> > > > + *cp++ = base64_table[ac & 0x3f];
> > > > +
> > > > + src += 3;
> > > > + srclen -= 3;
> > > > }
> > > > - while (bits < 0) {
> > > > - *cp++ = '=';
> > > > - bits += 2;
> > > > +
> > > > + switch (srclen) {
> > > > + case 2:
> > > > + ac = ((u32)src[0] << 16) |
> > > > + ((u32)src[1] << 8);
> > > > +
> > > > + *cp++ = base64_table[ac >> 18];
> > > > + *cp++ = base64_table[(ac >> 12) & 0x3f];
> > > > + *cp++ = base64_table[(ac >> 6) & 0x3f];
> > > > + if (padding)
> > > > + *cp++ = '=';
> > > > + break;
> > > > + case 1:
> > > > + ac = ((u32)src[0] << 16);
> > > > + *cp++ = base64_table[ac >> 18];
> > > > + *cp++ = base64_table[(ac >> 12) & 0x3f];
> > > > + if (padding) {
> > > > + *cp++ = '=';
> > > > + *cp++ = '=';
> > > > + }
> > > > + break;
> > > > }
> > > > return cp - dst;
> > > > }
> > > > @@ -128,39 +145,92 @@ EXPORT_SYMBOL_GPL(base64_encode);
> > > > *
> > > > * Decodes a string using the selected Base64 variant.
> > > > *
> > > > - * This implementation hasn't been optimized for performance.
> > > > - *
> > > > * Return: the length of the resulting decoded binary data in bytes,
> > > > * or -1 if the string isn't a valid Base64 string.
> > > > */
> > > > int base64_decode(const char *src, int srclen, u8 *dst, bool padding, enum base64_variant variant)
> > > > {
> > > > - u32 ac = 0;
> > > > - int bits = 0;
> > > > - int i;
> > > > u8 *bp = dst;
> > > > - s8 ch;
> > > > -
> > > > - for (i = 0; i < srclen; i++) {
> > > > - if (src[i] == '=') {
> > > > - ac = (ac << 6);
> > > > - bits += 6;
> > > > - if (bits >= 8)
> > > > - bits -= 8;
> > > > - continue;
> > > > - }
> > > > - ch = base64_rev_tables[variant][(u8)src[i]];
> > > > - if (ch == -1)
> > > > + s8 input1, input2, input3, input4;
> > > > + u32 val;
> > > > +
> > > > + if (srclen == 0)
> > > > + return 0;
> > >
> > > Doesn't look like this special case is necessary; all the if and while
> > > conditions below are false if srclen == 0, so the function will just
> > > end up returning 0 in that case anyways. It would be nice to avoid
> > > this branch, especially as it seems like an uncommon case.
> > >
> >
> > You're right. I'll remove it. Thanks.
> >
> > > > +
> > > > + /* Validate the input length for padding */
> > > > + if (unlikely(padding && (srclen & 0x03) != 0))
> > > > + return -1;
> > > > +
> > > > + while (srclen >= 4) {
> > > > + /* Decode the next 4 characters */
> > > > + input1 = base64_rev_tables[variant][(u8)src[0]];
> > > > + input2 = base64_rev_tables[variant][(u8)src[1]];
> > > > + input3 = base64_rev_tables[variant][(u8)src[2]];
> > > > + input4 = base64_rev_tables[variant][(u8)src[3]];
> > > > +
> > > > + /* Return error if any Base64 character is invalid */
> > > > + if (unlikely(input1 < 0 || input2 < 0 || (!padding && (input3 < 0 || input4 < 0))))
> > > > + return -1;
> > > > +
> > > > + /* Handle padding */
> > > > + if (unlikely(padding && ((input3 < 0 && input4 >= 0) ||
> > > > + (input3 < 0 && src[2] != '=') ||
> > > > + (input4 < 0 && src[3] != '=') ||
> > > > + (srclen > 4 && (input3 < 0 || input4 < 0)))))
> > >
> > > Would be preferable to check and strip the padding (i.e. decrease
> > > srclen) before this main loop. That way we could avoid several
> > > branches in this hot loop that are only necessary to handle the
> > > padding chars.
> > >
> >
> > You're right. As long as we check and strip the padding first, the
> > behavior with or without padding can be the same, and it could also
> > reduce some unnecessary branches. I'll make the change.
>
> As I said earlier.
> Calculate 'val' first using signed arithmetic.
> If it is non-negative there are three bytes to write.
> If negative then check for src[2] and src[3] being '=' (etc) before erroring out.
>
> That way there is only one check in the normal path.
>
> David
>
Thanks for the feedback. We’ll update the implementation accordingly.
Best regards,
Guan-Chun
> >
> > Best regards,
> > Guan-Chun
> >
> > > > + return -1;
> > > > + val = ((u32)input1 << 18) |
> > > > + ((u32)input2 << 12) |
> > > > + ((u32)((input3 < 0) ? 0 : input3) << 6) |
> > > > + (u32)((input4 < 0) ? 0 : input4);
> > > > +
> > > > + *bp++ = (u8)(val >> 16);
> > > > +
> > > > + if (input3 >= 0)
> > > > + *bp++ = (u8)(val >> 8);
> > > > + if (input4 >= 0)
> > > > + *bp++ = (u8)val;
> > > > +
> > > > + src += 4;
> > > > + srclen -= 4;
> > > > + }
> > > > +
> > > > + /* Handle leftover characters when padding is not used */
> > > > + if (!padding && srclen > 0) {
> > > > + switch (srclen) {
> > > > + case 2:
> > > > + input1 = base64_rev_tables[variant][(u8)src[0]];
> > > > + input2 = base64_rev_tables[variant][(u8)src[1]];
> > > > + if (unlikely(input1 < 0 || input2 < 0))
> > > > + return -1;
> > > > +
> > > > + val = ((u32)input1 << 6) | (u32)input2; /* 12 bits */
> > > > + if (unlikely(val & 0x0F))
> > > > + return -1; /* low 4 bits must be zero */
> > > > +
> > > > + *bp++ = (u8)(val >> 4);
> > > > + break;
> > > > + case 3:
> > > > + input1 = base64_rev_tables[variant][(u8)src[0]];
> > > > + input2 = base64_rev_tables[variant][(u8)src[1]];
> > > > + input3 = base64_rev_tables[variant][(u8)src[2]];
> > > > + if (unlikely(input1 < 0 || input2 < 0 || input3 < 0))
> > > > + return -1;
> > > > +
> > > > + val = ((u32)input1 << 12) |
> > > > + ((u32)input2 << 6) |
> > > > + (u32)input3; /* 18 bits */
> > > > +
> > > > + if (unlikely(val & 0x03))
> > > > + return -1; /* low 2 bits must be zero */
> > > > +
> > > > + *bp++ = (u8)(val >> 10);
> > > > + *bp++ = (u8)((val >> 2) & 0xFF);
> > >
> > > "& 0xFF" is redundant with the cast to u8.
> > >
> > > Best,
> > > Caleb
> > >
> > > > + break;
> > > > + default:
> > > > return -1;
> > > > - ac = (ac << 6) | ch;
> > > > - bits += 6;
> > > > - if (bits >= 8) {
> > > > - bits -= 8;
> > > > - *bp++ = (u8)(ac >> bits);
> > > > }
> > > > }
> > > > - if (ac & ((1 << bits) - 1))
> > > > - return -1;
> > > > +
> > > > return bp - dst;
> > > > }
> > > > EXPORT_SYMBOL_GPL(base64_decode);
> > > > --
> > > > 2.34.1
> > > >
> > > >
> >
>
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v3 2/6] lib/base64: Optimize base64_decode() with reverse lookup tables
2025-10-07 8:28 ` Guan-Chun Wu
@ 2025-10-07 14:57 ` Caleb Sander Mateos
2025-10-07 17:11 ` Eric Biggers
2025-10-07 18:23 ` David Laight
0 siblings, 2 replies; 31+ messages in thread
From: Caleb Sander Mateos @ 2025-10-07 14:57 UTC (permalink / raw)
To: Guan-Chun Wu
Cc: David Laight, akpm, axboe, ceph-devel, ebiggers, hch, home7438072,
idryomov, jaegeuk, kbusch, linux-fscrypt, linux-kernel,
linux-nvme, sagi, tytso, visitorckw, xiubli
On Tue, Oct 7, 2025 at 1:28 AM Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
>
> On Sun, Oct 05, 2025 at 06:18:03PM +0100, David Laight wrote:
> > On Wed, 1 Oct 2025 09:20:27 -0700
> > Caleb Sander Mateos <csander@purestorage.com> wrote:
> >
> > > On Wed, Oct 1, 2025 at 3:18 AM Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
> > > >
> > > > On Fri, Sep 26, 2025 at 04:33:12PM -0700, Caleb Sander Mateos wrote:
> > > > > On Thu, Sep 25, 2025 at 11:59 PM Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
> > > > > >
> > > > > > From: Kuan-Wei Chiu <visitorckw@gmail.com>
> > > > > >
> > > > > > Replace the use of strchr() in base64_decode() with precomputed reverse
> > > > > > lookup tables for each variant. This avoids repeated string scans and
> > > > > > improves performance. Use -1 in the tables to mark invalid characters.
> > > > > >
> > > > > > Decode:
> > > > > > 64B ~1530ns -> ~75ns (~20.4x)
> > > > > > 1KB ~27726ns -> ~1165ns (~23.8x)
> > > > > >
> > > > > > Signed-off-by: Kuan-Wei Chiu <visitorckw@gmail.com>
> > > > > > Co-developed-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
> > > > > > Signed-off-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
> > > > > > ---
> > > > > > lib/base64.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++----
> > > > > > 1 file changed, 61 insertions(+), 5 deletions(-)
> > > > > >
> > > > > > diff --git a/lib/base64.c b/lib/base64.c
> > > > > > index 1af557785..b20fdf168 100644
> > > > > > --- a/lib/base64.c
> > > > > > +++ b/lib/base64.c
> > > > > > @@ -21,6 +21,63 @@ static const char base64_tables[][65] = {
> > > > > > [BASE64_IMAP] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,",
> > > > > > };
> > > > > >
> > > > > > +static const s8 base64_rev_tables[][256] = {
> > > > > > + [BASE64_STD] = {
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
> > > > > > + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
> > > > > > + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
> > > > > > + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
> > > > > > + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
> > > > > > + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > + },
> > > > > > + [BASE64_URLSAFE] = {
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1,
> > > > > > + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
> > > > > > + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
> > > > > > + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63,
> > > > > > + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
> > > > > > + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > + },
> > > > > > + [BASE64_IMAP] = {
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, 63, -1, -1, -1,
> > > > > > + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
> > > > > > + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
> > > > > > + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
> > > > > > + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
> > > > > > + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > + },
> > > > >
> > > > > Do we actually need 3 separate lookup tables? It looks like all 3
> > > > > variants agree on the value of any characters they have in common. So
> > > > > we could combine them into a single lookup table that would work for a
> > > > > valid base64 string of any variant. The only downside I can see is
> > > > > that base64 strings which are invalid in some variants might no longer
> > > > > be rejected by base64_decode().
> > > > >
> > > >
> > > > In addition to the approach David mentioned, maybe we can use a common
> > > > lookup table for A–Z, a–z, and 0–9, and then handle the variant-specific
> > > > symbols with a switch.
> >
> > It is certainly possible to generate the initialiser from a #define to
> > avoid all the replicated source.
> >
> > > >
> > > > For example:
> > > >
> > > > static const s8 base64_rev_common[256] = {
> > > > [0 ... 255] = -1,
> > > > ['A'] = 0, ['B'] = 1, /* ... */, ['Z'] = 25,
> >
> > If you assume ASCII (I doubt Linux runs on any EBCDIC systems) you
> > can assume the characters are sequential and miss ['B'] = etc to
> > reduce the the line lengths.
> > (Even EBCDIC has A-I J-R S-Z and 0-9 as adjacent values)
> >
> > > > ['a'] = 26, /* ... */, ['z'] = 51,
> > > > ['0'] = 52, /* ... */, ['9'] = 61,
> > > > };
> > > >
> > > > static inline int base64_rev_lookup(u8 c, enum base64_variant variant) {
> > > > s8 v = base64_rev_common[c];
> > > > if (v != -1)
> > > > return v;
> > > >
> > > > switch (variant) {
> > > > case BASE64_STD:
> > > > if (c == '+') return 62;
> > > > if (c == '/') return 63;
> > > > break;
> > > > case BASE64_IMAP:
> > > > if (c == '+') return 62;
> > > > if (c == ',') return 63;
> > > > break;
> > > > case BASE64_URLSAFE:
> > > > if (c == '-') return 62;
> > > > if (c == '_') return 63;
> > > > break;
> > > > }
> > > > return -1;
> > > > }
> > > >
> > > > What do you think?
> > >
> > > That adds several branches in the hot loop, at least 2 of which are
> > > unpredictable for valid base64 input of a given variant (v != -1 as
> > > well as the first c check in the applicable switch case).
> >
> > I'd certainly pass in the character values for 62 and 63 so they are
> > determined well outside the inner loop.
> > Possibly even going as far as #define BASE64_STD ('+' << 8 | '/').
> >
> > > That seems like it would hurt performance, no?
> > > I think having 3 separate tables
> > > would be preferable to making the hot loop more branchy.
> >
> > Depends how common you think 62 and 63 are...
> > I guess 63 comes from 0xff bytes - so might be quite common.
> >
> > One thing I think you've missed is that the decode converts 4 characters
> > into 24 bits - which then need carefully writing into the output buffer.
> > There is no need to check whether each character is valid.
> > After:
> > val_24 = t[b[0]] | t[b[1]] << 6 | t[b[2]] << 12 | t[b[3]] << 18;
> > val_24 will be negative iff one of b[0..3] is invalid.
> > So you only need to check every 4 input characters, not for every one.
> > That does require separate tables.
> > (Or have a decoder that always maps "+-" to 62 and "/,_" to 63.)
> >
> > David
> >
>
> Thanks for the feedback.
> For the next revision, we’ll use a single lookup table that maps both +
> and - to 62, and /, _, and , to 63.
> Does this approach sound good to everyone?
Sounds fine to me. Perhaps worth pointing out that the decision to
accept any base64 variant in the decoder would likely be permanent,
since users may come to depend on it. But I don't see any issue with
it as long as all the base64 variants agree on the values of their
common symbols.
Best,
Caleb
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v3 2/6] lib/base64: Optimize base64_decode() with reverse lookup tables
2025-10-07 14:57 ` Caleb Sander Mateos
@ 2025-10-07 17:11 ` Eric Biggers
2025-10-07 18:23 ` David Laight
1 sibling, 0 replies; 31+ messages in thread
From: Eric Biggers @ 2025-10-07 17:11 UTC (permalink / raw)
To: Caleb Sander Mateos
Cc: Guan-Chun Wu, David Laight, akpm, axboe, ceph-devel, hch,
home7438072, idryomov, jaegeuk, kbusch, linux-fscrypt,
linux-kernel, linux-nvme, sagi, tytso, visitorckw, xiubli
On Tue, Oct 07, 2025 at 07:57:16AM -0700, Caleb Sander Mateos wrote:
> On Tue, Oct 7, 2025 at 1:28 AM Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
> >
> > On Sun, Oct 05, 2025 at 06:18:03PM +0100, David Laight wrote:
> > > On Wed, 1 Oct 2025 09:20:27 -0700
> > > Caleb Sander Mateos <csander@purestorage.com> wrote:
> > >
> > > > On Wed, Oct 1, 2025 at 3:18 AM Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
> > > > >
> > > > > On Fri, Sep 26, 2025 at 04:33:12PM -0700, Caleb Sander Mateos wrote:
> > > > > > On Thu, Sep 25, 2025 at 11:59 PM Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
> > > > > > >
> > > > > > > From: Kuan-Wei Chiu <visitorckw@gmail.com>
> > > > > > >
> > > > > > > Replace the use of strchr() in base64_decode() with precomputed reverse
> > > > > > > lookup tables for each variant. This avoids repeated string scans and
> > > > > > > improves performance. Use -1 in the tables to mark invalid characters.
> > > > > > >
> > > > > > > Decode:
> > > > > > > 64B ~1530ns -> ~75ns (~20.4x)
> > > > > > > 1KB ~27726ns -> ~1165ns (~23.8x)
> > > > > > >
> > > > > > > Signed-off-by: Kuan-Wei Chiu <visitorckw@gmail.com>
> > > > > > > Co-developed-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
> > > > > > > Signed-off-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
> > > > > > > ---
> > > > > > > lib/base64.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++----
> > > > > > > 1 file changed, 61 insertions(+), 5 deletions(-)
> > > > > > >
> > > > > > > diff --git a/lib/base64.c b/lib/base64.c
> > > > > > > index 1af557785..b20fdf168 100644
> > > > > > > --- a/lib/base64.c
> > > > > > > +++ b/lib/base64.c
> > > > > > > @@ -21,6 +21,63 @@ static const char base64_tables[][65] = {
> > > > > > > [BASE64_IMAP] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,",
> > > > > > > };
> > > > > > >
> > > > > > > +static const s8 base64_rev_tables[][256] = {
> > > > > > > + [BASE64_STD] = {
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
> > > > > > > + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
> > > > > > > + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
> > > > > > > + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
> > > > > > > + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
> > > > > > > + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > > + },
> > > > > > > + [BASE64_URLSAFE] = {
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1,
> > > > > > > + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
> > > > > > > + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
> > > > > > > + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63,
> > > > > > > + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
> > > > > > > + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > > + },
> > > > > > > + [BASE64_IMAP] = {
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, 63, -1, -1, -1,
> > > > > > > + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
> > > > > > > + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
> > > > > > > + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
> > > > > > > + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
> > > > > > > + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > > + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
> > > > > > > + },
> > > > > >
> > > > > > Do we actually need 3 separate lookup tables? It looks like all 3
> > > > > > variants agree on the value of any characters they have in common. So
> > > > > > we could combine them into a single lookup table that would work for a
> > > > > > valid base64 string of any variant. The only downside I can see is
> > > > > > that base64 strings which are invalid in some variants might no longer
> > > > > > be rejected by base64_decode().
> > > > > >
> > > > >
> > > > > In addition to the approach David mentioned, maybe we can use a common
> > > > > lookup table for A–Z, a–z, and 0–9, and then handle the variant-specific
> > > > > symbols with a switch.
> > >
> > > It is certainly possible to generate the initialiser from a #define to
> > > avoid all the replicated source.
> > >
> > > > >
> > > > > For example:
> > > > >
> > > > > static const s8 base64_rev_common[256] = {
> > > > > [0 ... 255] = -1,
> > > > > ['A'] = 0, ['B'] = 1, /* ... */, ['Z'] = 25,
> > >
> > > If you assume ASCII (I doubt Linux runs on any EBCDIC systems) you
> > > can assume the characters are sequential and miss ['B'] = etc to
> > > reduce the the line lengths.
> > > (Even EBCDIC has A-I J-R S-Z and 0-9 as adjacent values)
> > >
> > > > > ['a'] = 26, /* ... */, ['z'] = 51,
> > > > > ['0'] = 52, /* ... */, ['9'] = 61,
> > > > > };
> > > > >
> > > > > static inline int base64_rev_lookup(u8 c, enum base64_variant variant) {
> > > > > s8 v = base64_rev_common[c];
> > > > > if (v != -1)
> > > > > return v;
> > > > >
> > > > > switch (variant) {
> > > > > case BASE64_STD:
> > > > > if (c == '+') return 62;
> > > > > if (c == '/') return 63;
> > > > > break;
> > > > > case BASE64_IMAP:
> > > > > if (c == '+') return 62;
> > > > > if (c == ',') return 63;
> > > > > break;
> > > > > case BASE64_URLSAFE:
> > > > > if (c == '-') return 62;
> > > > > if (c == '_') return 63;
> > > > > break;
> > > > > }
> > > > > return -1;
> > > > > }
> > > > >
> > > > > What do you think?
> > > >
> > > > That adds several branches in the hot loop, at least 2 of which are
> > > > unpredictable for valid base64 input of a given variant (v != -1 as
> > > > well as the first c check in the applicable switch case).
> > >
> > > I'd certainly pass in the character values for 62 and 63 so they are
> > > determined well outside the inner loop.
> > > Possibly even going as far as #define BASE64_STD ('+' << 8 | '/').
> > >
> > > > That seems like it would hurt performance, no?
> > > > I think having 3 separate tables
> > > > would be preferable to making the hot loop more branchy.
> > >
> > > Depends how common you think 62 and 63 are...
> > > I guess 63 comes from 0xff bytes - so might be quite common.
> > >
> > > One thing I think you've missed is that the decode converts 4 characters
> > > into 24 bits - which then need carefully writing into the output buffer.
> > > There is no need to check whether each character is valid.
> > > After:
> > > val_24 = t[b[0]] | t[b[1]] << 6 | t[b[2]] << 12 | t[b[3]] << 18;
> > > val_24 will be negative iff one of b[0..3] is invalid.
> > > So you only need to check every 4 input characters, not for every one.
> > > That does require separate tables.
> > > (Or have a decoder that always maps "+-" to 62 and "/,_" to 63.)
> > >
> > > David
> > >
> >
> > Thanks for the feedback.
> > For the next revision, we’ll use a single lookup table that maps both +
> > and - to 62, and /, _, and , to 63.
> > Does this approach sound good to everyone?
>
> Sounds fine to me. Perhaps worth pointing out that the decision to
> accept any base64 variant in the decoder would likely be permanent,
> since users may come to depend on it. But I don't see any issue with
> it as long as all the base64 variants agree on the values of their
> common symbols.
No thanks. fs/crypto/ needs to have a correct Base64 decoder which
rejects invalid inputs, so that multiple filenames aren't accepted for
the same file. If lib/ won't provide that, then please keep fs/crypto/
as-is.
- Eric
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v3 2/6] lib/base64: Optimize base64_decode() with reverse lookup tables
2025-10-07 14:57 ` Caleb Sander Mateos
2025-10-07 17:11 ` Eric Biggers
@ 2025-10-07 18:23 ` David Laight
2025-10-09 12:25 ` Guan-Chun Wu
1 sibling, 1 reply; 31+ messages in thread
From: David Laight @ 2025-10-07 18:23 UTC (permalink / raw)
To: Caleb Sander Mateos
Cc: Guan-Chun Wu, akpm, axboe, ceph-devel, ebiggers, hch, home7438072,
idryomov, jaegeuk, kbusch, linux-fscrypt, linux-kernel,
linux-nvme, sagi, tytso, visitorckw, xiubli
On Tue, 7 Oct 2025 07:57:16 -0700
Caleb Sander Mateos <csander@purestorage.com> wrote:
> On Tue, Oct 7, 2025 at 1:28 AM Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
> >
> > On Sun, Oct 05, 2025 at 06:18:03PM +0100, David Laight wrote:
> > > On Wed, 1 Oct 2025 09:20:27 -0700
> > > Caleb Sander Mateos <csander@purestorage.com> wrote:
> > >
> > > > On Wed, Oct 1, 2025 at 3:18 AM Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
> > > > >
> > > > > On Fri, Sep 26, 2025 at 04:33:12PM -0700, Caleb Sander Mateos wrote:
> > > > > > On Thu, Sep 25, 2025 at 11:59 PM Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
> > > > > > >
> > > > > > > From: Kuan-Wei Chiu <visitorckw@gmail.com>
> > > > > > >
> > > > > > > Replace the use of strchr() in base64_decode() with precomputed reverse
> > > > > > > lookup tables for each variant. This avoids repeated string scans and
> > > > > > > improves performance. Use -1 in the tables to mark invalid characters.
> > > > > > >
> > > > > > > Decode:
> > > > > > > 64B ~1530ns -> ~75ns (~20.4x)
> > > > > > > 1KB ~27726ns -> ~1165ns (~23.8x)
> > > > > > >
> > > > > > > Signed-off-by: Kuan-Wei Chiu <visitorckw@gmail.com>
> > > > > > > Co-developed-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
> > > > > > > Signed-off-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
> > > > > > > ---
> > > > > > > lib/base64.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++----
> > > > > > > 1 file changed, 61 insertions(+), 5 deletions(-)
> > > > > > >
> > > > > > > diff --git a/lib/base64.c b/lib/base64.c
> > > > > > > index 1af557785..b20fdf168 100644
> > > > > > > --- a/lib/base64.c
> > > > > > > +++ b/lib/base64.c
> > > > > > > @@ -21,6 +21,63 @@ static const char base64_tables[][65] = {
> > > > > > > [BASE64_IMAP] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,",
> > > > > > > };
> > > > > > >
> > > > > > > +static const s8 base64_rev_tables[][256] = {
...
> > > > > > Do we actually need 3 separate lookup tables? It looks like all 3
> > > > > > variants agree on the value of any characters they have in common. So
> > > > > > we could combine them into a single lookup table that would work for a
> > > > > > valid base64 string of any variant. The only downside I can see is
> > > > > > that base64 strings which are invalid in some variants might no longer
> > > > > > be rejected by base64_decode().
> > > > > >
> > > > >
> > > > > In addition to the approach David mentioned, maybe we can use a common
> > > > > lookup table for A–Z, a–z, and 0–9, and then handle the variant-specific
> > > > > symbols with a switch.
> > >
> > > It is certainly possible to generate the initialiser from a #define to
> > > avoid all the replicated source.
> > >
> > > > >
> > > > > For example:
> > > > >
> > > > > static const s8 base64_rev_common[256] = {
> > > > > [0 ... 255] = -1,
> > > > > ['A'] = 0, ['B'] = 1, /* ... */, ['Z'] = 25,
> > >
> > > If you assume ASCII (I doubt Linux runs on any EBCDIC systems) you
> > > can assume the characters are sequential and miss ['B'] = etc to
> > > reduce the the line lengths.
> > > (Even EBCDIC has A-I J-R S-Z and 0-9 as adjacent values)
> > >
> > > > > ['a'] = 26, /* ... */, ['z'] = 51,
> > > > > ['0'] = 52, /* ... */, ['9'] = 61,
> > > > > };
> > > > >
> > > > > static inline int base64_rev_lookup(u8 c, enum base64_variant variant) {
> > > > > s8 v = base64_rev_common[c];
> > > > > if (v != -1)
> > > > > return v;
> > > > >
> > > > > switch (variant) {
> > > > > case BASE64_STD:
> > > > > if (c == '+') return 62;
> > > > > if (c == '/') return 63;
> > > > > break;
> > > > > case BASE64_IMAP:
> > > > > if (c == '+') return 62;
> > > > > if (c == ',') return 63;
> > > > > break;
> > > > > case BASE64_URLSAFE:
> > > > > if (c == '-') return 62;
> > > > > if (c == '_') return 63;
> > > > > break;
> > > > > }
> > > > > return -1;
> > > > > }
> > > > >
> > > > > What do you think?
> > > >
> > > > That adds several branches in the hot loop, at least 2 of which are
> > > > unpredictable for valid base64 input of a given variant (v != -1 as
> > > > well as the first c check in the applicable switch case).
> > >
> > > I'd certainly pass in the character values for 62 and 63 so they are
> > > determined well outside the inner loop.
> > > Possibly even going as far as #define BASE64_STD ('+' << 8 | '/').
> > >
> > > > That seems like it would hurt performance, no?
> > > > I think having 3 separate tables
> > > > would be preferable to making the hot loop more branchy.
> > >
> > > Depends how common you think 62 and 63 are...
> > > I guess 63 comes from 0xff bytes - so might be quite common.
> > >
> > > One thing I think you've missed is that the decode converts 4 characters
> > > into 24 bits - which then need carefully writing into the output buffer.
> > > There is no need to check whether each character is valid.
> > > After:
> > > val_24 = t[b[0]] | t[b[1]] << 6 | t[b[2]] << 12 | t[b[3]] << 18;
> > > val_24 will be negative iff one of b[0..3] is invalid.
> > > So you only need to check every 4 input characters, not for every one.
> > > That does require separate tables.
> > > (Or have a decoder that always maps "+-" to 62 and "/,_" to 63.)
> > >
> > > David
> > >
> >
> > Thanks for the feedback.
> > For the next revision, we’ll use a single lookup table that maps both +
> > and - to 62, and /, _, and , to 63.
> > Does this approach sound good to everyone?
>
> Sounds fine to me. Perhaps worth pointing out that the decision to
> accept any base64 variant in the decoder would likely be permanent,
> since users may come to depend on it. But I don't see any issue with
> it as long as all the base64 variants agree on the values of their
> common symbols.
If an incompatible version comes along it'll need a different function
(or similar). But there is no point over-engineering it now.
David
>
> Best,
> Caleb
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v3 2/6] lib/base64: Optimize base64_decode() with reverse lookup tables
2025-10-07 18:23 ` David Laight
@ 2025-10-09 12:25 ` Guan-Chun Wu
2025-10-10 9:51 ` David Laight
0 siblings, 1 reply; 31+ messages in thread
From: Guan-Chun Wu @ 2025-10-09 12:25 UTC (permalink / raw)
To: David Laight
Cc: Caleb Sander Mateos, akpm, axboe, ceph-devel, ebiggers, hch,
home7438072, idryomov, jaegeuk, kbusch, linux-fscrypt,
linux-kernel, linux-nvme, sagi, tytso, visitorckw, xiubli
On Tue, Oct 07, 2025 at 07:23:27PM +0100, David Laight wrote:
> On Tue, 7 Oct 2025 07:57:16 -0700
> Caleb Sander Mateos <csander@purestorage.com> wrote:
>
> > On Tue, Oct 7, 2025 at 1:28 AM Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
> > >
> > > On Sun, Oct 05, 2025 at 06:18:03PM +0100, David Laight wrote:
> > > > On Wed, 1 Oct 2025 09:20:27 -0700
> > > > Caleb Sander Mateos <csander@purestorage.com> wrote:
> > > >
> > > > > On Wed, Oct 1, 2025 at 3:18 AM Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
> > > > > >
> > > > > > On Fri, Sep 26, 2025 at 04:33:12PM -0700, Caleb Sander Mateos wrote:
> > > > > > > On Thu, Sep 25, 2025 at 11:59 PM Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
> > > > > > > >
> > > > > > > > From: Kuan-Wei Chiu <visitorckw@gmail.com>
> > > > > > > >
> > > > > > > > Replace the use of strchr() in base64_decode() with precomputed reverse
> > > > > > > > lookup tables for each variant. This avoids repeated string scans and
> > > > > > > > improves performance. Use -1 in the tables to mark invalid characters.
> > > > > > > >
> > > > > > > > Decode:
> > > > > > > > 64B ~1530ns -> ~75ns (~20.4x)
> > > > > > > > 1KB ~27726ns -> ~1165ns (~23.8x)
> > > > > > > >
> > > > > > > > Signed-off-by: Kuan-Wei Chiu <visitorckw@gmail.com>
> > > > > > > > Co-developed-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
> > > > > > > > Signed-off-by: Guan-Chun Wu <409411716@gms.tku.edu.tw>
> > > > > > > > ---
> > > > > > > > lib/base64.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++----
> > > > > > > > 1 file changed, 61 insertions(+), 5 deletions(-)
> > > > > > > >
> > > > > > > > diff --git a/lib/base64.c b/lib/base64.c
> > > > > > > > index 1af557785..b20fdf168 100644
> > > > > > > > --- a/lib/base64.c
> > > > > > > > +++ b/lib/base64.c
> > > > > > > > @@ -21,6 +21,63 @@ static const char base64_tables[][65] = {
> > > > > > > > [BASE64_IMAP] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,",
> > > > > > > > };
> > > > > > > >
> > > > > > > > +static const s8 base64_rev_tables[][256] = {
> ...
> > > > > > > Do we actually need 3 separate lookup tables? It looks like all 3
> > > > > > > variants agree on the value of any characters they have in common. So
> > > > > > > we could combine them into a single lookup table that would work for a
> > > > > > > valid base64 string of any variant. The only downside I can see is
> > > > > > > that base64 strings which are invalid in some variants might no longer
> > > > > > > be rejected by base64_decode().
> > > > > > >
> > > > > >
> > > > > > In addition to the approach David mentioned, maybe we can use a common
> > > > > > lookup table for A–Z, a–z, and 0–9, and then handle the variant-specific
> > > > > > symbols with a switch.
> > > >
> > > > It is certainly possible to generate the initialiser from a #define to
> > > > avoid all the replicated source.
> > > >
> > > > > >
> > > > > > For example:
> > > > > >
> > > > > > static const s8 base64_rev_common[256] = {
> > > > > > [0 ... 255] = -1,
> > > > > > ['A'] = 0, ['B'] = 1, /* ... */, ['Z'] = 25,
> > > >
> > > > If you assume ASCII (I doubt Linux runs on any EBCDIC systems) you
> > > > can assume the characters are sequential and miss ['B'] = etc to
> > > > reduce the the line lengths.
> > > > (Even EBCDIC has A-I J-R S-Z and 0-9 as adjacent values)
> > > >
> > > > > > ['a'] = 26, /* ... */, ['z'] = 51,
> > > > > > ['0'] = 52, /* ... */, ['9'] = 61,
> > > > > > };
> > > > > >
> > > > > > static inline int base64_rev_lookup(u8 c, enum base64_variant variant) {
> > > > > > s8 v = base64_rev_common[c];
> > > > > > if (v != -1)
> > > > > > return v;
> > > > > >
> > > > > > switch (variant) {
> > > > > > case BASE64_STD:
> > > > > > if (c == '+') return 62;
> > > > > > if (c == '/') return 63;
> > > > > > break;
> > > > > > case BASE64_IMAP:
> > > > > > if (c == '+') return 62;
> > > > > > if (c == ',') return 63;
> > > > > > break;
> > > > > > case BASE64_URLSAFE:
> > > > > > if (c == '-') return 62;
> > > > > > if (c == '_') return 63;
> > > > > > break;
> > > > > > }
> > > > > > return -1;
> > > > > > }
> > > > > >
> > > > > > What do you think?
> > > > >
> > > > > That adds several branches in the hot loop, at least 2 of which are
> > > > > unpredictable for valid base64 input of a given variant (v != -1 as
> > > > > well as the first c check in the applicable switch case).
> > > >
> > > > I'd certainly pass in the character values for 62 and 63 so they are
> > > > determined well outside the inner loop.
> > > > Possibly even going as far as #define BASE64_STD ('+' << 8 | '/').
> > > >
> > > > > That seems like it would hurt performance, no?
> > > > > I think having 3 separate tables
> > > > > would be preferable to making the hot loop more branchy.
> > > >
> > > > Depends how common you think 62 and 63 are...
> > > > I guess 63 comes from 0xff bytes - so might be quite common.
> > > >
> > > > One thing I think you've missed is that the decode converts 4 characters
> > > > into 24 bits - which then need carefully writing into the output buffer.
> > > > There is no need to check whether each character is valid.
> > > > After:
> > > > val_24 = t[b[0]] | t[b[1]] << 6 | t[b[2]] << 12 | t[b[3]] << 18;
> > > > val_24 will be negative iff one of b[0..3] is invalid.
> > > > So you only need to check every 4 input characters, not for every one.
> > > > That does require separate tables.
> > > > (Or have a decoder that always maps "+-" to 62 and "/,_" to 63.)
> > > >
> > > > David
> > > >
> > >
> > > Thanks for the feedback.
> > > For the next revision, we’ll use a single lookup table that maps both +
> > > and - to 62, and /, _, and , to 63.
> > > Does this approach sound good to everyone?
> >
> > Sounds fine to me. Perhaps worth pointing out that the decision to
> > accept any base64 variant in the decoder would likely be permanent,
> > since users may come to depend on it. But I don't see any issue with
> > it as long as all the base64 variants agree on the values of their
> > common symbols.
>
> If an incompatible version comes along it'll need a different function
> (or similar). But there is no point over-engineering it now.
>
> David
>
>
As Eric mentioned, the decoder in fs/crypto/ needs to reject invalid input.
One possible solution I came up with is to first create a shared
base64_rev_common lookup table as the base for all Base64 variants.
Then, depending on the variant (e.g., BASE64_STD, BASE64_URLSAFE, etc.), we
can dynamically adjust the character mappings for position 62 and position 63
at runtime, based on the variant.
Here are the changes to the code:
static const s8 base64_rev_common[256] = {
[0 ... 255] = -1,
['A'] = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
['a'] = 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
['0'] = 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
};
static const struct {
char char62, char63;
} base64_symbols[] = {
[BASE64_STD] = { '+', '/' },
[BASE64_URLSAFE] = { '-', '_' },
[BASE64_IMAP] = { '+', ',' },
};
int base64_decode(const char *src, int srclen, u8 *dst, bool padding, enum base64_variant variant)
{
u8 *bp = dst;
u8 pad_cnt = 0;
s8 input1, input2, input3, input4;
u32 val;
s8 base64_rev_tables[256];
/* Validate the input length for padding */
if (unlikely(padding && (srclen & 0x03) != 0))
return -1;
memcpy(base64_rev_tables, base64_rev_common, sizeof(base64_rev_common));
if (variant < BASE64_STD || variant > BASE64_IMAP)
return -1;
base64_rev_tables[base64_symbols[variant].char62] = 62;
base64_rev_tables[base64_symbols[variant].char63] = 63;
while (padding && srclen > 0 && src[srclen - 1] == '=') {
pad_cnt++;
srclen--;
if (pad_cnt > 2)
return -1;
}
while (srclen >= 4) {
/* Decode the next 4 characters */
input1 = base64_rev_tables[(u8)src[0]];
input2 = base64_rev_tables[(u8)src[1]];
input3 = base64_rev_tables[(u8)src[2]];
input4 = base64_rev_tables[(u8)src[3]];
val = (input1 << 18) |
(input2 << 12) |
(input3 << 6) |
input4;
if (unlikely((s32)val < 0))
return -1;
*bp++ = (u8)(val >> 16);
*bp++ = (u8)(val >> 8);
*bp++ = (u8)val;
src += 4;
srclen -= 4;
}
/* Handle leftover characters when padding is not used */
if (srclen > 0) {
switch (srclen) {
case 2:
input1 = base64_rev_tables[(u8)src[0]];
input2 = base64_rev_tables[(u8)src[1]];
val = (input1 << 6) | input2; /* 12 bits */
if (unlikely((s32)val < 0 || val & 0x0F))
return -1;
*bp++ = (u8)(val >> 4);
break;
case 3:
input1 = base64_rev_tables[(u8)src[0]];
input2 = base64_rev_tables[(u8)src[1]];
input3 = base64_rev_tables[(u8)src[2]];
val = (input1 << 12) |
(input2 << 6) |
input3; /* 18 bits */
if (unlikely((s32)val < 0 || val & 0x03))
return -1;
*bp++ = (u8)(val >> 10);
*bp++ = (u8)(val >> 2);
break;
default:
return -1;
}
}
return bp - dst;
}
Based on KUnit testing, the performance results are as follows:
base64_performance_tests: [64B] decode run : 40ns
base64_performance_tests: [1KB] decode run : 463ns
However, this approach introduces an issue. It uses 256 bytes of memory
on the stack for base64_rev_tables, which might not be ideal. Does anyone
have any thoughts or alternative suggestions to solve this issue, or is it
not really a concern?
Best regards,
Guan-Chun
> >
> > Best,
> > Caleb
>
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v3 2/6] lib/base64: Optimize base64_decode() with reverse lookup tables
2025-10-09 12:25 ` Guan-Chun Wu
@ 2025-10-10 9:51 ` David Laight
2025-10-13 9:49 ` Guan-Chun Wu
0 siblings, 1 reply; 31+ messages in thread
From: David Laight @ 2025-10-10 9:51 UTC (permalink / raw)
To: Guan-Chun Wu
Cc: Caleb Sander Mateos, akpm, axboe, ceph-devel, ebiggers, hch,
home7438072, idryomov, jaegeuk, kbusch, linux-fscrypt,
linux-kernel, linux-nvme, sagi, tytso, visitorckw, xiubli
On Thu, 9 Oct 2025 20:25:17 +0800
Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
...
> As Eric mentioned, the decoder in fs/crypto/ needs to reject invalid input.
(to avoid two different input buffers giving the same output)
Which is annoyingly reasonable.
> One possible solution I came up with is to first create a shared
> base64_rev_common lookup table as the base for all Base64 variants.
> Then, depending on the variant (e.g., BASE64_STD, BASE64_URLSAFE, etc.), we
> can dynamically adjust the character mappings for position 62 and position 63
> at runtime, based on the variant.
>
> Here are the changes to the code:
>
> static const s8 base64_rev_common[256] = {
> [0 ... 255] = -1,
> ['A'] = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
> 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
> ['a'] = 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
> 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
> ['0'] = 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
> };
>
> static const struct {
> char char62, char63;
> } base64_symbols[] = {
> [BASE64_STD] = { '+', '/' },
> [BASE64_URLSAFE] = { '-', '_' },
> [BASE64_IMAP] = { '+', ',' },
> };
>
> int base64_decode(const char *src, int srclen, u8 *dst, bool padding, enum base64_variant variant)
> {
> u8 *bp = dst;
> u8 pad_cnt = 0;
> s8 input1, input2, input3, input4;
> u32 val;
> s8 base64_rev_tables[256];
>
> /* Validate the input length for padding */
> if (unlikely(padding && (srclen & 0x03) != 0))
> return -1;
There is no need for an early check.
Pick it up after the loop when 'srclen != 0'.
>
> memcpy(base64_rev_tables, base64_rev_common, sizeof(base64_rev_common));
Ugg - having a memcpy() here is not a good idea.
It really is better to have 3 arrays, but use a 'mostly common' initialiser.
Perhaps:
#define BASE64_REV_INIT(ch_62, ch_63) = { \
[0 ... 255] = -1, \
['A'] = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, \
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, \
['a'] = 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, \
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, \
['0'] = 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, \
[ch_62] = 62, [ch_63] = 63, \
}
static const s8 base64_rev_maps[][256] = {
[BASE64_STD] = BASE64_REV_INIT('+', '/'),
[BASE64_URLSAFE] = BASE64_REV_INIT('-', '_'),
[BASE64_IMAP] = BASE64_REV_INIT('+', ',')
};
Then (after validating variant):
const s8 *map = base64_rev_maps[variant];
>
> if (variant < BASE64_STD || variant > BASE64_IMAP)
> return -1;
>
> base64_rev_tables[base64_symbols[variant].char62] = 62;
> base64_rev_tables[base64_symbols[variant].char63] = 63;
>
> while (padding && srclen > 0 && src[srclen - 1] == '=') {
> pad_cnt++;
> srclen--;
> if (pad_cnt > 2)
> return -1;
> }
I'm not sure I'd to that there.
You are (in some sense) optimising for padding.
From what I remember, "abcd" gives 24 bits, "abc=" 16 and "ab==" 8.
>
> while (srclen >= 4) {
> /* Decode the next 4 characters */
> input1 = base64_rev_tables[(u8)src[0]];
> input2 = base64_rev_tables[(u8)src[1]];
> input3 = base64_rev_tables[(u8)src[2]];
> input4 = base64_rev_tables[(u8)src[3]];
I'd be tempted to make src[] unsigned - probably be assigning the parameter
to a local at the top of the function.
Also you have input3 = ... src[2]...
Perhaps they should be input[0..3] instead.
>
> val = (input1 << 18) |
> (input2 << 12) |
> (input3 << 6) |
> input4;
Four lines is excessive, C doesn't require the () and I'm not sure the
compilers complain about << and |.
>
> if (unlikely((s32)val < 0))
> return -1;
Make 'val' signed - then you don't need the cast.
You can pick up the padding check here, something like:
val = input1 << 18 | input2 << 12;
if (!padding || val < 0 || src[3] != '=')
return -1;
*bp++ = val >> 16;
if (src[2] == '=')
return bp - dst;
if (input3 < 0)
return -1;
val |= input3 << 6;
*bp++ = val >> 8;
return bp - dst;
Or, if you really want to use the code below the loop:
if (!padding || src[3] != '=')
return -1;
padding = 0;
srclen -= 1 + (src[2] == '=');
break;
>
> *bp++ = (u8)(val >> 16);
> *bp++ = (u8)(val >> 8);
> *bp++ = (u8)val;
You don't need those casts.
>
> src += 4;
> srclen -= 4;
> }
>
> /* Handle leftover characters when padding is not used */
You are coming here with padding.
I'm not sure what should happen without padding.
For a multi-line file decode I suspect the characters need adding to
the start of the next line (ie lines aren't required to contain
multiples of 4 characters - even though they almost always will).
> if (srclen > 0) {
> switch (srclen) {
You don't need an 'if' and a 'switch'.
srclen is likely to be zero, but perhaps write as:
if (likely(!srclen))
return bp - dst;
if (padding || srclen == 1)
return -1;
val = base64_rev_tables[(u8)src[0]] << 12 | base64_rev_tables[(u8)src[1]] << 6;
*bp++ = val >> 10;
if (srclen == 1) {
if (val & 0x800003ff)
return -1;
} else {
val |= base64_rev_tables[(u8)src[2]];
if (val & 0x80000003)
return -1;
*bp++ = val >> 2;
}
return bp - dst;
}
David
> case 2:
> input1 = base64_rev_tables[(u8)src[0]];
> input2 = base64_rev_tables[(u8)src[1]];
> val = (input1 << 6) | input2; /* 12 bits */
> if (unlikely((s32)val < 0 || val & 0x0F))
> return -1;
>
> *bp++ = (u8)(val >> 4);
> break;
> case 3:
> input1 = base64_rev_tables[(u8)src[0]];
> input2 = base64_rev_tables[(u8)src[1]];
> input3 = base64_rev_tables[(u8)src[2]];
>
> val = (input1 << 12) |
> (input2 << 6) |
> input3; /* 18 bits */
> if (unlikely((s32)val < 0 || val & 0x03))
> return -1;
>
> *bp++ = (u8)(val >> 10);
> *bp++ = (u8)(val >> 2);
> break;
> default:
> return -1;
> }
> }
>
> return bp - dst;
> }
> Based on KUnit testing, the performance results are as follows:
> base64_performance_tests: [64B] decode run : 40ns
> base64_performance_tests: [1KB] decode run : 463ns
>
> However, this approach introduces an issue. It uses 256 bytes of memory
> on the stack for base64_rev_tables, which might not be ideal. Does anyone
> have any thoughts or alternative suggestions to solve this issue, or is it
> not really a concern?
>
> Best regards,
> Guan-Chun
>
> > >
> > > Best,
> > > Caleb
> >
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v3 2/6] lib/base64: Optimize base64_decode() with reverse lookup tables
2025-10-10 9:51 ` David Laight
@ 2025-10-13 9:49 ` Guan-Chun Wu
2025-10-14 8:14 ` David Laight
0 siblings, 1 reply; 31+ messages in thread
From: Guan-Chun Wu @ 2025-10-13 9:49 UTC (permalink / raw)
To: David Laight
Cc: Caleb Sander Mateos, akpm, axboe, ceph-devel, ebiggers, hch,
home7438072, idryomov, jaegeuk, kbusch, linux-fscrypt,
linux-kernel, linux-nvme, sagi, tytso, visitorckw, xiubli
On Fri, Oct 10, 2025 at 10:51:38AM +0100, David Laight wrote:
> On Thu, 9 Oct 2025 20:25:17 +0800
> Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
>
> ...
> > As Eric mentioned, the decoder in fs/crypto/ needs to reject invalid input.
>
> (to avoid two different input buffers giving the same output)
>
> Which is annoyingly reasonable.
>
> > One possible solution I came up with is to first create a shared
> > base64_rev_common lookup table as the base for all Base64 variants.
> > Then, depending on the variant (e.g., BASE64_STD, BASE64_URLSAFE, etc.), we
> > can dynamically adjust the character mappings for position 62 and position 63
> > at runtime, based on the variant.
> >
> > Here are the changes to the code:
> >
> > static const s8 base64_rev_common[256] = {
> > [0 ... 255] = -1,
> > ['A'] = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
> > 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
> > ['a'] = 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
> > 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
> > ['0'] = 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
> > };
> >
> > static const struct {
> > char char62, char63;
> > } base64_symbols[] = {
> > [BASE64_STD] = { '+', '/' },
> > [BASE64_URLSAFE] = { '-', '_' },
> > [BASE64_IMAP] = { '+', ',' },
> > };
> >
> > int base64_decode(const char *src, int srclen, u8 *dst, bool padding, enum base64_variant variant)
> > {
> > u8 *bp = dst;
> > u8 pad_cnt = 0;
> > s8 input1, input2, input3, input4;
> > u32 val;
> > s8 base64_rev_tables[256];
> >
> > /* Validate the input length for padding */
> > if (unlikely(padding && (srclen & 0x03) != 0))
> > return -1;
>
> There is no need for an early check.
> Pick it up after the loop when 'srclen != 0'.
>
I think the early check is still needed, since I'm removing the
padding '=' first.
This makes the handling logic consistent for both padded and unpadded
inputs, and avoids extra if conditions for padding inside the hot loop.
> >
> > memcpy(base64_rev_tables, base64_rev_common, sizeof(base64_rev_common));
>
> Ugg - having a memcpy() here is not a good idea.
> It really is better to have 3 arrays, but use a 'mostly common' initialiser.
> Perhaps:
> #define BASE64_REV_INIT(ch_62, ch_63) = { \
> [0 ... 255] = -1, \
> ['A'] = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, \
> 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, \
> ['a'] = 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, \
> 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, \
> ['0'] = 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, \
> [ch_62] = 62, [ch_63] = 63, \
> }
>
> static const s8 base64_rev_maps[][256] = {
> [BASE64_STD] = BASE64_REV_INIT('+', '/'),
> [BASE64_URLSAFE] = BASE64_REV_INIT('-', '_'),
> [BASE64_IMAP] = BASE64_REV_INIT('+', ',')
> };
>
> Then (after validating variant):
> const s8 *map = base64_rev_maps[variant];
>
Got it. I'll switch to using three static tables with a common initializer
as you suggested.
> >
> > if (variant < BASE64_STD || variant > BASE64_IMAP)
> > return -1;
> >
> > base64_rev_tables[base64_symbols[variant].char62] = 62;
> > base64_rev_tables[base64_symbols[variant].char63] = 63;
> >
> > while (padding && srclen > 0 && src[srclen - 1] == '=') {
> > pad_cnt++;
> > srclen--;
> > if (pad_cnt > 2)
> > return -1;
> > }
>
> I'm not sure I'd to that there.
> You are (in some sense) optimising for padding.
> From what I remember, "abcd" gives 24 bits, "abc=" 16 and "ab==" 8.
>
> >
> > while (srclen >= 4) {
> > /* Decode the next 4 characters */
> > input1 = base64_rev_tables[(u8)src[0]];
> > input2 = base64_rev_tables[(u8)src[1]];
> > input3 = base64_rev_tables[(u8)src[2]];
> > input4 = base64_rev_tables[(u8)src[3]];
>
> I'd be tempted to make src[] unsigned - probably be assigning the parameter
> to a local at the top of the function.
>
> Also you have input3 = ... src[2]...
> Perhaps they should be input[0..3] instead.
>
OK, I'll make the changes.
> >
> > val = (input1 << 18) |
> > (input2 << 12) |
> > (input3 << 6) |
> > input4;
>
> Four lines is excessive, C doesn't require the () and I'm not sure the
> compilers complain about << and |.
>
OK, I'll make the changes.
> >
> > if (unlikely((s32)val < 0))
> > return -1;
>
> Make 'val' signed - then you don't need the cast.
> You can pick up the padding check here, something like:
> val = input1 << 18 | input2 << 12;
> if (!padding || val < 0 || src[3] != '=')
> return -1;
> *bp++ = val >> 16;
> if (src[2] == '=')
> return bp - dst;
> if (input3 < 0)
> return -1;
> val |= input3 << 6;
> *bp++ = val >> 8;
> return bp - dst;
>
> Or, if you really want to use the code below the loop:
> if (!padding || src[3] != '=')
> return -1;
> padding = 0;
> srclen -= 1 + (src[2] == '=');
> break;
>
>
> >
> > *bp++ = (u8)(val >> 16);
> > *bp++ = (u8)(val >> 8);
> > *bp++ = (u8)val;
>
> You don't need those casts.
>
OK, I'll make the changes.
> >
> > src += 4;
> > srclen -= 4;
> > }
> >
> > /* Handle leftover characters when padding is not used */
>
> You are coming here with padding.
> I'm not sure what should happen without padding.
> For a multi-line file decode I suspect the characters need adding to
> the start of the next line (ie lines aren't required to contain
> multiples of 4 characters - even though they almost always will).
>
Ah, my mistake. I forgot to remove that comment.
Based on my observation, base64_decode() should process the entire input
buffer in a single call, so I believe it does not need to handle
multi-line input.
Best regards,
Guan-Chun
> > if (srclen > 0) {
> > switch (srclen) {
>
> You don't need an 'if' and a 'switch'.
> srclen is likely to be zero, but perhaps write as:
> if (likely(!srclen))
> return bp - dst;
> if (padding || srclen == 1)
> return -1;
>
> val = base64_rev_tables[(u8)src[0]] << 12 | base64_rev_tables[(u8)src[1]] << 6;
> *bp++ = val >> 10;
> if (srclen == 1) {
> if (val & 0x800003ff)
> return -1;
> } else {
> val |= base64_rev_tables[(u8)src[2]];
> if (val & 0x80000003)
> return -1;
> *bp++ = val >> 2;
> }
> return bp - dst;
> }
>
> David
>
> > case 2:
> > input1 = base64_rev_tables[(u8)src[0]];
> > input2 = base64_rev_tables[(u8)src[1]];
> > val = (input1 << 6) | input2; /* 12 bits */
> > if (unlikely((s32)val < 0 || val & 0x0F))
> > return -1;
> >
> > *bp++ = (u8)(val >> 4);
> > break;
> > case 3:
> > input1 = base64_rev_tables[(u8)src[0]];
> > input2 = base64_rev_tables[(u8)src[1]];
> > input3 = base64_rev_tables[(u8)src[2]];
> >
> > val = (input1 << 12) |
> > (input2 << 6) |
> > input3; /* 18 bits */
> > if (unlikely((s32)val < 0 || val & 0x03))
> > return -1;
> >
> > *bp++ = (u8)(val >> 10);
> > *bp++ = (u8)(val >> 2);
> > break;
> > default:
> > return -1;
> > }
> > }
> >
> > return bp - dst;
> > }
> > Based on KUnit testing, the performance results are as follows:
> > base64_performance_tests: [64B] decode run : 40ns
> > base64_performance_tests: [1KB] decode run : 463ns
> >
> > However, this approach introduces an issue. It uses 256 bytes of memory
> > on the stack for base64_rev_tables, which might not be ideal. Does anyone
> > have any thoughts or alternative suggestions to solve this issue, or is it
> > not really a concern?
> >
> > Best regards,
> > Guan-Chun
> >
> > > >
> > > > Best,
> > > > Caleb
> > >
>
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v3 2/6] lib/base64: Optimize base64_decode() with reverse lookup tables
2025-10-13 9:49 ` Guan-Chun Wu
@ 2025-10-14 8:14 ` David Laight
2025-10-16 10:07 ` Guan-Chun Wu
2025-10-27 13:12 ` Guan-Chun Wu
0 siblings, 2 replies; 31+ messages in thread
From: David Laight @ 2025-10-14 8:14 UTC (permalink / raw)
To: Guan-Chun Wu
Cc: Caleb Sander Mateos, akpm, axboe, ceph-devel, ebiggers, hch,
home7438072, idryomov, jaegeuk, kbusch, linux-fscrypt,
linux-kernel, linux-nvme, sagi, tytso, visitorckw, xiubli
On Mon, 13 Oct 2025 17:49:55 +0800
Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
> On Fri, Oct 10, 2025 at 10:51:38AM +0100, David Laight wrote:
> > On Thu, 9 Oct 2025 20:25:17 +0800
> > Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
> >
> > ...
> > > As Eric mentioned, the decoder in fs/crypto/ needs to reject invalid input.
> >
> > (to avoid two different input buffers giving the same output)
> >
> > Which is annoyingly reasonable.
> >
> > > One possible solution I came up with is to first create a shared
> > > base64_rev_common lookup table as the base for all Base64 variants.
> > > Then, depending on the variant (e.g., BASE64_STD, BASE64_URLSAFE, etc.), we
> > > can dynamically adjust the character mappings for position 62 and position 63
> > > at runtime, based on the variant.
> > >
> > > Here are the changes to the code:
> > >
> > > static const s8 base64_rev_common[256] = {
> > > [0 ... 255] = -1,
> > > ['A'] = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
> > > 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
> > > ['a'] = 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
> > > 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
> > > ['0'] = 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
> > > };
> > >
> > > static const struct {
> > > char char62, char63;
> > > } base64_symbols[] = {
> > > [BASE64_STD] = { '+', '/' },
> > > [BASE64_URLSAFE] = { '-', '_' },
> > > [BASE64_IMAP] = { '+', ',' },
> > > };
> > >
> > > int base64_decode(const char *src, int srclen, u8 *dst, bool padding, enum base64_variant variant)
> > > {
> > > u8 *bp = dst;
> > > u8 pad_cnt = 0;
> > > s8 input1, input2, input3, input4;
> > > u32 val;
> > > s8 base64_rev_tables[256];
> > >
> > > /* Validate the input length for padding */
> > > if (unlikely(padding && (srclen & 0x03) != 0))
> > > return -1;
> >
> > There is no need for an early check.
> > Pick it up after the loop when 'srclen != 0'.
> >
>
> I think the early check is still needed, since I'm removing the
> padding '=' first.
> This makes the handling logic consistent for both padded and unpadded
> inputs, and avoids extra if conditions for padding inside the hot loop.
The 'invalid input' check will detect the padding.
Then you don't get an extra check if there is no padding (probably normal).
I realised I didn't get it quite right - updated below.
>
> > >
> > > memcpy(base64_rev_tables, base64_rev_common, sizeof(base64_rev_common));
> >
> > Ugg - having a memcpy() here is not a good idea.
> > It really is better to have 3 arrays, but use a 'mostly common' initialiser.
> > Perhaps:
> > #define BASE64_REV_INIT(ch_62, ch_63) = { \
> > [0 ... 255] = -1, \
> > ['A'] = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, \
> > 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, \
> > ['a'] = 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, \
> > 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, \
> > ['0'] = 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, \
> > [ch_62] = 62, [ch_63] = 63, \
> > }
> >
> > static const s8 base64_rev_maps[][256] = {
> > [BASE64_STD] = BASE64_REV_INIT('+', '/'),
> > [BASE64_URLSAFE] = BASE64_REV_INIT('-', '_'),
> > [BASE64_IMAP] = BASE64_REV_INIT('+', ',')
> > };
> >
> > Then (after validating variant):
> > const s8 *map = base64_rev_maps[variant];
> >
>
> Got it. I'll switch to using three static tables with a common initializer
> as you suggested.
>
> > >
> > > if (variant < BASE64_STD || variant > BASE64_IMAP)
> > > return -1;
> > >
> > > base64_rev_tables[base64_symbols[variant].char62] = 62;
> > > base64_rev_tables[base64_symbols[variant].char63] = 63;
> > >
> > > while (padding && srclen > 0 && src[srclen - 1] == '=') {
> > > pad_cnt++;
> > > srclen--;
> > > if (pad_cnt > 2)
> > > return -1;
> > > }
> >
> > I'm not sure I'd to that there.
> > You are (in some sense) optimising for padding.
> > From what I remember, "abcd" gives 24 bits, "abc=" 16 and "ab==" 8.
> >
> > >
> > > while (srclen >= 4) {
> > > /* Decode the next 4 characters */
> > > input1 = base64_rev_tables[(u8)src[0]];
> > > input2 = base64_rev_tables[(u8)src[1]];
> > > input3 = base64_rev_tables[(u8)src[2]];
> > > input4 = base64_rev_tables[(u8)src[3]];
> >
> > I'd be tempted to make src[] unsigned - probably be assigning the parameter
> > to a local at the top of the function.
> >
> > Also you have input3 = ... src[2]...
> > Perhaps they should be input[0..3] instead.
> >
>
> OK, I'll make the changes.
>
> > >
> > > val = (input1 << 18) |
> > > (input2 << 12) |
> > > (input3 << 6) |
> > > input4;
> >
> > Four lines is excessive, C doesn't require the () and I'm not sure the
> > compilers complain about << and |.
> >
>
> OK, I'll make the changes.
>
> > >
> > > if (unlikely((s32)val < 0))
> > > return -1;
> >
> > Make 'val' signed - then you don't need the cast.
...
> > Or, if you really want to use the code below the loop:
> > if (!padding || src[3] != '=')
> > return -1;
> > padding = 0;
> > srclen -= 1 + (src[2] == '=');
> > break;
That is missing a test...
Change to:
if (!padding || srclen != 4 || src[3] != '=')
return -1;
padding = 0;
srclen = src[2] == '=' ? 2 : 3;
break;
The compiler will then optimise away the first checks after the
loop because it knows they can't happen.
> >
> >
> > >
> > > *bp++ = (u8)(val >> 16);
> > > *bp++ = (u8)(val >> 8);
> > > *bp++ = (u8)val;
> >
> > You don't need those casts.
> >
>
> OK, I'll make the changes.
>
> > >
> > > src += 4;
> > > srclen -= 4;
> > > }
> > >
> > > /* Handle leftover characters when padding is not used */
> >
> > You are coming here with padding.
> > I'm not sure what should happen without padding.
> > For a multi-line file decode I suspect the characters need adding to
> > the start of the next line (ie lines aren't required to contain
> > multiples of 4 characters - even though they almost always will).
> >
>
> Ah, my mistake. I forgot to remove that comment.
> Based on my observation, base64_decode() should process the entire input
> buffer in a single call, so I believe it does not need to handle
> multi-line input.
I was thinking of the the case where it is processing the output of
something like base64encode.
The caller will have separated out the lines, but I don't know whether
every line has to contain a multiple of 4 characters - or whether the
lines can be arbitrarily split after being encoded (I know that won't
normally happen - but you never know).
>
> Best regards,
> Guan-Chun
>
> > > if (srclen > 0) {
> > > switch (srclen) {
> >
> > You don't need an 'if' and a 'switch'.
> > srclen is likely to be zero, but perhaps write as:
> > if (likely(!srclen))
> > return bp - dst;
> > if (padding || srclen == 1)
> > return -1;
> >
> > val = base64_rev_tables[(u8)src[0]] << 12 | base64_rev_tables[(u8)src[1]] << 6;
> > *bp++ = val >> 10;
> > if (srclen == 1) {
Obviously should be (srclen == 2)
> > if (val & 0x800003ff)
> > return -1;
> > } else {
> > val |= base64_rev_tables[(u8)src[2]];
> > if (val & 0x80000003)
> > return -1;
> > *bp++ = val >> 2;
> > }
> > return bp - dst;
> > }
> >
> > David
David
> >
> > > case 2:
> > > input1 = base64_rev_tables[(u8)src[0]];
> > > input2 = base64_rev_tables[(u8)src[1]];
> > > val = (input1 << 6) | input2; /* 12 bits */
> > > if (unlikely((s32)val < 0 || val & 0x0F))
> > > return -1;
> > >
> > > *bp++ = (u8)(val >> 4);
> > > break;
> > > case 3:
> > > input1 = base64_rev_tables[(u8)src[0]];
> > > input2 = base64_rev_tables[(u8)src[1]];
> > > input3 = base64_rev_tables[(u8)src[2]];
> > >
> > > val = (input1 << 12) |
> > > (input2 << 6) |
> > > input3; /* 18 bits */
> > > if (unlikely((s32)val < 0 || val & 0x03))
> > > return -1;
> > >
> > > *bp++ = (u8)(val >> 10);
> > > *bp++ = (u8)(val >> 2);
> > > break;
> > > default:
> > > return -1;
> > > }
> > > }
> > >
> > > return bp - dst;
> > > }
> > > Based on KUnit testing, the performance results are as follows:
> > > base64_performance_tests: [64B] decode run : 40ns
> > > base64_performance_tests: [1KB] decode run : 463ns
> > >
> > > However, this approach introduces an issue. It uses 256 bytes of memory
> > > on the stack for base64_rev_tables, which might not be ideal. Does anyone
> > > have any thoughts or alternative suggestions to solve this issue, or is it
> > > not really a concern?
> > >
> > > Best regards,
> > > Guan-Chun
> > >
> > > > >
> > > > > Best,
> > > > > Caleb
> > > >
> >
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v3 2/6] lib/base64: Optimize base64_decode() with reverse lookup tables
2025-10-14 8:14 ` David Laight
@ 2025-10-16 10:07 ` Guan-Chun Wu
2025-10-27 13:12 ` Guan-Chun Wu
1 sibling, 0 replies; 31+ messages in thread
From: Guan-Chun Wu @ 2025-10-16 10:07 UTC (permalink / raw)
To: David Laight
Cc: Caleb Sander Mateos, akpm, axboe, ceph-devel, ebiggers, hch,
home7438072, idryomov, jaegeuk, kbusch, linux-fscrypt,
linux-kernel, linux-nvme, sagi, tytso, visitorckw, xiubli
On Tue, Oct 14, 2025 at 09:14:20AM +0100, David Laight wrote:
> On Mon, 13 Oct 2025 17:49:55 +0800
> Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
>
> > On Fri, Oct 10, 2025 at 10:51:38AM +0100, David Laight wrote:
> > > On Thu, 9 Oct 2025 20:25:17 +0800
> > > Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
> > >
> > > ...
> > > > As Eric mentioned, the decoder in fs/crypto/ needs to reject invalid input.
> > >
> > > (to avoid two different input buffers giving the same output)
> > >
> > > Which is annoyingly reasonable.
> > >
> > > > One possible solution I came up with is to first create a shared
> > > > base64_rev_common lookup table as the base for all Base64 variants.
> > > > Then, depending on the variant (e.g., BASE64_STD, BASE64_URLSAFE, etc.), we
> > > > can dynamically adjust the character mappings for position 62 and position 63
> > > > at runtime, based on the variant.
> > > >
> > > > Here are the changes to the code:
> > > >
> > > > static const s8 base64_rev_common[256] = {
> > > > [0 ... 255] = -1,
> > > > ['A'] = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
> > > > 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
> > > > ['a'] = 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
> > > > 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
> > > > ['0'] = 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
> > > > };
> > > >
> > > > static const struct {
> > > > char char62, char63;
> > > > } base64_symbols[] = {
> > > > [BASE64_STD] = { '+', '/' },
> > > > [BASE64_URLSAFE] = { '-', '_' },
> > > > [BASE64_IMAP] = { '+', ',' },
> > > > };
> > > >
> > > > int base64_decode(const char *src, int srclen, u8 *dst, bool padding, enum base64_variant variant)
> > > > {
> > > > u8 *bp = dst;
> > > > u8 pad_cnt = 0;
> > > > s8 input1, input2, input3, input4;
> > > > u32 val;
> > > > s8 base64_rev_tables[256];
> > > >
> > > > /* Validate the input length for padding */
> > > > if (unlikely(padding && (srclen & 0x03) != 0))
> > > > return -1;
> > >
> > > There is no need for an early check.
> > > Pick it up after the loop when 'srclen != 0'.
> > >
> >
> > I think the early check is still needed, since I'm removing the
> > padding '=' first.
> > This makes the handling logic consistent for both padded and unpadded
> > inputs, and avoids extra if conditions for padding inside the hot loop.
>
> The 'invalid input' check will detect the padding.
> Then you don't get an extra check if there is no padding (probably normal).
> I realised I didn't get it quite right - updated below.
>
> >
> > > >
> > > > memcpy(base64_rev_tables, base64_rev_common, sizeof(base64_rev_common));
> > >
> > > Ugg - having a memcpy() here is not a good idea.
> > > It really is better to have 3 arrays, but use a 'mostly common' initialiser.
> > > Perhaps:
> > > #define BASE64_REV_INIT(ch_62, ch_63) = { \
> > > [0 ... 255] = -1, \
> > > ['A'] = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, \
> > > 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, \
> > > ['a'] = 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, \
> > > 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, \
> > > ['0'] = 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, \
> > > [ch_62] = 62, [ch_63] = 63, \
> > > }
> > >
> > > static const s8 base64_rev_maps[][256] = {
> > > [BASE64_STD] = BASE64_REV_INIT('+', '/'),
> > > [BASE64_URLSAFE] = BASE64_REV_INIT('-', '_'),
> > > [BASE64_IMAP] = BASE64_REV_INIT('+', ',')
> > > };
> > >
> > > Then (after validating variant):
> > > const s8 *map = base64_rev_maps[variant];
> > >
> >
> > Got it. I'll switch to using three static tables with a common initializer
> > as you suggested.
> >
> > > >
> > > > if (variant < BASE64_STD || variant > BASE64_IMAP)
> > > > return -1;
> > > >
> > > > base64_rev_tables[base64_symbols[variant].char62] = 62;
> > > > base64_rev_tables[base64_symbols[variant].char63] = 63;
> > > >
> > > > while (padding && srclen > 0 && src[srclen - 1] == '=') {
> > > > pad_cnt++;
> > > > srclen--;
> > > > if (pad_cnt > 2)
> > > > return -1;
> > > > }
> > >
> > > I'm not sure I'd to that there.
> > > You are (in some sense) optimising for padding.
> > > From what I remember, "abcd" gives 24 bits, "abc=" 16 and "ab==" 8.
> > >
> > > >
> > > > while (srclen >= 4) {
> > > > /* Decode the next 4 characters */
> > > > input1 = base64_rev_tables[(u8)src[0]];
> > > > input2 = base64_rev_tables[(u8)src[1]];
> > > > input3 = base64_rev_tables[(u8)src[2]];
> > > > input4 = base64_rev_tables[(u8)src[3]];
> > >
> > > I'd be tempted to make src[] unsigned - probably be assigning the parameter
> > > to a local at the top of the function.
> > >
> > > Also you have input3 = ... src[2]...
> > > Perhaps they should be input[0..3] instead.
> > >
> >
> > OK, I'll make the changes.
> >
> > > >
> > > > val = (input1 << 18) |
> > > > (input2 << 12) |
> > > > (input3 << 6) |
> > > > input4;
> > >
> > > Four lines is excessive, C doesn't require the () and I'm not sure the
> > > compilers complain about << and |.
> > >
> >
> > OK, I'll make the changes.
> >
> > > >
> > > > if (unlikely((s32)val < 0))
> > > > return -1;
> > >
> > > Make 'val' signed - then you don't need the cast.
> ...
> > > Or, if you really want to use the code below the loop:
> > > if (!padding || src[3] != '=')
> > > return -1;
> > > padding = 0;
> > > srclen -= 1 + (src[2] == '=');
> > > break;
>
> That is missing a test...
> Change to:
> if (!padding || srclen != 4 || src[3] != '=')
> return -1;
> padding = 0;
> srclen = src[2] == '=' ? 2 : 3;
> break;
>
> The compiler will then optimise away the first checks after the
> loop because it knows they can't happen.
>
> > >
> > >
> > > >
> > > > *bp++ = (u8)(val >> 16);
> > > > *bp++ = (u8)(val >> 8);
> > > > *bp++ = (u8)val;
> > >
> > > You don't need those casts.
> > >
> >
> > OK, I'll make the changes.
> >
> > > >
> > > > src += 4;
> > > > srclen -= 4;
> > > > }
> > > >
> > > > /* Handle leftover characters when padding is not used */
> > >
> > > You are coming here with padding.
> > > I'm not sure what should happen without padding.
> > > For a multi-line file decode I suspect the characters need adding to
> > > the start of the next line (ie lines aren't required to contain
> > > multiples of 4 characters - even though they almost always will).
> > >
> >
> > Ah, my mistake. I forgot to remove that comment.
> > Based on my observation, base64_decode() should process the entire input
> > buffer in a single call, so I believe it does not need to handle
> > multi-line input.
>
> I was thinking of the the case where it is processing the output of
> something like base64encode.
> The caller will have separated out the lines, but I don't know whether
> every line has to contain a multiple of 4 characters - or whether the
> lines can be arbitrarily split after being encoded (I know that won't
> normally happen - but you never know).
>
I believe the splitting should be aligned to multiples of 4,
since Base64 encoding operates on 4-character blocks that represent 3 bytes
of data.
If it's split arbitrarily, the decoded result may differ from the original
data or even become invalid.
Best regards,
Guan-Chun
> >
> > Best regards,
> > Guan-Chun
> >
> > > > if (srclen > 0) {
> > > > switch (srclen) {
> > >
> > > You don't need an 'if' and a 'switch'.
> > > srclen is likely to be zero, but perhaps write as:
> > > if (likely(!srclen))
> > > return bp - dst;
> > > if (padding || srclen == 1)
> > > return -1;
> > >
> > > val = base64_rev_tables[(u8)src[0]] << 12 | base64_rev_tables[(u8)src[1]] << 6;
> > > *bp++ = val >> 10;
> > > if (srclen == 1) {
> Obviously should be (srclen == 2)
> > > if (val & 0x800003ff)
> > > return -1;
> > > } else {
> > > val |= base64_rev_tables[(u8)src[2]];
> > > if (val & 0x80000003)
> > > return -1;
> > > *bp++ = val >> 2;
> > > }
> > > return bp - dst;
> > > }
> > >
> > > David
>
> David
>
> > >
> > > > case 2:
> > > > input1 = base64_rev_tables[(u8)src[0]];
> > > > input2 = base64_rev_tables[(u8)src[1]];
> > > > val = (input1 << 6) | input2; /* 12 bits */
> > > > if (unlikely((s32)val < 0 || val & 0x0F))
> > > > return -1;
> > > >
> > > > *bp++ = (u8)(val >> 4);
> > > > break;
> > > > case 3:
> > > > input1 = base64_rev_tables[(u8)src[0]];
> > > > input2 = base64_rev_tables[(u8)src[1]];
> > > > input3 = base64_rev_tables[(u8)src[2]];
> > > >
> > > > val = (input1 << 12) |
> > > > (input2 << 6) |
> > > > input3; /* 18 bits */
> > > > if (unlikely((s32)val < 0 || val & 0x03))
> > > > return -1;
> > > >
> > > > *bp++ = (u8)(val >> 10);
> > > > *bp++ = (u8)(val >> 2);
> > > > break;
> > > > default:
> > > > return -1;
> > > > }
> > > > }
> > > >
> > > > return bp - dst;
> > > > }
> > > > Based on KUnit testing, the performance results are as follows:
> > > > base64_performance_tests: [64B] decode run : 40ns
> > > > base64_performance_tests: [1KB] decode run : 463ns
> > > >
> > > > However, this approach introduces an issue. It uses 256 bytes of memory
> > > > on the stack for base64_rev_tables, which might not be ideal. Does anyone
> > > > have any thoughts or alternative suggestions to solve this issue, or is it
> > > > not really a concern?
> > > >
> > > > Best regards,
> > > > Guan-Chun
> > > >
> > > > > >
> > > > > > Best,
> > > > > > Caleb
> > > > >
> > >
>
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v3 2/6] lib/base64: Optimize base64_decode() with reverse lookup tables
2025-10-14 8:14 ` David Laight
2025-10-16 10:07 ` Guan-Chun Wu
@ 2025-10-27 13:12 ` Guan-Chun Wu
2025-10-27 14:18 ` David Laight
1 sibling, 1 reply; 31+ messages in thread
From: Guan-Chun Wu @ 2025-10-27 13:12 UTC (permalink / raw)
To: David Laight
Cc: Caleb Sander Mateos, akpm, axboe, ceph-devel, ebiggers, hch,
home7438072, idryomov, jaegeuk, kbusch, linux-fscrypt,
linux-kernel, linux-nvme, sagi, tytso, visitorckw, xiubli
On Tue, Oct 14, 2025 at 09:14:20AM +0100, David Laight wrote:
> On Mon, 13 Oct 2025 17:49:55 +0800
> Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
>
> > On Fri, Oct 10, 2025 at 10:51:38AM +0100, David Laight wrote:
> > > On Thu, 9 Oct 2025 20:25:17 +0800
> > > Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
> > >
> > > ...
> > > > As Eric mentioned, the decoder in fs/crypto/ needs to reject invalid input.
> > >
> > > (to avoid two different input buffers giving the same output)
> > >
> > > Which is annoyingly reasonable.
> > >
> > > > One possible solution I came up with is to first create a shared
> > > > base64_rev_common lookup table as the base for all Base64 variants.
> > > > Then, depending on the variant (e.g., BASE64_STD, BASE64_URLSAFE, etc.), we
> > > > can dynamically adjust the character mappings for position 62 and position 63
> > > > at runtime, based on the variant.
> > > >
> > > > Here are the changes to the code:
> > > >
> > > > static const s8 base64_rev_common[256] = {
> > > > [0 ... 255] = -1,
> > > > ['A'] = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
> > > > 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
> > > > ['a'] = 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
> > > > 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
> > > > ['0'] = 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
> > > > };
> > > >
> > > > static const struct {
> > > > char char62, char63;
> > > > } base64_symbols[] = {
> > > > [BASE64_STD] = { '+', '/' },
> > > > [BASE64_URLSAFE] = { '-', '_' },
> > > > [BASE64_IMAP] = { '+', ',' },
> > > > };
> > > >
> > > > int base64_decode(const char *src, int srclen, u8 *dst, bool padding, enum base64_variant variant)
> > > > {
> > > > u8 *bp = dst;
> > > > u8 pad_cnt = 0;
> > > > s8 input1, input2, input3, input4;
> > > > u32 val;
> > > > s8 base64_rev_tables[256];
> > > >
> > > > /* Validate the input length for padding */
> > > > if (unlikely(padding && (srclen & 0x03) != 0))
> > > > return -1;
> > >
> > > There is no need for an early check.
> > > Pick it up after the loop when 'srclen != 0'.
> > >
> >
> > I think the early check is still needed, since I'm removing the
> > padding '=' first.
> > This makes the handling logic consistent for both padded and unpadded
> > inputs, and avoids extra if conditions for padding inside the hot loop.
>
> The 'invalid input' check will detect the padding.
> Then you don't get an extra check if there is no padding (probably normal).
> I realised I didn't get it quite right - updated below.
>
> >
> > > >
> > > > memcpy(base64_rev_tables, base64_rev_common, sizeof(base64_rev_common));
> > >
> > > Ugg - having a memcpy() here is not a good idea.
> > > It really is better to have 3 arrays, but use a 'mostly common' initialiser.
> > > Perhaps:
> > > #define BASE64_REV_INIT(ch_62, ch_63) = { \
> > > [0 ... 255] = -1, \
> > > ['A'] = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, \
> > > 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, \
> > > ['a'] = 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, \
> > > 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, \
> > > ['0'] = 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, \
> > > [ch_62] = 62, [ch_63] = 63, \
> > > }
> > >
> > > static const s8 base64_rev_maps[][256] = {
> > > [BASE64_STD] = BASE64_REV_INIT('+', '/'),
> > > [BASE64_URLSAFE] = BASE64_REV_INIT('-', '_'),
> > > [BASE64_IMAP] = BASE64_REV_INIT('+', ',')
> > > };
> > >
> > > Then (after validating variant):
> > > const s8 *map = base64_rev_maps[variant];
> > >
> >
> > Got it. I'll switch to using three static tables with a common initializer
> > as you suggested.
> >
> > > >
> > > > if (variant < BASE64_STD || variant > BASE64_IMAP)
> > > > return -1;
> > > >
> > > > base64_rev_tables[base64_symbols[variant].char62] = 62;
> > > > base64_rev_tables[base64_symbols[variant].char63] = 63;
> > > >
> > > > while (padding && srclen > 0 && src[srclen - 1] == '=') {
> > > > pad_cnt++;
> > > > srclen--;
> > > > if (pad_cnt > 2)
> > > > return -1;
> > > > }
> > >
> > > I'm not sure I'd to that there.
> > > You are (in some sense) optimising for padding.
> > > From what I remember, "abcd" gives 24 bits, "abc=" 16 and "ab==" 8.
> > >
> > > >
> > > > while (srclen >= 4) {
> > > > /* Decode the next 4 characters */
> > > > input1 = base64_rev_tables[(u8)src[0]];
> > > > input2 = base64_rev_tables[(u8)src[1]];
> > > > input3 = base64_rev_tables[(u8)src[2]];
> > > > input4 = base64_rev_tables[(u8)src[3]];
> > >
> > > I'd be tempted to make src[] unsigned - probably be assigning the parameter
> > > to a local at the top of the function.
> > >
> > > Also you have input3 = ... src[2]...
> > > Perhaps they should be input[0..3] instead.
> > >
> >
> > OK, I'll make the changes.
> >
> > > >
> > > > val = (input1 << 18) |
> > > > (input2 << 12) |
> > > > (input3 << 6) |
> > > > input4;
> > >
> > > Four lines is excessive, C doesn't require the () and I'm not sure the
> > > compilers complain about << and |.
> > >
> >
> > OK, I'll make the changes.
> >
> > > >
> > > > if (unlikely((s32)val < 0))
> > > > return -1;
> > >
> > > Make 'val' signed - then you don't need the cast.
> ...
> > > Or, if you really want to use the code below the loop:
> > > if (!padding || src[3] != '=')
> > > return -1;
> > > padding = 0;
> > > srclen -= 1 + (src[2] == '=');
> > > break;
>
> That is missing a test...
> Change to:
> if (!padding || srclen != 4 || src[3] != '=')
> return -1;
> padding = 0;
> srclen = src[2] == '=' ? 2 : 3;
> break;
>
> The compiler will then optimise away the first checks after the
> loop because it knows they can't happen.
Hi David,
I noticed your suggested approach:
val_24 = t[b[0]] | t[b[1]] << 6 | t[b[2]] << 12 | t[b[3]] << 18;
Per the C11 draft, this can lead to undefined behavior.
"If E1 has a signed type and nonnegative value, and E1 × 2^E2 is
representable in the result type, then that is the resulting value;
otherwise, the behavior is undefined."
Therefore, left-shifting a negative signed value is undefined behavior.
Perhaps we could change the code as shown below. What do you think?
int base64_decode(const char *src, int srclen, u8 *dst, bool padding, enum base64_variant variant)
{
u8 *bp = dst;
s8 input[4];
u32 val;
const u8 *s = (const u8 *)src;
const s8 *base64_rev_table = base64_rev_maps[variant];
while (srclen >= 4) {
input[0] = base64_rev_table[s[0]];
input[1] = base64_rev_table[s[1]];
input[2] = base64_rev_table[s[2]];
input[3] = base64_rev_table[s[3]];
if (unlikely((input[0] | input[1] | input[2] | input[3]) < 0)) {
if (!padding || srclen != 4 || s[3] != '=')
return -1;
padding = 0;
srclen = s[2] == '=' ? 2 : 3;
break;
}
val = (u32)input[0] << 18 | (u32)input[1] << 12 |
(u32)input[2] << 6 | (u32)input[3];
*bp++ = val >> 16;
*bp++ = val >> 8;
*bp++ = val;
s += 4;
srclen -= 4;
}
if (likely(!srclen))
return bp - dst;
if (padding || srclen == 1)
return -1;
input[0] = base64_rev_table[s[0]];
input[1] = base64_rev_table[s[1]];
if (unlikely(input[0] < 0 || input[1] < 0))
return -1;
val = (u32)input[0] << 18 | (u32)input[1] << 12;
if (srclen == 2) {
if (unlikely(input[1] & 0x0f))
return -1;
*bp++ = val >> 16;
} else {
input[2] = base64_rev_table[s[2]];
if (unlikely(input[2] < 0 || (input[2] & 0x03)))
return -1;
val |= (u32)input[2] << 6;
*bp++ = val >> 16;
*bp++ = val >> 8;
}
return bp - dst;
}
Best regards,
Guan-Chun
> > >
> > >
> > > >
> > > > *bp++ = (u8)(val >> 16);
> > > > *bp++ = (u8)(val >> 8);
> > > > *bp++ = (u8)val;
> > >
> > > You don't need those casts.
> > >
> >
> > OK, I'll make the changes.
> >
> > > >
> > > > src += 4;
> > > > srclen -= 4;
> > > > }
> > > >
> > > > /* Handle leftover characters when padding is not used */
> > >
> > > You are coming here with padding.
> > > I'm not sure what should happen without padding.
> > > For a multi-line file decode I suspect the characters need adding to
> > > the start of the next line (ie lines aren't required to contain
> > > multiples of 4 characters - even though they almost always will).
> > >
> >
> > Ah, my mistake. I forgot to remove that comment.
> > Based on my observation, base64_decode() should process the entire input
> > buffer in a single call, so I believe it does not need to handle
> > multi-line input.
>
> I was thinking of the the case where it is processing the output of
> something like base64encode.
> The caller will have separated out the lines, but I don't know whether
> every line has to contain a multiple of 4 characters - or whether the
> lines can be arbitrarily split after being encoded (I know that won't
> normally happen - but you never know).
>
> >
> > Best regards,
> > Guan-Chun
> >
> > > > if (srclen > 0) {
> > > > switch (srclen) {
> > >
> > > You don't need an 'if' and a 'switch'.
> > > srclen is likely to be zero, but perhaps write as:
> > > if (likely(!srclen))
> > > return bp - dst;
> > > if (padding || srclen == 1)
> > > return -1;
> > >
> > > val = base64_rev_tables[(u8)src[0]] << 12 | base64_rev_tables[(u8)src[1]] << 6;
> > > *bp++ = val >> 10;
> > > if (srclen == 1) {
> Obviously should be (srclen == 2)
> > > if (val & 0x800003ff)
> > > return -1;
> > > } else {
> > > val |= base64_rev_tables[(u8)src[2]];
> > > if (val & 0x80000003)
> > > return -1;
> > > *bp++ = val >> 2;
> > > }
> > > return bp - dst;
> > > }
> > >
> > > David
>
> David
>
> > >
> > > > case 2:
> > > > input1 = base64_rev_tables[(u8)src[0]];
> > > > input2 = base64_rev_tables[(u8)src[1]];
> > > > val = (input1 << 6) | input2; /* 12 bits */
> > > > if (unlikely((s32)val < 0 || val & 0x0F))
> > > > return -1;
> > > >
> > > > *bp++ = (u8)(val >> 4);
> > > > break;
> > > > case 3:
> > > > input1 = base64_rev_tables[(u8)src[0]];
> > > > input2 = base64_rev_tables[(u8)src[1]];
> > > > input3 = base64_rev_tables[(u8)src[2]];
> > > >
> > > > val = (input1 << 12) |
> > > > (input2 << 6) |
> > > > input3; /* 18 bits */
> > > > if (unlikely((s32)val < 0 || val & 0x03))
> > > > return -1;
> > > >
> > > > *bp++ = (u8)(val >> 10);
> > > > *bp++ = (u8)(val >> 2);
> > > > break;
> > > > default:
> > > > return -1;
> > > > }
> > > > }
> > > >
> > > > return bp - dst;
> > > > }
> > > > Based on KUnit testing, the performance results are as follows:
> > > > base64_performance_tests: [64B] decode run : 40ns
> > > > base64_performance_tests: [1KB] decode run : 463ns
> > > >
> > > > However, this approach introduces an issue. It uses 256 bytes of memory
> > > > on the stack for base64_rev_tables, which might not be ideal. Does anyone
> > > > have any thoughts or alternative suggestions to solve this issue, or is it
> > > > not really a concern?
> > > >
> > > > Best regards,
> > > > Guan-Chun
> > > >
> > > > > >
> > > > > > Best,
> > > > > > Caleb
> > > > >
> > >
>
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v3 2/6] lib/base64: Optimize base64_decode() with reverse lookup tables
2025-10-27 13:12 ` Guan-Chun Wu
@ 2025-10-27 14:18 ` David Laight
2025-10-28 6:58 ` Guan-Chun Wu
0 siblings, 1 reply; 31+ messages in thread
From: David Laight @ 2025-10-27 14:18 UTC (permalink / raw)
To: Guan-Chun Wu
Cc: Caleb Sander Mateos, akpm, axboe, ceph-devel, ebiggers, hch,
home7438072, idryomov, jaegeuk, kbusch, linux-fscrypt,
linux-kernel, linux-nvme, sagi, tytso, visitorckw, xiubli
On Mon, 27 Oct 2025 21:12:00 +0800
Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
...
> Hi David,
>
> I noticed your suggested approach:
> val_24 = t[b[0]] | t[b[1]] << 6 | t[b[2]] << 12 | t[b[3]] << 18;
> Per the C11 draft, this can lead to undefined behavior.
> "If E1 has a signed type and nonnegative value, and E1 × 2^E2 is
> representable in the result type, then that is the resulting value;
> otherwise, the behavior is undefined."
> Therefore, left-shifting a negative signed value is undefined behavior.
Don't worry about that, there are all sorts of places in the kernel
where shifts of negative values are technically undefined.
They are undefined because you get different values for 1's compliment
and 'sign overpunch' signed integers.
Even for 2's compliment C doesn't require a 'sign bit replicating'
right shift.
(And I suspect both gcc and clang only support 2's compliment.)
I don't think even clang is stupid enough to silently not emit any
instructions for shifts of negative values.
It is another place where it should be 'implementation defined' rather
than 'undefined' behaviour.
> Perhaps we could change the code as shown below. What do you think?
If you are really worried, change the '<< n' to '* (1 << n)' which
obfuscates the code.
The compiler will convert it straight back to a simple shift.
I bet that if you look hard enough even 'a | b' is undefined if
either is negative.
David
David
^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [PATCH v3 2/6] lib/base64: Optimize base64_decode() with reverse lookup tables
2025-10-27 14:18 ` David Laight
@ 2025-10-28 6:58 ` Guan-Chun Wu
0 siblings, 0 replies; 31+ messages in thread
From: Guan-Chun Wu @ 2025-10-28 6:58 UTC (permalink / raw)
To: David Laight
Cc: Caleb Sander Mateos, akpm, axboe, ceph-devel, ebiggers, hch,
home7438072, idryomov, jaegeuk, kbusch, linux-fscrypt,
linux-kernel, linux-nvme, sagi, tytso, visitorckw, xiubli
On Mon, Oct 27, 2025 at 02:18:02PM +0000, David Laight wrote:
> On Mon, 27 Oct 2025 21:12:00 +0800
> Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote:
>
> ...
> > Hi David,
> >
> > I noticed your suggested approach:
> > val_24 = t[b[0]] | t[b[1]] << 6 | t[b[2]] << 12 | t[b[3]] << 18;
> > Per the C11 draft, this can lead to undefined behavior.
> > "If E1 has a signed type and nonnegative value, and E1 × 2^E2 is
> > representable in the result type, then that is the resulting value;
> > otherwise, the behavior is undefined."
> > Therefore, left-shifting a negative signed value is undefined behavior.
>
> Don't worry about that, there are all sorts of places in the kernel
> where shifts of negative values are technically undefined.
>
> They are undefined because you get different values for 1's compliment
> and 'sign overpunch' signed integers.
> Even for 2's compliment C doesn't require a 'sign bit replicating'
> right shift.
> (And I suspect both gcc and clang only support 2's compliment.)
>
> I don't think even clang is stupid enough to silently not emit any
> instructions for shifts of negative values.
> It is another place where it should be 'implementation defined' rather
> than 'undefined' behaviour.
>
Hi David,
Thanks for your explanation. I'll proceed with the modification according
to your original suggestion.
Best regards,
Guan-Chun
> > Perhaps we could change the code as shown below. What do you think?
>
> If you are really worried, change the '<< n' to '* (1 << n)' which
> obfuscates the code.
> The compiler will convert it straight back to a simple shift.
>
> I bet that if you look hard enough even 'a | b' is undefined if
> either is negative.
>
> David
>
>
>
> David
^ permalink raw reply [flat|nested] 31+ messages in thread
end of thread, other threads:[~2025-10-28 6:58 UTC | newest]
Thread overview: 31+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-09-26 6:52 [PATCH v3 0/6] lib/base64: add generic encoder/decoder, migrate users Guan-Chun Wu
2025-09-26 6:55 ` [PATCH v3 1/6] lib/base64: Add support for multiple variants Guan-Chun Wu
2025-09-30 23:56 ` Caleb Sander Mateos
2025-10-01 14:09 ` Guan-Chun Wu
2025-09-26 6:55 ` [PATCH v3 2/6] lib/base64: Optimize base64_decode() with reverse lookup tables Guan-Chun Wu
2025-09-26 23:33 ` Caleb Sander Mateos
2025-09-28 6:37 ` Kuan-Wei Chiu
2025-10-01 10:18 ` Guan-Chun Wu
2025-10-01 16:20 ` Caleb Sander Mateos
2025-10-05 17:18 ` David Laight
2025-10-07 8:28 ` Guan-Chun Wu
2025-10-07 14:57 ` Caleb Sander Mateos
2025-10-07 17:11 ` Eric Biggers
2025-10-07 18:23 ` David Laight
2025-10-09 12:25 ` Guan-Chun Wu
2025-10-10 9:51 ` David Laight
2025-10-13 9:49 ` Guan-Chun Wu
2025-10-14 8:14 ` David Laight
2025-10-16 10:07 ` Guan-Chun Wu
2025-10-27 13:12 ` Guan-Chun Wu
2025-10-27 14:18 ` David Laight
2025-10-28 6:58 ` Guan-Chun Wu
2025-09-28 18:57 ` David Laight
2025-09-26 6:56 ` [PATCH v3 3/6] lib/base64: rework encode/decode for speed and stricter validation Guan-Chun Wu
2025-10-01 0:11 ` Caleb Sander Mateos
2025-10-01 9:39 ` Guan-Chun Wu
2025-10-06 20:52 ` David Laight
2025-10-07 8:34 ` Guan-Chun Wu
2025-09-26 6:56 ` [PATCH v3 4/6] lib: add KUnit tests for base64 encoding/decoding Guan-Chun Wu
2025-09-26 6:56 ` [PATCH v3 5/6] fscrypt: replace local base64url helpers with lib/base64 Guan-Chun Wu
2025-09-26 6:57 ` [PATCH v3 6/6] ceph: replace local base64 " Guan-Chun Wu
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox