File: System\Formats\Asn1\AsnDecoder.Integer.cs
Web Access
Project: src\src\libraries\System.Formats.Asn1\src\System.Formats.Asn1.csproj (System.Formats.Asn1)
// 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.Binary;
using System.Diagnostics;
using System.Numerics;
using System.Security.Cryptography;
 
namespace System.Formats.Asn1
{
    public static partial class AsnDecoder
    {
        /// <summary>
        ///   Reads an Integer value from <paramref name="source"/> with a specified tag under
        ///   the specified encoding rules, returning the contents as a slice of the buffer.
        /// </summary>
        /// <param name="source">The buffer containing encoded data.</param>
        /// <param name="ruleSet">The encoding constraints to use when interpreting the data.</param>
        /// <param name="bytesConsumed">
        ///   When this method returns, the total number of bytes for the encoded value.
        ///   This parameter is treated as uninitialized.
        /// </param>
        /// <param name="expectedTag">
        ///   The tag to check for before reading, or <see langword="null"/> for the default tag (Universal 2).
        /// </param>
        /// <returns>
        ///   The slice of the buffer containing the bytes of the Integer value, in signed big-endian form.
        /// </returns>
        /// <exception cref="ArgumentOutOfRangeException">
        ///   <paramref name="ruleSet"/> is not defined.
        /// </exception>
        /// <exception cref="AsnContentException">
        ///   The next value does not have the correct tag.
        ///
        ///   -or-
        ///
        ///   The length encoding is not valid under the current encoding rules.
        ///
        ///   -or-
        ///
        ///   The contents are not valid under the current encoding rules.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <paramref name="expectedTag"/>.<see cref="Asn1Tag.TagClass"/> is
        ///   <see cref="TagClass.Universal"/>, but
        ///   <paramref name="expectedTag"/>.<see cref="Asn1Tag.TagValue"/> is not correct for
        ///   the method.
        /// </exception>
        public static ReadOnlySpan<byte> ReadIntegerBytes(
            ReadOnlySpan<byte> source,
            AsnEncodingRules ruleSet,
            out int bytesConsumed,
            Asn1Tag? expectedTag = null)
        {
            return GetIntegerContents(
                source,
                ruleSet,
                expectedTag ?? Asn1Tag.Integer,
                UniversalTagNumber.Integer,
                out bytesConsumed);
        }
 
        /// <summary>
        ///   Reads an Integer value from <paramref name="source"/> with a specified tag under
        ///   the specified encoding rules.
        /// </summary>
        /// <param name="source">The buffer containing encoded data.</param>
        /// <param name="ruleSet">The encoding constraints to use when interpreting the data.</param>
        /// <param name="bytesConsumed">
        ///   When this method returns, the total number of bytes for the encoded value.
        ///   This parameter is treated as uninitialized.
        /// </param>
        /// <param name="expectedTag">
        ///   The tag to check for before reading, or <see langword="null"/> for the default tag (Universal 2).
        /// </param>
        /// <returns>
        ///   The decoded numeric value.
        /// </returns>
        /// <exception cref="ArgumentOutOfRangeException">
        ///   <paramref name="ruleSet"/> is not defined.
        /// </exception>
        /// <exception cref="AsnContentException">
        ///   The next value does not have the correct tag.
        ///
        ///   -or-
        ///
        ///   The length encoding is not valid under the current encoding rules.
        ///
        ///   -or-
        ///
        ///   The contents are not valid under the current encoding rules.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <paramref name="expectedTag"/>.<see cref="Asn1Tag.TagClass"/> is
        ///   <see cref="TagClass.Universal"/>, but
        ///   <paramref name="expectedTag"/>.<see cref="Asn1Tag.TagValue"/> is not correct for
        ///   the method.
        /// </exception>
        public static BigInteger ReadInteger(
            ReadOnlySpan<byte> source,
            AsnEncodingRules ruleSet,
            out int bytesConsumed,
            Asn1Tag? expectedTag = null)
        {
            ReadOnlySpan<byte> contents = ReadIntegerBytes(source, ruleSet, out int consumed, expectedTag);
 
#if NET
            BigInteger value = new BigInteger(contents, isBigEndian: true);
#else
            byte[] tmp = CryptoPool.Rent(contents.Length);
            BigInteger value;
 
            try
            {
                byte fill = (contents[0] & 0x80) == 0 ? (byte)0 : (byte)0xFF;
                // Fill the unused portions of tmp with positive or negative padding.
                tmp.AsSpan(contents.Length, tmp.Length - contents.Length).Fill(fill);
                contents.CopyTo(tmp);
                // Convert to Little-Endian.
                tmp.AsSpan(0, contents.Length).Reverse();
                value = new BigInteger(tmp);
            }
            finally
            {
                // Let CryptoPool.Return clear the whole tmp so that not even the sign bit
                // is returned to the array pool.
                CryptoPool.Return(tmp);
            }
#endif
 
            bytesConsumed = consumed;
            return value;
        }
 
