|
1 | 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 | | -// time complexity: O(n) |
5 | | -// space complexity: O(n) |
6 | | -// ref: https://datatracker.ietf.org/doc/html/rfc4648#section-4 |
7 | | -// ref: https://en.wikipedia.org/wiki/Base64 |
8 | | -// see base64_test.go |
| 2 | +// Description: Implements Base64 encoding and decoding as specified in the RFC4648 standard. |
| 3 | +// Time Complexity: O(n) - The encoding and decoding processes iterate through the input linearly. |
| 4 | +// Space Complexity: O(n) - The output size is proportional to the input size. |
| 5 | +// References: |
| 6 | +// - RFC4648 Base64 Encoding: https://datatracker.ietf.org/doc/html/rfc4648#section-4 |
| 7 | +// - Wikipedia Base64 Overview: https://en.wikipedia.org/wiki/Base64 |
| 8 | +// See also: base64_test.go for test cases and verification. |
9 | 9 |
|
10 | 10 | package conversion |
11 | 11 |
|
12 | 12 | import ( |
13 | | - "strings" // Used for efficient string builder (more efficient than simply appending strings) |
| 13 | + "strings" // Provides an efficient way to build strings without unnecessary memory allocations. |
14 | 14 | ) |
15 | 15 |
|
16 | 16 | const Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" |
17 | 17 |
|
18 | | -// Base64Encode encodes the received input bytes slice into a base64 string. |
19 | | -// The implementation follows the RFC4648 standard, which is documented |
20 | | -// at https://datatracker.ietf.org/doc/html/rfc4648#section-4 |
| 18 | +// Base64Encode converts a byte slice into a Base64-encoded string. |
| 19 | +// |
| 20 | +// The encoding follows the RFC4648 standard, where every 3 bytes of input |
| 21 | +// are converted into 4 characters using a 6-bit mapping table. If the input |
| 22 | +// length is not a multiple of 3, padding is added using '='. |
| 23 | +// |
| 24 | +// Example: |
| 25 | +// |
| 26 | +// Input: "hello" |
| 27 | +// Output: "aGVsbG8=" |
21 | 28 | func Base64Encode(input []byte) string { |
22 | | - var sb strings.Builder |
23 | | - // If not 24 bits (3 bytes) multiple, pad with 0 value bytes, and with "=" for the output |
| 29 | + var sb strings.Builder // Efficient string concatenation |
| 30 | + |
| 31 | + // Calculate padding required if the input length is not a multiple of 3. |
24 | 32 | var padding string |
25 | 33 | for i := len(input) % 3; i > 0 && i < 3; i++ { |
26 | 34 | var zeroByte byte |
27 | | - input = append(input, zeroByte) |
28 | | - padding += "=" |
| 35 | + input = append(input, zeroByte) // Append zero bytes to align the length |
| 36 | + padding += "=" // Add '=' padding to match the expected output length |
29 | 37 | } |
30 | 38 |
|
31 | | - // encode 24 bits per 24 bits (3 bytes per 3 bytes) |
| 39 | + // Process input 3 bytes at a time, converting them into 4 Base64 characters |
32 | 40 | for i := 0; i < len(input); i += 3 { |
33 | | - // select 3 8-bit input groups, and re-arrange them into 4 6-bit groups |
34 | | - // the literal 0x3F corresponds to the byte "0011 1111" |
35 | | - // the operation "byte & 0x3F" masks the two left-most bits |
| 41 | + // Extract 3 bytes and split them into four 6-bit groups |
| 42 | + // Each byte contributes to multiple output characters |
36 | 43 | group := [4]byte{ |
37 | | - input[i] >> 2, |
38 | | - (input[i]<<4)&0x3F + input[i+1]>>4, |
39 | | - (input[i+1]<<2)&0x3F + input[i+2]>>6, |
40 | | - input[i+2] & 0x3F, |
| 44 | + input[i] >> 2, // First 6 bits |
| 45 | + (input[i]<<4)&0x3F + input[i+1]>>4, // Next 6 bits (spanning two bytes) |
| 46 | + (input[i+1]<<2)&0x3F + input[i+2]>>6, // Next 6 bits (spanning two bytes) |
| 47 | + input[i+2] & 0x3F, // Last 6 bits |
41 | 48 | } |
42 | 49 |
|
43 | | - // translate each group into a char using the static map |
| 50 | + // Convert each 6-bit group to a Base64 character |
44 | 51 | for _, b := range group { |
45 | | - sb.WriteString(string(Alphabet[int(b)])) |
| 52 | + sb.WriteByte(Alphabet[b]) |
46 | 53 | } |
47 | 54 | } |
| 55 | + |
48 | 56 | encoded := sb.String() |
49 | 57 |
|
50 | | - // Apply the output padding |
| 58 | + // Apply '=' padding if necessary |
51 | 59 | encoded = encoded[:len(encoded)-len(padding)] + padding[:] |
52 | 60 |
|
53 | 61 | return encoded |
54 | 62 | } |
55 | 63 |
|
56 | | -// Base64Decode decodes the received input base64 string into a byte slice. |
57 | | -// The implementation follows the RFC4648 standard, which is documented |
58 | | -// at https://datatracker.ietf.org/doc/html/rfc4648#section-4 |
| 64 | +// Base64Decode converts a Base64-encoded string back into a byte slice. |
| 65 | +// |
| 66 | +// This function processes 4-character chunks from the input string, converting |
| 67 | +// them back into 3 original bytes. Padding ('=') characters at the end of the input |
| 68 | +// are ignored to restore the correct output length. |
| 69 | +// |
| 70 | +// Example: |
| 71 | +// |
| 72 | +// Input: "aGVsbG8=" |
| 73 | +// Output: "hello" |
59 | 74 | func Base64Decode(input string) []byte { |
60 | | - padding := strings.Count(input, "=") // Number of bytes which will be ignored |
| 75 | + padding := strings.Count(input, "=") // Count padding characters, which affect output size |
61 | 76 | var decoded []byte |
62 | 77 |
|
63 | | - // select 4 6-bit input groups, and re-arrange them into 3 8-bit groups |
| 78 | + // Process input in chunks of 4 Base64 characters at a time |
64 | 79 | for i := 0; i < len(input); i += 4 { |
65 | | - // translate each group into a byte using the static map |
| 80 | + // Convert each Base64 character back to its corresponding 6-bit value |
66 | 81 | byteInput := [4]byte{ |
67 | 82 | byte(strings.IndexByte(Alphabet, input[i])), |
68 | 83 | byte(strings.IndexByte(Alphabet, input[i+1])), |
69 | 84 | byte(strings.IndexByte(Alphabet, input[i+2])), |
70 | 85 | byte(strings.IndexByte(Alphabet, input[i+3])), |
71 | 86 | } |
72 | 87 |
|
| 88 | + // Reassemble original bytes from 6-bit groups |
73 | 89 | group := [3]byte{ |
74 | | - byteInput[0]<<2 + byteInput[1]>>4, |
75 | | - byteInput[1]<<4 + byteInput[2]>>2, |
76 | | - byteInput[2]<<6 + byteInput[3], |
| 90 | + byteInput[0]<<2 + byteInput[1]>>4, // First byte |
| 91 | + byteInput[1]<<4 + byteInput[2]>>2, // Second byte |
| 92 | + byteInput[2]<<6 + byteInput[3], // Third byte |
77 | 93 | } |
78 | 94 |
|
79 | 95 | decoded = append(decoded, group[:]...) |
80 | 96 | } |
81 | 97 |
|
| 98 | + // Remove extra bytes that were added due to padding |
82 | 99 | return decoded[:len(decoded)-padding] |
83 | 100 | } |
0 commit comments