2020-06-30 02:36:45 +00:00
|
|
|
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
2021-05-11 19:09:25 +00:00
|
|
|
// Package deephash hashes a Go value recursively, in a predictable
|
|
|
|
// order, without looping.
|
2021-07-03 04:30:29 +00:00
|
|
|
//
|
|
|
|
// This package, like most of the tailscale.com Go module, should be
|
|
|
|
// considered Tailscale-internal; we make no API promises.
|
2021-05-11 19:09:25 +00:00
|
|
|
package deephash
|
2020-06-28 17:58:21 +00:00
|
|
|
|
|
|
|
import (
|
2021-05-10 20:29:56 +00:00
|
|
|
"bufio"
|
2020-06-28 17:58:21 +00:00
|
|
|
"crypto/sha256"
|
2021-05-19 18:51:21 +00:00
|
|
|
"encoding/hex"
|
2020-06-28 17:58:21 +00:00
|
|
|
"fmt"
|
2021-05-11 20:17:12 +00:00
|
|
|
"hash"
|
2020-06-28 17:58:21 +00:00
|
|
|
"reflect"
|
2021-05-19 18:51:21 +00:00
|
|
|
"strconv"
|
2021-05-11 20:17:12 +00:00
|
|
|
"sync"
|
2020-06-28 17:58:21 +00:00
|
|
|
)
|
|
|
|
|
2021-07-05 04:25:15 +00:00
|
|
|
// hasher is reusable state for hashing a value.
|
|
|
|
// Get one via hasherPool.
|
|
|
|
type hasher struct {
|
|
|
|
h hash.Hash
|
|
|
|
bw *bufio.Writer
|
|
|
|
scratch [128]byte
|
2020-06-28 17:58:21 +00:00
|
|
|
}
|
|
|
|
|
2021-07-05 04:25:15 +00:00
|
|
|
// newHasher initializes a new hasher, for use by hasherPool.
|
|
|
|
func newHasher() *hasher {
|
|
|
|
h := &hasher{h: sha256.New()}
|
|
|
|
h.bw = bufio.NewWriterSize(h.h, h.h.BlockSize())
|
|
|
|
return h
|
|
|
|
}
|
|
|
|
|
|
|
|
// Hash returns the raw SHA-256 (not hex) of v.
|
|
|
|
func (h *hasher) Hash(v interface{}) (hash [sha256.Size]byte) {
|
|
|
|
h.bw.Flush()
|
|
|
|
h.h.Reset()
|
|
|
|
printTo(h.bw, v, h.scratch[:])
|
|
|
|
h.bw.Flush()
|
|
|
|
h.h.Sum(hash[:0])
|
|
|
|
return hash
|
|
|
|
}
|
|
|
|
|
|
|
|
var hasherPool = &sync.Pool{
|
|
|
|
New: func() interface{} { return newHasher() },
|
|
|
|
}
|
|
|
|
|
|
|
|
// Hash returns the raw SHA-256 hash of v.
|
|
|
|
func Hash(v interface{}) [sha256.Size]byte {
|
|
|
|
hasher := hasherPool.Get().(*hasher)
|
|
|
|
hasherPool.Put(hasher)
|
|
|
|
return hasher.Hash(v)
|
|
|
|
}
|
|
|
|
|
|
|
|
// UpdateHash sets last to the hex-encoded hash of v and reports whether its value changed.
|
2020-07-29 01:47:23 +00:00
|
|
|
func UpdateHash(last *string, v ...interface{}) (changed bool) {
|
2021-07-05 04:25:15 +00:00
|
|
|
sum := Hash(v)
|
|
|
|
if sha256EqualHex(sum, *last) {
|
|
|
|
// unchanged.
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
*last = hex.EncodeToString(sum[:])
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// sha256EqualHex reports whether hx is the hex encoding of sum.
|
|
|
|
func sha256EqualHex(sum [sha256.Size]byte, hx string) bool {
|
|
|
|
if len(hx) != len(sum)*2 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
const hextable = "0123456789abcdef"
|
|
|
|
j := 0
|
|
|
|
for _, v := range sum {
|
|
|
|
if hx[j] != hextable[v>>4] || hx[j+1] != hextable[v&0x0f] {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
j += 2
|
2020-07-29 01:47:23 +00:00
|
|
|
}
|
2021-07-05 04:25:15 +00:00
|
|
|
return true
|
2020-07-29 01:47:23 +00:00
|
|
|
}
|
|
|
|
|
2021-05-19 18:51:21 +00:00
|
|
|
func printTo(w *bufio.Writer, v interface{}, scratch []byte) {
|
|
|
|
print(w, reflect.ValueOf(v), make(map[uintptr]bool), scratch)
|
2020-06-28 17:58:21 +00:00
|
|
|
}
|
|
|
|
|
2021-05-24 22:13:18 +00:00
|
|
|
var appenderToType = reflect.TypeOf((*appenderTo)(nil)).Elem()
|
2021-05-10 21:15:31 +00:00
|
|
|
|
2021-05-24 21:31:24 +00:00
|
|
|
type appenderTo interface {
|
|
|
|
AppendTo([]byte) []byte
|
|
|
|
}
|
|
|
|
|
2021-05-11 20:17:12 +00:00
|
|
|
// print hashes v into w.
|
|
|
|
// It reports whether it was able to do so without hitting a cycle.
|
2021-05-19 18:51:21 +00:00
|
|
|
func print(w *bufio.Writer, v reflect.Value, visited map[uintptr]bool, scratch []byte) (acyclic bool) {
|
2020-06-28 17:58:21 +00:00
|
|
|
if !v.IsValid() {
|
2021-05-11 20:17:12 +00:00
|
|
|
return true
|
2020-06-28 17:58:21 +00:00
|
|
|
}
|
2021-05-10 21:15:31 +00:00
|
|
|
|
|
|
|
if v.CanInterface() {
|
2021-05-24 21:31:24 +00:00
|
|
|
// Use AppendTo methods, if available and cheap.
|
|
|
|
if v.CanAddr() && v.Type().Implements(appenderToType) {
|
|
|
|
a := v.Addr().Interface().(appenderTo)
|
|
|
|
scratch = a.AppendTo(scratch[:0])
|
2021-05-19 18:51:21 +00:00
|
|
|
w.Write(scratch)
|
2021-05-18 19:44:31 +00:00
|
|
|
return true
|
2021-05-24 21:31:24 +00:00
|
|
|
}
|
2021-05-10 21:15:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Generic handling.
|
2020-06-28 17:58:21 +00:00
|
|
|
switch v.Kind() {
|
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("unhandled kind %v for type %v", v.Kind(), v.Type()))
|
|
|
|
case reflect.Ptr:
|
|
|
|
ptr := v.Pointer()
|
|
|
|
if visited[ptr] {
|
2021-05-11 20:17:12 +00:00
|
|
|
return false
|
2020-06-28 17:58:21 +00:00
|
|
|
}
|
|
|
|
visited[ptr] = true
|
2021-05-19 18:51:21 +00:00
|
|
|
return print(w, v.Elem(), visited, scratch)
|
2020-06-28 17:58:21 +00:00
|
|
|
case reflect.Struct:
|
2021-05-11 20:17:12 +00:00
|
|
|
acyclic = true
|
2021-05-10 21:35:45 +00:00
|
|
|
w.WriteString("struct{\n")
|
2020-06-28 17:58:21 +00:00
|
|
|
for i, n := 0, v.NumField(); i < n; i++ {
|
2021-05-11 00:51:25 +00:00
|
|
|
fmt.Fprintf(w, " [%d]: ", i)
|
2021-05-19 18:51:21 +00:00
|
|
|
if !print(w, v.Field(i), visited, scratch) {
|
2021-05-11 20:17:12 +00:00
|
|
|
acyclic = false
|
|
|
|
}
|
2021-05-10 21:35:45 +00:00
|
|
|
w.WriteString("\n")
|
2020-06-28 17:58:21 +00:00
|
|
|
}
|
2021-05-11 00:52:29 +00:00
|
|
|
w.WriteString("}\n")
|
2021-05-11 20:17:12 +00:00
|
|
|
return acyclic
|
2020-06-28 17:58:21 +00:00
|
|
|
case reflect.Slice, reflect.Array:
|
|
|
|
if v.Type().Elem().Kind() == reflect.Uint8 && v.CanInterface() {
|
|
|
|
fmt.Fprintf(w, "%q", v.Interface())
|
2021-05-11 20:17:12 +00:00
|
|
|
return true
|
2020-06-28 17:58:21 +00:00
|
|
|
}
|
|
|
|
fmt.Fprintf(w, "[%d]{\n", v.Len())
|
2021-05-11 20:17:12 +00:00
|
|
|
acyclic = true
|
2020-06-28 17:58:21 +00:00
|
|
|
for i, ln := 0, v.Len(); i < ln; i++ {
|
|
|
|
fmt.Fprintf(w, " [%d]: ", i)
|
2021-05-19 18:51:21 +00:00
|
|
|
if !print(w, v.Index(i), visited, scratch) {
|
2021-05-11 20:17:12 +00:00
|
|
|
acyclic = false
|
|
|
|
}
|
2021-05-10 21:35:45 +00:00
|
|
|
w.WriteString("\n")
|
2020-06-28 17:58:21 +00:00
|
|
|
}
|
2021-05-10 21:35:45 +00:00
|
|
|
w.WriteString("}\n")
|
2021-05-11 20:17:12 +00:00
|
|
|
return acyclic
|
2020-06-28 17:58:21 +00:00
|
|
|
case reflect.Interface:
|
2021-05-19 18:51:21 +00:00
|
|
|
return print(w, v.Elem(), visited, scratch)
|
2020-06-28 17:58:21 +00:00
|
|
|
case reflect.Map:
|
2021-05-19 18:51:21 +00:00
|
|
|
if hashMapAcyclic(w, v, visited, scratch) {
|
2021-05-11 20:17:12 +00:00
|
|
|
return true
|
2020-06-28 17:58:21 +00:00
|
|
|
}
|
2021-05-19 18:51:21 +00:00
|
|
|
return hashMapFallback(w, v, visited, scratch)
|
2020-06-28 17:58:21 +00:00
|
|
|
case reflect.String:
|
2021-05-10 20:29:56 +00:00
|
|
|
w.WriteString(v.String())
|
2020-06-28 17:58:21 +00:00
|
|
|
case reflect.Bool:
|
|
|
|
fmt.Fprintf(w, "%v", v.Bool())
|
|
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
|
|
fmt.Fprintf(w, "%v", v.Int())
|
|
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
2021-05-24 21:41:58 +00:00
|
|
|
scratch = strconv.AppendUint(scratch[:0], v.Uint(), 10)
|
2021-05-19 18:51:21 +00:00
|
|
|
w.Write(scratch)
|
2020-06-28 17:58:21 +00:00
|
|
|
case reflect.Float32, reflect.Float64:
|
|
|
|
fmt.Fprintf(w, "%v", v.Float())
|
|
|
|
case reflect.Complex64, reflect.Complex128:
|
|
|
|
fmt.Fprintf(w, "%v", v.Complex())
|
|
|
|
}
|
2021-05-11 20:17:12 +00:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
type mapHasher struct {
|
|
|
|
xbuf [sha256.Size]byte // XOR'ed accumulated buffer
|
|
|
|
ebuf [sha256.Size]byte // scratch buffer
|
|
|
|
s256 hash.Hash // sha256 hash.Hash
|
|
|
|
bw *bufio.Writer // to hasher into ebuf
|
2021-05-18 16:20:52 +00:00
|
|
|
val valueCache // re-usable values for map iteration
|
2021-05-24 21:24:39 +00:00
|
|
|
iter *reflect.MapIter // re-usable map iterator
|
2021-05-11 20:17:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (mh *mapHasher) Reset() {
|
|
|
|
for i := range mh.xbuf {
|
|
|
|
mh.xbuf[i] = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mh *mapHasher) startEntry() {
|
|
|
|
for i := range mh.ebuf {
|
|
|
|
mh.ebuf[i] = 0
|
|
|
|
}
|
|
|
|
mh.bw.Flush()
|
|
|
|
mh.s256.Reset()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (mh *mapHasher) endEntry() {
|
|
|
|
mh.bw.Flush()
|
|
|
|
for i, b := range mh.s256.Sum(mh.ebuf[:0]) {
|
|
|
|
mh.xbuf[i] ^= b
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var mapHasherPool = &sync.Pool{
|
|
|
|
New: func() interface{} {
|
|
|
|
mh := new(mapHasher)
|
|
|
|
mh.s256 = sha256.New()
|
|
|
|
mh.bw = bufio.NewWriter(mh.s256)
|
2021-05-18 16:20:52 +00:00
|
|
|
mh.val = make(valueCache)
|
2021-05-24 21:24:39 +00:00
|
|
|
mh.iter = new(reflect.MapIter)
|
2021-05-11 20:17:12 +00:00
|
|
|
return mh
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2021-05-18 16:20:52 +00:00
|
|
|
type valueCache map[reflect.Type]reflect.Value
|
|
|
|
|
|
|
|
func (c valueCache) get(t reflect.Type) reflect.Value {
|
|
|
|
v, ok := c[t]
|
|
|
|
if !ok {
|
|
|
|
v = reflect.New(t).Elem()
|
|
|
|
c[t] = v
|
|
|
|
}
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
2021-05-11 20:17:12 +00:00
|
|
|
// hashMapAcyclic is the faster sort-free version of map hashing. If
|
|
|
|
// it detects a cycle it returns false and guarantees that nothing was
|
|
|
|
// written to w.
|
2021-05-19 18:51:21 +00:00
|
|
|
func hashMapAcyclic(w *bufio.Writer, v reflect.Value, visited map[uintptr]bool, scratch []byte) (acyclic bool) {
|
2021-05-11 20:17:12 +00:00
|
|
|
mh := mapHasherPool.Get().(*mapHasher)
|
|
|
|
defer mapHasherPool.Put(mh)
|
|
|
|
mh.Reset()
|
2021-05-24 21:24:39 +00:00
|
|
|
iter := mapIter(mh.iter, v)
|
|
|
|
defer mapIter(mh.iter, reflect.Value{}) // avoid pinning v from mh.iter when we return
|
2021-05-18 16:20:52 +00:00
|
|
|
k := mh.val.get(v.Type().Key())
|
|
|
|
e := mh.val.get(v.Type().Elem())
|
2021-05-11 20:17:12 +00:00
|
|
|
for iter.Next() {
|
2021-05-17 19:46:17 +00:00
|
|
|
key := iterKey(iter, k)
|
|
|
|
val := iterVal(iter, e)
|
2021-05-11 20:17:12 +00:00
|
|
|
mh.startEntry()
|
2021-05-19 18:51:21 +00:00
|
|
|
if !print(mh.bw, key, visited, scratch) {
|
2021-05-11 20:17:12 +00:00
|
|
|
return false
|
|
|
|
}
|
2021-05-19 18:51:21 +00:00
|
|
|
if !print(mh.bw, val, visited, scratch) {
|
2021-05-11 20:17:12 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
mh.endEntry()
|
|
|
|
}
|
|
|
|
w.Write(mh.xbuf[:])
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2021-05-19 18:51:21 +00:00
|
|
|
func hashMapFallback(w *bufio.Writer, v reflect.Value, visited map[uintptr]bool, scratch []byte) (acyclic bool) {
|
2021-05-11 20:17:12 +00:00
|
|
|
acyclic = true
|
|
|
|
sm := newSortedMap(v)
|
|
|
|
fmt.Fprintf(w, "map[%d]{\n", len(sm.Key))
|
|
|
|
for i, k := range sm.Key {
|
2021-05-19 18:51:21 +00:00
|
|
|
if !print(w, k, visited, scratch) {
|
2021-05-11 20:17:12 +00:00
|
|
|
acyclic = false
|
|
|
|
}
|
|
|
|
w.WriteString(": ")
|
2021-05-19 18:51:21 +00:00
|
|
|
if !print(w, sm.Value[i], visited, scratch) {
|
2021-05-11 20:17:12 +00:00
|
|
|
acyclic = false
|
|
|
|
}
|
|
|
|
w.WriteString("\n")
|
|
|
|
}
|
|
|
|
w.WriteString("}\n")
|
|
|
|
return acyclic
|
2020-06-28 17:58:21 +00:00
|
|
|
}
|