Skip to content

Commit 1603fab

Browse files
committed
typeddigest package
1 parent d50c716 commit 1603fab

File tree

2 files changed

+141
-0
lines changed

2 files changed

+141
-0
lines changed

pkg/typeddigest/typeddigest.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// A digest that contains the algoritm as a prefix. Example `sha256:d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592`
2+
package typeddigest
3+
4+
import (
5+
"bytes"
6+
"crypto/sha256"
7+
"encoding/hex"
8+
"errors"
9+
"fmt"
10+
"io"
11+
"strings"
12+
)
13+
14+
const (
15+
algSha256 = "sha256"
16+
)
17+
18+
type Hash struct {
19+
alg string // "sha256"
20+
digest []byte
21+
}
22+
23+
func (h *Hash) String() string {
24+
return fmt.Sprintf("%s:%x", h.alg, h.digest)
25+
}
26+
27+
func (h *Hash) Equal(other *Hash) bool {
28+
return h.alg == other.alg && bytes.Equal(h.digest, other.digest)
29+
}
30+
31+
func Parse(val string) (*Hash, error) {
32+
withErr := func(err error) (*Hash, error) { return nil, fmt.Errorf("typeddigest.Parse: %w", err) }
33+
34+
pos := strings.Index(val, ":")
35+
if pos == -1 {
36+
return withErr(errors.New("bad format, '<alg>:' prefix not found"))
37+
}
38+
39+
alg := val[:pos]
40+
digestHex := val[pos+1:]
41+
42+
if alg != algSha256 {
43+
return withErr(fmt.Errorf("unsupported algorithm: %s", alg))
44+
}
45+
46+
digest, err := hex.DecodeString(digestHex)
47+
if err != nil {
48+
return withErr(err)
49+
}
50+
51+
if expectedSize := sha256.Size; len(digest) != expectedSize {
52+
return withErr(fmt.Errorf("wrong digest size: expected %d; got %d", expectedSize, len(digest)))
53+
}
54+
55+
return &Hash{alg, digest}, nil
56+
}
57+
58+
func DigesterForAlgOf(other *Hash) func(io.Reader) (*Hash, error) {
59+
switch other.alg {
60+
case algSha256:
61+
return Sha256
62+
default:
63+
return func(io.Reader) (*Hash, error) {
64+
return nil, fmt.Errorf("typeddigest.DigesterForAlgOf: unsupported algorithm: %s", other.alg)
65+
}
66+
}
67+
}
68+
69+
func Sha256(input io.Reader) (*Hash, error) {
70+
withErr := func(err error) (*Hash, error) { return nil, fmt.Errorf("typeddigest.Sha256: %w", err) }
71+
72+
hash := sha256.New()
73+
if _, err := io.Copy(hash, input); err != nil {
74+
return withErr(err)
75+
}
76+
77+
return &Hash{algSha256, hash.Sum(nil)}, nil
78+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package typeddigest
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
"testing"
7+
8+
"github.com/function61/gokit/testing/assert"
9+
)
10+
11+
func TestParse(t *testing.T) {
12+
for _, tc := range []struct {
13+
input string
14+
output string
15+
}{
16+
{
17+
"sha256:d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592",
18+
"ok",
19+
},
20+
{
21+
"sha256:88",
22+
"ERROR: typeddigest.Parse: wrong digest size: expected 32; got 1",
23+
},
24+
{
25+
"md5:d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592",
26+
"ERROR: typeddigest.Parse: unsupported algorithm: md5",
27+
},
28+
{
29+
"",
30+
"ERROR: typeddigest.Parse: bad format, '<alg>:' prefix not found",
31+
},
32+
{
33+
"sha256:nothex",
34+
"ERROR: typeddigest.Parse: encoding/hex: invalid byte: U+006E 'n'",
35+
},
36+
} {
37+
tc := tc // pin
38+
t.Run(tc.input, func(t *testing.T) {
39+
th, err := Parse(tc.input)
40+
asOutput := func() string {
41+
if err != nil {
42+
return fmt.Sprintf("ERROR: %v", err)
43+
} else {
44+
// test stability
45+
//nolint:staticcheck // cannot upgrade to generics yet
46+
assert.EqualString(t, th.String(), tc.input)
47+
return "ok"
48+
}
49+
}()
50+
51+
//nolint:staticcheck // cannot upgrade to generics yet
52+
assert.EqualString(t, asOutput, tc.output)
53+
})
54+
}
55+
}
56+
57+
func TestSha256(t *testing.T) {
58+
th, err := Sha256(strings.NewReader("The quick brown fox jumps over the lazy dog"))
59+
assert.Ok(t, err)
60+
61+
//nolint:staticcheck // cannot upgrade to generics yet
62+
assert.EqualString(t, th.String(), "sha256:d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592")
63+
}

0 commit comments

Comments
 (0)