v0.0.32
This commit is contained in:
parent
1a9e5c70fc
commit
e733f30c38
254
dataext/structHash.go
Normal file
254
dataext/structHash.go
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
package dataext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||||
|
"hash"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StructHashOptions struct {
|
||||||
|
HashAlgo hash.Hash
|
||||||
|
Tag *string
|
||||||
|
SkipChannel bool
|
||||||
|
SkipFunc bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func StructHash(dat any, opt ...StructHashOptions) (r []byte, err error) {
|
||||||
|
defer func() {
|
||||||
|
if rec := recover(); rec != nil {
|
||||||
|
r = nil
|
||||||
|
err = errors.New(fmt.Sprintf("recovered panic: %v", rec))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
shopt := StructHashOptions{}
|
||||||
|
if len(opt) > 1 {
|
||||||
|
return nil, errors.New("multiple options supplied")
|
||||||
|
} else if len(opt) == 1 {
|
||||||
|
shopt = opt[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if shopt.HashAlgo == nil {
|
||||||
|
shopt.HashAlgo = sha256.New()
|
||||||
|
}
|
||||||
|
|
||||||
|
writer := new(bytes.Buffer)
|
||||||
|
|
||||||
|
if langext.IsNil(dat) {
|
||||||
|
shopt.HashAlgo.Reset()
|
||||||
|
shopt.HashAlgo.Write(writer.Bytes())
|
||||||
|
res := shopt.HashAlgo.Sum(nil)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = binarize(writer, reflect.ValueOf(dat), shopt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
shopt.HashAlgo.Reset()
|
||||||
|
shopt.HashAlgo.Write(writer.Bytes())
|
||||||
|
res := shopt.HashAlgo.Sum(nil)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeBinarized(writer io.Writer, dat any) error {
|
||||||
|
tmp := bytes.Buffer{}
|
||||||
|
err := binary.Write(&tmp, binary.LittleEndian, dat)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = binary.Write(writer, binary.LittleEndian, uint64(tmp.Len()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = writer.Write(tmp.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func binarize(writer io.Writer, dat reflect.Value, opt StructHashOptions) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
err = binary.Write(writer, binary.LittleEndian, uint8(dat.Kind()))
|
||||||
|
switch dat.Kind() {
|
||||||
|
case reflect.Ptr, reflect.Map, reflect.Array, reflect.Chan, reflect.Slice, reflect.Interface:
|
||||||
|
if dat.IsNil() {
|
||||||
|
err = binary.Write(writer, binary.LittleEndian, uint64(0))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = binary.Write(writer, binary.LittleEndian, uint64(len(dat.Type().String())))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = writer.Write([]byte(dat.Type().String()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch dat.Type().Kind() {
|
||||||
|
case reflect.Invalid:
|
||||||
|
return errors.New("cannot binarize value of kind <Invalid>")
|
||||||
|
case reflect.Bool:
|
||||||
|
return writeBinarized(writer, dat.Bool())
|
||||||
|
case reflect.Int:
|
||||||
|
return writeBinarized(writer, int64(dat.Int()))
|
||||||
|
case reflect.Int8:
|
||||||
|
fallthrough
|
||||||
|
case reflect.Int16:
|
||||||
|
fallthrough
|
||||||
|
case reflect.Int32:
|
||||||
|
fallthrough
|
||||||
|
case reflect.Int64:
|
||||||
|
return writeBinarized(writer, dat.Interface())
|
||||||
|
case reflect.Uint:
|
||||||
|
return writeBinarized(writer, uint64(dat.Int()))
|
||||||
|
case reflect.Uint8:
|
||||||
|
fallthrough
|
||||||
|
case reflect.Uint16:
|
||||||
|
fallthrough
|
||||||
|
case reflect.Uint32:
|
||||||
|
fallthrough
|
||||||
|
case reflect.Uint64:
|
||||||
|
return writeBinarized(writer, dat.Interface())
|
||||||
|
case reflect.Uintptr:
|
||||||
|
return errors.New("cannot binarize value of kind <Uintptr>")
|
||||||
|
case reflect.Float32:
|
||||||
|
fallthrough
|
||||||
|
case reflect.Float64:
|
||||||
|
return writeBinarized(writer, dat.Interface())
|
||||||
|
case reflect.Complex64:
|
||||||
|
return errors.New("cannot binarize value of kind <Complex64>")
|
||||||
|
case reflect.Complex128:
|
||||||
|
return errors.New("cannot binarize value of kind <Complex128>")
|
||||||
|
case reflect.Slice:
|
||||||
|
fallthrough
|
||||||
|
case reflect.Array:
|
||||||
|
return binarizeArrayOrSlice(writer, dat, opt)
|
||||||
|
case reflect.Chan:
|
||||||
|
if opt.SkipChannel {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("cannot binarize value of kind <Chan>")
|
||||||
|
case reflect.Func:
|
||||||
|
if opt.SkipFunc {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("cannot binarize value of kind <Func>")
|
||||||
|
case reflect.Interface:
|
||||||
|
return binarize(writer, dat.Elem(), opt)
|
||||||
|
case reflect.Map:
|
||||||
|
return binarizeMap(writer, dat, opt)
|
||||||
|
case reflect.Pointer:
|
||||||
|
return binarize(writer, dat.Elem(), opt)
|
||||||
|
case reflect.String:
|
||||||
|
v := dat.String()
|
||||||
|
err = binary.Write(writer, binary.LittleEndian, uint64(len(v)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = writer.Write([]byte(v))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case reflect.Struct:
|
||||||
|
return binarizeStruct(writer, dat, opt)
|
||||||
|
case reflect.UnsafePointer:
|
||||||
|
return errors.New("cannot binarize value of kind <UnsafePointer>")
|
||||||
|
default:
|
||||||
|
return errors.New("cannot binarize value of unknown kind <" + dat.Type().Kind().String() + ">")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func binarizeStruct(writer io.Writer, dat reflect.Value, opt StructHashOptions) error {
|
||||||
|
err := binary.Write(writer, binary.LittleEndian, uint64(dat.NumField()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < dat.NumField(); i++ {
|
||||||
|
|
||||||
|
if opt.Tag != nil {
|
||||||
|
if _, ok := dat.Type().Field(i).Tag.Lookup(*opt.Tag); !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = binary.Write(writer, binary.LittleEndian, uint64(len(dat.Type().Field(i).Name)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = writer.Write([]byte(dat.Type().Field(i).Name))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = binarize(writer, dat.Field(i), opt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func binarizeArrayOrSlice(writer io.Writer, dat reflect.Value, opt StructHashOptions) error {
|
||||||
|
err := binary.Write(writer, binary.LittleEndian, uint64(dat.Len()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < dat.Len(); i++ {
|
||||||
|
err := binarize(writer, dat.Index(i), opt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func binarizeMap(writer io.Writer, dat reflect.Value, opt StructHashOptions) error {
|
||||||
|
err := binary.Write(writer, binary.LittleEndian, uint64(dat.Len()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sub := make([][]byte, 0, dat.Len())
|
||||||
|
|
||||||
|
for _, k := range dat.MapKeys() {
|
||||||
|
tmp := bytes.Buffer{}
|
||||||
|
err = binarize(&tmp, dat.MapIndex(k), opt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sub = append(sub, tmp.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(sub, func(i1, i2 int) bool { return bytes.Compare(sub[i1], sub[i2]) < 0 })
|
||||||
|
|
||||||
|
for _, v := range sub {
|
||||||
|
_, err = writer.Write(v)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
143
dataext/structHash_test.go
Normal file
143
dataext/structHash_test.go
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
package dataext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"gogs.mikescher.com/BlackForestBytes/goext/langext"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func noErrStructHash(t *testing.T, dat any, opt ...StructHashOptions) []byte {
|
||||||
|
res, err := StructHash(dat, opt...)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
t.FailNow()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStructHashSimple(t *testing.T) {
|
||||||
|
|
||||||
|
assertEqual(t, "209bf774af36cc3a045c152d9f1269ef3684ad819c1359ee73ff0283a308fefa", noErrStructHash(t, "Hello"))
|
||||||
|
assertEqual(t, "c32f3626b981ae2997db656f3acad3f1dc9d30ef6b6d14296c023e391b25f71a", noErrStructHash(t, 0))
|
||||||
|
assertEqual(t, "01b781b03e9586b257d387057dfc70d9f06051e7d3c1e709a57e13cc8daf3e35", noErrStructHash(t, []byte{}))
|
||||||
|
assertEqual(t, "93e1dcd45c732fe0079b0fb3204c7c803f0921835f6bfee2e6ff263e73eed53c", noErrStructHash(t, []int{}))
|
||||||
|
assertEqual(t, "54f637a376aad55b3160d98ebbcae8099b70d91b9400df23fb3709855d59800a", noErrStructHash(t, []int{1, 2, 3}))
|
||||||
|
assertEqual(t, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", noErrStructHash(t, nil))
|
||||||
|
assertEqual(t, "349a7db91aa78fd30bbaa7c7f9c7bfb2fcfe72869b4861162a96713a852f60d3", noErrStructHash(t, []any{1, "", nil}))
|
||||||
|
assertEqual(t, "ca51aab87808bf0062a4a024de6aac0c2bad54275cc857a4944569f89fd245ad", noErrStructHash(t, struct{}{}))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStructHashSimpleStruct(t *testing.T) {
|
||||||
|
|
||||||
|
type t0 struct {
|
||||||
|
F1 int
|
||||||
|
F2 []string
|
||||||
|
F3 *int
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEqual(t, "a90bff751c70c738bb5cfc9b108e783fa9c19c0bc9273458e0aaee6e74aa1b92", noErrStructHash(t, t0{
|
||||||
|
F1: 10,
|
||||||
|
F2: []string{"1", "2", "3"},
|
||||||
|
F3: nil,
|
||||||
|
}))
|
||||||
|
|
||||||
|
assertEqual(t, "5d09090dc34ac59dd645f197a255f653387723de3afa1b614721ea5a081c675f", noErrStructHash(t, t0{
|
||||||
|
F1: 10,
|
||||||
|
F2: []string{"1", "2", "3"},
|
||||||
|
F3: langext.Ptr(99),
|
||||||
|
}))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStructHashLayeredStruct(t *testing.T) {
|
||||||
|
|
||||||
|
type t1_1 struct {
|
||||||
|
F10 float32
|
||||||
|
F12 float64
|
||||||
|
F15 bool
|
||||||
|
}
|
||||||
|
type t1_2 struct {
|
||||||
|
SV1 *t1_1
|
||||||
|
SV2 *t1_1
|
||||||
|
SV3 t1_1
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEqual(t, "fd4ca071fb40a288fee4b7a3dfdaab577b30cb8f80f81ec511e7afd72dc3b469", noErrStructHash(t, t1_2{
|
||||||
|
SV1: nil,
|
||||||
|
SV2: nil,
|
||||||
|
SV3: t1_1{
|
||||||
|
F10: 1,
|
||||||
|
F12: 2,
|
||||||
|
F15: false,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
assertEqual(t, "3fbf7c67d8121deda075cc86319a4e32d71744feb2cebf89b43bc682f072a029", noErrStructHash(t, t1_2{
|
||||||
|
SV1: nil,
|
||||||
|
SV2: &t1_1{},
|
||||||
|
SV3: t1_1{
|
||||||
|
F10: 3,
|
||||||
|
F12: 4,
|
||||||
|
F15: true,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
assertEqual(t, "b1791ccd1b346c3ede5bbffda85555adcd8216b93ffca23f14fe175ec47c5104", noErrStructHash(t, t1_2{
|
||||||
|
SV1: &t1_1{},
|
||||||
|
SV2: &t1_1{},
|
||||||
|
SV3: t1_1{
|
||||||
|
F10: 5,
|
||||||
|
F12: 6,
|
||||||
|
F15: false,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStructHashMap(t *testing.T) {
|
||||||
|
|
||||||
|
type t0 struct {
|
||||||
|
F1 int
|
||||||
|
F2 map[string]int
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEqual(t, "d50c53ad1fafb448c33fddd5aca01a86a2edf669ce2ecab07ba6fe877951d824", noErrStructHash(t, t0{
|
||||||
|
F1: 10,
|
||||||
|
F2: map[string]int{
|
||||||
|
"x": 1,
|
||||||
|
"0": 2,
|
||||||
|
"a": 99,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
assertEqual(t, "d50c53ad1fafb448c33fddd5aca01a86a2edf669ce2ecab07ba6fe877951d824", noErrStructHash(t, t0{
|
||||||
|
F1: 10,
|
||||||
|
F2: map[string]int{
|
||||||
|
"a": 99,
|
||||||
|
"x": 1,
|
||||||
|
"0": 2,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
m3 := make(map[string]int, 99)
|
||||||
|
m3["a"] = 0
|
||||||
|
m3["x"] = 0
|
||||||
|
m3["0"] = 0
|
||||||
|
|
||||||
|
m3["0"] = 99
|
||||||
|
m3["x"] = 1
|
||||||
|
m3["a"] = 2
|
||||||
|
|
||||||
|
assertEqual(t, "d50c53ad1fafb448c33fddd5aca01a86a2edf669ce2ecab07ba6fe877951d824", noErrStructHash(t, t0{
|
||||||
|
F1: 10,
|
||||||
|
F2: m3,
|
||||||
|
}))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertEqual(t *testing.T, expected string, actual []byte) {
|
||||||
|
actualStr := hex.EncodeToString(actual)
|
||||||
|
if actualStr != expected {
|
||||||
|
t.Errorf("values differ: Actual: '%v', Expected: '%v'", actualStr, expected)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user