255 lines
6.8 KiB
Text
255 lines
6.8 KiB
Text
#module_parameters(RunTests := false);
|
|
|
|
Base64Encode :: (str: string, $for_url := false) -> string, bool {
|
|
enc, ok := Base64Encode(str.([]u8), for_url);
|
|
return enc.(string), ok;
|
|
}
|
|
|
|
Base64Decode :: (str: string) -> string, bool {
|
|
enc, ok := Base64Decode(str.([]u8));
|
|
return enc.(string), ok;
|
|
}
|
|
|
|
Base64Encode :: ($$data: []u8, $for_url := false) -> []u8, bool {
|
|
if data.count == 0 return .[], false;
|
|
|
|
#if for_url {
|
|
Character_Set :: Encoding_Url;
|
|
}
|
|
else {
|
|
Character_Set :: Encoding_Standard;
|
|
}
|
|
|
|
padded := strings.contains(data.(string), Padding_Character);
|
|
encoded := basic.NewArray(EncodedLength(data.count, padded), u8, true);
|
|
|
|
src_idx := 0;
|
|
dst_idx := 0;
|
|
src_max := (data.count / 3) * 3;
|
|
while src_idx < src_max {
|
|
value: u64;
|
|
|
|
parts := (*value).(*u8);
|
|
(parts + 0).* = xx data[src_idx + 2];
|
|
(parts + 1).* = xx data[src_idx + 1];
|
|
(parts + 2).* = xx data[src_idx + 0];
|
|
|
|
encoded[dst_idx + 0] = Character_Set[value >> 18 & 0x3F];
|
|
encoded[dst_idx + 1] = Character_Set[value >> 12 & 0x3F];
|
|
encoded[dst_idx + 2] = Character_Set[value >> 6 & 0x3F];
|
|
encoded[dst_idx + 3] = Character_Set[value & 0x3F];
|
|
|
|
src_idx += 3;
|
|
dst_idx += 4;
|
|
}
|
|
|
|
remaining := data.count - src_idx;
|
|
if !remaining then return encoded, true;
|
|
|
|
value := data[src_idx + 0].(u64) << 16;
|
|
if remaining == 2 {
|
|
value |= data[src_idx + 1].(u64) << 8;
|
|
}
|
|
|
|
encoded[dst_idx + 0] = Character_Set[value >> 18 & 0x3F];
|
|
encoded[dst_idx + 1] = Character_Set[value >> 12 & 0x3F];
|
|
|
|
if remaining == {
|
|
case 2;
|
|
if padded {
|
|
encoded[dst_idx + 2] = Padding_Character;
|
|
encoded[dst_idx + 3] = Padding_Character;
|
|
}
|
|
case 1;
|
|
encoded[dst_idx + 2] = Character_Set[value >> 6 & 0x3F];
|
|
if padded {
|
|
encoded[dst_idx + 3] = Padding_Character;
|
|
}
|
|
}
|
|
|
|
return encoded, true;
|
|
}
|
|
|
|
Base64Decode :: (data: []u8) -> []u8, bool {
|
|
if !data.count return .[], false;
|
|
|
|
lookup :: (c: u8) -> u8 #expand {
|
|
if c >= #char "A" && c <= #char "Z" return c - #char "A";
|
|
if c >= #char "a" && c <= #char "z" return c - 71;
|
|
if c >= #char "0" && c <= #char "9" return c + 4;
|
|
if c == #char "+" || c == #char "-" return 62;
|
|
if c == #char "/" || c == #char "_" return 63;
|
|
return 255;
|
|
}
|
|
|
|
a4_to_a3 :: () #expand {
|
|
`a3[0] = (`a4[0] << 2) + ((`a4[1] & 0x30) >> 4);
|
|
`a3[1] = ((`a4[1] & 0xF) << 4) + ((`a4[2] & 0x3C) >> 2);
|
|
`a3[2] = ((`a4[2] & 0x3) << 6) + `a4[3];
|
|
}
|
|
|
|
padding := 0;
|
|
{
|
|
i := data.count - 1;
|
|
while i >= 0 {
|
|
if data[i] == Padding_Character {
|
|
padding += 1;
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
|
|
i -= 1;
|
|
}
|
|
}
|
|
|
|
a3: [3]u8;
|
|
a4: [4]u8;
|
|
|
|
i := 0;
|
|
offset := 0;
|
|
|
|
decoded := basic.NewArray(((6 * data.count) / 8) - padding, u8, true);
|
|
for 0..data.count - 1 {
|
|
chr := data[it];
|
|
if chr == Padding_Character break;
|
|
|
|
a4[i] = chr;
|
|
i += 1;
|
|
|
|
if i == 4 {
|
|
for 0..3 a4[it] = lookup(a4[it]);
|
|
|
|
a4_to_a3();
|
|
|
|
for 0..2 {
|
|
decoded.data[offset] = a3[it];
|
|
offset += 1;
|
|
}
|
|
|
|
i = 0;
|
|
}
|
|
}
|
|
|
|
if i > 0 {
|
|
for 0..3 a4[it] = lookup(a4[it]);
|
|
|
|
a4_to_a3();
|
|
|
|
for 0..i - 1 {
|
|
(decoded.data + offset + it).* = a3[it];
|
|
}
|
|
}
|
|
|
|
return decoded, true;
|
|
}
|
|
|
|
|
|
#scope_file
|
|
|
|
Padding_Character :: #char "=";
|
|
Encoding_Url :: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
Encoding_Standard :: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
|
|
EncodedLength :: (count: int, with_padding := false) -> int {
|
|
if with_padding then return (count + 2) / 3 * 4;
|
|
return (count * 8 + 5) / 6;
|
|
}
|
|
|
|
#import "jc";
|
|
|
|
basic :: #import "Basic"; // @future
|
|
strings :: #import "String"; // @future
|
|
|
|
|
|
// ----------------------------------------------------------
|
|
// TESTS
|
|
// ----------------------------------------------------------
|
|
|
|
#if RunTests #run {
|
|
Test("encodes", t => {
|
|
str :: "Hello, World";
|
|
|
|
encoded, ok := Base64Encode(str);
|
|
Expect(ok, "'%' did not properly encode!", str);
|
|
|
|
expected :: "SGVsbG8sIFdvcmxk";
|
|
Expect(encoded == expected, "wanted: '%', received: '%'", expected, encoded);
|
|
});
|
|
|
|
Test("decodes", (t) => {
|
|
encoded_str :: "SGVsbG8sIFdvcmxk";
|
|
|
|
decoded, ok := Base64Decode(encoded_str);
|
|
Expect(ok, "'%' did not properly decode!", encoded_str);
|
|
|
|
expected :: "Hello, World";
|
|
Expect(decoded == expected, "wanted: '%', received: '%'", expected, decoded);
|
|
});
|
|
|
|
Test("encodes/decodes padding", (t) => {
|
|
str :: "VkdocGN5QnBjeUJ6YjIxbGRHaHBibWNnYlc5eVpTQmpiMjF3YkdWNExDQnBkQ0J6YUc5MWJHUWdhR0YyWlNCd1lXUmthVzVuUHc9PQ==";
|
|
|
|
first_decode, f_ok := Base64Decode(str);
|
|
Expect(f_ok, "first decode failed!");
|
|
|
|
re_encode, r_ok := Base64Encode(first_decode);
|
|
Expect(r_ok, "re-encode failed!");
|
|
|
|
second_decode, s_ok := Base64Decode(re_encode);
|
|
Expect(s_ok, "second decode failed!");
|
|
|
|
for 0..first_decode.count - 1 {
|
|
Expect(first_decode[it] == second_decode[it], "Decoded byte '%' did not match expected output", it);
|
|
}
|
|
});
|
|
|
|
Test("encodes/decodes struct", (t) => {
|
|
Foo :: struct {
|
|
first: u64;
|
|
second: u64;
|
|
third: u64;
|
|
forth: u64;
|
|
}
|
|
|
|
foo: Foo;
|
|
foo.first = 123;
|
|
foo.second = 456;
|
|
foo.third = 789;
|
|
foo.forth = 100;
|
|
|
|
enc_builder: basic.String_Builder;
|
|
basic.print_to_builder(*enc_builder, "%,%,%,%", foo.first, foo.second, foo.third, foo.forth);
|
|
|
|
encoded, e_ok := Base64Encode(basic.builder_to_string(*enc_builder));
|
|
Expect(e_ok, "Encode of structure failed!");
|
|
|
|
decoded, d_ok := Base64Decode(encoded);
|
|
Expect(d_ok, "Decode of encoded structure failed!");
|
|
|
|
parts := strings.split(decoded, ",");
|
|
Expect(parts.count == 4, "Invalid number of parts after decode: %", parts.count);
|
|
|
|
bar: Foo = ---;
|
|
|
|
{
|
|
ptr := (*bar).(*u8);
|
|
info := Foo.(*Type_Info_Struct);
|
|
|
|
for info.members {
|
|
value, ok := strings.parse_int(*parts[it_index], u64);
|
|
Expect(ok, "Integer parse of value % ('%') failed!", it_index, parts[it_index]);
|
|
|
|
offset := (ptr + it.offset_in_bytes).(*u64);
|
|
offset.*= value;
|
|
}
|
|
}
|
|
|
|
Expect(foo.first == bar.first, "Foo.first (%, %) didn't match between encoding/decoding!", foo.first, bar.first);
|
|
Expect(foo.second == bar.second, "Foo.second (%, %) didn't match between encoding/decoding!", foo.second, bar.second);
|
|
Expect(foo.third == bar.third, "Foo.third (%, %) didn't match between encoding/decoding!", foo.third, bar.third);
|
|
Expect(foo.forth == bar.forth, "Foo.forth (%, %) didn't match between encoding/decoding!", foo.forth, bar.forth);
|
|
});
|
|
}
|
|
|
|
|