Skip to content

Commit ff33119

Browse files
authoredMar 30, 2023
feat(core): Default encoding/decoding limits (#1335)
* feat(core): Default encoding/decoding limits This PR adds new defaults for both client and server max encoding/decoding message size limits. By default, the max message decoding size is `4MB` and the max message encoding size is `usize::MAX`. This is follow up work from #1274 BREAKING: Default max message encoding/decoding limits * update generated code
1 parent b3358dc commit ff33119

File tree

12 files changed

+337
-6
lines changed

12 files changed

+337
-6
lines changed
 

‎tests/integration_tests/Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ futures-util = "0.3"
1414
prost = "0.11"
1515
tokio = {version = "1.0", features = ["macros", "rt-multi-thread", "net"]}
1616
tonic = {path = "../../tonic"}
17+
tracing-subscriber = {version = "0.3", features = ["env-filter"]}
1718

1819
[dev-dependencies]
1920
async-stream = "0.3"
@@ -25,7 +26,7 @@ tokio-stream = {version = "0.1.5", features = ["net"]}
2526
tower = {version = "0.4", features = []}
2627
tower-http = { version = "0.4", features = ["set-header", "trace"] }
2728
tower-service = "0.3"
28-
tracing-subscriber = {version = "0.3", features = ["env-filter"]}
29+
tracing = "0.1"
2930

3031
[build-dependencies]
3132
tonic-build = {path = "../../tonic-build"}

‎tests/integration_tests/proto/test.proto

+11
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,14 @@ service Test {
88

99
message Input {}
1010
message Output {}
11+
12+
service Test1 {
13+
rpc UnaryCall(Input1) returns (Output1);
14+
}
15+
16+
message Input1 {
17+
bytes buf = 1;
18+
}
19+
message Output1 {
20+
bytes buf = 1;
21+
}

‎tests/integration_tests/src/lib.rs

+6
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,9 @@ pub mod mock {
5353
}
5454
}
5555
}
56+
57+
pub fn trace_init() {
58+
let _ = tracing_subscriber::FmtSubscriber::builder()
59+
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
60+
.try_init();
61+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
use integration_tests::{
2+
pb::{test1_client, test1_server, Input1, Output1},
3+
trace_init,
4+
};
5+
use tonic::{
6+
transport::{Endpoint, Server},
7+
Code, Request, Response, Status,
8+
};
9+
10+
#[test]
11+
fn max_message_recv_size() {
12+
trace_init();
13+
14+
// Server recv
15+
assert_server_recv_max_success(128);
16+
// 5 is the size of the gRPC header
17+
assert_server_recv_max_success((4 * 1024 * 1024) - 5);
18+
// 4mb is the max recv size
19+
assert_server_recv_max_failure(4 * 1024 * 1024);
20+
assert_server_recv_max_failure(4 * 1024 * 1024 + 1);
21+
assert_server_recv_max_failure(8 * 1024 * 1024);
22+
23+
// Client recv
24+
assert_client_recv_max_success(128);
25+
// 5 is the size of the gRPC header
26+
assert_client_recv_max_success((4 * 1024 * 1024) - 5);
27+
// 4mb is the max recv size
28+
assert_client_recv_max_failure(4 * 1024 * 1024);
29+
assert_client_recv_max_failure(4 * 1024 * 1024 + 1);
30+
assert_client_recv_max_failure(8 * 1024 * 1024);
31+
32+
// Custom limit settings
33+
assert_test_case(TestCase {
34+
// 5 is the size of the gRPC header
35+
server_blob_size: 1024 - 5,
36+
client_recv_max: Some(1024),
37+
..Default::default()
38+
});
39+
assert_test_case(TestCase {
40+
server_blob_size: 1024,
41+
client_recv_max: Some(1024),
42+
expected_code: Some(Code::OutOfRange),
43+
..Default::default()
44+
});
45+
46+
assert_test_case(TestCase {
47+
// 5 is the size of the gRPC header
48+
client_blob_size: 1024 - 5,
49+
server_recv_max: Some(1024),
50+
..Default::default()
51+
});
52+
assert_test_case(TestCase {
53+
client_blob_size: 1024,
54+
server_recv_max: Some(1024),
55+
expected_code: Some(Code::OutOfRange),
56+
..Default::default()
57+
});
58+
}
59+
60+
#[test]
61+
fn max_message_send_size() {
62+
trace_init();
63+
64+
// Check client send limit works
65+
assert_test_case(TestCase {
66+
client_blob_size: 4 * 1024 * 1024,
67+
server_recv_max: Some(usize::MAX),
68+
..Default::default()
69+
});
70+
assert_test_case(TestCase {
71+
// 5 is the size of the gRPC header
72+
client_blob_size: 1024 - 5,
73+
server_recv_max: Some(usize::MAX),
74+
client_send_max: Some(1024),
75+
..Default::default()
76+
});
77+
assert_test_case(TestCase {
78+
// 5 is the size of the gRPC header
79+
client_blob_size: 4 * 1024 * 1024,
80+
server_recv_max: Some(usize::MAX),
81+
// Set client send limit to 1024
82+
client_send_max: Some(1024),
83+
// TODO: This should return OutOfRange
84+
// https://github.com/hyperium/tonic/issues/1334
85+
expected_code: Some(Code::Internal),
86+
..Default::default()
87+
});
88+
89+
// Check server send limit works
90+
assert_test_case(TestCase {
91+
server_blob_size: 4 * 1024 * 1024,
92+
client_recv_max: Some(usize::MAX),
93+
..Default::default()
94+
});
95+
assert_test_case(TestCase {
96+
// 5 is the gRPC header size
97+
server_blob_size: 1024 - 5,
98+
client_recv_max: Some(usize::MAX),
99+
// Set server send limit to 1024
100+
server_send_max: Some(1024),
101+
..Default::default()
102+
});
103+
assert_test_case(TestCase {
104+
server_blob_size: 4 * 1024 * 1024,
105+
client_recv_max: Some(usize::MAX),
106+
// Set server send limit to 1024
107+
server_send_max: Some(1024),
108+
expected_code: Some(Code::OutOfRange),
109+
..Default::default()
110+
});
111+
}
112+
113+
// Track caller doesn't work on async fn so we extract the async part
114+
// into a sync version and assert the response there using track track_caller
115+
// so that when this does panic it tells us which line in the test failed not
116+
// where we placed the panic call.
117+
118+
#[track_caller]
119+
fn assert_server_recv_max_success(size: usize) {
120+
let case = TestCase {
121+
client_blob_size: size,
122+
server_blob_size: 0,
123+
..Default::default()
124+
};
125+
126+
assert_test_case(case);
127+
}
128+
129+
#[track_caller]
130+
fn assert_server_recv_max_failure(size: usize) {
131+
let case = TestCase {
132+
client_blob_size: size,
133+
server_blob_size: 0,
134+
expected_code: Some(Code::OutOfRange),
135+
..Default::default()
136+
};
137+
138+
assert_test_case(case);
139+
}
140+
141+
#[track_caller]
142+
fn assert_client_recv_max_success(size: usize) {
143+
let case = TestCase {
144+
client_blob_size: 0,
145+
server_blob_size: size,
146+
..Default::default()
147+
};
148+
149+
assert_test_case(case);
150+
}
151+
152+
#[track_caller]
153+
fn assert_client_recv_max_failure(size: usize) {
154+
let case = TestCase {
155+
client_blob_size: 0,
156+
server_blob_size: size,
157+
expected_code: Some(Code::OutOfRange),
158+
..Default::default()
159+
};
160+
161+
assert_test_case(case);
162+
}
163+
164+
#[track_caller]
165+
fn assert_test_case(case: TestCase) {
166+
let res = max_message_run(&case);
167+
168+
match (case.expected_code, res) {
169+
(Some(_), Ok(())) => panic!("Expected failure, but got success"),
170+
(Some(code), Err(status)) => {
171+
if status.code() != code {
172+
panic!(
173+
"Expected failure, got failure but wrong code, got: {:?}",
174+
status
175+
)
176+
}
177+
}
178+
179+
(None, Err(status)) => panic!("Expected success, but got failure, got: {:?}", status),
180+
181+
_ => (),
182+
}
183+
}
184+
185+
#[derive(Default)]
186+
struct TestCase {
187+
client_blob_size: usize,
188+
server_blob_size: usize,
189+
client_recv_max: Option<usize>,
190+
server_recv_max: Option<usize>,
191+
client_send_max: Option<usize>,
192+
server_send_max: Option<usize>,
193+
194+
expected_code: Option<Code>,
195+
}
196+
197+
#[tokio::main]
198+
async fn max_message_run(case: &TestCase) -> Result<(), Status> {
199+
let client_blob = vec![0; case.client_blob_size];
200+
let server_blob = vec![0; case.server_blob_size];
201+
202+
let (client, server) = tokio::io::duplex(1024);
203+
204+
struct Svc(Vec<u8>);
205+
206+
#[tonic::async_trait]
207+
impl test1_server::Test1 for Svc {
208+
async fn unary_call(&self, _req: Request<Input1>) -> Result<Response<Output1>, Status> {
209+
Ok(Response::new(Output1 {
210+
buf: self.0.clone(),
211+
}))
212+
}
213+
}
214+
215+
let svc = test1_server::Test1Server::new(Svc(server_blob));
216+
217+
let svc = if let Some(size) = case.server_recv_max {
218+
svc.max_decoding_message_size(size)
219+
} else {
220+
svc
221+
};
222+
223+
let svc = if let Some(size) = case.server_send_max {
224+
svc.max_encoding_message_size(size)
225+
} else {
226+
svc
227+
};
228+
229+
tokio::spawn(async move {
230+
Server::builder()
231+
.add_service(svc)
232+
.serve_with_incoming(futures::stream::iter(vec![Ok::<_, std::io::Error>(server)]))
233+
.await
234+
.unwrap();
235+
});
236+
237+
// Move client to an option so we can _move_ the inner value
238+
// on the first attempt to connect. All other attempts will fail.
239+
let mut client = Some(client);
240+
let channel = Endpoint::try_from("http://[::]:50051")
241+
.unwrap()
242+
.connect_with_connector(tower::service_fn(move |_| {
243+
let client = client.take();
244+
245+
async move {
246+
if let Some(client) = client {
247+
Ok(client)
248+
} else {
249+
Err(std::io::Error::new(
250+
std::io::ErrorKind::Other,
251+
"Client already taken",
252+
))
253+
}
254+
}
255+
}))
256+
.await
257+
.unwrap();
258+
259+
let client = test1_client::Test1Client::new(channel);
260+
261+
let client = if let Some(size) = case.client_recv_max {
262+
client.max_decoding_message_size(size)
263+
} else {
264+
client
265+
};
266+
267+
let mut client = if let Some(size) = case.client_send_max {
268+
client.max_encoding_message_size(size)
269+
} else {
270+
client
271+
};
272+
273+
let req = Request::new(Input1 {
274+
buf: client_blob.clone(),
275+
});
276+
277+
client.unary_call(req).await.map(|_| ())
278+
}

‎tonic-build/src/client.rs

+4
Original file line numberDiff line numberDiff line change
@@ -136,13 +136,17 @@ pub(crate) fn generate_internal<T: Service>(
136136
}
137137

138138
/// Limits the maximum size of a decoded message.
139+
///
140+
/// Default: `4MB`
139141
#[must_use]
140142
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
141143
self.inner = self.inner.max_decoding_message_size(limit);
142144
self
143145
}
144146

