spikard 0.13.0 → 0.15.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Steepfile +6 -0
- data/ext/spikard_rb/extconf.rb +1 -2
- data/ext/spikard_rb/{Cargo.lock → native/Cargo.lock} +819 -424
- data/ext/spikard_rb/native/Cargo.toml +24 -0
- data/ext/spikard_rb/src/lib.rs +5366 -3
- data/lib/spikard/native.rb +86 -0
- data/lib/spikard/version.rb +6 -1
- data/lib/spikard.rb +8 -52
- data/lib/spikard_rb.so +0 -0
- data/sig/types.rbs +427 -0
- metadata +14 -243
- data/LICENSE +0 -1
- data/README.md +0 -285
- data/ext/spikard_rb/Cargo.toml +0 -17
- data/lib/spikard/app.rb +0 -458
- data/lib/spikard/background.rb +0 -58
- data/lib/spikard/config.rb +0 -506
- data/lib/spikard/converters.rb +0 -13
- data/lib/spikard/grpc.rb +0 -232
- data/lib/spikard/handler_wrapper.rb +0 -113
- data/lib/spikard/provide.rb +0 -315
- data/lib/spikard/response.rb +0 -198
- data/lib/spikard/schema.rb +0 -243
- data/lib/spikard/sse.rb +0 -111
- data/lib/spikard/streaming_response.rb +0 -44
- data/lib/spikard/testing.rb +0 -474
- data/lib/spikard/upload_file.rb +0 -131
- data/lib/spikard/websocket.rb +0 -59
- data/sig/spikard.rbs +0 -739
- data/vendor/crates/spikard-bindings-shared/Cargo.toml +0 -75
- data/vendor/crates/spikard-bindings-shared/examples/config_extraction.rs +0 -132
- data/vendor/crates/spikard-bindings-shared/src/config_extractor.rs +0 -905
- data/vendor/crates/spikard-bindings-shared/src/conversion_traits.rs +0 -210
- data/vendor/crates/spikard-bindings-shared/src/di_traits.rs +0 -252
- data/vendor/crates/spikard-bindings-shared/src/error_response.rs +0 -404
- data/vendor/crates/spikard-bindings-shared/src/grpc_metadata.rs +0 -199
- data/vendor/crates/spikard-bindings-shared/src/handler_base.rs +0 -252
- data/vendor/crates/spikard-bindings-shared/src/json_conversion.rs +0 -829
- data/vendor/crates/spikard-bindings-shared/src/lazy_cache.rs +0 -587
- data/vendor/crates/spikard-bindings-shared/src/lib.rs +0 -33
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_base.rs +0 -298
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_executor.rs +0 -594
- data/vendor/crates/spikard-bindings-shared/src/response_builder.rs +0 -743
- data/vendor/crates/spikard-bindings-shared/src/response_interpreter.rs +0 -944
- data/vendor/crates/spikard-bindings-shared/src/test_client_base.rs +0 -260
- data/vendor/crates/spikard-bindings-shared/src/validation_helpers.rs +0 -369
- data/vendor/crates/spikard-bindings-shared/tests/config_extractor_behavior.rs +0 -192
- data/vendor/crates/spikard-bindings-shared/tests/error_response_edge_cases.rs +0 -383
- data/vendor/crates/spikard-bindings-shared/tests/full_coverage.rs +0 -459
- data/vendor/crates/spikard-bindings-shared/tests/handler_base_integration.rs +0 -280
- data/vendor/crates/spikard-bindings-shared/tests/integration_tests.rs +0 -669
- data/vendor/crates/spikard-core/Cargo.toml +0 -55
- data/vendor/crates/spikard-core/src/bindings/mod.rs +0 -3
- data/vendor/crates/spikard-core/src/bindings/response.rs +0 -130
- data/vendor/crates/spikard-core/src/debug.rs +0 -127
- data/vendor/crates/spikard-core/src/di/container.rs +0 -711
- data/vendor/crates/spikard-core/src/di/dependency.rs +0 -273
- data/vendor/crates/spikard-core/src/di/error.rs +0 -118
- data/vendor/crates/spikard-core/src/di/factory.rs +0 -548
- data/vendor/crates/spikard-core/src/di/graph.rs +0 -507
- data/vendor/crates/spikard-core/src/di/mod.rs +0 -192
- data/vendor/crates/spikard-core/src/di/resolved.rs +0 -428
- data/vendor/crates/spikard-core/src/di/value.rs +0 -282
- data/vendor/crates/spikard-core/src/errors.rs +0 -72
- data/vendor/crates/spikard-core/src/http.rs +0 -492
- data/vendor/crates/spikard-core/src/lib.rs +0 -29
- data/vendor/crates/spikard-core/src/lifecycle.rs +0 -1273
- data/vendor/crates/spikard-core/src/metadata.rs +0 -378
- data/vendor/crates/spikard-core/src/parameters.rs +0 -2546
- data/vendor/crates/spikard-core/src/problem.rs +0 -358
- data/vendor/crates/spikard-core/src/request_data.rs +0 -1146
- data/vendor/crates/spikard-core/src/router.rs +0 -530
- data/vendor/crates/spikard-core/src/schema_registry.rs +0 -197
- data/vendor/crates/spikard-core/src/type_hints.rs +0 -311
- data/vendor/crates/spikard-core/src/validation/error_mapper.rs +0 -710
- data/vendor/crates/spikard-core/src/validation/mod.rs +0 -470
- data/vendor/crates/spikard-core/tests/bindings_response_tests.rs +0 -136
- data/vendor/crates/spikard-core/tests/di_dependency_defaults.rs +0 -37
- data/vendor/crates/spikard-core/tests/error_mapper.rs +0 -761
- data/vendor/crates/spikard-core/tests/parameters_edge_cases.rs +0 -106
- data/vendor/crates/spikard-core/tests/parameters_full.rs +0 -701
- data/vendor/crates/spikard-core/tests/parameters_schema_and_formats.rs +0 -301
- data/vendor/crates/spikard-core/tests/request_data_roundtrip.rs +0 -67
- data/vendor/crates/spikard-core/tests/validation_coverage.rs +0 -250
- data/vendor/crates/spikard-core/tests/validation_error_paths.rs +0 -45
- data/vendor/crates/spikard-http/Cargo.toml +0 -82
- data/vendor/crates/spikard-http/examples/sse-notifications.rs +0 -148
- data/vendor/crates/spikard-http/examples/websocket-chat.rs +0 -92
- data/vendor/crates/spikard-http/src/auth.rs +0 -301
- data/vendor/crates/spikard-http/src/background.rs +0 -1859
- data/vendor/crates/spikard-http/src/bindings/mod.rs +0 -3
- data/vendor/crates/spikard-http/src/bindings/response.rs +0 -1
- data/vendor/crates/spikard-http/src/body_metadata.rs +0 -8
- data/vendor/crates/spikard-http/src/cors.rs +0 -1026
- data/vendor/crates/spikard-http/src/debug.rs +0 -128
- data/vendor/crates/spikard-http/src/di_handler.rs +0 -1672
- data/vendor/crates/spikard-http/src/grpc/framing.rs +0 -653
- data/vendor/crates/spikard-http/src/grpc/handler.rs +0 -1211
- data/vendor/crates/spikard-http/src/grpc/mod.rs +0 -556
- data/vendor/crates/spikard-http/src/grpc/service.rs +0 -706
- data/vendor/crates/spikard-http/src/grpc/streaming.rs +0 -319
- data/vendor/crates/spikard-http/src/handler_response.rs +0 -901
- data/vendor/crates/spikard-http/src/handler_trait.rs +0 -1015
- data/vendor/crates/spikard-http/src/handler_trait_tests.rs +0 -290
- data/vendor/crates/spikard-http/src/jsonrpc/http_handler.rs +0 -502
- data/vendor/crates/spikard-http/src/jsonrpc/method_registry.rs +0 -648
- data/vendor/crates/spikard-http/src/jsonrpc/mod.rs +0 -60
- data/vendor/crates/spikard-http/src/jsonrpc/openrpc.rs +0 -325
- data/vendor/crates/spikard-http/src/jsonrpc/protocol.rs +0 -1207
- data/vendor/crates/spikard-http/src/jsonrpc/router.rs +0 -2262
- data/vendor/crates/spikard-http/src/lib.rs +0 -566
- data/vendor/crates/spikard-http/src/lifecycle/adapter.rs +0 -230
- data/vendor/crates/spikard-http/src/lifecycle.rs +0 -1193
- data/vendor/crates/spikard-http/src/middleware/mod.rs +0 -560
- data/vendor/crates/spikard-http/src/middleware/multipart.rs +0 -912
- data/vendor/crates/spikard-http/src/middleware/urlencoded.rs +0 -513
- data/vendor/crates/spikard-http/src/middleware/validation.rs +0 -768
- data/vendor/crates/spikard-http/src/openapi/mod.rs +0 -309
- data/vendor/crates/spikard-http/src/openapi/parameter_extraction.rs +0 -535
- data/vendor/crates/spikard-http/src/openapi/schema_conversion.rs +0 -1363
- data/vendor/crates/spikard-http/src/openapi/spec_generation.rs +0 -667
- data/vendor/crates/spikard-http/src/query_parser.rs +0 -793
- data/vendor/crates/spikard-http/src/response.rs +0 -720
- data/vendor/crates/spikard-http/src/server/fast_router.rs +0 -186
- data/vendor/crates/spikard-http/src/server/grpc_routing.rs +0 -1243
- data/vendor/crates/spikard-http/src/server/handler.rs +0 -1661
- data/vendor/crates/spikard-http/src/server/lifecycle_execution.rs +0 -253
- data/vendor/crates/spikard-http/src/server/mod.rs +0 -1717
- data/vendor/crates/spikard-http/src/server/request_extraction.rs +0 -871
- data/vendor/crates/spikard-http/src/server/routing_factory.rs +0 -618
- data/vendor/crates/spikard-http/src/sse.rs +0 -1409
- data/vendor/crates/spikard-http/src/testing/form.rs +0 -52
- data/vendor/crates/spikard-http/src/testing/multipart.rs +0 -64
- data/vendor/crates/spikard-http/src/testing/test_client.rs +0 -825
- data/vendor/crates/spikard-http/src/testing.rs +0 -617
- data/vendor/crates/spikard-http/src/websocket.rs +0 -1477
- data/vendor/crates/spikard-http/tests/auth_integration.rs +0 -645
- data/vendor/crates/spikard-http/tests/background_behavior.rs +0 -832
- data/vendor/crates/spikard-http/tests/common/grpc_helpers.rs +0 -1012
- data/vendor/crates/spikard-http/tests/common/handlers.rs +0 -309
- data/vendor/crates/spikard-http/tests/common/mod.rs +0 -33
- data/vendor/crates/spikard-http/tests/common/test_builders.rs +0 -628
- data/vendor/crates/spikard-http/tests/di_handler_error_responses.rs +0 -162
- data/vendor/crates/spikard-http/tests/di_integration.rs +0 -192
- data/vendor/crates/spikard-http/tests/doc_snippets.rs +0 -5
- data/vendor/crates/spikard-http/tests/grpc_bidirectional_streaming.rs +0 -430
- data/vendor/crates/spikard-http/tests/grpc_client_streaming.rs +0 -738
- data/vendor/crates/spikard-http/tests/grpc_error_handling_test.rs +0 -652
- data/vendor/crates/spikard-http/tests/grpc_integration_test.rs +0 -334
- data/vendor/crates/spikard-http/tests/grpc_metadata_test.rs +0 -532
- data/vendor/crates/spikard-http/tests/grpc_server_integration.rs +0 -495
- data/vendor/crates/spikard-http/tests/grpc_server_streaming.rs +0 -975
- data/vendor/crates/spikard-http/tests/lifecycle_execution.rs +0 -1093
- data/vendor/crates/spikard-http/tests/middleware_stack_integration.rs +0 -389
- data/vendor/crates/spikard-http/tests/multipart_behavior.rs +0 -656
- data/vendor/crates/spikard-http/tests/request_extraction_full.rs +0 -513
- data/vendor/crates/spikard-http/tests/server_auth_middleware_behavior.rs +0 -328
- data/vendor/crates/spikard-http/tests/server_config_builder.rs +0 -335
- data/vendor/crates/spikard-http/tests/server_configured_router_behavior.rs +0 -374
- data/vendor/crates/spikard-http/tests/server_cors_preflight.rs +0 -83
- data/vendor/crates/spikard-http/tests/server_handler_wrappers.rs +0 -464
- data/vendor/crates/spikard-http/tests/server_method_router_additional_behavior.rs +0 -286
- data/vendor/crates/spikard-http/tests/server_method_router_coverage.rs +0 -118
- data/vendor/crates/spikard-http/tests/server_middleware_behavior.rs +0 -99
- data/vendor/crates/spikard-http/tests/server_middleware_branches.rs +0 -204
- data/vendor/crates/spikard-http/tests/server_openapi_jsonrpc_static.rs +0 -427
- data/vendor/crates/spikard-http/tests/server_router_behavior.rs +0 -121
- data/vendor/crates/spikard-http/tests/sse_behavior.rs +0 -620
- data/vendor/crates/spikard-http/tests/sse_full_behavior.rs +0 -584
- data/vendor/crates/spikard-http/tests/sse_handler_behavior.rs +0 -130
- data/vendor/crates/spikard-http/tests/test_client_requests.rs +0 -167
- data/vendor/crates/spikard-http/tests/testing_helpers.rs +0 -87
- data/vendor/crates/spikard-http/tests/testing_module_coverage.rs +0 -155
- data/vendor/crates/spikard-http/tests/urlencoded_content_type.rs +0 -82
- data/vendor/crates/spikard-http/tests/websocket_behavior.rs +0 -663
- data/vendor/crates/spikard-http/tests/websocket_full_behavior.rs +0 -440
- data/vendor/crates/spikard-http/tests/websocket_integration.rs +0 -150
- data/vendor/crates/spikard-rb/Cargo.toml +0 -63
- data/vendor/crates/spikard-rb/build.rs +0 -200
- data/vendor/crates/spikard-rb/src/background.rs +0 -63
- data/vendor/crates/spikard-rb/src/config/mod.rs +0 -5
- data/vendor/crates/spikard-rb/src/config/server_config.rs +0 -401
- data/vendor/crates/spikard-rb/src/conversion.rs +0 -688
- data/vendor/crates/spikard-rb/src/di/builder.rs +0 -100
- data/vendor/crates/spikard-rb/src/di/mod.rs +0 -410
- data/vendor/crates/spikard-rb/src/grpc/handler.rs +0 -875
- data/vendor/crates/spikard-rb/src/grpc/mod.rs +0 -13
- data/vendor/crates/spikard-rb/src/gvl.rs +0 -80
- data/vendor/crates/spikard-rb/src/handler.rs +0 -699
- data/vendor/crates/spikard-rb/src/integration/mod.rs +0 -3
- data/vendor/crates/spikard-rb/src/lib.rs +0 -2268
- data/vendor/crates/spikard-rb/src/lifecycle.rs +0 -334
- data/vendor/crates/spikard-rb/src/metadata/mod.rs +0 -5
- data/vendor/crates/spikard-rb/src/metadata/route_extraction.rs +0 -507
- data/vendor/crates/spikard-rb/src/request.rs +0 -439
- data/vendor/crates/spikard-rb/src/runtime/mod.rs +0 -5
- data/vendor/crates/spikard-rb/src/runtime/server_runner.rs +0 -368
- data/vendor/crates/spikard-rb/src/server.rs +0 -304
- data/vendor/crates/spikard-rb/src/sse.rs +0 -231
- data/vendor/crates/spikard-rb/src/testing/client.rs +0 -698
- data/vendor/crates/spikard-rb/src/testing/mod.rs +0 -7
- data/vendor/crates/spikard-rb/src/testing/sse.rs +0 -108
- data/vendor/crates/spikard-rb/src/testing/websocket.rs +0 -573
- data/vendor/crates/spikard-rb/src/websocket.rs +0 -521
- data/vendor/crates/spikard-rb-macros/Cargo.toml +0 -20
- data/vendor/crates/spikard-rb-macros/src/lib.rs +0 -51
|
@@ -1,653 +0,0 @@
|
|
|
1
|
-
//! HTTP/2 gRPC frame parsing for client streaming support
|
|
2
|
-
//!
|
|
3
|
-
//! This module provides parsing of gRPC messages from HTTP/2 request bodies.
|
|
4
|
-
//! gRPC frames are structured according to RFC 9109 (gRPC over HTTP/2):
|
|
5
|
-
//!
|
|
6
|
-
//! ```text
|
|
7
|
-
//! +----------+----------+-+-+-+-+-+-+-+-+
|
|
8
|
-
//! |Compression| Length |
|
|
9
|
-
//! | Flags (1) | (4 bytes) |
|
|
10
|
-
//! +----------+----------+-+-+-+-+-+-+-+-+-+-+
|
|
11
|
-
//! | |
|
|
12
|
-
//! | Serialized Message (N bytes) |
|
|
13
|
-
//! | |
|
|
14
|
-
//! +-------------------------------------+
|
|
15
|
-
//! ```
|
|
16
|
-
//!
|
|
17
|
-
//! The compression flag indicates whether the message is compressed (1 = compressed, 0 = uncompressed).
|
|
18
|
-
//! The length is encoded as a big-endian u32, indicating the size of the message bytes.
|
|
19
|
-
//!
|
|
20
|
-
//! # Protocol Details
|
|
21
|
-
//!
|
|
22
|
-
//! - **Compression Flag**: 1 byte, value 0 or 1
|
|
23
|
-
//! - **Message Length**: 4 bytes, big-endian u32, maximum 4GB
|
|
24
|
-
//! - **Message Data**: N bytes, where N is the length from the header
|
|
25
|
-
//!
|
|
26
|
-
//! # Stream Processing
|
|
27
|
-
//!
|
|
28
|
-
//! The parser processes the HTTP/2 body stream by:
|
|
29
|
-
//! 1. Reading the 5-byte frame header (compression flag + length)
|
|
30
|
-
//! 2. Parsing the length as big-endian u32
|
|
31
|
-
//! 3. Validating the length against `max_message_size`
|
|
32
|
-
//! 4. Reading the message bytes
|
|
33
|
-
//! 5. Yielding the message
|
|
34
|
-
//! 6. Repeating until the body is exhausted
|
|
35
|
-
//!
|
|
36
|
-
//! # Error Handling
|
|
37
|
-
//!
|
|
38
|
-
//! The parser returns gRPC status codes according to RFC 9110:
|
|
39
|
-
//! - `INTERNAL`: Protocol parsing errors (incomplete frames, read errors)
|
|
40
|
-
//! - `RESOURCE_EXHAUSTED`: Message size exceeds limit
|
|
41
|
-
//! - `UNIMPLEMENTED`: Unsupported compression algorithm or compression disabled
|
|
42
|
-
//!
|
|
43
|
-
//! # Example
|
|
44
|
-
//!
|
|
45
|
-
//! ```ignore
|
|
46
|
-
//! use spikard_http::grpc::framing::parse_grpc_client_stream;
|
|
47
|
-
//! use axum::body::Body;
|
|
48
|
-
//! use bytes::Bytes;
|
|
49
|
-
//! use futures_util::StreamExt;
|
|
50
|
-
//!
|
|
51
|
-
//! let body = Body::from("...");
|
|
52
|
-
//! let max_size = 4 * 1024 * 1024; // 4MB
|
|
53
|
-
//! let mut stream = parse_grpc_client_stream(body, max_size, None, true).await?;
|
|
54
|
-
//!
|
|
55
|
-
//! while let Some(result) = stream.next().await {
|
|
56
|
-
//! match result {
|
|
57
|
-
//! Ok(message) => println!("Message: {:?}", message),
|
|
58
|
-
//! Err(status) => eprintln!("Error: {}", status),
|
|
59
|
-
//! }
|
|
60
|
-
//! }
|
|
61
|
-
//! ```
|
|
62
|
-
|
|
63
|
-
use bytes::{Buf, BufMut, Bytes, BytesMut};
|
|
64
|
-
use flate2::read::GzDecoder;
|
|
65
|
-
use futures_util::stream;
|
|
66
|
-
use std::io::Read;
|
|
67
|
-
use tonic::Status;
|
|
68
|
-
|
|
69
|
-
use super::streaming::MessageStream;
|
|
70
|
-
|
|
71
|
-
/// Size of the gRPC message header in bytes.
|
|
72
|
-
///
|
|
73
|
-
/// The header consists of:
|
|
74
|
-
/// - 1 byte compression flag
|
|
75
|
-
/// - 4 bytes big-endian message length
|
|
76
|
-
pub const GRPC_MESSAGE_HEADER_LEN: usize = 5;
|
|
77
|
-
|
|
78
|
-
/// Parses a unary gRPC payload from framed HTTP/2 body bytes.
|
|
79
|
-
///
|
|
80
|
-
/// Unary and server-streaming requests must carry exactly one framed message.
|
|
81
|
-
pub fn parse_unary_grpc_message(
|
|
82
|
-
framed_body: &[u8],
|
|
83
|
-
max_message_size: usize,
|
|
84
|
-
grpc_encoding: Option<&str>,
|
|
85
|
-
compression_enabled: bool,
|
|
86
|
-
) -> Result<Bytes, Status> {
|
|
87
|
-
let messages = parse_all_frames(
|
|
88
|
-
BytesMut::from(framed_body),
|
|
89
|
-
max_message_size,
|
|
90
|
-
grpc_encoding,
|
|
91
|
-
compression_enabled,
|
|
92
|
-
)?;
|
|
93
|
-
|
|
94
|
-
match messages.len() {
|
|
95
|
-
1 => Ok(messages.into_iter().next().expect("single message exists")),
|
|
96
|
-
count => Err(Status::invalid_argument(format!(
|
|
97
|
-
"Unary gRPC request must contain exactly one message frame, got {}",
|
|
98
|
-
count
|
|
99
|
-
))),
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/// Encodes a protobuf payload into a single gRPC message frame.
|
|
104
|
-
pub fn encode_grpc_message(payload: Bytes) -> Result<Bytes, Status> {
|
|
105
|
-
let message_length = u32::try_from(payload.len())
|
|
106
|
-
.map_err(|_| Status::resource_exhausted("gRPC message exceeds 4GB frame length limit"))?;
|
|
107
|
-
|
|
108
|
-
let mut framed = BytesMut::with_capacity(GRPC_MESSAGE_HEADER_LEN + payload.len());
|
|
109
|
-
framed.put_u8(0); // compression flag: uncompressed
|
|
110
|
-
framed.put_u32(message_length);
|
|
111
|
-
framed.extend_from_slice(&payload);
|
|
112
|
-
|
|
113
|
-
Ok(framed.freeze())
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/// Parses an HTTP/2 gRPC request body as a stream of messages
|
|
117
|
-
///
|
|
118
|
-
/// Reads the gRPC frame format from the body stream, validating each frame
|
|
119
|
-
/// and yielding individual message bytes.
|
|
120
|
-
///
|
|
121
|
-
/// # Arguments
|
|
122
|
-
///
|
|
123
|
-
/// * `body` - The HTTP/2 request body stream
|
|
124
|
-
/// * `max_message_size` - Maximum allowed message size in bytes (validated per message)
|
|
125
|
-
/// * `grpc_encoding` - Value of the request `grpc-encoding` header, if present
|
|
126
|
-
/// * `compression_enabled` - Whether compressed gRPC payloads are allowed
|
|
127
|
-
///
|
|
128
|
-
/// # Returns
|
|
129
|
-
///
|
|
130
|
-
/// A `MessageStream` yielding:
|
|
131
|
-
/// - `Ok(Bytes)`: A complete parsed message
|
|
132
|
-
/// - `Err(Status)`: A gRPC protocol error
|
|
133
|
-
///
|
|
134
|
-
/// # Errors
|
|
135
|
-
///
|
|
136
|
-
/// Returns gRPC errors for:
|
|
137
|
-
/// - Incomplete frame (EOF before 5-byte header): `INTERNAL`
|
|
138
|
-
/// - Incomplete message (EOF before all message bytes): `INTERNAL`
|
|
139
|
-
/// - Message size > max_message_size: `RESOURCE_EXHAUSTED`
|
|
140
|
-
/// - Compression flag set without valid `grpc-encoding`: `INVALID_ARGUMENT`
|
|
141
|
-
/// - Unsupported or disabled compression: `UNIMPLEMENTED`
|
|
142
|
-
/// - Read errors from the body stream: `INTERNAL`
|
|
143
|
-
///
|
|
144
|
-
/// # Example
|
|
145
|
-
///
|
|
146
|
-
/// ```ignore
|
|
147
|
-
/// let body = Body::from(vec![
|
|
148
|
-
/// 0x00, // compression: no
|
|
149
|
-
/// 0x00, 0x00, 0x00, 0x05, // length: 5 bytes
|
|
150
|
-
/// b'h', b'e', b'l', b'l', b'o', // message
|
|
151
|
-
/// ]);
|
|
152
|
-
///
|
|
153
|
-
/// let stream = parse_grpc_client_stream(body, 1024, None, true).await?;
|
|
154
|
-
/// ```
|
|
155
|
-
pub async fn parse_grpc_client_stream(
|
|
156
|
-
body: axum::body::Body,
|
|
157
|
-
max_message_size: usize,
|
|
158
|
-
grpc_encoding: Option<&str>,
|
|
159
|
-
compression_enabled: bool,
|
|
160
|
-
) -> Result<MessageStream, Status> {
|
|
161
|
-
// Convert body into bytes
|
|
162
|
-
let body_bytes = axum::body::to_bytes(body, usize::MAX)
|
|
163
|
-
.await
|
|
164
|
-
.map_err(|e| Status::internal(format!("Failed to read body: {}", e)))?;
|
|
165
|
-
|
|
166
|
-
// Create a buffered reader
|
|
167
|
-
let buffer = BytesMut::from(&body_bytes[..]);
|
|
168
|
-
|
|
169
|
-
// Parse frames from the buffer
|
|
170
|
-
let messages = parse_all_frames(buffer, max_message_size, grpc_encoding, compression_enabled)?;
|
|
171
|
-
|
|
172
|
-
// Convert to a MessageStream
|
|
173
|
-
Ok(Box::pin(stream::iter(messages.into_iter().map(Ok))))
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/// Internal: Parse all frames from a buffer
|
|
177
|
-
fn parse_all_frames(
|
|
178
|
-
mut buffer: BytesMut,
|
|
179
|
-
max_message_size: usize,
|
|
180
|
-
grpc_encoding: Option<&str>,
|
|
181
|
-
compression_enabled: bool,
|
|
182
|
-
) -> Result<Vec<Bytes>, Status> {
|
|
183
|
-
let mut messages = Vec::new();
|
|
184
|
-
|
|
185
|
-
while !buffer.is_empty() {
|
|
186
|
-
// Check if we have enough bytes for the frame header
|
|
187
|
-
if buffer.len() < GRPC_MESSAGE_HEADER_LEN {
|
|
188
|
-
return Err(Status::internal(
|
|
189
|
-
"Incomplete gRPC frame header: expected 5 bytes, got less",
|
|
190
|
-
));
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Read the compression flag (1 byte)
|
|
194
|
-
let compression_flag = buffer[0];
|
|
195
|
-
if compression_flag > 1 {
|
|
196
|
-
return Err(Status::invalid_argument(format!(
|
|
197
|
-
"Invalid gRPC compression flag: {}",
|
|
198
|
-
compression_flag
|
|
199
|
-
)));
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Read the message length (4 bytes, big-endian)
|
|
203
|
-
let length_bytes = &buffer[1..GRPC_MESSAGE_HEADER_LEN];
|
|
204
|
-
let message_length =
|
|
205
|
-
u32::from_be_bytes([length_bytes[0], length_bytes[1], length_bytes[2], length_bytes[3]]) as usize;
|
|
206
|
-
|
|
207
|
-
// Validate message length against max size
|
|
208
|
-
if message_length > max_message_size {
|
|
209
|
-
return Err(Status::resource_exhausted(format!(
|
|
210
|
-
"Message size {} exceeds maximum allowed size of {}",
|
|
211
|
-
message_length, max_message_size
|
|
212
|
-
)));
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Check if we have the complete message
|
|
216
|
-
let total_frame_size = GRPC_MESSAGE_HEADER_LEN + message_length;
|
|
217
|
-
if buffer.len() < total_frame_size {
|
|
218
|
-
return Err(Status::internal(
|
|
219
|
-
"Incomplete gRPC message: expected more bytes than available",
|
|
220
|
-
));
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Extract the message bytes and decompress if needed.
|
|
224
|
-
let message_bytes = &buffer[GRPC_MESSAGE_HEADER_LEN..total_frame_size];
|
|
225
|
-
let message = if compression_flag == 0 {
|
|
226
|
-
Bytes::copy_from_slice(message_bytes)
|
|
227
|
-
} else {
|
|
228
|
-
decompress_message(message_bytes, grpc_encoding, compression_enabled, max_message_size)?
|
|
229
|
-
};
|
|
230
|
-
messages.push(message);
|
|
231
|
-
|
|
232
|
-
// Advance the buffer
|
|
233
|
-
buffer.advance(total_frame_size);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
Ok(messages)
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
fn decompress_message(
|
|
240
|
-
message_bytes: &[u8],
|
|
241
|
-
grpc_encoding: Option<&str>,
|
|
242
|
-
compression_enabled: bool,
|
|
243
|
-
max_message_size: usize,
|
|
244
|
-
) -> Result<Bytes, Status> {
|
|
245
|
-
if !compression_enabled {
|
|
246
|
-
return Err(Status::unimplemented(
|
|
247
|
-
"gRPC message compression is disabled by server configuration",
|
|
248
|
-
));
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
let encoding = grpc_encoding
|
|
252
|
-
.map(|value| value.trim().to_ascii_lowercase())
|
|
253
|
-
.ok_or_else(|| Status::invalid_argument("Compressed gRPC message missing grpc-encoding header"))?;
|
|
254
|
-
|
|
255
|
-
let decompressed = match encoding.as_str() {
|
|
256
|
-
"gzip" => {
|
|
257
|
-
let mut decoder = GzDecoder::new(message_bytes);
|
|
258
|
-
let mut out = Vec::new();
|
|
259
|
-
decoder
|
|
260
|
-
.read_to_end(&mut out)
|
|
261
|
-
.map_err(|e| Status::internal(format!("Failed to decompress gzip gRPC frame: {}", e)))?;
|
|
262
|
-
out
|
|
263
|
-
}
|
|
264
|
-
"identity" => {
|
|
265
|
-
return Err(Status::invalid_argument(
|
|
266
|
-
"Compressed gRPC frame cannot use grpc-encoding=identity",
|
|
267
|
-
));
|
|
268
|
-
}
|
|
269
|
-
other => {
|
|
270
|
-
return Err(Status::unimplemented(format!("Unsupported grpc-encoding '{}'", other)));
|
|
271
|
-
}
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
if decompressed.len() > max_message_size {
|
|
275
|
-
return Err(Status::resource_exhausted(format!(
|
|
276
|
-
"Decompressed message size {} exceeds maximum allowed size of {}",
|
|
277
|
-
decompressed.len(),
|
|
278
|
-
max_message_size
|
|
279
|
-
)));
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
Ok(Bytes::from(decompressed))
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
#[cfg(test)]
|
|
286
|
-
mod tests {
|
|
287
|
-
use super::*;
|
|
288
|
-
use flate2::{Compression, write::GzEncoder};
|
|
289
|
-
use futures_util::StreamExt;
|
|
290
|
-
use std::io::Write;
|
|
291
|
-
|
|
292
|
-
#[tokio::test]
|
|
293
|
-
async fn test_single_frame_parsing() {
|
|
294
|
-
// Frame: compression=0, length=5, message="hello"
|
|
295
|
-
let frame = vec![
|
|
296
|
-
0x00, // compression: no
|
|
297
|
-
0x00, 0x00, 0x00, 0x05, // length: 5 bytes (big-endian)
|
|
298
|
-
b'h', b'e', b'l', b'l', b'o', // message
|
|
299
|
-
];
|
|
300
|
-
|
|
301
|
-
let body = axum::body::Body::from(frame);
|
|
302
|
-
let mut stream = parse_grpc_client_stream(body, 1024, None, true).await.unwrap();
|
|
303
|
-
let msg = stream.next().await;
|
|
304
|
-
|
|
305
|
-
assert!(msg.is_some());
|
|
306
|
-
assert!(msg.unwrap().is_ok());
|
|
307
|
-
let result = stream.next().await;
|
|
308
|
-
assert!(result.is_none());
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
#[test]
|
|
312
|
-
fn test_encode_grpc_message_adds_framing_header() {
|
|
313
|
-
let framed = encode_grpc_message(Bytes::from_static(b"hello")).unwrap();
|
|
314
|
-
|
|
315
|
-
assert_eq!(framed[0], 0x00);
|
|
316
|
-
assert_eq!(&framed[1..5], &[0x00, 0x00, 0x00, 0x05]);
|
|
317
|
-
assert_eq!(&framed[5..], b"hello");
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
#[test]
|
|
321
|
-
fn test_parse_unary_grpc_message_requires_exactly_one_frame() {
|
|
322
|
-
let mut body = Vec::new();
|
|
323
|
-
body.extend_from_slice(&[0x00, 0x00, 0x00, 0x00, 0x01, b'a']);
|
|
324
|
-
body.extend_from_slice(&[0x00, 0x00, 0x00, 0x00, 0x01, b'b']);
|
|
325
|
-
|
|
326
|
-
let err = parse_unary_grpc_message(&body, 1024, None, true).unwrap_err();
|
|
327
|
-
assert_eq!(err.code(), tonic::Code::InvalidArgument);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
#[tokio::test]
|
|
331
|
-
async fn test_multiple_frames() {
|
|
332
|
-
// Two frames back-to-back
|
|
333
|
-
let mut frame = Vec::new();
|
|
334
|
-
|
|
335
|
-
// Frame 1: "hello"
|
|
336
|
-
frame.push(0x00);
|
|
337
|
-
frame.extend_from_slice(&[0x00, 0x00, 0x00, 0x05]);
|
|
338
|
-
frame.extend_from_slice(b"hello");
|
|
339
|
-
|
|
340
|
-
// Frame 2: "world"
|
|
341
|
-
frame.push(0x00);
|
|
342
|
-
frame.extend_from_slice(&[0x00, 0x00, 0x00, 0x05]);
|
|
343
|
-
frame.extend_from_slice(b"world");
|
|
344
|
-
|
|
345
|
-
let body = axum::body::Body::from(frame);
|
|
346
|
-
let mut stream = parse_grpc_client_stream(body, 1024, None, true).await.unwrap();
|
|
347
|
-
|
|
348
|
-
let msg1 = stream.next().await;
|
|
349
|
-
assert!(msg1.is_some());
|
|
350
|
-
assert_eq!(msg1.unwrap().unwrap(), b"hello"[..]);
|
|
351
|
-
|
|
352
|
-
let msg2 = stream.next().await;
|
|
353
|
-
assert!(msg2.is_some());
|
|
354
|
-
assert_eq!(msg2.unwrap().unwrap(), b"world"[..]);
|
|
355
|
-
|
|
356
|
-
let msg3 = stream.next().await;
|
|
357
|
-
assert!(msg3.is_none());
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
#[tokio::test]
|
|
361
|
-
async fn test_empty_body() {
|
|
362
|
-
let body = axum::body::Body::from(Vec::<u8>::new());
|
|
363
|
-
let mut stream = parse_grpc_client_stream(body, 1024, None, true).await.unwrap();
|
|
364
|
-
|
|
365
|
-
let result = stream.next().await;
|
|
366
|
-
assert!(result.is_none());
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
#[tokio::test]
|
|
370
|
-
async fn test_frame_size_at_limit() {
|
|
371
|
-
let max_size = 10;
|
|
372
|
-
let message = b"0123456789"; // exactly 10 bytes
|
|
373
|
-
|
|
374
|
-
let mut frame = Vec::new();
|
|
375
|
-
frame.push(0x00);
|
|
376
|
-
frame.extend_from_slice(&[0x00, 0x00, 0x00, 0x0a]); // length: 10
|
|
377
|
-
frame.extend_from_slice(message);
|
|
378
|
-
|
|
379
|
-
let body = axum::body::Body::from(frame);
|
|
380
|
-
let mut stream = parse_grpc_client_stream(body, max_size, None, true).await.unwrap();
|
|
381
|
-
|
|
382
|
-
let msg = stream.next().await;
|
|
383
|
-
assert!(msg.is_some());
|
|
384
|
-
assert_eq!(msg.unwrap().unwrap(), message[..]);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
#[tokio::test]
|
|
388
|
-
async fn test_frame_exceeds_limit() {
|
|
389
|
-
let max_size = 5;
|
|
390
|
-
let message = b"toolong"; // 7 bytes, exceeds 5-byte limit
|
|
391
|
-
|
|
392
|
-
let mut frame = Vec::new();
|
|
393
|
-
frame.push(0x00);
|
|
394
|
-
frame.extend_from_slice(&[0x00, 0x00, 0x00, 0x07]); // length: 7
|
|
395
|
-
frame.extend_from_slice(message);
|
|
396
|
-
|
|
397
|
-
let body = axum::body::Body::from(frame);
|
|
398
|
-
let result = parse_grpc_client_stream(body, max_size, None, true).await;
|
|
399
|
-
|
|
400
|
-
assert!(result.is_err());
|
|
401
|
-
if let Err(status) = result {
|
|
402
|
-
assert_eq!(status.code(), tonic::Code::ResourceExhausted);
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
#[tokio::test]
|
|
407
|
-
async fn test_incomplete_frame_header() {
|
|
408
|
-
// Only 3 bytes of 5-byte header
|
|
409
|
-
let frame = vec![0x00, 0x00, 0x00];
|
|
410
|
-
|
|
411
|
-
let body = axum::body::Body::from(frame);
|
|
412
|
-
let result = parse_grpc_client_stream(body, 1024, None, true).await;
|
|
413
|
-
|
|
414
|
-
assert!(result.is_err());
|
|
415
|
-
if let Err(status) = result {
|
|
416
|
-
assert_eq!(status.code(), tonic::Code::Internal);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
#[tokio::test]
|
|
421
|
-
async fn test_incomplete_frame_body() {
|
|
422
|
-
// Header says 10 bytes but only provide 5
|
|
423
|
-
let mut frame = Vec::new();
|
|
424
|
-
frame.push(0x00);
|
|
425
|
-
frame.extend_from_slice(&[0x00, 0x00, 0x00, 0x0a]); // length: 10
|
|
426
|
-
frame.extend_from_slice(b"short"); // only 5 bytes
|
|
427
|
-
|
|
428
|
-
let body = axum::body::Body::from(frame);
|
|
429
|
-
let result = parse_grpc_client_stream(body, 1024, None, true).await;
|
|
430
|
-
|
|
431
|
-
assert!(result.is_err());
|
|
432
|
-
if let Err(status) = result {
|
|
433
|
-
assert_eq!(status.code(), tonic::Code::Internal);
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
#[tokio::test]
|
|
438
|
-
async fn test_compression_flag_set_with_missing_encoding_header() {
|
|
439
|
-
let mut frame = Vec::new();
|
|
440
|
-
frame.push(0x01); // compression: yes
|
|
441
|
-
frame.extend_from_slice(&[0x00, 0x00, 0x00, 0x05]);
|
|
442
|
-
frame.extend_from_slice(b"hello");
|
|
443
|
-
|
|
444
|
-
let body = axum::body::Body::from(frame);
|
|
445
|
-
let result = parse_grpc_client_stream(body, 1024, None, true).await;
|
|
446
|
-
|
|
447
|
-
assert!(result.is_err());
|
|
448
|
-
if let Err(status) = result {
|
|
449
|
-
assert_eq!(status.code(), tonic::Code::InvalidArgument);
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
#[tokio::test]
|
|
454
|
-
async fn test_compression_flag_set_with_unsupported_encoding() {
|
|
455
|
-
let mut frame = Vec::new();
|
|
456
|
-
frame.push(0x01); // compression: yes
|
|
457
|
-
frame.extend_from_slice(&[0x00, 0x00, 0x00, 0x05]);
|
|
458
|
-
frame.extend_from_slice(b"hello");
|
|
459
|
-
|
|
460
|
-
let body = axum::body::Body::from(frame);
|
|
461
|
-
let result = parse_grpc_client_stream(body, 1024, Some("br"), true).await;
|
|
462
|
-
|
|
463
|
-
assert!(result.is_err());
|
|
464
|
-
if let Err(status) = result {
|
|
465
|
-
assert_eq!(status.code(), tonic::Code::Unimplemented);
|
|
466
|
-
assert!(status.message().contains("Unsupported grpc-encoding"));
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
#[tokio::test]
|
|
471
|
-
async fn test_compression_flag_set_when_compression_disabled() {
|
|
472
|
-
let mut frame = Vec::new();
|
|
473
|
-
frame.push(0x01); // compression: yes
|
|
474
|
-
frame.extend_from_slice(&[0x00, 0x00, 0x00, 0x05]);
|
|
475
|
-
frame.extend_from_slice(b"hello");
|
|
476
|
-
|
|
477
|
-
let body = axum::body::Body::from(frame);
|
|
478
|
-
let result = parse_grpc_client_stream(body, 1024, Some("gzip"), false).await;
|
|
479
|
-
|
|
480
|
-
assert!(result.is_err());
|
|
481
|
-
if let Err(status) = result {
|
|
482
|
-
assert_eq!(status.code(), tonic::Code::Unimplemented);
|
|
483
|
-
assert!(status.message().contains("disabled"));
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
#[tokio::test]
|
|
488
|
-
async fn test_compression_flag_set_with_gzip_encoding_decompresses_message() {
|
|
489
|
-
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
|
|
490
|
-
encoder.write_all(b"hello").unwrap();
|
|
491
|
-
let compressed = encoder.finish().unwrap();
|
|
492
|
-
|
|
493
|
-
let mut frame = Vec::new();
|
|
494
|
-
frame.push(0x01); // compression: yes
|
|
495
|
-
frame.extend_from_slice(&(compressed.len() as u32).to_be_bytes());
|
|
496
|
-
frame.extend_from_slice(&compressed);
|
|
497
|
-
|
|
498
|
-
let body = axum::body::Body::from(frame);
|
|
499
|
-
let mut stream = parse_grpc_client_stream(body, 1024, Some("gzip"), true).await.unwrap();
|
|
500
|
-
|
|
501
|
-
let msg = stream.next().await.unwrap().unwrap();
|
|
502
|
-
assert_eq!(msg, Bytes::from_static(b"hello"));
|
|
503
|
-
assert!(stream.next().await.is_none());
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
#[tokio::test]
|
|
507
|
-
async fn test_large_message_length() {
|
|
508
|
-
// Test with large length value (but within max_message_size for this test)
|
|
509
|
-
let message = b"x".repeat(1000);
|
|
510
|
-
let mut frame = Vec::new();
|
|
511
|
-
frame.push(0x00);
|
|
512
|
-
frame.extend_from_slice(&[0x00, 0x00, 0x03, 0xe8]); // length: 1000 (big-endian)
|
|
513
|
-
frame.extend_from_slice(&message);
|
|
514
|
-
|
|
515
|
-
let body = axum::body::Body::from(frame);
|
|
516
|
-
let mut stream = parse_grpc_client_stream(body, 2000, None, true).await.unwrap();
|
|
517
|
-
|
|
518
|
-
let msg = stream.next().await;
|
|
519
|
-
assert!(msg.is_some());
|
|
520
|
-
assert_eq!(msg.unwrap().unwrap().len(), 1000);
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
#[tokio::test]
|
|
524
|
-
async fn test_zero_length_message() {
|
|
525
|
-
// Frame with 0-byte message (valid in gRPC)
|
|
526
|
-
let mut frame = Vec::new();
|
|
527
|
-
frame.push(0x00);
|
|
528
|
-
frame.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); // length: 0
|
|
529
|
-
|
|
530
|
-
let body = axum::body::Body::from(frame);
|
|
531
|
-
let mut stream = parse_grpc_client_stream(body, 1024, None, true).await.unwrap();
|
|
532
|
-
|
|
533
|
-
let msg = stream.next().await;
|
|
534
|
-
assert!(msg.is_some());
|
|
535
|
-
assert_eq!(msg.unwrap().unwrap().len(), 0);
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
#[tokio::test]
|
|
539
|
-
async fn test_multiple_frames_with_mixed_sizes() {
|
|
540
|
-
let mut frame = Vec::new();
|
|
541
|
-
|
|
542
|
-
// Frame 1: "abc" (3 bytes)
|
|
543
|
-
frame.push(0x00);
|
|
544
|
-
frame.extend_from_slice(&[0x00, 0x00, 0x00, 0x03]);
|
|
545
|
-
frame.extend_from_slice(b"abc");
|
|
546
|
-
|
|
547
|
-
// Frame 2: "defghij" (7 bytes)
|
|
548
|
-
frame.push(0x00);
|
|
549
|
-
frame.extend_from_slice(&[0x00, 0x00, 0x00, 0x07]);
|
|
550
|
-
frame.extend_from_slice(b"defghij");
|
|
551
|
-
|
|
552
|
-
// Frame 3: "" (0 bytes)
|
|
553
|
-
frame.push(0x00);
|
|
554
|
-
frame.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
|
|
555
|
-
|
|
556
|
-
// Frame 4: "x" (1 byte)
|
|
557
|
-
frame.push(0x00);
|
|
558
|
-
frame.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
|
|
559
|
-
frame.extend_from_slice(b"x");
|
|
560
|
-
|
|
561
|
-
let body = axum::body::Body::from(frame);
|
|
562
|
-
let mut stream = parse_grpc_client_stream(body, 1024, None, true).await.unwrap();
|
|
563
|
-
|
|
564
|
-
let msg1 = stream.next().await.unwrap().unwrap();
|
|
565
|
-
assert_eq!(msg1, b"abc"[..]);
|
|
566
|
-
|
|
567
|
-
let msg2 = stream.next().await.unwrap().unwrap();
|
|
568
|
-
assert_eq!(msg2, b"defghij"[..]);
|
|
569
|
-
|
|
570
|
-
let msg3 = stream.next().await.unwrap().unwrap();
|
|
571
|
-
assert_eq!(msg3.len(), 0);
|
|
572
|
-
|
|
573
|
-
let msg4 = stream.next().await.unwrap().unwrap();
|
|
574
|
-
assert_eq!(msg4, b"x"[..]);
|
|
575
|
-
|
|
576
|
-
let msg5 = stream.next().await;
|
|
577
|
-
assert!(msg5.is_none());
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
#[test]
|
|
581
|
-
fn test_big_endian_length_parsing() {
|
|
582
|
-
// Test that length is correctly parsed as big-endian
|
|
583
|
-
// Big-endian u32(256) = bytes [0x00, 0x00, 0x01, 0x00]
|
|
584
|
-
let buffer = BytesMut::from(
|
|
585
|
-
&[
|
|
586
|
-
0x00, // compression flag
|
|
587
|
-
0x00, 0x00, 0x01, 0x00, // length: 256 in big-endian
|
|
588
|
-
][..],
|
|
589
|
-
);
|
|
590
|
-
|
|
591
|
-
// Extract the 4-byte length manually to verify
|
|
592
|
-
let length_bytes = &buffer[1..5];
|
|
593
|
-
let length = u32::from_be_bytes([length_bytes[0], length_bytes[1], length_bytes[2], length_bytes[3]]);
|
|
594
|
-
|
|
595
|
-
assert_eq!(length, 256);
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
#[test]
|
|
599
|
-
fn test_big_endian_max_value() {
|
|
600
|
-
// Test maximum u32 value in big-endian
|
|
601
|
-
let buffer = BytesMut::from(
|
|
602
|
-
&[
|
|
603
|
-
0x00, 0xff, 0xff, 0xff, 0xff, // max u32
|
|
604
|
-
][..],
|
|
605
|
-
);
|
|
606
|
-
|
|
607
|
-
let length_bytes = &buffer[1..5];
|
|
608
|
-
let length = u32::from_be_bytes([length_bytes[0], length_bytes[1], length_bytes[2], length_bytes[3]]);
|
|
609
|
-
|
|
610
|
-
assert_eq!(length, u32::MAX);
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
#[tokio::test]
|
|
614
|
-
async fn test_error_message_includes_size_info() {
|
|
615
|
-
let max_size = 100;
|
|
616
|
-
let message = b"x".repeat(150);
|
|
617
|
-
|
|
618
|
-
let mut frame = Vec::new();
|
|
619
|
-
frame.push(0x00);
|
|
620
|
-
frame.extend_from_slice(&[0x00, 0x00, 0x00, 0x96]); // length: 150
|
|
621
|
-
frame.extend_from_slice(&message);
|
|
622
|
-
|
|
623
|
-
let body = axum::body::Body::from(frame);
|
|
624
|
-
let result = parse_grpc_client_stream(body, max_size, None, true).await;
|
|
625
|
-
|
|
626
|
-
assert!(result.is_err());
|
|
627
|
-
if let Err(status) = result {
|
|
628
|
-
assert!(status.message().contains("150"));
|
|
629
|
-
assert!(status.message().contains("100"));
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
#[tokio::test]
|
|
634
|
-
async fn test_stream_collects_all_messages() {
|
|
635
|
-
// Ensure that the returned stream properly yields all messages
|
|
636
|
-
let mut frame = Vec::new();
|
|
637
|
-
|
|
638
|
-
for i in 0..10 {
|
|
639
|
-
frame.push(0x00);
|
|
640
|
-
frame.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
|
|
641
|
-
frame.push(b'0' + i as u8);
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
let body = axum::body::Body::from(frame);
|
|
645
|
-
let stream = parse_grpc_client_stream(body, 1024, None, true).await.unwrap();
|
|
646
|
-
let messages: Vec<_> = futures_util::StreamExt::collect(stream).await;
|
|
647
|
-
|
|
648
|
-
assert_eq!(messages.len(), 10);
|
|
649
|
-
for (i, msg) in messages.iter().enumerate() {
|
|
650
|
-
assert_eq!(msg.as_ref().unwrap()[0], b'0' + i as u8);
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
}
|