AsnWriter
A writer for BER-, CER-, and DER-encoded ASN.1 data.
using System.Buffers;
using System.Buffers.Binary;
using System.Buffers.Text;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
namespace System.Formats.Asn1
{
public sealed class AsnWriter
{
private sealed class ArrayIndexSetOfValueComparer : IComparer<(int, int)>
{
private readonly byte[] _data;
public ArrayIndexSetOfValueComparer(byte[] data)
{
_data = data;
}
public int Compare((int, int) x, (int, int) y)
{
int item = x.Item1;
int item2 = x.Item2;
int item3 = y.Item1;
int item4 = y.Item2;
int num = SetOfValueComparer.Instance.Compare(new ReadOnlyMemory<byte>(_data, item, item2), new ReadOnlyMemory<byte>(_data, item3, item4));
if (num == 0)
return item - item3;
return num;
}
}
private readonly struct StackFrame : IEquatable<StackFrame>
{
public Asn1Tag Tag { get; }
public int Offset { get; }
public UniversalTagNumber ItemType { get; }
internal StackFrame(Asn1Tag tag, int offset, UniversalTagNumber itemType)
{
Tag = tag;
Offset = offset;
ItemType = itemType;
}
public void Deconstruct(out Asn1Tag tag, out int offset, out UniversalTagNumber itemType)
{
tag = Tag;
offset = Offset;
itemType = ItemType;
}
public bool Equals(StackFrame other)
{
if (Tag.Equals(other.Tag) && Offset == other.Offset)
return ItemType == other.ItemType;
return false;
}
public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object obj)
{
if (obj is StackFrame) {
StackFrame other = (StackFrame)obj;
return Equals(other);
}
return false;
}
public override int GetHashCode()
{
return (Tag, Offset, ItemType).GetHashCode();
}
public static bool operator ==(StackFrame left, StackFrame right)
{
return left.Equals(right);
}
public static bool operator !=(StackFrame left, StackFrame right)
{
return !left.Equals(right);
}
}
public readonly struct Scope : IDisposable
{
private readonly AsnWriter _writer;
private readonly StackFrame _frame;
private readonly int _depth;
internal Scope(AsnWriter writer)
{
_writer = writer;
_frame = _writer._nestingStack.Peek();
_depth = _writer._nestingStack.Count;
}
public void Dispose()
{
if (_writer != null && _writer._nestingStack.Count != 0) {
if (_writer._nestingStack.Peek() == _frame) {
switch (_frame.ItemType) {
case UniversalTagNumber.Set:
_writer.PopSetOf(_frame.Tag);
break;
case UniversalTagNumber.Sequence:
_writer.PopSequence(_frame.Tag);
break;
case UniversalTagNumber.OctetString:
_writer.PopOctetString(_frame.Tag);
break;
default:
throw new InvalidOperationException();
}
} else if (_writer._nestingStack.Count > _depth && _writer._nestingStack.Contains(_frame)) {
throw new InvalidOperationException(System.SR.AsnWriter_PopWrongTag);
}
}
}
}
private byte[] _buffer;
private int _offset;
private Stack<StackFrame> _nestingStack;
public AsnEncodingRules RuleSet { get; }
public AsnWriter(AsnEncodingRules ruleSet)
{
if (ruleSet != 0 && ruleSet != AsnEncodingRules.CER && ruleSet != AsnEncodingRules.DER)
throw new ArgumentOutOfRangeException("ruleSet");
RuleSet = ruleSet;
}
public AsnWriter(AsnEncodingRules ruleSet, int initialCapacity)
: this(ruleSet)
{
if (initialCapacity < 0)
throw new ArgumentOutOfRangeException("initialCapacity", System.SR.ArgumentOutOfRange_NeedNonNegNum);
if (initialCapacity > 0)
_buffer = new byte[initialCapacity];
}
public void Reset()
{
if (_offset > 0) {
Array.Clear(_buffer, 0, _offset);
_offset = 0;
_nestingStack?.Clear();
}
}
public int GetEncodedLength()
{
Stack<StackFrame> nestingStack = _nestingStack;
if (nestingStack != null && nestingStack.Count != 0)
throw new InvalidOperationException(System.SR.AsnWriter_EncodeUnbalancedStack);
return _offset;
}
public bool TryEncode(Span<byte> destination, out int bytesWritten)
{
Stack<StackFrame> nestingStack = _nestingStack;
if (nestingStack != null && nestingStack.Count != 0)
throw new InvalidOperationException(System.SR.AsnWriter_EncodeUnbalancedStack);
if (destination.Length < _offset) {
bytesWritten = 0;
return false;
}
if (_offset == 0) {
bytesWritten = 0;
return true;
}
bytesWritten = _offset;
_buffer.AsSpan(0, _offset).CopyTo(destination);
return true;
}
public int Encode(Span<byte> destination)
{
if (!TryEncode(destination, out int bytesWritten))
throw new ArgumentException(System.SR.Argument_DestinationTooShort, "destination");
return bytesWritten;
}
[System.Runtime.CompilerServices.NullableContext(1)]
public byte[] Encode()
{
Stack<StackFrame> nestingStack = _nestingStack;
if (nestingStack != null && nestingStack.Count != 0)
throw new InvalidOperationException(System.SR.AsnWriter_EncodeUnbalancedStack);
if (_offset == 0)
return Array.Empty<byte>();
return _buffer.AsSpan(0, _offset).ToArray();
}
private ReadOnlySpan<byte> EncodeAsSpan()
{
Stack<StackFrame> nestingStack = _nestingStack;
if (nestingStack != null && nestingStack.Count != 0)
throw new InvalidOperationException(System.SR.AsnWriter_EncodeUnbalancedStack);
if (_offset == 0)
return ReadOnlySpan<byte>.Empty;
return new ReadOnlySpan<byte>(_buffer, 0, _offset);
}
public bool EncodedValueEquals(ReadOnlySpan<byte> other)
{
return EncodeAsSpan().SequenceEqual(other);
}
[System.Runtime.CompilerServices.NullableContext(1)]
public bool EncodedValueEquals(AsnWriter other)
{
if (other == null)
throw new ArgumentNullException("other");
return EncodeAsSpan().SequenceEqual(other.EncodeAsSpan());
}
private void EnsureWriteCapacity(int pendingCount)
{
if (pendingCount < 0)
throw new OverflowException();
if (_buffer == null || _buffer.Length - _offset < pendingCount) {
int num = checked(_offset + pendingCount + 1023) / 1024;
byte[] buffer = _buffer;
Array.Resize(ref _buffer, 1024 * num);
buffer?.AsSpan(0, _offset).Clear();
}
}
private void WriteTag(Asn1Tag tag)
{
int num = tag.CalculateEncodedSize();
EnsureWriteCapacity(num);
if (!tag.TryEncode(_buffer.AsSpan(_offset, num), out int bytesWritten) || bytesWritten != num)
throw new InvalidOperationException();
_offset += num;
}
private void WriteLength(int length)
{
if (length == -1) {
EnsureWriteCapacity(1);
_buffer[_offset] = 128;
_offset++;
} else if (length < 128) {
EnsureWriteCapacity(1 + length);
_buffer[_offset] = (byte)length;
_offset++;
} else {
int encodedLengthSubsequentByteCount = GetEncodedLengthSubsequentByteCount(length);
EnsureWriteCapacity(encodedLengthSubsequentByteCount + 1 + length);
_buffer[_offset] = (byte)(128 | encodedLengthSubsequentByteCount);
int num = _offset + encodedLengthSubsequentByteCount;
int num2 = length;
do {
_buffer[num] = (byte)num2;
num2 >>= 8;
num--;
} while (num2 > 0);
_offset += encodedLengthSubsequentByteCount + 1;
}
}
private static int GetEncodedLengthSubsequentByteCount(int length)
{
if (length < 0)
throw new OverflowException();
if (length <= 127)
return 0;
if (length <= 255)
return 1;
if (length <= 65535)
return 2;
if (length <= 16777215)
return 3;
return 4;
}
[System.Runtime.CompilerServices.NullableContext(1)]
public void CopyTo(AsnWriter destination)
{
if (destination == null)
throw new ArgumentNullException("destination");
try {
destination.WriteEncodedValue(EncodeAsSpan());
} catch (ArgumentException innerException) {
throw new InvalidOperationException(new InvalidOperationException().Message, innerException);
}
}
public void WriteEncodedValue(ReadOnlySpan<byte> value)
{
if (!AsnDecoder.TryReadEncodedValue(value, RuleSet, out Asn1Tag _, out int _, out int _, out int bytesConsumed) || bytesConsumed != value.Length)
throw new ArgumentException(System.SR.Argument_WriteEncodedValue_OneValueAtATime, "value");
EnsureWriteCapacity(value.Length);
value.CopyTo(_buffer.AsSpan(_offset));
_offset += value.Length;
}
private void WriteEndOfContents()
{
EnsureWriteCapacity(2);
_buffer[_offset++] = 0;
_buffer[_offset++] = 0;
}
private Scope PushTag(Asn1Tag tag, UniversalTagNumber tagType)
{
if (_nestingStack == null)
_nestingStack = new Stack<StackFrame>();
WriteTag(tag);
_nestingStack.Push(new StackFrame(tag, _offset, tagType));
WriteLength(-1);
return new Scope(this);
}
private void PopTag(Asn1Tag tag, UniversalTagNumber tagType, bool sortContents = false)
{
if (_nestingStack == null || _nestingStack.Count == 0)
throw new InvalidOperationException(System.SR.AsnWriter_PopWrongTag);
_nestingStack.Peek().Deconstruct(out Asn1Tag tag2, out int offset, out UniversalTagNumber itemType);
Asn1Tag left = tag2;
int num = offset;
UniversalTagNumber universalTagNumber = itemType;
if (left != tag || universalTagNumber != tagType)
throw new InvalidOperationException(System.SR.AsnWriter_PopWrongTag);
_nestingStack.Pop();
if (sortContents)
SortContents(_buffer, num + 1, _offset);
if (RuleSet == AsnEncodingRules.CER && tagType != UniversalTagNumber.OctetString)
WriteEndOfContents();
else {
int num2 = _offset - 1 - num;
int num3 = num + 1;
if (tagType == UniversalTagNumber.OctetString) {
if (RuleSet == AsnEncodingRules.CER && num2 > 1000) {
int result;
int num4 = Math.DivRem(num2, 1000, out result);
int num5 = 4 * num4 + 2 + GetEncodedLengthSubsequentByteCount(result);
EnsureWriteCapacity(num5 + 2);
ReadOnlySpan<byte> readOnlySpan = _buffer.AsSpan(num3, num2);
Span<byte> span = _buffer.AsSpan(num3 + num5, num2);
readOnlySpan.CopyTo(span);
int num6 = num3 + num2 + num5 + 2;
_offset = num - tag.CalculateEncodedSize();
WriteConstructedCerOctetString(tag, span);
return;
}
int num7 = tag.CalculateEncodedSize();
tag2 = tag.AsPrimitive();
tag2.Encode(_buffer.AsSpan(num - num7, num7));
}
int encodedLengthSubsequentByteCount = GetEncodedLengthSubsequentByteCount(num2);
if (encodedLengthSubsequentByteCount == 0)
_buffer[num] = (byte)num2;
else {
EnsureWriteCapacity(encodedLengthSubsequentByteCount);
Buffer.BlockCopy(_buffer, num3, _buffer, num3 + encodedLengthSubsequentByteCount, num2);
int offset2 = _offset;
_offset = num;
WriteLength(num2);
_offset = offset2 + encodedLengthSubsequentByteCount;
}
}
}
private static void SortContents(byte[] buffer, int start, int end)
{
int num = end - start;
if (num != 0) {
AsnReader asnReader = new AsnReader(new ReadOnlyMemory<byte>(buffer, start, num), AsnEncodingRules.BER, default(AsnReaderOptions));
ReadOnlyMemory<byte> readOnlyMemory = asnReader.ReadEncodedValue();
if (asnReader.HasData) {
List<(int, int)> list = new List<(int, int)>();
list.Add((start, readOnlyMemory.Length));
int num2 = start + readOnlyMemory.Length;
do {
readOnlyMemory = asnReader.ReadEncodedValue();
list.Add((num2, readOnlyMemory.Length));
num2 += readOnlyMemory.Length;
} while (asnReader.HasData);
ArrayIndexSetOfValueComparer comparer = new ArrayIndexSetOfValueComparer(buffer);
list.Sort(comparer);
byte[] array = System.Security.Cryptography.CryptoPool.Rent(num);
num2 = 0;
foreach ((int, int) item3 in list) {
int item = item3.Item1;
int item2 = item3.Item2;
Buffer.BlockCopy(buffer, item, array, num2, item2);
num2 += item2;
}
Buffer.BlockCopy(array, 0, buffer, start, num);
System.Security.Cryptography.CryptoPool.Return(array, num);
}
}
}
private static void CheckUniversalTag(Asn1Tag? tag, UniversalTagNumber universalTagNumber)
{
if (tag.HasValue) {
Asn1Tag value = tag.Value;
if (value.TagClass == TagClass.Universal && value.TagValue != (int)universalTagNumber)
throw new ArgumentException(System.SR.Argument_UniversalValueIsFixed, "tag");
}
}
public void WriteBitString(ReadOnlySpan<byte> value, int unusedBitCount = 0, Asn1Tag? tag = default(Asn1Tag?))
{
CheckUniversalTag(tag, UniversalTagNumber.BitString);
WriteBitStringCore(tag ?? Asn1Tag.PrimitiveBitString, value, unusedBitCount);
}
private void WriteBitStringCore(Asn1Tag tag, ReadOnlySpan<byte> bitString, int unusedBitCount)
{
if (unusedBitCount < 0 || unusedBitCount > 7)
throw new ArgumentOutOfRangeException("unusedBitCount", unusedBitCount, System.SR.Argument_UnusedBitCountRange);
if (bitString.Length == 0 && unusedBitCount != 0)
throw new ArgumentException(System.SR.Argument_UnusedBitCountMustBeZero, "unusedBitCount");
byte lastByte = (byte)((!bitString.IsEmpty) ? bitString[bitString.Length - 1] : 0);
if (!CheckValidLastByte(lastByte, unusedBitCount))
throw new ArgumentException(System.SR.Argument_UnusedBitWasSet, "unusedBitCount");
if (RuleSet == AsnEncodingRules.CER && bitString.Length >= 1000)
WriteConstructedCerBitString(tag, bitString, unusedBitCount);
else {
WriteTag(tag.AsPrimitive());
WriteLength(bitString.Length + 1);
_buffer[_offset] = (byte)unusedBitCount;
_offset++;
bitString.CopyTo(_buffer.AsSpan(_offset));
_offset += bitString.Length;
}
}
private static bool CheckValidLastByte(byte lastByte, int unusedBitCount)
{
int num = (1 << unusedBitCount) - 1;
return (lastByte & num) == 0;
}
private static int DetermineCerBitStringTotalLength(Asn1Tag tag, int contentLength)
{
int result;
int num = Math.DivRem(contentLength, 999, out result);
int num2 = (result != 0) ? (3 + result + GetEncodedLengthSubsequentByteCount(result)) : 0;
return num * 1004 + num2 + 3 + tag.CalculateEncodedSize();
}
private void WriteConstructedCerBitString(Asn1Tag tag, ReadOnlySpan<byte> payload, int unusedBitCount)
{
int pendingCount = DetermineCerBitStringTotalLength(tag, payload.Length);
EnsureWriteCapacity(pendingCount);
int offset = _offset;
WriteTag(tag.AsConstructed());
WriteLength(-1);
byte[] buffer = _buffer;
ReadOnlySpan<byte> readOnlySpan = payload;
Asn1Tag primitiveBitString = Asn1Tag.PrimitiveBitString;
Span<byte> destination;
while (readOnlySpan.Length > 999) {
WriteTag(primitiveBitString);
WriteLength(1000);
_buffer[_offset] = 0;
_offset++;
destination = _buffer.AsSpan(_offset);
readOnlySpan.Slice(0, 999).CopyTo(destination);
readOnlySpan = readOnlySpan.Slice(999);
_offset += 999;
}
WriteTag(primitiveBitString);
WriteLength(readOnlySpan.Length + 1);
_buffer[_offset] = (byte)unusedBitCount;
_offset++;
destination = _buffer.AsSpan(_offset);
readOnlySpan.CopyTo(destination);
_offset += readOnlySpan.Length;
WriteEndOfContents();
}
public void WriteBoolean(bool value, Asn1Tag? tag = default(Asn1Tag?))
{
CheckUniversalTag(tag, UniversalTagNumber.Boolean);
WriteBooleanCore(tag?.AsPrimitive() ?? Asn1Tag.Boolean, value);
}
private void WriteBooleanCore(Asn1Tag tag, bool value)
{
WriteTag(tag);
WriteLength(1);
_buffer[_offset] = (byte)(value ? 255 : 0);
_offset++;
}
[System.Runtime.CompilerServices.NullableContext(1)]
public void WriteEnumeratedValue(Enum value, Asn1Tag? tag = default(Asn1Tag?))
{
if (value == null)
throw new ArgumentNullException("value");
WriteEnumeratedValue(tag?.AsPrimitive() ?? Asn1Tag.Enumerated, value.GetType(), value);
}
[System.Runtime.CompilerServices.NullableContext(1)]
public void WriteEnumeratedValue<[System.Runtime.CompilerServices.Nullable(0)] TEnum>(TEnum value, Asn1Tag? tag = default(Asn1Tag?)) where TEnum : Enum
{
WriteEnumeratedValue(tag?.AsPrimitive() ?? Asn1Tag.Enumerated, typeof(TEnum), value);
}
private void WriteEnumeratedValue(Asn1Tag tag, Type tEnum, object value)
{
CheckUniversalTag(tag, UniversalTagNumber.Enumerated);
Type enumUnderlyingType = tEnum.GetEnumUnderlyingType();
if (tEnum.IsDefined(typeof(FlagsAttribute), false))
throw new ArgumentException(System.SR.Argument_EnumeratedValueRequiresNonFlagsEnum, "tEnum");
if (enumUnderlyingType == typeof(ulong)) {
ulong value2 = Convert.ToUInt64(value);
WriteNonNegativeIntegerCore(tag, value2);
} else {
long value3 = Convert.ToInt64(value);
WriteIntegerCore(tag, value3);
}
}
public void WriteGeneralizedTime(DateTimeOffset value, bool omitFractionalSeconds = false, Asn1Tag? tag = default(Asn1Tag?))
{
CheckUniversalTag(tag, UniversalTagNumber.GeneralizedTime);
WriteGeneralizedTimeCore(tag?.AsPrimitive() ?? Asn1Tag.GeneralizedTime, value, omitFractionalSeconds);
}
private unsafe void WriteGeneralizedTimeCore(Asn1Tag tag, DateTimeOffset value, bool omitFractionalSeconds)
{
DateTimeOffset dateTimeOffset = value.ToUniversalTime();
Span<byte> destination = default(Span<byte>);
if (!omitFractionalSeconds) {
long num = dateTimeOffset.Ticks % 10000000;
if (num != 0) {
Span<byte> span = new Span<byte>(stackalloc byte[9], 9);
destination = span;
decimal d = num;
d /= 10000000;
if (!Utf8Formatter.TryFormat(d, destination, out int bytesWritten, new StandardFormat('G', byte.MaxValue)))
throw new InvalidOperationException();
destination = destination.Slice(1, bytesWritten - 1);
}
}
int length = 15 + destination.Length;
WriteTag(tag);
WriteLength(length);
int year = dateTimeOffset.Year;
int month = dateTimeOffset.Month;
int day = dateTimeOffset.Day;
int hour = dateTimeOffset.Hour;
int minute = dateTimeOffset.Minute;
int second = dateTimeOffset.Second;
Span<byte> span2 = _buffer.AsSpan(_offset);
StandardFormat format = new StandardFormat('D', 4);
StandardFormat format2 = new StandardFormat('D', 2);
if (!Utf8Formatter.TryFormat(year, span2.Slice(0, 4), out bytesWritten2, format) || !Utf8Formatter.TryFormat(month, span2.Slice(4, 2), out bytesWritten2, format2) || !Utf8Formatter.TryFormat(day, span2.Slice(6, 2), out bytesWritten2, format2) || !Utf8Formatter.TryFormat(hour, span2.Slice(8, 2), out bytesWritten2, format2) || !Utf8Formatter.TryFormat(minute, span2.Slice(10, 2), out bytesWritten2, format2) || !Utf8Formatter.TryFormat(second, span2.Slice(12, 2), out int bytesWritten2, format2))
throw new InvalidOperationException();
_offset += 14;
destination.CopyTo(span2.Slice(14));
_offset += destination.Length;
_buffer[_offset] = 90;
_offset++;
}
public void WriteInteger(long value, Asn1Tag? tag = default(Asn1Tag?))
{
CheckUniversalTag(tag, UniversalTagNumber.Integer);
WriteIntegerCore(tag?.AsPrimitive() ?? Asn1Tag.Integer, value);
}
[CLSCompliant(false)]
public void WriteInteger(ulong value, Asn1Tag? tag = default(Asn1Tag?))
{
CheckUniversalTag(tag, UniversalTagNumber.Integer);
WriteNonNegativeIntegerCore(tag?.AsPrimitive() ?? Asn1Tag.Integer, value);
}
public void WriteInteger(BigInteger value, Asn1Tag? tag = default(Asn1Tag?))
{
CheckUniversalTag(tag, UniversalTagNumber.Integer);
WriteIntegerCore(tag?.AsPrimitive() ?? Asn1Tag.Integer, value);
}
public void WriteInteger(ReadOnlySpan<byte> value, Asn1Tag? tag = default(Asn1Tag?))
{
CheckUniversalTag(tag, UniversalTagNumber.Integer);
WriteIntegerCore(tag?.AsPrimitive() ?? Asn1Tag.Integer, value);
}
public void WriteIntegerUnsigned(ReadOnlySpan<byte> value, Asn1Tag? tag = default(Asn1Tag?))
{
CheckUniversalTag(tag, UniversalTagNumber.Integer);
WriteIntegerUnsignedCore(tag?.AsPrimitive() ?? Asn1Tag.Integer, value);
}
private void WriteIntegerCore(Asn1Tag tag, long value)
{
if (value >= 0)
WriteNonNegativeIntegerCore(tag, (ulong)value);
else {
int num = (value >= -128) ? 1 : ((value >= -32768) ? 2 : ((value >= -8388608) ? 3 : ((value >= -2147483648) ? 4 : ((value >= -549755813888) ? 5 : ((value >= -140737488355328) ? 6 : ((value < -36028797018963968) ? 8 : 7))))));
WriteTag(tag);
WriteLength(num);
long num2 = value;
int num3 = _offset + num - 1;
do {
_buffer[num3] = (byte)num2;
num2 >>= 8;
num3--;
} while (num3 >= _offset);
_offset += num;
}
}
private void WriteNonNegativeIntegerCore(Asn1Tag tag, ulong value)
{
int num = (value < 128) ? 1 : ((value < 32768) ? 2 : ((value < 8388608) ? 3 : ((value < 2147483648) ? 4 : ((value < 549755813888) ? 5 : ((value < 140737488355328) ? 6 : ((value < 36028797018963968) ? 7 : ((value >= 9223372036854775808) ? 9 : 8)))))));
WriteTag(tag);
WriteLength(num);
ulong num2 = value;
int num3 = _offset + num - 1;
do {
_buffer[num3] = (byte)num2;
num2 >>= 8;
num3--;
} while (num3 >= _offset);
_offset += num;
}
private void WriteIntegerUnsignedCore(Asn1Tag tag, ReadOnlySpan<byte> value)
{
if (value.IsEmpty)
throw new ArgumentException(System.SR.Argument_IntegerCannotBeEmpty, "value");
if (value.Length > 1 && value[0] == 0 && value[1] < 128)
throw new ArgumentException(System.SR.Argument_IntegerRedundantByte, "value");
WriteTag(tag);
if (value[0] >= 128) {
WriteLength(checked(value.Length + 1));
_buffer[_offset] = 0;
_offset++;
} else
WriteLength(value.Length);
value.CopyTo(_buffer.AsSpan(_offset));
_offset += value.Length;
}
private void WriteIntegerCore(Asn1Tag tag, ReadOnlySpan<byte> value)
{
if (value.IsEmpty)
throw new ArgumentException(System.SR.Argument_IntegerCannotBeEmpty, "value");
if (BinaryPrimitives.TryReadUInt16BigEndian(value, out ushort value2)) {
ushort num = (ushort)(value2 & 65408);
if (num == 0 || num == 65408)
throw new ArgumentException(System.SR.Argument_IntegerRedundantByte, "value");
}
WriteTag(tag);
WriteLength(value.Length);
value.CopyTo(_buffer.AsSpan(_offset));
_offset += value.Length;
}
private void WriteIntegerCore(Asn1Tag tag, BigInteger value)
{
WriteTag(tag);
byte[] array = value.ToByteArray();
Array.Reverse((Array)array);
WriteLength(array.Length);
Buffer.BlockCopy(array, 0, _buffer, _offset, array.Length);
_offset += array.Length;
}
[System.Runtime.CompilerServices.NullableContext(1)]
public void WriteNamedBitList(Enum value, Asn1Tag? tag = default(Asn1Tag?))
{
if (value == null)
throw new ArgumentNullException("value");
CheckUniversalTag(tag, UniversalTagNumber.BitString);
WriteNamedBitList(tag, value.GetType(), value);
}
[System.Runtime.CompilerServices.NullableContext(1)]
public void WriteNamedBitList<[System.Runtime.CompilerServices.Nullable(0)] TEnum>(TEnum value, Asn1Tag? tag = default(Asn1Tag?)) where TEnum : Enum
{
CheckUniversalTag(tag, UniversalTagNumber.BitString);
WriteNamedBitList(tag, typeof(TEnum), (Enum)(object)value);
}
[System.Runtime.CompilerServices.NullableContext(1)]
public void WriteNamedBitList(BitArray value, Asn1Tag? tag = default(Asn1Tag?))
{
if (value == null)
throw new ArgumentNullException("value");
CheckUniversalTag(tag, UniversalTagNumber.BitString);
WriteBitArray(value, tag);
}
private void WriteNamedBitList(Asn1Tag? tag, Type tEnum, Enum value)
{
Type enumUnderlyingType = tEnum.GetEnumUnderlyingType();
if (!tEnum.IsDefined(typeof(FlagsAttribute), false))
throw new ArgumentException(System.SR.Argument_NamedBitListRequiresFlagsEnum, "tEnum");
ulong integralValue;
if (enumUnderlyingType == typeof(ulong))
integralValue = Convert.ToUInt64(value);
else {
long num = Convert.ToInt64(value);
integralValue = (ulong)num;
}
WriteNamedBitList(tag, integralValue);
}
private unsafe void WriteNamedBitList(Asn1Tag? tag, ulong integralValue)
{
Span<byte> span = new Span<byte>(stackalloc byte[8], 8);
Span<byte> span2 = span;
span2.Clear();
int num = -1;
int num2 = 0;
while (integralValue != 0) {
if ((integralValue & 1) != 0) {
span2[num2 / 8] |= (byte)(128 >> num2 % 8);
num = num2;
}
integralValue >>= 1;
num2++;
}
if (num < 0)
WriteBitString(ReadOnlySpan<byte>.Empty, 0, tag);
else {
int length = num / 8 + 1;
int unusedBitCount = 7 - num % 8;
WriteBitString(span2.Slice(0, length), unusedBitCount, tag);
}
}
private void WriteBitArray(BitArray value, Asn1Tag? tag)
{
if (value.Count == 0)
WriteBitString(ReadOnlySpan<byte>.Empty, 0, tag);
else {
int num = checked(value.Count + 7) / 8;
int unusedBitCount = num * 8 - value.Count;
byte[] array = System.Security.Cryptography.CryptoPool.Rent(num);
value.CopyTo(array, 0);
Span<byte> span = array.AsSpan(0, num);
AsnDecoder.ReverseBitsPerByte(span);
WriteBitString(span, unusedBitCount, tag);
System.Security.Cryptography.CryptoPool.Return(array, num);
}
}
public void WriteNull(Asn1Tag? tag = default(Asn1Tag?))
{
CheckUniversalTag(tag, UniversalTagNumber.Null);
WriteNullCore(tag?.AsPrimitive() ?? Asn1Tag.Null);
}
private void WriteNullCore(Asn1Tag tag)
{
WriteTag(tag);
WriteLength(0);
}
public Scope PushOctetString(Asn1Tag? tag = default(Asn1Tag?))
{
CheckUniversalTag(tag, UniversalTagNumber.OctetString);
return PushTag(tag?.AsConstructed() ?? Asn1Tag.ConstructedOctetString, UniversalTagNumber.OctetString);
}
public void PopOctetString(Asn1Tag? tag = default(Asn1Tag?))
{
CheckUniversalTag(tag, UniversalTagNumber.OctetString);
PopTag(tag?.AsConstructed() ?? Asn1Tag.ConstructedOctetString, UniversalTagNumber.OctetString, false);
}
public void WriteOctetString(ReadOnlySpan<byte> value, Asn1Tag? tag = default(Asn1Tag?))
{
CheckUniversalTag(tag, UniversalTagNumber.OctetString);
WriteOctetStringCore(tag ?? Asn1Tag.PrimitiveOctetString, value);
}
private void WriteOctetStringCore(Asn1Tag tag, ReadOnlySpan<byte> octetString)
{
if (RuleSet == AsnEncodingRules.CER && octetString.Length > 1000)
WriteConstructedCerOctetString(tag, octetString);
else {
WriteTag(tag.AsPrimitive());
WriteLength(octetString.Length);
octetString.CopyTo(_buffer.AsSpan(_offset));
_offset += octetString.Length;
}
}
private void WriteConstructedCerOctetString(Asn1Tag tag, ReadOnlySpan<byte> payload)
{
WriteTag(tag.AsConstructed());
WriteLength(-1);
int result;
int num = Math.DivRem(payload.Length, 1000, out result);
int num2 = (result != 0) ? (2 + result + GetEncodedLengthSubsequentByteCount(result)) : 0;
int pendingCount = num * 1004 + num2 + 2;
EnsureWriteCapacity(pendingCount);
byte[] buffer = _buffer;
int offset = _offset;
ReadOnlySpan<byte> readOnlySpan = payload;
Asn1Tag primitiveOctetString = Asn1Tag.PrimitiveOctetString;
Span<byte> destination;
while (readOnlySpan.Length > 1000) {
WriteTag(primitiveOctetString);
WriteLength(1000);
destination = _buffer.AsSpan(_offset);
readOnlySpan.Slice(0, 1000).CopyTo(destination);
_offset += 1000;
readOnlySpan = readOnlySpan.Slice(1000);
}
WriteTag(primitiveOctetString);
WriteLength(readOnlySpan.Length);
destination = _buffer.AsSpan(_offset);
readOnlySpan.CopyTo(destination);
_offset += readOnlySpan.Length;
WriteEndOfContents();
}
[System.Runtime.CompilerServices.NullableContext(1)]
public void WriteObjectIdentifier(string oidValue, Asn1Tag? tag = default(Asn1Tag?))
{
if (oidValue == null)
throw new ArgumentNullException("oidValue");
WriteObjectIdentifier(oidValue.AsSpan(), tag);
}
public void WriteObjectIdentifier(ReadOnlySpan<char> oidValue, Asn1Tag? tag = default(Asn1Tag?))
{
CheckUniversalTag(tag, UniversalTagNumber.ObjectIdentifier);
WriteObjectIdentifierCore(tag?.AsPrimitive() ?? Asn1Tag.ObjectIdentifier, oidValue);
}
private void WriteObjectIdentifierCore(Asn1Tag tag, ReadOnlySpan<char> oidValue)
{
if (oidValue.Length < 3)
throw new ArgumentException(System.SR.Argument_InvalidOidValue, "oidValue");
if (oidValue[1] != '.')
throw new ArgumentException(System.SR.Argument_InvalidOidValue, "oidValue");
byte[] array = System.Security.Cryptography.CryptoPool.Rent(oidValue.Length / 2);
int num = 0;
try {
int num2;
switch (oidValue[0]) {
case '0':
num2 = 0;
break;
case '1':
num2 = 1;
break;
case '2':
num2 = 2;
break;
default:
throw new ArgumentException(System.SR.Argument_InvalidOidValue, "oidValue");
}
int num3 = num2;
ReadOnlySpan<char> oidValue2 = oidValue.Slice(2);
BigInteger subIdentifier = ParseSubIdentifier(ref oidValue2);
subIdentifier += (BigInteger)(40 * num3);
int num4 = EncodeSubIdentifier(array.AsSpan(num), ref subIdentifier);
num += num4;
while (!oidValue2.IsEmpty) {
subIdentifier = ParseSubIdentifier(ref oidValue2);
num4 = EncodeSubIdentifier(array.AsSpan(num), ref subIdentifier);
num += num4;
}
WriteTag(tag);
WriteLength(num);
Buffer.BlockCopy(array, 0, _buffer, _offset, num);
_offset += num;
} finally {
System.Security.Cryptography.CryptoPool.Return(array, num);
}
}
private static BigInteger ParseSubIdentifier(ref ReadOnlySpan<char> oidValue)
{
int num = oidValue.IndexOf('.');
switch (num) {
case -1:
num = oidValue.Length;
break;
default:
if (num != oidValue.Length - 1)
break;
goto case 0;
case 0:
throw new ArgumentException(System.SR.Argument_InvalidOidValue, "oidValue");
}
BigInteger bigInteger = BigInteger.Zero;
for (int i = 0; i < num; i++) {
if (i > 0 && bigInteger == 0)
throw new ArgumentException(System.SR.Argument_InvalidOidValue, "oidValue");
bigInteger *= (BigInteger)10;
bigInteger += (BigInteger)AtoI(oidValue[i]);
}
oidValue = oidValue.Slice(Math.Min(oidValue.Length, num + 1));
return bigInteger;
}
private static int AtoI(char c)
{
if (c >= '0' && c <= '9')
return c - 48;
throw new ArgumentException(System.SR.Argument_InvalidOidValue, "oidValue");
}
private static int EncodeSubIdentifier(Span<byte> dest, ref BigInteger subIdentifier)
{
if (subIdentifier.IsZero) {
dest[0] = 0;
return 1;
}
BigInteger bigInteger = subIdentifier;
int num = 0;
do {
BigInteger value = bigInteger & (BigInteger)127;
byte b = (byte)value;
if (subIdentifier != bigInteger)
b = (byte)(b | 128);
bigInteger >>= 7;
dest[num] = b;
num++;
} while (bigInteger != BigInteger.Zero);
dest.Slice(0, num).Reverse();
return num;
}
public Scope PushSequence(Asn1Tag? tag = default(Asn1Tag?))
{
CheckUniversalTag(tag, UniversalTagNumber.Sequence);
return PushSequenceCore(tag?.AsConstructed() ?? Asn1Tag.Sequence);
}
public void PopSequence(Asn1Tag? tag = default(Asn1Tag?))
{
CheckUniversalTag(tag, UniversalTagNumber.Sequence);
PopSequenceCore(tag?.AsConstructed() ?? Asn1Tag.Sequence);
}
private Scope PushSequenceCore(Asn1Tag tag)
{
return PushTag(tag, UniversalTagNumber.Sequence);
}
private void PopSequenceCore(Asn1Tag tag)
{
PopTag(tag, UniversalTagNumber.Sequence, false);
}
public Scope PushSetOf(Asn1Tag? tag = default(Asn1Tag?))
{
CheckUniversalTag(tag, UniversalTagNumber.Set);
return PushSetOfCore(tag?.AsConstructed() ?? Asn1Tag.SetOf);
}
public void PopSetOf(Asn1Tag? tag = default(Asn1Tag?))
{
CheckUniversalTag(tag, UniversalTagNumber.Set);
PopSetOfCore(tag?.AsConstructed() ?? Asn1Tag.SetOf);
}
private Scope PushSetOfCore(Asn1Tag tag)
{
return PushTag(tag, UniversalTagNumber.Set);
}
private void PopSetOfCore(Asn1Tag tag)
{
bool sortContents = RuleSet == AsnEncodingRules.CER || RuleSet == AsnEncodingRules.DER;
PopTag(tag, UniversalTagNumber.Set, sortContents);
}
[System.Runtime.CompilerServices.NullableContext(1)]
public void WriteCharacterString(UniversalTagNumber encodingType, string value, Asn1Tag? tag = default(Asn1Tag?))
{
if (value == null)
throw new ArgumentNullException("value");
WriteCharacterString(encodingType, value.AsSpan(), tag);
}
public void WriteCharacterString(UniversalTagNumber encodingType, ReadOnlySpan<char> str, Asn1Tag? tag = default(Asn1Tag?))
{
CheckUniversalTag(tag, encodingType);
Encoding encoding = AsnCharacterStringEncodings.GetEncoding(encodingType);
WriteCharacterStringCore(tag ?? new Asn1Tag(encodingType, false), encoding, str);
}
private void WriteCharacterStringCore(Asn1Tag tag, Encoding encoding, ReadOnlySpan<char> str)
{
int byteCount = AsnCharacterStringEncodings.GetByteCount(encoding, str);
if (RuleSet == AsnEncodingRules.CER && byteCount > 1000)
WriteConstructedCerCharacterString(tag, encoding, str, byteCount);
else {
WriteTag(tag.AsPrimitive());
WriteLength(byteCount);
Span<byte> bytes = _buffer.AsSpan(_offset, byteCount);
int bytes2 = AsnCharacterStringEncodings.GetBytes(encoding, str, bytes);
if (bytes2 != byteCount)
throw new InvalidOperationException();
_offset += byteCount;
}
}
private void WriteConstructedCerCharacterString(Asn1Tag tag, Encoding encoding, ReadOnlySpan<char> str, int size)
{
byte[] array = System.Security.Cryptography.CryptoPool.Rent(size);
int bytes = AsnCharacterStringEncodings.GetBytes(encoding, str, array);
if (bytes != size)
throw new InvalidOperationException();
WriteConstructedCerOctetString(tag, array.AsSpan(0, size));
System.Security.Cryptography.CryptoPool.Return(array, size);
}
public void WriteUtcTime(DateTimeOffset value, Asn1Tag? tag = default(Asn1Tag?))
{
CheckUniversalTag(tag, UniversalTagNumber.UtcTime);
WriteUtcTimeCore(tag?.AsPrimitive() ?? Asn1Tag.UtcTime, value);
}
public void WriteUtcTime(DateTimeOffset value, int twoDigitYearMax, Asn1Tag? tag = default(Asn1Tag?))
{
CheckUniversalTag(tag, UniversalTagNumber.UtcTime);
value = value.ToUniversalTime();
if (value.Year > twoDigitYearMax || value.Year <= twoDigitYearMax - 100)
throw new ArgumentOutOfRangeException("value");
WriteUtcTimeCore(tag?.AsPrimitive() ?? Asn1Tag.UtcTime, value);
}
private void WriteUtcTimeCore(Asn1Tag tag, DateTimeOffset value)
{
WriteTag(tag);
WriteLength(13);
DateTimeOffset dateTimeOffset = value.ToUniversalTime();
int year = dateTimeOffset.Year;
int month = dateTimeOffset.Month;
int day = dateTimeOffset.Day;
int hour = dateTimeOffset.Hour;
int minute = dateTimeOffset.Minute;
int second = dateTimeOffset.Second;
Span<byte> span = _buffer.AsSpan(_offset);
StandardFormat format = new StandardFormat('D', 2);
if (!Utf8Formatter.TryFormat(year % 100, span.Slice(0, 2), out bytesWritten, format) || !Utf8Formatter.TryFormat(month, span.Slice(2, 2), out bytesWritten, format) || !Utf8Formatter.TryFormat(day, span.Slice(4, 2), out bytesWritten, format) || !Utf8Formatter.TryFormat(hour, span.Slice(6, 2), out bytesWritten, format) || !Utf8Formatter.TryFormat(minute, span.Slice(8, 2), out bytesWritten, format) || !Utf8Formatter.TryFormat(second, span.Slice(10, 2), out int bytesWritten, format))
throw new InvalidOperationException();
_buffer[_offset + 12] = 90;
_offset += 13;
}
}
}