/
sum.go
167 lines (150 loc) · 3.39 KB
/
sum.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
package nuggit
import (
"crypto/sha1"
"crypto/sha256"
"encoding/hex"
"fmt"
"hash"
"hash/crc32"
"strings"
"golang.org/x/exp/slices"
)
var crc32Tab = crc32.MakeTable(crc32.Castagnoli)
// Sums specifies checksums used for integrity checks
// on resources fetched from remote sources.
// The polynomial used for CRC32 is Castagnoli's polynomial.
type Sums struct {
// CRC32 hex encoded checksum.
CRC32 string `json:"crc32,omitempty"`
// SHA1 hex encoded hash.
SHA1 string `json:"sha1,omitempty"`
// SHA2 hex encoded hash.
SHA2 string `json:"sha2,omitempty"`
}
// Checksum creates checksums for the data.
func Checksum(data []byte) *Sums {
var sums Sums
var hashes [3]string
for i, h := range []hash.Hash{
crc32.New(crc32Tab),
sha1.New(),
sha256.New(),
} {
h.Write(data)
hashes[i] = hex.EncodeToString(h.Sum(nil))
}
sums.CRC32 = hashes[0]
sums.SHA1 = hashes[1]
sums.SHA2 = hashes[2]
return &sums
}
type SumTest struct {
Sum string
Expected string
Actual string
}
func (t SumTest) Fail() bool { return t.Expected != "" && t.Expected != t.Actual }
func (t SumTest) Format(verbose bool) string {
if !verbose {
if t.Fail() {
return fmt.Sprintf("%-6s FAIL", t.Sum)
}
return fmt.Sprintf("%-6s OK", t.Sum)
}
if t.Fail() {
return fmt.Sprintf("%-6s %s != %s", t.Sum, t.Expected, t.Actual)
}
return fmt.Sprintf("%-6s %s", t.Sum, t.Actual)
}
func (s SumTest) String() string {
return s.Format(false)
}
type SumTests []SumTest
func (s SumTests) Fail() bool {
for _, t := range s {
if t.Fail() {
return true
}
}
return false
}
// Format the response for the `nuggit sum` subcommand.
func (s SumTests) Format(verbose bool) string {
if s == nil {
return "PASS"
}
var sb strings.Builder
tests := slices.Clone(s)
slices.SortFunc(tests, func(a, b SumTest) int { return strings.Compare(a.Sum, b.Sum) })
for _, test := range tests {
fmt.Fprintln(&sb, test.Format(verbose))
}
return sb.String()
}
func (s SumTests) FormatError() (string, bool) {
var failed []string
var failDetails []string
tests := slices.Clone(s)
slices.SortFunc(tests, func(a, b SumTest) int { return strings.Compare(a.Sum, b.Sum) })
for _, t := range tests {
if t.Fail() {
failed = append(failed, t.Sum)
failDetails = append(failDetails, fmt.Sprintf("%s != %s", t.Expected, t.Actual))
}
}
if len(failed) == 0 {
return "", false
}
return fmt.Sprintf("unexpected sums for (%s): %s",
strings.Join(failed, ", "),
strings.Join(failDetails, ", ")), true
}
// Test the checksums sums against the other sums.
// The returned error is always of type *SumError.
func (s *Sums) Test(other *Sums) SumTests {
if s == nil {
return nil
}
var s2 Sums
if other != nil {
s2 = *other
}
if *s == s2 { // Fast check for equality.
return nil
}
// Slow check all fields.
// Only validate if Sum is nonempty in s.
var tt SumTests
for _, t := range []struct {
name string
want string
got string
}{{
name: "crc32",
want: s.CRC32,
got: other.CRC32,
}, {
name: "sha1",
want: s.SHA1,
got: other.SHA1,
}, {
name: "sha2",
want: s.SHA2,
got: other.SHA2,
}} {
tt = append(tt, SumTest{
Sum: t.name,
Expected: t.want,
Actual: t.got,
})
}
return tt
}
// TestBytes tests the bytes against the checksums.
// The returned error is always of type *SumError.
func (s *Sums) TestBytes(data []byte) SumTests {
if s == nil {
return nil
}
return s.Test(Checksum(data))
}