feat(bsr/checksum): Add function to load SHA256SUM

This provides a Sha256Sums type to store the information from a
SHA256SUM file in memory for easy lookup of the sums of files. It can be
used when checking the checksums of individual files without needing to
go back to disk.
This commit is contained in:
Timothy Messier committed 2023-05-31 12:18:19 +00:00
1 parent 74d10da7b4
commit 648842f031
2 files changed
+192

No files matched your search

@@ -0,0 +1,73 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package checksum
import (
"bufio"
"fmt"
"io"
"strings"
)
const (
sha256sumSep = " "
sha256sumBinarySep = " *"
sha256HexLength = 64
)
// Sha256Sums is a map of file names with their corresponding SHA256SUM.
type Sha256Sums map[string][]byte
// LoadSha256Sums reads from an io.Reader to populate the Sha256Sums map The
// reader is expected to return the sums in a format that is compatible with
// sha256sum, that is each line should contain the hex encoded sum followed by
// a separator and then the file name. The separator should either be two
// spaces (" ") or a space an asterisks (" *"). The former indicates a text
// file and the later a binary file. However, there is no difference between
// these for GNU systems. Therefore this also makes no distinction between the
// separators and checks for either.
//
// See: https://man7.org/linux/man-pages/man1/sha256sum.1.html
func LoadSha256Sums(r io.Reader) (Sha256Sums, error) {
const op = "checksum.LoadSha256Sums"
s := make(Sha256Sums)
scanner := bufio.NewScanner(r)
for scanner.Scan() {
var sum, file string
var ok bool
sum, file, ok = strings.Cut(scanner.Text(), sha256sumSep)
if !ok {
sum, file, ok = strings.Cut(scanner.Text(), sha256sumBinarySep)
}
if !ok {
return nil, fmt.Errorf("%s: improperly formated line", op)
}
if len(sum) != sha256HexLength {
return nil, fmt.Errorf("%s: improperly formated line", op)
}
if _, dup := s[file]; dup {
return nil, fmt.Errorf("%s: duplicate file", op)
}
s[file] = []byte(sum)
}
return s, nil
}
// Sum returns the sum for the provided file, or an error if there is no sum.
func (s Sha256Sums) Sum(f string) ([]byte, error) {
const op = "checksum.(Sha256Sums).Sum"
sum, ok := s[f]
if !ok {
return nil, fmt.Errorf("%s: no sum for file %s", op, f)
}
return sum, nil
}
@@ -0,0 +1,119 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package checksum_test
import (
"fmt"
"io"
"strings"
"testing"
"github.com/hashicorp/boundary/internal/bsr/internal/checksum"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestLoadSha256Sums(t *testing.T) {
cases := []struct {
name string
r io.Reader
want checksum.Sha256Sums
wantErr error
}{
{
"valid-multi-line-text",
strings.NewReader(`ffe65f1d98fafedea3514adc956c8ada5980c6c5d2552fd61f48401aefd5c00e file1
7849ccae8fa91bb281a118041abf921636e68853059329ac68fccd8d518cea53 file2
`),
checksum.Sha256Sums{
"file1": []byte("ffe65f1d98fafedea3514adc956c8ada5980c6c5d2552fd61f48401aefd5c00e"),
"file2": []byte("7849ccae8fa91bb281a118041abf921636e68853059329ac68fccd8d518cea53"),
},
nil,
},
{
"valid-multi-line-binary",
strings.NewReader(`ffe65f1d98fafedea3514adc956c8ada5980c6c5d2552fd61f48401aefd5c00e *file1
7849ccae8fa91bb281a118041abf921636e68853059329ac68fccd8d518cea53 *file2
`),
checksum.Sha256Sums{
"file1": []byte("ffe65f1d98fafedea3514adc956c8ada5980c6c5d2552fd61f48401aefd5c00e"),
"file2": []byte("7849ccae8fa91bb281a118041abf921636e68853059329ac68fccd8d518cea53"),
},
nil,
},
{
"valid-multi-line-text-and-binary",
strings.NewReader(`ffe65f1d98fafedea3514adc956c8ada5980c6c5d2552fd61f48401aefd5c00e file1
7849ccae8fa91bb281a118041abf921636e68853059329ac68fccd8d518cea53 *file2
`),
checksum.Sha256Sums{
"file1": []byte("ffe65f1d98fafedea3514adc956c8ada5980c6c5d2552fd61f48401aefd5c00e"),
"file2": []byte("7849ccae8fa91bb281a118041abf921636e68853059329ac68fccd8d518cea53"),
},
nil,
},
{
"valid-multi-line-binary-and-text",
strings.NewReader(`ffe65f1d98fafedea3514adc956c8ada5980c6c5d2552fd61f48401aefd5c00e *file1
7849ccae8fa91bb281a118041abf921636e68853059329ac68fccd8d518cea53 file2
`),
checksum.Sha256Sums{
"file1": []byte("ffe65f1d98fafedea3514adc956c8ada5980c6c5d2552fd61f48401aefd5c00e"),
"file2": []byte("7849ccae8fa91bb281a118041abf921636e68853059329ac68fccd8d518cea53"),
},
nil,
},
{
"invalid-single-space",
strings.NewReader(`ffe65f1d98fafedea3514adc956c8ada5980c6c5d2552fd61f48401aefd5c00e file1`),
nil,
fmt.Errorf("checksum.LoadSha256Sums: improperly formated line"),
},
{
"invalid-no-file",
strings.NewReader(`ffe65f1d98fafedea3514adc956c8ada5980c6c5d2552fd61f48401aefd5c00e`),
nil,
fmt.Errorf("checksum.LoadSha256Sums: improperly formated line"),
},
{
"invalid-no-sum-file",
strings.NewReader(`file1`),
nil,
fmt.Errorf("checksum.LoadSha256Sums: improperly formated line"),
},
{
"invalid-hash-too-short",
strings.NewReader(`ffe65f1d98fafedea3514adc956c8ada5980c6c5d2552fd61f48401aefd5c00 file1`),
nil,
fmt.Errorf("checksum.LoadSha256Sums: improperly formated line"),
},
{
"invalid-hash-too-long",
strings.NewReader(`ffe65f1d98fafedea3514adc956c8ada5980c6c5d2552fd61f48401aefd5c00ef file1`),
nil,
fmt.Errorf("checksum.LoadSha256Sums: improperly formated line"),
},
{
"invalid-duplicate-file-name",
strings.NewReader(`ffe65f1d98fafedea3514adc956c8ada5980c6c5d2552fd61f48401aefd5c00e file1
7849ccae8fa91bb281a118041abf921636e68853059329ac68fccd8d518cea53 file1
`),
nil,
fmt.Errorf("checksum.LoadSha256Sums: duplicate file"),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got, err := checksum.LoadSha256Sums(tc.r)
if tc.wantErr != nil {
require.EqualError(t, err, tc.wantErr.Error())
return
}
require.NoError(t, err)
assert.Equal(t, tc.want, got)
})
}
}