145147
/// Limits the maximum size of an encoded message.
148+
///
149+
/// Default: `usize::MAX`
146150
#[must_use]
147151
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
148152
self.inner = self.inner.max_encoding_message_size(limit);

‎tonic-build/src/server.rs

+4
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,17 @@ pub(crate) fn generate_internal<T: Service>(
8484

8585
let configure_max_message_size_methods = quote! {
8686
/// Limits the maximum size of a decoded message.
87+
///
88+
/// Default: `4MB`
8789
#[must_use]
8890
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
8991
self.max_decoding_message_size = Some(limit);
9092
self
9193
}
9294

9395
/// Limits the maximum size of an encoded message.
96+
///
97+
/// Default: `usize::MAX`
9498
#[must_use]
9599
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
96100
self.max_encoding_message_size = Some(limit);

‎tonic-health/src/generated/grpc.health.v1.rs

+8
Original file line numberDiff line numberDiff line change
@@ -115,12 +115,16 @@ pub mod health_client {
115115
self
116116
}
117117
/// Limits the maximum size of a decoded message.
118+
///
119+
/// Default: `4MB`
118120
#[must_use]
119121
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
120122
self.inner = self.inner.max_decoding_message_size(limit);
121123
self
122124
}
123125
/// Limits the maximum size of an encoded message.
126+
///
127+
/// Default: `usize::MAX`
124128
#[must_use]
125129
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
126130
self.inner = self.inner.max_encoding_message_size(limit);
@@ -282,12 +286,16 @@ pub mod health_server {
282286
self
283287
}
284288
/// Limits the maximum size of a decoded message.
289+
///
290+
/// Default: `4MB`
285291
#[must_use]
286292
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
287293
self.max_decoding_message_size = Some(limit);
288294
self
289295
}
290296
/// Limits the maximum size of an encoded message.
297+
///
298+
/// Default: `usize::MAX`
291299
#[must_use]
292300
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
293301
self.max_encoding_message_size = Some(limit);

