File: System\Xml\XmlBufferReader.cs
Web Access
Project: src\src\libraries\System.Private.DataContractSerialization\src\System.Private.DataContractSerialization.csproj (System.Private.DataContractSerialization)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Buffers.Binary;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Text;
using System.Xml;
 
namespace System.Xml
{
    internal sealed class XmlBufferReader
    {
        private readonly XmlDictionaryReader _reader;
        private Stream? _stream;
        private byte[]? _streamBuffer;
        private byte[] _buffer = null!; // initialized by SetBuffer
        private int _offsetMin;
        private int _offsetMax;
        private IXmlDictionary? _dictionary;
        private XmlBinaryReaderSession? _session;
        private int _offset;
        private const int maxBytesPerChar = 3;
        private char[]? _chars;
        private int _windowOffset;
        private int _windowOffsetMax;
        private ValueHandle? _listValue;
        private static readonly XmlBufferReader s_empty = new XmlBufferReader(Array.Empty<byte>());
 
        public XmlBufferReader(XmlDictionaryReader reader)
        {
            _reader = reader;
        }
 
        public XmlBufferReader(byte[] buffer)
        {
            _reader = null!; // this ctor is only used in 2 places internally, which will never touch _reader
            _buffer = buffer;
        }
 
        public static XmlBufferReader Empty
        {
            get
            {
                return s_empty;
            }
        }
 
        public byte[] Buffer
        {
            get
            {
                return _buffer;
            }
        }
 
        public bool IsStreamed
        {
            get
            {
                return _stream != null;
            }
        }
 
 
        public void SetBuffer(Stream stream, IXmlDictionary? dictionary, XmlBinaryReaderSession? session)
        {
            _streamBuffer ??= new byte[128];
            SetBuffer(stream, _streamBuffer, 0, 0, dictionary, session);
            _windowOffset = 0;
            _windowOffsetMax = _streamBuffer.Length;
        }
 
        public void SetBuffer(byte[] buffer, int offset, int count, IXmlDictionary? dictionary, XmlBinaryReaderSession? session)
        {
            SetBuffer(null, buffer, offset, count, dictionary, session);
        }
 
        private void SetBuffer(Stream? stream, byte[] buffer, int offset, int count, IXmlDictionary? dictionary, XmlBinaryReaderSession? session)
        {
            _stream = stream;
            _buffer = buffer;
            _offsetMin = offset;
            _offset = offset;
            _offsetMax = offset + count;
            _dictionary = dictionary;
            _session = session;
        }
 
        public void Close()
        {
            if (_streamBuffer != null && _streamBuffer.Length > 4096)
            {
                _streamBuffer = null;
            }
            if (_stream != null)
            {
                _stream.Dispose();
                _stream = null;
            }
            _buffer = Array.Empty<byte>();
            _offset = 0;
            _offsetMax = 0;
            _windowOffset = 0;
            _windowOffsetMax = 0;
            _dictionary = null;
            _session = null;
        }
 
        public bool EndOfFile
        {
            get
            {
                return _offset == _offsetMax && !TryEnsureByte();
            }
        }
 
        public byte GetByte()
        {
            int offset = _offset;
            if (offset < _offsetMax)
                return _buffer[offset];
            else
                return GetByteHard();
        }
 
        public void SkipByte()
        {
            Advance(1);
        }
 
        private byte GetByteHard()
        {
            EnsureByte();
            return _buffer[_offset];
        }
 
        public byte[] GetBuffer(int count, out int offset)
        {
            offset = _offset;
            if (offset <= _offsetMax - count)
                return _buffer;
            return GetBufferHard(count, out offset);
        }
 
        public byte[] GetBuffer(int count, out int offset, out int offsetMax)
        {
            offset = _offset;
            if (offset <= _offsetMax - count)
            {
                offsetMax = _offset + count;
            }
            else
            {
                TryEnsureBytes(Math.Min(count, _windowOffsetMax - offset));
                offsetMax = _offsetMax;
            }
            return _buffer;
        }
 
        public byte[] GetBuffer(out int offset, out int offsetMax)
        {
            offset = _offset;
            offsetMax = _offsetMax;
            return _buffer;
        }
 
        private byte[] GetBufferHard(int count, out int offset)
        {
            offset = _offset;
            EnsureBytes(count);
            return _buffer;
        }
 
        private void EnsureByte()
        {
            if (!TryEnsureByte())
                XmlExceptionHelper.ThrowUnexpectedEndOfFile(_reader);
        }
 
        private bool TryEnsureByte()
        {
            if (_stream == null)
                return false;
            Debug.Assert(_offsetMax < _windowOffsetMax);
            if (_offsetMax >= _buffer.Length)
                return TryEnsureBytes(1);
            int b = _stream.ReadByte();
            if (b == -1)
                return false;
            _buffer[_offsetMax++] = (byte)b;
            return true;
        }
 
