660 words
3 minutes
240301_base64

link#

1. Base64 Math Formula#

  • Base64 encodes 3 bytes → 4 characters.

  • Mathematical Relationship

output_length=4n3\text{output\_length} = 4 \cdot \left\lceil \frac{n}{3} \right\rceil

Bit Transformation#

  • 3 bytes (24 bits):
[aaaaaaaabbbbbbbbcccccccc]
  • Split into 6-bit groups:
[aaaaaa][aabbbb][bbbbcc][cccccc]
  • Each 6-bit value maps to one Base64 alphabet character.

zig code(ver 0.16)#

const std = @import("std");

fn base64_encode(allocator: std.mem.Allocator, input: []const u8) ![]const u8 {
    const alphabet_chars =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-+"[0..64].*;
    var encoder = std.base64.Base64Encoder.init(alphabet_chars, '=');

    const size = encoder.calcSize(input.len);
    const buf = try allocator.alloc(u8, size);
    const result = encoder.encode(buf, input);

    return result;
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    const allocator = gpa.allocator();

    const result = try base64_encode(allocator, "h");

    std.debug.print("h __result : {s}\n", .{result});

    const result02 = try base64_encode(allocator, "hello");
    std.debug.print("hello ___result02 : {s}\n", .{result02});
}

Rust code(ver 1.93.1)#

use base64::{Engine as _, engine::general_purpose};

fn base64_encode(input: &[u8]) -> String {
    // URL-safe style alphabet similar to your Zig example:
    // "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-+"
    // Note: Standard Rust URL_SAFE uses '-' and '_'
    // Your Zig uses '-' and '+', which is slightly custom.
    // We'll use STANDARD for closest match to '+'.

    general_purpose::STANDARD.encode(input)
}

fn main() {
    let result = base64_encode(b"h");
    println!("h __result : {}", result);

    let result02 = base64_encode(b"hello");
    println!("hello ___result02 : {}", result02);
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn simple_test() {
        assert_eq!(base64_encode(b"h"), "aA==");
        assert_eq!(base64_encode(b"hello"), "aGVsbG8=");
    }

    #[test]
    fn fuzz_example() {
        for i in 0..255u8 {
            let input = [i];
            let encoded = base64_encode(&input);
            let decoded = general_purpose::STANDARD
                .decode(encoded)
                .expect("decode failed");
            assert_eq!(decoded, input);
        }
    }
}

2. Memory Model Difference (Zig vs Rust)#

Zig#

allocator.alloc(u8, size)
  • Manual allocation
  • You return []const u8
  • Caller manages allocator

Rust#

fn base64_encode(input: &[u8]) -> String
  • String owns heap memory
  • Automatic deallocation (RAII)
  • No explicit allocator needed

3. std만 해서 Rust#

const BASE64_ALPHABET: &[u8; 64] =
    b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

fn base64_encode(input: &[u8]) -> String {
    let mut output = String::new();

    let mut i = 0;
    while i < input.len() {
        // Read up to 3 bytes
        let b0 = input[i];
        let b1 = if i + 1 < input.len() { input[i + 1] } else { 0 };
        let b2 = if i + 2 < input.len() { input[i + 2] } else { 0 };

        // Combine into 24 bits
        let triple: u32 = ((b0 as u32) << 16) | ((b1 as u32) << 8) | (b2 as u32);

        // Extract 6-bit chunks
        let c0 = ((triple >> 18) & 0x3F) as usize;
        let c1 = ((triple >> 12) & 0x3F) as usize;
        let c2 = ((triple >> 6) & 0x3F) as usize;
        let c3 = (triple & 0x3F) as usize;

        output.push(BASE64_ALPHABET[c0] as char);
        output.push(BASE64_ALPHABET[c1] as char);

        if i + 1 < input.len() {
            output.push(BASE64_ALPHABET[c2] as char);
        } else {
            output.push('=');
        }

        if i + 2 < input.len() {
            output.push(BASE64_ALPHABET[c3] as char);
        } else {
            output.push('=');
        }

        i += 3;
    }

    output
}

fn base64_decode(input: &str) -> Result<Vec<u8>, String> {
    let mut output = Vec::new();

    let mut buffer = 0u32;
    let mut bits = 0;

    for ch in input.chars() {
        if ch == '=' {
            break;
        }

        let val = match BASE64_ALPHABET.iter().position(|&c| c as char == ch) {
            Some(v) => v as u32,
            None => return Err(format!("Invalid character: {}", ch)),
        };

        buffer = (buffer << 6) | val;
        bits += 6;

        if bits >= 8 {
            bits -= 8;
            let byte = (buffer >> bits) & 0xFF;
            output.push(byte as u8);
        }
    }

    Ok(output)
}

fn main() {
    let result = base64_encode(b"h");
    println!("h __result : {}", result);

    let result02 = base64_encode(b"hello");
    println!("hello ___result02 : {}", result02);
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn simple_test() {
        assert_eq!(base64_encode(b"h"), "aA==");
        assert_eq!(base64_encode(b"hello"), "aGVsbG8=");
    }

    #[test]
    fn fuzz_example() {
        for i in 0..=255u8 {
            let input = [i];
            let encoded = base64_encode(&input);
            let decoded = base64_decode(&encoded).unwrap();
            assert_eq!(decoded, input);
        }
    }
}
  • result
h __result : aA==
hello ___result02 : aGVsbG8=

🧠 Memory Layout (Visual CLI)#

  • Example: "h" (ASCII 104)
Input:
01101000

Pad to 24 bits:
01101000 00000000 00000000

Split:
011010 000000 000000 000000

Decimal:
26 0 0 0

Base64:
a A = =

🔍 Why This Is Safe & Pure std#

  • No heap allocation except String / Vec
  • No unsafe
  • No external crates
  • Bitwise logic only
  • Works in no_std with minor modification
240301_base64
https://younghakim7.github.io/blog/posts/240301_base64/
Author
YoungHa
Published at
2024-03-01