Skip to content

Commit

Permalink
Replace cnf structhash with custom one
Browse files Browse the repository at this point in the history
  • Loading branch information
krhubert committed Sep 4, 2019
1 parent f576ae3 commit 26de5dd
Show file tree
Hide file tree
Showing 5 changed files with 416 additions and 9 deletions.
6 changes: 1 addition & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ require (
github.com/Microsoft/go-winio v0.4.12 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/btcsuite/btcd v0.0.0-20190807005414-4063feeff79a // indirect
github.com/cnf/structhash v0.0.0-20180104161610-62a607eb0224
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc // indirect
github.com/cosmos/cosmos-sdk v0.36.0
github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d
Expand Down Expand Up @@ -38,8 +37,6 @@ require (
github.com/magiconair/properties v1.8.1 // indirect
github.com/mattn/go-isatty v0.0.8 // indirect
github.com/mitchellh/go-homedir v1.1.0
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/mr-tron/base58 v1.1.1
github.com/mwitkow/go-proto-validators v0.0.0-20190212092829-1f388280e944 // indirect
github.com/onsi/ginkgo v1.8.0 // indirect
Expand All @@ -65,12 +62,11 @@ require (
github.com/tendermint/tendermint v0.32.2
github.com/tendermint/tm-db v0.1.1
github.com/vektra/mockery v0.0.0-20181123154057-e78b021dcbb5
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 // indirect
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 // indirect
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a // indirect
golang.org/x/text v0.3.2 // indirect
golang.org/x/tools v0.0.0-20190813142322-97f12d73768f // indirect
google.golang.org/appengine v1.4.0 // indirect
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64 // indirect
google.golang.org/grpc v1.23.0
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtE
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cnf/structhash v0.0.0-20180104161610-62a607eb0224 h1:rnCKRrdSBqc061l0CDuYB+7X3w6w8IK/VCSChJXv62g=
github.com/cnf/structhash v0.0.0-20180104161610-62a607eb0224/go.mod h1:pCxVEbcm3AMg7ejXyorUXi6HQCzOIBf7zEDVPtw0/U4=
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc h1:TP+534wVlf61smEIq1nwLLAjQVEK2EADoW3CX9AuT+8=
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
Expand Down
4 changes: 2 additions & 2 deletions hash/hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"fmt"
"hash"

"github.com/cnf/structhash"
"github.com/mesg-foundation/engine/hash/structhash"
"github.com/mr-tron/base58"
)

Expand All @@ -32,7 +32,7 @@ func (d *Digest) Sum(b []byte) Hash {
// WriteObject adds an interface data to the running hash.
// It never retruns an error.
func (d *Digest) WriteObject(v interface{}) (int, error) {
return d.Write(structhash.Dump(v, 0))
return d.Write(structhash.Dump(v))
}

// A Hash is a type for representing common hash.
Expand Down
211 changes: 211 additions & 0 deletions hash/structhash/structhash.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
package structhash

import (
"bytes"
"crypto/md5"
"crypto/sha1"
"fmt"
"reflect"
"sort"
"strconv"
"strings"
"sync"

"golang.org/x/crypto/sha3"
)

var bufPool = sync.Pool{
New: func() interface{} {
return &bytes.Buffer{}
},
}

// Sha1 takes a data structure and returns its md5 hash as string.
func Sha1(v interface{}) [sha1.Size]byte {
return sha1.Sum(serialize(v))
}

// Sha1 takes a data structure and returns its md5 hash as string.
func Sha3(v interface{}) [64]byte {
return sha3.Sum512(serialize(v))
}

// Md5 takes a data structure and returns its md5 hash.
func Md5(v interface{}) [md5.Size]byte {
return md5.Sum(serialize(v))
}

// Dump takes a data structure and returns its string representation.
func Dump(v interface{}) []byte {
return serialize(v)
}

func serialize(v interface{}) []byte {
return []byte(valueToString(reflect.ValueOf(v)))
}

func isEmptyValue(v reflect.Value) bool {
switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Complex64, reflect.Complex128:
c := v.Complex()
return real(c) == 0 && imag(c) == 0
case reflect.Interface, reflect.Ptr:
return v.IsNil()
}
return false
}

func valueToString(v reflect.Value) string {
buf := bufPool.Get().(*bytes.Buffer)
s := write(buf, v).String()
buf.Reset()
bufPool.Put(buf)
return s
}

//nolint:gocyclo
func write(buf *bytes.Buffer, v reflect.Value) *bytes.Buffer {
switch v.Kind() {
case reflect.Bool:
buf.WriteString(strconv.FormatBool(v.Bool()))
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
buf.WriteString(strconv.FormatInt(v.Int(), 16))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
buf.WriteString(strconv.FormatUint(v.Uint(), 16))
case reflect.Float32, reflect.Float64:
buf.WriteString(strconv.FormatFloat(v.Float(), 'e', -1, 64))
case reflect.String:
buf.WriteString("\"" + v.String() + "\"")
case reflect.Interface:
if v.IsNil() {
buf.WriteString("nil")
return buf
}
if v.CanInterface() {
write(buf, v.Elem())
}
case reflect.Struct:
vt := v.Type()
items := make([]string, 0)
for i := 0; i < v.NumField(); i++ {
sf := vt.Field(i)
to := parseTag(sf)
if to.skip || to.omitempty && isEmptyValue(v.Field(i)) {
continue
}

str := valueToString(v.Field(i))
// if field string == "" then it is chan,func or invalid type
// and skip it
if str == "" {
continue
}
items = append(items, to.name+":"+str)
}
sort.Strings(items)

buf.WriteByte('{')
for i := range items {
if i != 0 {
buf.WriteByte(',')
}
buf.WriteString(items[i])
}
buf.WriteByte('}')
case reflect.Map:
if v.IsNil() {
buf.WriteString("()nil")
return buf
}
buf.WriteByte('(')

keys := v.MapKeys()
items := make([]string, len(keys))

// Extract and sort the keys.
for i, key := range keys {
items[i] = valueToString(key) + ":" + valueToString(v.MapIndex(key))
}
sort.Strings(items)

for i := range items {
if i > 0 {
buf.WriteByte(',')
}
buf.WriteString(items[i])
}
buf.WriteByte(')')
case reflect.Slice:
if v.IsNil() {
buf.WriteString("[]nil")
return buf
}
fallthrough
case reflect.Array:
buf.WriteByte('[')
for i := 0; i < v.Len(); i++ {
if i != 0 {
buf.WriteByte(',')
}
write(buf, v.Index(i))
}
buf.WriteByte(']')
case reflect.Ptr:
if v.IsNil() {
buf.WriteString("nil")
return buf
}
write(buf, v.Elem())
case reflect.Complex64, reflect.Complex128:
c := v.Complex()
buf.WriteString(strconv.FormatFloat(real(c), 'e', -1, 64))
buf.WriteString(strconv.FormatFloat(imag(c), 'e', -1, 64))
buf.WriteString("i")
}
return buf
}

// tagOptions is the string struct field's "hash"
type tagOptions struct {
name string
omitempty bool
skip bool
}

// parseTag splits a struct field's hash tag into its name and
// comma-separated options.
func parseTag(f reflect.StructField) tagOptions {
tag := f.Tag.Get("hash")
if tag == "" || tag == "-" {
return tagOptions{skip: true}
}

var to tagOptions
options := strings.Split(tag, ",")
for _, option := range options {
switch {
case option == "omitempty":
to.omitempty = true
case strings.HasPrefix(option, "name:"):
to.name = option[len("name:"):]
default:
panic(fmt.Sprintf("structhash: field %s with tag hash:%q has invalid option %q", f.Name, tag, option))
}
}

// skip fields without name
if to.name == "" {
return tagOptions{skip: true}
}
return to
}
Loading

0 comments on commit 26de5dd

Please sign in to comment.