Skip to main content

stackforge_core/layer/mqttsn/
mod.rs

1//! MQTT-SN (MQTT for Sensor Networks) layer implementation.
2//!
3//! Implements MQTT-SN v1.2 packet parsing as a zero-copy view into a packet buffer.
4//!
5//! ## Header Format
6//!
7//! MQTT-SN uses a variable-length header:
8//!
9//! ```text
10//! Short form (length 2..=255):
11//!   Byte 0:    Length (1 byte)
12//!   Byte 1:    Message Type (1 byte)
13//!   Byte 2+:   Message body (variable)
14//!
15//! Extended form (length 256..=65535):
16//!   Byte 0:    0x01 (marker)
17//!   Byte 1-2:  Length (2 bytes, big-endian)
18//!   Byte 3:    Message Type (1 byte)
19//!   Byte 4+:   Message body (variable)
20//! ```
21//!
22//! ## Flags Byte (used in CONNECT, PUBLISH, SUBSCRIBE, etc.)
23//!
24//! ```text
25//! Bit:     7     6-5    4      3     2         1-0
26//! Field:   DUP   QoS    RETAIN WILL  CleanSess TID_TYPE
27//! ```
28//!
29//! ## Message Types
30//!
31//! | Value | Name            |
32//! |-------|-----------------|
33//! | 0x00  | ADVERTISE       |
34//! | 0x01  | SEARCHGW        |
35//! | 0x02  | GWINFO          |
36//! | 0x04  | CONNECT         |
37//! | 0x05  | CONNACK         |
38//! | 0x06  | WILLTOPICREQ    |
39//! | 0x07  | WILLTOPIC       |
40//! | 0x08  | WILLMSGREQ      |
41//! | 0x09  | WILLMSG         |
42//! | 0x0A  | REGISTER        |
43//! | 0x0B  | REGACK          |
44//! | 0x0C  | PUBLISH         |
45//! | 0x0D  | PUBACK          |
46//! | 0x0E  | PUBCOMP         |
47//! | 0x0F  | PUBREC          |
48//! | 0x10  | PUBREL          |
49//! | 0x12  | SUBSCRIBE       |
50//! | 0x13  | SUBACK          |
51//! | 0x14  | UNSUBSCRIBE     |
52//! | 0x15  | UNSUBACK        |
53//! | 0x16  | PINGREQ         |
54//! | 0x17  | PINGRESP        |
55//! | 0x18  | DISCONNECT      |
56//! | 0x1A  | WILLTOPICUPD    |
57//! | 0x1B  | WILLTOPICRESP   |
58//! | 0x1C  | WILLMSGUPD      |
59//! | 0x1D  | WILLMSGRESP     |
60//! | 0xFE  | ENCAPS_MSG      |
61
62pub mod builder;
63
64pub use builder::MqttSnBuilder;
65
66use crate::layer::field::{FieldError, FieldValue};
67use crate::layer::{Layer, LayerIndex, LayerKind};
68
69// ============================================================================
70// Constants
71// ============================================================================
72
73/// Minimum MQTT-SN header: 1 byte length + 1 byte type.
74pub const MQTTSN_MIN_HEADER_LEN: usize = 2;
75
76/// Default MQTT-SN UDP port.
77pub const MQTTSN_PORT: u16 = 1883;
78
79// Message type constants
80pub const ADVERTISE: u8 = 0x00;
81pub const SEARCHGW: u8 = 0x01;
82pub const GWINFO: u8 = 0x02;
83pub const CONNECT: u8 = 0x04;
84pub const CONNACK: u8 = 0x05;
85pub const WILLTOPICREQ: u8 = 0x06;
86pub const WILLTOPIC: u8 = 0x07;
87pub const WILLMSGREQ: u8 = 0x08;
88pub const WILLMSG: u8 = 0x09;
89pub const REGISTER: u8 = 0x0A;
90pub const REGACK: u8 = 0x0B;
91pub const PUBLISH: u8 = 0x0C;
92pub const PUBACK: u8 = 0x0D;
93pub const PUBCOMP: u8 = 0x0E;
94pub const PUBREC: u8 = 0x0F;
95pub const PUBREL: u8 = 0x10;
96pub const SUBSCRIBE: u8 = 0x12;
97pub const SUBACK: u8 = 0x13;
98pub const UNSUBSCRIBE: u8 = 0x14;
99pub const UNSUBACK: u8 = 0x15;
100pub const PINGREQ: u8 = 0x16;
101pub const PINGRESP: u8 = 0x17;
102pub const DISCONNECT: u8 = 0x18;
103pub const WILLTOPICUPD: u8 = 0x1A;
104pub const WILLTOPICRESP: u8 = 0x1B;
105pub const WILLMSGUPD: u8 = 0x1C;
106pub const WILLMSGRESP: u8 = 0x1D;
107pub const ENCAPS_MSG: u8 = 0xFE;
108
109// Return codes
110pub const RC_ACCEPTED: u8 = 0x00;
111pub const RC_REJ_CONGESTION: u8 = 0x01;
112pub const RC_REJ_INVALID_TID: u8 = 0x02;
113pub const RC_REJ_NOT_SUPPORTED: u8 = 0x03;
114
115// Topic ID types
116pub const TID_NORMAL: u8 = 0b00;
117pub const TID_PREDEF: u8 = 0b01;
118pub const TID_SHORT: u8 = 0b10;
119
120/// Field names for the MQTT-SN layer.
121pub static MQTTSN_FIELD_NAMES: &[&str] = &[
122    "length",
123    "type",
124    "flags",
125    "dup",
126    "qos",
127    "retain",
128    "will",
129    "cleansess",
130    "tid_type",
131    "gw_id",
132    "duration",
133    "radius",
134    "gw_addr",
135    "prot_id",
136    "client_id",
137    "return_code",
138    "tid",
139    "mid",
140    "data",
141    "topic_name",
142    "will_topic",
143    "will_msg",
144];
145
146// ============================================================================
147// Helper functions
148// ============================================================================
149
150/// Decode the MQTT-SN variable length field.
151///
152/// Returns `(header_size, packet_length)` where `header_size` is the number of
153/// bytes consumed by the length field itself (1 or 3 bytes).
154///
155/// - If byte 0 != 0x01: short form, length = byte 0, `header_size` = 1
156/// - If byte 0 == 0x01: extended form, length = u16(byte1, byte2), `header_size` = 3
157pub fn decode_mqttsn_length(buf: &[u8]) -> Result<(usize, u16), FieldError> {
158    if buf.is_empty() {
159        return Err(FieldError::BufferTooShort {
160            offset: 0,
161            need: 1,
162            have: 0,
163        });
164    }
165    if buf[0] == 0x01 {
166        // Extended length form
167        if buf.len() < 3 {
168            return Err(FieldError::BufferTooShort {
169                offset: 0,
170                need: 3,
171                have: buf.len(),
172            });
173        }
174        let len = u16::from_be_bytes([buf[1], buf[2]]);
175        Ok((3, len))
176    } else {
177        Ok((1, u16::from(buf[0])))
178    }
179}
180
181/// Check if a UDP payload looks like an MQTT-SN packet.
182///
183/// Validates that:
184/// 1. Buffer has at least 2 bytes
185/// 2. The length field is valid (>= 2, <= buffer length)
186/// 3. The message type is a known MQTT-SN type
187#[must_use]
188pub fn is_mqttsn_payload(buf: &[u8]) -> bool {
189    if buf.len() < MQTTSN_MIN_HEADER_LEN {
190        return false;
191    }
192    let (len_hdr_size, pkt_len) = match decode_mqttsn_length(buf) {
193        Ok(v) => v,
194        Err(_) => return false,
195    };
196    // Packet length must be at least header_size + 1 (for type byte)
197    if (pkt_len as usize) < len_hdr_size + 1 {
198        return false;
199    }
200    // Packet length should not exceed buffer
201    if (pkt_len as usize) > buf.len() {
202        return false;
203    }
204    let msg_type = buf[len_hdr_size];
205    is_known_message_type(msg_type)
206}
207
208/// Returns true if the given byte is a known MQTT-SN message type.
209fn is_known_message_type(t: u8) -> bool {
210    matches!(
211        t,
212        ADVERTISE
213            | SEARCHGW
214            | GWINFO
215            | CONNECT
216            | CONNACK
217            | WILLTOPICREQ
218            | WILLTOPIC
219            | WILLMSGREQ
220            | WILLMSG
221            | REGISTER
222            | REGACK
223            | PUBLISH
224            | PUBACK
225            | PUBCOMP
226            | PUBREC
227            | PUBREL
228            | SUBSCRIBE
229            | SUBACK
230            | UNSUBSCRIBE
231            | UNSUBACK
232            | PINGREQ
233            | PINGRESP
234            | DISCONNECT
235            | WILLTOPICUPD
236            | WILLTOPICRESP
237            | WILLMSGUPD
238            | WILLMSGRESP
239            | ENCAPS_MSG
240    )
241}
242
243/// Get the human-readable name for a message type.
244#[must_use]
245pub fn message_type_name(t: u8) -> &'static str {
246    match t {
247        ADVERTISE => "ADVERTISE",
248        SEARCHGW => "SEARCHGW",
249        GWINFO => "GWINFO",
250        CONNECT => "CONNECT",
251        CONNACK => "CONNACK",
252        WILLTOPICREQ => "WILLTOPICREQ",
253        WILLTOPIC => "WILLTOPIC",
254        WILLMSGREQ => "WILLMSGREQ",
255        WILLMSG => "WILLMSG",
256        REGISTER => "REGISTER",
257        REGACK => "REGACK",
258        PUBLISH => "PUBLISH",
259        PUBACK => "PUBACK",
260        PUBCOMP => "PUBCOMP",
261        PUBREC => "PUBREC",
262        PUBREL => "PUBREL",
263        SUBSCRIBE => "SUBSCRIBE",
264        SUBACK => "SUBACK",
265        UNSUBSCRIBE => "UNSUBSCRIBE",
266        UNSUBACK => "UNSUBACK",
267        PINGREQ => "PINGREQ",
268        PINGRESP => "PINGRESP",
269        DISCONNECT => "DISCONNECT",
270        WILLTOPICUPD => "WILLTOPICUPD",
271        WILLTOPICRESP => "WILLTOPICRESP",
272        WILLMSGUPD => "WILLMSGUPD",
273        WILLMSGRESP => "WILLMSGRESP",
274        ENCAPS_MSG => "ENCAPS_MSG",
275        _ => "UNKNOWN",
276    }
277}
278
279/// Get the human-readable name for a return code.
280#[must_use]
281pub fn return_code_name(rc: u8) -> &'static str {
282    match rc {
283        RC_ACCEPTED => "Accepted",
284        RC_REJ_CONGESTION => "Rejected: congestion",
285        RC_REJ_INVALID_TID => "Rejected: invalid topic ID",
286        RC_REJ_NOT_SUPPORTED => "Rejected: not supported",
287        _ => "Unknown",
288    }
289}
290
291// ============================================================================
292// MqttSnLayer
293// ============================================================================
294
295/// MQTT-SN layer -- a zero-copy view into a packet buffer.
296#[derive(Debug, Clone)]
297pub struct MqttSnLayer {
298    pub index: LayerIndex,
299}
300
301impl MqttSnLayer {
302    /// Create a new MQTT-SN layer from a layer index.
303    #[must_use]
304    pub fn new(index: LayerIndex) -> Self {
305        Self { index }
306    }
307
308    /// Get a slice of the buffer corresponding to this layer.
309    fn slice<'a>(&self, buf: &'a [u8]) -> &'a [u8] {
310        self.index.slice(buf)
311    }
312
313    /// Get the length header size (1 or 3 bytes) and the total packet length.
314    fn length_info(&self, buf: &[u8]) -> Result<(usize, u16), FieldError> {
315        let s = self.slice(buf);
316        decode_mqttsn_length(s)
317    }
318
319    /// Offset of the message type byte within the layer slice.
320    fn type_offset(&self, buf: &[u8]) -> Result<usize, FieldError> {
321        let (len_hdr_size, _) = self.length_info(buf)?;
322        Ok(len_hdr_size)
323    }
324
325    /// Offset of the body (after the type byte) within the layer slice.
326    fn body_offset(&self, buf: &[u8]) -> Result<usize, FieldError> {
327        Ok(self.type_offset(buf)? + 1)
328    }
329
330    // ========================================================================
331    // Core field accessors
332    // ========================================================================
333
334    /// Get the total packet length (as encoded in the length field).
335    pub fn packet_length(&self, buf: &[u8]) -> Result<u16, FieldError> {
336        let (_, len) = self.length_info(buf)?;
337        Ok(len)
338    }
339
340    /// Get the message type byte.
341    pub fn msg_type(&self, buf: &[u8]) -> Result<u8, FieldError> {
342        let s = self.slice(buf);
343        let off = self.type_offset(buf)?;
344        if s.len() <= off {
345            return Err(FieldError::BufferTooShort {
346                offset: self.index.start + off,
347                need: 1,
348                have: 0,
349            });
350        }
351        Ok(s[off])
352    }
353
354    // ========================================================================
355    // Flags byte accessors
356    // ========================================================================
357
358    /// Returns true if this message type has a flags byte.
359    fn has_flags(&self, buf: &[u8]) -> Result<bool, FieldError> {
360        let mt = self.msg_type(buf)?;
361        Ok(matches!(
362            mt,
363            CONNECT | WILLTOPIC | PUBLISH | SUBSCRIBE | SUBACK | UNSUBSCRIBE | WILLTOPICUPD
364        ))
365    }
366
367    /// Get the raw flags byte. Returns an error if this message type has no flags.
368    pub fn flags(&self, buf: &[u8]) -> Result<u8, FieldError> {
369        if !self.has_flags(buf)? {
370            return Err(FieldError::InvalidValue(format!(
371                "message type {} has no flags byte",
372                self.msg_type(buf)?
373            )));
374        }
375        let s = self.slice(buf);
376        let off = self.body_offset(buf)?;
377        if s.len() <= off {
378            return Err(FieldError::BufferTooShort {
379                offset: self.index.start + off,
380                need: 1,
381                have: 0,
382            });
383        }
384        Ok(s[off])
385    }
386
387    /// Get the DUP flag (bit 7 of flags byte).
388    pub fn dup(&self, buf: &[u8]) -> Result<bool, FieldError> {
389        Ok((self.flags(buf)? >> 7) & 1 == 1)
390    }
391
392    /// Get the `QoS` level (bits 6-5 of flags byte).
393    pub fn qos(&self, buf: &[u8]) -> Result<u8, FieldError> {
394        Ok((self.flags(buf)? >> 5) & 0x03)
395    }
396
397    /// Get the RETAIN flag (bit 4 of flags byte).
398    pub fn retain(&self, buf: &[u8]) -> Result<bool, FieldError> {
399        Ok((self.flags(buf)? >> 4) & 1 == 1)
400    }
401
402    /// Get the WILL flag (bit 3 of flags byte).
403    pub fn will(&self, buf: &[u8]) -> Result<bool, FieldError> {
404        Ok((self.flags(buf)? >> 3) & 1 == 1)
405    }
406
407    /// Get the `CleanSession` flag (bit 2 of flags byte).
408    pub fn cleansess(&self, buf: &[u8]) -> Result<bool, FieldError> {
409        Ok((self.flags(buf)? >> 2) & 1 == 1)
410    }
411
412    /// Get the topic ID type (bits 1-0 of flags byte).
413    pub fn tid_type(&self, buf: &[u8]) -> Result<u8, FieldError> {
414        Ok(self.flags(buf)? & 0x03)
415    }
416
417    // ========================================================================
418    // Message-specific field accessors
419    // ========================================================================
420
421    /// Get gateway ID (ADVERTISE, GWINFO).
422    pub fn gw_id(&self, buf: &[u8]) -> Result<u8, FieldError> {
423        let mt = self.msg_type(buf)?;
424        if mt != ADVERTISE && mt != GWINFO {
425            return Err(FieldError::InvalidValue(format!(
426                "gw_id not available for type {}",
427                message_type_name(mt)
428            )));
429        }
430        let s = self.slice(buf);
431        let off = self.body_offset(buf)?;
432        if s.len() <= off {
433            return Err(FieldError::BufferTooShort {
434                offset: self.index.start + off,
435                need: 1,
436                have: 0,
437            });
438        }
439        Ok(s[off])
440    }
441
442    /// Get duration field (ADVERTISE: offset body+1, CONNECT: offset `body+flags+prot_id`,
443    /// DISCONNECT: offset body+0 (optional)).
444    pub fn duration(&self, buf: &[u8]) -> Result<u16, FieldError> {
445        let mt = self.msg_type(buf)?;
446        let s = self.slice(buf);
447        let body = self.body_offset(buf)?;
448        let off = match mt {
449            ADVERTISE => body + 1,   // after gw_id
450            CONNECT => body + 1 + 1, // after flags + prot_id
451            DISCONNECT => body,      // optional, right at body start
452            _ => {
453                return Err(FieldError::InvalidValue(format!(
454                    "duration not available for type {}",
455                    message_type_name(mt)
456                )));
457            },
458        };
459        if s.len() < off + 2 {
460            return Err(FieldError::BufferTooShort {
461                offset: self.index.start + off,
462                need: 2,
463                have: s.len().saturating_sub(off),
464            });
465        }
466        Ok(u16::from_be_bytes([s[off], s[off + 1]]))
467    }
468
469    /// Get radius (SEARCHGW).
470    pub fn radius(&self, buf: &[u8]) -> Result<u8, FieldError> {
471        let mt = self.msg_type(buf)?;
472        if mt != SEARCHGW {
473            return Err(FieldError::InvalidValue(format!(
474                "radius not available for type {}",
475                message_type_name(mt)
476            )));
477        }
478        let s = self.slice(buf);
479        let off = self.body_offset(buf)?;
480        if s.len() <= off {
481            return Err(FieldError::BufferTooShort {
482                offset: self.index.start + off,
483                need: 1,
484                have: 0,
485            });
486        }
487        Ok(s[off])
488    }
489
490    /// Get gateway address bytes (GWINFO, after `gw_id`).
491    pub fn gw_addr<'a>(&self, buf: &'a [u8]) -> Result<&'a [u8], FieldError> {
492        let mt = self.msg_type(buf)?;
493        if mt != GWINFO {
494            return Err(FieldError::InvalidValue(format!(
495                "gw_addr not available for type {}",
496                message_type_name(mt)
497            )));
498        }
499        let s = self.slice(buf);
500        let (_, pkt_len) = self.length_info(buf)?;
501        let off = self.body_offset(buf)? + 1; // after gw_id
502        let end = (pkt_len as usize).min(s.len());
503        if off > end {
504            return Ok(&[]);
505        }
506        Ok(&s[off..end])
507    }
508
509    /// Get protocol ID (CONNECT, after flags byte).
510    pub fn prot_id(&self, buf: &[u8]) -> Result<u8, FieldError> {
511        let mt = self.msg_type(buf)?;
512        if mt != CONNECT {
513            return Err(FieldError::InvalidValue(format!(
514                "prot_id not available for type {}",
515                message_type_name(mt)
516            )));
517        }
518        let s = self.slice(buf);
519        let off = self.body_offset(buf)? + 1; // after flags
520        if s.len() <= off {
521            return Err(FieldError::BufferTooShort {
522                offset: self.index.start + off,
523                need: 1,
524                have: 0,
525            });
526        }
527        Ok(s[off])
528    }
529
530    /// Get client ID string (CONNECT, PINGREQ).
531    pub fn client_id<'a>(&self, buf: &'a [u8]) -> Result<&'a str, FieldError> {
532        let mt = self.msg_type(buf)?;
533        let s = self.slice(buf);
534        let (_, pkt_len) = self.length_info(buf)?;
535        let body = self.body_offset(buf)?;
536        let off = match mt {
537            CONNECT => body + 1 + 1 + 2, // after flags + prot_id + duration
538            PINGREQ => body,             // client_id is the entire body
539            _ => {
540                return Err(FieldError::InvalidValue(format!(
541                    "client_id not available for type {}",
542                    message_type_name(mt)
543                )));
544            },
545        };
546        let end = (pkt_len as usize).min(s.len());
547        if off > end {
548            return Ok("");
549        }
550        std::str::from_utf8(&s[off..end]).map_err(|e| FieldError::InvalidValue(e.to_string()))
551    }
552
553    /// Get return code (CONNACK, REGACK, PUBACK, SUBACK, WILLTOPICRESP, WILLMSGRESP).
554    pub fn return_code(&self, buf: &[u8]) -> Result<u8, FieldError> {
555        let mt = self.msg_type(buf)?;
556        let s = self.slice(buf);
557        let body = self.body_offset(buf)?;
558        let off = match mt {
559            CONNACK | WILLTOPICRESP | WILLMSGRESP => body,
560            REGACK | PUBACK => body + 4, // after tid(2) + mid(2)
561            SUBACK => body + 1 + 2 + 2,  // after flags(1) + tid(2) + mid(2)
562            _ => {
563                return Err(FieldError::InvalidValue(format!(
564                    "return_code not available for type {}",
565                    message_type_name(mt)
566                )));
567            },
568        };
569        if s.len() <= off {
570            return Err(FieldError::BufferTooShort {
571                offset: self.index.start + off,
572                need: 1,
573                have: 0,
574            });
575        }
576        Ok(s[off])
577    }
578
579    /// Get topic ID (REGISTER, REGACK, PUBLISH, PUBACK, SUBSCRIBE/UNSUBSCRIBE, SUBACK).
580    pub fn tid(&self, buf: &[u8]) -> Result<u16, FieldError> {
581        let mt = self.msg_type(buf)?;
582        let s = self.slice(buf);
583        let body = self.body_offset(buf)?;
584        let off = match mt {
585            REGISTER | REGACK | PUBACK => body,      // tid is first in body
586            PUBLISH => body + 1,                     // after flags
587            SUBSCRIBE | UNSUBSCRIBE => body + 1 + 2, // after flags + mid
588            SUBACK => body + 1,                      // after flags
589            _ => {
590                return Err(FieldError::InvalidValue(format!(
591                    "tid not available for type {}",
592                    message_type_name(mt)
593                )));
594            },
595        };
596        if s.len() < off + 2 {
597            return Err(FieldError::BufferTooShort {
598                offset: self.index.start + off,
599                need: 2,
600                have: s.len().saturating_sub(off),
601            });
602        }
603        Ok(u16::from_be_bytes([s[off], s[off + 1]]))
604    }
605
606    /// Get message ID (REGISTER, REGACK, PUBLISH, PUBACK, PUBCOMP, PUBREC, PUBREL,
607    /// SUBSCRIBE, SUBACK, UNSUBSCRIBE, UNSUBACK).
608    pub fn mid(&self, buf: &[u8]) -> Result<u16, FieldError> {
609        let mt = self.msg_type(buf)?;
610        let s = self.slice(buf);
611        let body = self.body_offset(buf)?;
612        let off = match mt {
613            REGISTER | REGACK | PUBACK => body + 2,       // after tid
614            PUBLISH => body + 1 + 2,                      // after flags + tid
615            PUBCOMP | PUBREC | PUBREL | UNSUBACK => body, // mid is first
616            SUBSCRIBE | UNSUBSCRIBE => body + 1,          // after flags
617            SUBACK => body + 1 + 2,                       // after flags + tid
618            _ => {
619                return Err(FieldError::InvalidValue(format!(
620                    "mid not available for type {}",
621                    message_type_name(mt)
622                )));
623            },
624        };
625        if s.len() < off + 2 {
626            return Err(FieldError::BufferTooShort {
627                offset: self.index.start + off,
628                need: 2,
629                have: s.len().saturating_sub(off),
630            });
631        }
632        Ok(u16::from_be_bytes([s[off], s[off + 1]]))
633    }
634
635    /// Get publish data payload (PUBLISH, after flags+tid+mid).
636    pub fn data<'a>(&self, buf: &'a [u8]) -> Result<&'a [u8], FieldError> {
637        let mt = self.msg_type(buf)?;
638        if mt != PUBLISH {
639            return Err(FieldError::InvalidValue(format!(
640                "data not available for type {}",
641                message_type_name(mt)
642            )));
643        }
644        let s = self.slice(buf);
645        let (_, pkt_len) = self.length_info(buf)?;
646        let off = self.body_offset(buf)? + 1 + 2 + 2; // flags + tid + mid
647        let end = (pkt_len as usize).min(s.len());
648        if off > end {
649            return Ok(&[]);
650        }
651        Ok(&s[off..end])
652    }
653
654    /// Get topic name (REGISTER, SUBSCRIBE/UNSUBSCRIBE with `TID_NORMAL`).
655    pub fn topic_name<'a>(&self, buf: &'a [u8]) -> Result<&'a str, FieldError> {
656        let mt = self.msg_type(buf)?;
657        let s = self.slice(buf);
658        let (_, pkt_len) = self.length_info(buf)?;
659        let body = self.body_offset(buf)?;
660        let off = match mt {
661            REGISTER => body + 2 + 2,                    // after tid + mid
662            SUBSCRIBE | UNSUBSCRIBE => body + 1 + 2 + 2, // after flags + mid + tid
663            _ => {
664                return Err(FieldError::InvalidValue(format!(
665                    "topic_name not available for type {}",
666                    message_type_name(mt)
667                )));
668            },
669        };
670        let end = (pkt_len as usize).min(s.len());
671        if off > end {
672            return Ok("");
673        }
674        std::str::from_utf8(&s[off..end]).map_err(|e| FieldError::InvalidValue(e.to_string()))
675    }
676
677    /// Get will topic (WILLTOPIC, WILLTOPICUPD).
678    pub fn will_topic<'a>(&self, buf: &'a [u8]) -> Result<&'a str, FieldError> {
679        let mt = self.msg_type(buf)?;
680        if mt != WILLTOPIC && mt != WILLTOPICUPD {
681            return Err(FieldError::InvalidValue(format!(
682                "will_topic not available for type {}",
683                message_type_name(mt)
684            )));
685        }
686        let s = self.slice(buf);
687        let (_, pkt_len) = self.length_info(buf)?;
688        let off = self.body_offset(buf)? + 1; // after flags
689        let end = (pkt_len as usize).min(s.len());
690        if off > end {
691            return Ok("");
692        }
693        std::str::from_utf8(&s[off..end]).map_err(|e| FieldError::InvalidValue(e.to_string()))
694    }
695
696    /// Get will message (WILLMSG, WILLMSGUPD).
697    pub fn will_msg<'a>(&self, buf: &'a [u8]) -> Result<&'a [u8], FieldError> {
698        let mt = self.msg_type(buf)?;
699        if mt != WILLMSG && mt != WILLMSGUPD {
700            return Err(FieldError::InvalidValue(format!(
701                "will_msg not available for type {}",
702                message_type_name(mt)
703            )));
704        }
705        let s = self.slice(buf);
706        let (_, pkt_len) = self.length_info(buf)?;
707        let off = self.body_offset(buf)?;
708        let end = (pkt_len as usize).min(s.len());
709        if off > end {
710            return Ok(&[]);
711        }
712        Ok(&s[off..end])
713    }
714
715    // ========================================================================
716    // Set field helpers
717    // ========================================================================
718
719    /// Set the message type byte in the buffer.
720    pub fn set_msg_type(&self, buf: &mut [u8], value: u8) -> Result<(), FieldError> {
721        let off = self.index.start + self.type_offset(buf)?;
722        if buf.len() <= off {
723            return Err(FieldError::BufferTooShort {
724                offset: off,
725                need: 1,
726                have: 0,
727            });
728        }
729        buf[off] = value;
730        Ok(())
731    }
732
733    /// Set the flags byte in the buffer.
734    pub fn set_flags(&self, buf: &mut [u8], value: u8) -> Result<(), FieldError> {
735        if !self.has_flags(buf)? {
736            return Err(FieldError::InvalidValue(
737                "this message type has no flags byte".into(),
738            ));
739        }
740        let off = self.index.start + self.body_offset(buf)?;
741        if buf.len() <= off {
742            return Err(FieldError::BufferTooShort {
743                offset: off,
744                need: 1,
745                have: 0,
746            });
747        }
748        buf[off] = value;
749        Ok(())
750    }
751
752    // ========================================================================
753    // Compute header length
754    // ========================================================================
755
756    /// Compute the header length for this MQTT-SN packet.
757    fn compute_header_len(&self, buf: &[u8]) -> usize {
758        let s = self.slice(buf);
759        match decode_mqttsn_length(s) {
760            Ok((_, pkt_len)) => (pkt_len as usize).min(s.len()),
761            Err(_) => s.len(),
762        }
763    }
764
765    // ========================================================================
766    // Summary
767    // ========================================================================
768
769    /// Generate a one-line summary of this MQTT-SN layer.
770    fn make_summary(&self, buf: &[u8]) -> String {
771        let mt = self.msg_type(buf).unwrap_or(0xFF);
772        let name = message_type_name(mt);
773        match mt {
774            PUBLISH => {
775                let tid = self.tid(buf).map_or_else(|_| "?".into(), |v| v.to_string());
776                format!("MQTT-SN {name} tid={tid}")
777            },
778            CONNECT => {
779                let cid = self.client_id(buf).unwrap_or("?");
780                format!("MQTT-SN {name} client_id={cid}")
781            },
782            CONNACK | WILLTOPICRESP | WILLMSGRESP => {
783                let rc = self.return_code(buf).unwrap_or(0xFF);
784                format!("MQTT-SN {} rc={} ({})", name, rc, return_code_name(rc))
785            },
786            _ => format!("MQTT-SN {name}"),
787        }
788    }
789
790    // ========================================================================
791    // Field access API
792    // ========================================================================
793
794    /// Get the field names for this layer.
795    #[must_use]
796    pub fn field_names() -> &'static [&'static str] {
797        MQTTSN_FIELD_NAMES
798    }
799
800    /// Get a field value by name.
801    pub fn get_field(&self, buf: &[u8], name: &str) -> Option<Result<FieldValue, FieldError>> {
802        match name {
803            "length" => Some(self.packet_length(buf).map(FieldValue::U16)),
804            "type" => Some(self.msg_type(buf).map(FieldValue::U8)),
805            "flags" => Some(self.flags(buf).map(FieldValue::U8)),
806            "dup" => Some(self.dup(buf).map(FieldValue::Bool)),
807            "qos" => Some(self.qos(buf).map(FieldValue::U8)),
808            "retain" => Some(self.retain(buf).map(FieldValue::Bool)),
809            "will" => Some(self.will(buf).map(FieldValue::Bool)),
810            "cleansess" => Some(self.cleansess(buf).map(FieldValue::Bool)),
811            "tid_type" => Some(self.tid_type(buf).map(FieldValue::U8)),
812            "gw_id" => Some(self.gw_id(buf).map(FieldValue::U8)),
813            "duration" => Some(self.duration(buf).map(FieldValue::U16)),
814            "radius" => Some(self.radius(buf).map(FieldValue::U8)),
815            "prot_id" => Some(self.prot_id(buf).map(FieldValue::U8)),
816            "return_code" => Some(self.return_code(buf).map(FieldValue::U8)),
817            "tid" => Some(self.tid(buf).map(FieldValue::U16)),
818            "mid" => Some(self.mid(buf).map(FieldValue::U16)),
819            "client_id" => Some(self.client_id(buf).map(|s| FieldValue::Str(s.to_string()))),
820            "topic_name" => Some(self.topic_name(buf).map(|s| FieldValue::Str(s.to_string()))),
821            "will_topic" => Some(self.will_topic(buf).map(|s| FieldValue::Str(s.to_string()))),
822            "data" => Some(self.data(buf).map(|d| FieldValue::Bytes(d.to_vec()))),
823            "will_msg" => Some(self.will_msg(buf).map(|d| FieldValue::Bytes(d.to_vec()))),
824            "gw_addr" => Some(self.gw_addr(buf).map(|d| FieldValue::Bytes(d.to_vec()))),
825            _ => None,
826        }
827    }
828
829    /// Set a field value by name.
830    pub fn set_field(
831        &self,
832        buf: &mut [u8],
833        name: &str,
834        value: FieldValue,
835    ) -> Option<Result<(), FieldError>> {
836        match name {
837            "type" => {
838                if let FieldValue::U8(v) = value {
839                    Some(self.set_msg_type(buf, v))
840                } else {
841                    Some(Err(FieldError::InvalidValue(format!(
842                        "type: expected U8, got {value:?}"
843                    ))))
844                }
845            },
846            "flags" => {
847                if let FieldValue::U8(v) = value {
848                    Some(self.set_flags(buf, v))
849                } else {
850                    Some(Err(FieldError::InvalidValue(format!(
851                        "flags: expected U8, got {value:?}"
852                    ))))
853                }
854            },
855            _ => None,
856        }
857    }
858}
859
860impl Layer for MqttSnLayer {
861    fn kind(&self) -> LayerKind {
862        LayerKind::MqttSn
863    }
864
865    fn summary(&self, data: &[u8]) -> String {
866        self.make_summary(data)
867    }
868
869    fn header_len(&self, data: &[u8]) -> usize {
870        self.compute_header_len(data)
871    }
872
873    fn field_names(&self) -> &'static [&'static str] {
874        MQTTSN_FIELD_NAMES
875    }
876}
877
878// ============================================================================
879// Tests
880// ============================================================================
881
882#[cfg(test)]
883mod tests {
884    use super::*;
885
886    /// Helper: build a short-form MQTT-SN packet from raw body bytes.
887    fn make_packet(msg_type: u8, body: &[u8]) -> Vec<u8> {
888        let total = 2 + body.len(); // 1 byte length + 1 byte type + body
889        assert!(total <= 255, "use make_extended_packet for >255 bytes");
890        let mut buf = Vec::with_capacity(total);
891        buf.push(total as u8);
892        buf.push(msg_type);
893        buf.extend_from_slice(body);
894        buf
895    }
896
897    /// Helper: create MqttSnLayer from buffer covering entire buffer.
898    fn layer_from(buf: &[u8]) -> MqttSnLayer {
899        MqttSnLayer::new(LayerIndex::new(LayerKind::MqttSn, 0, buf.len()))
900    }
901
902    #[test]
903    fn test_decode_length_short() {
904        let buf = [0x05, 0x04]; // length=5
905        let (hdr_size, len) = decode_mqttsn_length(&buf).unwrap();
906        assert_eq!(hdr_size, 1);
907        assert_eq!(len, 5);
908    }
909
910    #[test]
911    fn test_decode_length_extended() {
912        let buf = [0x01, 0x01, 0x00]; // length=256
913        let (hdr_size, len) = decode_mqttsn_length(&buf).unwrap();
914        assert_eq!(hdr_size, 3);
915        assert_eq!(len, 256);
916    }
917
918    #[test]
919    fn test_decode_length_empty() {
920        let buf: [u8; 0] = [];
921        assert!(decode_mqttsn_length(&buf).is_err());
922    }
923
924    #[test]
925    fn test_decode_length_extended_too_short() {
926        let buf = [0x01, 0x01]; // missing second length byte
927        assert!(decode_mqttsn_length(&buf).is_err());
928    }
929
930    #[test]
931    fn test_is_mqttsn_payload_valid_searchgw() {
932        let pkt = make_packet(SEARCHGW, &[0x00]);
933        assert!(is_mqttsn_payload(&pkt));
934    }
935
936    #[test]
937    fn test_is_mqttsn_payload_too_short() {
938        let buf = [0x01];
939        assert!(!is_mqttsn_payload(&buf));
940    }
941
942    #[test]
943    fn test_is_mqttsn_payload_unknown_type() {
944        let buf = [0x02, 0xFF]; // length=2, type=0xFF (unknown)
945        assert!(!is_mqttsn_payload(&buf));
946    }
947
948    #[test]
949    fn test_message_type_name_values() {
950        assert_eq!(message_type_name(CONNECT), "CONNECT");
951        assert_eq!(message_type_name(PUBLISH), "PUBLISH");
952        assert_eq!(message_type_name(PINGREQ), "PINGREQ");
953        assert_eq!(message_type_name(0xFF), "UNKNOWN");
954    }
955
956    #[test]
957    fn test_return_code_name_values() {
958        assert_eq!(return_code_name(RC_ACCEPTED), "Accepted");
959        assert_eq!(return_code_name(RC_REJ_CONGESTION), "Rejected: congestion");
960        assert_eq!(return_code_name(0xFF), "Unknown");
961    }
962
963    #[test]
964    fn test_advertise() {
965        let pkt = make_packet(ADVERTISE, &[0x01, 0x00, 0x3C]);
966        let l = layer_from(&pkt);
967        assert_eq!(l.msg_type(&pkt).unwrap(), ADVERTISE);
968        assert_eq!(l.gw_id(&pkt).unwrap(), 1);
969        assert_eq!(l.duration(&pkt).unwrap(), 60);
970        assert_eq!(l.packet_length(&pkt).unwrap(), 5);
971    }
972
973    #[test]
974    fn test_searchgw() {
975        let pkt = make_packet(SEARCHGW, &[0x03]);
976        let l = layer_from(&pkt);
977        assert_eq!(l.msg_type(&pkt).unwrap(), SEARCHGW);
978        assert_eq!(l.radius(&pkt).unwrap(), 3);
979    }
980
981    #[test]
982    fn test_gwinfo() {
983        let pkt = make_packet(GWINFO, &[0x01, 0xC0, 0xA8, 0x01, 0x01]);
984        let l = layer_from(&pkt);
985        assert_eq!(l.msg_type(&pkt).unwrap(), GWINFO);
986        assert_eq!(l.gw_id(&pkt).unwrap(), 1);
987        assert_eq!(l.gw_addr(&pkt).unwrap(), &[0xC0, 0xA8, 0x01, 0x01]);
988    }
989
990    #[test]
991    fn test_connect() {
992        let pkt = make_packet(CONNECT, &[0x0C, 0x01, 0x00, 0x3C, b't', b'e', b's', b't']);
993        let l = layer_from(&pkt);
994        assert_eq!(l.msg_type(&pkt).unwrap(), CONNECT);
995        assert_eq!(l.flags(&pkt).unwrap(), 0x0C);
996        assert!(l.will(&pkt).unwrap());
997        assert!(l.cleansess(&pkt).unwrap());
998        assert!(!l.dup(&pkt).unwrap());
999        assert_eq!(l.qos(&pkt).unwrap(), 0);
1000        assert!(!l.retain(&pkt).unwrap());
1001        assert_eq!(l.tid_type(&pkt).unwrap(), 0);
1002        assert_eq!(l.prot_id(&pkt).unwrap(), 1);
1003        assert_eq!(l.duration(&pkt).unwrap(), 60);
1004        assert_eq!(l.client_id(&pkt).unwrap(), "test");
1005    }
1006
1007    #[test]
1008    fn test_connack() {
1009        let pkt = make_packet(CONNACK, &[RC_ACCEPTED]);
1010        let l = layer_from(&pkt);
1011        assert_eq!(l.msg_type(&pkt).unwrap(), CONNACK);
1012        assert_eq!(l.return_code(&pkt).unwrap(), RC_ACCEPTED);
1013    }
1014
1015    #[test]
1016    fn test_register() {
1017        let pkt = make_packet(REGISTER, &[0x00, 0x01, 0x00, 0x02, b'a', b'/', b'b']);
1018        let l = layer_from(&pkt);
1019        assert_eq!(l.msg_type(&pkt).unwrap(), REGISTER);
1020        assert_eq!(l.tid(&pkt).unwrap(), 1);
1021        assert_eq!(l.mid(&pkt).unwrap(), 2);
1022        assert_eq!(l.topic_name(&pkt).unwrap(), "a/b");
1023    }
1024
1025    #[test]
1026    fn test_regack() {
1027        let pkt = make_packet(REGACK, &[0x00, 0x05, 0x00, 0x03, RC_ACCEPTED]);
1028        let l = layer_from(&pkt);
1029        assert_eq!(l.msg_type(&pkt).unwrap(), REGACK);
1030        assert_eq!(l.tid(&pkt).unwrap(), 5);
1031        assert_eq!(l.mid(&pkt).unwrap(), 3);
1032        assert_eq!(l.return_code(&pkt).unwrap(), RC_ACCEPTED);
1033    }
1034
1035    #[test]
1036    fn test_publish() {
1037        let pkt = make_packet(PUBLISH, &[0x20, 0x00, 0x01, 0x00, 0x02, b'h', b'i']);
1038        let l = layer_from(&pkt);
1039        assert_eq!(l.msg_type(&pkt).unwrap(), PUBLISH);
1040        assert_eq!(l.qos(&pkt).unwrap(), 1);
1041        assert_eq!(l.tid(&pkt).unwrap(), 1);
1042        assert_eq!(l.mid(&pkt).unwrap(), 2);
1043        assert_eq!(l.data(&pkt).unwrap(), b"hi");
1044    }
1045
1046    #[test]
1047    fn test_puback() {
1048        let pkt = make_packet(PUBACK, &[0x00, 0x01, 0x00, 0x02, RC_ACCEPTED]);
1049        let l = layer_from(&pkt);
1050        assert_eq!(l.msg_type(&pkt).unwrap(), PUBACK);
1051        assert_eq!(l.tid(&pkt).unwrap(), 1);
1052        assert_eq!(l.mid(&pkt).unwrap(), 2);
1053        assert_eq!(l.return_code(&pkt).unwrap(), RC_ACCEPTED);
1054    }
1055
1056    #[test]
1057    fn test_pubcomp() {
1058        let pkt = make_packet(PUBCOMP, &[0x00, 0x07]);
1059        let l = layer_from(&pkt);
1060        assert_eq!(l.msg_type(&pkt).unwrap(), PUBCOMP);
1061        assert_eq!(l.mid(&pkt).unwrap(), 7);
1062    }
1063
1064    #[test]
1065    fn test_suback() {
1066        let pkt = make_packet(SUBACK, &[0x00, 0x00, 0x01, 0x00, 0x02, RC_ACCEPTED]);
1067        let l = layer_from(&pkt);
1068        assert_eq!(l.msg_type(&pkt).unwrap(), SUBACK);
1069        assert_eq!(l.tid(&pkt).unwrap(), 1);
1070        assert_eq!(l.mid(&pkt).unwrap(), 2);
1071        assert_eq!(l.return_code(&pkt).unwrap(), RC_ACCEPTED);
1072    }
1073
1074    #[test]
1075    fn test_unsuback() {
1076        let pkt = make_packet(UNSUBACK, &[0x00, 0x05]);
1077        let l = layer_from(&pkt);
1078        assert_eq!(l.msg_type(&pkt).unwrap(), UNSUBACK);
1079        assert_eq!(l.mid(&pkt).unwrap(), 5);
1080    }
1081
1082    #[test]
1083    fn test_pingreq_empty() {
1084        let pkt = make_packet(PINGREQ, &[]);
1085        let l = layer_from(&pkt);
1086        assert_eq!(l.msg_type(&pkt).unwrap(), PINGREQ);
1087        assert_eq!(l.client_id(&pkt).unwrap(), "");
1088    }
1089
1090    #[test]
1091    fn test_pingreq_with_client_id() {
1092        let pkt = make_packet(PINGREQ, b"sensor1");
1093        let l = layer_from(&pkt);
1094        assert_eq!(l.msg_type(&pkt).unwrap(), PINGREQ);
1095        assert_eq!(l.client_id(&pkt).unwrap(), "sensor1");
1096    }
1097
1098    #[test]
1099    fn test_pingresp() {
1100        let pkt = make_packet(PINGRESP, &[]);
1101        let l = layer_from(&pkt);
1102        assert_eq!(l.msg_type(&pkt).unwrap(), PINGRESP);
1103    }
1104
1105    #[test]
1106    fn test_disconnect_empty() {
1107        let pkt = make_packet(DISCONNECT, &[]);
1108        let l = layer_from(&pkt);
1109        assert_eq!(l.msg_type(&pkt).unwrap(), DISCONNECT);
1110    }
1111
1112    #[test]
1113    fn test_disconnect_with_duration() {
1114        let pkt = make_packet(DISCONNECT, &[0x00, 0x3C]);
1115        let l = layer_from(&pkt);
1116        assert_eq!(l.msg_type(&pkt).unwrap(), DISCONNECT);
1117        assert_eq!(l.duration(&pkt).unwrap(), 60);
1118    }
1119
1120    #[test]
1121    fn test_willtopic() {
1122        let pkt = make_packet(WILLTOPIC, &[0x00, b'w', b'/', b't']);
1123        let l = layer_from(&pkt);
1124        assert_eq!(l.msg_type(&pkt).unwrap(), WILLTOPIC);
1125        assert_eq!(l.will_topic(&pkt).unwrap(), "w/t");
1126    }
1127
1128    #[test]
1129    fn test_willmsg() {
1130        let pkt = make_packet(WILLMSG, &[b'b', b'y', b'e']);
1131        let l = layer_from(&pkt);
1132        assert_eq!(l.msg_type(&pkt).unwrap(), WILLMSG);
1133        assert_eq!(l.will_msg(&pkt).unwrap(), b"bye");
1134    }
1135
1136    #[test]
1137    fn test_willtopicresp() {
1138        let pkt = make_packet(WILLTOPICRESP, &[RC_ACCEPTED]);
1139        let l = layer_from(&pkt);
1140        assert_eq!(l.msg_type(&pkt).unwrap(), WILLTOPICRESP);
1141        assert_eq!(l.return_code(&pkt).unwrap(), RC_ACCEPTED);
1142    }
1143
1144    #[test]
1145    fn test_willmsgresp() {
1146        let pkt = make_packet(WILLMSGRESP, &[RC_REJ_NOT_SUPPORTED]);
1147        let l = layer_from(&pkt);
1148        assert_eq!(l.msg_type(&pkt).unwrap(), WILLMSGRESP);
1149        assert_eq!(l.return_code(&pkt).unwrap(), RC_REJ_NOT_SUPPORTED);
1150    }
1151
1152    #[test]
1153    fn test_flags_all_bits() {
1154        let pkt = make_packet(CONNECT, &[0xFF, 0x01, 0x00, 0x3C]);
1155        let l = layer_from(&pkt);
1156        assert!(l.dup(&pkt).unwrap());
1157        assert_eq!(l.qos(&pkt).unwrap(), 3);
1158        assert!(l.retain(&pkt).unwrap());
1159        assert!(l.will(&pkt).unwrap());
1160        assert!(l.cleansess(&pkt).unwrap());
1161        assert_eq!(l.tid_type(&pkt).unwrap(), 0x03);
1162    }
1163
1164    #[test]
1165    fn test_get_field_type() {
1166        let pkt = make_packet(CONNECT, &[0x04, 0x01, 0x00, 0x3C, b'x']);
1167        let l = layer_from(&pkt);
1168        match l.get_field(&pkt, "type") {
1169            Some(Ok(FieldValue::U8(v))) => assert_eq!(v, CONNECT),
1170            other => panic!("expected Some(Ok(U8(CONNECT))), got {:?}", other),
1171        }
1172    }
1173
1174    #[test]
1175    fn test_get_field_unknown() {
1176        let pkt = make_packet(PINGRESP, &[]);
1177        let l = layer_from(&pkt);
1178        assert!(l.get_field(&pkt, "nonexistent").is_none());
1179    }
1180
1181    #[test]
1182    fn test_set_msg_type() {
1183        let mut pkt = make_packet(PINGREQ, &[]);
1184        let l = layer_from(&pkt);
1185        l.set_msg_type(&mut pkt, PINGRESP).unwrap();
1186        assert_eq!(l.msg_type(&pkt).unwrap(), PINGRESP);
1187    }
1188
1189    #[test]
1190    fn test_set_flags() {
1191        let mut pkt = make_packet(CONNECT, &[0x00, 0x01, 0x00, 0x3C]);
1192        let l = layer_from(&pkt);
1193        l.set_flags(&mut pkt, 0x0C).unwrap();
1194        assert!(l.will(&pkt).unwrap());
1195        assert!(l.cleansess(&pkt).unwrap());
1196    }
1197
1198    #[test]
1199    fn test_set_flags_no_flags_msg() {
1200        let mut pkt = make_packet(PINGRESP, &[]);
1201        let l = layer_from(&pkt);
1202        assert!(l.set_flags(&mut pkt, 0x00).is_err());
1203    }
1204
1205    #[test]
1206    fn test_summary_publish() {
1207        let pkt = make_packet(PUBLISH, &[0x00, 0x00, 0x05, 0x00, 0x01, b'x']);
1208        let l = layer_from(&pkt);
1209        let s = l.make_summary(&pkt);
1210        assert!(s.contains("PUBLISH"));
1211        assert!(s.contains("tid=5"));
1212    }
1213
1214    #[test]
1215    fn test_summary_connect() {
1216        let pkt = make_packet(CONNECT, &[0x04, 0x01, 0x00, 0x3C, b's', b'1']);
1217        let l = layer_from(&pkt);
1218        let s = l.make_summary(&pkt);
1219        assert!(s.contains("CONNECT"));
1220        assert!(s.contains("client_id=s1"));
1221    }
1222
1223    #[test]
1224    fn test_summary_connack() {
1225        let pkt = make_packet(CONNACK, &[RC_ACCEPTED]);
1226        let l = layer_from(&pkt);
1227        let s = l.make_summary(&pkt);
1228        assert!(s.contains("CONNACK"));
1229        assert!(s.contains("Accepted"));
1230    }
1231
1232    #[test]
1233    fn test_layer_trait_kind() {
1234        let pkt = make_packet(PINGRESP, &[]);
1235        let l = layer_from(&pkt);
1236        assert_eq!(l.kind(), LayerKind::MqttSn);
1237    }
1238
1239    #[test]
1240    fn test_layer_trait_header_len() {
1241        let pkt = make_packet(SEARCHGW, &[0x05]);
1242        let l = layer_from(&pkt);
1243        assert_eq!(Layer::header_len(&l, &pkt), 3);
1244    }
1245
1246    #[test]
1247    fn test_layer_trait_field_names() {
1248        let pkt = make_packet(PINGRESP, &[]);
1249        let l = layer_from(&pkt);
1250        let names = Layer::field_names(&l);
1251        assert!(names.contains(&"type"));
1252        assert!(names.contains(&"length"));
1253        assert!(names.contains(&"flags"));
1254    }
1255
1256    #[test]
1257    fn test_extended_length_packet() {
1258        let mut buf = vec![0u8; 260];
1259        buf[0] = 0x01;
1260        buf[1] = 0x01;
1261        buf[2] = 0x04; // length = 260
1262        buf[3] = PUBLISH;
1263        buf[4] = 0x00; // flags
1264        let l = layer_from(&buf);
1265        assert_eq!(l.packet_length(&buf).unwrap(), 260);
1266        assert_eq!(l.msg_type(&buf).unwrap(), PUBLISH);
1267    }
1268
1269    #[test]
1270    fn test_roundtrip_builder_parse() {
1271        let built = MqttSnBuilder::connect()
1272            .cleansess(true)
1273            .prot_id(1)
1274            .duration(60)
1275            .client_id(b"test")
1276            .build();
1277        let l = layer_from(&built);
1278        assert_eq!(l.msg_type(&built).unwrap(), CONNECT);
1279        assert!(l.cleansess(&built).unwrap());
1280        assert_eq!(l.prot_id(&built).unwrap(), 1);
1281        assert_eq!(l.duration(&built).unwrap(), 60);
1282        assert_eq!(l.client_id(&built).unwrap(), "test");
1283    }
1284}