Skip to main content

stackforge_core/layer/mqttsn/
builder.rs

1//! MQTT-SN packet builder.
2//!
3//! Provides a fluent API for constructing MQTT-SN v1.2 packets.
4//!
5//! # Examples
6//!
7//! ```rust
8//! use stackforge_core::layer::mqttsn::builder::MqttSnBuilder;
9//!
10//! // Simple PINGRESP
11//! let pkt = MqttSnBuilder::pingresp().build();
12//! assert_eq!(pkt, b"\x02\x17");
13//!
14//! // CONNECT with client ID
15//! let pkt = MqttSnBuilder::connect()
16//!     .cleansess(true)
17//!     .duration(30)
18//!     .client_id(b"test")
19//!     .build();
20//! assert_eq!(&pkt[0..2], &[0x0a, 0x04]); // length=10, type=CONNECT
21//! ```
22
23use super::{
24    ADVERTISE, CONNACK, CONNECT, DISCONNECT, GWINFO, PINGREQ, PINGRESP, PUBACK, PUBCOMP, PUBLISH,
25    PUBREC, PUBREL, RC_ACCEPTED, REGACK, REGISTER, SEARCHGW, SUBACK, SUBSCRIBE, TID_PREDEF,
26    TID_SHORT, UNSUBACK, UNSUBSCRIBE, WILLMSG, WILLMSGREQ, WILLMSGRESP, WILLMSGUPD, WILLTOPIC,
27    WILLTOPICREQ, WILLTOPICRESP, WILLTOPICUPD,
28};
29
30/// Builder for MQTT-SN packets.
31///
32/// The builder produces a complete MQTT-SN message with the appropriate
33/// length prefix (1-byte or 3-byte extended).
34#[derive(Debug, Clone)]
35pub struct MqttSnBuilder {
36    /// Message type byte
37    msg_type: u8,
38
39    // ----- Flags byte fields -----
40    dup: bool,
41    qos: u8,
42    retain: bool,
43    will: bool,
44    cleansess: bool,
45    tid_type: u8,
46
47    // ----- Message-specific fields -----
48    gw_id: u8,
49    duration: u16,
50    radius: u8,
51    gw_addr: Vec<u8>,
52    prot_id: u8,
53    client_id: Vec<u8>,
54    return_code: u8,
55    tid: u16,
56    mid: u16,
57    data: Vec<u8>,
58    topic_name: Vec<u8>,
59    will_topic: Vec<u8>,
60    will_msg: Vec<u8>,
61}
62
63impl Default for MqttSnBuilder {
64    fn default() -> Self {
65        Self {
66            msg_type: CONNECT,
67            dup: false,
68            qos: 0,
69            retain: false,
70            will: false,
71            cleansess: false,
72            tid_type: 0,
73            gw_id: 0,
74            duration: 0,
75            radius: 0,
76            gw_addr: Vec::new(),
77            prot_id: 0x01,
78            client_id: Vec::new(),
79            return_code: RC_ACCEPTED,
80            tid: 0,
81            mid: 0,
82            data: Vec::new(),
83            topic_name: Vec::new(),
84            will_topic: Vec::new(),
85            will_msg: Vec::new(),
86        }
87    }
88}
89
90impl MqttSnBuilder {
91    /// Create a new builder with CONNECT defaults.
92    #[must_use]
93    pub fn new() -> Self {
94        Self::default()
95    }
96
97    // ========================================================================
98    // Convenience constructors
99    // ========================================================================
100
101    /// ADVERTISE message builder.
102    #[must_use]
103    pub fn advertise() -> Self {
104        Self {
105            msg_type: ADVERTISE,
106            ..Default::default()
107        }
108    }
109
110    /// SEARCHGW message builder.
111    #[must_use]
112    pub fn searchgw() -> Self {
113        Self {
114            msg_type: SEARCHGW,
115            ..Default::default()
116        }
117    }
118
119    /// GWINFO message builder.
120    #[must_use]
121    pub fn gwinfo() -> Self {
122        Self {
123            msg_type: GWINFO,
124            ..Default::default()
125        }
126    }
127
128    /// CONNECT message builder.
129    #[must_use]
130    pub fn connect() -> Self {
131        Self {
132            msg_type: CONNECT,
133            ..Default::default()
134        }
135    }
136
137    /// CONNACK message builder.
138    #[must_use]
139    pub fn connack() -> Self {
140        Self {
141            msg_type: CONNACK,
142            ..Default::default()
143        }
144    }
145
146    /// WILLTOPICREQ message builder.
147    #[must_use]
148    pub fn willtopicreq() -> Self {
149        Self {
150            msg_type: WILLTOPICREQ,
151            ..Default::default()
152        }
153    }
154
155    /// WILLTOPIC message builder.
156    #[must_use]
157    pub fn willtopic() -> Self {
158        Self {
159            msg_type: WILLTOPIC,
160            ..Default::default()
161        }
162    }
163
164    /// WILLMSGREQ message builder.
165    #[must_use]
166    pub fn willmsgreq() -> Self {
167        Self {
168            msg_type: WILLMSGREQ,
169            ..Default::default()
170        }
171    }
172
173    /// WILLMSG message builder.
174    #[must_use]
175    pub fn willmsg() -> Self {
176        Self {
177            msg_type: WILLMSG,
178            ..Default::default()
179        }
180    }
181
182    /// REGISTER message builder.
183    #[must_use]
184    pub fn register() -> Self {
185        Self {
186            msg_type: REGISTER,
187            ..Default::default()
188        }
189    }
190
191    /// REGACK message builder.
192    #[must_use]
193    pub fn regack() -> Self {
194        Self {
195            msg_type: REGACK,
196            ..Default::default()
197        }
198    }
199
200    /// PUBLISH message builder.
201    #[must_use]
202    pub fn publish() -> Self {
203        Self {
204            msg_type: PUBLISH,
205            ..Default::default()
206        }
207    }
208
209    /// PUBACK message builder.
210    #[must_use]
211    pub fn puback() -> Self {
212        Self {
213            msg_type: PUBACK,
214            ..Default::default()
215        }
216    }
217
218    /// PUBCOMP message builder.
219    #[must_use]
220    pub fn pubcomp() -> Self {
221        Self {
222            msg_type: PUBCOMP,
223            ..Default::default()
224        }
225    }
226
227    /// PUBREC message builder.
228    #[must_use]
229    pub fn pubrec() -> Self {
230        Self {
231            msg_type: PUBREC,
232            ..Default::default()
233        }
234    }
235
236    /// PUBREL message builder.
237    #[must_use]
238    pub fn pubrel() -> Self {
239        Self {
240            msg_type: PUBREL,
241            ..Default::default()
242        }
243    }
244
245    /// SUBSCRIBE message builder.
246    #[must_use]
247    pub fn subscribe() -> Self {
248        Self {
249            msg_type: SUBSCRIBE,
250            ..Default::default()
251        }
252    }
253
254    /// SUBACK message builder.
255    #[must_use]
256    pub fn suback() -> Self {
257        Self {
258            msg_type: SUBACK,
259            ..Default::default()
260        }
261    }
262
263    /// UNSUBSCRIBE message builder.
264    #[must_use]
265    pub fn unsubscribe() -> Self {
266        Self {
267            msg_type: UNSUBSCRIBE,
268            ..Default::default()
269        }
270    }
271
272    /// UNSUBACK message builder.
273    #[must_use]
274    pub fn unsuback() -> Self {
275        Self {
276            msg_type: UNSUBACK,
277            ..Default::default()
278        }
279    }
280
281    /// PINGREQ message builder.
282    #[must_use]
283    pub fn pingreq() -> Self {
284        Self {
285            msg_type: PINGREQ,
286            ..Default::default()
287        }
288    }
289
290    /// PINGRESP message builder.
291    #[must_use]
292    pub fn pingresp() -> Self {
293        Self {
294            msg_type: PINGRESP,
295            ..Default::default()
296        }
297    }
298
299    /// DISCONNECT message builder.
300    #[must_use]
301    pub fn disconnect() -> Self {
302        Self {
303            msg_type: DISCONNECT,
304            ..Default::default()
305        }
306    }
307
308    /// WILLTOPICUPD message builder.
309    #[must_use]
310    pub fn willtopicupd() -> Self {
311        Self {
312            msg_type: WILLTOPICUPD,
313            ..Default::default()
314        }
315    }
316
317    /// WILLTOPICRESP message builder.
318    #[must_use]
319    pub fn willtopicresp() -> Self {
320        Self {
321            msg_type: WILLTOPICRESP,
322            ..Default::default()
323        }
324    }
325
326    /// WILLMSGUPD message builder.
327    #[must_use]
328    pub fn willmsgupd() -> Self {
329        Self {
330            msg_type: WILLMSGUPD,
331            ..Default::default()
332        }
333    }
334
335    /// WILLMSGRESP message builder.
336    #[must_use]
337    pub fn willmsgresp() -> Self {
338        Self {
339            msg_type: WILLMSGRESP,
340            ..Default::default()
341        }
342    }
343
344    // ========================================================================
345    // Fluent setters
346    // ========================================================================
347
348    /// Set the message type byte directly.
349    #[must_use]
350    pub fn msg_type(mut self, v: u8) -> Self {
351        self.msg_type = v;
352        self
353    }
354
355    /// Set the DUP flag.
356    #[must_use]
357    pub fn dup(mut self, v: bool) -> Self {
358        self.dup = v;
359        self
360    }
361
362    /// Set the `QoS` level (0-3).
363    #[must_use]
364    pub fn qos(mut self, v: u8) -> Self {
365        self.qos = v & 0x03;
366        self
367    }
368
369    /// Set the Retain flag.
370    #[must_use]
371    pub fn retain(mut self, v: bool) -> Self {
372        self.retain = v;
373        self
374    }
375
376    /// Set the Will flag.
377    #[must_use]
378    pub fn will(mut self, v: bool) -> Self {
379        self.will = v;
380        self
381    }
382
383    /// Set the `CleanSession` flag.
384    #[must_use]
385    pub fn cleansess(mut self, v: bool) -> Self {
386        self.cleansess = v;
387        self
388    }
389
390    /// Set the `TopicIdType` (0-3).
391    #[must_use]
392    pub fn tid_type(mut self, v: u8) -> Self {
393        self.tid_type = v & 0x03;
394        self
395    }
396
397    /// Set the Gateway ID.
398    #[must_use]
399    pub fn gw_id(mut self, v: u8) -> Self {
400        self.gw_id = v;
401        self
402    }
403
404    /// Set the Duration.
405    #[must_use]
406    pub fn duration(mut self, v: u16) -> Self {
407        self.duration = v;
408        self
409    }
410
411    /// Set the Radius.
412    #[must_use]
413    pub fn radius(mut self, v: u8) -> Self {
414        self.radius = v;
415        self
416    }
417
418    /// Set the Gateway Address.
419    #[must_use]
420    pub fn gw_addr(mut self, v: &[u8]) -> Self {
421        self.gw_addr = v.to_vec();
422        self
423    }
424
425    /// Set the Protocol ID.
426    #[must_use]
427    pub fn prot_id(mut self, v: u8) -> Self {
428        self.prot_id = v;
429        self
430    }
431
432    /// Set the Client ID (byte slice).
433    #[must_use]
434    pub fn client_id(mut self, v: &[u8]) -> Self {
435        self.client_id = v.to_vec();
436        self
437    }
438
439    /// Set the Client ID from a string.
440    #[must_use]
441    pub fn client_id_str(mut self, v: &str) -> Self {
442        self.client_id = v.as_bytes().to_vec();
443        self
444    }
445
446    /// Set the Return Code.
447    #[must_use]
448    pub fn return_code(mut self, v: u8) -> Self {
449        self.return_code = v;
450        self
451    }
452
453    /// Set the Topic ID.
454    #[must_use]
455    pub fn tid(mut self, v: u16) -> Self {
456        self.tid = v;
457        self
458    }
459
460    /// Set the Message ID.
461    #[must_use]
462    pub fn mid(mut self, v: u16) -> Self {
463        self.mid = v;
464        self
465    }
466
467    /// Set the Data / payload bytes.
468    #[must_use]
469    pub fn data(mut self, v: &[u8]) -> Self {
470        self.data = v.to_vec();
471        self
472    }
473
474    /// Set the Topic Name.
475    #[must_use]
476    pub fn topic_name(mut self, v: &[u8]) -> Self {
477        self.topic_name = v.to_vec();
478        self
479    }
480
481    /// Set the Topic Name from a string.
482    #[must_use]
483    pub fn topic_name_str(mut self, v: &str) -> Self {
484        self.topic_name = v.as_bytes().to_vec();
485        self
486    }
487
488    /// Set the Will Topic.
489    #[must_use]
490    pub fn will_topic_bytes(mut self, v: &[u8]) -> Self {
491        self.will_topic = v.to_vec();
492        self
493    }
494
495    /// Set the Will Topic from a string.
496    #[must_use]
497    pub fn will_topic_str(mut self, v: &str) -> Self {
498        self.will_topic = v.as_bytes().to_vec();
499        self
500    }
501
502    /// Set the Will Message.
503    #[must_use]
504    pub fn will_msg_bytes(mut self, v: &[u8]) -> Self {
505        self.will_msg = v.to_vec();
506        self
507    }
508
509    // ========================================================================
510    // Build helpers
511    // ========================================================================
512
513    /// Build the flags byte from the individual flag fields.
514    fn build_flags(&self) -> u8 {
515        let mut flags: u8 = 0;
516        if self.dup {
517            flags |= 1 << 7;
518        }
519        flags |= (self.qos & 0x03) << 5;
520        if self.retain {
521            flags |= 1 << 4;
522        }
523        if self.will {
524            flags |= 1 << 3;
525        }
526        if self.cleansess {
527            flags |= 1 << 2;
528        }
529        flags |= self.tid_type & 0x03;
530        flags
531    }
532
533    /// Build the body bytes (everything after the length + `msg_type` header).
534    fn build_body(&self) -> Vec<u8> {
535        let mut body = Vec::new();
536
537        match self.msg_type {
538            ADVERTISE => {
539                body.push(self.gw_id);
540                body.extend_from_slice(&self.duration.to_be_bytes());
541            },
542            SEARCHGW => {
543                body.push(self.radius);
544            },
545            GWINFO => {
546                body.push(self.gw_id);
547                body.extend_from_slice(&self.gw_addr);
548            },
549            CONNECT => {
550                body.push(self.build_flags());
551                body.push(self.prot_id);
552                body.extend_from_slice(&self.duration.to_be_bytes());
553                body.extend_from_slice(&self.client_id);
554            },
555            CONNACK => {
556                body.push(self.return_code);
557            },
558            WILLTOPICREQ | WILLMSGREQ => {
559                // Empty body -- only length + msg_type
560            },
561            WILLTOPIC | WILLTOPICUPD => {
562                body.push(self.build_flags());
563                body.extend_from_slice(&self.will_topic);
564            },
565            WILLMSG | WILLMSGUPD => {
566                body.extend_from_slice(&self.will_msg);
567            },
568            REGISTER => {
569                body.extend_from_slice(&self.tid.to_be_bytes());
570                body.extend_from_slice(&self.mid.to_be_bytes());
571                body.extend_from_slice(&self.topic_name);
572            },
573            REGACK => {
574                body.extend_from_slice(&self.tid.to_be_bytes());
575                body.extend_from_slice(&self.mid.to_be_bytes());
576                body.push(self.return_code);
577            },
578            PUBLISH => {
579                body.push(self.build_flags());
580                body.extend_from_slice(&self.tid.to_be_bytes());
581                body.extend_from_slice(&self.mid.to_be_bytes());
582                body.extend_from_slice(&self.data);
583            },
584            PUBACK => {
585                body.extend_from_slice(&self.tid.to_be_bytes());
586                body.extend_from_slice(&self.mid.to_be_bytes());
587                body.push(self.return_code);
588            },
589            PUBCOMP | PUBREC | PUBREL => {
590                body.extend_from_slice(&self.mid.to_be_bytes());
591            },
592            SUBSCRIBE => {
593                body.push(self.build_flags());
594                body.extend_from_slice(&self.mid.to_be_bytes());
595                // topic: if tid_type is TID_SHORT (2), topic is 2-byte short topic
596                // if tid_type is TID_PREDEF (1), topic is 2-byte predefined ID
597                // else: UTF-8 topic name
598                match self.tid_type {
599                    TID_PREDEF => body.extend_from_slice(&self.tid.to_be_bytes()),
600                    TID_SHORT => body.extend_from_slice(&self.tid.to_be_bytes()),
601                    _ => body.extend_from_slice(&self.topic_name),
602                }
603            },
604            SUBACK => {
605                body.push(self.build_flags());
606                body.extend_from_slice(&self.tid.to_be_bytes());
607                body.extend_from_slice(&self.mid.to_be_bytes());
608                body.push(self.return_code);
609            },
610            UNSUBSCRIBE => {
611                body.push(self.build_flags());
612                body.extend_from_slice(&self.mid.to_be_bytes());
613                match self.tid_type {
614                    TID_PREDEF => body.extend_from_slice(&self.tid.to_be_bytes()),
615                    TID_SHORT => body.extend_from_slice(&self.tid.to_be_bytes()),
616                    _ => body.extend_from_slice(&self.topic_name),
617                }
618            },
619            UNSUBACK => {
620                body.extend_from_slice(&self.mid.to_be_bytes());
621            },
622            PINGREQ => {
623                if !self.client_id.is_empty() {
624                    body.extend_from_slice(&self.client_id);
625                }
626            },
627            PINGRESP => {
628                // Empty body
629            },
630            DISCONNECT => {
631                if self.duration > 0 {
632                    body.extend_from_slice(&self.duration.to_be_bytes());
633                }
634            },
635            WILLTOPICRESP | WILLMSGRESP => {
636                body.push(self.return_code);
637            },
638            _ => {},
639        }
640
641        body
642    }
643
644    // ========================================================================
645    // Build
646    // ========================================================================
647
648    /// Serialize the MQTT-SN message into bytes.
649    ///
650    /// Automatically selects 1-byte or 3-byte extended length encoding:
651    /// - If total length < 256 and >= 2: single byte
652    /// - If total length >= 256: 0x01 prefix + 2-byte BE u16
653    #[must_use]
654    pub fn build(&self) -> Vec<u8> {
655        let body = self.build_body();
656        // Total = length_header + msg_type(1) + body
657        // Try 1-byte length first: total = 1 + 1 + body.len()
658        let total_short = 2 + body.len();
659
660        if total_short < 256 {
661            let mut pkt = Vec::with_capacity(total_short);
662            pkt.push(total_short as u8);
663            pkt.push(self.msg_type);
664            pkt.extend_from_slice(&body);
665            pkt
666        } else {
667            // Extended: 0x01 + 2-byte BE u16 total + msg_type + body
668            let total_ext = 3 + 1 + body.len();
669            let mut pkt = Vec::with_capacity(total_ext);
670            pkt.push(0x01);
671            pkt.extend_from_slice(&(total_ext as u16).to_be_bytes());
672            pkt.push(self.msg_type);
673            pkt.extend_from_slice(&body);
674            pkt
675        }
676    }
677
678    /// Serialize into an existing buffer. Returns the number of bytes written.
679    pub fn build_into(&self, buf: &mut Vec<u8>) -> usize {
680        let pkt = self.build();
681        let len = pkt.len();
682        buf.extend_from_slice(&pkt);
683        len
684    }
685}
686
687#[cfg(test)]
688mod tests {
689    use super::*;
690    use crate::layer::{LayerIndex, LayerKind};
691
692    fn make_layer(buf: &[u8]) -> MqttSnLayer {
693        let idx = LayerIndex::new(LayerKind::MqttSn, 0, buf.len());
694        MqttSnLayer::new(idx)
695    }
696
697    // ========================================================================
698    // Basic build tests
699    // ========================================================================
700
701    #[test]
702    fn test_build_advertise() {
703        let pkt = MqttSnBuilder::advertise()
704            .gw_id(0x98)
705            .duration(0x2b9a)
706            .build();
707        assert_eq!(pkt, b"\x05\x00\x98\x2b\x9a");
708    }
709
710    #[test]
711    fn test_build_searchgw() {
712        let pkt = MqttSnBuilder::searchgw().radius(0xcc).build();
713        assert_eq!(pkt, b"\x03\x01\xcc");
714    }
715
716    #[test]
717    fn test_build_gwinfo() {
718        let pkt = MqttSnBuilder::gwinfo()
719            .gw_id(0x42)
720            .gw_addr(&[192, 168, 1, 1])
721            .build();
722        assert_eq!(pkt.len(), 7); // 1 + 1 + 1 + 4
723        assert_eq!(pkt[0], 7);
724        assert_eq!(pkt[1], GWINFO);
725        assert_eq!(pkt[2], 0x42);
726        assert_eq!(&pkt[3..], &[192, 168, 1, 1]);
727    }
728
729    #[test]
730    fn test_build_connect() {
731        let pkt = MqttSnBuilder::connect()
732            .cleansess(true)
733            .prot_id(0x1a)
734            .duration(0x775b)
735            .client_id(b"test")
736            .build();
737        assert_eq!(pkt, b"\x0a\x04\x04\x1a\x77\x5b\x74\x65\x73\x74");
738    }
739
740    #[test]
741    fn test_build_connack() {
742        let pkt = MqttSnBuilder::connack()
743            .return_code(RC_REJ_INVALID_TID)
744            .build();
745        assert_eq!(pkt, b"\x03\x05\x02");
746    }
747
748    #[test]
749    fn test_build_publish() {
750        let pkt = MqttSnBuilder::publish()
751            .qos(2)
752            .tid(0x197f)
753            .mid(0x6a26)
754            .data(b"test")
755            .build();
756        assert_eq!(
757            pkt,
758            &[
759                0x0b, 0x0c, 0x40, 0x19, 0x7f, 0x6a, 0x26, b't', b'e', b's', b't'
760            ]
761        );
762    }
763
764    #[test]
765    fn test_build_puback() {
766        let pkt = MqttSnBuilder::puback()
767            .tid(0x0001)
768            .mid(0x0002)
769            .return_code(RC_ACCEPTED)
770            .build();
771        assert_eq!(pkt.len(), 7);
772        assert_eq!(pkt[0], 7);
773        assert_eq!(pkt[1], PUBACK);
774        assert_eq!(&pkt[2..4], &[0x00, 0x01]); // tid
775        assert_eq!(&pkt[4..6], &[0x00, 0x02]); // mid
776        assert_eq!(pkt[6], RC_ACCEPTED);
777    }
778
779    #[test]
780    fn test_build_pubcomp() {
781        let pkt = MqttSnBuilder::pubcomp().mid(0x1234).build();
782        assert_eq!(pkt.len(), 4);
783        assert_eq!(pkt[0], 4);
784        assert_eq!(pkt[1], PUBCOMP);
785        assert_eq!(&pkt[2..4], &[0x12, 0x34]);
786    }
787
788    #[test]
789    fn test_build_register() {
790        let pkt = MqttSnBuilder::register()
791            .tid(0x0001)
792            .mid(0x0002)
793            .topic_name(b"test/topic")
794            .build();
795        assert_eq!(pkt.len(), 16); // 1 + 1 + 2 + 2 + 10
796        assert_eq!(pkt[0], 16);
797        assert_eq!(pkt[1], REGISTER);
798        assert_eq!(&pkt[2..4], &[0x00, 0x01]); // tid
799        assert_eq!(&pkt[4..6], &[0x00, 0x02]); // mid
800        assert_eq!(&pkt[6..], b"test/topic");
801    }
802
803    #[test]
804    fn test_build_regack() {
805        let pkt = MqttSnBuilder::regack()
806            .tid(0x0010)
807            .mid(0x0020)
808            .return_code(RC_ACCEPTED)
809            .build();
810        assert_eq!(pkt.len(), 7);
811        assert_eq!(pkt[0], 7);
812        assert_eq!(pkt[1], REGACK);
813        assert_eq!(&pkt[2..4], &[0x00, 0x10]); // tid
814        assert_eq!(&pkt[4..6], &[0x00, 0x20]); // mid
815        assert_eq!(pkt[6], RC_ACCEPTED);
816    }
817
818    #[test]
819    fn test_build_subscribe_normal_topic() {
820        let pkt = MqttSnBuilder::subscribe()
821            .qos(1)
822            .tid_type(TID_NORMAL)
823            .mid(0x0001)
824            .topic_name(b"sensors/temp")
825            .build();
826        assert_eq!(pkt[1], SUBSCRIBE);
827        let flags = pkt[2];
828        assert_eq!((flags >> 5) & 0x03, 1); // QoS=1
829        assert_eq!(flags & 0x03, TID_NORMAL);
830        assert_eq!(&pkt[3..5], &[0x00, 0x01]); // mid
831        assert_eq!(&pkt[5..], b"sensors/temp");
832    }
833
834    #[test]
835    fn test_build_subscribe_predef_tid() {
836        let pkt = MqttSnBuilder::subscribe()
837            .qos(1)
838            .tid_type(TID_PREDEF)
839            .mid(0x0001)
840            .tid(0x0042)
841            .build();
842        assert_eq!(pkt[1], SUBSCRIBE);
843        let flags = pkt[2];
844        assert_eq!(flags & 0x03, TID_PREDEF);
845        assert_eq!(&pkt[5..7], &[0x00, 0x42]); // predefined tid
846    }
847
848    #[test]
849    fn test_build_subscribe_short_topic() {
850        // Short topic is encoded as a 2-byte value in the tid field
851        let short = u16::from_be_bytes([b'a', b'b']);
852        let pkt = MqttSnBuilder::subscribe()
853            .qos(0)
854            .tid_type(TID_SHORT)
855            .mid(0x0005)
856            .tid(short)
857            .build();
858        assert_eq!(pkt[1], SUBSCRIBE);
859        let flags = pkt[2];
860        assert_eq!(flags & 0x03, TID_SHORT);
861        assert_eq!(&pkt[5..7], &[b'a', b'b']);
862    }
863
864    #[test]
865    fn test_build_suback() {
866        let pkt = MqttSnBuilder::suback()
867            .qos(1)
868            .tid(0x0001)
869            .mid(0x0002)
870            .return_code(RC_ACCEPTED)
871            .build();
872        assert_eq!(pkt.len(), 8);
873        assert_eq!(pkt[0], 8);
874        assert_eq!(pkt[1], SUBACK);
875        // flags, tid(2), mid(2), rc
876        let flags = pkt[2];
877        assert_eq!((flags >> 5) & 0x03, 1);
878        assert_eq!(&pkt[3..5], &[0x00, 0x01]); // tid
879        assert_eq!(&pkt[5..7], &[0x00, 0x02]); // mid
880        assert_eq!(pkt[7], RC_ACCEPTED);
881    }
882
883    #[test]
884    fn test_build_unsubscribe_normal() {
885        let pkt = MqttSnBuilder::unsubscribe()
886            .tid_type(TID_NORMAL)
887            .mid(0x0003)
888            .topic_name(b"test/t")
889            .build();
890        assert_eq!(pkt[1], UNSUBSCRIBE);
891        let flags = pkt[2];
892        assert_eq!(flags & 0x03, TID_NORMAL);
893        assert_eq!(&pkt[3..5], &[0x00, 0x03]); // mid
894        assert_eq!(&pkt[5..], b"test/t");
895    }
896
897    #[test]
898    fn test_build_unsuback() {
899        let pkt = MqttSnBuilder::unsuback().mid(0x0003).build();
900        assert_eq!(pkt.len(), 4);
901        assert_eq!(pkt[0], 4);
902        assert_eq!(pkt[1], UNSUBACK);
903        assert_eq!(&pkt[2..4], &[0x00, 0x03]);
904    }
905
906    #[test]
907    fn test_build_pingreq_with_client_id() {
908        let pkt = MqttSnBuilder::pingreq().client_id(b"sensor1").build();
909        assert_eq!(pkt[0] as usize, pkt.len());
910        assert_eq!(pkt[1], PINGREQ);
911        assert_eq!(&pkt[2..], b"sensor1");
912    }
913
914    #[test]
915    fn test_build_pingresp() {
916        let pkt = MqttSnBuilder::pingresp().build();
917        assert_eq!(pkt, b"\x02\x17");
918    }
919
920    #[test]
921    fn test_build_disconnect_no_duration() {
922        let pkt = MqttSnBuilder::disconnect().build();
923        assert_eq!(pkt, b"\x02\x18");
924    }
925
926    #[test]
927    fn test_build_disconnect_with_duration() {
928        let pkt = MqttSnBuilder::disconnect().duration(0x0312).build();
929        assert_eq!(pkt, b"\x04\x18\x03\x12");
930    }
931
932    #[test]
933    fn test_build_willtopic() {
934        let pkt = MqttSnBuilder::willtopic()
935            .qos(1)
936            .retain(true)
937            .will_topic_str("lastwill")
938            .build();
939        assert_eq!(pkt[1], WILLTOPIC);
940        let flags = pkt[2];
941        assert_eq!((flags >> 5) & 0x03, 1); // QoS=1
942        assert_eq!((flags >> 4) & 1, 1); // Retain=true
943        assert_eq!(&pkt[3..], b"lastwill");
944    }
945
946    #[test]
947    fn test_build_willmsg() {
948        let pkt = MqttSnBuilder::willmsg().will_msg_bytes(b"goodbye").build();
949        assert_eq!(pkt[1], WILLMSG);
950        assert_eq!(&pkt[2..], b"goodbye");
951    }
952
953    #[test]
954    fn test_build_willtopicresp() {
955        let pkt = MqttSnBuilder::willtopicresp()
956            .return_code(RC_ACCEPTED)
957            .build();
958        assert_eq!(pkt.len(), 3);
959        assert_eq!(pkt[0], 3);
960        assert_eq!(pkt[1], WILLTOPICRESP);
961        assert_eq!(pkt[2], RC_ACCEPTED);
962    }
963
964    #[test]
965    fn test_build_willmsgresp() {
966        let pkt = MqttSnBuilder::willmsgresp()
967            .return_code(RC_REJ_CONGESTION)
968            .build();
969        assert_eq!(pkt.len(), 3);
970        assert_eq!(pkt[0], 3);
971        assert_eq!(pkt[1], WILLMSGRESP);
972        assert_eq!(pkt[2], RC_REJ_CONGESTION);
973    }
974
975    // ========================================================================
976    // Round-trip tests (build then parse)
977    // ========================================================================
978
979    #[test]
980    fn test_roundtrip_connect() {
981        let pkt = MqttSnBuilder::connect()
982            .cleansess(true)
983            .will(true)
984            .prot_id(0x01)
985            .duration(60)
986            .client_id(b"mydevice")
987            .build();
988        let layer = make_layer(&pkt);
989        assert_eq!(layer.msg_type(&pkt).unwrap(), CONNECT);
990        assert_eq!(layer.cleansess(&pkt).unwrap(), true);
991        assert_eq!(layer.will(&pkt).unwrap(), true);
992        assert_eq!(layer.prot_id(&pkt).unwrap(), 0x01);
993        assert_eq!(layer.duration(&pkt).unwrap(), 60);
994        assert_eq!(layer.client_id(&pkt).unwrap(), "mydevice");
995    }
996
997    #[test]
998    fn test_roundtrip_publish() {
999        let pkt = MqttSnBuilder::publish()
1000            .dup(true)
1001            .qos(1)
1002            .retain(true)
1003            .tid(0xBEEF)
1004            .mid(0xCAFE)
1005            .data(b"hello world")
1006            .build();
1007        let layer = make_layer(&pkt);
1008        assert_eq!(layer.msg_type(&pkt).unwrap(), PUBLISH);
1009        assert_eq!(layer.dup(&pkt).unwrap(), true);
1010        assert_eq!(layer.qos(&pkt).unwrap(), 1);
1011        assert_eq!(layer.retain(&pkt).unwrap(), true);
1012        assert_eq!(layer.tid(&pkt).unwrap(), 0xBEEF);
1013        assert_eq!(layer.mid(&pkt).unwrap(), 0xCAFE);
1014        assert_eq!(layer.data(&pkt).unwrap(), b"hello world");
1015    }
1016
1017    #[test]
1018    fn test_roundtrip_register() {
1019        let pkt = MqttSnBuilder::register()
1020            .tid(0x0042)
1021            .mid(0x0001)
1022            .topic_name(b"sensors/temp")
1023            .build();
1024        let layer = make_layer(&pkt);
1025        assert_eq!(layer.msg_type(&pkt).unwrap(), REGISTER);
1026        assert_eq!(layer.tid(&pkt).unwrap(), 0x0042);
1027        assert_eq!(layer.mid(&pkt).unwrap(), 0x0001);
1028        assert_eq!(layer.topic_name(&pkt).unwrap(), "sensors/temp");
1029    }
1030
1031    #[test]
1032    fn test_roundtrip_suback() {
1033        let pkt = MqttSnBuilder::suback()
1034            .qos(2)
1035            .tid(0x0010)
1036            .mid(0x0020)
1037            .return_code(RC_ACCEPTED)
1038            .build();
1039        let layer = make_layer(&pkt);
1040        assert_eq!(layer.msg_type(&pkt).unwrap(), SUBACK);
1041        assert_eq!(layer.qos(&pkt).unwrap(), 2);
1042        assert_eq!(layer.tid(&pkt).unwrap(), 0x0010);
1043        assert_eq!(layer.mid(&pkt).unwrap(), 0x0020);
1044        assert_eq!(layer.return_code(&pkt).unwrap(), RC_ACCEPTED);
1045    }
1046
1047    #[test]
1048    fn test_extended_length_builder() {
1049        // Build a message with a large payload that requires extended length
1050        let big_data = vec![0xAA; 300];
1051        let pkt = MqttSnBuilder::publish()
1052            .qos(0)
1053            .tid(0x0001)
1054            .mid(0x0001)
1055            .data(&big_data)
1056            .build();
1057        // Should use extended length encoding (0x01 prefix)
1058        assert_eq!(pkt[0], 0x01);
1059        let total_len = u16::from_be_bytes([pkt[1], pkt[2]]);
1060        assert_eq!(total_len as usize, pkt.len());
1061        assert_eq!(pkt[3], PUBLISH);
1062        let layer = make_layer(&pkt);
1063        assert_eq!(layer.msg_type(&pkt).unwrap(), PUBLISH);
1064        assert_eq!(layer.data(&pkt).unwrap().len(), 300);
1065    }
1066
1067    #[test]
1068    fn test_flags_all_set() {
1069        let pkt = MqttSnBuilder::publish()
1070            .dup(true)
1071            .qos(3)
1072            .retain(true)
1073            .tid_type(TID_SHORT)
1074            .tid(0x4142) // "AB"
1075            .mid(0x0001)
1076            .data(b"x")
1077            .build();
1078        let layer = make_layer(&pkt);
1079        let flags = layer.flags(&pkt).unwrap();
1080        // DUP=1(bit7), QoS=3(bits6-5), Retain=1(bit4), Will=0(bit3), CleanSess=0(bit2), TidType=2(bits1-0)
1081        assert_eq!(flags & 0x80, 0x80); // DUP
1082        assert_eq!((flags >> 5) & 0x03, 3); // QoS
1083        assert_eq!(flags & 0x10, 0x10); // Retain
1084        assert_eq!(flags & 0x03, TID_SHORT); // TidType
1085    }
1086
1087    #[test]
1088    fn test_build_into() {
1089        let builder = MqttSnBuilder::pingresp();
1090        let mut buf = Vec::new();
1091        let written = builder.build_into(&mut buf);
1092        assert_eq!(written, 2);
1093        assert_eq!(buf, b"\x02\x17");
1094    }
1095}