mirror of
https://github.com/vlang/v.git
synced 2025-09-09 07:15:50 -04:00
203 lines
6.1 KiB
V
203 lines
6.1 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 of deserialization of bytes array into some Element.
|
|
//
|
|
|
|
// decode decodes single element from bytes, its not allowing trailing data.
|
|
//
|
|
// Examples:
|
|
//
|
|
// Original object was Utf8String with tag == 12 (0c)
|
|
// ```v
|
|
// import x.encoding.asn1
|
|
//
|
|
// original_obj := asn1.Utf8String.new('hi')!
|
|
// bytes_data := [u8(0x0C), 0x02, 0x68, 0x69]
|
|
// decoded_obj := asn1.decode(bytes_data)!
|
|
// assert decoded_obj.equal(original_obj)
|
|
// ```
|
|
pub fn decode(bytes []u8) !Element {
|
|
// call Element.decode_with_rule directly
|
|
el, pos := Element.decode_with_rule(bytes, 0, .der)!
|
|
if pos > bytes.len {
|
|
return error('decode on data with trailing data')
|
|
}
|
|
return el
|
|
}
|
|
|
|
// decode_with_options decodes single element from bytes with options support, its not allowing trailing data.
|
|
// Its accepts options string to drive decoding process.
|
|
//
|
|
// Examples:
|
|
//
|
|
// `UTF8String` with implicit tagging definded as `[5] IMPLICIT UTF8String` was encoded into `85 02 68 69`
|
|
//
|
|
// ```v
|
|
// original_obj := asn1.Utf8String.new('hi')!
|
|
// implicit_bytes := [u8(0x85), 0x02, 0x68, 0x69]
|
|
// obj_2 := asn1.decode_with_options(implicit_bytes, 'context_specific:5;implicit;inner:12')!
|
|
//
|
|
// assert obj_2.equal(original_obj)
|
|
// dump(obj_2) // Output: obj_2: asn1.Element(Utf8String: (hi))
|
|
// ```
|
|
//
|
|
// `UTF8String` with explicit tagging defined as `[5] EXPLICIT UTF8String` was encoded into `A5 04 0C 02 68 69`
|
|
//
|
|
// ```v
|
|
// explicit_bytes := [u8(0xA5), 0x04, 0x0C, 0x02, 0x68, 0x69]
|
|
// obj_3 := asn1.decode_with_options(explicit_bytes, 'context_specific:5;explicit;inner:0x0c')!
|
|
//
|
|
// assert obj_3.equal(original_obj)
|
|
// dump(obj_3) // output: obj_3: asn1.Element(Utf8String: (hi))
|
|
// ```
|
|
pub fn decode_with_options(bytes []u8, opt string) !Element {
|
|
// if null-option length, call Element.decode_with_rule directly
|
|
if opt.len == 0 {
|
|
el, pos := Element.decode_with_rule(bytes, 0, .der)!
|
|
if pos > bytes.len {
|
|
return error('decode on data with trailing data')
|
|
}
|
|
return el
|
|
}
|
|
fo := FieldOptions.from_string(opt)!
|
|
return decode_with_field_options(bytes, fo)!
|
|
}
|
|
|
|
// decode_with_field_options is similar to `decode_with_options`, but its more controllable through FieldOptions.
|
|
pub fn decode_with_field_options(bytes []u8, fo FieldOptions) !Element {
|
|
// TODO
|
|
if bytes.len == 0 {
|
|
return error('Empty bytes')
|
|
}
|
|
fo.check_wrapper()!
|
|
|
|
// check for optional, and return it, maybe nil optional
|
|
// expected optional tag is outer wrapper tag
|
|
wrp_tag := fo.wrapper_tag()!
|
|
if fo.optional {
|
|
opt := decode_optional(bytes, wrp_tag)!
|
|
return opt
|
|
}
|
|
// read an element from bytes
|
|
mut p := Parser.new(bytes)
|
|
tlv := p.read_tlv()!
|
|
p.finish()!
|
|
|
|
// semantically no wraps
|
|
if fo.cls == '' {
|
|
return tlv
|
|
}
|
|
|
|
// wrapped
|
|
if tlv.tag().class != wrp_tag.class {
|
|
return error('Get different class')
|
|
}
|
|
if tlv.tag().number != wrp_tag.number {
|
|
return error('Get different tag number')
|
|
}
|
|
// TODO: default
|
|
return tlv.unwrap_with_field_options(fo)!
|
|
}
|
|
|
|
fn decode_optional(bytes []u8, expected_tag Tag) !Element {
|
|
mut p := Parser.new(bytes)
|
|
ct := p.peek_tag()!
|
|
// when the tag is equal expected_tag, this mean, present this optional element
|
|
if ct.equal(expected_tag) {
|
|
// present
|
|
el := p.read_tlv()!
|
|
mut opt := Optional.new(el, true)!
|
|
return opt
|
|
}
|
|
// optional element with no-presence semantic
|
|
el := RawElement.new(expected_tag, []u8{})!
|
|
opt := Optional.new(el, false)!
|
|
return opt
|
|
}
|
|
|
|
// unwrap_with_options performs unwrapping operations to the element with options provided.
|
|
// Its technically reverse operation of the `.wrap()` applied to the element
|
|
// with the same options. If you provide with diferent options,
|
|
// the result is in undesired behaviour, even its success
|
|
pub fn (el Element) unwrap_with_options(opt string) !Element {
|
|
if opt.len == 0 {
|
|
return el
|
|
}
|
|
fo := FieldOptions.from_string(opt)!
|
|
return el.unwrap_with_field_options(fo)!
|
|
}
|
|
|
|
// unwrap_with_field_options performs unwrapping operations with FieldOptions.
|
|
pub fn (el Element) unwrap_with_field_options(fo FieldOptions) !Element {
|
|
if fo.cls == '' {
|
|
// no unwrap
|
|
return el
|
|
}
|
|
el.validate_options(fo)!
|
|
// first, checks class of the element being to unwrap, should not universal class.
|
|
if el.tag().class == .universal {
|
|
return error('you cant unwrap universal element')
|
|
}
|
|
// its also happens to fo.cls, should not be an universal class
|
|
if fo.cls == 'universal' {
|
|
return error('you cant unwrap universal element')
|
|
}
|
|
|
|
// element being unwrap should have matching with tag within options.
|
|
mode := TaggedMode.from_string(fo.mode)!
|
|
inner_tag := fo.inner_tag()!
|
|
if mode == .explicit {
|
|
if !el.tag().constructed {
|
|
return error('explicit mode should have constructed tag')
|
|
}
|
|
// checks inner tag from payload
|
|
tag, _ := Tag.decode_with_rule(el.payload()!, 0, .der)!
|
|
if !tag.equal(inner_tag) {
|
|
return error('Get unexpected inner tag from payload')
|
|
}
|
|
}
|
|
inner_form := inner_tag.constructed
|
|
constructed := if mode == .explicit { true } else { inner_form }
|
|
|
|
// check for tag equality
|
|
// The tag of element being unwrapped with tag from FieldOptions should matching.
|
|
// Its should comes from same options on wrapping.
|
|
cls := TagClass.from_string(fo.cls)!
|
|
if el.tag().class != cls {
|
|
return error('unmatching outer tag class')
|
|
}
|
|
built_tag := Tag.new(cls, constructed, fo.tagnum)!
|
|
// check outer tag equality
|
|
if !el.tag().equal(built_tag) {
|
|
return error('Element tag unequal with tag from options')
|
|
}
|
|
|
|
// return unwrapped element
|
|
return unwrap(el, mode, inner_tag)!
|
|
}
|
|
|
|
// unwrap the provided element, turn into inner element.
|
|
fn unwrap(el Element, mode TaggedMode, inner_tag Tag) !Element {
|
|
match mode {
|
|
.explicit {
|
|
// el.payload is serialized of inner element
|
|
bytes := el.payload()!
|
|
inner_elem := decode(bytes)!
|
|
// recheck the tag
|
|
if !inner_elem.tag().equal(inner_tag) {
|
|
return error('unmatching inner_tag')
|
|
}
|
|
return inner_elem
|
|
}
|
|
.implicit {
|
|
// el.payload() is content of inner element
|
|
bytes := el.payload()!
|
|
inner_elem := parse_element(inner_tag, bytes)!
|
|
return inner_elem
|
|
}
|
|
}
|
|
}
|