forked from Logicalis/asn1
-
Notifications
You must be signed in to change notification settings - Fork 3
/
context.go
217 lines (202 loc) · 5.75 KB
/
context.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
package asn1
import (
"fmt"
"io/ioutil"
"log"
"reflect"
)
// Context keeps options that affect the ASN.1 encoding and decoding
//
// Use the NewContext() function to create a new Context instance:
//
// ctx := ber.NewContext()
// // Set options, ex:
// ctx.SetDer(true, true)
// // And call decode or encode functions
// bytes, err := ctx.EncodeWithOptions(value, "explicit,application,tag:5")
// ...
//
type Context struct {
log *log.Logger
choices map[string][]choiceEntry
der struct {
encoding bool
decoding bool
}
}
// Choice represents one option available for a CHOICE element.
type Choice struct {
Type reflect.Type
Options string
}
// Internal register with information about the each CHOICE.
type choiceEntry struct {
expectedElement
typ reflect.Type
opts *fieldOptions
}
// NewContext creates and initializes a new context. The returned Context does
// not contains any registered choice and it's set to DER encoding and BER
// decoding.
func NewContext() *Context {
ctx := &Context{}
ctx.log = defaultLogger()
ctx.choices = make(map[string][]choiceEntry)
ctx.SetDer(true, false)
return ctx
}
// getChoices returns a list of choices for a given name.
func (ctx *Context) getChoices(choice string) ([]choiceEntry, error) {
entries := ctx.choices[choice]
if entries == nil {
return nil, syntaxError("invalid choice '%s'", choice)
}
return entries, nil
}
// getChoiceByType returns the choice associated to a given name and type.
func (ctx *Context) getChoiceByType(choice string, t reflect.Type) (entry choiceEntry, err error) {
entries, err := ctx.getChoices(choice)
if err != nil {
return
}
for _, current := range entries {
if current.typ == t {
entry = current
return
}
}
err = syntaxError("invalid Go type '%s' for choice '%s'", t, choice)
return
}
// getChoiceByTag returns the choice associated to a given tag.
func (ctx *Context) getChoiceByTag(choice string, class, tag uint) (entry choiceEntry, err error) {
entries, err := ctx.getChoices(choice)
if err != nil {
return
}
for _, current := range entries {
if current.class == class && current.tag == tag {
entry = current
return
}
}
// TODO convert tag to text
err = syntaxError("invalid tag [%d,%d] for choice '%s'", class, tag, choice)
return
}
// addChoiceEntry adds a single choice to the list associated to a given name.
func (ctx *Context) addChoiceEntry(choice string, entry choiceEntry) error {
for _, current := range ctx.choices[choice] {
if current.class == entry.class && current.tag == entry.tag {
return fmt.Errorf(
"choice already registered: %s{%d, %d}",
choice, entry.class, entry.tag)
}
}
ctx.choices[choice] = append(ctx.choices[choice], entry)
return nil
}
// AddChoice registers a list of types as options to a given choice.
//
// The string choice refers to a choice name defined into an element via
// additional options for DecodeWithOptions and EncodeWithOptions of via
// struct tags.
//
// For example, considering that a field "Value" can be an INTEGER or an OCTET
// STRING indicating two types of errors, each error with a different tag
// number, the following can be used:
//
// // Error types
// type SimpleError string
// type ComplextError string
// // The main object
// type SomeSequence struct {
// // ...
// Value interface{} `asn1:"choice:value"`
// // ...
// }
// // A Context with the registered choices
// ctx := asn1.NewContext()
// ctx.AddChoice("value", []asn1.Choice {
// {
// Type: reflect.TypeOf(int(0)),
// },
// {
// Type: reflect.TypeOf(SimpleError("")),
// Options: "tag:1",
// },
// {
// Type: reflect.TypeOf(ComplextError("")),
// Options: "tag:2",
// },
// })
//
// Some important notes:
//
// 1. Any choice value must be an interface. During decoding the necessary type
// will be allocated to keep the parsed value.
//
// 2. The INTEGER type will be encoded using its default class and tag number
// and so it's not necessary to specify any Options for it.
//
// 3. The two error types in our example are encoded as strings, in order to
// make possible to differentiate both types during encoding they actually need
// to be different types. This is solved by defining two alias types:
// SimpleError and ComplextError.
//
// 4. Since both errors use the same encoding type, ASN.1 says they must have
// distinguished tags. For that, the appropriate tag is defined for each type.
//
// To encode a choice value, all that is necessary is to set the choice field
// with the proper object. To decode a choice value, a type switch can be used
// to determine which type was used.
//
func (ctx *Context) AddChoice(choice string, entries []Choice) error {
for _, e := range entries {
opts, err := parseOptions(e.Options)
if err != nil {
return err
}
// Skip if the ignore tag is given
if opts == nil {
continue
}
if opts.choice != nil {
// TODO Add support for nested choices.
return syntaxError(
"nested choices are not allowed: '%s' inside '%s'",
*opts.choice, choice)
}
raw := rawValue{}
elem, err := ctx.getExpectedElement(&raw, e.Type, opts)
if err != nil {
return err
}
err = ctx.addChoiceEntry(choice, choiceEntry{
expectedElement: elem,
typ: e.Type,
opts: opts,
})
if err != nil {
return err
}
}
return nil
}
// defaultLogger returns the default Logger. It's used to initialize a new context
// or when the logger is set to nil.
func defaultLogger() *log.Logger {
return log.New(ioutil.Discard, "", 0)
}
// SetLogger defines the logger used.
func (ctx *Context) SetLogger(logger *log.Logger) {
if logger == nil {
logger = defaultLogger()
}
ctx.log = logger
}
// SetDer sets DER mode for encofing and decoding.
func (ctx *Context) SetDer(encoding bool, decoding bool) {
ctx.der.encoding = encoding
ctx.der.decoding = decoding
}