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