File: System\Xml\BinHexDecoder.cs
Web Access
Project: src\src\libraries\System.Private.Xml\src\System.Private.Xml.csproj (System.Private.Xml)
// 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.Diagnostics;
 
namespace System.Xml
{
    internal sealed class BinHexDecoder : IncrementalReadDecoder
    {
        //
        // Fields
        //
        private byte[]? _buffer;
        private int _startIndex;
        private int _curIndex;
        private int _endIndex;
        private bool _hasHalfByteCached;
        private byte _cachedHalfByte;
 
        //
        // IncrementalReadDecoder interface
        //
        internal override int DecodedCount
        {
            get
            {
                return _curIndex - _startIndex;
            }
        }
 
        internal override bool IsFull
        {
            get
            {
                return _curIndex == _endIndex;
            }
        }
 
        internal override int Decode(char[] chars, int startPos, int len)
        {
            ArgumentNullException.ThrowIfNull(chars);
 
            ArgumentOutOfRangeException.ThrowIfNegative(len);
            ArgumentOutOfRangeException.ThrowIfNegative(startPos);
            ArgumentOutOfRangeException.ThrowIfGreaterThan(len, chars.Length - startPos);
 
            if (len == 0)
            {
                return 0;
            }
 
            Decode(chars.AsSpan(startPos, len), _buffer.AsSpan(_curIndex, _endIndex - _curIndex),
                ref _hasHalfByteCached, ref _cachedHalfByte,
                out int charsDecoded, out int bytesDecoded);
 
            _curIndex += bytesDecoded;
            return charsDecoded;
        }
 
        internal override int Decode(string str, int startPos, int len)
        {
            ArgumentNullException.ThrowIfNull(str);
 
            ArgumentOutOfRangeException.ThrowIfNegative(len);
            ArgumentOutOfRangeException.ThrowIfNegative(startPos);
            ArgumentOutOfRangeException.ThrowIfGreaterThan(len, str.Length - startPos);
 
            if (len == 0)
            {
                return 0;
            }
 
            Decode(str.AsSpan(startPos, len), _buffer.AsSpan(_curIndex, _endIndex - _curIndex),
                ref _hasHalfByteCached, ref _cachedHalfByte,
                out int charsDecoded, out int bytesDecoded);
 
            _curIndex += bytesDecoded;
            return charsDecoded;
        }
 
        internal override void Reset()
        {
            _hasHalfByteCached = false;
            _cachedHalfByte = 0;
        }
 
        internal override void SetNextOutputBuffer(Array buffer, int index, int count)
        {
            Debug.Assert(buffer != null);
            Debug.Assert(count >= 0);
            Debug.Assert(index >= 0);
            Debug.Assert(buffer.Length - index >= count);
            Debug.Assert((buffer as byte[]) != null);
 
            _buffer = (byte[])buffer;
            _startIndex = index;
            _curIndex = index;
            _endIndex = index + count;
        }
 
        //
        // Static methods
        //
        public static byte[] Decode(ReadOnlySpan<char> chars, bool allowOddChars)
        {
            int len = chars.Length;
            if (len == 0)
            {
                return Array.Empty<byte>();
            }
 
            byte[] bytes = new byte[(len + 1) / 2];
            bool hasHalfByteCached = false;
            byte cachedHalfByte = 0;
 
            Decode(chars, bytes, ref hasHalfByteCached, ref cachedHalfByte, out _, out int bytesDecoded);
 
            if (hasHalfByteCached && !allowOddChars)
            {
                throw new XmlException(SR.Xml_InvalidBinHexValueOddCount, new string(chars));
            }
 
            if (bytesDecoded < bytes.Length)
            {
                Array.Resize(ref bytes, bytesDecoded);
            }
 
            return bytes;
        }
 
        //
        // Private methods
        //
 
        private static void Decode(ReadOnlySpan<char> chars,
                                   Span<byte> bytes,
                                   ref bool hasHalfByteCached, ref byte cachedHalfByte,
                                   out int charsDecoded, out int bytesDecoded)
        {
            int iByte = 0;
            int iChar = 0;
 
            for (; iChar < chars.Length; iChar++)
            {
                if ((uint)iByte >= (uint)bytes.Length)
                {
                    break; // ran out of space in the destination buffer
                }
 
                byte halfByte;
                char ch = chars[iChar];
 
                int val = HexConverter.FromChar(ch);
                if (val != 0xFF)
                {
                    halfByte = (byte)val;
                }
                else if (XmlCharType.IsWhiteSpace(ch))
                {
                    continue;
                }
                else
                {
                    throw new XmlException(SR.Xml_InvalidBinHexValue, chars.ToString());
                }
 
                if (hasHalfByteCached)
                {
                    bytes[iByte++] = (byte)((cachedHalfByte << 4) + halfByte);
                    hasHalfByteCached = false;
                }
                else
                {
                    cachedHalfByte = halfByte;
                    hasHalfByteCached = true;
                }
            }
 
            bytesDecoded = iByte;
            charsDecoded = iChar;
        }
    }
}