1pub mod messages;
17mod types;
18
19#[cfg(test)]
20mod testdata;
21
22pub use self::types::*;
23
24use core::fmt::Debug;
25use core::marker::PhantomData;
26use core::mem;
27
28use internet_checksum::Checksum;
29use net_types::ip::Ipv4Addr;
30use packet::{
31 AsFragmentedByteSlice, BufferView, FragmentedByteSlice, FragmentedBytesMut, InnerPacketBuilder,
32 PacketBuilder, PacketConstraints, ParsablePacket, ParseMetadata, SerializeTarget,
33};
34use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Ref, SplitByteSlice, Unaligned};
35
36use self::messages::IgmpMessageType;
37use crate::error::ParseError;
38
39pub trait MessageType<B> {
50 type FixedHeader: Sized
55 + Copy
56 + Clone
57 + FromBytes
58 + IntoBytes
59 + KnownLayout
60 + Immutable
61 + Unaligned
62 + Debug;
63
64 type VariableBody: Sized;
66
67 const TYPE: IgmpMessageType;
72
73 type MaxRespTime: Sized + IgmpMaxRespCode + Debug;
89
90 fn parse_body<BV: BufferView<B>>(
92 header: &Self::FixedHeader,
93 bytes: BV,
94 ) -> Result<Self::VariableBody, ParseError>
95 where
96 B: SplitByteSlice;
97
98 fn body_bytes(body: &Self::VariableBody) -> &[u8]
106 where
107 B: SplitByteSlice;
108}
109
110pub trait IgmpMaxRespCode {
117 fn as_code(&self) -> u8;
119 fn from_code(code: u8) -> Self;
121}
122
123impl IgmpMaxRespCode for () {
127 fn as_code(&self) -> u8 {
128 0
129 }
130
131 fn from_code(_code: u8) {}
132}
133
134pub trait IgmpNonEmptyBody {}
138
139#[derive(Debug)]
141pub struct IgmpPacketBuilder<B, M: MessageType<B>> {
142 max_resp_time: M::MaxRespTime,
143 message_header: M::FixedHeader,
144 _marker: PhantomData<B>,
145}
146
147impl<B, M: MessageType<B, MaxRespTime = ()>> IgmpPacketBuilder<B, M> {
148 pub fn new(msg_header: M::FixedHeader) -> IgmpPacketBuilder<B, M> {
150 IgmpPacketBuilder { max_resp_time: (), message_header: msg_header, _marker: PhantomData }
151 }
152}
153
154impl<B, M: MessageType<B>> IgmpPacketBuilder<B, M> {
155 pub fn new_with_resp_time(
157 msg_header: M::FixedHeader,
158 max_resp_time: M::MaxRespTime,
159 ) -> IgmpPacketBuilder<B, M> {
160 IgmpPacketBuilder { max_resp_time, message_header: msg_header, _marker: PhantomData }
161 }
162}
163
164impl<B, M: MessageType<B>> IgmpPacketBuilder<B, M> {
165 fn serialize_headers<BB: packet::Fragment>(
166 &self,
167 mut headers_buff: &mut [u8],
168 body: FragmentedByteSlice<'_, BB>,
169 ) {
170 use packet::BufferViewMut;
171 let mut bytes = &mut headers_buff;
172 let mut header_prefix =
175 bytes.take_obj_front_zero::<HeaderPrefix>().expect("too few bytes for IGMP message");
176 header_prefix.set_msg_type(M::TYPE);
177 header_prefix.max_resp_code = self.max_resp_time.as_code();
178
179 let mut header =
180 bytes.take_obj_front_zero::<M::FixedHeader>().expect("too few bytes for IGMP message");
181 *header = self.message_header;
182
183 let checksum = compute_checksum_fragmented(&header_prefix, &Ref::bytes(&header), &body);
184 header_prefix.checksum = checksum;
185 }
186}
187
188const fn total_header_size<F>() -> usize {
189 mem::size_of::<HeaderPrefix>() + mem::size_of::<F>()
190}
191
192impl<B, M: MessageType<B, VariableBody = ()>> InnerPacketBuilder for IgmpPacketBuilder<B, M> {
195 fn bytes_len(&self) -> usize {
196 total_header_size::<M::FixedHeader>()
197 }
198
199 fn serialize(&self, buffer: &mut [u8]) {
200 let empty = FragmentedByteSlice::<&'static [u8]>::new_empty();
201 self.serialize_headers(buffer, empty);
202 }
203}
204
205impl<B, M: MessageType<B>> PacketBuilder for IgmpPacketBuilder<B, M>
206where
207 M::VariableBody: IgmpNonEmptyBody,
208{
209 fn constraints(&self) -> PacketConstraints {
210 PacketConstraints::new(total_header_size::<M::FixedHeader>(), 0, 0, core::usize::MAX)
211 }
212
213 fn serialize(
214 &self,
215 target: &mut SerializeTarget<'_>,
216 message_body: FragmentedBytesMut<'_, '_>,
217 ) {
218 self.serialize_headers(target.header, message_body);
219 }
220}
221
222#[derive(Default, Debug, IntoBytes, KnownLayout, FromBytes, Immutable, Unaligned)]
230#[repr(C)]
231pub struct HeaderPrefix {
232 msg_type: u8,
233 max_resp_code: u8,
240 checksum: [u8; 2],
241}
242
243impl HeaderPrefix {
244 fn set_msg_type<T: Into<u8>>(&mut self, msg_type: T) {
245 self.msg_type = msg_type.into();
246 }
247}
248
249#[derive(Debug)]
255pub struct IgmpMessage<B: SplitByteSlice, M: MessageType<B>> {
256 prefix: Ref<B, HeaderPrefix>,
257 header: Ref<B, M::FixedHeader>,
258 body: M::VariableBody,
259}
260
261impl<B: SplitByteSlice, M: MessageType<B>> IgmpMessage<B, M> {
262 pub fn builder(&self) -> IgmpPacketBuilder<B, M> {
264 IgmpPacketBuilder::new_with_resp_time(*self.header, self.max_response_time())
265 }
266
267 pub fn max_response_time(&self) -> M::MaxRespTime {
269 M::MaxRespTime::from_code(self.prefix.max_resp_code)
270 }
271
272 pub fn header(&self) -> &M::FixedHeader {
274 &self.header
275 }
276
277 pub fn body(&self) -> &M::VariableBody {
279 &self.body
280 }
281}
282
283fn compute_checksum_fragmented<BB: packet::Fragment>(
284 header_prefix: &HeaderPrefix,
285 header: &[u8],
286 body: &FragmentedByteSlice<'_, BB>,
287) -> [u8; 2] {
288 let mut c = Checksum::new();
289 c.add_bytes(&[header_prefix.msg_type, header_prefix.max_resp_code]);
290 c.add_bytes(&header_prefix.checksum);
291 c.add_bytes(header);
292 for p in body.iter_fragments() {
293 c.add_bytes(p);
294 }
295 c.checksum()
296}
297
298impl<B: SplitByteSlice, M: MessageType<B>> IgmpMessage<B, M> {
299 fn compute_checksum(header_prefix: &HeaderPrefix, header: &[u8], body: &[u8]) -> [u8; 2] {
300 let mut body = [body];
301 compute_checksum_fragmented(header_prefix, header, &body.as_fragmented_byte_slice())
302 }
303}
304
305impl<B: SplitByteSlice, M: MessageType<B, FixedHeader = Ipv4Addr>> IgmpMessage<B, M> {
306 pub fn group_addr(&self) -> Ipv4Addr {
308 *self.header
309 }
310}
311
312impl<B: SplitByteSlice, M: MessageType<B>> ParsablePacket<B, ()> for IgmpMessage<B, M> {
313 type Error = ParseError;
314
315 fn parse_metadata(&self) -> ParseMetadata {
316 let header_len = Ref::bytes(&self.prefix).len() + Ref::bytes(&self.header).len();
317 ParseMetadata::from_packet(header_len, M::body_bytes(&self.body).len(), 0)
318 }
319
320 fn parse<BV: BufferView<B>>(mut buffer: BV, _args: ()) -> Result<Self, ParseError> {
321 let prefix = buffer
322 .take_obj_front::<HeaderPrefix>()
323 .ok_or_else(debug_err_fn!(ParseError::Format, "too few bytes for header prefix"))?;
324
325 let header = buffer
326 .take_obj_front::<M::FixedHeader>()
327 .ok_or_else(debug_err_fn!(ParseError::Format, "too few bytes for header"))?;
328
329 let checksum = Self::compute_checksum(&prefix, &Ref::bytes(&header), buffer.as_ref());
330 if checksum != [0, 0] {
331 return debug_err!(
332 Err(ParseError::Checksum),
333 "invalid checksum, got 0x{:x?}",
334 prefix.checksum
335 );
336 }
337
338 if prefix.msg_type != M::TYPE.into() {
339 return debug_err!(Err(ParseError::NotExpected), "unexpected message type");
340 }
341
342 let body = M::parse_body(&header, buffer)?;
343
344 Ok(IgmpMessage { prefix, header, body })
345 }
346}
347
348pub fn peek_message_type<MessageType: TryFrom<u8>>(
366 bytes: &[u8],
367) -> Result<(MessageType, bool), ParseError> {
368 let long_message =
371 bytes.len() > (core::mem::size_of::<HeaderPrefix>() + core::mem::size_of::<Ipv4Addr>());
372 let (header, _) = Ref::<_, HeaderPrefix>::from_prefix(bytes).map_err(Into::into).map_err(
373 |_: zerocopy::SizeError<_, _>| debug_err!(ParseError::Format, "too few bytes for header"),
374 )?;
375 let msg_type = MessageType::try_from(header.msg_type).map_err(|_| {
376 debug_err!(ParseError::NotSupported, "unrecognized message type: {:x}", header.msg_type,)
377 })?;
378 Ok((msg_type, long_message))
379}
380
381#[cfg(test)]
382mod tests {
383
384 use packet::{ParseBuffer, Serializer};
385
386 use super::*;
387 use crate::igmp::messages::*;
388 use crate::ip::Ipv4Proto;
389 use crate::ipv4::options::Ipv4Option;
390 use crate::ipv4::{Ipv4Header, Ipv4Packet, Ipv4PacketBuilder, Ipv4PacketBuilderWithOptions};
391
392 fn serialize_to_bytes<
393 B: SplitByteSlice + Debug,
394 M: MessageType<B, VariableBody = ()> + Debug,
395 >(
396 igmp: &IgmpMessage<B, M>,
397 src_ip: Ipv4Addr,
398 dst_ip: Ipv4Addr,
399 ) -> Vec<u8> {
400 let ipv4 = Ipv4PacketBuilder::new(src_ip, dst_ip, 1, Ipv4Proto::Igmp);
401 let with_options =
402 Ipv4PacketBuilderWithOptions::new(ipv4, &[Ipv4Option::RouterAlert { data: 0 }])
403 .unwrap();
404
405 igmp.builder()
406 .into_serializer()
407 .encapsulate(with_options)
408 .serialize_vec_outer()
409 .unwrap()
410 .as_ref()
411 .to_vec()
412 }
413
414 fn test_parse_and_serialize<
415 M: for<'a> MessageType<&'a [u8], VariableBody = ()> + Debug,
416 F: for<'a> FnOnce(&Ipv4Packet<&'a [u8]>),
417 G: for<'a> FnOnce(&IgmpMessage<&'a [u8], M>),
418 >(
419 mut pkt: &[u8],
420 check_ip: F,
421 check_igmp: G,
422 ) {
423 let orig_req = pkt;
424
425 let ip = pkt.parse_with::<_, Ipv4Packet<_>>(()).unwrap();
426 let src_ip = ip.src_ip();
427 let dst_ip = ip.dst_ip();
428 check_ip(&ip);
429 let mut req: &[u8] = pkt;
430 let igmp = req.parse_with::<_, IgmpMessage<_, M>>(()).unwrap();
431 check_igmp(&igmp);
432
433 let data = serialize_to_bytes(&igmp, src_ip, dst_ip);
434 assert_eq!(&data[..], orig_req);
435 }
436
437 #[test]
443 fn test_parse_and_serialize_igmpv2_report_with_options() {
444 use crate::testdata::igmpv2_membership::report::*;
445 test_parse_and_serialize::<IgmpMembershipReportV2, _, _>(
446 IP_PACKET_BYTES,
447 |ip| {
448 assert_eq!(ip.ttl(), 1);
449 assert_eq!(ip.iter_options().count(), 1);
450 let option = ip.iter_options().next().unwrap();
451 assert_eq!(option, Ipv4Option::RouterAlert { data: 0 });
452 assert_eq!(ip.header_len(), 24);
453 assert_eq!(ip.src_ip(), SOURCE);
454 assert_eq!(ip.dst_ip(), MULTICAST);
455 },
456 |igmp| {
457 assert_eq!(*igmp.header, MULTICAST);
458 },
459 )
460 }
461
462 #[test]
463 fn test_parse_and_serialize_igmpv2_query_with_options() {
464 use crate::testdata::igmpv2_membership::query::*;
465 test_parse_and_serialize::<IgmpMembershipQueryV2, _, _>(
466 IP_PACKET_BYTES,
467 |ip| {
468 assert_eq!(ip.ttl(), 1);
469 assert_eq!(ip.iter_options().count(), 1);
470 let option = ip.iter_options().next().unwrap();
471 assert_eq!(option, Ipv4Option::RouterAlert { data: 0 });
472 assert_eq!(ip.header_len(), 24);
473 assert_eq!(ip.src_ip(), SOURCE);
474 assert_eq!(ip.dst_ip(), MULTICAST);
475 },
476 |igmp| {
477 assert_eq!(*igmp.header, MULTICAST);
478 },
479 )
480 }
481
482 #[test]
483 fn test_parse_and_serialize_igmpv2_leave_with_options() {
484 use crate::testdata::igmpv2_membership::leave::*;
485 test_parse_and_serialize::<IgmpLeaveGroup, _, _>(
486 IP_PACKET_BYTES,
487 |ip| {
488 assert_eq!(ip.ttl(), 1);
489 assert_eq!(ip.iter_options().count(), 1);
490 let option = ip.iter_options().next().unwrap();
491 assert_eq!(option, Ipv4Option::RouterAlert { data: 0 });
492 assert_eq!(ip.header_len(), 24);
493 assert_eq!(ip.src_ip(), SOURCE);
494 assert_eq!(ip.dst_ip(), DESTINATION);
495 },
496 |igmp| {
497 assert_eq!(*igmp.header, MULTICAST);
498 },
499 )
500 }
501}