From 89f0692e60fa12e5da4f76fe84bcd6f7f14c5c42 Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Sat, 5 Aug 2023 15:11:06 +0900 Subject: [PATCH] Fixed bugs for Integer decoding; some optimization --- Bencodex/Decoder.cs | 130 ++++++++++++++++++++++++++------------------ 1 file changed, 76 insertions(+), 54 deletions(-) diff --git a/Bencodex/Decoder.cs b/Bencodex/Decoder.cs index 9dfe2ef..44621aa 100644 --- a/Bencodex/Decoder.cs +++ b/Bencodex/Decoder.cs @@ -42,8 +42,6 @@ public IValue Decode() private IValue? DecodeValue() { - const byte e = 0x65; // 'e' - switch (ReadByte()) { case 0x65: // 'e' @@ -61,7 +59,7 @@ public IValue Decode() return new Bencodex.Types.Boolean(false); case 0x69: // 'i' - BigInteger integer = ReadDigits(e, BigInteger.Parse); + BigInteger integer = ReadInteger(); return new Integer(integer); case 0x75: // 'u' @@ -246,76 +244,100 @@ private int ReadLength() return length; } - private byte[] ReadDigits(byte delimiter) + /// + /// Reads the value portion of an encoded and + /// its end token 'e'. + /// + /// The buffer to fill. Its size may be adjusted + /// when necessary. + /// The number of bytes read until 'e' is encountered from the + /// internal . + /// + /// This is called only from after + /// a beginning token 'i' has been consumed. + /// + private int ReadDigits(ref byte[] buffer) { - const int defaultBufferSize = 10; - byte[] buffer = new byte[defaultBufferSize]; - - var b = ReadByte(); - bool minus = false; - if (b == 0x2d) // '-' + const byte e = 0x65; + int digitsLength = 0; + byte b = ReadByte(); + while (b != e) { - minus = true; + if (digitsLength >= buffer.Length) + { + Array.Resize(ref buffer, buffer.Length * 2); + } + + buffer[digitsLength] = b; + digitsLength++; b = ReadByte(); } - int digitsLength; - - if (minus) - { - buffer[0] = 0x2d; - buffer[1] = b; - digitsLength = 2; - } - else - { - buffer[0] = b; - digitsLength = 1; - } + return digitsLength; + } - byte lastByte = b; + /// + /// Reads the value portion of an encoded and + /// its end token 'e'. + /// + /// A corresponding to the + /// value portion of an encoded . + /// Thrown for any reason + /// where the byte array representing the value portion is + /// invalid. + private BigInteger ReadInteger() + { + const byte zero = 0x30; + const byte plus = 0x2b; + const byte minus = 0x2d; - while (lastByte != delimiter) + const int defaultBufferSize = 10; + byte[] buffer = new byte[defaultBufferSize]; + int length = ReadDigits(ref buffer); + + // Checks for invalid formats allowed by BigInteger.Parse below. + // - "": Handled by BigInteger.Parse. Not allowed. + // - "x": Handled by bigInteger.Parse. Non-digits aren't allowed. + // - "+x...": Starting with a '+'. + // - "0x...": Starting with a '0' without immediately terminating. + // - "-0...": Starting with a '-' followed by a '0'. + if (length >= 2) { -#pragma warning disable SA1131 - if (lastByte < 0x30 || 0x39 < lastByte) // not '0'-'9' + if (buffer[0] == plus) { throw new DecodingException( - $"Expected a digit (0x30-0x39), but got 0x{lastByte:x} at {_offset}." - ); + $"Encountered an unexpected byte 0x{plus:x} " + + $"at {_offset - length}"); } -#pragma warning restore SA1131 - - lastByte = ReadByte(); - if (digitsLength >= buffer.Length) + if (buffer[0] == zero) { - Array.Resize(ref buffer, buffer.Length * 2); + throw new DecodingException( + $"Encountered an unexpected byte 0x{buffer[1]:x} " + + $"at {_offset - length + 1}"); } - buffer[digitsLength] = lastByte; - digitsLength++; + if (buffer[0] == minus && buffer[1] == zero) + { + throw new DecodingException( + $"Encountered an unexpected byte 0x{buffer[1]:x}" + + $"at {_offset - length + 1}"); + } } - digitsLength--; - Array.Resize(ref buffer, digitsLength); - - return buffer; - } - - private T ReadDigits( - byte delimiter, - Func converter - ) - { - byte[] buffer = ReadDigits(delimiter); - var digits = new char[buffer.Length]; - for (int i = 0; i < buffer.Length; i++) + try { - digits[i] = (char)buffer[i]; + return BigInteger.Parse( + Encoding.ASCII.GetString(buffer, 0, length), + NumberStyles.AllowLeadingSign, + CultureInfo.InvariantCulture); + } + catch (Exception e) + { + throw new DecodingException( + $"Encountered an invalid encoded integer at {_offset - length}", + e); } - - return converter(new string(digits), CultureInfo.InvariantCulture); } private (byte[] ByteArray, int OffsetAfterColon) ReadByteArray()