Skip to content

feat: add Conversion/base64 algorithm #437

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Nov 19, 2021
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,20 @@ Read our [Contribution Guidelines](CONTRIBUTING.md) before you contribute.

1. [`Node`](./structure/avl/avl.go#L8): No description provided.

---
</details><details>
<summary> <strong> base64 </strong> </summary>

---

##### Package base64 is an implementation of the eponymous algorithm, as defined in the RFC4648 standard. See https://datatracker.ietf.org/doc/html/rfc4648#section-4

---
##### Functions:

1. [`Encode`](./cipher/base64/base64.go#L19): Encode : encodes the given bytes slice into a base64 string.
2. [`Decode`](./cipher/base64/base64.go#L54): Decode : Decodes the given base64 string into a bytes slice.


---
</details><details>
Expand Down
80 changes: 80 additions & 0 deletions cipher/base64/base64.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Package base64
// base64.go
// description: The base64 encodage algorithm as defined in the RFC4648 standard.
// author: [Paul Leydier] (https://github.com/paul-leydier)
// ref: https://datatracker.ietf.org/doc/html/rfc4648#section-4
// ref: https://en.wikipedia.org/wiki/Base64
// see base64_test.go
package base64

import (
"strings" // Used for efficient string builder (more efficient than simply appending strings)
)

const Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

// Encode encodes the received input bytes slice into a base64 string.
// The implementation follows the RFC4648 standard, which is documented
// at https://datatracker.ietf.org/doc/html/rfc4648#section-4
func Encode(input []byte) (encoded string) {
var sb strings.Builder
// If not 24 bits (3 bytes) multiple, pad with 0 value bytes, and with "=" for the output
var padding string
for i := len(input) % 3; i > 0 && i < 3; i++ {
var zeroByte byte
input = append(input, zeroByte)
padding += "="
}

// encode 24 bits per 24 bits (3 bytes per 3 bytes)
for i := 0; i < len(input); i += 3 {
// select 3 8-bit input groups, and re-arrange them into 4 6-bit groups
// the literal 0x3F corresponds to the byte "0011 1111"
// the operation "byte & 0x3F" masks the two left-most bits
group := [4]byte{
input[i] >> 2,
(input[i]<<4)&0x3F + input[i+1]>>4,
(input[i+1]<<2)&0x3F + input[i+2]>>6,
input[i+2] & 0x3F,
}

// translate each group into a char using the static map
for _, b := range group {
sb.WriteString(string(Alphabet[int(b)]))
}
}
encoded = sb.String()

// Apply the output padding
encoded = encoded[:len(encoded)-len(padding)] + padding[:]

return
}

// Decode decodes the received input base64 string into a byte slice.
// The implementation follows the RFC4648 standard, which is documented
// at https://datatracker.ietf.org/doc/html/rfc4648#section-4
func Decode(input string) (decoded []byte) {
padding := strings.Count(input, "=") // Number of bytes which will be ignored

// select 4 6-bit input groups, and re-arrange them into 3 8-bit groups
for i := 0; i < len(input); i += 4 {
// translate each group into a byte using the static map
byteInput := [4]byte{
byte(strings.IndexByte(Alphabet, input[i])),
byte(strings.IndexByte(Alphabet, input[i+1])),
byte(strings.IndexByte(Alphabet, input[i+2])),
byte(strings.IndexByte(Alphabet, input[i+3])),
}

group := [3]byte{
byteInput[0]<<2 + byteInput[1]>>4,
byteInput[1]<<4 + byteInput[2]>>2,
byteInput[2]<<6 + byteInput[3],
}

decoded = append(decoded, group[:]...)
}

return decoded[:len(decoded)-padding]
}
111 changes: 111 additions & 0 deletions cipher/base64/base64_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package base64

import "testing"

func TestEncode(t *testing.T) {
testCases := []struct {
in string
expected string
}{
{"Hello World!", "SGVsbG8gV29ybGQh"}, // multiple of 3 byte length (multiple of 24-bits)
{"Hello World!a", "SGVsbG8gV29ybGQhYQ=="}, // multiple of 3 byte length + 1
{"Hello World!ab", "SGVsbG8gV29ybGQhYWI="}, // multiple of 3 byte length + 2
{"", ""}, // empty byte slice
{"6", "Ng=="}, // short text
{"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
}

for _, tc := range testCases {
result := Encode([]byte(tc.in))
if result != tc.expected {
t.Fatalf("Encode(%s) = %s, want %s", tc.in, result, tc.expected)
}
}
}

func BenchmarkEncode(b *testing.B) {
benchmarks := []struct {
name string
in string
expected string
}{
{"Hello World!", "Hello World!", "SGVsbG8gV29ybGQh"}, // multiple of 3 byte length (multiple of 24-bits)
{"Hello World!a", "Hello World!a", "SGVsbG8gV29ybGQhYQ=="}, // multiple of 3 byte length + 1
{"Hello World!ab", "Hello World!ab", "SGVsbG8gV29ybGQhYWI="}, // multiple of 3 byte length + 2
{"Empty", "", ""}, // empty byte slice
{"6", "6", "Ng=="}, // short text
{"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
}

for _, bm := range benchmarks {
b.Run(bm.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
Encode([]byte(bm.in))
}
})
}
}

func TestDecode(t *testing.T) {
testCases := []struct {
expected string
in string
}{
{"Hello World!", "SGVsbG8gV29ybGQh"}, // multiple of 3 byte length (multiple of 24-bits)
{"Hello World!a", "SGVsbG8gV29ybGQhYQ=="}, // multiple of 3 byte length + 1
{"Hello World!ab", "SGVsbG8gV29ybGQhYWI="}, // multiple of 3 byte length + 2
{"", ""}, // empty byte slice
{"6", "Ng=="}, // short text
{"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
}

for _, tc := range testCases {
result := string(Decode(tc.in))
if result != tc.expected {
t.Fatalf("Decode(%s) = %s, want %s", tc.in, result, tc.expected)
}
}
}

func BenchmarkDecode(b *testing.B) {
benchmarks := []struct {
name string
expected string
in string
}{
{"Hello World!", "Hello World!", "SGVsbG8gV29ybGQh"}, // multiple of 3 byte length (multiple of 24-bits)
{"Hello World!a", "Hello World!a", "SGVsbG8gV29ybGQhYQ=="}, // multiple of 3 byte length + 1
{"Hello World!ab", "Hello World!ab", "SGVsbG8gV29ybGQhYWI="}, // multiple of 3 byte length + 2
{"Empty", "", ""}, // empty byte slice
{"6", "6", "Ng=="}, // short text
{"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
}

for _, bm := range benchmarks {
b.Run(bm.name, func(b *testing.B) {
for i := 0; i < b.N; i++ {
Decode(bm.in)
}
})
}
}

func TestEncodeDecodeInverse(t *testing.T) {
testCases := []struct {
in string
}{
{"Hello World!"}, // multiple of 3 byte length (multiple of 24-bits)
{"Hello World!a"}, // multiple of 3 byte length + 1
{"Hello World!ab"}, // multiple of 3 byte length + 2
{""}, // empty byte slice
{"6"}, // short text
{"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
}

for _, tc := range testCases {
result := string(Decode(Encode([]byte(tc.in))))
if result != tc.in {
t.Fatalf("Decode(Encode(%s)) = %s, want %s", tc.in, result, tc.in)
}
}
}