Functional Weave
Code in Python

encoding.base64@1.0.0

impl/rust/base64_decode.rs

2,441 bytes · the Rust implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

use super::funejson::Value;  ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one

fn sextet(b: u8) -> i64 {
    match b {
        b'A'..=b'Z' => (b - b'A') as i64,
        b'a'..=b'z' => (b - b'a' + 26) as i64,
        b'0'..=b'9' => (b - b'0' + 52) as i64,
        b'+' => 62,
        b'/' => 63,
        _ => -1,
    }
}

/// Standard, padded base64 back to bytes, strictly: no whitespace, no
/// characters outside the alphabet, padding to a multiple of four, and zero
/// bits after the last byte, so every byte string has exactly one spelling.
///
/// # Panics
/// Panics on any of those.
pub fn base64_decode(text: &str) -> Vec<i64> {
    let mut values: Vec<i64> = Vec::with_capacity(text.len());
    let mut padding = 0usize;
    // Bytes, not chars: every accepted character is ASCII, and any byte of a
    // multi-byte character fails the alphabet check just as the character does.
    for &b in text.as_bytes() {
        if b == b'=' {
            padding += 1;
            continue;
        }
        let v = sextet(b);
        if v < 0 || padding > 0 {
            panic!("base64 text may only contain A-Z, a-z, 0-9, + and /, with = padding at the end");
        }
        values.push(v);
    }
    if text.len() % 4 != 0 {
        panic!("base64 text must be a multiple of 4 characters long, received {}", text.len());
    }
    if padding > 2 {
        panic!("base64 text has too much = padding");
    }
    let mut out = Vec::with_capacity(values.len() / 4 * 3);
    let mut chunks = values.chunks_exact(4);
    for c in &mut chunks {
        let n = (c[0] << 18) | (c[1] << 12) | (c[2] << 6) | c[3];
        out.extend_from_slice(&[(n >> 16) & 255, (n >> 8) & 255, n & 255]);
    }
    match chunks.remainder() {
        [a, b] => {
            if b & 15 != 0 {
                panic!("base64 text has non-zero bits after its last byte");
            }
            out.push(((a << 2) | (b >> 4)) & 255);
        }
        [a, b, c] => {
            if c & 3 != 0 {
                panic!("base64 text has non-zero bits after its last byte");
            }
            let n = (a << 18) | (b << 12) | (c << 6);
            out.extend_from_slice(&[(n >> 16) & 255, (n >> 8) & 255]);
        }
        _ => {}
    }
    out
}

pub fn fune_vector(args: &[Value]) -> Value {
    let text = match &args[0] {
        Value::Str(s) => s.as_str(),
        _ => panic!("base64 text must be a string"),
    };
    Value::Arr(base64_decode(text).into_iter().map(Value::Int).collect())
}