I was looking for a reliable and lightweight Base64 implementation in C without external library dependencies. Instead of reinventing the wheel, I decided to port the Base64 implementation from the Linux kernel to userspace. This repository contains the ported implementation, a small usage example and a userspace adaptation of the original KUnit tests.
- Standard Base64 (RFC 4648).
- Base64 URL-safe variant.
- IMAP Base64 variant.
- Optional
=padding. - Userspace C implementation.
- No external library dependencies.
- Userspace adaptation of the Linux kernel's Base64 tests.
The project includes a simple Makefile. It requires no external dependencies other than a standard C compiler (GCC/Clang).
To build the example program:
make base64
./base64To build and run a userspace adaptation of the original KUnit tests and benchmarks:
make test- C compiler with GNU C17 support (GCC or Clang).
- GNU Make.
The core algorithm is kept as close as possible to the Linux kernel implementation. However, a few kernel-specific elements had to be abstracted:
- Headers: Replaced
<linux/kernel.h>and<linux/types.h>with a customtypes.husing<stdint.h>and<stdbool.h>. - Branch prediction: Kernel macros
likely()andunlikely()have been commented out to avoid depending on kernel-specific branch prediction macros. If your compiler supports them (e.g., using__builtin_expectin GCC/Clang), you can easily uncomment them in the source. - KUnit compatibility: A minimal userspace compatibility layer provides the subset of KUnit APIs needed to run the original tests outside the Linux kernel, while keeping the test source as close as possible to the original.
The benchmark output format is inspired by the original Linux kernel commit description.
Here are the results of a test run on an Intel(R) Core(TM) i7-3610QM CPU:
KTAP version 1
1..1
# Subtest: base64
1..4
# base64_performance_tests: [64B] encode run : 107ns
# base64_performance_tests: [64B] decode run : 127ns
# base64_performance_tests: [1KB] encode run : 1129ns
# base64_performance_tests: [1KB] decode run : 1326ns
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
ok 1 base64
These numbers are illustrative microbenchmark results. Performance can vary significantly depending on CPU, compiler, optimization settings and system load.
The userspace port keeps the original implementation as close as possible, while replacing kernel-specific dependencies with small userspace equivalents. For full transparency, here is a detailed breakdown of the modifications made to the original kernel files:
| Original code | My code |
|---|---|
12. #include <linux/kernel.h> |
12. // #include <linux/kernel.h> |
13. #include <linux/types.h> |
13. #include "types.h" |
14. #include <linux/export.h> |
14. // #include <linux/export.h> |
15. #include <linux/string.h> |
15. // #include <linux/string.h> |
16. #include <linux/base64.h> |
16. #include "base64.h" |
55. static const s8 base64_rev_maps[][256] = { |
55. static const int8_t base64_rev_maps[][256] = { |
80. int base64_encode(const u8 *src, [...]) |
80. int base64_encode(const uint8_t *src, [...]) |
82. u32 ac = 0; |
82. uint32_t ac = 0; |
118. EXPORT_SYMBOL_GPL(base64_encode); |
118. // EXPORT_SYMBOL_GPL(base64_encode); |
133. int base64_decode([...], u8 *dst, [...]) |
133. int base64_decode([...], uint8_t *dst, [...]) |
135. u8 *bp = dst; |
135. uint8_t *bp = dst; |
136. s8 input[4]; |
136. int8_t input[4]; |
137. s32 val; |
137. int32_t val; |
138. const u8 *s = (const u8 *)src; |
138. const uint8_t *s = (const uint8_t *)src; |
139. const s8 *base64_rev_tables = [...]; |
139. const int8_t *base64_rev_tables = [...]; |
149. if (unlikely(val < 0)) { |
149. if (/* unlikely */(val < 0)) { |
165. if (likely(!srclen)) |
165. if (/* likely */(!srclen)) |
185. EXPORT_SYMBOL_GPL(base64_decode); |
185. // EXPORT_SYMBOL_GPL(base64_decode); |
| Original code | My code |
|---|---|
9. #include <linux/types.h> |
9. #include "types.h" |
17. #define BASE64_CHARS(nbytes) DIV_ROUND_UP((nbytes) * 4, 3) |
17. #define BASE64_CHARS(nbytes) ((((nbytes) + 2) / 3) * 4) |
19. int base64_encode(const u8 *src, [...]) |
19. int base64_encode(const uint8_t *src, [...]) |
20. int base64_decode([...], u8 *dst, [...]) |
20. int base64_decode([...], uint8_t *dst, [...]) |
| Original code | My code |
|---|---|
8. #include <kunit/test.h> |
8. #include "test.h" |
9. #include <linux/base64.h> |
9. #include "base64.h" |
12. static u64 bench_encode_ns(const u8 *data, [...], |
12. static uint64_t bench_encode_ns(const uint8_t *data, [...], |
15. u64 t0, t1; |
15. uint64_t t0, t1; |
22. return div64_u64(t1 - t0, (u64)reps); |
22. return (uint64_t)(t1 - t0) / (uint64_t)reps; |
25. static u64 bench_decode_ns([...], u8 *dst, [...], |
25. static uint64_t bench_decode_ns([...], uint8_t *dst, [...], |
28. u64 t0, t1; |
28. uint64_t t0, t1; |
35. return div64_u64(t1 - t0, (u64)reps); |
35. return (uint64_t)(t1 - t0) / (uint64_t)reps; |
42. size_t outlen = DIV_ROUND_UP(size, 3) * 4; |
42. size_t outlen = BASE64_CHARS(size); |
43. u8 *in = kmalloc(size, GFP_KERNEL); |
43. uint8_t *in = kmalloc(size, GFP_KERNEL); |
45. u8 *decoded = kmalloc(size, GFP_KERNEL); |
45. uint8_t *decoded = kmalloc(size, GFP_KERNEL); |
61. u64 t1 = [...]; |
61. uint64_t t1 = [...]; |
63. kunit_info([...], t1); |
63. kunit_info([...], (unsigned long long)t1); |
65. u64 t2 = [...]; |
65. uint64_t t2 = [...]; |
82. static void expect_encode_ok([...], const u8 *src, [...], |
82. static void expect_encode_ok([...], const uint8_t *src, [...], |
97. const u8 *expected, [...], |
97. const uint8_t *expected, [...], |
100. u8 buf[128]; |
100. uint8_t buf[128]; |
111. u8 buf[64]; |
111. uint8_t buf[64]; |
121. expect_encode_ok(test, (const u8 *)"", [...]); |
121. expect_encode_ok(test, (const uint8_t *)"", [...]); |
[...] |
[...] |
155. expect_encode_ok(test, (const u8 *)"", [...]); |
155. expect_encode_ok(test, (const uint8_t *)"", [...]); |
163. expect_decode_ok(test, (const u8 *)"", [...]); |
163. expect_decode_ok(test, (const uint8_t *)"", [...]); |
[...] |
[...] |
206. expect_decode_ok(test, (const u8 *)"", [...]); |
206. expect_decode_ok(test, (const uint8_t *)"", [...]); |
227. const u8 sample1[] = [...]; |
227. const uint8_t sample1[] = [...]; |
229. u8 back[128]; |
229. uint8_t back[128]; |
266. u8 tmp[8]; |
266. uint8_t tmp[8]; |
292. MODULE_AUTHOR("[...]"); |
292. // MODULE_AUTHOR("[...]"); |
293. MODULE_DESCRIPTION("[...]"); |
293. // MODULE_DESCRIPTION("[...]"); |
294. MODULE_LICENSE("GPL"); |
294. // MODULE_LICENSE("GPL"); |
This project is distributed under the GNU General Public License v2.0. Parts of the implementation are derived from the Linux kernel and retain their original copyright and licensing information.