1use crate::frame::{FrameWriter, MultiEntries, WriteError};
11use crate::ids::MAX_ANNOUNCE_FLOOD_HOPS;
12use crate::items::CHANNEL_IDENTIFIER_LEN;
13use crate::status::Status;
14
15pub mod opt {
17 pub const KIND: u32 = 1;
19 pub const FLOOD_HOPS: u32 = 2;
21 pub const FULL_SOURCE: u32 = 3;
24 pub const CHANNEL: u32 = 4;
27}
28
29#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
31pub enum AnnouncementKind {
32 #[default]
34 Advertisement = 0,
35 Beacon = 1,
37}
38
39impl AnnouncementKind {
40 pub const fn code(self) -> u8 {
42 self as u8
43 }
44
45 pub const fn from_code(code: u8) -> Option<Self> {
47 match code {
48 0 => Some(Self::Advertisement),
49 1 => Some(Self::Beacon),
50 _ => None,
51 }
52 }
53
54 pub const fn defaults_to_full_source(self) -> bool {
61 matches!(self, Self::Advertisement)
62 }
63}
64
65#[derive(Clone, Copy, Debug, PartialEq, Eq)]
67pub enum AnnounceError {
68 Invalid,
71 Parse,
73}
74
75impl AnnounceError {
76 pub const fn status(self) -> Status {
78 match self {
79 Self::Invalid => Status::INVALID_ARGUMENT,
80 Self::Parse => Status::PARSE_ERROR,
81 }
82 }
83}
84
85#[derive(Clone, Copy, Debug, PartialEq, Eq)]
88pub struct Announcement {
89 pub kind: AnnouncementKind,
90 pub flood_hops: u8,
93 pub full_source: bool,
95 pub channel: Option<[u8; CHANNEL_IDENTIFIER_LEN]>,
98}
99
100impl Default for Announcement {
101 fn default() -> Self {
102 Self::new(AnnouncementKind::Advertisement)
103 }
104}
105
106impl Announcement {
107 pub const fn new(kind: AnnouncementKind) -> Self {
109 Self {
110 kind,
111 flood_hops: 0,
112 full_source: kind.defaults_to_full_source(),
113 channel: None,
114 }
115 }
116
117 pub const fn floods(&self) -> bool {
119 self.flood_hops > 0
120 }
121
122 pub fn parse(payload: &[u8]) -> Result<Self, AnnounceError> {
124 let mut request = Self::default();
125 let mut seen = 0u32;
126
127 for entry in MultiEntries::new(payload) {
128 let entry = entry.map_err(|_| AnnounceError::Parse)?;
129 let bit = match entry.key {
130 opt::KIND | opt::FLOOD_HOPS | opt::FULL_SOURCE | opt::CHANNEL => 1u32 << entry.key,
131 _ => return Err(AnnounceError::Invalid),
132 };
133 if seen & bit != 0 {
134 return Err(AnnounceError::Invalid);
135 }
136 seen |= bit;
137
138 match (entry.key, entry.value) {
139 (opt::KIND, &[code]) => {
140 request.kind =
141 AnnouncementKind::from_code(code).ok_or(AnnounceError::Invalid)?;
142 }
143 (opt::FLOOD_HOPS, &[hops]) if hops <= MAX_ANNOUNCE_FLOOD_HOPS => {
144 request.flood_hops = hops;
145 }
146 (opt::FULL_SOURCE, &[0]) => request.full_source = false,
147 (opt::FULL_SOURCE, &[1]) => request.full_source = true,
148 (opt::CHANNEL, value) => {
149 let identifier = value.try_into().map_err(|_| AnnounceError::Invalid)?;
150 request.channel = Some(identifier);
151 }
152 _ => return Err(AnnounceError::Invalid),
153 }
154 }
155
156 if seen & (1 << opt::FULL_SOURCE) == 0 {
157 request.full_source = request.kind.defaults_to_full_source();
158 }
159 Ok(request)
160 }
161
162 pub fn write(&self, writer: &mut FrameWriter<'_>) -> Result<(), WriteError> {
165 if self.kind != AnnouncementKind::Advertisement {
166 writer.write_entry(opt::KIND, &[self.kind.code()])?;
167 }
168 if self.flood_hops != 0 {
169 writer.write_entry(opt::FLOOD_HOPS, &[self.flood_hops])?;
170 }
171 if self.full_source != self.kind.defaults_to_full_source() {
172 writer.write_entry(opt::FULL_SOURCE, &[u8::from(self.full_source)])?;
173 }
174 if let Some(channel) = &self.channel {
175 writer.write_entry(opt::CHANNEL, channel)?;
176 }
177 Ok(())
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184 use crate::frame::{self, Cmd, Frame};
185
186 fn round_trip(request: &Announcement) -> Announcement {
187 let mut buf = [0u8; 64];
188 let len = frame::announce(&mut buf, 3, request).unwrap();
189 let parsed = Frame::parse(&buf[..len]).unwrap();
190 assert_eq!(parsed.command(), Some(Cmd::Announce));
191 assert_eq!(parsed.header.tid(), 3);
192 Announcement::parse(parsed.payload).unwrap()
193 }
194
195 #[test]
196 fn an_empty_payload_is_a_neighbor_advertisement() {
197 let request = Announcement::parse(&[]).unwrap();
198 assert_eq!(request.kind, AnnouncementKind::Advertisement);
199 assert_eq!(request.flood_hops, 0);
200 assert!(request.full_source);
201 assert_eq!(request.channel, None);
202 assert!(!request.floods());
203 assert_eq!(request, Announcement::default());
204
205 let mut buf = [0u8; 8];
207 assert_eq!(frame::announce(&mut buf, 0, &request).unwrap(), 2);
208 }
209
210 #[test]
211 fn a_beacon_defaults_to_the_source_hint() {
212 let request = Announcement::new(AnnouncementKind::Beacon);
213 assert!(!request.full_source);
214 assert_eq!(round_trip(&request), request);
215
216 let mut buf = [0u8; 32];
219 let mut writer = FrameWriter::new(&mut buf, 0, Cmd::Announce).unwrap();
220 writer.write_entry(opt::FLOOD_HOPS, &[4]).unwrap();
221 writer
222 .write_entry(opt::KIND, &[AnnouncementKind::Beacon.code()])
223 .unwrap();
224 let len = writer.finish();
225 let parsed = Announcement::parse(Frame::parse(&buf[..len]).unwrap().payload).unwrap();
226 assert_eq!(parsed.kind, AnnouncementKind::Beacon);
227 assert!(!parsed.full_source);
228 assert_eq!(parsed.flood_hops, 4);
229 }
230
231 #[test]
232 fn every_option_round_trips() {
233 let requests = [
234 Announcement::default(),
235 Announcement {
236 kind: AnnouncementKind::Beacon,
237 flood_hops: MAX_ANNOUNCE_FLOOD_HOPS,
238 full_source: true,
239 channel: Some([0xAB; CHANNEL_IDENTIFIER_LEN]),
240 },
241 Announcement {
242 kind: AnnouncementKind::Advertisement,
243 flood_hops: 5,
244 full_source: false,
245 channel: None,
246 },
247 ];
248 for request in requests {
249 assert_eq!(round_trip(&request), request);
250 }
251 }
252
253 #[test]
254 fn unknown_and_repeated_options_are_refused() {
255 let mut buf = [0u8; 32];
256
257 let mut writer = FrameWriter::new(&mut buf, 0, Cmd::Announce).unwrap();
258 writer.write_entry(9, &[0]).unwrap();
259 let len = writer.finish();
260 assert_eq!(
261 Announcement::parse(Frame::parse(&buf[..len]).unwrap().payload),
262 Err(AnnounceError::Invalid)
263 );
264
265 let mut writer = FrameWriter::new(&mut buf, 0, Cmd::Announce).unwrap();
266 writer.write_entry(opt::FLOOD_HOPS, &[1]).unwrap();
267 writer.write_entry(opt::FLOOD_HOPS, &[2]).unwrap();
268 let len = writer.finish();
269 assert_eq!(
270 Announcement::parse(Frame::parse(&buf[..len]).unwrap().payload),
271 Err(AnnounceError::Invalid)
272 );
273 }
274
275 #[test]
276 fn out_of_range_and_misshaped_values_are_refused() {
277 let cases: [(u32, &[u8]); 6] = [
278 (opt::KIND, &[2]),
279 (opt::KIND, &[]),
280 (opt::FLOOD_HOPS, &[MAX_ANNOUNCE_FLOOD_HOPS + 1]),
281 (opt::FULL_SOURCE, &[2]),
282 (opt::CHANNEL, &[0xAB; 3]),
283 (opt::CHANNEL, &[0xAB; CHANNEL_IDENTIFIER_LEN + 1]),
284 ];
285 for (key, value) in cases {
286 let mut buf = [0u8; 32];
287 let mut writer = FrameWriter::new(&mut buf, 0, Cmd::Announce).unwrap();
288 writer.write_entry(key, value).unwrap();
289 let len = writer.finish();
290 assert_eq!(
291 Announcement::parse(Frame::parse(&buf[..len]).unwrap().payload),
292 Err(AnnounceError::Invalid),
293 "option {key} value {value:?}"
294 );
295 }
296 }
297
298 #[test]
299 fn a_truncated_entry_list_is_a_parse_error() {
300 assert_eq!(
302 Announcement::parse(&[4, opt::FLOOD_HOPS as u8, 1]),
303 Err(AnnounceError::Parse)
304 );
305 }
306
307 #[test]
308 fn errors_carry_the_status_the_device_answers() {
309 assert_eq!(AnnounceError::Invalid.status(), Status::INVALID_ARGUMENT);
310 assert_eq!(AnnounceError::Parse.status(), Status::PARSE_ERROR);
311 }
312}