From 0e47c428ebef2a2d83dd7114af455b96b5a5721a Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 22 May 2025 11:58:01 +0200 Subject: [PATCH] Adding upstream version 0.0~git20231022.c6c9f9a. Signed-off-by: Daniel Baumann --- .github/workflows/main.yml | 36 ++++++ .gitignore | 2 + LICENSE | 29 +++++ README.md | 63 +++++++++++ decode_map.go | 22 ++++ decoder.go | 223 +++++++++++++++++++++++++++++++++++++ decoder_test.go | 149 +++++++++++++++++++++++++ generate_decode_map.go | 58 ++++++++++ go.mod | 3 + 9 files changed, 585 insertions(+) create mode 100644 .github/workflows/main.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 decode_map.go create mode 100644 decoder.go create mode 100644 decoder_test.go create mode 100644 generate_decode_map.go create mode 100644 go.mod diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..315230e --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,36 @@ +on: [push, pull_request] +name: Test +jobs: + test: + strategy: + matrix: + go-version: [1.18.x, 1.x] + os: [ubuntu-latest] + arch: ["", "386"] + fail-fast: false + runs-on: ${{ matrix.os }} + steps: + - name: Install Go + uses: actions/setup-go@v4 + with: + go-version: ${{ matrix.go-version }} + - name: Checkout code + uses: actions/checkout@v4 + - name: Check formatting + run: diff -u <(echo -n) <(gofmt -d .) + if: runner.os != 'Windows' + - name: Run go vet + env: + GOARCH: ${{ matrix.arch }} + run: go vet ./... + - name: Run staticcheck + uses: dominikh/staticcheck-action@v1.3.0 + with: + version: "2023.1.6" + install-go: false + cache-key: ${{ matrix.go-version }} + if: ${{ matrix.go-version == '1.x' }} + - name: Run tests + env: + GOARCH: ${{ matrix.arch }} + run: go test -vet=off ./... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c38fa4e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea +*.iml diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1690e22 --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2023 Dmitry Panov. All rights reserved. + +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c4abf18 --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ +# Universal Lenient Base64 Decoder + +This is a modified version of the standard base64 decoder from `encoding/base64`. Key differences: + +- Accepts both Standard and URL-safe encoding, no need to specify it explicitly. +- Works for both padded and raw inputs. +- The input can be a `string` or a `[]byte`, no allocation or copy is performed for either. + +The goal was to create a decoder compatible with the one used in nodejs (and possibly elsewhere too). Unfortunately +the standard package does not expose enough API to do this efficiently, and trying all 4 possible variants +(standard/url, padded/raw) seemed wasteful. + +Because there is no need to convert the input from `string` to `[]byte`, decoding strings (specially long ones) +is more efficient than with the standard library. Below is the comparison between `BenchmarkDecodeBase64Std` +and `BenchmarkDecodeBase64` on go1.21.3: + +``` +goos: linux +goarch: amd64 +pkg: base64dec +cpu: Intel(R) Core(TM) i7-2600S CPU @ 2.80GHz + │ old.txt │ new.txt │ + │ sec/op │ sec/op vs base │ +DecodeBase64/2-8 28.85n ± 3% 22.95n ± 2% -20.45% (p=0.000 n=10) +DecodeBase64/4-8 48.05n ± 0% 41.41n ± 1% -13.83% (p=0.000 n=10) +DecodeBase64/8-8 38.31n ± 2% 29.55n ± 1% -22.89% (p=0.000 n=10) +DecodeBase64/64-8 406.7n ± 5% 113.0n ± 2% -72.20% (p=0.000 n=10) +DecodeBase64/8192-8 31.670µ ± 10% 9.995µ ± 2% -68.44% (p=0.000 n=10) +geomean 232.8n 126.0n -45.89% + + │ old.txt │ new.txt │ + │ B/s │ B/s vs base │ +DecodeBase64/2-8 132.2Mi ± 3% 166.2Mi ± 2% +25.70% (p=0.000 n=10) +DecodeBase64/4-8 158.8Mi ± 0% 184.2Mi ± 1% +16.04% (p=0.000 n=10) +DecodeBase64/8-8 298.7Mi ± 2% 387.3Mi ± 1% +29.67% (p=0.000 n=10) +DecodeBase64/64-8 206.4Mi ± 5% 742.4Mi ± 2% +259.71% (p=0.000 n=10) +DecodeBase64/8192-8 329.0Mi ± 11% 1042.4Mi ± 2% +216.87% (p=0.000 n=10) +geomean 211.8Mi 391.4Mi +84.81% + + │ old.txt │ new.txt │ + │ B/op │ B/op vs base │ +DecodeBase64/2-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ +DecodeBase64/4-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ +DecodeBase64/8-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ +DecodeBase64/64-8 96.00 ± 0% 0.00 ± 0% -100.00% (p=0.000 n=10) +DecodeBase64/8192-8 12.00Ki ± 0% 0.00Ki ± 0% -100.00% (p=0.000 n=10) +geomean ² ? ² ³ +¹ all samples are equal +² summaries must be >0 to compute geomean +³ ratios must be >0 to compute geomean + + │ old.txt │ new.txt │ + │ allocs/op │ allocs/op vs base │ +DecodeBase64/2-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ +DecodeBase64/4-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ +DecodeBase64/8-8 0.000 ± 0% 0.000 ± 0% ~ (p=1.000 n=10) ¹ +DecodeBase64/64-8 1.000 ± 0% 0.000 ± 0% -100.00% (p=0.000 n=10) +DecodeBase64/8192-8 1.000 ± 0% 0.000 ± 0% -100.00% (p=0.000 n=10) +geomean ² ? ² ³ +¹ all samples are equal +² summaries must be >0 to compute geomean +³ ratios must be >0 to compute geomean +``` \ No newline at end of file diff --git a/decode_map.go b/decode_map.go new file mode 100644 index 0000000..bce96a7 --- /dev/null +++ b/decode_map.go @@ -0,0 +1,22 @@ +// Code generated by generate_decode_map.go. DO NOT EDIT. + +package base64dec + +var decodeMap = [256]byte{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3e, 0xff, 0x3e, 0xff, 0x3f, + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0x3f, + 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +} diff --git a/decoder.go b/decoder.go new file mode 100644 index 0000000..cb74e7b --- /dev/null +++ b/decoder.go @@ -0,0 +1,223 @@ +//go:generate go run generate_decode_map.go + +// Package base64dec contains a universal base64 decoder that works on both the standard and url-safe variants, padded and raw. +// The code is based on the standard encoding/base64 package. +package base64dec + +import ( + "encoding/base64" + "encoding/binary" + "strconv" +) + +const padChar = '=' + +type ByteSeq interface { + []byte | string +} + +// DecodeBase64 decodes src and writes at most base64.RawStdEncoding.DecodedLen(len(src)) +// bytes to dst and returns the number of bytes written. If src contains invalid base64 data, it will return the +// number of bytes successfully written and base64.CorruptInputError. +// New line characters (\r and \n) are ignored. +// The input can be in the standard or the alternate (aka url-safe) encoding. It can be padded or un-padded. +// If there is a correct padding, it is consumed and no error is returned. If there is no padding where it's required, +// no error is returned. If there is an incorrect padding (i.e. too many or too few characters) it is treated +// as garbage at the end (i.e. the error will point to the first padding character). +func DecodeBase64[T ByteSeq](dst []byte, src T) (n int, err error) { + if len(src) == 0 { + return 0, nil + } + + si := 0 + for strconv.IntSize >= 64 && len(src)-si >= 8 && len(dst)-n >= 8 { + src2 := src[si : si+8] + if dn, ok := assemble64( + decodeMap[src2[0]], + decodeMap[src2[1]], + decodeMap[src2[2]], + decodeMap[src2[3]], + decodeMap[src2[4]], + decodeMap[src2[5]], + decodeMap[src2[6]], + decodeMap[src2[7]], + ); ok { + binary.BigEndian.PutUint64(dst[n:], dn) + n += 6 + si += 8 + } else { + var ninc int + si, ninc, err = decodeQuantum(dst[n:], src, si) + n += ninc + if err != nil { + return n, err + } + } + } + + for len(src)-si >= 4 && len(dst)-n >= 4 { + src2 := src[si : si+4] + if dn, ok := assemble32( + decodeMap[src2[0]], + decodeMap[src2[1]], + decodeMap[src2[2]], + decodeMap[src2[3]], + ); ok { + binary.BigEndian.PutUint32(dst[n:], dn) + n += 3 + si += 4 + } else { + var ninc int + si, ninc, err = decodeQuantum(dst[n:], src, si) + n += ninc + if err != nil { + return n, err + } + } + } + + for si < len(src) { + var ninc int + si, ninc, err = decodeQuantum(dst[n:], src, si) + n += ninc + if err != nil { + return n, err + } + } + return n, err +} + +// assemble32 assembles 4 base64 digits into 3 bytes. +// Each digit comes from the decode map, and will be 0xff +// if it came from an invalid character. +func assemble32(n1, n2, n3, n4 byte) (dn uint32, ok bool) { + // Check that all the digits are valid. If any of them was 0xff, their + // bitwise OR will be 0xff. + if n1|n2|n3|n4 == 0xff { + return 0, false + } + return uint32(n1)<<26 | + uint32(n2)<<20 | + uint32(n3)<<14 | + uint32(n4)<<8, + true +} + +// assemble64 assembles 8 base64 digits into 6 bytes. +// Each digit comes from the decode map, and will be 0xff +// if it came from an invalid character. +func assemble64(n1, n2, n3, n4, n5, n6, n7, n8 byte) (dn uint64, ok bool) { + // Check that all the digits are valid. If any of them was 0xff, their + // bitwise OR will be 0xff. + if n1|n2|n3|n4|n5|n6|n7|n8 == 0xff { + return 0, false + } + return uint64(n1)<<58 | + uint64(n2)<<52 | + uint64(n3)<<46 | + uint64(n4)<<40 | + uint64(n5)<<34 | + uint64(n6)<<28 | + uint64(n7)<<22 | + uint64(n8)<<16, + true +} + +// decodeQuantum decodes up to 4 base64 bytes. The received parameters are +// the destination buffer dst, the source buffer src and an index in the +// source buffer si. +// It returns the number of bytes read from src, the number of bytes written +// to dst, and an error, if any. +func decodeQuantum[T ByteSeq](dst []byte, src T, si int) (nsi, n int, err error) { + // Decode quantum using the base64 alphabet + var dbuf [4]byte + dlen := 4 + + for j := 0; j < len(dbuf); j++ { + if len(src) == si { + if j == 0 { + return si, 0, nil + } + dlen = j + break + } + in := src[si] + si++ + + out := decodeMap[in] + if out != 0xff { + dbuf[j] = out + continue + } + + if in == '\n' || in == '\r' { + j-- + continue + } + + dlen = j + + if rune(in) != padChar { + err = base64.CorruptInputError(si - 1) + break + } + + // We've reached the end and there's padding + switch j { + case 0, 1: + // incorrect padding + err = base64.CorruptInputError(si - 1) + case 2: + // "==" is expected, the first "=" is already consumed. + // skip over newlines + for si < len(src) && (src[si] == '\n' || src[si] == '\r') { + si++ + } + if si == len(src) { + // not enough padding + err = base64.CorruptInputError(si - 1) + break + } else if rune(src[si]) != padChar { + // incorrect padding + err = base64.CorruptInputError(si - 1) + break + } + + si++ + } + + if err == nil { + // skip over newlines + for si < len(src) && (src[si] == '\n' || src[si] == '\r') { + si++ + } + if si < len(src) { + // trailing garbage + err = base64.CorruptInputError(si) + } + } + break + } + + if dlen == 0 { + return si, 0, err + } + + // Convert 4x 6bit source bytes into 3 bytes + val := uint(dbuf[0])<<18 | uint(dbuf[1])<<12 | uint(dbuf[2])<<6 | uint(dbuf[3]) + dbuf[2], dbuf[1], dbuf[0] = byte(val>>0), byte(val>>8), byte(val>>16) + switch dlen { + case 4: + dst[2] = dbuf[2] + dbuf[2] = 0 + fallthrough + case 3: + dst[1] = dbuf[1] + dbuf[1] = 0 + fallthrough + case 2: + dst[0] = dbuf[0] + } + + return si, dlen - 1, err +} diff --git a/decoder_test.go b/decoder_test.go new file mode 100644 index 0000000..c377667 --- /dev/null +++ b/decoder_test.go @@ -0,0 +1,149 @@ +package base64dec + +import ( + "bytes" + "encoding/base64" + "fmt" + "testing" +) + +func TestDecodeBase64(t *testing.T) { + f := func(t *testing.T, input, expectedResult string, expectedError error) { + decoded := make([]byte, base64.RawStdEncoding.DecodedLen(len(input))) + n, err := DecodeBase64(decoded, input) + if err != expectedError { + t.Fatal(err) + } + encoded := base64.StdEncoding.EncodeToString(decoded[:n]) + if encoded != expectedResult { + t.Fatal(encoded) + } + } + + t.Run("empty input", func(t *testing.T) { + f(t, "", "", nil) + }) + + t.Run("newlines only", func(t *testing.T) { + f(t, "\r\n", "", nil) + }) + + t.Run("correct padding", func(t *testing.T) { + f(t, "Z9CA-w==", "Z9CA+w==", nil) + }) + + t.Run("correct padding split by newline", func(t *testing.T) { + f(t, "Z9CA-w=\n=", "Z9CA+w==", nil) + }) + + t.Run("correct padding with concatenation", func(t *testing.T) { + f(t, "Z9CA-w==Z9CA-w==", "Z9CA+w==", base64.CorruptInputError(8)) + }) + + t.Run("trailing newline", func(t *testing.T) { + f(t, "Z9CA+wZ9CA-w\n", "Z9CA+wZ9CA+w", nil) + }) + + t.Run("trailing newline with padding", func(t *testing.T) { + f(t, "Z9CA+wZ9CA-www==\n", "Z9CA+wZ9CA+www==", nil) + }) + + t.Run("garbage after newline", func(t *testing.T) { + f(t, "Z9CA+wZ9CA-www==\n?", "Z9CA+wZ9CA+www==", base64.CorruptInputError(17)) + }) + + t.Run("no padding", func(t *testing.T) { + f(t, "Z9CA-w", "Z9CA+w==", nil) + }) + + t.Run("no padding, garbage at the end", func(t *testing.T) { + f(t, "Z9CA-w???", "Z9CA+w==", base64.CorruptInputError(6)) + }) + + t.Run("not enough padding", func(t *testing.T) { + f(t, "Z9CA-w=", "Z9CA+w==", base64.CorruptInputError(6)) + }) + + t.Run("incorrect padding", func(t *testing.T) { + f(t, "Z9CA====", "Z9CA", base64.CorruptInputError(4)) + }) + + t.Run("incorrect padding with extra base64", func(t *testing.T) { + f(t, "Z9CA-w=Z9CA-w=", "Z9CA+w==", base64.CorruptInputError(6)) + }) + + t.Run("incorrect padding with garbage", func(t *testing.T) { + f(t, "Z9CA-w=???", "Z9CA+w==", base64.CorruptInputError(6)) + }) + +} + +func FuzzDecodeBase64(f *testing.F) { + f.Add([]byte{}) + f.Add([]byte("\x14\xfb\x9c\x03\xd9\x7e")) + f.Add([]byte("\x14\xfb\x9c\x03\xd9")) + f.Add([]byte("\x14\xfb\x9c\x03")) + + f.Fuzz(func(t *testing.T, b []byte) { + encoded := base64.StdEncoding.EncodeToString(b) + decoded := make([]byte, len(b)) + n, err := DecodeBase64(decoded, encoded) + if err != nil { + t.Fatalf("%v: %v", b, err) + } + if !bytes.Equal(decoded[:n], b) { + t.Fatal(b) + } + + }) +} + +func FuzzDecodeBase64String(f *testing.F) { + f.Add("Z9CA-w") + f.Add("=\n=") + f.Add("=") + f.Add("====") + + f.Fuzz(func(t *testing.T, s string) { + decoded := make([]byte, base64.RawStdEncoding.DecodedLen(len(s))) + _, _ = DecodeBase64(decoded, s) // should not panic + }) +} + +func BenchmarkDecodeBase64(b *testing.B) { + sizes := []int{2, 4, 8, 64, 8192} + dst := make([]byte, 8192) + benchFunc := func(b *testing.B, benchSize int) { + data := base64.StdEncoding.EncodeToString(make([]byte, benchSize)) + b.SetBytes(int64(len(data))) + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + DecodeBase64(dst, data) + } + } + for _, size := range sizes { + b.Run(fmt.Sprintf("%d", size), func(b *testing.B) { + benchFunc(b, size) + }) + } +} + +func BenchmarkDecodeBase64Std(b *testing.B) { + sizes := []int{2, 4, 8, 64, 8192} + dst := make([]byte, 8192) + benchFunc := func(b *testing.B, benchSize int) { + data := base64.StdEncoding.EncodeToString(make([]byte, benchSize)) + b.SetBytes(int64(len(data))) + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + base64.StdEncoding.Decode(dst, []byte(data)) + } + } + for _, size := range sizes { + b.Run(fmt.Sprintf("%d", size), func(b *testing.B) { + benchFunc(b, size) + }) + } +} diff --git a/generate_decode_map.go b/generate_decode_map.go new file mode 100644 index 0000000..991439f --- /dev/null +++ b/generate_decode_map.go @@ -0,0 +1,58 @@ +//go:build ignore + +package main + +import ( + "bytes" + "fmt" + "log" + "os" +) + +func generateDecodeMap() [256]byte { + const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + + var decodeMap [256]byte + for i := 0; i < len(decodeMap); i++ { + decodeMap[i] = 0xff + } + + for i := 0; i < len(alphabet); i++ { + decodeMap[alphabet[i]] = byte(i) + } + + // for URL encoding + decodeMap['-'] = decodeMap['+'] + decodeMap['_'] = decodeMap['/'] + + return decodeMap +} + +func main() { + f, err := os.OpenFile("decode_map.go", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + log.Fatal(err) + } + m := generateDecodeMap() + var b bytes.Buffer + fmt.Fprintf(&b, "// Code generated by generate_decode_map.go. DO NOT EDIT.\n\n") + fmt.Fprintf(&b, "package base64dec\n\n") + fmt.Fprintf(&b, "var decodeMap = [256]byte{") + for i, v := range m { + if i%16 == 0 { + fmt.Fprintf(&b, "\n\t") + } else { + fmt.Fprint(&b, " ") + } + fmt.Fprintf(&b, "0x%02x,", v) + } + fmt.Fprintf(&b, "\n}\n") + _, err = f.Write(b.Bytes()) + if err != nil { + log.Fatal(err) + } + err = f.Close() + if err != nil { + log.Fatal(err) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1c08594 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/dop251/base64dec + +go 1.18