Skip to main content

shuyi_platform/link/
base64.rs

1//! Base64 (RFC 4648 ยง4, the standard alphabet, with padding): how the link
2//! carries a picture in a JSON line, and how MCP carries an image.
3
4use crate::alloc_prelude::*;
5use alloc::format;
6
7/// The standard alphabet.
8const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
9
10/// `bytes` in standard base64, with padding.
11pub fn encode(bytes: &[u8]) -> String {
12    let mut out = String::with_capacity(bytes.len().div_ceil(3) * 4);
13    for chunk in bytes.chunks(3) {
14        let n = u32::from_be_bytes([
15            0,
16            chunk[0],
17            chunk.get(1).copied().unwrap_or(0),
18            chunk.get(2).copied().unwrap_or(0),
19        ]);
20        for i in 0..4 {
21            if i <= chunk.len() {
22                out.push(char::from(ALPHABET[((n >> (18 - 6 * i)) & 63) as usize]));
23            } else {
24                out.push('=');
25            }
26        }
27    }
28    out
29}
30
31/// The value of one base64 character, if it is one.
32fn value(c: u8) -> Option<u32> {
33    let v = match c {
34        b'A'..=b'Z' => c - b'A',
35        b'a'..=b'z' => c - b'a' + 26,
36        b'0'..=b'9' => c - b'0' + 52,
37        b'+' => 62,
38        b'/' => 63,
39        _ => return None,
40    };
41    Some(u32::from(v))
42}
43
44/// The bytes `text` encodes in standard base64 with padding, or why it
45/// isn't that.
46pub fn decode(text: &str) -> Result<Vec<u8>, String> {
47    let bytes = text.as_bytes();
48    if !bytes.len().is_multiple_of(4) {
49        return Err(format!(
50            "base64: {} characters, not a multiple of 4",
51            bytes.len()
52        ));
53    }
54    let mut out = Vec::with_capacity(bytes.len() / 4 * 3);
55    let quads = bytes.len() / 4;
56    for (i, quad) in bytes.chunks(4).enumerate() {
57        let pad = quad.iter().rev().take_while(|&&c| c == b'=').count();
58        if pad > 2 || (pad > 0 && i + 1 != quads) {
59            return Err("base64: padding before the end".to_owned());
60        }
61        let mut n = 0u32;
62        for &c in &quad[..4 - pad] {
63            let v = value(c)
64                .ok_or_else(|| format!("base64: {:?} is no base64 character", char::from(c)))?;
65            n = n * 64 + v;
66        }
67        n <<= 6 * pad;
68        let [_, a, b, c] = n.to_be_bytes();
69        out.extend_from_slice(&[a, b, c][..3 - pad]);
70    }
71    Ok(out)
72}