diff --git a/src/libraries/System.Formats.Asn1/src/System.Formats.Asn1.csproj b/src/libraries/System.Formats.Asn1/src/System.Formats.Asn1.csproj
index f7a85ae076f779..ae8c2927edeaed 100644
--- a/src/libraries/System.Formats.Asn1/src/System.Formats.Asn1.csproj
+++ b/src/libraries/System.Formats.Asn1/src/System.Formats.Asn1.csproj
@@ -54,9 +54,18 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/AsnCharacterStringEncodings.cs b/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/AsnCharacterStringEncodings.cs
index 5bbc3fef701b81..86cdd4376915fe 100644
--- a/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/AsnCharacterStringEncodings.cs
+++ b/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/AsnCharacterStringEncodings.cs
@@ -117,7 +117,7 @@ public override unsafe int GetChars(byte* bytes, int byteCount, char* chars, int
}
}
- internal sealed class IA5Encoding : RestrictedAsciiStringEncoding
+ internal sealed class IA5Encoding : RestrictedAsciiRangeEncoding
{
// T-REC-X.680-201508 sec 41, Table 8.
// ISO International Register of Coded Character Sets to be used with Escape Sequences 001
@@ -133,7 +133,7 @@ internal IA5Encoding()
}
}
- internal sealed class VisibleStringEncoding : RestrictedAsciiStringEncoding
+ internal sealed class VisibleStringEncoding : RestrictedAsciiRangeEncoding
{
// T-REC-X.680-201508 sec 41, Table 8.
// ISO International Register of Coded Character Sets to be used with Escape Sequences 006
diff --git a/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/AsnCharacterStringEncodings.downlevel.cs b/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/AsnCharacterStringEncodings.downlevel.cs
new file mode 100644
index 00000000000000..4c674f4e7f5f74
--- /dev/null
+++ b/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/AsnCharacterStringEncodings.downlevel.cs
@@ -0,0 +1,13 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Formats.Asn1
+{
+ internal abstract class RestrictedAsciiRangeEncoding : RestrictedAsciiStringEncoding
+ {
+ protected RestrictedAsciiRangeEncoding(byte minCharAllowed, byte maxCharAllowed)
+ : base(minCharAllowed, maxCharAllowed)
+ {
+ }
+ }
+}
diff --git a/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/AsnCharacterStringEncodings.net.cs b/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/AsnCharacterStringEncodings.net.cs
new file mode 100644
index 00000000000000..033c72633c7b38
--- /dev/null
+++ b/src/libraries/System.Formats.Asn1/src/System/Formats/Asn1/AsnCharacterStringEncodings.net.cs
@@ -0,0 +1,191 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+using System.Numerics;
+using System.Runtime.InteropServices;
+
+namespace System.Formats.Asn1
+{
+ internal abstract class RestrictedAsciiRangeEncoding : SpanBasedEncoding
+ {
+ private readonly byte _minCharAllowed;
+ private readonly byte _range;
+
+ protected RestrictedAsciiRangeEncoding(byte minCharAllowed, byte maxCharAllowed)
+ {
+ Debug.Assert(minCharAllowed <= maxCharAllowed);
+ Debug.Assert(maxCharAllowed <= 0x7F);
+
+ _minCharAllowed = minCharAllowed;
+ _range = (byte)(maxCharAllowed - minCharAllowed);
+ }
+
+ public override int GetMaxByteCount(int charCount)
+ {
+ return charCount;
+ }
+
+ public override int GetMaxCharCount(int byteCount)
+ {
+ return byteCount;
+ }
+
+ protected override int GetBytes(ReadOnlySpan chars, Span bytes, bool write)
+ {
+ int position = 0;
+
+ if (chars.Length >= Vector.Count && Vector.IsHardwareAccelerated)
+ {
+ position = GetBytesVectorized(chars, bytes, write);
+ }
+
+ for (; position < chars.Length; position++)
+ {
+ char c = chars[position];
+
+ if (!IsAllowed(c))
+ {
+ EncoderFallback.CreateFallbackBuffer().Fallback(c, position);
+
+ Debug.Fail("Fallback should have thrown");
+ throw new InvalidOperationException();
+ }
+
+ if (write)
+ {
+ bytes[position] = (byte)c;
+ }
+ }
+
+ return chars.Length;
+ }
+
+ protected override int GetChars(ReadOnlySpan bytes, Span chars, bool write)
+ {
+ int position = 0;
+
+ if (bytes.Length >= Vector.Count && Vector.IsHardwareAccelerated)
+ {
+ position = GetCharsVectorized(bytes, chars, write);
+ }
+
+ for (; position < bytes.Length; position++)
+ {
+ byte b = bytes[position];
+
+ if (!IsAllowed(b))
+ {
+ DecoderFallback.CreateFallbackBuffer().Fallback(
+ new[] { b },
+ position);
+
+ Debug.Fail("Fallback should have thrown");
+ throw new InvalidOperationException();
+ }
+
+ if (write)
+ {
+ chars[position] = (char)b;
+ }
+ }
+
+ return bytes.Length;
+ }
+
+ // The vectorization is left out of the GetChars and GetBytes directly to not regress the code size
+ // and register allocation for small inputs. Instead they are extracted methods.
+ private int GetBytesVectorized(ReadOnlySpan chars, Span bytes, bool write)
+ {
+ int available = write ? Math.Min(chars.Length, bytes.Length) : chars.Length;
+ int vectorizedLength = available - (available % Vector.Count);
+ int position = 0;
+
+ Debug.Assert(Vector.Count == 2 * Vector.Count);
+
+ // Revisit this cast when Vector is supported: https://github.com/dotnet/runtime/issues/127611
+ ReadOnlySpan source = MemoryMarshal.Cast(chars);
+ Vector minCharAllowed = new Vector(_minCharAllowed);
+ Vector range = new Vector(_range);
+
+ for (; position < vectorizedLength; position += Vector.Count)
+ {
+ Vector lower = new Vector(source.Slice(position));
+ Vector upper = new Vector(source.Slice(position + Vector.Count));
+
+ if (!IsAllowed(lower, minCharAllowed, range) || !IsAllowed(upper, minCharAllowed, range))
+ {
+ // If any element in the vector is not allowed, we break out and return the position before the
+ // current vector's width so that it goes down the scalar path. The scalar path will determine the
+ // precise location of the invalid element.
+ break;
+ }
+
+ if (write)
+ {
+ Vector.Narrow(lower, upper).CopyTo(bytes.Slice(position));
+ }
+ }
+
+ return position;
+ }
+
+ private int GetCharsVectorized(ReadOnlySpan bytes, Span chars, bool write)
+ {
+ int available = write ? Math.Min(bytes.Length, chars.Length) : bytes.Length;
+ int vectorizedLength = available - (available % Vector.Count);
+ int position = 0;
+
+ Debug.Assert(Vector.Count == 2 * Vector.Count);
+
+ // Revisit this cast when Vector is supported: https://github.com/dotnet/runtime/issues/127611
+ Span destination = write ? MemoryMarshal.Cast(chars) : Span.Empty;
+ Vector minCharAllowed = new Vector(_minCharAllowed);
+ Vector range = new Vector(_range);
+
+ for (; position < vectorizedLength; position += Vector.Count)
+ {
+ Vector source = new Vector(bytes.Slice(position));
+
+ if (!IsAllowed(source, minCharAllowed, range))
+ {
+ // If any element in the vector is not allowed, we break out and return the position before the
+ // current vector's width so that it goes down the scalar path. The scalar path will determine the
+ // precise location of the invalid element.
+ break;
+ }
+
+ if (write)
+ {
+ Vector.Widen(source, out Vector lower, out Vector upper);
+ lower.CopyTo(destination.Slice(position));
+ upper.CopyTo(destination.Slice(position + Vector.Count));
+ }
+ }
+
+ return position;
+ }
+
+ private bool IsAllowed(byte value)
+ {
+ return (byte)(value - _minCharAllowed) <= _range;
+ }
+
+ private bool IsAllowed(char value)
+ {
+ return (uint)(value - _minCharAllowed) <= _range;
+ }
+
+ private static bool IsAllowed(Vector value, Vector minCharAllowed, Vector range)
+ {
+ Vector offset = value - minCharAllowed;
+ return Vector.LessThanOrEqualAll(offset, range);
+ }
+
+ private static bool IsAllowed(Vector value, Vector minCharAllowed, Vector range)
+ {
+ Vector offset = value - minCharAllowed;
+ return Vector.LessThanOrEqualAll(offset, range);
+ }
+ }
+}
diff --git a/src/libraries/System.Formats.Asn1/tests/Reader/ComprehensiveReadTests.cs b/src/libraries/System.Formats.Asn1/tests/Reader/ComprehensiveReadTests.cs
index ca01bed1b7157e..f2234318c3f856 100644
--- a/src/libraries/System.Formats.Asn1/tests/Reader/ComprehensiveReadTests.cs
+++ b/src/libraries/System.Formats.Asn1/tests/Reader/ComprehensiveReadTests.cs
@@ -1,8 +1,12 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Buffers;
+using System.Collections.Generic;
+using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using System.Text;
using Test.Cryptography;
using Xunit;
using X509KeyUsageCSharpStyle=System.Formats.Asn1.Tests.Reader.ReadNamedBitListBase.X509KeyUsageCSharpStyle;
@@ -11,6 +15,95 @@ namespace System.Formats.Asn1.Tests.Reader
{
public static class ComprehensiveReadTests
{
+ public static IEnumerable