fidl_fuchsia_net_ndp_ext/
lib.rs1use std::num::{NonZeroU32, NonZeroU64};
6
7use derivative::Derivative;
8use futures::StreamExt;
9
10use fidl_fuchsia_net_ext as fnet_ext;
11use fidl_fuchsia_net_ndp as fnet_ndp;
12
13#[derive(Debug, PartialEq, Eq, Clone, Copy)]
16pub enum BodyLengthError {
17 MaxLengthExceeded,
20 NotMultipleOf8,
23}
24
25#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Derivative)]
34#[derivative(Debug)]
35pub struct OptionBody<B = Vec<u8>> {
36 #[derivative(Debug = "ignore")]
39 bytes: B,
40}
41
42impl<B> OptionBody<B> {
43 pub fn into_inner(self) -> B {
45 self.bytes
46 }
47}
48
49impl<B: AsRef<[u8]>> OptionBody<B> {
50 pub fn new(bytes: B) -> Result<Self, BodyLengthError> {
51 let len = bytes.as_ref().len();
52 if len > fnet_ndp::MAX_OPTION_BODY_LENGTH as usize {
53 return Err(BodyLengthError::MaxLengthExceeded);
54 }
55 if (len + 2) % 8 != 0 {
56 return Err(BodyLengthError::NotMultipleOf8);
57 }
58 Ok(Self { bytes })
59 }
60
61 fn as_ref(&self) -> &[u8] {
62 self.bytes.as_ref()
63 }
64
65 pub fn to_owned(&self) -> OptionBody {
66 let Self { bytes } = self;
67 OptionBody { bytes: bytes.as_ref().to_vec() }
68 }
69}
70
71pub type OptionBodyRef<'a> = OptionBody<&'a [u8]>;
72
73#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
76pub enum FidlConversionError {
77 #[error("required field not set: {0}")]
79 MissingField(&'static str),
80 #[error("body length error: {0:?}")]
82 BodyLength(BodyLengthError),
83 #[error("interface ID must be non-zero")]
85 ZeroInterfaceId,
86}
87
88#[derive(Debug, PartialEq, Eq)]
91pub enum TryParseAsOptionResult<O> {
92 Parsed(O),
94 OptionTypeMismatch,
97 ParseErr(packet::records::options::OptionParseErr),
100}
101
102#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
109pub struct OptionWatchEntry {
110 pub interface_id: NonZeroU64,
113 pub source_address: net_types::ip::Ipv6Addr,
116 pub option_type: fnet_ndp::OptionType,
118 pub body: OptionBody,
120}
121
122impl OptionWatchEntry {
123 pub fn try_parse_as_rdnss(
125 &self,
126 ) -> TryParseAsOptionResult<packet_formats::icmp::ndp::options::RecursiveDnsServer<'_>> {
127 if self.option_type
128 != u8::from(packet_formats::icmp::ndp::options::NdpOptionType::RecursiveDnsServer)
129 {
130 return TryParseAsOptionResult::OptionTypeMismatch;
131 }
132 packet_formats::icmp::ndp::options::RecursiveDnsServer::parse(self.body.as_ref())
133 .map_or_else(TryParseAsOptionResult::ParseErr, TryParseAsOptionResult::Parsed)
134 }
135}
136
137impl TryFrom<fnet_ndp::OptionWatchEntry> for OptionWatchEntry {
138 type Error = FidlConversionError;
139
140 fn try_from(fidl_entry: fnet_ndp::OptionWatchEntry) -> Result<Self, Self::Error> {
141 let fnet_ndp::OptionWatchEntry {
142 interface_id,
143 source_address,
144 option_type,
145 body,
146 __source_breaking,
147 } = fidl_entry;
148
149 let interface_id = interface_id.ok_or(FidlConversionError::MissingField("interface_id"))?;
150 let source_address =
151 source_address.ok_or(FidlConversionError::MissingField("source_address"))?;
152 let option_type = option_type.ok_or(FidlConversionError::MissingField("option_type"))?;
153 let body = OptionBody::new(body.ok_or(FidlConversionError::MissingField("body"))?)
154 .map_err(FidlConversionError::BodyLength)?;
155 Ok(Self {
156 interface_id: NonZeroU64::new(interface_id)
157 .ok_or(FidlConversionError::ZeroInterfaceId)?,
158 source_address: fnet_ext::FromExt::from_ext(source_address),
159 option_type,
160 body,
161 })
162 }
163}
164
165impl From<OptionWatchEntry> for fnet_ndp::OptionWatchEntry {
166 fn from(value: OptionWatchEntry) -> Self {
167 let OptionWatchEntry { interface_id, source_address, option_type, body } = value;
168 Self {
169 interface_id: Some(interface_id.get()),
170 source_address: Some(fnet_ext::FromExt::from_ext(source_address)),
171 option_type: Some(option_type),
172 body: Some(body.into_inner()),
173 __source_breaking: fidl::marker::SourceBreaking,
174 }
175 }
176}
177
178#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
180pub enum OptionWatchStreamItem {
181 Entry(OptionWatchEntry),
183 Dropped(NonZeroU32),
186}
187
188impl OptionWatchStreamItem {
189 pub fn try_into_entry(self) -> Result<OptionWatchEntry, Self> {
191 match self {
192 Self::Entry(entry) => Ok(entry),
193 Self::Dropped(_) => Err(self),
194 }
195 }
196}
197
198#[derive(Debug, Clone, thiserror::Error)]
200pub enum OptionWatchStreamError {
201 #[error(transparent)]
202 Fidl(#[from] fidl::Error),
203 #[error(transparent)]
204 Conversion(#[from] FidlConversionError),
205}
206
207pub async fn create_watcher_stream(
216 provider: &fnet_ndp::RouterAdvertisementOptionWatcherProviderProxy,
217 params: &fnet_ndp::RouterAdvertisementOptionWatcherParams,
218) -> Option<
219 Result<
220 impl futures::Stream<Item = Result<OptionWatchStreamItem, OptionWatchStreamError>> + use<>,
221 fidl::Error,
222 >,
223> {
224 let (proxy, server_end) = fidl::endpoints::create_proxy::<fnet_ndp::OptionWatcherMarker>();
225 if let Err(e) = provider.new_router_advertisement_option_watcher(server_end, ¶ms) {
226 return Some(Err(e));
227 }
228 proxy
229 .probe()
230 .await
231 .map_err(|e| match e {
232 fidl::Error::ClientChannelClosed { .. } => return None,
235 err => return Some(err),
236 })
237 .ok()?;
238
239 Some(Ok(futures::stream::try_unfold(proxy, |proxy| async move {
240 Ok(Some((proxy.watch_options().await?, proxy)))
241 })
242 .flat_map(|result: Result<_, fidl::Error>| match result {
243 Err(e) => {
244 futures::stream::once(futures::future::ready(Err(OptionWatchStreamError::Fidl(e))))
245 .left_stream()
246 }
247 Ok((batch, dropped)) => futures::stream::iter(
248 NonZeroU32::new(dropped).map(|dropped| Ok(OptionWatchStreamItem::Dropped(dropped))),
249 )
250 .chain(futures::stream::iter(batch.into_iter().map(|entry| {
251 OptionWatchEntry::try_from(entry)
252 .map(OptionWatchStreamItem::Entry)
253 .map_err(OptionWatchStreamError::Conversion)
254 })))
255 .right_stream(),
256 })))
257}
258
259#[cfg(test)]
260mod test {
261 use super::*;
262
263 use packet::records::options::OptionParseErr;
264 use packet_formats::icmp::ndp::options::RecursiveDnsServer;
265 use test_case::test_case;
266
267 use fidl_fuchsia_net as fnet;
268
269 const INTERFACE_ID: NonZeroU64 = NonZeroU64::new(1).unwrap();
270 const NET_SOURCE_ADDRESS: net_types::ip::Ipv6Addr = net_declare::net_ip_v6!("fe80::1");
271 const FIDL_SOURCE_ADDRESS: fnet::Ipv6Address = net_declare::fidl_ip_v6!("fe80::1");
272 const OPTION_TYPE: u8 = 1;
273 const BODY: [u8; 6] = [1, 2, 3, 4, 5, 6];
274
275 fn valid_fidl_entry() -> fnet_ndp::OptionWatchEntry {
276 fnet_ndp::OptionWatchEntry {
277 interface_id: Some(INTERFACE_ID.get()),
278 source_address: Some(FIDL_SOURCE_ADDRESS),
279 option_type: Some(OPTION_TYPE),
280 body: Some(BODY.to_vec()),
281 __source_breaking: fidl::marker::SourceBreaking,
282 }
283 }
284
285 fn valid_ext_entry() -> OptionWatchEntry {
286 OptionWatchEntry {
287 interface_id: INTERFACE_ID,
288 source_address: NET_SOURCE_ADDRESS,
289 option_type: OPTION_TYPE,
290 body: OptionBody::new(BODY.to_vec()).expect("should be valid option body"),
291 }
292 }
293
294 #[test_case(valid_fidl_entry() => Ok(valid_ext_entry()))]
295 #[test_case(fnet_ndp::OptionWatchEntry {
296 interface_id: None,
297 ..valid_fidl_entry()
298 } => Err(FidlConversionError::MissingField("interface_id")))]
299 #[test_case(fnet_ndp::OptionWatchEntry {
300 source_address: None,
301 ..valid_fidl_entry()
302 } => Err(FidlConversionError::MissingField("source_address")))]
303 #[test_case(fnet_ndp::OptionWatchEntry {
304 option_type: None,
305 ..valid_fidl_entry()
306 } => Err(FidlConversionError::MissingField("option_type")))]
307 #[test_case(fnet_ndp::OptionWatchEntry {
308 body: None,
309 ..valid_fidl_entry()
310 } => Err(FidlConversionError::MissingField("body")))]
311 #[test_case(fnet_ndp::OptionWatchEntry {
312 interface_id: Some(0),
313 ..valid_fidl_entry()
314 } => Err(FidlConversionError::ZeroInterfaceId))]
315 #[test_case(fnet_ndp::OptionWatchEntry {
316 body: Some(vec![1; fnet_ndp::MAX_OPTION_BODY_LENGTH as usize + 1]),
317 ..valid_fidl_entry()
318 } => Err(FidlConversionError::BodyLength(BodyLengthError::MaxLengthExceeded)))]
319 #[test_case(fnet_ndp::OptionWatchEntry {
320 body: Some(vec![1; 7]),
321 ..valid_fidl_entry()
322 } => Err(FidlConversionError::BodyLength(BodyLengthError::NotMultipleOf8)))]
323 fn convert_option_watch_entry(
324 entry: fnet_ndp::OptionWatchEntry,
325 ) -> Result<OptionWatchEntry, FidlConversionError> {
326 OptionWatchEntry::try_from(entry)
327 }
328
329 fn recursive_dns_server_option_and_bytes() -> (RecursiveDnsServer<'static>, Vec<u8>) {
330 const ADDRESSES: [net_types::ip::Ipv6Addr; 2] =
331 [net_declare::net_ip_v6!("2001:db8::1"), net_declare::net_ip_v6!("2001:db8::2")];
332 let option = RecursiveDnsServer::new(u32::MAX, &ADDRESSES);
333 let builder = packet_formats::icmp::ndp::options::NdpOptionBuilder::RecursiveDnsServer(
334 option.clone(),
335 );
336 let len = packet::records::options::OptionBuilder::serialized_len(&builder);
337 let mut data = vec![0u8; len];
338 packet::records::options::OptionBuilder::serialize_into(&builder, &mut data);
339 (option, data)
340 }
341
342 #[test]
343 fn try_parse_as_rdnss_succeeds() {
344 let (option, bytes) = recursive_dns_server_option_and_bytes();
345 let entry = OptionWatchEntry {
346 interface_id: INTERFACE_ID,
347 source_address: NET_SOURCE_ADDRESS,
348 option_type: u8::from(
349 packet_formats::icmp::ndp::options::NdpOptionType::RecursiveDnsServer,
350 ),
351 body: OptionBody::new(bytes).unwrap(),
352 };
353 assert_eq!(entry.try_parse_as_rdnss(), TryParseAsOptionResult::Parsed(option));
354 }
355
356 #[test]
357 fn try_parse_as_rdnss_option_type_mismatch() {
358 let (_option, bytes) = recursive_dns_server_option_and_bytes();
359 let entry = OptionWatchEntry {
360 interface_id: INTERFACE_ID,
361 source_address: NET_SOURCE_ADDRESS,
362 option_type: u8::from(packet_formats::icmp::ndp::options::NdpOptionType::Nonce),
363 body: OptionBody::new(bytes).unwrap(),
364 };
365 assert_eq!(entry.try_parse_as_rdnss(), TryParseAsOptionResult::OptionTypeMismatch);
366 }
367
368 #[test]
369 fn try_parse_as_rdnss_fails() {
370 let (_option, bytes) = recursive_dns_server_option_and_bytes();
371 let entry = OptionWatchEntry {
372 interface_id: INTERFACE_ID,
373 source_address: NET_SOURCE_ADDRESS,
374 option_type: u8::from(
375 packet_formats::icmp::ndp::options::NdpOptionType::RecursiveDnsServer,
376 ),
377 body: OptionBody::new(vec![0u8; bytes.len()]).unwrap(),
378 };
379 assert_eq!(entry.try_parse_as_rdnss(), TryParseAsOptionResult::ParseErr(OptionParseErr));
380 }
381}