        /// <summary>
        ///   Attempts to read an Integer value from <paramref name="source"/> with a specified tag under
        ///   the specified encoding rules as a signed 32-bit value.
        /// </summary>
        /// <param name="source">The buffer containing encoded data.</param>
        /// <param name="ruleSet">The encoding constraints to use when interpreting the data.</param>
        /// <param name="value">
        ///   On success, receives the interpreted numeric value.
        ///   This parameter is treated as uninitialized.
        /// </param>
        /// <param name="bytesConsumed">
        ///   When this method returns, the total number of bytes for the encoded value.
        ///   This parameter is treated as uninitialized.
        /// </param>
        /// <param name="expectedTag">
        ///   The tag to check for before reading, or <see langword="null"/> for the default tag (Universal 2).
        /// </param>
        /// <returns>
        ///   <see langword="true"/> if the Integer represents value is between
        ///   <see cref="int.MinValue">Int32.MinValue</see> and <see cref="int.MaxValue">Int32.MaxValue</see>, inclusive; otherwise,
        ///   <see langword="false"/>.
        /// </returns>
        /// <exception cref="ArgumentOutOfRangeException">
        ///   <paramref name="ruleSet"/> is not defined.
        /// </exception>
        /// <exception cref="AsnContentException">
        ///   The next value does not have the correct tag.
        ///
        ///   -or-
        ///
        ///   The length encoding is not valid under the current encoding rules.
        ///
        ///   -or-
        ///
        ///   The contents are not valid under the current encoding rules.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <paramref name="expectedTag"/>.<see cref="Asn1Tag.TagClass"/> is
        ///   <see cref="TagClass.Universal"/>, but
        ///   <paramref name="expectedTag"/>.<see cref="Asn1Tag.TagValue"/> is not correct for
        ///   the method.
        /// </exception>
        public static bool TryReadInt32(
            ReadOnlySpan<byte> source,
            AsnEncodingRules ruleSet,
            out int value,
            out int bytesConsumed,
            Asn1Tag? expectedTag = null)
        {
            if (TryReadSignedInteger(
                source,
                ruleSet,
                sizeof(int),
                expectedTag ?? Asn1Tag.Integer,
                UniversalTagNumber.Integer,
                out long longValue,
                out bytesConsumed))
            {
                value = (int)longValue;
                return true;
            }
 
            value = 0;
            return false;
        }
 
        /// <summary>
        ///   Attempts to read an Integer value from <paramref name="source"/> with a specified tag under
        ///   the specified encoding rules as an unsigned 32-bit value.
        /// </summary>
        /// <param name="source">The buffer containing encoded data.</param>
        /// <param name="ruleSet">The encoding constraints to use when interpreting the data.</param>
        /// <param name="value">
        ///   On success, receives the interpreted numeric value.
        ///   This parameter is treated as uninitialized.
        /// </param>
        /// <param name="bytesConsumed">
        ///   When this method returns, the total number of bytes for the encoded value.
        ///   This parameter is treated as uninitialized.
        /// </param>
        /// <param name="expectedTag">
        ///   The tag to check for before reading, or <see langword="null"/> for the default tag (Universal 2).
        /// </param>
        /// <returns>
        ///   <see langword="true"/> if the Integer represents value is between
        ///   <see cref="uint.MinValue">UInt32.MinValue</see> and <see cref="uint.MaxValue">UInt32.MaxValue</see>, inclusive; otherwise,
        ///   <see langword="false"/>.
        /// </returns>
        /// <exception cref="ArgumentOutOfRangeException">
        ///   <paramref name="ruleSet"/> is not defined.
        /// </exception>
        /// <exception cref="AsnContentException">
        ///   The next value does not have the correct tag.
        ///
        ///   -or-
        ///
        ///   The length encoding is not valid under the current encoding rules.
        ///
        ///   -or-
        ///
        ///   The contents are not valid under the current encoding rules.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <paramref name="expectedTag"/>.<see cref="Asn1Tag.TagClass"/> is
        ///   <see cref="TagClass.Universal"/>, but
        ///   <paramref name="expectedTag"/>.<see cref="Asn1Tag.TagValue"/> is not correct for
        ///   the method.
        /// </exception>
        [CLSCompliant(false)]
        public static bool TryReadUInt32(
            ReadOnlySpan<byte> source,
            AsnEncodingRules ruleSet,
            out uint value,
            out int bytesConsumed,
            Asn1Tag? expectedTag = null)
        {
            if (TryReadUnsignedInteger(
                source,
                ruleSet,
                sizeof(uint),
                expectedTag ?? Asn1Tag.Integer,
                UniversalTagNumber.Integer,
                out ulong ulongValue,
                out bytesConsumed))
            {
                value = (uint)ulongValue;
                return true;
            }
 
            value = 0;
            return false;
        }
 
