Skip to content

Instantly share code, notes, and snippets.

@Tetralux
Last active August 3, 2019 15:52
Show Gist options
  • Save Tetralux/e4a2900691e61a4b821867ae41470c65 to your computer and use it in GitHub Desktop.
Save Tetralux/e4a2900691e61a4b821867ae41470c65 to your computer and use it in GitHub Desktop.
Quick and dirty base64 implemention in Odin
/*
A simple base64 encoder/decoder.
Written by Tetralux@github.
Created December 2018.
*/
package base64;
using import "core:fmt";
import "core:strings";
ENC_TABLE := [64]byte { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' };
DEC_TABLE := [128]int {
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, 62, -1, -1, -1, 63,
52, 53, 54, 55, 56, 57, 58, 59,
60, 61, -1, -1, -1, -1, -1, -1,
-1, 0, 1, 2, 3, 4, 5, 6,
7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, -1, -1, -1, -1, -1,
-1, 26, 27, 28, 29, 30, 31, 32,
33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48,
49, 50, 51, -1, -1, -1, -1, -1
};
PADDING :: '=';
encode :: proc(auto_cast data: []byte) -> string #no_bounds_check {
length := len(data);
if length == 0 do return "";
b := strings.make_builder();
for i := 0; i < length; i += 3 {
c0, c1, c2 := int(data[i]), 0, 0;
if i+1 < length do c1 = int(data[i+1]);
if i+2 < length do c2 = int(data[i+2]);
block := (c0 << 16) | (max(c1, 0) << 8) | max(c2, 0);
strings.write_byte(&b, ENC_TABLE[block >> 18 & 63]);
strings.write_byte(&b, ENC_TABLE[block >> 12 & 63]);
strings.write_byte(&b, i + 1 >= length ? PADDING : ENC_TABLE[block >> 6 & 63]);
strings.write_byte(&b, i + 2 >= length ? PADDING : ENC_TABLE[block & 63]);
}
return strings.to_string(b);
}
decode :: proc(auto_cast str: string) -> (bytes: []byte, ok: bool, bad_char_index: int) #no_bounds_check #require_results {
length := len(str);
if length == 0 do return nil, true, -1;
b := make([dynamic]u8, 0, len(str) * 4 / 3);
for i := 0; i < length; i += 4 {
b0, b1, b2, b3 := int(str[i]), int(str[i+1]), 0, 0;
if i+2 < length do b2 = int(str[i+2]);
if i+3 < length do b3 = int(str[i+3]);
d0, d1, d2, d3: int;
d0 = to_bits(b0);
if d0 == -1 do return nil, false, i;
d1 = to_bits(b1);
if d1 == -1 do return nil, false, i+1;
d2f, d3f := false, false;
if i+2 < length && b2 != '=' {
d2 = to_bits(b2);
if d2 == -1 do return nil, false, i+2;
d2f = true;
}
if i+3 < length && b3 != '=' {
d3 = to_bits(b3);
if d3 == -1 do return nil, false, i+3;
d3f = true;
}
block := (d0 << 18) | (d1 << 12) | (d2 << 6) | d3;
append(&b, byte(block >> 16));
if d2f do append(&b, byte(block >> 8));
if d3f do append(&b, byte(block >> 0));
}
return b[:], true, -1;
}
to_bits :: inline proc(auto_cast c: int) -> int {
return c <= 127 ? DEC_TABLE[c] : -1;
}
// TODO: Use mem.equal when available.
/*
equal :: proc(a, b: []byte) -> bool {
if len(a) != len(b) do return false;
// if &a[0] == &b[0] do return true;
for i in 0..len(a)-1 {
if a[i] != b[i] do return false;
}
return true;
}
// @CompilerBug: Produces no error when these are used, but no executable is generated.
// tobase64 :: base64_encode;
// unbase64 :: base64_decode;
// @Test
import "core:mem";
import "core:time";
import "core:hash";
main :: proc() {
DATA_SIZE :: 50000;
data := make([]byte, DATA_SIZE);
buffer := make([]byte, DATA_SIZE*9); // I'm not smart enough to figure out why DATA_SIZE*2 is not enough right now. XD
// NOTE: This crashes for some reason. :CrashingArena
// arena: mem.Arena;
// mem.init_arena(&arena, buffer);
// context.allocator = mem.arena_allocator(&arena);
encode_sum := 0;
decode_sum := 0;
passes := 0;
random: [1]byte = {0xff};
#no_bounds_check for i := 4; i < len(data); i += 1 {
scratch: mem.Scratch_Allocator;
mem.scratch_allocator_init(&scratch, buffer);
allocator := mem.scratch_allocator(&scratch);
context.allocator = allocator;
// :CrashingArena
// mark := mem.begin_arena_temp_memory(&arena);
// defer mem.end_arena_temp_memory(mark);
passes += 1;
random[0] = byte(hash.crc32(random[:]));
data[i] = random[0];
s := data[:i];
start := time.now();
t := encode(s);
time_to_encode := time.diff(start, time.now());
encode_sum += int(time_to_encode);
start = time.now();
u, ok, bad_char_index := decode(t);
if u == nil {
printf("failed to decode char at index %v\n", bad_char_index);
printf(" ... '%v'\n", t[bad_char_index-10:bad_char_index+10]);
panicf("decoding failed with %v base64 chars\n", len(t));
}
time_to_decode := time.diff(start, time.now());
decode_sum += int(time_to_decode);
// fmt.printf("'%v', %v\n\n", u, e);
assertf(equal(u, s), "original(%v)='%v' => encoded='%v' => decoded(%v)='%v'", len(s), string(s), string(t), len(string(u)), string(u));
// fmt.printf("n=%v: e=%v d=%v\n", len(s), time_to_encode, time_to_decode);
}
avg_enc_ms := f64(time.duration_nanoseconds(time.Duration(encode_sum))) / f64(passes) / 1000000;
avg_dec_ms := f64(time.duration_nanoseconds(time.Duration(decode_sum))) / f64(passes) / 1000000;
println("timings:");
printf("average encoding time %v ms\n", avg_enc_ms);
printf("average decoding time %v ms\n", avg_dec_ms);
}
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment