| 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 functionality of Element's serialization. |
| 7 | // |
| 8 | |
| 9 | // `encode` serializes element into bytes array. By default, its encode in .der rule with empty options. |
| 10 | // See `encode_with_options` if you want pass an option string. See `field.v` for more option in detail. |
| 11 | // |
| 12 | // Examples: |
| 13 | // |
| 14 | // ```v |
| 15 | // import x.encoding.asn1 |
| 16 | // |
| 17 | // obj := asn1.Utf8String.new('hi')! |
| 18 | // out := asn1.encode(obj)! |
| 19 | // assert out == [u8(0x0C), 0x02, 0x68, 0x69] |
| 20 | // ``` |
| 21 | pub fn encode(el Element) ![]u8 { |
| 22 | // without options, we call `.encode_with_rule` directly on element. |
| 23 | return encode_with_rule(el, .der)! |
| 24 | } |
| 25 | |
| 26 | // `encode_with_options` serializes element into bytes array with options string passed to drive the result. |
| 27 | // |
| 28 | // Examples: |
| 29 | // |
| 30 | // `Utf8String` defined as `[5] IMPLICIT UTF8String` was encoded into `85 02 68 69`. |
| 31 | // `Utf8String` defined as `[5] EXPLICIT UTF8String` was encoded into `A5 04 0C 02 68 69`. |
| 32 | // |
| 33 | // ```v |
| 34 | // obj := asn1.Utf8String.new('hi')! |
| 35 | // implicit_out := asn1.encode_with_options(obj, 'context_specific:5;implicit;inner:12')! |
| 36 | // assert implicit_out == [u8(0x85), 0x02, 0x68, 0x69] |
| 37 | // |
| 38 | // explicit_out := asn1.encode_with_options(obj, 'context_specific:5;explicit;inner:0x0c')! |
| 39 | // assert explicit_out == [u8(0xA5), 0x04, 0x0C, 0x02, 0x68, 0x69] |
| 40 | // ``` |
| 41 | pub fn encode_with_options(el Element, opt string) ![]u8 { |
| 42 | // treated as without option when empty |
| 43 | if opt.len == 0 { |
| 44 | return encode_with_rule(el, .der)! |
| 45 | } |
| 46 | fo := FieldOptions.from_string(opt)! |
| 47 | return encode_with_field_options(el, fo)! |
| 48 | } |
| 49 | |
| 50 | // `encode_with_field_options` serializes this element into bytes array with options defined in fo. |
| 51 | pub fn encode_with_field_options(el Element, fo FieldOptions) ![]u8 { |
| 52 | // validates options again this element. |
| 53 | el.validate_options(fo)! |
| 54 | |
| 55 | // check for default_value for this element |
| 56 | // if we have it matching with current element, |
| 57 | // by default, in .der mode, it should not be serialized. |
| 58 | if fo.has_default { |
| 59 | def_element := fo.default_value or { return error('bad default_value') } |
| 60 | // If this element is equal with default_value, by default its should not be serialized. |
| 61 | if el.equal(def_element) { |
| 62 | return []u8{} |
| 63 | } |
| 64 | } |
| 65 | |
| 66 | // apply field options, turns this element |
| 67 | // into optional, wrapped element or original one. |
| 68 | new_el := el.apply_field_options(fo)! |
| 69 | |
| 70 | // if new_el is Optional, encode with optional behaviour |
| 71 | if new_el is Optional { |
| 72 | return new_el.encode()! |
| 73 | } |
| 74 | // otherwise, just serializing it |
| 75 | return encode_with_rule(new_el, .der)! |
| 76 | } |
| 77 | |
| 78 | // Helper for wrapping element |
| 79 | // |
| 80 | // |
| 81 | // into_optional turns this element into Optional with present bit. |
| 82 | // When you set with_present into true, its makes this optional was present. |
| 83 | fn (el Element) into_optional(with_present bool) !Element { |
| 84 | if el is Optional { |
| 85 | return error('already optional element') |
| 86 | } |
| 87 | return Optional.new(el, with_present)! |
| 88 | } |
| 89 | |
| 90 | // apply_field_options applies rules in field options into current element |
| 91 | // and turns this into another element. |
| 92 | // by default, optional attribute is more higher precedence over wrapper attribut, ie, |
| 93 | // take the wrap step and then turn into optional (if true) |
| 94 | fn (el Element) apply_field_options(fo FieldOptions) !Element { |
| 95 | el.validate_options(fo)! |
| 96 | // if there a wrapper |
| 97 | if fo.cls != '' { |
| 98 | wrapped_el := el.wrap_with_options(fo)! |
| 99 | if fo.optional { |
| 100 | return wrapped_el.into_optional(fo.present)! |
| 101 | } |
| 102 | // not-optional, just return wrapped element |
| 103 | return wrapped_el |
| 104 | } |
| 105 | // no-wrapper, check for optional |
| 106 | if fo.optional { |
| 107 | return el.into_optional(fo.present)! |
| 108 | } |
| 109 | // otherwise, its no-wrapper and non-optional |
| 110 | return el |
| 111 | } |
| 112 | |
| 113 | // set_default_value installs default value within FieldOptions for the element |
| 114 | pub fn (el Element) set_default_value(mut fo FieldOptions, value Element) ! { |
| 115 | // the default tag should match with the current tag |
| 116 | if !el.tag().equal(value.tag()) { |
| 117 | return error('unmatching tag of default value') |
| 118 | } |
| 119 | fo.install_default(value, false)! |
| 120 | el.validate_default(fo)! |
| 121 | } |
| 122 | |
| 123 | // wrap_with_rule wraps universal element into another class. |
| 124 | // we prohibit dan defines some rules when its happen and returns an error instead |
| 125 | // 1. wrapping into .universal class is not allowed |
| 126 | // 2. wrapping with the same class is not allowed too |
| 127 | // 3. wrapping non-universal class element is not allowed (maybe removed on futures.) |
| 128 | // Notes : |
| 129 | // Three additional information about tagging: |
| 130 | // CHOICEs are always explicitly tagged even if implicit tagging is in effect. |
| 131 | // EXPLICIT TAGs are always constructed, they encapsulate the TLV they prefix. |
| 132 | // An IMPLICIT TAG 'inherits' the constructed bit of the TLV whose 'T' is overwritten, |
| 133 | // examples: |
| 134 | // a) '[5] IMPLICIT INTEGER' has tag 0x85 (overwriting 0x02 = INTEGER) |
| 135 | // b) '[5] IMPLICIT SEQUENCE' has tag 0xA5 (overwriting 0x30 = SEQUENCE, CONSTRUCTED) |
| 136 | fn (el Element) wrap_with_options(fo FieldOptions) !Element { |
| 137 | // validates options. |
| 138 | el.validate_options(fo)! |
| 139 | |
| 140 | mode := TaggedMode.from_string(fo.mode)! |
| 141 | cls := TagClass.from_string(fo.cls)! |
| 142 | |
| 143 | return wrap(el, cls, fo.tagnum, mode)! |
| 144 | } |
| 145 | |
| 146 | // wrao performs wrapping to element and turns this element into another one. |
| 147 | fn wrap(el Element, cls TagClass, number int, mode TaggedMode) !Element { |
| 148 | if el is Optional { |
| 149 | return error('Optional cant be wrapped') |
| 150 | } |
| 151 | if cls == .universal { |
| 152 | return error('you cant wrap into universal') |
| 153 | } |
| 154 | match cls { |
| 155 | .context_specific { |
| 156 | return ContextElement.from_element(el, number, mode)! |
| 157 | } |
| 158 | .application { |
| 159 | return ApplicationElement.from_element(el, number, mode)! |
| 160 | } |
| 161 | .private { |
| 162 | return PrivateELement.from_element(el, number, mode)! |
| 163 | } |
| 164 | else { |
| 165 | return error('Wraps to the wrong class') |
| 166 | } |
| 167 | } |
| 168 | } |
| 169 | |
| 170 | // encode_with_rule encodes element into bytes array with rule. |
| 171 | fn encode_with_rule(el Element, rule EncodingRule) ![]u8 { |
| 172 | if rule != .der && rule != .ber { |
| 173 | return error('Element: unsupported rule') |
| 174 | } |
| 175 | mut dst := []u8{} |
| 176 | |
| 177 | // when this element is Optional without presence flag, by default would |
| 178 | // serialize this element into empty bytes otherwise, would serialize underlying element. |
| 179 | if el is Optional { |
| 180 | return el.encode()! |
| 181 | } |
| 182 | // otherwise, just serializes as normal |
| 183 | el.tag().encode_with_rule(mut dst, rule)! |
| 184 | // calculates the length of element, and serialize this length |
| 185 | payload := el.payload()! |
| 186 | length := Length.new(payload.len)! |
| 187 | length.encode_with_rule(mut dst, rule)! |
| 188 | // append the element payload to destination |
| 189 | dst << payload |
| 190 | |
| 191 | return dst |
| 192 | } |
| 193 | |