v/vlib/x/encoding/asn1/element_encode.v

193 lines
6.3 KiB
V

// Copyright (c) 2022, 2024 blackshirt. All rights reserved.
// Use of this source code is governed by a MIT License
// that can be found in the LICENSE file.
module asn1
// Handling functionality of Element's serialization.
//
// `encode` serializes element into bytes array. By default, its encode in .der rule with empty options.
// See `encode_with_options` if you want pass an option string. See `field.v` for more option in detail.
//
// Examples:
//
// ```v
// import x.encoding.asn1
//
// obj := asn1.Utf8String.new('hi')!
// out := asn1.encode(obj)!
// assert out == [u8(0x0C), 0x02, 0x68, 0x69]
// ```
pub fn encode(el Element) ![]u8 {
// without options, we call `.encode_with_rule` directly on element.
return encode_with_rule(el, .der)!
}
// `encode_with_options` serializes element into bytes array with options string passed to drive the result.
//
// Examples:
//
// `Utf8String` defined as `[5] IMPLICIT UTF8String` was encoded into `85 02 68 69`.
// `Utf8String` defined as `[5] EXPLICIT UTF8String` was encoded into `A5 04 0C 02 68 69`.
//
// ```v
// obj := asn1.Utf8String.new('hi')!
// implicit_out := asn1.encode_with_options(obj, 'context_specific:5;implicit;inner:12')!
// assert implicit_out == [u8(0x85), 0x02, 0x68, 0x69]
//
// explicit_out := asn1.encode_with_options(obj, 'context_specific:5;explicit;inner:0x0c')!
// assert explicit_out == [u8(0xA5), 0x04, 0x0C, 0x02, 0x68, 0x69]
// ```
pub fn encode_with_options(el Element, opt string) ![]u8 {
// treated as without option when empty
if opt.len == 0 {
return encode_with_rule(el, .der)!
}
fo := FieldOptions.from_string(opt)!
return encode_with_field_options(el, fo)!
}
// `encode_with_field_options` serializes this element into bytes array with options defined in fo.
pub fn encode_with_field_options(el Element, fo FieldOptions) ![]u8 {
// validates options again this element.
el.validate_options(fo)!
// check for default_value for this element
// if we have it matching with current element,
// by default, in .der mode, it should not be serialized.
if fo.has_default {
def_element := fo.default_value or { return error('bad default_value') }
// If this element is equal with default_value, by default its should not be serialized.
if el.equal(def_element) {
return []u8{}
}
}
// apply field options, turns this element
// into optional, wrapped element or original one.
new_el := el.apply_field_options(fo)!
// if new_el is Optional, encode with optional behaviour
if new_el is Optional {
return new_el.encode()!
}
// otherwise, just serializing it
return encode_with_rule(new_el, .der)!
}
// Helper for wrapping element
//
//
// into_optional turns this element into Optional with present bit.
// When you set with_present into true, its makes this optional was present.
fn (el Element) into_optional(with_present bool) !Element {
if el is Optional {
return error('already optional element')
}
return Optional.new(el, with_present)!
}
// apply_field_options applies rules in field options into current element
// and turns this into another element.
// by default, optional attribute is more higher precedence over wrapper attribut, ie,
// take the wrap step and then turn into optional (if true)
fn (el Element) apply_field_options(fo FieldOptions) !Element {
el.validate_options(fo)!
// if there a wrapper
if fo.cls != '' {
wrapped_el := el.wrap_with_options(fo)!
if fo.optional {
return wrapped_el.into_optional(fo.present)!
}
// not-optional, just return wrapped element
return wrapped_el
}
// no-wrapper, check for optional
if fo.optional {
return el.into_optional(fo.present)!
}
// otherwise, its no-wrapper and non-optional
return el
}
// set_default_value installs default value within FieldOptions for the element
pub fn (el Element) set_default_value(mut fo FieldOptions, value Element) ! {
// the default tag should match with the current tag
if !el.tag().equal(value.tag()) {
return error('unmatching tag of default value')
}
fo.install_default(value, false)!
el.validate_default(fo)!
}
// wrap_with_rule wraps universal element into another class.
// we prohibit dan defines some rules when its happen and returns an error instead
// 1. wrapping into .universal class is not allowed
// 2. wrapping with the same class is not allowed too
// 3. wrapping non-universal class element is not allowed (maybe removed on futures.)
// Notes :
// Three additional information about tagging:
// CHOICEs are always explicitly tagged even if implicit tagging is in effect.
// EXPLICIT TAGs are always constructed, they encapsulate the TLV they prefix.
// An IMPLICIT TAG 'inherits' the constructed bit of the TLV whose 'T' is overwritten,
// examples:
// a) '[5] IMPLICIT INTEGER' has tag 0x85 (overwriting 0x02 = INTEGER)
// b) '[5] IMPLICIT SEQUENCE' has tag 0xA5 (overwriting 0x30 = SEQUENCE, CONSTRUCTED)
fn (el Element) wrap_with_options(fo FieldOptions) !Element {
// validates options.
el.validate_options(fo)!
mode := TaggedMode.from_string(fo.mode)!
cls := TagClass.from_string(fo.cls)!
return wrap(el, cls, fo.tagnum, mode)!
}
// wrao performs wrapping to element and turns this element into another one.
fn wrap(el Element, cls TagClass, number int, mode TaggedMode) !Element {
if el is Optional {
return error('Optional cant be wrapped')
}
if cls == .universal {
return error('you cant wrap into universal')
}
match cls {
.context_specific {
return ContextElement.from_element(el, number, mode)!
}
.application {
return ApplicationElement.from_element(el, number, mode)!
}
.private {
return PrivateELement.from_element(el, number, mode)!
}
else {
return error('Wraps to the wrong class')
}
}
}
// encode_with_rule encodes element into bytes array with rule.
fn encode_with_rule(el Element, rule EncodingRule) ![]u8 {
if rule != .der && rule != .ber {
return error('Element: unsupported rule')
}
mut dst := []u8{}
// when this element is Optional without presence flag, by default would
// serialize this element into empty bytes otherwise, would serialize underlying element.
if el is Optional {
return el.encode()!
}
// otherwise, just serializes as normal
el.tag().encode_with_rule(mut dst, rule)!
// calculates the length of element, and serialize this length
payload := el.payload()!
length := Length.new(payload.len)!
length.encode_with_rule(mut dst, rule)!
// append the element payload to destination
dst << payload
return dst
}