umsh_core/
builder.rs

1use core::{marker::PhantomData, ops::Range};
2
3use crate::{
4    BuildError, ChannelId, Fcf, FloodHops, MicSize, NodeHint, OptionNumber, PacketType, PublicKey,
5    Scf, SecInfo, UnsealedPacket, options::OptionEncoder,
6};
7
8/// Typestate markers used by [`PacketBuilder`] and its specialized builders.
9pub mod state {
10    /// Builder state that still requires a source address.
11    pub struct NeedsSource;
12    /// Builder state that still requires a frame counter.
13    pub struct NeedsCounter;
14    /// Builder state where options and payload can be configured.
15    pub struct Configuring;
16    /// Builder state after payload bytes have been staged.
17    pub struct Complete;
18}
19
20/// Entry point for typestate packet construction.
21///
22/// The builder consumes itself as it transitions through required stages so
23/// invalid packet layouts are rejected at compile time where possible.
24pub struct PacketBuilder<'a> {
25    buf: &'a mut [u8],
26}
27
28impl<'a> PacketBuilder<'a> {
29    /// Start building a packet into `buf`.
30    pub fn new(buf: &'a mut [u8]) -> Self {
31        Self { buf }
32    }
33
34    /// Begin a broadcast packet.
35    pub fn broadcast(self) -> BroadcastBuilder<'a, state::NeedsSource> {
36        Builder::new(self.buf, PacketType::Broadcast)
37    }
38
39    /// Build a MAC ACK packet with all required addressing fixed up front.
40    pub fn mac_ack(self, dst: NodeHint, ack_tag: [u8; 8]) -> MacAckBuilder<'a, state::Configuring> {
41        let mut builder = Builder::new(self.buf, PacketType::MacAck);
42        builder.ack_dst = Some(dst);
43        builder.ack_tag = Some(ack_tag);
44        builder
45    }
46
47    /// Begin a unicast packet to `dst`.
48    pub fn unicast(self, dst: NodeHint) -> UnicastBuilder<'a, state::NeedsSource> {
49        let mut builder = Builder::new(self.buf, PacketType::Unicast);
50        builder.dst = Some(dst);
51        builder
52    }
53
54    /// Begin a multicast packet on `channel`.
55    pub fn multicast(self, channel: ChannelId) -> MulticastBuilder<'a, state::NeedsSource> {
56        let mut builder = Builder::new(self.buf, PacketType::Multicast);
57        builder.channel = Some(channel);
58        builder
59    }
60
61    /// Begin a blind-unicast packet addressed through `channel` to `dst`.
62    pub fn blind_unicast(
63        self,
64        channel: ChannelId,
65        dst: NodeHint,
66    ) -> BlindUnicastBuilder<'a, state::NeedsSource> {
67        let mut builder = Builder::new(self.buf, PacketType::BlindUnicast);
68        builder.channel = Some(channel);
69        builder.dst = Some(dst);
70        builder
71    }
72}
73
74/// Broadcast packet builder alias.
75pub type BroadcastBuilder<'a, S> = Builder<'a, BroadcastKind, S>;
76/// MAC ACK packet builder alias.
77pub type MacAckBuilder<'a, S> = Builder<'a, MacAckKind, S>;
78/// Unicast packet builder alias.
79pub type UnicastBuilder<'a, S> = Builder<'a, UnicastKind, S>;
80/// Multicast packet builder alias.
81pub type MulticastBuilder<'a, S> = Builder<'a, MulticastKind, S>;
82/// Blind-unicast packet builder alias.
83pub type BlindUnicastBuilder<'a, S> = Builder<'a, BlindUnicastKind, S>;
84
85/// Marker type for broadcast builders.
86pub struct BroadcastKind;
87/// Marker type for MAC ACK builders.
88pub struct MacAckKind;
89/// Marker type for unicast builders.
90pub struct UnicastKind;
91/// Marker type for multicast builders.
92pub struct MulticastKind;
93/// Marker type for blind-unicast builders.
94pub struct BlindUnicastKind;
95
96enum SourceValue {
97    Hint(NodeHint),
98    Full(PublicKey),
99}
100
101/// Internal generic builder implementation shared by all packet kinds.
102pub struct Builder<'a, K, S> {
103    buf: &'a mut [u8],
104    packet_type: PacketType,
105    options_len: usize,
106    options_scratch: Option<Range<usize>>,
107    last_option_number: Option<u16>,
108    option_error: Option<BuildError>,
109    source: Option<SourceValue>,
110    dst: Option<NodeHint>,
111    channel: Option<ChannelId>,
112    ack_dst: Option<NodeHint>,
113    ack_tag: Option<[u8; 8]>,
114    frame_counter: Option<u32>,
115    encrypted: bool,
116    mic_size: MicSize,
117    salt: Option<u16>,
118    flood_hops: Option<FloodHops>,
119    payload: Option<Range<usize>>,
120    blind_addr: Option<Range<usize>>,
121    _marker: PhantomData<(K, S)>,
122}
123
124impl<'a, K, S> Builder<'a, K, S> {
125    fn new(buf: &'a mut [u8], packet_type: PacketType) -> Self {
126        Self {
127            buf,
128            packet_type,
129            options_len: 0,
130            options_scratch: None,
131            last_option_number: None,
132            option_error: None,
133            source: None,
134            dst: None,
135            channel: None,
136            ack_dst: None,
137            ack_tag: None,
138            frame_counter: None,
139            encrypted: matches!(
140                packet_type,
141                PacketType::BlindUnicast | PacketType::BlindUnicastAckReq
142            ),
143            mic_size: MicSize::Mic16,
144            salt: None,
145            flood_hops: None,
146            payload: None,
147            blind_addr: None,
148            _marker: PhantomData,
149        }
150    }
151
152    fn with_state<NS>(self) -> Builder<'a, K, NS> {
153        Builder {
154            buf: self.buf,
155            packet_type: self.packet_type,
156            options_len: self.options_len,
157            options_scratch: self.options_scratch,
158            last_option_number: self.last_option_number,
159            option_error: self.option_error,
160            source: self.source,
161            dst: self.dst,
162            channel: self.channel,
163            ack_dst: self.ack_dst,
164            ack_tag: self.ack_tag,
165            frame_counter: self.frame_counter,
166            encrypted: self.encrypted,
167            mic_size: self.mic_size,
168            salt: self.salt,
169            flood_hops: self.flood_hops,
170            payload: self.payload,
171            blind_addr: self.blind_addr,
172            _marker: PhantomData,
173        }
174    }
175
176    fn push_option(&mut self, number: u16, value: &[u8]) {
177        if self.option_error.is_some() {
178            return;
179        }
180        if let Some(last) = self.last_option_number {
181            if number < last {
182                self.option_error = Some(BuildError::OptionOutOfOrder);
183                return;
184            }
185        }
186        let mut live = match self.last_option_number {
187            Some(last_number) => {
188                OptionEncoder::with_last_number(&mut self.buf[1 + self.options_len..], last_number)
189            }
190            None => OptionEncoder::new(&mut self.buf[1 + self.options_len..]),
191        };
192        match live.put(number, value) {
193            Ok(()) => {
194                self.options_len += live.finish();
195                self.last_option_number = Some(number);
196            }
197            Err(err) => self.option_error = Some(err.into()),
198        }
199    }
200
201    /// Move the options block staged at `buf[1..1 + options_len]` into a tail
202    /// scratch region so the prefix fields can overwrite its original slot.
203    fn stash_options(&mut self) -> Result<(), BuildError> {
204        if self.options_len == 0 {
205            return Ok(());
206        }
207        let payload_len = self
208            .payload
209            .as_ref()
210            .map(|p| p.end - p.start)
211            .unwrap_or(0);
212        let scratch_end = self
213            .buf
214            .len()
215            .checked_sub(payload_len)
216            .ok_or(BuildError::BufferTooSmall)?;
217        let scratch_start = scratch_end
218            .checked_sub(self.options_len)
219            .ok_or(BuildError::BufferTooSmall)?;
220        if scratch_start < 1 + self.options_len {
221            return Err(BuildError::BufferTooSmall);
222        }
223        self.buf.copy_within(1..1 + self.options_len, scratch_start);
224        self.options_scratch = Some(scratch_start..scratch_end);
225        Ok(())
226    }
227
228    /// Copy the options block from tail scratch to the current cursor. If a
229    /// payload follows, append the `0xFF` end-of-options marker.
230    fn emit_options(
231        &mut self,
232        cursor: &mut usize,
233        has_payload: bool,
234    ) -> Result<Range<usize>, BuildError> {
235        let start = *cursor;
236        if let Some(scratch) = self.options_scratch.clone() {
237            let len = scratch.end - scratch.start;
238            let end = start
239                .checked_add(len)
240                .ok_or(BuildError::BufferTooSmall)?;
241            if end > self.buf.len() {
242                return Err(BuildError::BufferTooSmall);
243            }
244            self.buf.copy_within(scratch, start);
245            *cursor = end;
246        }
247        if has_payload {
248            let marker_slot = *cursor;
249            if marker_slot >= self.buf.len() {
250                return Err(BuildError::BufferTooSmall);
251            }
252            self.buf[marker_slot] = 0xFF;
253            *cursor = marker_slot + 1;
254        }
255        Ok(start..*cursor)
256    }
257
258    fn write_common_prefix(&mut self) -> Result<usize, BuildError> {
259        if let Some(err) = self.option_error {
260            return Err(err);
261        }
262        self.stash_options()?;
263        let full_source = matches!(self.source, Some(SourceValue::Full(_)));
264        let fcf = Fcf::new(self.packet_type, full_source, self.flood_hops.is_some());
265        if self.buf.is_empty() {
266            return Err(BuildError::BufferTooSmall);
267        }
268        self.buf[0] = fcf.0;
269        let mut cursor = 1;
270        if let Some(fhops) = self.flood_hops {
271            self.buf
272                .get_mut(cursor)
273                .ok_or(BuildError::BufferTooSmall)
274                .map(|slot| *slot = fhops.0)?;
275            cursor += 1;
276        }
277        Ok(cursor)
278    }
279
280    fn write_source(&mut self, cursor: &mut usize) -> Result<(), BuildError> {
281        match self.source {
282            Some(SourceValue::Hint(hint)) => {
283                let end = *cursor + 3;
284                self.buf
285                    .get_mut(*cursor..end)
286                    .ok_or(BuildError::BufferTooSmall)?
287                    .copy_from_slice(&hint.0);
288                *cursor = end;
289            }
290            Some(SourceValue::Full(key)) => {
291                let end = *cursor + 32;
292                self.buf
293                    .get_mut(*cursor..end)
294                    .ok_or(BuildError::BufferTooSmall)?
295                    .copy_from_slice(&key.0);
296                *cursor = end;
297            }
298            None => return Err(BuildError::MissingSource),
299        }
300        Ok(())
301    }
302
303    fn stage_payload(&mut self, data: &[u8]) {
304        let scratch_start = match self.buf.len().checked_sub(data.len()) {
305            Some(value) => value,
306            None => {
307                self.option_error = Some(BuildError::BufferTooSmall);
308                return;
309            }
310        };
311        if let Some(slot) = self.buf.get_mut(scratch_start..scratch_start + data.len()) {
312            slot.copy_from_slice(data);
313            self.payload = Some(scratch_start..scratch_start + data.len());
314        } else {
315            self.option_error = Some(BuildError::BufferTooSmall);
316        }
317    }
318
319    fn copy_staged_payload(&mut self, cursor: &mut usize) -> Result<Range<usize>, BuildError> {
320        let payload = self.payload.clone().ok_or(BuildError::MissingPayload)?;
321        let len = payload.end - payload.start;
322        let start = *cursor;
323        let end = start + len;
324        if end > self.buf.len() {
325            return Err(BuildError::BufferTooSmall);
326        }
327        self.buf.copy_within(payload, start);
328        *cursor = end;
329        Ok(start..end)
330    }
331
332    fn stage_blind_addr(&mut self, cursor: &mut usize) -> Result<Range<usize>, BuildError> {
333        let dst = self.dst.ok_or(BuildError::MissingDestination)?;
334        let start = *cursor;
335        let dst_end = start + 3;
336        self.buf
337            .get_mut(start..dst_end)
338            .ok_or(BuildError::BufferTooSmall)?
339            .copy_from_slice(&dst.0);
340        *cursor = dst_end;
341        self.write_source(cursor)?;
342        let end = *cursor;
343        Ok(start..end)
344    }
345}
346
347impl<'a> BroadcastBuilder<'a, state::NeedsSource> {
348    /// Encode the source address as a three-byte hint.
349    pub fn source_hint(mut self, hint: NodeHint) -> BroadcastBuilder<'a, state::Configuring> {
350        self.source = Some(SourceValue::Hint(hint));
351        self.with_state()
352    }
353
354    /// Encode the source address as a full public key.
355    pub fn source_full(mut self, key: &PublicKey) -> BroadcastBuilder<'a, state::Configuring> {
356        self.source = Some(SourceValue::Full(*key));
357        self.with_state()
358    }
359}
360
361impl<'a> UnicastBuilder<'a, state::NeedsSource> {
362    /// Encode the source address as a three-byte hint.
363    pub fn source_hint(mut self, hint: NodeHint) -> UnicastBuilder<'a, state::NeedsCounter> {
364        self.source = Some(SourceValue::Hint(hint));
365        self.with_state()
366    }
367
368    /// Encode the source address as a full public key.
369    pub fn source_full(mut self, key: &PublicKey) -> UnicastBuilder<'a, state::NeedsCounter> {
370        self.source = Some(SourceValue::Full(*key));
371        self.with_state()
372    }
373}
374
375impl<'a> MulticastBuilder<'a, state::NeedsSource> {
376    /// Encode the source address as a three-byte hint.
377    pub fn source_hint(mut self, hint: NodeHint) -> MulticastBuilder<'a, state::NeedsCounter> {
378        self.source = Some(SourceValue::Hint(hint));
379        self.with_state()
380    }
381
382    /// Encode the source address as a full public key.
383    pub fn source_full(mut self, key: &PublicKey) -> MulticastBuilder<'a, state::NeedsCounter> {
384        self.source = Some(SourceValue::Full(*key));
385        self.with_state()
386    }
387}
388
389impl<'a> BlindUnicastBuilder<'a, state::NeedsSource> {
390    /// Encode the source address as a three-byte hint.
391    pub fn source_hint(mut self, hint: NodeHint) -> BlindUnicastBuilder<'a, state::NeedsCounter> {
392        self.source = Some(SourceValue::Hint(hint));
393        self.with_state()
394    }
395
396    /// Encode the source address as a full public key.
397    pub fn source_full(mut self, key: &PublicKey) -> BlindUnicastBuilder<'a, state::NeedsCounter> {
398        self.source = Some(SourceValue::Full(*key));
399        self.with_state()
400    }
401}
402
403impl<'a> UnicastBuilder<'a, state::NeedsCounter> {
404    /// Set the frame counter for the secure packet.
405    pub fn frame_counter(mut self, counter: u32) -> UnicastBuilder<'a, state::Configuring> {
406        self.frame_counter = Some(counter);
407        self.with_state()
408    }
409}
410
411impl<'a> MulticastBuilder<'a, state::NeedsCounter> {
412    /// Set the frame counter for the secure packet.
413    pub fn frame_counter(mut self, counter: u32) -> MulticastBuilder<'a, state::Configuring> {
414        self.frame_counter = Some(counter);
415        self.with_state()
416    }
417}
418
419impl<'a> BlindUnicastBuilder<'a, state::NeedsCounter> {
420    /// Set the frame counter for the secure packet.
421    pub fn frame_counter(mut self, counter: u32) -> BlindUnicastBuilder<'a, state::Configuring> {
422        self.frame_counter = Some(counter);
423        self.with_state()
424    }
425}
426
427macro_rules! impl_configuring_common {
428    ($name:ident<$state:ty>) => {
429        impl<'a> $name<'a, $state> {
430            /// Set the initial flood-hop budget.
431            pub fn flood_hops(mut self, remaining: u8) -> Self {
432                if let Some(value) = FloodHops::new(remaining, 0) {
433                    self.flood_hops = Some(value);
434                }
435                self
436            }
437
438            /// Add the region-code option.
439            pub fn region_code(mut self, code: [u8; 2]) -> Self {
440                self.push_option(OptionNumber::RegionCode.as_u16(), &code);
441                self
442            }
443
444            /// Add an empty trace-route option.
445            pub fn trace_route(mut self) -> Self {
446                self.push_option(OptionNumber::TraceRoute.as_u16(), &[]);
447                self
448            }
449
450            /// Add a source-route option from a router-hint slice.
451            pub fn source_route(mut self, hops: &[crate::RouterHint]) -> Self {
452                let mut encoded = [0u8; 30];
453                let needed = hops.len() * 2;
454                if needed > encoded.len() {
455                    self.option_error = Some(BuildError::BufferTooSmall);
456                    return self;
457                }
458                for (index, hop) in hops.iter().enumerate() {
459                    encoded[index * 2..index * 2 + 2].copy_from_slice(&hop.0);
460                }
461                self.push_option(OptionNumber::SourceRoute.as_u16(), &encoded[..needed]);
462                self
463            }
464
465            /// Add an arbitrary option number/value pair.
466            pub fn option(mut self, number: OptionNumber, value: &[u8]) -> Self {
467                self.push_option(number.as_u16(), value);
468                self
469            }
470        }
471    };
472}
473
474impl_configuring_common!(BroadcastBuilder<state::Configuring>);
475impl_configuring_common!(MacAckBuilder<state::Configuring>);
476impl_configuring_common!(UnicastBuilder<state::Configuring>);
477impl_configuring_common!(MulticastBuilder<state::Configuring>);
478impl_configuring_common!(BlindUnicastBuilder<state::Configuring>);
479
480impl<'a> UnicastBuilder<'a, state::Configuring> {
481    /// Upgrade the packet type to ACK-requested unicast.
482    pub fn ack_requested(mut self) -> Self {
483        self.packet_type = PacketType::UnicastAckReq;
484        self
485    }
486
487    /// Mark the packet body for encryption.
488    pub fn encrypted(mut self) -> Self {
489        self.encrypted = true;
490        self
491    }
492
493    /// Select the MIC size reserved in the packet footer.
494    pub fn mic_size(mut self, size: MicSize) -> Self {
495        self.mic_size = size;
496        self
497    }
498
499    /// Attach an explicit salt value to SECINFO.
500    pub fn salt(mut self, salt: u16) -> Self {
501        self.salt = Some(salt);
502        self
503    }
504
505    /// Stage the application payload and advance to the terminal builder state.
506    pub fn payload(mut self, data: &[u8]) -> UnicastBuilder<'a, state::Complete> {
507        self.stage_payload(data);
508        self.with_state()
509    }
510}
511
512impl<'a> MulticastBuilder<'a, state::Configuring> {
513    /// Mark the packet body for encryption.
514    pub fn encrypted(mut self) -> Self {
515        self.encrypted = true;
516        self
517    }
518
519    /// Select the MIC size reserved in the packet footer.
520    pub fn mic_size(mut self, size: MicSize) -> Self {
521        self.mic_size = size;
522        self
523    }
524
525    /// Attach an explicit salt value to SECINFO.
526    pub fn salt(mut self, salt: u16) -> Self {
527        self.salt = Some(salt);
528        self
529    }
530
531    /// Stage the application payload and advance to the terminal builder state.
532    pub fn payload(mut self, data: &[u8]) -> MulticastBuilder<'a, state::Complete> {
533        self.stage_payload(data);
534        self.with_state()
535    }
536}
537
538impl<'a> BlindUnicastBuilder<'a, state::Configuring> {
539    /// Upgrade the packet type to ACK-requested blind unicast.
540    pub fn ack_requested(mut self) -> Self {
541        self.packet_type = PacketType::BlindUnicastAckReq;
542        self
543    }
544
545    /// Mark the body and blinded address block for encryption.
546    pub fn encrypted(mut self) -> Self {
547        self.encrypted = true;
548        self
549    }
550
551    /// Emit a blind-unicast packet without encrypting the payload.
552    ///
553    /// This exists primarily for tests and for explicit policy-rejection
554    /// scenarios where higher layers need to construct a frame they expect to
555    /// reject. Normal blind-unicast traffic should remain encrypted.
556    pub fn unencrypted(mut self) -> Self {
557        self.encrypted = false;
558        self
559    }
560
561    /// Select the MIC size reserved in the packet footer.
562    pub fn mic_size(mut self, size: MicSize) -> Self {
563        self.mic_size = size;
564        self
565    }
566
567    /// Attach an explicit salt value to SECINFO.
568    pub fn salt(mut self, salt: u16) -> Self {
569        self.salt = Some(salt);
570        self
571    }
572
573    /// Stage the application payload and advance to the terminal builder state.
574    pub fn payload(mut self, data: &[u8]) -> BlindUnicastBuilder<'a, state::Complete> {
575        self.stage_payload(data);
576        self.with_state()
577    }
578}
579
580impl<'a> BroadcastBuilder<'a, state::Configuring> {
581    /// Stage a broadcast payload.
582    pub fn payload(mut self, data: &[u8]) -> BroadcastBuilder<'a, state::Complete> {
583        self.stage_payload(data);
584        self.with_state()
585    }
586
587    /// Finalize the broadcast packet and return the written frame bytes.
588    ///
589    /// Layout: `FCF [FHOPS] SRC OPTIONS [0xFF PAYLOAD]`
590    pub fn build(mut self) -> Result<&'a [u8], BuildError> {
591        let has_payload = self.payload.is_some();
592        let mut cursor = self.write_common_prefix()?;
593        self.write_source(&mut cursor)?;
594        self.emit_options(&mut cursor, has_payload)?;
595        if has_payload {
596            let _ = self.copy_staged_payload(&mut cursor)?;
597        }
598        Ok(&self.buf[..cursor])
599    }
600}
601
602impl<'a> BroadcastBuilder<'a, state::Complete> {
603    /// Finalize the broadcast packet and return the written frame bytes.
604    pub fn build(self) -> Result<&'a [u8], BuildError> {
605        self.with_state::<state::Configuring>().build()
606    }
607}
608
609impl<'a> MacAckBuilder<'a, state::Configuring> {
610    /// Finalize the MAC ACK packet and return the written frame bytes.
611    ///
612    /// Layout: `FCF [FHOPS] DST OPTIONS ACK_TAG(8)`
613    /// The `0xFF` end-marker is omitted — the ACK_TAG trailer is at a fixed offset.
614    pub fn build(mut self) -> Result<&'a [u8], BuildError> {
615        let mut cursor = self.write_common_prefix()?;
616        let dst = self.ack_dst.ok_or(BuildError::MissingDestination)?;
617        self.buf
618            .get_mut(cursor..cursor + 3)
619            .ok_or(BuildError::BufferTooSmall)?
620            .copy_from_slice(&dst.0);
621        cursor += 3;
622        self.emit_options(&mut cursor, false)?;
623        let ack_tag = self.ack_tag.ok_or(BuildError::MissingAckTag)?;
624        self.buf
625            .get_mut(cursor..cursor + 8)
626            .ok_or(BuildError::BufferTooSmall)?
627            .copy_from_slice(&ack_tag);
628        cursor += 8;
629        Ok(&self.buf[..cursor])
630    }
631}
632
633impl<'a> UnicastBuilder<'a, state::Complete> {
634    pub fn build(self) -> Result<UnsealedPacket<'a>, BuildError> {
635        self.with_state::<state::Configuring>().build()
636    }
637}
638
639impl<'a> UnicastBuilder<'a, state::Configuring> {
640    /// Layout: `FCF [FHOPS] DST SRC SECINFO OPTIONS [0xFF PAYLOAD] MIC`
641    pub fn build(mut self) -> Result<UnsealedPacket<'a>, BuildError> {
642        let has_payload = self.payload.is_some();
643        let mut cursor = self.write_common_prefix()?;
644        let dst = self.dst.ok_or(BuildError::MissingDestination)?;
645        self.buf
646            .get_mut(cursor..cursor + 3)
647            .ok_or(BuildError::BufferTooSmall)?
648            .copy_from_slice(&dst.0);
649        cursor += 3;
650        self.write_source(&mut cursor)?;
651        let scf = Scf::new(self.encrypted, self.mic_size, self.salt.is_some());
652        let sec_info = SecInfo {
653            scf,
654            frame_counter: self.frame_counter.ok_or(BuildError::MissingFrameCounter)?,
655            salt: self.salt,
656        };
657        let sec_start = cursor;
658        cursor += sec_info.encode(
659            self.buf
660                .get_mut(cursor..)
661                .ok_or(BuildError::BufferTooSmall)?,
662        )?;
663        let opts_range = self.emit_options(&mut cursor, has_payload)?;
664        let body_start = cursor;
665        let body_range = if has_payload {
666            self.copy_staged_payload(&mut cursor)?
667        } else {
668            body_start..body_start
669        };
670        let mic_start = cursor;
671        let mic_end = mic_start + self.mic_size.byte_len();
672        self.buf
673            .get_mut(mic_start..mic_end)
674            .ok_or(BuildError::BufferTooSmall)?
675            .fill(0);
676        cursor = mic_end;
677        Ok(UnsealedPacket::new(
678            self.buf,
679            cursor,
680            body_range,
681            None,
682            mic_start..mic_end,
683            sec_start..sec_start + sec_info.wire_len(),
684            opts_range,
685        ))
686    }
687}
688
689impl<'a> MulticastBuilder<'a, state::Complete> {
690    pub fn build(self) -> Result<UnsealedPacket<'a>, BuildError> {
691        self.with_state::<state::Configuring>().build()
692    }
693}
694
695impl<'a> MulticastBuilder<'a, state::Configuring> {
696    /// Layout (E=1): `FCF [FHOPS] CHANNEL SECINFO OPTIONS 0xFF ENC(SRC+PAYLOAD) MIC`
697    /// Layout (E=0): `FCF [FHOPS] CHANNEL SECINFO OPTIONS 0xFF SRC [PAYLOAD] MIC`
698    pub fn build(mut self) -> Result<UnsealedPacket<'a>, BuildError> {
699        let mut cursor = self.write_common_prefix()?;
700        let channel = self.channel.ok_or(BuildError::MissingChannel)?;
701        self.buf
702            .get_mut(cursor..cursor + 2)
703            .ok_or(BuildError::BufferTooSmall)?
704            .copy_from_slice(&channel.0);
705        cursor += 2;
706        let scf = Scf::new(self.encrypted, self.mic_size, self.salt.is_some());
707        let sec_info = SecInfo {
708            scf,
709            frame_counter: self.frame_counter.ok_or(BuildError::MissingFrameCounter)?,
710            salt: self.salt,
711        };
712        let sec_start = cursor;
713        cursor += sec_info.encode(
714            self.buf
715                .get_mut(cursor..)
716                .ok_or(BuildError::BufferTooSmall)?,
717        )?;
718        // SRC (and optionally payload) always follow OPTIONS, so 0xFF is always emitted.
719        let opts_range = self.emit_options(&mut cursor, true)?;
720        let body_start = cursor;
721        self.write_source(&mut cursor)?;
722        let payload_range = self.copy_staged_payload(&mut cursor)?;
723        let body_range = if self.encrypted {
724            body_start..payload_range.end
725        } else {
726            payload_range
727        };
728        let mic_start = cursor;
729        let mic_end = mic_start + self.mic_size.byte_len();
730        self.buf
731            .get_mut(mic_start..mic_end)
732            .ok_or(BuildError::BufferTooSmall)?
733            .fill(0);
734        cursor = mic_end;
735        Ok(UnsealedPacket::new(
736            self.buf,
737            cursor,
738            body_range,
739            None,
740            mic_start..mic_end,
741            sec_start..sec_start + sec_info.wire_len(),
742            opts_range,
743        ))
744    }
745}
746
747impl<'a> BlindUnicastBuilder<'a, state::Complete> {
748    pub fn build(self) -> Result<UnsealedPacket<'a>, BuildError> {
749        self.with_state::<state::Configuring>().build()
750    }
751}
752
753impl<'a> BlindUnicastBuilder<'a, state::Configuring> {
754    /// Layout (E=1): `FCF [FHOPS] CHANNEL SECINFO OPTIONS 0xFF ENC_DST_SRC ENC_PAYLOAD MIC`
755    /// Layout (E=0): `FCF [FHOPS] CHANNEL SECINFO OPTIONS 0xFF DST SRC [PAYLOAD] MIC`
756    pub fn build(mut self) -> Result<UnsealedPacket<'a>, BuildError> {
757        let mut cursor = self.write_common_prefix()?;
758        let channel = self.channel.ok_or(BuildError::MissingChannel)?;
759        self.buf
760            .get_mut(cursor..cursor + 2)
761            .ok_or(BuildError::BufferTooSmall)?
762            .copy_from_slice(&channel.0);
763        cursor += 2;
764        let scf = Scf::new(self.encrypted, self.mic_size, self.salt.is_some());
765        let sec_info = SecInfo {
766            scf,
767            frame_counter: self.frame_counter.ok_or(BuildError::MissingFrameCounter)?,
768            salt: self.salt,
769        };
770        let sec_start = cursor;
771        cursor += sec_info.encode(
772            self.buf
773                .get_mut(cursor..)
774                .ok_or(BuildError::BufferTooSmall)?,
775        )?;
776        // DST+SRC (and optionally payload) always follow OPTIONS, so 0xFF is always emitted.
777        let opts_range = self.emit_options(&mut cursor, true)?;
778        let blind_addr_range = self.stage_blind_addr(&mut cursor)?;
779        let body_range = self.copy_staged_payload(&mut cursor)?;
780        let mic_start = cursor;
781        let mic_end = mic_start + self.mic_size.byte_len();
782        self.buf
783            .get_mut(mic_start..mic_end)
784            .ok_or(BuildError::BufferTooSmall)?
785            .fill(0);
786        cursor = mic_end;
787        Ok(UnsealedPacket::new(
788            self.buf,
789            cursor,
790            body_range,
791            Some(blind_addr_range),
792            mic_start..mic_end,
793            sec_start..sec_start + sec_info.wire_len(),
794            opts_range,
795        ))
796    }
797}