/// <summary>Decodes an encoded value from an <paramref name="input"/> /// stream.</summary> /// <param name="input">An input stream to decode.</param> /// <returns>A decoded value.</returns> /// <exception cref="ArgumentException">Thrown when a given /// <paramref name="input"/> stream is not readable.</exception> /// <exception cref="DecodingException">Thrown when a binary /// representation of an <paramref name="input"/> stream is not a valid /// Bencodex encoding.</exception> public IValue Decode(Stream input) { if (!input.CanRead) { throw new ArgumentException( "stream cannot be read", nameof(input) ); } var buffer = new ByteChunkQueue(); IValue value = Decode(buffer, input); if (buffer.Empty) { buffer.ReadFrom(input, 1); } if (!buffer.Empty) { long offset = input.Position; throw new DecodingException( $"an unexpected byte at {offset - buffer.ByteLength}: " + $"0x{buffer.FirstByte:x}" ); } return(value); }
private byte[] DecodeBinary(ByteChunkQueue buffer, Stream input) { const byte colon = 0x3a; // ':' long length = DecodeDigits(colon, buffer, input, long.Parse); if (length < 1) { return(new byte[0]); } byte[] popped = buffer.Pop(length); if (popped.LongLength < length) { byte[] result = new byte[length]; popped.CopyTo(result, 0); // FIXME: These Int64 to Int32 casts should be corrected. input.Read( result, (int)popped.LongLength, (int)(length - popped.LongLength) ); return(result); } return(popped); }
private string DecodeText(ByteChunkQueue buffer, Stream input) { byte[] singleByteBuffer = buffer.Pop(1); if (singleByteBuffer.Length < 1) { throw new DecodingException( $"expected 'u' (0x75) at {input.Position - 1}, " + "but the stream terminates" ); } if (singleByteBuffer[0] != 0x75) { throw new DecodingException( $"expected 'u' (0x75) at {input.Position - 1}, " + $"but 0x{singleByteBuffer[0]:x} is given" ); } long pos = input.Position; byte[] utf8 = DecodeBinary(buffer, input); try { return(Encoding.UTF8.GetString(utf8)); } catch (ArgumentException e) { throw new DecodingException( $"expected a UTF-8 sequence at {pos}", e ); } }
public void EdgeCase1() { var q = new ByteChunkQueue(); q.Append(new byte[] { 1, 2, 3, 4 }); byte[] popped = q.Pop(4); Assert.Equal(new byte[] { 1, 2, 3, 4 }, popped); Assert.EndsWith("[]", q.ToString()); }
private T DecodeDigits <T>( byte terminator, ByteChunkQueue buffer, Stream input, Func <string, T> converter ) { if (buffer.Empty) { int read = buffer.ReadFrom(input, 1); if (read < 1) { throw new DecodingException( $"expected one or more digits at {input.Position}, " + "but the byte stream terminates" ); } } long pos; while ((pos = buffer.IndexOf(terminator)) < 0) { int read = buffer.ReadFrom(input, 8); if (read < 0) { throw new DecodingException( $"expected a byte 0x{terminator:x} at " + $"{input.Position}, but the byte stream terminates" ); } } byte[] digitBytes = buffer.Pop(pos); if (!digitBytes.All(b => b >= 0x30 && b < 0x40)) { long digitsOffset = input.Position - buffer.ByteLength - digitBytes.LongLength; throw new DecodingException( $"expected 10-base digits at {digitsOffset}: " + BitConverter.ToString(digitBytes) ); } buffer.Pop(1); // pop terminator string digits = Encoding.ASCII.GetString(digitBytes); return(converter(digits)); }
public void EdgeCase2() { var q = new ByteChunkQueue(); q.Append(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }); byte[] popped = q.Pop(7); Assert.Equal(new byte[] { 1, 2, 3, 4, 5, 6, 7 }, popped); Assert.EndsWith("[01-02-03-04-05-06-07|08]", q.ToString()); q.Append(new byte[] { 9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10 }); Assert.EndsWith("[01-02-03-04-05-06-07|08 09-0A-0B-0C-0D-0E-0F-10]", q.ToString()); popped = q.Pop(1); Assert.Equal(new byte[] { 8 }, popped); Assert.EndsWith("[09-0A-0B-0C-0D-0E-0F-10]", q.ToString()); popped = q.Pop(1); Assert.Equal(new byte[] { 9 }, popped); Assert.EndsWith("[09|0A-0B-0C-0D-0E-0F-10]", q.ToString()); }
private IValue Decode(ByteChunkQueue buffer, Stream input) { if (buffer.Empty) { buffer.ReadFrom(input, 1); } long pos = input.Position - buffer.ByteLength; switch (buffer.FirstByte) { case null: throw new DecodingException( $"stream terminates unexpectedly at {pos}" ); case 0x6e: // 'n' buffer.Pop(1); return(default(Null)); case 0x74: // 't' buffer.Pop(1); return(new Bencodex.Types.Boolean(true)); case 0x66: // 'f' buffer.Pop(1); return(new Bencodex.Types.Boolean(false)); case 0x69: // 'i' buffer.Pop(1); if (buffer.Empty) { buffer.ReadFrom(input, 1); } bool negative = false; if (buffer.FirstByte == 0x2d) // '-' { buffer.Pop(1); negative = true; } const byte e = 0x65; // 'e' BigInteger integer = DecodeDigits(e, buffer, input, BigInteger.Parse); return(new Integer(negative ? -integer : integer)); case 0x75: // 'u' string text = DecodeText(buffer, input); return(new Text(text)); case 0x6c: // 'l' buffer.Pop(1); var elements = new List <IValue>(); while (true) { if (buffer.Empty) { buffer.ReadFrom(input, 1); } if (buffer.FirstByte == 0x65) // 'e' { buffer.Pop(1); break; } IValue element = Decode(buffer, input); elements.Add(element); } return(new List(elements)); case 0x64: // 'd' buffer.Pop(1); var pairs = new List <KeyValuePair <IKey, IValue> >(); while (true) { if (buffer.Empty) { buffer.ReadFrom(input, 1); } byte?firstByte = buffer.FirstByte; if (firstByte == null) { throw new DecodingException( $"expected 'e' (0x65) at {pos}, but " + "the stream terminates unexpectedly" ); } if (firstByte == 0x65) // 'e' { buffer.Pop(1); break; } IKey key; if (firstByte == 0x75) // 'u' { string textKey = DecodeText(buffer, input); key = new Text(textKey); } else if (firstByte >= 0x30 && firstByte < 0x40) { byte[] binaryKey = DecodeBinary(buffer, input); key = new Binary(binaryKey); } else { throw new DecodingException( $"an unexpected byte 0x{firstByte:x} at {pos}" ); } IValue value = Decode(buffer, input); pairs.Add(new KeyValuePair <IKey, IValue>(key, value)); } return(new Dictionary(pairs)); 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' byte[] binary = DecodeBinary(buffer, input); return(new Binary(binary)); } throw new DecodingException( $"an unexpected byte 0x{buffer.FirstByte:x} at {pos}" ); }
public void TestByteChunkQueue() { var q = new ByteChunkQueue(); // {} Assert.Equal(0, q.ByteLength); Assert.True(q.Empty); Assert.Throws <ArgumentException>(() => q.Pop(0)); Assert.Throws <ArgumentException>(() => q.Pop(-1)); Assert.Empty(q.Pop(1)); Assert.Empty(q.Pop(2)); Assert.True(q.StartsWith(new byte[0])); Assert.False(q.StartsWith(new byte[] { 1, 2 })); Assert.Null(q.FirstByte); Assert.Equal(-1, q.IndexOf(1)); Assert.EndsWith("[]", q.ToString()); q.Append(new byte[] { 1, 2, 3, 4 }); // {1 2 3 4} Assert.Equal(4, q.ByteLength); Assert.False(q.Empty); Assert.True(q.StartsWith(new byte[0])); Assert.True(q.StartsWith(new byte[] { 1, 2 })); Assert.True(q.StartsWith(new byte[] { 1, 2, 3, 4 })); Assert.False(q.StartsWith(new byte[] { 1, 2, 3, 4, 5 })); Assert.False(q.StartsWith(new byte[] { 1, 3 })); Assert.Equal((byte)1, q.FirstByte); Assert.Equal(0, q.IndexOf(1)); Assert.Equal(2, q.IndexOf(3)); Assert.Equal(-1, q.IndexOf(5)); Assert.EndsWith("[01-02-03-04]", q.ToString()); byte[] popped = q.Pop(2); // {1 2 > 3 4} Assert.Equal(new byte[] { 1, 2 }, popped); Assert.Equal(2, q.ByteLength); Assert.False(q.Empty); Assert.True(q.StartsWith(new byte[0])); Assert.True(q.StartsWith(new byte[] { 3 })); Assert.True(q.StartsWith(new byte[] { 3, 4 })); Assert.False(q.StartsWith(new byte[] { 3, 4, 5 })); Assert.False(q.StartsWith(new byte[] { 1, 2 })); Assert.Equal((byte)3, q.FirstByte); Assert.Equal(-1, q.IndexOf(1)); Assert.Equal(0, q.IndexOf(3)); Assert.Equal(1, q.IndexOf(4)); Assert.Equal(-1, q.IndexOf(5)); Assert.EndsWith("[01-02|03-04]", q.ToString()); var input = new MemoryStream( new byte[] { 5, 6, 7, 8, 9, 10, 11, 12 } ); q.ReadFrom(input, 4); // {1 2 > 3 4, 5 6 7 8} Assert.Equal(6, q.ByteLength); Assert.False(q.Empty); Assert.True(q.StartsWith(new byte[0])); Assert.True(q.StartsWith(new byte[] { 3 })); Assert.True(q.StartsWith(new byte[] { 3, 4 })); Assert.True(q.StartsWith(new byte[] { 3, 4, 5 })); Assert.True(q.StartsWith(new byte[] { 3, 4, 5, 6, 7, 8 })); Assert.False(q.StartsWith(new byte[] { 3, 4, 5, 6, 7, 8, 9 })); Assert.False(q.StartsWith(new byte[] { 1, 2 })); Assert.Equal((byte)3, q.FirstByte); Assert.Equal(-1, q.IndexOf(1)); Assert.Equal(0, q.IndexOf(3)); Assert.Equal(4, q.IndexOf(7)); Assert.Equal(-1, q.IndexOf(9)); Assert.EndsWith("[01-02|03-04 05-06-07-08]", q.ToString()); popped = q.Pop(4); // {5 6 > 7 8} Assert.Equal(new byte[] { 3, 4, 5, 6 }, popped); Assert.Equal(2, q.ByteLength); Assert.False(q.Empty); Assert.True(q.StartsWith(new byte[0])); Assert.True(q.StartsWith(new byte[] { 7 })); Assert.True(q.StartsWith(new byte[] { 7, 8 })); Assert.False(q.StartsWith(new byte[] { 7, 8, 9 })); Assert.False(q.StartsWith(new byte[] { 1, 2 })); Assert.False(q.StartsWith(new byte[] { 3 })); Assert.Equal((byte)7, q.FirstByte); Assert.Equal(-1, q.IndexOf(3)); Assert.Equal(0, q.IndexOf(7)); Assert.Equal(1, q.IndexOf(8)); Assert.Equal(-1, q.IndexOf(9)); Assert.EndsWith("[05-06|07-08]", q.ToString()); q.ReadFrom(input, 3); // {5 6 > 7 8, 9 10 11} Assert.Equal(5, q.ByteLength); Assert.False(q.Empty); Assert.True(q.StartsWith(new byte[0])); Assert.True(q.StartsWith(new byte[] { 7 })); Assert.True(q.StartsWith(new byte[] { 7, 8 })); Assert.True(q.StartsWith(new byte[] { 7, 8, 9 })); Assert.True(q.StartsWith(new byte[] { 7, 8, 9, 10, 11 })); Assert.False(q.StartsWith(new byte[] { 7, 8, 9, 10, 11, 12 })); Assert.False(q.StartsWith(new byte[] { 1, 2 })); Assert.False(q.StartsWith(new byte[] { 3 })); Assert.Equal((byte)7, q.FirstByte); Assert.Equal(-1, q.IndexOf(3)); Assert.Equal(-1, q.IndexOf(6)); Assert.Equal(0, q.IndexOf(7)); Assert.Equal(2, q.IndexOf(9)); Assert.Equal(4, q.IndexOf(11)); Assert.Equal(-1, q.IndexOf(12)); Assert.EndsWith("[05-06|07-08 09-0A-0B]", q.ToString()); q.ReadFrom(input, 10); // {5 6 > 7 8, 9 10 11, 12} Assert.Equal(6, q.ByteLength); Assert.False(q.Empty); Assert.True(q.StartsWith(new byte[0])); Assert.True(q.StartsWith(new byte[] { 7 })); Assert.True(q.StartsWith(new byte[] { 7, 8, 9 })); Assert.True(q.StartsWith(new byte[] { 7, 8, 9, 10, 11 })); Assert.True(q.StartsWith(new byte[] { 7, 8, 9, 10, 11, 12 })); Assert.False(q.StartsWith(new byte[] { 7, 8, 9, 10, 11, 12, 13 })); Assert.False(q.StartsWith(new byte[] { 1, 2 })); Assert.False(q.StartsWith(new byte[] { 3 })); Assert.Equal((byte)7, q.FirstByte); Assert.Equal(-1, q.IndexOf(6)); Assert.Equal(0, q.IndexOf(7)); Assert.Equal(5, q.IndexOf(12)); Assert.Equal(-1, q.IndexOf(13)); Assert.EndsWith("[05-06|07-08 09-0A-0B 0C]", q.ToString()); q.Append(new byte[] { 13, 14 }); // {5 6 > 7 8, 9 10 11, 12, 13 14} Assert.Equal(8, q.ByteLength); Assert.False(q.Empty); Assert.True(q.StartsWith(new byte[0])); Assert.True(q.StartsWith(new byte[] { 7 })); Assert.True(q.StartsWith(new byte[] { 7, 8, 9 })); Assert.True(q.StartsWith(new byte[] { 7, 8, 9, 10, 11, 12 })); Assert.True(q.StartsWith(new byte[] { 7, 8, 9, 10, 11, 12, 13 })); Assert.False( q.StartsWith(new byte[] { 7, 8, 9, 10, 11, 12, 13, 14, 15 }) ); Assert.False(q.StartsWith(new byte[] { 1, 2 })); Assert.False(q.StartsWith(new byte[] { 3 })); Assert.Equal((byte)7, q.FirstByte); Assert.Equal(-1, q.IndexOf(6)); Assert.Equal(0, q.IndexOf(7)); Assert.Equal(7, q.IndexOf(14)); Assert.Equal(-1, q.IndexOf(15)); Assert.EndsWith("[05-06|07-08 09-0A-0B 0C 0D-0E]", q.ToString()); q.Append(new byte[] { 15 }); // {5 6 > 7 8, 9 10 11, 12, 13 14, 15} Assert.Equal(9, q.ByteLength); Assert.False(q.Empty); Assert.True(q.StartsWith(new byte[0])); Assert.True(q.StartsWith(new byte[] { 7 })); Assert.True(q.StartsWith(new byte[] { 7, 8, 9 })); Assert.True( q.StartsWith(new byte[] { 7, 8, 9, 10, 11, 12, 13, 14, 15 }) ); Assert.False( q.StartsWith(new byte[] { 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }) ); Assert.False(q.StartsWith(new byte[] { 1, 2 })); Assert.False(q.StartsWith(new byte[] { 3 })); Assert.Equal((byte)7, q.FirstByte); Assert.Equal(-1, q.IndexOf(6)); Assert.Equal(0, q.IndexOf(7)); Assert.Equal(8, q.IndexOf(15)); Assert.Equal(-1, q.IndexOf(16)); Assert.EndsWith("[05-06|07-08 09-0A-0B 0C 0D-0E 0F]", q.ToString()); popped = q.Pop(7); // {13 > 14, 15} Assert.Equal(new byte[] { 7, 8, 9, 10, 11, 12, 13 }, popped); Assert.Equal(2, q.ByteLength); Assert.False(q.Empty); Assert.True(q.StartsWith(new byte[0])); Assert.True(q.StartsWith(new byte[] { 14 })); Assert.True(q.StartsWith(new byte[] { 14, 15 })); Assert.False(q.StartsWith(new byte[] { 14, 15, 16 })); Assert.False(q.StartsWith(new byte[] { 1, 2 })); Assert.False(q.StartsWith(new byte[] { 7 })); Assert.False(q.StartsWith(new byte[] { 7, 8, 9 })); Assert.Equal((byte)14, q.FirstByte); Assert.Equal(-1, q.IndexOf(7)); Assert.Equal(-1, q.IndexOf(13)); Assert.Equal(0, q.IndexOf(14)); Assert.Equal(1, q.IndexOf(15)); Assert.Equal(-1, q.IndexOf(16)); Assert.EndsWith("[0D|0E 0F]", q.ToString()); popped = q.Pop(10); // {} Assert.Equal(new byte[] { 14, 15 }, popped); Assert.Equal(0, q.ByteLength); Assert.True(q.Empty); Assert.True(q.StartsWith(new byte[0])); Assert.False(q.StartsWith(new byte[] { 1, 2 })); Assert.False(q.StartsWith(new byte[] { 14 })); Assert.False(q.StartsWith(new byte[] { 14, 15 })); Assert.False(q.StartsWith(new byte[] { 14, 15, 16 })); Assert.Null(q.FirstByte); Assert.Equal(-1, q.IndexOf(13)); Assert.Equal(-1, q.IndexOf(15)); Assert.Equal(-1, q.IndexOf(16)); Assert.EndsWith("[]", q.ToString()); }