Last active
August 3, 2019 15:52
-
-
Save Tetralux/e4a2900691e61a4b821867ae41470c65 to your computer and use it in GitHub Desktop.
Quick and dirty base64 implemention in Odin
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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