rocketmq_protocol/protocol/
encoded_frame.rs1use bytes::BufMut;
16use bytes::Bytes;
17use bytes::BytesMut;
18use rocketmq_error::Result;
19
20use super::remoting_command::RemotingCommand;
21
22const FRAME_PREFIX_BYTES: usize = 8;
23const SERIALIZE_TYPE_BYTES: usize = 4;
24const MAX_HEADER_BYTES: usize = 0x00ff_ffff;
25
26#[derive(Clone, Debug, Eq, PartialEq)]
32pub struct EncodedFrame {
33 prefix: [u8; FRAME_PREFIX_BYTES],
34 header: Bytes,
35 body: Bytes,
36}
37
38#[derive(Clone, Debug, Eq, PartialEq)]
44pub struct EncodedFrameHead {
45 prefix: [u8; FRAME_PREFIX_BYTES],
46 header: Bytes,
47 body_len: usize,
48}
49
50impl EncodedFrame {
51 pub fn from_command(mut command: RemotingCommand) -> Result<Self> {
59 let body = command.take_body().unwrap_or_default();
60 let (prefix, header) = encode_header_segments(&mut command, body.len())?;
61 validate_announced_payload_len(&prefix, header.len(), body.len())?;
62 Ok(Self { prefix, header, body })
63 }
64
65 #[inline]
67 #[must_use]
68 pub fn encoded_len(&self) -> usize {
69 FRAME_PREFIX_BYTES + self.header.len() + self.body.len()
70 }
71
72 #[inline]
74 #[must_use]
75 pub fn segments(&self) -> [&[u8]; 3] {
76 [&self.prefix, self.header.as_ref(), self.body.as_ref()]
77 }
78
79 pub fn copy_to(&self, destination: &mut BytesMut) {
84 destination.reserve(self.encoded_len());
85 destination.put_slice(&self.prefix);
86 destination.put_slice(&self.header);
87 destination.put_slice(&self.body);
88 }
89
90 #[must_use]
94 pub fn into_bytes(self) -> Bytes {
95 let mut contiguous = BytesMut::with_capacity(self.encoded_len());
96 self.copy_to(&mut contiguous);
97 contiguous.freeze()
98 }
99}
100
101impl EncodedFrameHead {
102 pub fn from_command_and_body_len(mut command: RemotingCommand, body_len: usize) -> Result<Self> {
109 if command.body().is_some() {
110 return Err(crate::error::serialization_encode_failed(
111 "remoting-command-file-body",
112 "command must not contain an in-memory body when an external body length is supplied",
113 ));
114 }
115 let (prefix, header) = encode_header_segments(&mut command, body_len)?;
116 validate_announced_payload_len(&prefix, header.len(), body_len)?;
117 Ok(Self {
118 prefix,
119 header,
120 body_len,
121 })
122 }
123
124 #[inline]
126 #[must_use]
127 pub fn segments(&self) -> [&[u8]; 2] {
128 [&self.prefix, self.header.as_ref()]
129 }
130
131 #[inline]
133 #[must_use]
134 pub const fn body_len(&self) -> usize {
135 self.body_len
136 }
137
138 #[inline]
140 #[must_use]
141 pub fn encoded_len(&self) -> usize {
142 FRAME_PREFIX_BYTES + self.header.len() + self.body_len
143 }
144}
145
146fn encode_header_segments(command: &mut RemotingCommand, body_len: usize) -> Result<([u8; FRAME_PREFIX_BYTES], Bytes)> {
147 let mut encoded_header = BytesMut::new();
148 command.try_fast_header_encode_with_body_length(&mut encoded_header, body_len)?;
149 if encoded_header.len() < FRAME_PREFIX_BYTES {
150 return Err(crate::error::serialization_encode_failed(
151 "remoting-command",
152 "encoded header omitted the RocketMQ frame prefix",
153 ));
154 }
155 let prefix_bytes = encoded_header.split_to(FRAME_PREFIX_BYTES);
156 let header = encoded_header.freeze();
157 if header.len() > MAX_HEADER_BYTES {
158 return Err(crate::error::serialization_encode_failed(
159 "remoting-command",
160 format!(
161 "encoded header is {} bytes, exceeding the 24-bit wire limit",
162 header.len()
163 ),
164 ));
165 }
166 let mut prefix = [0_u8; FRAME_PREFIX_BYTES];
167 prefix.copy_from_slice(&prefix_bytes);
168 let announced_header = u32::from_be_bytes([prefix[4], prefix[5], prefix[6], prefix[7]]) & MAX_HEADER_BYTES as u32;
169 if announced_header as usize != header.len() {
170 return Err(crate::error::serialization_encode_failed(
171 "remoting-command",
172 "fast header encoder produced an inconsistent header length",
173 ));
174 }
175 Ok((prefix, header))
176}
177
178fn checked_payload_len(header_len: usize, body_len: usize) -> Result<i32> {
179 let payload_len = SERIALIZE_TYPE_BYTES
180 .checked_add(header_len)
181 .and_then(|length| length.checked_add(body_len))
182 .ok_or_else(|| {
183 crate::error::serialization_encode_failed("remoting-command", "encoded frame length overflow")
184 })?;
185 i32::try_from(payload_len).map_err(|_| {
186 crate::error::serialization_encode_failed(
187 "remoting-command",
188 format!("encoded payload is {payload_len} bytes, exceeding the signed 32-bit wire limit"),
189 )
190 })
191}
192
193fn validate_announced_payload_len(prefix: &[u8; FRAME_PREFIX_BYTES], header_len: usize, body_len: usize) -> Result<()> {
194 let expected = checked_payload_len(header_len, body_len)?;
195 let announced = i32::from_be_bytes([prefix[0], prefix[1], prefix[2], prefix[3]]);
196 if announced != expected {
197 return Err(crate::error::serialization_encode_failed(
198 "remoting-command",
199 "fast header encoder produced inconsistent wire lengths",
200 ));
201 }
202 Ok(())
203}
204
205#[cfg(test)]
206mod tests {
207 use std::collections::HashMap;
208
209 use bytes::BufMut;
210 use bytes::Bytes;
211 use bytes::BytesMut;
212 use cheetah_string::CheetahString;
213
214 use super::EncodedFrame;
215 use super::EncodedFrameHead;
216 use crate::protocol::command_custom_header::CommandCustomHeader;
217 use crate::protocol::command_custom_header::HeaderEncodeCapability;
218 use crate::protocol::header::client_request_header::GetRouteInfoRequestHeader;
219 use crate::protocol::header::message_operation_header::send_message_request_header_v2::SendMessageRequestHeaderV2;
220 use crate::protocol::remoting_command::RemotingCommand;
221 use crate::protocol::SerializeType;
222 use crate::ProtocolContractViolation;
223
224 struct FailingDirectHeader;
225
226 impl CommandCustomHeader for FailingDirectHeader {
227 fn to_map(&self) -> Option<HashMap<CheetahString, CheetahString>> {
228 Some(HashMap::new())
229 }
230
231 fn encode_capability(&self) -> HeaderEncodeCapability {
232 HeaderEncodeCapability::DirectBinary
233 }
234
235 fn encode_direct_binary(&self, _out: &mut BytesMut) -> Result<(), ProtocolContractViolation> {
236 Err(ProtocolContractViolation::FastCodecUnavailable {
237 header: "FailingDirectHeader",
238 })
239 }
240 }
241
242 fn legacy_contiguous(mut command: RemotingCommand) -> Bytes {
243 let mut bytes = BytesMut::new();
244 command.fast_header_encode(&mut bytes);
245 if let Some(body) = command.take_body() {
246 bytes.put(body);
247 }
248 bytes.freeze()
249 }
250
251 #[test]
252 fn encoded_frame_matches_json_contiguous_encoding_byte_for_byte() {
253 let command = RemotingCommand::create_remoting_command(105)
254 .set_opaque(73)
255 .set_remark("segmented-json")
256 .set_command_custom_header(GetRouteInfoRequestHeader::new("json-topic", None))
257 .set_body(Bytes::from_static(b"json-body"));
258 let expected = legacy_contiguous(command.clone());
259 let frame = EncodedFrame::from_command(command).expect("JSON command should encode");
260
261 assert_eq!(frame.encoded_len(), expected.len());
262 assert_eq!(frame.into_bytes(), expected);
263 }
264
265 #[test]
266 fn encoded_frame_matches_rocketmq_contiguous_encoding_byte_for_byte() {
267 let command = RemotingCommand::create_remoting_command(106)
268 .set_opaque(74)
269 .set_serialize_type(SerializeType::ROCKETMQ)
270 .set_command_custom_header(GetRouteInfoRequestHeader::new("binary-topic", Some(false)))
271 .set_body(Bytes::from_static(b"binary-body"));
272 let expected = legacy_contiguous(command.clone());
273 let frame = EncodedFrame::from_command(command).expect("RocketMQ command should encode");
274
275 assert_eq!(frame.encoded_len(), expected.len());
276 assert_eq!(frame.into_bytes(), expected);
277 }
278
279 #[test]
280 fn encoded_frame_exposes_prefix_header_and_body_without_coalescing() {
281 let body = Bytes::from_static(b"owned-body");
282 let frame = EncodedFrame::from_command(
283 RemotingCommand::create_remoting_command(107)
284 .set_opaque(75)
285 .set_body(body.clone()),
286 )
287 .expect("command should encode");
288 let [prefix, header, encoded_body] = frame.segments();
289
290 assert_eq!(prefix.len(), 8);
291 assert!(!header.is_empty());
292 assert_eq!(encoded_body, body.as_ref());
293 assert_eq!(frame.encoded_len(), prefix.len() + header.len() + encoded_body.len());
294 }
295
296 #[test]
297 fn encoded_frame_head_matches_the_complete_frame_byte_for_byte() {
298 let body = Bytes::from(vec![0x5a; 64 * 1024]);
299 let command = RemotingCommand::create_remoting_command(108)
300 .set_opaque(91)
301 .set_remark("external-file-body");
302 let head = EncodedFrameHead::from_command_and_body_len(command.clone(), body.len()).unwrap();
303 let complete = EncodedFrame::from_command(command.set_body(body.clone()))
304 .unwrap()
305 .into_bytes();
306 let [prefix, header] = head.segments();
307 let mut reconstructed = BytesMut::with_capacity(head.encoded_len());
308 reconstructed.put_slice(prefix);
309 reconstructed.put_slice(header);
310 reconstructed.put_slice(&body);
311
312 assert_eq!(reconstructed.freeze(), complete);
313 assert_eq!(head.body_len(), body.len());
314 }
315
316 #[test]
317 fn frame_head_and_complete_frame_match_for_all_body_sizes_and_protocols() {
318 for serialize_type in [SerializeType::JSON, SerializeType::ROCKETMQ] {
319 for body_len in [0, 1, 128, 4 * 1024, 64 * 1024, 1024 * 1024, 4 * 1024 * 1024] {
320 let body = Bytes::from(vec![0x5a; body_len]);
321 let command = RemotingCommand::create_remoting_command(108)
322 .set_opaque(92)
323 .set_serialize_type(serialize_type)
324 .set_remark("body-length-matrix");
325 let head = EncodedFrameHead::from_command_and_body_len(command.clone(), body_len).unwrap();
326 let complete = EncodedFrame::from_command(command.set_body(body.clone()))
327 .unwrap()
328 .into_bytes();
329 let [prefix, header] = head.segments();
330 let mut reconstructed = BytesMut::with_capacity(head.encoded_len());
331 reconstructed.put_slice(prefix);
332 reconstructed.put_slice(header);
333 reconstructed.put_slice(&body);
334
335 assert_eq!(
336 reconstructed.freeze(),
337 complete,
338 "{serialize_type:?} body_len={body_len}"
339 );
340 }
341 }
342 }
343
344 #[test]
345 fn explicit_body_length_rejects_an_in_memory_body_mismatch_atomically() {
346 let mut command = RemotingCommand::create_remoting_command(109).set_body(Bytes::from_static(b"body"));
347 let mut destination = BytesMut::from(&b"existing"[..]);
348
349 assert!(command
350 .try_fast_header_encode_with_body_length(&mut destination, 3)
351 .is_err());
352 assert_eq!(destination.as_ref(), b"existing");
353 }
354
355 #[test]
356 fn encoded_frame_head_rejects_ambiguous_or_oversized_bodies() {
357 let with_body = RemotingCommand::create_remoting_command(109).set_body(Bytes::from_static(b"ambiguous"));
358 assert!(EncodedFrameHead::from_command_and_body_len(with_body, 9).is_err());
359
360 let command = RemotingCommand::create_remoting_command(110);
361 assert!(EncodedFrameHead::from_command_and_body_len(command, i32::MAX as usize).is_err());
362 }
363
364 #[test]
365 fn encoded_frame_canonicalizes_ext_field_order_for_all_protocols() {
366 for serialize_type in [SerializeType::JSON, SerializeType::ROCKETMQ] {
367 let first = HashMap::from([
368 (
369 CheetahString::from_static_str("zeta"),
370 CheetahString::from_static_str("last"),
371 ),
372 (
373 CheetahString::from_static_str("alpha"),
374 CheetahString::from_static_str("first"),
375 ),
376 ]);
377 let second = HashMap::from([
378 (
379 CheetahString::from_static_str("alpha"),
380 CheetahString::from_static_str("first"),
381 ),
382 (
383 CheetahString::from_static_str("zeta"),
384 CheetahString::from_static_str("last"),
385 ),
386 ]);
387 let encode = |ext_fields| {
388 EncodedFrame::from_command(
389 RemotingCommand::create_remoting_command(108)
390 .set_opaque(76)
391 .set_serialize_type(serialize_type)
392 .set_ext_fields(ext_fields),
393 )
394 .expect("command should encode")
395 .into_bytes()
396 };
397
398 assert_eq!(encode(first), encode(second));
399 }
400 }
401
402 #[test]
403 fn direct_header_encoding_is_identical_after_multiple_command_clones() {
404 let header = SendMessageRequestHeaderV2 {
405 a: "producer-a".into(),
406 b: "topic-a".into(),
407 c: "TBW102".into(),
408 d: 4,
409 e: 2,
410 f: 0,
411 g: 42,
412 h: 1,
413 ..Default::default()
414 };
415 let original = RemotingCommand::create_remoting_command(310)
416 .set_serialize_type(SerializeType::ROCKETMQ)
417 .set_command_custom_header(header);
418 let first_clone = original.clone();
419 let second_clone = first_clone.clone();
420
421 let frames = [original, first_clone, second_clone].map(|command| {
422 EncodedFrame::from_command(command)
423 .expect("shared direct header should encode")
424 .into_bytes()
425 });
426 assert_eq!(frames[0], frames[1]);
427 assert_eq!(frames[1], frames[2]);
428
429 for frame in frames {
430 let mut input = BytesMut::from(frame.as_ref());
431 let decoded = RemotingCommand::decode(&mut input)
432 .expect("frame should decode")
433 .expect("frame should be complete");
434 let decoded_header = decoded
435 .decode_command_custom_header::<SendMessageRequestHeaderV2>()
436 .expect("direct fields should remain present");
437 assert_eq!(decoded_header.a.as_str(), "producer-a");
438 assert_eq!(decoded_header.b.as_str(), "topic-a");
439 assert_eq!(decoded_header.g, 42);
440 }
441 }
442
443 #[test]
444 fn fallible_direct_header_encoding_rolls_back_the_destination() {
445 let mut command = RemotingCommand::create_remoting_command(311)
446 .set_serialize_type(SerializeType::ROCKETMQ)
447 .set_command_custom_header(FailingDirectHeader);
448 let mut destination = BytesMut::from(&b"existing"[..]);
449
450 assert!(command.try_fast_header_encode(&mut destination).is_err());
451 assert_eq!(destination.as_ref(), b"existing");
452 }
453
454 #[test]
455 fn materialized_direct_header_is_not_encoded_twice_after_clone() {
456 let header = SendMessageRequestHeaderV2 {
457 a: "producer-b".into(),
458 b: "topic-b".into(),
459 c: "TBW102".into(),
460 d: 4,
461 e: 3,
462 f: 0,
463 g: 43,
464 h: 1,
465 ..Default::default()
466 };
467 let mut original = RemotingCommand::create_remoting_command(312)
468 .set_serialize_type(SerializeType::ROCKETMQ)
469 .set_command_custom_header(header);
470 original.materialize_custom_header_to_ext_fields();
471 let cloned = original.clone();
472
473 let original_frame = EncodedFrame::from_command(original).unwrap().into_bytes();
474 let cloned_frame = EncodedFrame::from_command(cloned).unwrap().into_bytes();
475 assert_eq!(original_frame, cloned_frame);
476 }
477}