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