v2 / vlib / x / encoding / asn1 / element_decode.v
202 lines · 188 sloc · 6.06 KB · 94905820e6da47fbb60eb4f30a3c553ba6d73a95
Raw
1// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
2// Use of this source code is governed by a MIT License
3// that can be found in the LICENSE file.
4module asn1
5
6// Handling of deserialization of bytes array into some Element.
7//
8
9// decode decodes single element from bytes, its not allowing trailing data.
10//
11// Examples:
12//
13// Original object was Utf8String with tag == 12 (0c)
14// ```v
15// import x.encoding.asn1
16//
17// original_obj := asn1.Utf8String.new('hi')!
18// bytes_data := [u8(0x0C), 0x02, 0x68, 0x69]
19// decoded_obj := asn1.decode(bytes_data)!
20// assert decoded_obj.equal(original_obj)
21// ```
22pub fn decode(bytes []u8) !Element {
23 // call Element.decode_with_rule directly
24 el, pos := Element.decode_with_rule(bytes, 0, .der)!
25 if pos > bytes.len {
26 return error('decode on data with trailing data')
27 }
28 return el
29}
30
31// decode_with_options decodes single element from bytes with options support, its not allowing trailing data.
32// Its accepts options string to drive decoding process.
33//
34// Examples:
35//
36// `UTF8String` with implicit tagging definded as `[5] IMPLICIT UTF8String` was encoded into `85 02 68 69`
37//
38// ```v
39// original_obj := asn1.Utf8String.new('hi')!
40// implicit_bytes := [u8(0x85), 0x02, 0x68, 0x69]
41// obj_2 := asn1.decode_with_options(implicit_bytes, 'context_specific:5;implicit;inner:12')!
42//
43// assert obj_2.equal(original_obj)
44// dump(obj_2) // Output: obj_2: asn1.Element(Utf8String: (hi))
45// ```
46//
47// `UTF8String` with explicit tagging defined as `[5] EXPLICIT UTF8String` was encoded into `A5 04 0C 02 68 69`
48//
49// ```v
50// explicit_bytes := [u8(0xA5), 0x04, 0x0C, 0x02, 0x68, 0x69]
51// obj_3 := asn1.decode_with_options(explicit_bytes, 'context_specific:5;explicit;inner:0x0c')!
52//
53// assert obj_3.equal(original_obj)
54// dump(obj_3) // output: obj_3: asn1.Element(Utf8String: (hi))
55// ```
56pub fn decode_with_options(bytes []u8, opt string) !Element {
57 // if null-option length, call Element.decode_with_rule directly
58 if opt.len == 0 {
59 el, pos := Element.decode_with_rule(bytes, 0, .der)!
60 if pos > bytes.len {
61 return error('decode on data with trailing data')
62 }
63 return el
64 }
65 fo := FieldOptions.from_string(opt)!
66 return decode_with_field_options(bytes, fo)!
67}
68
69// decode_with_field_options is similar to `decode_with_options`, but its more controllable through FieldOptions.
70pub fn decode_with_field_options(bytes []u8, fo FieldOptions) !Element {
71 // TODO
72 if bytes.len == 0 {
73 return error('Empty bytes')
74 }
75 fo.check_wrapper()!
76
77 // check for optional, and return it, maybe nil optional
78 // expected optional tag is outer wrapper tag
79 wrp_tag := fo.wrapper_tag()!
80 if fo.optional {
81 opt := decode_optional(bytes, wrp_tag)!
82 return opt
83 }
84 // read an element from bytes
85 mut p := Parser.new(bytes)
86 tlv := p.read_tlv()!
87 p.finish()!
88
89 // semantically no wraps
90 if fo.cls == '' {
91 return tlv
92 }
93
94 // wrapped
95 if tlv.tag().class != wrp_tag.class {
96 return error('Get different class')
97 }
98 if tlv.tag().number != wrp_tag.number {
99 return error('Get different tag number')
100 }
101 // TODO: default
102 return tlv.unwrap_with_field_options(fo)!
103}
104
105fn decode_optional(bytes []u8, expected_tag Tag) !Element {
106 mut p := Parser.new(bytes)
107 ct := p.peek_tag()!
108 // when the tag is equal expected_tag, this mean, present this optional element
109 if ct.equal(expected_tag) {
110 // present
111 el := p.read_tlv()!
112 mut opt := Optional.new(el, true)!
113 return opt
114 }
115 // optional element with no-presence semantic
116 el := RawElement.new(expected_tag, []u8{})!
117 opt := Optional.new(el, false)!
118 return opt
119}
120
121// unwrap_with_options performs unwrapping operations to the element with options provided.
122// Its technically reverse operation of the `.wrap()` applied to the element
123// with the same options. If you provide with diferent options,
124// the result is in undesired behaviour, even its success
125pub fn (el Element) unwrap_with_options(opt string) !Element {
126 if opt.len == 0 {
127 return el
128 }
129 fo := FieldOptions.from_string(opt)!
130 return el.unwrap_with_field_options(fo)!
131}
132
133// unwrap_with_field_options performs unwrapping operations with FieldOptions.
134pub fn (el Element) unwrap_with_field_options(fo FieldOptions) !Element {
135 if fo.cls == '' {
136 // no unwrap
137 return el
138 }
139 el.validate_options(fo)!
140 // first, checks class of the element being to unwrap, should not universal class.
141 if el.tag().class == .universal {
142 return error('you cant unwrap universal element')
143 }
144 // its also happens to fo.cls, should not be an universal class
145 if fo.cls == 'universal' {
146 return error('you cant unwrap universal element')
147 }
148
149 // element being unwrap should have matching with tag within options.
150 mode := TaggedMode.from_string(fo.mode)!
151 inner_tag := fo.inner_tag()!
152 if mode == .explicit {
153 if !el.tag().constructed {
154 return error('explicit mode should have constructed tag')
155 }
156 // checks inner tag from payload
157 tag, _ := Tag.decode_with_rule(el.payload()!, 0, .der)!
158 if !tag.equal(inner_tag) {
159 return error('Get unexpected inner tag from payload')
160 }
161 }
162 inner_form := inner_tag.constructed
163 constructed := if mode == .explicit { true } else { inner_form }
164
165 // check for tag equality
166 // The tag of element being unwrapped with tag from FieldOptions should matching.
167 // Its should comes from same options on wrapping.
168 cls := TagClass.from_string(fo.cls)!
169 if el.tag().class != cls {
170 return error('unmatching outer tag class')
171 }
172 built_tag := Tag.new(cls, constructed, fo.tagnum)!
173 // check outer tag equality
174 if !el.tag().equal(built_tag) {
175 return error('Element tag unequal with tag from options')
176 }
177
178 // return unwrapped element
179 return unwrap(el, mode, inner_tag)!
180}
181
182// unwrap the provided element, turn into inner element.
183fn unwrap(el Element, mode TaggedMode, inner_tag Tag) !Element {
184 match mode {
185 .explicit {
186 // el.payload is serialized of inner element
187 bytes := el.payload()!
188 inner_elem := decode(bytes)!
189 // recheck the tag
190 if !inner_elem.tag().equal(inner_tag) {
191 return error('unmatching inner_tag')
192 }
193 return inner_elem
194 }
195 .implicit {
196 // el.payload() is content of inner element
197 bytes := el.payload()!
198 inner_elem := parse_element(inner_tag, bytes)!
199 return inner_elem
200 }
201 }
202}
203