diff --git a/cosmos/module_client.go b/cosmos/module_client.go index a0b23c788..59f940ab6 100644 --- a/cosmos/module_client.go +++ b/cosmos/module_client.go @@ -7,6 +7,7 @@ import ( executionpb "github.com/mesg-foundation/engine/execution" "github.com/mesg-foundation/engine/ext/xos" "github.com/mesg-foundation/engine/hash" + "github.com/mesg-foundation/engine/hash/hashserializer" instancepb "github.com/mesg-foundation/engine/instance" ownershippb "github.com/mesg-foundation/engine/ownership" processpb "github.com/mesg-foundation/engine/process" @@ -223,7 +224,7 @@ func (mc *ModuleClient) CreateRunner(req *api.CreateRunnerRequest) (*runnerpb.Ru if err != nil { return nil, err } - envHash := hash.Dump(xos.EnvMergeSlices(s.Configuration.Env, req.Env)) + envHash := hash.Dump(hashserializer.StringSlice(xos.EnvMergeSlices(s.Configuration.Env, req.Env))) acc, err := mc.GetAccount() if err != nil { return nil, err diff --git a/e2e/instance_test.go b/e2e/instance_test.go index 3917f5da9..d8b09dce4 100644 --- a/e2e/instance_test.go +++ b/e2e/instance_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/mesg-foundation/engine/hash" + "github.com/mesg-foundation/engine/hash/hashserializer" "github.com/mesg-foundation/engine/instance" pb "github.com/mesg-foundation/engine/protobuf/api" "github.com/stretchr/testify/require" @@ -19,14 +20,14 @@ func testInstance(t *testing.T) { require.NoError(t, err) require.Equal(t, testInstanceHash, resp.Hash) require.Equal(t, testServiceHash, resp.ServiceHash) - require.Equal(t, hash.Dump([]string{"BAR=3", "FOO=1", "REQUIRED=4"}), resp.EnvHash) + require.Equal(t, hash.Dump(hashserializer.StringSlice([]string{"BAR=3", "FOO=1", "REQUIRED=4"})), resp.EnvHash) }) t.Run("lcd", func(t *testing.T) { var inst *instance.Instance lcdGet(t, "instance/get/"+testInstanceHash.String(), &inst) require.Equal(t, testInstanceHash, inst.Hash) require.Equal(t, testServiceHash, inst.ServiceHash) - require.Equal(t, hash.Dump([]string{"BAR=3", "FOO=1", "REQUIRED=4"}), inst.EnvHash) + require.Equal(t, hash.Dump(hashserializer.StringSlice([]string{"BAR=3", "FOO=1", "REQUIRED=4"})), inst.EnvHash) }) }) diff --git a/event/serialize.go b/event/serialize.go new file mode 100644 index 000000000..980d49289 --- /dev/null +++ b/event/serialize.go @@ -0,0 +1,15 @@ +package event + +import "github.com/mesg-foundation/engine/hash/hashserializer" + +// HashSerialize returns the hashserialized string of this type +func (data *Event) HashSerialize() string { + if data == nil { + return "" + } + return hashserializer.New(). + AddString("2", data.InstanceHash.String()). + AddString("3", data.Key). + Add("4", data.Data). + HashSerialize() +} diff --git a/event/serialize_test.go b/event/serialize_test.go new file mode 100644 index 000000000..afe11781a --- /dev/null +++ b/event/serialize_test.go @@ -0,0 +1,32 @@ +package event + +import ( + "testing" + + "github.com/mesg-foundation/engine/hash" + "github.com/mesg-foundation/engine/protobuf/types" + "github.com/stretchr/testify/require" +) + +var data = &Event{ + Key: "eventKey", + InstanceHash: hash.Int(10), + Data: &types.Struct{ + Fields: map[string]*types.Value{ + "foo": { + Kind: &types.Value_StringValue{StringValue: "bar"}, + }, + }, + }, +} + +func TestHashSerialize(t *testing.T) { + require.Equal(t, "2:g35TxFqwMx95vCk63fTxGTHb6ei4W24qg5t2x6xD3cT;3:eventKey;4:1:foo:3:bar;;;;", data.HashSerialize()) + require.Equal(t, "CGQ1DWeSsf13BDovLNb9zHXTVMUPNcTWY4DaSAFNN88T", hash.Dump(data).String()) +} + +func BenchmarkHashSerialize(b *testing.B) { + for i := 0; i < b.N; i++ { + data.HashSerialize() + } +} diff --git a/execution/serialize.go b/execution/serialize.go new file mode 100644 index 000000000..b1047284e --- /dev/null +++ b/execution/serialize.go @@ -0,0 +1,19 @@ +package execution + +import "github.com/mesg-foundation/engine/hash/hashserializer" + +// HashSerialize returns the hashserialized string of this type +func (data *Execution) HashSerialize() string { + return hashserializer.New(). + AddString("2", data.ParentHash.String()). + AddString("3", data.EventHash.String()). + AddString("5", data.InstanceHash.String()). + AddString("6", data.TaskKey). + Add("7", data.Inputs). + AddStringSlice("10", data.Tags). + AddString("11", data.ProcessHash.String()). + AddString("12", data.NodeKey). + AddString("13", data.ExecutorHash.String()). + AddString("14", data.Price). + HashSerialize() +} diff --git a/execution/serialize_test.go b/execution/serialize_test.go new file mode 100644 index 000000000..cf65ec85f --- /dev/null +++ b/execution/serialize_test.go @@ -0,0 +1,77 @@ +package execution + +import ( + "testing" + + "github.com/mesg-foundation/engine/hash" + "github.com/mesg-foundation/engine/protobuf/types" + "github.com/stretchr/testify/require" +) + +var instanceHash, _ = hash.Decode("5M8pQvBCPYwzwxe2bZUbV2g8bSgpsotp441xYvVBNMhd") +var exec = New(nil, instanceHash, nil, nil, "", "taskKey", "", &types.Struct{ + Fields: map[string]*types.Value{ + "a": { + Kind: &types.Value_StringValue{ + StringValue: "b", + }, + }, + "b": { + Kind: &types.Value_NumberValue{ + NumberValue: 3.14159265359, + }, + }, + "c": { + Kind: &types.Value_BoolValue{ + BoolValue: true, + }, + }, + "d": { + Kind: &types.Value_ListValue{ + ListValue: &types.ListValue{ + Values: []*types.Value{ + { + Kind: &types.Value_NullValue{ + NullValue: types.NullValue_NULL_VALUE, + }, + }, + { + Kind: &types.Value_StringValue{ + StringValue: "hello", + }, + }, + }, + }, + }, + }, + "e": { + Kind: &types.Value_NullValue{ + NullValue: types.NullValue_NULL_VALUE, + }, + }, + "f": { + Kind: &types.Value_StructValue{ + StructValue: &types.Struct{ + Fields: map[string]*types.Value{ + "a": { + Kind: &types.Value_StringValue{ + StringValue: "hello", + }, + }, + }, + }, + }, + }, + }, +}, nil, nil) + +func TestHashSerialize(t *testing.T) { + require.Equal(t, "5:5M8pQvBCPYwzwxe2bZUbV2g8bSgpsotp441xYvVBNMhd;6:taskKey;7:1:a:3:b;;b:2:3.14159265359;;c:4:true;;d:6:1:1:3:hello;;;;;f:5:1:a:3:hello;;;;;;;", exec.HashSerialize()) + require.Equal(t, "CNT7drUzuRuv59bbTXhoD2AUzs8vrQgi7XqfAxeHvxGf", hash.Dump(exec).String()) +} + +func BenchmarkHashSerialize(b *testing.B) { + for i := 0; i < b.N; i++ { + exec.HashSerialize() + } +} diff --git a/go.mod b/go.mod index 686b76907..8d0704569 100644 --- a/go.mod +++ b/go.mod @@ -52,7 +52,6 @@ require ( github.com/tendermint/go-amino v0.15.1 github.com/tendermint/tendermint v0.33.0 github.com/tendermint/tm-db v0.4.0 - golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 google.golang.org/grpc v1.27.1 gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/go-playground/validator.v9 v9.31.0 diff --git a/hash/hash.go b/hash/hash.go index cf85e8f81..d57065f77 100644 --- a/hash/hash.go +++ b/hash/hash.go @@ -8,51 +8,32 @@ import ( "encoding/json" "errors" "fmt" - "hash" - "github.com/mesg-foundation/engine/hash/structhash" + "github.com/mesg-foundation/engine/hash/hashserializer" "github.com/mr-tron/base58" ) -// DefaultHash is a default hashing algorithm - "sha256". -var DefaultHash = sha256.New +// size is the default size for the hashing algorithm. +const size = sha256.Size -// size is a default size for hashing algorithm. -var size = DefaultHash().Size() +// sumFunc is the default function to hash. +var sumFunc = sha256.Sum256 var errInvalidLen = errors.New("hash: invalid length") -// Digest represents the partial evaluation of a checksum. -type Digest struct { - hash.Hash -} - -// Sum appends the current checksum to b and returns the Hash. -func (d *Digest) Sum(b []byte) Hash { - return Hash(d.Hash.Sum(b)) -} - -// 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)) -} - // A Hash is a type for representing common hash. type Hash []byte -// New returns new hash from a given integer. -func New() *Digest { - return &Digest{ - Hash: DefaultHash(), - } +// Dump takes a structure that implement HashSerializable and returns its hash. +func Dump(v hashserializer.HashSerializable) Hash { + h := sumFunc([]byte(v.HashSerialize())) + return Hash(h[:]) } -// Dump takes an interface and returns its hash representation. -func Dump(v interface{}) Hash { - d := New() - d.WriteObject(v) - return d.Sum(nil) +// Sum takes a slice of byte and returns its hash. +func Sum(v []byte) Hash { + h := sumFunc(v) + return Hash(h[:]) } // Int returns a new hash from a given integer. @@ -150,12 +131,12 @@ func (h Hash) Valid() bool { return len(h) == 0 || len(h) == size } -// MarshalJSON mashals hash into encoded json string. +// MarshalJSON marshals hash into encoded json string. func (h Hash) MarshalJSON() ([]byte, error) { return json.Marshal(base58.Encode(h)) } -// UnmarshalJSON unmashals hex encoded json string into hash. +// UnmarshalJSON unmarshals hex encoded json string into hash. func (h *Hash) UnmarshalJSON(data []byte) error { var str string if err := json.Unmarshal(data, &str); err != nil { diff --git a/hash/hash_test.go b/hash/hash_test.go index 6607d9237..5beaec91d 100644 --- a/hash/hash_test.go +++ b/hash/hash_test.go @@ -5,6 +5,7 @@ import ( "testing" "testing/quick" + "github.com/mesg-foundation/engine/hash/hashserializer" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -14,16 +15,22 @@ var ( one = Int(1) ) -func TestDigest(t *testing.T) { - d := New() - d.Write([]byte{0}) +type testDump struct { + a int + b string + c []string +} - hash := d.Sum(nil) - assert.Equal(t, hash.String(), "8RBsoeyoRwajj86MZfZE6gMDJQVYGYcdSfx1zxqxNHbr") +func (data testDump) HashSerialize() string { + return hashserializer.New(). + AddInt("1", data.a). + AddString("2", data.b). + AddStringSlice("3", data.c). + HashSerialize() } func TestDump(t *testing.T) { - assert.Equal(t, Dump(struct{}{}).String(), "5ajuwjHoLj33yG5t5UFsJtUb3vnRaJQEMPqSLz6VyoHK") + assert.Equal(t, "2MREjs2XD2Lcyea4XhsAXBU6zrJE39JRHKrpHU4Q2dgj", Dump(testDump{42, "hello", []string{"c", "b", "a"}}).String()) } func TestInt(t *testing.T) { diff --git a/hash/hashserializer/hashserializer.go b/hash/hashserializer/hashserializer.go new file mode 100644 index 000000000..01c5ecfce --- /dev/null +++ b/hash/hashserializer/hashserializer.go @@ -0,0 +1,190 @@ +// Package hashserializer creates a string representing arbitrary value that can then be used to generate a deterministic hash. +// +// Each value must have a unique prefix. +// +// Empty or default value are discarded. +// +// The order to add the value should always be the same if you want the created string to be deterministic. +// +// Each value can be added to the HashSerializer using one of the AddXXX function alongside the prefix: +// New(). +// AddBool(prefix, value). +// AddFloat(prefix, value). +// AddInt(prefix, value). +// AddString(prefix, value). +// AddStringSlice(prefix, value). +// Add(prefix, value) // value must implement function `HashSerialize() string`. +// HashSerialize() +// +// Format +// +// The format of the generated string for one value with a prefix is: +// prefix:value; +// where value is the string representation of the value. +// +// When adding multiple values, they are append together: +// prefix1:value1;prefix2:value2; +// +// Empty and default value +// +// Empty and default values are discarded and not part of the generated string. For slices and maps, if every value is empty then the slice or maps is also considered as empty. +// +// The following values are all considered as empty and will not be part of the generated string: +// string("") +// bool(false) +// int(0) +// float(0.0) +// []string{} +// []string{""} +// map[string]string{} +// map[string]string{"a":"", "b":""} +// +// String +// +// String values are the simplest are the value doesn't need to be encoded. +// +// For prefix "foo" and value "bar", the result is: +// foo:bar; +// +// Bool +// +// Only bool value true produce an output as the default value false is discarded. +// +// For prefix "foo" and value "true", the result is: +// foo:true; +// +// Int +// +// Int values are encoded to string using base 10 representation. If the value is negative, the sign "-" should prefix the value. +// +// For prefix "foo" and value "42", the result is: +// foo:42; +// +// Float +// +// Float values are encoded to string using base 10 with no exponent. If the value is negative, the sign "-" should prefix the value. +// +// For prefix "foo" and value "3.14159265359", the result is: +// foo:3.14159265359; +// +// Slice +// +// For slices, prefixes are the index of the elements. Empty value are discarded. +// +// []string{"foo", "", "bar"} +// 0:foo;2:bar; +// +// Map +// +// For maps, prefixes are the keys. The maps must be sorted by keys to get a deterministic result. Empty value are discarded. +// +// map[string]string{"c":"bar", "b": "", "a":"foo"} +// a:foo;c:bar; +// +// Struct +// +// Struct should implement the function `HashSerialize() string` to return the hash-serialized string. +// +// Nested +// +// When adding a struct, a slice or a map, the value is hash-serialized before added to the string. +// +// For prefix "foo" and value `[]string{"foo", "", "bar"}`, the result is: +// foo:0:foo;2:bar;; +// +// +package hashserializer + +import ( + "bytes" + "strconv" +) + +// HashSerializable represents a serializable struct. +type HashSerializable interface { + HashSerialize() string +} + +// HashSerializer helps to create hash-serialized string of a struct. +type HashSerializer struct { + buf bytes.Buffer +} + +// New returns a new HashSerializer. +func New() *HashSerializer { + return &HashSerializer{} +} + +// HashSerialize returns the hash-serialized constructed string. +func (s *HashSerializer) HashSerialize() string { + return s.buf.String() +} + +// AddString adds a string to the serializer. +// If value is empty, nothing is added to the HashSerializer. +func (s *HashSerializer) AddString(prefix string, value string) *HashSerializer { + if value != "" { + s.buf.WriteString(prefix) + s.buf.WriteString(":") + s.buf.WriteString(value) + s.buf.WriteString(";") + } + return s +} + +// AddBool adds a boolean to the serializer. +// If value is false, nothing is added to the HashSerializer. +func (s *HashSerializer) AddBool(prefix string, value bool) *HashSerializer { + if value { + s.AddString(prefix, "true") + } + return s +} + +// AddFloat adds a float to the serializer. +// If value is 0, nothing is added to the HashSerializer. +func (s *HashSerializer) AddFloat(prefix string, value float64) *HashSerializer { + if value != 0 { + s.AddString(prefix, strconv.FormatFloat(value, 'f', -1, 64)) + } + return s +} + +// AddInt adds a int to the serializer. +// If value is 0, nothing is added to the HashSerializer. +func (s *HashSerializer) AddInt(prefix string, value int) *HashSerializer { + if value != 0 { + s.AddString(prefix, strconv.Itoa(value)) + } + return s +} + +// Add adds a struct implementing HashSerializable to the serializer. +// The function HashSerialize will be called on value and its result injected as a string in HashSerializer. +// If value.HashSerialize() returns an empty string, nothing is added to the HashSerializer. +func (s *HashSerializer) Add(prefix string, value HashSerializable) *HashSerializer { + s.AddString(prefix, value.HashSerialize()) + return s +} + +// AddStringSlice adds a slice of string to the serializer. +// If value is an empty slice or each element is empty, nothing is added to the HashSerializer. +func (s *HashSerializer) AddStringSlice(prefix string, value []string) *HashSerializer { + s.Add(prefix, StringSlice(value)) + return s +} + +// StringSlice is an helper type to easily hash-serialized slice of string. +type StringSlice []string + +// HashSerialize returns the hash-serialized string of this type. +func (s StringSlice) HashSerialize() string { + if s == nil || len(s) == 0 { + return "" + } + ser := New() + for i, value := range s { + ser.AddString(strconv.Itoa(i), value) + } + return ser.HashSerialize() +} diff --git a/hash/hashserializer/hashserializer_test.go b/hash/hashserializer/hashserializer_test.go new file mode 100644 index 000000000..081f878c6 --- /dev/null +++ b/hash/hashserializer/hashserializer_test.go @@ -0,0 +1,162 @@ +package hashserializer + +import ( + "fmt" +) + +type testSerializable struct { + a int + b string + c []string +} + +func (data testSerializable) HashSerialize() string { + return New(). + AddInt("1", data.a). + AddString("2", data.b). + AddStringSlice("3", data.c). + HashSerialize() +} + +func ExampleHashSerializer() { + ser := New(). + AddBool("1", true). // 1:true; + AddBool("2", false). // discarded + AddFloat("3", 3.14159265359). // 3:3.14159265359; + AddFloat("4", 0.0). // discarded + AddFloat("5", -42.14159265359). // 5:-42.14159265359; + AddInt("6", 42). // 6:42; + AddInt("7", 0.0). // discarded + AddInt("8", -42). // 8:-42; + AddString("9", ""). // discarded + AddString("10", "hello"). // 10:hello; + AddStringSlice("11", []string{"c", "b", "a"}). // 11:0:c;1:b;2:a;; + AddStringSlice("12", []string{}). // discarded + AddStringSlice("13", []string{"c", "", "a"}). // 13:0:c;2:a;; + Add("14", testSerializable{42, "hello", []string{"c", "b", "a"}}). // 14:1:42;2:hello;3:0:c;1:b;2:a;;; + Add("15", testSerializable{}) // discarded + fmt.Println(ser.HashSerialize()) + // Output: 1:true;3:3.14159265359;5:-42.14159265359;6:42;8:-42;10:hello;11:0:c;1:b;2:a;;13:0:c;2:a;;14:1:42;2:hello;3:0:c;1:b;2:a;;; +} + +func ExampleHashSerializer_AddBool_true() { + ser := New(). + AddBool("1", true) + fmt.Println(ser.HashSerialize()) + // Output: 1:true; +} + +func ExampleHashSerializer_AddBool_false() { + ser := New(). + AddBool("2", false) + fmt.Println(ser.HashSerialize()) + // Output: +} + +func ExampleHashSerializer_AddFloat() { + ser := New(). + AddFloat("3", 3.14159265359) + fmt.Println(ser.HashSerialize()) + // Output: 3:3.14159265359; +} + +func ExampleHashSerializer_AddFloat_zero() { + ser := New(). + AddFloat("4", 0.0) + fmt.Println(ser.HashSerialize()) + // Output: +} + +func ExampleHashSerializer_AddFloat_negative() { + ser := New(). + AddFloat("5", -42.14159265359) + fmt.Println(ser.HashSerialize()) + // Output: 5:-42.14159265359; +} + +func ExampleHashSerializer_AddInt() { + ser := New(). + AddInt("6", 42) + fmt.Println(ser.HashSerialize()) + // Output: 6:42; +} + +func ExampleHashSerializer_AddInt_zero() { + ser := New(). + AddInt("7", 0.0) + fmt.Println(ser.HashSerialize()) + // Output: +} + +func ExampleHashSerializer_AddInt_negative() { + ser := New(). + AddInt("8", -42) + fmt.Println(ser.HashSerialize()) + // Output: 8:-42; +} + +func ExampleHashSerializer_AddString_empty() { + ser := New(). + AddString("9", "") + fmt.Println(ser.HashSerialize()) + // Output: +} + +func ExampleHashSerializer_AddString() { + ser := New(). + AddString("10", "hello") + fmt.Println(ser.HashSerialize()) + // Output: 10:hello; +} + +func ExampleHashSerializer_AddStringSlice() { + ser := New(). + AddStringSlice("11", []string{"c", "b", "a"}) + fmt.Println(ser.HashSerialize()) + // Output: 11:0:c;1:b;2:a;; +} + +func ExampleHashSerializer_AddStringSlice_empty() { + ser := New(). + AddStringSlice("12", []string{}) + fmt.Println(ser.HashSerialize()) + // Output: +} + +func ExampleHashSerializer_AddStringSlice_empty_value() { + ser := New(). + AddStringSlice("13", []string{"c", "", "a"}) + fmt.Println(ser.HashSerialize()) + // Output: 13:0:c;2:a;; +} + +func ExampleHashSerializer_Add() { + ser := New(). + Add("14", testSerializable{42, "hello", []string{"c", "b", "a"}}) + fmt.Println(ser.HashSerialize()) + // Output: 14:1:42;2:hello;3:0:c;1:b;2:a;;; +} + +func ExampleHashSerializer_Add_empty() { + ser := New(). + Add("15", testSerializable{}) + fmt.Println(ser.HashSerialize()) + // Output: +} + +func ExampleStringSlice() { + ser := New(). + Add("1", StringSlice([]string{"a", "b", "c"})) + fmt.Println(ser.HashSerialize()) + // Output: 1:0:a;1:b;2:c;; +} + +func ExampleHashSerializable_HashSerialize() { + s := testSerializable{ + a: 42, + b: "hello", + c: []string{"c", "b", "a"}, + } + fmt.Println(s.HashSerialize()) + //Output: 1:42;2:hello;3:0:c;1:b;2:a;; +} diff --git a/hash/structhash/structhash.go b/hash/structhash/structhash.go deleted file mode 100644 index 9d706515b..000000000 --- a/hash/structhash/structhash.go +++ /dev/null @@ -1,224 +0,0 @@ -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 sha1 hash as string. -func Sha1(v interface{}) [sha1.Size]byte { - return sha1.Sum(serialize(v)) -} - -// Sha3 takes a data structure and returns its sha3 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 - } - 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) - - // NOTE: structhash will allow to process all interface types. - // gogo/protobuf is not able to set tags for directly oneof interface. - // see: https://github.com/gogo/protobuf/issues/623 - if to.name == "" && reflect.Zero(sf.Type).Kind() == reflect.Interface && (v.Field(i).Elem().Kind() == reflect.Struct || (v.Field(i).Elem().Kind() == reflect.Ptr && v.Field(i).Elem().Elem().Kind() == reflect.Struct)) { - to.skip = false - to.name = sf.Name - } - - 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 == "" { - // NOTE: skip - interface with no tags can still be serialzied - return tagOptions{skip: true} - } - - if tag == "-" { - // force skip - set name for "-" - return tagOptions{name: "-", 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 -} diff --git a/hash/structhash/structhash_test.go b/hash/structhash/structhash_test.go deleted file mode 100644 index 9aa2f0bdf..000000000 --- a/hash/structhash/structhash_test.go +++ /dev/null @@ -1,317 +0,0 @@ -package structhash - -import ( - "fmt" - "reflect" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestSha1(t *testing.T) { - assert.Equal(t, "da39a3ee5e6b4b0d3255bfef95601890afd80709", fmt.Sprintf("%x", Sha1(nil))) -} - -func TestSha3(t *testing.T) { - assert.Equal(t, "a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26", fmt.Sprintf("%x", Sha3(nil))) -} - -func TestMd5(t *testing.T) { - assert.Equal(t, "d41d8cd98f00b204e9800998ecf8427e", fmt.Sprintf("%x", Md5(nil))) -} - -//nolint:megacheck -func TestDump(t *testing.T) { - int1 := int(1) - tests := []struct { - v interface{} - s string - }{ - {nil, ""}, - {make(chan string), ""}, - {func() {}, ""}, - {false, "false"}, - {(*int)(nil), "nil"}, - {int(0), "0"}, - {uint(0), "0"}, - {0.0, "0e+00"}, - {"", `""`}, - {interface{}(0), "0"}, - {map[int]int(nil), "()nil"}, - {map[int]int{}, "()"}, - {map[int]int{0: 0}, "(0:0)"}, - {map[int]int{0: 0, 1: 1}, "(0:0,1:1)"}, - {map[int]int{1: 1, 0: 0}, "(0:0,1:1)"}, - {[]int(nil), "[]nil"}, - {[]*int{nil}, "[nil]"}, - {[]int{}, "[]"}, - {[]int{0}, "[0]"}, - {[]int{0, 1}, "[0,1]"}, - {[0]int{}, "[]"}, - {[1]int{0}, "[0]"}, - {[2]int{0, 1}, "[0,1]"}, - {complex(0, 0), "0e+000e+00i"}, - {(*struct{})(nil), "nil"}, - {struct{}{}, "{}"}, - { - struct { - a chan int `hash:"name:a"` - }{}, - "{}", - }, - { - struct { - a int `hash:"omitempty"` - }{1}, - "{}", - }, - { - struct { - a interface{} `hash:"name:a"` - }{nil}, - "{a:nil}", - }, - { - struct { - a interface{} `hash:"name:a"` - }{ - struct { - b map[string]int `hash:"name:b"` - }{ - map[string]int{ - "c": 1, - }, - }, - }, - "{a:{b:(\"c\":1)}}", - }, - { - struct { - a interface{} `hash:"name:a"` - }{ - struct { - c map[string]int `hash:"name:c"` - b map[string]int `hash:"name:b"` - }{ - c: map[string]int{ - "100": 1, - "1": 1, - }, - b: map[string]int{ - "100": 1, - "1": 1, - }, - }, - }, - "{a:{b:(\"1\":1,\"100\":1),c:(\"1\":1,\"100\":1)}}", - }, - // NOTE: structhash will allow to process all interface types. - // gogo/protobuf is not able to set tags for directly oneof interface. - // see: https://github.com/gogo/protobuf/issues/623 - { - struct { - a interface{} - }{ - struct { - b map[string]int `hash:"name:b"` - }{ - map[string]int{ - "c": 1, - }, - }, - }, - "{a:{b:(\"c\":1)}}", - }, - { - struct { - A interface{} `hash:"name:A"` - }{0}, - "{A:0}", - }, - { - struct { - a int `hash:"name:a"` - }{0}, - "{a:0}", - }, - { - struct { - a int `hash:"name:a"` - b int `hash:"name:b"` - }{0, 1}, - "{a:0,b:1}", - }, - { - struct { - a struct { - a bool `hash:"name:a"` - } `hash:"name:a"` - }{a: struct { - a bool `hash:"name:a"` - }{a: false}}, - "{a:{a:false}}", - }, - { - struct { - a *struct { - b bool `hash:"name:b"` - } `hash:"name:a"` - }{a: &struct { - b bool `hash:"name:b"` - }{b: false}}, - "{a:{b:false}}", - }, - { - struct { - a interface{} - }{ - struct { - b int - c interface{} - }{ - b: 1, - c: 2, - }, - }, - "{a:{}}", - }, - { - struct { - a interface{} - }{ - &struct { - b int `hash:"name:b"` - c interface{} `hash:"name:c"` - }{ - b: 1, - c: 2, - }, - }, - "{a:{b:1,c:2}}", - }, - { - struct { - a interface{} - }{ - &struct { - b *int - c interface{} - }{ - b: &int1, - c: &int1, - }, - }, - "{a:{}}", - }, - { - struct { - a interface{} - }{ - &struct { - b *int - c interface{} - }{ - b: nil, - c: nil, - }, - }, - "{a:{}}", - }, - { - struct { - a interface{} - }{nil}, - "{}", - }, - } - - for _, tt := range tests { - s := Dump(tt.v) - assert.Equalf(t, tt.s, string(s), "type %s: %v", reflect.TypeOf(tt.v), tt.v) - } -} - -//nolint:megacheck -func TestTag(t *testing.T) { - tests := []struct { - v interface{} - s string - }{ - { - struct { - a int `hash:"-"` - b uint `hash:"-"` - c bool `hash:"-"` - d string `hash:"-"` - e []int `hash:"-"` - f float64 `hash:"-"` - g complex128 `hash:"-"` - h interface{} `hash:"-"` - i *struct{} `hash:"-"` - j *[]uint `hash:"-"` - k chan int `hash:"-"` - }{}, - "{}", - }, - { - struct { - a int `hash:"name:a,omitempty"` - b uint `hash:"name:b,omitempty"` - c bool `hash:"name:c,omitempty"` - d string `hash:"name:d,omitempty"` - e []int `hash:"name:e,omitempty"` - f float64 `hash:"name:f,omitempty"` - g complex128 `hash:"name:g,omitempty"` - h interface{} `hash:"name:h,omitempty"` - i *struct{} `hash:"name:i,omitempty"` - j *[]uint `hash:"name:j,omitempty"` - k chan int `hash:"name:k,omitempty"` - }{}, - "{}", - }, - { - struct { - a int `hash:"name:b"` - }{}, - "{b:0}", - }, - { - struct { - a int `hash:"name:b"` - b int `hash:"name:a"` - }{0, 1}, - "{a:1,b:0}", - }, - { - struct { - a int `hash:"name:b,omitempty"` - b int `hash:"name:a,omitempty"` - }{0, 1}, - "{a:1}", - }, - { - struct { - a int - b int `hash:"-"` - c int `hash:"name:c"` - }{1, 2, 3}, - "{c:3}", - }, - } - - for _, tt := range tests { - assert.Equalf(t, tt.s, string(serialize(tt.v)), "type %s: %v", reflect.TypeOf(tt.v), tt.v) - } -} - -func TestTagPanicInvalidOption(t *testing.T) { - defer func() { - if r := recover(); r == nil { - t.Errorf("serialize did not panic with invalid option") - } - }() - - serialize(struct { - a int `hash:"name:a,omitempty,invalid"` - }{0}) -} diff --git a/instance/serialize.go b/instance/serialize.go new file mode 100644 index 000000000..9961ca177 --- /dev/null +++ b/instance/serialize.go @@ -0,0 +1,14 @@ +package instance + +import "github.com/mesg-foundation/engine/hash/hashserializer" + +// HashSerialize returns the hashserialized string of this type +func (data *Instance) HashSerialize() string { + if data == nil { + return "" + } + return hashserializer.New(). + AddString("2", data.ServiceHash.String()). + AddString("3", data.EnvHash.String()). + HashSerialize() +} diff --git a/instance/serialize_test.go b/instance/serialize_test.go new file mode 100644 index 000000000..b881e52f4 --- /dev/null +++ b/instance/serialize_test.go @@ -0,0 +1,24 @@ +package instance + +import ( + "testing" + + "github.com/mesg-foundation/engine/hash" + "github.com/stretchr/testify/require" +) + +var data = &Instance{ + ServiceHash: hash.Int(10), + EnvHash: hash.Int(5), +} + +func TestHashSerialize(t *testing.T) { + require.Equal(t, "2:g35TxFqwMx95vCk63fTxGTHb6ei4W24qg5t2x6xD3cT;3:LX3EUdRUBUa3TbsYXLEUdj9J3prXkWXvLYSWyYyc2Jj;", data.HashSerialize()) + require.Equal(t, "BwWnWRgpPfB9SPmSRKYZp8Dq1LEpCpwHAGZFsJJEg1nd", hash.Dump(data).String()) +} + +func BenchmarkHashSerialize(b *testing.B) { + for i := 0; i < b.N; i++ { + data.HashSerialize() + } +} diff --git a/ownership/serialize.go b/ownership/serialize.go new file mode 100644 index 000000000..2e45746cd --- /dev/null +++ b/ownership/serialize.go @@ -0,0 +1,14 @@ +package ownership + +import "github.com/mesg-foundation/engine/hash/hashserializer" + +// HashSerialize returns the hashserialized string of this type +func (data *Ownership) HashSerialize() string { + if data == nil { + return "" + } + return hashserializer.New(). + AddString("2", data.Owner). + AddString("3", data.ResourceHash.String()). + HashSerialize() +} diff --git a/ownership/serialize_test.go b/ownership/serialize_test.go new file mode 100644 index 000000000..70b406f77 --- /dev/null +++ b/ownership/serialize_test.go @@ -0,0 +1,25 @@ +package ownership + +import ( + "testing" + + "github.com/mesg-foundation/engine/hash" + "github.com/stretchr/testify/require" +) + +var data = &Ownership{ + Owner: "hello", + ResourceHash: hash.Int(5), + Resource: Ownership_Process, +} + +func TestHashSerialize(t *testing.T) { + require.Equal(t, "2:hello;3:LX3EUdRUBUa3TbsYXLEUdj9J3prXkWXvLYSWyYyc2Jj;", data.HashSerialize()) + require.Equal(t, "GzZBiyQWRkDAAwnpckM9VvZxtZZEZii7UUsjjxycZJ8N", hash.Dump(data).String()) +} + +func BenchmarkHashSerialize(b *testing.B) { + for i := 0; i < b.N; i++ { + data.HashSerialize() + } +} diff --git a/process/serialize.go b/process/serialize.go new file mode 100644 index 000000000..fd89e0bb1 --- /dev/null +++ b/process/serialize.go @@ -0,0 +1,242 @@ +package process + +import ( + "sort" + "strconv" + + "github.com/mesg-foundation/engine/hash/hashserializer" +) + +// HashSerialize returns the hashserialized string of this type +func (data *Process) HashSerialize() string { + if data == nil { + return "" + } + return hashserializer.New(). + AddString("2", data.Name). + Add("4", processNodes(data.Nodes)). + Add("5", processEdges(data.Edges)). + HashSerialize() +} + +// HashSerialize returns the hashserialized string of this type +func (data *Process_Node) HashSerialize() string { + if data == nil { + return "" + } + return hashserializer.New(). + AddString("1", data.Key). + Add("2", data.GetResult()). + Add("3", data.GetEvent()). + Add("4", data.GetTask()). + Add("5", data.GetMap()). + Add("6", data.GetFilter()). + HashSerialize() +} + +// HashSerialize returns the hashserialized string of this type +func (data *Process_Node_Result) HashSerialize() string { + if data == nil { + return "" + } + return hashserializer.New(). + AddString("2", data.InstanceHash.String()). + AddString("3", data.TaskKey). + HashSerialize() +} + +// HashSerialize returns the hashserialized string of this type +func (data *Process_Node_Event) HashSerialize() string { + if data == nil { + return "" + } + return hashserializer.New(). + AddString("2", data.InstanceHash.String()). + AddString("3", data.EventKey). + HashSerialize() +} + +// HashSerialize returns the hashserialized string of this type +func (data *Process_Node_Task) HashSerialize() string { + if data == nil { + return "" + } + return hashserializer.New(). + AddString("2", data.InstanceHash.String()). + AddString("3", data.TaskKey). + HashSerialize() +} + +// HashSerialize returns the hashserialized string of this type +func (data *Process_Node_Map) HashSerialize() string { + if data == nil { + return "" + } + return hashserializer.New(). + Add("1", mapProcessNodeMapOutput(data.Outputs)). + HashSerialize() +} + +type mapProcessNodeMapOutput map[string]*Process_Node_Map_Output + +// HashSerialize returns the hashserialized string of this type +func (data mapProcessNodeMapOutput) HashSerialize() string { + if data == nil || len(data) == 0 { + return "" + } + keys := make([]string, 0, len(data)) + for k := range data { + keys = append(keys, k) + } + sort.Strings(keys) + ser := hashserializer.New() + for _, key := range keys { + ser.Add(key, data[key]) + } + return ser.HashSerialize() +} + +// HashSerialize returns the hashserialized string of this type +func (data *Process_Node_Map_Output) HashSerialize() string { + if data == nil { + return "" + } + return hashserializer.New(). + AddInt("1", int(data.GetNull())). + AddString("2", data.GetStringConst()). + AddFloat("3", data.GetDoubleConst()). + AddBool("4", data.GetBoolConst()). + Add("5", data.GetRef()). + Add("6", data.GetList()). + Add("7", data.GetMap()). + HashSerialize() +} + +// HashSerialize returns the hashserialized string of this type +func (data *Process_Node_Map_Output_Map) HashSerialize() string { + if data == nil { + return "" + } + return hashserializer.New(). + Add("1", mapProcessNodeMapOutput(data.Outputs)). + HashSerialize() +} + +// HashSerialize returns the hashserialized string of this type +func (data *Process_Node_Map_Output_List) HashSerialize() string { + if data == nil { + return "" + } + return hashserializer.New(). + Add("1", processNodeMapOutputs(data.Outputs)). + HashSerialize() +} + +type processNodeMapOutputs []*Process_Node_Map_Output + +// HashSerialize returns the hashserialized string of this type +func (data processNodeMapOutputs) HashSerialize() string { + if data == nil || len(data) == 0 { + return "" + } + ser := hashserializer.New() + for i, value := range data { + ser.Add(strconv.Itoa(i), value) + } + return ser.HashSerialize() +} + +// HashSerialize returns the hashserialized string of this type +func (data *Process_Node_Map_Output_Reference) HashSerialize() string { + if data == nil { + return "" + } + return hashserializer.New(). + AddString("1", data.NodeKey). + Add("2", data.Path). + HashSerialize() +} + +// HashSerialize returns the hashserialized string of this type +func (data *Process_Node_Map_Output_Reference_Path) HashSerialize() string { + if data == nil { + return "" + } + return hashserializer.New(). + AddString("1", data.GetKey()). + AddInt("2", int(data.GetIndex())). + Add("3", data.Path). + HashSerialize() +} + +// HashSerialize returns the hashserialized string of this type +func (data *Process_Node_Filter) HashSerialize() string { + if data == nil { + return "" + } + return hashserializer.New(). + Add("2", processNodeFilterConditions(data.Conditions)). + HashSerialize() +} + +type processNodeFilterConditions []Process_Node_Filter_Condition + +// HashSerialize returns the hashserialized string of this type +func (data processNodeFilterConditions) HashSerialize() string { + if data == nil || len(data) == 0 { + return "" + } + ser := hashserializer.New() + for i, value := range data { + ser.Add(strconv.Itoa(i), value) + } + return ser.HashSerialize() +} + +// HashSerialize returns the hashserialized string of this type +func (data Process_Node_Filter_Condition) HashSerialize() string { + return hashserializer.New(). + AddString("1", data.Key). + AddInt("2", int(data.Predicate)). + AddString("3", data.Value). + HashSerialize() +} + +// HashSerialize returns the hashserialized string of this type +func (data *Process_Edge) HashSerialize() string { + if data == nil { + return "" + } + return hashserializer.New(). + AddString("1", data.Src). + AddString("2", data.Dst). + HashSerialize() +} + +type processNodes []*Process_Node + +// HashSerialize returns the hashserialized string of this type +func (data processNodes) HashSerialize() string { + if data == nil || len(data) == 0 { + return "" + } + ser := hashserializer.New() + for i, value := range data { + ser.Add(strconv.Itoa(i), value) + } + return ser.HashSerialize() +} + +type processEdges []*Process_Edge + +// HashSerialize returns the hashserialized string of this type +func (data processEdges) HashSerialize() string { + if data == nil || len(data) == 0 { + return "" + } + ser := hashserializer.New() + for i, value := range data { + ser.Add(strconv.Itoa(i), value) + } + return ser.HashSerialize() +} diff --git a/process/serialize_test.go b/process/serialize_test.go new file mode 100644 index 000000000..9837ab05d --- /dev/null +++ b/process/serialize_test.go @@ -0,0 +1,43 @@ +package process + +import ( + "testing" + + "github.com/mesg-foundation/engine/hash" + "github.com/stretchr/testify/require" +) + +var data = &Process{ + Name: "name", + Nodes: []*Process_Node{ + { + Key: "nodeKey1", + Type: &Process_Node_Event_{ + &Process_Node_Event{ + InstanceHash: hash.Int(5), + EventKey: "eventKey", + }, + }, + }, { + Key: "nodeKey2", + Type: &Process_Node_Task_{&Process_Node_Task{ + InstanceHash: hash.Int(2), + TaskKey: "-", + }}, + }, + }, + Edges: []*Process_Edge{ + {Src: "nodeKey1", Dst: "nodeKey2"}, + }, +} + +func TestHashSerialize(t *testing.T) { + require.Equal(t, "2:name;4:0:1:nodeKey1;3:2:LX3EUdRUBUa3TbsYXLEUdj9J3prXkWXvLYSWyYyc2Jj;3:eventKey;;;1:1:nodeKey2;4:2:8opHzTAnfzRpPEx21XtnrVTX28YQuCpAjcn1PczScKh;3:-;;;;5:0:1:nodeKey1;2:nodeKey2;;;", data.HashSerialize()) + require.Equal(t, "HUQ6EKW3fQPDhpCZu65x5ETQxtSCDKLJk9Qw9PKkLvzg", hash.Dump(data).String()) +} + +func BenchmarkHashSerialize(b *testing.B) { + for i := 0; i < b.N; i++ { + data.HashSerialize() + } +} diff --git a/protobuf/types/serialize.go b/protobuf/types/serialize.go new file mode 100644 index 000000000..9a8b000be --- /dev/null +++ b/protobuf/types/serialize.go @@ -0,0 +1,76 @@ +package types + +import ( + "sort" + "strconv" + + "github.com/mesg-foundation/engine/hash/hashserializer" +) + +// HashSerialize returns the hashserialized string of this type +func (data *Struct) HashSerialize() string { + if data == nil { + return "" + } + return hashserializer.New(). + Add("1", mapValue(data.Fields)). + HashSerialize() +} + +type mapValue map[string]*Value + +// HashSerialize returns the hashserialized string of this type +func (data mapValue) HashSerialize() string { + if data == nil || len(data) == 0 { + return "" + } + keys := make([]string, 0, len(data)) + for k := range data { + keys = append(keys, k) + } + sort.Strings(keys) + ser := hashserializer.New() + for _, key := range keys { + ser.Add(key, data[key]) + } + return ser.HashSerialize() +} + +// HashSerialize returns the hashserialized string of this type +func (data *Value) HashSerialize() string { + if data == nil { + return "" + } + return hashserializer.New(). + AddInt("1", int(data.GetNullValue())). + AddFloat("2", data.GetNumberValue()). + AddString("3", data.GetStringValue()). + AddBool("4", data.GetBoolValue()). + Add("5", data.GetStructValue()). + Add("6", data.GetListValue()). + HashSerialize() +} + +// HashSerialize returns the hashserialized string of this type +func (data *ListValue) HashSerialize() string { + if data == nil { + return "" + } + return hashserializer.New(). + Add("1", values(data.Values)). + HashSerialize() +} + +type values []*Value + +// HashSerialize returns the hashserialized string of this type +func (data values) HashSerialize() string { + if data == nil || len(data) == 0 { + return "" + } + ser := hashserializer.New() + for i, value := range data { + ser.Add(strconv.Itoa(i), value) + } + return ser.HashSerialize() +} diff --git a/protobuf/types/serialize_test.go b/protobuf/types/serialize_test.go new file mode 100644 index 000000000..c73e3c41d --- /dev/null +++ b/protobuf/types/serialize_test.go @@ -0,0 +1,74 @@ +package types + +import ( + "testing" + + "github.com/mesg-foundation/engine/hash" + "github.com/stretchr/testify/require" +) + +var ( + structSort1 = &Struct{ + Fields: map[string]*Value{ + "10000": {Kind: &Value_NumberValue{NumberValue: 10}}, + "1": {Kind: &Value_StringValue{StringValue: "value"}}, + "null": {Kind: &Value_NullValue{NullValue: NullValue_NULL_VALUE}}, + "number": {Kind: &Value_NumberValue{NumberValue: 10}}, + "string": {Kind: &Value_StringValue{StringValue: "value"}}, + "bool": {Kind: &Value_BoolValue{BoolValue: true}}, + "struct": {Kind: &Value_StructValue{StructValue: &Struct{Fields: map[string]*Value{ + "10000": {Kind: &Value_NumberValue{NumberValue: 10}}, + "1": {Kind: &Value_StringValue{StringValue: "value"}}, + "null": {Kind: &Value_NullValue{NullValue: NullValue_NULL_VALUE}}, + "number": {Kind: &Value_NumberValue{NumberValue: 10}}, + "struct": {Kind: &Value_StructValue{StructValue: &Struct{Fields: map[string]*Value{ + "10000": {Kind: &Value_NumberValue{NumberValue: 10}}, + "1": {Kind: &Value_StringValue{StringValue: "value"}}, + "null": {Kind: &Value_NullValue{NullValue: NullValue_NULL_VALUE}}, + "number": {Kind: &Value_NumberValue{NumberValue: 10}}, + }}}}, + }}}}, + "list": {Kind: &Value_ListValue{ListValue: &ListValue{Values: []*Value{ + {Kind: &Value_StringValue{StringValue: "value"}}, + }}}}, + }, + } + structSort2 = &Struct{ + Fields: map[string]*Value{ + "1": {Kind: &Value_StringValue{StringValue: "value"}}, + "struct": {Kind: &Value_StructValue{StructValue: &Struct{Fields: map[string]*Value{ + "1": {Kind: &Value_StringValue{StringValue: "value"}}, + "null": {Kind: &Value_NullValue{NullValue: NullValue_NULL_VALUE}}, + "struct": {Kind: &Value_StructValue{StructValue: &Struct{Fields: map[string]*Value{ + "number": {Kind: &Value_NumberValue{NumberValue: 10}}, + "10000": {Kind: &Value_NumberValue{NumberValue: 10}}, + "1": {Kind: &Value_StringValue{StringValue: "value"}}, + "null": {Kind: &Value_NullValue{NullValue: NullValue_NULL_VALUE}}, + }}}}, + "number": {Kind: &Value_NumberValue{NumberValue: 10}}, + "10000": {Kind: &Value_NumberValue{NumberValue: 10}}, + }}}}, + "10000": {Kind: &Value_NumberValue{NumberValue: 10}}, + "number": {Kind: &Value_NumberValue{NumberValue: 10}}, + "null": {Kind: &Value_NullValue{NullValue: NullValue_NULL_VALUE}}, + "bool": {Kind: &Value_BoolValue{BoolValue: true}}, + "string": {Kind: &Value_StringValue{StringValue: "value"}}, + "list": {Kind: &Value_ListValue{ListValue: &ListValue{Values: []*Value{ + {Kind: &Value_StringValue{StringValue: "value"}}, + }}}}, + }, + } +) + +func TestHashSerialize(t *testing.T) { + require.Equal(t, "1:1:3:value;;10000:2:10;;bool:4:true;;list:6:1:0:3:value;;;;;number:2:10;;string:3:value;;struct:5:1:1:3:value;;10000:2:10;;number:2:10;;struct:5:1:1:3:value;;10000:2:10;;number:2:10;;;;;;;;;", structSort1.HashSerialize()) + require.Equal(t, "GbNibbLgXTJsUVKgkCkiw5AYMVmD7JawqFHbtdkeZqka", hash.Dump(structSort1).String()) + require.Equal(t, structSort1.HashSerialize(), structSort2.HashSerialize()) + require.Equal(t, hash.Dump(structSort1).String(), hash.Dump(structSort2).String()) +} + +func BenchmarkHashSerialize(b *testing.B) { + for i := 0; i < b.N; i++ { + structSort1.HashSerialize() + } +} diff --git a/protobuf/types/service.proto b/protobuf/types/service.proto index 92e4959b1..ad3dbfcff 100644 --- a/protobuf/types/service.proto +++ b/protobuf/types/service.proto @@ -120,12 +120,12 @@ message Service { // Args to pass to the container. repeated string args = 4 [ - (gogoproto.moretags) = 'hash:"name:5" validate:"dive,printascii"' + (gogoproto.moretags) = 'hash:"name:4" validate:"dive,printascii"' ]; // Command to run the container. string command = 5 [ - (gogoproto.moretags) = 'hash:"name:4" validate:"printascii"' + (gogoproto.moretags) = 'hash:"name:5" validate:"printascii"' ]; // Default env vars to apply to service's instance on runtime. diff --git a/protobuf/types/struct_test.go b/protobuf/types/struct_test.go index 2a0637127..b60ed9d05 100644 --- a/protobuf/types/struct_test.go +++ b/protobuf/types/struct_test.go @@ -10,19 +10,17 @@ import ( func TestStructHash(t *testing.T) { hashes := make(map[string]bool) - structs := []Struct{ - {Fields: map[string]*Value{}}, - {Fields: map[string]*Value{"v": {Kind: &Value_NullValue{}}}}, - {Fields: map[string]*Value{"v": {Kind: &Value_NumberValue{}}}}, - {Fields: map[string]*Value{"v": {Kind: &Value_StringValue{}}}}, - {Fields: map[string]*Value{"v": {Kind: &Value_BoolValue{}}}}, - {Fields: map[string]*Value{"v": {Kind: &Value_StructValue{}}}}, - {Fields: map[string]*Value{"v": {Kind: &Value_ListValue{}}}}, + structs := []*Struct{ + {Fields: map[string]*Value{"v": {Kind: &Value_NumberValue{NumberValue: 1}}}}, + {Fields: map[string]*Value{"v": {Kind: &Value_StringValue{StringValue: "1"}}}}, + {Fields: map[string]*Value{"v": {Kind: &Value_BoolValue{BoolValue: true}}}}, + {Fields: map[string]*Value{"v": {Kind: &Value_StructValue{StructValue: &Struct{Fields: map[string]*Value{"1": {}}}}}}}, + {Fields: map[string]*Value{"v": {Kind: &Value_ListValue{ListValue: &ListValue{Values: []*Value{{Kind: &Value_NumberValue{NumberValue: 1}}}}}}}}, } for _, s := range structs { - hashes[string(hash.Dump(s))] = true + hashes[hash.Dump(s).String()] = true } - require.Equal(t, len(hashes), len(structs)) + require.Equal(t, len(structs), len(hashes)) } func TestStructMarshal(t *testing.T) { @@ -93,7 +91,7 @@ func TestStructMarshal(t *testing.T) { require.NoError(t, err) structValueSort2, err = cdc.MarshalBinaryLengthPrefixed(structSort2) require.NoError(t, err) - require.True(t, hash.Dump(structValueSort1).Equal(hash.Dump(structValueSort2))) + require.True(t, hash.Sum(structValueSort1).Equal(hash.Sum(structValueSort2))) }) t.Run("Unmarshal", func(t *testing.T) { require.NoError(t, cdc.UnmarshalBinaryLengthPrefixed(structValueSort1, &structUnm1)) @@ -116,7 +114,7 @@ func TestStructMarshal(t *testing.T) { require.NoError(t, err) structValueSort2, err = cdc.MarshalJSON(structSort2) require.NoError(t, err) - require.True(t, hash.Dump(structValueSort1).Equal(hash.Dump(structValueSort2))) + require.True(t, hash.Sum(structValueSort1).Equal(hash.Sum(structValueSort2))) }) t.Run("Unmarshal", func(t *testing.T) { require.NoError(t, cdc.UnmarshalJSON(structValueSort1, &structUnm1)) diff --git a/runner/builder/builder.go b/runner/builder/builder.go index b5022f147..4003cc768 100644 --- a/runner/builder/builder.go +++ b/runner/builder/builder.go @@ -8,6 +8,7 @@ import ( "github.com/mesg-foundation/engine/cosmos" "github.com/mesg-foundation/engine/ext/xos" "github.com/mesg-foundation/engine/hash" + "github.com/mesg-foundation/engine/hash/hashserializer" instancepb "github.com/mesg-foundation/engine/instance" "github.com/mesg-foundation/engine/protobuf/api" runnerpb "github.com/mesg-foundation/engine/runner" @@ -44,7 +45,7 @@ func (b *Builder) Create(req *api.CreateRunnerRequest) (*runnerpb.Runner, error) } instanceEnv := xos.EnvMergeSlices(srv.Configuration.Env, req.Env) - envHash := hash.Dump(instanceEnv) + envHash := hash.Dump(hashserializer.StringSlice(instanceEnv)) // TODO: should be done by instance or runner instanceHash := hash.Dump(&instancepb.Instance{ ServiceHash: srv.Hash, diff --git a/runner/builder/container.go b/runner/builder/container.go index 459339a24..fabe272ca 100644 --- a/runner/builder/container.go +++ b/runner/builder/container.go @@ -16,6 +16,7 @@ import ( "github.com/mesg-foundation/engine/ext/xerrors" "github.com/mesg-foundation/engine/ext/xos" "github.com/mesg-foundation/engine/hash" + "github.com/mesg-foundation/engine/hash/hashserializer" "github.com/mesg-foundation/engine/service" ) @@ -209,7 +210,7 @@ func namespace(hash hash.Hash) string { // dependencyNamespace builds the namespace of a dependency. func dependencyNamespace(namespace string, dependencyKey string) string { - return hash.Dump(namespace + dependencyKey).String() + return hash.Sum([]byte(namespace + dependencyKey)).String() } func convertPorts(dPorts []string) []container.Port { @@ -267,9 +268,9 @@ func convertVolumesFrom(s *service.Service, dVolumesFrom []string) ([]container. // volumeKey creates a key for service's volume based on the sid to make sure that the volume // will stay the same for different versions of the service. func volumeKey(s *service.Service, dependency, volume string) string { - return hash.Dump([]string{ + return hash.Dump(hashserializer.StringSlice([]string{ s.Sid, dependency, volume, - }).String() + })).String() } diff --git a/runner/serialize.go b/runner/serialize.go new file mode 100644 index 000000000..6cde0253d --- /dev/null +++ b/runner/serialize.go @@ -0,0 +1,14 @@ +package runner + +import "github.com/mesg-foundation/engine/hash/hashserializer" + +// HashSerialize returns the hashserialized string of this type +func (data *Runner) HashSerialize() string { + if data == nil { + return "" + } + return hashserializer.New(). + AddString("2", data.Address). + AddString("3", data.InstanceHash.String()). + HashSerialize() +} diff --git a/runner/serialize_test.go b/runner/serialize_test.go new file mode 100644 index 000000000..887768aa1 --- /dev/null +++ b/runner/serialize_test.go @@ -0,0 +1,24 @@ +package runner + +import ( + "testing" + + "github.com/mesg-foundation/engine/hash" + "github.com/stretchr/testify/require" +) + +var data = &Runner{ + Address: "hello", + InstanceHash: hash.Int(10), +} + +func TestHashSerialize(t *testing.T) { + require.Equal(t, "2:hello;3:g35TxFqwMx95vCk63fTxGTHb6ei4W24qg5t2x6xD3cT;", data.HashSerialize()) + require.Equal(t, "8kS6ayPnvzjqNFTY9RYyEwhD3D4HMq9zZnDg7iFcUQYi", hash.Dump(data).String()) +} + +func BenchmarkHashSerialize(b *testing.B) { + for i := 0; i < b.N; i++ { + data.HashSerialize() + } +} diff --git a/service/serialize.go b/service/serialize.go new file mode 100644 index 000000000..cf5627fa4 --- /dev/null +++ b/service/serialize.go @@ -0,0 +1,153 @@ +package service + +import ( + "strconv" + + "github.com/mesg-foundation/engine/hash/hashserializer" +) + +// HashSerialize returns the hashserialized string of this type +func (data *Service) HashSerialize() string { + if data == nil { + return "" + } + return hashserializer.New(). + AddString("1", data.Name). + AddString("2", data.Description). + Add("5", serviceTasks(data.Tasks)). + Add("6", serviceEvents(data.Events)). + Add("7", serviceDependencies(data.Dependencies)). + Add("8", data.Configuration). + AddString("9", data.Repository). + AddString("12", data.Sid). + AddString("13", data.Source). + HashSerialize() +} + +// HashSerialize returns the hashserialized string of this type +func (data Service_Configuration) HashSerialize() string { + return hashserializer.New(). + AddStringSlice("1", data.Volumes). + AddStringSlice("2", data.VolumesFrom). + AddStringSlice("3", data.Ports). + AddStringSlice("4", data.Args). + AddString("5", data.Command). + AddStringSlice("6", data.Env). + HashSerialize() +} + +// HashSerialize returns the hashserialized string of this type +func (data *Service_Task) HashSerialize() string { + if data == nil { + return "" + } + return hashserializer.New(). + AddString("1", data.Name). + AddString("2", data.Description). + Add("6", serviceParameters(data.Inputs)). + Add("7", serviceParameters(data.Outputs)). + AddString("8", data.Key). + HashSerialize() +} + +// HashSerialize returns the hashserialized string of this type +func (data *Service_Parameter) HashSerialize() string { + if data == nil { + return "" + } + return hashserializer.New(). + AddString("1", data.Name). + AddString("2", data.Description). + AddString("3", data.Type). + AddBool("4", data.Optional). + AddString("8", data.Key). + AddBool("9", data.Repeated). + Add("10", serviceParameters(data.Object)). + HashSerialize() +} + +// HashSerialize returns the hashserialized string of this type +func (data *Service_Event) HashSerialize() string { + if data == nil { + return "" + } + return hashserializer.New(). + AddString("1", data.Name). + AddString("2", data.Description). + Add("3", serviceParameters(data.Data)). + AddString("4", data.Key). + HashSerialize() +} + +// HashSerialize returns the hashserialized string of this type +func (data *Service_Dependency) HashSerialize() string { + if data == nil { + return "" + } + return hashserializer.New(). + AddString("1", data.Image). + AddStringSlice("2", data.Volumes). + AddStringSlice("3", data.VolumesFrom). + AddStringSlice("4", data.Ports). + AddString("5", data.Command). + AddStringSlice("6", data.Args). + AddString("8", data.Key). + AddStringSlice("9", data.Env). + HashSerialize() +} + +type serviceTasks []*Service_Task + +// HashSerialize returns the hashserialized string of this type +func (data serviceTasks) HashSerialize() string { + if data == nil || len(data) == 0 { + return "" + } + ser := hashserializer.New() + for i, value := range data { + ser.Add(strconv.Itoa(i), value) + } + return ser.HashSerialize() +} + +type serviceParameters []*Service_Parameter + +// HashSerialize returns the hashserialized string of this type +func (data serviceParameters) HashSerialize() string { + if data == nil || len(data) == 0 { + return "" + } + ser := hashserializer.New() + for i, value := range data { + ser.Add(strconv.Itoa(i), value) + } + return ser.HashSerialize() +} + +type serviceEvents []*Service_Event + +// HashSerialize returns the hashserialized string of this type +func (data serviceEvents) HashSerialize() string { + if data == nil || len(data) == 0 { + return "" + } + ser := hashserializer.New() + for i, value := range data { + ser.Add(strconv.Itoa(i), value) + } + return ser.HashSerialize() +} + +type serviceDependencies []*Service_Dependency + +// HashSerialize returns the hashserialized string of this type +func (data serviceDependencies) HashSerialize() string { + if data == nil || len(data) == 0 { + return "" + } + ser := hashserializer.New() + for i, value := range data { + ser.Add(strconv.Itoa(i), value) + } + return ser.HashSerialize() +} diff --git a/service/serialize_test.go b/service/serialize_test.go new file mode 100644 index 000000000..87b33224b --- /dev/null +++ b/service/serialize_test.go @@ -0,0 +1,45 @@ +package service + +import ( + "testing" + + "github.com/mesg-foundation/engine/hash" + "github.com/stretchr/testify/require" +) + +var sp = []*Service_Parameter{ + { + Key: "key", + Type: "Number", + Optional: true, + }, +} + +var s = &Service{ + Sid: "hello", + Name: "world", + Events: []*Service_Event{ + { + Key: "event", + Data: sp, + }, + }, + Tasks: []*Service_Task{ + { + Key: "task", + Inputs: sp, + Outputs: sp, + }, + }, +} + +func TestHashSerialize(t *testing.T) { + require.Equal(t, "1:world;5:0:6:0:3:Number;4:true;8:key;;;7:0:3:Number;4:true;8:key;;;8:task;;;6:0:3:0:3:Number;4:true;8:key;;;4:event;;;12:hello;", s.HashSerialize()) + require.Equal(t, "5Hwubvgm5eDFJyXXpEEnTcYNFR7Jppmq94vmQb7oxXfX", hash.Dump(s).String()) +} + +func BenchmarkHashSerialize(b *testing.B) { + for i := 0; i < b.N; i++ { + s.HashSerialize() + } +} diff --git a/service/service.pb.go b/service/service.pb.go index 7436853bf..136b30671 100644 --- a/service/service.pb.go +++ b/service/service.pb.go @@ -208,9 +208,9 @@ type Service_Configuration struct { // List of ports the container exposes. Ports []string `protobuf:"bytes,3,rep,name=ports,proto3" json:"ports,omitempty" hash:"name:3" validate:"unique,dive,portmap"` // Args to pass to the container. - Args []string `protobuf:"bytes,4,rep,name=args,proto3" json:"args,omitempty" hash:"name:5" validate:"dive,printascii"` + Args []string `protobuf:"bytes,4,rep,name=args,proto3" json:"args,omitempty" hash:"name:4" validate:"dive,printascii"` // Command to run the container. - Command string `protobuf:"bytes,5,opt,name=command,proto3" json:"command,omitempty" hash:"name:4" validate:"printascii"` + Command string `protobuf:"bytes,5,opt,name=command,proto3" json:"command,omitempty" hash:"name:5" validate:"printascii"` // Default env vars to apply to service's instance on runtime. Env []string `protobuf:"bytes,6,rep,name=env,proto3" json:"env,omitempty" hash:"name:6" validate:"unique,dive,env"` XXX_NoUnkeyedLiteral struct{} `json:"-"` @@ -301,67 +301,66 @@ func init() { func init() { proto.RegisterFile("service.proto", fileDescriptor_a0b84a42fa06f626) } var fileDescriptor_a0b84a42fa06f626 = []byte{ - // 945 bytes of a gzipped FileDescriptorProto + // 944 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x97, 0xdf, 0x72, 0xdb, 0x44, 0x14, 0xc6, 0xeb, 0x48, 0xb6, 0xe3, 0x93, 0xf8, 0x82, 0xbd, 0x5a, 0x3c, 0x10, 0xa9, 0x62, 0x06, 0x5c, 0x70, 0xec, 0xc6, 0x6e, 0x43, 0x93, 0x4e, 0x80, 0x9a, 0xd2, 0xa1, 0xcc, 0xf0, 0xcf, 0x81, 0x61, 0xa6, 0x33, 0xbd, 0x58, 0x4b, 0x1b, 0x65, 0x49, 0xb4, 0xab, 0xae, 0x56, 0x1e, 0x7c, 0xcf, 0x0b, 0xf0, 0x16, 0xf0, 0x08, 0xdc, 0x72, 0xd5, 0x07, 0xe0, 0x8a, 0x0b, 0xcd, 0xc0, 0x23, 0xe8, - 0x09, 0x18, 0xad, 0xed, 0x44, 0x8e, 0x65, 0xe7, 0x4f, 0xb9, 0xa1, 0x77, 0x56, 0xe6, 0x7c, 0xbf, - 0x4f, 0x3a, 0xe7, 0x3b, 0xda, 0x08, 0xea, 0x11, 0x95, 0x23, 0xe6, 0xd2, 0x76, 0x28, 0x85, 0x12, - 0x08, 0x02, 0x1a, 0xf9, 0x6d, 0x35, 0x0e, 0x69, 0xd4, 0x70, 0x7c, 0xe1, 0x8b, 0x8e, 0xfe, 0xfb, - 0x30, 0x3e, 0xea, 0x64, 0x57, 0xfa, 0x42, 0xff, 0x9a, 0xd4, 0x3b, 0x7f, 0x62, 0xa8, 0x1e, 0x4e, - 0x08, 0xc8, 0x07, 0xf3, 0x98, 0x44, 0xc7, 0x18, 0xec, 0x52, 0x73, 0xb3, 0x7f, 0xf8, 0x32, 0xb1, - 0x6e, 0xfd, 0x95, 0x58, 0x1f, 0xf8, 0x4c, 0x1d, 0xc7, 0xc3, 0xb6, 0x2b, 0x82, 0x4e, 0x06, 0xdf, - 0x3e, 0x12, 0x31, 0xf7, 0x88, 0x62, 0x82, 0x77, 0x28, 0xf7, 0x19, 0xa7, 0x9d, 0x4c, 0xd5, 0xfe, - 0x9c, 0x44, 0xc7, 0x69, 0x62, 0xbd, 0x95, 0x5d, 0xec, 0x3b, 0xdb, 0x8e, 0x3d, 0x22, 0xa7, 0xcc, - 0x23, 0x8a, 0xee, 0x3b, 0x92, 0xbe, 0x88, 0x99, 0xa4, 0x9e, 0x33, 0xd0, 0x06, 0xe8, 0x5b, 0x30, - 0x22, 0xe6, 0xe1, 0x4d, 0xbb, 0xd4, 0xac, 0xf5, 0x3f, 0x4e, 0x13, 0xeb, 0xe1, 0x44, 0xc4, 0x49, - 0x40, 0xf7, 0x77, 0xba, 0x45, 0xd2, 0x56, 0x28, 0x19, 0x57, 0x24, 0x72, 0x19, 0x6b, 0x05, 0xe4, - 0xa7, 0x83, 0xdd, 0x5e, 0xcb, 0x13, 0x01, 0x61, 0xdc, 0x19, 0x64, 0x2c, 0xf4, 0x18, 0xcc, 0x4c, - 0x8d, 0x4b, 0x9a, 0x79, 0x37, 0x4d, 0xac, 0x56, 0x9e, 0x79, 0x09, 0xd2, 0x19, 0x68, 0x35, 0x7a, - 0x0a, 0x1b, 0x1e, 0x8d, 0x5c, 0xc9, 0xc2, 0xec, 0xf1, 0xf0, 0x9a, 0x86, 0xbd, 0x97, 0x26, 0xd6, - 0x3b, 0x39, 0xd8, 0xdc, 0xfd, 0xe5, 0x19, 0x79, 0x2d, 0x92, 0x50, 0x77, 0x05, 0x3f, 0x62, 0x7e, + 0x09, 0x18, 0xad, 0xed, 0x44, 0x8e, 0x65, 0x27, 0x69, 0xe0, 0x02, 0xee, 0xac, 0xcc, 0xf9, 0x7e, + 0xdf, 0xea, 0x9c, 0xef, 0x48, 0x11, 0xd4, 0x23, 0x2a, 0x47, 0xcc, 0xa5, 0xed, 0x50, 0x0a, 0x25, + 0x10, 0x04, 0x34, 0xf2, 0xdb, 0x6a, 0x1c, 0xd2, 0xa8, 0xe1, 0xf8, 0xc2, 0x17, 0x1d, 0xfd, 0xf7, + 0x61, 0x7c, 0xd4, 0xc9, 0xae, 0xf4, 0x85, 0xfe, 0x35, 0xa9, 0x77, 0x7e, 0xc7, 0x50, 0x3d, 0x9c, + 0x10, 0x90, 0x0f, 0xe6, 0x31, 0x89, 0x8e, 0x31, 0xd8, 0xa5, 0xe6, 0x66, 0xff, 0xf0, 0x65, 0x62, + 0xdd, 0xfa, 0x23, 0xb1, 0xde, 0xf3, 0x99, 0x3a, 0x8e, 0x87, 0x6d, 0x57, 0x04, 0x9d, 0x0c, 0xbe, + 0x7d, 0x24, 0x62, 0xee, 0x11, 0xc5, 0x04, 0xef, 0x50, 0xee, 0x33, 0x4e, 0x3b, 0x99, 0xaa, 0xfd, + 0x29, 0x89, 0x8e, 0xd3, 0xc4, 0x7a, 0x23, 0xbb, 0xd8, 0x77, 0xb6, 0x1d, 0x7b, 0x44, 0x4e, 0x99, + 0x47, 0x14, 0xdd, 0x77, 0x24, 0x7d, 0x11, 0x33, 0x49, 0x3d, 0x67, 0xa0, 0x0d, 0xd0, 0xd7, 0x60, + 0x44, 0xcc, 0xc3, 0x9b, 0x76, 0xa9, 0x59, 0xeb, 0x7f, 0x98, 0x26, 0xd6, 0xc3, 0x89, 0x88, 0x93, + 0x80, 0xee, 0xef, 0x74, 0x8b, 0xa4, 0xad, 0x50, 0x32, 0xae, 0x48, 0xe4, 0x32, 0xd6, 0x0a, 0xc8, + 0x0f, 0x07, 0xbb, 0xbd, 0x96, 0x27, 0x02, 0xc2, 0xb8, 0x33, 0xc8, 0x58, 0xe8, 0x31, 0x98, 0x99, + 0x1a, 0x97, 0x34, 0xf3, 0x6e, 0x9a, 0x58, 0xad, 0x3c, 0xf3, 0x12, 0xa4, 0x33, 0xd0, 0x6a, 0xf4, + 0x14, 0x36, 0x3c, 0x1a, 0xb9, 0x92, 0x85, 0xd9, 0xed, 0xe1, 0x35, 0x0d, 0x7b, 0x27, 0x4d, 0xac, + 0xb7, 0x72, 0xb0, 0xb9, 0xf3, 0xe5, 0x19, 0x79, 0x2d, 0x92, 0x50, 0x77, 0x05, 0x3f, 0x62, 0x7e, 0x2c, 0x75, 0xaf, 0xf0, 0xba, 0x5d, 0x6a, 0x6e, 0x74, 0x6f, 0xb7, 0xcf, 0x07, 0xd4, 0x9e, 0x36, - 0xbe, 0xfd, 0x69, 0xbe, 0xb0, 0x7f, 0x27, 0x6b, 0x7c, 0x9a, 0x58, 0xb7, 0x73, 0x9e, 0x0f, 0x8a, + 0xbe, 0xfd, 0x71, 0xbe, 0xb0, 0x7f, 0x27, 0x6b, 0x7c, 0x9a, 0x58, 0xb7, 0x73, 0x9e, 0x0f, 0x8a, 0xdb, 0x39, 0x6f, 0x81, 0x9e, 0x41, 0x59, 0x91, 0xe8, 0x24, 0xc2, 0x65, 0xdb, 0x68, 0x6e, 0x74, - 0x71, 0x91, 0xd7, 0x77, 0x24, 0x3a, 0xe9, 0xbf, 0x9f, 0x26, 0xd6, 0xbb, 0x39, 0xfc, 0xfd, 0x3c, + 0x71, 0x91, 0xd7, 0x37, 0x24, 0x3a, 0xe9, 0xbf, 0x9b, 0x26, 0xd6, 0xdb, 0x39, 0xfc, 0xfd, 0x3c, 0xde, 0x63, 0x23, 0xda, 0x3a, 0xf7, 0x98, 0x20, 0xd1, 0x73, 0xa8, 0xd0, 0x11, 0xe5, 0x2a, 0xc2, - 0x15, 0x0d, 0x7f, 0xb3, 0x08, 0xfe, 0x59, 0x56, 0xb1, 0x40, 0xdf, 0x5d, 0x41, 0x9f, 0x42, 0x11, + 0x15, 0x0d, 0x7f, 0xbd, 0x08, 0xfe, 0x49, 0x56, 0xb1, 0x40, 0xdf, 0x5d, 0x41, 0x9f, 0x42, 0x11, 0x87, 0x4d, 0x8f, 0x86, 0x94, 0x7b, 0x94, 0xbb, 0x8c, 0x46, 0xb8, 0xaa, 0x4d, 0xb6, 0x8a, 0x4c, - 0x1e, 0xcf, 0xea, 0xc6, 0x0b, 0x4e, 0x1f, 0xae, 0x70, 0x9a, 0xe3, 0xa3, 0x2f, 0x00, 0x24, 0x0d, + 0x1e, 0xcf, 0xea, 0xc6, 0x0b, 0x4e, 0xef, 0xaf, 0x70, 0x9a, 0xe3, 0xa3, 0xcf, 0x00, 0x24, 0x0d, 0x45, 0xc4, 0x94, 0x90, 0x63, 0x5c, 0xd3, 0x83, 0xbe, 0x48, 0xdb, 0xcb, 0xd3, 0x44, 0xc0, 0x14, 0x0d, 0x42, 0x35, 0x6e, 0xc5, 0x92, 0x39, 0x83, 0x9c, 0x1a, 0x3d, 0x85, 0x4a, 0x24, 0x62, 0xe9, 0x52, 0x5c, 0xd7, 0x9c, 0x9d, 0x34, 0xb1, 0xb6, 0xf3, 0xe9, 0xeb, 0x5d, 0x1a, 0xbf, 0x29, 0xa0, - 0xf1, 0xdb, 0x1a, 0x94, 0x75, 0x13, 0xd1, 0x1e, 0x18, 0x27, 0x74, 0x8c, 0xcd, 0xc2, 0x08, 0xde, - 0x5b, 0x16, 0xc1, 0x4c, 0x83, 0x1e, 0xce, 0xed, 0xc2, 0x45, 0xed, 0xce, 0x32, 0xed, 0x7f, 0xbe, - 0x02, 0xcf, 0xc1, 0xf4, 0x88, 0x22, 0xd8, 0xd0, 0xb3, 0x7c, 0xbb, 0x68, 0x96, 0xdf, 0x10, 0x49, - 0x02, 0xaa, 0xa8, 0x5c, 0x68, 0x7e, 0x6f, 0xc5, 0x28, 0x35, 0xb6, 0xf1, 0x8b, 0x01, 0x66, 0x96, - 0xe6, 0x59, 0xab, 0xd6, 0x0b, 0x6f, 0xf5, 0xc1, 0xff, 0xa2, 0x55, 0x04, 0x2a, 0x8c, 0x87, 0xf1, - 0xd9, 0x76, 0x5d, 0xb3, 0x59, 0x2b, 0x37, 0x6c, 0x02, 0x46, 0x2e, 0x54, 0x45, 0xac, 0xb4, 0x47, - 0xf5, 0x26, 0x1e, 0xab, 0x76, 0x6b, 0x46, 0x6e, 0xfc, 0x6c, 0x42, 0xed, 0x0c, 0xf1, 0x3a, 0x0c, - 0xe6, 0x04, 0xcc, 0xac, 0x41, 0xd8, 0xd0, 0x8c, 0x1f, 0xd2, 0xc4, 0x3a, 0x5c, 0x16, 0xd2, 0xa2, - 0xa3, 0x4a, 0x70, 0x2a, 0x8e, 0x0e, 0x0e, 0x95, 0x64, 0xdc, 0xb7, 0xbf, 0x8a, 0x83, 0x21, 0x95, - 0x76, 0x5f, 0x88, 0x53, 0x4a, 0xb8, 0xfd, 0xf5, 0xf0, 0x47, 0xea, 0x2a, 0xfb, 0x11, 0x1f, 0x3b, - 0x03, 0x6d, 0x82, 0xb6, 0x61, 0x5d, 0x68, 0x5b, 0x72, 0xaa, 0x17, 0x7f, 0xbd, 0xff, 0x46, 0x9a, - 0x58, 0xf5, 0xb9, 0xc5, 0x1f, 0x9c, 0x95, 0x64, 0xe5, 0x92, 0x86, 0x94, 0x28, 0xea, 0xe9, 0x37, - 0xd8, 0x62, 0xf9, 0x9e, 0x33, 0x38, 0x2b, 0x41, 0x0c, 0x2a, 0x42, 0x5b, 0x62, 0xb8, 0xca, 0xfc, - 0xbb, 0x69, 0x62, 0xb5, 0xf3, 0x3d, 0xbf, 0x9b, 0x7f, 0xd8, 0x98, 0xb3, 0x17, 0x31, 0x6d, 0x5d, - 0xcc, 0xda, 0xc4, 0xa0, 0xf1, 0x87, 0x01, 0xf5, 0xb9, 0x43, 0x0d, 0x7d, 0x09, 0xd5, 0x91, 0x38, - 0x8d, 0x03, 0x1a, 0xe1, 0x92, 0x6d, 0x34, 0x6b, 0xfd, 0x5e, 0x9a, 0x58, 0x9d, 0x65, 0x23, 0xcd, - 0xd3, 0xf3, 0xa3, 0x99, 0x31, 0xd0, 0xf7, 0xb0, 0x31, 0xfd, 0xf9, 0x44, 0x8a, 0x00, 0xaf, 0x15, - 0x22, 0xbb, 0x57, 0x41, 0xe6, 0x39, 0xe8, 0x09, 0x94, 0x43, 0x21, 0x55, 0xa4, 0x5f, 0x59, 0x8b, - 0xff, 0x46, 0xf4, 0x96, 0x02, 0x85, 0x54, 0x01, 0x09, 0x9d, 0xc1, 0x44, 0x8e, 0x3e, 0x01, 0x93, - 0x48, 0x3f, 0xc2, 0xa6, 0xc6, 0xb4, 0xd2, 0xc4, 0x6a, 0xae, 0x3c, 0x6d, 0xe7, 0x22, 0x9c, 0x29, - 0xd1, 0x23, 0xa8, 0xba, 0x22, 0x08, 0x08, 0xf7, 0x70, 0xf9, 0x7a, 0x47, 0xc0, 0x4c, 0x87, 0x3e, - 0x02, 0x83, 0xf2, 0x91, 0x7e, 0xa1, 0x2c, 0xde, 0xc3, 0xee, 0xb2, 0x47, 0xa1, 0x7c, 0xe4, 0x0c, - 0x32, 0x61, 0xe3, 0x77, 0x13, 0xe0, 0xfc, 0xac, 0x7d, 0x95, 0x65, 0x3e, 0x80, 0x32, 0x0b, 0x88, - 0x7f, 0xed, 0x6d, 0x9e, 0xa8, 0xf2, 0xd9, 0x79, 0x85, 0x41, 0x2f, 0xcb, 0x8e, 0x51, 0x88, 0xec, - 0xdd, 0x3c, 0x3b, 0x66, 0x61, 0x76, 0xee, 0x5d, 0x37, 0x3b, 0x57, 0x98, 0xdb, 0x4d, 0xb3, 0x73, - 0xff, 0xaa, 0xd9, 0xa9, 0x15, 0xde, 0xc3, 0xde, 0xa5, 0xd9, 0xe9, 0xf7, 0x5e, 0xfe, 0xbd, 0x75, - 0xeb, 0xd7, 0x7f, 0xb6, 0x4a, 0xcf, 0xee, 0x5c, 0xfe, 0xf9, 0x30, 0xfd, 0x82, 0x19, 0x56, 0xf4, - 0x27, 0x49, 0xef, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x41, 0xba, 0xa3, 0x55, 0xd3, 0x0c, 0x00, - 0x00, + 0xf1, 0xcb, 0x1a, 0x94, 0x75, 0x13, 0xd1, 0x1e, 0x18, 0x27, 0x74, 0x8c, 0xcd, 0xc2, 0x08, 0xde, + 0x5b, 0x16, 0xc1, 0x4c, 0x83, 0x1e, 0xce, 0xed, 0xc2, 0x45, 0xed, 0xce, 0x32, 0xed, 0x3f, 0xbe, + 0x02, 0xcf, 0xc1, 0xf4, 0x88, 0x22, 0xd8, 0xd0, 0xb3, 0x7c, 0xb3, 0x68, 0x96, 0x5f, 0x11, 0x49, + 0x02, 0xaa, 0xa8, 0x5c, 0x68, 0x7e, 0x6f, 0xc5, 0x28, 0x35, 0xb6, 0xf1, 0x93, 0x01, 0x66, 0x96, + 0xe6, 0x59, 0xab, 0xd6, 0x0b, 0x8f, 0xfa, 0xe0, 0x3f, 0xd1, 0x2a, 0x02, 0x15, 0xc6, 0xc3, 0xf8, + 0x6c, 0xbb, 0xae, 0xd9, 0xac, 0x95, 0x1b, 0x36, 0x01, 0x23, 0x17, 0xaa, 0x22, 0x56, 0xda, 0xa3, + 0xfa, 0x2a, 0x1e, 0xab, 0x76, 0x6b, 0x46, 0x6e, 0xfc, 0x68, 0x42, 0xed, 0x0c, 0xf1, 0x7f, 0x18, + 0xcc, 0x09, 0x98, 0x59, 0x83, 0xb0, 0xa1, 0x19, 0xdf, 0xa5, 0x89, 0x75, 0xb8, 0x2c, 0xa4, 0x45, + 0xaf, 0x2a, 0xc1, 0xa9, 0x38, 0x3a, 0x38, 0x54, 0x92, 0x71, 0xdf, 0xfe, 0x22, 0x0e, 0x86, 0x54, + 0xda, 0x7d, 0x21, 0x4e, 0x29, 0xe1, 0xf6, 0x97, 0xc3, 0xef, 0xa9, 0xab, 0xec, 0x47, 0x7c, 0xec, + 0x0c, 0xb4, 0x09, 0xda, 0x86, 0x75, 0xa1, 0x6d, 0xc9, 0xa9, 0x5e, 0xfc, 0xf5, 0xfe, 0x6b, 0x69, + 0x62, 0xd5, 0xe7, 0x16, 0x7f, 0x70, 0x56, 0x92, 0x95, 0x4b, 0x1a, 0x52, 0xa2, 0xa8, 0xa7, 0x9f, + 0x60, 0x8b, 0xe5, 0x7b, 0xce, 0xe0, 0xac, 0x04, 0x31, 0xa8, 0x08, 0x6d, 0x89, 0xe1, 0x2a, 0xf3, + 0xef, 0xa6, 0x89, 0xd5, 0xce, 0xf7, 0xfc, 0x6e, 0xfe, 0x66, 0x63, 0xce, 0x5e, 0xc4, 0xb4, 0x75, + 0x31, 0x6b, 0x13, 0x83, 0xc6, 0x6f, 0x06, 0xd4, 0xe7, 0x5e, 0x6a, 0xe8, 0x73, 0xa8, 0x8e, 0xc4, + 0x69, 0x1c, 0xd0, 0x08, 0x97, 0x6c, 0xa3, 0x59, 0xeb, 0xf7, 0xd2, 0xc4, 0xea, 0x2c, 0x1b, 0x69, + 0x9e, 0x9e, 0x1f, 0xcd, 0x8c, 0x81, 0xbe, 0x85, 0x8d, 0xe9, 0xcf, 0x27, 0x52, 0x04, 0x78, 0xad, + 0x10, 0xd9, 0xbd, 0x0a, 0x32, 0xcf, 0x41, 0x4f, 0xa0, 0x1c, 0x0a, 0xa9, 0x22, 0xfd, 0xc8, 0x5a, + 0xfc, 0x37, 0xa2, 0xb7, 0x14, 0x28, 0xa4, 0x0a, 0x48, 0xe8, 0x0c, 0x26, 0x72, 0xf4, 0x11, 0x98, + 0x44, 0xfa, 0x11, 0x36, 0x35, 0xa6, 0x95, 0x26, 0x56, 0x73, 0xd9, 0xd3, 0x7b, 0xe1, 0x40, 0x5a, + 0x89, 0x1e, 0x41, 0xd5, 0x15, 0x41, 0x40, 0xb8, 0x87, 0xcb, 0x85, 0xf1, 0xbd, 0xbf, 0x2c, 0xbe, + 0x33, 0x1d, 0xfa, 0x00, 0x0c, 0xca, 0x47, 0xfa, 0x81, 0xb2, 0x78, 0x86, 0xdd, 0x65, 0xb7, 0x42, + 0xf9, 0xc8, 0x19, 0x64, 0xc2, 0xc6, 0xaf, 0x26, 0xc0, 0xf9, 0xbb, 0xf6, 0x26, 0xcb, 0x7c, 0x00, + 0x65, 0x16, 0x10, 0xff, 0xda, 0xdb, 0x3c, 0x51, 0xe5, 0xb3, 0x73, 0x83, 0x41, 0x2f, 0xcb, 0x8e, + 0x51, 0x88, 0xec, 0xbd, 0x7a, 0x76, 0xcc, 0xc2, 0xec, 0xdc, 0xbb, 0x6e, 0x76, 0xae, 0x30, 0xb7, + 0x7f, 0x3d, 0x3b, 0xb5, 0xc2, 0x33, 0xec, 0x5d, 0x9a, 0x9d, 0x7e, 0xef, 0xe5, 0x9f, 0x5b, 0xb7, + 0x7e, 0xfe, 0x6b, 0xab, 0xf4, 0xec, 0xce, 0xe5, 0x9f, 0x0f, 0xd3, 0x2f, 0x98, 0x61, 0x45, 0x7f, + 0x92, 0xf4, 0xfe, 0x0e, 0x00, 0x00, 0xff, 0xff, 0x0b, 0x45, 0x02, 0x3a, 0xd3, 0x0c, 0x00, 0x00, } func (this *Service) Equal(that interface{}) bool {