| 1 | module main |
| 2 | |
| 3 | import x.encoding.asn1 |
| 4 | |
| 5 | // This example takes more complex scenario, in the sense of nested wrapping, the use of |
| 6 | // other class Element supported in this module. |
| 7 | // |
| 8 | // This examples is taken from ITU-T X.690 Information technology – ASN.1 encoding rules: |
| 9 | // Specification of Basic Encoding Rules (BER), Canonical Encoding Rules (CER) and |
| 10 | // Distinguished Encoding Rules (DER) document. |
| 11 | // |
| 12 | // Especially from Annex A. Example of encodings of the document. |
| 13 | |
| 14 | // from A.1 ASN.1 description of the record structure. |
| 15 | // The structure of the hypothetical personnel record is formally described below using ASN.1 specified in |
| 16 | // ITU-T Rec. X.680 | ISO/IEC 8824-1 for defining types. |
| 17 | // |
| 18 | // PersonnelRecord ::= [APPLICATION 0] IMPLICIT SET { |
| 19 | // name Name, |
| 20 | // title [0] VisibleString, |
| 21 | // number EmployeeNumber, |
| 22 | // dateOfHire [1] Date, |
| 23 | // nameOfSpouse [2] Name, |
| 24 | // children [3] IMPLICIT SEQUENCE OF ChildInformation DEFAULT {} |
| 25 | // } |
| 26 | // |
| 27 | struct PersonnelRecord { |
| 28 | name Name |
| 29 | title asn1.VisibleString |
| 30 | number EmployeeNumber |
| 31 | date_of_hire Date |
| 32 | name_of_spouse Name |
| 33 | children asn1.SequenceOf[ChildInformation] |
| 34 | } |
| 35 | |
| 36 | fn (pr PersonnelRecord) tag() asn1.Tag { |
| 37 | return asn1.default_set_tag |
| 38 | } |
| 39 | |
| 40 | fn (pr PersonnelRecord) payload() ![]u8 { |
| 41 | mut out := []u8{} |
| 42 | out << asn1.encode(pr.name)! |
| 43 | out << asn1.encode_with_options(pr.title, 'context_specific:0;explicit;inner:26')! |
| 44 | out << asn1.encode(pr.number)! |
| 45 | out << asn1.encode_with_options(pr.date_of_hire, |
| 46 | 'context_specific: 1; explicit; inner:application,false,3')! |
| 47 | out << asn1.encode_with_options(pr.name_of_spouse, |
| 48 | 'context_specific: 2; explicit; inner:application,true,1')! |
| 49 | out << asn1.encode_with_options(pr.children, 'context_specific: 3; implicit; inner:16')! |
| 50 | |
| 51 | return out |
| 52 | } |
| 53 | |
| 54 | // deserializer of PersonellRecord bytes |
| 55 | fn PersonnelRecord.decode(bytes []u8) !PersonnelRecord { |
| 56 | // we perfrom decoding as a reverse of the serialization with the same options. |
| 57 | // after that, we get an Element (with the Set type of PersonellRecord) |
| 58 | el := asn1.decode_with_options(bytes, 'application:0;implicit;inner:17')! |
| 59 | assert el.tag() == asn1.default_set_tag |
| 60 | set := el.into_object[asn1.Set]()! |
| 61 | |
| 62 | // fields is series of element ([]Element), PersonellRecord fields |
| 63 | fields := set.fields() |
| 64 | |
| 65 | // we turn series of element into underlying desired type. |
| 66 | // its rather clumsy to transforms it, because Sequenve (and Set) fields is an []Element, so you should care |
| 67 | // to turn this into desired object |
| 68 | name_app := fields[0].into_object[asn1.ApplicationElement]()! |
| 69 | name := Name(name_app) |
| 70 | |
| 71 | // Title |
| 72 | title_elem := fields[1].unwrap_with_options('context_specific:0;explicit;inner:26')! |
| 73 | title := title_elem.into_object[asn1.VisibleString]()! |
| 74 | |
| 75 | // EmployeNumber |
| 76 | emp_num := fields[2].into_object[asn1.ApplicationElement]()! |
| 77 | employe_number := EmployeeNumber(emp_num) |
| 78 | |
| 79 | // dateOfHire |
| 80 | doh := |
| 81 | fields[3].unwrap_with_options('context_specific: 1; explicit; inner:application,false,3')! |
| 82 | doh_app := doh.into_object[asn1.ApplicationElement]()! |
| 83 | date_of_hire := Date(doh_app) |
| 84 | |
| 85 | // nameOfSpouse |
| 86 | nosp := |
| 87 | fields[4].unwrap_with_options('context_specific: 2; explicit; inner:application,true,1')! |
| 88 | nosp_app := nosp.into_object[asn1.ApplicationElement]()! |
| 89 | name_of_spouse := Name(nosp_app) |
| 90 | |
| 91 | // The children is Sequence of Set, first we unwrap it then turn into sequence. |
| 92 | children := fields[5].unwrap_with_options('context_specific: 3; implicit; inner:16')! |
| 93 | children_seq := children.into_object[asn1.Sequence]()! |
| 94 | childset_fields := children_seq.fields() // []Element |
| 95 | |
| 96 | mut childset := []ChildInformation{} |
| 97 | for item in childset_fields { |
| 98 | // item is an Element |
| 99 | obj := item.into_object[asn1.Set]()! |
| 100 | i := ChildInformation.from_set(obj)! |
| 101 | childset << i |
| 102 | } |
| 103 | |
| 104 | pr := PersonnelRecord{ |
| 105 | name: name |
| 106 | title: title |
| 107 | number: employe_number |
| 108 | date_of_hire: date_of_hire |
| 109 | name_of_spouse: name_of_spouse |
| 110 | children: asn1.SequenceOf.from_list[ChildInformation](childset)! |
| 111 | } |
| 112 | return pr |
| 113 | } |
| 114 | |
| 115 | // ChildInformation ::= SET { |
| 116 | // name Name, |
| 117 | // dateOfBirth [0] Date |
| 118 | // } |
| 119 | // |
| 120 | // Name ::= [APPLICATION 1] IMPLICIT SEQUENCE { |
| 121 | // givenName VisibleString, |
| 122 | // initial VisibleString, |
| 123 | // familyName VisibleString |
| 124 | // } |
| 125 | // |
| 126 | // EmployeeNumber ::= [APPLICATION 2] IMPLICIT INTEGER |
| 127 | // Date ::= [APPLICATION 3] IMPLICIT VisibleString -- YYYYMMDD |
| 128 | |
| 129 | // ChildInformation ::= SET { |
| 130 | // name Name, |
| 131 | // dateOfBirth [0] Date |
| 132 | // } |
| 133 | struct ChildInformation { |
| 134 | name Name |
| 135 | date_of_birth Date |
| 136 | } |
| 137 | |
| 138 | // s should Set with series of Element with []ChildInformation within underlying fields. |
| 139 | fn ChildInformation.from_set(s asn1.Set) !ChildInformation { |
| 140 | if s.fields().len != 2 { |
| 141 | return error('Bad ChildInformation set') |
| 142 | } |
| 143 | fields := s.fields() // serialized name and dateOfBirth of ChildInformation |
| 144 | name := fields[0].into_object[asn1.ApplicationElement]()! |
| 145 | doh := |
| 146 | fields[1].unwrap_with_options('context_specific: 0; explicit; inner:application,false,3')! |
| 147 | date := doh.into_object[asn1.ApplicationElement]()! |
| 148 | ch := ChildInformation{ |
| 149 | name: Name(name) |
| 150 | date_of_birth: Date(date) |
| 151 | } |
| 152 | return ch |
| 153 | } |
| 154 | |
| 155 | fn (ci ChildInformation) tag() asn1.Tag { |
| 156 | return asn1.default_set_tag |
| 157 | } |
| 158 | |
| 159 | fn (ci ChildInformation) payload() ![]u8 { |
| 160 | mut out := []u8{} |
| 161 | out << asn1.encode(ci.name)! |
| 162 | out << asn1.encode_with_options(ci.date_of_birth, |
| 163 | 'context_specific: 0; explicit; inner:application,false,3')! |
| 164 | |
| 165 | return out |
| 166 | } |
| 167 | |
| 168 | // EmployeeNumber ::= [APPLICATION 2] IMPLICIT INTEGER |
| 169 | type EmployeeNumber = asn1.ApplicationElement |
| 170 | |
| 171 | fn EmployeeNumber.new(val asn1.Integer) !asn1.ApplicationElement { |
| 172 | return asn1.ApplicationElement.from_element(val, 2, .implicit)! |
| 173 | } |
| 174 | |
| 175 | // Date ::= [APPLICATION 3] IMPLICIT VisibleString -- YYYYMMDD |
| 176 | type Date = asn1.ApplicationElement |
| 177 | |
| 178 | fn Date.new(val asn1.VisibleString) !asn1.ApplicationElement { |
| 179 | return asn1.ApplicationElement.from_element(val, 3, .implicit)! |
| 180 | } |
| 181 | |
| 182 | // Name ::= [APPLICATION 1] IMPLICIT SEQUENCE { |
| 183 | // givenName VisibleString, |
| 184 | // initial VisibleString, |
| 185 | // familyName VisibleString |
| 186 | // } |
| 187 | type Name = asn1.ApplicationElement |
| 188 | |
| 189 | fn Name.new(el NameEntry) !asn1.ApplicationElement { |
| 190 | return asn1.ApplicationElement.from_element(el, 1, .implicit)! |
| 191 | } |
| 192 | |
| 193 | struct NameEntry { |
| 194 | given_name asn1.VisibleString |
| 195 | initial asn1.VisibleString |
| 196 | family_name asn1.VisibleString |
| 197 | } |
| 198 | |
| 199 | fn (n NameEntry) tag() asn1.Tag { |
| 200 | return asn1.default_sequence_tag |
| 201 | } |
| 202 | |
| 203 | fn (n NameEntry) payload() ![]u8 { |
| 204 | mut out := []u8{} |
| 205 | out << asn1.encode(n.given_name)! |
| 206 | out << asn1.encode(n.initial)! |
| 207 | out << asn1.encode(n.family_name)! |
| 208 | |
| 209 | return out |
| 210 | } |
| 211 | |
| 212 | // The value of John Smith's personnel record is formally described below using ASN.1. |
| 213 | // { name {givenName "John",initial "P",familyName "Smith"}, |
| 214 | // title "Director", |
| 215 | // number 51, |
| 216 | // dateOfHire "19710917", |
| 217 | // nameOfSpouse {givenName "Mary",initial "T",familyName "Smith"}, |
| 218 | // children { |
| 219 | // { name {givenName "Ralph",initial "T",familyName "Smith"}, |
| 220 | // dateOfBirth "19571111" |
| 221 | // }, |
| 222 | // { name {givenName "Susan",initial "B",familyName "Jones"}, |
| 223 | // dateOfBirth "19590717" |
| 224 | // } |
| 225 | // } |
| 226 | // } |
| 227 | |
| 228 | // Representation of this record value |
| 229 | // |
| 230 | // 60 8185 |
| 231 | // 61 10 1A 04 'John' // name |
| 232 | // 1A 01 'P' |
| 233 | // 1A 05 'Smith' |
| 234 | // A0 0A 1A 08 'Director' // title |
| 235 | // 42 01 33 // number |
| 236 | // A1 0A 43 08 '19710917' // dateOfHire |
| 237 | // A2 12 61 10 1A 04 'Mary' // nameOfSpouse |
| 238 | // 1A 01 'T' |
| 239 | // 1A 05 'Smith' |
| 240 | // A3 42 31 1F 61 11 1A 05 'Ralph' => 52 61 6c 70 68 // children |
| 241 | // 1A 01 'T' => 54 |
| 242 | // 1A 05 'Smith' => 53 6d 69 74 68 |
| 243 | // A0 0A 43 08 '19571111' => 31 39 35 37 31 31 31 31 |
| 244 | // 31 1F 61 11 1A 05 'Susan' => 53 75 73 61 6e |
| 245 | // 1A 01 'B' => 42 |
| 246 | // 1A 05 'Jones' => 4a 6f 6e 65 73 |
| 247 | // A0 0A 43 08 '19590717' => 31 39 35 39 30 37 31 37 |
| 248 | |
| 249 | fn main() { |
| 250 | // We detailed every pieces of element |
| 251 | |
| 252 | // PersonnelRecord.name |
| 253 | pr_name := Name.new(NameEntry{ |
| 254 | given_name: asn1.VisibleString.new('John')! |
| 255 | initial: asn1.VisibleString.new('P')! |
| 256 | family_name: asn1.VisibleString.new('Smith')! |
| 257 | })! |
| 258 | // 61 10 1A 04 'John' => 4a 6f 68 6e // name |
| 259 | // 1A 01 'P' => 50 |
| 260 | // 1A 05 'Smith' => 53 6d 69 74 68 |
| 261 | pr_name_bytes := [u8(0x61), 0x10, 0x1A, 0x04, 0x4a, 0x6f, 0x68, 0x6e, 0x1A, 0x01, 0x50, 0x1A, |
| 262 | 0x05, 0x53, 0x6d, 0x69, 0x74, 0x68] |
| 263 | |
| 264 | assert asn1.encode(pr_name)! == pr_name_bytes |
| 265 | |
| 266 | // PersonnelRecord.title |
| 267 | // A0 0A 1A 08 'Director' => 44 69 72 65 63 74 6f 72 // title |
| 268 | title := asn1.VisibleString.new('Director')! |
| 269 | title_bytes := [u8(0xA0), 0x0A, 0x1A, 0x08, u8(0x44), 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72] |
| 270 | assert asn1.encode_with_options(title, 'context_specific:0;explicit;inner:26')! == title_bytes |
| 271 | |
| 272 | // PersonnelRecord.EmployeeNumber |
| 273 | // 42 01 33 // number |
| 274 | emp_num := EmployeeNumber.new(asn1.Integer.from_int(51))! |
| 275 | emp_bytes := [u8(0x42), 0x01, 0x33] |
| 276 | assert asn1.encode(emp_num)! == emp_bytes |
| 277 | |
| 278 | // PersonnelRecord.dateOfHire |
| 279 | // A1 0A 43 08 '19710917' => 31 39 37 31 30 39 31 37 // dateOfHire |
| 280 | // dateOfHire [1] Date, |
| 281 | doh := Date.new(asn1.VisibleString.new('19710917')!)! |
| 282 | doh_bytes := [u8(0xA1), 0x0A, 0x43, 0x08, 0x31, 0x39, 0x37, 0x31, 0x30, 0x39, 0x31, 0x37] |
| 283 | assert asn1.encode_with_options(doh, 'context_specific: 1; explicit; inner:application,false,3')! == doh_bytes |
| 284 | |
| 285 | // PersonnelRecord.nameOfSpouse |
| 286 | // A2 12 61 10 1A 04 'Mary' => 4d 61 72 79 // nameOfSpouse |
| 287 | // 1A 01 'T' => 54 |
| 288 | // 1A 05 'Smith' => 53 6d 69 74 68 |
| 289 | nosp := Name.new(NameEntry{ |
| 290 | given_name: asn1.VisibleString.new('Mary')! |
| 291 | initial: asn1.VisibleString.new('T')! |
| 292 | family_name: asn1.VisibleString.new('Smith')! |
| 293 | })! |
| 294 | nosp_bytes := [u8(0xA2), 0x12, 0x61, 0x10, 0x1A, 0x04, 0x4d, 0x61, 0x72, 0x79, 0x1A, 0x01, |
| 295 | 0x54, 0x1A, 0x05, 0x53, 0x6d, 0x69, 0x74, 0x68] |
| 296 | // nameOfSpouse [2] Name, |
| 297 | assert asn1.encode_with_options(nosp, 'context_specific: 2; explicit; inner:application,true,1')! == nosp_bytes |
| 298 | |
| 299 | // { name {givenName "Ralph",initial "T",familyName "Smith"}, |
| 300 | // dateOfBirth "19571111" |
| 301 | // }, |
| 302 | childinfo0 := ChildInformation{ |
| 303 | name: Name.new(NameEntry{ |
| 304 | given_name: asn1.VisibleString.new('Ralph')! |
| 305 | initial: asn1.VisibleString.new('T')! |
| 306 | family_name: asn1.VisibleString.new('Smith')! |
| 307 | })! |
| 308 | date_of_birth: Date.new(asn1.VisibleString.new('19571111')!)! |
| 309 | } |
| 310 | // 31 1F 61 11 1A 05 'Ralph' => 52 61 6c 70 68 |
| 311 | // 1A 01 'T' => 54 |
| 312 | // 1A 05 'Smith' => 53 6d 69 74 68 |
| 313 | // A0 0A 43 08 '19571111' => 31 39 35 37 31 31 31 31 |
| 314 | ch0_bytes := [u8(0x31), 0x1F, 0x61, 0x11, 0x1A, 0x05, 0x52, 0x61, 0x6c, 0x70, 0x68, 0x1A, 0x01, |
| 315 | 0x54, 0x1A, 0x05, 0x53, 0x6d, 0x69, 0x74, 0x68, 0xA0, 0x0A, 0x43, 0x08, 0x31, 0x39, 0x35, |
| 316 | 0x37, 0x31, 0x31, 0x31, 0x31] |
| 317 | assert asn1.encode(childinfo0)! == ch0_bytes |
| 318 | |
| 319 | childinfo1 := ChildInformation{ |
| 320 | name: Name.new(NameEntry{ |
| 321 | given_name: asn1.VisibleString.new('Susan')! |
| 322 | initial: asn1.VisibleString.new('B')! |
| 323 | family_name: asn1.VisibleString.new('Jones')! |
| 324 | })! |
| 325 | date_of_birth: Date.new(asn1.VisibleString.new('19590717')!)! |
| 326 | } |
| 327 | // 31 1F 61 11 1A 05 'Susan' => 53 75 73 61 6e |
| 328 | // 1A 01 'B' => 42 |
| 329 | // 1A 05 'Jones' => 4a 6f 6e 65 73 |
| 330 | // A0 0A 43 08 '19590717' => 31 39 35 39 30 37 31 37 |
| 331 | ch1_bytes := [u8(0x31), 0x1F, 0x61, 0x11, 0x1A, 0x05, 0x53, 0x75, 0x73, 0x61, 0x6e, 0x1A, 0x01, |
| 332 | 0x42, 0x1A, 0x05, 0x4a, 0x6f, 0x6e, 0x65, 0x73, 0xA0, 0x0A, 0x43, 0x08, 0x31, 0x39, 0x35, |
| 333 | 0x39, 0x30, 0x37, 0x31, 0x37] |
| 334 | assert asn1.encode(childinfo1)! == ch1_bytes |
| 335 | |
| 336 | // PersonnelRecord.children |
| 337 | children := asn1.SequenceOf.from_list[ChildInformation]([childinfo0, childinfo1])! |
| 338 | // A3 42 31 1F 61 11 1A 05 'Ralph' => 52 61 6c 70 68 // children |
| 339 | // 1A 01 'T' => 54 |
| 340 | // 1A 05 'Smith' => 53 6d 69 74 68 |
| 341 | // A0 0A 43 08 '19571111' => 31 39 35 37 31 31 31 31 |
| 342 | // 31 1F 61 11 1A 05 'Susan' => 53 75 73 61 6e |
| 343 | // 1A 01 'B' => 42 |
| 344 | // 1A 05 'Jones' => 4a 6f 6e 65 73 |
| 345 | // A0 0A 43 08 '19590717' => 31 39 35 39 30 37 31 37 |
| 346 | children_bytes := [u8(0xA3), 0x42, u8(0x31), 0x1F, 0x61, 0x11, 0x1A, 0x05, 0x52, 0x61, 0x6c, |
| 347 | 0x70, 0x68, 0x1A, 0x01, 0x54, 0x1A, 0x05, 0x53, 0x6d, 0x69, 0x74, 0x68, 0xA0, 0x0A, 0x43, |
| 348 | 0x08, 0x31, 0x39, 0x35, 0x37, 0x31, 0x31, 0x31, 0x31, u8(0x31), 0x1F, 0x61, 0x11, 0x1A, |
| 349 | 0x05, 0x53, 0x75, 0x73, 0x61, 0x6e, 0x1A, 0x01, 0x42, 0x1A, 0x05, 0x4a, 0x6f, 0x6e, 0x65, |
| 350 | 0x73, 0xA0, 0x0A, 0x43, 0x08, 0x31, 0x39, 0x35, 0x39, 0x30, 0x37, 0x31, 0x37] |
| 351 | assert asn1.encode_with_options(children, 'context_specific: 3; implicit; inner:16')! == children_bytes |
| 352 | |
| 353 | // PersonnelRecord entries |
| 354 | pr := PersonnelRecord{ |
| 355 | name: pr_name |
| 356 | title: title |
| 357 | number: emp_num |
| 358 | date_of_hire: doh |
| 359 | name_of_spouse: nosp |
| 360 | children: children |
| 361 | } |
| 362 | |
| 363 | mut expected_record_bytes := [u8(0x60), 0x81, 0x85] |
| 364 | expected_record_bytes << pr_name_bytes |
| 365 | expected_record_bytes << title_bytes |
| 366 | expected_record_bytes << emp_bytes |
| 367 | expected_record_bytes << doh_bytes |
| 368 | expected_record_bytes << nosp_bytes |
| 369 | expected_record_bytes << children_bytes |
| 370 | |
| 371 | // PersonnelRecord ::= [APPLICATION 0] IMPLICIT SET { |
| 372 | pr_record_output := asn1.encode_with_options(pr, 'application:0;implicit;inner:17')! |
| 373 | assert pr_record_output == expected_record_bytes |
| 374 | |
| 375 | pr_record := PersonnelRecord.decode(pr_record_output)! |
| 376 | pr_record_encoded_back := |
| 377 | asn1.encode_with_options(pr_record, 'application:0;implicit;inner:17')! |
| 378 | |
| 379 | dump(pr_record_encoded_back == expected_record_bytes) // true |
| 380 | } |
| 381 | |