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