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