| 1 | module main |
| 2 | |
| 3 | import x.encoding.asn1 |
| 4 | |
| 5 | // This example was taken from https://www.oss.com/asn1/resources/asn1-made-simple/asn1-quick-reference/sequence.html |
| 6 | // But little modified with removed optional key in the structure and serialization. |
| 7 | // Example schema: |
| 8 | // |
| 9 | // ModuleName DEFINITIONS AUTOMATIC TAGS ::= BEGIN |
| 10 | // PersonelEntry ::= SEQUENCE { |
| 11 | // name OCTET STRING, |
| 12 | // location INTEGER { home(0), field(1), roving(2)} OPTIONAL, |
| 13 | // age INTEGER OPTIONAL |
| 14 | // } |
| 15 | // END |
| 16 | |
| 17 | // The syntax above is equivalent with the following one: |
| 18 | |
| 19 | // PersonelEntry ::= SEQUENCE { |
| 20 | // name [0] IMPLICIT OCTET STRING, |
| 21 | // location [1] IMPLICIT INTEGER {home(0), field(1), roving(2)} OPTIONAL, |
| 22 | // age [2] IMPLICIT INTEGER OPTIONAL |
| 23 | // } |
| 24 | struct PersonelEntry { |
| 25 | mut: |
| 26 | name asn1.OctetString @[context_specific: 0; implicit; inner: 4] |
| 27 | location asn1.Integer @[context_specific: 1; implicit; inner: 2] |
| 28 | age asn1.Integer @[context_specific: 2; implicit; inner: 2] |
| 29 | } |
| 30 | |
| 31 | fn (pr PersonelEntry) tag() asn1.Tag { |
| 32 | return asn1.default_sequence_tag |
| 33 | } |
| 34 | |
| 35 | fn (pr PersonelEntry) payload() ![]u8 { |
| 36 | mut out := []u8{} |
| 37 | |
| 38 | // by default, in .der, optional element was not included in the output, so, we remove optional options here. |
| 39 | out << asn1.encode_with_options(pr.name, 'context_specific:0; implicit; inner:4')! |
| 40 | out << asn1.encode_with_options(pr.location, 'context_specific:1; implicit; inner:2')! |
| 41 | // the example the third element is optional, but in .der it would not be serializable until you set it to present |
| 42 | out << asn1.encode_with_options(pr.age, 'context_specific:2; implicit; inner:2')! |
| 43 | |
| 44 | return out |
| 45 | } |
| 46 | |
| 47 | // This is an example of way how we can write routine for decode PersonelEntry from bytes. |
| 48 | fn PersonelEntry.decode(bytes []u8) !PersonelEntry { |
| 49 | // decode should produces Sequence type |
| 50 | elem := asn1.decode(bytes)! |
| 51 | assert elem.tag().equal(asn1.default_sequence_tag) |
| 52 | |
| 53 | // cast it into Sequence type and get the fields |
| 54 | seq := elem as asn1.Sequence |
| 55 | fields := seq.fields() |
| 56 | |
| 57 | // every fields of the sequence is raw of wrapped element, so we should unwrap it with |
| 58 | // the same options used to wrap in encode step, and turn to the real underlying object. |
| 59 | el_name := fields[0].unwrap_with_options('context_specific:0; implicit; inner:4')! |
| 60 | name := el_name.into_object[asn1.OctetString]()! |
| 61 | |
| 62 | el_location := fields[1].unwrap_with_options('context_specific:1; implicit; inner:2')! |
| 63 | location := el_location.into_object[asn1.Integer]()! |
| 64 | |
| 65 | el_age := fields[2].unwrap_with_options('context_specific:2; implicit; inner:2')! |
| 66 | age := el_age.into_object[asn1.Integer]()! |
| 67 | |
| 68 | return PersonelEntry{ |
| 69 | name: name |
| 70 | location: location |
| 71 | age: age |
| 72 | } |
| 73 | } |
| 74 | |
| 75 | // expected output : |
| 76 | // 30 10 |
| 77 | // 80 08 6269672068656164 // bytestr: 'big head' |
| 78 | // 81 01 02 |
| 79 | // 82 01 1A |
| 80 | fn main() { |
| 81 | expected_output := [u8(0x30), 0x10, u8(0x80), 0x08, 0x62, 0x69, 0x67, 0x20, 0x68, 0x65, 0x61, |
| 82 | 0x64, u8(0x81), 0x01, 0x02, u8(0x82), 0x01, 0x1A] |
| 83 | |
| 84 | rock_star1 := PersonelEntry{ |
| 85 | name: asn1.OctetString.from_hexstring('6269672068656164')! |
| 86 | location: asn1.Integer.from_int(2) |
| 87 | age: asn1.Integer.from_int(26) |
| 88 | } |
| 89 | |
| 90 | // serializes the object into bytes array and check the result |
| 91 | out := asn1.encode(rock_star1)! |
| 92 | dump(out == expected_output) // out == expected_output: true |
| 93 | |
| 94 | // deserializes bytes back into PersonelEntry object. |
| 95 | out_back := PersonelEntry.decode(out)! |
| 96 | dump(out_back) |
| 97 | // out_back: PersonelEntry{ |
| 98 | // name: OctetString (big head) |
| 99 | // location: Integer 2 |
| 100 | // age: Integer 26 |
| 101 | // } |
| 102 | dump(out_back.name == rock_star1.name) // true |
| 103 | dump(out_back.location == rock_star1.location) // true |
| 104 | dump(out_back.age == rock_star1.age) // true |
| 105 | } |
| 106 | |