        /// <summary>
        ///   Attempts to read an Integer value from <paramref name="source"/> with a specified tag under
        ///   the specified encoding rules as a signed 64-bit value.
        /// </summary>
        /// <param name="source">The buffer containing encoded data.</param>
        /// <param name="ruleSet">The encoding constraints to use when interpreting the data.</param>
        /// <param name="value">
        ///   On success, receives the interpreted numeric value.
        ///   This parameter is treated as uninitialized.
        /// </param>
        /// <param name="bytesConsumed">
        ///   When this method returns, the total number of bytes for the encoded value.
        ///   This parameter is treated as uninitialized.
        /// </param>
        /// <param name="expectedTag">
        ///   The tag to check for before reading, or <see langword="null"/> for the default tag (Universal 2).
        /// </param>
        /// <returns>
        ///   <see langword="true"/> if the Integer represents value is between
        ///   <see cref="long.MinValue">Int64.MinValue</see> and <see cref="long.MaxValue">Int64.MaxValue</see>, inclusive; otherwise,
        ///   <see langword="false"/>.
        /// </returns>
        /// <exception cref="ArgumentOutOfRangeException">
        ///   <paramref name="ruleSet"/> is not defined.
        /// </exception>
        /// <exception cref="AsnContentException">
        ///   The next value does not have the correct tag.
        ///
        ///   -or-
        ///
        ///   The length encoding is not valid under the current encoding rules.
        ///
        ///   -or-
        ///
        ///   The contents are not valid under the current encoding rules.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <paramref name="expectedTag"/>.<see cref="Asn1Tag.TagClass"/> is
        ///   <see cref="TagClass.Universal"/>, but
        ///   <paramref name="expectedTag"/>.<see cref="Asn1Tag.TagValue"/> is not correct for
        ///   the method.
        /// </exception>
        public static bool TryReadInt64(
            ReadOnlySpan<byte> source,
            AsnEncodingRules ruleSet,
            out long value,
            out int bytesConsumed,
            Asn1Tag? expectedTag = null)
        {
            return TryReadSignedInteger(
                source,
                ruleSet,
                sizeof(long),
                expectedTag ?? Asn1Tag.Integer,
                UniversalTagNumber.Integer,
                out value,
                out bytesConsumed);
        }
 
        /// <summary>
        ///   Attempts to read an Integer value from <paramref name="source"/> with a specified tag under
        ///   the specified encoding rules as an unsigned 64-bit value.
        /// </summary>
        /// <param name="source">The buffer containing encoded data.</param>
        /// <param name="ruleSet">The encoding constraints to use when interpreting the data.</param>
        /// <param name="value">
        ///   On success, receives the interpreted numeric value.
        ///   This parameter is treated as uninitialized.
        /// </param>
        /// <param name="bytesConsumed">
        ///   When this method returns, the total number of bytes for the encoded value.
        ///   This parameter is treated as uninitialized.
        /// </param>
        /// <param name="expectedTag">
        ///   The tag to check for before reading, or <see langword="null"/> for the default tag (Universal 2).
        /// </param>
        /// <returns>
        ///   <see langword="true"/> if the Integer represents value is between
        ///   <see cref="ulong.MinValue">UInt64.MinValue</see> and <see cref="ulong.MaxValue">UInt64.MaxValue</see>, inclusive; otherwise,
        ///   <see langword="false"/>.
        /// </returns>
        /// <exception cref="ArgumentOutOfRangeException">
        ///   <paramref name="ruleSet"/> is not defined.
        /// </exception>
        /// <exception cref="AsnContentException">
        ///   The next value does not have the correct tag.
        ///
        ///   -or-
        ///
        ///   The length encoding is not valid under the current encoding rules.
        ///
        ///   -or-
        ///
        ///   The contents are not valid under the current encoding rules.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <paramref name="expectedTag"/>.<see cref="Asn1Tag.TagClass"/> is
        ///   <see cref="TagClass.Universal"/>, but
        ///   <paramref name="expectedTag"/>.<see cref="Asn1Tag.TagValue"/> is not correct for
        ///   the method.
        /// </exception>
        [CLSCompliant(false)]
        public static bool TryReadUInt64(
            ReadOnlySpan<byte> source,
            AsnEncodingRules ruleSet,
            out ulong value,
            out int bytesConsumed,
            Asn1Tag? expectedTag = null)
        {
            return TryReadUnsignedInteger(
                source,
                ruleSet,
                sizeof(ulong),
                expectedTag ?? Asn1Tag.Integer,
                UniversalTagNumber.Integer,
                out value,
                out bytesConsumed);
        }
 
