Skip to main content

rocketmq_protocol/common/message/
message_decoder.rs

1// Copyright 2023 The RocketMQ Rust Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::borrow::Cow;
16use std::collections::HashMap;
17use std::io::Cursor;
18use std::net::IpAddr;
19use std::net::Ipv4Addr;
20use std::net::Ipv6Addr;
21use std::net::SocketAddr;
22use std::str;
23
24use byteorder::BigEndian;
25use byteorder::ByteOrder;
26use bytes::Buf;
27use bytes::BufMut;
28use bytes::Bytes;
29use bytes::BytesMut;
30use cheetah_string::CheetahBuilder;
31use cheetah_string::CheetahString;
32use memchr::memchr;
33
34use crate::common::compression::compressor_factory::CompressorFactory;
35use rocketmq_model::codec::string_to_bytes;
36use rocketmq_model::common::message::message_client_ext::MessageClientExt;
37use rocketmq_model::common::message::message_ext::MessageExt;
38use rocketmq_model::common::message::message_id::MessageId;
39use rocketmq_model::common::message::message_property::MessageProperties;
40use rocketmq_model::common::message::message_single::Message;
41use rocketmq_model::common::message::MessageConst;
42use rocketmq_model::common::message::MessageTrait;
43use rocketmq_model::common::message::MessageVersion;
44use rocketmq_model::common::sys_flag::message_sys_flag::MessageSysFlag;
45use rocketmq_model::utils::crc32_utils::crc32;
46use rocketmq_model::utils::message_utils::build_message_id;
47
48pub const CHARSET_UTF8: &str = "UTF-8";
49pub const MESSAGE_MAGIC_CODE_POSITION: usize = 4;
50pub const MESSAGE_FLAG_POSITION: usize = 16;
51pub const MESSAGE_PHYSIC_OFFSET_POSITION: usize = 28;
52pub const MESSAGE_STORE_TIMESTAMP_POSITION: usize = 56;
53pub const MESSAGE_MAGIC_CODE: i32 = -626843481;
54pub const MESSAGE_MAGIC_CODE_V2: i32 = -626843477;
55pub const BLANK_MAGIC_CODE: i32 = -875286124;
56pub const NAME_VALUE_SEPARATOR: char = '\u{0001}';
57pub const PROPERTY_SEPARATOR: char = '\u{0002}';
58pub const PHY_POS_POSITION: usize = 4 + 4 + 4 + 4 + 4 + 8;
59pub const QUEUE_OFFSET_POSITION: usize = 4 + 4 + 4 + 4 + 4;
60pub const SYSFLAG_POSITION: usize = 4 + 4 + 4 + 4 + 4 + 8 + 8;
61pub const BORN_TIMESTAMP_POSITION: usize = 4 + 4 + 4 + 4 + 4 + 8 + 8 + 4 + 8;
62
63const SMALL_PROPERTIES_PREALLOC_THRESHOLD: usize = 16;
64const SMALL_PROPERTY_PREALLOC_BYTES: usize = 34;
65
66macro_rules! decode_message_properties_into {
67    ($map:ident, $properties:expr) => {{
68        let properties = $properties;
69        let bytes = properties.as_bytes();
70        let mut index = 0;
71        let len = bytes.len();
72        while index < len {
73            let new_index = memchr(PROPERTY_SEPARATOR as u8, &bytes[index..]).map_or(len, |i| index + i);
74            if new_index - index >= 3 {
75                if let Some(kv_sep_index) = memchr(NAME_VALUE_SEPARATOR as u8, &bytes[index..new_index]) {
76                    let kv_sep_index = index + kv_sep_index;
77                    if kv_sep_index > index && kv_sep_index < new_index - 1 {
78                        let k = &properties[index..kv_sep_index];
79                        let v = &properties[kv_sep_index + 1..new_index];
80                        $map.insert(CheetahString::from_slice(k), CheetahString::from_slice(v));
81                    }
82                }
83            }
84            index = new_index + 1;
85        }
86    }};
87}
88
89fn get_i16_checked(buffer: &mut Bytes) -> Option<i16> {
90    if buffer.remaining() < 2 {
91        None
92    } else {
93        Some(buffer.get_i16())
94    }
95}
96
97fn get_i32_checked(buffer: &mut Bytes) -> Option<i32> {
98    if buffer.remaining() < 4 {
99        None
100    } else {
101        Some(buffer.get_i32())
102    }
103}
104
105fn split_to_checked(buffer: &mut Bytes, len: usize) -> Option<Bytes> {
106    if buffer.remaining() < len {
107        None
108    } else {
109        Some(buffer.split_to(len))
110    }
111}
112
113pub fn string_to_message_properties(properties: Option<&CheetahString>) -> HashMap<CheetahString, CheetahString> {
114    if let Some(properties) = properties {
115        str_to_message_properties(Some(properties.as_str()))
116    } else {
117        HashMap::new()
118    }
119}
120
121pub fn str_to_message_properties(properties: Option<&str>) -> HashMap<CheetahString, CheetahString> {
122    let mut map = HashMap::new();
123    if let Some(properties) = properties {
124        decode_message_properties_into!(map, properties);
125    }
126    map
127}
128
129/// Encodes message properties with RocketMQ's name/value and property
130/// separators.
131#[inline]
132pub fn message_properties_to_string(properties: &HashMap<CheetahString, CheetahString>) -> CheetahString {
133    if properties.is_empty() {
134        return CheetahString::empty();
135    }
136
137    let property_count = properties.len();
138    let capacity = if property_count <= SMALL_PROPERTIES_PREALLOC_THRESHOLD {
139        property_count * SMALL_PROPERTY_PREALLOC_BYTES
140    } else {
141        let mut len = 0;
142        for (name, value) in properties.iter() {
143            len += name.len();
144            len += value.len();
145            len += 2; // separator
146        }
147        len
148    };
149
150    let mut builder = CheetahBuilder::with_capacity(capacity);
151    for (name, value) in properties.iter() {
152        builder.push_str(name.as_str());
153        builder.push(NAME_VALUE_SEPARATOR);
154
155        builder.push_str(value.as_str());
156        builder.push(PROPERTY_SEPARATOR);
157    }
158    builder.finish()
159}
160
161/// Converts UTF-8 message protocol bytes into a [`CheetahString`], preserving
162/// `String::from_utf8_lossy` compatibility for invalid input.
163pub fn cheetah_from_utf8_lossy(bytes: &[u8]) -> CheetahString {
164    match String::from_utf8_lossy(bytes) {
165        Cow::Borrowed(value) => CheetahString::from_slice(value),
166        Cow::Owned(value) => CheetahString::from_string(value),
167    }
168}
169
170pub fn decode_client(
171    byte_buffer: &mut Bytes,
172    read_body: bool,
173    de_compress_body: bool,
174    is_set_properties_string: bool,
175    check_crc: bool,
176) -> Option<MessageClientExt> {
177    /*if let Some(msg_ext) = decode(
178        byte_buffer,
179        read_body,
180        de_compress_body,
181        false,
182        is_set_properties_string,
183        check_crc,
184    ) {
185        Some(MessageClientExt {
186            message_ext_inner: msg_ext,
187        })
188    } else {
189        None
190    }*/
191    decode(
192        byte_buffer,
193        read_body,
194        de_compress_body,
195        false,
196        is_set_properties_string,
197        check_crc,
198    )
199    .map(|msg_ext| MessageClientExt {
200        message_ext_inner: msg_ext,
201    })
202}
203
204//this method will optimize later
205pub fn decode(
206    byte_buffer: &mut Bytes,
207    read_body: bool,
208    de_compress_body: bool,
209    _is_client: bool,
210    is_set_properties_string: bool,
211    check_crc: bool,
212) -> Option<MessageExt> {
213    let frame = crate::protocol::body::message_codec::decode_message_frame(byte_buffer).ok()?;
214
215    if read_body && !frame.body.is_empty() && check_crc && crc32(&frame.body) != frame.body_crc {
216        return None;
217    }
218
219    let mut msg_ext = MessageExt::default();
220    msg_ext.set_store_size(frame.store_size);
221    msg_ext.set_body_crc(frame.body_crc);
222    msg_ext.set_queue_id(frame.queue_id);
223    msg_ext.message.set_flag(frame.flag);
224    msg_ext.set_queue_offset(frame.queue_offset);
225    msg_ext.set_commit_log_offset(frame.commit_log_offset);
226    msg_ext.set_sys_flag(frame.sys_flag);
227    msg_ext.set_born_timestamp(frame.born_timestamp);
228    msg_ext.set_born_host(frame.born_host);
229    msg_ext.set_store_timestamp(frame.store_timestamp);
230    msg_ext.set_store_host(frame.store_host);
231    msg_ext.set_reconsume_times(frame.reconsume_times);
232    msg_ext.set_prepared_transaction_offset(frame.prepared_transaction_offset);
233    msg_ext.message.set_topic(frame.topic);
234
235    if read_body && !frame.body.is_empty() {
236        let mut body = frame.body;
237        if de_compress_body && (frame.sys_flag & MessageSysFlag::COMPRESSED_FLAG) == MessageSysFlag::COMPRESSED_FLAG {
238            let compression_type = MessageSysFlag::try_get_compression_type(frame.sys_flag).ok()?;
239            body = compression_type.try_decompression(&body).ok()?;
240        }
241        msg_ext.message.set_body(Some(body));
242    }
243
244    let mut properties = frame.properties;
245    if is_set_properties_string && !frame.properties_string.is_empty() {
246        properties.insert(
247            CheetahString::from_static_str("propertiesString"),
248            frame.properties_string,
249        );
250    }
251    *msg_ext.message.properties_mut() = MessageProperties::from_map(properties);
252    msg_ext.set_msg_id(CheetahString::from_string(build_message_id(
253        frame.store_host,
254        frame.commit_log_offset,
255    )));
256
257    Some(msg_ext)
258}
259
260pub fn count_inner_msg_num(bytes: Option<Bytes>) -> u32 {
261    match bytes {
262        None => 0,
263        Some(mut bytes) => {
264            let mut count = 0;
265            while bytes.has_remaining() {
266                let size = bytes.slice(0..4).get_i32();
267                if size as usize > bytes.len() {
268                    break;
269                }
270                let _ = bytes.split_to(size as usize);
271                count += 1;
272            }
273            count
274        }
275    }
276}
277
278pub fn encode_messages(messages: &[Message]) -> Bytes {
279    let mut bytes = BytesMut::new();
280    for message in messages {
281        encode_message_into(message, &mut bytes);
282    }
283    bytes.freeze()
284}
285
286pub fn encode_message(message: &Message) -> Bytes {
287    let mut bytes = BytesMut::new();
288    encode_message_into(message, &mut bytes);
289    bytes.freeze()
290}
291
292/// Encodes one producer message directly into an existing aggregate buffer.
293///
294/// This uses the same Java-compatible batch wire layout as [`encode_message`] while avoiding a
295/// temporary allocation for every message in a batch.
296pub fn encode_message_into(message: &Message, bytes: &mut BytesMut) {
297    let empty_body = Bytes::new();
298    let body = message.get_body().unwrap_or(&empty_body);
299    let body_len = body.len();
300    let properties = message_properties_to_string(message.properties().as_map());
301    let properties_bytes = properties.as_bytes();
302    let properties_length = properties_bytes.len();
303
304    let store_size = 4 // 1 TOTALSIZE
305             + 4 // 2 MAGICCOD
306             + 4 // 3 BODYCRC
307             + 4 // 4 FLAG
308             + 4 + body_len // 4 BODY
309             + 2 + properties_length;
310
311    bytes.reserve(store_size);
312
313    // 1 TOTALSIZE
314    bytes.put_i32(store_size as i32);
315
316    // 2 MAGICCODE
317    bytes.put_i32(0);
318
319    // 3 BODYCRC
320    bytes.put_u32(0);
321
322    // 4 FLAG
323    bytes.put_i32(message.flag());
324
325    // 5 BODY
326    bytes.put_i32(body_len as i32);
327    bytes.put_slice(body);
328
329    // 6 PROPERTIES
330    bytes.put_i16(properties_length as i16);
331    bytes.put_slice(properties_bytes);
332}
333
334pub fn decodes_batch(byte_buffer: &mut Bytes, read_body: bool, decompress_body: bool) -> Vec<MessageExt> {
335    let mut messages = Vec::new();
336    while byte_buffer.has_remaining() {
337        if let Some(msg_ext) = decode(byte_buffer, read_body, decompress_body, false, false, false) {
338            messages.push(msg_ext);
339        } else {
340            break;
341        }
342    }
343    messages
344}
345
346fn split_next_message_frame(byte_buffer: &mut Bytes) -> Option<Bytes> {
347    let remaining = byte_buffer.remaining();
348    if remaining < 4 {
349        return None;
350    }
351
352    let mut header = byte_buffer.clone();
353    let store_size = header.get_i32();
354    if store_size <= 0 || store_size as usize > remaining {
355        return None;
356    }
357
358    Some(byte_buffer.split_to(store_size as usize))
359}
360
361pub fn decodes_batch_with_metadata_filter<F>(
362    byte_buffer: &mut Bytes,
363    read_body: bool,
364    decompress_body: bool,
365    mut should_decode_body: F,
366) -> Vec<MessageExt>
367where
368    F: FnMut(&MessageExt) -> bool,
369{
370    let mut messages = Vec::new();
371    while byte_buffer.has_remaining() {
372        let Some(frame) = split_next_message_frame(byte_buffer) else {
373            break;
374        };
375        let mut metadata_frame = frame.clone();
376        let Some(metadata) = decode(&mut metadata_frame, false, false, false, false, false) else {
377            break;
378        };
379        if !should_decode_body(&metadata) {
380            continue;
381        }
382
383        let mut full_frame = frame;
384        if let Some(msg_ext) = decode(&mut full_frame, read_body, decompress_body, false, false, false) {
385            messages.push(msg_ext);
386        } else {
387            break;
388        }
389    }
390    messages
391}
392
393pub fn decodes_batch_client(byte_buffer: &mut Bytes, read_body: bool, decompress_body: bool) -> Vec<MessageClientExt> {
394    let mut messages = Vec::new();
395    while byte_buffer.has_remaining() {
396        if let Some(msg_ext) = decode_client(byte_buffer, read_body, decompress_body, false, false) {
397            messages.push(msg_ext);
398        } else {
399            break;
400        }
401    }
402    messages
403}
404
405pub fn decode_messages_from(mut message_ext: MessageExt, vec_: &mut Vec<MessageExt>) {
406    let Some(body) = message_ext.message.body_mut().raw_mut().as_mut() else {
407        return;
408    };
409    let messages = decode_messages(body);
410    for message in messages {
411        let mut message_ext_inner = MessageExt {
412            message,
413            ..MessageExt::default()
414        };
415        message_ext_inner.set_topic(message_ext.topic().to_owned());
416        message_ext_inner.queue_offset = message_ext.queue_offset;
417        message_ext_inner.queue_id = message_ext.queue_id;
418        message_ext_inner.set_flag(message_ext.get_flag());
419        //MessageAccessor::set_properties(&mut
420        // message_client_ext,message.get_properties().clone()); messageClientExt.
421        // setBody(message.getBody())
422        message_ext_inner.store_host = message_ext.store_host;
423        message_ext_inner.born_host = message_ext.born_host;
424        message_ext_inner.store_timestamp = message_ext.store_timestamp;
425        message_ext_inner.born_timestamp = message_ext.born_timestamp;
426        message_ext_inner.sys_flag = message_ext.sys_flag;
427        message_ext_inner.commit_log_offset = message_ext.commit_log_offset;
428        message_ext_inner.set_wait_store_msg_ok(message_ext.is_wait_store_msg_ok());
429        vec_.push(message_ext_inner);
430    }
431}
432
433pub fn decode_messages(buffer: &mut Bytes) -> Vec<Message> {
434    let mut messages = Vec::new();
435    while buffer.has_remaining() {
436        let Some(message) = try_decode_message(buffer) else {
437            break;
438        };
439        messages.push(message);
440    }
441    messages
442}
443
444pub fn decode_message(buffer: &mut Bytes) -> Message {
445    try_decode_message(buffer).unwrap_or_default()
446}
447
448fn try_decode_message(buffer: &mut Bytes) -> Option<Message> {
449    // 1 TOTALSIZE
450    let store_size = get_i32_checked(buffer)?;
451    if store_size <= 0 {
452        return None;
453    }
454
455    // 2 MAGICCODE
456    let _ = get_i32_checked(buffer)?;
457
458    // 3 BODYCRC
459    let _ = get_i32_checked(buffer)?;
460
461    // 4 FLAG
462    let flag = get_i32_checked(buffer)?;
463
464    // 5 BODY
465    let body_len = get_i32_checked(buffer)?;
466    if body_len < 0 {
467        return None;
468    }
469    let body = split_to_checked(buffer, body_len as usize)?;
470
471    // 6 properties
472    let properties_length = get_i16_checked(buffer)?;
473    if properties_length < 0 {
474        return None;
475    }
476    let properties = split_to_checked(buffer, properties_length as usize)?;
477    //string_to_message_properties(Some(&String::from_utf8_lossy(properties.as_ref()).
478    // to_string()));
479    let message_properties = str_to_message_properties(Some(str::from_utf8(&properties).ok()?));
480
481    let mut message = Message::default();
482    message.set_body(Some(body));
483    message.set_properties(message_properties);
484    message.set_flag(flag);
485    Some(message)
486}
487
488const MSG_ID_IPV4_LEN: usize = 32;
489const MSG_ID_IPV6_LEN: usize = 56;
490
491pub fn validate_message_id(msg_id: &str) -> Result<(), String> {
492    let msg_id = msg_id.trim();
493
494    if msg_id.is_empty() {
495        return Err("Message ID cannot be empty".to_string());
496    }
497
498    let len = msg_id.len();
499    if len != MSG_ID_IPV4_LEN && len != MSG_ID_IPV6_LEN {
500        return Err(format!(
501            "Invalid message ID length: {len}. Expected {MSG_ID_IPV4_LEN} characters (IPv4) or {MSG_ID_IPV6_LEN} \
502             characters (IPv6)"
503        ));
504    }
505
506    if !msg_id.bytes().all(|b| b.is_ascii_hexdigit()) {
507        return Err("Message ID must be a valid hexadecimal string".to_string());
508    }
509
510    Ok(())
511}
512
513pub fn decode_message_id(msg_id: &str) -> Result<MessageId, String> {
514    validate_message_id(msg_id)?;
515    let bytes = string_to_bytes(msg_id).ok_or_else(|| "Failed to decode message ID: invalid hex string".to_string())?;
516    let mut buffer = Bytes::from(bytes);
517    let address = if msg_id.len() == 32 {
518        let mut ip = [0u8; 4];
519        buffer.copy_to_slice(&mut ip);
520        let port = buffer.get_i32();
521        SocketAddr::new(IpAddr::V4(Ipv4Addr::from(ip)), port as u16)
522    } else {
523        let mut ip = [0u8; 16];
524        buffer.copy_to_slice(&mut ip);
525        let port = buffer.get_i32();
526        SocketAddr::new(IpAddr::V6(Ipv6Addr::from(ip)), port as u16)
527    };
528    Ok(MessageId {
529        address,
530        offset: buffer.get_i64(),
531    })
532}
533
534pub fn encode(message_ext: &MessageExt, need_compress: bool) -> rocketmq_error::Result<Bytes> {
535    let body = message_ext
536        .get_body()
537        .ok_or_else(|| crate::error::invalid_argument("message body is required"))?;
538    let topic = message_ext.topic().as_bytes();
539    let topic_len = topic.len();
540    let properties = message_properties_to_string(message_ext.get_properties());
541    let properties_bytes = properties.as_bytes();
542    let properties_length = properties_bytes.len();
543    let sys_flag = message_ext.sys_flag;
544    let born_host_length = if (sys_flag & MessageSysFlag::BORNHOST_V6_FLAG) == 0 {
545        8
546    } else {
547        20
548    };
549    let store_host_address_length = if (sys_flag & MessageSysFlag::STOREHOSTADDRESS_V6_FLAG) == 0 {
550        8
551    } else {
552        20
553    };
554    let new_body = if need_compress && (sys_flag & MessageSysFlag::COMPRESSED_FLAG) == MessageSysFlag::COMPRESSED_FLAG {
555        let compressor = CompressorFactory::get_compressor(MessageSysFlag::try_get_compression_type(sys_flag)?);
556        let compressed_body = compressor.compress(body, 5)?;
557        Some(compressed_body)
558    } else {
559        None
560    };
561    let body_len = new_body.as_ref().map_or(body.len(), |b| b.len());
562    let store_size = if message_ext.store_size > 0 {
563        message_ext.store_size as usize
564    } else {
565        4 // 1 TOTALSIZE
566             + 4 // 2 MAGICCODE
567             + 4 // 3 BODYCRC
568             + 4 // 4 QUEUEID
569             + 4 // 5 FLAG
570             + 8 // 6 QUEUEOFFSET
571             + 8 // 7 PHYSICALOFFSET
572             + 4 // 8 SYSFLAG
573             + 8 // 9 BORNTIMESTAMP
574             + born_host_length // 10 BORNHOST
575             + 8 // 11 STORETIMESTAMP
576             + store_host_address_length // 12 STOREHOSTADDRESS
577             + 4 // 13 RECONSUMETIMES
578             + 8 // 14 Prepared Transaction Offset
579             + 4 + body_len // 14 BODY
580             + 1 + topic_len // 15 TOPIC
581             + 2 + properties_length // 16 propertiesLength
582    };
583    let mut byte_buffer = BytesMut::with_capacity(store_size);
584
585    // 1 TOTALSIZE
586    byte_buffer.put_i32(store_size as i32);
587
588    // 2 MAGICCODE
589    byte_buffer.put_i32(MESSAGE_MAGIC_CODE);
590
591    // 3 BODYCRC
592    byte_buffer.put_u32(message_ext.body_crc);
593
594    // 4 QUEUEID
595    byte_buffer.put_i32(message_ext.queue_id);
596
597    // 5 FLAG
598    byte_buffer.put_i32(message_ext.message.flag());
599
600    // 6 QUEUEOFFSET
601    byte_buffer.put_i64(message_ext.queue_offset);
602
603    // 7 PHYSICALOFFSET
604    byte_buffer.put_i64(message_ext.commit_log_offset);
605
606    // 8 SYSFLAG
607    byte_buffer.put_i32(message_ext.sys_flag);
608
609    // 9 BORNTIMESTAMP
610    byte_buffer.put_i64(message_ext.born_timestamp);
611
612    // 10 BORNHOST
613
614    let born_host = message_ext.born_host;
615    match born_host {
616        SocketAddr::V4(value) => byte_buffer.extend(value.ip().octets()),
617        SocketAddr::V6(value) => byte_buffer.extend(value.ip().octets()),
618    };
619
620    byte_buffer.put_i32(born_host.port() as i32);
621
622    // 11 STORETIMESTAMP
623    byte_buffer.put_i64(message_ext.store_timestamp);
624
625    // 12 STOREHOST
626
627    let store_host = message_ext.store_host;
628    match store_host {
629        SocketAddr::V4(value) => byte_buffer.extend(value.ip().octets()),
630        SocketAddr::V6(value) => byte_buffer.extend(value.ip().octets()),
631    };
632
633    byte_buffer.put_i32(store_host.port() as i32);
634
635    // 13 RECONSUMETIMES
636    byte_buffer.put_i32(message_ext.reconsume_times);
637
638    // 14 Prepared Transaction Offset
639    byte_buffer.put_i64(message_ext.prepared_transaction_offset);
640
641    // 15 BODY
642    byte_buffer.put_i32(body_len as i32);
643    if let Some(new_body) = new_body {
644        byte_buffer.put_slice(&new_body);
645    } else {
646        byte_buffer.put_slice(body);
647    }
648
649    // 16 TOPIC
650    byte_buffer.put_u8(topic_len as u8);
651    byte_buffer.put_slice(topic);
652
653    // 17 properties
654    byte_buffer.put_i16(properties_length as i16);
655    byte_buffer.put_slice(properties_bytes);
656
657    Ok(byte_buffer.freeze())
658}
659
660pub fn encode_uniquely(message_ext: &MessageExt, need_compress: bool) -> rocketmq_error::Result<Bytes> {
661    let body = message_ext
662        .get_body()
663        .ok_or_else(|| crate::error::invalid_argument("message body is required"))?;
664    let topics = message_ext.topic().as_bytes();
665    let topic_len = topics.len();
666    let properties = message_properties_to_string(message_ext.get_properties());
667    let properties_bytes = properties.as_bytes();
668    let properties_length = properties_bytes.len();
669    let sys_flag = message_ext.sys_flag;
670    let born_host_length = if (sys_flag & MessageSysFlag::BORNHOST_V6_FLAG) == 0 {
671        8
672    } else {
673        20
674    };
675    let new_body = if need_compress && (sys_flag & MessageSysFlag::COMPRESSED_FLAG) == MessageSysFlag::COMPRESSED_FLAG {
676        let compressor = CompressorFactory::get_compressor(MessageSysFlag::try_get_compression_type(sys_flag)?);
677        let compressed_body = compressor.compress(body, 5)?;
678        Some(compressed_body)
679    } else {
680        None
681    };
682    let body_len = new_body.as_ref().map_or(body.len(), |b| b.len());
683    let store_size = if message_ext.store_size > 0 {
684        message_ext.store_size as usize
685    } else {
686        4 // 1 TOTALSIZE
687             + 4 // 2 MAGICCODE
688             + 4 // 3 BODYCRC
689             + 4 // 4 QUEUEID
690             + 4 // 5 FLAG
691             + 8 // 6 QUEUEOFFSET
692             + 8 // 7 PHYSICALOFFSET
693             + 4 // 8 SYSFLAG
694             + 8 // 9 BORNTIMESTAMP
695             + born_host_length // 10 BORNHOST
696             + 4 // 11 RECONSUMETIMES
697             + 8 // 12 Prepared Transaction Offset
698             + 4 + body_len // 13 BODY
699             + 1 + topic_len // 14 TOPIC
700             + 2 + properties_length // 15 propertiesLength
701    };
702    let capacity = if message_ext.store_size > 0 {
703        store_size.saturating_sub(8)
704    } else {
705        store_size
706    };
707    let mut byte_buffer = BytesMut::with_capacity(capacity);
708
709    // 1 TOTALSIZE
710    byte_buffer.put_i32(store_size as i32);
711
712    // 2 MAGICCODE
713    byte_buffer.put_i32(MESSAGE_MAGIC_CODE);
714
715    // 3 BODYCRC
716    byte_buffer.put_u32(message_ext.body_crc);
717
718    // 4 QUEUEID
719    byte_buffer.put_i32(message_ext.queue_id);
720
721    // 5 FLAG
722    byte_buffer.put_i32(message_ext.message.flag());
723
724    // 6 QUEUEOFFSET
725    byte_buffer.put_i64(message_ext.queue_offset);
726
727    // 7 PHYSICALOFFSET
728    byte_buffer.put_i64(message_ext.commit_log_offset);
729
730    // 8 SYSFLAG
731    byte_buffer.put_i32(message_ext.sys_flag);
732
733    // 9 BORNTIMESTAMP
734    byte_buffer.put_i64(message_ext.born_timestamp);
735
736    // 10 BORNHOST
737
738    let born_host = message_ext.born_host;
739    match born_host {
740        SocketAddr::V4(value) => byte_buffer.extend(value.ip().octets()),
741        SocketAddr::V6(value) => byte_buffer.extend(value.ip().octets()),
742    };
743    byte_buffer.put_i32(born_host.port() as i32);
744
745    // 11 RECONSUMETIMES
746    byte_buffer.put_i32(message_ext.reconsume_times);
747
748    // 12 Prepared Transaction Offset
749    byte_buffer.put_i64(message_ext.prepared_transaction_offset);
750
751    // 13 BODY
752    byte_buffer.put_i32(body_len as i32);
753    if let Some(new_body) = new_body {
754        byte_buffer.put_slice(&new_body);
755    } else {
756        byte_buffer.put_slice(body);
757    }
758
759    // 14 TOPIC
760    byte_buffer.put_i16(topic_len as i16);
761    byte_buffer.put_slice(topics);
762
763    // 15 properties
764    byte_buffer.put_i16(properties_length as i16);
765    byte_buffer.put_slice(properties_bytes);
766
767    Ok(byte_buffer.freeze())
768}
769
770pub fn create_crc32(mut input: &mut [u8], crc32: u32) {
771    input.put(MessageConst::PROPERTY_CRC32.as_bytes());
772    input.put_u8(NAME_VALUE_SEPARATOR as u8);
773    let mut crc32 = crc32;
774    for _ in 0..10 {
775        let mut b = b'0';
776        if crc32 > 0 {
777            b += (crc32 % 10) as u8;
778            crc32 /= 10;
779        }
780        input.put_u8(b);
781    }
782    input.put_u8(PROPERTY_SEPARATOR as u8);
783}
784
785pub fn decode_properties(bytes: &mut Bytes) -> Option<HashMap<CheetahString, CheetahString>> {
786    // Ensure we have enough bytes to read SYSFLAG and MAGICCODE.
787    if bytes.len() < SYSFLAG_POSITION + 4 {
788        return None;
789    }
790
791    // Read sysFlag and magicCode using fixed positions.
792    let sys_flag = BigEndian::read_i32(&bytes[SYSFLAG_POSITION..SYSFLAG_POSITION + 4]);
793    let magic_code = BigEndian::read_i32(&bytes[MESSAGE_MAGIC_CODE_POSITION..MESSAGE_MAGIC_CODE_POSITION + 4]);
794    let version = match MessageVersion::value_of_magic_code(magic_code) {
795        Ok(value) => value,
796        Err(_) => return None,
797    };
798
799    // Determine address lengths.
800    let bornhost_length = if (sys_flag & MessageSysFlag::BORNHOST_V6_FLAG) == 0 {
801        8
802    } else {
803        20
804    };
805    let storehost_address_length = if (sys_flag & MessageSysFlag::STOREHOSTADDRESS_V6_FLAG) == 0 {
806        8
807    } else {
808        20
809    };
810
811    // Calculate the bodySizePosition as in Java.
812    let body_size_position = 4   // TOTALSIZE
813        + 4   // MAGICCODE
814        + 4   // BODYCRC
815        + 4   // QUEUEID
816        + 4   // FLAG
817        + 8   // QUEUEOFFSET
818        + 8   // PHYSICALOFFSET
819        + 4   // SYSFLAG
820        + 8   // BORNTIMESTAMP
821        + bornhost_length // BORNHOST
822        + 8   // STORETIMESTAMP
823        + storehost_address_length // STOREHOSTADDRESS
824        + 4   // RECONSUMETIMES
825        + 8; // Prepared Transaction Offset
826
827    if bytes.len() < body_size_position + 4 {
828        return None;
829    }
830
831    // Read the body size stored as an int.
832    let body_size = BigEndian::read_i32(&bytes[body_size_position..body_size_position + 4]) as usize;
833
834    // Compute the topic length position.
835    let topic_length_position = body_size_position + 4 + body_size;
836    if bytes.len() < topic_length_position {
837        return None;
838    }
839
840    // Create a Cursor over the slice starting at the topic length position.
841    let slice = &bytes[topic_length_position..];
842    let _cursor = Cursor::new(slice);
843    let topic_length_size = version.get_topic_length_size();
844    bytes.advance(topic_length_position);
845    let topic_length = version.get_topic_length(bytes);
846
847    // Calculate the properties position.
848    let properties_position = topic_length_position + topic_length_size + topic_length;
849    if bytes.len() < properties_position + 2 {
850        return None;
851    }
852
853    // Read a short (2 bytes) as propertiesLength.
854    let properties_length = BigEndian::read_i16(&bytes[properties_position..properties_position + 2]);
855
856    // Advance past the short value.
857    let properties_start = properties_position + 2;
858    if properties_length > 0 {
859        let end = properties_start + (properties_length as usize);
860        if bytes.len() < end {
861            return None;
862        }
863        let properties_bytes = &bytes[properties_start..end];
864        if let Ok(properties_string) = String::from_utf8(properties_bytes.to_vec()) {
865            Some(string_to_message_properties(Some(&CheetahString::from_string(
866                properties_string,
867            ))))
868        } else {
869            None
870        }
871    } else {
872        None
873    }
874}
875
876#[cfg(test)]
877mod tests {
878    use bytes::BufMut;
879    use bytes::BytesMut;
880
881    use super::*;
882
883    fn message_ext_with_tag(queue_offset: i64, tag: &str, body_size: usize) -> MessageExt {
884        let message = Message::builder()
885            .topic("MetadataFilterTopic")
886            .tags(tag)
887            .body(vec![b'x'; body_size])
888            .build()
889            .expect("test message should build");
890
891        let mut message_ext = MessageExt::default();
892        message_ext.set_message_inner(message);
893        message_ext.set_queue_id(1);
894        message_ext.set_queue_offset(queue_offset);
895        message_ext.set_commit_log_offset(queue_offset * 1024);
896        message_ext
897    }
898
899    #[test]
900    fn encode_messages_matches_concatenated_single_message_wire_bytes() {
901        let messages = vec![
902            Message::builder()
903                .topic("BatchTopic")
904                .tags("TagA")
905                .body_slice(b"first")
906                .build_unchecked(),
907            Message::builder()
908                .topic("BatchTopic")
909                .keys(vec!["key-a".to_string(), "key-b".to_string()])
910                .body_slice(b"second-message")
911                .build_unchecked(),
912        ];
913        let mut expected = BytesMut::new();
914        for message in &messages {
915            expected.put_slice(&encode_message(message));
916        }
917
918        assert_eq!(encode_messages(&messages), expected.freeze());
919    }
920
921    #[test]
922    fn decodes_batch_with_metadata_filter_skips_bodies_for_rejected_frames() {
923        let rejected = message_ext_with_tag(0, "Skip", 1024);
924        let accepted = message_ext_with_tag(1, "Keep", 32);
925        let mut payload = BytesMut::new();
926        payload.put_slice(&encode(&rejected, false).expect("rejected message should encode"));
927        payload.put_slice(&encode(&accepted, false).expect("accepted message should encode"));
928        let mut payload = payload.freeze();
929        let mut inspected = Vec::new();
930
931        let messages = decodes_batch_with_metadata_filter(&mut payload, true, false, |metadata| {
932            inspected.push((metadata.queue_offset, metadata.get_body().is_some(), metadata.tags()));
933            metadata.tags().as_deref() == Some("Keep")
934        });
935
936        assert_eq!(messages.len(), 1);
937        assert_eq!(messages[0].queue_offset, 1);
938        assert_eq!(
939            messages[0].get_body().expect("accepted body should materialize").len(),
940            32
941        );
942        assert_eq!(payload.len(), 0);
943        assert_eq!(inspected.len(), 2);
944        assert_eq!(inspected[0], (0, false, Some(CheetahString::from_static_str("Skip"))));
945        assert_eq!(inspected[1], (1, false, Some(CheetahString::from_static_str("Keep"))));
946    }
947
948    #[test]
949    fn message_properties_to_string_preserves_separator_format() {
950        let mut properties = HashMap::new();
951        properties.insert(
952            CheetahString::from_static_str("key"),
953            CheetahString::from_static_str("value"),
954        );
955
956        let encoded = message_properties_to_string(&properties);
957
958        assert_eq!(encoded, "key\u{0001}value\u{0002}");
959    }
960
961    #[test]
962    fn message_properties_to_string_empty_properties_returns_empty_string() {
963        let properties = HashMap::new();
964
965        let encoded = message_properties_to_string(&properties);
966
967        assert!(encoded.is_empty());
968    }
969
970    #[test]
971    fn message_properties_to_string_round_trips_small_property_map() {
972        let properties = [
973            ("topic", "benchmark-topic"),
974            ("keys", "key-a"),
975            ("tags", "tag-a"),
976            ("trace", "trace-001"),
977        ]
978        .into_iter()
979        .map(|(key, value)| {
980            (
981                CheetahString::from_static_str(key),
982                CheetahString::from_static_str(value),
983            )
984        })
985        .collect::<HashMap<_, _>>();
986
987        let encoded = message_properties_to_string(&properties);
988        let decoded = string_to_message_properties(Some(&encoded));
989
990        assert_eq!(decoded, properties);
991    }
992
993    #[test]
994    fn string_and_str_property_decoders_share_behavior() {
995        let encoded =
996            CheetahString::from_static_str("valid\u{0001}value\u{0002}empty\u{0001}\u{0002}next\u{0001}two\u{0002}");
997
998        let from_cheetah = string_to_message_properties(Some(&encoded));
999        let from_str = str_to_message_properties(Some(encoded.as_str()));
1000
1001        assert_eq!(from_cheetah, from_str);
1002        assert_eq!(from_cheetah.len(), 2);
1003    }
1004
1005    #[test]
1006    fn str_to_message_properties_ignores_empty_and_malformed_segments() {
1007        let encoded = concat!(
1008            "valid\u{0001}value\u{0002}",
1009            "\u{0002}",
1010            "empty_value\u{0001}\u{0002}",
1011            "missing_separator\u{0002}",
1012            "second\u{0001}two\u{0002}"
1013        );
1014
1015        let decoded = str_to_message_properties(Some(encoded));
1016
1017        assert_eq!(decoded.len(), 2);
1018        assert_eq!(decoded.get("valid").map(CheetahString::as_str), Some("value"));
1019        assert_eq!(decoded.get("second").map(CheetahString::as_str), Some("two"));
1020    }
1021
1022    #[test]
1023    fn decode_messages_rejects_invalid_utf8_properties() {
1024        let mut bytes = BytesMut::new();
1025        let store_size = 4 // TOTALSIZE
1026            + 4 // MAGICCODE
1027            + 4 // BODYCRC
1028            + 4 // FLAG
1029            + 4 // BODY LENGTH
1030            + 2 // PROPERTIES LENGTH
1031            + 1; // INVALID PROPERTY BYTE
1032
1033        bytes.put_i32(store_size);
1034        bytes.put_i32(0);
1035        bytes.put_i32(0);
1036        bytes.put_i32(0);
1037        bytes.put_i32(0);
1038        bytes.put_i16(1);
1039        bytes.put_u8(0xff);
1040
1041        let mut bytes = bytes.freeze();
1042
1043        assert!(decode_messages(&mut bytes).is_empty());
1044    }
1045
1046    #[test]
1047    fn cheetah_from_utf8_lossy_borrows_valid_utf8() {
1048        let encoded = b"key\x01value\x02";
1049
1050        let decoded = cheetah_from_utf8_lossy(encoded);
1051
1052        assert_eq!(decoded, "key\u{0001}value\u{0002}");
1053    }
1054
1055    #[test]
1056    fn cheetah_from_utf8_lossy_preserves_lossy_invalid_utf8_behavior() {
1057        let encoded = b"key\x01\xff\x02";
1058
1059        let decoded = cheetah_from_utf8_lossy(encoded);
1060
1061        assert_eq!(decoded.as_str(), String::from_utf8_lossy(encoded).as_ref());
1062    }
1063
1064    #[test]
1065    fn count_inner_msg_num_counts_correctly_for_multiple_messages() {
1066        let mut bytes = BytesMut::new();
1067        bytes.put_i32(8);
1068        bytes.put_slice(&[0, 0, 0, 0]);
1069        bytes.put_i32(8);
1070        bytes.put_slice(&[0, 0, 0, 0]);
1071        assert_eq!(count_inner_msg_num(Some(bytes.freeze())), 2);
1072    }
1073
1074    #[test]
1075    fn count_inner_msg_num_counts_correctly_for_single_message() {
1076        let mut bytes = BytesMut::new();
1077        bytes.put_i32(8);
1078        bytes.put_slice(&[0, 0, 0, 0]);
1079        assert_eq!(count_inner_msg_num(Some(bytes.freeze())), 1);
1080    }
1081
1082    #[test]
1083    fn count_inner_msg_num_counts_zero_for_no_messages() {
1084        let bytes = BytesMut::new();
1085        assert_eq!(count_inner_msg_num(Some(bytes.freeze())), 0);
1086    }
1087
1088    #[test]
1089    fn count_inner_msg_num_ignores_incomplete_messages() {
1090        let mut bytes = BytesMut::new();
1091        bytes.put_i32(4);
1092        assert_eq!(count_inner_msg_num(Some(bytes.freeze())), 1);
1093    }
1094
1095    #[test]
1096    fn decode_message_id_ipv4() {
1097        let msg_id = "7F0000010007D8260BF075769D36C348";
1098        let message_id = decode_message_id(msg_id).unwrap();
1099        assert_eq!(message_id.address, "127.0.0.1:55334".parse().unwrap());
1100        assert_eq!(message_id.offset, 860316681131967304);
1101    }
1102
1103    #[test]
1104    fn encode_with_compression() {
1105        let mut message_ext = MessageExt::default();
1106        message_ext.set_body(Bytes::from("Hello, World!"));
1107        let result = encode(&message_ext, true);
1108        assert!(result.is_ok());
1109        let bytes = result.unwrap();
1110        assert!(!bytes.is_empty());
1111    }
1112
1113    #[test]
1114    fn encode_with_unknown_compression_type_returns_error() {
1115        let mut message_ext = MessageExt::default();
1116        message_ext.set_body(Bytes::from_static(b"Hello, World!"));
1117        message_ext.set_sys_flag(MessageSysFlag::COMPRESSED_FLAG | (0x7 << 8));
1118
1119        let result = encode(&message_ext, true);
1120
1121        assert!(result.is_err());
1122    }
1123
1124    #[test]
1125    fn decode_compressed_body_uses_sys_flag_compression_type() {
1126        let body = Bytes::from_static(b"Hello from compressed RocketMQ body");
1127
1128        for compression_flag in [
1129            MessageSysFlag::COMPRESSION_LZ4_TYPE,
1130            MessageSysFlag::COMPRESSION_ZSTD_TYPE,
1131            MessageSysFlag::COMPRESSION_ZLIB_TYPE,
1132        ] {
1133            let mut message_ext = MessageExt::default();
1134            message_ext.set_body(body.clone());
1135            message_ext.set_sys_flag(MessageSysFlag::COMPRESSED_FLAG | compression_flag);
1136            let mut encoded = encode(&message_ext, true).expect("compressed message should encode");
1137
1138            let decoded =
1139                decode(&mut encoded, true, true, false, false, false).expect("compressed message should decode");
1140
1141            assert_eq!(decoded.get_body(), Some(&body));
1142        }
1143    }
1144
1145    #[test]
1146    fn encode_without_compression() {
1147        let mut message_ext = MessageExt::default();
1148        message_ext.set_body(Bytes::from("Hello, World!"));
1149        let result = encode(&message_ext, false);
1150        assert!(result.is_ok());
1151        let bytes = result.unwrap();
1152        assert!(!bytes.is_empty());
1153    }
1154
1155    #[test]
1156    fn encode_with_empty_body() {
1157        let mut message_ext = MessageExt::default();
1158        message_ext.set_body(Bytes::new());
1159        let result = encode(&message_ext, false);
1160        assert!(result.is_ok());
1161        let bytes = result.unwrap();
1162        assert!(!bytes.is_empty());
1163    }
1164
1165    #[test]
1166    fn encode_with_large_body() {
1167        let mut message_ext = MessageExt::default();
1168        let large_body = vec![0u8; 1024 * 1024];
1169        message_ext.set_body(Bytes::from(large_body));
1170        let result = encode(&message_ext, false);
1171        assert!(result.is_ok());
1172        let bytes = result.unwrap();
1173        assert!(!bytes.is_empty());
1174    }
1175
1176    #[test]
1177    fn encode_uniquely_with_compression() {
1178        let mut message_ext = MessageExt::default();
1179        message_ext.set_body(Bytes::from("Hello, World!"));
1180        let result = encode_uniquely(&message_ext, true);
1181        assert!(result.is_ok());
1182        let bytes = result.unwrap();
1183        assert!(!bytes.is_empty());
1184    }
1185
1186    #[test]
1187    fn encode_uniquely_without_compression() {
1188        let mut message_ext = MessageExt::default();
1189        message_ext.set_body(Bytes::from("Hello, World!"));
1190        let result = encode_uniquely(&message_ext, false);
1191        assert!(result.is_ok());
1192        let bytes = result.unwrap();
1193        assert!(!bytes.is_empty());
1194    }
1195
1196    #[test]
1197    fn encode_uniquely_with_empty_body() {
1198        let mut message_ext = MessageExt::default();
1199        message_ext.set_body(Bytes::new());
1200        let result = encode_uniquely(&message_ext, false);
1201        assert!(result.is_ok());
1202        let bytes = result.unwrap();
1203        assert!(!bytes.is_empty());
1204    }
1205
1206    #[test]
1207    fn encode_uniquely_with_large_body() {
1208        let mut message_ext = MessageExt::default();
1209        let large_body = vec![0u8; 1024 * 1024];
1210        message_ext.set_body(Bytes::from(large_body));
1211        let result = encode_uniquely(&message_ext, false);
1212        assert!(result.is_ok());
1213        let bytes = result.unwrap();
1214        assert!(!bytes.is_empty());
1215    }
1216
1217    #[test]
1218    fn decode_properties_returns_none_if_bytes_length_is_insufficient() {
1219        let mut bytes = Bytes::from(vec![0; SYSFLAG_POSITION + 3]);
1220        assert!(decode_properties(&mut bytes).is_none());
1221    }
1222
1223    #[test]
1224    fn decode_properties_returns_none_if_magic_code_is_invalid() {
1225        let mut bytes = BytesMut::from_iter(vec![0u8; SYSFLAG_POSITION + 4 + MESSAGE_MAGIC_CODE_POSITION + 4]);
1226        BigEndian::write_i32(&mut bytes[SYSFLAG_POSITION..SYSFLAG_POSITION + 4], 0);
1227        BigEndian::write_i32(
1228            &mut bytes[MESSAGE_MAGIC_CODE_POSITION..MESSAGE_MAGIC_CODE_POSITION + 4],
1229            -1,
1230        );
1231        assert!(decode_properties(&mut bytes.freeze()).is_none());
1232    }
1233
1234    #[test]
1235    fn decode_properties_returns_none_if_body_size_is_insufficient() {
1236        let mut bytes = BytesMut::from_iter(vec![0u8; SYSFLAG_POSITION + 4 + MESSAGE_MAGIC_CODE_POSITION + 4 + 4]);
1237        BigEndian::write_i32(&mut bytes[SYSFLAG_POSITION..SYSFLAG_POSITION + 4], 0);
1238        BigEndian::write_i32(
1239            &mut bytes[MESSAGE_MAGIC_CODE_POSITION..MESSAGE_MAGIC_CODE_POSITION + 4],
1240            MESSAGE_MAGIC_CODE,
1241        );
1242        assert!(decode_properties(&mut bytes.freeze()).is_none());
1243    }
1244
1245    #[test]
1246    fn decode_properties_returns_none_if_topic_length_is_insufficient() {
1247        let mut bytes = BytesMut::from_iter(vec![
1248            0u8;
1249            SYSFLAG_POSITION + 4 + MESSAGE_MAGIC_CODE_POSITION + 4 + 4 + 4
1250        ]);
1251        BigEndian::write_i32(&mut bytes[SYSFLAG_POSITION..SYSFLAG_POSITION + 4], 0);
1252        BigEndian::write_i32(
1253            &mut bytes[MESSAGE_MAGIC_CODE_POSITION..MESSAGE_MAGIC_CODE_POSITION + 4],
1254            MESSAGE_MAGIC_CODE,
1255        );
1256        BigEndian::write_i32(
1257            &mut bytes[SYSFLAG_POSITION + 4 + MESSAGE_MAGIC_CODE_POSITION + 4
1258                ..SYSFLAG_POSITION + 4 + MESSAGE_MAGIC_CODE_POSITION + 4 + 4],
1259            0,
1260        );
1261        assert!(decode_properties(&mut bytes.freeze()).is_none());
1262    }
1263
1264    #[test]
1265    fn decode_properties_returns_none_if_properties_length_is_insufficient() {
1266        let mut bytes = BytesMut::from_iter(vec![
1267            0u8;
1268            SYSFLAG_POSITION + 4 + MESSAGE_MAGIC_CODE_POSITION + 4 + 4 + 4 + 2
1269        ]);
1270        BigEndian::write_i32(&mut bytes[SYSFLAG_POSITION..SYSFLAG_POSITION + 4], 0);
1271        BigEndian::write_i32(
1272            &mut bytes[MESSAGE_MAGIC_CODE_POSITION..MESSAGE_MAGIC_CODE_POSITION + 4],
1273            MESSAGE_MAGIC_CODE,
1274        );
1275        BigEndian::write_i32(
1276            &mut bytes[SYSFLAG_POSITION + 4 + MESSAGE_MAGIC_CODE_POSITION + 4
1277                ..SYSFLAG_POSITION + 4 + MESSAGE_MAGIC_CODE_POSITION + 4 + 4],
1278            0,
1279        );
1280        BigEndian::write_i16(
1281            &mut bytes[SYSFLAG_POSITION + 4 + MESSAGE_MAGIC_CODE_POSITION + 4 + 4 + 4
1282                ..SYSFLAG_POSITION + 4 + MESSAGE_MAGIC_CODE_POSITION + 4 + 4 + 4 + 2],
1283            1,
1284        );
1285        assert!(decode_properties(&mut bytes.freeze()).is_none());
1286    }
1287
1288    #[test]
1289    fn validate_message_id_ipv4_32_chars() {
1290        let result = validate_message_id("AC11000100002A9F0000000000000001");
1291        assert!(result.is_ok());
1292    }
1293
1294    #[test]
1295    fn validate_message_id_ipv6_56_chars() {
1296        let result = validate_message_id("20010db800000000000000000000000100002A9F0000000000000001");
1297        assert!(result.is_ok());
1298    }
1299
1300    #[test]
1301    fn validate_message_id_ipv6_40_chars_rejected() {
1302        let result = validate_message_id("20010db800000000000000000000000100000001");
1303        assert!(result.is_err());
1304        if let Err(e) = result {
1305            assert!(e.contains("Invalid message ID length"));
1306            assert!(e.contains("56 characters (IPv6)"));
1307        }
1308    }
1309
1310    #[test]
1311    fn decode_message_id_ipv6() {
1312        let msg_id = "20010db800000000000000000000000100002A9F0000000000000001";
1313        let message_id = decode_message_id(msg_id).unwrap();
1314        assert_eq!(message_id.address, "[2001:db8::1]:10911".parse().unwrap());
1315        assert_eq!(message_id.offset, 1);
1316    }
1317
1318    #[test]
1319    fn decode_message_id_ipv6_full_address() {
1320        let msg_id = "20010db81234567800000000abcdef0100002A9F0000000000001234";
1321        let message_id = decode_message_id(msg_id).unwrap();
1322        assert_eq!(
1323            message_id.address,
1324            "[2001:db8:1234:5678::abcd:ef01]:10911".parse().unwrap()
1325        );
1326        assert_eq!(message_id.offset, 4660);
1327    }
1328}