        private void EnsureBytes(int count)
        {
            if (!TryEnsureBytes(count))
                XmlExceptionHelper.ThrowUnexpectedEndOfFile(_reader);
        }
 
        private bool TryEnsureBytes(int count)
        {
            if (_stream == null)
                return false;
 
            Debug.Assert(_offset <= int.MaxValue - count);
 
            // The data could be coming from an untrusted source, so we use a standard
            // "multiply by 2" growth algorithm to avoid overly large memory utilization.
            // Constant value of 256 comes from MemoryStream implementation.
 
            do
            {
                int newOffsetMax = _offset + count;
                if (newOffsetMax <= _offsetMax)
                    return true;
                Debug.Assert(newOffsetMax <= _windowOffsetMax);
                if (newOffsetMax > _buffer.Length)
                {
                    byte[] newBuffer = new byte[Math.Max(256, _buffer.Length * 2)];
                    System.Buffer.BlockCopy(_buffer, 0, newBuffer, 0, _offsetMax);
                    newOffsetMax = Math.Min(newOffsetMax, newBuffer.Length);
                    _buffer = newBuffer;
                    _streamBuffer = newBuffer;
                }
                int needed = newOffsetMax - _offsetMax;
                Debug.Assert(needed > 0);
                int read = _stream.ReadAtLeast(_buffer.AsSpan(_offsetMax, needed), needed, throwOnEndOfStream: false);
                _offsetMax += read;
 
                if (read < needed)
                {
                    return false;
                }
            } while (true);
        }
 
        public void Advance(int count)
        {
            Debug.Assert(_offset + count <= _offsetMax);
            _offset += count;
        }
 
        public void InsertBytes(byte[] buffer, int offset, int count)
        {
            Debug.Assert(_stream != null);
            if (_offsetMax > buffer.Length - count)
            {
                byte[] newBuffer = new byte[_offsetMax + count];
                System.Buffer.BlockCopy(_buffer, 0, newBuffer, 0, _offsetMax);
                _buffer = newBuffer;
                _streamBuffer = newBuffer;
            }
            System.Buffer.BlockCopy(_buffer, _offset, _buffer, _offset + count, _offsetMax - _offset);
            _offsetMax += count;
            System.Buffer.BlockCopy(buffer, offset, _buffer, _offset, count);
        }
 
        public void SetWindow(int windowOffset, int windowLength)
        {
            // [0...elementOffset-1][elementOffset..offset][offset..offsetMax-1][offsetMax..buffer.Length]
            // ^--Elements, Attributes in scope
            //                      ^-- The node just consumed
            //                                             ^--Data buffered, not consumed
            //                                                                  ^--Unused space
            if (windowOffset > int.MaxValue - windowLength)
                windowLength = int.MaxValue - windowOffset;
 
            if (_offset != windowOffset)
            {
                System.Buffer.BlockCopy(_buffer, _offset, _buffer, windowOffset, _offsetMax - _offset);
                _offsetMax = windowOffset + (_offsetMax - _offset);
                _offset = windowOffset;
            }
            _windowOffset = windowOffset;
            _windowOffsetMax = Math.Max(windowOffset + windowLength, _offsetMax);
        }
 
        public int Offset
        {
            get
            {
                return _offset;
            }
            set
            {
                Debug.Assert(value >= _offsetMin && value <= _offsetMax);
                _offset = value;
            }
        }
 
        public int ReadBytes(int count)
        {
            Debug.Assert(count >= 0);
            int offset = _offset;
            if (offset > _offsetMax - count)
                EnsureBytes(count);
            _offset += count;
            return offset;
        }
 
        public int ReadMultiByteUInt31()
        {
            int i = GetByte();
            Advance(1);
            if ((i & 0x80) == 0)
                return i;
            i &= 0x7F;
 
            int j = GetByte();
            Advance(1);
            i |= ((j & 0x7F) << 7);
            if ((j & 0x80) == 0)
                return i;
 
            int k = GetByte();
            Advance(1);
            i |= ((k & 0x7F) << 14);
            if ((k & 0x80) == 0)
                return i;
 
            int l = GetByte();
            Advance(1);
            i |= ((l & 0x7F) << 21);
            if ((l & 0x80) == 0)
                return i;
 
            int m = GetByte();
            Advance(1);
            i |= (m << 28);
            if ((m & 0xF8) != 0)
                XmlExceptionHelper.ThrowInvalidBinaryFormat(_reader);
 
            return i;
        }
 
        public int ReadUInt8()
        {
            byte b = GetByte();
            Advance(1);
            return b;
        }
 
        public int ReadInt8()
            => (sbyte)ReadUInt8();
 
        public int ReadUInt16()
            => BitConverter.IsLittleEndian ? ReadRawBytes<ushort>() : BinaryPrimitives.ReverseEndianness(ReadRawBytes<ushort>());
 
        public int ReadInt16()
            => (short)ReadUInt16();
 
        public int ReadInt32()
            => BitConverter.IsLittleEndian ? ReadRawBytes<int>() : BinaryPrimitives.ReverseEndianness(ReadRawBytes<int>());
 