        private static ReadOnlySpan<byte> GetIntegerContents(
            ReadOnlySpan<byte> source,
            AsnEncodingRules ruleSet,
            Asn1Tag expectedTag,
            UniversalTagNumber tagNumber,
            out int bytesConsumed)
        {
            // T-REC-X.690-201508 sec 8.3.1
            ReadOnlySpan<byte> contents = GetPrimitiveContentSpan(
                source,
                ruleSet,
                expectedTag,
                tagNumber,
                out int consumed);
 
            // T-REC-X.690-201508 sec 8.3.1
            if (contents.IsEmpty)
            {
                throw new AsnContentException();
            }
 
            // T-REC-X.690-201508 sec 8.3.2
            if (BinaryPrimitives.TryReadUInt16BigEndian(contents, out ushort bigEndianValue))
            {
                const ushort RedundancyMask = 0b1111_1111_1000_0000;
                ushort masked = (ushort)(bigEndianValue & RedundancyMask);
 
                // If the first 9 bits are all 0 or are all 1, the value is invalid.
                if (masked == 0 || masked == RedundancyMask)
                {
                    throw new AsnContentException();
                }
            }
 
            bytesConsumed = consumed;
            return contents;
        }
 
        private static bool TryReadSignedInteger(
            ReadOnlySpan<byte> source,
            AsnEncodingRules ruleSet,
            int sizeLimit,
            Asn1Tag expectedTag,
            UniversalTagNumber tagNumber,
            out long value,
            out int bytesConsumed)
        {
            Debug.Assert(sizeLimit <= sizeof(long));
 
            ReadOnlySpan<byte> contents = GetIntegerContents(
                source,
                ruleSet,
                expectedTag,
                tagNumber,
                out int consumed);
 
            if (contents.Length > sizeLimit)
            {
                value = 0;
                bytesConsumed = 0;
                return false;
            }
 
            bool isNegative = (contents[0] & 0x80) != 0;
            long accum = isNegative ? -1 : 0;
 
            for (int i = 0; i < contents.Length; i++)
            {
                accum <<= 8;
                accum |= contents[i];
            }
 
            bytesConsumed = consumed;
            value = accum;
            return true;
        }
 
        private static bool TryReadUnsignedInteger(
            ReadOnlySpan<byte> source,
            AsnEncodingRules ruleSet,
            int sizeLimit,
            Asn1Tag expectedTag,
            UniversalTagNumber tagNumber,
            out ulong value,
            out int bytesConsumed)
        {
            Debug.Assert(sizeLimit <= sizeof(ulong));
 
            ReadOnlySpan<byte> contents = GetIntegerContents(
                source,
                ruleSet,
                expectedTag,
                tagNumber,
                out int consumed);
 
            bool isNegative = (contents[0] & 0x80) != 0;
 
            if (isNegative)
            {
                bytesConsumed = 0;
                value = 0;
                return false;
            }
 
            // Ignore any padding zeros.
            if (contents.Length > 1 && contents[0] == 0)
            {
                contents = contents.Slice(1);
            }
 
            if (contents.Length > sizeLimit)
            {
                bytesConsumed = 0;
                value = 0;
                return false;
            }
 
            ulong accum = 0;
 
            for (int i = 0; i < contents.Length; i++)
            {
                accum <<= 8;
                accum |= contents[i];
            }
 
            bytesConsumed = consumed;
            value = accum;
            return true;
        }
    }
 