‎tonic-reflection/src/generated/grpc.reflection.v1alpha.rs

+8
Original file line numberDiff line numberDiff line change
@@ -212,12 +212,16 @@ pub mod server_reflection_client {
212212
self
213213
}
214214
/// Limits the maximum size of a decoded message.
215+
///
216+
/// Default: `4MB`
215217
#[must_use]
216218
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
217219
self.inner = self.inner.max_decoding_message_size(limit);
218220
self
219221
}
220222
/// Limits the maximum size of an encoded message.
223+
///
224+
/// Default: `usize::MAX`
221225
#[must_use]
222226
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
223227
self.inner = self.inner.max_encoding_message_size(limit);
@@ -330,12 +334,16 @@ pub mod server_reflection_server {
330334
self
331335
}
332336
/// Limits the maximum size of a decoded message.
337+
///
338+
/// Default: `4MB`
333339
#[must_use]
334340
pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
335341
self.max_decoding_message_size = Some(limit);
336342
self
337343
}
338344
/// Limits the maximum size of an encoded message.
345+
///
346+
/// Default: `usize::MAX`
339347
#[must_use]
340348
pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
341349
self.max_encoding_message_size = Some(limit);

‎tonic/src/codec/decode.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::compression::{decompress, CompressionEncoding};
2-
use super::{DecodeBuf, Decoder, DEFAULT_MAX_MESSAGE_SIZE, HEADER_SIZE};
2+
use super::{DecodeBuf, Decoder, DEFAULT_MAX_RECV_MESSAGE_SIZE, HEADER_SIZE};
33
use crate::{body::BoxBody, metadata::MetadataMap, Code, Status};
44
use bytes::{Buf, BufMut, BytesMut};
55
use futures_core::Stream;
@@ -174,7 +174,9 @@ impl StreamingInner {
174174
};
175175