        public int ReadUInt31()
        {
            int i = ReadInt32();
            if (i < 0)
                XmlExceptionHelper.ThrowInvalidBinaryFormat(_reader);
            return i;
        }
 
        public long ReadInt64()
            => BitConverter.IsLittleEndian ? ReadRawBytes<long>() : BinaryPrimitives.ReverseEndianness(ReadRawBytes<long>());
 
        public float ReadSingle()
        {
            float f = BinaryPrimitives.ReadSingleLittleEndian(GetBuffer(sizeof(float), out int offset).AsSpan(offset, sizeof(float)));
            Advance(sizeof(float));
            return f;
        }
 
        public double ReadDouble()
        {
            double d = BinaryPrimitives.ReadDoubleLittleEndian(GetBuffer(sizeof(double), out int offset).AsSpan(offset, sizeof(double)));
            Advance(sizeof(double));
            return d;
        }
 
        public decimal ReadDecimal()
        {
            if (BitConverter.IsLittleEndian)
            {
                return ReadRawBytes<decimal>();
            }
            else
            {
                byte[] buffer = GetBuffer(ValueHandleLength.Decimal, out int offset);
                ReadOnlySpan<byte> bytes = buffer.AsSpan(offset, sizeof(decimal));
                ReadOnlySpan<int> span =
                [
                    BinaryPrimitives.ReadInt32LittleEndian(bytes.Slice(8, 4)),
                    BinaryPrimitives.ReadInt32LittleEndian(bytes.Slice(12, 4)),
                    BinaryPrimitives.ReadInt32LittleEndian(bytes.Slice(4, 4)),
                    BinaryPrimitives.ReadInt32LittleEndian(bytes.Slice(0, 4))
                ];
 
                Advance(ValueHandleLength.Decimal);
                return new decimal(span);
            }
        }
 
        public UniqueId ReadUniqueId()
        {
            int offset;
            byte[] buffer = GetBuffer(ValueHandleLength.UniqueId, out offset);
            UniqueId uniqueId = new UniqueId(buffer, offset);
            Advance(ValueHandleLength.UniqueId);
            return uniqueId;
        }
 
        public DateTime ReadDateTime()
        {
            long value = 0;
            try
            {
                value = ReadInt64();
                return DateTime.FromBinary(value);
            }
            catch (ArgumentException exception)
            {
                throw XmlExceptionHelper.CreateConversionException(value.ToString(CultureInfo.InvariantCulture), "DateTime", exception);
            }
            catch (FormatException exception)
            {
                throw XmlExceptionHelper.CreateConversionException(value.ToString(CultureInfo.InvariantCulture), "DateTime", exception);
            }
            catch (OverflowException exception)
            {
                throw XmlExceptionHelper.CreateConversionException(value.ToString(CultureInfo.InvariantCulture), "DateTime", exception);
            }
        }
 
        public TimeSpan ReadTimeSpan()
        {
            long value = 0;
            try
            {
                value = ReadInt64();
                return TimeSpan.FromTicks(value);
            }
            catch (ArgumentException exception)
            {
                throw XmlExceptionHelper.CreateConversionException(value.ToString(CultureInfo.InvariantCulture), "TimeSpan", exception);
            }
            catch (FormatException exception)
            {
                throw XmlExceptionHelper.CreateConversionException(value.ToString(CultureInfo.InvariantCulture), "TimeSpan", exception);
            }
            catch (OverflowException exception)
            {
                throw XmlExceptionHelper.CreateConversionException(value.ToString(CultureInfo.InvariantCulture), "TimeSpan", exception);
            }
        }
 
        public Guid ReadGuid()
        {
            int offset;
            _ = GetBuffer(ValueHandleLength.Guid, out offset);
            Guid guid = GetGuid(offset);
            Advance(ValueHandleLength.Guid);
            return guid;
        }
 
        public string ReadUTF8String(int length)
        {
            int offset;
            _ = GetBuffer(length, out offset);
            char[] chars = GetCharBuffer(length);
            int charCount = GetChars(offset, length, chars);
            string value = new string(chars, 0, charCount);
            Advance(length);
            return value;
        }
 
        public void ReadRawArrayBytes<T>(Span<T> dst)
            where T : unmanaged
        {
            ReadRawArrayBytes(MemoryMarshal.AsBytes(dst));
        }
 
        public void ReadRawArrayBytes(Span<byte> dst)
        {
            if (dst.Length > 0)
            {
                GetBuffer(dst.Length, out _offset)
                    .AsSpan(_offset, dst.Length)
                    .CopyTo(dst);
 
                Advance(dst.Length);
            }
        }
 
        private char[] GetCharBuffer(int count)
        {
            if (count > 1024)
                return new char[count];
            if (_chars == null || _chars.Length < count)
                _chars = new char[count];
            return _chars;
        }
 
