From df2e9f68ef32e83e6c018bf32ae7862d53a7095a Mon Sep 17 00:00:00 2001 From: fyrchik Date: Tue, 22 Jan 2019 15:15:04 +0300 Subject: [PATCH] Add StackParam JSON Unmarshaler (#116) --- pkg/rpc/stack_param.go | 174 ++++++++++++++++++++++++++++++++++++ pkg/rpc/stack_param_test.go | 105 ++++++++++++++++++++++ pkg/rpc/types.go | 6 -- 3 files changed, 279 insertions(+), 6 deletions(-) create mode 100644 pkg/rpc/stack_param.go create mode 100644 pkg/rpc/stack_param_test.go diff --git a/pkg/rpc/stack_param.go b/pkg/rpc/stack_param.go new file mode 100644 index 000000000..d20f89cbd --- /dev/null +++ b/pkg/rpc/stack_param.go @@ -0,0 +1,174 @@ +package rpc + +import ( + "encoding/hex" + "encoding/json" + "strconv" + + "github.com/CityOfZion/neo-go/pkg/util" + "github.com/pkg/errors" +) + +type StackParamType int + +const ( + Unknown StackParamType = -1 + Signature StackParamType = 0x00 + Boolean StackParamType = 0x01 + Integer StackParamType = 0x02 + Hash160 StackParamType = 0x03 + Hash256 StackParamType = 0x04 + ByteArray StackParamType = 0x05 + PublicKey StackParamType = 0x06 + String StackParamType = 0x07 + Array StackParamType = 0x10 + InteropInterface StackParamType = 0xf0 + Void StackParamType = 0xff +) + +func (t StackParamType) String() string { + switch t { + case Signature: + return "Signature" + case Boolean: + return "Boolean" + case Integer: + return "Integer" + case Hash160: + return "Hash160" + case Hash256: + return "Hash256" + case ByteArray: + return "ByteArray" + case PublicKey: + return "PublicKey" + case String: + return "String" + case Array: + return "Array" + case InteropInterface: + return "InteropInterface" + case Void: + return "Void" + default: + return "Unknown" + } +} + +func StackParamTypeFromString(s string) (StackParamType, error) { + switch s { + case "Signature": + return Signature, nil + case "Boolean": + return Boolean, nil + case "Integer": + return Integer, nil + case "Hash160": + return Hash160, nil + case "Hash256": + return Hash256, nil + case "ByteArray": + return ByteArray, nil + case "PublicKey": + return PublicKey, nil + case "String": + return String, nil + case "Array": + return Array, nil + case "InteropInterface": + return InteropInterface, nil + case "Void": + return Void, nil + default: + return Unknown, errors.Errorf("unknown stack parameter type: %s", s) + } +} + +func (p *StackParamType) UnmarshalJSON(data []byte) (err error) { + var ( + s = string(data) + l = len(s) + ) + if l < 2 || s[0] != '"' || s[l-1] != '"' { + *p = Unknown + return errors.Errorf("invalid type: %s", s) + } + *p, err = StackParamTypeFromString(s[1 : l-1]) + return +} + +// StackParam respresent a stack parameter. +type StackParam struct { + Type StackParamType `json:"type"` + Value interface{} `json:"value"` +} + +type rawStackParam struct { + Type StackParamType `json:"type"` + Value json.RawMessage `json:"value"` +} + +// UnmarshalJSON implements Unmarshaler interface +func (p *StackParam) UnmarshalJSON(data []byte) (err error) { + var ( + r rawStackParam + i int64 + s string + b []byte + ) + + if err = json.Unmarshal(data, &r); err != nil { + return + } + + switch p.Type = r.Type; r.Type { + case ByteArray: + if err = json.Unmarshal(r.Value, &s); err != nil { + return + } + if b, err = hex.DecodeString(s); err != nil { + return + } + p.Value = b + case String: + if err = json.Unmarshal(r.Value, &s); err != nil { + return + } + p.Value = s + case Integer: + if err = json.Unmarshal(r.Value, &i); err == nil { + p.Value = i + return + } + // sometimes integer comes as string + if err = json.Unmarshal(r.Value, &s); err != nil { + return + } + if i, err = strconv.ParseInt(s, 10, 64); err != nil { + return + } + p.Value = i + case Array: + //https://github.com/neo-project/neo/blob/3d59ecca5a8deb057bdad94b3028a6d5e25ac088/neo/Network/RPC/RpcServer.cs#L67 + var rs []StackParam + if err = json.Unmarshal(r.Value, &rs); err != nil { + return + } + p.Value = rs + case Hash160: + var h util.Uint160 + if err = json.Unmarshal(r.Value, &h); err != nil { + return + } + p.Value = h + case Hash256: + var h util.Uint256 + if err = json.Unmarshal(r.Value, &h); err != nil { + return + } + p.Value = h + default: + return errors.New("not implemented") + } + return +} diff --git a/pkg/rpc/stack_param_test.go b/pkg/rpc/stack_param_test.go new file mode 100644 index 000000000..9291a7392 --- /dev/null +++ b/pkg/rpc/stack_param_test.go @@ -0,0 +1,105 @@ +package rpc + +import ( + "encoding/json" + "reflect" + "testing" + + "github.com/CityOfZion/neo-go/pkg/util" +) + +var testCases = []struct { + input string + result StackParam +}{ + { + input: `{"type":"Integer","value":12345}`, + result: StackParam{Type: Integer, Value: int64(12345)}, + }, + { + input: `{"type":"Integer","value":"12345"}`, + result: StackParam{Type: Integer, Value: int64(12345)}, + }, + { + input: `{"type":"ByteArray","value":"010203"}`, + result: StackParam{Type: ByteArray, Value: []byte{0x01, 0x02, 0x03}}, + }, + { + input: `{"type":"String","value":"Some string"}`, + result: StackParam{Type: String, Value: "Some string"}, + }, + { + input: `{"type":"Array","value":[ +{"type": "String", "value": "str 1"}, +{"type": "Integer", "value": 2}]}`, + result: StackParam{ + Type: Array, + Value: []StackParam{ + {Type: String, Value: "str 1"}, + {Type: Integer, Value: int64(2)}, + }, + }, + }, +} + +var errorCases = []string{ + `{"type": "ByteArray","value":`, // incorrect JSON + `{"type": "ByteArray","value":1}`, // incorrect Value + `{"type": "ByteArray","value":"12zz"}`, // incorrect ByteArray value + `{"type": "String","value":`, // incorrect JSON + `{"type": "String","value":1}`, // incorrect Value + `{"type": "Integer","value": "nn"}`, // incorrect Integer value + `{"type": "Integer","value": []}`, // incorrect Integer value + `{"type": "Array","value": 123}`, // incorrect Array value + `{"type": "Hash160","value": "0bcd"}`, // incorrect Uint160 value + `{"type": "Hash256","value": "0bcd"}`, // incorrect Uint256 value + `{"type": "Stringg","value": ""}`, // incorrect type +} + +func TestStackParam_UnmarshalJSON(t *testing.T) { + var ( + err error + r, s StackParam + ) + for _, tc := range testCases { + if err = json.Unmarshal([]byte(tc.input), &s); err != nil { + t.Errorf("error while unmarhsalling: %v", err) + } else if !reflect.DeepEqual(s, tc.result) { + t.Errorf("got (%v), expected (%v)", s, tc.result) + } + } + + // Hash160 unmarshalling + err = json.Unmarshal([]byte(`{"type": "Hash160","value": "0bcd2978634d961c24f5aea0802297ff128724d6"}`), &s) + if err != nil { + t.Errorf("error while unmarhsalling: %v", err) + } + + h160, err := util.Uint160DecodeString("0bcd2978634d961c24f5aea0802297ff128724d6") + if err != nil { + t.Errorf("unmarshal error: %v", err) + } + + if r = (StackParam{Type: Hash160, Value: h160}); !reflect.DeepEqual(s, r) { + t.Errorf("got (%v), expected (%v)", s, r) + } + + // Hash256 unmarshalling + err = json.Unmarshal([]byte(`{"type": "Hash256","value": "f037308fa0ab18155bccfc08485468c112409ea5064595699e98c545f245f32d"}`), &s) + if err != nil { + t.Errorf("error while unmarhsalling: %v", err) + } + h256, err := util.Uint256DecodeString("f037308fa0ab18155bccfc08485468c112409ea5064595699e98c545f245f32d") + if err != nil { + t.Errorf("unmarshal error: %v", err) + } + if r = (StackParam{Type: Hash256, Value: h256}); !reflect.DeepEqual(s, r) { + t.Errorf("got (%v), expected (%v)", s, r) + } + + for _, input := range errorCases { + if err = json.Unmarshal([]byte(input), &s); err == nil { + t.Errorf("expected error, got (nil)") + } + } +} diff --git a/pkg/rpc/types.go b/pkg/rpc/types.go index 797fc6348..d1ce4fd5c 100644 --- a/pkg/rpc/types.go +++ b/pkg/rpc/types.go @@ -17,12 +17,6 @@ type InvokeResult struct { Stack []*StackParam } -// StackParam respresent a stack parameter. -type StackParam struct { - Type string `json:"type"` - Value interface{} `json:"value"` -} - // AccountStateResponse holds the getaccountstate response. type AccountStateResponse struct { responseHeader