Skip to content

Commit a4effe2

Browse files
committed
webrtc: Optimize WebRtcMessage encode/decode to reduce allocations
1 parent 2ec58e4 commit a4effe2

File tree

1 file changed

+32
-18
lines changed

1 file changed

+32
-18
lines changed

src/transport/webrtc/util.rs

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,11 @@
1919
// DEALINGS IN THE SOFTWARE.
2020

2121
use crate::{
22-
codec::unsigned_varint::UnsignedVarint,
2322
error::ParseError,
2423
transport::webrtc::schema::{self, webrtc::message::Flag},
2524
};
2625

2726
use prost::Message;
28-
use tokio_util::codec::{Decoder, Encoder};
2927

3028
/// WebRTC mesage.
3129
#[derive(Debug)]
@@ -39,34 +37,50 @@ pub struct WebRtcMessage {
3937

4038
impl WebRtcMessage {
4139
/// Encode WebRTC message with optional flag.
40+
///
41+
/// Uses a single allocation by pre-calculating the total size and encoding
42+
/// the varint length prefix and protobuf message directly into the output buffer.
4243
pub fn encode(payload: Vec<u8>, flag: Option<Flag>) -> Vec<u8> {
4344
let protobuf_payload = schema::webrtc::Message {
4445
message: (!payload.is_empty()).then_some(payload),
4546
flag: flag.map(|f| f as i32),
4647
};
47-
let mut payload = Vec::with_capacity(protobuf_payload.encoded_len());
48+
49+
// Calculate sizes upfront for single allocation
50+
// usize_buffer() is a fixed [u8; 10] array (max varint size)
51+
const VARINT_MAX_LEN: usize = 10;
52+
let protobuf_len = protobuf_payload.encoded_len();
53+
54+
// Single allocation for the entire output
55+
let mut out_buf = Vec::with_capacity(VARINT_MAX_LEN + protobuf_len);
56+
57+
// Encode varint length prefix directly
58+
let mut varint_buf = unsigned_varint::encode::usize_buffer();
59+
let varint_slice = unsigned_varint::encode::usize(protobuf_len, &mut varint_buf);
60+
out_buf.extend_from_slice(varint_slice);
61+
62+
// Encode protobuf directly into output buffer
4863
protobuf_payload
49-
.encode(&mut payload)
64+
.encode(&mut out_buf)
5065
.expect("Vec<u8> to provide needed capacity");
5166

52-
let mut out_buf = bytes::BytesMut::with_capacity(payload.len() + 4);
53-
let mut codec = UnsignedVarint::new(None);
54-
let _result = codec.encode(payload.into(), &mut out_buf);
55-
56-
out_buf.into()
67+
out_buf
5768
}
5869

5970
/// Decode payload into [`WebRtcMessage`].
71+
///
72+
/// Decodes the varint length prefix directly from the slice without allocations,
73+
/// then decodes the protobuf message from the remaining bytes.
6074
pub fn decode(payload: &[u8]) -> Result<Self, ParseError> {
61-
// TODO: https://github.com/paritytech/litep2p/issues/352 set correct size
62-
let mut codec = UnsignedVarint::new(None);
63-
let mut data = bytes::BytesMut::from(payload);
64-
let result = codec
65-
.decode(&mut data)
66-
.map_err(|_| ParseError::InvalidData)?
67-
.ok_or(ParseError::InvalidData)?;
68-
69-
match schema::webrtc::Message::decode(result) {
75+
// Decode varint length prefix directly from slice (no allocation)
76+
// Returns (decoded_length, remaining_bytes_after_varint)
77+
let (len, remaining) =
78+
unsigned_varint::decode::usize(payload).map_err(|_| ParseError::InvalidData)?;
79+
80+
// Get exactly `len` bytes of protobuf data (no allocation)
81+
let protobuf_data = remaining.get(..len).ok_or(ParseError::InvalidData)?;
82+
83+
match schema::webrtc::Message::decode(protobuf_data) {
7084
Ok(message) => Ok(Self {
7185
payload: message.message,
7286
flag: message.flag.and_then(|f| Flag::try_from(f).ok()),

0 commit comments

Comments
 (0)