        private int GetChars(int offset, int length, char[] chars)
        {
            byte[] buffer = _buffer;
            for (int i = 0; i < length; i++)
            {
                byte b = buffer[offset + i];
                if (b >= 0x80)
                    return i + XmlConverter.ToChars(buffer, offset + i, length - i, chars, i);
                chars[i] = (char)b;
            }
            return length;
        }
 
        private int GetChars(int offset, int length, char[] chars, int charOffset)
        {
            byte[] buffer = _buffer;
            for (int i = 0; i < length; i++)
            {
                byte b = buffer[offset + i];
                if (b >= 0x80)
                    return i + XmlConverter.ToChars(buffer, offset + i, length - i, chars, charOffset + i);
                chars[charOffset + i] = (char)b;
            }
            return length;
        }
 
        public string GetString(int offset, int length)
        {
            char[] chars = GetCharBuffer(length);
            int charCount = GetChars(offset, length, chars);
            return new string(chars, 0, charCount);
        }
 
        public string GetUnicodeString(int offset, int length)
        {
            return XmlConverter.ToStringUnicode(_buffer, offset, length);
        }
 
        public string GetString(int offset, int length, XmlNameTable nameTable)
        {
            char[] chars = GetCharBuffer(length);
            int charCount = GetChars(offset, length, chars);
            return nameTable.Add(chars, 0, charCount);
        }
 
        public int GetEscapedChars(int offset, int length, char[] chars)
        {
            byte[] buffer = _buffer;
            int charCount = 0;
            int textOffset = offset;
            int offsetMax = offset + length;
            while (true)
            {
                while (offset < offsetMax && IsAttrChar(buffer[offset]))
                    offset++;
                charCount += GetChars(textOffset, offset - textOffset, chars, charCount);
                if (offset == offsetMax)
                    break;
                textOffset = offset;
                if (buffer[offset] == '&')
                {
                    while (offset < offsetMax && buffer[offset] != ';')
                        offset++;
                    offset++;
                    int ch = GetCharEntity(textOffset, offset - textOffset);
                    textOffset = offset;
                    if (ch > char.MaxValue)
                    {
                        SurrogateChar surrogate = new SurrogateChar(ch);
                        chars[charCount++] = surrogate.HighChar;
                        chars[charCount++] = surrogate.LowChar;
                    }
                    else
                    {
                        chars[charCount++] = (char)ch;
                    }
                }
                else if (buffer[offset] == '\n' || buffer[offset] == '\t')
                {
                    chars[charCount++] = ' ';
                    offset++;
                    textOffset = offset;
                }
                else // '\r'
                {
                    chars[charCount++] = ' ';
                    offset++;
 
                    if (offset < offsetMax && buffer[offset] == '\n')
                        offset++;
 
                    textOffset = offset;
                }
            }
            return charCount;
        }
 
        private static bool IsAttrChar(int ch)
        {
            switch (ch)
            {
                case '&':
                case '\r':
                case '\t':
                case '\n':
                    return false;
 
                default:
                    return true;
            }
        }
 
        public string GetEscapedString(int offset, int length)
        {
            char[] chars = GetCharBuffer(length);
            int charCount = GetEscapedChars(offset, length, chars);
            return new string(chars, 0, charCount);
        }
 
        public string GetEscapedString(int offset, int length, XmlNameTable nameTable)
        {
            char[] chars = GetCharBuffer(length);
            int charCount = GetEscapedChars(offset, length, chars);
            return nameTable.Add(chars, 0, charCount);
        }
 
        private int GetLessThanCharEntity(int offset, int length)
        {
            byte[] buffer = _buffer;
            if (length != 4 ||
                buffer[offset + 1] != (byte)'l' ||
                buffer[offset + 2] != (byte)'t')
            {
                XmlExceptionHelper.ThrowInvalidCharRef(_reader);
            }
            return (int)'<';
        }
 
        private int GetGreaterThanCharEntity(int offset, int length)
        {
            byte[] buffer = _buffer;
            if (length != 4 ||
                buffer[offset + 1] != (byte)'g' ||
                buffer[offset + 2] != (byte)'t')
            {
                XmlExceptionHelper.ThrowInvalidCharRef(_reader);
            }
            return (int)'>';
        }
 
        private int GetQuoteCharEntity(int offset, int length)
        {
            byte[] buffer = _buffer;
            if (length != 6 ||
                buffer[offset + 1] != (byte)'q' ||
                buffer[offset + 2] != (byte)'u' ||
                buffer[offset + 3] != (byte)'o' ||
                buffer[offset + 4] != (byte)'t')
            {
                XmlExceptionHelper.ThrowInvalidCharRef(_reader);
            }
            return (int)'"';
        }
 
        private int GetAmpersandCharEntity(int offset, int length)
        {
            byte[] buffer = _buffer;
            if (length != 5 ||
                buffer[offset + 1] != (byte)'a' ||
                buffer[offset + 2] != (byte)'m' ||
                buffer[offset + 3] != (byte)'p')
            {
                XmlExceptionHelper.ThrowInvalidCharRef(_reader);
            }
            return (int)'&';
        }
 
