rclone/fs/hash/hash.go
Nick Craig-Wood e43b5ce5e5 Remove github.com/pkg/errors and replace with std library version
This is possible now that we no longer support go1.12 and brings
rclone into line with standard practices in the Go world.

This also removes errors.New and errors.Errorf from lib/errors and
prefers the stdlib errors package over lib/errors.
2021-11-07 11:53:30 +00:00

360 lines
8 KiB
Go

package hash
import (
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"hash"
"hash/crc32"
"io"
"strings"
"github.com/jzelinskie/whirlpool"
)
// Type indicates a standard hashing algorithm
type Type int
type hashDefinition struct {
width int
name string
alias string
newFunc func() hash.Hash
hashType Type
}
var (
type2hash = map[Type]*hashDefinition{}
name2hash = map[string]*hashDefinition{}
alias2hash = map[string]*hashDefinition{}
supported = []Type{}
)
// RegisterHash adds a new Hash to the list and returns it Type
func RegisterHash(name, alias string, width int, newFunc func() hash.Hash) Type {
hashType := Type(1 << len(supported))
supported = append(supported, hashType)
definition := &hashDefinition{
name: name,
alias: alias,
width: width,
newFunc: newFunc,
hashType: hashType,
}
type2hash[hashType] = definition
name2hash[name] = definition
alias2hash[alias] = definition
return hashType
}
// ErrUnsupported should be returned by filesystem,
// if it is requested to deliver an unsupported hash type.
var ErrUnsupported = errors.New("hash type not supported")
var (
// None indicates no hashes are supported
None Type
// MD5 indicates MD5 support
MD5 Type
// SHA1 indicates SHA-1 support
SHA1 Type
// Whirlpool indicates Whirlpool support
Whirlpool Type
// CRC32 indicates CRC-32 support
CRC32 Type
// SHA256 indicates SHA-256 support
SHA256 Type
)
func init() {
MD5 = RegisterHash("md5", "MD5", 32, md5.New)
SHA1 = RegisterHash("sha1", "SHA-1", 40, sha1.New)
Whirlpool = RegisterHash("whirlpool", "Whirlpool", 128, whirlpool.New)
CRC32 = RegisterHash("crc32", "CRC-32", 8, func() hash.Hash { return crc32.NewIEEE() })
SHA256 = RegisterHash("sha256", "SHA-256", 64, sha256.New)
}
// Supported returns a set of all the supported hashes by
// HashStream and MultiHasher.
func Supported() Set {
return NewHashSet(supported...)
}
// Width returns the width in characters for any HashType
func Width(hashType Type) int {
if hash := type2hash[hashType]; hash != nil {
return hash.width
}
return 0
}
// Stream will calculate hashes of all supported hash types.
func Stream(r io.Reader) (map[Type]string, error) {
return StreamTypes(r, Supported())
}
// StreamTypes will calculate hashes of the requested hash types.
func StreamTypes(r io.Reader, set Set) (map[Type]string, error) {
hashers, err := fromTypes(set)
if err != nil {
return nil, err
}
_, err = io.Copy(toMultiWriter(hashers), r)
if err != nil {
return nil, err
}
var ret = make(map[Type]string)
for k, v := range hashers {
ret[k] = hex.EncodeToString(v.Sum(nil))
}
return ret, nil
}
// String returns a string representation of the hash type.
// The function will panic if the hash type is unknown.
func (h Type) String() string {
if h == None {
return "none"
}
if hash := type2hash[h]; hash != nil {
return hash.name
}
panic(fmt.Sprintf("internal error: unknown hash type: 0x%x", int(h)))
}
// Set a Type from a flag.
// Both name and alias are accepted.
func (h *Type) Set(s string) error {
if s == "none" || s == "None" {
*h = None
return nil
}
if hash := name2hash[strings.ToLower(s)]; hash != nil {
*h = hash.hashType
return nil
}
if hash := alias2hash[s]; hash != nil {
*h = hash.hashType
return nil
}
return fmt.Errorf("Unknown hash type %q", s)
}
// Type of the value
func (h Type) Type() string {
return "string"
}
// fromTypes will return hashers for all the requested types.
// The types must be a subset of SupportedHashes,
// and this function must support all types.
func fromTypes(set Set) (map[Type]hash.Hash, error) {
if !set.SubsetOf(Supported()) {
return nil, fmt.Errorf("requested set %08x contains unknown hash types", int(set))
}
hashers := map[Type]hash.Hash{}
for _, t := range set.Array() {
hash := type2hash[t]
if hash == nil {
panic(fmt.Sprintf("internal error: Unsupported hash type %v", t))
}
hashers[t] = hash.newFunc()
}
return hashers, nil
}
// toMultiWriter will return a set of hashers into a
// single multiwriter, where one write will update all
// the hashers.
func toMultiWriter(h map[Type]hash.Hash) io.Writer {
// Convert to to slice
var w = make([]io.Writer, 0, len(h))
for _, v := range h {
w = append(w, v)
}
return io.MultiWriter(w...)
}
// A MultiHasher will construct various hashes on
// all incoming writes.
type MultiHasher struct {
w io.Writer
size int64
h map[Type]hash.Hash // Hashes
}
// NewMultiHasher will return a hash writer that will write all
// supported hash types.
func NewMultiHasher() *MultiHasher {
h, err := NewMultiHasherTypes(Supported())
if err != nil {
panic("internal error: could not create multihasher")
}
return h
}
// NewMultiHasherTypes will return a hash writer that will write
// the requested hash types.
func NewMultiHasherTypes(set Set) (*MultiHasher, error) {
hashers, err := fromTypes(set)
if err != nil {
return nil, err
}
m := MultiHasher{h: hashers, w: toMultiWriter(hashers)}
return &m, nil
}
func (m *MultiHasher) Write(p []byte) (n int, err error) {
n, err = m.w.Write(p)
m.size += int64(n)
return n, err
}
// Sums returns the sums of all accumulated hashes as hex encoded
// strings.
func (m *MultiHasher) Sums() map[Type]string {
dst := make(map[Type]string)
for k, v := range m.h {
dst[k] = hex.EncodeToString(v.Sum(nil))
}
return dst
}
// Sum returns the specified hash from the multihasher
func (m *MultiHasher) Sum(hashType Type) ([]byte, error) {
h, ok := m.h[hashType]
if !ok {
return nil, ErrUnsupported
}
return h.Sum(nil), nil
}
// Size returns the number of bytes written
func (m *MultiHasher) Size() int64 {
return m.size
}
// A Set Indicates one or more hash types.
type Set int
// NewHashSet will create a new hash set with the hash types supplied
func NewHashSet(t ...Type) Set {
h := Set(None)
return h.Add(t...)
}
// Add one or more hash types to the set.
// Returns the modified hash set.
func (h *Set) Add(t ...Type) Set {
for _, v := range t {
*h |= Set(v)
}
return *h
}
// Contains returns true if the
func (h Set) Contains(t Type) bool {
return int(h)&int(t) != 0
}
// Overlap returns the overlapping hash types
func (h Set) Overlap(t Set) Set {
return Set(int(h) & int(t))
}
// SubsetOf will return true if all types of h
// is present in the set c
func (h Set) SubsetOf(c Set) bool {
return int(h)|int(c) == int(c)
}
// GetOne will return a hash type.
// Currently the first is returned, but it could be
// improved to return the strongest.
func (h Set) GetOne() Type {
v := int(h)
i := uint(0)
for v != 0 {
if v&1 != 0 {
return Type(1 << i)
}
i++
v >>= 1
}
return None
}
// Array returns an array of all hash types in the set
func (h Set) Array() (ht []Type) {
v := int(h)
i := uint(0)
for v != 0 {
if v&1 != 0 {
ht = append(ht, Type(1<<i))
}
i++
v >>= 1
}
return ht
}
// Count returns the number of hash types in the set
func (h Set) Count() int {
if int(h) == 0 {
return 0
}
// credit: https://code.google.com/u/arnehormann/
x := uint64(h)
x -= (x >> 1) & 0x5555555555555555
x = (x>>2)&0x3333333333333333 + x&0x3333333333333333
x += x >> 4
x &= 0x0f0f0f0f0f0f0f0f
x *= 0x0101010101010101
return int(x >> 56)
}
// String returns a string representation of the hash set.
// The function will panic if it contains an unknown type.
func (h Set) String() string {
a := h.Array()
var r []string
for _, v := range a {
r = append(r, v.String())
}
return "[" + strings.Join(r, ", ") + "]"
}
// Equals checks to see if src == dst, but ignores empty strings
// and returns true if either is empty.
func Equals(src, dst string) bool {
if src == "" || dst == "" {
return true
}
return src == dst
}
// HelpString returns help message with supported hashes
func HelpString(indent int) string {
padding := strings.Repeat(" ", indent)
var help strings.Builder
help.WriteString(padding)
help.WriteString("Supported hashes are:\n")
for _, h := range supported {
fmt.Fprintf(&help, "%s * %v\n", padding, h.String())
}
return help.String()
}