@@ 1,16 1,133 @@
#include <stdint.h>
#include <janet.h>
+enum base_32_scheme {
+ BASE_32_SCHEME_ZERO,
+ BASE_32_SCHEME_RFC4648,
+ BASE_32_SCHEME_Z_BASE_32,
+ BASE_32_SCHEME_CROCKFORD,
+ BASE_32_SCHEME_HEX,
+ BASE_32_SCHEME_GEOHASH,
+ BASE_32_SCHEME_WORDSAFE,
+};
+extern ssize_t base_32_encode(const uint8_t*, size_t, uint8_t*, size_t, enum base_32_scheme);
+extern ssize_t base_32_decode(const uint8_t*, size_t, uint8_t*, size_t, enum base_32_scheme);
extern ssize_t base_64_encode(const uint8_t*, size_t, uint8_t*, size_t);
+extern ssize_t base_64_decode(const uint8_t*, size_t, uint8_t*, size_t);
+
+static enum base_32_scheme _base32_scheme(Janet v) {
+ if (janet_keyeq(v, "rfc4648")) {
+ return BASE_32_SCHEME_RFC4648;
+ } else if (janet_keyeq(v, "z-base-32")) {
+ return BASE_32_SCHEME_Z_BASE_32;
+ } else if (janet_keyeq(v, "crockford")) {
+ return BASE_32_SCHEME_CROCKFORD;
+ } else if (janet_keyeq(v, "hex")) {
+ return BASE_32_SCHEME_HEX;
+ } else if (janet_keyeq(v, "geohash")) {
+ return BASE_32_SCHEME_GEOHASH;
+ } else if (janet_keyeq(v, "wordsafe")) {
+ return BASE_32_SCHEME_WORDSAFE;
+ } else {
+ return BASE_32_SCHEME_ZERO;
+ }
+}
+
+static Janet Baseenc_base32Decode(int argc, Janet argv[]) {
+ janet_arity(argc, 1, 3);
+ JanetByteView data = janet_getbytes(argv, 0);
+ JanetBuffer *buf;
+ size_t out_len = (data.len + 7) / 8 * 5;
+ if (argc >= 2 && janet_type(argv[1]) != JANET_NIL) {
+ buf = janet_getbuffer(argv, 1);
+ janet_buffer_ensure(buf, out_len, 1);
+ } else {
+ buf = janet_buffer(out_len);
+ }
+
+ enum base_32_scheme scheme = BASE_32_SCHEME_RFC4648;
+ if (argc >= 3) {
+ scheme = _base32_scheme(argv[2]);
+ if (scheme == BASE_32_SCHEME_ZERO) {
+ janet_panicf("invalid base32 scheme: %q", argv[2]);
+ }
+ }
+
+ ssize_t len = base_32_decode(&data.bytes[0], (size_t)data.len,
+ &buf->data[0], (size_t)buf->capacity, scheme);
+ if (len == -1) {
+ janet_panic("buffer overflow");
+ } else if (len == -2) {
+ janet_panic("invalid base32 data");
+ } else if (len == -3) {
+ janet_panic("unknown scheme");
+ }
+ buf->count = (int32_t)len;
+ return janet_wrap_buffer(buf);
+}
+
+static Janet Baseenc_base32Encode(int argc, Janet argv[]) {
+ janet_arity(argc, 1, 3);
+ JanetByteView data = janet_getbytes(argv, 0);
+ JanetBuffer *buf;
+ size_t out_len = (data.len + 4) / 5 * 8;
+ if (argc >= 2 && janet_type(argv[1]) != JANET_NIL) {
+ buf = janet_getbuffer(argv, 1);
+ janet_buffer_ensure(buf, out_len, 1);
+ } else {
+ buf = janet_buffer(out_len);
+ }
+
+ enum base_32_scheme scheme = BASE_32_SCHEME_RFC4648;
+ if (argc >= 3) {
+ scheme = _base32_scheme(argv[2]);
+ if (scheme == BASE_32_SCHEME_ZERO) {
+ janet_panicf("invalid base32 scheme: %q", argv[2]);
+ }
+ }
+
+ ssize_t len = base_32_encode(&data.bytes[0], (size_t)data.len,
+ &buf->data[0], (size_t)buf->capacity, scheme);
+ if (len == -1) {
+ janet_panic("buffer overflow");
+ }
+ buf->count = (int32_t)len;
+ return janet_wrap_buffer(buf);
+}
+
+static Janet Baseenc_base64Decode(int argc, Janet argv[]) {
+ janet_arity(argc, 1, 2);
+ JanetByteView data = janet_getbytes(argv, 0);
+ JanetBuffer *buf;
+ size_t out_len = (data.len + 3) / 4 * 3;
+ if (argc > 1) {
+ buf = janet_getbuffer(argv, 1);
+ janet_buffer_ensure(buf, out_len, 1);
+ } else {
+ buf = janet_buffer(out_len);
+ }
+
+ ssize_t len = base_64_decode(&data.bytes[0], (size_t)data.len,
+ &buf->data[0], (size_t)buf->capacity);
+ if (len == -1) {
+ janet_panic("buffer overflow");
+ } else if (len == -2) {
+ janet_panic("invalid base64 data");
+ }
+ buf->count = (int32_t)len;
+ return janet_wrap_buffer(buf);
+}
static Janet Baseenc_base64Encode(int argc, Janet argv[]) {
janet_arity(argc, 1, 2);
JanetByteView data = janet_getbytes(argv, 0);
JanetBuffer *buf;
+ size_t out_len = (data.len + 2) / 3 * 4;
if (argc > 1) {
buf = janet_getbuffer(argv, 1);
+ janet_buffer_ensure(buf, out_len, 1);
} else {
- buf = janet_buffer((data.len + 2) / 3 * 4);
+ buf = janet_buffer(out_len);
}
ssize_t len = base_64_encode(&data.bytes[0], (size_t)data.len,
@@ 22,28 139,9 @@ static Janet Baseenc_base64Encode(int ar
return janet_wrap_buffer(buf);
}
-static Janet Baseenc_base64Decode(int argc, Janet argv[]) {
- janet_arity(argc, 1, 2);
- JanetByteView data = janet_getbytes(argv, 0);
- JanetBuffer *buf;
- if (argc > 1) {
- buf = janet_getbuffer(argv, 1);
- } else {
- buf = janet_buffer((data.len + 3) / 4 * 3);
- }
-
- ssize_t len = base_64_decode(&data.bytes[0], (size_t)data.len,
- &buf->data[0], (size_t)buf->capacity);
- if (len == -1) {
- janet_panic("buffer overflow");
- } else if (len == -2) {
- janet_panic("invalid base64 string");
- }
- buf->count = (int32_t)len;
- return janet_wrap_buffer(buf);
-}
-
static const JanetReg Baseenc_cfuns[] = {
+ { "base32-encode", Baseenc_base32Encode, NULL },
+ { "base32-decode", Baseenc_base32Decode, NULL },
{ "base64-encode", Baseenc_base64Encode, NULL },
{ "base64-decode", Baseenc_base64Decode, NULL },
{ NULL, NULL, NULL },
@@ 1,8 1,221 @@
const std = @import("std");
-const EncDecError = error{
- BufferOverflow,
- InvalidEncoding,
+const Base32 = struct {
+ const pad = @as(u8, '=');
+ const alphabet_rfc4648: *const [32]u8 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
+ const alphabet_z_base_32: *const [32]u8 = "ybndrfg8ejkmcpqxot1uwisza345h769";
+ const alphabet_crockford: *const [32]u8 = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
+ const alphabet_hex: *const [32]u8 = "0123456789ABCDEFGHIJKLMNOPQRSTUV";
+ const alphabet_geohash: *const [32]u8 = "0123456789bcdefghjkmnpqrstuvwxyz";
+ const alphabet_wordsafe: *const [32]u8 = "23456789CFGHJMPQRVWXcfghjmpqrvwx";
+
+ const ReverseTransform = enum {
+ None,
+ Lowercase,
+ Uppercase,
+ Crockford,
+ };
+ fn buildReverse(src: *const [32]u8, transform: ReverseTransform) [256]u8 {
+ var rev: [256]u8 = .{255} ** 256;
+ for (src) |x, i| {
+ const i_ = @intCast(u8, i);
+ rev[x] = i_;
+ if (transform == .Lowercase and x >= 'A' and x <= 'Z') {
+ rev[x + ('a' - 'A')] = i_;
+ } else if (transform == .Uppercase and x >= 'a' and x <= 'z') {
+ rev[x - ('a' - 'A')] = i_;
+ } else if (transform == .Crockford) {
+ switch (x) {
+ '0' => {
+ rev['O'] = i_;
+ rev['o'] = i_;
+ },
+ '1' => {
+ rev['I'] = i_;
+ rev['i'] = i_;
+ rev['L'] = i_;
+ rev['l'] = i_;
+ rev['1'] = i_;
+ },
+ else => {},
+ }
+ if (x >= 'A' and x <= 'Z') {
+ rev[x + ('a' - 'A')] = i_;
+ }
+ }
+ }
+ rev[pad] = 0;
+ return rev;
+ }
+ const dict_rfc4648 = buildReverse(alphabet_rfc4648, .Lowercase);
+ const dict_z_base_32 = buildReverse(alphabet_z_base_32, .Uppercase);
+ const dict_crockford = buildReverse(alphabet_crockford, .Crockford);
+ const dict_hex = buildReverse(alphabet_hex, .Lowercase);
+ const dict_geohash = buildReverse(alphabet_geohash, .Uppercase);
+ const dict_wordsafe = buildReverse(alphabet_wordsafe, .None);
+
+ const Scheme = enum { rfc4648, z_base_32, crockford, hex, geohash, wordsafe };
+
+ inline fn encodeMask(alphabet: *const [32]u8, out: *[8]u8, in: *const [5]u8) void {
+ // 0 1 2 3
+ // 0123456789012345678901234567890123456789
+ // <===0==><===1==><===2==><===3==><===4==>
+ // <=a=><=b=><=c=><=d=><=e=><=f=><=g=><=h=>
+ const a = in[0] >> 3;
+ const b = (in[0] << 2 & 0b11100) | (in[1] >> 6 & 0b00011);
+ const c = in[1] >> 1 & 0b11111;
+ const d = (in[1] << 4 & 0b10000) | (in[2] >> 4 & 0b01111);
+ const e = (in[2] << 1 & 0b11110) | (in[3] >> 7 & 0b00001);
+ const f = in[3] >> 2 & 0b11111;
+ const g = (in[3] << 3 & 0b11000) | (in[4] >> 5 & 0b00111);
+ const h = in[4] & 0b11111;
+ out[0] = alphabet[a];
+ out[1] = alphabet[b];
+ out[2] = alphabet[c];
+ out[3] = alphabet[d];
+ out[4] = alphabet[e];
+ out[5] = alphabet[f];
+ out[6] = alphabet[g];
+ out[7] = alphabet[h];
+ }
+ /// How many characters with partial data will be emitted for a message with
+ /// length `i` mod 5.
+ const incomplete: [5]usize = .{ 0, 2, 4, 5, 7 };
+ fn encode(in: []const u8, out: []u8, scheme: Scheme) !usize {
+ const uses_padding = switch (scheme) {
+ // Slightly opinionated, pardon.
+ .rfc4648, .hex => true,
+ else => false,
+ };
+ const alphabet = switch (scheme) {
+ .rfc4648 => alphabet_rfc4648,
+ .z_base_32 => alphabet_z_base_32,
+ .crockford => alphabet_crockford,
+ .hex => alphabet_hex,
+ .geohash => alphabet_geohash,
+ .wordsafe => alphabet_wordsafe,
+ };
+
+ const odd = @mod(in.len, 5);
+ var enc_len = if (uses_padding)
+ @divTrunc(in.len + 4, 5) * 8
+ else
+ @divTrunc(in.len, 5) * 8 + incomplete[odd];
+ if (out.len < enc_len) return error.BufferOverflow;
+
+ const limit = in.len - odd;
+ var i = @as(usize, 0);
+ var j = @as(usize, 0);
+ while (i < limit) : ({
+ i += 5;
+ j += 8;
+ }) {
+ encodeMask(alphabet, out[j..][0..8], in[i..][0..5]);
+ }
+
+ const partials = incomplete[odd];
+ var partial_in: [5]u8 = .{0} ** 5;
+ var tmp: [8]u8 = undefined;
+ std.mem.copy(u8, partial_in[0..odd], in[i..(i + odd)]);
+ encodeMask(alphabet, &tmp, &partial_in);
+ if (uses_padding and odd > 0) {
+ std.mem.set(u8, tmp[partials..], pad);
+ std.mem.copy(u8, out[j..], &tmp);
+ j += tmp.len;
+ } else {
+ std.mem.copy(u8, out[j..], tmp[0..partials]);
+ j += partials;
+ }
+ return j;
+ }
+
+ inline fn decodeUnmask(out: *[5]u8, in: *[8]u8) void {
+ // 0 1 2 3
+ // 0123456789012345678901234567890123456789
+ // <===a==><===b==><===c==><===d==><===e==>
+ // <=0=><=1=><=2=><=3=><=4=><=5=><=6=><=7=>
+ const a = in[0] << 3 | in[1] >> 2;
+ const b = (in[1] << 6 & 0b1100_0000) | (in[2] << 1) | (in[3] >> 4 & 1);
+ const c = (in[3] << 4 & 0b1111_0000) | (in[4] >> 1 & 0b0000_1111);
+ const d = (in[4] << 7 & 0b1000_0000) | (in[5] << 2) | (in[6] >> 3 & 0b111);
+ const e = (in[6] << 5) | in[7];
+ out[0] = a;
+ out[1] = b;
+ out[2] = c;
+ out[3] = d;
+ out[4] = e;
+ }
+
+ // Given `i` odd characters, how many complete bytes do they form.
+ const decode_incomplete: [8]u8 = .{ 0, 0, 1, 1, 2, 3, 3, 4 };
+
+ fn decode(in: []const u8, out: []u8, scheme: Scheme) !usize {
+ if (in.len == 0) {
+ return 0;
+ }
+ var in_len = in.len;
+ var padding = @as(usize, 0);
+ var i = in.len - 1;
+ while (i >= 0) : (i -= 1) {
+ if (in[i] == pad) {
+ padding += 1;
+ } else {
+ break;
+ }
+ }
+ in_len -= padding;
+ const odd = @mod(in_len, 8);
+
+ var dec_len = @divTrunc(in_len, 8) * 5;
+ dec_len += decode_incomplete[odd];
+ if (out.len < dec_len) return error.BufferOverflow;
+
+ const dict = switch (scheme) {
+ .rfc4648 => dict_rfc4648,
+ .z_base_32 => dict_z_base_32,
+ .crockford => dict_crockford,
+ .hex => dict_hex,
+ .geohash => dict_geohash,
+ .wordsafe => dict_wordsafe,
+ };
+
+ var block: [8]u8 = undefined;
+ const limit = in_len - odd;
+ i = 0;
+ var j = @as(usize, 0);
+ while (i < limit) : ({
+ i += 8;
+ j += 5;
+ }) {
+ std.mem.copy(u8, block[0..], in[i..][0..8]);
+ var set = @as(u8, 0);
+ for (block) |*x| {
+ x.* = dict[x.*];
+ set |= x.*;
+ }
+ if (set & 0x80 != 0) return error.InvalidEncoding;
+
+ decodeUnmask(out[j..][0..5], &block);
+ }
+
+ std.mem.set(u8, block[0..], pad);
+ std.mem.copy(u8, block[0..], in[i..(i + odd)]);
+
+ var set = @as(u8, 0);
+ for (block[0..odd]) |*x| {
+ x.* = dict[x.*];
+ set |= x.*;
+ }
+ if (set & 0x80 != 0) return error.InvalidEncoding;
+
+ var out_partial: [5]u8 = undefined;
+ decodeUnmask(out_partial[0..5], &block);
+ //std.debug.print("[decode] {s} (partial: {
+ std.mem.copy(u8, out[j..], out_partial[0..decode_incomplete[odd]]);
+ j += decode_incomplete[odd];
+
+ return j;
+ }
};
const Base64 = struct {
@@ 29,7 242,7 @@ const Base64 = struct {
out[2] = alphabet[(b << 2 & 0b111100) | (c >> 6 & 0b000011)];
out[3] = alphabet[c & 0b111111];
}
- fn encode(in: []const u8, out: []u8) EncDecError!usize {
+ fn encode(in: []const u8, out: []u8) !usize {
// Pre-check sizes to avoid unnecessary branching.
const sym = @divTrunc(in.len + 2, 3) * 4;
if (out.len < sym) {
@@ 77,7 290,7 @@ const Base64 = struct {
tgt[1] = (b << 4 & 0b1111_0000) | (c >> 2 & 0b0000_1111);
tgt[2] = (c << 6 & 0b1100_0000) | d;
}
- fn decode(in: []const u8, out: []u8) EncDecError!usize {
+ fn decode(in: []const u8, out: []u8) !usize {
if (in.len == 0) {
return 0;
}
@@ 166,13 379,66 @@ const Base64 = struct {
}
};
+const Base32Scheme = enum(c_uint) {
+ zero,
+ rfc4648,
+ z_base_32,
+ crockford,
+ hex,
+ geohash,
+ wordsafe,
+ _,
+
+ fn fromInternal(scheme: Base32.Scheme) Base32Scheme {
+ return switch (scheme) {
+ .rfc4648 => .rfc4648,
+ .z_base_32 => .z_base_32,
+ .crockford => .crockford,
+ .hex => .hex,
+ .geohash => .geohash,
+ .wordsafe => .wordsafe,
+ };
+ }
+ fn toInternal(self: Base32Scheme) ?Base32.Scheme {
+ return switch (self) {
+ .rfc4648 => .rfc4648,
+ .z_base_32 => .z_base_32,
+ .crockford => .crockford,
+ .hex => .hex,
+ .geohash => .geohash,
+ .wordsafe => .wordsafe,
+ else => null,
+ };
+ }
+};
+
+export fn base_32_encode(in_buf: [*]const u8, in_len: usize, out_buf: [*]u8, out_len: usize, scheme: Base32Scheme) isize {
+ const in = in_buf[0..in_len];
+ const out = out_buf[0..out_len];
+ const sch = scheme.toInternal() orelse return -3;
+
+ return @intCast(isize, Base32.encode(in, out, sch) catch |err| switch (err) {
+ error.BufferOverflow => return -1,
+ });
+}
+
+export fn base_32_decode(in_buf: [*]const u8, in_len: usize, out_buf: [*]u8, out_len: usize, scheme: Base32Scheme) isize {
+ const in = in_buf[0..in_len];
+ const out = out_buf[0..out_len];
+ const sch = scheme.toInternal() orelse return -3;
+
+ return @intCast(isize, Base32.decode(in, out, sch) catch |err| switch (err) {
+ error.BufferOverflow => return -1,
+ error.InvalidEncoding => return -2,
+ });
+}
+
export fn base_64_encode(in_buf: [*]const u8, in_len: usize, out_buf: [*]u8, out_len: usize) isize {
const in = in_buf[0..in_len];
const out = out_buf[0..out_len];
return @intCast(isize, Base64.encode(in, out) catch |err| switch (err) {
error.BufferOverflow => return -1,
- error.InvalidEncoding => unreachable,
});
}
@@ 211,6 477,94 @@ fn fromHex(comptime src: []const u8) []c
}
const alloc = std.testing.allocator;
const expectEqualSlices = std.testing.expectEqualSlices;
+
+const discard_medicine = "Discard medicine more than two years old.";
+
+test "base32 encode" {
+ const TestCase = struct {
+ scheme: Base32Scheme,
+ vector: []const u8,
+ expected: []const u8,
+
+ const Self = @This();
+ fn tc(scheme: Base32Scheme, vector: []const u8, expected: []const u8) Self {
+ return .{
+ .scheme = scheme,
+ .vector = vector,
+ .expected = expected,
+ };
+ }
+ };
+ const tc = TestCase.tc;
+
+ for ([_]TestCase{
+ tc(.rfc4648, "", ""),
+ tc(.rfc4648, &.{0}, "AA======"),
+ tc(.rfc4648, &.{ 0, 0 }, "AAAA===="),
+ tc(.rfc4648, &.{ 0, 0, 0 }, "AAAAA==="),
+ tc(.rfc4648, &.{ 0, 0, 0, 0 }, "AAAAAAA="),
+ tc(.rfc4648, &.{ 0, 0, 0, 0, 0 }, "AAAAAAAA"),
+ tc(.rfc4648, "hello", "NBSWY3DP"),
+ tc(.rfc4648, discard_medicine, "IRUXGY3BOJSCA3LFMRUWG2LOMUQG233SMUQHI2DBNYQHI53PEB4WKYLSOMQG63DEFY======"),
+ tc(.z_base_32, discard_medicine, "etwzga5bqj1ny5mfctwsg4mqcwog4551cwo8e4dbpao8e75xrbhskam1qcog65drfa"),
+ tc(.crockford, discard_medicine, "8HMQ6RV1E9J20VB5CHMP6TBECMG6TVVJCMG78T31DRG78XVF41WPARBJECG6YV345R"),
+ tc(.hex, discard_medicine, "8HKN6OR1E9I20RB5CHKM6QBECKG6QRRICKG78Q31DOG78TRF41SMAOBIECG6UR345O======"),
+ tc(.geohash, discard_medicine, "8jnr6sv1f9k20vc5djnq6ucfdnh6uvvkdnh78u31esh78xvg41wqbsckfdh6yv345s"),
+ tc(.wordsafe, discard_medicine, "CVch8jq3PFW42qH7JVcg8pHPJcR8pqqWJcR9Cp53MjR9CvqQ63rgGjHWPJR8wq567j"),
+ }) |c| {
+ const actual = try alloc.alloc(u8, @maximum(c.expected.len, 1));
+ defer alloc.free(actual);
+ const len = base_32_encode(c.vector.ptr, c.vector.len, actual.ptr, actual.len, c.scheme);
+ if (len < 0) {
+ std.debug.print("base32 encode: buffer overflow `{s}` ({})\n", .{ c.vector, c.scheme });
+ return error.BufferOverflow;
+ }
+ try expectEqualSlices(u8, c.expected, actual[0..@intCast(usize, len)]);
+ }
+}
+
+test "base32 decode" {
+ const TestCase = struct {
+ scheme: Base32.Scheme,
+ vector: []const u8,
+ expected: []const u8,
+ const Self = @This();
+ fn tc(scheme: Base32.Scheme, vector: []const u8, expected: []const u8) Self {
+ return .{ .scheme = scheme, .vector = vector, .expected = expected };
+ }
+ };
+ const tc = TestCase.tc;
+
+ for ([_]TestCase{
+ tc(.rfc4648, "", ""),
+ tc(.rfc4648, "AA======", &.{0}),
+ tc(.rfc4648, "AAAA====", &.{ 0, 0 }),
+ tc(.rfc4648, "AAAAA===", &.{ 0, 0, 0 }),
+ tc(.rfc4648, "AAAAAAA=", &.{ 0, 0, 0, 0 }),
+ tc(.rfc4648, "AAAAAAAA", &(.{0} ** 5)),
+ tc(.rfc4648, "NBSWY3DP", "hello"),
+ tc(.rfc4648, "nbswy3dp", "hello"),
+ tc(.rfc4648, "NBXWYYI=", "hola"),
+ tc(.rfc4648, "NBXWYYI", "hola"),
+ tc(.rfc4648, "nbxwyyi", "hola"),
+ tc(.crockford, "D1QPRR8", "hola"),
+ tc(.crockford, "DIQPRR8", "hola"),
+ tc(.crockford, "DLQPRR8", "hola"),
+ tc(.crockford, "8i04og2O", "@@@@@"),
+ tc(.rfc4648, "IRUXGY3BOJSCA3LFMRUWG2LOMUQG233SMUQHI2DBNYQHI53PEB4WKYLSOMQG63DEFY======", discard_medicine),
+ tc(.z_base_32, "etwzga5bqj1ny5mfctwsg4mqcwog4551cwo8e4dbpao8e75xrbhskam1qcog65drfa", discard_medicine),
+ tc(.crockford, "8HMQ6RV1E9J20VB5CHMP6TBECMG6TVVJCMG78T31DRG78XVF41WPARBJECG6YV345R", discard_medicine),
+ tc(.hex, "8HKN6OR1E9I20RB5CHKM6QBECKG6QRRICKG78Q31DOG78TRF41SMAOBIECG6UR345O======", discard_medicine),
+ tc(.geohash, "8jnr6sv1f9k20vc5djnq6ucfdnh6uvvkdnh78u31esh78xvg41wqbsckfdh6yv345s", discard_medicine),
+ tc(.wordsafe, "CVch8jq3PFW42qH7JVcg8pHPJcR8pqqWJcR9Cp53MjR9CvqQ63rgGjHWPJR8wq567j", discard_medicine),
+ }) |c| {
+ const actual = try alloc.alloc(u8, @maximum(c.expected.len, 1));
+ defer alloc.free(actual);
+ const len = try Base32.decode(c.vector, actual, c.scheme);
+ try expectEqualSlices(u8, c.expected, actual[0..len]);
+ }
+}
+
test "base64 encode" {
for ([_][2][]const u8{
.{ "", "" },
@@ 218,7 572,7 @@ test "base64 encode" {
.{ ([_]u8{0})[0..] ** 2, "AAA=" },
.{ ([_]u8{0})[0..] ** 3, "AAAA" },
.{ "hey", "aGV5" },
- .{ "Discard medicine more than two years old.", "RGlzY2FyZCBtZWRpY2luZSBtb3JlIHRoYW4gdHdvIHllYXJzIG9sZC4=" },
+ .{ discard_medicine, "RGlzY2FyZCBtZWRpY2luZSBtb3JlIHRoYW4gdHdvIHllYXJzIG9sZC4=" },
}) |c| {
const vector = c[0];
const expected = c[1];
@@ 246,7 600,7 @@ test "base64 decode" {
.{ "AAAAAAA=", &.{ 0, 0, 0, 0, 0 } },
.{ "AAAAAAAA", &.{ 0, 0, 0, 0, 0, 0 } },
.{ "aGV5", "hey" },
- .{ "RGlzY2FyZCBtZWRpY2luZSBtb3JlIHRoYW4gdHdvIHllYXJzIG9sZC4=", "Discard medicine more than two years old." },
+ .{ "RGlzY2FyZCBtZWRpY2luZSBtb3JlIHRoYW4gdHdvIHllYXJzIG9sZC4=", discard_medicine },
}) |c| {
const vector = c[0];
const expected = c[1];