diff options
Diffstat (limited to 'hash.go')
| -rw-r--r-- | hash.go | 98 |
1 files changed, 63 insertions, 35 deletions
@@ -5,52 +5,80 @@ import ( "crypto/sha256" "encoding/hex" "fmt" + "unsafe" ) -const maxHashSize = 32 +// HashType is a constraint that enumerates the supported hash sizes. +type HashType interface { + [sha1.Size]byte | [sha256.Size]byte +} -// Hash represents a Git object identifier. -type Hash [maxHashSize]byte +type ( + // SHA1Hash represents a SHA-1 hash. + SHA1Hash = [sha1.Size]byte -// hashFunc is a function that computes a hash from input data. -type hashFunc func([]byte) [maxHashSize]byte + // SHA256Hash represents a SHA-256 hash. + SHA256Hash = [sha256.Size]byte +) -// hashFuncs maps hash size to hash function. -var hashFuncs = map[int]hashFunc{ - sha1.Size: func(data []byte) [maxHashSize]byte { - var result [maxHashSize]byte - sum := sha1.Sum(data) - copy(result[:], sum[:]) - return result - }, - sha256.Size: func(data []byte) [maxHashSize]byte { - var result [maxHashSize]byte - sum := sha256.Sum256(data) - copy(result[:], sum[:]) - return result - }, +// Hash represents a Git object identifier with type-level hash size. +type Hash[T HashType] struct { + v T } -// ParseHashWithSize converts a hex string into a Hash for a given hash size. -func ParseHashWithSize(s string, hashSize int) (Hash, error) { - var id Hash - if len(s) != hashSize*2 { - return id, fmt.Errorf("furgit: invalid hash length %d", len(s)) +// hashLen returns the hash size for a given hash type. +func hashLen[T HashType]() int { + var zero T + return len(zero) +} + +// String returns the hash as a hex string. +func (h Hash[T]) String() string { + return hex.EncodeToString(unsafe.Slice((*byte)(unsafe.Pointer(&h.v)), hashLen[T]())) +} + +// Bytes returns a mutable copy of the underlying bytes. +func (h Hash[T]) Bytes() []byte { + s := unsafe.Slice((*byte)(unsafe.Pointer(&h.v)), hashLen[T]()) + return append([]byte(nil), s...) +} + +// Slice returns a read-only slice view of the underlying bytes. +func (h *Hash[T]) Slice() []byte { + return unsafe.Slice((*byte)(unsafe.Pointer(&h.v)), hashLen[T]()) +} + +// ParseHash converts a hex string into a Hash for the given hash type. +func ParseHash[T HashType](s string) (Hash[T], error) { + var out Hash[T] + wantHex := hashLen[T]() * 2 + + if len(s) != wantHex { + return out, fmt.Errorf("furgit: invalid hash length %d, want %d", len(s), wantHex) } - data, err := hex.DecodeString(s) + raw, err := hex.DecodeString(s) if err != nil { - return id, fmt.Errorf("furgit: decode hash: %w", err) + return out, fmt.Errorf("furgit: decode hash: %w", err) } - copy(id[:], data) - return id, nil + slice := unsafe.Slice((*byte)(unsafe.Pointer(&out.v)), hashLen[T]()) + copy(slice, raw) + return out, nil } -// StringWithSize returns the ID as hex for a given hash size. -func (id Hash) StringWithSize(hashSize int) string { - return hex.EncodeToString(id[:hashSize]) -} +// computeRawHash computes a hash from data. +func computeRawHash[T HashType](data []byte) Hash[T] { + var out Hash[T] + slice := unsafe.Slice((*byte)(unsafe.Pointer(&out.v)), hashLen[T]()) -// BytesWithSize returns a mutable copy of the underlying bytes for a given hash size. -func (id Hash) BytesWithSize(hashSize int) []byte { - return append([]byte(nil), id[:hashSize]...) + switch hashLen[T]() { + case sha1.Size: + sum := sha1.Sum(data) + copy(slice, sum[:]) + case sha256.Size: + sum := sha256.Sum256(data) + copy(slice, sum[:]) + default: + panic("furgit: unsupported hash length") + } + return out } |
