aboutsummaryrefslogtreecommitdiff
path: root/hash.go
diff options
context:
space:
mode:
authorGravatar Runxi Yu2025-11-16 00:00:00 +0000
committerGravatar Runxi Yu2025-11-16 00:00:00 +0000
commit94bfb1fa147f80e6ec39009d41fc2f853925e0a5 (patch)
treee2c8063f3fbc58527e21f1f88e72f9e32071c28a /hash.go
parentREADME: Remove example program as it's unmaintainable now (diff)
signature
hash: Generic hash-algorithm API
Diffstat (limited to 'hash.go')
-rw-r--r--hash.go98
1 files changed, 63 insertions, 35 deletions
diff --git a/hash.go b/hash.go
index 336d5322..53dff11b 100644
--- a/hash.go
+++ b/hash.go
@@ -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
}