Skip to content

Commit fbe18a4

Browse files
authored
Merge pull request #166 from Astn/aus-1003-reserved-names
AUS-1003, AUS-1004: Reserved names and limits to master
2 parents 81f77bc + a64baf9 commit fbe18a4

15 files changed

Lines changed: 889 additions & 35 deletions

‎AustinHarris.JsonRpcTestN/AspNetCoreTests.cs‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ public async Task StartHost()
7272

7373
_app = builder.Build();
7474
_app.MapJsonRpc("/rpc");
75+
_app.MapJsonRpc("/raised-limit", new JsonRpcOptions { MaxRequestBytes = 6 * 1024 * 1024 });
7576
await _app.StartAsync();
7677

7778
var addresses = _app.Services.GetRequiredService<IServer>().Features.Get<IServerAddressesFeature>().Addresses;
@@ -157,11 +158,28 @@ public async Task Http_DiService_IsBoundAndSeesHttpContext()
157158
[Test]
158159
public async Task Http_LargeBody_Is413()
159160
{
160-
var big = "{\"jsonrpc\":\"2.0\",\"method\":\"internal.echo\",\"params\":[\"" + new string('x', 5 * 1024 * 1024) + "\"],\"id\":1}";
161+
var big = SizedDocument(4 * 1024 * 1024 + 1);
161162
var response = await PostAsync(big);
162163
Assert.AreEqual(HttpStatusCode.RequestEntityTooLarge, response.StatusCode);
163164
}
164165

