diff --git a/Bencodex/Decoder.cs b/Bencodex/Decoder.cs index 44621aa..838d52e 100644 --- a/Bencodex/Decoder.cs +++ b/Bencodex/Decoder.cs @@ -12,10 +12,8 @@ namespace Bencodex { internal sealed class Decoder { - private readonly byte[] _tinyBuffer = new byte[1]; private readonly Stream _stream; private byte _lastRead; - private bool _didBack; private int _offset; public Decoder(Stream stream) @@ -24,7 +22,6 @@ public Decoder(Stream stream) // with BufferedStream: stream = new BufferedStream(stream); _stream = stream; _lastRead = 0; - _didBack = false; _offset = 0; } @@ -95,21 +92,8 @@ public IValue Decode() return new Dictionary(builder.ToImmutable()); - case 0x30: // '0' - case 0x31: // '1' - case 0x32: // '2' - case 0x33: // '3' - case 0x34: // '4' - case 0x35: // '5' - case 0x36: // '6' - case 0x37: // '7' - case 0x38: // '8' - case 0x39: // '9' - Back(); + default: return ReadBinary(); - - case { } b: - throw new DecodingException($"An unexpected byte 0x{b:x} at {_offset - 1}."); } } @@ -123,107 +107,57 @@ public IValue Decode() case 0x75: // 'u': return ReadTextAfterPrefix(); - case 0x30: // '0' - case 0x31: // '1' - case 0x32: // '2' - case 0x33: // '3' - case 0x34: // '4' - case 0x35: // '5' - case 0x36: // '6' - case 0x37: // '7' - case 0x38: // '8' - case 0x39: // '9' - Back(); + default: return ReadBinary(); - - case { } b: - throw new DecodingException( - $"Expected a dictionary key, but got an unexpected byte 0x{b:x} at " + - $"{_offset - 1}." - ); } } - private byte[] Read(byte[] buffer) + /// + /// Fills given from the internal . + /// + /// The buffer to fill. + /// Thrown when the internal + /// terminates before is completely filled. + /// This is used only for decoding a or + /// a after the separator token ':' has been consumed. + /// + private void Read(byte[] buffer) { var length = buffer.Length; - if (_didBack) - { - buffer[0] = _lastRead; - length--; - } + int read = _stream.Read(buffer, 0, length); + _offset += read; - int correction = _didBack ? 1 : 0; - int read = _stream.Read(buffer, correction, length); if (read < length) { - Array.Resize(ref buffer, read + correction); - } - - _offset += read + correction; - if (buffer.Length > 0) - { - _lastRead = buffer[buffer.Length - 1]; + throw new DecodingException( + $"The byte stream terminates at {_offset}."); } - - _didBack = false; - return buffer; } private byte ReadByte() { - if (_didBack) - { - _didBack = false; - _offset++; - return _lastRead; - } - - int read = _stream.Read(_tinyBuffer, 0, 1); - if (read > 0) - { - _lastRead = _tinyBuffer[0]; - } - - _offset++; - _didBack = false; - return read == 0 + int read = _stream.ReadByte(); + _lastRead = read < 0 ? throw new DecodingException($"The byte stream terminates unexpectedly at {_offset}.") - : _tinyBuffer[0]; + : (byte)read; + _offset++; + return _lastRead; } // Checks end of stream. Should be called only once at the very end. private bool EndOfStream() { - if (_didBack) - { - return false; - } - - return _stream.Read(_tinyBuffer, 0, 1) == 0; - } - - private void Back() - { - if (_offset < 1) - { - throw new DecodingException( - "Unexpected internal error: failed to rewind the stream buffer." - ); - } - - _didBack = true; - _offset--; + return _stream.ReadByte() < 0; } // Reads the length portion for byte strings and unicode strings. - private int ReadLength() + private int ReadLength(bool peeked) { const byte colon = 0x3a; // ':' const int asciiZero = 0x30; // '0' int length = 0; - byte lastByte = ReadByte(); + byte lastByte = peeked ? _lastRead : ReadByte(); while (lastByte != colon) { #pragma warning disable SA1131 @@ -340,49 +274,30 @@ private BigInteger ReadInteger() } } - private (byte[] ByteArray, int OffsetAfterColon) ReadByteArray() - { - int length = ReadLength(); - if (length < 1) - { - return (Array.Empty(), _offset); - } - - int pos = _offset; - byte[] buffer = new byte[length]; - byte[] bytes = Read(buffer); - if (bytes.Length < length) - { - throw new DecodingException( - $"The byte stream terminates at {_offset} with insufficient " + - $"{length - bytes.Length} bytes." - ); - } - - return (bytes, pos); - } - private Binary ReadBinary() { - (byte[] bytes, _) = ReadByteArray(); - return new Binary(bytes); + int length = ReadLength(peeked: true); + byte[] buffer = new byte[length]; + Read(buffer); + return new Binary(buffer); } private Text ReadTextAfterPrefix() { - (byte[] bytes, int pos) = ReadByteArray(); + int start = _offset - 1; + int length = ReadLength(peeked: false); + byte[] buffer = new byte[length]; + Read(buffer); - string textContent; try { - textContent = Encoding.UTF8.GetString(bytes); + return new Text(Encoding.UTF8.GetString(buffer)); } catch (ArgumentException e) { - throw new DecodingException($"Expected a UTF-8 sequence at {pos}.", e); + throw new DecodingException( + $"Failed to decode {nameof(Text)} starting from {start}.", e); } - - return new Text(textContent); } } } diff --git a/CHANGES.md b/CHANGES.md index 3c983be..be2bddb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,8 +9,10 @@ To be released. - Fixed a bug where a wrongly encoded `byte[]` could be decoded into an `Integer`. [[#97]] - Optimized decoding `Integer`s both for speed and memory. [[#97]] + - Optimized for faster decoding on encoded `List`s and `Dictionary`s. [[#98]] [#97]: https://github.com/planetarium/bencodex.net/pull/97 +[#98]: https://github.com/planetarium/bencodex.net/pull/98 Version 0.13.0