-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathunmarshal.go
216 lines (192 loc) · 5.88 KB
/
unmarshal.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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
package netstring
import (
"fmt"
"reflect"
"strconv"
)
// Unmarshal takes incoming "keyed" netstrings and populates "message". Message must be a
// pointer to a "basic-struct" with the same restrictions as discussed in Marshal.
//
// Each netstring is read via Decoder.DecodeKeyed() until a "keyed" netstring matches
// "eom". Each netstring is decoded into the field with a "netstring" tag matching the
// netstring "key".
//
// The end-of-message sentinel, "eom", can be any valid Key excepting
// netstring.NoKey. When the "eom" netstring is seen, the message is considered fully
// populated, the "eom" message is discarded and control is returned to the caller.
//
// If "message" is not a pointer to a "basic-struct" an error is returned. Only exported
// fields with "netstring" tags are considered for incoming "keyed" netstrings. If
// "message" contains duplicate "netstring" tag values an error is returned.
//
// The "unknown" variable is set with the key of any incoming "keyed" netstring which has
// no corresponding field in "message". Obviously only one "unknown" is visible to the
// caller even though there may be multiple occurrences. Since an unknown key may be
// acceptable to the application, it is left to the caller to decide whether this
// situation results in an error, an alert to upgrade, or silence.
//
// An example:
//
// type record struct {
// Age int `netstring:"a"`
// Country string `netstring:"c"`
// TLD []byte `netstring:"t"`
// CountryCode []byte `netstring:"C"`
// Name string `netstring:"n"`
// }
//
// bbuf := bytes.NewBufferString("3:Mr0,3:a22,11:cNew Zeland,3:C64,4:nBob,1:Z,")
// dec := netstring.NewDecoder(bbuf)
// k, v, e := dec.DecodeKeyed()
// if k == 'M' && string(v) == "r0" { // Dispatch on message type
// msg := &record{}
// dec.Unmarshal('Z', msg)
// }
//
// Note how the first netstring is used to determine which struct to Unmarshal into.
func (dec *Decoder) Unmarshal(eom Key, message any) (unknown Key, err error) {
k, e := eom.Assess()
if e != nil {
err = e
return
}
if !k {
err = ErrBadMarshalEOM
return
}
vo := reflect.ValueOf(message) // vo is a reflect.Value
if !vo.IsValid() {
err = ErrBadMarshalValue
return
}
to := vo.Type()
kind := vo.Kind()
if kind != reflect.Pointer { // Must be a Pointer so we can set it!
err = ErrBadUnmarshalMsg
return
}
vo = vo.Elem()
to = vo.Type()
kind = vo.Kind()
if kind != reflect.Struct { // Only go one-level deep, so no **struct{}
err = ErrBadUnmarshalMsg
return
}
// Evaluate message fields
type field struct {
seen bool
name string
kind reflect.Kind
value reflect.Value
maxint int64
}
keyToField := make(map[Key]*field)
for ix := 0; ix < to.NumField(); ix++ {
sf := to.Field(ix) // Get StructField
if !sf.IsExported() {
continue
}
tag := sf.Tag.Get("netstring")
if len(tag) == 0 {
continue
}
if len(tag) != 1 {
err = fmt.Errorf(errorPrefix+"%s tag '%s' (0x%X) is not a single character",
sf.Name, tag, tag)
return
}
key := Key(tag[0])
var keyed bool
keyed, err = key.Assess()
if err != nil {
return
}
if !keyed {
err = fmt.Errorf(errorPrefix+"%s tag '%s' (0x%X) is not a valid netstring.Key",
sf.Name, tag, tag)
return
}
if f, ok := keyToField[key]; ok {
err = fmt.Errorf(errorPrefix+"Duplicate tag '%s' for '%s' and '%s'",
tag, sf.Name, f.name)
return
}
vf := vo.Field(ix)
kind := sf.Type.Kind()
// Some kinds need further checking
switch kind {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: // Do nothing
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: // Do nothing
case reflect.Float32, reflect.Float64: // Do nothing
case reflect.String: // Do nothing
case reflect.Slice: // Is it a byte slice?
eKind := sf.Type.Elem().Kind()
if eKind != reflect.Uint8 {
err = fmt.Errorf(errorPrefix+"%s type unsupported (%s of %s)",
sf.Name, kind, eKind)
return
}
default:
err = fmt.Errorf(errorPrefix+"%s type unsupported (%s)",
sf.Name, kind)
return
}
keyToField[key] = &field{false, sf.Name, kind, vf, 0} // field looks good, stash it in the map
}
// Have all the information about message destination fields so start consuming
// keyed netstrings and map them into the "basic-struct" destination fields.
for {
k, v, e := dec.DecodeKeyed()
if e != nil {
err = e
return
}
if k == eom {
return
}
field, ok := keyToField[k]
if !ok {
unknown = k
continue
}
if field.seen {
err = fmt.Errorf(errorPrefix+"Duplicate key '%s' in decode stream for %s",
k.String(), field.name)
return
}
field.seen = true
switch field.kind {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
vi, e := strconv.ParseInt(string(v), 10, 64)
if e != nil || field.value.OverflowInt(vi) {
err = fmt.Errorf(errorPrefix+"Cannot convert '%s' to int for %s (%s)",
string(v), field.name, field.kind)
return
}
field.value.SetInt(vi)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
vi, e := strconv.ParseUint(string(v), 10, 64)
if e != nil || field.value.OverflowUint(vi) {
err = fmt.Errorf(errorPrefix+"Cannot convert '%s' to uint for %s - overflows %s",
string(v), field.name, field.kind)
return
}
field.value.SetUint(vi)
case reflect.Float32, reflect.Float64:
vf, e := strconv.ParseFloat(string(v), 64)
if e != nil || field.value.OverflowFloat(vf) {
err = fmt.Errorf(errorPrefix+"Cannot convert '%s' to float for %s - overflows %s",
string(v), field.name, field.kind)
return
}
field.value.SetFloat(vf)
case reflect.String:
field.value.SetString(string(v))
case reflect.Slice:
field.value.SetBytes(v)
default:
err = fmt.Errorf(errorPrefix+"%s Internal Error type (%s) ducked early check",
field.name, kind)
}
}
}