1use std::collections::HashMap;
16use std::str;
17
18use bytes::Buf;
19use bytes::BufMut;
20use bytes::Bytes;
21use bytes::BytesMut;
22use cheetah_string::CheetahString;
23
24use crate::protocol::command_custom_header::CommandCustomHeader;
25use crate::protocol::command_custom_header::HeaderEncodeCapability;
26use crate::protocol::header_codec::BinaryHeaderFields;
27use crate::protocol::header_field_merge::has_custom_ext_collision;
28use crate::protocol::header_field_merge::merge_header_and_dynamic;
29use crate::protocol::remoting_command::RemotingCommand;
30use crate::protocol::LanguageCode;
31use crate::ProtocolContractViolation;
32
33const MAP_ENTRY_ENCODED_BYTES_ESTIMATE: usize = 16;
34const MIN_MAP_CAPACITY: usize = 4;
35const MAX_MAP_CAPACITY: usize = 1024;
36
37fn decoding_error(required: usize, available: usize) -> rocketmq_error::Error {
38 crate::error::serialization_decode_failed("binary", format!("required {required} bytes, got {available}"))
39}
40
41fn trailing_header_error(remaining: usize) -> rocketmq_error::Error {
42 crate::error::serialization_decode_failed(
43 "binary",
44 format!("ROCKETMQ header has {remaining} trailing bytes after extension fields"),
45 )
46}
47
48enum SortedExtFields<'a> {
49 Direct(std::collections::hash_map::Iter<'a, CheetahString, CheetahString>),
50 Sorted(std::vec::IntoIter<(&'a CheetahString, &'a CheetahString)>),
51}
52
53impl<'a> Iterator for SortedExtFields<'a> {
54 type Item = (&'a CheetahString, &'a CheetahString);
55
56 fn next(&mut self) -> Option<Self::Item> {
57 match self {
58 Self::Direct(entries) => entries.find(|(key, _)| !key.is_empty()),
59 Self::Sorted(entries) => entries.next(),
60 }
61 }
62}
63
64fn sorted_ext_fields(map: &HashMap<CheetahString, CheetahString>) -> SortedExtFields<'_> {
65 if map.len() <= 1 {
66 return SortedExtFields::Direct(map.iter());
67 }
68 let mut entries = map.iter().filter(|(key, _)| !key.is_empty()).collect::<Vec<_>>();
69 entries.sort_unstable_by(|(left, _), (right, _)| left.as_str().cmp(right.as_str()));
70 SortedExtFields::Sorted(entries.into_iter())
71}
72
73pub struct RocketMQSerializable;
74
75impl RocketMQSerializable {
76 pub(crate) const INITIAL_ENCODE_CAPACITY: usize = 256;
77
78 #[inline]
79 fn estimated_map_capacity(encoded_len: usize) -> usize {
80 (encoded_len / MAP_ENTRY_ENCODED_BYTES_ESTIMATE).clamp(MIN_MAP_CAPACITY, MAX_MAP_CAPACITY)
81 }
82
83 #[inline]
85 pub fn write_str(buf: &mut BytesMut, use_short_length: bool, s: &str) -> usize {
86 let bytes = s.as_bytes();
87 let len = bytes.len();
88
89 let length_size = if use_short_length {
90 buf.put_u16(len as u16);
91 2
92 } else {
93 buf.put_u32(len as u32);
94 4
95 };
96
97 buf.put_slice(bytes); length_size + len
99 }
100
101 #[inline]
103 pub fn read_str(
104 buf: &mut BytesMut,
105 use_short_length: bool,
106 limit: usize,
107 ) -> rocketmq_error::Result<Option<CheetahString>> {
108 let len = if use_short_length {
110 if buf.remaining() < 2 {
111 return Err(decoding_error(2, buf.remaining()));
112 }
113 buf.get_u16() as usize
114 } else {
115 if buf.remaining() < 4 {
116 return Err(decoding_error(4, buf.remaining()));
117 }
118 buf.get_u32() as usize
119 };
120
121 if len == 0 {
123 return Ok(None);
124 }
125
126 if len > limit {
128 return Err(decoding_error(len, limit));
129 }
130
131 if buf.remaining() < len {
133 return Err(decoding_error(len, buf.remaining()));
134 }
135
136 let bytes = buf.split_to(len).freeze();
138 let value = CheetahString::try_copy_from_bytes(bytes)
139 .map_err(|error| crate::error::serialization_source("decode", "binary", error.into_parts().1))?;
140 Ok(Some(value))
141 }
142
143 #[inline]
145 pub fn rocketmq_protocol_encode(cmd: &mut RemotingCommand, buf: &mut BytesMut) -> usize {
146 let checkpoint = buf.len();
147 match Self::try_rocketmq_protocol_encode(cmd, buf) {
148 Ok(encoded) => encoded,
149 Err(_) => {
150 buf.truncate(checkpoint);
151 0
152 }
153 }
154 }
155
156 #[inline]
163 pub fn try_rocketmq_protocol_encode(
164 cmd: &RemotingCommand,
165 buf: &mut BytesMut,
166 ) -> Result<usize, ProtocolContractViolation> {
167 Self::try_rocketmq_protocol_encode_with_capability(cmd, buf, cmd.custom_header_encode_capability())
168 }
169
170 pub(crate) fn try_rocketmq_protocol_encode_with_capability(
171 cmd: &RemotingCommand,
172 buf: &mut BytesMut,
173 capability: HeaderEncodeCapability,
174 ) -> Result<usize, ProtocolContractViolation> {
175 buf.reserve(Self::INITIAL_ENCODE_CAPACITY);
179 let checkpoint = buf.len();
180 let result = Self::try_rocketmq_protocol_encode_inner(cmd, buf, capability);
181 if result.is_err() {
182 buf.truncate(checkpoint);
183 }
184 result
185 }
186
187 fn try_rocketmq_protocol_encode_inner(
188 cmd: &RemotingCommand,
189 buf: &mut BytesMut,
190 capability: HeaderEncodeCapability,
191 ) -> Result<usize, ProtocolContractViolation> {
192 let begin_index = buf.len();
193
194 buf.put_u16(cmd.code() as u16); buf.put_u8(cmd.language().get_code()); buf.put_u16(cmd.version() as u16); buf.put_i32(cmd.opaque()); buf.put_i32(cmd.flag()); if let Some(remark) = cmd.remark() {
203 Self::write_str(buf, false, remark.as_str());
204 } else {
205 buf.put_i32(0);
206 }
207
208 let map_len_index = buf.len();
210 buf.put_i32(0);
211
212 match cmd.command_custom_header_ref() {
215 Some(header)
216 if capability == HeaderEncodeCapability::DirectBinary
217 && !has_custom_ext_collision(header, cmd.ext_fields()) =>
218 {
219 header.encode_direct_binary(buf)?;
220 if let Some(ext_fields) = cmd.ext_fields() {
221 Self::write_ext_fields(buf, ext_fields)?;
222 }
223 }
224 Some(header) => {
225 let merged = merge_header_and_dynamic(header, cmd.ext_fields())?;
226 Self::write_ext_fields(buf, &merged)?;
227 }
228 None => {
229 if let Some(ext_fields) = cmd.ext_fields() {
230 Self::write_ext_fields(buf, ext_fields)?;
231 }
232 }
233 }
234
235 let current_length = buf.len();
237 let ext_fields_length = Self::checked_ext_fields_length(current_length - map_len_index - 4)?;
238 buf[map_len_index..map_len_index + 4].copy_from_slice(&ext_fields_length.to_be_bytes());
239
240 Ok(buf.len() - begin_index)
241 }
242
243 pub(crate) fn try_rocketmq_protocol_encode_direct(
247 cmd: &RemotingCommand,
248 header: &dyn CommandCustomHeader,
249 buf: &mut BytesMut,
250 ) -> Result<usize, ProtocolContractViolation> {
251 const FIXED_HEADER_LENGTH: usize = 21;
252 const EXT_FIELDS_LENGTH_OFFSET: usize = 17;
253
254 let begin_index = buf.len();
255 let mut fixed = [0u8; FIXED_HEADER_LENGTH];
256 fixed[..2].copy_from_slice(&(cmd.code() as u16).to_be_bytes());
257 fixed[2] = cmd.language().get_code();
258 fixed[3..5].copy_from_slice(&(cmd.version() as u16).to_be_bytes());
259 fixed[5..9].copy_from_slice(&cmd.opaque().to_be_bytes());
260 fixed[9..13].copy_from_slice(&cmd.flag().to_be_bytes());
261 buf.extend_from_slice(&fixed);
262
263 header.encode_direct_binary(buf)?;
264
265 let ext_fields_length = Self::checked_ext_fields_length(buf.len() - begin_index - FIXED_HEADER_LENGTH)?;
266 let map_len_index = begin_index + EXT_FIELDS_LENGTH_OFFSET;
267 buf[map_len_index..map_len_index + 4].copy_from_slice(&ext_fields_length.to_be_bytes());
268 Ok(buf.len() - begin_index)
269 }
270
271 #[inline]
272 fn write_ext_fields(
273 buf: &mut BytesMut,
274 fields: &HashMap<CheetahString, CheetahString>,
275 ) -> Result<(), ProtocolContractViolation> {
276 for (key, value) in sorted_ext_fields(fields) {
277 let key_length = Self::checked_dynamic_key_length(key.len())?;
278 let value_length = Self::checked_dynamic_value_length(value.len())?;
279 buf.put_u16(key_length);
280 buf.put_slice(key.as_bytes());
281 buf.put_i32(value_length);
282 buf.put_slice(value.as_bytes());
283 }
284 Ok(())
285 }
286
287 #[inline]
288 fn checked_ext_fields_length(length: usize) -> Result<i32, ProtocolContractViolation> {
289 i32::try_from(length).map_err(|_| ProtocolContractViolation::ExtensionFieldsLengthOverflow)
290 }
291
292 #[inline]
293 fn checked_dynamic_key_length(length: usize) -> Result<u16, ProtocolContractViolation> {
294 i16::try_from(length)
295 .map(|length| length as u16)
296 .map_err(|_| ProtocolContractViolation::DynamicKeyLengthOverflow)
297 }
298
299 #[inline]
300 fn checked_dynamic_value_length(length: usize) -> Result<i32, ProtocolContractViolation> {
301 i32::try_from(length).map_err(|_| ProtocolContractViolation::DynamicValueLengthOverflow)
302 }
303
304 pub fn rocket_mq_protocol_encode_bytes(cmd: &RemotingCommand) -> Bytes {
305 let remark_bytes = cmd.remark().map(|remark| remark.as_bytes().to_vec());
306 let remark_len = remark_bytes.as_ref().map_or(0, |v| v.len());
307
308 let ext_fields_bytes = if let Some(ext) = cmd.get_ext_fields() {
309 Self::map_serialize(ext)
310 } else {
311 None
312 };
313 let ext_len = ext_fields_bytes.as_ref().map_or(0, |v| v.len());
314
315 let total_len = Self::cal_total_len(remark_len, ext_len);
316 let mut header_buffer = BytesMut::with_capacity(total_len);
317
318 header_buffer.put_i16(cmd.code() as i16);
320
321 header_buffer.put_u8(cmd.language().get_code());
323
324 header_buffer.put_i16(cmd.version() as i16);
326
327 header_buffer.put_i32(cmd.opaque());
329
330 header_buffer.put_i32(cmd.flag());
332
333 if let Some(remark_bytes) = remark_bytes {
335 header_buffer.put_i32(remark_bytes.len() as i32);
336 header_buffer.put(remark_bytes.as_ref());
337 } else {
338 header_buffer.put_i32(0);
339 }
340
341 if let Some(ext_fields_bytes) = ext_fields_bytes {
343 header_buffer.put_i32(ext_fields_bytes.len() as i32);
344 header_buffer.put(ext_fields_bytes.as_ref());
345 } else {
346 header_buffer.put_i32(0);
347 }
348
349 header_buffer.freeze()
350 }
351
352 #[inline]
354 pub fn map_serialize(map: &HashMap<CheetahString, CheetahString>) -> Option<BytesMut> {
355 if map.is_empty() {
356 return None;
357 }
358
359 let mut total_length = 0;
361 let mut valid_entries = 0;
362
363 for (key, value) in map.iter() {
364 if !key.is_empty() {
365 total_length += 2 + key.len() + 4 + value.len();
366 valid_entries += 1;
367 }
368 }
369
370 if valid_entries == 0 {
371 return None;
372 }
373
374 let mut content = BytesMut::with_capacity(total_length);
376
377 for (key, value) in sorted_ext_fields(map) {
379 content.put_u16(key.len() as u16);
381 content.put_slice(key.as_bytes());
382
383 content.put_i32(value.len() as i32);
385 content.put_slice(value.as_bytes());
386 }
387
388 Some(content)
389 }
390
391 pub fn cal_total_len(remark_len: usize, ext_len: usize) -> usize {
392 2 + 1 + 2 + 4 + 4 + 4 + remark_len + 4 + ext_len }
408
409 pub fn rocket_mq_protocol_decode(
410 header_buffer: &mut BytesMut,
411 header_len: usize,
412 ) -> rocketmq_error::Result<RemotingCommand> {
413 let available = header_buffer.remaining();
414 if available < header_len {
415 return Err(decoding_error(header_len, available));
416 }
417 if available > header_len {
418 return Err(trailing_header_error(available - header_len));
419 }
420 Self::rocket_mq_protocol_decode_bytes(header_buffer.split().freeze())
421 }
422
423 pub(crate) fn rocket_mq_protocol_decode_bytes(header: Bytes) -> rocketmq_error::Result<RemotingCommand> {
425 const FIXED_HEADER_LEN: usize = 13;
426 const LENGTH_FIELD_LEN: usize = 4;
427
428 if header.len() < FIXED_HEADER_LEN {
429 return Err(decoding_error(FIXED_HEADER_LEN, header.len()));
430 }
431
432 let code = i16::from_be_bytes([header[0], header[1]]) as i32;
433 let language = LanguageCode::from(header[2]);
434 let version = i16::from_be_bytes([header[3], header[4]]) as i32;
435 let opaque = i32::from_be_bytes([header[5], header[6], header[7], header[8]]);
436 let flag = i32::from_be_bytes([header[9], header[10], header[11], header[12]]);
437
438 let mut cursor = FIXED_HEADER_LEN;
439 if header.len() - cursor < LENGTH_FIELD_LEN {
440 return Err(decoding_error(LENGTH_FIELD_LEN, header.len() - cursor));
441 }
442 let remark_length = u32::from_be_bytes([
443 header[cursor],
444 header[cursor + 1],
445 header[cursor + 2],
446 header[cursor + 3],
447 ]) as usize;
448 cursor += LENGTH_FIELD_LEN;
449 if remark_length > header.len() - cursor {
450 return Err(decoding_error(remark_length, header.len() - cursor));
451 }
452 let remark = if remark_length == 0 {
453 None
454 } else {
455 let end = cursor + remark_length;
456 let value = CheetahString::try_copy_from_bytes(header.slice(cursor..end))
457 .map_err(|error| crate::error::serialization_source("decode", "binary", error.into_parts().1))?;
458 cursor = end;
459 Some(value)
460 };
461
462 if header.len() - cursor < LENGTH_FIELD_LEN {
463 return Err(decoding_error(LENGTH_FIELD_LEN, header.len() - cursor));
464 }
465 let ext_fields_length = i32::from_be_bytes([
466 header[cursor],
467 header[cursor + 1],
468 header[cursor + 2],
469 header[cursor + 3],
470 ]);
471 cursor += LENGTH_FIELD_LEN;
472 let payload = if ext_fields_length > 0 {
473 let ext_fields_length = ext_fields_length as usize;
474 if ext_fields_length > header.len() - cursor {
475 return Err(decoding_error(ext_fields_length, header.len() - cursor));
476 }
477 let end = cursor + ext_fields_length;
478 let payload = header.slice(cursor..end);
479 cursor = end;
480 payload
481 } else {
482 Bytes::new()
483 };
484 if cursor != header.len() {
485 return Err(trailing_header_error(header.len() - cursor));
486 }
487
488 let ext_fields = BinaryHeaderFields::new(payload)?;
489 Ok(RemotingCommand::from_binary_wire_parts(
490 code, language, version, opaque, flag, remark, ext_fields,
491 ))
492 }
493
494 #[inline]
496 pub fn map_deserialize(
497 buffer: &mut BytesMut,
498 len: usize,
499 ) -> rocketmq_error::Result<HashMap<CheetahString, CheetahString>> {
500 if len == 0 {
501 return Ok(HashMap::new());
502 }
503 if len > buffer.remaining() {
504 return Err(decoding_error(len, buffer.remaining()));
505 }
506
507 let estimated_entries = Self::estimated_map_capacity(len);
510 let mut map = HashMap::with_capacity(estimated_entries);
511
512 let target_remaining = buffer.remaining().saturating_sub(len);
513
514 while buffer.remaining() > target_remaining {
515 let key = Self::read_str(buffer, true, len)?.ok_or_else(|| decoding_error(0, 0))?;
517
518 if let Some(value) = Self::read_str(buffer, false, len)? {
520 map.insert(key, value);
521 }
522 }
523
524 Ok(map)
525 }
526}
527
528#[cfg(test)]
529mod tests {
530 use bytes::BufMut;
531 use bytes::BytesMut;
532
533 use super::*;
534
535 fn minimal_header_without_ext_len() -> BytesMut {
536 let mut buf = BytesMut::new();
537 buf.put_i16(0);
538 buf.put_u8(LanguageCode::JAVA.get_code());
539 buf.put_i16(0);
540 buf.put_i32(0);
541 buf.put_i32(0);
542 buf.put_i32(0);
543 buf
544 }
545
546 #[test]
547 fn write_str_short_length() {
548 let mut buf = BytesMut::new();
549 let written = RocketMQSerializable::write_str(&mut buf, true, "test");
550 assert_eq!(written, 6);
551 assert_eq!(buf, BytesMut::from(&[0, 4, 116, 101, 115, 116][..]));
552 }
553
554 #[test]
555 fn write_str_long_length() {
556 let mut buf = BytesMut::new();
557 let written = RocketMQSerializable::write_str(&mut buf, false, "test");
558 assert_eq!(written, 8);
559 assert_eq!(buf, BytesMut::from(&[0, 0, 0, 4, 116, 101, 115, 116][..]));
560 }
561
562 #[test]
563 fn read_str_short_length() {
564 let mut buf = BytesMut::from(&[0, 4, 116, 101, 115, 116][..]);
565 let read = RocketMQSerializable::read_str(&mut buf, true, 10).unwrap();
566 assert_eq!(read, Some("test".into()));
567 }
568
569 #[test]
570 fn read_str_long_length() {
571 let mut buf = BytesMut::from(&[0, 0, 0, 4, 116, 101, 115, 116][..]);
572 let read = RocketMQSerializable::read_str(&mut buf, false, 10).unwrap();
573 assert_eq!(read, Some("test".into()));
574 }
575
576 #[test]
577 fn read_str_exceeds_limit() {
578 let mut buf = BytesMut::from(&[0, 0, 0, 4, 116, 101, 115, 116][..]);
579 let read = RocketMQSerializable::read_str(&mut buf, false, 2);
580 assert!(read.is_err());
581 }
582
583 #[test]
584 fn read_str_rejects_invalid_utf8() {
585 let mut buf = BytesMut::from(&[0, 2, 0xff, 0xfe][..]);
586 let read = RocketMQSerializable::read_str(&mut buf, true, 10);
587 assert!(read.is_err());
588 }
589
590 #[test]
591 fn map_serialize_empty() {
592 let map = HashMap::new();
593 let serialized = RocketMQSerializable::map_serialize(&map);
594 assert!(serialized.is_none());
595 }
596
597 #[test]
598 fn map_serialize_non_empty() {
599 let mut map = HashMap::new();
600 map.insert("key".into(), "value".into());
601 let serialized = RocketMQSerializable::map_serialize(&map).unwrap();
602 assert_eq!(
603 serialized,
604 BytesMut::from(&[0, 3, 107, 101, 121, 0, 0, 0, 5, 118, 97, 108, 117, 101][..])
605 );
606 }
607
608 #[test]
609 fn map_serialize_preserves_present_empty_value() {
610 let map = HashMap::from([("key".into(), CheetahString::new())]);
611
612 let serialized = RocketMQSerializable::map_serialize(&map).unwrap();
613
614 assert_eq!(serialized, BytesMut::from(&[0, 3, 107, 101, 121, 0, 0, 0, 0][..]));
615 }
616
617 #[test]
618 fn map_deserialize_empty() {
619 let mut buf = BytesMut::new();
620 let deserialized = RocketMQSerializable::map_deserialize(&mut buf, 0).unwrap();
621 assert!(deserialized.is_empty());
622 }
623
624 #[test]
625 fn map_deserialize_non_empty() {
626 let mut buf = BytesMut::from(&[0, 3, 107, 101, 121, 0, 0, 0, 5, 118, 97, 108, 117, 101][..]);
627 let deserialized = RocketMQSerializable::map_deserialize(&mut buf, 14).unwrap();
628 assert_eq!(deserialized, [("key".into(), "value".into())].iter().cloned().collect());
629 }
630
631 #[test]
632 fn map_capacity_estimate_covers_compact_request_headers_and_stays_bounded() {
633 assert_eq!(RocketMQSerializable::estimated_map_capacity(1), 4);
634 assert_eq!(RocketMQSerializable::estimated_map_capacity(292), 18);
635 assert_eq!(RocketMQSerializable::estimated_map_capacity(385), 24);
636 assert_eq!(RocketMQSerializable::estimated_map_capacity(usize::MAX), 1024);
637 }
638
639 #[test]
640 fn map_deserialize_normalizes_zero_length_value_to_absent() {
641 let mut buf = BytesMut::from(&[0, 3, 107, 101, 121, 0, 0, 0, 0][..]);
642
643 let deserialized = RocketMQSerializable::map_deserialize(&mut buf, 9).unwrap();
644
645 assert!(deserialized.is_empty());
646 assert!(buf.is_empty());
647 }
648
649 #[test]
650 fn extension_field_length_checks_limit_and_limit_plus_one() {
651 assert_eq!(
652 RocketMQSerializable::checked_ext_fields_length(i32::MAX as usize).unwrap(),
653 i32::MAX
654 );
655 assert!(matches!(
656 RocketMQSerializable::checked_ext_fields_length(i32::MAX as usize + 1),
657 Err(ProtocolContractViolation::ExtensionFieldsLengthOverflow)
658 ));
659 assert_eq!(
660 RocketMQSerializable::checked_dynamic_value_length(i32::MAX as usize).unwrap(),
661 i32::MAX
662 );
663 assert!(matches!(
664 RocketMQSerializable::checked_dynamic_value_length(i32::MAX as usize + 1),
665 Err(ProtocolContractViolation::DynamicValueLengthOverflow)
666 ));
667 }
668
669 #[test]
670 fn dynamic_key_length_java_compatible() {
671 let maximum = i16::MAX as usize;
672
673 assert_eq!(
674 RocketMQSerializable::checked_dynamic_key_length(maximum - 1).unwrap(),
675 (i16::MAX - 1) as u16
676 );
677 assert_eq!(
678 RocketMQSerializable::checked_dynamic_key_length(maximum).unwrap(),
679 i16::MAX as u16
680 );
681 assert!(matches!(
682 RocketMQSerializable::checked_dynamic_key_length(maximum + 1),
683 Err(ProtocolContractViolation::DynamicKeyLengthOverflow)
684 ));
685 }
686
687 #[test]
688 fn rocketmq_protocol_decode_rejects_short_fixed_header_without_panic() {
689 let mut buf = BytesMut::from(&[0_u8; 12][..]);
690 if RocketMQSerializable::rocket_mq_protocol_decode(&mut buf, 12).is_ok() {
691 panic!("short fixed header should decode to error");
692 }
693 }
694
695 #[test]
696 fn rocketmq_protocol_decode_rejects_missing_ext_length_without_panic() {
697 let mut buf = minimal_header_without_ext_len();
698 let header_len = buf.len();
699 if RocketMQSerializable::rocket_mq_protocol_decode(&mut buf, header_len).is_ok() {
700 panic!("missing ext length should decode to error");
701 }
702 }
703
704 #[test]
705 fn rocketmq_protocol_decode_rejects_truncated_ext_fields_without_panic() {
706 let mut buf = minimal_header_without_ext_len();
707 buf.put_i32(10);
708 let header_len = buf.len();
709 if RocketMQSerializable::rocket_mq_protocol_decode(&mut buf, header_len).is_ok() {
710 panic!("truncated ext fields should decode to error");
711 }
712 }
713
714 #[test]
715 fn rocketmq_protocol_decode_rejects_bytes_after_declared_ext_fields() {
716 let mut buf = minimal_header_without_ext_len();
717 buf.put_i32(0);
718 buf.put_u8(1);
719 let header_len = buf.len();
720
721 assert!(RocketMQSerializable::rocket_mq_protocol_decode(&mut buf, header_len).is_err());
722 }
723
724 #[test]
725 fn bytes_decoder_preserves_remark_and_extension_fields() {
726 let mut buf = BytesMut::new();
727 buf.put_i16(10);
728 buf.put_u8(LanguageCode::RUST.get_code());
729 buf.put_i16(501);
730 buf.put_i32(7);
731 buf.put_i32(1);
732 RocketMQSerializable::write_str(&mut buf, false, "remark");
733 let ext_length_offset = buf.len();
734 buf.put_i32(0);
735 let ext_start = buf.len();
736 buf.put_u16(3);
737 buf.extend_from_slice(b"key");
738 buf.put_i32(5);
739 buf.extend_from_slice(b"value");
740 let ext_length = (buf.len() - ext_start) as i32;
741 buf[ext_length_offset..ext_length_offset + 4].copy_from_slice(&ext_length.to_be_bytes());
742
743 let command = RocketMQSerializable::rocket_mq_protocol_decode_bytes(buf.freeze()).unwrap();
744
745 assert_eq!(command.code(), 10);
746 assert_eq!(command.language(), LanguageCode::RUST);
747 assert_eq!(command.version(), 501);
748 assert_eq!(command.opaque(), 7);
749 assert_eq!(command.flag(), 1);
750 assert_eq!(command.remark().map(CheetahString::as_str), Some("remark"));
751 assert_eq!(command.ext_fields().unwrap().get("key").unwrap(), "value");
752 }
753
754 #[test]
755 fn binary_decode_does_not_generate_an_outbound_request_id() {
756 let mut buf = BytesMut::new();
757 buf.put_i16(105);
758 buf.put_u8(LanguageCode::JAVA.get_code());
759 buf.put_i16(321);
760 buf.put_i32(0x0102_0304);
761 buf.put_i32(3);
762 RocketMQSerializable::write_str(&mut buf, false, "wire");
763 buf.put_i32(0);
764 let before = crate::protocol::remoting_command::request_id_generation_count();
765
766 let command = RocketMQSerializable::rocket_mq_protocol_decode_bytes(buf.freeze()).unwrap();
767
768 assert_eq!(
769 crate::protocol::remoting_command::request_id_generation_count(),
770 before,
771 "binary decode must not consume an outbound correlation ID"
772 );
773 assert_eq!(command.code(), 105);
774 assert_eq!(command.language(), LanguageCode::JAVA);
775 assert_eq!(command.version(), 321);
776 assert_eq!(command.opaque(), 0x0102_0304);
777 assert_eq!(command.flag(), 3);
778 assert_eq!(command.remark().map(CheetahString::as_str), Some("wire"));
779 assert!(command.ext_fields().is_some_and(HashMap::is_empty));
780 assert_eq!(command.get_serialize_type(), crate::protocol::SerializeType::ROCKETMQ);
781 }
782}