1 /**
2     Canonical Encoding Rules (CER) is a standard for encoding ASN.1 data.
3     CER is often used for cryptgraphically-signed data, such as X.509
4     certificates, because CER's defining feature is that there is only one way
5     to encode each data type, which means that two encodings of the same data
6     could not have different cryptographic signatures. For this reason, CER
7     is generally regarded as the most secure encoding standard for ASN.1.
8     Like Basic Encoding Rules (BER), Canonical Encoding Rules (CER), and
9     Packed Encoding Rules (PER), Canonical Encoding Rules (CER) is a
10     specification created by the
11     $(LINK https://www.itu.int/en/pages/default.aspx, International Telecommunications Union),
12     and specified in $(LINK http://www.itu.int/rec/T-REC-X.690/en, X.690 - ASN.1 encoding rules)
13 
14     Authors:
15     $(UL
16         $(LI $(PERSON Jonathan M. Wilbur, jonathan@wilbur.space, http://jonathan.wilbur.space))
17     )
18     Copyright: Copyright (C) Jonathan M. Wilbur
19     License: $(LINK https://mit-license.org/, MIT License)
20     Standards:
21     $(UL
22         $(LI $(LINK https://www.itu.int/rec/T-REC-X.680/en, X.680 - Abstract Syntax Notation One (ASN.1)))
23         $(LI $(LINK http://www.itu.int/rec/T-REC-X.690/en, X.690 - ASN.1 encoding rules))
24     )
25     See_Also:
26     $(UL
27         $(LI $(LINK https://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One, The Wikipedia Page on ASN.1))
28         $(LI $(LINK https://en.wikipedia.org/wiki/X.690, The Wikipedia Page on X.690))
29         $(LI $(LINK https://www.strozhevsky.com/free_docs/asn1_in_simple_words.pdf, ASN.1 By Simple Words))
30         $(LI $(LINK http://www.oss.com/asn1/resources/books-whitepapers-pubs/dubuisson-asn1-book.PDF, ASN.1: Communication Between Heterogeneous Systems))
31     )
32 */
33 module codecs.cer;
34 public import codec;
35 public import interfaces : Byteable;
36 public import types.identification;
37 
38 ///
39 public alias cerOID = canonicalEncodingRulesObjectIdentifier;
40 ///
41 public alias cerObjectID = canonicalEncodingRulesObjectIdentifier;
42 ///
43 public alias cerObjectIdentifier = canonicalEncodingRulesObjectIdentifier;
44 /**
45     The object identifier assigned to the Canonical Encoding Rules (CER), per the
46     $(LINK https://www.itu.int/en/pages/default.aspx,
47         International Telecommunications Union)'s,
48     $(LINK http://www.itu.int/rec/T-REC-X.690/en, X.690 - ASN.1 encoding rules)
49 
50     $(MONO {joint-iso-itu-t asn1(1) ber-derived(2) canonical-encoding(0)} )
51 */
52 public immutable OID canonicalEncodingRulesObjectIdentifier = cast(immutable(OID)) new OID(2, 1, 2, 0);
53 
54 ///
55 public alias CERElement = CanonicalEncodingRulesElement;
56 /**
57     The unit of encoding and decoding for Canonical Encoding Rules (CER).
58 
59     There are three parts to an element encoded according to the Canonical
60     Encoding Rules (CER):
61 
62     $(UL
63         $(LI A Type Tag, which specifies what data type is encoded)
64         $(LI A Length Tag, which specifies how many subsequent bytes encode the data)
65         $(LI The Encoded Value)
66     )
67 
68     They appear in the binary encoding in that order, and as such, the encoding
69     scheme is sometimes described as "TLV," which stands for Type-Length-Value.
70 
71     This class provides a properties for getting and setting bit fields of
72     the type tag, but most of it is functionality for encoding data per
73     the specification.
74 
75     As an example, this is what encoding a simple INTEGER looks like:
76 
77     ---
78     CERElement cv = new CERElement();
79     cv.tagNumber = 0x02u; // "2" means this is an INTEGER
80     cv.integer = 1433; // Now the data is encoded.
81     transmit(cast(ubyte[]) cv); // transmit() is a made-up function.
82     ---
83 
84     And this is what decoding looks like:
85 
86     ---
87     ubyte[] data = receive(); // receive() is a made-up function.
88     CERElement cv2 = new CERElement(data);
89 
90     long x;
91     if (cv.tagNumber == 0x02u) // it is an INTEGER
92     {
93         x = cv.integer;
94     }
95     // Now x is 1433!
96     ---
97 */
98 /* FIXME:
99     This class should be "final," but a bug in the DMD compiler produces
100     unlinkable objects if a final class inherits an alias to an internal
101     member of a parent class.
102 
103     I have reported this to the D Language Foundation's Bugzilla site on
104     17 October, 2017, and this bug can be viewed here:
105     https://issues.dlang.org/show_bug.cgi?id=17909
106 */
107 public
108 class CanonicalEncodingRulesElement : ASN1Element!CERElement, Byteable
109 {
110     @system
111     unittest
112     {
113         writeln("Running unit tests for codec: " ~ typeof(this).stringof);
114     }
115 
116     ///
117     public ASN1TagClass tagClass;
118     ///
119     public ASN1Construction construction;
120     ///
121     public size_t tagNumber;
122 
123     /// The length of the value in octets
124     final public @property @safe nothrow
125     size_t length() const
126     {
127         return this.value.length;
128     }
129 
130     /// The octets of the encoded value.
131     public ubyte[] value;
132 
133     /**
134         "Decodes" an $(MONO END OF CONTENT), by which I mean: returns nothing, but
135         throws exceptions if the element is not correct.
136 
137         Throws:
138         $(UL
139             $(LI $(D ASN1ConstructionException) if the element is marked as "constructed")
140             $(LI $(D ASN1ValueSizeException) if there are any content octets)
141         )
142     */
143     override public @property @safe
144     void endOfContent() const
145     {
146         if (this.construction != ASN1Construction.primitive)
147             throw new ASN1ConstructionException
148             (this.construction, "decode an END OF CONTENT");
149 
150         if (this.value.length != 0u)
151             throw new ASN1ValueSizeException
152             (0u, 0u, this.value.length, "decode an END OF CONTENT");
153     }
154 
155     /**
156         Decodes a $(D bool).
157 
158         A $(D 0xFF) byte will be interpreted as $(MONO TRUE). A zero byte
159         ($(D 0x00)) will be interpreted as $(MONO FALSE). Any other value,
160         or any length other than 1 byte will throw an exception.
161 
162         Throws:
163         $(UL
164             $(LI $(D ASN1ConstructionException)
165                 if the encoded value is not primitively-constructed)
166             $(LI $(D ASN1ValueSizeException)
167                 if the encoded value is not exactly 1 byte in size)
168             $(LI $(D ASN1ValueException)
169                 if the encoded value is not either 0xFF or 0x00)
170         )
171     */
172     override public @property @safe
173     bool boolean() const
174     {
175         if (this.construction != ASN1Construction.primitive)
176             throw new ASN1ConstructionException
177             (this.construction, "decode a BOOLEAN");
178 
179         if (this.value.length != 1u)
180             throw new ASN1ValueSizeException
181             (1u, 1u, this.value.length, "decode a BOOLEAN");
182 
183         if (this.value[0] == 0xFFu)
184         {
185             return true;
186         }
187         else if (this.value[0] == 0x00u)
188         {
189             return false;
190         }
191         else
192         {
193             throw new ASN1ValueException
194             (
195                 "This exception was thrown because you attempted to decode a BOOLEAN " ~
196                 "that was encoded on a byte that was not 0xFF or 0x00 using the CER " ~
197                 "codec. Any encoding of a boolean other than 0xFF (true) or 0x00 " ~
198                 "(false) is restricted by the CER codec. " ~ notWhatYouMeantText ~
199                 forMoreInformationText ~ debugInformationText ~ reportBugsText
200             );
201         }
202     }
203 
204     /// Encodes a $(D bool)
205     override public @property @safe nothrow
206     void boolean(in bool value)
207     out
208     {
209         assert(this.value.length > 0u);
210     }
211     body
212     {
213         scope(success) this.construction = ASN1Construction.primitive;
214         this.value = [(value ? 0xFFu : 0x00u)];
215     }
216 
217     @safe
218     unittest
219     {
220         CERElement cv = new CERElement();
221         cv.value = [ 0xFFu ];
222         assert(cv.boolean == true);
223         cv.value = [ 0x00u ];
224         assert(cv.boolean == false);
225         cv.value = [ 0x01u, 0x00u ];
226         assertThrown!ASN1ValueSizeException(cv.boolean);
227         cv.value = [];
228         assertThrown!ASN1ValueSizeException(cv.boolean);
229         cv.value = [ 0x01u ];
230         assertThrown!ASN1ValueException(cv.boolean);
231     }
232 
233     /**
234         Decodes a signed integer.
235 
236         Throws:
237         $(UL
238             $(LI $(D ASN1ConstructionException)
239                 if the encoded value is not primitively-constructed)
240             $(LI $(D ASN1ValueSizeException)
241                 if the value is too big to decode to a signed integral type,
242                 or if the value is zero bytes)
243             $(LI $(D ASN1ValuePaddingException)
244                 if there are padding bytes)
245         )
246     */
247     public @property @system
248     T integer(T)() const
249     if (isIntegral!T && isSigned!T)
250     {
251         if (this.construction != ASN1Construction.primitive)
252             throw new ASN1ConstructionException
253             (this.construction, "decode an INTEGER");
254 
255         if (this.value.length == 1u)
256             return cast(T) cast(byte) this.value[0];
257 
258         if (this.value.length == 0u || this.value.length > T.sizeof)
259             throw new ASN1ValueSizeException
260             (1u, long.sizeof, this.value.length, "decode an INTEGER");
261 
262         if
263         (
264             (this.value[0] == 0x00u && (!(this.value[1] & 0x80u))) || // Unnecessary positive leading bytes
265             (this.value[0] == 0xFFu && (this.value[1] & 0x80u)) // Unnecessary negative leading bytes
266         )
267             throw new ASN1ValuePaddingException
268             (
269                 "This exception was thrown because you attempted to decode " ~
270                 "an INTEGER that was encoded on more than the minimum " ~
271                 "necessary bytes. " ~
272                 notWhatYouMeantText ~ forMoreInformationText ~
273                 debugInformationText ~ reportBugsText
274             );
275 
276         /* NOTE:
277             Because the INTEGER is stored in two's complement form, you
278             can't just apppend 0x00u to the big end of it until it is as long
279             as T in bytes, then cast to T. Instead, you have to first determine
280             if the encoded integer is negative or positive. If it is negative,
281             then you actually want to append 0xFFu to the big end until it is
282             as big as T, so you get the two's complement form of whatever T
283             you choose.
284 
285             The line immediately below this determines whether the padding byte
286             should be 0xFF or 0x00 based on the most significant bit of the
287             most significant byte (which, since CER encodes big-endian, will
288             always be the first byte). If set (1), the number is negative, and
289             hence, the padding byte should be 0xFF. If not, it is positive,
290             and the padding byte should be 0x00.
291         */
292         immutable ubyte paddingByte = ((this.value[0] & 0x80u) ? 0xFFu : 0x00u);
293         ubyte[] value = this.value.dup; // Duplication is necessary to prevent modifying the source bytes
294         while (value.length < T.sizeof)
295             value = (paddingByte ~ value);
296         version (LittleEndian) reverse(value);
297         version (unittest) assert(value.length == T.sizeof);
298         return *cast(T *) value.ptr;
299     }
300 
301     /// Encodes a signed integral type
302     public @property @system nothrow
303     void integer(T)(in T value)
304     if (isIntegral!T && isSigned!T)
305     out
306     {
307         assert(this.value.length > 0u);
308     }
309     body
310     {
311         scope(success) this.construction = ASN1Construction.primitive;
312         if (value <= byte.max && value >= byte.min)
313         {
314             this.value = [ cast(ubyte) cast(byte) value ];
315             return;
316         }
317 
318         ubyte[] ub;
319         ub.length = T.sizeof;
320         *cast(T *)&ub[0] = value;
321         version (LittleEndian) reverse(ub);
322 
323         /*
324             An INTEGER must be encoded on the fewest number of bytes than can
325             encode it. The loops below identify how many bytes can be
326             truncated from the start of the INTEGER, with one loop for positive
327             and another loop for negative numbers.
328 
329             From X.690, Section 8.3.2:
330 
331             If the contents octets of an integer value encoding consist of more
332             than one octet, then the bits of the first octet and bit 8 of the
333             second octet:
334                 a) shall not all be ones; and
335                 b) shall not all be zero.
336                 NOTE – These rules ensure that an integer value is always
337                 encoded in the smallest possible number of octets.
338         */
339         size_t startOfNonPadding = 0u;
340         if (T.sizeof > 1u)
341         {
342             if (value >= 0)
343             {
344                 for (size_t i = 0u; i < ub.length-1; i++)
345                 {
346                     if (ub[i] != 0x00u) break;
347                     if (!(ub[i+1] & 0x80u)) startOfNonPadding++;
348                 }
349             }
350             else
351             {
352                 for (size_t i = 0u; i < ub.length-1; i++)
353                 {
354                     if (ub[i] != 0xFFu) break;
355                     if (ub[i+1] & 0x80u) startOfNonPadding++;
356                 }
357             }
358         }
359 
360         this.value = ub[startOfNonPadding .. $];
361     }
362 
363     // Ensure that INTEGER 0 gets encoded on a single null byte.
364     @system
365     unittest
366     {
367         CERElement el = new CERElement();
368 
369         el.integer!byte = cast(byte) 0x00;
370         assert(el.value == [ 0x00u ]);
371 
372         el.integer!short = cast(short) 0x0000;
373         assert(el.value == [ 0x00u ]);
374 
375         el.integer!int = cast(int) 0;
376         assert(el.value == [ 0x00u ]);
377 
378         el.integer!long = cast(long) 0;
379         assert(el.value == [ 0x00u ]);
380 
381         el.value = [];
382         assertThrown!ASN1ValueSizeException(el.integer!byte);
383         assertThrown!ASN1ValueSizeException(el.integer!short);
384         assertThrown!ASN1ValueSizeException(el.integer!int);
385         assertThrown!ASN1ValueSizeException(el.integer!long);
386     }
387 
388     // Test encoding -0 for the sake of CVE-2016-2108
389     @system
390     unittest
391     {
392         CERElement el = new CERElement();
393 
394         el.integer!byte = -0;
395         assertNotThrown!RangeError(el.integer!byte);
396         assertNotThrown!ASN1Exception(el.integer!byte);
397 
398         el.integer!short = -0;
399         assertNotThrown!RangeError(el.integer!short);
400         assertNotThrown!ASN1Exception(el.integer!short);
401 
402         el.integer!int = -0;
403         assertNotThrown!RangeError(el.integer!int);
404         assertNotThrown!ASN1Exception(el.integer!int);
405 
406         el.integer!long = -0;
407         assertNotThrown!RangeError(el.integer!long);
408         assertNotThrown!ASN1Exception(el.integer!long);
409     }
410 
411     /**
412         Decodes an array of $(D bool)s representing a string of bits.
413 
414         Returns: an array of $(D bool)s, where each $(D bool) represents a bit
415             in the encoded bit string
416 
417         Throws:
418         $(UL
419             $(LI $(D ASN1ValueSizeException)
420                 if the any primitive contains 0 bytes)
421             $(LI $(D ASN1ValueException)
422                 if the first byte has a value greater
423                 than seven, or if the first byte indicates the presence of
424                 padding bits when no subsequent bytes exist, or if any primitive
425                 but the last in a constructed BIT STRING uses padding bits, or
426                 if any of the padding bits are set)
427             $(LI $(D ASN1RecursionException)
428                 if using constructed form and the element
429                 is constructed of too many nested constructed elements)
430             $(LI $(D ASN1TagClassException)
431                 if any nested primitives do not share the
432                 same tag class as their outer constructed element)
433             $(LI $(D ASN1TagNumberException)
434                 if any nested primitives do not share the
435                 same tag number as their outer constructed element)
436         )
437     */
438     override public @property @system
439     bool[] bitString() const
440     in
441     {
442         // If the blank constructor ever stops producing an EOC,
443         // this method must change.
444         CERElement test = new CERElement();
445         assert(test.tagNumber == 0x00u);
446         assert(test.length == 0u);
447     }
448     body
449     {
450         if (this.construction == ASN1Construction.primitive)
451         {
452             if (this.value.length == 0u || this.value.length > 1000u)
453                 throw new ASN1ValueSizeException
454                 (1u, 1000u, this.value.length, "decode a primitively-encoded BIT STRING");
455 
456             if (this.value[0] > 0x07u)
457                 throw new ASN1ValueException
458                 (
459                     "In Canonical Encoding Rules (CER), the first byte of the encoded " ~
460                     "binary value (after the type and length bytes, of course) " ~
461                     "is used to indicate how many unused bits there are at the " ~
462                     "end of the BIT STRING. Since everything is encoded in bytes " ~
463                     "in Canonical Encoding Rules (CER), but a BIT STRING may not " ~
464                     "necessarily encode a number of bits, divisible by eight " ~
465                     "there may be bits at the end of the BIT STRING that will " ~
466                     "need to be identified as padding instead of meaningful data." ~
467                     "Since a byte is eight bits, the largest number that the " ~
468                     "first byte should encode is 7, since, if you have eight " ~
469                     "unused bits or more, you may as well truncate an entire " ~
470                     "byte from the encoded data. This exception was thrown because " ~
471                     "you attempted to decode a BIT STRING whose first byte " ~
472                     "had a value greater than seven. The value was: " ~
473                     text(this.value[0]) ~ ". " ~ notWhatYouMeantText ~
474                     forMoreInformationText ~ debugInformationText ~ reportBugsText
475                 );
476 
477             if (this.value[0] > 0x00u && this.value.length <= 1u)
478                 throw new ASN1ValueException
479                 (
480                     "This exception was thrown because you attempted to decode a " ~
481                     "BIT STRING that had a misleading first byte, which indicated " ~
482                     "that there were more than zero padding bits, but there were " ~
483                     "no subsequent octets supplied, which contain the octet-" ~
484                     "aligned bits and padding. This may have been a mistake on " ~
485                     "the part of the encoder, but this looks really suspicious: " ~
486                     "it is likely that an attempt was made to hack your systems " ~
487                     "by inducing an out-of-bounds read from an array. " ~
488                     notWhatYouMeantText ~ forMoreInformationText ~
489                     debugInformationText ~ reportBugsText
490                 );
491 
492             bool[] ret;
493             for (size_t i = 1; i < this.value.length; i++)
494             {
495                 ret ~= [
496                     (this.value[i] & 0b10000000u ? true : false),
497                     (this.value[i] & 0b01000000u ? true : false),
498                     (this.value[i] & 0b00100000u ? true : false),
499                     (this.value[i] & 0b00010000u ? true : false),
500                     (this.value[i] & 0b00001000u ? true : false),
501                     (this.value[i] & 0b00000100u ? true : false),
502                     (this.value[i] & 0b00000010u ? true : false),
503                     (this.value[i] & 0b00000001u ? true : false)
504                 ];
505             }
506 
507             foreach (immutable bit; ret[$-this.value[0] .. $])
508             {
509                 if (bit == true)
510                     throw new ASN1ValueException
511                     (
512                         "This exception was thrown because you attempted to decode " ~
513                         "a BIT STRING whose padding bits were not entirely zeroes. " ~
514                         "If you were using the Basic Encoding Rules (BER), this " ~
515                         "would not be a problem, but under Canonical Encoding " ~
516                         "Rules (CER), padding the BIT STRING with anything other " ~
517                         "zeroes is forbidden. " ~
518                         notWhatYouMeantText ~ forMoreInformationText ~
519                         debugInformationText ~ reportBugsText
520                     );
521             }
522 
523             ret.length -= this.value[0];
524             return ret;
525         }
526         else
527         {
528             if (this.valueRecursionCount++ == this.nestingRecursionLimit)
529                 throw new ASN1RecursionException(this.nestingRecursionLimit, "parse a BIT STRING");
530             scope(exit) this.valueRecursionCount--; // So exceptions do not leave residual recursion.
531 
532             CERElement[] substrings = this.sequence;
533             if (substrings.length < 2u)
534                 throw new ASN1ValueException
535                 (
536                     "This exception was thrown because you attempted to decode " ~
537                     "a constructed BIT STRING that was constituted by fewer " ~
538                     "than two nested elements. When using the Canonical Encoding " ~
539                     "Rules (CER), a legitimate constructed BIT STRING must " ~
540                     "contain at least two nested elements, and each but the " ~
541                     "last must contain exactly 1000 content octets. The last " ~
542                     "nested element may have between 1 and 1000 content " ~
543                     "octets inclusively. This element should have been encoded " ~
544                     "primitively using definite-length encoding."
545                 );
546 
547             foreach (substring; substrings[0 .. $-1])
548             {
549                 if (substring.length != 1000u)
550                     throw new ASN1ValueSizeException
551                     (1000u, 1000u, substring.length, "decode a substring of a constructed BIT STRING");
552 
553                 if
554                 (
555                     substring.construction == ASN1Construction.primitive &&
556                     substring.length > 0u &&
557                     substring.value[0] != 0x00u
558                 )
559                     throw new ASN1ValueException
560                     (
561                         "This exception was thrown because you attempted to " ~
562                         "decode a constructed BIT STRING that contained a " ~
563                         "substring whose first byte indicated a non-zero " ~
564                         "number of padding bits, despite not being the " ~
565                         "last substring of the constructed BIT STRING. " ~
566                         "Only the last substring may have padding bits. "
567                     );
568             }
569 
570             if (substrings[$-1].length == 0u)
571                 throw new ASN1ValueSizeException
572                 (1u, 1000u, substrings[$-1].length, "decode the last substring of a constructed OCTET STRING");
573 
574             Appender!(bool[]) appendy = appender!(bool[])();
575             foreach (substring; substrings)
576             {
577                 if (substring.tagClass != this.tagClass)
578                     throw new ASN1TagClassException
579                     ([ this.tagClass ], substring.tagClass, "decode a substring of a constructed BIT STRING");
580 
581                 if (substring.construction != ASN1Construction.primitive)
582                     throw new ASN1ConstructionException
583                     (substring.construction, "decode a substring of a constructed OCTET STRING");
584 
585                 if (substring.tagNumber != this.tagNumber)
586                     throw new ASN1TagNumberException
587                     ([ this.tagNumber ], substring.tagNumber, "decode a substring of a constructed BIT STRING");
588 
589                 appendy.put(substring.bitString);
590             }
591             return appendy.data;
592         }
593     }
594 
595     /// Encodes an array of $(D bool)s representing a string of bits.
596     override public @property @system
597     void bitString(in bool[] value)
598     out
599     {
600         assert(this.value.length > 0u);
601     }
602     body
603     {
604         ubyte[] ub;
605         ub.length = ((value.length / 8u) + (value.length % 8u ? 1u : 0u));
606 
607         for (size_t i = 0u; i < value.length; i++)
608         {
609             if (value[i] == false) continue;
610             ub[(i/8u)] |= (0b10000000u >> (i % 8u));
611         }
612 
613         if (ub.length <= 1000u)
614         {
615             this.value = [ cast(ubyte) (8u - (value.length % 8u)) ] ~ ub;
616             if (this.value[0] == 0x08u) this.value[0] = 0x00u;
617         }
618         else
619         {
620             CERElement[] primitives;
621             size_t i = 0u;
622             while (i+999u < value.length)
623             {
624                 CERElement x = new CERElement();
625                 x.tagNumber = this.tagNumber;
626                 x.construction = ASN1Construction.primitive;
627                 x.value = [ cast(ubyte) 0u ] ~ ub[i .. i+999u];
628                 primitives ~= x;
629                 i += 999u;
630             }
631 
632             CERElement y = new CERElement();
633             y.tagNumber = this.tagNumber;
634             y.construction = ASN1Construction.primitive;
635             y.value = ([ cast(ubyte) 0u ] ~ ub[i .. $]);
636             primitives ~= y;
637 
638             CERElement z = new CERElement();
639             primitives ~= z;
640 
641             this.sequence = primitives;
642             this.construction = ASN1Construction.constructed;
643         }
644     }
645 
646     @system
647     unittest
648     {
649         bool[] data;
650         data.length = 289u;
651         for (size_t i = 0u; i < data.length; i++)
652         {
653             data[i] = cast(bool) (i % 3);
654         }
655         CERElement el = new CERElement();
656         el.bitString = data;
657         assert(el.bitString == data);
658     }
659 
660     @system
661     unittest
662     {
663         void test(size_t length)
664         {
665             bool[] data;
666             data.length = length;
667             for (size_t i = 0u; i < data.length; i++)
668             {
669                 data[i] = cast(bool) (i % 3);
670             }
671             CERElement el = new CERElement();
672             el.bitString = data;
673             assert(el.bitString == data);
674         }
675         test(0u);
676         test(1u);
677         test(8u);
678         test(127u);
679         test(128u);
680         test(129u);
681         test(192u);
682         test(999u);
683         test(1000u);
684         test(1001u);
685         test(2017u);
686     }
687 
688     // Ensure that 1s in the padding get PUNISHED with an exception
689     @system
690     unittest
691     {
692         CERElement el = new CERElement();
693         el.value = [ 0x07u, 0b11000000u ];
694         assertThrown!ASN1ValueException(el.bitString);
695 
696         el.value = [ 0x01u, 0b11111111u ];
697         assertThrown!ASN1ValueException(el.bitString);
698 
699         el.value = [ 0x00u, 0b11111111u ];
700         assertNotThrown!ASN1ValueException(el.bitString);
701     }
702 
703     // Test a BIT STRING with a deceptive first byte.
704     @system
705     unittest
706     {
707         CERElement el = new CERElement();
708         el.value = [ 0x01u ];
709         assertThrown!ASN1ValueException(el.bitString);
710     }
711 
712     /**
713         Decodes an $(MONO OCTET STRING) into an unsigned byte array.
714 
715         Throws:
716         $(UL
717             $(LI $(D ASN1RecursionException)
718                 if using constructed form and the element
719                 is constructed of too many nested constructed elements)
720             $(LI $(D ASN1TagClassException)
721                 if any nested primitives do not share the
722                 same tag class as their outer constructed element)
723             $(LI $(D ASN1TagNumberException)
724                 if any nested primitives do not share the
725                 same tag number as their outer constructed element)
726         )
727     */
728     override public @property @system
729     ubyte[] octetString() const
730     {
731         if (this.construction == ASN1Construction.primitive)
732         {
733             if (this.value.length > 1000u)
734                 throw new ASN1ValueSizeException
735                 (0u, 1000u, this.value.length, "decode a primitively-encoded OCTET STRING");
736 
737             return this.value.dup;
738         }
739         else
740         {
741             if (this.valueRecursionCount++ == this.nestingRecursionLimit)
742                 throw new ASN1RecursionException(this.nestingRecursionLimit, "parse an OCTET STRING");
743             scope(exit) this.valueRecursionCount--; // So exceptions do not leave residual recursion.
744 
745             CERElement[] substrings = this.sequence;
746             if (substrings.length < 2u)
747                 throw new ASN1ValueException
748                 (
749                     "This exception was thrown because you attempted to decode " ~
750                     "a constructed OCTET STRING that was constituted by fewer " ~
751                     "than two nested elements. When using the Canonical Encoding " ~
752                     "Rules (CER), a legitimate constructed OCTET STRING must " ~
753                     "contain at least two nested elements, and each but the " ~
754                     "last must contain exactly 1000 content octets. The last " ~
755                     "nested element may have between 1 and 1000 content " ~
756                     "octets inclusively. This element should have been encoded " ~
757                     "primitively using definite-length encoding."
758                 );
759 
760             foreach (substring; substrings[0 .. $-1])
761             {
762                 if (substring.length != 1000u)
763                     throw new ASN1ValueSizeException
764                     (1000u, 1000u, substring.length, "decode a substring of a constructed OCTET STRING");
765             }
766 
767             if (substrings[$-1].length == 0u)
768                 throw new ASN1ValueSizeException
769                 (1u, 1000u, substrings[$-1].length, "decode the last substring of a constructed OCTET STRING");
770 
771             Appender!(ubyte[]) appendy = appender!(ubyte[])();
772             foreach (substring; substrings)
773             {
774                 if (substring.tagClass != this.tagClass)
775                     throw new ASN1TagClassException
776                     ([ this.tagClass ], substring.tagClass, "decode a substring of a constructed OCTET STRING");
777 
778                 if (substring.construction != ASN1Construction.primitive)
779                     throw new ASN1ConstructionException
780                     (substring.construction, "decode a substring of a constructed OCTET STRING");
781 
782                 if (substring.tagNumber != this.tagNumber)
783                     throw new ASN1TagNumberException
784                     ([ this.tagNumber ], substring.tagNumber, "decode a substring of a constructed OCTET STRING");
785 
786                 appendy.put(substring.octetString);
787             }
788             return appendy.data;
789         }
790     }
791 
792     /// Encodes an $(MONO OCTET STRING) from an unsigned byte ($(D ubyte)) array.
793     override public @property @system
794     void octetString(in ubyte[] value)
795     in
796     {
797         // If the blank constructor ever stops producing an EOC,
798         // this method must change.
799         CERElement test = new CERElement();
800         assert(test.tagNumber == 0x00u);
801         assert(test.length == 0u);
802     }
803     body
804     {
805         if (value.length <= 1000u)
806         {
807             this.value = value.dup;
808         }
809         else
810         {
811             CERElement[] primitives;
812             size_t i = 0u;
813             while (i+1000u < value.length)
814             {
815                 CERElement x = new CERElement();
816                 x.tagNumber = this.tagNumber;
817                 x.construction = ASN1Construction.primitive;
818                 x.value = value[i .. i+1000u].dup;
819                 primitives ~= x;
820                 i += 1000u;
821             }
822 
823             CERElement y = new CERElement();
824             y.tagNumber = this.tagNumber;
825             y.construction = ASN1Construction.primitive;
826             y.value = value[i .. $].dup;
827             primitives ~= y;
828 
829             this.sequence = primitives;
830             this.construction = ASN1Construction.constructed;
831         }
832     }
833 
834     @system
835     unittest
836     {
837         void test(size_t length)
838         {
839             ubyte[] data;
840             data.length = length;
841             for (size_t i = 0u; i < data.length; i++)
842             {
843                 data[i] = (i % 9u);
844             }
845             CERElement el = new CERElement();
846             el.octetString = data;
847             assert(el.octetString == data);
848         }
849         test(0u);
850         test(1u);
851         test(8u);
852         test(127u);
853         test(128u);
854         test(129u);
855         test(192u);
856         test(999u);
857         test(1000u);
858         test(1001u);
859         test(2017u);
860     }
861 
862     /**
863         "Decodes" a $(D null), by which I mean: returns nothing, but
864         throws exceptions if the element is not correct.
865 
866         Note:
867             I had to name this method $(D nill), because $(D null) is a keyword in D.
868 
869         Throws:
870         $(UL
871             $(LI $(D ASN1ConstructionException) if the element is marked as "constructed")
872             $(LI $(D ASN1ValueSizeException) if there are any content octets)
873         )
874     */
875     override public @property @safe
876     void nill() const
877     {
878         if (this.construction != ASN1Construction.primitive)
879             throw new ASN1ConstructionException
880             (this.construction, "decode a NULL");
881 
882         if (this.value.length != 0u)
883             throw new ASN1ValueSizeException
884             (0u, 0u, this.value.length, "decode a NULL");
885     }
886 
887     /**
888         Decodes an $(MONO OBJECT IDENTIFIER).
889         See $(MONO source/types/universal/objectidentifier.d) for information about
890         the $(D ObjectIdentifier) class (aliased as $(D OID)).
891 
892         Throws:
893         $(UL
894             $(LI $(D ASN1ConstructionException) if the element is marked as "constructed")
895             $(LI $(D ASN1ValueSizeException) if there are no value bytes)
896             $(LI $(D ASN1ValuePaddingException) if a single OID number is encoded with
897                 "leading zero bytes" ($(D 0x80u)))
898             $(LI $(D ASN1ValueOverflowException) if a single OID number is too big to
899                 decode to a $(D size_t))
900             $(LI $(D ASN1TruncationException) if a single OID number is "cut off")
901         )
902 
903         Standards:
904         $(UL
905             $(LI $(LINK http://www.itu.int/rec/T-REC-X.660-201107-I/en, X.660))
906         )
907     */
908     override public @property @system
909     OID objectIdentifier() const
910     out (value)
911     {
912         assert(value.length >= 2u);
913     }
914     body
915     {
916         if (this.construction != ASN1Construction.primitive)
917             throw new ASN1ConstructionException
918             (this.construction, "decode an OBJECT IDENTIFIER");
919 
920         if (this.value.length == 0u)
921             throw new ASN1ValueSizeException
922             (1u, size_t.max, 0u, "decode an OBJECT IDENTIFIER");
923 
924         if (this.value.length >= 2u)
925         {
926             // Skip the first, because it is fine if it is 0x80
927             // Skip the last because it will be checked next
928             foreach (immutable octet; this.value[1 .. $-1])
929             {
930                 if (octet == 0x80u)
931                     throw new ASN1ValuePaddingException
932                     (
933                         "This exception was thrown because you attempted to decode " ~
934                         "an OBJECT IDENTIFIER that contained a number that was " ~
935                         "encoded on more than the minimum necessary octets. This " ~
936                         "is indicated by an occurrence of the octet 0x80, which " ~
937                         "is the encoded equivalent of a leading zero. " ~
938                         notWhatYouMeantText ~ forMoreInformationText ~
939                         debugInformationText ~ reportBugsText
940                     );
941             }
942 
943             if ((this.value[$-1] & 0x80u) == 0x80u)
944                 throw new ASN1TruncationException
945                 (size_t.max, this.value.length, "decode an OBJECT IDENTIFIER");
946         }
947 
948         size_t[] numbers;
949         if (this.value[0] >= 0x50u)
950         {
951             numbers = [ 2u, (this.value[0] - 0x50u) ];
952         }
953         else if (this.value[0] >= 0x28u)
954         {
955             numbers = [ 1u, (this.value[0] - 0x28u) ];
956         }
957         else
958         {
959             numbers = [ 0u, this.value[0] ];
960         }
961 
962         // Breaks bytes into groups, where each group encodes one OID component.
963         ubyte[][] byteGroups;
964         size_t lastTerminator = 1;
965         for (size_t i = 1; i < this.length; i++)
966         {
967             if (!(this.value[i] & 0x80u))
968             {
969                 byteGroups ~= cast(ubyte[]) this.value[lastTerminator .. i+1];
970                 lastTerminator = i+1;
971             }
972         }
973 
974         // Converts each group of bytes to a number.
975         foreach (const byteGroup; byteGroups)
976         {
977             if (byteGroup.length > size_t.sizeof)
978                 throw new ASN1ValueOverflowException
979                 (
980                     "This exception was thrown because you attempted to decode " ~
981                     "a OBJECT IDENTIFIER that encoded a number on more than " ~
982                     "size_t.sizeof bytes. " ~
983                     notWhatYouMeantText ~ forMoreInformationText ~
984                     debugInformationText ~ reportBugsText
985                 );
986 
987             numbers ~= 0u;
988             for (size_t i = 0u; i < byteGroup.length; i++)
989             {
990                 numbers[$-1] <<= 7;
991                 numbers[$-1] |= cast(size_t) (byteGroup[i] & 0x7Fu);
992             }
993         }
994 
995         // Constructs the array of OIDNodes from the array of numbers.
996         OIDNode[] nodes;
997         foreach (number; numbers)
998         {
999             nodes ~= OIDNode(number);
1000         }
1001 
1002         return new OID(nodes);
1003     }
1004 
1005     /**
1006         Encodes an $(MONO OBJECT IDENTIFIER).
1007         See $(MONO source/types/universal/objectidentifier.d) for information about
1008         the $(D ObjectIdentifier) class (aliased as $(OID)).
1009 
1010         Standards:
1011         $(UL
1012             $(LI $(LINK http://www.itu.int/rec/T-REC-X.660-201107-I/en, X.660))
1013         )
1014     */
1015     override public @property @system
1016     void objectIdentifier(in OID value)
1017     in
1018     {
1019         assert(value.length >= 2u);
1020         assert(value.numericArray[0] <= 2u);
1021         if (value.numericArray[0] == 2u)
1022             assert(value.numericArray[1] <= 175u);
1023         else
1024             assert(value.numericArray[1] <= 39u);
1025     }
1026     out
1027     {
1028         assert(this.value.length > 0u);
1029     }
1030     body
1031     {
1032         scope(success) this.construction = ASN1Construction.primitive;
1033         size_t[] numbers = value.numericArray();
1034         this.value = [ cast(ubyte) (numbers[0] * 40u + numbers[1]) ];
1035         if (numbers.length > 2u)
1036         {
1037             foreach (number; numbers[2 .. $])
1038             {
1039                 if (number < 128u)
1040                 {
1041                     this.value ~= cast(ubyte) number;
1042                     continue;
1043                 }
1044 
1045                 ubyte[] encodedOIDNode;
1046                 while (number != 0u)
1047                 {
1048                     ubyte[] numberBytes;
1049                     numberBytes.length = size_t.sizeof;
1050                     *cast(size_t *) numberBytes.ptr = number;
1051                     if ((numberBytes[0] & 0x80u) == 0u) numberBytes[0] |= 0x80u;
1052                     encodedOIDNode = numberBytes[0] ~ encodedOIDNode;
1053                     number >>= 7u;
1054                 }
1055 
1056                 encodedOIDNode[$-1] &= 0x7Fu;
1057                 this.value ~= encodedOIDNode;
1058             }
1059         }
1060     }
1061 
1062     @system
1063     unittest
1064     {
1065         CERElement element = new CERElement();
1066 
1067         // All values of octet[0] should pass.
1068         for (ubyte i = 0x00u; i < 0xFFu; i++)
1069         {
1070             element.value = [ i ];
1071             assertNotThrown!Exception(element.objectIdentifier);
1072         }
1073 
1074         // All values of octet[0] should pass.
1075         for (ubyte i = 0x00u; i < 0xFFu; i++)
1076         {
1077             element.value = [ i, 0x14u ];
1078             assertNotThrown!Exception(element.objectIdentifier);
1079         }
1080     }
1081 
1082     @system
1083     unittest
1084     {
1085         CERElement element = new CERElement();
1086 
1087         // Tests for the "leading zero byte," 0x80
1088         element.value = [ 0x29u, 0x80u, 0x14u ];
1089         assertThrown!ASN1ValuePaddingException(element.objectIdentifier);
1090         element.value = [ 0x29u, 0x80u, 0x80u ];
1091         assertThrown!ASN1ValuePaddingException(element.objectIdentifier);
1092         element.value = [ 0x80u, 0x80u, 0x80u ];
1093         assertThrown!ASN1ValuePaddingException(element.objectIdentifier);
1094 
1095         // Test for non-terminating components
1096         element.value = [ 0x29u, 0x81u ];
1097         assertThrown!ASN1TruncationException(element.objectIdentifier);
1098         element.value = [ 0x29u, 0x80u ];
1099         assertThrown!ASN1TruncationException(element.objectIdentifier);
1100         element.value = [ 0x29u, 0x14u, 0x81u ];
1101         assertThrown!ASN1TruncationException(element.objectIdentifier);
1102         element.value = [ 0x29u, 0x14u, 0x80u ];
1103         assertThrown!ASN1TruncationException(element.objectIdentifier);
1104 
1105         // This one should not fail. 0x80u is valid for the first octet.
1106         element.value = [ 0x80u, 0x14u, 0x14u ];
1107         assertNotThrown!ASN1ValuePaddingException(element.objectIdentifier);
1108     }
1109 
1110     /**
1111         Decodes an $(D ObjectDescriptor), which is a string consisting of only
1112         graphical characters. In fact, $(D ObjectDescriptor) is actually implicitly
1113         just a $(MONO GraphicString)! The formal specification for an $(D ObjectDescriptor)
1114         is:
1115 
1116         $(MONO ObjectDescriptor ::= [UNIVERSAL 7] IMPLICIT GraphicString)
1117 
1118         $(MONO GraphicString) is just a string containing only characters between
1119         and including $(D 0x20) and $(D 0x7E), therefore ObjectDescriptor is just
1120         $(D 0x20) and $(D 0x7E).
1121 
1122         Throws:
1123         $(UL
1124             $(LI $(D ASN1ValueCharactersException)
1125                 if the encoded value contains any character outside of
1126                 $(D 0x20) to $(D 0x7E), which means any control characters or $(MONO DELETE))
1127             $(LI $(D ASN1RecursionException)
1128                 if using constructed form and the element
1129                 is constructed of too many nested constructed elements)
1130             $(LI $(D ASN1TagClassException)
1131                 if any nested primitives do not share the
1132                 same tag class as their outer constructed element)
1133             $(LI $(D ASN1TagNumberException)
1134                 if any nested primitives do not share the
1135                 same tag number as their outer constructed element)
1136         )
1137 
1138         Citations:
1139         $(UL
1140             $(LI Dubuisson, Olivier. “Basic Encoding Rules (BER).”
1141                 $(I ASN.1: Communication between Heterogeneous Systems),
1142                 Morgan Kaufmann, 2001, pp. 175-178.)
1143             $(LI $(LINK https://en.wikipedia.org/wiki/ISO/IEC_2022, The Wikipedia Page on ISO 2022))
1144             $(LI $(LINK https://www.iso.org/standard/22747.html, ISO 2022))
1145         )
1146     */
1147     override public @property @system
1148     string objectDescriptor() const
1149     {
1150         if (this.construction == ASN1Construction.primitive)
1151         {
1152             if (this.value.length > 1000u)
1153                 throw new ASN1ValueSizeException
1154                 (0u, 1000u, this.value.length, "decode a primitively-encoded ObjectDescriptor");
1155 
1156             foreach (immutable character; this.value)
1157             {
1158                 if ((!character.isGraphical) && (character != ' '))
1159                     throw new ASN1ValueCharactersException
1160                     ("all characters within the range 0x20 to 0x7E", character, "ObjectDescriptor");
1161             }
1162             return cast(string) this.value;
1163         }
1164         else
1165         {
1166             if (this.valueRecursionCount++ == this.nestingRecursionLimit)
1167                 throw new ASN1RecursionException(this.nestingRecursionLimit, "parse an ObjectDescriptor");
1168             scope(exit) this.valueRecursionCount--; // So exceptions do not leave residual recursion.
1169 
1170             CERElement[] substrings = this.sequence;
1171             if (substrings.length < 2u)
1172                 throw new ASN1ValueException
1173                 (
1174                     "This exception was thrown because you attempted to decode " ~
1175                     "a constructed ObjectDescriptor that was constituted by fewer " ~
1176                     "than two nested elements. When using the Canonical Encoding " ~
1177                     "Rules (CER), a legitimate constructed ObjectDescriptor must " ~
1178                     "contain at least two nested elements, and each but the " ~
1179                     "last must contain exactly 1000 content octets. The last " ~
1180                     "nested element may have between 1 and 1000 content " ~
1181                     "octets inclusively. This element should have been encoded " ~
1182                     "primitively using definite-length encoding."
1183                 );
1184 
1185             foreach (substring; substrings[0 .. $-1])
1186             {
1187                 if (substring.length != 1000u)
1188                     throw new ASN1ValueSizeException
1189                     (1000u, 1000u, substring.length, "decode a substring of a constructed ObjectDescriptor");
1190             }
1191 
1192             if (substrings[$-1].length == 0u)
1193                 throw new ASN1ValueSizeException
1194                 (1u, 1000u, substrings[$-1].length, "decode the last substring of a constructed ObjectDescriptor");
1195 
1196             Appender!string appendy = appender!string();
1197             foreach (substring; substrings)
1198             {
1199                 if (substring.tagClass != this.tagClass)
1200                     throw new ASN1TagClassException
1201                     ([ this.tagClass ], substring.tagClass, "decode a substring of a constructed ObjectDescriptor");
1202 
1203                 if (substring.construction != ASN1Construction.primitive)
1204                     throw new ASN1ConstructionException
1205                     (substring.construction, "decode a substring of a constructed ObjectDescriptor");
1206 
1207                 if (substring.tagNumber != this.tagNumber)
1208                     throw new ASN1TagNumberException
1209                     ([ this.tagNumber ], substring.tagNumber, "decode a substring of a constructed ObjectDescriptor");
1210 
1211                 appendy.put(substring.octetString);
1212             }
1213             return appendy.data;
1214         }
1215     }
1216 
1217     /**
1218         Encodes an $(D ObjectDescriptor), which is a string consisting of only
1219         graphical characters. In fact, $(D ObjectDescriptor) is actually implicitly
1220         just a $(MONO GraphicString)! The formal specification for an $(D ObjectDescriptor)
1221         is:
1222 
1223         $(MONO ObjectDescriptor ::= [UNIVERSAL 7] IMPLICIT GraphicString)
1224 
1225         $(MONO GraphicString) is just a string containing only characters between
1226         and including $(D 0x20) and $(D 0x7E), therefore ObjectDescriptor is just
1227         $(D 0x20) and $(D 0x7E).
1228 
1229         Throws:
1230         $(UL
1231             $(LI $(D ASN1ValueCharactersException)
1232                 if the string value contains any character outside of
1233                 $(D 0x20) to $(D 0x7E), which means any control characters or $(MONO DELETE))
1234         )
1235 
1236         Citations:
1237         $(UL
1238             $(LI Dubuisson, Olivier. “Basic Encoding Rules (BER).”
1239                 $(I ASN.1: Communication between Heterogeneous Systems),
1240                 Morgan Kaufmann, 2001, pp. 175-178.)
1241             $(LI $(LINK https://en.wikipedia.org/wiki/ISO/IEC_2022, The Wikipedia Page on ISO 2022))
1242             $(LI $(LINK https://www.iso.org/standard/22747.html, ISO 2022))
1243         )
1244     */
1245     override public @property @system
1246     void objectDescriptor(in string value)
1247     {
1248         foreach (immutable character; value)
1249         {
1250             if ((!character.isGraphical) && (character != ' '))
1251                 throw new ASN1ValueCharactersException
1252                 ("all characters within the range 0x20 to 0x7E", character, "ObjectDescriptor");
1253         }
1254 
1255         if (value.length <= 1000u)
1256         {
1257             this.value = cast(ubyte[]) value;
1258         }
1259         else
1260         {
1261             CERElement[] primitives;
1262             size_t i = 0u;
1263             while (i+1000u < value.length)
1264             {
1265                 CERElement x = new CERElement();
1266                 x.tagNumber = this.tagNumber;
1267                 x.construction = ASN1Construction.primitive;
1268                 x.value = cast(ubyte[]) value[i .. i+1000u];
1269                 primitives ~= x;
1270                 i += 1000u;
1271             }
1272 
1273             CERElement y = new CERElement();
1274             y.tagNumber = this.tagNumber;
1275             y.construction = ASN1Construction.primitive;
1276             y.value = cast(ubyte[]) value[i .. $];
1277             primitives ~= y;
1278 
1279             this.sequence = primitives;
1280             this.construction = ASN1Construction.constructed;
1281         }
1282     }
1283 
1284     @system
1285     unittest
1286     {
1287         void test(size_t length)
1288         {
1289             char[] data;
1290             data.length = length;
1291             for (size_t i = 0u; i < data.length; i++)
1292             {
1293                 data[i] = ((i % 0x20u) + 0x41u);
1294             }
1295             CERElement el = new CERElement();
1296             el.objectDescriptor = cast(string) data;
1297             assert(el.objectDescriptor == cast(string) data);
1298         }
1299         test(0u);
1300         test(1u);
1301         test(8u);
1302         test(127u);
1303         test(128u);
1304         test(129u);
1305         test(192u);
1306         test(999u);
1307         test(1000u);
1308         test(1001u);
1309         test(2017u);
1310     }
1311 
1312     /**
1313         Decodes an $(MONO EXTERNAL).
1314 
1315         According to the
1316         $(LINK https://www.itu.int/en/pages/default.aspx, International Telecommunications Union)'s
1317         $(LINK https://www.itu.int/rec/T-REC-X.680/en, X.680 - Abstract Syntax Notation One (ASN.1)),
1318         the abstract definition for an $(MONO EXTERNAL), after removing the comments in the
1319         specification, is as follows:
1320 
1321         $(PRE
1322             EXTERNAL ::= [UNIVERSAL 8] SEQUENCE {
1323                 identification CHOICE {
1324                     syntaxes SEQUENCE {
1325                         abstract OBJECT IDENTIFIER,
1326                         transfer OBJECT IDENTIFIER },
1327                     syntax OBJECT IDENTIFIER,
1328                     presentation-context-id INTEGER,
1329                     context-negotiation SEQUENCE {
1330                         presentation-context-id INTEGER,
1331                         transfer-syntax OBJECT IDENTIFIER },
1332                     transfer-syntax OBJECT IDENTIFIER,
1333                     fixed NULL },
1334                 data-value-descriptor ObjectDescriptor OPTIONAL,
1335                 data-value OCTET STRING }
1336                     ( WITH COMPONENTS {
1337                         ... ,
1338                         identification ( WITH COMPONENTS {
1339                             ... ,
1340                             syntaxes ABSENT,
1341                             transfer-syntax ABSENT,
1342                             fixed ABSENT } ) } )
1343         )
1344 
1345         Note that the abstract syntax resembles that of $(MONO EmbeddedPDV) and
1346         $(MONO CharacterString), except with a $(MONO WITH COMPONENTS) constraint that removes some
1347         of our choices of $(MONO identification).
1348         As can be seen on page 303 of Olivier Dubuisson's
1349         $(I $(LINK http://www.oss.com/asn1/resources/books-whitepapers-pubs/dubuisson-asn1-book.PDF,
1350             ASN.1: Communication Between Heterogeneous Systems)),
1351         after applying the $(MONO WITH COMPONENTS) constraint, our reduced syntax becomes:
1352 
1353         $(PRE
1354             EXTERNAL ::= [UNIVERSAL 8] IMPLICIT SEQUENCE {
1355                 identification CHOICE {
1356                     syntax OBJECT IDENTIFIER,
1357                     presentation-context-id INTEGER,
1358                     context-negotiation SEQUENCE {
1359                         presentation-context-id INTEGER,
1360                         transfer-syntax OBJECT IDENTIFIER } },
1361                 data-value-descriptor ObjectDescriptor OPTIONAL,
1362                 data-value OCTET STRING }
1363         )
1364 
1365         But, according to the
1366         $(LINK https://www.itu.int/en/pages/default.aspx, International Telecommunications Union)'s
1367         $(LINK http://www.itu.int/rec/T-REC-X.690/en, X.690 - ASN.1 encoding rules),
1368         section 8.18, when encoded using Basic Encoding Rules (BER), is encoded as
1369         follows, for compatibility reasons:
1370 
1371         $(PRE
1372             EXTERNAL ::= [UNIVERSAL 8] IMPLICIT SEQUENCE {
1373                 direct-reference  OBJECT IDENTIFIER OPTIONAL,
1374                 indirect-reference  INTEGER OPTIONAL,
1375                 data-value-descriptor  ObjectDescriptor  OPTIONAL,
1376                 encoding  CHOICE {
1377                     single-ASN1-type  [0] ANY,
1378                     octet-aligned     [1] IMPLICIT OCTET STRING,
1379                     arbitrary         [2] IMPLICIT BIT STRING } }
1380         )
1381 
1382         The definition above is the pre-1994 definition of $(MONO EXTERNAL). The $(MONO syntax)
1383         field of the post-1994 definition maps to the $(MONO direct-reference) field of
1384         the pre-1994 definition. The $(MONO presentation-context-id) field of the post-1994
1385         definition maps to the $(MONO indirect-reference) field of the pre-1994 definition.
1386         If $(MONO context-negotiation) is used, per the abstract syntax, then the
1387         $(MONO presentation-context-id) field of the $(MONO context-negotiation) $(MONO SEQUENCE) in the
1388         post-1994 definition maps to the $(MONO indirect-reference) field of the pre-1994
1389         definition, and the $(MONO transfer-syntax) field of the $(MONO context-negotiation)
1390         $(MONO SEQUENCE) maps to the $(MONO direct-reference) field of the pre-1994 definition.
1391 
1392         The following additional constraints are applied to the abstract syntax
1393         when using Canonical Encoding Rules or Distinguished Encoding Rules,
1394         which are also defined in the
1395         $(LINK https://www.itu.int/en/pages/default.aspx,
1396         International Telecommunications Union)'s
1397         $(LINK http://www.itu.int/rec/T-REC-X.690/en, X.690 - ASN.1 encoding rules):
1398 
1399         $(PRE
1400             EXTERNAL ( WITH COMPONENTS {
1401                 ... ,
1402                 identification ( WITH COMPONENTS {
1403                     ... ,
1404                     presentation-context-id ABSENT,
1405                     context-negotiation ABSENT } ) } )
1406         )
1407 
1408         The stated purpose of the constraints shown above is to restrict the use of
1409         the $(MONO presentation-context-id), either by itself or within the
1410         $(MONO context-negotiation), which makes the following the effective abstract
1411         syntax of $(MONO EXTERNAL) when using Canonical Encoding Rules or
1412         Distinguished Encoding Rules:
1413 
1414         $(PRE
1415             EXTERNAL ::= [UNIVERSAL 8] SEQUENCE {
1416                 identification CHOICE {
1417                     syntaxes SEQUENCE {
1418                         abstract OBJECT IDENTIFIER,
1419                         transfer OBJECT IDENTIFIER },
1420                     syntax OBJECT IDENTIFIER,
1421                     presentation-context-id INTEGER,
1422                     context-negotiation SEQUENCE {
1423                         presentation-context-id INTEGER,
1424                         transfer-syntax OBJECT IDENTIFIER },
1425                     transfer-syntax OBJECT IDENTIFIER,
1426                     fixed NULL },
1427                 data-value-descriptor ObjectDescriptor OPTIONAL,
1428                 data-value OCTET STRING }
1429                     ( WITH COMPONENTS {
1430                         ... ,
1431                         identification ( WITH COMPONENTS {
1432                             ... ,
1433                             syntaxes ABSENT,
1434                             presentation-context-id ABSENT,
1435                             context-negotiation ABSENT,
1436                             transfer-syntax ABSENT,
1437                             fixed ABSENT } ) } )
1438         )
1439 
1440         With the constraints applied, the abstract syntax for $(MONO EXTERNAL)s encoded
1441         using Canonical Encoding Rules or Distinguished Encoding Rules becomes:
1442 
1443         $(PRE
1444             EXTERNAL ::= [UNIVERSAL 8] SEQUENCE {
1445                 identification CHOICE {
1446                     syntax OBJECT IDENTIFIER },
1447                 data-value-descriptor ObjectDescriptor OPTIONAL,
1448                 data-value OCTET STRING }
1449         )
1450 
1451         Upon removing the $(MONO CHOICE) tag (since you have no choice but to use syntax
1452         at this point), the encoding definition when using
1453         Canonical Encoding Rules or Distinguished Encoding Rules:
1454 
1455         $(PRE
1456             EXTERNAL ::= [UNIVERSAL 8] SEQUENCE {
1457                 syntax OBJECT IDENTIFIER,
1458                 data-value-descriptor ObjectDescriptor OPTIONAL,
1459                 data-value OCTET STRING }
1460         )
1461 
1462         Returns: an instance of $(D types.universal.external.External)
1463 
1464         Throws:
1465         $(UL
1466             $(LI $(D ASN1ValueException)
1467                 if the SEQUENCE does not contain two to four elements)
1468             $(LI $(D ASN1RecursionException)
1469                 if using constructed form and the element
1470                 is constructed of too many nested constructed elements)
1471             $(LI $(D ASN1TagClassException)
1472                 if any nested primitives do not have the correct tag class)
1473             $(LI $(D ASN1ConstructionException)
1474                 if any element has the wrong construction)
1475             $(LI $(D ASN1TagNumberException)
1476                 if any nested primitives do not have the correct tag number)
1477             $(LI $(D ASN1ValueCharactersException)
1478                 if a data-value-descriptor is supplied with invalid characters)
1479         )
1480     */
1481     deprecated override public @property @system
1482     External external() const
1483     {
1484         if (this.construction != ASN1Construction.constructed)
1485             throw new ASN1ConstructionException
1486             (this.construction, "decode an EXTERNAL");
1487 
1488         const CERElement[] components = this.sequence;
1489         External ext = External();
1490         ASN1ContextSwitchingTypeID identification = ASN1ContextSwitchingTypeID();
1491 
1492         if (components.length < 2u || components.length > 3u)
1493             throw new ASN1ValueException
1494             (
1495                 "This exception was thrown because you attempted to decode " ~
1496                 "an EXTERNAL that contained too many or too few elements. " ~
1497                 "An EXTERNAL should have either two and three elements: " ~
1498                 "a direct-reference (syntax), an optional " ~
1499                 "data-value-descriptor, and an encoding (data-value). " ~
1500                 notWhatYouMeantText ~ forMoreInformationText ~
1501                 debugInformationText ~ reportBugsText
1502             );
1503 
1504         // Every component except the last must be universal class
1505         foreach (const component; components[0 .. $-1])
1506         {
1507             if (component.tagClass != ASN1TagClass.universal)
1508                 throw new ASN1TagClassException
1509                 ([ ASN1TagClass.universal ], component.tagClass, "decode all but the last component of an EXTERNAL");
1510         }
1511 
1512         // The last tag must be context-specific class
1513         if (components[$-1].tagClass != ASN1TagClass.contextSpecific)
1514             throw new ASN1TagClassException
1515             ([ ASN1TagClass.contextSpecific ], components[$-1].tagClass, "decode the last component of an EXTERNAL");
1516 
1517         // The first component should always be primitive
1518         if (components[0].construction != ASN1Construction.primitive)
1519             throw new ASN1ConstructionException
1520             (components[0].construction, "decode the first component of an EXTERNAL");
1521 
1522         if (components[0].tagNumber != ASN1UniversalType.objectIdentifier)
1523             throw new ASN1TagNumberException
1524             ([ 2u, 6u ], components[0].tagNumber, "decode the first component of an EXTERNAL");
1525 
1526         identification.directReference = components[0].objectIdentifier;
1527         if (components.length == 3u)
1528         {
1529             if (components[1].tagNumber != ASN1UniversalType.objectDescriptor)
1530                 throw new ASN1TagNumberException
1531                 (
1532                     [ ASN1UniversalType.objectDescriptor ],
1533                     components[1].tagNumber,
1534                     "decode the second of three subcomponents of an EXTERNAL"
1535                 );
1536 
1537             ext.dataValueDescriptor = components[1].objectDescriptor;
1538         }
1539 
1540         switch (components[$-1].tagNumber)
1541         {
1542             case (0u): // single-ASN1-value
1543             {
1544                 ext.encoding = ASN1ExternalEncodingChoice.singleASN1Type;
1545                 break;
1546             }
1547             case (1u): // octet-aligned
1548             {
1549                 ext.encoding = ASN1ExternalEncodingChoice.octetAligned;
1550                 break;
1551             }
1552             case (2u): // arbitrary
1553             {
1554                 ext.encoding = ASN1ExternalEncodingChoice.arbitrary;
1555                 break;
1556             }
1557             default:
1558                 throw new ASN1TagNumberException
1559                 ([ 0u, 1u, 2u ], components[$-1].tagNumber, "decode an EXTERNAL identification");
1560         }
1561 
1562         ext.dataValue = components[$-1].value.dup;
1563         ext.identification = identification;
1564         return ext;
1565     }
1566 
1567     /**
1568         Encodes an $(MONO EXTERNAL).
1569 
1570         According to the
1571         $(LINK https://www.itu.int/en/pages/default.aspx, International Telecommunications Union)'s
1572         $(LINK https://www.itu.int/rec/T-REC-X.680/en, X.680 - Abstract Syntax Notation One (ASN.1)),
1573         the abstract definition for an $(MONO EXTERNAL), after removing the comments in the
1574         specification, is as follows:
1575 
1576         $(PRE
1577             EXTERNAL ::= [UNIVERSAL 8] SEQUENCE {
1578                 identification CHOICE {
1579                     syntaxes SEQUENCE {
1580                         abstract OBJECT IDENTIFIER,
1581                         transfer OBJECT IDENTIFIER },
1582                     syntax OBJECT IDENTIFIER,
1583                     presentation-context-id INTEGER,
1584                     context-negotiation SEQUENCE {
1585                         presentation-context-id INTEGER,
1586                         transfer-syntax OBJECT IDENTIFIER },
1587                     transfer-syntax OBJECT IDENTIFIER,
1588                     fixed NULL },
1589                 data-value-descriptor ObjectDescriptor OPTIONAL,
1590                 data-value OCTET STRING }
1591                     ( WITH COMPONENTS {
1592                         ... ,
1593                         identification ( WITH COMPONENTS {
1594                             ... ,
1595                             syntaxes ABSENT,
1596                             transfer-syntax ABSENT,
1597                             fixed ABSENT } ) } )
1598         )
1599 
1600         Note that the abstract syntax resembles that of $(MONO EmbeddedPDV) and
1601         $(MONO CharacterString), except with a $(MONO WITH COMPONENTS) constraint that removes some
1602         of our choices of $(MONO identification).
1603         As can be seen on page 303 of Olivier Dubuisson's
1604         $(I $(LINK http://www.oss.com/asn1/resources/books-whitepapers-pubs/dubuisson-asn1-book.PDF,
1605             ASN.1: Communication Between Heterogeneous Systems)),
1606         after applying the $(MONO WITH COMPONENTS) constraint, our reduced syntax becomes:
1607 
1608         $(PRE
1609             EXTERNAL ::= [UNIVERSAL 8] IMPLICIT SEQUENCE {
1610                 identification CHOICE {
1611                     syntax OBJECT IDENTIFIER,
1612                     presentation-context-id INTEGER,
1613                     context-negotiation SEQUENCE {
1614                         presentation-context-id INTEGER,
1615                         transfer-syntax OBJECT IDENTIFIER } },
1616                 data-value-descriptor ObjectDescriptor OPTIONAL,
1617                 data-value OCTET STRING }
1618         )
1619 
1620         But, according to the
1621         $(LINK https://www.itu.int/en/pages/default.aspx, International Telecommunications Union)'s
1622         $(LINK http://www.itu.int/rec/T-REC-X.690/en, X.690 - ASN.1 encoding rules),
1623         section 8.18, when encoded using Basic Encoding Rules (BER), is encoded as
1624         follows, for compatibility reasons:
1625 
1626         $(PRE
1627             EXTERNAL ::= [UNIVERSAL 8] IMPLICIT SEQUENCE {
1628                 direct-reference  OBJECT IDENTIFIER OPTIONAL,
1629                 indirect-reference  INTEGER OPTIONAL,
1630                 data-value-descriptor  ObjectDescriptor  OPTIONAL,
1631                 encoding  CHOICE {
1632                     single-ASN1-type  [0] ANY,
1633                     octet-aligned     [1] IMPLICIT OCTET STRING,
1634                     arbitrary         [2] IMPLICIT BIT STRING } }
1635         )
1636 
1637         The definition above is the pre-1994 definition of $(MONO EXTERNAL). The $(MONO syntax)
1638         field of the post-1994 definition maps to the $(MONO direct-reference) field of
1639         the pre-1994 definition. The $(MONO presentation-context-id) field of the post-1994
1640         definition maps to the $(MONO indirect-reference) field of the pre-1994 definition.
1641         If $(MONO context-negotiation) is used, per the abstract syntax, then the
1642         $(MONO presentation-context-id) field of the $(MONO context-negotiation) $(MONO SEQUENCE) in the
1643         post-1994 definition maps to the $(MONO indirect-reference) field of the pre-1994
1644         definition, and the $(MONO transfer-syntax) field of the $(MONO context-negotiation)
1645         $(MONO SEQUENCE) maps to the $(MONO direct-reference) field of the pre-1994 definition.
1646 
1647         The following additional constraints are applied to the abstract syntax
1648         when using Canonical Encoding Rules or Distinguished Encoding Rules,
1649         which are also defined in the
1650         $(LINK https://www.itu.int/en/pages/default.aspx,
1651         International Telecommunications Union)'s
1652         $(LINK http://www.itu.int/rec/T-REC-X.690/en, X.690 - ASN.1 encoding rules):
1653 
1654         $(PRE
1655             EXTERNAL ( WITH COMPONENTS {
1656                 ... ,
1657                 identification ( WITH COMPONENTS {
1658                     ... ,
1659                     presentation-context-id ABSENT,
1660                     context-negotiation ABSENT } ) } )
1661         )
1662 
1663         The stated purpose of the constraints shown above is to restrict the use of
1664         the $(MONO presentation-context-id), either by itself or within the
1665         $(MONO context-negotiation), which makes the following the effective abstract
1666         syntax of $(MONO EXTERNAL) when using Canonical Encoding Rules or
1667         Distinguished Encoding Rules:
1668 
1669         $(PRE
1670             EXTERNAL ::= [UNIVERSAL 8] SEQUENCE {
1671                 identification CHOICE {
1672                     syntaxes SEQUENCE {
1673                         abstract OBJECT IDENTIFIER,
1674                         transfer OBJECT IDENTIFIER },
1675                     syntax OBJECT IDENTIFIER,
1676                     presentation-context-id INTEGER,
1677                     context-negotiation SEQUENCE {
1678                         presentation-context-id INTEGER,
1679                         transfer-syntax OBJECT IDENTIFIER },
1680                     transfer-syntax OBJECT IDENTIFIER,
1681                     fixed NULL },
1682                 data-value-descriptor ObjectDescriptor OPTIONAL,
1683                 data-value OCTET STRING }
1684                     ( WITH COMPONENTS {
1685                         ... ,
1686                         identification ( WITH COMPONENTS {
1687                             ... ,
1688                             syntaxes ABSENT,
1689                             presentation-context-id ABSENT,
1690                             context-negotiation ABSENT,
1691                             transfer-syntax ABSENT,
1692                             fixed ABSENT } ) } )
1693         )
1694 
1695         With the constraints applied, the abstract syntax for $(MONO EXTERNAL)s encoded
1696         using Canonical Encoding Rules or Distinguished Encoding Rules becomes:
1697 
1698         $(PRE
1699             EXTERNAL ::= [UNIVERSAL 8] SEQUENCE {
1700                 identification CHOICE {
1701                     syntax OBJECT IDENTIFIER },
1702                 data-value-descriptor ObjectDescriptor OPTIONAL,
1703                 data-value OCTET STRING }
1704         )
1705 
1706         Upon removing the $(MONO CHOICE) tag (since you have no choice but to use syntax
1707         at this point), the encoding definition when using
1708         Canonical Encoding Rules or Distinguished Encoding Rules:
1709 
1710         $(PRE
1711             EXTERNAL ::= [UNIVERSAL 8] SEQUENCE {
1712                 syntax OBJECT IDENTIFIER,
1713                 data-value-descriptor ObjectDescriptor OPTIONAL,
1714                 data-value OCTET STRING }
1715         )
1716 
1717         Returns: an instance of $(D types.universal.external.External)
1718 
1719         Throws:
1720         $(UL
1721             $(LI $(D ASN1ValueException)
1722                 if something other than $(D syntax) is used for $(MONO identification))
1723             $(LI $(D ASN1ValueCharactersException)
1724                 if a data-value-descriptor is supplied with invalid characters)
1725         )
1726     */
1727     deprecated override public @property @system
1728     void external(in External value)
1729     out
1730     {
1731         assert(this.value.length > 0u);
1732     }
1733     body
1734     {
1735         scope(success) this.construction = ASN1Construction.constructed;
1736         CERElement[] components = [];
1737 
1738         if (!(value.identification.syntax.isNull))
1739         {
1740             CERElement directReference = new CERElement();
1741             directReference.tagNumber = ASN1UniversalType.objectIdentifier;
1742             directReference.objectIdentifier = value.identification.directReference;
1743             components ~= directReference;
1744         }
1745         else // it must be the presentationContextID / indirectReference INTEGER
1746             throw new ASN1ValueException
1747             (
1748                 "This exception was thrown because you attempted to encode an " ~
1749                 "EXTERNAL that used something other than syntax as the CHOICE " ~
1750                 "of identification, which is not permitted when using " ~
1751                 "Canonical Encoding Rules (CER). " ~
1752                 notWhatYouMeantText ~ forMoreInformationText ~
1753                 debugInformationText ~ reportBugsText
1754             );
1755 
1756         CERElement dataValueDescriptor = new CERElement();
1757         dataValueDescriptor.tagNumber = ASN1UniversalType.objectDescriptor;
1758         dataValueDescriptor.objectDescriptor = value.dataValueDescriptor;
1759         components ~= dataValueDescriptor;
1760 
1761         CERElement dataValue = new CERElement();
1762         dataValue.tagClass = ASN1TagClass.contextSpecific;
1763         dataValue.tagNumber = value.encoding;
1764         dataValue.value = value.dataValue.dup;
1765 
1766         components ~= dataValue;
1767         this.sequence = components;
1768     }
1769 
1770     /*
1771         Since a CER-encoded EXTERNAL can only use the syntax field for
1772         the CHOICE of identification, this unit test ensures that an
1773         exception if thrown if an alternative identification is supplied.
1774     */
1775     @system
1776     unittest
1777     {
1778         ASN1ContextSwitchingTypeID id = ASN1ContextSwitchingTypeID();
1779         id.presentationContextID = 27L;
1780 
1781         External input = External();
1782         input.identification = id;
1783         input.dataValueDescriptor = "external";
1784         input.dataValue = [ 0x01u, 0x02u, 0x03u, 0x04u ];
1785 
1786         CERElement el = new CERElement();
1787         assertThrown!ASN1ValueException(el.external = input);
1788     }
1789 
1790     // Inspired by CVE-2017-9023
1791     @system
1792     unittest
1793     {
1794         ubyte[] external = [ // This is valid
1795             0x08u, 0x09u, // EXTERNAL, Length 9
1796                 0x02u, 0x01u, 0x1Bu, // INTEGER 27
1797                 0x81u, 0x04u, 0x01u, 0x02u, 0x03u, 0x04u // OCTET STRING 1,2,3,4
1798         ];
1799 
1800         // Valid values for octet[2]: 02 06
1801         for (ubyte i = 0x07u; i < 0x1Eu; i++)
1802         {
1803             external[2] = i;
1804             size_t x = 0u;
1805             CERElement el = new CERElement(x, external);
1806             assertThrown!ASN1Exception(el.external);
1807         }
1808 
1809         // Valid values for octet[5]: 80 - 82 (Anything else is an invalid value)
1810         for (ubyte i = 0x82u; i < 0x9Eu; i++)
1811         {
1812             external[5] = i;
1813             size_t x = 0u;
1814             CERElement el = new CERElement(x, external);
1815             assertThrown!ASN1Exception(el.external);
1816         }
1817     }
1818 
1819     // Assert that duplicate elements throw exceptions
1820     @system
1821     unittest
1822     {
1823         ubyte[] external;
1824 
1825         external = [ // This is invalid
1826             0x08u, 0x0Cu, // EXTERNAL, Length 12
1827                 0x02u, 0x01u, 0x1Bu, // INTEGER 27
1828                 0x02u, 0x01u, 0x1Bu, // INTEGER 27
1829                 0x81, 0x04u, 0x01u, 0x02u, 0x03u, 0x04u // OCTET STRING 1,2,3,4
1830         ];
1831         assertThrown!ASN1Exception((new CERElement(external)).external);
1832 
1833         external = [ // This is invalid
1834             0x08u, 0x0Eu, // EXTERNAL, Length 14
1835                 0x06u, 0x02u, 0x2Au, 0x03u, // OBJECT IDENTIFIER 1.2.3
1836                 0x06u, 0x02u, 0x2Au, 0x03u, // OBJECT IDENTIFIER 1.2.3
1837                 0x81, 0x04u, 0x01u, 0x02u, 0x03u, 0x04u // OCTET STRING 1,2,3,4
1838         ];
1839         assertThrown!ASN1Exception((new CERElement(external)).external);
1840 
1841         external = [ // This is invalid
1842             0x08u, 0x12u, // EXTERNAL, Length 18
1843                 0x06u, 0x02u, 0x2Au, 0x03u, // OBJECT IDENTIFIER 1.2.3
1844                 0x07u, 0x02u, 0x45u, 0x45u, // ObjectDescriptor "EE"
1845                 0x07u, 0x02u, 0x45u, 0x45u, // ObjectDescriptor "EE"
1846                 0x81u, 0x04u, 0x01u, 0x02u, 0x03u, 0x04u // OCTET STRING 1,2,3,4
1847         ];
1848         assertThrown!ASN1Exception((new CERElement(external)).external);
1849 
1850         external = [ // This is invalid
1851             0x08u, 0x14u, // EXTERNAL, Length 20
1852                 0x06u, 0x02u, 0x2Au, 0x03u, // OBJECT IDENTIFIER 1.2.3
1853                 0x07u, 0x02u, 0x45u, 0x45u, // ObjectDescriptor "EE"
1854                 0x81u, 0x04u, 0x01u, 0x02u, 0x03u, 0x04u, // OCTET STRING 1,2,3,4
1855                 0x81u, 0x04u, 0x01u, 0x02u, 0x03u, 0x04u // OCTET STRING 1,2,3,4
1856         ];
1857         assertThrown!ASN1Exception((new CERElement(external)).external);
1858     }
1859 
1860     /**
1861         Decodes a floating-point type.
1862 
1863         Note that this method assumes that your machine uses
1864         $(LINK http://ieeexplore.ieee.org/document/4610935/, IEEE 754-2008)
1865         floating point format.
1866 
1867         Throws:
1868         $(UL
1869             $(LI $(D ASN1ConstructionException) if the element is marked as "constructed")
1870             $(LI $(D ASN1TruncationException) if the value appears to be "cut off")
1871             $(LI $(D ConvException) if character-encoding cannot be converted to
1872                 the selected floating-point type, T)
1873             $(LI $(D ConvOverflowException) if the character-encoding encodes a
1874                 number that is too big for the selected floating-point
1875                 type to express)
1876             $(LI $(D ASN1ValueSizeException) if the binary-encoding contains fewer
1877                 bytes than the information byte purports, or if the
1878                 binary-encoded mantissa is too big to be expressed by an
1879                 unsigned long integer)
1880             $(LI $(D ASN1ValueException) if a complicated-form exponent or a
1881                 non-zero-byte mantissa encodes a zero or even number, of if a base-10
1882                 (character-encoded) REAL is has something wrong that is not
1883                 covered by $(D ASN1ValueCharactersException) or
1884                 $(D ASN1ValuePaddingException))
1885             $(LI $(D ASN1ValueUndefinedException) if both bits indicating the base in the
1886                 information byte of a binary-encoded $(MONO REAL)'s information byte
1887                 are set, which would indicate an invalid base, or if a special
1888                 value has been indicated that is not defined by the specification)
1889             $(LI $(D ASN1ValuePaddingException) if a base-10 (character-encoded)
1890                 REAL is encoded with leading zeroes, or trailing zeroes on the
1891                 mantissa, or if the mantissa is encoded on more than the minimal
1892                 necessary octets)
1893             $(LI $(D ASN1ValueCharactersException) if a base-10 (character-encoded)
1894                 REAL is encoded with characters that are not numeric, a decimal, E,
1895                 or plus or minus)
1896         )
1897 
1898         Citations:
1899         $(UL
1900             $(LI Dubuisson, Olivier. “Basic Encoding Rules (BER).”
1901                 $(I ASN.1: Communication between Heterogeneous Systems),
1902                 Morgan Kaufmann, 2001, pp. 400-402.)
1903             $(LI $(LINK https://www.iso.org/standard/12285.html, ISO 6093))
1904         )
1905     */
1906     public @property @system
1907     T realNumber(T)() const
1908     if (isFloatingPoint!T)
1909     {
1910         if (this.construction != ASN1Construction.primitive)
1911             throw new ASN1ConstructionException
1912             (this.construction, "decode a REAL");
1913 
1914         if (this.value.length == 0u) return cast(T) 0.0;
1915         switch (this.value[0] & 0b11000000u)
1916         {
1917             case (0b01000000u):
1918             {
1919                 if (this.value[0] == ASN1SpecialRealValue.notANumber) return T.nan;
1920                 if (this.value[0] == ASN1SpecialRealValue.minusZero) return -0.0;
1921                 if (this.value[0] == ASN1SpecialRealValue.plusInfinity) return T.infinity;
1922                 if (this.value[0] == ASN1SpecialRealValue.minusInfinity) return -T.infinity;
1923                 throw new ASN1ValueUndefinedException
1924                 (
1925                     "This exception was thrown because you attempted to decode " ~
1926                     "a REAL whose information byte indicated a special value " ~
1927                     "not recognized by the specification. The only special " ~
1928                     "values recognized by the specification are PLUS-INFINITY, " ~
1929                     "MINUS-INFINITY, NOT-A-NUMBER, and minus zero, identified " ~
1930                     "by information bytes of 0x40, 0x41 0x42, 0x43 respectively. " ~
1931                     notWhatYouMeantText ~ forMoreInformationText ~
1932                     debugInformationText ~ reportBugsText
1933                 );
1934             }
1935             case (0b00000000u): // Character Encoding
1936             {
1937                 /* NOTE:
1938                     Specification X.690 lays out very strict standards for the
1939                     Canonical Encoding Rules (CER) and Distinguished Encoding
1940                     Rules (DER) base-10-encoded REAL.
1941 
1942                     The character encoding form must be NR3, from ISO 6093, but
1943                     with even more restrictions applied.
1944 
1945                     It must be encoded like so:
1946                     * No whitespace whatsoever
1947                     * No leading zeroes under any circumstance.
1948                     * No trailing zeroes under any circumstance.
1949                     * No plus sign unless exponent is 0.
1950 
1951                     A valid encoding looks like this: 22.E-5
1952                 */
1953                 import std.conv : to;
1954                 import std.string : indexOf;
1955 
1956                 immutable string invalidNR3RealMessage =
1957                     "This exception was thrown because you attempted to decode " ~
1958                     "a base-10 encoded REAL that was encoded with improper " ~
1959                     "format. When using Canonical Encoding Rules (CER) or " ~
1960                     "Distinguished Encoding Rules (DER), the base-10 encoded " ~
1961                     "REAL must be encoded in the NR3 format specified in " ~
1962                     "ISO 6093. Further, there may be no whitespace, no leading " ~
1963                     "zeroes, no trailing zeroes on the mantissa, before or " ~
1964                     "after the decimal point, and no plus sign should ever " ~
1965                     "appear, unless the exponent is 0, in which case, the " ~
1966                     "exponent should read '+0'. Further, there must be a " ~
1967                     "decimal point, immediately followed by a capital 'E'." ~
1968                     "Your problem, in this case, was that your encoded value ";
1969 
1970                 // Smallest possible is '#.E#'. Decimal is necessary.
1971                 if (this.value.length < 5u)
1972                     throw new ASN1ValueSizeException
1973                     (5u, size_t.max, this.value.length, "decode a base-10 encoded REAL");
1974 
1975                 if (this.value[0] != 0b00000011u)
1976                     throw new ASN1ValueException
1977                     (invalidNR3RealMessage ~ "was not NR3 format at all.");
1978 
1979                 string valueString = cast(string) this.value[1 .. $];
1980 
1981                 foreach (character; valueString)
1982                 {
1983                     import std.ascii : isWhite;
1984                     if
1985                     (
1986                         character.isWhite ||
1987                         character == ',' ||
1988                         character == '_'
1989                     )
1990                         throw new ASN1ValueCharactersException
1991                         ("1234567890+-.E", character, "decode a base-10 encoded REAL");
1992                 }
1993 
1994                 if
1995                 (
1996                     valueString[0] == '0' ||
1997                     (valueString[0] == '-' && valueString[1] == '0')
1998                 )
1999                     throw new ASN1ValuePaddingException
2000                     (invalidNR3RealMessage ~ "contained a leading zero.");
2001 
2002                 ptrdiff_t indexOfDecimalPoint = valueString.indexOf(".");
2003                 if (indexOfDecimalPoint == -1)
2004                     throw new ASN1ValueException
2005                     (invalidNR3RealMessage ~ "contained no decimal point.");
2006 
2007                 if (valueString[indexOfDecimalPoint+1] != 'E')
2008                     throw new ASN1ValueException
2009                     (invalidNR3RealMessage ~ "contained no 'E'.");
2010 
2011                 if (valueString[indexOfDecimalPoint-1] == '0')
2012                     throw new ASN1ValuePaddingException
2013                     (invalidNR3RealMessage ~ "contained a trailing zero on the mantissa.");
2014 
2015                 if (valueString[$-2 .. $] != "+0" && canFind(valueString, '+'))
2016                     throw new ASN1ValueException
2017                     (invalidNR3RealMessage ~ "contained an illegitimate plus sign.");
2018 
2019                 if (canFind(valueString, "E0") || canFind(valueString, "E-0"))
2020                     throw new ASN1ValuePaddingException
2021                     (invalidNR3RealMessage ~ "contained a leading zero on the exponent.");
2022 
2023                 return to!(T)(valueString);
2024             }
2025             case 0b10000000u, 0b11000000u: // Binary Encoding
2026             {
2027                 ulong mantissa;
2028                 short exponent;
2029                 ubyte scale;
2030                 ubyte base;
2031                 size_t startOfMantissa;
2032 
2033                 switch (this.value[0] & 0b00000011u)
2034                 {
2035                     case 0b00000000u: // Exponent on the following octet
2036                     {
2037                         if (this.value.length < 3u)
2038                             throw new ASN1TruncationException
2039                             (3u, this.value.length, "decode a REAL exponent");
2040 
2041                         exponent = cast(short) cast(byte) this.value[1];
2042                         startOfMantissa = 2u;
2043                         break;
2044                     }
2045                     case 0b00000001u: // Exponent on the following two octets
2046                     {
2047                         if (this.value.length < 4u)
2048                             throw new ASN1TruncationException
2049                             (4u, this.value.length, "decode a REAL exponent");
2050 
2051                         ubyte[] exponentBytes = this.value[1 .. 3].dup;
2052                         version (LittleEndian) exponentBytes = [ exponentBytes[1], exponentBytes[0] ];
2053                         exponent = *cast(short *) exponentBytes.ptr;
2054 
2055                         if (exponent <= byte.max && exponent >= byte.min)
2056                             throw new ASN1ValuePaddingException
2057                             (
2058                                 "This exception was thrown because you attempted " ~
2059                                 "to decode a binary-encoded REAL whose exponent " ~
2060                                 "was encoded on more bytes than necessary. This " ~
2061                                 "would not be a problem if you were using the " ~
2062                                 "Basic Encoding Rules (BER), but the Canonical " ~
2063                                 "Encoding Rules (CER) and Distinguished Encoding " ~
2064                                 "Rules (DER) require that the exponent be " ~
2065                                 "encoded on the fewest possible bytes. " ~
2066                                 notWhatYouMeantText ~ forMoreInformationText ~
2067                                 debugInformationText ~ reportBugsText
2068                             );
2069 
2070                         startOfMantissa = 3u;
2071                         break;
2072                     }
2073                     case 0b00000010u: // Exponent on the following three octets
2074                     case 0b00000011u: // Complicated
2075                     {
2076                         throw new ASN1ValueOverflowException
2077                         (
2078                             "This exception was thrown because, according to " ~
2079                             "section 11.3.1 of specification X.690, a REAL's " ~
2080                             "exponent must be encoded on the fewest possible " ~
2081                             "octets, but you attempted to decode one that was " ~
2082                             "either too big to fit in an IEEE 754 floating " ~
2083                             "point type, or would have had unnecessary leading " ~
2084                             "bytes if it could. "
2085                         );
2086                     }
2087                     default: assert(0, "Impossible binary exponent encoding on REAL type");
2088                 }
2089 
2090                 if (this.value.length - startOfMantissa > ulong.sizeof)
2091                     throw new ASN1ValueOverflowException
2092                     (
2093                         "This exception was thrown because you attempted to " ~
2094                         "decode a REAL whose mantissa was encoded on too many " ~
2095                         "bytes to decode to the largest unsigned integral data " ~
2096                         "type. "
2097                     );
2098 
2099                 ubyte[] mantissaBytes = this.value[startOfMantissa .. $].dup;
2100 
2101                 if (mantissaBytes[0] == 0x00u)
2102                     throw new ASN1ValuePaddingException
2103                     (
2104                         "This exception was thrown because you attempted to decode " ~
2105                         "a REAL mantissa that was encoded on more than the minimum " ~
2106                         "necessary bytes. " ~
2107                         notWhatYouMeantText ~ forMoreInformationText ~
2108                         debugInformationText ~ reportBugsText
2109                     );
2110 
2111                 while (mantissaBytes.length < ulong.sizeof)
2112                     mantissaBytes = (0x00u ~ mantissaBytes);
2113                 version (LittleEndian) reverse(mantissaBytes);
2114                 version (unittest) assert(mantissaBytes.length == ulong.sizeof);
2115                 mantissa = *cast(ulong *) mantissaBytes.ptr;
2116 
2117                 if (mantissa == 0u)
2118                     throw new ASN1ValueException
2119                     (
2120                         "This exception was thrown because you attempted to " ~
2121                         "decode a REAL that was encoded on more than zero " ~
2122                         "bytes, but whose mantissa encoded a zero. This " ~
2123                         "is prohibited by specification X.690. If the " ~
2124                         "abstract value encoded is a real number of zero, " ~
2125                         "the REAL must be encoded upon zero bytes. " ~
2126                         notWhatYouMeantText ~ forMoreInformationText ~
2127                         debugInformationText ~ reportBugsText
2128                     );
2129 
2130                 switch (this.value[0] & 0b00110000u)
2131                 {
2132                     case (0b00000000u):
2133                     {
2134                         /* NOTE:
2135                             Section 11.3.1 of X.690 states that, for Canonical Encoding Rules
2136                             (CER) and Distinguished Encoding Rules (DER), the mantissa must be
2137                             zero or odd.
2138                         */
2139                         if (!(mantissa & 1u))
2140                             throw new ASN1ValueException
2141                             (
2142                                 "This exception was thrown because you attempted to " ~
2143                                 "decode a base-2 encoded REAL whose mantissa was " ~
2144                                 "not zero or odd. Both Canonical Encoding Rules (CER) " ~
2145                                 "and Distinguished Encoding Rules (DER) require that " ~
2146                                 "a base-2 encoded REAL's mantissa be shifted so that " ~
2147                                 "it is either zero or odd. "
2148                             );
2149 
2150                         base = 0x02u;
2151                         break;
2152                     }
2153                     case (0b00010000u): base = 0x08u; break;
2154                     case (0b00100000u): base = 0x10u; break;
2155                     default:
2156                         throw new ASN1ValueUndefinedException
2157                         (
2158                             "This exception was thrown because you attempted to " ~
2159                             "decode a REAL that had both base bits in the " ~
2160                             "information block set, the meaning of which is " ~
2161                             "not specified. " ~
2162                             notWhatYouMeantText ~ forMoreInformationText ~
2163                             debugInformationText ~ reportBugsText
2164                         );
2165                 }
2166 
2167                 if (this.value[0] & 0b00001100u)
2168                     throw new ASN1ValueException
2169                     (
2170                         "This exception was thrown because you attempted to " ~
2171                         "decode a REAL whose scale was not zero. This would " ~
2172                         "not be a problem if you were using the Basic " ~
2173                         "Encoding Rules (BER), but specification X.690 " ~
2174                         "says that, when using the Canonical Encoding Rules " ~
2175                         "(CER) or Distinguished Encoding Rules (DER), the " ~
2176                         "scale must be zero. " ~
2177                         notWhatYouMeantText ~ forMoreInformationText ~
2178                         debugInformationText ~ reportBugsText
2179                     );
2180 
2181                 /*
2182                     For some reason that I have yet to discover, you must
2183                     cast the exponent to T. If you do not, specifically
2184                     any usage of realNumber!T() outside of this library will
2185                     produce a "floating point exception 8" message and
2186                     crash. For some reason, all of the tests pass within
2187                     this library without doing this.
2188                 */
2189                 return (
2190                     ((this.value[0] & 0b01000000u) ? -1.0 : 1.0) *
2191                     cast(T) mantissa *
2192                     2^^scale *
2193                     (cast(T) base)^^(cast(T) exponent) // base must be cast
2194                 );
2195             }
2196             default: assert(0, "Impossible information byte value appeared!");
2197         }
2198     }
2199 
2200     /**
2201         Encodes a floating-point type, using base-2 binary encoding.
2202 
2203         Note that this method assumes that your machine uses
2204         $(LINK http://ieeexplore.ieee.org/document/4610935/, IEEE 754-2008)
2205         floating point format.
2206 
2207         Citations:
2208         $(UL
2209             $(LI Dubuisson, Olivier. “Basic Encoding Rules (BER).”
2210                 $(I ASN.1: Communication between Heterogeneous Systems),
2211                 Morgan Kaufmann, 2001, pp. 400-402.)
2212             $(LI $(LINK https://www.iso.org/standard/12285.html, ISO 6093))
2213         )
2214     */
2215     public @property @system nothrow
2216     void realNumber(T)(in T value)
2217     if (isFloatingPoint!T)
2218     {
2219         scope(success) this.construction = ASN1Construction.primitive;
2220         /* NOTE:
2221             You must use isIdentical() to compare FP types to negative zero,
2222             because the basic == operator does not distinguish between zero
2223             and negative zero.
2224 
2225             isNaN() must be used to compare NaNs, because comparison using ==
2226             does not work for that at all.
2227 
2228             Also, this cannot go in a switch statement, because FP types
2229             cannot be the switch value.
2230         */
2231         if (isIdentical(value, 0.0))
2232         {
2233             this.value = [];
2234             return;
2235         }
2236         else if (isIdentical(value, -0.0))
2237         {
2238             this.value = [ ASN1SpecialRealValue.minusZero ];
2239             return;
2240         }
2241         if (value.isNaN)
2242         {
2243             this.value = [ ASN1SpecialRealValue.notANumber ];
2244             return;
2245         }
2246         else if (value == T.infinity)
2247         {
2248             this.value = [ ASN1SpecialRealValue.plusInfinity ];
2249             return;
2250         }
2251         else if (value == -T.infinity)
2252         {
2253             this.value = [ ASN1SpecialRealValue.minusInfinity ];
2254             return;
2255         }
2256 
2257         real realValue = cast(real) value;
2258         bool positive = true;
2259         ulong mantissa;
2260         short exponent;
2261 
2262         /*
2263             Per the IEEE specifications, the exponent of a floating-point
2264             type is stored with a bias, meaning that the exponent counts
2265             up from a negative number, the reaches zero at the bias. We
2266             subtract the bias from the raw binary exponent to get the
2267             actual exponent encoded in the IEEE floating-point number.
2268             In the case of an x86 80-bit extended-precision floating-point
2269             type, the bias is 16383. In the case of double-precision, it is
2270             1023. For single-precision, it is 127.
2271 
2272             We then subtract the number of bits in the fraction from the
2273             exponent, which is equivalent to having had multiplied the
2274             fraction enough to have made it an integer represented by the
2275             same sequence of bits.
2276         */
2277         ubyte[] realBytes;
2278         realBytes.length = real.sizeof;
2279         *cast(real *)&realBytes[0] = realValue;
2280 
2281         version (BigEndian)
2282         {
2283             static if (real.sizeof > 10u) realBytes = realBytes[real.sizeof-10 .. $];
2284             positive = ((realBytes[0] & 0x80u) ? false : true);
2285         }
2286         else version (LittleEndian)
2287         {
2288             static if (real.sizeof > 10u) realBytes.length = 10u;
2289             positive = ((realBytes[$-1] & 0x80u) ? false : true);
2290         }
2291         else assert(0, "Could not determine endianness");
2292 
2293         static if (real.mant_dig == 64) // x86 Extended Precision
2294         {
2295             version (BigEndian)
2296             {
2297                 exponent = (((*cast(short *) &realBytes[0]) & 0x7FFF) - 16383 - 63); // 16383 is the bias
2298                 mantissa = *cast(ulong *) &realBytes[2];
2299             }
2300             else version (LittleEndian)
2301             {
2302                 exponent = (((*cast(short *) &realBytes[8]) & 0x7FFF) - 16383 - 63); // 16383 is the bias
2303                 mantissa = *cast(ulong *) &realBytes[0];
2304             }
2305             else assert(0, "Could not determine endianness");
2306         }
2307         else if (T.mant_dig == 53) // Double Precision
2308         {
2309             /*
2310                 The IEEE 754 double-precision floating point type only stores
2311                 the fractional part of the mantissa, because there is an
2312                 implicit 1 prior to the fractional part. To retrieve the actual
2313                 mantissa encoded, we flip the bit that comes just before the
2314                 most significant bit of the fractional part of the number.
2315             */
2316             version (BigEndian)
2317             {
2318                 exponent = (((*cast(short *) &realBytes[0]) & 0x7FFF) - 1023 - 53); // 1023 is the bias
2319                 mantissa = (((*cast(ulong *) &realBytes[2]) & 0x000FFFFFFFFFFFFFu) | 0x0010000000000000u);
2320             }
2321             else version (LittleEndian)
2322             {
2323                 exponent = (((*cast(short *) &realBytes[8]) & 0x7FFF) - 1023 - 53); // 1023 is the bias
2324                 mantissa = (((*cast(ulong *) &realBytes[0]) & 0x000FFFFFFFFFFFFFu) | 0x0010000000000000u);
2325             }
2326             else assert(0, "Could not determine endianness");
2327         }
2328         else if (T.mant_dig == 24) // Single Precision
2329         {
2330             /*
2331                 The IEEE 754 single-precision floating point type only stores
2332                 the fractional part of the mantissa, because there is an
2333                 implicit 1 prior to the fractional part. To retrieve the actual
2334                 mantissa encoded, we flip the bit that comes just before the
2335                 most significant bit of the fractional part of the number.
2336             */
2337             version (BigEndian)
2338             {
2339                 exponent = ((((*cast(short *) &realBytes[0]) & 0x7F80) >> 7) - 127 - 23); // 127 is the bias
2340                 mantissa = cast(ulong) (((*cast(uint *) &realBytes[2]) & 0x007FFFFFu) | 0x00800000u);
2341             }
2342             else version (LittleEndian)
2343             {
2344                 exponent = ((((*cast(short *) &realBytes[8]) & 0x7F80) >> 7) - 127 - 23); // 127 is the bias
2345                 mantissa = cast(ulong) (((*cast(uint *) &realBytes[0]) & 0x007FFFFFu) | 0x00800000u);
2346             }
2347             else assert(0, "Could not determine endianness");
2348         }
2349         else assert(0, "Unrecognized real floating-point format.");
2350 
2351         /* NOTE:
2352             Section 11.3.1 of X.690 states that, for Canonical Encoding Rules
2353             (CER) and Distinguished Encoding Rules (DER), the mantissa must be
2354             zero or odd.
2355         */
2356         if (mantissa != 0u)
2357         {
2358             while (!(mantissa & 1u))
2359             {
2360                 mantissa >>= 1;
2361                 exponent++;
2362             }
2363             version(unittest) assert(mantissa & 1u);
2364         }
2365 
2366         ubyte[] exponentBytes;
2367         exponentBytes.length = short.sizeof;
2368         *cast(short *)exponentBytes.ptr = exponent;
2369         version (LittleEndian) exponentBytes = [ exponentBytes[1], exponentBytes[0] ]; // Manual reversal (optimization)
2370         if
2371         (
2372             (exponentBytes[0] == 0x00u && (!(exponentBytes[1] & 0x80u))) || // Unnecessary positive leading bytes
2373             (exponentBytes[0] == 0xFFu && (exponentBytes[1] & 0x80u)) // Unnecessary negative leading bytes
2374         )
2375             exponentBytes = exponentBytes[1 .. 2];
2376 
2377         ubyte[] mantissaBytes;
2378         mantissaBytes.length = ulong.sizeof;
2379         *cast(ulong *)mantissaBytes.ptr = cast(ulong) mantissa;
2380         version (LittleEndian) reverse(mantissaBytes);
2381 
2382         size_t startOfNonPadding = 0u;
2383         for (size_t i = 0u; i < mantissaBytes.length-1; i++)
2384         {
2385             if (mantissaBytes[i] != 0x00u) break;
2386             startOfNonPadding++;
2387         }
2388         mantissaBytes = mantissaBytes[startOfNonPadding .. $];
2389 
2390         ubyte infoByte =
2391             0x80u | // First bit gets set for base2, base8, or base16 encoding
2392             (positive ? 0x00u : 0x40u) | // 1 = negative, 0 = positive
2393             // Scale = 0
2394             cast(ubyte) (exponentBytes.length == 1u ?
2395                 ASN1RealExponentEncoding.followingOctet :
2396                 ASN1RealExponentEncoding.following2Octets);
2397 
2398         this.value = (infoByte ~ exponentBytes ~ mantissaBytes);
2399     }
2400 
2401     @system
2402     unittest
2403     {
2404         CERElement el = new CERElement();
2405 
2406         // float
2407         el.realNumber!float = cast(float) float.nan;
2408         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.notANumber ]);
2409         el.realNumber!double = cast(double) float.nan;
2410         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.notANumber ]);
2411         el.realNumber!real = cast(real) float.nan;
2412         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.notANumber ]);
2413 
2414         el.realNumber!float = cast(float) 0.0;
2415         assert(el.value == []);
2416         el.realNumber!double = cast(float) 0.0;
2417         assert(el.value == []);
2418         el.realNumber!real = cast(float) 0.0;
2419         assert(el.value == []);
2420 
2421         el.realNumber!float = cast(float) -0.0;
2422         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusZero ]);
2423         el.realNumber!double = cast(float) -0.0;
2424         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusZero ]);
2425         el.realNumber!real = cast(float) -0.0;
2426         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusZero ]);
2427 
2428         el.realNumber!float = cast(float) float.infinity;
2429         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.plusInfinity ]);
2430         el.realNumber!double = cast(double) float.infinity;
2431         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.plusInfinity ]);
2432         el.realNumber!real = cast(real) float.infinity;
2433         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.plusInfinity ]);
2434 
2435         el.realNumber!float = cast(float) -float.infinity;
2436         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusInfinity ]);
2437         el.realNumber!double = cast(double) -float.infinity;
2438         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusInfinity ]);
2439         el.realNumber!real = cast(real) -float.infinity;
2440         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusInfinity ]);
2441 
2442         // double
2443         el.realNumber!float = cast(float) double.nan;
2444         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.notANumber ]);
2445         el.realNumber!double = cast(double) double.nan;
2446         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.notANumber ]);
2447         el.realNumber!real = cast(real) double.nan;
2448         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.notANumber ]);
2449 
2450         el.realNumber!float = cast(double) 0.0;
2451         assert(el.value == []);
2452         el.realNumber!double = cast(double) 0.0;
2453         assert(el.value == []);
2454         el.realNumber!real = cast(double) 0.0;
2455         assert(el.value == []);
2456 
2457         el.realNumber!float = cast(double) -0.0;
2458         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusZero ]);
2459         el.realNumber!double = cast(double) -0.0;
2460         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusZero ]);
2461         el.realNumber!real = cast(double) -0.0;
2462         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusZero ]);
2463 
2464         el.realNumber!float = cast(float) double.infinity;
2465         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.plusInfinity ]);
2466         el.realNumber!double = cast(double) double.infinity;
2467         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.plusInfinity ]);
2468         el.realNumber!real = cast(real) double.infinity;
2469         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.plusInfinity ]);
2470 
2471         el.realNumber!float = cast(float) -double.infinity;
2472         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusInfinity ]);
2473         el.realNumber!double = cast(double) -double.infinity;
2474         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusInfinity ]);
2475         el.realNumber!real = cast(real) -double.infinity;
2476         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusInfinity ]);
2477 
2478         // real
2479         el.realNumber!float = cast(float) real.nan;
2480         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.notANumber ]);
2481         el.realNumber!double = cast(double) real.nan;
2482         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.notANumber ]);
2483         el.realNumber!real = cast(real) real.nan;
2484         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.notANumber ]);
2485 
2486         el.realNumber!float = cast(real) 0.0;
2487         assert(el.value == []);
2488         el.realNumber!double = cast(real) 0.0;
2489         assert(el.value == []);
2490         el.realNumber!real = cast(real) 0.0;
2491         assert(el.value == []);
2492 
2493         el.realNumber!float = cast(real) -0.0;
2494         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusZero ]);
2495         el.realNumber!double = cast(real) -0.0;
2496         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusZero ]);
2497         el.realNumber!real = cast(real) -0.0;
2498         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusZero ]);
2499 
2500         el.realNumber!float = cast(float) real.infinity;
2501         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.plusInfinity ]);
2502         el.realNumber!double = cast(double) real.infinity;
2503         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.plusInfinity ]);
2504         el.realNumber!real = cast(real) real.infinity;
2505         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.plusInfinity ]);
2506 
2507         el.realNumber!float = cast(float) -real.infinity;
2508         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusInfinity ]);
2509         el.realNumber!double = cast(double) -real.infinity;
2510         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusInfinity ]);
2511         el.realNumber!real = cast(real) -real.infinity;
2512         assert(el.value == [ cast(ubyte) ASN1SpecialRealValue.minusInfinity ]);
2513     }
2514 
2515     // Positive Testing Base-10 (Character-Encoded) REALs
2516     @system
2517     unittest
2518     {
2519         immutable string[] tests = [
2520             "1.E1",
2521             "2.E10",
2522             "4.E100",
2523             "1.E-1",
2524             "2.E-10",
2525             "4.E-100",
2526             "-1.E1",
2527             "-2.E10",
2528             "-4.E100",
2529             "-1.E-1",
2530             "-2.E-10",
2531             "-4.E-100",
2532             "19.E1",
2533             "29.E10",
2534             "49.E100",
2535             "19.E-1",
2536             "29.E-10",
2537             "49.E-100",
2538             "-19.E1",
2539             "-29.E10",
2540             "-49.E100",
2541             "-19.E-1",
2542             "-29.E-10",
2543             "-49.E-100",
2544             "33.E+0"
2545         ];
2546 
2547         CERElement el = new CERElement();
2548 
2549         foreach (test; tests)
2550         {
2551             el.value = [ 0b00000011u ];
2552             el.value ~= cast(ubyte[]) test;
2553             assertNotThrown!ASN1ValueException(el.realNumber!float);
2554             assertNotThrown!ASN1ValueException(el.realNumber!double);
2555             assertNotThrown!ASN1ValueException(el.realNumber!real);
2556         }
2557     }
2558 
2559     // Negative Testing Base-10 (Character-Encoded) REALs
2560     @system
2561     unittest
2562     {
2563         immutable string[] tests = [
2564             " 1.E1", // Leading whitespace
2565             "1.E1 ", // Trailing whitespace
2566             "1 .E1", // Internal whitespace
2567             "1. E1", // Internal whitespace
2568             "1.E 1", // Internal whitespace
2569             "+1.E1", // Leading plus sign
2570             "01.E1", // Leading zero
2571             "10.E1", // Trailing zero
2572             "1.0E1", // Fractional zero
2573             "1.E+1", // Leading plus sign
2574             "1.E01", // Leading zero
2575             "1E100", // No decimal point
2576             "1.1",   // No 'E'
2577             ""       // Empty string
2578         ];
2579 
2580         CERElement el = new CERElement();
2581 
2582         foreach (test; tests)
2583         {
2584             el.value = [ 0b00000011u ];
2585             el.value ~= cast(ubyte[]) test;
2586             assertThrown!ASN1ValueException(el.realNumber!float);
2587             assertThrown!ASN1ValueException(el.realNumber!double);
2588             assertThrown!ASN1ValueException(el.realNumber!real);
2589         }
2590     }
2591 
2592     /**
2593         Decodes a signed integer, which represents a selection from an
2594         $(MONO ENUMERATION) of choices.
2595 
2596         Throws:
2597         $(UL
2598             $(LI $(D ASN1ConstructionException)
2599                 if the encoded value is not primitively-constructed)
2600             $(LI $(D ASN1ValueSizeException)
2601                 if the value is too big to decode to a signed integral type,
2602                 or if the value is zero bytes)
2603             $(LI $(D ASN1ValuePaddingException)
2604                 if there are padding bytes)
2605         )
2606     */
2607     public @property @system
2608     T enumerated(T)() const
2609     if (isIntegral!T && isSigned!T)
2610     {
2611         if (this.construction != ASN1Construction.primitive)
2612             throw new ASN1ConstructionException
2613             (this.construction, "decode an ENUMERATED");
2614 
2615         if (this.value.length == 1u)
2616             return cast(T) cast(byte) this.value[0];
2617 
2618         if (this.value.length == 0u || this.value.length > T.sizeof)
2619             throw new ASN1ValueSizeException
2620             (1u, long.sizeof, this.value.length, "decode an ENUMERATED");
2621 
2622         if
2623         (
2624             (this.value[0] == 0x00u && (!(this.value[1] & 0x80u))) || // Unnecessary positive leading bytes
2625             (this.value[0] == 0xFFu && (this.value[1] & 0x80u)) // Unnecessary negative leading bytes
2626         )
2627             throw new ASN1ValueException
2628             (
2629                 "This exception was thrown because you attempted to decode " ~
2630                 "an ENUMERATED that was encoded on more than the minimum " ~
2631                 "necessary bytes. " ~
2632                 notWhatYouMeantText ~ forMoreInformationText ~
2633                 debugInformationText ~ reportBugsText
2634             );
2635 
2636         /* NOTE:
2637             Because the ENUMERATED is stored in two's complement form, you
2638             can't just apppend 0x00u to the big end of it until it is as long
2639             as T in bytes, then cast to T. Instead, you have to first determine
2640             if the encoded integer is negative or positive. If it is negative,
2641             then you actually want to append 0xFFu to the big end until it is
2642             as big as T, so you get the two's complement form of whatever T
2643             you choose.
2644 
2645             The line immediately below this determines whether the padding byte
2646             should be 0xFF or 0x00 based on the most significant bit of the
2647             most significant byte (which, since CER encodes big-endian, will
2648             always be the first byte). If set (1), the number is negative, and
2649             hence, the padding byte should be 0xFF. If not, it is positive,
2650             and the padding byte should be 0x00.
2651         */
2652         immutable ubyte paddingByte = ((this.value[0] & 0x80u) ? 0xFFu : 0x00u);
2653         ubyte[] value = this.value.dup; // Duplication is necessary to prevent modifying the source bytes
2654         while (value.length < T.sizeof)
2655             value = (paddingByte ~ value);
2656         version (LittleEndian) reverse(value);
2657         version (unittest) assert(value.length == T.sizeof);
2658         return *cast(T *) value.ptr;
2659     }
2660 
2661     /// Encodes an $(MONO ENUMERATED) type from an integer.
2662     public @property @system nothrow
2663     void enumerated(T)(in T value)
2664     out
2665     {
2666         assert(this.value.length > 0u);
2667     }
2668     body
2669     {
2670         scope(success) this.construction = ASN1Construction.primitive;
2671         if (value <= byte.max && value >= byte.min)
2672         {
2673             this.value = [ cast(ubyte) cast(byte) value ];
2674             return;
2675         }
2676 
2677         ubyte[] ub;
2678         ub.length = T.sizeof;
2679         *cast(T *)&ub[0] = value;
2680         version (LittleEndian) reverse(ub);
2681 
2682         /*
2683             An ENUMERATED must be encoded on the fewest number of bytes than can
2684             encode it. The loops below identify how many bytes can be
2685             truncated from the start of the ENUMERATED, with one loop for positive
2686             and another loop for negative numbers. ENUMERATED is encoded in the
2687             same exact way that INTEGER is encoded.
2688 
2689             From X.690, Section 8.3.2:
2690 
2691             If the contents octets of an integer value encoding consist of more
2692             than one octet, then the bits of the first octet and bit 8 of the
2693             second octet:
2694                 a) shall not all be ones; and
2695                 b) shall not all be zero.
2696                 NOTE – These rules ensure that an integer value is always
2697                 encoded in the smallest possible number of octets.
2698         */
2699         size_t startOfNonPadding = 0u;
2700         if (T.sizeof > 1u)
2701         {
2702             if (value >= 0)
2703             {
2704                 for (size_t i = 0u; i < ub.length-1; i++)
2705                 {
2706                     if (ub[i] != 0x00u) break;
2707                     if (!(ub[i+1] & 0x80u)) startOfNonPadding++;
2708                 }
2709             }
2710             else
2711             {
2712                 for (size_t i = 0u; i < ub.length-1; i++)
2713                 {
2714                     if (ub[i] != 0xFFu) break;
2715                     if (ub[i+1] & 0x80u) startOfNonPadding++;
2716                 }
2717             }
2718         }
2719 
2720         this.value = ub[startOfNonPadding .. $];
2721     }
2722 
2723     // Ensure that ENUMERATED 0 gets encoded on a single null byte.
2724     @system
2725     unittest
2726     {
2727         CERElement el = new CERElement();
2728 
2729         el.enumerated!byte = cast(byte) 0x00;
2730         assert(el.value == [ 0x00u ]);
2731 
2732         el.enumerated!short = cast(short) 0x0000;
2733         assert(el.value == [ 0x00u ]);
2734 
2735         el.enumerated!int = cast(int) 0;
2736         assert(el.value == [ 0x00u ]);
2737 
2738         el.enumerated!long = cast(long) 0;
2739         assert(el.value == [ 0x00u ]);
2740 
2741         el.value = [];
2742         assertThrown!ASN1ValueSizeException(el.enumerated!byte);
2743         assertThrown!ASN1ValueSizeException(el.enumerated!short);
2744         assertThrown!ASN1ValueSizeException(el.enumerated!int);
2745         assertThrown!ASN1ValueSizeException(el.enumerated!long);
2746     }
2747 
2748     // Test encoding -0 for the sake of CVE-2016-2108
2749     @system
2750     unittest
2751     {
2752         CERElement el = new CERElement();
2753 
2754         el.enumerated!byte = -0;
2755         assertNotThrown!RangeError(el.enumerated!byte);
2756         assertNotThrown!ASN1Exception(el.enumerated!byte);
2757 
2758         el.enumerated!short = -0;
2759         assertNotThrown!RangeError(el.enumerated!short);
2760         assertNotThrown!ASN1Exception(el.enumerated!short);
2761 
2762         el.enumerated!int = -0;
2763         assertNotThrown!RangeError(el.enumerated!int);
2764         assertNotThrown!ASN1Exception(el.enumerated!int);
2765 
2766         el.enumerated!long = -0;
2767         assertNotThrown!RangeError(el.enumerated!long);
2768         assertNotThrown!ASN1Exception(el.enumerated!long);
2769     }
2770 
2771     /**
2772         Decodes an $(MONO EmbeddedPDV), which is a constructed data type, defined in
2773             the $(LINK https://www.itu.int, International Telecommunications Union)'s
2774             $(LINK https://www.itu.int/rec/T-REC-X.680/en, X.680).
2775 
2776         The specification defines $(MONO EmbeddedPDV) as:
2777 
2778         $(PRE
2779             EmbeddedPDV ::= [UNIVERSAL 11] IMPLICIT SEQUENCE {
2780                 identification CHOICE {
2781                     syntaxes SEQUENCE {
2782                         abstract OBJECT IDENTIFIER,
2783                         transfer OBJECT IDENTIFIER },
2784                     syntax OBJECT IDENTIFIER,
2785                     presentation-context-id INTEGER,
2786                     context-negotiation SEQUENCE {
2787                         presentation-context-id INTEGER,
2788                         transfer-syntax OBJECT IDENTIFIER },
2789                     transfer-syntax OBJECT IDENTIFIER,
2790                     fixed NULL },
2791                 data-value-descriptor ObjectDescriptor OPTIONAL,
2792                 data-value OCTET STRING }
2793             (WITH COMPONENTS { ... , data-value-descriptor ABSENT })
2794         )
2795 
2796         This assumes $(MONO AUTOMATIC TAGS), so all of the $(MONO identification)
2797         choices will be $(MONO CONTEXT-SPECIFIC) and numbered from 0 to 5.
2798 
2799         The following additional constraints are applied to the abstract syntax
2800         when using Canonical Encoding Rules or Distinguished Encoding Rules,
2801         which are also defined in the
2802         $(LINK https://www.itu.int/en/pages/default.aspx, International Telecommunications Union)'s
2803         $(LINK http://www.itu.int/rec/T-REC-X.690/en, X.690 - ASN.1 encoding rules):
2804 
2805         $(PRE
2806             EmbeddedPDV ( WITH COMPONENTS {
2807                 ... ,
2808                 identification ( WITH COMPONENTS {
2809                     ... ,
2810                     presentation-context-id ABSENT,
2811                     context-negotiation ABSENT } ) } )
2812         )
2813 
2814         The stated purpose of the constraints shown above is to restrict the use of
2815         the $(MONO presentation-context-id), either by itself or within the
2816         context-negotiation, which makes the following the effective abstract
2817         syntax of $(MONO EmbeddedPDV) when using Canonical Encoding Rules or
2818         Distinguished Encoding Rules:
2819 
2820         $(PRE
2821             EmbeddedPDV ::= [UNIVERSAL 11] IMPLICIT SEQUENCE {
2822                 identification CHOICE {
2823                     syntaxes SEQUENCE {
2824                         abstract OBJECT IDENTIFIER,
2825                         transfer OBJECT IDENTIFIER },
2826                     syntax OBJECT IDENTIFIER,
2827                     presentation-context-id INTEGER,
2828                     context-negotiation SEQUENCE {
2829                         presentation-context-id INTEGER,
2830                         transfer-syntax OBJECT IDENTIFIER },
2831                     transfer-syntax OBJECT IDENTIFIER,
2832                     fixed NULL },
2833                 data-value-descriptor ObjectDescriptor OPTIONAL,
2834                 data-value OCTET STRING }
2835                     ( WITH COMPONENTS {
2836                         ... ,
2837                         identification ( WITH COMPONENTS {
2838                             ... ,
2839                             presentation-context-id ABSENT,
2840                             context-negotiation ABSENT } ) } )
2841         )
2842 
2843         With the constraints applied, the abstract syntax for $(MONO EmbeddedPDV)s encoded
2844         using Canonical Encoding Rules or Distinguished Encoding Rules becomes:
2845 
2846         $(PRE
2847             EmbeddedPDV ::= [UNIVERSAL 11] IMPLICIT SEQUENCE {
2848                 identification CHOICE {
2849                     syntaxes SEQUENCE {
2850                         abstract OBJECT IDENTIFIER,
2851                         transfer OBJECT IDENTIFIER },
2852                     syntax OBJECT IDENTIFIER,
2853                     transfer-syntax OBJECT IDENTIFIER,
2854                     fixed NULL },
2855                 data-value-descriptor ObjectDescriptor OPTIONAL,
2856                 data-value OCTET STRING }
2857         )
2858 
2859         Returns: an instance of $(D types.universal.embeddedpdv.EmbeddedPDV)
2860 
2861         Throws:
2862         $(UL
2863             $(LI $(D ASN1ValueException) if encoded EmbeddedPDV has too few or too many
2864                 elements, or if syntaxes or context-negotiation element has
2865                 too few or too many elements)
2866             $(LI $(D ASN1ValueSizeException) if encoded INTEGER is too large to decode)
2867             $(LI $(D ASN1RecursionException) if using constructed form and the element
2868                 is constructed of too many nested constructed elements)
2869             $(LI $(D ASN1TagClassException) if any nested primitives do not have the
2870                 correct tag class)
2871             $(LI $(D ASN1ConstructionException) if any element has the wrong construction)
2872             $(LI $(D ASN1TagNumberException) if any nested primitives do not have the
2873                 correct tag number)
2874         )
2875     */
2876     override public @property @system
2877     EmbeddedPDV embeddedPresentationDataValue() const
2878     {
2879         if (this.construction != ASN1Construction.constructed)
2880             throw new ASN1ConstructionException
2881             (this.construction, "decode an EmbeddedPDV");
2882 
2883         const CERElement[] components = this.sequence;
2884         ASN1ContextSwitchingTypeID identification = ASN1ContextSwitchingTypeID();
2885 
2886         if (components.length != 2u)
2887             throw new ASN1ValueException
2888             (
2889                 "This exception was thrown because you attempted to decode " ~
2890                 "an EmbeddedPDV that contained too many or too few elements. " ~
2891                 "An EmbeddedPDV should have only two elements: " ~
2892                 "an identification CHOICE, and a data-value OCTET STRING, " ~
2893                 "in that order. " ~
2894                 notWhatYouMeantText ~ forMoreInformationText ~
2895                 debugInformationText ~ reportBugsText
2896             );
2897 
2898         if (components[0].tagClass != ASN1TagClass.contextSpecific)
2899             throw new ASN1TagClassException
2900             (
2901                 [ ASN1TagClass.contextSpecific ],
2902                 components[0].tagClass,
2903                 "decode the first component of an EmbeddedPDV"
2904             );
2905 
2906         if (components[1].tagClass != ASN1TagClass.contextSpecific)
2907             throw new ASN1TagClassException
2908             (
2909                 [ ASN1TagClass.contextSpecific ],
2910                 components[1].tagClass,
2911                 "decode the second component of an EmbeddedPDV"
2912             );
2913 
2914         /* NOTE:
2915             See page 224 of Dubuisson, item 11:
2916             It sounds like, even if you have an ABSENT constraint applied,
2917             all automatically-tagged items still have the same numbers as
2918             though the constrained component were PRESENT.
2919         */
2920         if (components[0].tagNumber != 0u)
2921             throw new ASN1TagNumberException
2922             ([ 0u ], components[0].tagNumber, "decode the first component of an EmbeddedPDV");
2923 
2924         if (components[1].tagNumber != 2u)
2925             throw new ASN1TagNumberException
2926             ([ 2u ], components[1].tagNumber, "decode the second component of an EmbeddedPDV");
2927 
2928 
2929         ubyte[] bytes = components[0].value.dup;
2930         const CERElement identificationChoice = new CERElement(bytes);
2931         switch (identificationChoice.tagNumber)
2932         {
2933             case (0u): // syntaxes
2934             {
2935                 if (identificationChoice.construction != ASN1Construction.constructed)
2936                     throw new ASN1ConstructionException
2937                     (identificationChoice.construction, "decode the syntaxes component of an EmbeddedPDV");
2938 
2939                 const CERElement[] syntaxesComponents = identificationChoice.sequence;
2940 
2941                 if (syntaxesComponents.length != 2u)
2942                     throw new ASN1ValueException
2943                     (
2944                         "This exception was thrown because you attempted to " ~
2945                         "decode an EmbeddedPDV whose syntaxes component " ~
2946                         "contained an invalid number of elements. The " ~
2947                         "syntaxes component should contain abstract and transfer " ~
2948                         "syntax OBJECT IDENTIFIERS, in that order. " ~
2949                         notWhatYouMeantText ~ forMoreInformationText ~
2950                         debugInformationText ~ reportBugsText
2951                     );
2952 
2953                 // Class Validation
2954                 if (syntaxesComponents[0].tagClass != ASN1TagClass.contextSpecific)
2955                     throw new ASN1TagClassException
2956                     (
2957                         [ ASN1TagClass.contextSpecific ],
2958                         syntaxesComponents[0].tagClass,
2959                         "decode the first syntaxes component of an EmbeddedPDV"
2960                     );
2961 
2962                 if (syntaxesComponents[1].tagClass != ASN1TagClass.contextSpecific)
2963                     throw new ASN1TagClassException
2964                     (
2965                         [ ASN1TagClass.contextSpecific ],
2966                         syntaxesComponents[1].tagClass,
2967                         "decode the second syntaxes component of an EmbeddedPDV"
2968                     );
2969 
2970                 // Construction Validation
2971                 if (syntaxesComponents[0].construction != ASN1Construction.primitive)
2972                     throw new ASN1ConstructionException
2973                     (syntaxesComponents[0].construction, "decode the first syntaxes component of an EmbeddedPDV");
2974 
2975                 if (syntaxesComponents[1].construction != ASN1Construction.primitive)
2976                     throw new ASN1ConstructionException
2977                     (syntaxesComponents[1].construction, "decode the second syntaxes component of an EmbeddedPDV");
2978 
2979                 // Number Validation
2980                 if (syntaxesComponents[0].tagNumber != 0u)
2981                     throw new ASN1TagNumberException
2982                     (
2983                         [ 0u ],
2984                         syntaxesComponents[0].tagNumber,
2985                         "decode the first syntaxes component of an EmbeddedPDV"
2986                     );
2987 
2988                 if (syntaxesComponents[1].tagNumber != 1u)
2989                     throw new ASN1TagNumberException
2990                     (
2991                         [ 1u ],
2992                         syntaxesComponents[1].tagNumber,
2993                         "decode the second syntaxes component of an EmbeddedPDV"
2994                     );
2995 
2996                 identification.syntaxes  = ASN1Syntaxes(
2997                     syntaxesComponents[0].objectIdentifier,
2998                     syntaxesComponents[1].objectIdentifier
2999                 );
3000 
3001                 break;
3002             }
3003             case (1u): // syntax
3004             {
3005                 identification.syntax = identificationChoice.objectIdentifier;
3006                 break;
3007             }
3008             case (4u): // transfer-syntax
3009             {
3010                 identification.transferSyntax = identificationChoice.objectIdentifier;
3011                 break;
3012             }
3013             case (5u): // fixed
3014             {
3015                 identification.fixed = true;
3016                 break;
3017             }
3018             default:
3019                 throw new ASN1TagNumberException
3020                 ([ 0u, 1u, 4u, 5u ], identificationChoice.tagNumber, "decode an EmbeddedPDV identification");
3021         }
3022 
3023         EmbeddedPDV pdv = EmbeddedPDV();
3024         pdv.identification = identification;
3025         pdv.dataValue = components[1].octetString;
3026         return pdv;
3027     }
3028 
3029     /**
3030         Encodes an $(MONO EmbeddedPDV), which is a constructed data type, defined in
3031             the $(LINK https://www.itu.int, International Telecommunications Union)'s
3032             $(LINK https://www.itu.int/rec/T-REC-X.680/en, X.680).
3033 
3034         The specification defines $(MONO EmbeddedPDV) as:
3035 
3036         $(PRE
3037             EmbeddedPDV ::= [UNIVERSAL 11] IMPLICIT SEQUENCE {
3038                 identification CHOICE {
3039                     syntaxes SEQUENCE {
3040                         abstract OBJECT IDENTIFIER,
3041                         transfer OBJECT IDENTIFIER },
3042                     syntax OBJECT IDENTIFIER,
3043                     presentation-context-id INTEGER,
3044                     context-negotiation SEQUENCE {
3045                         presentation-context-id INTEGER,
3046                         transfer-syntax OBJECT IDENTIFIER },
3047                     transfer-syntax OBJECT IDENTIFIER,
3048                     fixed NULL },
3049                 data-value-descriptor ObjectDescriptor OPTIONAL,
3050                 data-value OCTET STRING }
3051             (WITH COMPONENTS { ... , data-value-descriptor ABSENT })
3052         )
3053 
3054         This assumes $(MONO AUTOMATIC TAGS), so all of the $(MONO identification)
3055         choices will be $(MONO CONTEXT-SPECIFIC) and numbered from 0 to 5.
3056 
3057         The following additional constraints are applied to the abstract syntax
3058         when using Canonical Encoding Rules or Distinguished Encoding Rules,
3059         which are also defined in the
3060         $(LINK https://www.itu.int/en/pages/default.aspx, International Telecommunications Union)'s
3061         $(LINK http://www.itu.int/rec/T-REC-X.690/en, X.690 - ASN.1 encoding rules):
3062 
3063         $(PRE
3064             EmbeddedPDV ( WITH COMPONENTS {
3065                 ... ,
3066                 identification ( WITH COMPONENTS {
3067                     ... ,
3068                     presentation-context-id ABSENT,
3069                     context-negotiation ABSENT } ) } )
3070         )
3071 
3072         The stated purpose of the constraints shown above is to restrict the use of
3073         the $(MONO presentation-context-id), either by itself or within the
3074         context-negotiation, which makes the following the effective abstract
3075         syntax of $(MONO EmbeddedPDV) when using Canonical Encoding Rules or
3076         Distinguished Encoding Rules:
3077 
3078         $(PRE
3079             EmbeddedPDV ::= [UNIVERSAL 11] IMPLICIT SEQUENCE {
3080                 identification CHOICE {
3081                     syntaxes SEQUENCE {
3082                         abstract OBJECT IDENTIFIER,
3083                         transfer OBJECT IDENTIFIER },
3084                     syntax OBJECT IDENTIFIER,
3085                     presentation-context-id INTEGER,
3086                     context-negotiation SEQUENCE {
3087                         presentation-context-id INTEGER,
3088                         transfer-syntax OBJECT IDENTIFIER },
3089                     transfer-syntax OBJECT IDENTIFIER,
3090                     fixed NULL },
3091                 data-value-descriptor ObjectDescriptor OPTIONAL,
3092                 data-value OCTET STRING }
3093                     ( WITH COMPONENTS {
3094                         ... ,
3095                         identification ( WITH COMPONENTS {
3096                             ... ,
3097                             presentation-context-id ABSENT,
3098                             context-negotiation ABSENT } ) } )
3099         )
3100 
3101         With the constraints applied, the abstract syntax for $(MONO EmbeddedPDV)s encoded
3102         using Canonical Encoding Rules or Distinguished Encoding Rules becomes:
3103 
3104         $(PRE
3105             EmbeddedPDV ::= [UNIVERSAL 11] IMPLICIT SEQUENCE {
3106                 identification CHOICE {
3107                     syntaxes SEQUENCE {
3108                         abstract OBJECT IDENTIFIER,
3109                         transfer OBJECT IDENTIFIER },
3110                     syntax OBJECT IDENTIFIER,
3111                     transfer-syntax OBJECT IDENTIFIER,
3112                     fixed NULL },
3113                 data-value-descriptor ObjectDescriptor OPTIONAL,
3114                 data-value OCTET STRING }
3115         )
3116 
3117         If the supplied $(MONO identification) for the EmbeddedPDV is a
3118         $(MONO presentation-context-id) or a $(MONO context-negotiation),
3119         no exception will be thrown; the $(MONO identification) will be set to
3120         $(MONO fixed) silently.
3121 
3122         Throws:
3123         $(UL
3124             $(LI $(D ASN1ValueException) if encoded ObjectDescriptor contains invalid characters)
3125         )
3126     */
3127     override public @property @system
3128     void embeddedPresentationDataValue(in EmbeddedPDV value)
3129     out
3130     {
3131         assert(this.value.length > 0u);
3132     }
3133     body
3134     {
3135         scope(success) this.construction = ASN1Construction.constructed;
3136         CERElement identification = new CERElement();
3137         identification.tagClass = ASN1TagClass.contextSpecific;
3138         identification.tagNumber = 0u; // CHOICE is EXPLICIT, even with automatic tagging.
3139 
3140         CERElement identificationChoice = new CERElement();
3141         identificationChoice.tagClass = ASN1TagClass.contextSpecific;
3142         if (!(value.identification.syntaxes.isNull))
3143         {
3144             CERElement abstractSyntax = new CERElement();
3145             abstractSyntax.tagClass = ASN1TagClass.contextSpecific;
3146             abstractSyntax.tagNumber = 0u;
3147             abstractSyntax.objectIdentifier = value.identification.syntaxes.abstractSyntax;
3148 
3149             CERElement transferSyntax = new CERElement();
3150             transferSyntax.tagClass = ASN1TagClass.contextSpecific;
3151             transferSyntax.tagNumber = 1u;
3152             transferSyntax.objectIdentifier = value.identification.syntaxes.transferSyntax;
3153 
3154             identificationChoice.construction = ASN1Construction.constructed;
3155             identificationChoice.tagNumber = 0u;
3156             identificationChoice.sequence = [ abstractSyntax, transferSyntax ];
3157         }
3158         else if (!(value.identification.syntax.isNull))
3159         {
3160             identificationChoice.tagNumber = 1u;
3161             identificationChoice.objectIdentifier = value.identification.syntax;
3162         }
3163         else if (!(value.identification.transferSyntax.isNull))
3164         {
3165             identificationChoice.tagNumber = 4u;
3166             identificationChoice.objectIdentifier = value.identification.transferSyntax;
3167         }
3168         else
3169         {
3170             identificationChoice.tagNumber = 5u;
3171             identificationChoice.value = [];
3172         }
3173 
3174         // This makes identification: [CONTEXT 0][L][CONTEXT #][L][V]
3175         identification.value = cast(ubyte[]) identificationChoice;
3176 
3177         CERElement dataValue = new CERElement();
3178         dataValue.tagClass = ASN1TagClass.contextSpecific;
3179         dataValue.tagNumber = 2u;
3180         dataValue.octetString = value.dataValue;
3181 
3182         this.sequence = [ identification, dataValue ];
3183     }
3184 
3185     /* NOTE:
3186         This unit test had to be moved out of ASN1Element because CER and CER
3187         do not support encoding of presentation-context-id in EmbeddedPDV.
3188 
3189         This unit test ensures that, if you attempt to create an EmbeddedPDV
3190         with presentation-context-id as the CHOICE of identification, the
3191         encoded EmbeddedPDV's identification defaults to fixed.
3192     */
3193     @system
3194     unittest
3195     {
3196         ASN1ContextSwitchingTypeID id = ASN1ContextSwitchingTypeID();
3197         id.presentationContextID = 27L;
3198 
3199         EmbeddedPDV input = EmbeddedPDV();
3200         input.identification = id;
3201         input.dataValue = [ 0x01u, 0x02u, 0x03u, 0x04u ];
3202 
3203         CERElement el = new CERElement();
3204         el.tagNumber = 0x08u;
3205         el.embeddedPDV = input;
3206         EmbeddedPDV output = el.embeddedPDV;
3207         assert(output.identification.fixed == true);
3208         assert(output.dataValue == [ 0x01u, 0x02u, 0x03u, 0x04u ]);
3209     }
3210 
3211     /* NOTE:
3212         This unit test had to be moved out of ASN1Element because CER and CER
3213         do not support encoding of context-negotiation in EmbeddedPDV.
3214 
3215         This unit test ensures that, if you attempt to create an EmbeddedPDV
3216         with context-negotiation as the CHOICE of identification, the
3217         encoded EmbeddedPDV's identification defaults to fixed.
3218     */
3219     @system
3220     unittest
3221     {
3222         ASN1ContextNegotiation cn = ASN1ContextNegotiation();
3223         cn.presentationContextID = 27L;
3224         cn.transferSyntax = new OID(1, 3, 6, 4, 1, 256, 39);
3225 
3226         ASN1ContextSwitchingTypeID id = ASN1ContextSwitchingTypeID();
3227         id.contextNegotiation = cn;
3228 
3229         EmbeddedPDV input = EmbeddedPDV();
3230         input.identification = id;
3231         input.dataValue = [ 0x13u, 0x15u, 0x17u, 0x19u ];
3232 
3233         CERElement el = new CERElement();
3234         el.embeddedPDV = input;
3235         EmbeddedPDV output = el.embeddedPDV;
3236         assert(output.identification.fixed == true);
3237         assert(output.dataValue == [ 0x13u, 0x15u, 0x17u, 0x19u ]);
3238     }
3239 
3240     // Inspired by CVE-2017-9023
3241     @system
3242     unittest
3243     {
3244         ubyte[] data = [ // This is valid.
3245             0x0Bu, 0x0Au, // EmbeddedPDV, Length 11
3246                 0x80u, 0x02u, // CHOICE
3247                     0x85u, 0x00u, // NULL
3248                 0x82u, 0x04u, 0x01u, 0x02u, 0x03u, 0x04u ]; // OCTET STRING
3249 
3250         // Valid values for data[2]: 80
3251         for (ubyte i = 0x81u; i < 0x9Eu; i++)
3252         {
3253             data[2] = i;
3254             size_t x = 0u;
3255             CERElement el = new CERElement(x, data);
3256             assertThrown!ASN1Exception(el.embeddedPDV);
3257         }
3258 
3259         // Valid values for data[4]: 80-85
3260         for (ubyte i = 0x86u; i < 0x9Eu; i++)
3261         {
3262             data[4] = i;
3263             size_t x = 0u;
3264             CERElement el = new CERElement(x, data);
3265             assertThrown!ASN1Exception(el.embeddedPDV);
3266         }
3267 
3268         // Valid values for data[6]: 82
3269         for (ubyte i = 0x83u; i < 0x9Eu; i++)
3270         {
3271             data[6] = i;
3272             size_t x = 0u;
3273             CERElement el = new CERElement(x, data);
3274             assertThrown!ASN1Exception(el.embeddedPDV);
3275         }
3276     }
3277 
3278     /**
3279         Decodes the value to UTF-8 characters.
3280 
3281         Throws:
3282         $(UL
3283             $(LI $(D UTF8Exception)
3284                 if the encoded value does not decode to UTF-8)
3285             $(LI $(D ASN1RecursionException)
3286                 if using constructed form and the element
3287                 is constructed of too many nested constructed elements)
3288             $(LI $(D ASN1TagClassException)
3289                 if any nested primitives do not share the
3290                 same tag class as their outer constructed element)
3291             $(LI $(D ASN1TagNumberException)
3292                 if any nested primitives do not share the
3293                 same tag number as their outer constructed element)
3294         )
3295     */
3296     override public @property @system
3297     string unicodeTransformationFormat8String() const
3298     {
3299         if (this.construction == ASN1Construction.primitive)
3300         {
3301             if (this.value.length > 1000u)
3302                 throw new ASN1ValueSizeException
3303                 (0u, 1000u, this.value.length, "decode a primitively-encoded UTF8String");
3304 
3305             return cast(string) this.value;
3306         }
3307         else
3308         {
3309             if (this.valueRecursionCount++ == this.nestingRecursionLimit)
3310                 throw new ASN1RecursionException(this.nestingRecursionLimit, "parse a UTF8String");
3311             scope(exit) this.valueRecursionCount--; // So exceptions do not leave residual recursion.
3312 
3313             CERElement[] substrings = this.sequence;
3314             if (substrings.length < 2u)
3315                 throw new ASN1ValueException
3316                 (
3317                     "This exception was thrown because you attempted to decode " ~
3318                     "a constructed UTF8String that was constituted by fewer " ~
3319                     "than two nested elements. When using the Canonical Encoding " ~
3320                     "Rules (CER), a legitimate constructed UTF8String must " ~
3321                     "contain at least two nested elements, and each but the " ~
3322                     "last must contain exactly 1000 content octets. The last " ~
3323                     "nested element may have between 1 and 1000 content " ~
3324                     "octets inclusively. This element should have been encoded " ~
3325                     "primitively using definite-length encoding."
3326                 );
3327 
3328             foreach (substring; substrings[0 .. $-1])
3329             {
3330                 if (substring.length != 1000u)
3331                     throw new ASN1ValueSizeException
3332                     (1000u, 1000u, substring.length, "decode a substring of a constructed UTF8String");
3333             }
3334 
3335             if (substrings[$-1].length == 0u)
3336                 throw new ASN1ValueSizeException
3337                 (1u, 1000u, substrings[$-1].length, "decode the last substring of a constructed UTF8String");
3338 
3339             Appender!string appendy = appender!string();
3340             foreach (substring; substrings)
3341             {
3342                 if (substring.tagClass != this.tagClass)
3343                     throw new ASN1TagClassException
3344                     ([ this.tagClass ], substring.tagClass, "decode a substring of a constructed UTF8String");
3345 
3346                 if (substring.construction != ASN1Construction.primitive)
3347                     throw new ASN1ConstructionException
3348                     (substring.construction, "decode a substring of a constructed UTF8String");
3349 
3350                 if (substring.tagNumber != this.tagNumber)
3351                     throw new ASN1TagNumberException
3352                     ([ this.tagNumber ], substring.tagNumber, "decode a substring of a constructed UTF8String");
3353 
3354                 appendy.put(substring.utf8String);
3355             }
3356             return appendy.data;
3357         }
3358     }
3359 
3360     /// Encodes a UTF-8 string to bytes.
3361     override public @property @system
3362     void unicodeTransformationFormat8String(in string value)
3363     {
3364         if (value.length <= 1000u)
3365         {
3366             this.value = cast(ubyte[]) value.dup;
3367         }
3368         else
3369         {
3370             CERElement[] primitives;
3371             size_t i = 0u;
3372             while (i+1000u < value.length)
3373             {
3374                 CERElement x = new CERElement();
3375                 x.tagNumber = this.tagNumber;
3376                 x.construction = ASN1Construction.primitive;
3377                 x.value = cast(ubyte[]) value[i .. i+1000u];
3378                 primitives ~= x;
3379                 i += 1000u;
3380             }
3381 
3382             CERElement y = new CERElement();
3383             y.tagNumber = this.tagNumber;
3384             y.construction = ASN1Construction.primitive;
3385             y.value = cast(ubyte[]) value[i .. $];
3386             primitives ~= y;
3387 
3388             this.sequence = primitives;
3389             this.construction = ASN1Construction.constructed;
3390         }
3391     }
3392 
3393     @system
3394     unittest
3395     {
3396         void test(size_t length)
3397         {
3398             char[] data;
3399             data.length = length;
3400             for (size_t i = 0u; i < data.length; i++)
3401             {
3402                 data[i] = cast(char) ((i % 0x10) + 0x41);
3403             }
3404             CERElement el = new CERElement();
3405             el.utf8String = cast(string) data;
3406             assert(el.utf8String == cast(string) data);
3407         }
3408         test(0u);
3409         test(1u);
3410         test(8u);
3411         test(127u);
3412         test(128u);
3413         test(129u);
3414         test(192u);
3415         test(999u);
3416         test(1000u);
3417         test(1001u);
3418         test(2017u);
3419     }
3420 
3421     /**
3422         Decodes a $(MONO RELATIVE OBJECT IDENTIFIER).
3423 
3424         Throws:
3425         $(UL
3426             $(LI $(D ASN1ConstructionException) if the element is marked as "constructed")
3427             $(LI $(D ASN1ValuePaddingException) if a single OID number is encoded with
3428                 "leading zero bytes" ($(D 0x80u)))
3429             $(LI $(D ASN1ValueOverflowException) if a single OID number is too big to
3430                 decode to a $(D size_t))
3431             $(LI $(D ASN1TruncationException) if a single OID number is "cut off")
3432         )
3433 
3434         Standards:
3435         $(UL
3436             $(LI $(LINK http://www.itu.int/rec/T-REC-X.660-201107-I/en, X.660))
3437         )
3438     */
3439     override public @property @system
3440     OIDNode[] relativeObjectIdentifier() const
3441     {
3442         if (this.construction != ASN1Construction.primitive)
3443             throw new ASN1ConstructionException
3444             (this.construction, "decode an RELATIVE OID");
3445 
3446         if (this.value.length == 0u) return [];
3447         foreach (immutable octet; this.value)
3448         {
3449             if (octet == 0x80u)
3450                 throw new ASN1ValuePaddingException
3451                 (
3452                     "This exception was thrown because you attempted to decode " ~
3453                     "a RELATIVE OID that contained a number that was " ~
3454                     "encoded on more than the minimum necessary octets. This " ~
3455                     "is indicated by an occurrence of the octet 0x80, which " ~
3456                     "is the encoded equivalent of a leading zero. " ~
3457                     notWhatYouMeantText ~ forMoreInformationText ~
3458                     debugInformationText ~ reportBugsText
3459                 );
3460         }
3461 
3462         if (this.value[$-1] > 0x80u)
3463             throw new ASN1TruncationException
3464             (size_t.max, this.value.length, "decode a RELATIVE OID");
3465 
3466         // Breaks bytes into groups, where each group encodes one OID component.
3467         ubyte[][] byteGroups;
3468         size_t lastTerminator = 0u;
3469         for (size_t i = 0u; i < this.length; i++)
3470         {
3471             if (!(this.value[i] & 0x80u))
3472             {
3473                 byteGroups ~= cast(ubyte[]) this.value[lastTerminator .. i+1];
3474                 lastTerminator = i+1;
3475             }
3476         }
3477 
3478         // Converts each group of bytes to a number.
3479         size_t[] numbers;
3480         foreach (const byteGroup; byteGroups)
3481         {
3482             if (byteGroup.length > size_t.sizeof)
3483                 throw new ASN1ValueOverflowException
3484                 (
3485                     "This exception was thrown because you attempted to decode " ~
3486                     "a RELATIVE OID that encoded a number on more than " ~
3487                     "size_t.sizeof bytes. " ~
3488                     notWhatYouMeantText ~ forMoreInformationText ~
3489                     debugInformationText ~ reportBugsText
3490                 );
3491 
3492             numbers ~= 0u;
3493             for (size_t i = 0u; i < byteGroup.length; i++)
3494             {
3495                 numbers[$-1] <<= 7;
3496                 numbers[$-1] |= cast(size_t) (byteGroup[i] & 0x7Fu);
3497             }
3498         }
3499 
3500         // Constructs the array of OIDNodes from the array of numbers.
3501         OIDNode[] nodes;
3502         foreach (number; numbers)
3503         {
3504             nodes ~= OIDNode(number);
3505         }
3506 
3507         return nodes;
3508     }
3509 
3510     /**
3511         Encodes a $(MONO RELATIVE OBJECT IDENTIFIER).
3512 
3513         Standards:
3514             $(LINK http://www.itu.int/rec/T-REC-X.660-201107-I/en, X.660)
3515     */
3516     override public @property @system nothrow
3517     void relativeObjectIdentifier(in OIDNode[] value)
3518     {
3519         scope(success) this.construction = ASN1Construction.primitive;
3520         foreach (node; value)
3521         {
3522             size_t number = node.number;
3523             if (number < 128u)
3524             {
3525                 this.value ~= cast(ubyte) number;
3526                 continue;
3527             }
3528 
3529             ubyte[] encodedOIDNode;
3530             while (number != 0u)
3531             {
3532                 ubyte[] numberBytes;
3533                 numberBytes.length = size_t.sizeof;
3534                 *cast(size_t *) numberBytes.ptr = number;
3535                 if ((numberBytes[0] & 0x80u) == 0u) numberBytes[0] |= 0x80u;
3536                 encodedOIDNode = numberBytes[0] ~ encodedOIDNode;
3537                 number >>= 7u;
3538             }
3539 
3540             encodedOIDNode[$-1] &= 0x7Fu;
3541             this.value ~= encodedOIDNode;
3542         }
3543     }
3544 
3545     @system
3546     unittest
3547     {
3548         CERElement element = new CERElement();
3549 
3550         // All values of octet[0] should pass.
3551         for (ubyte i = 0x00u; i < 0x80u; i++)
3552         {
3553             element.value = [ i ];
3554             assertNotThrown!Exception(element.roid);
3555         }
3556 
3557         // All values of octet[0] should pass.
3558         for (ubyte i = 0x81u; i < 0xFFu; i++)
3559         {
3560             element.value = [ i, 0x14u ];
3561             assertNotThrown!Exception(element.roid);
3562         }
3563     }
3564 
3565     @system
3566     unittest
3567     {
3568         CERElement element = new CERElement();
3569 
3570         // Tests for the "leading zero byte," 0x80
3571         element.value = [ 0x29u, 0x80u, 0x14u ];
3572         assertThrown!ASN1ValuePaddingException(element.roid);
3573         element.value = [ 0x29u, 0x80u, 0x80u ];
3574         assertThrown!ASN1ValuePaddingException(element.roid);
3575         element.value = [ 0x80u, 0x80u, 0x80u ];
3576         assertThrown!ASN1ValuePaddingException(element.roid);
3577         element.value = [ 0x29u, 0x14u, 0x80u ];
3578         assertThrown!ASN1ValuePaddingException(element.roid);
3579         element.value = [ 0x29u, 0x80u ];
3580         assertThrown!ASN1ValuePaddingException(element.roid);
3581 
3582         // Test for non-terminating components
3583         element.value = [ 0x29u, 0x81u ];
3584         assertThrown!ASN1TruncationException(element.roid);
3585         element.value = [ 0x29u, 0x14u, 0x81u ];
3586         assertThrown!ASN1TruncationException(element.roid);
3587     }
3588 
3589     /**
3590         Decodes a sequence of elements
3591 
3592         Throws:
3593         $(UL
3594             $(LI $(D ASN1ConstructionException) if the element is marked as "primitive")
3595             $(LI And all of the exceptions thrown by the constructor)
3596         )
3597     */
3598     override public @property @system
3599     CERElement[] sequence() const
3600     {
3601         if (this.construction != ASN1Construction.constructed)
3602             throw new ASN1ConstructionException
3603             (this.construction, "decode a SEQUENCE");
3604 
3605         ubyte[] data = this.value.dup;
3606         CERElement[] result;
3607         while (data.length > 0u)
3608             result ~= new CERElement(data);
3609         return result;
3610     }
3611 
3612     /// Encodes a sequence of elements
3613     override public @property @system
3614     void sequence(in CERElement[] value)
3615     {
3616         scope(success) this.construction = ASN1Construction.constructed;
3617         ubyte[] result;
3618         foreach (cv; value)
3619         {
3620             result ~= cv.toBytes;
3621         }
3622         this.value = result;
3623     }
3624 
3625     /**
3626         Decodes a set of elements
3627 
3628         Throws:
3629         $(UL
3630             $(LI $(D ASN1ConstructionException) if the element is marked as "primitive")
3631             $(LI And all of the exceptions thrown by the constructor)
3632         )
3633     */
3634     override public @property @system
3635     CERElement[] set() const
3636     {
3637         if (this.construction != ASN1Construction.constructed)
3638             throw new ASN1ConstructionException
3639             (this.construction, "decode a SET");
3640 
3641         ubyte[] data = this.value.dup;
3642         CERElement[] result;
3643         while (data.length > 0u)
3644             result ~= new CERElement(data);
3645         return result;
3646     }
3647 
3648     /// Encodes a set of elements
3649     override public @property @system
3650     void set(in CERElement[] value)
3651     {
3652         scope(success) this.construction = ASN1Construction.constructed;
3653         ubyte[] result;
3654         foreach (cv; value)
3655         {
3656             result ~= cv.toBytes;
3657         }
3658         this.value = result;
3659     }
3660 
3661     /**
3662         Decodes a string, where the characters of the string are limited to
3663         0 - 9 and $(MONO SPACE).
3664 
3665         Throws:
3666         $(UL
3667             $(LI $(D ASN1ValueCharactersException) if any character other than 0-9 or space is encoded.)
3668             $(LI $(D ASN1RecursionException)
3669                 if using constructed form and the element
3670                 is constructed of too many nested constructed elements)
3671             $(LI $(D ASN1TagClassException)
3672                 if any nested primitives do not share the
3673                 same tag class as their outer constructed element)
3674             $(LI $(D ASN1TagNumberException)
3675                 if any nested primitives do not share the
3676                 same tag number as their outer constructed element)
3677         )
3678     */
3679     override public @property @system
3680     string numericString() const
3681     {
3682         if (this.construction == ASN1Construction.primitive)
3683         {
3684             if (this.value.length > 1000u)
3685                 throw new ASN1ValueSizeException
3686                 (0u, 1000u, this.value.length, "decode a primitively-encoded NumericString");
3687 
3688             foreach (immutable character; this.value)
3689             {
3690                 if (!canFind(numericStringCharacters, character))
3691                     throw new ASN1ValueCharactersException
3692                     ("1234567890 ", character, "NumericString");
3693             }
3694             return cast(string) this.value;
3695         }
3696         else
3697         {
3698             if (this.valueRecursionCount++ == this.nestingRecursionLimit)
3699                 throw new ASN1RecursionException(this.nestingRecursionLimit, "parse a NumericString");
3700             scope(exit) this.valueRecursionCount--; // So exceptions do not leave residual recursion.
3701 
3702             CERElement[] substrings = this.sequence;
3703             if (substrings.length < 2u)
3704                 throw new ASN1ValueException
3705                 (
3706                     "This exception was thrown because you attempted to decode " ~
3707                     "a constructed NumericString that was constituted by fewer " ~
3708                     "than two nested elements. When using the Canonical Encoding " ~
3709                     "Rules (CER), a legitimate constructed NumericString must " ~
3710                     "contain at least two nested elements, and each but the " ~
3711                     "last must contain exactly 1000 content octets. The last " ~
3712                     "nested element may have between 1 and 1000 content " ~
3713                     "octets inclusively. This element should have been encoded " ~
3714                     "primitively using definite-length encoding."
3715                 );
3716 
3717             foreach (substring; substrings[0 .. $-1])
3718             {
3719                 if (substring.length != 1000u)
3720                     throw new ASN1ValueSizeException
3721                     (1000u, 1000u, substring.length, "decode a substring of a constructed NumericString");
3722             }
3723 
3724             if (substrings[$-1].length == 0u)
3725                 throw new ASN1ValueSizeException
3726                 (1u, 1000u, substrings[$-1].length, "decode the last substring of a constructed NumericString");
3727 
3728             Appender!string appendy = appender!string();
3729             foreach (substring; substrings)
3730             {
3731                 if (substring.tagClass != this.tagClass)
3732                     throw new ASN1TagClassException
3733                     ([ this.tagClass ], substring.tagClass, "decode a substring of a constructed NumericString");
3734 
3735                 if (substring.construction != ASN1Construction.primitive)
3736                     throw new ASN1ConstructionException
3737                     (substring.construction, "decode a substring of a constructed NumericString");
3738 
3739                 if (substring.tagNumber != this.tagNumber)
3740                     throw new ASN1TagNumberException
3741                     ([ this.tagNumber ], substring.tagNumber, "decode a substring of a constructed NumericString");
3742 
3743                 appendy.put(substring.numericString);
3744             }
3745             return appendy.data;
3746         }
3747     }
3748 
3749     /**
3750         Encodes a string, where the characters of the string are limited to
3751         0 - 9 and space.
3752 
3753         Throws:
3754         $(UL
3755             $(LI $(D ASN1ValueCharactersException) if any character other than 0-9 or space is supplied.)
3756         )
3757     */
3758     override public @property @system
3759     void numericString(in string value)
3760     {
3761         foreach (immutable character; value)
3762         {
3763             if (!canFind(numericStringCharacters, character))
3764                 throw new ASN1ValueCharactersException
3765                 ("1234567890 ", character, "NumericString");
3766         }
3767 
3768         if (value.length <= 1000u)
3769         {
3770             this.value = cast(ubyte[]) value.dup;
3771         }
3772         else
3773         {
3774             CERElement[] primitives;
3775             size_t i = 0u;
3776             while (i+1000u < value.length)
3777             {
3778                 CERElement x = new CERElement();
3779                 x.tagNumber = this.tagNumber;
3780                 x.construction = ASN1Construction.primitive;
3781                 x.value = cast(ubyte[]) value[i .. i+1000u];
3782                 primitives ~= x;
3783                 i += 1000u;
3784             }
3785 
3786             CERElement y = new CERElement();
3787             y.tagNumber = this.tagNumber;
3788             y.construction = ASN1Construction.primitive;
3789             y.value = cast(ubyte[]) value[i .. $];
3790             primitives ~= y;
3791 
3792             this.sequence = primitives;
3793             this.construction = ASN1Construction.constructed;
3794         }
3795     }
3796 
3797     @system
3798     unittest
3799     {
3800         void test(size_t length)
3801         {
3802             char[] data;
3803             data.length = length;
3804             for (size_t i = 0u; i < data.length; i++)
3805             {
3806                 data[i] = cast(char) ((i % 0x09) + 0x30);
3807             }
3808             CERElement el = new CERElement();
3809             el.numericString = cast(string) data;
3810             assert(el.numericString == cast(string) data);
3811         }
3812         test(0u);
3813         test(1u);
3814         test(8u);
3815         test(127u);
3816         test(128u);
3817         test(129u);
3818         test(192u);
3819         test(999u);
3820         test(1000u);
3821         test(1001u);
3822         test(2017u);
3823     }
3824 
3825     /**
3826         Decodes a string that will only contain characters a-z, A-Z, 0-9,
3827         space, apostrophe, parentheses, comma, minus, plus, period,
3828         forward slash, colon, equals, and question mark.
3829 
3830         Throws:
3831         $(UL
3832             $(LI $(D ASN1ValueCharactersException)
3833                 if any character other than a-z, A-Z,
3834                 0-9, space, apostrophe, parentheses, comma, minus, plus,
3835                 period, forward slash, colon, equals, or question mark are
3836                 encoded)
3837             $(LI $(D ASN1RecursionException)
3838                 if using constructed form and the element
3839                 is constructed of too many nested constructed elements)
3840             $(LI $(D ASN1TagClassException)
3841                 if any nested primitives do not share the
3842                 same tag class as their outer constructed element)
3843             $(LI $(D ASN1TagNumberException)
3844                 if any nested primitives do not share the
3845                 same tag number as their outer constructed element)
3846         )
3847     */
3848     override public @property @system
3849     string printableString() const
3850     {
3851         if (this.construction == ASN1Construction.primitive)
3852         {
3853             if (this.value.length > 1000u)
3854                 throw new ASN1ValueSizeException
3855                 (0u, 1000u, this.value.length, "decode a primitively-encoded PrintableString");
3856 
3857             foreach (immutable character; this.value)
3858             {
3859                 if (!canFind(printableStringCharacters, character))
3860                     throw new ASN1ValueCharactersException
3861                     (printableStringCharacters, character, "PrintableString");
3862             }
3863             return cast(string) this.value;
3864         }
3865         else
3866         {
3867             if (this.valueRecursionCount++ == this.nestingRecursionLimit)
3868                 throw new ASN1RecursionException(this.nestingRecursionLimit, "parse a PrintableString");
3869             scope(exit) this.valueRecursionCount--; // So exceptions do not leave residual recursion.
3870 
3871             CERElement[] substrings = this.sequence;
3872             if (substrings.length < 2u)
3873                 throw new ASN1ValueException
3874                 (
3875                     "This exception was thrown because you attempted to decode " ~
3876                     "a constructed PrintableString that was constituted by fewer " ~
3877                     "than two nested elements. When using the Canonical Encoding " ~
3878                     "Rules (CER), a legitimate constructed PrintableString must " ~
3879                     "contain at least two nested elements, and each but the " ~
3880                     "last must contain exactly 1000 content octets. The last " ~
3881                     "nested element may have between 1 and 1000 content " ~
3882                     "octets inclusively. This element should have been encoded " ~
3883                     "primitively using definite-length encoding."
3884                 );
3885 
3886             foreach (substring; substrings[0 .. $-1])
3887             {
3888                 if (substring.length != 1000u)
3889                     throw new ASN1ValueSizeException
3890                     (1000u, 1000u, substring.length, "decode a substring of a constructed PrintableString");
3891             }
3892 
3893             if (substrings[$-1].length == 0u)
3894                 throw new ASN1ValueSizeException
3895                 (1u, 1000u, substrings[$-1].length, "decode the last substring of a constructed PrintableString");
3896 
3897             Appender!string appendy = appender!string();
3898             foreach (substring; substrings)
3899             {
3900                 if (substring.tagClass != this.tagClass)
3901                     throw new ASN1TagClassException
3902                     ([ this.tagClass ], substring.tagClass, "decode a substring of a constructed PrintableString");
3903 
3904                 if (substring.construction != ASN1Construction.primitive)
3905                     throw new ASN1ConstructionException
3906                     (substring.construction, "decode a substring of a constructed PrintableString");
3907 
3908                 if (substring.tagNumber != this.tagNumber)
3909                     throw new ASN1TagNumberException
3910                     ([ this.tagNumber ], substring.tagNumber, "decode a substring of a constructed PrintableString");
3911 
3912                 appendy.put(substring.printableString);
3913             }
3914             return appendy.data;
3915         }
3916     }
3917 
3918     /**
3919         Encodes a string that may only contain characters a-z, A-Z, 0-9,
3920         space, apostrophe, parentheses, comma, minus, plus, period,
3921         forward slash, colon, equals, and question mark.
3922 
3923         Throws:
3924             $(LI $(D ASN1ValueCharactersException) if any character other than a-z, A-Z,
3925                 0-9, space, apostrophe, parentheses, comma, minus, plus,
3926                 period, forward slash, colon, equals, or question mark are
3927                 supplied)
3928     */
3929     override public @property @system
3930     void printableString(in string value)
3931     {
3932         foreach (immutable character; value)
3933         {
3934             if (!canFind(printableStringCharacters, character))
3935                 throw new ASN1ValueCharactersException
3936                 (printableStringCharacters, character, "PrintableString");
3937         }
3938 
3939         if (value.length <= 1000u)
3940         {
3941             this.value = cast(ubyte[]) value.dup;
3942         }
3943         else
3944         {
3945             CERElement[] primitives;
3946             size_t i = 0u;
3947             while (i+1000u < value.length)
3948             {
3949                 CERElement x = new CERElement();
3950                 x.tagNumber = this.tagNumber;
3951                 x.construction = ASN1Construction.primitive;
3952                 x.value = cast(ubyte[]) value[i .. i+1000u];
3953                 primitives ~= x;
3954                 i += 1000u;
3955             }
3956 
3957             CERElement y = new CERElement();
3958             y.tagNumber = this.tagNumber;
3959             y.construction = ASN1Construction.primitive;
3960             y.value = cast(ubyte[]) value[i .. $];
3961             primitives ~= y;
3962 
3963             this.sequence = primitives;
3964             this.construction = ASN1Construction.constructed;
3965         }
3966     }
3967 
3968     @system
3969     unittest
3970     {
3971         void test(size_t length)
3972         {
3973             char[] data;
3974             data.length = length;
3975             for (size_t i = 0u; i < data.length; i++)
3976             {
3977                 data[i] = cast(char) ((i % 0x10) + 0x41);
3978             }
3979             CERElement el = new CERElement();
3980             el.printableString = cast(string) data;
3981             assert(el.printableString == cast(string) data);
3982         }
3983         test(0u);
3984         test(1u);
3985         test(8u);
3986         test(127u);
3987         test(128u);
3988         test(129u);
3989         test(192u);
3990         test(999u);
3991         test(1000u);
3992         test(1001u);
3993         test(2017u);
3994     }
3995 
3996     /**
3997         Literally just returns the value bytes.
3998 
3999         Returns: an unsigned byte array, where each byte is a T.61 character.
4000 
4001         Throws:
4002         $(UL
4003             $(LI $(D ASN1RecursionException)
4004                 if using constructed form and the element
4005                 is constructed of too many nested constructed elements)
4006             $(LI $(D ASN1TagClassException)
4007                 if any nested primitives do not share the
4008                 same tag class as their outer constructed element)
4009             $(LI $(D ASN1TagNumberException)
4010                 if any nested primitives do not share the
4011                 same tag number as their outer constructed element)
4012         )
4013     */
4014     override public @property @system
4015     ubyte[] teletexString() const
4016     {
4017         if (this.construction == ASN1Construction.primitive)
4018         {
4019             if (this.value.length > 1000u)
4020                 throw new ASN1ValueSizeException
4021                 (0u, 1000u, this.value.length, "decode a primitively-encoded TeletexString");
4022 
4023             return this.value.dup;
4024         }
4025         else
4026         {
4027             if (this.valueRecursionCount++ == this.nestingRecursionLimit)
4028                 throw new ASN1RecursionException(this.nestingRecursionLimit, "parse a TeletexString");
4029             scope(exit) this.valueRecursionCount--; // So exceptions do not leave residual recursion.
4030 
4031             CERElement[] substrings = this.sequence;
4032             if (substrings.length < 2u)
4033                 throw new ASN1ValueException
4034                 (
4035                     "This exception was thrown because you attempted to decode " ~
4036                     "a constructed TeletexString that was constituted by fewer " ~
4037                     "than two nested elements. When using the Canonical Encoding " ~
4038                     "Rules (CER), a legitimate constructed TeletexString must " ~
4039                     "contain at least two nested elements, and each but the " ~
4040                     "last must contain exactly 1000 content octets. The last " ~
4041                     "nested element may have between 1 and 1000 content " ~
4042                     "octets inclusively. This element should have been encoded " ~
4043                     "primitively using definite-length encoding."
4044                 );
4045 
4046             foreach (substring; substrings[0 .. $-1])
4047             {
4048                 if (substring.length != 1000u)
4049                     throw new ASN1ValueSizeException
4050                     (1000u, 1000u, substring.length, "decode a substring of a constructed TeletexString");
4051             }
4052 
4053             if (substrings[$-1].length == 0u)
4054                 throw new ASN1ValueSizeException
4055                 (1u, 1000u, substrings[$-1].length, "decode the last substring of a constructed TeletexString");
4056 
4057             Appender!(ubyte[]) appendy = appender!(ubyte[])();
4058             foreach (substring; substrings)
4059             {
4060                 if (substring.tagClass != this.tagClass)
4061                     throw new ASN1TagClassException
4062                     ([ this.tagClass ], substring.tagClass, "decode a substring of a constructed TeletexString");
4063 
4064                 if (substring.construction != ASN1Construction.primitive)
4065                     throw new ASN1ConstructionException
4066                     (substring.construction, "decode a substring of a constructed TeletexString");
4067 
4068                 if (substring.tagNumber != this.tagNumber)
4069                     throw new ASN1TagNumberException
4070                     ([ this.tagNumber ], substring.tagNumber, "decode a substring of a constructed TeletexString");
4071 
4072                 appendy.put(substring.teletexString);
4073             }
4074             return appendy.data;
4075         }
4076     }
4077 
4078     /// Literally just sets the value bytes.
4079     override public @property @system
4080     void teletexString(in ubyte[] value)
4081     {
4082         if (value.length <= 1000u)
4083         {
4084             this.value = value.dup;
4085         }
4086         else
4087         {
4088             CERElement[] primitives;
4089             size_t i = 0u;
4090             while (i+1000u < value.length)
4091             {
4092                 CERElement x = new CERElement();
4093                 x.tagNumber = this.tagNumber;
4094                 x.construction = ASN1Construction.primitive;
4095                 x.value = value[i .. i+1000u].dup;
4096                 primitives ~= x;
4097                 i += 1000u;
4098             }
4099 
4100             CERElement y = new CERElement();
4101             y.tagNumber = this.tagNumber;
4102             y.construction = ASN1Construction.primitive;
4103             y.value = value[i .. $].dup;
4104             primitives ~= y;
4105 
4106             this.sequence = primitives;
4107             this.construction = ASN1Construction.constructed;
4108         }
4109     }
4110 
4111     @system
4112     unittest
4113     {
4114         void test(size_t length)
4115         {
4116             ubyte[] data;
4117             data.length = length;
4118             for (size_t i = 0u; i < data.length; i++)
4119             {
4120                 data[i] = (i % 9u);
4121             }
4122             CERElement el = new CERElement();
4123             el.teletexString = data;
4124             assert(el.teletexString == data);
4125         }
4126         test(0u);
4127         test(1u);
4128         test(8u);
4129         test(127u);
4130         test(128u);
4131         test(129u);
4132         test(192u);
4133         test(999u);
4134         test(1000u);
4135         test(1001u);
4136         test(2017u);
4137     }
4138 
4139     /**
4140         Literally just returns the value bytes.
4141 
4142         Returns: an unsigned byte array, where each byte is a Videotex character.
4143 
4144         Throws:
4145         $(UL
4146             $(LI $(D ASN1RecursionException)
4147                 if using constructed form and the element
4148                 is constructed of too many nested constructed elements)
4149             $(LI $(D ASN1TagClassException)
4150                 if any nested primitives do not share the
4151                 same tag class as their outer constructed element)
4152             $(LI $(D ASN1TagNumberException)
4153                 if any nested primitives do not share the
4154                 same tag number as their outer constructed element)
4155         )
4156     */
4157     override public @property @system
4158     ubyte[] videotexString() const
4159     {
4160         if (this.construction == ASN1Construction.primitive)
4161         {
4162             if (this.value.length > 1000u)
4163                 throw new ASN1ValueSizeException
4164                 (0u, 1000u, this.value.length, "decode a primitively-encoded VideotexString");
4165 
4166             return this.value.dup;
4167         }
4168         else
4169         {
4170             if (this.valueRecursionCount++ == this.nestingRecursionLimit)
4171                 throw new ASN1RecursionException(this.nestingRecursionLimit, "parse a VideotexString");
4172             scope(exit) this.valueRecursionCount--; // So exceptions do not leave residual recursion.
4173 
4174             CERElement[] substrings = this.sequence;
4175             if (substrings.length < 2u)
4176                 throw new ASN1ValueException
4177                 (
4178                     "This exception was thrown because you attempted to decode " ~
4179                     "a constructed VideotexString that was constituted by fewer " ~
4180                     "than two nested elements. When using the Canonical Encoding " ~
4181                     "Rules (CER), a legitimate constructed VideotexString must " ~
4182                     "contain at least two nested elements, and each but the " ~
4183                     "last must contain exactly 1000 content octets. The last " ~
4184                     "nested element may have between 1 and 1000 content " ~
4185                     "octets inclusively. This element should have been encoded " ~
4186                     "primitively using definite-length encoding."
4187                 );
4188 
4189             foreach (substring; substrings[0 .. $-1])
4190             {
4191                 if (substring.length != 1000u)
4192                     throw new ASN1ValueSizeException
4193                     (1000u, 1000u, substring.length, "decode a substring of a constructed VideotexString");
4194             }
4195 
4196             if (substrings[$-1].length == 0u)
4197                 throw new ASN1ValueSizeException
4198                 (1u, 1000u, substrings[$-1].length, "decode the last substring of a constructed VideotexString");
4199 
4200             Appender!(ubyte[]) appendy = appender!(ubyte[])();
4201             foreach (substring; substrings)
4202             {
4203                 if (substring.tagClass != this.tagClass)
4204                     throw new ASN1TagClassException
4205                     ([ this.tagClass ], substring.tagClass, "decode a substring of a constructed VideotexString");
4206 
4207                 if (substring.construction != ASN1Construction.primitive)
4208                     throw new ASN1ConstructionException
4209                     (substring.construction, "decode a substring of a constructed VideotexString");
4210 
4211                 if (substring.tagNumber != this.tagNumber)
4212                     throw new ASN1TagNumberException
4213                     ([ this.tagNumber ], substring.tagNumber, "decode a substring of a constructed VideotexString");
4214 
4215                 appendy.put(substring.videotexString);
4216             }
4217             return appendy.data;
4218         }
4219     }
4220 
4221     /// Literally just sets the value bytes.
4222     override public @property @system
4223     void videotexString(in ubyte[] value)
4224     {
4225         if (value.length <= 1000u)
4226         {
4227             this.value = value.dup;
4228         }
4229         else
4230         {
4231             CERElement[] primitives;
4232             size_t i = 0u;
4233             while (i+1000u < value.length)
4234             {
4235                 CERElement x = new CERElement();
4236                 x.tagNumber = this.tagNumber;
4237                 x.construction = ASN1Construction.primitive;
4238                 x.value = value[i .. i+1000u].dup;
4239                 primitives ~= x;
4240                 i += 1000u;
4241             }
4242 
4243             CERElement y = new CERElement();
4244             y.tagNumber = this.tagNumber;
4245             y.construction = ASN1Construction.primitive;
4246             y.value = value[i .. $].dup;
4247             primitives ~= y;
4248 
4249             this.sequence = primitives;
4250             this.construction = ASN1Construction.constructed;
4251         }
4252     }
4253 
4254     @system
4255     unittest
4256     {
4257         void test(size_t length)
4258         {
4259             ubyte[] data;
4260             data.length = length;
4261             for (size_t i = 0u; i < data.length; i++)
4262             {
4263                 data[i] = (i % 9u);
4264             }
4265             CERElement el = new CERElement();
4266             el.videotexString = data;
4267             assert(el.videotexString == data);
4268         }
4269         test(0u);
4270         test(1u);
4271         test(8u);
4272         test(127u);
4273         test(128u);
4274         test(129u);
4275         test(192u);
4276         test(999u);
4277         test(1000u);
4278         test(1001u);
4279         test(2017u);
4280     }
4281 
4282     /**
4283         Decodes a string that only contains ASCII characters.
4284 
4285         $(MONO IA5String) differs from ASCII ever so slightly: IA5 is international,
4286         leaving 10 characters up to be locale-specific:
4287 
4288         $(TABLE
4289             $(TR $(TH Byte) $(TH ASCII Character))
4290             $(TR $(TD 0x40) $(TD @))
4291             $(TR $(TD 0x5B) $(TD [))
4292             $(TR $(TD 0x5C) $(TD \))
4293             $(TR $(TD 0x5D) $(TD ]))
4294             $(TR $(TD 0x5E) $(TD ^))
4295             $(TR $(TD 0x60) $(TD `))
4296             $(TR $(TD 0x7B) $(TD {))
4297             $(TR $(TD 0x7C) $(TD /))
4298             $(TR $(TD 0x7D) $(TD }))
4299             $(TR $(TD 0x7E) $(TD ~))
4300         )
4301 
4302         Throws:
4303         $(UL
4304             $(LI $(D ASN1ValueCharactersException) if any encoded character is not ASCII)
4305             $(LI $(D ASN1RecursionException)
4306                 if using constructed form and the element
4307                 is constructed of too many nested constructed elements)
4308             $(LI $(D ASN1TagClassException)
4309                 if any nested primitives do not share the
4310                 same tag class as their outer constructed element)
4311             $(LI $(D ASN1TagNumberException)
4312                 if any nested primitives do not share the
4313                 same tag number as their outer constructed element)
4314         )
4315     */
4316     override public @property @system
4317     string internationalAlphabetNumber5String() const
4318     {
4319         if (this.construction == ASN1Construction.primitive)
4320         {
4321             if (this.value.length > 1000u)
4322                 throw new ASN1ValueSizeException
4323                 (0u, 1000u, this.value.length, "decode a primitively-encoded IA5String");
4324 
4325             foreach (immutable character; this.value)
4326             {
4327                 if (!character.isASCII)
4328                     throw new ASN1ValueCharactersException
4329                     ("all ASCII characters", character, "IA5String");
4330             }
4331             return cast(string) this.value;
4332         }
4333         else
4334         {
4335             if (this.valueRecursionCount++ == this.nestingRecursionLimit)
4336                 throw new ASN1RecursionException(this.nestingRecursionLimit, "parse a IA5String");
4337             scope(exit) this.valueRecursionCount--; // So exceptions do not leave residual recursion.
4338 
4339             CERElement[] substrings = this.sequence;
4340             if (substrings.length < 2u)
4341                 throw new ASN1ValueException
4342                 (
4343                     "This exception was thrown because you attempted to decode " ~
4344                     "a constructed IA5String that was constituted by fewer " ~
4345                     "than two nested elements. When using the Canonical Encoding " ~
4346                     "Rules (CER), a legitimate constructed IA5String must " ~
4347                     "contain at least two nested elements, and each but the " ~
4348                     "last must contain exactly 1000 content octets. The last " ~
4349                     "nested element may have between 1 and 1000 content " ~
4350                     "octets inclusively. This element should have been encoded " ~
4351                     "primitively using definite-length encoding."
4352                 );
4353 
4354             foreach (substring; substrings[0 .. $-1])
4355             {
4356                 if (substring.length != 1000u)
4357                     throw new ASN1ValueSizeException
4358                     (1000u, 1000u, substring.length, "decode a substring of a constructed IA5String");
4359             }
4360 
4361             if (substrings[$-1].length == 0u)
4362                 throw new ASN1ValueSizeException
4363                 (1u, 1000u, substrings[$-1].length, "decode the last substring of a constructed IA5String");
4364 
4365             Appender!string appendy = appender!string();
4366             foreach (substring; substrings)
4367             {
4368                 if (substring.tagClass != this.tagClass)
4369                     throw new ASN1TagClassException
4370                     ([ this.tagClass ], substring.tagClass, "decode a substring of a constructed IA5String");
4371 
4372                 if (substring.construction != ASN1Construction.primitive)
4373                     throw new ASN1ConstructionException
4374                     (substring.construction, "decode a substring of a constructed IA5String");
4375 
4376                 if (substring.tagNumber != this.tagNumber)
4377                     throw new ASN1TagNumberException
4378                     ([ this.tagNumber ], substring.tagNumber, "decode a substring of a constructed IA5String");
4379 
4380                 appendy.put(substring.ia5String);
4381             }
4382             return appendy.data;
4383         }
4384     }
4385 
4386     /**
4387         Encodes a string that may only contain ASCII characters.
4388 
4389         $(MONO IA5String) differs from ASCII ever so slightly: IA5 is international,
4390         leaving 10 characters up to be locale-specific:
4391 
4392         $(TABLE
4393             $(TR $(TH Byte) $(TH ASCII Character))
4394             $(TR $(TD 0x40) $(TD @))
4395             $(TR $(TD 0x5B) $(TD [))
4396             $(TR $(TD 0x5C) $(TD \))
4397             $(TR $(TD 0x5D) $(TD ]))
4398             $(TR $(TD 0x5E) $(TD ^))
4399             $(TR $(TD 0x60) $(TD `))
4400             $(TR $(TD 0x7B) $(TD {))
4401             $(TR $(TD 0x7C) $(TD /))
4402             $(TR $(TD 0x7D) $(TD }))
4403             $(TR $(TD 0x7E) $(TD ~))
4404         )
4405 
4406         Throws:
4407         $(UL
4408             $(LI $(D ASN1ValueCharactersException) if any encoded character is not ASCII)
4409         )
4410     */
4411     override public @property @system
4412     void internationalAlphabetNumber5String(in string value)
4413     {
4414         foreach (immutable character; value)
4415         {
4416             if (!character.isASCII)
4417                 throw new ASN1ValueCharactersException
4418                 ("all ASCII characters", character, "IA5String");
4419         }
4420 
4421         if (value.length <= 1000u)
4422         {
4423             this.value = cast(ubyte[]) value.dup;
4424         }
4425         else
4426         {
4427             CERElement[] primitives;
4428             size_t i = 0u;
4429             while (i+1000u < value.length)
4430             {
4431                 CERElement x = new CERElement();
4432                 x.tagNumber = this.tagNumber;
4433                 x.construction = ASN1Construction.primitive;
4434                 x.value = cast(ubyte[]) value[i .. i+1000u];
4435                 primitives ~= x;
4436                 i += 1000u;
4437             }
4438 
4439             CERElement y = new CERElement();
4440             y.tagNumber = this.tagNumber;
4441             y.construction = ASN1Construction.primitive;
4442             y.value = cast(ubyte[]) value[i .. $];
4443             primitives ~= y;
4444 
4445             this.sequence = primitives;
4446             this.construction = ASN1Construction.constructed;
4447         }
4448     }
4449 
4450     @system
4451     unittest
4452     {
4453         void test(size_t length)
4454         {
4455             char[] data;
4456             data.length = length;
4457             for (size_t i = 0u; i < data.length; i++)
4458             {
4459                 data[i] = cast(char) ((i % 0x40) + 0x30);
4460             }
4461             CERElement el = new CERElement();
4462             el.ia5String = cast(string) data;
4463             assert(el.ia5String == cast(string) data);
4464         }
4465         test(0u);
4466         test(1u);
4467         test(8u);
4468         test(127u);
4469         test(128u);
4470         test(129u);
4471         test(192u);
4472         test(999u);
4473         test(1000u);
4474         test(1001u);
4475         test(2017u);
4476     }
4477 
4478     /**
4479         Decodes a $(LINK https://dlang.org/phobos/std_datetime_date.html#.DateTime, DateTime).
4480         The value is just the ASCII character representation of the UTC-formatted timestamp.
4481 
4482         An UTC Timestamp looks like:
4483         $(UL
4484             $(LI $(MONO 9912312359Z))
4485             $(LI $(MONO 991231235959+0200))
4486         )
4487 
4488         If the first digit of the two-digit year is 7, 6, 5, 4, 3, 2, 1, or 0,
4489         meaning that the date refers to the first 80 years of the century, this
4490         assumes we are talking about the 21st century and prepend '20' when
4491         creating the ISO Date String. Otherwise, it assumes we are talking
4492         about the 20th century, and prepend '19' when creating the string.
4493 
4494         See_Also:
4495         $(UL
4496             $(LI $(LINK https://www.obj-sys.com/asn1tutorial/node15.html, UTCTime))
4497             $(LI $(LINK https://dlang.org/phobos/std_datetime_date.html#.DateTime, DateTime))
4498         )
4499 
4500         Throws:
4501         $(UL
4502             $(LI $(D ASN1ValueException) if the encoded value does not end with a 'Z')
4503             $(LI $(D ASN1ValueCharactersException) if any character is not valid in a $(MONO Visiblestring))
4504             $(LI $(D DateTimeException) if the encoded string cannot be decoded to a DateTime)
4505             $(LI $(D ASN1RecursionException)
4506                 if using constructed form and the element
4507                 is constructed of too many nested constructed elements)
4508             $(LI $(D ASN1TagClassException)
4509                 if any nested primitives do not share the
4510                 same tag class as their outer constructed element)
4511             $(LI $(D ASN1TagNumberException)
4512                 if any nested primitives do not share the
4513                 same tag number as their outer constructed element)
4514         )
4515     */
4516     override public @property @system
4517     DateTime coordinatedUniversalTime() const
4518     {
4519         if (this.construction != ASN1Construction.primitive)
4520             throw new ASN1ConstructionException(this.construction, "decode a UTCTime");
4521 
4522         // Mandated in X.690, section 11.8.2
4523         if (this.value.length != 13u) // YYMMDDhhmmssZ
4524             throw new ASN1ValueSizeException(13u, 13u, this.value.length, "decode a UTCTime");
4525 
4526         // Mandated in X.690, section 11.8.1
4527         if (this.value[$-1] != 'Z')
4528             throw new ASN1ValueException
4529             (
4530                 "This exception was thrown because you attempted to decode " ~
4531                 "a UTCTime whose encoding did not terminate with a 'Z', as " ~
4532                 "is expected by the Canonical Encoding Rules (CER) and " ~
4533                 "Distinguished Encoding Rules (DER). " ~
4534                 notWhatYouMeantText ~ forMoreInformationText ~
4535                 debugInformationText ~ reportBugsText
4536             );
4537 
4538         /** NOTE:
4539             .fromISOString() MUST be called from SysTime, not DateTime. There
4540             is a subtle difference in how .fromISOString() works in both SysTime
4541             and DateTime: SysTime's accepts the "Z" at the end (indicating that
4542             the time is in GMT).
4543 
4544             If you use DateTime.fromISOString, you will get a DateTimeException
4545             whose cryptic message reads "Invalid ISO String: " followed,
4546             strangely, by only the last six characters of the string.
4547         */
4548         immutable string dt = (((this.value[0] <= '7') ? "20" : "19") ~ cast(string) this.value);
4549         return cast(DateTime) SysTime.fromISOString(dt[0 .. 8].idup ~ "T" ~ dt[8 .. $].idup);
4550     }
4551 
4552     /**
4553         Encodes a DateTime. The value is just the ASCII character representation of
4554         the UTC-formatted timestamp.
4555 
4556         An UTC Timestamp looks like:
4557         $(UL
4558             $(LI $(MONO 9912312359Z))
4559             $(LI $(MONO 991231235959+0200))
4560         )
4561 
4562         See_Also:
4563             $(LINK https://www.obj-sys.com/asn1tutorial/node15.html, UTCTime)
4564     */
4565     override public @property @system
4566     void coordinatedUniversalTime(in DateTime value)
4567     out
4568     {
4569         // YYMMDDhhmmssZ is the only acceptable format for CER
4570         assert(this.value.length == 13u);
4571         assert(this.value[$-1] == 'Z');
4572     }
4573     body
4574     {
4575         scope(success) this.construction = ASN1Construction.primitive;
4576         immutable SysTime st = SysTime(value, UTC());
4577         this.value = cast(ubyte[]) ((st.toUTC()).toISOString()[2 .. $].replace("T", ""));
4578     }
4579 
4580     @system
4581     unittest
4582     {
4583         CERElement el = new CERElement();
4584         el.utcTime = DateTime(2017, 10, 3);
4585         assert(el.visibleString == "171003000000Z");
4586     }
4587 
4588     /**
4589         Decodes a DateTime. The value is just the ASCII character representation of
4590         the $(LINK https://www.iso.org/iso-8601-date-and-time-format.html, ISO 8601)-formatted timestamp.
4591 
4592         An ISO-8601 Timestamp looks like:
4593         $(UL
4594             $(LI $(MONO 19851106210627.3))
4595             $(LI $(MONO 19851106210627.3Z))
4596             $(LI $(MONO 19851106210627.3-0500))
4597         )
4598 
4599         Throws:
4600         $(UL
4601             $(LI $(D ASN1ValueException) if the encoded value did not end with a 'Z'
4602                 or contained a misplaced or unnecessary decimal point)
4603             $(LI $(D ASN1ValueCharactersException) if any character is not valid in a $(MONO Visiblestring))
4604             $(LI $(D DateTimeException) if the encoded string cannot be decoded to a DateTime)
4605             $(LI $(D ASN1ValuePaddingException) if the seconds fraction contains trailing zeroes)
4606             $(LI $(D ASN1RecursionException)
4607                 if using constructed form and the element
4608                 is constructed of too many nested constructed elements)
4609             $(LI $(D ASN1TagClassException)
4610                 if any nested primitives do not share the
4611                 same tag class as their outer constructed element)
4612             $(LI $(D ASN1TagNumberException)
4613                 if any nested primitives do not share the
4614                 same tag number as their outer constructed element)
4615         )
4616 
4617         Standards:
4618         $(UL
4619             $(LI $(LINK https://www.iso.org/iso-8601-date-and-time-format.html, ISO 8601))
4620         )
4621     */
4622     override public @property @system
4623     DateTime generalizedTime() const
4624     {
4625         if (this.construction != ASN1Construction.primitive)
4626             throw new ASN1ConstructionException(this.construction, "decode a GeneralizedTime");
4627 
4628         if (this.value.length < 15u || this.value.length > 1000u)
4629             throw new ASN1ValueSizeException(15u, 1000u, this.value.length, "decode a GeneralizedTime");
4630 
4631         // Inferred, because YYYYMMDDhhmmss.Z could not be valid.
4632         if (this.value.length == 16u)
4633             throw new ASN1ValueException
4634             (
4635                 "This exception was thrown because you attempted to decode a " ~
4636                 "GeneralizedTime that was encoded on too few bytes to be " ~
4637                 "correct. A valid GeneralizedTime cannot be 16 bytes. " ~
4638                 notWhatYouMeantText ~ forMoreInformationText ~
4639                 debugInformationText ~ reportBugsText
4640             );
4641 
4642         // Mandated in X.690, section 11.7.1
4643         if (this.value[$-1] != 'Z')
4644             throw new ASN1ValueException
4645             (
4646                 "This exception was thrown because you attempted to decode " ~
4647                 "a GeneralizedTime whose encoding did not terminate with a 'Z', " ~
4648                 "as is expected by the Canonical Encoding Rules (CER) and " ~
4649                 "Distinguished Encoding Rules (DER). " ~
4650                 notWhatYouMeantText ~ forMoreInformationText ~
4651                 debugInformationText ~ reportBugsText
4652             );
4653 
4654         ptrdiff_t indexOfDecimalPoint = (cast(string) this.value).indexOf('.');
4655         if (indexOfDecimalPoint != -1)
4656         {
4657             if (this.value[$-2] == '0')
4658                 throw new ASN1ValuePaddingException
4659                 (
4660                     "This exception was thrown because you attempted to decode " ~
4661                     "a GeneralizedTime that contained trailing zeroes in the " ~
4662                     "fraction-of-seconds part. " ~
4663                     notWhatYouMeantText ~ forMoreInformationText ~
4664                     debugInformationText ~ reportBugsText
4665                 );
4666 
4667             if (indexOfDecimalPoint != 14)
4668                 throw new ASN1ValueException
4669                 (
4670                     "This exception was thrown because you attempted to decode " ~
4671                     "a GeneralizedTime whose decimal point was misplaced. " ~
4672                     notWhatYouMeantText ~ forMoreInformationText ~
4673                     debugInformationText ~ reportBugsText
4674                 );
4675         }
4676         else
4677         {
4678             if ((cast(string) this.value).indexOf(',') != -1)
4679                 throw new ASN1ValueCharactersException
4680                 ("1234567890Z.", ',', "decode a GeneralizedTime");
4681         }
4682 
4683         /** NOTE:
4684             .fromISOString() MUST be called from SysTime, not DateTime. There
4685             is a subtle difference in how .fromISOString() works in both SysTime
4686             and DateTime: SysTime's accepts the "Z" at the end (indicating that
4687             the time is in GMT).
4688 
4689             If you use DateTime.fromISOString, you will get a DateTimeException
4690             whose cryptic message reads "Invalid ISO String: " followed,
4691             strangely, by only the last six characters of the string.
4692         */
4693         immutable string dt = cast(string) this.value;
4694         return cast(DateTime) SysTime.fromISOString(dt[0 .. 8].idup ~ "T" ~ dt[8 .. $].idup);
4695     }
4696 
4697     /**
4698         Encodes a DateTime.
4699 
4700         The value is just the ASCII character representation of
4701         the $(LINK https://www.iso.org/iso-8601-date-and-time-format.html,
4702         ISO 8601)-formatted timestamp.
4703 
4704         An ISO-8601 Timestamp looks like:
4705         $(UL
4706             $(LI $(MONO 19851106210627.3))
4707             $(LI $(MONO 19851106210627.3Z))
4708             $(LI $(MONO 19851106210627.3-0500))
4709         )
4710 
4711         Standards:
4712         $(UL
4713             $(LI $(LINK https://www.iso.org/iso-8601-date-and-time-format.html, ISO 8601))
4714         )
4715     */
4716     override public @property @system
4717     void generalizedTime(in DateTime value)
4718     out
4719     {
4720         assert(this.value.length > 0u);
4721         assert((cast(string) this.value).indexOf("T") == -1);
4722     }
4723     body
4724     {
4725         scope(success) this.construction = ASN1Construction.primitive;
4726         immutable SysTime st = SysTime(value, UTC());
4727         this.value = cast(ubyte[]) ((st.toUTC()).toISOString().replace("T", ""));
4728     }
4729 
4730     // Positive tests
4731     @system
4732     unittest
4733     {
4734         CERElement el = new CERElement();
4735 
4736         el.generalizedTime = DateTime(2017, 10, 3);
4737         assert(el.visibleString == "20171003000000Z");
4738 
4739         el.visibleString = "20171003000000.003Z";
4740         assertNotThrown!Exception(el.generalizedTime);
4741 
4742         el.visibleString = "20171003000000.0000003Z";
4743         assertNotThrown!Exception(el.generalizedTime);
4744     }
4745 
4746     // Negative tests
4747     @system
4748     unittest
4749     {
4750         CERElement el = new CERElement();
4751 
4752         el.visibleString = "20171003000000.Z";
4753         assertThrown!ASN1Exception(el.generalizedTime);
4754 
4755         el.visibleString = "20171003000000.0Z";
4756         assertThrown!ASN1Exception(el.generalizedTime);
4757 
4758         el.visibleString = "20171003000000.000000Z";
4759         assertThrown!ASN1Exception(el.generalizedTime);
4760 
4761         el.visibleString = "0171003000000Z";
4762         assertThrown!ASN1Exception(el.generalizedTime);
4763 
4764         el.visibleString = "2017100300000Z"; // Missing 1 trailing zero.
4765         assertThrown!ASN1Exception(el.generalizedTime);
4766     }
4767 
4768     /**
4769         Decodes an ASCII string that contains only characters between and
4770         including $(D 0x20) and $(D 0x75). Deprecated, according to page 182 of the
4771         Dubuisson book.
4772 
4773         Throws:
4774         $(UL
4775             $(LI $(D ASN1ValueCharactersException) if any non-graphical character (including space) is encoded)
4776             $(LI $(D ASN1RecursionException)
4777                 if using constructed form and the element
4778                 is constructed of too many nested constructed elements)
4779             $(LI $(D ASN1TagClassException)
4780                 if any nested primitives do not share the
4781                 same tag class as their outer constructed element)
4782             $(LI $(D ASN1TagNumberException)
4783                 if any nested primitives do not share the
4784                 same tag number as their outer constructed element)
4785         )
4786 
4787         Citations:
4788         $(UL
4789             $(LI Dubuisson, Olivier. “Basic Encoding Rules (BER).”
4790                 $(I ASN.1: Communication between Heterogeneous Systems),
4791                 Morgan Kaufmann, 2001, pp. 175-178.)
4792             $(LI $(LINK https://en.wikipedia.org/wiki/ISO/IEC_2022, The Wikipedia Page on ISO 2022))
4793             $(LI $(LINK https://www.iso.org/standard/22747.html, ISO 2022))
4794         )
4795     */
4796     override public @property @system
4797     string graphicString() const
4798     {
4799         if (this.construction == ASN1Construction.primitive)
4800         {
4801             if (this.value.length > 1000u)
4802                 throw new ASN1ValueSizeException
4803                 (0u, 1000u, this.value.length, "decode a primitively-encoded GraphicString");
4804 
4805             foreach (immutable character; this.value)
4806             {
4807                 if (!character.isGraphical && character != ' ')
4808                     throw new ASN1ValueCharactersException
4809                     ("all characters within the range 0x20 to 0x7E", character, "GraphicString");
4810             }
4811             return cast(string) this.value;
4812         }
4813         else
4814         {
4815             if (this.valueRecursionCount++ == this.nestingRecursionLimit)
4816                 throw new ASN1RecursionException(this.nestingRecursionLimit, "parse a GraphicString");
4817             scope(exit) this.valueRecursionCount--; // So exceptions do not leave residual recursion.
4818 
4819             CERElement[] substrings = this.sequence;
4820             if (substrings.length < 2u)
4821                 throw new ASN1ValueException
4822                 (
4823                     "This exception was thrown because you attempted to decode " ~
4824                     "a constructed GraphicString that was constituted by fewer " ~
4825                     "than two nested elements. When using the Canonical Encoding " ~
4826                     "Rules (CER), a legitimate constructed GraphicString must " ~
4827                     "contain at least two nested elements, and each but the " ~
4828                     "last must contain exactly 1000 content octets. The last " ~
4829                     "nested element may have between 1 and 1000 content " ~
4830                     "octets inclusively. This element should have been encoded " ~
4831                     "primitively using definite-length encoding."
4832                 );
4833 
4834             foreach (substring; substrings[0 .. $-1])
4835             {
4836                 if (substring.length != 1000u)
4837                     throw new ASN1ValueSizeException
4838                     (1000u, 1000u, substring.length, "decode a substring of a constructed GraphicString");
4839             }
4840 
4841             if (substrings[$-1].length == 0u)
4842                 throw new ASN1ValueSizeException
4843                 (1u, 1000u, substrings[$-1].length, "decode the last substring of a constructed GraphicString");
4844 
4845             Appender!string appendy = appender!string();
4846             foreach (substring; substrings)
4847             {
4848                 if (substring.tagClass != this.tagClass)
4849                     throw new ASN1TagClassException
4850                     ([ this.tagClass ], substring.tagClass, "decode a substring of a constructed GraphicString");
4851 
4852                 if (substring.construction != ASN1Construction.primitive)
4853                     throw new ASN1ConstructionException
4854                     (substring.construction, "decode a substring of a constructed GraphicString");
4855 
4856                 if (substring.tagNumber != this.tagNumber)
4857                     throw new ASN1TagNumberException
4858                     ([ this.tagNumber ], substring.tagNumber, "decode a substring of a constructed GraphicString");
4859 
4860                 appendy.put(substring.graphicString);
4861             }
4862             return appendy.data;
4863         }
4864     }
4865 
4866     /**
4867         Encodes an ASCII string that may contain only characters between and
4868         including $(D 0x20) and $(D 0x75). Deprecated, according to page 182
4869         of the Dubuisson book.
4870 
4871         Throws:
4872         $(UL
4873             $(LI $(D ASN1ValueCharactersException) if any non-graphical character (including space) is supplied)
4874         )
4875 
4876         Citations:
4877         $(UL
4878             $(LI Dubuisson, Olivier. “Basic Encoding Rules (BER).”
4879                 $(I ASN.1: Communication between Heterogeneous Systems),
4880                 Morgan Kaufmann, 2001, pp. 175-178.)
4881             $(LI $(LINK https://en.wikipedia.org/wiki/ISO/IEC_2022, The Wikipedia Page on ISO 2022))
4882             $(LI $(LINK https://www.iso.org/standard/22747.html, ISO 2022))
4883         )
4884     */
4885     override public @property @system
4886     void graphicString(in string value)
4887     {
4888         foreach (immutable character; value)
4889         {
4890             if (!character.isGraphical && character != ' ')
4891                 throw new ASN1ValueCharactersException
4892                 ("all characters within the range 0x20 to 0x7E", character, "GraphicString");
4893         }
4894 
4895         if (value.length <= 1000u)
4896         {
4897             this.value = cast(ubyte[]) value.dup;
4898         }
4899         else
4900         {
4901             CERElement[] primitives;
4902             size_t i = 0u;
4903             while (i+1000u < value.length)
4904             {
4905                 CERElement x = new CERElement();
4906                 x.tagNumber = this.tagNumber;
4907                 x.construction = ASN1Construction.primitive;
4908                 x.value = cast(ubyte[]) value[i .. i+1000u];
4909                 primitives ~= x;
4910                 i += 1000u;
4911             }
4912 
4913             CERElement y = new CERElement();
4914             y.tagNumber = this.tagNumber;
4915             y.construction = ASN1Construction.primitive;
4916             y.value = cast(ubyte[]) value[i .. $];
4917             primitives ~= y;
4918 
4919             this.sequence = primitives;
4920             this.construction = ASN1Construction.constructed;
4921         }
4922     }
4923 
4924     @system
4925     unittest
4926     {
4927         void test(size_t length)
4928         {
4929             char[] data;
4930             data.length = length;
4931             for (size_t i = 0u; i < data.length; i++)
4932             {
4933                 data[i] = cast(char) ((i % 0x40) + 0x20);
4934             }
4935             CERElement el = new CERElement();
4936             el.graphicString = cast(string) data;
4937             assert(el.graphicString == cast(string) data);
4938         }
4939         test(0u);
4940         test(1u);
4941         test(8u);
4942         test(127u);
4943         test(128u);
4944         test(129u);
4945         test(192u);
4946         test(999u);
4947         test(1000u);
4948         test(1001u);
4949         test(2017u);
4950     }
4951 
4952     /**
4953         Decodes a string that only contains characters between and including
4954         $(D 0x20) and $(D 0x7E). (Honestly, I don't know how this differs from
4955         $(MONO GraphicalString).)
4956 
4957         Throws:
4958         $(UL
4959             $(LI $(D ASN1ValueCharactersException)
4960                 if any non-graphical character (including space) is encoded)
4961             $(LI $(D ASN1RecursionException)
4962                 if using constructed form and the element
4963                 is constructed of too many nested constructed elements)
4964             $(LI $(D ASN1TagClassException)
4965                 if any nested primitives do not share the
4966                 same tag class as their outer constructed element)
4967             $(LI $(D ASN1TagNumberException)
4968                 if any nested primitives do not share the
4969                 same tag number as their outer constructed element)
4970         )
4971     */
4972     override public @property @system
4973     string visibleString() const
4974     {
4975         if (this.construction == ASN1Construction.primitive)
4976         {
4977             if (this.value.length > 1000u)
4978                 throw new ASN1ValueSizeException
4979                 (0u, 1000u, this.value.length, "decode a primitively-encoded VisibleString");
4980 
4981             foreach (immutable character; this.value)
4982             {
4983                 if (!character.isGraphical && character != ' ')
4984                     throw new ASN1ValueCharactersException
4985                     ("all characters within the range 0x20 to 0x7E", character, "VisibleString");
4986             }
4987             return cast(string) this.value;
4988         }
4989         else
4990         {
4991             if (this.valueRecursionCount++ == this.nestingRecursionLimit)
4992                 throw new ASN1RecursionException(this.nestingRecursionLimit, "parse a VisibleString");
4993             scope(exit) this.valueRecursionCount--; // So exceptions do not leave residual recursion.
4994 
4995             CERElement[] substrings = this.sequence;
4996             if (substrings.length < 2u)
4997                 throw new ASN1ValueException
4998                 (
4999                     "This exception was thrown because you attempted to decode " ~
5000                     "a constructed VisibleString that was constituted by fewer " ~
5001                     "than two nested elements. When using the Canonical Encoding " ~
5002                     "Rules (CER), a legitimate constructed VisibleString must " ~
5003                     "contain at least two nested elements, and each but the " ~
5004                     "last must contain exactly 1000 content octets. The last " ~
5005                     "nested element may have between 1 and 1000 content " ~
5006                     "octets inclusively. This element should have been encoded " ~
5007                     "primitively using definite-length encoding."
5008                 );
5009 
5010             foreach (substring; substrings[0 .. $-1])
5011             {
5012                 if (substring.length != 1000u)
5013                     throw new ASN1ValueSizeException
5014                     (1000u, 1000u, substring.length, "decode a substring of a constructed VisibleString");
5015             }
5016 
5017             if (substrings[$-1].length == 0u)
5018                 throw new ASN1ValueSizeException
5019                 (1u, 1000u, substrings[$-1].length, "decode the last substring of a constructed VisibleString");
5020 
5021             Appender!string appendy = appender!string();
5022             foreach (substring; substrings)
5023             {
5024                 if (substring.tagClass != this.tagClass)
5025                     throw new ASN1TagClassException
5026                     ([ this.tagClass ], substring.tagClass, "decode a substring of a constructed VisibleString");
5027 
5028                 if (substring.construction != ASN1Construction.primitive)
5029                     throw new ASN1ConstructionException
5030                     (substring.construction, "decode a substring of a constructed VisibleString");
5031 
5032                 if (substring.tagNumber != this.tagNumber)
5033                     throw new ASN1TagNumberException
5034                     ([ this.tagNumber ], substring.tagNumber, "decode a substring of a constructed VisibleString");
5035 
5036                 appendy.put(substring.visibleString);
5037             }
5038             return appendy.data;
5039         }
5040     }
5041 
5042     /**
5043         Encodes a string that only contains characters between and including
5044         $(D 0x20) and $(D 0x7E). (Honestly, I don't know how this differs from
5045         $(MONO GraphicalString).)
5046 
5047         Throws:
5048         $(UL
5049             $(LI $(D ASN1ValueCharactersException)
5050                 if any non-graphical character (including space) is supplied.)
5051         )
5052     */
5053     override public @property @system
5054     void visibleString(in string value)
5055     {
5056         foreach (immutable character; value)
5057         {
5058             if (!character.isGraphical && character != ' ')
5059                 throw new ASN1ValueCharactersException
5060                 ("all characters within the range 0x20 to 0x7E", character, "VisibleString");
5061         }
5062 
5063         if (value.length <= 1000u)
5064         {
5065             this.value = cast(ubyte[]) value.dup;
5066         }
5067         else
5068         {
5069             CERElement[] primitives;
5070             size_t i = 0u;
5071             while (i+1000u < value.length)
5072             {
5073                 CERElement x = new CERElement();
5074                 x.tagNumber = this.tagNumber;
5075                 x.construction = ASN1Construction.primitive;
5076                 x.value = cast(ubyte[]) value[i .. i+1000u];
5077                 primitives ~= x;
5078                 i += 1000u;
5079             }
5080 
5081             CERElement y = new CERElement();
5082             y.tagNumber = this.tagNumber;
5083             y.construction = ASN1Construction.primitive;
5084             y.value = cast(ubyte[]) value[i .. $];
5085             primitives ~= y;
5086 
5087             this.sequence = primitives;
5088             this.construction = ASN1Construction.constructed;
5089         }
5090     }
5091 
5092     @system
5093     unittest
5094     {
5095         void test(size_t length)
5096         {
5097             char[] data;
5098             data.length = length;
5099             for (size_t i = 0u; i < data.length; i++)
5100             {
5101                 data[i] = cast(char) ((i % 0x40) + 0x20);
5102             }
5103             CERElement el = new CERElement();
5104             el.visibleString = cast(string) data;
5105             assert(el.visibleString == cast(string) data);
5106         }
5107         test(0u);
5108         test(1u);
5109         test(8u);
5110         test(127u);
5111         test(128u);
5112         test(129u);
5113         test(192u);
5114         test(999u);
5115         test(1000u);
5116         test(1001u);
5117         test(2017u);
5118     }
5119 
5120     /**
5121         Decodes a string containing only ASCII characters. Deprecated, according
5122         to page 182 of the Dubuisson book.
5123 
5124         Throws:
5125         $(UL
5126             $(LI $(D ASN1ValueCharactersException) if any encoded character is not ASCII)
5127             $(LI $(D ASN1RecursionException)
5128                 if using constructed form and the element
5129                 is constructed of too many nested constructed elements)
5130             $(LI $(D ASN1TagClassException)
5131                 if any nested primitives do not share the
5132                 same tag class as their outer constructed element)
5133             $(LI $(D ASN1TagNumberException)
5134                 if any nested primitives do not share the
5135                 same tag number as their outer constructed element)
5136         )
5137 
5138         Citations:
5139         $(UL
5140             $(LI Dubuisson, Olivier. “Basic Encoding Rules (BER).”
5141                 $(I ASN.1: Communication between Heterogeneous Systems),
5142                 Morgan Kaufmann, 2001, p. 182.)
5143             $(LI $(LINK https://en.wikipedia.org/wiki/ISO/IEC_2022, The Wikipedia Page on ISO 2022))
5144             $(LI $(LINK https://www.iso.org/standard/22747.html, ISO 2022))
5145         )
5146     */
5147     override public @property @system
5148     string generalString() const
5149     {
5150         if (this.construction == ASN1Construction.primitive)
5151         {
5152             if (this.value.length > 1000u)
5153                 throw new ASN1ValueSizeException
5154                 (0u, 1000u, this.value.length, "decode a primitively-encoded GeneralString");
5155 
5156             foreach (immutable character; this.value)
5157             {
5158                 if (!character.isASCII)
5159                     throw new ASN1ValueCharactersException
5160                     ("all ASCII characters", character, "GeneralString");
5161             }
5162             return cast(string) this.value;
5163         }
5164         else
5165         {
5166             if (this.valueRecursionCount++ == this.nestingRecursionLimit)
5167                 throw new ASN1RecursionException(this.nestingRecursionLimit, "parse a GeneralString");
5168             scope(exit) this.valueRecursionCount--; // So exceptions do not leave residual recursion.
5169 
5170             CERElement[] substrings = this.sequence;
5171             if (substrings.length < 2u)
5172                 throw new ASN1ValueException
5173                 (
5174                     "This exception was thrown because you attempted to decode " ~
5175                     "a constructed GeneralString that was constituted by fewer " ~
5176                     "than two nested elements. When using the Canonical Encoding " ~
5177                     "Rules (CER), a legitimate constructed GeneralString must " ~
5178                     "contain at least two nested elements, and each but the " ~
5179                     "last must contain exactly 1000 content octets. The last " ~
5180                     "nested element may have between 1 and 1000 content " ~
5181                     "octets inclusively. This element should have been encoded " ~
5182                     "primitively using definite-length encoding."
5183                 );
5184 
5185             foreach (substring; substrings[0 .. $-1])
5186             {
5187                 if (substring.length != 1000u)
5188                     throw new ASN1ValueSizeException
5189                     (1000u, 1000u, substring.length, "decode a substring of a constructed GeneralString");
5190             }
5191 
5192             if (substrings[$-1].length == 0u)
5193                 throw new ASN1ValueSizeException
5194                 (1u, 1000u, substrings[$-1].length, "decode the last substring of a constructed GeneralString");
5195 
5196             Appender!string appendy = appender!string();
5197             foreach (substring; substrings)
5198             {
5199                 if (substring.tagClass != this.tagClass)
5200                     throw new ASN1TagClassException
5201                     ([ this.tagClass ], substring.tagClass, "decode a substring of a constructed GeneralString");
5202 
5203                 if (substring.construction != ASN1Construction.primitive)
5204                     throw new ASN1ConstructionException
5205                     (substring.construction, "decode a substring of a constructed GeneralString");
5206 
5207                 if (substring.tagNumber != this.tagNumber)
5208                     throw new ASN1TagNumberException
5209                     ([ this.tagNumber ], substring.tagNumber, "decode a substring of a constructed GeneralString");
5210 
5211                 appendy.put(substring.generalString);
5212             }
5213             return appendy.data;
5214         }
5215     }
5216 
5217     /**
5218         Encodes a string containing only ASCII characters. Deprecated,
5219         according to page 182 of the Dubuisson book.
5220 
5221         Throws:
5222         $(UL
5223             $(LI $(D ASN1ValueCharactersException) if any encoded character is not ASCII)
5224         )
5225 
5226         Citations:
5227         $(UL
5228             $(LI Dubuisson, Olivier. “Basic Encoding Rules (BER).”
5229                 $(I ASN.1: Communication between Heterogeneous Systems),
5230                 Morgan Kaufmann, 2001, p. 182.)
5231         )
5232     */
5233     override public @property @system
5234     void generalString(in string value)
5235     {
5236         foreach (immutable character; value)
5237         {
5238             if (!character.isASCII)
5239                 throw new ASN1ValueCharactersException
5240                 ("all ASCII characters", character, "GeneralString");
5241         }
5242 
5243         if (value.length <= 1000u)
5244         {
5245             this.value = cast(ubyte[]) value.dup;
5246         }
5247         else
5248         {
5249             CERElement[] primitives;
5250             size_t i = 0u;
5251             while (i+1000u < value.length)
5252             {
5253                 CERElement x = new CERElement();
5254                 x.tagNumber = this.tagNumber;
5255                 x.construction = ASN1Construction.primitive;
5256                 x.value = cast(ubyte[]) value[i .. i+1000u];
5257                 primitives ~= x;
5258                 i += 1000u;
5259             }
5260 
5261             CERElement y = new CERElement();
5262             y.tagNumber = this.tagNumber;
5263             y.construction = ASN1Construction.primitive;
5264             y.value = cast(ubyte[]) value[i .. $];
5265             primitives ~= y;
5266 
5267             this.sequence = primitives;
5268             this.construction = ASN1Construction.constructed;
5269         }
5270     }
5271 
5272     @system
5273     unittest
5274     {
5275         void test(size_t length)
5276         {
5277             char[] data;
5278             data.length = length;
5279             for (size_t i = 0u; i < data.length; i++)
5280             {
5281                 data[i] = cast(char) ((i % 0x40) + 0x20);
5282             }
5283             CERElement el = new CERElement();
5284             el.generalString = cast(string) data;
5285             assert(el.generalString == cast(string) data);
5286         }
5287         test(0u);
5288         test(1u);
5289         test(8u);
5290         test(127u);
5291         test(128u);
5292         test(129u);
5293         test(192u);
5294         test(999u);
5295         test(1000u);
5296         test(1001u);
5297         test(2017u);
5298     }
5299 
5300     /**
5301         Decodes a $(MONO dstring) of UTF-32 characters.
5302 
5303         Throws:
5304         $(UL
5305             $(LI $(D ASN1ValueException)
5306                 if the encoded bytes is not evenly divisible by four)
5307             $(LI $(D ASN1RecursionException)
5308                 if using constructed form and the element
5309                 is constructed of too many nested constructed elements)
5310             $(LI $(D ASN1TagClassException)
5311                 if any nested primitives do not share the
5312                 same tag class as their outer constructed element)
5313             $(LI $(D ASN1TagNumberException)
5314                 if any nested primitives do not share the
5315                 same tag number as their outer constructed element)
5316         )
5317     */
5318     override public @property @system
5319     dstring universalString() const
5320     {
5321         if (this.construction == ASN1Construction.primitive)
5322         {
5323             if (this.value.length > 1000u)
5324                 throw new ASN1ValueSizeException
5325                 (0u, 1000u, this.value.length, "decode a primitively-encoded UniversalString");
5326 
5327             if (this.value.length == 0u) return ""d;
5328             if (this.value.length % 4u)
5329                 throw new ASN1ValueException
5330                 (
5331                     "This exception was thrown because you tried to decode " ~
5332                     "a UniversalString that contained a number of bytes that " ~
5333                     "is not divisible by four. " ~
5334                     notWhatYouMeantText ~ forMoreInformationText ~
5335                     debugInformationText ~ reportBugsText
5336                 );
5337 
5338             version (BigEndian)
5339             {
5340                 return cast(dstring) this.value;
5341             }
5342             else version (LittleEndian)
5343             {
5344                 dstring ret;
5345                 size_t i = 0u;
5346                 while (i < this.value.length-3u)
5347                 {
5348                     ubyte[] character;
5349                     character.length = 4u;
5350                     character[3] = this.value[i++];
5351                     character[2] = this.value[i++];
5352                     character[1] = this.value[i++];
5353                     character[0] = this.value[i++];
5354                     ret ~= (*cast(dchar *) character.ptr);
5355                 }
5356                 return ret;
5357             }
5358             else
5359             {
5360                 static assert(0, "Could not determine endianness!");
5361             }
5362         }
5363         else
5364         {
5365             if (this.valueRecursionCount++ == this.nestingRecursionLimit)
5366                 throw new ASN1RecursionException(this.nestingRecursionLimit, "parse a UniversalString");
5367             scope(exit) this.valueRecursionCount--; // So exceptions do not leave residual recursion.
5368 
5369             CERElement[] substrings = this.sequence;
5370             if (substrings.length < 2u)
5371                 throw new ASN1ValueException
5372                 (
5373                     "This exception was thrown because you attempted to decode " ~
5374                     "a constructed UniversalString that was constituted by fewer " ~
5375                     "than two nested elements. When using the Canonical Encoding " ~
5376                     "Rules (CER), a legitimate constructed UniversalString must " ~
5377                     "contain at least two nested elements, and each but the " ~
5378                     "last must contain exactly 1000 content octets. The last " ~
5379                     "nested element may have between 1 and 1000 content " ~
5380                     "octets inclusively. This element should have been encoded " ~
5381                     "primitively using definite-length encoding."
5382                 );
5383 
5384             foreach (substring; substrings[0 .. $-1])
5385             {
5386                 if (substring.length != 1000u)
5387                     throw new ASN1ValueSizeException
5388                     (1000u, 1000u, substring.length, "decode a substring of a constructed UniversalString");
5389             }
5390 
5391             if (substrings[$-1].length == 0u)
5392                 throw new ASN1ValueSizeException
5393                 (1u, 1000u, substrings[$-1].length, "decode the last substring of a constructed UniversalString");
5394 
5395             Appender!dstring appendy = appender!dstring();
5396             foreach (substring; substrings)
5397             {
5398                 if (substring.tagClass != this.tagClass)
5399                     throw new ASN1TagClassException
5400                     ([ this.tagClass ], substring.tagClass, "decode a substring of a constructed UniversalString");
5401 
5402                 if (substring.construction != ASN1Construction.primitive)
5403                     throw new ASN1ConstructionException
5404                     (substring.construction, "decode a substring of a constructed UniversalString");
5405 
5406                 if (substring.tagNumber != this.tagNumber)
5407                     throw new ASN1TagNumberException
5408                     ([ this.tagNumber ], substring.tagNumber, "decode a substring of a constructed UniversalString");
5409 
5410                 appendy.put(substring.universalString);
5411             }
5412             return appendy.data;
5413         }
5414     }
5415 
5416     /// Encodes a $(MONO dstring) of UTF-32 characters.
5417     override public @property @system
5418     void universalString(in dstring value)
5419     {
5420         if (value.length <= 250u)
5421         {
5422             version (BigEndian)
5423             {
5424                 this.value = cast(ubyte[]) value.dup;
5425             }
5426             else version (LittleEndian)
5427             {
5428                 foreach (immutable character; value)
5429                 {
5430                     ubyte[] charBytes = cast(ubyte[]) *cast(char[4] *) &character;
5431                     reverse(charBytes);
5432                     this.value ~= charBytes;
5433                 }
5434             }
5435             else
5436             {
5437                 static assert(0, "Could not determine endianness!");
5438             }
5439         }
5440         else
5441         {
5442             CERElement[] primitives;
5443             size_t i = 0u;
5444             while (i+250u < value.length)
5445             {
5446                 CERElement x = new CERElement();
5447                 x.tagNumber = this.tagNumber;
5448                 x.construction = ASN1Construction.primitive;
5449                 version (BigEndian)
5450                 {
5451                     x.value = cast(ubyte[]) value[i .. i+250u];
5452                 }
5453                 else version (LittleEndian)
5454                 {
5455                     foreach (immutable character; value[i .. i+250u])
5456                     {
5457                         ubyte[] charBytes = cast(ubyte[]) *cast(char[4] *) &character;
5458                         reverse(charBytes);
5459                         x.value ~= charBytes;
5460                     }
5461                 }
5462                 else
5463                 {
5464                     static assert(0, "Could not determine endianness!");
5465                 }
5466                 primitives ~= x;
5467                 i += 250u;
5468             }
5469 
5470             CERElement y = new CERElement();
5471             y.tagNumber = this.tagNumber;
5472             y.construction = ASN1Construction.primitive;
5473             version (BigEndian)
5474             {
5475                 y.value = cast(ubyte[]) value[i .. $];
5476             }
5477             else version (LittleEndian)
5478             {
5479                 foreach (immutable character; value[i .. $])
5480                 {
5481                     ubyte[] charBytes = cast(ubyte[]) *cast(char[4] *) &character;
5482                     reverse(charBytes);
5483                     y.value ~= charBytes;
5484                 }
5485             }
5486             else
5487             {
5488                 static assert(0, "Could not determine endianness!");
5489             }
5490             primitives ~= y;
5491 
5492             this.sequence = primitives;
5493             this.construction = ASN1Construction.constructed;
5494         }
5495     }
5496 
5497     @system
5498     unittest
5499     {
5500         void test(size_t length)
5501         {
5502             dchar[] data;
5503             data.length = length;
5504             for (size_t i = 0u; i < data.length; i++)
5505             {
5506                 data[i] = cast(dchar) ((i % 0x60u) + 0x20u);
5507             }
5508             CERElement el = new CERElement();
5509             el.universalString = cast(dstring) data;
5510             assert(el.universalString == cast(dstring) data);
5511         }
5512         test(0u);
5513         test(1u);
5514         test(8u);
5515         test(127u);
5516         test(128u);
5517         test(129u);
5518         test(192u);
5519         test(999u);
5520         test(1000u);
5521         test(1001u);
5522         test(2017u);
5523     }
5524 
5525     /**
5526         Decodes a $(MONO CharacterString), which is a constructed data type, defined
5527         in the $(LINK https://www.itu.int, International Telecommunications Union)'s
5528             $(LINK https://www.itu.int/rec/T-REC-X.680/en, X.680).
5529 
5530         The specification defines $(MONO CharacterString) as:
5531 
5532         $(PRE
5533             CHARACTER STRING ::= [UNIVERSAL 29] SEQUENCE {
5534                 identification CHOICE {
5535                     syntaxes SEQUENCE {
5536                         abstract OBJECT IDENTIFIER,
5537                         transfer OBJECT IDENTIFIER },
5538                     syntax OBJECT IDENTIFIER,
5539                     presentation-context-id INTEGER,
5540                     context-negotiation SEQUENCE {
5541                         presentation-context-id INTEGER,
5542                         transfer-syntax OBJECT IDENTIFIER },
5543                     transfer-syntax OBJECT IDENTIFIER,
5544                     fixed NULL },
5545                 string-value OCTET STRING }
5546         )
5547 
5548         This assumes $(MONO AUTOMATIC TAGS), so all of the $(MONO identification)
5549         choices will be $(MONO CONTEXT-SPECIFIC) and numbered from 0 to 5.
5550 
5551         Returns: an instance of $(D types.universal.characterstring.CharacterString).
5552 
5553         Throws:
5554         $(UL
5555             $(LI $(D ASN1ValueException) if encoded $(MONO CharacterString) has too few or too many
5556                 elements, or if $(MONO syntaxes) or $(MONO context-negotiation) element has
5557                 too few or too many elements)
5558             $(LI $(D ASN1ValueSizeException) if encoded $(MONO INTEGER) is too large to decode)
5559             $(LI $(D ASN1RecursionException) if using constructed form and the element
5560                 is constructed of too many nested constructed elements)
5561             $(LI $(D ASN1TagClassException) if any nested primitives do not have the
5562                 correct tag class)
5563             $(LI $(D ASN1ConstructionException) if any element has the wrong construction)
5564             $(LI $(D ASN1TagNumberException) if any nested primitives do not have the
5565                 correct tag number)
5566         )
5567     */
5568     override public @property @system
5569     CharacterString characterString() const
5570     {
5571         if (this.construction != ASN1Construction.constructed)
5572             throw new ASN1ConstructionException
5573             (this.construction, "decode an INTEGER");
5574 
5575         const CERElement[] components = this.sequence;
5576         ASN1ContextSwitchingTypeID identification = ASN1ContextSwitchingTypeID();
5577 
5578         if (components.length != 2u)
5579             throw new ASN1ValueException
5580             (
5581                 "This exception was thrown because you attempted to decode " ~
5582                 "a CharacterString that contained too many or too few elements. " ~
5583                 "A CharacterString should have only two elements: " ~
5584                 "an identification CHOICE, and a data-value OCTET STRING, " ~
5585                 "in that order. " ~
5586                 notWhatYouMeantText ~ forMoreInformationText ~
5587                 debugInformationText ~ reportBugsText
5588             );
5589 
5590         if (components[0].tagClass != ASN1TagClass.contextSpecific)
5591             throw new ASN1TagClassException
5592             (
5593                 [ ASN1TagClass.contextSpecific ],
5594                 components[0].tagClass,
5595                 "decode the first component of a CharacterString"
5596             );
5597 
5598         if (components[1].tagClass != ASN1TagClass.contextSpecific)
5599             throw new ASN1TagClassException
5600             (
5601                 [ ASN1TagClass.contextSpecific ],
5602                 components[1].tagClass,
5603                 "decode the second component of a CharacterString"
5604             );
5605 
5606         /* NOTE:
5607             See page 224 of Dubuisson, item 11:
5608             It sounds like, even if you have an ABSENT constraint applied,
5609             all automatically-tagged items still have the same numbers as
5610             though the constrained component were PRESENT.
5611         */
5612         if (components[0].tagNumber != 0u)
5613             throw new ASN1TagNumberException
5614             ([ 0u ], components[0].tagNumber, "decode the first component of a CharacterString");
5615 
5616         if (components[1].tagNumber != 2u)
5617             throw new ASN1TagNumberException
5618             ([ 2u ], components[1].tagNumber, "decode the second component of a CharacterString");
5619 
5620         ubyte[] bytes = components[0].value.dup;
5621         const CERElement identificationChoice = new CERElement(bytes);
5622         switch (identificationChoice.tagNumber)
5623         {
5624             case (0u): // syntaxes
5625             {
5626                 if (identificationChoice.construction != ASN1Construction.constructed)
5627                     throw new ASN1ConstructionException
5628                     (identificationChoice.construction, "decode the syntaxes component of a CharacterString");
5629 
5630                 const CERElement[] syntaxesComponents = identificationChoice.sequence;
5631 
5632                 if (syntaxesComponents.length != 2u)
5633                     throw new ASN1ValueException
5634                     (
5635                         "This exception was thrown because you attempted to " ~
5636                         "decode a CharacterString whose syntaxes component " ~
5637                         "contained an invalid number of elements. The " ~
5638                         "syntaxes component should contain abstract and transfer " ~
5639                         "syntax OBJECT IDENTIFIERS, in that order. " ~
5640                         notWhatYouMeantText ~ forMoreInformationText ~
5641                         debugInformationText ~ reportBugsText
5642                     );
5643 
5644                 // Class Validation
5645                 if (syntaxesComponents[0].tagClass != ASN1TagClass.contextSpecific)
5646                     throw new ASN1TagClassException
5647                     (
5648                         [ ASN1TagClass.contextSpecific ],
5649                         syntaxesComponents[0].tagClass,
5650                         "decode the first syntaxes component of a CharacterString"
5651                     );
5652 
5653                 if (syntaxesComponents[1].tagClass != ASN1TagClass.contextSpecific)
5654                     throw new ASN1TagClassException
5655                     (
5656                         [ ASN1TagClass.contextSpecific ],
5657                         syntaxesComponents[1].tagClass,
5658                         "decode the second syntaxes component of a CharacterString"
5659                     );
5660 
5661                 // Construction Validation
5662                 if (syntaxesComponents[0].construction != ASN1Construction.primitive)
5663                     throw new ASN1ConstructionException
5664                     (syntaxesComponents[0].construction, "decode the first syntaxes component of a CharacterString");
5665 
5666                 if (syntaxesComponents[1].construction != ASN1Construction.primitive)
5667                     throw new ASN1ConstructionException
5668                     (syntaxesComponents[1].construction, "decode the second syntaxes component of a CharacterString");
5669 
5670                 // Number Validation
5671                 if (syntaxesComponents[0].tagNumber != 0u)
5672                     throw new ASN1TagNumberException
5673                     (
5674                         [ 0u ],
5675                         syntaxesComponents[0].tagNumber,
5676                         "decode the first syntaxes component of a CharacterString"
5677                     );
5678 
5679                 if (syntaxesComponents[1].tagNumber != 1u)
5680                     throw new ASN1TagNumberException
5681                     (
5682                         [ 1u ],
5683                         syntaxesComponents[1].tagNumber,
5684                         "decode the second syntaxes component of a CharacterString"
5685                     );
5686 
5687                 identification.syntaxes  = ASN1Syntaxes(
5688                     syntaxesComponents[0].objectIdentifier,
5689                     syntaxesComponents[1].objectIdentifier
5690                 );
5691 
5692                 break;
5693             }
5694             case (1u): // syntax
5695             {
5696                 identification.syntax = identificationChoice.objectIdentifier;
5697                 break;
5698             }
5699             case (4u): // transfer-syntax
5700             {
5701                 identification.transferSyntax = identificationChoice.objectIdentifier;
5702                 break;
5703             }
5704             case (5u): // fixed
5705             {
5706                 identification.fixed = true;
5707                 break;
5708             }
5709             default:
5710                 throw new ASN1TagNumberException
5711                 (
5712                     [ 0u, 1u, 4u, 5u ],
5713                     identificationChoice.tagNumber,
5714                     "decode a CharacterString identification"
5715                 );
5716         }
5717 
5718         CharacterString cs = CharacterString();
5719         cs.identification = identification;
5720         cs.stringValue = components[1].octetString;
5721         return cs;
5722     }
5723 
5724     /**
5725         Encodes a $(MONO CharacterString), which is a constructed data type, defined
5726         in the $(LINK https://www.itu.int, International Telecommunications Union)'s
5727             $(LINK https://www.itu.int/rec/T-REC-X.680/en, X.680).
5728 
5729         The specification defines $(MONO CharacterString) as:
5730 
5731         $(PRE
5732             CHARACTER STRING ::= [UNIVERSAL 29] SEQUENCE {
5733                 identification CHOICE {
5734                     syntaxes SEQUENCE {
5735                         abstract OBJECT IDENTIFIER,
5736                         transfer OBJECT IDENTIFIER },
5737                     syntax OBJECT IDENTIFIER,
5738                     presentation-context-id INTEGER,
5739                     context-negotiation SEQUENCE {
5740                         presentation-context-id INTEGER,
5741                         transfer-syntax OBJECT IDENTIFIER },
5742                     transfer-syntax OBJECT IDENTIFIER,
5743                     fixed NULL },
5744                 string-value OCTET STRING }
5745         )
5746 
5747         This assumes $(MONO AUTOMATIC TAGS), so all of the $(MONO identification)
5748         choices will be $(MONO CONTEXT-SPECIFIC) and numbered from 0 to 5.
5749     */
5750     override public @property @system
5751     void characterString(in CharacterString value)
5752     out
5753     {
5754         assert(this.value.length > 0u);
5755     }
5756     body
5757     {
5758         scope(success) this.construction = ASN1Construction.constructed;
5759         CERElement identification = new CERElement();
5760         identification.tagClass = ASN1TagClass.contextSpecific;
5761         identification.tagNumber = 0u; // CHOICE is EXPLICIT, even with automatic tagging.
5762 
5763         CERElement identificationChoice = new CERElement();
5764         identificationChoice.tagClass = ASN1TagClass.contextSpecific;
5765         if (!(value.identification.syntaxes.isNull))
5766         {
5767             CERElement abstractSyntax = new CERElement();
5768             abstractSyntax.tagClass = ASN1TagClass.contextSpecific;
5769             abstractSyntax.tagNumber = 0u;
5770             abstractSyntax.objectIdentifier = value.identification.syntaxes.abstractSyntax;
5771 
5772             CERElement transferSyntax = new CERElement();
5773             transferSyntax.tagClass = ASN1TagClass.contextSpecific;
5774             transferSyntax.tagNumber = 1u;
5775             transferSyntax.objectIdentifier = value.identification.syntaxes.transferSyntax;
5776 
5777             identificationChoice.construction = ASN1Construction.constructed;
5778             identificationChoice.tagNumber = 0u;
5779             identificationChoice.sequence = [ abstractSyntax, transferSyntax ];
5780         }
5781         else if (!(value.identification.syntax.isNull))
5782         {
5783             identificationChoice.tagNumber = 1u;
5784             identificationChoice.objectIdentifier = value.identification.syntax;
5785         }
5786         else if (!(value.identification.transferSyntax.isNull))
5787         {
5788             identificationChoice.tagNumber = 4u;
5789             identificationChoice.objectIdentifier = value.identification.transferSyntax;
5790         }
5791         else
5792         {
5793             identificationChoice.tagNumber = 5u;
5794             identificationChoice.value = [];
5795         }
5796 
5797         // This makes identification: [CONTEXT 0][L][CONTEXT #][L][V]
5798         identification.value = cast(ubyte[]) identificationChoice;
5799 
5800         CERElement stringValue = new CERElement();
5801         stringValue.tagClass = ASN1TagClass.contextSpecific;
5802         stringValue.tagNumber = 2u;
5803         stringValue.octetString = value.stringValue;
5804 
5805         this.sequence = [ identification, stringValue ];
5806     }
5807 
5808     /* NOTE:
5809         This unit test had to be moved out of ASN1Element because CER and CER
5810         do not support encoding of context-negotiation in CharacterString.
5811 
5812         This unit test ensures that, if you attempt to create a CharacterString
5813         with context-negotiation as the CHOICE of identification, the
5814         encoded CharacterString's identification defaults to fixed.
5815     */
5816     @system
5817     unittest
5818     {
5819         ASN1ContextNegotiation cn = ASN1ContextNegotiation();
5820         cn.presentationContextID = 27L;
5821         cn.transferSyntax = new OID(1, 3, 6, 4, 1, 256, 39);
5822 
5823         ASN1ContextSwitchingTypeID id = ASN1ContextSwitchingTypeID();
5824         id.contextNegotiation = cn;
5825 
5826         CharacterString input = CharacterString();
5827         input.identification = id;
5828         input.stringValue = [ 'H', 'E', 'N', 'L', 'O' ];
5829 
5830         CERElement el = new CERElement();
5831         el.characterString = input;
5832         CharacterString output = el.characterString;
5833         assert(output.identification.fixed == true);
5834         assert(output.stringValue == [ 'H', 'E', 'N', 'L', 'O' ]);
5835     }
5836 
5837     // Inspired by CVE-2017-9023
5838     @system
5839     unittest
5840     {
5841         ubyte[] data = [ // This is valid.
5842             0x1Eu, 0x0Au, // CharacterString, Length 11
5843                 0x80u, 0x02u, // CHOICE
5844                     0x85u, 0x00u, // NULL
5845                 0x82u, 0x04u, 0x01u, 0x02u, 0x03u, 0x04u ]; // OCTET STRING
5846 
5847         // Valid values for data[2]: 80
5848         for (ubyte i = 0x81u; i < 0x9Eu; i++)
5849         {
5850             data[2] = i;
5851             size_t x = 0u;
5852             CERElement el = new CERElement(x, data);
5853             assertThrown!ASN1Exception(el.characterString);
5854         }
5855 
5856         // Valid values for data[4]: 80-85
5857         for (ubyte i = 0x86u; i < 0x9Eu; i++)
5858         {
5859             data[4] = i;
5860             size_t x = 0u;
5861             CERElement el = new CERElement(x, data);
5862             assertThrown!ASN1Exception(el.characterString);
5863         }
5864 
5865         // Valid values for data[6]: 82
5866         for (ubyte i = 0x83u; i < 0x9Eu; i++)
5867         {
5868             data[6] = i;
5869             size_t x = 0u;
5870             CERElement el = new CERElement(x, data);
5871             assertThrown!ASN1Exception(el.characterString);
5872         }
5873     }
5874 
5875     /**
5876         Decodes a $(MONO wstring) of UTF-16 characters.
5877 
5878         Throws:
5879         $(UL
5880             $(LI $(D ASN1ValueException)
5881                 if the encoded bytes is not evenly divisible by two)
5882             $(LI $(D ASN1RecursionException)
5883                 if using constructed form and the element
5884                 is constructed of too many nested constructed elements)
5885             $(LI $(D ASN1TagClassException)
5886                 if any nested primitives do not share the
5887                 same tag class as their outer constructed element)
5888             $(LI $(D ASN1TagNumberException)
5889                 if any nested primitives do not share the
5890                 same tag number as their outer constructed element)
5891         )
5892     */
5893     override public @property @system
5894     wstring basicMultilingualPlaneString() const
5895     {
5896         if (this.construction == ASN1Construction.primitive)
5897         {
5898             if (this.value.length > 1000u)
5899                 throw new ASN1ValueSizeException
5900                 (0u, 1000u, this.value.length, "decode a primitively-encoded BMPString");
5901 
5902             if (this.value.length == 0u) return ""w;
5903             if (this.value.length % 2u)
5904                 throw new ASN1ValueException
5905                 (
5906                     "This exception was thrown because you tried to decode " ~
5907                     "a BMPString that contained a number of bytes that " ~
5908                     "is not divisible by two. " ~
5909                     notWhatYouMeantText ~ forMoreInformationText ~
5910                     debugInformationText ~ reportBugsText
5911                 );
5912 
5913             version (BigEndian)
5914             {
5915                 return cast(wstring) this.value;
5916             }
5917             else version (LittleEndian)
5918             {
5919                 wstring ret;
5920                 size_t i = 0u;
5921                 while (i < this.value.length-1u)
5922                 {
5923                     ubyte[] character;
5924                     character.length = 2u;
5925                     character[1] = this.value[i++];
5926                     character[0] = this.value[i++];
5927                     ret ~= (*cast(wchar *) character.ptr);
5928                 }
5929                 return ret;
5930             }
5931             else
5932             {
5933                 static assert(0, "Could not determine endianness!");
5934             }
5935         }
5936         else
5937         {
5938             if (this.valueRecursionCount++ == this.nestingRecursionLimit)
5939                 throw new ASN1RecursionException(this.nestingRecursionLimit, "parse a BMPString");
5940             scope(exit) this.valueRecursionCount--; // So exceptions do not leave residual recursion.
5941 
5942             CERElement[] substrings = this.sequence;
5943             if (substrings.length < 2u)
5944                 throw new ASN1ValueException
5945                 (
5946                     "This exception was thrown because you attempted to decode " ~
5947                     "a constructed BMPString that was constituted by fewer " ~
5948                     "than two nested elements. When using the Canonical Encoding " ~
5949                     "Rules (CER), a legitimate constructed BMPString must " ~
5950                     "contain at least two nested elements, and each but the " ~
5951                     "last must contain exactly 1000 content octets. The last " ~
5952                     "nested element may have between 1 and 1000 content " ~
5953                     "octets inclusively. This element should have been encoded " ~
5954                     "primitively using definite-length encoding."
5955                 );
5956 
5957             foreach (substring; substrings[0 .. $-1])
5958             {
5959                 if (substring.length != 1000u)
5960                     throw new ASN1ValueSizeException
5961                     (1000u, 1000u, substring.length, "decode a substring of a constructed BMPString");
5962             }
5963 
5964             if (substrings[$-1].length == 0u)
5965                 throw new ASN1ValueSizeException
5966                 (1u, 1000u, substrings[$-1].length, "decode the last substring of a constructed BMPString");
5967 
5968             Appender!wstring appendy = appender!wstring();
5969             foreach (substring; substrings)
5970             {
5971                 if (substring.tagClass != this.tagClass)
5972                     throw new ASN1TagClassException
5973                     ([ this.tagClass ], substring.tagClass, "decode a substring of a constructed BMPString");
5974 
5975                 if (substring.construction != ASN1Construction.primitive)
5976                     throw new ASN1ConstructionException
5977                     (substring.construction, "decode a substring of a constructed BMPString");
5978 
5979                 if (substring.tagNumber != this.tagNumber)
5980                     throw new ASN1TagNumberException
5981                     ([ this.tagNumber ], substring.tagNumber, "decode a substring of a constructed BMPString");
5982 
5983                 appendy.put(substring.bmpString);
5984             }
5985             return appendy.data;
5986         }
5987     }
5988 
5989     /// Encodes a $(MONO wstring) of UTF-16 characters.
5990     override public @property @system
5991     void basicMultilingualPlaneString(in wstring value)
5992     {
5993         if (value.length <= 500u)
5994         {
5995             version (BigEndian)
5996             {
5997                 this.value = cast(ubyte[]) value.dup;
5998             }
5999             else version (LittleEndian)
6000             {
6001                 foreach (immutable character; value)
6002                 {
6003                     ubyte[] charBytes = cast(ubyte[]) *cast(char[2] *) &character;
6004                     reverse(charBytes);
6005                     this.value ~= charBytes;
6006                 }
6007             }
6008             else
6009             {
6010                 static assert(0, "Could not determine endianness!");
6011             }
6012         }
6013         else
6014         {
6015             CERElement[] primitives;
6016             size_t i = 0u;
6017             while (i+500u < value.length)
6018             {
6019                 CERElement x = new CERElement();
6020                 x.tagNumber = this.tagNumber;
6021                 x.construction = ASN1Construction.primitive;
6022                 version (BigEndian)
6023                 {
6024                     x.value = cast(ubyte[]) value[i .. i+500u];
6025                 }
6026                 else version (LittleEndian)
6027                 {
6028                     foreach (immutable character; value[i .. i+500u])
6029                     {
6030                         ubyte[] charBytes = cast(ubyte[]) *cast(char[2] *) &character;
6031                         reverse(charBytes);
6032                         x.value ~= charBytes;
6033                     }
6034                 }
6035                 else
6036                 {
6037                     static assert(0, "Could not determine endianness!");
6038                 }
6039                 primitives ~= x;
6040                 i += 500u;
6041             }
6042 
6043             CERElement y = new CERElement();
6044             y.tagNumber = this.tagNumber;
6045             y.construction = ASN1Construction.primitive;
6046             version (BigEndian)
6047             {
6048                 y.value = cast(ubyte[]) value[i .. $];
6049             }
6050             else version (LittleEndian)
6051             {
6052                 foreach (immutable character; value[i .. $])
6053                 {
6054                     ubyte[] charBytes = cast(ubyte[]) *cast(char[2] *) &character;
6055                     reverse(charBytes);
6056                     y.value ~= charBytes;
6057                 }
6058             }
6059             else
6060             {
6061                 static assert(0, "Could not determine endianness!");
6062             }
6063             primitives ~= y;
6064 
6065             this.sequence = primitives;
6066             this.construction = ASN1Construction.constructed;
6067         }
6068     }
6069 
6070     @system
6071     unittest
6072     {
6073         void test(size_t length)
6074         {
6075             wchar[] data;
6076             data.length = length;
6077             for (size_t i = 0u; i < data.length; i++)
6078             {
6079                 data[i] = cast(wchar) ((i % 0x60u) + 0x20u);
6080             }
6081             CERElement el = new CERElement();
6082             el.bmpString = cast(wstring) data;
6083             assert(el.bmpString == cast(wstring) data);
6084         }
6085         test(0u);
6086         test(1u);
6087         test(8u);
6088         test(127u);
6089         test(128u);
6090         test(129u);
6091         test(192u);
6092         test(999u);
6093         test(1000u);
6094         test(1001u);
6095         test(2017u);
6096     }
6097 
6098     /// Creates an $(MONO END OF CONTENT)
6099     public @safe @nogc nothrow
6100     this()
6101     {
6102         this.tagNumber = 0u;
6103         this.value = [];
6104     }
6105 
6106     /**
6107         Creates a $(D CERElement) from the supplied bytes, inferring that the first
6108         byte is the type tag. The supplied $(D ubyte[]) array is "chomped" by
6109         reference, so the original array will grow shorter as $(D CERElement)s are
6110         generated.
6111 
6112         Throws:
6113             All of the same exceptions as $(D fromBytes())
6114 
6115         Examples:
6116         ---
6117         // Decoding looks like:
6118         CERElement[] result;
6119         while (bytes.length > 0)
6120             result ~= new CERElement(bytes);
6121 
6122         // Encoding looks like:
6123         ubyte[] result;
6124         foreach (cv; cervalues)
6125         {
6126             result ~= cast(ubyte[]) cv;
6127         }
6128         ---
6129     */
6130     public @system
6131     this(ref ubyte[] bytes)
6132     {
6133         size_t bytesRead = this.fromBytes(bytes);
6134         bytes = bytes[bytesRead .. $];
6135     }
6136 
6137     /**
6138         Creates a $(D CERElement) from the supplied bytes, inferring that the first
6139         byte is the type tag. The supplied $(D ubyte[]) array is read, starting
6140         from the index specified by $(D bytesRead), and increments
6141         $(D bytesRead) by the number of bytes read.
6142 
6143         Throws:
6144             All of the same exceptions as $(D fromBytes())
6145 
6146         Examples:
6147         ---
6148         // Decoding looks like:
6149         CERElement[] result;
6150         size_t i = 0u;
6151         while (i < bytes.length)
6152             result ~= new CERElement(i, bytes);
6153 
6154         // Encoding looks like:
6155         ubyte[] result;
6156         foreach (cv; cervalues)
6157         {
6158             result ~= cast(ubyte[]) cv;
6159         }
6160         ---
6161     */
6162     public @system
6163     this(ref size_t bytesRead, in ubyte[] bytes)
6164     {
6165         bytesRead += this.fromBytes(bytes[bytesRead .. $].dup);
6166     }
6167 
6168     /**
6169         Returns: the number of bytes read
6170 
6171         Throws:
6172         $(UL
6173             $(LI $(D ASN1TagPaddingException) if the tag number is "padded" with
6174                 "leading zero bytes" ($(D 0x80u)))
6175             $(LI $(D ASN1TagOverflowException) if the tag number is too large to
6176                 fit into a $(D size_t))
6177             $(LI $(D ASN1LengthUndefinedException) if the reserved length byte of
6178                 $(D 0xFF) is encountered)
6179             $(LI $(D ASN1LengthOverflowException) if the length is too large to fit
6180                 into a $(D size_t))
6181             $(LI $(D ASN1TruncationException) if the tag, length, or value appear to
6182                 be truncated)
6183             $(LI $(D ASN1ConstructionException) if the length is indefinite, but the
6184                 element is marked as being encoded primitively)
6185             $(LI $(D ASN1RecursionException) if, when trying to determine the end of
6186                 an indefinite-length encoded element, the parser has to recurse
6187                 too deep)
6188         )
6189     */
6190     public
6191     size_t fromBytes (in ubyte[] bytes)
6192     {
6193         if (bytes.length < 2u)
6194             throw new ASN1TruncationException
6195             (
6196                 2u,
6197                 bytes.length,
6198                 "decode the tag of a Canonical Encoding Rules (CER) encoded element"
6199             );
6200 
6201         // Index of what we are currently parsing.
6202         size_t cursor = 0u;
6203 
6204         switch (bytes[cursor] & 0b11000000u)
6205         {
6206             case (0b00000000u): this.tagClass = ASN1TagClass.universal; break;
6207             case (0b01000000u): this.tagClass = ASN1TagClass.application; break;
6208             case (0b10000000u): this.tagClass = ASN1TagClass.contextSpecific; break;
6209             case (0b11000000u): this.tagClass = ASN1TagClass.privatelyDefined; break;
6210             default: assert(0, "Impossible tag class appeared!");
6211         }
6212 
6213         this.construction = ((bytes[cursor] & 0b00100000u) ?
6214             ASN1Construction.constructed : ASN1Construction.primitive);
6215 
6216         this.tagNumber = (bytes[cursor] & 0b00011111u);
6217         cursor++;
6218         if (this.tagNumber >= 31u)
6219         {
6220             /* NOTE:
6221                 Section 8.1.2.4.2, point C of the International
6222                 Telecommunications Union's X.690 specification says:
6223 
6224                 "bits 7 to 1 of the first subsequent octet shall not all be zero."
6225 
6226                 in reference to the bytes used to encode the tag number in long
6227                 form, which happens when the least significant five bits of the
6228                 first byte are all set.
6229 
6230                 This essentially means that the long-form tag number must be
6231                 encoded on the fewest possible octets. If the first byte is
6232                 0x80, then it is not encoded on the fewest possible octets.
6233             */
6234             if (bytes[cursor] == 0b10000000u)
6235                 throw new ASN1TagPaddingException
6236                 (
6237                     "This exception was thrown because you attempted to decode " ~
6238                     "a Canonical Encoding Rules (CER) encoded element whose tag " ~
6239                     "number was encoded in long form in the octets following " ~
6240                     "the first octet of the type tag, and whose tag number " ~
6241                     "was encoded with a 'leading zero' byte, 0x80. When " ~
6242                     "using Canonical Encoding Rules (CER), the tag number must " ~
6243                     "be encoded on the smallest number of octets possible, " ~
6244                     "which the inclusion of leading zero bytes necessarily " ~
6245                     "contradicts. " ~
6246                     forMoreInformationText ~ debugInformationText ~ reportBugsText
6247                 );
6248 
6249             this.tagNumber = 0u;
6250 
6251             // This loop looks for the end of the encoded tag number.
6252             immutable size_t limit = ((bytes.length-1 >= size_t.sizeof) ? size_t.sizeof : bytes.length-1);
6253             while (cursor < limit)
6254             {
6255                 if (!(bytes[cursor++] & 0x80u)) break;
6256             }
6257 
6258             if (bytes[cursor-1] & 0x80u)
6259             {
6260                 if (limit == bytes.length-1)
6261                 {
6262                     throw new ASN1TruncationException
6263                     (size_t.max, bytes.length, "decode an ASN.1 tag number");
6264                 }
6265                 else
6266                 {
6267                     throw new ASN1TagOverflowException
6268                     (
6269                         "This exception was thrown because you attempted to decode " ~
6270                         "a Canonical Encoding Rules (CER) encoded element that encoded " ~
6271                         "a tag number that was either too large to decode or " ~
6272                         "terminated prematurely."
6273                     );
6274                 }
6275             }
6276 
6277             for (size_t i = 1; i < cursor; i++)
6278             {
6279                 this.tagNumber <<= 7;
6280                 this.tagNumber |= cast(size_t) (bytes[i] & 0x7Fu);
6281             }
6282         }
6283 
6284         // Length
6285         if ((bytes[cursor] & 0x80u) == 0x80u)
6286         {
6287             immutable ubyte numberOfLengthOctets = (bytes[cursor] & 0x7Fu);
6288             if (numberOfLengthOctets) // Definite Long or Reserved
6289             {
6290                 if (numberOfLengthOctets == 0b01111111u) // Reserved
6291                     throw new ASN1LengthUndefinedException();
6292 
6293                 // Definite Long, if it has made it this far
6294 
6295                 if (this.construction == ASN1Construction.constructed)
6296                     throw new ASN1ConstructionException
6297                     (this.construction, "decode a long definite-length Canonical Encoding Rules (CER) encoded element");
6298 
6299                 if (numberOfLengthOctets > size_t.sizeof)
6300                     throw new ASN1LengthOverflowException();
6301 
6302                 if (cursor + numberOfLengthOctets >= bytes.length)
6303                     throw new ASN1TruncationException
6304                     (
6305                         numberOfLengthOctets,
6306                         ((bytes.length - 1) - cursor),
6307                         "decode the length of a Basic Encoding Rules (BER) encoded element"
6308                     );
6309 
6310                 if (bytes[++cursor] == 0x00u)
6311                     throw new ASN1LengthException
6312                     (
6313                         "This exception was thrown because you attempted to " ~
6314                         "decode a Canonical Encoding Rules (CER) encoded " ~
6315                         "element whose length was encoded in definite long " ~
6316                         "form, and encoded on more octets than necessary, " ~
6317                         "which is prohibited by the specification for " ~
6318                         "Canonical Encoding Rules (CER). " ~
6319                         forMoreInformationText ~ debugInformationText ~ reportBugsText
6320                     );
6321 
6322                 ubyte[] lengthNumberOctets;
6323                 lengthNumberOctets.length = size_t.sizeof;
6324                 for (ubyte i = numberOfLengthOctets; i > 0u; i--)
6325                 {
6326                     lengthNumberOctets[size_t.sizeof-i] = bytes[cursor+numberOfLengthOctets-i];
6327                 }
6328                 version (LittleEndian) reverse(lengthNumberOctets);
6329                 size_t length = *cast(size_t *) lengthNumberOctets.ptr;
6330 
6331                 if (length <= 127u)
6332                     throw new ASN1LengthException
6333                     (
6334                         "This exception was thrown because you attempted to " ~
6335                         "decode a Canonical Encoding Rules (CER) encoded " ~
6336                         "element whose length was encoded in definite long " ~
6337                         "form, and encoded on more octets than necessary, " ~
6338                         "which is prohibited by the specification for " ~
6339                         "Canonical Encoding Rules (CER). Specifically, it " ~
6340                         "was encoded in definite-long form when it was less " ~
6341                         "than or equal to 127, which could have been encoded " ~
6342                         "in definite-short form. " ~
6343                         forMoreInformationText ~ debugInformationText ~ reportBugsText
6344                     );
6345 
6346                 if ((cursor + length) < cursor) // This catches an overflow.
6347                     throw new ASN1LengthException
6348                     (
6349                         "This exception was thrown because you attempted to " ~
6350                         "decode a Canonical Encoding Rules (CER) encoded element " ~
6351                         "that indicated that it was exceedingly large--so " ~
6352                         "large, in fact, that it cannot be stored on this " ~
6353                         "computer (18 exabytes if you are on a 64-bit system). " ~
6354                         "This may indicate that the data you attempted to " ~
6355                         "decode was either corrupted, malformed, or deliberately " ~
6356                         "crafted to hack you. You would be wise to ensure that " ~
6357                         "you are running the latest stable version of this " ~
6358                         "library. "
6359                     );
6360 
6361                 cursor += (numberOfLengthOctets);
6362 
6363                 if ((cursor + length) > bytes.length)
6364                     throw new ASN1TruncationException
6365                     (
6366                         length,
6367                         (bytes.length - cursor),
6368                         "decode the value of a Canonical Encoding Rules (CER) encoded element"
6369                     );
6370 
6371                 this.value = bytes[cursor .. cursor+length].dup;
6372                 return (cursor + length);
6373             }
6374             else // Indefinite
6375             {
6376                 if (this.construction != ASN1Construction.constructed)
6377                     throw new ASN1ConstructionException
6378                     (this.construction, "decode an indefinite-length element");
6379 
6380                 if (++(this.lengthRecursionCount) > this.nestingRecursionLimit)
6381                 {
6382                     this.lengthRecursionCount = 0u;
6383                     throw new ASN1RecursionException
6384                     (
6385                         this.nestingRecursionLimit,
6386                         "decode a Canonical Encoding Rules (CER) encoded element " ~
6387                         "whose value was encoded with indefinite length form " ~
6388                         "and constructed recursively from deeply nested elements"
6389                     );
6390                 }
6391 
6392                 immutable size_t startOfValue = ++cursor;
6393                 size_t sentinel = cursor; // Used to track the length of the nested elements.
6394                 while (sentinel < bytes.length)
6395                 {
6396                     CERElement child = new CERElement(sentinel, bytes);
6397                     if
6398                     (
6399                         child.tagClass == ASN1TagClass.universal &&
6400                         child.construction == ASN1Construction.primitive &&
6401                         child.tagNumber == ASN1UniversalType.endOfContent &&
6402                         child.length == 0u
6403                     )
6404                     break;
6405                 }
6406 
6407                 if (sentinel == bytes.length && (bytes[sentinel-1] != 0x00u || bytes[sentinel-2] != 0x00u))
6408                     throw new ASN1TruncationException
6409                     (
6410                         length,
6411                         (length + 2u),
6412                         "find the END OF CONTENT octets (two consecutive null " ~
6413                         "bytes) of an indefinite-length encoded " ~
6414                         "Canonical Encoding Rules (CER) element"
6415                     );
6416 
6417                 this.lengthRecursionCount--;
6418                 this.value = bytes[startOfValue .. sentinel-2u].dup;
6419                 return (sentinel);
6420             }
6421         }
6422         else // Definite Short
6423         {
6424             if (this.construction == ASN1Construction.constructed)
6425                 throw new ASN1ConstructionException
6426                 (this.construction, "decode a short definite-length Canonical Encoding Rules (CER) encoded element");
6427 
6428             ubyte length = (bytes[cursor++] & 0x7Fu);
6429 
6430             if ((cursor + length) > bytes.length)
6431                 throw new ASN1TruncationException
6432                 (
6433                     length,
6434                     ((bytes.length - 1) - cursor),
6435                     "decode the value of a Canonical Encoding Rules (CER) encoded element"
6436                 );
6437 
6438             this.value = bytes[cursor .. cursor+length].dup;
6439             return (cursor + length);
6440         }
6441     }
6442 
6443     /**
6444         This differs from $(D this.value) in that
6445         $(D this.value) only returns the value octets, whereas
6446         $(D this.toBytes) returns the type tag, length tag / octets,
6447         and the value octets, all concatenated.
6448 
6449         This is the exact same as $(D this.opCast!(ubyte[])()).
6450 
6451         Returns: type tag, length tag, and value, all concatenated as a $(D ubyte) array.
6452     */
6453     public @property @system nothrow
6454     ubyte[] toBytes() const
6455     {
6456         ubyte[] tagBytes = [ 0x00u ];
6457         tagBytes[0] |= cast(ubyte) this.tagClass;
6458         tagBytes[0] |= cast(ubyte) this.construction;
6459 
6460         if (this.tagNumber < 31u)
6461         {
6462             tagBytes[0] |= cast(ubyte) this.tagNumber;
6463         }
6464         else
6465         {
6466             /*
6467                 Per section 8.1.2.4 of X.690:
6468                 The last five bits of the first byte being set indicate that
6469                 the tag number is encoded in base-128 on the subsequent octets,
6470                 using the first bit of each subsequent octet to indicate if the
6471                 encoding continues on the next octet, just like how the
6472                 individual numbers of OBJECT IDENTIFIER and RELATIVE OBJECT
6473                 IDENTIFIER are encoded.
6474             */
6475             tagBytes[0] |= cast(ubyte) 0b00011111u;
6476             size_t number = this.tagNumber; // We do not want to modify by reference.
6477             ubyte[] encodedNumber;
6478             while (number != 0u)
6479             {
6480                 ubyte[] numberbytes;
6481                 numberbytes.length = size_t.sizeof+1;
6482                 *cast(size_t *) numberbytes.ptr = number;
6483                 if ((numberbytes[0] & 0x80u) == 0u) numberbytes[0] |= 0x80u;
6484                 encodedNumber = numberbytes[0] ~ encodedNumber;
6485                 number >>= 7u;
6486             }
6487             tagBytes ~= encodedNumber;
6488             tagBytes[$-1] &= 0x7Fu; // Set first bit of last byte to zero.
6489         }
6490 
6491         ubyte[] lengthOctets = [ 0x00u ];
6492         switch (this.construction)
6493         {
6494             case (ASN1Construction.primitive):
6495             {
6496                 if (this.length < 127u)
6497                 {
6498                     lengthOctets = [ cast(ubyte) this.length ];
6499                 }
6500                 else
6501                 {
6502                     size_t length = cast(size_t) this.value.length;
6503                     ubyte[] lengthNumberOctets = cast(ubyte[]) *cast(ubyte[size_t.sizeof] *) &length;
6504                     version (LittleEndian) reverse(lengthNumberOctets);
6505                     size_t startOfNonPadding = 0u;
6506                     for (size_t i = 0u; i < size_t.sizeof; i++)
6507                     {
6508                         if (lengthNumberOctets[i] != 0x00u) break;
6509                         startOfNonPadding++;
6510                     }
6511                     lengthNumberOctets = lengthNumberOctets[startOfNonPadding .. $];
6512                     lengthOctets = [ cast(ubyte) (0x80u + lengthNumberOctets.length) ];
6513                     lengthOctets ~= lengthNumberOctets;
6514                 }
6515                 break;
6516             }
6517             case (ASN1Construction.constructed):
6518             {
6519                 lengthOctets = [ 0x80u ];
6520                 break;
6521             }
6522             default:
6523             {
6524                 assert(0, "Invalid ASN1Construction encountered!");
6525             }
6526         }
6527         return (
6528             tagBytes ~
6529             lengthOctets ~
6530             this.value ~
6531             (this.construction == ASN1Construction.constructed ? cast(ubyte[]) [ 0x00u, 0x00u ] : cast(ubyte[]) [])
6532         );
6533     }
6534 
6535     /**
6536         This differs from $(D this.value) in that
6537         $(D this.value) only returns the value octets, whereas
6538         $(D this.toBytes) returns the type tag, length tag / octets,
6539         and the value octets, all concatenated.
6540 
6541         This is the exact same as $(D this.toBytes()).
6542 
6543         Returns: type tag, length tag, and value, all concatenated as a $(D ubyte) array.
6544     */
6545     public @system nothrow
6546     ubyte[] opCast(T = ubyte[])()
6547     {
6548         return this.toBytes();
6549     }
6550 
6551 }
6552 
6553 // Tests of all types using definite-short encoding.
6554 @system
6555 unittest
6556 {
6557     // Test data
6558     immutable ubyte[] dataEndOfContent = [ 0x00u, 0x00u ];
6559     immutable ubyte[] dataBoolean = [ 0x01u, 0x01u, 0xFFu ];
6560     immutable ubyte[] dataInteger = [ 0x02u, 0x01u, 0x1Bu ];
6561     immutable ubyte[] dataBitString = [ 0x03u, 0x03u, 0x07u, 0xF0u, 0x80u ];
6562     immutable ubyte[] dataOctetString = [ 0x04u, 0x04u, 0xFF, 0x00u, 0x88u, 0x14u ];
6563     immutable ubyte[] dataNull = [ 0x05u, 0x00u ];
6564     immutable ubyte[] dataOID = [ 0x06u, 0x04u, 0x2Bu, 0x06u, 0x04u, 0x01u ];
6565     immutable ubyte[] dataOD = [ 0x07u, 0x05u, 'H', 'N', 'E', 'L', 'O' ];
6566     immutable ubyte[] dataExternal = [
6567         0x28u, 0x80u, 0x06u, 0x03u, 0x29u, 0x05u, 0x07u, 0x82u,
6568         0x04u, 0x01u, 0x02u, 0x03u, 0x04u, 0x00u, 0x00u ];
6569     immutable ubyte[] dataReal = [ 0x09u, 0x03u, 0x80u, 0xFBu, 0x05u ]; // 0.15625 (From StackOverflow question)
6570     immutable ubyte[] dataEnum = [ 0x0Au, 0x01u, 0x3Fu ];
6571     immutable ubyte[] dataEmbeddedPDV = [
6572         0x2Bu, 0x80u, 0x80u, 0x02u, 0x85u, 0x00u, 0x82u, 0x04u,
6573         0x01u, 0x02u, 0x03u, 0x04u, 0x00u, 0x00u ];
6574     immutable ubyte[] dataUTF8 = [ 0x0Cu, 0x05u, 'H', 'E', 'N', 'L', 'O' ];
6575     immutable ubyte[] dataROID = [ 0x0Du, 0x03u, 0x06u, 0x04u, 0x01u ];
6576     // sequence
6577     // set
6578     immutable ubyte[] dataNumeric = [ 0x12u, 0x07u, '8', '6', '7', '5', '3', '0', '9' ];
6579     immutable ubyte[] dataPrintable = [ 0x13u, 0x06u, '8', '6', ' ', 'b', 'f', '8' ];
6580     immutable ubyte[] dataTeletex = [ 0x14u, 0x06u, 0xFFu, 0x05u, 0x04u, 0x03u, 0x02u, 0x01u ];
6581     immutable ubyte[] dataVideotex = [ 0x15u, 0x06u, 0xFFu, 0x05u, 0x04u, 0x03u, 0x02u, 0x01u ];
6582     immutable ubyte[] dataIA5 = [ 0x16u, 0x08u, 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S' ];
6583     immutable ubyte[] dataUTC = [ 0x17u, 0x0Du, '1', '7', '0', '8', '3', '1', '1', '3', '4', '5', '0', '0', 'Z' ];
6584     immutable ubyte[] dataGT = [ 0x18u, 0x0Fu, '2', '0', '1', '7', '0', '8', '3', '1', '1', '3', '4', '5', '0', '0', 'Z' ];
6585     immutable ubyte[] dataGraphic = [ 0x19u, 0x0Bu, 'P', 'o', 'w', 'e', 'r', 'T', 'h', 'i', 'r', 's', 't' ];
6586     immutable ubyte[] dataVisible = [ 0x1Au, 0x0Bu, 'P', 'o', 'w', 'e', 'r', 'T', 'h', 'i', 'r', 's', 't' ];
6587     immutable ubyte[] dataGeneral = [ 0x1Bu, 0x0Bu, 'P', 'o', 'w', 'e', 'r', 'T', 'h', 'i', 'r', 's', 't' ];
6588     immutable ubyte[] dataUniversal = [
6589         0x1Cu, 0x10u,
6590         0x00u, 0x00u, 0x00u, 0x61u,
6591         0x00u, 0x00u, 0x00u, 0x62u,
6592         0x00u, 0x00u, 0x00u, 0x63u,
6593         0x00u, 0x00u, 0x00u, 0x64u
6594     ]; // Big-endian "abcd"
6595     immutable ubyte[] dataCharacter = [
6596         0x3Du, 0x80u, 0x80u, 0x06u, 0x81u, 0x04u, 0x29u, 0x06u,
6597         0x04u, 0x01u, 0x82u, 0x05u, 0x48u, 0x45u, 0x4Eu, 0x4Cu,
6598         0x4Fu, 0x00u, 0x00u ];
6599     immutable ubyte[] dataBMP = [ 0x1Eu, 0x08u, 0x00u, 0x61u, 0x00u, 0x62u, 0x00u, 0x63u, 0x00u, 0x64u ]; // Big-endian "abcd"
6600 
6601     // Combine it all
6602     ubyte[] data =
6603         (dataEndOfContent ~
6604         dataBoolean ~
6605         dataInteger ~
6606         dataBitString ~
6607         dataOctetString ~
6608         dataNull ~
6609         dataOID ~
6610         dataOD ~
6611         dataExternal ~
6612         dataReal ~
6613         dataEnum ~
6614         dataEmbeddedPDV ~
6615         dataUTF8 ~
6616         dataROID ~
6617         dataNumeric ~
6618         dataPrintable ~
6619         dataTeletex ~
6620         dataVideotex ~
6621         dataIA5 ~
6622         dataUTC ~
6623         dataGT ~
6624         dataGraphic ~
6625         dataVisible ~
6626         dataGeneral ~
6627         dataUniversal ~
6628         dataCharacter ~
6629         dataBMP).dup;
6630 
6631     CERElement[] result;
6632 
6633     size_t i = 0u;
6634     while (i < data.length)
6635         result ~= new CERElement(i, data);
6636 
6637     // Pre-processing
6638     External x = result[8].external;
6639     EmbeddedPDV m = result[11].embeddedPresentationDataValue;
6640     CharacterString c = result[25].characterString;
6641 
6642     // Ensure accessors decode the data correctly.
6643     assert(result[1].boolean == true);
6644     assert(result[2].integer!long == 27L);
6645     assert(result[3].bitString == [ true, true, true, true, false, false, false, false, true ]);
6646     assert(result[4].octetString == [ 0xFFu, 0x00u, 0x88u, 0x14u ]);
6647     assert(result[6].objectIdentifier == new OID(OIDNode(0x01u), OIDNode(0x03u), OIDNode(0x06u), OIDNode(0x04u), OIDNode(0x01u)));
6648     assert(result[7].objectDescriptor == result[7].objectDescriptor);
6649     assert((x.identification.syntax == new OID(1u, 1u, 5u, 7u)) && (x.dataValue == [ 0x01u, 0x02u, 0x03u, 0x04u ]));
6650     assert(result[9].realNumber!float == 0.15625);
6651     assert(result[9].realNumber!double == 0.15625);
6652     assert(result[10].enumerated!long == 63L);
6653     assert((m.identification.fixed == true) && (m.dataValue == [ 0x01u, 0x02u, 0x03u, 0x04u ]));
6654     assert(result[12].utf8String == "HENLO");
6655     assert(result[13].relativeObjectIdentifier == [ OIDNode(6), OIDNode(4), OIDNode(1) ]);
6656     assert(result[14].numericString == "8675309");
6657     assert(result[15].printableString ==  "86 bf8");
6658     assert(result[16].teletexString == [ 0xFFu, 0x05u, 0x04u, 0x03u, 0x02u, 0x01u ]);
6659     assert(result[17].videotexString == [ 0xFFu, 0x05u, 0x04u, 0x03u, 0x02u, 0x01u ]);
6660     assert(result[18].ia5String == "BORTHERS");
6661     assert(result[19].utcTime == DateTime(2017, 8, 31, 13, 45));
6662     assert(result[20].generalizedTime == DateTime(2017, 8, 31, 13, 45));
6663     assert(result[21].graphicString == "PowerThirst");
6664     assert(result[22].visibleString == "PowerThirst");
6665     assert(result[23].generalString == "PowerThirst");
6666     assert(result[24].universalString == "abcd"d);
6667     assert((c.identification.syntax == new OID(1u, 1u, 6u, 4u, 1u)) && (c.stringValue == "HENLO"w));
6668 
6669     result = [];
6670     while (data.length > 0)
6671         result ~= new CERElement(data);
6672 
6673     // Pre-processing
6674     x = result[8].external;
6675     m = result[11].embeddedPresentationDataValue;
6676     c = result[25].characterString;
6677 
6678     // Ensure accessors decode the data correctly.
6679     assert(result[1].boolean == true);
6680     assert(result[2].integer!long == 27L);
6681     assert(result[3].bitString == [ true, true, true, true, false, false, false, false, true ]);
6682     assert(result[4].octetString == [ 0xFFu, 0x00u, 0x88u, 0x14u ]);
6683     assert(result[6].objectIdentifier == new OID(OIDNode(0x01u), OIDNode(0x03u), OIDNode(0x06u), OIDNode(0x04u), OIDNode(0x01u)));
6684     assert(result[7].objectDescriptor == result[7].objectDescriptor);
6685     assert((x.identification.syntax == new OID(1u, 1u, 5u, 7u)) && (x.dataValue == [ 0x01u, 0x02u, 0x03u, 0x04u ]));
6686     assert(result[9].realNumber!float == 0.15625);
6687     assert(result[9].realNumber!double == 0.15625);
6688     assert(result[10].enumerated!long == 63L);
6689     assert((m.identification.fixed == true) && (m.dataValue == [ 0x01u, 0x02u, 0x03u, 0x04u ]));
6690     assert(result[12].utf8String == "HENLO");
6691     assert(result[13].relativeObjectIdentifier == [ OIDNode(6), OIDNode(4), OIDNode(1) ]);
6692     assert(result[14].numericString == "8675309");
6693     assert(result[15].printableString ==  "86 bf8");
6694     assert(result[16].teletexString == [ 0xFFu, 0x05u, 0x04u, 0x03u, 0x02u, 0x01u ]);
6695     assert(result[17].videotexString == [ 0xFFu, 0x05u, 0x04u, 0x03u, 0x02u, 0x01u ]);
6696     assert(result[18].ia5String == "BORTHERS");
6697     assert(result[19].utcTime == DateTime(2017, 8, 31, 13, 45));
6698     assert(result[20].generalizedTime == DateTime(2017, 8, 31, 13, 45));
6699     assert(result[21].graphicString == "PowerThirst");
6700     assert(result[22].visibleString == "PowerThirst");
6701     assert(result[23].generalString == "PowerThirst");
6702     assert(result[24].universalString == "abcd"d);
6703     assert((c.identification.syntax == new OID(1u, 1u, 6u, 4u, 1u)) && (c.stringValue == "HENLO"w));
6704 }
6705 
6706 // Test of definite-long encoding
6707 @system
6708 unittest
6709 {
6710     ubyte[] data = [ // 192 characters of boomer-posting
6711         0x0Cu, 0x81u, 0xC0u,
6712         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
6713         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
6714         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
6715         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
6716         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
6717         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
6718         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
6719         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
6720         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
6721         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
6722         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
6723         'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n'
6724     ];
6725 
6726     data = (data ~ data ~ data); // Triple the data, to catch any bugs that arise with subsequent values.
6727 
6728     CERElement[] result;
6729     size_t i = 0u;
6730     while (i < data.length)
6731         result ~= new CERElement(i, data);
6732 
6733     assert(result.length == 3);
6734     assert(result[0].utf8String[0 .. 5] == "AMREN");
6735     assert(result[1].utf8String[6 .. 14] == "BORTHERS");
6736     assert(result[2].utf8String[$-2] == '!');
6737 
6738     result = [];
6739     while (data.length > 0)
6740         result ~= new CERElement(data);
6741 
6742     assert(result.length == 3);
6743     assert(result[0].utf8String[0 .. 5] == "AMREN");
6744     assert(result[1].utf8String[6 .. 14] == "BORTHERS");
6745     assert(result[2].utf8String[$-2] == '!');
6746 }
6747 
6748 // Test of indefinite-length encoding
6749 // @system
6750 // unittest
6751 // {
6752 //     ubyte[] data = [ // 192 characters of boomer-posting
6753 //         0x2Cu, 0x80u,
6754 //         0x0Cu, 0x10u, 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
6755 //         0x0Cu, 0x10u, 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
6756 //         0x0Cu, 0x10u, 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
6757 //         0x0Cu, 0x10u, 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
6758 //         0x0Cu, 0x10u, 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
6759 //         0x0Cu, 0x10u, 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
6760 //         0x0Cu, 0x10u, 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
6761 //         0x0Cu, 0x10u, 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
6762 //         0x0Cu, 0x10u, 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
6763 //         0x0Cu, 0x10u, 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
6764 //         0x0Cu, 0x10u, 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
6765 //         0x0Cu, 0x10u, 'A', 'M', 'R', 'E', 'N', ' ', 'B', 'O', 'R', 'T', 'H', 'E', 'R', 'S', '!', '\n',
6766 //         0x00u, 0x00u
6767 //     ];
6768 
6769 //     data = (data ~ data ~ data); // Triple the data, to catch any bugs that arise with subsequent values.
6770 
6771 //     CERElement[] result;
6772 //     size_t i = 0u;
6773 //     while (i < data.length)
6774 //         result ~= new CERElement(i, data);
6775 
6776 //     assert(result.length == 3);
6777 //     assert(result[0].length == 216u);
6778 //     assert(result[0].utf8String[2 .. 7] == "AMREN");
6779 //     assert(result[1].utf8String[8 .. 16] == "BORTHERS");
6780 //     assert(result[2].utf8String[$-2] == '!');
6781 
6782 //     result = [];
6783 //     while (data.length > 0)
6784 //         result ~= new CERElement(data);
6785 
6786 //     assert(result.length == 3);
6787 //     assert(result[0].utf8String[2 .. 7] == "AMREN");
6788 //     assert(result[1].utf8String[8 .. 16] == "BORTHERS");
6789 //     assert(result[2].utf8String[$-2] == '!');
6790 // }
6791 
6792 // Test deeply (but not too deeply) nested indefinite-length elements
6793 @system
6794 unittest
6795 {
6796     ubyte[] data = [
6797         0x2Cu, 0x80u,
6798             0x2Cu, 0x80u,
6799                 0x0Cu, 0x02u, 'H', 'I',
6800                 0x00u, 0x00u,
6801             0x00u, 0x00u ];
6802     assertNotThrown!ASN1Exception(new CERElement(data));
6803 }
6804 
6805 // Try to induce infinite recursion for an indefinite-length element
6806 @system
6807 unittest
6808 {
6809     ubyte[] invalid = [];
6810     for (size_t i = 0u; i < CERElement.nestingRecursionLimit+1; i++)
6811     {
6812         invalid ~= [ 0x2Cu, 0x80u ];
6813     }
6814     assertThrown!ASN1RecursionException(new CERElement(invalid));
6815 }
6816 
6817 // Try to crash everything with a short indefinite-length element
6818 @system
6819 unittest
6820 {
6821     ubyte[] invalid;
6822     invalid = [ 0x2Cu, 0x80u, 0x01u ];
6823     assertThrown!ASN1Exception(new CERElement(invalid));
6824     invalid = [ 0x2Cu, 0x80u, 0x00u ];
6825     assertThrown!ASN1Exception(new CERElement(invalid));
6826 }
6827 
6828 // Test an embedded value with two adjacent null octets
6829 @system
6830 unittest
6831 {
6832     ubyte[] data = [
6833         0x24u, 0x80u,
6834             0x04u, 0x04u, 0x00u, 0x00u, 0x00u, 0x00u, // These should not indicate the end.
6835             0x00u, 0x00u ]; // These should.
6836     assert((new CERElement(data)).value == [ 0x04u, 0x04u, 0x00u, 0x00u, 0x00u, 0x00u ]);
6837 }
6838 
6839 /*
6840     Test of OCTET STRING encoding on 500 bytes (+4 for type and length tags)
6841 
6842     The number 500 was specifically selected for this test because CER
6843     uses 1000 as the threshold after which OCTET STRING must be represented
6844     as a constructed sequence of definite-length-encoded OCTET STRINGS,
6845     followed by an EOC element, but 500 is also big enough to require
6846     the length to be encoded on two octets in definite-long form.
6847 */
6848 @system
6849 unittest
6850 {
6851     ubyte[] test;
6852     test.length = 504u;
6853     test[0] = cast(ubyte) ASN1UniversalType.octetString;
6854     test[1] = 0b10000010u; // Length is encoded on next two octets
6855     test[2] = 0x01u; // Most significant byte of length
6856     test[3] = 0xF4u; // Least significant byte of length
6857     test[4] = 0x0Au; // First byte of the encoded value
6858     test[5 .. $-1] = 0x0Bu;
6859     test[$-1] = 0x0Cu;
6860 
6861     CERElement el;
6862     assertNotThrown!Exception(el = new CERElement(test));
6863     ubyte[] output = el.octetString;
6864     assert(output.length == 500u);
6865     assert(output[0] == 0x0Au);
6866     assert(output[1] == 0x0Bu);
6867     assert(output[$-2] == 0x0Bu);
6868     assert(output[$-1] == 0x0Cu);
6869 }
6870 
6871 // Assert all single-byte encodings do not decode successfully.
6872 @system
6873 unittest
6874 {
6875     for (ubyte i = 0x00u; i < ubyte.max; i++)
6876     {
6877         ubyte[] data = [i];
6878         assertThrown!Exception(new CERElement(data));
6879     }
6880 
6881     size_t index;
6882     for (ubyte i = 0x00u; i < ubyte.max; i++)
6883     {
6884         immutable ubyte[] data = [i];
6885         assertThrown!Exception(new CERElement(index, data));
6886     }
6887 }
6888 
6889 // Test long-form tag number (when # >= 31) with leading zero bytes (0x80)
6890 @system
6891 unittest
6892 {
6893     ubyte[] invalid;
6894     invalid = [ 0b10011111u, 0b10000000u ];
6895     assertThrown!ASN1TagException(new CERElement(invalid));
6896 
6897     invalid = [ 0b10011111u, 0b10000000u, 0b10000000u ];
6898     assertThrown!ASN1TagException(new CERElement(invalid));
6899 
6900     invalid = [ 0b10011111u, 0b10000000u, 0b10000111u ];
6901     assertThrown!ASN1TagException(new CERElement(invalid));
6902 }
6903 
6904 // Test long-form tag numbers do not encode with leading zeroes
6905 @system
6906 unittest
6907 {
6908     ubyte[] invalid;
6909     CERElement element = new CERElement();
6910     element.tagNumber = 73u;
6911     assert((element.toBytes)[1] != 0x80u);
6912 }
6913 
6914 // Test that a value that is a byte too short does not throw a RangeError.
6915 @system
6916 unittest
6917 {
6918     ubyte[] test = [ 0x00u, 0x03u, 0x00u, 0x00u ];
6919     assertThrown!ASN1TruncationException(new CERElement(test));
6920 }
6921 
6922 // Test that a misleading definite-long length byte does not throw a RangeError.
6923 @system
6924 unittest
6925 {
6926     ubyte[] invalid = [ 0b00000000u, 0b10000001u ];
6927     assertThrown!ASN1TruncationException(new CERElement(invalid));
6928 }
6929 
6930 // Test that leading zeroes in definite long length encodings throw exceptions
6931 @system
6932 unittest
6933 {
6934     ubyte[] invalid = [ 0b00000000u, 0b10000010u, 0b00000000u, 0b00000001u ];
6935     assertThrown!ASN1LengthException(new CERElement(invalid));
6936 }
6937 
6938 // Test that a very large value does not cause a segfault or something
6939 @system
6940 unittest
6941 {
6942     size_t biggest = cast(size_t) int.max;
6943     ubyte[] big = [ 0x00u, 0x80u ];
6944     big[1] += cast(ubyte) int.sizeof;
6945     big ~= cast(ubyte[]) *cast(ubyte[int.sizeof] *) &biggest;
6946     big ~= [ 0x01u, 0x02u, 0x03u ]; // Plus some arbitrary data.
6947     assertThrown!ASN1TruncationException(new CERElement(big));
6948 }
6949 
6950 // Test that the largest possible item does not cause a segfault or something
6951 @system
6952 unittest
6953 {
6954     size_t biggest = size_t.max;
6955     ubyte[] big = [ 0x00u, 0x80u ];
6956     big[1] += cast(ubyte) size_t.sizeof;
6957     big ~= cast(ubyte[]) *cast(ubyte[size_t.sizeof] *) &biggest;
6958     big ~= [ 0x01u, 0x02u, 0x03u ]; // Plus some arbitrary data.
6959     assertThrown!ASN1LengthException(new CERElement(big));
6960 }
6961 
6962 // Test that a short indefinite-length element does not throw a RangeError
6963 @system
6964 unittest
6965 {
6966     ubyte[] naughty = [ 0x3F, 0x00u, 0x80, 0x00u ];
6967     size_t bytesRead = 0u;
6968     assertThrown!ASN1TruncationException(new CERElement(bytesRead, naughty));
6969 }
6970 
6971 // Test that a short indefinite-length element does not throw a RangeError
6972 @system
6973 unittest
6974 {
6975     ubyte[] naughty = [ 0x3F, 0x00u, 0x80, 0x00u, 0x00u ];
6976     size_t bytesRead = 0u;
6977     assertNotThrown!ASN1TruncationException(new CERElement(bytesRead, naughty));
6978 }
6979 
6980 // Test that a valueless long-form definite-length element does not throw a RangeError
6981 @system
6982 unittest
6983 {
6984     ubyte[] naughty = [ 0x00u, 0x82, 0x00u, 0x01u ];
6985     size_t bytesRead = 0u;
6986     assertThrown!ASN1LengthException(new CERElement(bytesRead, naughty));
6987 }
6988 
6989 // PyASN1 Comparison Testing
6990 @system
6991 unittest
6992 {
6993     CERElement e = new CERElement();
6994     e.boolean = false;
6995     assert(e.value == [ 0x00u ]);
6996     e.integer = 5;
6997     assert(e.value == [ 0x05u ]);
6998     e.bitString = [
6999         true, false, true, true, false, false, true, true,
7000         true, false, false, false ];
7001     assert(e.value == [ 0x04u, 0xB3u, 0x80u ]);
7002     e.bitString = [
7003         true, false, true, true, false, true, false, false ];
7004     assert(e.value == [ 0x00u, 0xB4u ]);
7005     e.objectIdentifier = new OID(1, 2, 0, 256, 79999, 7);
7006     assert(e.value == [
7007         0x2Au, 0x00u, 0x82u, 0x00u, 0x84u, 0xF0u, 0x7Fu, 0x07u ]);
7008     e.enumerated = 5;
7009     assert(e.value == [ 0x05u ]);
7010     e.enumerated = 90000;
7011     assert(e.value == [ 0x01u, 0x5Fu, 0x90u ]);
7012 }
7013 
7014 // Test that all data types that cannot have value length = 0 throw exceptions.
7015 // See CVE-2015-5726.
7016 @system
7017 unittest
7018 {
7019     CERElement el = new CERElement();
7020     assertThrown!ASN1Exception(el.boolean);
7021     assertThrown!ASN1Exception(el.integer!byte);
7022     assertThrown!ASN1Exception(el.integer!short);
7023     assertThrown!ASN1Exception(el.integer!int);
7024     assertThrown!ASN1Exception(el.integer!long);
7025     assertThrown!ASN1Exception(el.bitString);
7026     assertThrown!ASN1Exception(el.objectIdentifier);
7027     assertThrown!ASN1Exception(el.enumerated!byte);
7028     assertThrown!ASN1Exception(el.enumerated!short);
7029     assertThrown!ASN1Exception(el.enumerated!int);
7030     assertThrown!ASN1Exception(el.enumerated!long);
7031     assertThrown!ASN1Exception(el.generalizedTime);
7032     assertThrown!ASN1Exception(el.utcTime);
7033 }