    public partial class AsnReader
    {
        /// <summary>
        ///   Reads the next value as a Integer with a specified tag, returning the contents
        ///   as a <see cref="ReadOnlyMemory{T}"/> over the original data.
        /// </summary>
        /// <param name="expectedTag">
        ///   The tag to check for before reading, or <see langword="null"/> for the default tag (Universal 2).
        /// </param>
        /// <returns>
        ///   The bytes of the Integer value, in signed big-endian form.
        /// </returns>
        /// <exception cref="AsnContentException">
        ///   The next value does not have the correct tag.
        ///
        ///   -or-
        ///
        ///   The length encoding is not valid under the current encoding rules.
        ///
        ///   -or-
        ///
        ///   The contents are not valid under the current encoding rules.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <paramref name="expectedTag"/>.<see cref="Asn1Tag.TagClass"/> is
        ///   <see cref="TagClass.Universal"/>, but
        ///   <paramref name="expectedTag"/>.<see cref="Asn1Tag.TagValue"/> is not correct for
        ///   the method.
        /// </exception>
        public ReadOnlyMemory<byte> ReadIntegerBytes(Asn1Tag? expectedTag = null)
        {
            ReadOnlySpan<byte> bytes =
                AsnDecoder.ReadIntegerBytes(_data.Span, RuleSet, out int consumed, expectedTag);
 
            ReadOnlyMemory<byte> ret = AsnDecoder.Slice(_data, bytes);
 
            _data = _data.Slice(consumed);
            return ret;
        }
 
        /// <summary>
        ///   Reads the next value as an Integer with a specified tag.
        /// </summary>
        /// <param name="expectedTag">
        ///   The tag to check for before reading, or <see langword="null"/> for the default tag (Universal 2).
        /// </param>
        /// <returns>
        ///   The decoded value.
        /// </returns>
        /// <exception cref="AsnContentException">
        ///   The next value does not have the correct tag.
        ///
        ///   -or-
        ///
        ///   The length encoding is not valid under the current encoding rules.
        ///
        ///   -or-
        ///
        ///   The contents are not valid under the current encoding rules.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <paramref name="expectedTag"/>.<see cref="Asn1Tag.TagClass"/> is
        ///   <see cref="TagClass.Universal"/>, but
        ///   <paramref name="expectedTag"/>.<see cref="Asn1Tag.TagValue"/> is not correct for
        ///   the method.
        /// </exception>
        public BigInteger ReadInteger(Asn1Tag? expectedTag = null)
        {
            BigInteger ret = AsnDecoder.ReadInteger(_data.Span, RuleSet, out int consumed, expectedTag);
            _data = _data.Slice(consumed);
            return ret;
        }
 
        /// <summary>
        ///   Attempts to read the next value as an Integer with a specified tag,
        ///   as a signed 32-bit value.
        /// </summary>
        /// <param name="value">
        ///   On success, receives the decoded value.
        /// </param>
        /// <param name="expectedTag">
        ///   The tag to check for before reading, or <see langword="null"/> for the default tag (Universal 2).
        /// </param>
        /// <returns>
        ///   <see langword="false"/> and does not advance the reader if the value is not between
        ///   <see cref="int.MinValue">Int32.MinValue</see> and <see cref="int.MaxValue">Int32.MaxValue</see>, inclusive; otherwise
        ///   <see langword="true"/> is returned and the reader advances.
        /// </returns>
        /// <exception cref="AsnContentException">
        ///   The next value does not have the correct tag.
        ///
        ///   -or-
        ///
        ///   The length encoding is not valid under the current encoding rules.
        ///
        ///   -or-
        ///
        ///   The contents are not valid under the current encoding rules.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <paramref name="expectedTag"/>.<see cref="Asn1Tag.TagClass"/> is
        ///   <see cref="TagClass.Universal"/>, but
        ///   <paramref name="expectedTag"/>.<see cref="Asn1Tag.TagValue"/> is not correct for
        ///   the method.
        /// </exception>
        public bool TryReadInt32(out int value, Asn1Tag? expectedTag = null)
        {
            bool ret = AsnDecoder.TryReadInt32(_data.Span, RuleSet, out value, out int read, expectedTag);
            _data = _data.Slice(read);
            return ret;
        }
 