        private int GetApostropheCharEntity(int offset, int length)
        {
            byte[] buffer = _buffer;
            if (length != 6 ||
                buffer[offset + 1] != (byte)'a' ||
                buffer[offset + 2] != (byte)'p' ||
                buffer[offset + 3] != (byte)'o' ||
                buffer[offset + 4] != (byte)'s')
            {
                XmlExceptionHelper.ThrowInvalidCharRef(_reader);
            }
            return (int)'\'';
        }
 
        private int GetDecimalCharEntity(int offset, int length)
        {
            byte[] buffer = _buffer;
            Debug.Assert(buffer[offset + 0] == '&');
            Debug.Assert(buffer[offset + 1] == '#');
            Debug.Assert(buffer[offset + length - 1] == ';');
            int value = 0;
            for (int i = 2; i < length - 1; i++)
            {
                byte ch = buffer[offset + i];
                if (ch < (byte)'0' || ch > (byte)'9')
                    XmlExceptionHelper.ThrowInvalidCharRef(_reader);
                value = value * 10 + (ch - '0');
                if (value > SurrogateChar.MaxValue)
                    XmlExceptionHelper.ThrowInvalidCharRef(_reader);
            }
            return value;
        }
 
        private int GetHexCharEntity(int offset, int length)
        {
            byte[] buffer = _buffer;
            Debug.Assert(buffer[offset + 0] == '&');
            Debug.Assert(buffer[offset + 1] == '#');
            Debug.Assert(buffer[offset + 2] == 'x');
            Debug.Assert(buffer[offset + length - 1] == ';');
            int value = 0;
            for (int i = 3; i < length - 1; i++)
            {
                byte ch = buffer[offset + i];
                int digit = HexConverter.FromChar(ch);
                if (digit == 0xFF)
                    XmlExceptionHelper.ThrowInvalidCharRef(_reader);
                Debug.Assert(digit >= 0 && digit < 16);
                value = value * 16 + digit;
                if (value > SurrogateChar.MaxValue)
                    XmlExceptionHelper.ThrowInvalidCharRef(_reader);
            }
            return value;
        }
 
        public int GetCharEntity(int offset, int length)
        {
            if (length < 3)
                XmlExceptionHelper.ThrowInvalidCharRef(_reader);
            byte[] buffer = _buffer;
            Debug.Assert(buffer[offset] == '&');
            Debug.Assert(buffer[offset + length - 1] == ';');
            switch (buffer[offset + 1])
            {
                case (byte)'l':
                    return GetLessThanCharEntity(offset, length);
                case (byte)'g':
                    return GetGreaterThanCharEntity(offset, length);
                case (byte)'a':
                    if (buffer[offset + 2] == (byte)'m')
                        return GetAmpersandCharEntity(offset, length);
                    else
                        return GetApostropheCharEntity(offset, length);
                case (byte)'q':
                    return GetQuoteCharEntity(offset, length);
                case (byte)'#':
                    if (buffer[offset + 2] == (byte)'x')
                        return GetHexCharEntity(offset, length);
                    else
                        return GetDecimalCharEntity(offset, length);
                default:
                    XmlExceptionHelper.ThrowInvalidCharRef(_reader);
                    return 0;
            }
        }
 
 
        public bool IsWhitespaceKey(int key)
        {
            string s = GetDictionaryString(key).Value;
            return XmlConverter.IsWhitespace(s);
        }
 
        public bool IsWhitespaceUTF8(int offset, int length)
        {
            return XmlConverter.IsWhitespace(_buffer.AsSpan(offset, length));
        }
 
        public bool IsWhitespaceUnicode(int offset, int length)
        {
            for (int i = 0; i < length; i += sizeof(char))
            {
                char ch = (char)GetInt16(offset + i);
                if (!XmlConverter.IsWhitespace(ch))
                    return false;
            }
            return true;
        }
 
        public bool Equals2(int key1, int key2, XmlBufferReader bufferReader2)
        {
            // If the keys aren't from the same dictionary, they still might be the same
            if (key1 == key2)
                return true;
            else
                return GetDictionaryString(key1).Value == bufferReader2.GetDictionaryString(key2).Value;
        }
 
        public bool Equals2(int key1, XmlDictionaryString xmlString2)
        {
            if ((key1 & 1) == 0 && xmlString2.Dictionary == _dictionary)
                return xmlString2.Key == (key1 >> 1);
            else
                return GetDictionaryString(key1).Value == xmlString2.Value;
        }
 
        public bool Equals2(int offset1, int length1, byte[] buffer2)
        {
            int length2 = buffer2.Length;
            if (length1 != length2)
                return false;
            byte[] buffer1 = _buffer;
            for (int i = 0; i < length1; i++)
            {
                if (buffer1[offset1 + i] != buffer2[i])
                    return false;
            }
            return true;
        }
 