166+
[Test]
167+
public async Task Http_RaisedTransportLimit_UsesCoreDocumentLimit()
168+
{
169+
var response = await _http.PostAsync("/raised-limit",
170+
new StringContent(SizedDocument(4 * 1024 * 1024 + 1), Encoding.UTF8, "application/json"));
171+
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
172+
Assert.AreEqual("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Invalid Request\",\"data\":{\"limit\":\"maxDocumentBytes\",\"maximum\":4194304}},\"id\":null}",
173+
await response.Content.ReadAsStringAsync());
174+
}
175+
176+
private static string SizedDocument(int bytes)
177+
{
178+
const string prefix = "{\"method\":\"IntToInt\",\"params\":[\"";
179+
const string suffix = "\"],\"id\":1}";
180+
return prefix + new string('x', bytes - prefix.Length - suffix.Length) + suffix;
181+
}
182+
165183
[Test]
166184
public async Task Tcp_TwoDocumentsInOneWrite_AreAnsweredInOrder()
167185
{
Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
using System;
2+
using System.Buffers;
3+
using System.Text;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using AustinHarris.JsonRpc;
7+
using AustinHarris.JsonRpc.Serialization;
8+
using NUnit.Framework;
9+
10+
namespace AustinHarris.JsonRpcTestN
11+
{
12+
[TestFixture]
13+
[NonParallelizable]
14+
public class LimitsTests
15+
{
16+
private const string Call = "{\"jsonrpc\":\"2.0\",\"method\":\"limits.hit\",\"id\":1}";
17+
private const string Notification = "{\"jsonrpc\":\"2.0\",\"method\":\"limits.hit\"}";
18+
private const string Result = "{\"jsonrpc\":\"2.0\",\"result\":7,\"id\":1}";
19+
private string _session;
20+
private Service _service;
21+
22+
public sealed class Service
23+
{
24+
public int Calls;
25+
[JsonRpcMethod("limits.hit")]
26+
public int Hit() { Interlocked.Increment(ref Calls); return 7; }
27+
}
28+
29+
private sealed class Segment : ReadOnlySequenceSegment<byte>
30+
{
31+
internal Segment(ReadOnlyMemory<byte> memory) { Memory = memory; }
32+
internal Segment Append(ReadOnlyMemory<byte> memory)
33+
{
34+
var next = new Segment(memory) { RunningIndex = RunningIndex + Memory.Length };
35+
Next = next;
36+
return next;
37+
}
38+
39+
internal Segment AppendAt(long index, ReadOnlyMemory<byte> memory)
40+
{
41+
var next = new Segment(memory) { RunningIndex = index };
42+
Next = next;
43+
return next;
44+
}
45+
}
46+
47+
[SetUp]
48+
public void SetUp()
49+
{
50+
_session = "limits-" + Guid.NewGuid().ToString("N");
51+
_service = new Service();
52+
ServiceBinder.BindService(_session, _service);
53+
Config.SetLimits(JsonRpcLimits.Default);
54+
}
55+
56+
[TearDown]
57+
public void TearDown()
58+
{
59+
Config.SetLimits(JsonRpcLimits.Default);
60+
Handler.DestroySession(_session);
61+
}
62+
63+
private static ReadOnlySequence<byte> Split(byte[] bytes)
64+
{
65+
int middle = bytes.Length / 2;
66+
var first = new Segment(bytes.AsMemory(0, middle));
67+
var last = first.Append(bytes.AsMemory(middle));
68+
return new ReadOnlySequence<byte>(first, 0, last, last.Memory.Length);
69+
}
70+
71+
private static string Text(ArrayBufferWriter<byte> output) => Encoding.UTF8.GetString(output.WrittenSpan);
72+
73+
private static string LimitError(string name, long maximum) =>
74+
"{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32600,\"message\":\"Invalid Request\",\"data\":{\"limit\":\"" +
75+
name + "\",\"maximum\":" + maximum + "}},\"id\":null}";
76+
77+
[TestCase("jsmn")]
78+
[TestCase("newtonsoft")]
79+
[TestCase("stj")]
80+
public async Task DocumentBytes_AllPublicEntriesRejectBeforeDispatch(string serializerName)
81+
{
82+
var serializer = SerializerCatalog.Create(serializerName);
83+
byte[] bytes = Encoding.UTF8.GetBytes(Call);
84+
long maximum = bytes.Length - 1;
85+
string expected = LimitError("maxDocumentBytes", maximum);
86+
Config.SetLimits(new JsonRpcLimits(maximum, 0));
87+
88+
var output = new ArrayBufferWriter<byte>();
89+
var single = new ReadOnlySequence<byte>(bytes);
90+
JsonRpcProcessor.Process(_session, in single, output, serializer: serializer);
91+
Assert.AreEqual(expected, Text(output), "sync single-segment sequence");
92+
output.Clear();
93+
var multi = Split(bytes);
94+
JsonRpcProcessor.Process(_session, in multi, output, serializer: serializer);
95+
Assert.AreEqual(expected, Text(output), "sync multi-segment sequence");
96+
output.Clear();
97+
JsonRpcProcessor.Process(_session, bytes.AsMemory(), output, serializer: serializer);
98+
Assert.AreEqual(expected, Text(output), "sync memory");
99+
output.Clear();
100+
JsonRpcProcessor.Process(_session, bytes.AsSpan(), output, serializer: serializer);
101+
Assert.AreEqual(expected, Text(output), "sync span");
102+
Assert.AreEqual(expected, Encoding.UTF8.GetString(JsonRpcProcessor.ProcessBytes(_session, bytes.AsSpan(), serializer: serializer)));
103+
Assert.AreEqual(expected, JsonRpcProcessor.ProcessSync(_session, Call, null, serializer));
104+
Assert.AreEqual(expected, await JsonRpcProcessor.Process(_session, Call, null, serializer));
105+
106+
output.Clear();
107+
await JsonRpcProcessor.ProcessAsync(_session, single, output, serializer: serializer);
108+
Assert.AreEqual(expected, Text(output), "async single-segment sequence");
109+
output.Clear();
110+
await JsonRpcProcessor.ProcessAsync(_session, multi, output, serializer: serializer);
111+
Assert.AreEqual(expected, Text(output), "async multi-segment sequence");
112+
output.Clear();
113+
await JsonRpcProcessor.ProcessAsync(_session, bytes.AsMemory(), output, serializer: serializer);
114+
Assert.AreEqual(expected, Text(output), "async memory");
115+
output.Clear();
116+
await JsonRpcProcessor.ProcessAsync(_session, bytes.AsSpan(), output, serializer: serializer);
117+
Assert.AreEqual(expected, Text(output), "async span");
118+
Assert.AreEqual(expected, await JsonRpcProcessor.ProcessAsync(_session, Call, serializer: serializer));
119+
Assert.AreEqual(0, _service.Calls);
120+
}
121+
122+
[Test]
123+
public async Task DefaultSessionAndStateWrappersInheritByteCheck()
124+
{
125+
Config.SetLimits(new JsonRpcLimits(1, 0));
126+
string expected = LimitError("maxDocumentBytes", 1);
127+
Assert.AreEqual(expected, JsonRpcProcessor.ProcessSync(Call));
128+
Assert.AreEqual(expected, JsonRpcProcessor.ProcessSync(SerializerCatalog.Create("jsmn"), Call));
129+
Assert.AreEqual(expected, await JsonRpcProcessor.Process(Call));
130+
Assert.AreEqual(expected, await JsonRpcProcessor.Process(SerializerCatalog.Create("jsmn"), Call));
131+
Assert.AreEqual(expected, await JsonRpcProcessor.ProcessAsync(Call));
132+
133+
async Task<string> StateResult(bool defaultSession)
134+
{
135+
var completion = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
136+
var state = new JsonRpcStateAsync(ar => completion.SetResult(((JsonRpcStateAsync)ar).Result), null) { JsonRpc = Call };
137+
if (defaultSession) JsonRpcProcessor.Process(state);
138+
else JsonRpcProcessor.Process(_session, state);
139+
return await completion.Task.WaitAsync(TimeSpan.FromSeconds(5));
140+
}
141+
142+
Assert.AreEqual(expected, await StateResult(false));
143+
Assert.AreEqual(expected, await StateResult(true));
144+
Assert.AreEqual(0, _service.Calls);
145+
}
146+
147+
[Test]
148+
public async Task SequenceLengthIsCheckedBeforeIntConversionOrFlattening()
149+
{
150+
Config.SetLimits(_session, new JsonRpcLimits(1, 0));
151+
var first = new Segment(ReadOnlyMemory<byte>.Empty);
152+
var last = first.AppendAt((long)int.MaxValue + 1, ReadOnlyMemory<byte>.Empty);
153+
var sequence = new ReadOnlySequence<byte>(first, 0, last, 0);
154+
var output = new ArrayBufferWriter<byte>();
155+
JsonRpcProcessor.Process(_session, in sequence, output);
156+
Assert.AreEqual(LimitError("maxDocumentBytes", 1), Text(output));
157+
output.Clear();
158+
await JsonRpcProcessor.ProcessAsync(_session, sequence, output);
159+
Assert.AreEqual(LimitError("maxDocumentBytes", 1), Text(output));
160+
}
161+
162+
[Test]
163+
public void AsyncPreCancellationWinsOverDocumentLimit()
164+
{
165+
Config.SetLimits(_session, new JsonRpcLimits(1, 0));
166+
using var cts = new CancellationTokenSource();
167+
cts.Cancel();
168+
var bytes = Encoding.UTF8.GetBytes(Call);
169+
var output = new ArrayBufferWriter<byte>();
170+
var sequence = new ReadOnlySequence<byte>(bytes);
171+
Assert.IsTrue(JsonRpcProcessor.ProcessAsync(_session, sequence, output, cancellationToken: cts.Token).IsCanceled);
172+
Assert.IsTrue(JsonRpcProcessor.ProcessAsync(_session, bytes.AsMemory(), output, cancellationToken: cts.Token).IsCanceled);
173+
Assert.IsTrue(JsonRpcProcessor.ProcessAsync(_session, bytes.AsSpan(), output, cancellationToken: cts.Token).IsCanceled);
174+
Assert.IsTrue(JsonRpcProcessor.ProcessAsync(_session, Call, cancellationToken: cts.Token).IsCanceled);
175+
Assert.AreEqual(0, output.WrittenCount);
176+
}
177+
178+
[TestCase("jsmn")]
179+
[TestCase("newtonsoft")]
180+
[TestCase("stj")]
181+
public async Task BatchCountRejectsWholeMixedAndNotificationOnlyBatches(string serializerName)
182+
{
183+
var serializer = SerializerCatalog.Create(serializerName);
184+
Config.SetLimits(_session, new JsonRpcLimits(0, 2));
185+
string expected = LimitError("maxBatchCount", 2);
186+
string mixed = "[" + Call + "," + Notification + ",42]";
187+
string notifications = "[" + Notification + "," + Notification + "," + Notification + "]";
188+
foreach (string batch in new[] { mixed, notifications })
189+
{
190+
Assert.AreEqual(expected, JsonRpcProcessor.ProcessSync(_session, batch, null, serializer));
191+
Assert.AreEqual(expected, await JsonRpcProcessor.ProcessAsync(_session, batch, serializer: serializer));
192+
}
193+
Assert.AreEqual(0, _service.Calls);
194+
}
195+
196+
[TestCase("jsmn")]
197+
[TestCase("newtonsoft")]
198+
[TestCase("stj")]
199+
public async Task StringLimitUsesUtf8ByteCount(string serializerName)
200+
{
201+
var serializer = SerializerCatalog.Create(serializerName);
202+
string ascii = "{\"method\":\"limits.hit\",\"id\":1,\"note\":\"aa\"}";
203+
string multibyte = "{\"method\":\"limits.hit\",\"id\":1,\"note\":\"€€\"}";
204+
int maximum = Encoding.UTF8.GetByteCount(ascii);
205+
Config.SetLimits(_session, new JsonRpcLimits(maximum, 0));
206+
Assert.AreEqual(Result, JsonRpcProcessor.ProcessSync(_session, ascii, null, serializer));
207+
Assert.AreEqual(LimitError("maxDocumentBytes", maximum), JsonRpcProcessor.ProcessSync(_session, multibyte, null, serializer));
208+
Assert.AreEqual(LimitError("maxDocumentBytes", maximum), await JsonRpcProcessor.ProcessAsync(_session, multibyte, serializer: serializer));
209+
Assert.AreEqual(1, _service.Calls);
210+
}
211+
212+
[Test]
213+
public void ConfigurationRejectsNegativesAndNullGlobal_AndSessionCanInherit()
214+
{
215+
Assert.AreEqual(4 * 1024 * 1024, JsonRpcLimits.Default.MaxDocumentBytes);
216+
Assert.AreEqual(1024, JsonRpcLimits.Default.MaxBatchCount);
217+
Assert.AreEqual(0, JsonRpcLimits.Unlimited.MaxDocumentBytes);
218+
Assert.AreEqual(0, JsonRpcLimits.Unlimited.MaxBatchCount);
219+
Assert.AreEqual("maxDocumentBytes", Assert.Throws<ArgumentOutOfRangeException>(() => new JsonRpcLimits(-1, 1)).ParamName);
220+
Assert.AreEqual("maxBatchCount", Assert.Throws<ArgumentOutOfRangeException>(() => new JsonRpcLimits(1, -1)).ParamName);
221+
Assert.Throws<ArgumentNullException>(() => Config.SetLimits(null));
222+
223+
var global = new JsonRpcLimits(1, 0);
224+
Config.SetLimits(global);
225+
Config.SetLimits(_session, null);
226+
Assert.AreSame(global, Config.Limits);
227+
Assert.AreEqual(LimitError("maxDocumentBytes", 1), JsonRpcProcessor.ProcessSync(_session, Call, null));
228+
Config.SetLimits(_session, new JsonRpcLimits(0, 0));
229+
Assert.AreEqual(Result, JsonRpcProcessor.ProcessSync(_session, Call, null));
230+
Assert.AreEqual(1, _service.Calls);
231+
Config.SetLimits(_session, null);
232+
Assert.AreEqual(LimitError("maxDocumentBytes", 1), JsonRpcProcessor.ProcessSync(_session, Call, null));
233+
234+
string created = "limits-created-" + Guid.NewGuid().ToString("N");
235+
try
236+
{
237+
Config.SetLimits(created, null);
238+
Assert.IsTrue(Handler.TryGetSessionHandler(created, out var handler));
239+
Assert.IsNull(handler.Limits);
240+
}
241+
finally { Handler.DestroySession(created); }
242+
}
243+
244+
[Test]
245+
public void ZeroDisablesEachField_AndUnlimitedAcceptsFiveMiB()
246+
{
247+
Config.SetLimits(_session, new JsonRpcLimits(0, 1));
248+
string large = "{\"method\":\"limits.hit\",\"id\":1,\"note\":\"" + new string('x', 5 * 1024 * 1024) + "\"}";
249+
Assert.AreEqual(Result, JsonRpcProcessor.ProcessSync(_session, large, null));
250+
Config.SetLimits(_session, new JsonRpcLimits(0, 0));
251+
Assert.AreEqual("[" + Result + "," + Result + "]", JsonRpcProcessor.ProcessSync(_session, "[" + Call + "," + Call + "]", null));
252+
Config.SetLimits(_session, JsonRpcLimits.Unlimited);
253+
Assert.AreEqual(Result, JsonRpcProcessor.ProcessSync(_session, large, null));
254+
Assert.AreEqual(4, _service.Calls);
255+
}
256+
257+
[Test]
258+
public async Task LimitErrorsReachParseHandler_ButNotPreProcessHandler()
259+
{
260+
int parsed = 0, pre = 0;
261+
Config.SetLimits(_session, new JsonRpcLimits(1, 1));
262+
Config.SetParseErrorHandler(_session, (raw, error) =>
263+
{
264+
Assert.IsNotNull(raw);
265+
Assert.AreEqual(-32600, error.code);
266+
Assert.IsInstanceOf<LimitExceededInfo>(error.data);
267+
parsed++;
268+
return error;
269+
});
270+
Config.SetPreProcessHandler(_session, (request, context) => { pre++; return null; });
271+
try
272+
{
273+
Assert.AreEqual(LimitError("maxDocumentBytes", 1), JsonRpcProcessor.ProcessSync(_session, Call, null));
274+
Config.SetLimits(_session, new JsonRpcLimits(0, 1));
275+
Assert.AreEqual(LimitError("maxBatchCount", 1), await JsonRpcProcessor.ProcessAsync(_session, "[" + Call + "," + Call + "]"));
276+
Assert.AreEqual(2, parsed);
277+
Assert.AreEqual(0, pre);
278+
Assert.AreEqual(0, _service.Calls);
279+
}
280+
finally
281+
{
282+
Config.SetParseErrorHandler(_session, null);
283+
Config.SetPreProcessHandler(_session, null);
284+
}
285+
}
286+
}
287+
}

0 commit comments

Comments
 (0)