Skip to content

Commit 6fd8a07

Browse files
paul-leydiertjgurwara99raklaptudirmsiriak
authored
feat: add Conversion/base64 algorithm (#437)
* feat: base64 encoding * test: base64 encoding * docs: Added base64 declaration to README.md * feat: use string builder to drastically improve efficiency of Encode * docs: formatting * feat: base64 decoding * test: base64 decoding * docs: added base64 Decode function to README.md * test: test base64 Encode and Decode inverse functions * docs: base64 Decode docstring * docs: improve package documentation Co-authored-by: Taj <[email protected]> * feat: remove usage of predefined return values * feat: base64 move to conversion package * fix: remove deleted line in README.md * Update conversion/base64.go * Update conversion/base64.go Co-authored-by: Taj <[email protected]> Co-authored-by: Rak Laptudirm <[email protected]> Co-authored-by: Andrii Siriak <[email protected]>
1 parent 57a20cf commit 6fd8a07

File tree

2 files changed

+192
-0
lines changed

2 files changed

+192
-0
lines changed

conversion/base64.go

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// base64.go
2+
// description: The base64 encoding algorithm as defined in the RFC4648 standard.
3+
// author: [Paul Leydier] (https://github.com/paul-leydier)
4+
// ref: https://datatracker.ietf.org/doc/html/rfc4648#section-4
5+
// ref: https://en.wikipedia.org/wiki/Base64
6+
// see base64_test.go
7+
8+
package conversion
9+
10+
import (
11+
"strings" // Used for efficient string builder (more efficient than simply appending strings)
12+
)
13+
14+
const Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
15+
16+
// Base64Encode encodes the received input bytes slice into a base64 string.
17+
// The implementation follows the RFC4648 standard, which is documented
18+
// at https://datatracker.ietf.org/doc/html/rfc4648#section-4
19+
func Base64Encode(input []byte) string {
20+
var sb strings.Builder
21+
// If not 24 bits (3 bytes) multiple, pad with 0 value bytes, and with "=" for the output
22+
var padding string
23+
for i := len(input) % 3; i > 0 && i < 3; i++ {
24+
var zeroByte byte
25+
input = append(input, zeroByte)
26+
padding += "="
27+
}
28+
29+
// encode 24 bits per 24 bits (3 bytes per 3 bytes)
30+
for i := 0; i < len(input); i += 3 {
31+
// select 3 8-bit input groups, and re-arrange them into 4 6-bit groups
32+
// the literal 0x3F corresponds to the byte "0011 1111"
33+
// the operation "byte & 0x3F" masks the two left-most bits
34+
group := [4]byte{
35+
input[i] >> 2,
36+
(input[i]<<4)&0x3F + input[i+1]>>4,
37+
(input[i+1]<<2)&0x3F + input[i+2]>>6,
38+
input[i+2] & 0x3F,
39+
}
40+
41+
// translate each group into a char using the static map
42+
for _, b := range group {
43+
sb.WriteString(string(Alphabet[int(b)]))
44+
}
45+
}
46+
encoded := sb.String()
47+
48+
// Apply the output padding
49+
encoded = encoded[:len(encoded)-len(padding)] + padding[:]
50+
51+
return encoded
52+
}
53+
54+
// Base64Decode decodes the received input base64 string into a byte slice.
55+
// The implementation follows the RFC4648 standard, which is documented
56+
// at https://datatracker.ietf.org/doc/html/rfc4648#section-4
57+
func Base64Decode(input string) []byte {
58+
padding := strings.Count(input, "=") // Number of bytes which will be ignored
59+
var decoded []byte
60+
61+
// select 4 6-bit input groups, and re-arrange them into 3 8-bit groups
62+
for i := 0; i < len(input); i += 4 {
63+
// translate each group into a byte using the static map
64+
byteInput := [4]byte{
65+
byte(strings.IndexByte(Alphabet, input[i])),
66+
byte(strings.IndexByte(Alphabet, input[i+1])),
67+
byte(strings.IndexByte(Alphabet, input[i+2])),
68+
byte(strings.IndexByte(Alphabet, input[i+3])),
69+
}
70+
71+
group := [3]byte{
72+
byteInput[0]<<2 + byteInput[1]>>4,
73+
byteInput[1]<<4 + byteInput[2]>>2,
74+
byteInput[2]<<6 + byteInput[3],
75+
}
76+
77+
decoded = append(decoded, group[:]...)
78+
}
79+
80+
return decoded[:len(decoded)-padding]
81+
}

conversion/base64_test.go

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package conversion
2+
3+
import "testing"
4+
5+
func TestBase64Encode(t *testing.T) {
6+
testCases := []struct {
7+
in string
8+
expected string
9+
}{
10+
{"Hello World!", "SGVsbG8gV29ybGQh"}, // multiple of 3 byte length (multiple of 24-bits)
11+
{"Hello World!a", "SGVsbG8gV29ybGQhYQ=="}, // multiple of 3 byte length + 1
12+
{"Hello World!ab", "SGVsbG8gV29ybGQhYWI="}, // multiple of 3 byte length + 2
13+
{"", ""}, // empty byte slice
14+
{"6", "Ng=="}, // short text
15+
{"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdCwgc2VkIGRvIGVpdXNtb2QgdGVtcG9yIGluY2lkaWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWduYSBhbGlxdWEuIFV0IGVuaW0gYWQgbWluaW0gdmVuaWFtLCBxdWlzIG5vc3RydWQgZXhlcmNpdGF0aW9uIHVsbGFtY28gbGFib3JpcyBuaXNpIHV0IGFsaXF1aXAgZXggZWEgY29tbW9kbyBjb25zZXF1YXQuIER1aXMgYXV0ZSBpcnVyZSBkb2xvciBpbiByZXByZWhlbmRlcml0IGluIHZvbHVwdGF0ZSB2ZWxpdCBlc3NlIGNpbGx1bSBkb2xvcmUgZXUgZnVnaWF0IG51bGxhIHBhcmlhdHVyLiBFeGNlcHRldXIgc2ludCBvY2NhZWNhdCBjdXBpZGF0YXQgbm9uIHByb2lkZW50LCBzdW50IGluIGN1bHBhIHF1aSBvZmZpY2lhIGRlc2VydW50IG1vbGxpdCBhbmltIGlkIGVzdCBsYWJvcnVtLg=="}, // Long text
16+
}
17+
18+
for _, tc := range testCases {
19+
result := Base64Encode([]byte(tc.in))
20+
if result != tc.expected {
21+
t.Fatalf("Base64Encode(%s) = %s, want %s", tc.in, result, tc.expected)
22+
}
23+
}
24+
}
25+
26+
func BenchmarkBase64Encode(b *testing.B) {
27+
benchmarks := []struct {
28+
name string
29+
in string
30+
expected string
31+
}{
32+
{"Hello World!", "Hello World!", "SGVsbG8gV29ybGQh"}, // multiple of 3 byte length (multiple of 24-bits)
33+
{"Hello World!a", "Hello World!a", "SGVsbG8gV29ybGQhYQ=="}, // multiple of 3 byte length + 1
34+
{"Hello World!ab", "Hello World!ab", "SGVsbG8gV29ybGQhYWI="}, // multiple of 3 byte length + 2
35+
{"Empty", "", ""}, // empty byte slice
36+
{"6", "6", "Ng=="}, // short text
37+
{"Lorem ipsum", "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdCwgc2VkIGRvIGVpdXNtb2QgdGVtcG9yIGluY2lkaWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWduYSBhbGlxdWEuIFV0IGVuaW0gYWQgbWluaW0gdmVuaWFtLCBxdWlzIG5vc3RydWQgZXhlcmNpdGF0aW9uIHVsbGFtY28gbGFib3JpcyBuaXNpIHV0IGFsaXF1aXAgZXggZWEgY29tbW9kbyBjb25zZXF1YXQuIER1aXMgYXV0ZSBpcnVyZSBkb2xvciBpbiByZXByZWhlbmRlcml0IGluIHZvbHVwdGF0ZSB2ZWxpdCBlc3NlIGNpbGx1bSBkb2xvcmUgZXUgZnVnaWF0IG51bGxhIHBhcmlhdHVyLiBFeGNlcHRldXIgc2ludCBvY2NhZWNhdCBjdXBpZGF0YXQgbm9uIHByb2lkZW50LCBzdW50IGluIGN1bHBhIHF1aSBvZmZpY2lhIGRlc2VydW50IG1vbGxpdCBhbmltIGlkIGVzdCBsYWJvcnVtLg=="}, // Long text
38+
}
39+
40+
for _, bm := range benchmarks {
41+
b.Run(bm.name, func(b *testing.B) {
42+
for i := 0; i < b.N; i++ {
43+
Base64Encode([]byte(bm.in))
44+
}
45+
})
46+
}
47+
}
48+
49+
func TestBase64Decode(t *testing.T) {
50+
testCases := []struct {
51+
expected string
52+
in string
53+
}{
54+
{"Hello World!", "SGVsbG8gV29ybGQh"}, // multiple of 3 byte length (multiple of 24-bits)
55+
{"Hello World!a", "SGVsbG8gV29ybGQhYQ=="}, // multiple of 3 byte length + 1
56+
{"Hello World!ab", "SGVsbG8gV29ybGQhYWI="}, // multiple of 3 byte length + 2
57+
{"", ""}, // empty byte slice
58+
{"6", "Ng=="}, // short text
59+
{"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdCwgc2VkIGRvIGVpdXNtb2QgdGVtcG9yIGluY2lkaWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWduYSBhbGlxdWEuIFV0IGVuaW0gYWQgbWluaW0gdmVuaWFtLCBxdWlzIG5vc3RydWQgZXhlcmNpdGF0aW9uIHVsbGFtY28gbGFib3JpcyBuaXNpIHV0IGFsaXF1aXAgZXggZWEgY29tbW9kbyBjb25zZXF1YXQuIER1aXMgYXV0ZSBpcnVyZSBkb2xvciBpbiByZXByZWhlbmRlcml0IGluIHZvbHVwdGF0ZSB2ZWxpdCBlc3NlIGNpbGx1bSBkb2xvcmUgZXUgZnVnaWF0IG51bGxhIHBhcmlhdHVyLiBFeGNlcHRldXIgc2ludCBvY2NhZWNhdCBjdXBpZGF0YXQgbm9uIHByb2lkZW50LCBzdW50IGluIGN1bHBhIHF1aSBvZmZpY2lhIGRlc2VydW50IG1vbGxpdCBhbmltIGlkIGVzdCBsYWJvcnVtLg=="}, // Long text
60+
}
61+
62+
for _, tc := range testCases {
63+
result := string(Base64Decode(tc.in))
64+
if result != tc.expected {
65+
t.Fatalf("Base64Decode(%s) = %s, want %s", tc.in, result, tc.expected)
66+
}
67+
}
68+
}
69+
70+
func BenchmarkBase64Decode(b *testing.B) {
71+
benchmarks := []struct {
72+
name string
73+
expected string
74+
in string
75+
}{
76+
{"Hello World!", "Hello World!", "SGVsbG8gV29ybGQh"}, // multiple of 3 byte length (multiple of 24-bits)
77+
{"Hello World!a", "Hello World!a", "SGVsbG8gV29ybGQhYQ=="}, // multiple of 3 byte length + 1
78+
{"Hello World!ab", "Hello World!ab", "SGVsbG8gV29ybGQhYWI="}, // multiple of 3 byte length + 2
79+
{"Empty", "", ""}, // empty byte slice
80+
{"6", "6", "Ng=="}, // short text
81+
{"Lorem ipsum", "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdCwgc2VkIGRvIGVpdXNtb2QgdGVtcG9yIGluY2lkaWR1bnQgdXQgbGFib3JlIGV0IGRvbG9yZSBtYWduYSBhbGlxdWEuIFV0IGVuaW0gYWQgbWluaW0gdmVuaWFtLCBxdWlzIG5vc3RydWQgZXhlcmNpdGF0aW9uIHVsbGFtY28gbGFib3JpcyBuaXNpIHV0IGFsaXF1aXAgZXggZWEgY29tbW9kbyBjb25zZXF1YXQuIER1aXMgYXV0ZSBpcnVyZSBkb2xvciBpbiByZXByZWhlbmRlcml0IGluIHZvbHVwdGF0ZSB2ZWxpdCBlc3NlIGNpbGx1bSBkb2xvcmUgZXUgZnVnaWF0IG51bGxhIHBhcmlhdHVyLiBFeGNlcHRldXIgc2ludCBvY2NhZWNhdCBjdXBpZGF0YXQgbm9uIHByb2lkZW50LCBzdW50IGluIGN1bHBhIHF1aSBvZmZpY2lhIGRlc2VydW50IG1vbGxpdCBhbmltIGlkIGVzdCBsYWJvcnVtLg=="}, // Long text
82+
}
83+
84+
for _, bm := range benchmarks {
85+
b.Run(bm.name, func(b *testing.B) {
86+
for i := 0; i < b.N; i++ {
87+
Base64Decode(bm.in)
88+
}
89+
})
90+
}
91+
}
92+
93+
func TestBase64EncodeDecodeInverse(t *testing.T) {
94+
testCases := []struct {
95+
in string
96+
}{
97+
{"Hello World!"}, // multiple of 3 byte length (multiple of 24-bits)
98+
{"Hello World!a"}, // multiple of 3 byte length + 1
99+
{"Hello World!ab"}, // multiple of 3 byte length + 2
100+
{""}, // empty byte slice
101+
{"6"}, // short text
102+
{"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."}, // Long text
103+
}
104+
105+
for _, tc := range testCases {
106+
result := string(Base64Decode(Base64Encode([]byte(tc.in))))
107+
if result != tc.in {
108+
t.Fatalf("Base64Decode(Base64Encode(%s)) = %s, want %s", tc.in, result, tc.in)
109+
}
110+
}
111+
}

0 commit comments

Comments
 (0)