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::mqttsn::{MqttSnLayer, RC_REJ_CONGESTION, RC_REJ_INVALID_TID, TID_NORMAL};
691    use crate::layer::{LayerIndex, LayerKind};
692
693    fn make_layer(buf: &[u8]) -> MqttSnLayer {
694        let idx = LayerIndex::new(LayerKind::MqttSn, 0, buf.len());
695        MqttSnLayer::new(idx)
696    }
697
698    // ========================================================================
699    // Basic build tests
700    // ========================================================================
701
702    #[test]
703    fn test_build_advertise() {
704        let pkt = MqttSnBuilder::advertise()
705            .gw_id(0x98)
706            .duration(0x2b9a)
707            .build();
708        assert_eq!(pkt, b"\x05\x00\x98\x2b\x9a");
709    }
710
711    #[test]
712    fn test_build_searchgw() {
713        let pkt = MqttSnBuilder::searchgw().radius(0xcc).build();
714        assert_eq!(pkt, b"\x03\x01\xcc");
715    }
716
717    #[test]
718    fn test_build_gwinfo() {
719        let pkt = MqttSnBuilder::gwinfo()
720            .gw_id(0x42)
721            .gw_addr(&[192, 168, 1, 1])
722            .build();
723        assert_eq!(pkt.len(), 7); // 1 + 1 + 1 + 4
724        assert_eq!(pkt[0], 7);
725        assert_eq!(pkt[1], GWINFO);
726        assert_eq!(pkt[2], 0x42);
727        assert_eq!(&pkt[3..], &[192, 168, 1, 1]);
728    }
729
730    #[test]
731    fn test_build_connect() {
732        let pkt = MqttSnBuilder::connect()
733            .cleansess(true)
734            .prot_id(0x1a)
735            .duration(0x775b)
736            .client_id(b"test")
737            .build();
738        assert_eq!(pkt, b"\x0a\x04\x04\x1a\x77\x5b\x74\x65\x73\x74");
739    }
740
741    #[test]
742    fn test_build_connack() {
743        let pkt = MqttSnBuilder::connack()
744            .return_code(RC_REJ_INVALID_TID)
745            .build();
746        assert_eq!(pkt, b"\x03\x05\x02");
747    }
748
749    #[test]
750    fn test_build_publish() {
751        let pkt = MqttSnBuilder::publish()
752            .qos(2)
753            .tid(0x197f)
754            .mid(0x6a26)
755            .data(b"test")
756            .build();
757        assert_eq!(
758            pkt,
759            &[
760                0x0b, 0x0c, 0x40, 0x19, 0x7f, 0x6a, 0x26, b't', b'e', b's', b't'
761            ]
762        );
763    }
764
765    #[test]
766    fn test_build_puback() {
767        let pkt = MqttSnBuilder::puback()
768            .tid(0x0001)
769            .mid(0x0002)
770            .return_code(RC_ACCEPTED)
771            .build();
772        assert_eq!(pkt.len(), 7);
773        assert_eq!(pkt[0], 7);
774        assert_eq!(pkt[1], PUBACK);
775        assert_eq!(&pkt[2..4], &[0x00, 0x01]); // tid
776        assert_eq!(&pkt[4..6], &[0x00, 0x02]); // mid
777        assert_eq!(pkt[6], RC_ACCEPTED);
778    }
779
780    #[test]
781    fn test_build_pubcomp() {
782        let pkt = MqttSnBuilder::pubcomp().mid(0x1234).build();
783        assert_eq!(pkt.len(), 4);
784        assert_eq!(pkt[0], 4);
785        assert_eq!(pkt[1], PUBCOMP);
786        assert_eq!(&pkt[2..4], &[0x12, 0x34]);
787    }
788
789    #[test]
790    fn test_build_register() {
791        let pkt = MqttSnBuilder::register()
792            .tid(0x0001)
793            .mid(0x0002)
794            .topic_name(b"test/topic")
795            .build();
796        assert_eq!(pkt.len(), 16); // 1 + 1 + 2 + 2 + 10
797        assert_eq!(pkt[0], 16);
798        assert_eq!(pkt[1], REGISTER);
799        assert_eq!(&pkt[2..4], &[0x00, 0x01]); // tid
800        assert_eq!(&pkt[4..6], &[0x00, 0x02]); // mid
801        assert_eq!(&pkt[6..], b"test/topic");
802    }
803
804    #[test]
805    fn test_build_regack() {
806        let pkt = MqttSnBuilder::regack()
807            .tid(0x0010)
808            .mid(0x0020)
809            .return_code(RC_ACCEPTED)
810            .build();
811        assert_eq!(pkt.len(), 7);
812        assert_eq!(pkt[0], 7);
813        assert_eq!(pkt[1], REGACK);
814        assert_eq!(&pkt[2..4], &[0x00, 0x10]); // tid
815        assert_eq!(&pkt[4..6], &[0x00, 0x20]); // mid
816        assert_eq!(pkt[6], RC_ACCEPTED);
817    }
818
819    #[test]
820    fn test_build_subscribe_normal_topic() {
821        let pkt = MqttSnBuilder::subscribe()
822            .qos(1)
823            .tid_type(TID_NORMAL)
824            .mid(0x0001)
825            .topic_name(b"sensors/temp")
826            .build();
827        assert_eq!(pkt[1], SUBSCRIBE);
828        let flags = pkt[2];
829        assert_eq!((flags >> 5) & 0x03, 1); // QoS=1
830        assert_eq!(flags & 0x03, TID_NORMAL);
831        assert_eq!(&pkt[3..5], &[0x00, 0x01]); // mid
832        assert_eq!(&pkt[5..], b"sensors/temp");
833    }
834
835    #[test]
836    fn test_build_subscribe_predef_tid() {
837        let pkt = MqttSnBuilder::subscribe()
838            .qos(1)
839            .tid_type(TID_PREDEF)
840            .mid(0x0001)
841            .tid(0x0042)
842            .build();
843        assert_eq!(pkt[1], SUBSCRIBE);
844        let flags = pkt[2];
845        assert_eq!(flags & 0x03, TID_PREDEF);
846        assert_eq!(&pkt[5..7], &[0x00, 0x42]); // predefined tid
847    }
848
849    #[test]
850    fn test_build_subscribe_short_topic() {
851        // Short topic is encoded as a 2-byte value in the tid field
852        let short = u16::from_be_bytes([b'a', b'b']);
853        let pkt = MqttSnBuilder::subscribe()
854            .qos(0)
855            .tid_type(TID_SHORT)
856            .mid(0x0005)
857            .tid(short)
858            .build();
859        assert_eq!(pkt[1], SUBSCRIBE);
860        let flags = pkt[2];
861        assert_eq!(flags & 0x03, TID_SHORT);
862        assert_eq!(&pkt[5..7], &[b'a', b'b']);
863    }
864
865    #[test]
866    fn test_build_suback() {
867        let pkt = MqttSnBuilder::suback()
868            .qos(1)
869            .tid(0x0001)
870            .mid(0x0002)
871            .return_code(RC_ACCEPTED)
872            .build();
873        assert_eq!(pkt.len(), 8);
874        assert_eq!(pkt[0], 8);
875        assert_eq!(pkt[1], SUBACK);
876        // flags, tid(2), mid(2), rc
877        let flags = pkt[2];
878        assert_eq!((flags >> 5) & 0x03, 1);
879        assert_eq!(&pkt[3..5], &[0x00, 0x01]); // tid
880        assert_eq!(&pkt[5..7], &[0x00, 0x02]); // mid
881        assert_eq!(pkt[7], RC_ACCEPTED);
882    }
883
884    #[test]
885    fn test_build_unsubscribe_normal() {
886        let pkt = MqttSnBuilder::unsubscribe()
887            .tid_type(TID_NORMAL)
888            .mid(0x0003)
889            .topic_name(b"test/t")
890            .build();
891        assert_eq!(pkt[1], UNSUBSCRIBE);
892        let flags = pkt[2];
893        assert_eq!(flags & 0x03, TID_NORMAL);
894        assert_eq!(&pkt[3..5], &[0x00, 0x03]); // mid
895        assert_eq!(&pkt[5..], b"test/t");
896    }
897
898    #[test]
899    fn test_build_unsuback() {
900        let pkt = MqttSnBuilder::unsuback().mid(0x0003).build();
901        assert_eq!(pkt.len(), 4);
902        assert_eq!(pkt[0], 4);
903        assert_eq!(pkt[1], UNSUBACK);
904        assert_eq!(&pkt[2..4], &[0x00, 0x03]);
905    }
906
907    #[test]
908    fn test_build_pingreq_with_client_id() {
909        let pkt = MqttSnBuilder::pingreq().client_id(b"sensor1").build();
910        assert_eq!(pkt[0] as usize, pkt.len());
911        assert_eq!(pkt[1], PINGREQ);
912        assert_eq!(&pkt[2..], b"sensor1");
913    }
914
915    #[test]
916    fn test_build_pingresp() {
917        let pkt = MqttSnBuilder::pingresp().build();
918        assert_eq!(pkt, b"\x02\x17");
919    }
920
921    #[test]
922    fn test_build_disconnect_no_duration() {
923        let pkt = MqttSnBuilder::disconnect().build();
924        assert_eq!(pkt, b"\x02\x18");
925    }
926
927    #[test]
928    fn test_build_disconnect_with_duration() {
929        let pkt = MqttSnBuilder::disconnect().duration(0x0312).build();
930        assert_eq!(pkt, b"\x04\x18\x03\x12");
931    }
932
933    #[test]
934    fn test_build_willtopic() {
935        let pkt = MqttSnBuilder::willtopic()
936            .qos(1)
937            .retain(true)
938            .will_topic_str("lastwill")
939            .build();
940        assert_eq!(pkt[1], WILLTOPIC);
941        let flags = pkt[2];
942        assert_eq!((flags >> 5) & 0x03, 1); // QoS=1
943        assert_eq!((flags >> 4) & 1, 1); // Retain=true
944        assert_eq!(&pkt[3..], b"lastwill");
945    }
946
947    #[test]
948    fn test_build_willmsg() {
949        let pkt = MqttSnBuilder::willmsg().will_msg_bytes(b"goodbye").build();
950        assert_eq!(pkt[1], WILLMSG);
951        assert_eq!(&pkt[2..], b"goodbye");
952    }
953
954    #[test]
955    fn test_build_willtopicresp() {
956        let pkt = MqttSnBuilder::willtopicresp()
957            .return_code(RC_ACCEPTED)
958            .build();
959        assert_eq!(pkt.len(), 3);
960        assert_eq!(pkt[0], 3);
961        assert_eq!(pkt[1], WILLTOPICRESP);
962        assert_eq!(pkt[2], RC_ACCEPTED);
963    }
964
965    #[test]
966    fn test_build_willmsgresp() {
967        let pkt = MqttSnBuilder::willmsgresp()
968            .return_code(RC_REJ_CONGESTION)
969            .build();
970        assert_eq!(pkt.len(), 3);
971        assert_eq!(pkt[0], 3);
972        assert_eq!(pkt[1], WILLMSGRESP);
973        assert_eq!(pkt[2], RC_REJ_CONGESTION);
974    }
975
976    // ========================================================================
977    // Round-trip tests (build then parse)
978    // ========================================================================
979
980    #[test]
981    fn test_roundtrip_connect() {
982        let pkt = MqttSnBuilder::connect()
983            .cleansess(true)
984            .will(true)
985            .prot_id(0x01)
986            .duration(60)
987            .client_id(b"mydevice")
988            .build();
989        let layer = make_layer(&pkt);
990        assert_eq!(layer.msg_type(&pkt).unwrap(), CONNECT);
991        assert_eq!(layer.cleansess(&pkt).unwrap(), true);
992        assert_eq!(layer.will(&pkt).unwrap(), true);
993        assert_eq!(layer.prot_id(&pkt).unwrap(), 0x01);
994        assert_eq!(layer.duration(&pkt).unwrap(), 60);
995        assert_eq!(layer.client_id(&pkt).unwrap(), "mydevice");
996    }
997
998    #[test]
999    fn test_roundtrip_publish() {
1000        let pkt = MqttSnBuilder::publish()
1001            .dup(true)
1002            .qos(1)
1003            .retain(true)
1004            .tid(0xBEEF)
1005            .mid(0xCAFE)
1006            .data(b"hello world")
1007            .build();
1008        let layer = make_layer(&pkt);
1009        assert_eq!(layer.msg_type(&pkt).unwrap(), PUBLISH);
1010        assert_eq!(layer.dup(&pkt).unwrap(), true);
1011        assert_eq!(layer.qos(&pkt).unwrap(), 1);
1012        assert_eq!(layer.retain(&pkt).unwrap(), true);
1013        assert_eq!(layer.tid(&pkt).unwrap(), 0xBEEF);
1014        assert_eq!(layer.mid(&pkt).unwrap(), 0xCAFE);
1015        assert_eq!(layer.data(&pkt).unwrap(), b"hello world");
1016    }
1017
1018    #[test]
1019    fn test_roundtrip_register() {
1020        let pkt = MqttSnBuilder::register()
1021            .tid(0x0042)
1022            .mid(0x0001)
1023            .topic_name(b"sensors/temp")
1024            .build();
1025        let layer = make_layer(&pkt);
1026        assert_eq!(layer.msg_type(&pkt).unwrap(), REGISTER);
1027        assert_eq!(layer.tid(&pkt).unwrap(), 0x0042);
1028        assert_eq!(layer.mid(&pkt).unwrap(), 0x0001);
1029        assert_eq!(layer.topic_name(&pkt).unwrap(), "sensors/temp");
1030    }
1031
1032    #[test]
1033    fn test_roundtrip_suback() {
1034        let pkt = MqttSnBuilder::suback()
1035            .qos(2)
1036            .tid(0x0010)
1037            .mid(0x0020)
1038            .return_code(RC_ACCEPTED)
1039            .build();
1040        let layer = make_layer(&pkt);
1041        assert_eq!(layer.msg_type(&pkt).unwrap(), SUBACK);
1042        assert_eq!(layer.qos(&pkt).unwrap(), 2);
1043        assert_eq!(layer.tid(&pkt).unwrap(), 0x0010);
1044        assert_eq!(layer.mid(&pkt).unwrap(), 0x0020);
1045        assert_eq!(layer.return_code(&pkt).unwrap(), RC_ACCEPTED);
1046    }
1047
1048    #[test]
1049    fn test_extended_length_builder() {
1050        // Build a message with a large payload that requires extended length
1051        let big_data = vec![0xAA; 300];
1052        let pkt = MqttSnBuilder::publish()
1053            .qos(0)
1054            .tid(0x0001)
1055            .mid(0x0001)
1056            .data(&big_data)
1057            .build();
1058        // Should use extended length encoding (0x01 prefix)
1059        assert_eq!(pkt[0], 0x01);
1060        let total_len = u16::from_be_bytes([pkt[1], pkt[2]]);
1061        assert_eq!(total_len as usize, pkt.len());
1062        assert_eq!(pkt[3], PUBLISH);
1063        let layer = make_layer(&pkt);
1064        assert_eq!(layer.msg_type(&pkt).unwrap(), PUBLISH);
1065        assert_eq!(layer.data(&pkt).unwrap().len(), 300);
1066    }
1067
1068    #[test]
1069    fn test_flags_all_set() {
1070        let pkt = MqttSnBuilder::publish()
1071            .dup(true)
1072            .qos(3)
1073            .retain(true)
1074            .tid_type(TID_SHORT)
1075            .tid(0x4142) // "AB"
1076            .mid(0x0001)
1077            .data(b"x")
1078            .build();
1079        let layer = make_layer(&pkt);
1080        let flags = layer.flags(&pkt).unwrap();
1081        // DUP=1(bit7), QoS=3(bits6-5), Retain=1(bit4), Will=0(bit3), CleanSess=0(bit2), TidType=2(bits1-0)
1082        assert_eq!(flags & 0x80, 0x80); // DUP
1083        assert_eq!((flags >> 5) & 0x03, 3); // QoS
1084        assert_eq!(flags & 0x10, 0x10); // Retain
1085        assert_eq!(flags & 0x03, TID_SHORT); // TidType
1086    }
1087
1088    #[test]
1089    fn test_build_into() {
1090        let builder = MqttSnBuilder::pingresp();
1091        let mut buf = Vec::new();
1092        let written = builder.build_into(&mut buf);
1093        assert_eq!(written, 2);
1094        assert_eq!(buf, b"\x02\x17");
1095    }
1096}