| 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. |
| 4 | module 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 | // ``` |
| 22 | pub 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 | // ``` |
| 56 | pub 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. |
| 70 | pub 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 | |
| 105 | fn 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 |
| 125 | pub 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. |
| 134 | pub 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. |
| 183 | fn 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 | |