        public bool Equals2(int offset1, int length1, XmlBufferReader bufferReader2, int offset2, int length2)
        {
            if (length1 != length2)
                return false;
            byte[] buffer1 = _buffer;
            byte[] buffer2 = bufferReader2._buffer;
            for (int i = 0; i < length1; i++)
            {
                if (buffer1[offset1 + i] != buffer2[offset2 + i])
                    return false;
            }
            return true;
        }
 
        public bool Equals2(int offset1, int length1, int offset2, int length2)
        {
            if (length1 != length2)
                return false;
            if (offset1 == offset2)
                return true;
            byte[] buffer = _buffer;
            for (int i = 0; i < length1; i++)
            {
                if (buffer[offset1 + i] != buffer[offset2 + i])
                    return false;
            }
            return true;
        }
 
        public unsafe bool Equals2(int offset1, int length1, string s2)
        {
            int byteLength = length1;
            int charLength = s2.Length;
 
            // N Unicode chars will be represented in at least N bytes, but
            // no more than N * 3 bytes.  If the byte count falls outside of this
            // range, then the strings cannot be equal.
            if (byteLength < charLength || byteLength > charLength * maxBytesPerChar)
                return false;
 
            byte[] buffer = _buffer;
            if (length1 < 8)
            {
                int length = Math.Min(byteLength, charLength);
                int offset = offset1;
                for (int i = 0; i < length; i++)
                {
                    byte b = buffer[offset + i];
                    if (b >= 0x80)
                        return XmlConverter.ToString(buffer, offset1, length1) == s2;
                    if (s2[i] != (char)b)
                        return false;
                }
                return byteLength == charLength;
            }
            else
            {
                int length = Math.Min(byteLength, charLength);
                fixed (byte* _pb = &buffer[offset1])
                {
                    byte* pb = _pb;
                    byte* pbMax = pb + length;
                    fixed (char* _pch = s2)
                    {
                        char* pch = _pch;
                        // Try to do the fast comparison in ASCII space
                        int t = 0;
                        while (pb < pbMax && *pb < 0x80)
                        {
                            t = *pb - (byte)*pch;
                            // The code generated is better if we break out then return
                            if (t != 0)
                                break;
                            pb++;
                            pch++;
                        }
                        if (t != 0)
                            return false;
                        if (pb == pbMax)
                            return (byteLength == charLength);
                    }
                }
                return XmlConverter.ToString(buffer, offset1, length1) == s2;
            }
        }
 
        public int Compare(int offset1, int length1, int offset2, int length2)
        {
            byte[] buffer = _buffer;
            int length = Math.Min(length1, length2);
            for (int i = 0; i < length; i++)
            {
                int s = buffer[offset1 + i] - buffer[offset2 + i];
                if (s != 0)
                    return s;
            }
            return length1 - length2;
        }
 
        public byte GetByte(int offset)
        {
            return _buffer[offset];
        }
 
        public int GetInt8(int offset)
        {
            return (sbyte)GetByte(offset);
        }
 
        private unsafe T ReadRawBytes<T>() where T : unmanaged
        {
            ReadOnlySpan<byte> buffer = GetBuffer(sizeof(T), out int offset)
                .AsSpan(offset, sizeof(T));
            T value = MemoryMarshal.Read<T>(buffer);
 
            Advance(sizeof(T));
            return value;
        }
 
        private unsafe T ReadRawBytes<T>(int offset) where T : unmanaged
            => MemoryMarshal.Read<T>(_buffer.AsSpan(offset, sizeof(T)));
 
        public int GetInt16(int offset)
            => BitConverter.IsLittleEndian ? ReadRawBytes<short>(offset) : BinaryPrimitives.ReverseEndianness(ReadRawBytes<short>(offset));
 
        public int GetInt32(int offset)
            => BitConverter.IsLittleEndian ? ReadRawBytes<int>(offset) : BinaryPrimitives.ReverseEndianness(ReadRawBytes<int>(offset));
 
        public long GetInt64(int offset)
            => BitConverter.IsLittleEndian ? ReadRawBytes<long>(offset) : BinaryPrimitives.ReverseEndianness(ReadRawBytes<long>(offset));
 
        public ulong GetUInt64(int offset)
            => (ulong)GetInt64(offset);
 
        public float GetSingle(int offset)
            => BinaryPrimitives.ReadSingleLittleEndian(_buffer.AsSpan(offset, sizeof(float)));
 
        public double GetDouble(int offset)
            => BinaryPrimitives.ReadDoubleLittleEndian(_buffer.AsSpan(offset, sizeof(double)));
 
