-
Notifications
You must be signed in to change notification settings - Fork 77
/
serialization.go
177 lines (161 loc) · 4.44 KB
/
serialization.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
package stackitem
import (
"errors"
"fmt"
"math/big"
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
"github.com/nspcc-dev/neo-go/pkg/io"
)
// SerializeItem encodes given Item into the byte slice.
func SerializeItem(item Item) ([]byte, error) {
w := io.NewBufBinWriter()
EncodeBinaryStackItem(item, w.BinWriter)
if w.Err != nil {
return nil, w.Err
}
return w.Bytes(), nil
}
// EncodeBinaryStackItem encodes given Item into the given BinWriter. It's
// similar to io.Serializable's EncodeBinary, but works with Item
// interface.
func EncodeBinaryStackItem(item Item, w *io.BinWriter) {
serializeItemTo(item, w, false, make(map[Item]bool))
}
// EncodeBinaryStackItemAppExec encodes given Item into the given BinWriter. It's
// similar to EncodeBinaryStackItem but allows to encode interop (only type, value is lost).
func EncodeBinaryStackItemAppExec(item Item, w *io.BinWriter) {
bw := io.NewBufBinWriter()
serializeItemTo(item, bw.BinWriter, true, make(map[Item]bool))
if bw.Err != nil {
w.WriteBytes([]byte{byte(InvalidT)})
return
}
w.WriteBytes(bw.Bytes())
}
func serializeItemTo(item Item, w *io.BinWriter, allowInvalid bool, seen map[Item]bool) {
if seen[item] {
w.Err = errors.New("recursive structures can't be serialized")
return
}
if item == nil && allowInvalid {
w.WriteBytes([]byte{byte(InvalidT)})
return
}
switch t := item.(type) {
case *ByteArray:
w.WriteBytes([]byte{byte(ByteArrayT)})
w.WriteVarBytes(t.Value().([]byte))
case *Buffer:
w.WriteBytes([]byte{byte(BufferT)})
w.WriteVarBytes(t.Value().([]byte))
case *Bool:
w.WriteBytes([]byte{byte(BooleanT)})
w.WriteBool(t.Value().(bool))
case *BigInteger:
w.WriteBytes([]byte{byte(IntegerT)})
w.WriteVarBytes(bigint.ToBytes(t.Value().(*big.Int)))
case *Interop:
if allowInvalid {
w.WriteBytes([]byte{byte(InteropT)})
return
}
w.Err = errors.New("interop item can't be serialized")
case *Array, *Struct:
seen[item] = true
_, isArray := t.(*Array)
if isArray {
w.WriteBytes([]byte{byte(ArrayT)})
} else {
w.WriteBytes([]byte{byte(StructT)})
}
arr := t.Value().([]Item)
w.WriteVarUint(uint64(len(arr)))
for i := range arr {
serializeItemTo(arr[i], w, allowInvalid, seen)
}
case *Map:
seen[item] = true
w.WriteBytes([]byte{byte(MapT)})
w.WriteVarUint(uint64(len(t.Value().([]MapElement))))
for i := range t.Value().([]MapElement) {
serializeItemTo(t.Value().([]MapElement)[i].Key, w, allowInvalid, seen)
serializeItemTo(t.Value().([]MapElement)[i].Value, w, allowInvalid, seen)
}
case Null:
w.WriteB(byte(AnyT))
}
}
// DeserializeItem decodes Item from the given byte slice.
func DeserializeItem(data []byte) (Item, error) {
r := io.NewBinReaderFromBuf(data)
item := DecodeBinaryStackItem(r)
if r.Err != nil {
return nil, r.Err
}
return item, nil
}
// DecodeBinaryStackItem decodes previously serialized Item from the given
// reader. It's similar to the io.Serializable's DecodeBinary(), but implemented
// as a function because Item itself is an interface. Caveat: always check
// reader's error value before using the returned Item.
func DecodeBinaryStackItem(r *io.BinReader) Item {
return decodeBinaryStackItem(r, false)
}
// DecodeBinaryStackItemAppExec is similar to DecodeBinaryStackItem
// but allows Interop values to be present.
func DecodeBinaryStackItemAppExec(r *io.BinReader) Item {
return decodeBinaryStackItem(r, true)
}
func decodeBinaryStackItem(r *io.BinReader, allowInvalid bool) Item {
var t = Type(r.ReadB())
if r.Err != nil {
return nil
}
switch t {
case ByteArrayT, BufferT:
data := r.ReadVarBytes(MaxSize)
return NewByteArray(data)
case BooleanT:
var b = r.ReadBool()
return NewBool(b)
case IntegerT:
data := r.ReadVarBytes(bigint.MaxBytesLen)
num := bigint.FromBytes(data)
return NewBigInteger(num)
case ArrayT, StructT:
size := int(r.ReadVarUint())
arr := make([]Item, size)
for i := 0; i < size; i++ {
arr[i] = DecodeBinaryStackItem(r)
}
if t == ArrayT {
return NewArray(arr)
}
return NewStruct(arr)
case MapT:
size := int(r.ReadVarUint())
m := NewMap()
for i := 0; i < size; i++ {
key := DecodeBinaryStackItem(r)
value := DecodeBinaryStackItem(r)
if r.Err != nil {
break
}
m.Add(key, value)
}
return m
case AnyT:
return Null{}
case InteropT:
if allowInvalid {
return NewInterop(nil)
}
fallthrough
default:
if t == InvalidT && allowInvalid {
return nil
}
r.Err = fmt.Errorf("unknown type: %v", t)
return nil
}
}