diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 2520b22..5ed9c50 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -8,12 +8,12 @@ jobs: - name: Set up Go 1.15 uses: actions/setup-go@v3 with: - go-version: 1.15 + go-version: '1.15' id: go - name: Check out code uses: actions/checkout@v3 - name: Test - run: go test -v ./... + run: go test -race -v ./... test-1_16: name: Test 1.16 @@ -22,12 +22,12 @@ jobs: - name: Set up Go 1.16 uses: actions/setup-go@v3 with: - go-version: 1.16 + go-version: '1.16' id: go - name: Check out code uses: actions/checkout@v3 - name: Test - run: go test -v ./... + run: go test -race -v ./... test-1_17: name: Test 1.17 @@ -36,12 +36,12 @@ jobs: - name: Set up Go 1.17 uses: actions/setup-go@v3 with: - go-version: 1.17 + go-version: '1.17' id: go - name: Check out code uses: actions/checkout@v3 - name: Test - run: go test -v ./... + run: go test -race -v ./... test-1_18: name: Test 1.18 @@ -50,12 +50,12 @@ jobs: - name: Set up Go 1.18 uses: actions/setup-go@v3 with: - go-version: 1.18 + go-version: '1.18' id: go - name: Check out code uses: actions/checkout@v3 - name: Test - run: go test -v ./... + run: go test -race -v ./... test-1_19: name: Test 1.19 @@ -64,15 +64,29 @@ jobs: - name: Set up Go 1.19 uses: actions/setup-go@v3 with: - go-version: 1.19 + go-version: '1.19' id: go - name: Check out code uses: actions/checkout@v3 - name: Test - run: go test -v ./... -coverprofile=coverage.txt -covermode=atomic + run: go test -race -v ./... + + test-1_20: + name: Test 1.20 + runs-on: ubuntu-latest + steps: + - name: Set up Go 1.20 + uses: actions/setup-go@v3 + with: + go-version: '1.20' + id: go + - name: Check out code + uses: actions/checkout@v3 + - name: Test + run: go test -race -v ./... -coverprofile=coverage.out -covermode=atomic - name: Codecov uses: codecov/codecov-action@v3 with: token: ${{secrets.CODECOV_TOKEN}} - file: ./coverage.txt + file: ./coverage.out diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 65086c4..882248d 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -16,5 +16,5 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: - version: v1.50 + version: v1.51 args: -c .golangci.yml -v diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index cc3292d..1812300 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -14,12 +14,10 @@ jobs: run: echo "RELEASE_VERSION=$(echo ${GITHUB_REF} | cut -d'/' -f3)" >> $GITHUB_ENV - name: Create Release id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} + uses: ncipollo/release-action@v1 with: - tag_name: ${{ github.ref }} - release_name: ${{ env.RELEASE_VERSION }} - body: ${{ env.RELEASE_VERSION }} + token: ${{ secrets.RELEASE_TOKEN }} + name: ${{ env.RELEASE_VERSION }} + omitBody: true draft: true - prerelease: false \ No newline at end of file + prerelease: false diff --git a/.golangci.yml b/.golangci.yml index 197e293..751f076 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -16,7 +16,6 @@ linters: - wsl - gochecknoglobals - gomnd - - paralleltest - nonamedreturns linters-settings: govet: diff --git a/CHANGELOG.md b/CHANGELOG.md index d6ba062..b86aaf4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +BUG FIXES: + +* fix panic when call `Decode()` in parallel +* fix panic when string input of `Decode()` is empty +* return error when there isn't enough characters in string input of `Decode()` + ## v1.1.0 NOTES: diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..911d932 --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +default: gotest/html + +.PHONY: gotest gotest/html pkgsite pkgsite/install + +gotest: + go test -race -covermode=count -v ./... + +gotest/html: + go test -race -v ./... -coverprofile=coverage.out && go tool cover -html=coverage.out + +pkgsite: pkgsite/install + @echo "open -> http://localhost:8080/$(shell go list -m)" + pkgsite + +pkgsite/install: + go install golang.org/x/pkgsite/cmd/pkgsite@latest diff --git a/jdecode.go b/jdecode.go index f5ebd00..4e469fb 100644 --- a/jdecode.go +++ b/jdecode.go @@ -9,14 +9,31 @@ import ( // MagicPrefix: Junos encrypted secret prefix. const MagicPrefix = "$9$" +// ErrDiffGapDec is returned when can't decode a character due to a missing or extra character(s). +// +// This is detected by a difference in length of the internal `gaps` and `decode` list. +var ErrDiffGapDec = errors.New("junosdecode: missing or extra character(s) (gaps and decode size not the same)") + +// ErrEmptySecret is returned when can't decode due to empty input. +var ErrEmptySecret = errors.New("junosdecode: no secret to decode") + +// ErrNotEnoughChars is returned when can't decode due to not enough characters input. +var ErrNotEnoughChars = errors.New("junosdecode: not enough characters") + // Decode Junos encrypted secret ($9$). func Decode(encryptedSecret string) (secretDecoded string, _ error) { - initialize() + if encryptedSecret == "" { + return "", ErrEmptySecret + } chars := strings.TrimPrefix(encryptedSecret, MagicPrefix) + if len(chars) < 3 { + return "", ErrNotEnoughChars + } + dict := newDictAlpha() var first string first, chars = nibble(chars, 1) firstR := []rune(first) - _, chars = nibble(chars, extra[firstR[0]]) + _, chars = nibble(chars, dict.extra[firstR[0]]) prev := first for chars != "" { decode := encoding[len(secretDecoded)%len(encoding)] @@ -24,7 +41,7 @@ func Decode(encryptedSecret string) (secretDecoded string, _ error) { nib, chars = nibble(chars, len(decode)) var gaps []int for _, i := range nib { - g := gap(prev, string(i)) + g := dict.gap(prev, string(i)) prev = string(i) gaps = append(gaps, g) } @@ -38,13 +55,6 @@ func Decode(encryptedSecret string) (secretDecoded string, _ error) { return secretDecoded, nil } -var family = []string{ - "QzF3n6/9CAtpu0O", - "B1IREhcSyrleKvMW8LXx", - "7N-dVbwsY2g4oaJZGUDj", - "iHkq.mPf5T", -} - var encoding = [][]int{ { 1, 4, 32, @@ -69,33 +79,38 @@ var encoding = [][]int{ }, } -var ( +type dictAlpha struct { numAlpha map[int]rune alphaNum map[rune]int extra map[rune]int -) +} -// initialize fill numAlpha, alphaNum, extra with family. -func initialize() { - numAlpha = make(map[int]rune) - alphaNum = make(map[rune]int) - extra = make(map[rune]int) +// newDictAlpha create dictAlpha with data filled. +func newDictAlpha() dictAlpha { + family := []string{ + "QzF3n6/9CAtpu0O", + "B1IREhcSyrleKvMW8LXx", + "7N-dVbwsY2g4oaJZGUDj", + "iHkq.mPf5T", + } + dict := dictAlpha{ + numAlpha: make(map[int]rune), + alphaNum: make(map[rune]int), + extra: make(map[rune]int), + } for i, r := range []rune(strings.Join(family, "")) { - numAlpha[i] = r - alphaNum[r] = i + dict.numAlpha[i] = r + dict.alphaNum[r] = i } for i, fam := range family { for _, c := range fam { - extra[c] = 3 - i + dict.extra[c] = 3 - i } } -} -// ErrDiffGapDec is returned when can't decode a character due to a missing or extra character(s). -// -// This is detected by a difference in length of the internal `gaps` and `decode` list. -var ErrDiffGapDec = errors.New("junosdecode: missing or extra character(s) (gaps and decode size not the same)") + return dict +} // gapDecode. func gapDecode(gaps []int, dec []int) (string, error) { @@ -122,11 +137,11 @@ func nibble(cref string, length int) (string, string) { } // gap betwean characters. -func gap(c1 string, c2 string) int { +func (d *dictAlpha) gap(c1 string, c2 string) int { c1rune := []rune(c1) c2rune := []rune(c2) - return pmod((alphaNum[c2rune[0]]-alphaNum[c1rune[0]]), (len(numAlpha))) - 1 + return pmod((d.alphaNum[c2rune[0]]-d.alphaNum[c1rune[0]]), (len(d.numAlpha))) - 1 } // modulus positive (same as python). diff --git a/jdecode_test.go b/jdecode_test.go index 80392f9..360bfbe 100644 --- a/jdecode_test.go +++ b/jdecode_test.go @@ -1,6 +1,8 @@ package junosdecode_test import ( + "errors" + "fmt" "math/rand" "testing" @@ -14,6 +16,8 @@ const ( // TestDecode decode example password. func TestDecodePassword(t *testing.T) { + t.Parallel() + passwordDecoded, err := junosdecode.Decode(junWordCoded) if err != nil { t.Errorf("error on decode %v", err) @@ -25,6 +29,8 @@ func TestDecodePassword(t *testing.T) { // TestDecodeBadEncoded try decode encrypted secret but with missing or extra character. func TestDecodeBadEncoded(t *testing.T) { + t.Parallel() + // remove last character if passwordDecoded, err := junosdecode.Decode(junWordCoded[:len(junWordCoded)-1]); err == nil { t.Errorf("missing character in junWordCoded not detected, passwordDecoded: %s", passwordDecoded) @@ -39,3 +45,51 @@ func TestDecodeBadEncoded(t *testing.T) { t.Errorf("extra characters in junWordCoded not detected, passwordDecoded: %s", passwordDecoded) } } + +func TestDecodeEmptyEncoded(t *testing.T) { + t.Parallel() + + // empty input + if passwordDecoded, err := junosdecode.Decode(""); err == nil { + t.Errorf("empty input not detected, passwordDecoded: %s", passwordDecoded) + } else if !errors.Is(err, junosdecode.ErrEmptySecret) { + t.Errorf("got unexpected error: %s", err) + } +} + +func TestDecodeNotEnoughCharsEncoded(t *testing.T) { + t.Parallel() + + // MagicPrefix input + if passwordDecoded, err := junosdecode.Decode(junosdecode.MagicPrefix); err == nil { + t.Errorf("MagicPrefix input not detected, passwordDecoded: %s", passwordDecoded) + } else if !errors.Is(err, junosdecode.ErrNotEnoughChars) { + t.Errorf("got unexpected error: %s", err) + } + + // MagicPrefix and 2 chars input + if passwordDecoded, err := junosdecode.Decode(junosdecode.MagicPrefix + "!!"); err == nil { + t.Errorf("MagicPrefix + 2 chars input not detected, passwordDecoded: %s", passwordDecoded) + } else if !errors.Is(err, junosdecode.ErrNotEnoughChars) { + t.Errorf("got unexpected error: %s", err) + } +} + +func TestDecodeParrallel(t *testing.T) { + t.Parallel() + + for i, v := range []string{junWordCoded, junWordCoded, junWordCoded} { + word := v + t.Run(fmt.Sprintf("Loop number %d", i), func(t *testing.T) { + t.Parallel() + + output, err := junosdecode.Decode(word) + if err != nil { + t.Errorf("got unexpected error: %s", err) + } + if output != junWordDecoded { + t.Errorf("got unexpected decoded word: %s", output) + } + }) + } +}