        public decimal GetDecimal(int offset)
        {
            if (BitConverter.IsLittleEndian)
            {
                return ReadRawBytes<decimal>(offset);
            }
            else
            {
                ReadOnlySpan<byte> bytes = _buffer.AsSpan(offset, sizeof(decimal));
                ReadOnlySpan<int> span =
                [
                    BinaryPrimitives.ReadInt32LittleEndian(bytes.Slice(8, 4)),
                    BinaryPrimitives.ReadInt32LittleEndian(bytes.Slice(12, 4)),
                    BinaryPrimitives.ReadInt32LittleEndian(bytes.Slice(4, 4)),
                    BinaryPrimitives.ReadInt32LittleEndian(bytes.Slice(0, 4))
                ];
 
                return new decimal(span);
            }
        }
 
        public UniqueId GetUniqueId(int offset)
            => new UniqueId(_buffer, offset);
 
        public Guid GetGuid(int offset)
            => new Guid(_buffer.AsSpan(offset, ValueHandleLength.Guid));
 
        public void GetBase64(int srcOffset, byte[] buffer, int dstOffset, int count)
        {
            System.Buffer.BlockCopy(_buffer, srcOffset, buffer, dstOffset, count);
        }
 
 
        public XmlBinaryNodeType GetNodeType()
        {
            return (XmlBinaryNodeType)GetByte();
        }
 
        public void SkipNodeType()
        {
            SkipByte();
        }
 
        public object[] GetList(int offset, int count)
        {
            int bufferOffset = this.Offset;
            this.Offset = offset;
            try
            {
                object[] objects = new object[count];
                for (int i = 0; i < count; i++)
                {
                    XmlBinaryNodeType nodeType = GetNodeType();
                    SkipNodeType();
                    Debug.Assert(nodeType != XmlBinaryNodeType.StartListText);
                    ReadValue(nodeType, _listValue!);
                    objects[i] = _listValue!.ToObject();
                }
                return objects;
            }
            finally
            {
                this.Offset = bufferOffset;
            }
        }
 
 
        public XmlDictionaryString GetDictionaryString(int key)
        {
            IXmlDictionary keyDictionary;
            if ((key & 1) != 0)
            {
                keyDictionary = _session!;
            }
            else
            {
                keyDictionary = _dictionary!;
            }
 
            XmlDictionaryString? s;
            if (!keyDictionary.TryLookup(key >> 1, out s))
                XmlExceptionHelper.ThrowInvalidBinaryFormat(_reader);
 
            return s;
        }
 
        public int ReadDictionaryKey()
        {
            int key = ReadMultiByteUInt31();
            if ((key & 1) != 0)
            {
                if (_session == null)
                    XmlExceptionHelper.ThrowInvalidBinaryFormat(_reader);
                int sessionKey = (key >> 1);
                if (!_session.TryLookup(sessionKey, out _))
                {
                    if (sessionKey < XmlDictionaryString.MinKey || sessionKey > XmlDictionaryString.MaxKey)
                        XmlExceptionHelper.ThrowXmlDictionaryStringIDOutOfRange(_reader);
                    XmlExceptionHelper.ThrowXmlDictionaryStringIDUndefinedSession(_reader, sessionKey);
                }
            }
            else
            {
                if (_dictionary == null)
                    XmlExceptionHelper.ThrowInvalidBinaryFormat(_reader);
                int staticKey = (key >> 1);
                if (!_dictionary.TryLookup(staticKey, out _))
                {
                    if (staticKey < XmlDictionaryString.MinKey || staticKey > XmlDictionaryString.MaxKey)
                        XmlExceptionHelper.ThrowXmlDictionaryStringIDOutOfRange(_reader);
                    XmlExceptionHelper.ThrowXmlDictionaryStringIDUndefinedStatic(_reader, staticKey);
                }
            }
 
            return key;
        }
 
