1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Collections.Generic;
6 using System.Diagnostics;
7 using System.Text;
8 
9 namespace System.Security.Cryptography.Asn1
10 {
11     // ITU-T-REC.X.680-201508 sec 4.
12     internal enum AsnEncodingRules
13     {
14         BER,
15         CER,
16         DER,
17     }
18 
19     // Uses a masked overlay of the tag class encoding.
20     // T-REC-X.690-201508 sec 8.1.2.2
21     internal enum TagClass : byte
22     {
23         Universal = 0,
24         Application = 0b0100_0000,
25         ContextSpecific = 0b1000_0000,
26         Private = 0b1100_0000,
27     }
28 
29     // ITU-T-REC.X.680-201508 sec 8.6
30     internal enum UniversalTagNumber
31     {
32         EndOfContents = 0,
33         Boolean = 1,
34         Integer = 2,
35         BitString = 3,
36         OctetString = 4,
37         Null = 5,
38         ObjectIdentifier = 6,
39         ObjectDescriptor = 7,
40         External = 8,
41         InstanceOf = External,
42         Real = 9,
43         Enumerated = 10,
44         Embedded = 11,
45         UTF8String = 12,
46         RelativeObjectIdentifier = 13,
47         Time = 14,
48         // 15 is reserved
49         Sequence = 16,
50         SequenceOf = Sequence,
51         Set = 17,
52         SetOf = Set,
53         NumericString = 18,
54         PrintableString = 19,
55         TeletexString = 20,
56         T61String = TeletexString,
57         VideotexString = 21,
58         IA5String = 22,
59         UtcTime = 23,
60         GeneralizedTime = 24,
61         GraphicString = 25,
62         VisibleString = 26,
63         ISO646String = VisibleString,
64         GeneralString = 27,
65         UniversalString = 28,
66         UnrestrictedCharacterString = 29,
67         BMPString = 30,
68         Date = 31,
69         TimeOfDay = 32,
70         DateTime = 33,
71         Duration = 34,
72         ObjectIdentifierIRI = 35,
73         RelativeObjectIdentifierIRI = 36,
74     }
75 
76     // Represents a BER-family encoded tag.
77     // T-REC-X.690-201508 sec 8.1.2
78     internal struct Asn1Tag : IEquatable<Asn1Tag>
79     {
80         private const byte ClassMask = 0b1100_0000;
81         private const byte ConstructedMask = 0b0010_0000;
82         private const byte ControlMask = ClassMask | ConstructedMask;
83         private const byte TagNumberMask = 0b0001_1111;
84 
85         internal static readonly Asn1Tag EndOfContents = new Asn1Tag(0, (int)UniversalTagNumber.EndOfContents);
86         internal static readonly Asn1Tag Boolean = new Asn1Tag(0, (int)UniversalTagNumber.Boolean);
87         internal static readonly Asn1Tag Integer = new Asn1Tag(0, (int)UniversalTagNumber.Integer);
88         internal static readonly Asn1Tag PrimitiveBitString = new Asn1Tag(0, (int)UniversalTagNumber.BitString);
89         internal static readonly Asn1Tag ConstructedBitString = new Asn1Tag(ConstructedMask, (int)UniversalTagNumber.BitString);
90         internal static readonly Asn1Tag PrimitiveOctetString = new Asn1Tag(0, (int)UniversalTagNumber.OctetString);
91         internal static readonly Asn1Tag ConstructedOctetString = new Asn1Tag(ConstructedMask, (int)UniversalTagNumber.OctetString);
92         internal static readonly Asn1Tag Null = new Asn1Tag(0, (int)UniversalTagNumber.Null);
93         internal static readonly Asn1Tag ObjectIdentifier = new Asn1Tag(0, (int)UniversalTagNumber.ObjectIdentifier);
94         internal static readonly Asn1Tag Enumerated = new Asn1Tag(0, (int)UniversalTagNumber.Enumerated);
95         internal static readonly Asn1Tag Sequence = new Asn1Tag(ConstructedMask, (int)UniversalTagNumber.Sequence);
96         internal static readonly Asn1Tag SetOf = new Asn1Tag(ConstructedMask, (int)UniversalTagNumber.SetOf);
97         internal static readonly Asn1Tag UtcTime = new Asn1Tag(0, (int)UniversalTagNumber.UtcTime);
98         internal static readonly Asn1Tag GeneralizedTime = new Asn1Tag(0, (int)UniversalTagNumber.GeneralizedTime);
99 
100         private readonly byte _controlFlags;
101         private readonly int _tagValue;
102 
103         public TagClass TagClass => (TagClass)(_controlFlags & ClassMask);
104         public bool IsConstructed => (_controlFlags & ConstructedMask) != 0;
105         public int TagValue => _tagValue;
106 
Asn1TagSystem.Security.Cryptography.Asn1.Asn1Tag107         private Asn1Tag(byte controlFlags, int tagValue)
108         {
109             _controlFlags = (byte)(controlFlags & ControlMask);
110             _tagValue = tagValue;
111         }
112 
Asn1TagSystem.Security.Cryptography.Asn1.Asn1Tag113         public Asn1Tag(UniversalTagNumber universalTagNumber, bool isConstructed = false)
114             : this(isConstructed ? ConstructedMask : (byte)0, (int)universalTagNumber)
115         {
116             // T-REC-X.680-201508 sec 8.6 (Table 1)
117             const UniversalTagNumber ReservedIndex = (UniversalTagNumber)15;
118 
119             if (universalTagNumber < UniversalTagNumber.EndOfContents ||
120                 universalTagNumber > UniversalTagNumber.RelativeObjectIdentifierIRI ||
121                 universalTagNumber == ReservedIndex)
122             {
123                 throw new ArgumentOutOfRangeException(nameof(universalTagNumber));
124             }
125         }
126 
Asn1TagSystem.Security.Cryptography.Asn1.Asn1Tag127         public Asn1Tag(TagClass tagClass, int tagValue, bool isConstructed = false)
128             : this((byte)((byte)tagClass | (isConstructed ? ConstructedMask : 0)), tagValue)
129         {
130             if (tagClass < TagClass.Universal || tagClass > TagClass.Private)
131             {
132                 throw new ArgumentOutOfRangeException(nameof(tagClass));
133             }
134 
135             if (tagValue < 0)
136             {
137                 throw new ArgumentOutOfRangeException(nameof(tagValue));
138             }
139         }
140 
AsConstructedSystem.Security.Cryptography.Asn1.Asn1Tag141         public Asn1Tag AsConstructed()
142         {
143             return new Asn1Tag((byte)(_controlFlags | ConstructedMask), _tagValue);
144         }
145 
AsPrimitiveSystem.Security.Cryptography.Asn1.Asn1Tag146         public Asn1Tag AsPrimitive()
147         {
148             return new Asn1Tag((byte)(_controlFlags & ~ConstructedMask), _tagValue);
149         }
150 
TryParseSystem.Security.Cryptography.Asn1.Asn1Tag151         public static bool TryParse(ReadOnlySpan<byte> source, out Asn1Tag tag, out int bytesRead)
152         {
153             tag = default(Asn1Tag);
154             bytesRead = 0;
155 
156             if (source.IsEmpty)
157             {
158                 return false;
159             }
160 
161             byte first = source[bytesRead];
162             bytesRead++;
163             uint tagValue = (uint)(first & TagNumberMask);
164 
165             if (tagValue == TagNumberMask)
166             {
167                 // Multi-byte encoding
168                 // T-REC-X.690-201508 sec 8.1.2.4
169                 const byte ContinuationFlag = 0x80;
170                 const byte ValueMask = ContinuationFlag - 1;
171 
172                 tagValue = 0;
173                 byte current;
174 
175                 do
176                 {
177                     if (source.Length <= bytesRead)
178                     {
179                         bytesRead = 0;
180                         return false;
181                     }
182 
183                     current = source[bytesRead];
184                     byte currentValue = (byte)(current & ValueMask);
185                     bytesRead++;
186 
187                     // If TooBigToShift is shifted left 7, the content bit shifts out.
188                     // So any value greater than or equal to this cannot be shifted without loss.
189                     const int TooBigToShift = 0b00000010_00000000_00000000_00000000;
190 
191                     if (tagValue >= TooBigToShift)
192                     {
193                         bytesRead = 0;
194                         return false;
195                     }
196 
197                     tagValue <<= 7;
198                     tagValue |= currentValue;
199 
200                     // The first byte cannot have the value 0 (T-REC-X.690-201508 sec 8.1.2.4.2.c)
201                     if (tagValue == 0)
202                     {
203                         bytesRead = 0;
204                         return false;
205                     }
206                 }
207                 while ((current & ContinuationFlag) == ContinuationFlag);
208 
209                 // This encoding is only valid for tag values greater than 30.
210                 // (T-REC-X.690-201508 sec 8.1.2.3, 8.1.2.4)
211                 if (tagValue <= 30)
212                 {
213                     bytesRead = 0;
214                     return false;
215                 }
216 
217                 // There's not really any ambiguity, but prevent negative numbers from showing up.
218                 if (tagValue > int.MaxValue)
219                 {
220                     bytesRead = 0;
221                     return false;
222                 }
223             }
224 
225             Debug.Assert(bytesRead > 0);
226             tag = new Asn1Tag(first, (int)tagValue);
227             return true;
228         }
229 
CalculateEncodedSizeSystem.Security.Cryptography.Asn1.Asn1Tag230         public int CalculateEncodedSize()
231         {
232             const int SevenBits = 0b0111_1111;
233             const int FourteenBits = 0b0011_1111_1111_1111;
234             const int TwentyOneBits = 0b0001_1111_1111_1111_1111_1111;
235             const int TwentyEightBits = 0b0000_1111_1111_1111_1111_1111_1111_1111;
236 
237             if (TagValue < TagNumberMask)
238                 return 1;
239             if (TagValue <= SevenBits)
240                 return 2;
241             if (TagValue <= FourteenBits)
242                 return 3;
243             if (TagValue <= TwentyOneBits)
244                 return 4;
245             if (TagValue <= TwentyEightBits)
246                 return 5;
247 
248             return 6;
249         }
250 
TryWriteSystem.Security.Cryptography.Asn1.Asn1Tag251         public bool TryWrite(Span<byte> destination, out int bytesWritten)
252         {
253             int spaceRequired = CalculateEncodedSize();
254 
255             if (destination.Length < spaceRequired)
256             {
257                 bytesWritten = 0;
258                 return false;
259             }
260 
261             if (spaceRequired == 1)
262             {
263                 byte value = (byte)(_controlFlags | TagValue);
264                 destination[0] = value;
265                 bytesWritten = 1;
266                 return true;
267             }
268 
269             byte firstByte = (byte)(_controlFlags | TagNumberMask);
270             destination[0] = firstByte;
271 
272             int remaining = TagValue;
273             int idx = spaceRequired - 1;
274 
275             while (remaining > 0)
276             {
277                 int segment = remaining & 0x7F;
278 
279                 // The last byte doesn't get the marker, which we write first.
280                 if (remaining != TagValue)
281                 {
282                     segment |= 0x80;
283                 }
284 
285                 Debug.Assert(segment <= byte.MaxValue);
286                 destination[idx] = (byte)segment;
287                 remaining >>= 7;
288                 idx--;
289             }
290 
291             Debug.Assert(idx == 0);
292             bytesWritten = spaceRequired;
293             return true;
294         }
295 
EqualsSystem.Security.Cryptography.Asn1.Asn1Tag296         public bool Equals(Asn1Tag other)
297         {
298             return _controlFlags == other._controlFlags && _tagValue == other._tagValue;
299         }
300 
EqualsSystem.Security.Cryptography.Asn1.Asn1Tag301         public override bool Equals(object obj)
302         {
303             if (ReferenceEquals(null, obj)) return false;
304             return obj is Asn1Tag && Equals((Asn1Tag)obj);
305         }
306 
GetHashCodeSystem.Security.Cryptography.Asn1.Asn1Tag307         public override int GetHashCode()
308         {
309             // Most TagValue values will be in the 0-30 range,
310             // the GetHashCode value only has collisions when TagValue is
311             // between 2^29 and uint.MaxValue
312             return (_controlFlags << 24) ^ _tagValue;
313         }
314 
operator ==System.Security.Cryptography.Asn1.Asn1Tag315         public static bool operator ==(Asn1Tag left, Asn1Tag right)
316         {
317             return left.Equals(right);
318         }
319 
operator !=System.Security.Cryptography.Asn1.Asn1Tag320         public static bool operator !=(Asn1Tag left, Asn1Tag right)
321         {
322             return !left.Equals(right);
323         }
324 
ToStringSystem.Security.Cryptography.Asn1.Asn1Tag325         public override string ToString()
326         {
327             const string ConstructedPrefix = "Constructed ";
328             string classAndValue;
329 
330             if (TagClass == TagClass.Universal)
331             {
332                 classAndValue = ((UniversalTagNumber)TagValue).ToString();
333             }
334             else
335             {
336                 classAndValue = TagClass + "-" + TagValue;
337             }
338 
339             if (IsConstructed)
340             {
341                 return ConstructedPrefix + classAndValue;
342             }
343 
344             return classAndValue;
345         }
346     }
347 
348     internal static class AsnCharacterStringEncodings
349     {
350         private static readonly Text.Encoding s_utf8Encoding = new UTF8Encoding(false, throwOnInvalidBytes: true);
351         private static readonly Text.Encoding s_bmpEncoding = new BMPEncoding();
352         private static readonly Text.Encoding s_ia5Encoding = new IA5Encoding();
353         private static readonly Text.Encoding s_visibleStringEncoding = new VisibleStringEncoding();
354         private static readonly Text.Encoding s_printableStringEncoding = new PrintableStringEncoding();
355 
GetEncoding(UniversalTagNumber encodingType)356         internal static Text.Encoding GetEncoding(UniversalTagNumber encodingType)
357         {
358             switch (encodingType)
359             {
360                 case UniversalTagNumber.UTF8String:
361                     return s_utf8Encoding;
362                 case UniversalTagNumber.PrintableString:
363                     return s_printableStringEncoding;
364                 case UniversalTagNumber.IA5String:
365                     return s_ia5Encoding;
366                 case UniversalTagNumber.VisibleString:
367                     return s_visibleStringEncoding;
368                 case UniversalTagNumber.BMPString:
369                     return s_bmpEncoding;
370                 default:
371                     throw new ArgumentOutOfRangeException(nameof(encodingType), encodingType, null);
372             }
373         }
374     }
375 
376     internal abstract class SpanBasedEncoding : Text.Encoding
377     {
SpanBasedEncoding()378         protected SpanBasedEncoding()
379             : base(0, EncoderFallback.ExceptionFallback, DecoderFallback.ExceptionFallback)
380         {
381         }
382 
GetBytes(ReadOnlySpan<char> chars, Span<byte> bytes, bool write)383         protected abstract int GetBytes(ReadOnlySpan<char> chars, Span<byte> bytes, bool write);
GetChars(ReadOnlySpan<byte> bytes, Span<char> chars, bool write)384         protected abstract int GetChars(ReadOnlySpan<byte> bytes, Span<char> chars, bool write);
385 
GetByteCount(char[] chars, int index, int count)386         public override int GetByteCount(char[] chars, int index, int count)
387         {
388             return GetByteCount(new ReadOnlySpan<char>(chars, index, count));
389         }
390 
GetByteCount(char* chars, int count)391         public override unsafe int GetByteCount(char* chars, int count)
392         {
393             return GetByteCount(new ReadOnlySpan<char>(chars, count));
394         }
395 
GetByteCount(string s)396         public override int GetByteCount(string s)
397         {
398             return GetByteCount(s.AsReadOnlySpan());
399         }
400 
401         public
402 #if netcoreapp
403             override
404 #endif
GetByteCount(ReadOnlySpan<char> chars)405         int GetByteCount(ReadOnlySpan<char> chars)
406         {
407             return GetBytes(chars, Span<byte>.Empty, write: false);
408         }
409 
GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex)410         public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex)
411         {
412             return GetBytes(
413                 new ReadOnlySpan<char>(chars, charIndex, charCount),
414                 new Span<byte>(bytes, byteIndex, bytes.Length - byteIndex),
415                 write: true);
416         }
417 
GetBytes(char* chars, int charCount, byte* bytes, int byteCount)418         public override unsafe int GetBytes(char* chars, int charCount, byte* bytes, int byteCount)
419         {
420             return GetBytes(
421                 new ReadOnlySpan<char>(chars, charCount),
422                 new Span<byte>(bytes, byteCount),
423                 write: true);
424         }
425 
GetCharCount(byte[] bytes, int index, int count)426         public override int GetCharCount(byte[] bytes, int index, int count)
427         {
428             return GetCharCount(new ReadOnlySpan<byte>(bytes, index, count));
429         }
430 
GetCharCount(byte* bytes, int count)431         public override unsafe int GetCharCount(byte* bytes, int count)
432         {
433             return GetCharCount(new ReadOnlySpan<byte>(bytes, count));
434         }
435 
436         public
437 #if netcoreapp
438             override
439 #endif
GetCharCount(ReadOnlySpan<byte> bytes)440         int GetCharCount(ReadOnlySpan<byte> bytes)
441         {
442             return GetChars(bytes, Span<char>.Empty, write: false);
443         }
444 
GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex)445         public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex)
446         {
447             return GetChars(
448                 new ReadOnlySpan<byte>(bytes, byteIndex, byteCount),
449                 new Span<char>(chars, charIndex, chars.Length - charIndex),
450                 write: true);
451         }
452 
GetChars(byte* bytes, int byteCount, char* chars, int charCount)453         public override unsafe int GetChars(byte* bytes, int byteCount, char* chars, int charCount)
454         {
455             return GetChars(
456                 new ReadOnlySpan<byte>(bytes, byteCount),
457                 new Span<char>(chars, charCount),
458                 write: true);
459         }
460     }
461 
462     internal class IA5Encoding : RestrictedAsciiStringEncoding
463     {
464         // T-REC-X.680-201508 sec 41, Table 8.
465         // ISO International Register of Coded Character Sets to be used with Escape Sequences 001
466         //   is ASCII 0x00 - 0x1F
467         // ISO International Register of Coded Character Sets to be used with Escape Sequences 006
468         //   is ASCII 0x21 - 0x7E
469         // Space is ASCII 0x20, delete is ASCII 0x7F.
470         //
471         // The net result is all of 7-bit ASCII
IA5Encoding()472         internal IA5Encoding()
473             : base(0x00, 0x7F)
474         {
475         }
476     }
477 
478     internal class VisibleStringEncoding : RestrictedAsciiStringEncoding
479     {
480         // T-REC-X.680-201508 sec 41, Table 8.
481         // ISO International Register of Coded Character Sets to be used with Escape Sequences 006
482         //   is ASCII 0x21 - 0x7E
483         // Space is ASCII 0x20.
VisibleStringEncoding()484         internal VisibleStringEncoding()
485             : base(0x20, 0x7E)
486         {
487         }
488     }
489 
490     internal class PrintableStringEncoding : RestrictedAsciiStringEncoding
491     {
492         // T-REC-X.680-201508 sec 41.4
PrintableStringEncoding()493         internal PrintableStringEncoding()
494             : base("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 '()+,-./:=?")
495         {
496         }
497     }
498 
499     internal abstract class RestrictedAsciiStringEncoding : SpanBasedEncoding
500     {
501         private readonly bool[] _isAllowed;
502 
RestrictedAsciiStringEncoding(byte minCharAllowed, byte maxCharAllowed)503         protected RestrictedAsciiStringEncoding(byte minCharAllowed, byte maxCharAllowed)
504         {
505             Debug.Assert(minCharAllowed <= maxCharAllowed);
506             Debug.Assert(maxCharAllowed <= 0x7F);
507 
508             bool[] isAllowed = new bool[0x80];
509 
510             for (byte charCode = minCharAllowed; charCode <= maxCharAllowed; charCode++)
511             {
512                 isAllowed[charCode] = true;
513             }
514 
515             _isAllowed = isAllowed;
516         }
517 
RestrictedAsciiStringEncoding(IEnumerable<char> allowedChars)518         protected RestrictedAsciiStringEncoding(IEnumerable<char> allowedChars)
519         {
520             bool[] isAllowed = new bool[0x7F];
521 
522             foreach (char c in allowedChars)
523             {
524                 if (c >= isAllowed.Length)
525                 {
526                     throw new ArgumentOutOfRangeException(nameof(allowedChars));
527                 }
528 
529                 Debug.Assert(isAllowed[c] == false);
530                 isAllowed[c] = true;
531             }
532 
533             _isAllowed = isAllowed;
534         }
535 
GetMaxByteCount(int charCount)536         public override int GetMaxByteCount(int charCount)
537         {
538             return charCount;
539         }
540 
GetMaxCharCount(int byteCount)541         public override int GetMaxCharCount(int byteCount)
542         {
543             return byteCount;
544         }
545 
GetBytes(ReadOnlySpan<char> chars, Span<byte> bytes, bool write)546         protected override int GetBytes(ReadOnlySpan<char> chars, Span<byte> bytes, bool write)
547         {
548             if (chars.IsEmpty)
549             {
550                 return 0;
551             }
552 
553             for (int i = 0; i < chars.Length; i++)
554             {
555                 char c = chars[i];
556 
557                 if ((uint)c >= (uint)_isAllowed.Length || !_isAllowed[c])
558                 {
559                     EncoderFallback.CreateFallbackBuffer().Fallback(c, i);
560 
561                     Debug.Fail("Fallback should have thrown");
562                     throw new CryptographicException();
563                 }
564 
565                 if (write)
566                 {
567                     bytes[i] = (byte)c;
568                 }
569             }
570 
571             return chars.Length;
572         }
573 
GetChars(ReadOnlySpan<byte> bytes, Span<char> chars, bool write)574         protected override int GetChars(ReadOnlySpan<byte> bytes, Span<char> chars, bool write)
575         {
576             if (bytes.IsEmpty)
577             {
578                 return 0;
579             }
580 
581             for (int i = 0; i < bytes.Length; i++)
582             {
583                 byte b = bytes[i];
584 
585                 if ((uint)b >= (uint)_isAllowed.Length || !_isAllowed[b])
586                 {
587                     DecoderFallback.CreateFallbackBuffer().Fallback(
588                         new[] { b },
589                         i);
590 
591                     Debug.Fail("Fallback should have thrown");
592                     throw new CryptographicException();
593                 }
594 
595                 if (write)
596                 {
597                     chars[i] = (char)b;
598                 }
599             }
600 
601             return bytes.Length;
602         }
603     }
604 
605     /// <summary>
606     /// Big-Endian UCS-2 encoding (the same as UTF-16BE, but disallowing surrogate pairs to leave plane 0)
607     /// </summary>
608     // T-REC-X.690-201508 sec 8.23.8 says to see ISO/IEC 10646:2003 section 13.1.
609     // ISO/IEC 10646:2003 sec 13.1 says each character is represented by "two octets".
610     // ISO/IEC 10646:2003 sec 6.3 says that when serialized as octets to use big endian.
611     internal class BMPEncoding : SpanBasedEncoding
612     {
GetBytes(ReadOnlySpan<char> chars, Span<byte> bytes, bool write)613         protected override int GetBytes(ReadOnlySpan<char> chars, Span<byte> bytes, bool write)
614         {
615             if (chars.IsEmpty)
616             {
617                 return 0;
618             }
619 
620             int writeIdx = 0;
621 
622             for (int i = 0; i < chars.Length; i++)
623             {
624                 char c = chars[i];
625 
626                 if (char.IsSurrogate(c))
627                 {
628                     EncoderFallback.CreateFallbackBuffer().Fallback(c, i);
629 
630                     Debug.Fail("Fallback should have thrown");
631                     throw new CryptographicException();
632                 }
633 
634                 ushort val16 = c;
635 
636                 if (write)
637                 {
638                     bytes[writeIdx + 1] = (byte)val16;
639                     bytes[writeIdx] = (byte)(val16 >> 8);
640                 }
641 
642                 writeIdx += 2;
643             }
644 
645             return writeIdx;
646         }
647 
GetChars(ReadOnlySpan<byte> bytes, Span<char> chars, bool write)648         protected override int GetChars(ReadOnlySpan<byte> bytes, Span<char> chars, bool write)
649         {
650             if (bytes.IsEmpty)
651             {
652                 return 0;
653             }
654 
655             if (bytes.Length % 2 != 0)
656             {
657                 DecoderFallback.CreateFallbackBuffer().Fallback(
658                     bytes.Slice(bytes.Length - 1).ToArray(),
659                     bytes.Length - 1);
660 
661                 Debug.Fail("Fallback should have thrown");
662                 throw new CryptographicException();
663             }
664 
665             int writeIdx = 0;
666 
667             for (int i = 0; i < bytes.Length; i += 2)
668             {
669                 int val = bytes[i] << 8 | bytes[i + 1];
670                 char c = (char)val;
671 
672                 if (char.IsSurrogate(c))
673                 {
674                     DecoderFallback.CreateFallbackBuffer().Fallback(
675                         bytes.Slice(i, 2).ToArray(),
676                         i);
677 
678                     Debug.Fail("Fallback should have thrown");
679                     throw new CryptographicException();
680                 }
681 
682                 if (write)
683                 {
684                     chars[writeIdx] = c;
685                 }
686 
687                 writeIdx++;
688             }
689 
690             return writeIdx;
691         }
692 
GetMaxByteCount(int charCount)693         public override int GetMaxByteCount(int charCount)
694         {
695             checked
696             {
697                 return charCount * 2;
698             }
699         }
700 
GetMaxCharCount(int byteCount)701         public override int GetMaxCharCount(int byteCount)
702         {
703             return byteCount / 2;
704         }
705     }
706 
707     internal class SetOfValueComparer : IComparer<ReadOnlyMemory<byte>>
708     {
709         internal static SetOfValueComparer Instance { get; } = new SetOfValueComparer();
710 
Compare(ReadOnlyMemory<byte> x, ReadOnlyMemory<byte> y)711         public int Compare(ReadOnlyMemory<byte> x, ReadOnlyMemory<byte> y)
712         {
713             ReadOnlySpan<byte> xSpan = x.Span;
714             ReadOnlySpan<byte> ySpan = y.Span;
715 
716             int min = Math.Min(x.Length, y.Length);
717             int diff;
718 
719             for (int i = 0; i < min; i++)
720             {
721                 int xVal = xSpan[i];
722                 byte yVal = ySpan[i];
723                 diff = xVal - yVal;
724 
725                 if (diff != 0)
726                 {
727                     return diff;
728                 }
729             }
730 
731             // The sorting rules (T-REC-X.690-201508 sec 11.6) say that the shorter one
732             // counts as if it are padded with as many 0x00s on the right as required for
733             // comparison.
734             //
735             // But, since a shorter definite value will have already had the length bytes
736             // compared, it was already different.  And a shorter indefinite value will
737             // have hit end-of-contents, making it already different.
738             //
739             // This is here because the spec says it should be, but no values are known
740             // which will make diff != 0.
741             diff = x.Length - y.Length;
742 
743             if (diff != 0)
744             {
745                 return diff;
746             }
747 
748             return 0;
749         }
750     }
751 }
752