/// <summary> /// Parses the next <see cref="BList"/> from the reader. /// </summary> /// <param name="reader">The reader to parse from.</param> /// <param name="cancellationToken"></param> /// <returns>The parsed <see cref="BList"/>.</returns> /// <exception cref="InvalidBencodeException{BList}">Invalid bencode.</exception> public override async ValueTask <BList> ParseAsync(PipeBencodeReader reader, CancellationToken cancellationToken = default) { if (reader == null) { throw new ArgumentNullException(nameof(reader)); } var startPosition = reader.Position; // Lists must start with 'l' if (await reader.ReadCharAsync(cancellationToken).ConfigureAwait(false) != 'l') { throw InvalidBencodeException <BList> .UnexpectedChar('l', reader.PreviousChar, startPosition); } var list = new BList(); // Loop until next character is the end character 'e' or end of stream while (await reader.PeekCharAsync(cancellationToken).ConfigureAwait(false) != 'e' && await reader.PeekCharAsync(cancellationToken).ConfigureAwait(false) != default) { // Decode next object in stream var bObject = await BencodeParser.ParseAsync(reader, cancellationToken).ConfigureAwait(false); list.Add(bObject); } if (await reader.ReadCharAsync(cancellationToken).ConfigureAwait(false) != 'e') { throw InvalidBencodeException <BList> .MissingEndChar(startPosition); } return(list); }
/// <summary> /// Parses the next <see cref="BString"/> from the reader. /// </summary> /// <param name="reader">The reader to parse from.</param> /// <param name="cancellationToken"></param> /// <returns>The parsed <see cref="BString"/>.</returns> /// <exception cref="InvalidBencodeException{BString}">Invalid bencode.</exception> /// <exception cref="UnsupportedBencodeException{BString}">The bencode is unsupported by this library.</exception> public override async ValueTask <BString> ParseAsync(PipeBencodeReader reader, CancellationToken cancellationToken = default) { if (reader == null) { throw new ArgumentNullException(nameof(reader)); } var startPosition = reader.Position; using (var memoryOwner = MemoryPool <char> .Shared.Rent(BString.LengthMaxDigits)) { var lengthString = memoryOwner.Memory; var lengthStringCount = 0; for (var c = await reader.ReadCharAsync(cancellationToken).ConfigureAwait(false); c != default && c.IsDigit(); c = await reader.ReadCharAsync(cancellationToken).ConfigureAwait(false)) { EnsureLengthStringBelowMaxLength(lengthStringCount, startPosition); lengthString.Span[lengthStringCount++] = c; } EnsurePreviousCharIsColon(reader.PreviousChar, reader.Position); var stringLength = ParseStringLength(lengthString.Span, lengthStringCount, startPosition); var bytes = new byte[stringLength]; var bytesRead = await reader.ReadAsync(bytes, cancellationToken).ConfigureAwait(false); EnsureExpectedBytesRead(bytesRead, stringLength, startPosition); return(new BString(bytes, Encoding)); } }
/// <summary> /// Parses the next <see cref="BDictionary"/> and its contained keys and values from the reader. /// </summary> /// <param name="reader">The reader to parse from.</param> /// <param name="cancellationToken"></param> /// <returns>The parsed <see cref="BDictionary"/>.</returns> /// <exception cref="InvalidBencodeException{BDictionary}">Invalid bencode.</exception> public override async ValueTask <BDictionary> ParseAsync(PipeBencodeReader reader, CancellationToken cancellationToken = default) { if (reader == null) { throw new ArgumentNullException(nameof(reader)); } var startPosition = reader.Position; // Dictionaries must start with 'd' if (await reader.ReadCharAsync(cancellationToken).ConfigureAwait(false) != 'd') { throw InvalidBencodeException <BDictionary> .UnexpectedChar('d', reader.PreviousChar, startPosition); } var dictionary = new BDictionary(); // Loop until next character is the end character 'e' or end of stream while (await reader.PeekCharAsync(cancellationToken).ConfigureAwait(false) != 'e' && await reader.PeekCharAsync(cancellationToken).ConfigureAwait(false) != default) { BString key; try { // Decode next string in stream as the key key = await BencodeParser.ParseAsync <BString>(reader, cancellationToken).ConfigureAwait(false); } catch (BencodeException ex) { throw InvalidException("Could not parse dictionary key. Keys must be strings.", ex, startPosition); } IBObject value; try { // Decode next object in stream as the value value = await BencodeParser.ParseAsync(reader, cancellationToken).ConfigureAwait(false); } catch (BencodeException ex) { throw InvalidException($"Could not parse dictionary value for the key '{key}'. There needs to be a value for each key.", ex, startPosition); } if (dictionary.ContainsKey(key)) { throw InvalidException($"The dictionary already contains the key '{key}'. Duplicate keys are not supported.", startPosition); } dictionary.Add(key, value); } if (await reader.ReadCharAsync(cancellationToken).ConfigureAwait(false) != 'e') { throw InvalidBencodeException <BDictionary> .MissingEndChar(startPosition); } return(dictionary); }
public async Task CanReadAsyncBeforeWrite() { var bytes = Encoding.UTF8.GetBytes("abc").AsMemory(); var(reader, writer) = new Pipe(); var bencodeReader = new PipeBencodeReader(reader); var readTask = bencodeReader.ReadCharAsync(); await writer.WriteAsync(bytes.Slice(0, 2)); writer.Complete(); var c = await readTask; c.Should().Be('a'); }
/// <summary> /// Parses the next <see cref="BNumber"/> from the reader. /// </summary> /// <param name="reader">The reader to parse from.</param> /// <param name="cancellationToken"></param> /// <returns>The parsed <see cref="BNumber"/>.</returns> /// <exception cref="InvalidBencodeException{BNumber}">Invalid bencode.</exception> /// <exception cref="UnsupportedBencodeException{BNumber}">The bencode is unsupported by this library.</exception> public override async ValueTask <BNumber> ParseAsync(PipeBencodeReader reader, CancellationToken cancellationToken = default) { if (reader == null) { throw new ArgumentNullException(nameof(reader)); } var startPosition = reader.Position; // Numbers must start with 'i' if (await reader.ReadCharAsync(cancellationToken).ConfigureAwait(false) != 'i') { throw InvalidBencodeException <BNumber> .UnexpectedChar('i', reader.PreviousChar, startPosition); } using var memoryOwner = MemoryPool <char> .Shared.Rent(BNumber.MaxDigits); var digits = memoryOwner.Memory; var digitCount = 0; for (var c = await reader.ReadCharAsync(cancellationToken).ConfigureAwait(false); c != default && c != 'e'; c = await reader.ReadCharAsync(cancellationToken).ConfigureAwait(false)) { digits.Span[digitCount++] = c; } if (digitCount == 0) { throw NoDigitsException(startPosition); } // Last read character should be 'e' if (reader.PreviousChar != 'e') { throw InvalidBencodeException <BNumber> .MissingEndChar(startPosition); } return(ParseNumber(digits.Span.Slice(0, digitCount), startPosition)); }
public async Task CanReadLessThanRequested() { var bytes = Encoding.UTF8.GetBytes("abc").AsMemory(); var(reader, writer) = new Pipe(); var bencodeReader = new PipeBencodeReader(reader); await writer.WriteAsync(bytes.Slice(0, 1)); var buffer = new byte[bytes.Length]; var readTask = bencodeReader.ReadAsync(buffer); await writer.WriteAsync(bytes.Slice(1, 1)); writer.Complete(); var bytesRead = await readTask; bytesRead.Should().Be(2); buffer[0].Should().Be((byte)'a'); buffer[1].Should().Be((byte)'b'); buffer[2].Should().Be(default);
/// <summary> /// Parses the next <see cref="BDictionary"/> from the reader as a <see cref="Torrent"/>. /// </summary> /// <param name="pipeReader">The reader to parse from.</param> /// <param name="cancellationToken"></param> /// <returns>The parsed <see cref="Torrent"/>.</returns> public override async ValueTask <Torrent> ParseAsync(PipeBencodeReader pipeReader, CancellationToken cancellationToken = default) { var data = await BencodeParser.ParseAsync <BDictionary>(pipeReader, cancellationToken).ConfigureAwait(false); return(CreateTorrent(data)); }
/// <summary> /// Parses an <see cref="IBObject"/> of type <typeparamref name="T"/> from a <see cref="PipeBencodeReader"/>. /// </summary> /// <param name="pipeReader">The pipe reader to read from.</param> /// <param name="cancellationToken"></param> /// <returns>The parsed object.</returns> public abstract ValueTask <T> ParseAsync(PipeBencodeReader pipeReader, CancellationToken cancellationToken = default);
async ValueTask <IBObject> IBObjectParser.ParseAsync(PipeBencodeReader pipeReader, CancellationToken cancellationToken) { return(await ParseAsync(pipeReader, cancellationToken).ConfigureAwait(false)); }
public override ValueTask <IBObject> ParseAsync(PipeBencodeReader pipeReader, CancellationToken cancellationToken = default) { throw new System.NotImplementedException(); }
/// <summary> /// Parses an <see cref="IBObject"/> of type <typeparamref name="T"/> from the <see cref="PipeReader"/>. /// </summary> /// <typeparam name="T">The type of <see cref="IBObject"/> to parse as.</typeparam> public static ValueTask <T> ParseAsync <T>(this IBencodeParser parser, PipeReader pipeReader, CancellationToken cancellationToken = default) where T : class, IBObject { var reader = new PipeBencodeReader(pipeReader); return(parser.ParseAsync <T>(reader, cancellationToken)); }
/// <summary> /// Parses an <see cref="IBObject"/> from the <see cref="PipeReader"/>. /// </summary> public static ValueTask <IBObject> ParseAsync(this IBencodeParser parser, PipeReader pipeReader, CancellationToken cancellationToken = default) { var reader = new PipeBencodeReader(pipeReader); return(parser.ParseAsync(reader, cancellationToken)); }
public PipeBencodeReaderTests() { PipeBencodeReader = new PipeBencodeReader(PipeReader); }
internal static Task SkipBytesAsync(this PipeBencodeReader reader, int length) { return(reader.ReadAsync(new byte[length]).AsTask()); }