-
-
Notifications
You must be signed in to change notification settings - Fork 30
Expand file tree
/
Copy pathBNumberParser.cs
More file actions
178 lines (149 loc) · 7.16 KB
/
Copy pathBNumberParser.cs
File metadata and controls
178 lines (149 loc) · 7.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
using System;
using System.Buffers;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BencodeNET.Exceptions;
using BencodeNET.IO;
using BencodeNET.Objects;
namespace BencodeNET.Parsing
{
/// <summary>
/// A parser for bencoded numbers.
/// </summary>
public class BNumberParser : BObjectParser<BNumber>
{
/// <summary>
/// The minimum stream length in bytes for a valid number ('i0e').
/// </summary>
protected const int MinimumLength = 3;
/// <summary>
/// The encoding used for parsing.
/// </summary>
public override Encoding Encoding => Encoding.UTF8;
/// <summary>
/// Parses the next <see cref="BNumber"/> from the reader.
/// </summary>
/// <param name="reader">The reader to parse from.</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 BNumber Parse(BencodeReader reader)
{
if (reader == null) throw new ArgumentNullException(nameof(reader));
if (reader.Length < MinimumLength)
throw InvalidBencodeException<BNumber>.BelowMinimumLength(MinimumLength, reader.Length.Value, reader.Position);
var startPosition = reader.Position;
// Numbers must start with 'i'
if (reader.ReadChar() != 'i')
throw InvalidBencodeException<BNumber>.UnexpectedChar('i', reader.PreviousChar, startPosition);
var digits = ArrayPool<char>.Shared.Rent(BNumber.MaxDigits);
try
{
var digitCount = 0;
for (var c = reader.ReadChar(); c != default && c != 'e'; c = reader.ReadChar())
{
digits[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[..digitCount], startPosition);
}
finally
{
ArrayPool<char>.Shared.Return(digits);
}
}
/// <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);
var digits = ArrayPool<char>.Shared.Rent(BNumber.MaxDigits);
try
{
var digitCount = 0;
for (var c = await reader.ReadCharAsync(cancellationToken).ConfigureAwait(false);
c != default && c != 'e';
c = await reader.ReadCharAsync(cancellationToken).ConfigureAwait(false))
{
digits[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.AsSpan()[..digitCount], startPosition);
}
finally
{
ArrayPool<char>.Shared.Return(digits);
}
}
private BNumber ParseNumber(in ReadOnlySpan<char> digits, long startPosition)
{
var isNegative = digits[0] == '-';
var numberOfDigits = isNegative ? digits.Length - 1 : digits.Length;
// We do not support numbers that cannot be stored as a long (Int64)
if (numberOfDigits > BNumber.MaxDigits)
{
throw UnsupportedException(
$"The number '{digits.AsString()}' has more than 19 digits and cannot be stored as a long (Int64) and therefore is not supported.",
startPosition);
}
// We need at least one digit
if (numberOfDigits < 1)
throw NoDigitsException(startPosition);
var firstDigit = isNegative ? digits[1] : digits[0];
// Leading zeros are not valid
if (firstDigit == '0' && numberOfDigits > 1)
throw InvalidException($"Leading '0's are not valid. Found value '{digits.AsString()}'.", startPosition);
// '-0' is not valid either
if (firstDigit == '0' && numberOfDigits == 1 && isNegative)
throw InvalidException("'-0' is not a valid number.", startPosition);
if (!ParseUtil.TryParseLongFast(digits, out var number))
{
var nonSignChars = isNegative ? digits.Slice(1) : digits;
if (nonSignChars.AsString().Any(x => !x.IsDigit()))
throw InvalidException($"The value '{digits.AsString()}' is not a valid number.", startPosition);
throw UnsupportedException(
$"The value '{digits.AsString()}' is not a valid long (Int64). Supported values range from '{long.MinValue:N0}' to '{long.MaxValue:N0}'.",
startPosition);
}
return new BNumber(number);
}
private static InvalidBencodeException<BNumber> NoDigitsException(long startPosition)
{
return new InvalidBencodeException<BNumber>(
$"It contains no digits. The number starts at position {startPosition}.",
startPosition);
}
private static InvalidBencodeException<BNumber> InvalidException(string message, long startPosition)
{
return new InvalidBencodeException<BNumber>(
$"{message} The number starts at position {startPosition}.",
startPosition);
}
private static UnsupportedBencodeException<BNumber> UnsupportedException(string message, long startPosition)
{
return new UnsupportedBencodeException<BNumber>(
$"{message} The number starts at position {startPosition}.",
startPosition);
}
}
}