176176
let len = self.buf.get_u32() as usize;
177-
let limit = self.max_message_size.unwrap_or(DEFAULT_MAX_MESSAGE_SIZE);
177+
let limit = self
178+
.max_message_size
179+
.unwrap_or(DEFAULT_MAX_RECV_MESSAGE_SIZE);
178180
if len > limit {
179181
return Err(Status::new(
180182
Code::OutOfRange,

‎tonic/src/codec/encode.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::compression::{compress, CompressionEncoding, SingleMessageCompressionOverride};
2-
use super::{EncodeBuf, Encoder, DEFAULT_MAX_MESSAGE_SIZE, HEADER_SIZE};
2+
use super::{EncodeBuf, Encoder, DEFAULT_MAX_SEND_MESSAGE_SIZE, HEADER_SIZE};
33
use crate::{Code, Status};
44
use bytes::{BufMut, Bytes, BytesMut};
55
use futures_core::{Stream, TryStream};
@@ -141,7 +141,7 @@ fn finish_encoding(
141141
buf: &mut BytesMut,
142142
) -> Result<Bytes, Status> {
143143
let len = buf.len() - HEADER_SIZE;
144-
let limit = max_message_size.unwrap_or(DEFAULT_MAX_MESSAGE_SIZE);
144+
let limit = max_message_size.unwrap_or(DEFAULT_MAX_SEND_MESSAGE_SIZE);
145145
if len > limit {
146146
return Err(Status::new(
147147
Code::OutOfRange,

‎tonic/src/codec/mod.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ const HEADER_SIZE: usize =
3030
std::mem::size_of::<u32>();
3131

3232
// The default maximum uncompressed size in bytes for a message. Defaults to 4MB.
33-
const DEFAULT_MAX_MESSAGE_SIZE: usize = 4 * 1024 * 1024;
33+
const DEFAULT_MAX_RECV_MESSAGE_SIZE: usize = 4 * 1024 * 1024;
34+
const DEFAULT_MAX_SEND_MESSAGE_SIZE: usize = usize::MAX;
3435

3536
/// Trait that knows how to encode and decode gRPC messages.
3637
pub trait Codec {

‎tonic/src/lib.rs

+8
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,14 @@
5353
//! to build even more feature rich clients and servers. This module also provides the ability to
5454
//! enable TLS using [`rustls`], via the `tls` feature flag.
5555
//!
56+
//! # Code generated client/server configuration
57+
//!
58+
//! ## Max Message Size
59+
//!
60+
//! Currently, both servers and clients can be configured to set the max message encoding and
61+
//! decoding size. This will ensure that an incoming gRPC message will not exahust the systems
62+
//! memory. By default, the decoding message limit is `4MB` and the encoding limit is `usize::MAX`.
63+
//!
5664
//! [gRPC]: https://grpc.io
5765
//! [`tonic`]: https://github.com/hyperium/tonic
5866
//! [`tokio`]: https://docs.rs/tokio

0 commit comments

Comments
 (0)
Please sign in to comment.