-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Changes from 10 commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
7d88902
feat: base64 encoding
paul-leydier 928796e
test: base64 encoding
paul-leydier 0bc3df2
docs: Added base64 declaration to README.md
paul-leydier 3740ae7
feat: use string builder to drastically improve efficiency of Encode
paul-leydier 53333f4
docs: formatting
paul-leydier 7401545
feat: base64 decoding
paul-leydier 3922cde
test: base64 decoding
paul-leydier e24f6e5
docs: added base64 Decode function to README.md
paul-leydier a411c0a
test: test base64 Encode and Decode inverse functions
paul-leydier 2271102
docs: base64 Decode docstring
paul-leydier 10fb550
docs: improve package documentation
paul-leydier 3ff3e78
feat: remove usage of predefined return values
paul-leydier ff6cc15
feat: base64 move to conversion package
paul-leydier 312acb9
fix: remove deleted line in README.md
paul-leydier 4047440
Merge branch 'master' into base64
raklaptudirm 6e1241e
Merge branch 'master' into base64
tjgurwara99 28f9f49
Update conversion/base64.go
siriak c43e922
Update conversion/base64.go
siriak File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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) { | ||
paul-leydier marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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] | ||
} |
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
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) | ||
} | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.