forked from Logicalis/asn1
-
Notifications
You must be signed in to change notification settings - Fork 3
/
encode.go
304 lines (268 loc) · 7.28 KB
/
encode.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
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
package asn1
import (
"reflect"
"sort"
"unicode"
)
// Encode returns the ASN.1 encoding of obj.
//
// See (*Context).EncodeWithOptions() for further details.
func (ctx *Context) Encode(obj interface{}) (data []byte, err error) {
return ctx.EncodeWithOptions(obj, "")
}
// EncodeWithOptions returns the ASN.1 encoding of obj using additional options.
//
// See (*Context).DecodeWithOptions() for further details regarding types and
// options.
func (ctx *Context) EncodeWithOptions(obj interface{}, options string) (data []byte, err error) {
opts, err := parseOptions(options)
if err != nil {
return nil, err
}
// Return nil if the ignore tag is given
if opts == nil {
return
}
value := reflect.ValueOf(obj)
raw, err := ctx.encode(value, opts)
if err != nil {
return
}
data, err = raw.encode()
return
}
// Main encode function
func (ctx *Context) encode(value reflect.Value, opts *fieldOptions) (*rawValue, error) {
// Skip the interface type
value = getActualType(value)
// If a value is missing the default value is used
empty := isEmpty(value)
if opts.defaultValue != nil {
if empty && !ctx.der.encoding {
defaultValue, err := ctx.newDefaultValue(value.Type(), opts)
if err != nil {
return nil, err
}
value = defaultValue
empty = false
}
}
// Encode data
raw, err := ctx.encodeValue(value, opts)
if err != nil {
return nil, err
}
// Since the empty flag is already calculated, check if it's optional
if (opts.optional || opts.defaultValue != nil) && empty {
return nil, nil
}
// Modify the data generated based on the given tags
raw, err = ctx.applyOptions(value, raw, opts)
if err != nil {
return nil, err
}
return raw, nil
}
func (ctx *Context) encodeValue(value reflect.Value, opts *fieldOptions) (raw *rawValue, err error) {
raw = &rawValue{}
encoder := encoderFunction(nil)
// Special types:
objType := value.Type()
switch objType {
case bigIntType:
raw.Tag = tagInteger
encoder = ctx.encodeBigInt
case bitStringType:
raw.Tag = tagBitString
encoder = ctx.encodeBitString
case oidType:
raw.Tag = tagOid
encoder = ctx.encodeOid
case nullType:
raw.Tag = tagNull
encoder = ctx.encodeNull
case enumType:
raw.Tag = tagEnum
encoder = ctx.encodeInt
}
if encoder == nil {
// Generic types:
switch value.Kind() {
case reflect.Bool:
raw.Tag = tagBoolean
encoder = ctx.encodeBool
case reflect.String:
raw.Tag = tagOctetString
encoder = ctx.encodeString
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
raw.Tag = tagInteger
encoder = ctx.encodeInt
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
raw.Tag = tagInteger
encoder = ctx.encodeUint
case reflect.Struct:
raw.Tag = tagSequence
raw.Constructed = true
encoder = ctx.encodeStruct
if opts.set {
encoder = ctx.encodeStructAsSet
}
case reflect.Array, reflect.Slice:
if objType.Elem().Kind() == reflect.Uint8 {
raw.Tag = tagOctetString
encoder = ctx.encodeOctetString
} else {
raw.Tag = tagSequence
raw.Constructed = true
encoder = ctx.encodeSlice
}
}
}
if encoder == nil {
return nil, syntaxError("invalid Go type: %s", value.Type())
}
raw.Content, err = encoder(value)
return
}
// applyOptions modifies a raw value based on the given options.
func (ctx *Context) applyOptions(value reflect.Value, raw *rawValue, opts *fieldOptions) (*rawValue, error) {
// Change sequence to set
if opts.set {
if raw.Class != classUniversal || raw.Tag != tagSequence {
return nil, syntaxError("Go type '%s' does not accept the flag 'set'", value.Type())
}
raw.Tag = tagSet
}
// Check if this type is an Asn.1 choice
if opts.choice != nil {
entry, err := ctx.getChoiceByType(*opts.choice, value.Type())
if err != nil {
return nil, err
}
raw, err = ctx.applyOptions(value, raw, entry.opts)
raw.Class = entry.class
raw.Tag = entry.tag
}
// Add an enclosing tag
if opts.explicit {
if opts.tag == nil {
return nil, syntaxError(
"invalid flag 'explicit' without tag on Go type '%s'",
value.Type())
}
content, err := raw.encode()
if err != nil {
return nil, err
}
raw = &rawValue{}
raw.Constructed = true
raw.Content = content
}
// Change tag
if opts.tag != nil {
raw.Class = classContextSpecific
raw.Tag = uint(*opts.tag)
}
// Change class
if opts.universal {
raw.Class = classUniversal
}
if opts.application {
raw.Class = classApplication
}
// Use the indefinite length encoding
if opts.indefinite {
if !raw.Constructed {
return nil, syntaxError(
"invalid flag 'indefinite' on Go type: %s",
value.Type())
}
raw.Indefinite = true
}
return raw, nil
}
// isEmpty checks is a value is empty.
func isEmpty(value reflect.Value) bool {
defaultValue := reflect.Zero(value.Type())
return reflect.DeepEqual(value.Interface(), defaultValue.Interface())
}
// isFieldExported checks is the field name starts with a capital letter.
func isFieldExported(field reflect.StructField) bool {
return unicode.IsUpper([]rune(field.Name)[0])
}
// getRawValuesFromFields encodes each valid field ofa struct value and returns
// a slice of raw values.
func (ctx *Context) getRawValuesFromFields(value reflect.Value) ([]*rawValue, error) {
// Encode each child to a raw value
children := []*rawValue{}
for i := 0; i < value.NumField(); i++ {
fieldValue := value.Field(i)
fieldStruct := value.Type().Field(i)
// Ignore field that are not exported (that starts with lowercase)
if isFieldExported(fieldStruct) {
tag := fieldStruct.Tag.Get(tagKey)
opts, err := parseOptions(tag)
if err != nil {
return nil, err
}
// Skip if the ignore tag is given
if opts == nil {
continue
}
raw, err := ctx.encode(fieldValue, opts)
if err != nil {
return nil, err
}
children = append(children, raw)
}
}
return children, nil
}
// encodeRawValues is a helper function to encode raw value in sequence.
func (ctx *Context) encodeRawValues(values ...*rawValue) ([]byte, error) {
content := []byte{}
for _, raw := range values {
buf, err := raw.encode()
if err != nil {
return nil, err
}
content = append(content, buf...)
}
return content, nil
}
// encodeStruct encodes structs fields in order.
func (ctx *Context) encodeStruct(value reflect.Value) ([]byte, error) {
// Encode each child to a raw value
children, err := ctx.getRawValuesFromFields(value)
if err != nil {
return nil, err
}
return ctx.encodeRawValues(children...)
}
// encodeStructAsSet works similarly to encodeStruct, but in Der mode the
// fields are encoded in ascending order of their tags.
func (ctx *Context) encodeStructAsSet(value reflect.Value) ([]byte, error) {
// Encode each child to a raw value
children, err := ctx.getRawValuesFromFields(value)
if err != nil {
return nil, err
}
// Sort if necessary
if ctx.der.encoding {
sort.Sort(rawValueSlice(children))
}
return ctx.encodeRawValues(children...)
}
// encodeSlice encodes a slice or array as a sequence of values.
func (ctx *Context) encodeSlice(value reflect.Value) ([]byte, error) {
content := []byte{}
for i := 0; i < value.Len(); i++ {
itemValue := value.Index(i)
childBytes, err := ctx.EncodeWithOptions(itemValue.Interface(), "")
if err != nil {
return nil, err
}
content = append(content, childBytes...)
}
return content, nil
}