pomerium/internal/hashutil/hashutil.go
Joe Kralicky 18cb47421a
Optimize Policy RouteID and Checksum
This significantly optimizes the (*Policy).RouteID() and
(*Policy).Checksum() methods for both speed and memory usage.

A new method (*Policy).ChecksumWithID(uint64) can be used to skip a call
to RouteID() if the ID is already known. Checksum() is implemented in
terms of this new method, and will always recompute the route ID on each
call.

RouteID() does not allocate heap memory. Checksum() may allocate heap memory,
depending on which fields are set. If all of the following are true,
Checksum() makes zero allocations:
1. The policy uses redirect or direct-response mode
2. The policy has no sub-policies
3. The policy has no response header rewrite config
2024-11-05 15:59:39 -05:00

221 lines
5.1 KiB
Go

// Package hashutil provides NON-CRYPTOGRAPHIC utility functions for hashing.
//
// http://cyan4973.github.io/xxHash/
//
//nolint:errcheck // discarding return values with _ increases inliner cost
package hashutil
import (
"encoding/binary"
"github.com/cespare/xxhash/v2"
"github.com/mitchellh/hashstructure/v2"
)
// MustHash returns the xxhash of an arbitrary value or struct. Returns 0
// on error.
// NOT SUITABLE FOR CRYTOGRAPHIC HASHING.
func MustHash(v any) uint64 {
hash, err := Hash(v)
if err != nil {
hash = 0
}
return hash
}
// Hash returns the xxhash of an arbitrary value or struct.
// NOT SUITABLE FOR CRYTOGRAPHIC HASHING.
func Hash(v any) (uint64, error) {
opts := &hashstructure.HashOptions{
Hasher: xxhash.New(),
}
return hashstructure.Hash(v, hashstructure.FormatV2, opts)
}
// MapHash efficiently computes a non-cryptographic hash of a map of strings.
func MapHash(iv uint64, m map[string]string) uint64 {
accum := iv
var lenBuf [4]byte
binary.BigEndian.PutUint32(lenBuf[:], uint32(len(m)))
accum ^= xxhash.Sum64(lenBuf[:])
var kvBuf [16]byte
for k, v := range m {
binary.BigEndian.PutUint64(kvBuf[0:8], xxhash.Sum64String(k))
binary.BigEndian.PutUint64(kvBuf[8:16], xxhash.Sum64String(v))
accum ^= xxhash.Sum64(kvBuf[:])
}
return accum
}
type Digest struct {
xxhash.Digest
}
func NewDigest() *Digest {
var d Digest
d.Reset()
return &d
}
// WriteStringWithLen writes the string's length, then its contents to the hash.
func (d *Digest) WriteStringWithLen(s string) {
d.WriteInt32(int32(len(s)))
d.WriteString(s)
}
// WriteStringWithLen writes the byte array's length, then its contents to
// the hash.
func (d *Digest) WriteWithLen(b []byte) {
d.WriteInt32(int32(len(b)))
d.Write(b)
}
// WriteBool writes a single byte (1 or 0) to the hash.
func (d *Digest) WriteBool(b bool) {
if b {
d.Write([]byte{1})
} else {
d.Write([]byte{0})
}
}
// WriteUint32 writes a uint16 to the hash.
func (d *Digest) WriteUint16(t uint16) {
var buf [2]byte
binary.LittleEndian.PutUint16(buf[:], t)
d.Write(buf[:])
}
// WriteUint32 writes a uint32 to the hash.
func (d *Digest) WriteUint32(t uint32) {
var buf [4]byte
binary.LittleEndian.PutUint32(buf[:], t)
d.Write(buf[:])
}
// WriteUint32 writes a uint64 to the hash.
func (d *Digest) WriteUint64(t uint64) {
var buf [8]byte
binary.LittleEndian.PutUint64(buf[:], t)
d.Write(buf[:])
}
// WriteInt16 writes an int16 to the hash.
func (d *Digest) WriteInt16(t int16) {
var buf [2]byte
binary.LittleEndian.PutUint16(buf[:], uint16(t))
d.Write(buf[:])
}
// WriteInt32 writes an int32 to the hash.
func (d *Digest) WriteInt32(t int32) {
var buf [4]byte
binary.LittleEndian.PutUint32(buf[:], uint32(t))
d.Write(buf[:])
}
// WriteInt64 writes an int64 to the hash.
func (d *Digest) WriteInt64(t int64) {
var buf [8]byte
binary.LittleEndian.PutUint64(buf[:], uint64(t))
d.Write(buf[:])
}
// WriteStringPtr writes one byte (1 or 0) indicating whether the pointer is non-nil,
// followed by the value if present.
func (d *Digest) WriteStringPtr(t *string) {
if t == nil {
d.Write([]byte{0})
} else {
d.Write([]byte{1})
d.WriteString(*t)
}
}
// WriteStringPtr writes one byte (1 or 0) indicating whether the pointer is non-nil,
// followed by the string's length and value, if present.
func (d *Digest) WriteStringPtrWithLen(t *string) {
if t == nil {
d.Write([]byte{0})
} else {
d.Write([]byte{1})
d.WriteStringWithLen(*t)
}
}
// WriteBoolPtr writes one byte (1 or 0) indicating whether the pointer is non-nil,
// followed by the value if present.
func (d *Digest) WriteBoolPtr(t *bool) {
if t == nil {
d.Write([]byte{0})
} else {
d.Write([]byte{1})
d.WriteBool(*t)
}
}
// WriteUint16Ptr writes one byte (1 or 0) indicating whether the pointer is non-nil,
// followed by the value if present.
func (d *Digest) WriteUint16Ptr(t *uint16) {
if t == nil {
d.Write([]byte{0})
} else {
d.Write([]byte{1})
d.WriteUint16(*t)
}
}
// WriteUint32Ptr writes one byte (1 or 0) indicating whether the pointer is non-nil,
// followed by the value if present.
func (d *Digest) WriteUint32Ptr(t *uint32) {
if t == nil {
d.Write([]byte{0})
} else {
d.Write([]byte{1})
d.WriteUint32(*t)
}
}
// WriteUint64Ptr writes one byte (1 or 0) indicating whether the pointer is non-nil,
// followed by the value if present.
func (d *Digest) WriteUint64Ptr(t *uint64) {
if t == nil {
d.Write([]byte{0})
} else {
d.Write([]byte{1})
d.WriteUint64(*t)
}
}
// WriteInt16Ptr writes one byte (1 or 0) indicating whether the pointer is non-nil,
// followed by the value if present.
func (d *Digest) WriteInt16Ptr(t *int16) {
if t == nil {
d.Write([]byte{0})
} else {
d.Write([]byte{1})
d.WriteInt16(*t)
}
}
// WriteInt32Ptr writes one byte (1 or 0) indicating whether the pointer is non-nil,
// followed by the value if present.
func (d *Digest) WriteInt32Ptr(t *int32) {
if t == nil {
d.Write([]byte{0})
} else {
d.Write([]byte{1})
d.WriteInt32(*t)
}
}
// WriteInt64Ptr writes one byte (1 or 0) indicating whether the pointer is non-nil,
// followed by the value if present.
func (d *Digest) WriteInt64Ptr(t *int64) {
if t == nil {
d.Write([]byte{0})
} else {
d.Write([]byte{1})
d.WriteInt64(*t)
}
}