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