Skip to content

Latest commit

 

History

13 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Linux kernel's Base64 in userspace

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.

Features

  • 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.

Building and running

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
./base64

To build and run a userspace adaptation of the original KUnit tests and benchmarks:

make test

Requirements

  • C compiler with GNU C17 support (GCC or Clang).
  • GNU Make.

Porting notes

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 custom types.h using <stdint.h> and <stdbool.h>.
  • Branch prediction: Kernel macros likely() and unlikely() have been commented out to avoid depending on kernel-specific branch prediction macros. If your compiler supports them (e.g., using __builtin_expect in 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.

Benchmarks

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.

Line-by-line changes

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:

base64.c

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);

base64.h

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, [...])

base64_kunit.c

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");

License

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.

About

A standalone userspace port of the Linux kernel's Base64 implementation in C, with no external library dependencies, including a userspace adaptation of its KUnit tests.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages