Initial project import

This commit is contained in:
Agila Forge Importer committed 2026-07-13 15:20:32 +00:00
commit 23f2d29e32
11957 files changed
+1754577

No files matched your search

@@ -0,0 +1,18 @@
This file contains examples on about how to use this `asn1` module to write your ASN.1 element,
how to serialize it and write routines how to decode it. Its contains 4 of examples, ie:
- `example0.v` contains examples on how to write simple thing for some parts of
Kerberos Network Authentication protocol parser, how to define your structure
based on the ASN.1 Schema, use tagged field for wrapping element, how to do serialization
and deserialization of this object, etc.
- `example1.v` contains sample from already availables on the internet world, taken from
[oss](https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/sequence.html).
Its contains more complex structure, write serializer and deserializer for that.
- `example2.v` contains samples encoding for DER mode comes from `ITU X.690 Document`,
Especially from Annex A. Example of encodings of the document.
This examples contains some complex nested elements wrapping detailed in step by step
for building the ASN.1 DER serializer with this modules.
- `examples3.v` is an example from README files.
@@ -0,0 +1,113 @@
module main
import x.encoding.asn1
// Previously, defined type KerberosString = asn1.GeneralString directly
// without reduplicating required Element methods, would produces
// RUNTIME ERROR: invalid memory access.
// **Updates**
// Its has been resolved in [22901](https://github.com/vlang/v/issues/22901)
// Thanks to @felipensp
type KerberosString = asn1.GeneralString
fn KerberosString.new(s string) !KerberosString {
return KerberosString(asn1.GeneralString.new(s)!)
}
type KerberosStringList = []KerberosString
fn (ksl KerberosStringList) tag() asn1.Tag {
return asn1.default_sequence_tag
}
fn (ksl KerberosStringList) payload() ![]u8 {
mut out := []u8{}
for item in ksl {
// maybe produces x00000000: at ???: RUNTIME ERROR: invalid memory access
// `item` cannot be used as interface object outside `unsafe` blocks as it
// might be stored on stack. Consider declaring `KerberosString` as `@[heap]`
obj := unsafe { item }
out << asn1.encode(obj)!
}
return out
}
// PrincipalName ::= SEQUENCE {
// name-type [0] Int32,
// name-string [1] SEQUENCE OF KerberosString
// }
struct PrincipalName {
name_type asn1.Integer @[context_specific: 0; explicit; inner: 2] // integer tag = (universal, false, 2)
// defines name_string as asn1.SequenceOf[KerberosString] would produces unexpected result
// when you build payload with `asn1.make_payload`,
// see issues at https://github.com/vlang/v/issues/22721
// so we build with KerberosStringList
name_string KerberosStringList @[context_specific: 1; explicit; inner: 16] // sequence tag = (universal, true, 16)
}
fn (pn PrincipalName) tag() asn1.Tag {
return asn1.default_sequence_tag
}
fn (pn PrincipalName) payload() ![]u8 {
kd := asn1.new_key_default()
payload := asn1.make_payload[PrincipalName](pn, kd)!
return payload
}
fn PrincipalName.decode(bytes []u8) !PrincipalName {
// decode should produces Sequence type
elem := asn1.decode(bytes)!
assert elem.tag().equal(asn1.default_sequence_tag)
// cast it into Sequence type and get the fields
seq := elem.into_object[asn1.Sequence]()!
fields := seq.fields()
// every fields of the sequence is raw of wrapped element, so we should unwrap it with
// the same options used to wrap in encode step, and turn to the real underlying object.
el_name_type := fields[0].unwrap_with_options('context_specific: 0; explicit; inner: 2')!
name_type := el_name_type.into_object[asn1.Integer]()!
// tag 16 is sequence tag, its decoded into Sequence type
el_name_string := fields[1].unwrap_with_options('context_specific: 1; explicit; inner: 16')!
el_seq := el_name_string.into_object[asn1.Sequence]()!
// KerberosString string has a general_string tag, so it would be parsed as asn1.GeneralString
// so, we transforms it into KerberosString back and build KerberosStringList.
mut a := []KerberosString{}
for item in el_seq.fields() {
gst := item.into_object[asn1.GeneralString]()!
obj := KerberosString(gst)
a << obj
}
name_string_list := KerberosStringList(a)
return PrincipalName{
name_type: name_type
name_string: name_string_list
}
}
fn main() {
// Basically this is a Kerberos PrincipalName data
data := [u8(0x30), 0x15, 0xa0, 0x03, 0x02, 0x01, 0x01, 0xa1, 0x0e, 0x30, 0x0c, 0x1b, 0x0a,
0x62, 0x6f, 0x62, 0x62, 0x61, 0x2d, 0x66, 0x65, 0x74, 0x74]
els := [KerberosString.new('bobba-fett')!]
pn := PrincipalName{
name_type: asn1.Integer.from_int(1)
name_string: KerberosStringList(els)
}
out1 := asn1.encode(pn)!
back := PrincipalName.decode(data)!
dump(back == pn) // should assert to true
dump(out1.hex() == data.hex()) // should assert to true
// run this produces:
// [examples/examples_0.v:128] back == pn: true
// [examples/examples_0.v:129] out1.hex() == data.hex(): true
}
@@ -0,0 +1,105 @@
module main
import x.encoding.asn1
// This example was taken from https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/sequence.html
// But little modified with removed optional key in the structure and serialization.
// Example schema:
//
// ModuleName DEFINITIONS AUTOMATIC TAGS ::= BEGIN
// PersonelEntry ::= SEQUENCE {
// name OCTET STRING,
// location INTEGER { home(0), field(1), roving(2)} OPTIONAL,
// age INTEGER OPTIONAL
// }
// END
// The syntax above is equivalent with the following one:
// PersonelEntry ::= SEQUENCE {
// name [0] IMPLICIT OCTET STRING,
// location [1] IMPLICIT INTEGER {home(0), field(1), roving(2)} OPTIONAL,
// age [2] IMPLICIT INTEGER OPTIONAL
// }
struct PersonelEntry {
mut:
name asn1.OctetString @[context_specific: 0; implicit; inner: 4]
location asn1.Integer @[context_specific: 1; implicit; inner: 2]
age asn1.Integer @[context_specific: 2; implicit; inner: 2]
}
fn (pr PersonelEntry) tag() asn1.Tag {
return asn1.default_sequence_tag
}
fn (pr PersonelEntry) payload() ![]u8 {
mut out := []u8{}
// by default, in .der, optional element was not included in the output, so, we remove optional options here.
out << asn1.encode_with_options(pr.name, 'context_specific:0; implicit; inner:4')!
out << asn1.encode_with_options(pr.location, 'context_specific:1; implicit; inner:2')!
// the example the third element is optional, but in .der it would not be serializable until you set it to present
out << asn1.encode_with_options(pr.age, 'context_specific:2; implicit; inner:2')!
return out
}
// This is an example of way how we can write routine for decode PersonelEntry from bytes.
fn PersonelEntry.decode(bytes []u8) !PersonelEntry {
// decode should produces Sequence type
elem := asn1.decode(bytes)!
assert elem.tag().equal(asn1.default_sequence_tag)
// cast it into Sequence type and get the fields
seq := elem as asn1.Sequence
fields := seq.fields()
// every fields of the sequence is raw of wrapped element, so we should unwrap it with
// the same options used to wrap in encode step, and turn to the real underlying object.
el_name := fields[0].unwrap_with_options('context_specific:0; implicit; inner:4')!
name := el_name.into_object[asn1.OctetString]()!
el_location := fields[1].unwrap_with_options('context_specific:1; implicit; inner:2')!
location := el_location.into_object[asn1.Integer]()!
el_age := fields[2].unwrap_with_options('context_specific:2; implicit; inner:2')!
age := el_age.into_object[asn1.Integer]()!
return PersonelEntry{
name: name
location: location
age: age
}
}
// expected output :
// 30 10
// 80 08 6269672068656164 // bytestr: 'big head'
// 81 01 02
// 82 01 1A
fn main() {
expected_output := [u8(0x30), 0x10, u8(0x80), 0x08, 0x62, 0x69, 0x67, 0x20, 0x68, 0x65, 0x61,
0x64, u8(0x81), 0x01, 0x02, u8(0x82), 0x01, 0x1A]
rock_star1 := PersonelEntry{
name: asn1.OctetString.from_hexstring('6269672068656164')!
location: asn1.Integer.from_int(2)
age: asn1.Integer.from_int(26)
}
// serializes the object into bytes array and check the result
out := asn1.encode(rock_star1)!
dump(out == expected_output) // out == expected_output: true
// deserializes bytes back into PersonelEntry object.
out_back := PersonelEntry.decode(out)!
dump(out_back)
// out_back: PersonelEntry{
// name: OctetString (big head)
// location: Integer 2
// age: Integer 26
// }
dump(out_back.name == rock_star1.name) // true
dump(out_back.location == rock_star1.location) // true
dump(out_back.age == rock_star1.age) // true
}
@@ -0,0 +1,380 @@
module main
import x.encoding.asn1
// This example takes more complex scenario, in the sense of nested wrapping, the use of
// other class Element supported in this module.
//
// This examples is taken from ITU-T X.690 Information technology – ASN.1 encoding rules:
// Specification of Basic Encoding Rules (BER), Canonical Encoding Rules (CER) and
// Distinguished Encoding Rules (DER) document.
//
// Especially from Annex A. Example of encodings of the document.
// from A.1 ASN.1 description of the record structure.
// The structure of the hypothetical personnel record is formally described below using ASN.1 specified in
// ITU-T Rec. X.680 | ISO/IEC 8824-1 for defining types.
//
// PersonnelRecord ::= [APPLICATION 0] IMPLICIT SET {
// name Name,
// title [0] VisibleString,
// number EmployeeNumber,
// dateOfHire [1] Date,
// nameOfSpouse [2] Name,
// children [3] IMPLICIT SEQUENCE OF ChildInformation DEFAULT {}
// }
//
struct PersonnelRecord {
name Name
title asn1.VisibleString
number EmployeeNumber
date_of_hire Date
name_of_spouse Name
children asn1.SequenceOf[ChildInformation]
}
fn (pr PersonnelRecord) tag() asn1.Tag {
return asn1.default_set_tag
}
fn (pr PersonnelRecord) payload() ![]u8 {
mut out := []u8{}
out << asn1.encode(pr.name)!
out << asn1.encode_with_options(pr.title, 'context_specific:0;explicit;inner:26')!
out << asn1.encode(pr.number)!
out << asn1.encode_with_options(pr.date_of_hire,
'context_specific: 1; explicit; inner:application,false,3')!
out << asn1.encode_with_options(pr.name_of_spouse,
'context_specific: 2; explicit; inner:application,true,1')!
out << asn1.encode_with_options(pr.children, 'context_specific: 3; implicit; inner:16')!
return out
}
// deserializer of PersonellRecord bytes
fn PersonnelRecord.decode(bytes []u8) !PersonnelRecord {
// we perfrom decoding as a reverse of the serialization with the same options.
// after that, we get an Element (with the Set type of PersonellRecord)
el := asn1.decode_with_options(bytes, 'application:0;implicit;inner:17')!
assert el.tag() == asn1.default_set_tag
set := el.into_object[asn1.Set]()!
// fields is series of element ([]Element), PersonellRecord fields
fields := set.fields()
// we turn series of element into underlying desired type.
// its rather clumsy to transforms it, because Sequenve (and Set) fields is an []Element, so you should care
// to turn this into desired object
name_app := fields[0].into_object[asn1.ApplicationElement]()!
name := Name(name_app)
// Title
title_elem := fields[1].unwrap_with_options('context_specific:0;explicit;inner:26')!
title := title_elem.into_object[asn1.VisibleString]()!
// EmployeNumber
emp_num := fields[2].into_object[asn1.ApplicationElement]()!
employe_number := EmployeeNumber(emp_num)
// dateOfHire
doh :=
fields[3].unwrap_with_options('context_specific: 1; explicit; inner:application,false,3')!
doh_app := doh.into_object[asn1.ApplicationElement]()!
date_of_hire := Date(doh_app)
// nameOfSpouse
nosp :=
fields[4].unwrap_with_options('context_specific: 2; explicit; inner:application,true,1')!
nosp_app := nosp.into_object[asn1.ApplicationElement]()!
name_of_spouse := Name(nosp_app)
// The children is Sequence of Set, first we unwrap it then turn into sequence.
children := fields[5].unwrap_with_options('context_specific: 3; implicit; inner:16')!
children_seq := children.into_object[asn1.Sequence]()!
childset_fields := children_seq.fields() // []Element
mut childset := []ChildInformation{}
for item in childset_fields {
// item is an Element
obj := item.into_object[asn1.Set]()!
i := ChildInformation.from_set(obj)!
childset << i
}
pr := PersonnelRecord{
name: name
title: title
number: employe_number
date_of_hire: date_of_hire
name_of_spouse: name_of_spouse
children: asn1.SequenceOf.from_list[ChildInformation](childset)!
}
return pr
}
// ChildInformation ::= SET {
// name Name,
// dateOfBirth [0] Date
// }
//
// Name ::= [APPLICATION 1] IMPLICIT SEQUENCE {
// givenName VisibleString,
// initial VisibleString,
// familyName VisibleString
// }
//
// EmployeeNumber ::= [APPLICATION 2] IMPLICIT INTEGER
// Date ::= [APPLICATION 3] IMPLICIT VisibleString -- YYYYMMDD
// ChildInformation ::= SET {
// name Name,
// dateOfBirth [0] Date
// }
struct ChildInformation {
name Name
date_of_birth Date
}
// s should Set with series of Element with []ChildInformation within underlying fields.
fn ChildInformation.from_set(s asn1.Set) !ChildInformation {
if s.fields().len != 2 {
return error('Bad ChildInformation set')
}
fields := s.fields() // serialized name and dateOfBirth of ChildInformation
name := fields[0].into_object[asn1.ApplicationElement]()!
doh :=
fields[1].unwrap_with_options('context_specific: 0; explicit; inner:application,false,3')!
date := doh.into_object[asn1.ApplicationElement]()!
ch := ChildInformation{
name: Name(name)
date_of_birth: Date(date)
}
return ch
}
fn (ci ChildInformation) tag() asn1.Tag {
return asn1.default_set_tag
}
fn (ci ChildInformation) payload() ![]u8 {
mut out := []u8{}
out << asn1.encode(ci.name)!
out << asn1.encode_with_options(ci.date_of_birth,
'context_specific: 0; explicit; inner:application,false,3')!
return out
}
// EmployeeNumber ::= [APPLICATION 2] IMPLICIT INTEGER
type EmployeeNumber = asn1.ApplicationElement
fn EmployeeNumber.new(val asn1.Integer) !asn1.ApplicationElement {
return asn1.ApplicationElement.from_element(val, 2, .implicit)!
}
// Date ::= [APPLICATION 3] IMPLICIT VisibleString -- YYYYMMDD
type Date = asn1.ApplicationElement
fn Date.new(val asn1.VisibleString) !asn1.ApplicationElement {
return asn1.ApplicationElement.from_element(val, 3, .implicit)!
}
// Name ::= [APPLICATION 1] IMPLICIT SEQUENCE {
// givenName VisibleString,
// initial VisibleString,
// familyName VisibleString
// }
type Name = asn1.ApplicationElement
fn Name.new(el NameEntry) !asn1.ApplicationElement {
return asn1.ApplicationElement.from_element(el, 1, .implicit)!
}
struct NameEntry {
given_name asn1.VisibleString
initial asn1.VisibleString
family_name asn1.VisibleString
}
fn (n NameEntry) tag() asn1.Tag {
return asn1.default_sequence_tag
}
fn (n NameEntry) payload() ![]u8 {
mut out := []u8{}
out << asn1.encode(n.given_name)!
out << asn1.encode(n.initial)!
out << asn1.encode(n.family_name)!
return out
}
// The value of John Smith's personnel record is formally described below using ASN.1.
// { name {givenName "John",initial "P",familyName "Smith"},
// title "Director",
// number 51,
// dateOfHire "19710917",
// nameOfSpouse {givenName "Mary",initial "T",familyName "Smith"},
// children {
// { name {givenName "Ralph",initial "T",familyName "Smith"},
// dateOfBirth "19571111"
// },
// { name {givenName "Susan",initial "B",familyName "Jones"},
// dateOfBirth "19590717"
// }
// }
// }
// Representation of this record value
//
// 60 8185
// 61 10 1A 04 'John' // name
// 1A 01 'P'
// 1A 05 'Smith'
// A0 0A 1A 08 'Director' // title
// 42 01 33 // number
// A1 0A 43 08 '19710917' // dateOfHire
// A2 12 61 10 1A 04 'Mary' // nameOfSpouse
// 1A 01 'T'
// 1A 05 'Smith'
// A3 42 31 1F 61 11 1A 05 'Ralph' => 52 61 6c 70 68 // children
// 1A 01 'T' => 54
// 1A 05 'Smith' => 53 6d 69 74 68
// A0 0A 43 08 '19571111' => 31 39 35 37 31 31 31 31
// 31 1F 61 11 1A 05 'Susan' => 53 75 73 61 6e
// 1A 01 'B' => 42
// 1A 05 'Jones' => 4a 6f 6e 65 73
// A0 0A 43 08 '19590717' => 31 39 35 39 30 37 31 37
fn main() {
// We detailed every pieces of element
// PersonnelRecord.name
pr_name := Name.new(NameEntry{
given_name: asn1.VisibleString.new('John')!
initial: asn1.VisibleString.new('P')!
family_name: asn1.VisibleString.new('Smith')!
})!
// 61 10 1A 04 'John' => 4a 6f 68 6e // name
// 1A 01 'P' => 50
// 1A 05 'Smith' => 53 6d 69 74 68
pr_name_bytes := [u8(0x61), 0x10, 0x1A, 0x04, 0x4a, 0x6f, 0x68, 0x6e, 0x1A, 0x01, 0x50, 0x1A,
0x05, 0x53, 0x6d, 0x69, 0x74, 0x68]
assert asn1.encode(pr_name)! == pr_name_bytes
// PersonnelRecord.title
// A0 0A 1A 08 'Director' => 44 69 72 65 63 74 6f 72 // title
title := asn1.VisibleString.new('Director')!
title_bytes := [u8(0xA0), 0x0A, 0x1A, 0x08, u8(0x44), 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72]
assert asn1.encode_with_options(title, 'context_specific:0;explicit;inner:26')! == title_bytes
// PersonnelRecord.EmployeeNumber
// 42 01 33 // number
emp_num := EmployeeNumber.new(asn1.Integer.from_int(51))!
emp_bytes := [u8(0x42), 0x01, 0x33]
assert asn1.encode(emp_num)! == emp_bytes
// PersonnelRecord.dateOfHire
// A1 0A 43 08 '19710917' => 31 39 37 31 30 39 31 37 // dateOfHire
// dateOfHire [1] Date,
doh := Date.new(asn1.VisibleString.new('19710917')!)!
doh_bytes := [u8(0xA1), 0x0A, 0x43, 0x08, 0x31, 0x39, 0x37, 0x31, 0x30, 0x39, 0x31, 0x37]
assert asn1.encode_with_options(doh, 'context_specific: 1; explicit; inner:application,false,3')! == doh_bytes
// PersonnelRecord.nameOfSpouse
// A2 12 61 10 1A 04 'Mary' => 4d 61 72 79 // nameOfSpouse
// 1A 01 'T' => 54
// 1A 05 'Smith' => 53 6d 69 74 68
nosp := Name.new(NameEntry{
given_name: asn1.VisibleString.new('Mary')!
initial: asn1.VisibleString.new('T')!
family_name: asn1.VisibleString.new('Smith')!
})!
nosp_bytes := [u8(0xA2), 0x12, 0x61, 0x10, 0x1A, 0x04, 0x4d, 0x61, 0x72, 0x79, 0x1A, 0x01,
0x54, 0x1A, 0x05, 0x53, 0x6d, 0x69, 0x74, 0x68]
// nameOfSpouse [2] Name,
assert asn1.encode_with_options(nosp, 'context_specific: 2; explicit; inner:application,true,1')! == nosp_bytes
// { name {givenName "Ralph",initial "T",familyName "Smith"},
// dateOfBirth "19571111"
// },
childinfo0 := ChildInformation{
name: Name.new(NameEntry{
given_name: asn1.VisibleString.new('Ralph')!
initial: asn1.VisibleString.new('T')!
family_name: asn1.VisibleString.new('Smith')!
})!
date_of_birth: Date.new(asn1.VisibleString.new('19571111')!)!
}
// 31 1F 61 11 1A 05 'Ralph' => 52 61 6c 70 68
// 1A 01 'T' => 54
// 1A 05 'Smith' => 53 6d 69 74 68
// A0 0A 43 08 '19571111' => 31 39 35 37 31 31 31 31
ch0_bytes := [u8(0x31), 0x1F, 0x61, 0x11, 0x1A, 0x05, 0x52, 0x61, 0x6c, 0x70, 0x68, 0x1A, 0x01,
0x54, 0x1A, 0x05, 0x53, 0x6d, 0x69, 0x74, 0x68, 0xA0, 0x0A, 0x43, 0x08, 0x31, 0x39, 0x35,
0x37, 0x31, 0x31, 0x31, 0x31]
assert asn1.encode(childinfo0)! == ch0_bytes
childinfo1 := ChildInformation{
name: Name.new(NameEntry{
given_name: asn1.VisibleString.new('Susan')!
initial: asn1.VisibleString.new('B')!
family_name: asn1.VisibleString.new('Jones')!
})!
date_of_birth: Date.new(asn1.VisibleString.new('19590717')!)!
}
// 31 1F 61 11 1A 05 'Susan' => 53 75 73 61 6e
// 1A 01 'B' => 42
// 1A 05 'Jones' => 4a 6f 6e 65 73
// A0 0A 43 08 '19590717' => 31 39 35 39 30 37 31 37
ch1_bytes := [u8(0x31), 0x1F, 0x61, 0x11, 0x1A, 0x05, 0x53, 0x75, 0x73, 0x61, 0x6e, 0x1A, 0x01,
0x42, 0x1A, 0x05, 0x4a, 0x6f, 0x6e, 0x65, 0x73, 0xA0, 0x0A, 0x43, 0x08, 0x31, 0x39, 0x35,
0x39, 0x30, 0x37, 0x31, 0x37]
assert asn1.encode(childinfo1)! == ch1_bytes
// PersonnelRecord.children
children := asn1.SequenceOf.from_list[ChildInformation]([childinfo0, childinfo1])!
// A3 42 31 1F 61 11 1A 05 'Ralph' => 52 61 6c 70 68 // children
// 1A 01 'T' => 54
// 1A 05 'Smith' => 53 6d 69 74 68
// A0 0A 43 08 '19571111' => 31 39 35 37 31 31 31 31
// 31 1F 61 11 1A 05 'Susan' => 53 75 73 61 6e
// 1A 01 'B' => 42
// 1A 05 'Jones' => 4a 6f 6e 65 73
// A0 0A 43 08 '19590717' => 31 39 35 39 30 37 31 37
children_bytes := [u8(0xA3), 0x42, u8(0x31), 0x1F, 0x61, 0x11, 0x1A, 0x05, 0x52, 0x61, 0x6c,
0x70, 0x68, 0x1A, 0x01, 0x54, 0x1A, 0x05, 0x53, 0x6d, 0x69, 0x74, 0x68, 0xA0, 0x0A, 0x43,
0x08, 0x31, 0x39, 0x35, 0x37, 0x31, 0x31, 0x31, 0x31, u8(0x31), 0x1F, 0x61, 0x11, 0x1A,
0x05, 0x53, 0x75, 0x73, 0x61, 0x6e, 0x1A, 0x01, 0x42, 0x1A, 0x05, 0x4a, 0x6f, 0x6e, 0x65,
0x73, 0xA0, 0x0A, 0x43, 0x08, 0x31, 0x39, 0x35, 0x39, 0x30, 0x37, 0x31, 0x37]
assert asn1.encode_with_options(children, 'context_specific: 3; implicit; inner:16')! == children_bytes
// PersonnelRecord entries
pr := PersonnelRecord{
name: pr_name
title: title
number: emp_num
date_of_hire: doh
name_of_spouse: nosp
children: children
}
mut expected_record_bytes := [u8(0x60), 0x81, 0x85]
expected_record_bytes << pr_name_bytes
expected_record_bytes << title_bytes
expected_record_bytes << emp_bytes
expected_record_bytes << doh_bytes
expected_record_bytes << nosp_bytes
expected_record_bytes << children_bytes
// PersonnelRecord ::= [APPLICATION 0] IMPLICIT SET {
pr_record_output := asn1.encode_with_options(pr, 'application:0;implicit;inner:17')!
assert pr_record_output == expected_record_bytes
pr_record := PersonnelRecord.decode(pr_record_output)!
pr_record_encoded_back :=
asn1.encode_with_options(pr_record, 'application:0;implicit;inner:17')!
dump(pr_record_encoded_back == expected_record_bytes) // true
}
@@ -0,0 +1,88 @@
module main
import x.encoding.asn1
// This example of encode a sequence containing a UTF-8 string, an integer
// and an explicitly tagged object identifier, conforming to the following
// ASN.1 schema specification:
// ```asn.1
// Example ::= SEQUENCE {
// greeting UTF8String,
// answer INTEGER,
// type [1] EXPLICIT OBJECT IDENTIFIER
// }
// ```
// You can represent above structure with related structure in `v`, similar like:
struct Example {
greeting asn1.Utf8String
answer asn1.Integer
// you can tag your struct fields with supported options.
tipe asn1.ObjectIdentifier @[context_specific: 1; explicit; inner: 6]
}
fn (ex Example) tag() asn1.Tag {
return asn1.default_sequence_tag
}
// you can build your payload manually or use `asn1.make_payload`, but with aware,
// if your structure contains generic, its maybe not work (currently).
fn (ex Example) payload() ![]u8 {
kd := asn1.KeyDefault(map[string]asn1.Element{})
payload := asn1.make_payload[Example](ex, kd)!
return payload
}
// You can write routines for deserialize Example structure. This is only examples way,
// but its possible to use other way with the help from this module, like use
// `Parser` codec.
fn Example.decode(bytes []u8) !Example {
// just call raw .decode on bytes
// by example, its should produce sequence type.
elem := asn1.decode(bytes)!
assert elem.tag().equal(asn1.default_sequence_tag) // should true
// cast produced element into Sequence type and get the fields.
seq := elem.into_object[asn1.Sequence]()!
fields := seq.fields()
// and then, turn every field into desired object based your schema.
// first two field is not wrapped element, so just turn into real object
greeting := fields[0].into_object[asn1.Utf8String]()!
answer := fields[1].into_object[asn1.Integer]()!
// the third field is context_specific wrapped element, just unwrap it with the
// same options used to encode
oid_tipe := fields[2].unwrap_with_options('context_specific:1;explicit; inner:6')!
tipe := oid_tipe.into_object[asn1.ObjectIdentifier]()!
// then build your Example structure
ex := Example{
greeting: greeting
answer: answer
tipe: tipe
}
return ex
}
fn main() {
expected_output := [u8(0x30), 18, u8(12), 5, 72, 101, 108, 108, 111, u8(2), 1, 42, u8(0xA1),
6, 6, 4, 43, 6, 1, 3]
ex := Example{
greeting: asn1.Utf8String.new('Hello')!
answer: asn1.Integer.from_int(42)
tipe: asn1.ObjectIdentifier.new('1.3.6.1.3')!
}
// serialize the Example object
out := asn1.encode(ex)!
assert out == expected_output
// test with data
example_obj := Example.decode(out)!
dump(ex.greeting == example_obj.greeting)
dump(ex.answer == example_obj.answer)
dump(ex.tipe == example_obj.tipe)
}