starpc 0.41.2 → 0.43.1
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.
- package/README.md +101 -20
- package/dist/mock/mock.pb.d.ts +1 -1
- package/dist/mock/mock.pb.js +4 -5
- package/dist/mock/mock_srpc.pb.d.ts +3 -3
- package/dist/mock/mock_srpc.pb.js +5 -5
- package/echo/Cargo.toml +21 -0
- package/echo/build.rs +15 -0
- package/echo/echo.pb.cc +405 -0
- package/echo/echo.pb.go +9 -27
- package/echo/echo.pb.h +364 -0
- package/echo/echo_srpc.pb.go +1 -1
- package/echo/gen/mod.rs +3 -0
- package/echo/main.rs +162 -0
- package/go.mod +18 -10
- package/go.sum +28 -18
- package/mock/mock.pb.cc +394 -0
- package/mock/mock.pb.go +9 -27
- package/mock/mock.pb.h +366 -0
- package/mock/mock.pb.ts +11 -13
- package/mock/mock_srpc.pb.go +1 -1
- package/mock/mock_srpc.pb.ts +12 -9
- package/package.json +27 -25
- package/srpc/Cargo.toml +26 -0
- package/srpc/build.rs +15 -0
- package/srpc/client.rs +356 -0
- package/srpc/codec.rs +225 -0
- package/srpc/error.rs +177 -0
- package/srpc/handler.rs +163 -0
- package/srpc/invoker.rs +192 -0
- package/srpc/lib.rs +107 -0
- package/srpc/message.rs +9 -0
- package/srpc/mux.rs +353 -0
- package/srpc/packet.rs +334 -0
- package/srpc/proto/mod.rs +10 -0
- package/srpc/rpc.rs +777 -0
- package/srpc/rpcproto.pb.cc +1381 -0
- package/srpc/rpcproto.pb.go +75 -183
- package/srpc/rpcproto.pb.h +1451 -0
- package/srpc/server.rs +337 -0
- package/srpc/stream.rs +304 -0
- package/srpc/testing.rs +290 -0
- package/srpc/tests/integration_test.rs +495 -0
- package/srpc/transport.rs +218 -0
- package/Makefile +0 -154
package/srpc/client.rs
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
//! Client implementation for starpc.
|
|
2
|
+
//!
|
|
3
|
+
//! This module provides the client-side API for making RPC calls,
|
|
4
|
+
//! supporting unary, client streaming, server streaming, and
|
|
5
|
+
//! bidirectional streaming patterns.
|
|
6
|
+
|
|
7
|
+
use async_trait::async_trait;
|
|
8
|
+
use bytes::Bytes;
|
|
9
|
+
use prost::Message;
|
|
10
|
+
use std::sync::Arc;
|
|
11
|
+
|
|
12
|
+
use crate::error::{Error, Result};
|
|
13
|
+
use crate::rpc::{ClientRpc, PacketWriter};
|
|
14
|
+
use crate::stream::{Context, Stream, StreamExt};
|
|
15
|
+
use crate::transport::create_packet_channel;
|
|
16
|
+
|
|
17
|
+
/// Receiver for incoming packets.
|
|
18
|
+
pub type PacketReceiver = tokio::sync::mpsc::Receiver<crate::proto::Packet>;
|
|
19
|
+
|
|
20
|
+
/// Trait for opening streams to a remote server.
|
|
21
|
+
///
|
|
22
|
+
/// Implementations of this trait provide the transport layer for RPC calls.
|
|
23
|
+
/// Each call to `open_stream` should return a new, independent stream.
|
|
24
|
+
#[async_trait]
|
|
25
|
+
pub trait OpenStream: Send + Sync {
|
|
26
|
+
/// Opens a new stream and returns a packet writer and a receiver for incoming packets.
|
|
27
|
+
async fn open_stream(&self) -> Result<(Arc<dyn PacketWriter>, PacketReceiver)>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/// Client trait for making RPC calls.
|
|
31
|
+
///
|
|
32
|
+
/// This trait defines the core client operations matching the Go Client interface.
|
|
33
|
+
#[async_trait]
|
|
34
|
+
pub trait Client: Send + Sync {
|
|
35
|
+
/// Executes a unary RPC call.
|
|
36
|
+
///
|
|
37
|
+
/// Sends the input message and waits for a single response.
|
|
38
|
+
async fn exec_call<I, O>(&self, service: &str, method: &str, input: &I) -> Result<O>
|
|
39
|
+
where
|
|
40
|
+
I: Message + Send + Sync,
|
|
41
|
+
O: Message + Default;
|
|
42
|
+
|
|
43
|
+
/// Opens a new stream for a streaming RPC.
|
|
44
|
+
///
|
|
45
|
+
/// # Arguments
|
|
46
|
+
/// * `service` - The service ID
|
|
47
|
+
/// * `method` - The method ID
|
|
48
|
+
/// * `first_msg` - Optional initial message data
|
|
49
|
+
async fn new_stream(
|
|
50
|
+
&self,
|
|
51
|
+
service: &str,
|
|
52
|
+
method: &str,
|
|
53
|
+
first_msg: Option<&[u8]>,
|
|
54
|
+
) -> Result<Box<dyn Stream>>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// Boxed Client trait object.
|
|
58
|
+
pub type BoxClient = Box<dyn Client>;
|
|
59
|
+
|
|
60
|
+
/// Standard starpc client implementation.
|
|
61
|
+
///
|
|
62
|
+
/// This is the primary client implementation that works with any transport
|
|
63
|
+
/// implementing the `OpenStream` trait.
|
|
64
|
+
pub struct SrpcClient<T: OpenStream> {
|
|
65
|
+
/// The stream opener.
|
|
66
|
+
opener: T,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
impl<T: OpenStream> SrpcClient<T> {
|
|
70
|
+
/// Creates a new client with the given stream opener.
|
|
71
|
+
pub fn new(opener: T) -> Self {
|
|
72
|
+
Self { opener }
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
#[async_trait]
|
|
77
|
+
impl<T: OpenStream + 'static> Client for SrpcClient<T> {
|
|
78
|
+
async fn exec_call<I, O>(&self, service: &str, method: &str, input: &I) -> Result<O>
|
|
79
|
+
where
|
|
80
|
+
I: Message + Send + Sync,
|
|
81
|
+
O: Message + Default,
|
|
82
|
+
{
|
|
83
|
+
// Marshal the input.
|
|
84
|
+
let input_data = input.encode_to_vec();
|
|
85
|
+
|
|
86
|
+
// Open a stream.
|
|
87
|
+
let (writer, mut receiver) = self.opener.open_stream().await?;
|
|
88
|
+
|
|
89
|
+
// Create the client RPC.
|
|
90
|
+
let ctx = Context::new();
|
|
91
|
+
let rpc = Arc::new(ClientRpc::new(
|
|
92
|
+
ctx.clone(),
|
|
93
|
+
service.to_string(),
|
|
94
|
+
method.to_string(),
|
|
95
|
+
writer,
|
|
96
|
+
));
|
|
97
|
+
|
|
98
|
+
// Start the RPC with the input data.
|
|
99
|
+
rpc.start(Some(Bytes::from(input_data))).await?;
|
|
100
|
+
|
|
101
|
+
// Spawn a task to handle incoming packets.
|
|
102
|
+
let rpc_clone = rpc.clone();
|
|
103
|
+
let packet_handler = tokio::spawn(async move {
|
|
104
|
+
while let Some(packet) = receiver.recv().await {
|
|
105
|
+
if rpc_clone.handle_packet(packet).await.is_err() {
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
let _ = rpc_clone.handle_stream_close(None).await;
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Send close to indicate we're done sending.
|
|
113
|
+
rpc.close_send().await?;
|
|
114
|
+
|
|
115
|
+
// Receive the response.
|
|
116
|
+
let output: O = rpc.msg_recv().await?;
|
|
117
|
+
|
|
118
|
+
// Wait for the RPC to complete properly (receive any trailing packets).
|
|
119
|
+
// This ensures we process any completion/error packets from the server
|
|
120
|
+
// before cleaning up.
|
|
121
|
+
let _ = rpc.wait().await;
|
|
122
|
+
|
|
123
|
+
// Close the RPC to signal completion.
|
|
124
|
+
let _ = rpc.close().await;
|
|
125
|
+
|
|
126
|
+
// Clean up the packet handler.
|
|
127
|
+
packet_handler.abort();
|
|
128
|
+
|
|
129
|
+
Ok(output)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async fn new_stream(
|
|
133
|
+
&self,
|
|
134
|
+
service: &str,
|
|
135
|
+
method: &str,
|
|
136
|
+
first_msg: Option<&[u8]>,
|
|
137
|
+
) -> Result<Box<dyn Stream>> {
|
|
138
|
+
// Open a stream.
|
|
139
|
+
let (writer, mut receiver) = self.opener.open_stream().await?;
|
|
140
|
+
|
|
141
|
+
// Create the client RPC.
|
|
142
|
+
let ctx = Context::new();
|
|
143
|
+
let rpc = Arc::new(ClientRpc::new(
|
|
144
|
+
ctx.clone(),
|
|
145
|
+
service.to_string(),
|
|
146
|
+
method.to_string(),
|
|
147
|
+
writer,
|
|
148
|
+
));
|
|
149
|
+
|
|
150
|
+
// Start the RPC.
|
|
151
|
+
let first_data = first_msg.map(|d| Bytes::from(d.to_vec()));
|
|
152
|
+
rpc.start(first_data).await?;
|
|
153
|
+
|
|
154
|
+
// Spawn a task to handle incoming packets.
|
|
155
|
+
let rpc_clone = rpc.clone();
|
|
156
|
+
let packet_handler = tokio::spawn(async move {
|
|
157
|
+
while let Some(packet) = receiver.recv().await {
|
|
158
|
+
if rpc_clone.handle_packet(packet).await.is_err() {
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
let _ = rpc_clone.handle_stream_close(None).await;
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Return a stream wrapper that provides the Stream interface.
|
|
166
|
+
Ok(Box::new(ClientStream {
|
|
167
|
+
rpc,
|
|
168
|
+
packet_handler: tokio::sync::Mutex::new(Some(packet_handler)),
|
|
169
|
+
}))
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/// Stream wrapper for client-side streaming.
|
|
174
|
+
struct ClientStream {
|
|
175
|
+
rpc: Arc<ClientRpc>,
|
|
176
|
+
/// Handle to the background packet handler task.
|
|
177
|
+
/// Aborted when the stream is closed.
|
|
178
|
+
packet_handler: tokio::sync::Mutex<Option<tokio::task::JoinHandle<()>>>,
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
#[async_trait]
|
|
182
|
+
impl Stream for ClientStream {
|
|
183
|
+
fn context(&self) -> &Context {
|
|
184
|
+
self.rpc.context()
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async fn send_bytes(&self, data: Bytes) -> Result<()> {
|
|
188
|
+
self.rpc.send_bytes(data).await
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async fn recv_bytes(&self) -> Result<Bytes> {
|
|
192
|
+
self.rpc.recv_bytes().await
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async fn close_send(&self) -> Result<()> {
|
|
196
|
+
self.rpc.close_send().await
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async fn close(&self) -> Result<()> {
|
|
200
|
+
let _ = self.rpc.close().await;
|
|
201
|
+
// Abort the background packet handler to ensure cleanup.
|
|
202
|
+
if let Some(handle) = self.packet_handler.lock().await.take() {
|
|
203
|
+
handle.abort();
|
|
204
|
+
}
|
|
205
|
+
Ok(())
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/// Transport-based stream openers.
|
|
210
|
+
///
|
|
211
|
+
/// This module provides `OpenStream` implementations for common transport types.
|
|
212
|
+
pub mod transport {
|
|
213
|
+
use super::*;
|
|
214
|
+
use std::sync::Mutex;
|
|
215
|
+
use tokio::io::{AsyncRead, AsyncWrite};
|
|
216
|
+
|
|
217
|
+
/// A simple stream opener over a single connection.
|
|
218
|
+
///
|
|
219
|
+
/// This opener consumes a single transport connection on the first call
|
|
220
|
+
/// to `open_stream`. Subsequent calls will fail with `StreamClosed`.
|
|
221
|
+
///
|
|
222
|
+
/// For multiplexed connections (multiple concurrent streams), use yamux
|
|
223
|
+
/// or similar multiplexing protocols.
|
|
224
|
+
///
|
|
225
|
+
/// # Example
|
|
226
|
+
///
|
|
227
|
+
/// ```ignore
|
|
228
|
+
/// use tokio::net::TcpStream;
|
|
229
|
+
/// use starpc::client::transport::SingleStreamOpener;
|
|
230
|
+
///
|
|
231
|
+
/// let stream = TcpStream::connect("127.0.0.1:8080").await?;
|
|
232
|
+
/// let opener = SingleStreamOpener::new(stream);
|
|
233
|
+
/// let client = SrpcClient::new(opener);
|
|
234
|
+
/// ```
|
|
235
|
+
pub struct SingleStreamOpener<T> {
|
|
236
|
+
inner: Mutex<Option<T>>,
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
impl<T: AsyncRead + AsyncWrite + Send + Unpin + 'static> SingleStreamOpener<T> {
|
|
240
|
+
/// Creates a new single stream opener.
|
|
241
|
+
pub fn new(transport: T) -> Self {
|
|
242
|
+
Self {
|
|
243
|
+
inner: Mutex::new(Some(transport)),
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
#[async_trait]
|
|
249
|
+
impl<T: AsyncRead + AsyncWrite + Send + Unpin + 'static> OpenStream for SingleStreamOpener<T> {
|
|
250
|
+
async fn open_stream(&self) -> Result<(Arc<dyn PacketWriter>, PacketReceiver)> {
|
|
251
|
+
let transport = self
|
|
252
|
+
.inner
|
|
253
|
+
.lock()
|
|
254
|
+
.unwrap()
|
|
255
|
+
.take()
|
|
256
|
+
.ok_or(Error::StreamClosed)?;
|
|
257
|
+
|
|
258
|
+
let (read_half, write_half) = tokio::io::split(transport);
|
|
259
|
+
Ok(create_packet_channel(read_half, write_half))
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/// Re-export TransportPacketWriter for direct use.
|
|
264
|
+
pub use crate::transport::TransportPacketWriter;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
#[cfg(test)]
|
|
268
|
+
mod tests {
|
|
269
|
+
use super::*;
|
|
270
|
+
use std::sync::atomic::{AtomicBool, Ordering};
|
|
271
|
+
use std::sync::Mutex;
|
|
272
|
+
|
|
273
|
+
struct MockWriter {
|
|
274
|
+
closed: AtomicBool,
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
impl MockWriter {
|
|
278
|
+
fn new() -> Self {
|
|
279
|
+
Self {
|
|
280
|
+
closed: AtomicBool::new(false),
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
#[async_trait]
|
|
286
|
+
impl PacketWriter for MockWriter {
|
|
287
|
+
async fn write_packet(&self, _packet: crate::proto::Packet) -> Result<()> {
|
|
288
|
+
Ok(())
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async fn close(&self) -> Result<()> {
|
|
292
|
+
self.closed.store(true, Ordering::SeqCst);
|
|
293
|
+
Ok(())
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
struct MockOpener {
|
|
298
|
+
writer: Arc<MockWriter>,
|
|
299
|
+
receiver: Mutex<Option<PacketReceiver>>,
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
impl MockOpener {
|
|
303
|
+
fn new() -> (Self, tokio::sync::mpsc::Sender<crate::proto::Packet>) {
|
|
304
|
+
let (tx, rx) = tokio::sync::mpsc::channel(32);
|
|
305
|
+
(
|
|
306
|
+
Self {
|
|
307
|
+
writer: Arc::new(MockWriter::new()),
|
|
308
|
+
receiver: Mutex::new(Some(rx)),
|
|
309
|
+
},
|
|
310
|
+
tx,
|
|
311
|
+
)
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
#[async_trait]
|
|
316
|
+
impl OpenStream for MockOpener {
|
|
317
|
+
async fn open_stream(&self) -> Result<(Arc<dyn PacketWriter>, PacketReceiver)> {
|
|
318
|
+
let rx = self
|
|
319
|
+
.receiver
|
|
320
|
+
.lock()
|
|
321
|
+
.unwrap()
|
|
322
|
+
.take()
|
|
323
|
+
.ok_or(Error::StreamClosed)?;
|
|
324
|
+
Ok((self.writer.clone(), rx))
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
#[tokio::test]
|
|
329
|
+
async fn test_client_new_stream() {
|
|
330
|
+
let (opener, _tx) = MockOpener::new();
|
|
331
|
+
let client = SrpcClient::new(opener);
|
|
332
|
+
|
|
333
|
+
let stream = client
|
|
334
|
+
.new_stream("test.Service", "TestMethod", Some(b"hello"))
|
|
335
|
+
.await
|
|
336
|
+
.unwrap();
|
|
337
|
+
|
|
338
|
+
assert!(!stream.context().is_cancelled());
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
#[tokio::test]
|
|
342
|
+
async fn test_single_stream_opener_only_once() {
|
|
343
|
+
use tokio::io::duplex;
|
|
344
|
+
|
|
345
|
+
let (client_stream, _server_stream) = duplex(1024);
|
|
346
|
+
let opener = transport::SingleStreamOpener::new(client_stream);
|
|
347
|
+
|
|
348
|
+
// First open should succeed
|
|
349
|
+
let result1 = opener.open_stream().await;
|
|
350
|
+
assert!(result1.is_ok());
|
|
351
|
+
|
|
352
|
+
// Second open should fail
|
|
353
|
+
let result2 = opener.open_stream().await;
|
|
354
|
+
assert!(matches!(result2, Err(Error::StreamClosed)));
|
|
355
|
+
}
|
|
356
|
+
}
|
package/srpc/codec.rs
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
//! Length-prefixed packet codec for starpc wire format.
|
|
2
|
+
//!
|
|
3
|
+
//! Wire format: 4-byte little-endian u32 length prefix + protobuf-encoded packet.
|
|
4
|
+
|
|
5
|
+
use bytes::{Buf, BufMut, BytesMut};
|
|
6
|
+
use prost::Message;
|
|
7
|
+
use tokio_util::codec::{Decoder, Encoder};
|
|
8
|
+
|
|
9
|
+
use crate::error::{Error, Result};
|
|
10
|
+
use crate::proto::Packet;
|
|
11
|
+
|
|
12
|
+
/// Maximum message size (10MB, same as Go implementation).
|
|
13
|
+
pub const MAX_MESSAGE_SIZE: usize = 10_000_000;
|
|
14
|
+
|
|
15
|
+
/// Length of the size prefix in bytes.
|
|
16
|
+
const SIZE_PREFIX_LEN: usize = 4;
|
|
17
|
+
|
|
18
|
+
/// Codec for encoding and decoding starpc packets with length-prefix framing.
|
|
19
|
+
#[derive(Debug, Default, Clone)]
|
|
20
|
+
pub struct PacketCodec;
|
|
21
|
+
|
|
22
|
+
impl PacketCodec {
|
|
23
|
+
/// Creates a new packet codec.
|
|
24
|
+
pub fn new() -> Self {
|
|
25
|
+
Self
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
impl Decoder for PacketCodec {
|
|
30
|
+
type Item = Packet;
|
|
31
|
+
type Error = Error;
|
|
32
|
+
|
|
33
|
+
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>> {
|
|
34
|
+
// Need at least the size prefix.
|
|
35
|
+
if src.len() < SIZE_PREFIX_LEN {
|
|
36
|
+
return Ok(None);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Read the length prefix (little-endian u32).
|
|
40
|
+
let mut size_bytes = [0u8; SIZE_PREFIX_LEN];
|
|
41
|
+
size_bytes.copy_from_slice(&src[..SIZE_PREFIX_LEN]);
|
|
42
|
+
let msg_size = u32::from_le_bytes(size_bytes) as usize;
|
|
43
|
+
|
|
44
|
+
// Validate message size.
|
|
45
|
+
if msg_size == 0 {
|
|
46
|
+
return Err(Error::MessageSizeZero);
|
|
47
|
+
}
|
|
48
|
+
if msg_size > MAX_MESSAGE_SIZE {
|
|
49
|
+
return Err(Error::MessageTooLarge(msg_size, MAX_MESSAGE_SIZE));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Check if we have the complete message.
|
|
53
|
+
let total_size = SIZE_PREFIX_LEN + msg_size;
|
|
54
|
+
if src.len() < total_size {
|
|
55
|
+
// Reserve capacity for the remaining data.
|
|
56
|
+
src.reserve(total_size - src.len());
|
|
57
|
+
return Ok(None);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Consume the length prefix.
|
|
61
|
+
src.advance(SIZE_PREFIX_LEN);
|
|
62
|
+
|
|
63
|
+
// Decode the packet.
|
|
64
|
+
let packet_bytes = src.split_to(msg_size);
|
|
65
|
+
let packet = Packet::decode(&packet_bytes[..])?;
|
|
66
|
+
|
|
67
|
+
Ok(Some(packet))
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
impl Encoder<Packet> for PacketCodec {
|
|
72
|
+
type Error = Error;
|
|
73
|
+
|
|
74
|
+
fn encode(&mut self, item: Packet, dst: &mut BytesMut) -> Result<()> {
|
|
75
|
+
// Calculate the encoded size.
|
|
76
|
+
let msg_size = item.encoded_len();
|
|
77
|
+
|
|
78
|
+
// Validate message size.
|
|
79
|
+
if msg_size > MAX_MESSAGE_SIZE {
|
|
80
|
+
return Err(Error::MessageTooLarge(msg_size, MAX_MESSAGE_SIZE));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Reserve space for the length prefix and message.
|
|
84
|
+
dst.reserve(SIZE_PREFIX_LEN + msg_size);
|
|
85
|
+
|
|
86
|
+
// Write the length prefix (little-endian u32).
|
|
87
|
+
dst.put_u32_le(msg_size as u32);
|
|
88
|
+
|
|
89
|
+
// Encode the packet directly into the buffer.
|
|
90
|
+
item.encode(dst)?;
|
|
91
|
+
|
|
92
|
+
Ok(())
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// Encode a packet to bytes with length prefix.
|
|
97
|
+
pub fn encode_packet(packet: &Packet) -> Result<Vec<u8>> {
|
|
98
|
+
let msg_size = packet.encoded_len();
|
|
99
|
+
if msg_size > MAX_MESSAGE_SIZE {
|
|
100
|
+
return Err(Error::MessageTooLarge(msg_size, MAX_MESSAGE_SIZE));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let mut buf = Vec::with_capacity(SIZE_PREFIX_LEN + msg_size);
|
|
104
|
+
buf.extend_from_slice(&(msg_size as u32).to_le_bytes());
|
|
105
|
+
packet.encode(&mut buf)?;
|
|
106
|
+
|
|
107
|
+
Ok(buf)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/// Decode a packet from bytes (without length prefix).
|
|
111
|
+
pub fn decode_packet(bytes: &[u8]) -> Result<Packet> {
|
|
112
|
+
Ok(Packet::decode(bytes)?)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
#[cfg(test)]
|
|
116
|
+
mod tests {
|
|
117
|
+
use super::*;
|
|
118
|
+
use crate::proto::{packet::Body, CallData, CallStart};
|
|
119
|
+
|
|
120
|
+
#[test]
|
|
121
|
+
fn test_codec_roundtrip_call_start() {
|
|
122
|
+
let mut codec = PacketCodec::new();
|
|
123
|
+
let mut buf = BytesMut::new();
|
|
124
|
+
|
|
125
|
+
let packet = Packet {
|
|
126
|
+
body: Some(Body::CallStart(CallStart {
|
|
127
|
+
rpc_service: "test.Service".into(),
|
|
128
|
+
rpc_method: "TestMethod".into(),
|
|
129
|
+
data: vec![1, 2, 3],
|
|
130
|
+
data_is_zero: false,
|
|
131
|
+
})),
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
codec.encode(packet.clone(), &mut buf).unwrap();
|
|
135
|
+
|
|
136
|
+
let decoded = codec.decode(&mut buf).unwrap().unwrap();
|
|
137
|
+
assert_eq!(decoded, packet);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
#[test]
|
|
141
|
+
fn test_codec_roundtrip_call_data() {
|
|
142
|
+
let mut codec = PacketCodec::new();
|
|
143
|
+
let mut buf = BytesMut::new();
|
|
144
|
+
|
|
145
|
+
let packet = Packet {
|
|
146
|
+
body: Some(Body::CallData(CallData {
|
|
147
|
+
data: vec![4, 5, 6],
|
|
148
|
+
data_is_zero: false,
|
|
149
|
+
complete: true,
|
|
150
|
+
error: String::new(),
|
|
151
|
+
})),
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
codec.encode(packet.clone(), &mut buf).unwrap();
|
|
155
|
+
|
|
156
|
+
let decoded = codec.decode(&mut buf).unwrap().unwrap();
|
|
157
|
+
assert_eq!(decoded, packet);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
#[test]
|
|
161
|
+
fn test_codec_roundtrip_call_cancel() {
|
|
162
|
+
let mut codec = PacketCodec::new();
|
|
163
|
+
let mut buf = BytesMut::new();
|
|
164
|
+
|
|
165
|
+
let packet = Packet {
|
|
166
|
+
body: Some(Body::CallCancel(true)),
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
codec.encode(packet.clone(), &mut buf).unwrap();
|
|
170
|
+
|
|
171
|
+
let decoded = codec.decode(&mut buf).unwrap().unwrap();
|
|
172
|
+
assert_eq!(decoded, packet);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
#[test]
|
|
176
|
+
fn test_codec_partial_read() {
|
|
177
|
+
let mut codec = PacketCodec::new();
|
|
178
|
+
let mut buf = BytesMut::new();
|
|
179
|
+
|
|
180
|
+
let packet = Packet {
|
|
181
|
+
body: Some(Body::CallData(CallData {
|
|
182
|
+
data: vec![1, 2, 3, 4, 5],
|
|
183
|
+
data_is_zero: false,
|
|
184
|
+
complete: false,
|
|
185
|
+
error: String::new(),
|
|
186
|
+
})),
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// Encode the packet.
|
|
190
|
+
codec.encode(packet.clone(), &mut buf).unwrap();
|
|
191
|
+
|
|
192
|
+
// Split the buffer to simulate partial read.
|
|
193
|
+
let full_buf = buf.clone();
|
|
194
|
+
buf.truncate(3); // Only the first 3 bytes.
|
|
195
|
+
|
|
196
|
+
// Should return None (need more data).
|
|
197
|
+
assert!(codec.decode(&mut buf).unwrap().is_none());
|
|
198
|
+
|
|
199
|
+
// Add the rest of the data.
|
|
200
|
+
buf.extend_from_slice(&full_buf[3..]);
|
|
201
|
+
|
|
202
|
+
// Now it should decode.
|
|
203
|
+
let decoded = codec.decode(&mut buf).unwrap().unwrap();
|
|
204
|
+
assert_eq!(decoded, packet);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
#[test]
|
|
208
|
+
fn test_codec_message_too_large() {
|
|
209
|
+
let mut codec = PacketCodec::new();
|
|
210
|
+
let mut buf = BytesMut::new();
|
|
211
|
+
|
|
212
|
+
// Create a message that exceeds the maximum size.
|
|
213
|
+
let packet = Packet {
|
|
214
|
+
body: Some(Body::CallData(CallData {
|
|
215
|
+
data: vec![0u8; MAX_MESSAGE_SIZE + 1],
|
|
216
|
+
data_is_zero: false,
|
|
217
|
+
complete: false,
|
|
218
|
+
error: String::new(),
|
|
219
|
+
})),
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
let result = codec.encode(packet, &mut buf);
|
|
223
|
+
assert!(matches!(result, Err(Error::MessageTooLarge(_, _))));
|
|
224
|
+
}
|
|
225
|
+
}
|