2017-04-17 16:08:41 -07:00
|
|
|
const assert = @import("debug.zig").assert;
|
|
|
|
const mem = @import("mem.zig");
|
|
|
|
|
|
|
|
pub const standard_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
|
|
|
|
|
|
|
pub fn encode(dest: []u8, source: []const u8) -> []u8 {
|
|
|
|
return encodeWithAlphabet(dest, source, standard_alphabet);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn decode(dest: []u8, source: []const u8) -> []u8 {
|
|
|
|
return decodeWithAlphabet(dest, source, standard_alphabet);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn encodeWithAlphabet(dest: []u8, source: []const u8, alphabet: []const u8) -> []u8 {
|
|
|
|
assert(alphabet.len == 65);
|
|
|
|
assert(dest.len >= calcEncodedSize(source.len));
|
|
|
|
|
|
|
|
var i: usize = 0;
|
|
|
|
var out_index: usize = 0;
|
2017-05-03 15:12:07 -07:00
|
|
|
while (i + 2 < source.len) : (i += 3) {
|
2017-04-17 16:08:41 -07:00
|
|
|
dest[out_index] = alphabet[(source[i] >> 2) & 0x3f];
|
|
|
|
out_index += 1;
|
|
|
|
|
|
|
|
dest[out_index] = alphabet[((source[i] & 0x3) <<% 4) |
|
|
|
|
((source[i + 1] & 0xf0) >> 4)];
|
|
|
|
out_index += 1;
|
|
|
|
|
|
|
|
dest[out_index] = alphabet[((source[i + 1] & 0xf) <<% 2) |
|
|
|
|
((source[i + 2] & 0xc0) >> 6)];
|
|
|
|
out_index += 1;
|
|
|
|
|
|
|
|
dest[out_index] = alphabet[source[i + 2] & 0x3f];
|
|
|
|
out_index += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (i < source.len) {
|
|
|
|
dest[out_index] = alphabet[(source[i] >> 2) & 0x3f];
|
|
|
|
out_index += 1;
|
|
|
|
|
|
|
|
if (i + 1 == source.len) {
|
|
|
|
dest[out_index] = alphabet[(source[i] & 0x3) <<% 4];
|
|
|
|
out_index += 1;
|
|
|
|
|
|
|
|
dest[out_index] = alphabet[64];
|
|
|
|
out_index += 1;
|
|
|
|
} else {
|
|
|
|
dest[out_index] = alphabet[((source[i] & 0x3) <<% 4) |
|
|
|
|
((source[i + 1] & 0xf0) >> 4)];
|
|
|
|
out_index += 1;
|
|
|
|
|
|
|
|
dest[out_index] = alphabet[(source[i + 1] & 0xf) <<% 2];
|
|
|
|
out_index += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
dest[out_index] = alphabet[64];
|
|
|
|
out_index += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return dest[0...out_index];
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn decodeWithAlphabet(dest: []u8, source: []const u8, alphabet: []const u8) -> []u8 {
|
|
|
|
assert(alphabet.len == 65);
|
|
|
|
|
|
|
|
var ascii6 = []u8{64} ** 256;
|
|
|
|
for (alphabet) |c, i| {
|
|
|
|
ascii6[c] = u8(i);
|
|
|
|
}
|
|
|
|
|
|
|
|
return decodeWithAscii6BitMap(dest, source, ascii6[0...], alphabet[64]);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn decodeWithAscii6BitMap(dest: []u8, source: []const u8, ascii6: []const u8, pad_char: u8) -> []u8 {
|
|
|
|
assert(ascii6.len == 256);
|
|
|
|
assert(dest.len >= calcExactDecodedSizeWithPadChar(source, pad_char));
|
|
|
|
|
|
|
|
var src_index: usize = 0;
|
|
|
|
var dest_index: usize = 0;
|
|
|
|
var in_buf_len: usize = source.len;
|
|
|
|
|
|
|
|
while (in_buf_len > 0 and source[in_buf_len - 1] == pad_char) {
|
|
|
|
in_buf_len -= 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (in_buf_len > 4) {
|
|
|
|
dest[dest_index] = ascii6[source[src_index + 0]] <<% 2 |
|
|
|
|
ascii6[source[src_index + 1]] >> 4;
|
|
|
|
dest_index += 1;
|
|
|
|
|
|
|
|
dest[dest_index] = ascii6[source[src_index + 1]] <<% 4 |
|
|
|
|
ascii6[source[src_index + 2]] >> 2;
|
|
|
|
dest_index += 1;
|
|
|
|
|
|
|
|
dest[dest_index] = ascii6[source[src_index + 2]] <<% 6 |
|
|
|
|
ascii6[source[src_index + 3]];
|
|
|
|
dest_index += 1;
|
|
|
|
|
|
|
|
src_index += 4;
|
|
|
|
in_buf_len -= 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (in_buf_len > 1) {
|
|
|
|
dest[dest_index] = ascii6[source[src_index + 0]] <<% 2 |
|
|
|
|
ascii6[source[src_index + 1]] >> 4;
|
|
|
|
dest_index += 1;
|
|
|
|
}
|
|
|
|
if (in_buf_len > 2) {
|
|
|
|
dest[dest_index] = ascii6[source[src_index + 1]] <<% 4 |
|
|
|
|
ascii6[source[src_index + 2]] >> 2;
|
|
|
|
dest_index += 1;
|
|
|
|
}
|
|
|
|
if (in_buf_len > 3) {
|
|
|
|
dest[dest_index] = ascii6[source[src_index + 2]] <<% 6 |
|
|
|
|
ascii6[source[src_index + 3]];
|
|
|
|
dest_index += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return dest[0...dest_index];
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn calcEncodedSize(source_len: usize) -> usize {
|
|
|
|
return (((source_len * 4) / 3 + 3) / 4) * 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Computes the upper bound of the decoded size based only on the encoded length.
|
|
|
|
/// To compute the exact decoded size, see ::calcExactDecodedSize
|
|
|
|
pub fn calcMaxDecodedSize(encoded_len: usize) -> usize {
|
|
|
|
return @divExact(encoded_len * 3, 4);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Computes the number of decoded bytes there will be. This function must
|
|
|
|
/// be given the encoded buffer because there might be padding
|
|
|
|
/// bytes at the end ('=' in the standard alphabet)
|
|
|
|
pub fn calcExactDecodedSize(encoded: []const u8) -> usize {
|
|
|
|
return calcExactDecodedSizeWithAlphabet(encoded, standard_alphabet);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn calcExactDecodedSizeWithAlphabet(encoded: []const u8, alphabet: []const u8) -> usize {
|
|
|
|
assert(alphabet.len == 65);
|
|
|
|
return calcExactDecodedSizeWithPadChar(encoded, alphabet[64]);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn calcExactDecodedSizeWithPadChar(encoded: []const u8, pad_char: u8) -> usize {
|
|
|
|
var buf_len = encoded.len;
|
|
|
|
|
|
|
|
while (buf_len > 0 and encoded[buf_len - 1] == pad_char) {
|
|
|
|
buf_len -= 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (buf_len * 3) / 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
test "base64" {
|
|
|
|
testBase64();
|
|
|
|
comptime testBase64();
|
|
|
|
}
|
|
|
|
|
|
|
|
fn testBase64() {
|
|
|
|
testBase64Case("", "");
|
|
|
|
testBase64Case("f", "Zg==");
|
|
|
|
testBase64Case("fo", "Zm8=");
|
|
|
|
testBase64Case("foo", "Zm9v");
|
|
|
|
testBase64Case("foob", "Zm9vYg==");
|
|
|
|
testBase64Case("fooba", "Zm9vYmE=");
|
|
|
|
testBase64Case("foobar", "Zm9vYmFy");
|
|
|
|
}
|
|
|
|
|
|
|
|
fn testBase64Case(expected_decoded: []const u8, expected_encoded: []const u8) {
|
|
|
|
const calculated_decoded_len = calcExactDecodedSize(expected_encoded);
|
|
|
|
assert(calculated_decoded_len == expected_decoded.len);
|
|
|
|
|
|
|
|
const calculated_encoded_len = calcEncodedSize(expected_decoded.len);
|
|
|
|
assert(calculated_encoded_len == expected_encoded.len);
|
|
|
|
|
|
|
|
var buf: [100]u8 = undefined;
|
|
|
|
|
|
|
|
const actual_decoded = decode(buf[0...], expected_encoded);
|
|
|
|
assert(actual_decoded.len == expected_decoded.len);
|
|
|
|
assert(mem.eql(u8, expected_decoded, actual_decoded));
|
|
|
|
|
|
|
|
const actual_encoded = encode(buf[0...], expected_decoded);
|
|
|
|
assert(actual_encoded.len == expected_encoded.len);
|
|
|
|
assert(mem.eql(u8, expected_encoded, actual_encoded));
|
|
|
|
}
|