summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEuAndreh <eu@euandre.org>2025-05-15 13:23:59 -0300
committerEuAndreh <eu@euandre.org>2025-05-15 15:13:15 -0300
commit81f0c44d475e57fd56c6d8009e0c88f65d2fd84e (patch)
treefb2708925293b0bb7cdf3ab81d4df19c0b0d5af9
parentrm -rf tests/functional/version/ (diff)
downloadscrypt-81f0c44d475e57fd56c6d8009e0c88f65d2fd84e.tar.gz
scrypt-81f0c44d475e57fd56c6d8009e0c88f65d2fd84e.tar.xz
src/scrypt.go: Improve CLI handling, add tests alongside
-rw-r--r--src/scrypt.go90
-rw-r--r--tests/benchmarks/hash/scrypt.go2
-rw-r--r--tests/functional/hash-and-check/scrypt.go8
-rw-r--r--tests/fuzz/api/scrypt.go2
-rwxr-xr-xtests/integration.sh2
-rw-r--r--tests/scrypt.go112
6 files changed, 187 insertions, 29 deletions
diff --git a/src/scrypt.go b/src/scrypt.go
index 511546f..6e212b4 100644
--- a/src/scrypt.go
+++ b/src/scrypt.go
@@ -3,12 +3,14 @@ package scrypt
import (
"crypto/rand"
"encoding/hex"
+ "flag"
"fmt"
"io"
"os"
"slices"
gt "gotext"
+ g "gobang"
)
@@ -39,17 +41,29 @@ var (
-type HashInput struct{
+type HashInputT struct{
Password []byte
Salt []byte
}
-type CheckInput struct{
+type CheckInputT struct{
Password []byte
Salt []byte
Hash []byte
}
+type argsT struct{
+ allArgs []string
+ input HashInputT
+}
+
+type envT struct{
+ args argsT
+ in io.Reader
+ out io.Writer
+ err io.Writer
+}
+
// Package scrypt implements the scrypt key derivation function as defined in
@@ -108,7 +122,7 @@ func scrypt(
return out, nil
}
-func Hash(input HashInput) ([]byte, error) {
+func Hash(input HashInputT) ([]byte, error) {
if len(input.Salt) < _SALT_MIN_LENGTH {
return nil, ErrSaltTooSmall
}
@@ -137,8 +151,8 @@ func Salt() ([]byte, error) {
return SaltFrom(rand.Reader)
}
-func Check(input CheckInput) (bool, error) {
- hashInput := HashInput{
+func Check(input CheckInputT) (bool, error) {
+ hashInput := HashInputT{
Password: input.Password,
Salt: input.Salt,
}
@@ -151,29 +165,67 @@ func Check(input CheckInput) (bool, error) {
return slices.Equal(candidate, input.Hash), nil
}
+func usage(argv0 string, w io.Writer) {
+ fmt.Fprintf(
+ w,
+ "Usage: %s PASSWORD SALT\n",
+ argv0,
+ )
+}
+func getopt(allArgs []string, w io.Writer) (argsT, int) {
+ argv0 := allArgs[0]
+ argv := allArgs[1:]
+ fs := flag.NewFlagSet("", flag.ContinueOnError)
+ fs.Usage = func() {}
+ fs.SetOutput(w)
-func Main() {
- if len(os.Args) != 3 {
- fmt.Fprintf(os.Stderr, gt.Gettext("Usage: scrypt PASSWORD SALT\n"))
- os.Exit(2)
+ if fs.Parse(argv) != nil {
+ usage(argv0, w)
+ return argsT{}, 2
+ }
+
+ subArgs := fs.Args()
+ if len(subArgs) != 2 {
+ usage(argv0, w)
+ return argsT{}, 2
}
- password := []byte(os.Args[1])
- salt := []byte(os.Args[2])
- input := HashInput{
+ password := []byte(subArgs[0])
+ salt := []byte(subArgs[1])
+ input := HashInputT{
Password: password,
Salt: salt,
}
- payload, err := Hash(input)
+ return argsT{
+ allArgs: allArgs,
+ input: input,
+ }, 0
+}
+
+func run(env envT) int {
+ payload, err := Hash(env.args.input)
if err != nil {
- if err == ErrSaltTooSmall {
- fmt.Fprintln(os.Stderr, err)
- os.Exit(2)
- }
- panic(err)
+ fmt.Fprintln(env.err, err)
+ return 1
}
- fmt.Println(hex.EncodeToString(payload))
+ fmt.Fprintln(env.out, hex.EncodeToString(payload))
+ return 0
+}
+
+
+
+func Main() {
+ g.Init()
+ gt.Init(Name, LOCALEDIR)
+ args, rc := getopt(os.Args, os.Stderr)
+ g.ExitIf(rc)
+ os.Exit(run(envT{
+ args: args,
+ in: os.Stdin,
+ out: os.Stdout,
+ err: os.Stderr,
+ }))
}
diff --git a/tests/benchmarks/hash/scrypt.go b/tests/benchmarks/hash/scrypt.go
index 54657d0..894e6da 100644
--- a/tests/benchmarks/hash/scrypt.go
+++ b/tests/benchmarks/hash/scrypt.go
@@ -18,7 +18,7 @@ func MainTest() {
password := []byte("password")
salt := []byte("salt0123456789abcdef0123456789abcdef")
- input := HashInput{
+ input := HashInputT{
Password: password,
Salt: salt,
}
diff --git a/tests/functional/hash-and-check/scrypt.go b/tests/functional/hash-and-check/scrypt.go
index 065b9b5..9eeeb22 100644
--- a/tests/functional/hash-and-check/scrypt.go
+++ b/tests/functional/hash-and-check/scrypt.go
@@ -14,14 +14,14 @@ func MainTest() {
salt = []byte("a fixed salt____________________")
)
- hashInput := HashInput{
+ hashInput := HashInputT{
Password: password,
Salt: salt,
}
hash, err := Hash(hashInput)
g.TErrorIf(err)
- checkInput := CheckInput{
+ checkInput := CheckInputT{
Password: password,
Salt: salt,
Hash: hash,
@@ -38,14 +38,14 @@ func MainTest() {
salt, err := Salt()
g.TErrorIf(err)
- hashInput := HashInput{
+ hashInput := HashInputT{
Password: password,
Salt: salt,
}
hash, err := Hash(hashInput)
g.TErrorIf(err)
- checkInput := CheckInput{
+ checkInput := CheckInputT{
Password: password,
Salt: salt,
Hash: hash,
diff --git a/tests/fuzz/api/scrypt.go b/tests/fuzz/api/scrypt.go
index c037add..7c2c503 100644
--- a/tests/fuzz/api/scrypt.go
+++ b/tests/fuzz/api/scrypt.go
@@ -14,7 +14,7 @@ func api(f *testing.F) {
return
}
- input := HashInput{
+ input := HashInputT{
Password: password,
Salt: salt,
}
diff --git a/tests/integration.sh b/tests/integration.sh
index e31ae4d..8115ce5 100755
--- a/tests/integration.sh
+++ b/tests/integration.sh
@@ -13,7 +13,7 @@ test_needs_minimum_salt_length() {
trap 'rm -f "$OUT" "$ERR"' EXIT
STATUS=0
./scrypt.bin password salt 1>"$OUT" 2>"$ERR" || STATUS=$?
- assert_status 2
+ assert_status 1
assert_empty_stdout
assert_fgrep_stderr 'salt is too small'
rm -f "$OUT" "$ERR"
diff --git a/tests/scrypt.go b/tests/scrypt.go
index 710ea82..0a9f599 100644
--- a/tests/scrypt.go
+++ b/tests/scrypt.go
@@ -217,7 +217,7 @@ func test_Hash() {
salt, err := Salt()
g.TErrorIf(err)
- input := HashInput{
+ input := HashInputT{
Password: password,
Salt: salt,
}
@@ -236,7 +236,7 @@ func test_Check() {
g.TestStart("Check()")
h := func(password []byte, salt []byte) []byte {
- input := HashInput{
+ input := HashInputT{
Password: password,
Salt: salt,
}
@@ -246,7 +246,7 @@ func test_Check() {
}
chk := func(password []byte, salt []byte, hash []byte) bool {
- input := CheckInput{
+ input := CheckInputT{
Password: password,
Salt: salt,
Hash: hash,
@@ -301,12 +301,118 @@ func test_Check() {
})
}
+func test_usage() {
+ g.TestStart("usage()")
+
+ g.Testing("it writes the usage string to the io.Writer", func() {
+ w := strings.Builder{}
+ usage("xxx", &w)
+
+ expected := g.Heredoc(`
+ Usage: xxx PASSWORD SALT
+ `)
+ g.TAssertEqual(w.String(), expected)
+ })
+}
+
+func test_getopt() {
+ g.TestStart("getopt()")
+
+ usage := g.Heredoc(`
+ Usage: $0 PASSWORD SALT
+ `)
+
+ g.Testing("we suppress the default error message", func() {
+ w := strings.Builder{}
+ argv := []string{"$0", "-h"}
+ _, rc := getopt(argv, &w)
+
+ g.TAssertEqual(w.String(), usage)
+ g.TAssertEqual(rc, 2)
+ })
+
+ g.Testing("we get an unsupported option error", func() {
+ w := strings.Builder{}
+ argv := []string{"$0", "-a"}
+ _, rc := getopt(argv, &w)
+
+ const message = "flag provided but not defined: -a\n"
+ g.TAssertEqual(w.String(), message + usage)
+ g.TAssertEqual(rc, 2)
+ })
+
+ g.Testing("the args have the input data", func() {
+ w := strings.Builder{}
+ argv := []string{"$0", "abcdef", "aaaabbbbccccdddd"}
+ args, rc := getopt(argv, &w)
+
+ expected := argsT{
+ allArgs: []string{"$0", "abcdef", "aaaabbbbccccdddd"},
+ input: HashInputT{
+ Password: []byte("abcdef"),
+ Salt: []byte("aaaabbbbccccdddd"),
+ },
+ }
+
+ g.TAssertEqual(args, expected)
+ g.TAssertEqual(rc, 0)
+ })
+}
+
+func test_run() {
+ g.TestStart("run()")
+
+ g.Testing("a small salt returns 1", func() {
+ w := strings.Builder{}
+ env := envT{
+ args: argsT{
+ input: HashInputT{
+ Salt: []byte("a salt"),
+ },
+ },
+ err: &w,
+ }
+
+ rc := run(env)
+ g.TAssertEqual(rc, 1)
+ g.TAssertEqual(w.String(), "scrypt: salt is too small\n")
+ })
+
+ g.Testing("return of 0 and output otherwise", func() {
+ w := strings.Builder{}
+ env := envT{
+ args: argsT{
+ input: HashInputT{
+ Salt: []byte(
+ "eeeeffffgggghhhh" +
+ "iiiijjjjjkkkkllll",
+ ),
+ Password: []byte("password"),
+ },
+ },
+ out: &w,
+ }
+
+ const expected =
+ "9073ca9ad51942a30b20af1747e98195" +
+ "960e76fd8f20b246821692ce82c2d6f7\n"
+ rc := run(env)
+ g.TAssertEqual(w.String(), expected)
+ g.TAssertEqual(rc, 0)
+ })
+}
+
func MainTest() {
+ g.Init()
+
test_scrypt()
test_SaltFrom()
test_Salt()
test_Hash()
test_Check()
+ test_usage()
+ test_getopt()
+ test_run()
}