* [PATCH v5 0/6] lib/base64: add generic encoder/decoder, migrate users
@ 2025-11-14 5:58 Guan-Chun Wu
2025-11-14 6:00 ` [PATCH v5 1/6] lib/base64: Add support for multiple variants Guan-Chun Wu
` (5 more replies)
0 siblings, 6 replies; 17+ messages in thread
From: Guan-Chun Wu @ 2025-11-14 5:58 UTC (permalink / raw)
To: akpm, ebiggers, tytso, jaegeuk, xiubli, idryomov, kbusch, axboe,
hch, sagi
Cc: visitorckw, 409411716, home7438072, andriy.shevchenko,
david.laight.linux, 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 ~43-52x.
Thanks,
Guan-Chun Wu
Link: https://lore.kernel.org/lkml/20251029101725.541758-1-409411716@gms.tku.edu.tw/
---
v4 -> v5:
- lib/base64: Fixed initializer-overrides compiler error by replacing designated
initializer approach with macro-based constant expressions.
---
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 | 188 +++++++++++++++++-------
lib/tests/Makefile | 1 +
lib/tests/base64_kunit.c | 294 +++++++++++++++++++++++++++++++++++++
11 files changed, 472 insertions(+), 206 deletions(-)
create mode 100644 lib/tests/base64_kunit.c
--
2.34.1
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH v5 1/6] lib/base64: Add support for multiple variants 2025-11-14 5:58 [PATCH v5 0/6] lib/base64: add generic encoder/decoder, migrate users Guan-Chun Wu @ 2025-11-14 6:00 ` Guan-Chun Wu 2025-11-14 9:13 ` David Laight 2025-11-14 6:01 ` [PATCH v5 2/6] lib/base64: Optimize base64_decode() with reverse lookup tables Guan-Chun Wu ` (4 subsequent siblings) 5 siblings, 1 reply; 17+ messages in thread From: Guan-Chun Wu @ 2025-11-14 6:00 UTC (permalink / raw) To: 409411716 Cc: akpm, andriy.shevchenko, axboe, ceph-devel, david.laight.linux, 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 | 62 ++++++++++++++++++++++---------------- 3 files changed, 46 insertions(+), 30 deletions(-) diff --git a/drivers/nvme/common/auth.c b/drivers/nvme/common/auth.c index 1f51fbebd9fa..e07e7d4bf8b6 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 660d4cb1ef31..a2c6c9222da3 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 b736a7a431c5..a7c20a8e8e98 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]; @@ -48,44 +53,49 @@ int base64_encode(const u8 *src, int srclen, char *dst) *cp++ = base64_table[(ac << (6 - bits)) & 0x3f]; bits -= 6; } - while (bits < 0) { - *cp++ = '='; - bits += 2; + if (padding) { + while (bits < 0) { + *cp++ = '='; + bits += 2; + } } return cp - 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]); - - if (src[i] == '=') { - ac = (ac << 6); - bits += 6; - if (bits >= 8) - bits -= 8; - continue; + if (padding) { + if (src[i] == '=') { + ac = (ac << 6); + bits += 6; + if (bits >= 8) + bits -= 8; + continue; + } } if (p == NULL || src[i] == 0) return -1; -- 2.34.1 ^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: [PATCH v5 1/6] lib/base64: Add support for multiple variants 2025-11-14 6:00 ` [PATCH v5 1/6] lib/base64: Add support for multiple variants Guan-Chun Wu @ 2025-11-14 9:13 ` David Laight 0 siblings, 0 replies; 17+ messages in thread From: David Laight @ 2025-11-14 9:13 UTC (permalink / raw) To: Guan-Chun Wu Cc: akpm, andriy.shevchenko, axboe, ceph-devel, ebiggers, hch, home7438072, idryomov, jaegeuk, kbusch, linux-fscrypt, linux-kernel, linux-nvme, sagi, tytso, visitorckw, xiubli On Fri, 14 Nov 2025 14:00:45 +0800 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> Reviewed-by: David Laight <david.laight.linux@gmail.com> > --- > drivers/nvme/common/auth.c | 4 +-- > include/linux/base64.h | 10 ++++-- > lib/base64.c | 62 ++++++++++++++++++++++---------------- > 3 files changed, 46 insertions(+), 30 deletions(-) > > diff --git a/drivers/nvme/common/auth.c b/drivers/nvme/common/auth.c > index 1f51fbebd9fa..e07e7d4bf8b6 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 660d4cb1ef31..a2c6c9222da3 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 b736a7a431c5..a7c20a8e8e98 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]; > @@ -48,44 +53,49 @@ int base64_encode(const u8 *src, int srclen, char *dst) > *cp++ = base64_table[(ac << (6 - bits)) & 0x3f]; > bits -= 6; > } > - while (bits < 0) { > - *cp++ = '='; > - bits += 2; > + if (padding) { > + while (bits < 0) { > + *cp++ = '='; > + bits += 2; > + } > } > return cp - 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]); > - > - if (src[i] == '=') { > - ac = (ac << 6); > - bits += 6; > - if (bits >= 8) > - bits -= 8; > - continue; > + if (padding) { > + if (src[i] == '=') { > + ac = (ac << 6); > + bits += 6; > + if (bits >= 8) > + bits -= 8; > + continue; > + } > } > if (p == NULL || src[i] == 0) > return -1; ^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v5 2/6] lib/base64: Optimize base64_decode() with reverse lookup tables 2025-11-14 5:58 [PATCH v5 0/6] lib/base64: add generic encoder/decoder, migrate users Guan-Chun Wu 2025-11-14 6:00 ` [PATCH v5 1/6] lib/base64: Add support for multiple variants Guan-Chun Wu @ 2025-11-14 6:01 ` Guan-Chun Wu 2025-11-14 9:14 ` David Laight 2025-11-14 6:01 ` [PATCH v5 3/6] lib/base64: rework encode/decode for speed and stricter validation Guan-Chun Wu ` (3 subsequent siblings) 5 siblings, 1 reply; 17+ messages in thread From: Guan-Chun Wu @ 2025-11-14 6:01 UTC (permalink / raw) To: 409411716 Cc: akpm, andriy.shevchenko, axboe, ceph-devel, david.laight.linux, 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 -> ~80ns (~19.1x) 1KB ~27726ns -> ~1239ns (~22.4x) 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 | 51 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/lib/base64.c b/lib/base64.c index a7c20a8e8e98..9d1074bb821c 100644 --- a/lib/base64.c +++ b/lib/base64.c @@ -21,6 +21,49 @@ static const char base64_tables[][65] = { [BASE64_IMAP] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,", }; +/** + * Initialize the base64 reverse mapping for a single character + * This macro maps a character to its corresponding base64 value, + * returning -1 if the character is invalid. + * char 'A'-'Z' maps to 0-25, 'a'-'z' maps to 26-51, '0'-'9' maps to 52-61, + * ch_62 maps to 62, ch_63 maps to 63, and other characters return -1 + */ +#define INIT_1(v, ch_62, ch_63) \ + [v] = (v) >= 'A' && (v) <= 'Z' ? (v) - 'A' \ + : (v) >= 'a' && (v) <= 'z' ? (v) - 'a' + 26 \ + : (v) >= '0' && (v) <= '9' ? (v) - '0' + 52 \ + : (v) == (ch_62) ? 62 : (v) == (ch_63) ? 63 : -1 +/** + * Recursive macros to generate multiple Base64 reverse mapping table entries. + * Each macro generates a sequence of entries in the lookup table: + * INIT_2 generates 2 entries, INIT_4 generates 4, INIT_8 generates 8, and so on up to INIT_32. + */ +#define INIT_2(v, ...) INIT_1(v, __VA_ARGS__), INIT_1((v) + 1, __VA_ARGS__) +#define INIT_4(v, ...) INIT_2(v, __VA_ARGS__), INIT_2((v) + 2, __VA_ARGS__) +#define INIT_8(v, ...) INIT_4(v, __VA_ARGS__), INIT_4((v) + 4, __VA_ARGS__) +#define INIT_16(v, ...) INIT_8(v, __VA_ARGS__), INIT_8((v) + 8, __VA_ARGS__) +#define INIT_32(v, ...) INIT_16(v, __VA_ARGS__), INIT_16((v) + 16, __VA_ARGS__) + +#define BASE64_REV_INIT(ch_62, ch_63) { \ + [0 ... 0x1f] = -1, \ + INIT_32(0x20, ch_62, ch_63), \ + INIT_32(0x40, ch_62, ch_63), \ + INIT_32(0x60, ch_62, ch_63), \ + [0x80 ... 0xff] = -1 } + +static const s8 base64_rev_maps[][256] = { + [BASE64_STD] = BASE64_REV_INIT('+', '/'), + [BASE64_URLSAFE] = BASE64_REV_INIT('-', '_'), + [BASE64_IMAP] = BASE64_REV_INIT('+', ',') +}; + +#undef BASE64_REV_INIT +#undef INIT_32 +#undef INIT_16 +#undef INIT_8 +#undef INIT_4 +#undef INIT_2 +#undef INIT_1 /** * base64_encode() - Base64-encode some binary data * @src: the binary data to encode @@ -84,10 +127,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 (padding) { if (src[i] == '=') { ac = (ac << 6); @@ -97,9 +139,10 @@ int base64_decode(const char *src, int srclen, u8 *dst, bool padding, enum base6 continue; } } - if (p == NULL || src[i] == 0) + ch = base64_rev_maps[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] 17+ messages in thread
* Re: [PATCH v5 2/6] lib/base64: Optimize base64_decode() with reverse lookup tables 2025-11-14 6:01 ` [PATCH v5 2/6] lib/base64: Optimize base64_decode() with reverse lookup tables Guan-Chun Wu @ 2025-11-14 9:14 ` David Laight 0 siblings, 0 replies; 17+ messages in thread From: David Laight @ 2025-11-14 9:14 UTC (permalink / raw) To: Guan-Chun Wu Cc: akpm, andriy.shevchenko, axboe, ceph-devel, ebiggers, hch, home7438072, idryomov, jaegeuk, kbusch, linux-fscrypt, linux-kernel, linux-nvme, sagi, tytso, visitorckw, xiubli On Fri, 14 Nov 2025 14:01:07 +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 -> ~80ns (~19.1x) > 1KB ~27726ns -> ~1239ns (~22.4x) > > 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> Reviewed-by: David Laight <david.laight.linux@gmail.com> > --- > lib/base64.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++---- > 1 file changed, 47 insertions(+), 4 deletions(-) > > diff --git a/lib/base64.c b/lib/base64.c > index a7c20a8e8e98..9d1074bb821c 100644 > --- a/lib/base64.c > +++ b/lib/base64.c > @@ -21,6 +21,49 @@ static const char base64_tables[][65] = { > [BASE64_IMAP] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,", > }; > > +/** > + * Initialize the base64 reverse mapping for a single character > + * This macro maps a character to its corresponding base64 value, > + * returning -1 if the character is invalid. > + * char 'A'-'Z' maps to 0-25, 'a'-'z' maps to 26-51, '0'-'9' maps to 52-61, > + * ch_62 maps to 62, ch_63 maps to 63, and other characters return -1 > + */ > +#define INIT_1(v, ch_62, ch_63) \ > + [v] = (v) >= 'A' && (v) <= 'Z' ? (v) - 'A' \ > + : (v) >= 'a' && (v) <= 'z' ? (v) - 'a' + 26 \ > + : (v) >= '0' && (v) <= '9' ? (v) - '0' + 52 \ > + : (v) == (ch_62) ? 62 : (v) == (ch_63) ? 63 : -1 > +/** > + * Recursive macros to generate multiple Base64 reverse mapping table entries. > + * Each macro generates a sequence of entries in the lookup table: > + * INIT_2 generates 2 entries, INIT_4 generates 4, INIT_8 generates 8, and so on up to INIT_32. > + */ > +#define INIT_2(v, ...) INIT_1(v, __VA_ARGS__), INIT_1((v) + 1, __VA_ARGS__) > +#define INIT_4(v, ...) INIT_2(v, __VA_ARGS__), INIT_2((v) + 2, __VA_ARGS__) > +#define INIT_8(v, ...) INIT_4(v, __VA_ARGS__), INIT_4((v) + 4, __VA_ARGS__) > +#define INIT_16(v, ...) INIT_8(v, __VA_ARGS__), INIT_8((v) + 8, __VA_ARGS__) > +#define INIT_32(v, ...) INIT_16(v, __VA_ARGS__), INIT_16((v) + 16, __VA_ARGS__) > + > +#define BASE64_REV_INIT(ch_62, ch_63) { \ > + [0 ... 0x1f] = -1, \ > + INIT_32(0x20, ch_62, ch_63), \ > + INIT_32(0x40, ch_62, ch_63), \ > + INIT_32(0x60, ch_62, ch_63), \ > + [0x80 ... 0xff] = -1 } > + > +static const s8 base64_rev_maps[][256] = { > + [BASE64_STD] = BASE64_REV_INIT('+', '/'), > + [BASE64_URLSAFE] = BASE64_REV_INIT('-', '_'), > + [BASE64_IMAP] = BASE64_REV_INIT('+', ',') > +}; > + > +#undef BASE64_REV_INIT > +#undef INIT_32 > +#undef INIT_16 > +#undef INIT_8 > +#undef INIT_4 > +#undef INIT_2 > +#undef INIT_1 > /** > * base64_encode() - Base64-encode some binary data > * @src: the binary data to encode > @@ -84,10 +127,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 (padding) { > if (src[i] == '=') { > ac = (ac << 6); > @@ -97,9 +139,10 @@ int base64_decode(const char *src, int srclen, u8 *dst, bool padding, enum base6 > continue; > } > } > - if (p == NULL || src[i] == 0) > + ch = base64_rev_maps[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] 17+ messages in thread
* [PATCH v5 3/6] lib/base64: rework encode/decode for speed and stricter validation 2025-11-14 5:58 [PATCH v5 0/6] lib/base64: add generic encoder/decoder, migrate users Guan-Chun Wu 2025-11-14 6:00 ` [PATCH v5 1/6] lib/base64: Add support for multiple variants Guan-Chun Wu 2025-11-14 6:01 ` [PATCH v5 2/6] lib/base64: Optimize base64_decode() with reverse lookup tables Guan-Chun Wu @ 2025-11-14 6:01 ` Guan-Chun Wu 2025-11-14 9:18 ` David Laight 2025-11-14 6:01 ` [PATCH v5 4/6] lib: add KUnit tests for base64 encoding/decoding Guan-Chun Wu ` (2 subsequent siblings) 5 siblings, 1 reply; 17+ messages in thread From: Guan-Chun Wu @ 2025-11-14 6:01 UTC (permalink / raw) To: 409411716 Cc: akpm, andriy.shevchenko, axboe, ceph-devel, david.laight.linux, 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 -> ~35ns (~43.7x) 1KB ~27726ns -> ~530ns (~52.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 | 109 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 68 insertions(+), 41 deletions(-) diff --git a/lib/base64.c b/lib/base64.c index 9d1074bb821c..1a6d8fe37eda 100644 --- a/lib/base64.c +++ b/lib/base64.c @@ -79,28 +79,38 @@ static const s8 base64_rev_maps[][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; } - if (padding) { - while (bits < 0) { + + 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++ = '='; - bits += 2; } + break; } return cp - dst; } @@ -116,41 +126,58 @@ 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; + s8 input[4]; + s32 val; + const u8 *s = (const u8 *)src; + const s8 *base64_rev_tables = base64_rev_maps[variant]; - for (i = 0; i < srclen; i++) { - if (padding) { - if (src[i] == '=') { - ac = (ac << 6); - bits += 6; - if (bits >= 8) - bits -= 8; - continue; - } - } - ch = base64_rev_maps[variant][(u8)src[i]]; - if (ch == -1) - return -1; - ac = (ac << 6) | ch; - bits += 6; - if (bits >= 8) { - bits -= 8; - *bp++ = (u8)(ac >> bits); + while (srclen >= 4) { + input[0] = base64_rev_tables[s[0]]; + input[1] = base64_rev_tables[s[1]]; + input[2] = base64_rev_tables[s[2]]; + input[3] = base64_rev_tables[s[3]]; + + val = input[0] << 18 | input[1] << 12 | input[2] << 6 | input[3]; + + if (unlikely(val < 0)) { + if (!padding || srclen != 4 || s[3] != '=') + return -1; + padding = 0; + srclen = s[2] == '=' ? 2 : 3; + break; } + + *bp++ = val >> 16; + *bp++ = val >> 8; + *bp++ = val; + + s += 4; + srclen -= 4; } - if (ac & ((1 << bits) - 1)) + + if (likely(!srclen)) + return bp - dst; + if (padding || srclen == 1) return -1; + + val = (base64_rev_tables[s[0]] << 12) | (base64_rev_tables[s[1]] << 6); + *bp++ = val >> 10; + + if (srclen == 2) { + if (val & 0x800003ff) + return -1; + } else { + val |= base64_rev_tables[s[2]]; + if (val & 0x80000003) + return -1; + *bp++ = val >> 2; + } return bp - dst; } EXPORT_SYMBOL_GPL(base64_decode); -- 2.34.1 ^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: [PATCH v5 3/6] lib/base64: rework encode/decode for speed and stricter validation 2025-11-14 6:01 ` [PATCH v5 3/6] lib/base64: rework encode/decode for speed and stricter validation Guan-Chun Wu @ 2025-11-14 9:18 ` David Laight 2025-11-16 10:28 ` Guan-Chun Wu 0 siblings, 1 reply; 17+ messages in thread From: David Laight @ 2025-11-14 9:18 UTC (permalink / raw) To: Guan-Chun Wu Cc: akpm, andriy.shevchenko, axboe, ceph-devel, ebiggers, hch, home7438072, idryomov, jaegeuk, kbusch, linux-fscrypt, linux-kernel, linux-nvme, sagi, tytso, visitorckw, xiubli On Fri, 14 Nov 2025 14:01:32 +0800 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 -> ~35ns (~43.7x) > 1KB ~27726ns -> ~530ns (~52.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> Reviewed-by: David Laight <david.laight.linux@gmail.com> But see minor nit below. > --- > lib/base64.c | 109 ++++++++++++++++++++++++++++++++------------------- > 1 file changed, 68 insertions(+), 41 deletions(-) > > diff --git a/lib/base64.c b/lib/base64.c > index 9d1074bb821c..1a6d8fe37eda 100644 > --- a/lib/base64.c > +++ b/lib/base64.c > @@ -79,28 +79,38 @@ static const s8 base64_rev_maps[][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]; There is no need for the (u32) casts. All char/short values are promoted to 'int' prior to any maths. > + *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; > } > - if (padding) { > - while (bits < 0) { > + > + 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++ = '='; > - bits += 2; > } > + break; > } > return cp - dst; > } > @@ -116,41 +126,58 @@ 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; > + s8 input[4]; > + s32 val; > + const u8 *s = (const u8 *)src; > + const s8 *base64_rev_tables = base64_rev_maps[variant]; > > - for (i = 0; i < srclen; i++) { > - if (padding) { > - if (src[i] == '=') { > - ac = (ac << 6); > - bits += 6; > - if (bits >= 8) > - bits -= 8; > - continue; > - } > - } > - ch = base64_rev_maps[variant][(u8)src[i]]; > - if (ch == -1) > - return -1; > - ac = (ac << 6) | ch; > - bits += 6; > - if (bits >= 8) { > - bits -= 8; > - *bp++ = (u8)(ac >> bits); > + while (srclen >= 4) { > + input[0] = base64_rev_tables[s[0]]; > + input[1] = base64_rev_tables[s[1]]; > + input[2] = base64_rev_tables[s[2]]; > + input[3] = base64_rev_tables[s[3]]; > + > + val = input[0] << 18 | input[1] << 12 | input[2] << 6 | input[3]; > + > + if (unlikely(val < 0)) { > + if (!padding || srclen != 4 || s[3] != '=') > + return -1; > + padding = 0; > + srclen = s[2] == '=' ? 2 : 3; > + break; > } > + > + *bp++ = val >> 16; > + *bp++ = val >> 8; > + *bp++ = val; > + > + s += 4; > + srclen -= 4; > } > - if (ac & ((1 << bits) - 1)) > + > + if (likely(!srclen)) > + return bp - dst; > + if (padding || srclen == 1) > return -1; > + > + val = (base64_rev_tables[s[0]] << 12) | (base64_rev_tables[s[1]] << 6); > + *bp++ = val >> 10; > + > + if (srclen == 2) { > + if (val & 0x800003ff) > + return -1; > + } else { > + val |= base64_rev_tables[s[2]]; > + if (val & 0x80000003) > + return -1; > + *bp++ = val >> 2; > + } > return bp - dst; > } > EXPORT_SYMBOL_GPL(base64_decode); ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v5 3/6] lib/base64: rework encode/decode for speed and stricter validation 2025-11-14 9:18 ` David Laight @ 2025-11-16 10:28 ` Guan-Chun Wu 2025-11-17 17:46 ` Andrew Morton 0 siblings, 1 reply; 17+ messages in thread From: Guan-Chun Wu @ 2025-11-16 10:28 UTC (permalink / raw) To: David Laight Cc: akpm, andriy.shevchenko, axboe, ceph-devel, ebiggers, hch, home7438072, idryomov, jaegeuk, kbusch, linux-fscrypt, linux-kernel, linux-nvme, sagi, tytso, visitorckw, xiubli On Fri, Nov 14, 2025 at 09:18:30AM +0000, David Laight wrote: > On Fri, 14 Nov 2025 14:01:32 +0800 > 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 -> ~35ns (~43.7x) > > 1KB ~27726ns -> ~530ns (~52.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> > > Reviewed-by: David Laight <david.laight.linux@gmail.com> > > But see minor nit below. Hi David, Thanks for the review and for pointing this out. Andrew, would it be possible for you to fold this small change (removing the redundant casts) directly when updating the patch? If that’s not convenient, I can resend an updated version of the series instead. Best regards, Guan-Chun > > --- > > lib/base64.c | 109 ++++++++++++++++++++++++++++++++------------------- > > 1 file changed, 68 insertions(+), 41 deletions(-) > > > > diff --git a/lib/base64.c b/lib/base64.c > > index 9d1074bb821c..1a6d8fe37eda 100644 > > --- a/lib/base64.c > > +++ b/lib/base64.c > > @@ -79,28 +79,38 @@ static const s8 base64_rev_maps[][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]; > > There is no need for the (u32) casts. > All char/short values are promoted to 'int' prior to any maths. > > > + *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; > > } > > - if (padding) { > > - while (bits < 0) { > > + > > + 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++ = '='; > > - bits += 2; > > } > > + break; > > } > > return cp - dst; > > } > > @@ -116,41 +126,58 @@ 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; > > + s8 input[4]; > > + s32 val; > > + const u8 *s = (const u8 *)src; > > + const s8 *base64_rev_tables = base64_rev_maps[variant]; > > > > - for (i = 0; i < srclen; i++) { > > - if (padding) { > > - if (src[i] == '=') { > > - ac = (ac << 6); > > - bits += 6; > > - if (bits >= 8) > > - bits -= 8; > > - continue; > > - } > > - } > > - ch = base64_rev_maps[variant][(u8)src[i]]; > > - if (ch == -1) > > - return -1; > > - ac = (ac << 6) | ch; > > - bits += 6; > > - if (bits >= 8) { > > - bits -= 8; > > - *bp++ = (u8)(ac >> bits); > > + while (srclen >= 4) { > > + input[0] = base64_rev_tables[s[0]]; > > + input[1] = base64_rev_tables[s[1]]; > > + input[2] = base64_rev_tables[s[2]]; > > + input[3] = base64_rev_tables[s[3]]; > > + > > + val = input[0] << 18 | input[1] << 12 | input[2] << 6 | input[3]; > > + > > + if (unlikely(val < 0)) { > > + if (!padding || srclen != 4 || s[3] != '=') > > + return -1; > > + padding = 0; > > + srclen = s[2] == '=' ? 2 : 3; > > + break; > > } > > + > > + *bp++ = val >> 16; > > + *bp++ = val >> 8; > > + *bp++ = val; > > + > > + s += 4; > > + srclen -= 4; > > } > > - if (ac & ((1 << bits) - 1)) > > + > > + if (likely(!srclen)) > > + return bp - dst; > > + if (padding || srclen == 1) > > return -1; > > + > > + val = (base64_rev_tables[s[0]] << 12) | (base64_rev_tables[s[1]] << 6); > > + *bp++ = val >> 10; > > + > > + if (srclen == 2) { > > + if (val & 0x800003ff) > > + return -1; > > + } else { > > + val |= base64_rev_tables[s[2]]; > > + if (val & 0x80000003) > > + return -1; > > + *bp++ = val >> 2; > > + } > > return bp - dst; > > } > > EXPORT_SYMBOL_GPL(base64_decode); > ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v5 3/6] lib/base64: rework encode/decode for speed and stricter validation 2025-11-16 10:28 ` Guan-Chun Wu @ 2025-11-17 17:46 ` Andrew Morton 2025-11-18 10:38 ` Andy Shevchenko 0 siblings, 1 reply; 17+ messages in thread From: Andrew Morton @ 2025-11-17 17:46 UTC (permalink / raw) To: Guan-Chun Wu Cc: David Laight, andriy.shevchenko, axboe, ceph-devel, ebiggers, hch, home7438072, idryomov, jaegeuk, kbusch, linux-fscrypt, linux-kernel, linux-nvme, sagi, tytso, visitorckw, xiubli On Sun, 16 Nov 2025 18:28:49 +0800 Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote: > > Reviewed-by: David Laight <david.laight.linux@gmail.com> > > > > But see minor nit below. > > Hi David, > > Thanks for the review and for pointing this out. > > Andrew, would it be possible for you to fold this small change > (removing the redundant casts) directly when updating the patch? > If that’s not convenient, I can resend an updated version of the > series instead. Sure, I added this: --- a/lib/base64.c~lib-base64-rework-encode-decode-for-speed-and-stricter-validation-fix +++ a/lib/base64.c @@ -83,7 +83,7 @@ int base64_encode(const u8 *src, int src const char *base64_table = base64_tables[variant]; while (srclen >= 3) { - ac = (u32)src[0] << 16 | (u32)src[1] << 8 | (u32)src[2]; + ac = src[0] << 16 | src[1] << 8 | src[2]; *cp++ = base64_table[ac >> 18]; *cp++ = base64_table[(ac >> 12) & 0x3f]; *cp++ = base64_table[(ac >> 6) & 0x3f]; @@ -95,7 +95,7 @@ int base64_encode(const u8 *src, int src switch (srclen) { case 2: - ac = (u32)src[0] << 16 | (u32)src[1] << 8; + ac = src[0] << 16 | src[1] << 8; *cp++ = base64_table[ac >> 18]; *cp++ = base64_table[(ac >> 12) & 0x3f]; *cp++ = base64_table[(ac >> 6) & 0x3f]; @@ -103,7 +103,7 @@ int base64_encode(const u8 *src, int src *cp++ = '='; break; case 1: - ac = (u32)src[0] << 16; + ac = src[0] << 16; *cp++ = base64_table[ac >> 18]; *cp++ = base64_table[(ac >> 12) & 0x3f]; if (padding) { _ ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v5 3/6] lib/base64: rework encode/decode for speed and stricter validation 2025-11-17 17:46 ` Andrew Morton @ 2025-11-18 10:38 ` Andy Shevchenko 2025-11-18 17:24 ` Andrew Morton 0 siblings, 1 reply; 17+ messages in thread From: Andy Shevchenko @ 2025-11-18 10:38 UTC (permalink / raw) To: Andrew Morton Cc: Guan-Chun Wu, David Laight, axboe, ceph-devel, ebiggers, hch, home7438072, idryomov, jaegeuk, kbusch, linux-fscrypt, linux-kernel, linux-nvme, sagi, tytso, visitorckw, xiubli On Mon, Nov 17, 2025 at 09:46:52AM -0800, Andrew Morton wrote: > On Sun, 16 Nov 2025 18:28:49 +0800 Guan-Chun Wu <409411716@gms.tku.edu.tw> wrote: > > > > Reviewed-by: David Laight <david.laight.linux@gmail.com> > > > > > > But see minor nit below. > > > > Hi David, > > > > Thanks for the review and for pointing this out. > > > > Andrew, would it be possible for you to fold this small change > > (removing the redundant casts) directly when updating the patch? > > If that’s not convenient, I can resend an updated version of the > > series instead. > > Sure, I added this: The file also needs to fix the kernel-doc $ scripts/kernel-doc -none -v -Wall lib/base64.c Warning: lib/base64.c:24 This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst * Initialize the base64 reverse mapping for a single character Warning: lib/base64.c:36 This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst * Recursive macros to generate multiple Base64 reverse mapping table entries. Info: lib/base64.c:67 Scanning doc for function base64_encode Info: lib/base64.c:119 Scanning doc for function base64_decode -- With Best Regards, Andy Shevchenko ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v5 3/6] lib/base64: rework encode/decode for speed and stricter validation 2025-11-18 10:38 ` Andy Shevchenko @ 2025-11-18 17:24 ` Andrew Morton 0 siblings, 0 replies; 17+ messages in thread From: Andrew Morton @ 2025-11-18 17:24 UTC (permalink / raw) To: Andy Shevchenko Cc: Guan-Chun Wu, David Laight, axboe, ceph-devel, ebiggers, hch, home7438072, idryomov, jaegeuk, kbusch, linux-fscrypt, linux-kernel, linux-nvme, sagi, tytso, visitorckw, xiubli On Tue, 18 Nov 2025 11:38:46 +0100 Andy Shevchenko <andriy.shevchenko@intel.com> wrote: > The file also needs to fix the kernel-doc > > $ scripts/kernel-doc -none -v -Wall lib/base64.c > Warning: lib/base64.c:24 This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst > * Initialize the base64 reverse mapping for a single character > Warning: lib/base64.c:36 This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst > * Recursive macros to generate multiple Base64 reverse mapping table entries. > Info: lib/base64.c:67 Scanning doc for function base64_encode > Info: lib/base64.c:119 Scanning doc for function base64_decode Thanks. --- a/lib/base64.c~lib-base64-optimize-base64_decode-with-reverse-lookup-tables-fix +++ a/lib/base64.c @@ -21,7 +21,7 @@ static const char base64_tables[][65] = [BASE64_IMAP] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,", }; -/** +/* * Initialize the base64 reverse mapping for a single character * This macro maps a character to its corresponding base64 value, * returning -1 if the character is invalid. @@ -33,7 +33,8 @@ static const char base64_tables[][65] = : (v) >= 'a' && (v) <= 'z' ? (v) - 'a' + 26 \ : (v) >= '0' && (v) <= '9' ? (v) - '0' + 52 \ : (v) == (ch_62) ? 62 : (v) == (ch_63) ? 63 : -1 -/** + +/* * Recursive macros to generate multiple Base64 reverse mapping table entries. * Each macro generates a sequence of entries in the lookup table: * INIT_2 generates 2 entries, INIT_4 generates 4, INIT_8 generates 8, and so on up to INIT_32. _ ^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v5 4/6] lib: add KUnit tests for base64 encoding/decoding 2025-11-14 5:58 [PATCH v5 0/6] lib/base64: add generic encoder/decoder, migrate users Guan-Chun Wu ` (2 preceding siblings ...) 2025-11-14 6:01 ` [PATCH v5 3/6] lib/base64: rework encode/decode for speed and stricter validation Guan-Chun Wu @ 2025-11-14 6:01 ` Guan-Chun Wu 2025-11-14 6:02 ` [PATCH v5 5/6] fscrypt: replace local base64url helpers with lib/base64 Guan-Chun Wu 2025-11-14 6:02 ` [PATCH v5 6/6] ceph: replace local base64 " Guan-Chun Wu 5 siblings, 0 replies; 17+ messages in thread From: Guan-Chun Wu @ 2025-11-14 6:01 UTC (permalink / raw) To: 409411716 Cc: akpm, andriy.shevchenko, axboe, ceph-devel, david.laight.linux, 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 : 35ns # base64_performance_tests: [1KB] encode run : 510ns # base64_performance_tests: [1KB] decode run : 530ns 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 Reviewed-by: Kuan-Wei Chiu <visitorckw@gmail.com> Signed-off-by: Guan-Chun Wu <409411716@gms.tku.edu.tw> --- 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 3034e294d50d..8f55ab41d62a 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -2815,8 +2815,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 f7460831cfdd..601dba4b7d96 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 000000000000..f7252070c359 --- /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] 17+ messages in thread
* [PATCH v5 5/6] fscrypt: replace local base64url helpers with lib/base64 2025-11-14 5:58 [PATCH v5 0/6] lib/base64: add generic encoder/decoder, migrate users Guan-Chun Wu ` (3 preceding siblings ...) 2025-11-14 6:01 ` [PATCH v5 4/6] lib: add KUnit tests for base64 encoding/decoding Guan-Chun Wu @ 2025-11-14 6:02 ` Guan-Chun Wu 2025-11-14 6:02 ` [PATCH v5 6/6] ceph: replace local base64 " Guan-Chun Wu 5 siblings, 0 replies; 17+ messages in thread From: Guan-Chun Wu @ 2025-11-14 6:02 UTC (permalink / raw) To: 409411716 Cc: akpm, andriy.shevchenko, axboe, ceph-devel, david.laight.linux, 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 43-52x speedups compared to the previous implementation. Reviewed-by: Kuan-Wei Chiu <visitorckw@gmail.com> Signed-off-by: Guan-Chun Wu <409411716@gms.tku.edu.tw> --- 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 8e4c213d418b..a9a4432d12ba 100644 --- a/fs/crypto/fname.c +++ b/fs/crypto/fname.c @@ -16,6 +16,7 @@ #include <linux/export.h> #include <linux/namei.h> #include <linux/scatterlist.h> +#include <linux/base64.h> #include "fscrypt_private.h" @@ -71,7 +72,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) { @@ -162,84 +163,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] 17+ messages in thread
* [PATCH v5 6/6] ceph: replace local base64 helpers with lib/base64 2025-11-14 5:58 [PATCH v5 0/6] lib/base64: add generic encoder/decoder, migrate users Guan-Chun Wu ` (4 preceding siblings ...) 2025-11-14 6:02 ` [PATCH v5 5/6] fscrypt: replace local base64url helpers with lib/base64 Guan-Chun Wu @ 2025-11-14 6:02 ` Guan-Chun Wu 2025-11-14 18:07 ` Viacheslav Dubeyko 5 siblings, 1 reply; 17+ messages in thread From: Guan-Chun Wu @ 2025-11-14 6:02 UTC (permalink / raw) To: 409411716 Cc: akpm, andriy.shevchenko, axboe, ceph-devel, david.laight.linux, 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 43-52x speedups compared to the previous local implementation. Reviewed-by: Kuan-Wei Chiu <visitorckw@gmail.com> Signed-off-by: Guan-Chun Wu <409411716@gms.tku.edu.tw> --- 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 7026e794813c..b6016dcffbb6 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); @@ -318,7 +265,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 */ @@ -412,7 +359,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; @@ -426,7 +374,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 23612b2e9837..b748e2060bc9 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 d18c0eaef9b7..0fa7c7777242 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 a6e260d9e420..b691343cb7f1 100644 --- a/fs/ceph/inode.c +++ b/fs/ceph/inode.c @@ -958,7 +958,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] 17+ messages in thread
* Re: [PATCH v5 6/6] ceph: replace local base64 helpers with lib/base64 2025-11-14 6:02 ` [PATCH v5 6/6] ceph: replace local base64 " Guan-Chun Wu @ 2025-11-14 18:07 ` Viacheslav Dubeyko 2025-11-16 10:36 ` Guan-Chun Wu 0 siblings, 1 reply; 17+ messages in thread From: Viacheslav Dubeyko @ 2025-11-14 18:07 UTC (permalink / raw) To: 409411716@gms.tku.edu.tw Cc: david.laight.linux@gmail.com, linux-nvme@lists.infradead.org, sagi@grimberg.me, kbusch@kernel.org, idryomov@gmail.com, linux-fscrypt@vger.kernel.org, Xiubo Li, akpm@linux-foundation.org, linux-kernel@vger.kernel.org, ebiggers@kernel.org, andriy.shevchenko@intel.com, hch@lst.de, home7438072@gmail.com, axboe@kernel.dk, tytso@mit.edu, visitorckw@gmail.com, jaegeuk@kernel.org, ceph-devel@vger.kernel.org On Fri, 2025-11-14 at 14:02 +0800, Guan-Chun Wu wrote: > 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 43-52x speedups compared to the previous local > implementation. > > Reviewed-by: Kuan-Wei Chiu <visitorckw@gmail.com> > Signed-off-by: Guan-Chun Wu <409411716@gms.tku.edu.tw> > --- > 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 7026e794813c..b6016dcffbb6 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); > @@ -318,7 +265,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 */ > @@ -412,7 +359,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; > @@ -426,7 +374,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 23612b2e9837..b748e2060bc9 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 d18c0eaef9b7..0fa7c7777242 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 a6e260d9e420..b691343cb7f1 100644 > --- a/fs/ceph/inode.c > +++ b/fs/ceph/inode.c > @@ -958,7 +958,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", Looks good! Reviewed-by: Viacheslav Dubeyko <Slava.Dubeyko@ibm.com> Have you run xfstests for this patchset? Thanks, Slava. ^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v5 6/6] ceph: replace local base64 helpers with lib/base64 2025-11-14 18:07 ` Viacheslav Dubeyko @ 2025-11-16 10:36 ` Guan-Chun Wu 2025-11-17 19:42 ` Viacheslav Dubeyko 0 siblings, 1 reply; 17+ messages in thread From: Guan-Chun Wu @ 2025-11-16 10:36 UTC (permalink / raw) To: Viacheslav Dubeyko Cc: david.laight.linux@gmail.com, linux-nvme@lists.infradead.org, sagi@grimberg.me, kbusch@kernel.org, idryomov@gmail.com, linux-fscrypt@vger.kernel.org, Xiubo Li, akpm@linux-foundation.org, linux-kernel@vger.kernel.org, ebiggers@kernel.org, andriy.shevchenko@intel.com, hch@lst.de, home7438072@gmail.com, axboe@kernel.dk, tytso@mit.edu, visitorckw@gmail.com, jaegeuk@kernel.org, ceph-devel@vger.kernel.org On Fri, Nov 14, 2025 at 06:07:26PM +0000, Viacheslav Dubeyko wrote: > On Fri, 2025-11-14 at 14:02 +0800, Guan-Chun Wu wrote: > > 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 43-52x speedups compared to the previous local > > implementation. > > > > Reviewed-by: Kuan-Wei Chiu <visitorckw@gmail.com> > > Signed-off-by: Guan-Chun Wu <409411716@gms.tku.edu.tw> > > --- > > 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 7026e794813c..b6016dcffbb6 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); > > @@ -318,7 +265,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 */ > > @@ -412,7 +359,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; > > @@ -426,7 +374,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 23612b2e9837..b748e2060bc9 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 d18c0eaef9b7..0fa7c7777242 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 a6e260d9e420..b691343cb7f1 100644 > > --- a/fs/ceph/inode.c > > +++ b/fs/ceph/inode.c > > @@ -958,7 +958,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", > > Looks good! > > Reviewed-by: Viacheslav Dubeyko <Slava.Dubeyko@ibm.com> > > Have you run xfstests for this patchset? Hi Slava, Thanks for the review. I haven't run xfstests on this patchset yet. Best regards, Guan-Chun > > Thanks, > Slava. ^ permalink raw reply [flat|nested] 17+ messages in thread
* RE: [PATCH v5 6/6] ceph: replace local base64 helpers with lib/base64 2025-11-16 10:36 ` Guan-Chun Wu @ 2025-11-17 19:42 ` Viacheslav Dubeyko 0 siblings, 0 replies; 17+ messages in thread From: Viacheslav Dubeyko @ 2025-11-17 19:42 UTC (permalink / raw) To: 409411716@gms.tku.edu.tw Cc: david.laight.linux@gmail.com, Xiubo Li, sagi@grimberg.me, idryomov@gmail.com, linux-nvme@lists.infradead.org, linux-fscrypt@vger.kernel.org, kbusch@kernel.org, akpm@linux-foundation.org, linux-kernel@vger.kernel.org, ebiggers@kernel.org, visitorckw@gmail.com, hch@lst.de, home7438072@gmail.com, axboe@kernel.dk, tytso@mit.edu, andriy.shevchenko@intel.com, jaegeuk@kernel.org, ceph-devel@vger.kernel.org On Sun, 2025-11-16 at 18:36 +0800, Guan-Chun Wu wrote: > On Fri, Nov 14, 2025 at 06:07:26PM +0000, Viacheslav Dubeyko wrote: > > On Fri, 2025-11-14 at 14:02 +0800, Guan-Chun Wu wrote: > > > 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 43-52x speedups compared to the previous local > > > implementation. > > > > > > Reviewed-by: Kuan-Wei Chiu <visitorckw@gmail.com> > > > Signed-off-by: Guan-Chun Wu <409411716@gms.tku.edu.tw> > > > --- > > > 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 7026e794813c..b6016dcffbb6 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); > > > @@ -318,7 +265,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 */ > > > @@ -412,7 +359,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; > > > @@ -426,7 +374,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 23612b2e9837..b748e2060bc9 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 d18c0eaef9b7..0fa7c7777242 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 a6e260d9e420..b691343cb7f1 100644 > > > --- a/fs/ceph/inode.c > > > +++ b/fs/ceph/inode.c > > > @@ -958,7 +958,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", > > > > Looks good! > > > > Reviewed-by: Viacheslav Dubeyko <Slava.Dubeyko@ibm.com> > > > > Have you run xfstests for this patchset? > > Hi Slava, > > Thanks for the review. > > I haven't run xfstests on this patchset yet. > > I have run the xfstests for CephFS with applied patchset. I don't see any new issues. We had failures with generic/452 generic/639 before applying the patchset. So, as far as I can see, patchset works well. Tested-by: Viacheslav Dubeyko <Slava.Dubeyko@ibm.com> Thanks, Slava. ^ permalink raw reply [flat|nested] 17+ messages in thread
end of thread, other threads:[~2025-11-18 17:24 UTC | newest] Thread overview: 17+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-11-14 5:58 [PATCH v5 0/6] lib/base64: add generic encoder/decoder, migrate users Guan-Chun Wu 2025-11-14 6:00 ` [PATCH v5 1/6] lib/base64: Add support for multiple variants Guan-Chun Wu 2025-11-14 9:13 ` David Laight 2025-11-14 6:01 ` [PATCH v5 2/6] lib/base64: Optimize base64_decode() with reverse lookup tables Guan-Chun Wu 2025-11-14 9:14 ` David Laight 2025-11-14 6:01 ` [PATCH v5 3/6] lib/base64: rework encode/decode for speed and stricter validation Guan-Chun Wu 2025-11-14 9:18 ` David Laight 2025-11-16 10:28 ` Guan-Chun Wu 2025-11-17 17:46 ` Andrew Morton 2025-11-18 10:38 ` Andy Shevchenko 2025-11-18 17:24 ` Andrew Morton 2025-11-14 6:01 ` [PATCH v5 4/6] lib: add KUnit tests for base64 encoding/decoding Guan-Chun Wu 2025-11-14 6:02 ` [PATCH v5 5/6] fscrypt: replace local base64url helpers with lib/base64 Guan-Chun Wu 2025-11-14 6:02 ` [PATCH v5 6/6] ceph: replace local base64 " Guan-Chun Wu 2025-11-14 18:07 ` Viacheslav Dubeyko 2025-11-16 10:36 ` Guan-Chun Wu 2025-11-17 19:42 ` Viacheslav Dubeyko
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).