First round of linter cleanups.
This commit is contained in:
@@ -65,8 +65,6 @@ linters:
|
|||||||
- funcorder # checks the order of functions, methods, and constructors
|
- funcorder # checks the order of functions, methods, and constructors
|
||||||
- funlen # tool for detection of long functions
|
- funlen # tool for detection of long functions
|
||||||
- gocheckcompilerdirectives # validates go compiler directive comments (//go:)
|
- gocheckcompilerdirectives # validates go compiler directive comments (//go:)
|
||||||
- gochecknoglobals # checks that no global variables exist
|
|
||||||
- gochecknoinits # checks that no init functions are present in Go code
|
|
||||||
- gochecksumtype # checks exhaustiveness on Go "sum types"
|
- gochecksumtype # checks exhaustiveness on Go "sum types"
|
||||||
- gocognit # computes and checks the cognitive complexity of functions
|
- gocognit # computes and checks the cognitive complexity of functions
|
||||||
- goconst # finds repeated strings that could be replaced by a constant
|
- goconst # finds repeated strings that could be replaced by a constant
|
||||||
@@ -368,7 +366,7 @@ linters:
|
|||||||
max-func-lines: 0
|
max-func-lines: 0
|
||||||
|
|
||||||
nolintlint:
|
nolintlint:
|
||||||
# Exclude following linters from requiring an explanation.
|
# Exclude the following linters from requiring an explanation.
|
||||||
# Default: []
|
# Default: []
|
||||||
allow-no-explanation: [ funlen, gocognit, golines ]
|
allow-no-explanation: [ funlen, gocognit, golines ]
|
||||||
# Enable to require an explanation of nonzero length after each nolint directive.
|
# Enable to require an explanation of nonzero length after each nolint directive.
|
||||||
@@ -444,8 +442,11 @@ linters:
|
|||||||
presets:
|
presets:
|
||||||
- std-error-handling
|
- std-error-handling
|
||||||
- common-false-positives
|
- common-false-positives
|
||||||
# Excluding configuration per-path, per-linter, per-text and per-source.
|
|
||||||
rules:
|
rules:
|
||||||
|
- path: 'ahash/ahash.go'
|
||||||
|
linters: [ staticcheck, gosec ]
|
||||||
|
- path: 'backoff/backoff_test.go'
|
||||||
|
linters: [ testpackage ]
|
||||||
- source: 'TODO'
|
- source: 'TODO'
|
||||||
linters: [ godot ]
|
linters: [ godot ]
|
||||||
- text: 'should have a package comment'
|
- text: 'should have a package comment'
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
package ahash
|
package ahash
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5" // #nosec G505
|
||||||
"crypto/sha1"
|
"crypto/sha1" // #nosec G501
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"errors"
|
"errors"
|
||||||
@@ -17,34 +17,15 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"git.wntrmute.dev/kyle/goutils/assert"
|
|
||||||
"golang.org/x/crypto/blake2b"
|
"golang.org/x/crypto/blake2b"
|
||||||
"golang.org/x/crypto/blake2s"
|
"golang.org/x/crypto/blake2s"
|
||||||
"golang.org/x/crypto/md4"
|
"golang.org/x/crypto/md4" // #nosec G506
|
||||||
"golang.org/x/crypto/ripemd160"
|
"golang.org/x/crypto/ripemd160" // #nosec G507
|
||||||
"golang.org/x/crypto/sha3"
|
"golang.org/x/crypto/sha3"
|
||||||
|
|
||||||
|
"git.wntrmute.dev/kyle/goutils/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func sha224Slicer(bs []byte) []byte {
|
|
||||||
sum := sha256.Sum224(bs)
|
|
||||||
return sum[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func sha256Slicer(bs []byte) []byte {
|
|
||||||
sum := sha256.Sum256(bs)
|
|
||||||
return sum[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func sha384Slicer(bs []byte) []byte {
|
|
||||||
sum := sha512.Sum384(bs)
|
|
||||||
return sum[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func sha512Slicer(bs []byte) []byte {
|
|
||||||
sum := sha512.Sum512(bs)
|
|
||||||
return sum[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hash represents a generic hash function that may or may not be secure. It
|
// Hash represents a generic hash function that may or may not be secure. It
|
||||||
// satisfies the hash.Hash interface.
|
// satisfies the hash.Hash interface.
|
||||||
type Hash struct {
|
type Hash struct {
|
||||||
@@ -247,17 +228,17 @@ func init() {
|
|||||||
// HashList returns a sorted list of all the hash algorithms supported by the
|
// HashList returns a sorted list of all the hash algorithms supported by the
|
||||||
// package.
|
// package.
|
||||||
func HashList() []string {
|
func HashList() []string {
|
||||||
return hashList[:]
|
return hashList
|
||||||
}
|
}
|
||||||
|
|
||||||
// SecureHashList returns a sorted list of all the secure (cryptographic) hash
|
// SecureHashList returns a sorted list of all the secure (cryptographic) hash
|
||||||
// algorithms supported by the package.
|
// algorithms supported by the package.
|
||||||
func SecureHashList() []string {
|
func SecureHashList() []string {
|
||||||
return secureHashList[:]
|
return secureHashList
|
||||||
}
|
}
|
||||||
|
|
||||||
// InsecureHashList returns a sorted list of all the insecure hash algorithms
|
// InsecureHashList returns a sorted list of all the insecure hash algorithms
|
||||||
// supported by the package.
|
// supported by the package.
|
||||||
func InsecureHashList() []string {
|
func InsecureHashList() []string {
|
||||||
return insecureHashList[:]
|
return insecureHashList
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
package ahash
|
package ahash_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"git.wntrmute.dev/kyle/goutils/ahash"
|
||||||
"git.wntrmute.dev/kyle/goutils/assert"
|
"git.wntrmute.dev/kyle/goutils/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSecureHash(t *testing.T) {
|
func TestSecureHash(t *testing.T) {
|
||||||
algo := "sha256"
|
algo := "sha256"
|
||||||
h, err := New(algo)
|
h, err := ahash.New(algo)
|
||||||
assert.NoErrorT(t, err)
|
assert.NoErrorT(t, err)
|
||||||
assert.BoolT(t, h.IsSecure(), algo+" should be a secure hash")
|
assert.BoolT(t, h.IsSecure(), algo+" should be a secure hash")
|
||||||
assert.BoolT(t, h.HashAlgo() == algo, "hash returned the wrong HashAlgo")
|
assert.BoolT(t, h.HashAlgo() == algo, "hash returned the wrong HashAlgo")
|
||||||
@@ -19,28 +21,28 @@ func TestSecureHash(t *testing.T) {
|
|||||||
|
|
||||||
var data []byte
|
var data []byte
|
||||||
var expected = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
var expected = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||||
sum, err := Sum(algo, data)
|
sum, err := ahash.Sum(algo, data)
|
||||||
assert.NoErrorT(t, err)
|
assert.NoErrorT(t, err)
|
||||||
assert.BoolT(t, fmt.Sprintf("%x", sum) == expected, fmt.Sprintf("expected hash %s but have %x", expected, sum))
|
assert.BoolT(t, hex.EncodeToString(sum) == expected, fmt.Sprintf("expected hash %s but have %x", expected, sum))
|
||||||
|
|
||||||
data = []byte("hello, world")
|
data = []byte("hello, world")
|
||||||
buf := bytes.NewBuffer(data)
|
buf := bytes.NewBuffer(data)
|
||||||
expected = "09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b"
|
expected = "09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b"
|
||||||
sum, err = SumReader(algo, buf)
|
sum, err = ahash.SumReader(algo, buf)
|
||||||
assert.NoErrorT(t, err)
|
assert.NoErrorT(t, err)
|
||||||
assert.BoolT(t, fmt.Sprintf("%x", sum) == expected, fmt.Sprintf("expected hash %s but have %x", expected, sum))
|
assert.BoolT(t, hex.EncodeToString(sum) == expected, fmt.Sprintf("expected hash %s but have %x", expected, sum))
|
||||||
|
|
||||||
data = []byte("hello world")
|
data = []byte("hello world")
|
||||||
_, err = h.Write(data)
|
_, err = h.Write(data)
|
||||||
assert.NoErrorT(t, err)
|
assert.NoErrorT(t, err)
|
||||||
unExpected := "09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b"
|
unExpected := "09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b"
|
||||||
sum = h.Sum(nil)
|
sum = h.Sum(nil)
|
||||||
assert.BoolT(t, fmt.Sprintf("%x", sum) != unExpected, fmt.Sprintf("hash shouldn't have returned %x", unExpected))
|
assert.BoolT(t, hex.EncodeToString(sum) != unExpected, fmt.Sprintf("hash shouldn't have returned %x", unExpected))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInsecureHash(t *testing.T) {
|
func TestInsecureHash(t *testing.T) {
|
||||||
algo := "md5"
|
algo := "md5"
|
||||||
h, err := New(algo)
|
h, err := ahash.New(algo)
|
||||||
assert.NoErrorT(t, err)
|
assert.NoErrorT(t, err)
|
||||||
assert.BoolT(t, !h.IsSecure(), algo+" shouldn't be a secure hash")
|
assert.BoolT(t, !h.IsSecure(), algo+" shouldn't be a secure hash")
|
||||||
assert.BoolT(t, h.HashAlgo() == algo, "hash returned the wrong HashAlgo")
|
assert.BoolT(t, h.HashAlgo() == algo, "hash returned the wrong HashAlgo")
|
||||||
@@ -49,28 +51,28 @@ func TestInsecureHash(t *testing.T) {
|
|||||||
|
|
||||||
var data []byte
|
var data []byte
|
||||||
var expected = "d41d8cd98f00b204e9800998ecf8427e"
|
var expected = "d41d8cd98f00b204e9800998ecf8427e"
|
||||||
sum, err := Sum(algo, data)
|
sum, err := ahash.Sum(algo, data)
|
||||||
assert.NoErrorT(t, err)
|
assert.NoErrorT(t, err)
|
||||||
assert.BoolT(t, fmt.Sprintf("%x", sum) == expected, fmt.Sprintf("expected hash %s but have %x", expected, sum))
|
assert.BoolT(t, hex.EncodeToString(sum) == expected, fmt.Sprintf("expected hash %s but have %x", expected, sum))
|
||||||
|
|
||||||
data = []byte("hello, world")
|
data = []byte("hello, world")
|
||||||
buf := bytes.NewBuffer(data)
|
buf := bytes.NewBuffer(data)
|
||||||
expected = "e4d7f1b4ed2e42d15898f4b27b019da4"
|
expected = "e4d7f1b4ed2e42d15898f4b27b019da4"
|
||||||
sum, err = SumReader(algo, buf)
|
sum, err = ahash.SumReader(algo, buf)
|
||||||
assert.NoErrorT(t, err)
|
assert.NoErrorT(t, err)
|
||||||
assert.BoolT(t, fmt.Sprintf("%x", sum) == expected, fmt.Sprintf("expected hash %s but have %x", expected, sum))
|
assert.BoolT(t, hex.EncodeToString(sum) == expected, fmt.Sprintf("expected hash %s but have %x", expected, sum))
|
||||||
|
|
||||||
data = []byte("hello world")
|
data = []byte("hello world")
|
||||||
_, err = h.Write(data)
|
_, err = h.Write(data)
|
||||||
assert.NoErrorT(t, err)
|
assert.NoErrorT(t, err)
|
||||||
unExpected := "e4d7f1b4ed2e42d15898f4b27b019da4"
|
unExpected := "e4d7f1b4ed2e42d15898f4b27b019da4"
|
||||||
sum = h.Sum(nil)
|
sum = h.Sum(nil)
|
||||||
assert.BoolT(t, fmt.Sprintf("%x", sum) != unExpected, fmt.Sprintf("hash shouldn't have returned %x", unExpected))
|
assert.BoolT(t, hex.EncodeToString(sum) != unExpected, fmt.Sprintf("hash shouldn't have returned %x", unExpected))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHash32(t *testing.T) {
|
func TestHash32(t *testing.T) {
|
||||||
algo := "crc32-ieee"
|
algo := "crc32-ieee"
|
||||||
h, err := New(algo)
|
h, err := ahash.New(algo)
|
||||||
assert.NoErrorT(t, err)
|
assert.NoErrorT(t, err)
|
||||||
assert.BoolT(t, !h.IsSecure(), algo+" shouldn't be a secure hash")
|
assert.BoolT(t, !h.IsSecure(), algo+" shouldn't be a secure hash")
|
||||||
assert.BoolT(t, h.HashAlgo() == algo, "hash returned the wrong HashAlgo")
|
assert.BoolT(t, h.HashAlgo() == algo, "hash returned the wrong HashAlgo")
|
||||||
@@ -102,7 +104,7 @@ func TestHash32(t *testing.T) {
|
|||||||
|
|
||||||
func TestHash64(t *testing.T) {
|
func TestHash64(t *testing.T) {
|
||||||
algo := "crc64"
|
algo := "crc64"
|
||||||
h, err := New(algo)
|
h, err := ahash.New(algo)
|
||||||
assert.NoErrorT(t, err)
|
assert.NoErrorT(t, err)
|
||||||
assert.BoolT(t, !h.IsSecure(), algo+" shouldn't be a secure hash")
|
assert.BoolT(t, !h.IsSecure(), algo+" shouldn't be a secure hash")
|
||||||
assert.BoolT(t, h.HashAlgo() == algo, "hash returned the wrong HashAlgo")
|
assert.BoolT(t, h.HashAlgo() == algo, "hash returned the wrong HashAlgo")
|
||||||
@@ -133,9 +135,9 @@ func TestHash64(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestListLengthSanity(t *testing.T) {
|
func TestListLengthSanity(t *testing.T) {
|
||||||
all := HashList()
|
all := ahash.HashList()
|
||||||
secure := SecureHashList()
|
secure := ahash.SecureHashList()
|
||||||
insecure := InsecureHashList()
|
insecure := ahash.InsecureHashList()
|
||||||
|
|
||||||
assert.BoolT(t, len(all) == len(secure)+len(insecure))
|
assert.BoolT(t, len(all) == len(secure)+len(insecure))
|
||||||
}
|
}
|
||||||
@@ -146,11 +148,11 @@ func TestSumLimitedReader(t *testing.T) {
|
|||||||
extendedData := bytes.NewBufferString("hello, world! this is an extended message")
|
extendedData := bytes.NewBufferString("hello, world! this is an extended message")
|
||||||
expected := "09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b"
|
expected := "09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b"
|
||||||
|
|
||||||
hash, err := SumReader("sha256", data)
|
hash, err := ahash.SumReader("sha256", data)
|
||||||
assert.NoErrorT(t, err)
|
assert.NoErrorT(t, err)
|
||||||
assert.BoolT(t, fmt.Sprintf("%x", hash) == expected, fmt.Sprintf("have hash %x, want %s", hash, expected))
|
assert.BoolT(t, hex.EncodeToString(hash) == expected, fmt.Sprintf("have hash %x, want %s", hash, expected))
|
||||||
|
|
||||||
extendedHash, err := SumLimitedReader("sha256", extendedData, int64(dataLen))
|
extendedHash, err := ahash.SumLimitedReader("sha256", extendedData, int64(dataLen))
|
||||||
assert.NoErrorT(t, err)
|
assert.NoErrorT(t, err)
|
||||||
|
|
||||||
assert.BoolT(t, bytes.Equal(hash, extendedHash), fmt.Sprintf("have hash %x, want %x", extendedHash, hash))
|
assert.BoolT(t, bytes.Equal(hash, extendedHash), fmt.Sprintf("have hash %x, want %x", extendedHash, hash))
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
package assert
|
package assert
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -16,11 +17,13 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const callerSkip = 2
|
||||||
|
|
||||||
// NoDebug can be set to true to cause all asserts to be ignored.
|
// NoDebug can be set to true to cause all asserts to be ignored.
|
||||||
var NoDebug bool
|
var NoDebug bool
|
||||||
|
|
||||||
func die(what string, a ...string) {
|
func die(what string, a ...string) {
|
||||||
_, file, line, ok := runtime.Caller(2)
|
_, file, line, ok := runtime.Caller(callerSkip)
|
||||||
if !ok {
|
if !ok {
|
||||||
panic(what)
|
panic(what)
|
||||||
}
|
}
|
||||||
@@ -31,30 +34,32 @@ func die(what string, a ...string) {
|
|||||||
s = ": " + s
|
s = ": " + s
|
||||||
}
|
}
|
||||||
panic(what + s)
|
panic(what + s)
|
||||||
} else {
|
|
||||||
fmt.Fprintf(os.Stderr, "%s", what)
|
|
||||||
if len(a) > 0 {
|
|
||||||
s := strings.Join(a, ", ")
|
|
||||||
fmt.Fprintln(os.Stderr, ": "+s)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(os.Stderr, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(os.Stderr, "\t%s line %d\n", file, line)
|
|
||||||
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "%s", what)
|
||||||
|
if len(a) > 0 {
|
||||||
|
s := strings.Join(a, ", ")
|
||||||
|
fmt.Fprintln(os.Stderr, ": "+s)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(os.Stderr, "\t%s line %d\n", file, line)
|
||||||
|
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bool asserts that cond is false.
|
// Bool asserts that cond is false.
|
||||||
//
|
//
|
||||||
// For example, this would replace
|
// For example, this would replace
|
||||||
// if x < 0 {
|
//
|
||||||
// log.Fatal("x is subzero")
|
// if x < 0 {
|
||||||
// }
|
// log.Fatal("x is subzero")
|
||||||
|
// }
|
||||||
//
|
//
|
||||||
// The same assertion would be
|
// The same assertion would be
|
||||||
// assert.Bool(x, "x is subzero")
|
//
|
||||||
|
// assert.Bool(x, "x is subzero")
|
||||||
func Bool(cond bool, s ...string) {
|
func Bool(cond bool, s ...string) {
|
||||||
if NoDebug {
|
if NoDebug {
|
||||||
return
|
return
|
||||||
@@ -68,11 +73,12 @@ func Bool(cond bool, s ...string) {
|
|||||||
// Error asserts that err is not nil, e.g. that an error has occurred.
|
// Error asserts that err is not nil, e.g. that an error has occurred.
|
||||||
//
|
//
|
||||||
// For example,
|
// For example,
|
||||||
// if err == nil {
|
//
|
||||||
// log.Fatal("call to <something> should have failed")
|
// if err == nil {
|
||||||
// }
|
// log.Fatal("call to <something> should have failed")
|
||||||
// // becomes
|
// }
|
||||||
// assert.Error(err, "call to <something> should have failed")
|
// // becomes
|
||||||
|
// assert.Error(err, "call to <something> should have failed")
|
||||||
func Error(err error, s ...string) {
|
func Error(err error, s ...string) {
|
||||||
if NoDebug {
|
if NoDebug {
|
||||||
return
|
return
|
||||||
@@ -100,7 +106,7 @@ func NoError(err error, s ...string) {
|
|||||||
|
|
||||||
// ErrorEq asserts that the actual error is the expected error.
|
// ErrorEq asserts that the actual error is the expected error.
|
||||||
func ErrorEq(expected, actual error) {
|
func ErrorEq(expected, actual error) {
|
||||||
if NoDebug || (expected == actual) {
|
if NoDebug || (errors.Is(expected, actual)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +161,7 @@ func NoErrorT(t *testing.T, err error) {
|
|||||||
// ErrorEqT compares a pair of errors, calling Fatal on it if they
|
// ErrorEqT compares a pair of errors, calling Fatal on it if they
|
||||||
// don't match.
|
// don't match.
|
||||||
func ErrorEqT(t *testing.T, expected, actual error) {
|
func ErrorEqT(t *testing.T, expected, actual error) {
|
||||||
if NoDebug || (expected == actual) {
|
if NoDebug || (errors.Is(expected, actual)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,29 +10,21 @@
|
|||||||
// backoff is configured with a maximum duration that will not be
|
// backoff is configured with a maximum duration that will not be
|
||||||
// exceeded.
|
// exceeded.
|
||||||
//
|
//
|
||||||
// The `New` function will attempt to use the system's cryptographic
|
// This package uses math/rand/v2 for jitter, which is automatically
|
||||||
// random number generator to seed a Go math/rand random number
|
// seeded from a cryptographically secure source.
|
||||||
// source. If this fails, the package will panic on startup.
|
|
||||||
package backoff
|
package backoff
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
"math"
|
"math"
|
||||||
mrand "math/rand"
|
"math/rand/v2"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var prngMu sync.Mutex
|
|
||||||
var prng *mrand.Rand
|
|
||||||
|
|
||||||
// DefaultInterval is used when a Backoff is initialised with a
|
// DefaultInterval is used when a Backoff is initialised with a
|
||||||
// zero-value Interval.
|
// zero-value Interval.
|
||||||
var DefaultInterval = 5 * time.Minute
|
var DefaultInterval = 5 * time.Minute
|
||||||
|
|
||||||
// DefaultMaxDuration is maximum amount of time that the backoff will
|
// DefaultMaxDuration is the maximum amount of time that the backoff will
|
||||||
// delay for.
|
// delay for.
|
||||||
var DefaultMaxDuration = 6 * time.Hour
|
var DefaultMaxDuration = 6 * time.Hour
|
||||||
|
|
||||||
@@ -50,10 +42,9 @@ type Backoff struct {
|
|||||||
// interval controls the time step for backing off.
|
// interval controls the time step for backing off.
|
||||||
interval time.Duration
|
interval time.Duration
|
||||||
|
|
||||||
// noJitter controls whether to use the "Full Jitter"
|
// noJitter controls whether to use the "Full Jitter" improvement to attempt
|
||||||
// improvement to attempt to smooth out spikes in a high
|
// to smooth out spikes in a high-contention scenario. If noJitter is set to
|
||||||
// contention scenario. If noJitter is set to true, no
|
// true, no jitter will be introduced.
|
||||||
// jitter will be introduced.
|
|
||||||
noJitter bool
|
noJitter bool
|
||||||
|
|
||||||
// decay controls the decay of n. If it is non-zero, n is
|
// decay controls the decay of n. If it is non-zero, n is
|
||||||
@@ -65,17 +56,17 @@ type Backoff struct {
|
|||||||
lastTry time.Time
|
lastTry time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new backoff with the specified max duration and
|
// New creates a new backoff with the specified maxDuration duration and
|
||||||
// interval. Zero values may be used to use the default values.
|
// interval. Zero values may be used to use the default values.
|
||||||
//
|
//
|
||||||
// Panics if either max or interval is negative.
|
// Panics if either dMax or interval is negative.
|
||||||
func New(max time.Duration, interval time.Duration) *Backoff {
|
func New(dMax time.Duration, interval time.Duration) *Backoff {
|
||||||
if max < 0 || interval < 0 {
|
if dMax < 0 || interval < 0 {
|
||||||
panic("backoff: max or interval is negative")
|
panic("backoff: dMax or interval is negative")
|
||||||
}
|
}
|
||||||
|
|
||||||
b := &Backoff{
|
b := &Backoff{
|
||||||
maxDuration: max,
|
maxDuration: dMax,
|
||||||
interval: interval,
|
interval: interval,
|
||||||
}
|
}
|
||||||
b.setup()
|
b.setup()
|
||||||
@@ -84,31 +75,12 @@ func New(max time.Duration, interval time.Duration) *Backoff {
|
|||||||
|
|
||||||
// NewWithoutJitter works similarly to New, except that the created
|
// NewWithoutJitter works similarly to New, except that the created
|
||||||
// Backoff will not use jitter.
|
// Backoff will not use jitter.
|
||||||
func NewWithoutJitter(max time.Duration, interval time.Duration) *Backoff {
|
func NewWithoutJitter(dMax time.Duration, interval time.Duration) *Backoff {
|
||||||
b := New(max, interval)
|
b := New(dMax, interval)
|
||||||
b.noJitter = true
|
b.noJitter = true
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
var buf [8]byte
|
|
||||||
var n int64
|
|
||||||
|
|
||||||
_, err := io.ReadFull(rand.Reader, buf[:])
|
|
||||||
if err != nil {
|
|
||||||
panic(err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// G115: Intentional uint64->int64 conversion. Overflow is acceptable here
|
|
||||||
// since math/rand.NewSource accepts any int64 value (positive or negative)
|
|
||||||
// as a valid seed. The conversion ensures uniform distribution across the
|
|
||||||
// entire int64 range.
|
|
||||||
n = int64(binary.LittleEndian.Uint64(buf[:])) // #nosec G115
|
|
||||||
|
|
||||||
src := mrand.NewSource(n)
|
|
||||||
prng = mrand.New(src)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Backoff) setup() {
|
func (b *Backoff) setup() {
|
||||||
if b.interval == 0 {
|
if b.interval == 0 {
|
||||||
b.interval = DefaultInterval
|
b.interval = DefaultInterval
|
||||||
@@ -126,35 +98,44 @@ func (b *Backoff) Duration() time.Duration {
|
|||||||
|
|
||||||
b.decayN()
|
b.decayN()
|
||||||
|
|
||||||
t := b.duration(b.n)
|
d := b.duration(b.n)
|
||||||
|
|
||||||
if b.n < math.MaxUint64 {
|
if b.n < math.MaxUint64 {
|
||||||
b.n++
|
b.n++
|
||||||
}
|
}
|
||||||
|
|
||||||
if !b.noJitter {
|
if !b.noJitter {
|
||||||
prngMu.Lock()
|
d = time.Duration(rand.Int64N(int64(d))) // #nosec G404
|
||||||
t = time.Duration(prng.Int63n(int64(t)))
|
|
||||||
prngMu.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return t
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const maxN uint64 = 63
|
||||||
|
|
||||||
// requires b to be locked.
|
// requires b to be locked.
|
||||||
func (b *Backoff) duration(n uint64) (t time.Duration) {
|
func (b *Backoff) duration(n uint64) time.Duration {
|
||||||
// Saturate pow
|
// Use left shift on the underlying integer representation to avoid
|
||||||
pow := time.Duration(math.MaxInt64)
|
// multiplying time.Duration by time.Duration (which is semantically
|
||||||
if n < 63 {
|
// incorrect and flagged by linters).
|
||||||
pow = 1 << n
|
if n >= maxN {
|
||||||
|
// Saturate when n would overflow a 64-bit shift or exceed maxDuration.
|
||||||
|
return b.maxDuration
|
||||||
}
|
}
|
||||||
|
|
||||||
t = b.interval * pow
|
// Calculate 2^n * interval using a shift. Detect overflow by checking
|
||||||
if t/pow != b.interval || t > b.maxDuration {
|
// for sign change or monotonicity loss and clamp to maxDuration.
|
||||||
t = b.maxDuration
|
shifted := b.interval << n
|
||||||
|
if shifted < 0 || shifted < b.interval {
|
||||||
|
// Overflow occurred during the shift; clamp to maxDuration.
|
||||||
|
return b.maxDuration
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
if shifted > b.maxDuration {
|
||||||
|
return b.maxDuration
|
||||||
|
}
|
||||||
|
|
||||||
|
return shifted
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset resets the attempt counter of a backoff.
|
// Reset resets the attempt counter of a backoff.
|
||||||
@@ -178,7 +159,7 @@ func (b *Backoff) SetDecay(decay time.Duration) {
|
|||||||
b.decay = decay
|
b.decay = decay
|
||||||
}
|
}
|
||||||
|
|
||||||
// requires b to be locked
|
// requires b to be locked.
|
||||||
func (b *Backoff) decayN() {
|
func (b *Backoff) decayN() {
|
||||||
if b.decay == 0 {
|
if b.decay == 0 {
|
||||||
return
|
return
|
||||||
@@ -190,7 +171,9 @@ func (b *Backoff) decayN() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lastDuration := b.duration(b.n - 1)
|
lastDuration := b.duration(b.n - 1)
|
||||||
decayed := time.Since(b.lastTry) > lastDuration+b.decay
|
// Reset when the elapsed time is at least the previous backoff plus decay.
|
||||||
|
// Using ">=" avoids boundary flakiness in tests and real usage.
|
||||||
|
decayed := time.Since(b.lastTry) >= lastDuration+b.decay
|
||||||
b.lastTry = time.Now()
|
b.lastTry = time.Now()
|
||||||
|
|
||||||
if !decayed {
|
if !decayed {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
// If given New with 0's and no jitter, ensure that certain invariants are met:
|
// If given New with 0's and no jitter, ensure that certain invariants are met:
|
||||||
//
|
//
|
||||||
// - the default max duration and interval should be used
|
// - the default maxDuration duration and interval should be used
|
||||||
// - noJitter should be true
|
// - noJitter should be true
|
||||||
// - the RNG should not be initialised
|
// - the RNG should not be initialised
|
||||||
// - the first duration should be equal to the default interval
|
// - the first duration should be equal to the default interval
|
||||||
@@ -17,7 +17,11 @@ func TestDefaults(t *testing.T) {
|
|||||||
b := NewWithoutJitter(0, 0)
|
b := NewWithoutJitter(0, 0)
|
||||||
|
|
||||||
if b.maxDuration != DefaultMaxDuration {
|
if b.maxDuration != DefaultMaxDuration {
|
||||||
t.Fatalf("expected new backoff to use the default max duration (%s), but have %s", DefaultMaxDuration, b.maxDuration)
|
t.Fatalf(
|
||||||
|
"expected new backoff to use the default maxDuration duration (%s), but have %s",
|
||||||
|
DefaultMaxDuration,
|
||||||
|
b.maxDuration,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.interval != DefaultInterval {
|
if b.interval != DefaultInterval {
|
||||||
@@ -48,7 +52,7 @@ func TestSetup(t *testing.T) {
|
|||||||
func TestTries(t *testing.T) {
|
func TestTries(t *testing.T) {
|
||||||
b := NewWithoutJitter(5, 1)
|
b := NewWithoutJitter(5, 1)
|
||||||
|
|
||||||
for i := uint64(0); i < 3; i++ {
|
for i := range uint64(3) {
|
||||||
if b.n != i {
|
if b.n != i {
|
||||||
t.Fatalf("want tries=%d, have tries=%d", i, b.n)
|
t.Fatalf("want tries=%d, have tries=%d", i, b.n)
|
||||||
}
|
}
|
||||||
@@ -73,7 +77,7 @@ func TestTries(t *testing.T) {
|
|||||||
func TestReset(t *testing.T) {
|
func TestReset(t *testing.T) {
|
||||||
const iter = 10
|
const iter = 10
|
||||||
b := New(1000, 1)
|
b := New(1000, 1)
|
||||||
for i := 0; i < iter; i++ {
|
for range iter {
|
||||||
_ = b.Duration()
|
_ = b.Duration()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,17 +92,17 @@ func TestReset(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const decay = 5 * time.Millisecond
|
const decay = 5 * time.Millisecond
|
||||||
const max = 10 * time.Millisecond
|
const maxDuration = 10 * time.Millisecond
|
||||||
const interval = time.Millisecond
|
const interval = time.Millisecond
|
||||||
|
|
||||||
func TestDecay(t *testing.T) {
|
func TestDecay(t *testing.T) {
|
||||||
const iter = 10
|
const iter = 10
|
||||||
|
|
||||||
b := NewWithoutJitter(max, 1)
|
b := NewWithoutJitter(maxDuration, 1)
|
||||||
b.SetDecay(decay)
|
b.SetDecay(decay)
|
||||||
|
|
||||||
var backoff time.Duration
|
var backoff time.Duration
|
||||||
for i := 0; i < iter; i++ {
|
for range iter {
|
||||||
backoff = b.Duration()
|
backoff = b.Duration()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +131,7 @@ func TestDecaySaturation(t *testing.T) {
|
|||||||
b.SetDecay(decay)
|
b.SetDecay(decay)
|
||||||
|
|
||||||
var duration time.Duration
|
var duration time.Duration
|
||||||
for i := 0; i <= 2; i++ {
|
for range 3 {
|
||||||
duration = b.Duration()
|
duration = b.Duration()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +149,7 @@ func TestDecaySaturation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ExampleBackoff_SetDecay() {
|
func ExampleBackoff_SetDecay() {
|
||||||
b := NewWithoutJitter(max, interval)
|
b := NewWithoutJitter(maxDuration, interval)
|
||||||
b.SetDecay(decay)
|
b.SetDecay(decay)
|
||||||
|
|
||||||
// try 0
|
// try 0
|
||||||
|
|||||||
@@ -158,59 +158,87 @@ type EncryptedContentInfo struct {
|
|||||||
EncryptedContent []byte `asn1:"tag:0,optional"`
|
EncryptedContent []byte `asn1:"tag:0,optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func unmarshalInit(raw []byte) (init initPKCS7, err error) {
|
||||||
|
_, err = asn1.Unmarshal(raw, &init)
|
||||||
|
if err != nil {
|
||||||
|
return initPKCS7{}, certerr.ParsingError(certerr.ErrorSourceCertificate, err)
|
||||||
|
}
|
||||||
|
return init, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func populateData(msg *PKCS7, content asn1.RawValue) error {
|
||||||
|
msg.ContentInfo = "Data"
|
||||||
|
_, err := asn1.Unmarshal(content.Bytes, &msg.Content.Data)
|
||||||
|
if err != nil {
|
||||||
|
return certerr.ParsingError(certerr.ErrorSourceCertificate, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func populateSignedData(msg *PKCS7, contentBytes []byte) error {
|
||||||
|
msg.ContentInfo = "SignedData"
|
||||||
|
var sd signedData
|
||||||
|
if _, err := asn1.Unmarshal(contentBytes, &sd); err != nil {
|
||||||
|
return certerr.ParsingError(certerr.ErrorSourceCertificate, err)
|
||||||
|
}
|
||||||
|
if len(sd.Certificates.Bytes) != 0 {
|
||||||
|
certs, err := x509.ParseCertificates(sd.Certificates.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return certerr.ParsingError(certerr.ErrorSourceCertificate, err)
|
||||||
|
}
|
||||||
|
msg.Content.SignedData.Certificates = certs
|
||||||
|
}
|
||||||
|
if len(sd.Crls.Bytes) != 0 {
|
||||||
|
crl, err := x509.ParseRevocationList(sd.Crls.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return certerr.ParsingError(certerr.ErrorSourceCertificate, err)
|
||||||
|
}
|
||||||
|
msg.Content.SignedData.Crl = crl
|
||||||
|
}
|
||||||
|
msg.Content.SignedData.Version = sd.Version
|
||||||
|
msg.Content.SignedData.Raw = contentBytes
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func populateEncryptedData(msg *PKCS7, contentBytes []byte) error {
|
||||||
|
msg.ContentInfo = "EncryptedData"
|
||||||
|
var ed EncryptedData
|
||||||
|
if _, err := asn1.Unmarshal(contentBytes, &ed); err != nil {
|
||||||
|
return certerr.ParsingError(certerr.ErrorSourceCertificate, err)
|
||||||
|
}
|
||||||
|
if ed.Version != 0 {
|
||||||
|
return certerr.ParsingError(certerr.ErrorSourceCertificate, errors.New("only PKCS #7 encryptedData version 0 is supported"))
|
||||||
|
}
|
||||||
|
msg.Content.EncryptedData = ed
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ParsePKCS7 attempts to parse the DER encoded bytes of a
|
// ParsePKCS7 attempts to parse the DER encoded bytes of a
|
||||||
// PKCS7 structure.
|
// PKCS7 structure.
|
||||||
func ParsePKCS7(raw []byte) (msg *PKCS7, err error) {
|
func ParsePKCS7(raw []byte) (msg *PKCS7, err error) {
|
||||||
|
|
||||||
var pkcs7 initPKCS7
|
pkcs7, err := unmarshalInit(raw)
|
||||||
_, err = asn1.Unmarshal(raw, &pkcs7)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, certerr.ParsingError(certerr.ErrorSourceCertificate, err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
msg = new(PKCS7)
|
msg = new(PKCS7)
|
||||||
msg.Raw = pkcs7.Raw
|
msg.Raw = pkcs7.Raw
|
||||||
msg.ContentInfo = pkcs7.ContentType.String()
|
msg.ContentInfo = pkcs7.ContentType.String()
|
||||||
switch {
|
|
||||||
case msg.ContentInfo == ObjIDData:
|
|
||||||
msg.ContentInfo = "Data"
|
|
||||||
_, err = asn1.Unmarshal(pkcs7.Content.Bytes, &msg.Content.Data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, certerr.ParsingError(certerr.ErrorSourceCertificate, err)
|
|
||||||
}
|
|
||||||
case msg.ContentInfo == ObjIDSignedData:
|
|
||||||
msg.ContentInfo = "SignedData"
|
|
||||||
var signedData signedData
|
|
||||||
_, err = asn1.Unmarshal(pkcs7.Content.Bytes, &signedData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, certerr.ParsingError(certerr.ErrorSourceCertificate, err)
|
|
||||||
}
|
|
||||||
if len(signedData.Certificates.Bytes) != 0 {
|
|
||||||
msg.Content.SignedData.Certificates, err = x509.ParseCertificates(signedData.Certificates.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, certerr.ParsingError(certerr.ErrorSourceCertificate, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(signedData.Crls.Bytes) != 0 {
|
|
||||||
msg.Content.SignedData.Crl, err = x509.ParseRevocationList(signedData.Crls.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, certerr.ParsingError(certerr.ErrorSourceCertificate, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
msg.Content.SignedData.Version = signedData.Version
|
|
||||||
msg.Content.SignedData.Raw = pkcs7.Content.Bytes
|
|
||||||
case msg.ContentInfo == ObjIDEncryptedData:
|
|
||||||
msg.ContentInfo = "EncryptedData"
|
|
||||||
var encryptedData EncryptedData
|
|
||||||
_, err = asn1.Unmarshal(pkcs7.Content.Bytes, &encryptedData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, certerr.ParsingError(certerr.ErrorSourceCertificate, err)
|
|
||||||
}
|
|
||||||
if encryptedData.Version != 0 {
|
|
||||||
return nil, certerr.ParsingError(certerr.ErrorSourceCertificate, errors.New("only PKCS #7 encryptedData version 0 is supported"))
|
|
||||||
}
|
|
||||||
msg.Content.EncryptedData = encryptedData
|
|
||||||
|
|
||||||
|
switch msg.ContentInfo {
|
||||||
|
case ObjIDData:
|
||||||
|
if err := populateData(msg, pkcs7.Content); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case ObjIDSignedData:
|
||||||
|
if err := populateSignedData(msg, pkcs7.Content.Bytes); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case ObjIDEncryptedData:
|
||||||
|
if err := populateEncryptedData(msg, pkcs7.Content.Bytes); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return nil, certerr.ParsingError(certerr.ErrorSourceCertificate, errors.New("only PKCS# 7 content of type data, signed data or encrypted data can be parsed"))
|
return nil, certerr.ParsingError(certerr.ErrorSourceCertificate, errors.New("only PKCS# 7 content of type data, signed data or encrypted data can be parsed"))
|
||||||
}
|
}
|
||||||
|
|||||||
6
go.mod
6
go.mod
@@ -1,14 +1,14 @@
|
|||||||
module git.wntrmute.dev/kyle/goutils
|
module git.wntrmute.dev/kyle/goutils
|
||||||
|
|
||||||
go 1.22
|
go 1.24.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/hashicorp/go-syslog v1.0.0
|
github.com/hashicorp/go-syslog v1.0.0
|
||||||
github.com/kr/text v0.2.0
|
github.com/kr/text v0.2.0
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/pkg/sftp v1.12.0
|
github.com/pkg/sftp v1.12.0
|
||||||
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b
|
golang.org/x/crypto v0.44.0
|
||||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad
|
golang.org/x/sys v0.38.0
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
5
go.sum
5
go.sum
@@ -27,12 +27,17 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
|||||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b h1:Qwe1rC8PSniVfAFPFJeyUkB+zcysC3RgJBAGk7eqBEU=
|
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b h1:Qwe1rC8PSniVfAFPFJeyUkB+zcysC3RgJBAGk7eqBEU=
|
||||||
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
|
||||||
|
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
|
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
|
||||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||||
|
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
||||||
|
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
|
|||||||
Reference in New Issue
Block a user