        public void ReadValue(XmlBinaryNodeType nodeType, ValueHandle value)
        {
            switch (nodeType)
            {
                case XmlBinaryNodeType.EmptyText:
                    value.SetValue(ValueHandleType.Empty);
                    break;
                case XmlBinaryNodeType.ZeroText:
                    value.SetValue(ValueHandleType.Zero);
                    break;
                case XmlBinaryNodeType.OneText:
                    value.SetValue(ValueHandleType.One);
                    break;
                case XmlBinaryNodeType.TrueText:
                    value.SetValue(ValueHandleType.True);
                    break;
                case XmlBinaryNodeType.FalseText:
                    value.SetValue(ValueHandleType.False);
                    break;
                case XmlBinaryNodeType.BoolText:
                    value.SetValue(ReadUInt8() != 0 ? ValueHandleType.True : ValueHandleType.False);
                    break;
                case XmlBinaryNodeType.Chars8Text:
                    ReadValue(value, ValueHandleType.UTF8, ReadUInt8());
                    break;
                case XmlBinaryNodeType.Chars16Text:
                    ReadValue(value, ValueHandleType.UTF8, ReadUInt16());
                    break;
                case XmlBinaryNodeType.Chars32Text:
                    ReadValue(value, ValueHandleType.UTF8, ReadUInt31());
                    break;
                case XmlBinaryNodeType.UnicodeChars8Text:
                    ReadUnicodeValue(value, ReadUInt8());
                    break;
                case XmlBinaryNodeType.UnicodeChars16Text:
                    ReadUnicodeValue(value, ReadUInt16());
                    break;
                case XmlBinaryNodeType.UnicodeChars32Text:
                    ReadUnicodeValue(value, ReadUInt31());
                    break;
                case XmlBinaryNodeType.Bytes8Text:
                    ReadValue(value, ValueHandleType.Base64, ReadUInt8());
                    break;
                case XmlBinaryNodeType.Bytes16Text:
                    ReadValue(value, ValueHandleType.Base64, ReadUInt16());
                    break;
                case XmlBinaryNodeType.Bytes32Text:
                    ReadValue(value, ValueHandleType.Base64, ReadUInt31());
                    break;
                case XmlBinaryNodeType.DictionaryText:
                    value.SetDictionaryValue(ReadDictionaryKey());
                    break;
                case XmlBinaryNodeType.UniqueIdText:
                    ReadValue(value, ValueHandleType.UniqueId, ValueHandleLength.UniqueId);
                    break;
                case XmlBinaryNodeType.GuidText:
                    ReadValue(value, ValueHandleType.Guid, ValueHandleLength.Guid);
                    break;
                case XmlBinaryNodeType.DecimalText:
                    ReadValue(value, ValueHandleType.Decimal, ValueHandleLength.Decimal);
                    break;
                case XmlBinaryNodeType.Int8Text:
                    ReadValue(value, ValueHandleType.Int8, ValueHandleLength.Int8);
                    break;
                case XmlBinaryNodeType.Int16Text:
                    ReadValue(value, ValueHandleType.Int16, ValueHandleLength.Int16);
                    break;
                case XmlBinaryNodeType.Int32Text:
                    ReadValue(value, ValueHandleType.Int32, ValueHandleLength.Int32);
                    break;
                case XmlBinaryNodeType.Int64Text:
                    ReadValue(value, ValueHandleType.Int64, ValueHandleLength.Int64);
                    break;
                case XmlBinaryNodeType.UInt64Text:
                    ReadValue(value, ValueHandleType.UInt64, ValueHandleLength.UInt64);
                    break;
                case XmlBinaryNodeType.FloatText:
                    ReadValue(value, ValueHandleType.Single, ValueHandleLength.Single);
                    break;
                case XmlBinaryNodeType.DoubleText:
                    ReadValue(value, ValueHandleType.Double, ValueHandleLength.Double);
                    break;
                case XmlBinaryNodeType.TimeSpanText:
                    ReadValue(value, ValueHandleType.TimeSpan, ValueHandleLength.TimeSpan);
                    break;
                case XmlBinaryNodeType.DateTimeText:
                    ReadValue(value, ValueHandleType.DateTime, ValueHandleLength.DateTime);
                    break;
                case XmlBinaryNodeType.StartListText:
                    ReadList(value);
                    break;
                case XmlBinaryNodeType.QNameDictionaryText:
                    ReadQName(value);
                    break;
                default:
                    XmlExceptionHelper.ThrowInvalidBinaryFormat(_reader);
                    break;
            }
        }
 
        private void ReadValue(ValueHandle value, ValueHandleType type, int length)
        {
            int offset = ReadBytes(length);
            value.SetValue(type, offset, length);
        }
 
        private void ReadUnicodeValue(ValueHandle value, int length)
        {
            if ((length & 1) != 0)
                XmlExceptionHelper.ThrowInvalidBinaryFormat(_reader);
            ReadValue(value, ValueHandleType.Unicode, length);
        }
 
        private void ReadList(ValueHandle value)
        {
            _listValue ??= new ValueHandle(this);
            int count = 0;
            int offset = this.Offset;
            while (true)
            {
                XmlBinaryNodeType nodeType = GetNodeType();
                SkipNodeType();
                if (nodeType == XmlBinaryNodeType.StartListText)
                    XmlExceptionHelper.ThrowInvalidBinaryFormat(_reader);
                if (nodeType == XmlBinaryNodeType.EndListText)
                    break;
                ReadValue(nodeType, _listValue);
                count++;
            }
            value.SetValue(ValueHandleType.List, offset, count);
        }
 
 
        public void ReadQName(ValueHandle value)
        {
            int prefix = ReadUInt8();
            if (prefix >= 26)
                XmlExceptionHelper.ThrowInvalidBinaryFormat(_reader);
            int key = ReadDictionaryKey();
            value.SetQNameValue(prefix, key);
        }
 
        public int[] GetRows()
        {
            if (_buffer == null)
            {
                return new int[1] { 0 };
            }
 
            List<int> list = new List<int>();
            list.Add(_offsetMin);
            for (int i = _offsetMin; i < _offsetMax; i++)
            {
                if (_buffer[i] == (byte)13 || _buffer[i] == (byte)10)
                {
                    if (i + 1 < _offsetMax && _buffer[i + 1] == (byte)10)
                        i++;
                    list.Add(i + 1);
                }
            }
            return list.ToArray();
        }
    }
}