        /// <summary>
        ///   Attempts to read the next value as an Integer with a specified tag,
        ///   as an unsigned 32-bit value.
        /// </summary>
        /// <param name="value">
        ///   On success, receives the decoded value.
        /// </param>
        /// <param name="expectedTag">
        ///   The tag to check for before reading, or <see langword="null"/> for the default tag (Universal 2).
        /// </param>
        /// <returns>
        ///   <see langword="false"/> and does not advance the reader if the value is not between
        ///   <see cref="uint.MinValue">UInt32.MinValue</see> and <see cref="uint.MaxValue">UInt32.MaxValue</see>, inclusive; otherwise
        ///   <see langword="true"/> is returned and the reader advances.
        /// </returns>
        /// <exception cref="AsnContentException">
        ///   The next value does not have the correct tag.
        ///
        ///   -or-
        ///
        ///   The length encoding is not valid under the current encoding rules.
        ///
        ///   -or-
        ///
        ///   The contents are not valid under the current encoding rules.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <paramref name="expectedTag"/>.<see cref="Asn1Tag.TagClass"/> is
        ///   <see cref="TagClass.Universal"/>, but
        ///   <paramref name="expectedTag"/>.<see cref="Asn1Tag.TagValue"/> is not correct for
        ///   the method.
        /// </exception>
        [CLSCompliant(false)]
        public bool TryReadUInt32(out uint value, Asn1Tag? expectedTag = null)
        {
            bool ret = AsnDecoder.TryReadUInt32(_data.Span, RuleSet, out value, out int read, expectedTag);
            _data = _data.Slice(read);
            return ret;
        }
 
        /// <summary>
        ///   Attempts to read the next value as an Integer with a specified tag,
        ///   as a signed 64-bit value.
        /// </summary>
        /// <param name="value">
        ///   On success, receives the decoded value.
        /// </param>
        /// <param name="expectedTag">
        ///   The tag to check for before reading, or <see langword="null"/> for the default tag (Universal 2).
        /// </param>
        /// <returns>
        ///   <see langword="false"/> and does not advance the reader if the value is not between
        ///   <see cref="long.MinValue">Int64.MinValue</see> and <see cref="long.MaxValue">Int64.MaxValue</see>, inclusive; otherwise
        ///   <see langword="true"/> is returned and the reader advances.
        /// </returns>
        /// <exception cref="AsnContentException">
        ///   The next value does not have the correct tag.
        ///
        ///   -or-
        ///
        ///   The length encoding is not valid under the current encoding rules.
        ///
        ///   -or-
        ///
        ///   The contents are not valid under the current encoding rules.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <paramref name="expectedTag"/>.<see cref="Asn1Tag.TagClass"/> is
        ///   <see cref="TagClass.Universal"/>, but
        ///   <paramref name="expectedTag"/>.<see cref="Asn1Tag.TagValue"/> is not correct for
        ///   the method.
        /// </exception>
        public bool TryReadInt64(out long value, Asn1Tag? expectedTag = null)
        {
            bool ret = AsnDecoder.TryReadInt64(_data.Span, RuleSet, out value, out int read, expectedTag);
            _data = _data.Slice(read);
            return ret;
        }
 
        /// <summary>
        ///   Attempts to read the next value as an Integer with a specified tag,
        ///   as an unsigned 64-bit value.
        /// </summary>
        /// <param name="value">
        ///   On success, receives the decoded value.
        /// </param>
        /// <param name="expectedTag">
        ///   The tag to check for before reading, or <see langword="null"/> for the default tag (Universal 2).
        /// </param>
        /// <returns>
        ///   <see langword="false"/> and does not advance the reader if the value is not between
        ///   <see cref="ulong.MinValue">Int64.MinValue</see> and <see cref="ulong.MaxValue">Int64.MaxValue</see>, inclusive; otherwise
        ///   <see langword="true"/> is returned and the reader advances.
        /// </returns>
        /// <exception cref="AsnContentException">
        ///   The next value does not have the correct tag.
        ///
        ///   -or-
        ///
        ///   The length encoding is not valid under the current encoding rules.
        ///
        ///   -or-
        ///
        ///   The contents are not valid under the current encoding rules.
        /// </exception>
        /// <exception cref="ArgumentException">
        ///   <paramref name="expectedTag"/>.<see cref="Asn1Tag.TagClass"/> is
        ///   <see cref="TagClass.Universal"/>, but
        ///   <paramref name="expectedTag"/>.<see cref="Asn1Tag.TagValue"/> is not correct for
        ///   the method.
        /// </exception>
        [CLSCompliant(false)]
        public bool TryReadUInt64(out ulong value, Asn1Tag? expectedTag = null)
        {
            bool ret = AsnDecoder.TryReadUInt64(_data.Span, RuleSet, out value, out int read, expectedTag);
            _data = _data.Slice(read);
            return ret;
        }
    }
}