|
| 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 | +} |
0 commit comments