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 NestablePacketBuilder, NoOpSerializationContext, PacketBuilder, PacketConstraints,
33 ParsablePacket, ParseMetadata, SerializationContext, SerializeTarget,
34};
35use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Ref, SplitByteSlice, Unaligned};
36
37use self::messages::IgmpMessageType;
38use crate::error::ParseError;
39
40pub trait MessageType<B> {
51 type FixedHeader: Sized
56 + Copy
57 + Clone
58 + FromBytes
59 + IntoBytes
60 + KnownLayout
61 + Immutable
62 + Unaligned
63 + Debug;
64
65 type VariableBody: Sized;
67
68 const TYPE: IgmpMessageType;
73
74 type MaxRespTime: Sized + IgmpMaxRespCode + Debug;
90
91 fn parse_body<BV: BufferView<B>>(
93 header: &Self::FixedHeader,
94 bytes: BV,
95 ) -> Result<Self::VariableBody, ParseError>
96 where
97 B: SplitByteSlice;
98
99 fn body_bytes(body: &Self::VariableBody) -> &[u8]
107 where
108 B: SplitByteSlice;
109}
110
111pub trait IgmpMaxRespCode {
118 fn as_code(&self) -> u8;
120 fn from_code(code: u8) -> Self;
122}
123
124impl IgmpMaxRespCode for () {
128 fn as_code(&self) -> u8 {
129 0
130 }
131
132 fn from_code(_code: u8) {}
133}
134
135pub trait IgmpNonEmptyBody {}
139
140#[derive(Debug)]
142pub struct IgmpPacketBuilder<B, M: MessageType<B>> {
143 max_resp_time: M::MaxRespTime,
144 message_header: M::FixedHeader,
145 _marker: PhantomData<B>,
146}
147
148impl<B, M: MessageType<B, MaxRespTime = ()>> IgmpPacketBuilder<B, M> {
149 pub fn new(msg_header: M::FixedHeader) -> IgmpPacketBuilder<B, M> {
151 IgmpPacketBuilder { max_resp_time: (), message_header: msg_header, _marker: PhantomData }
152 }
153}
154
155impl<B, M: MessageType<B>> IgmpPacketBuilder<B, M> {
156 pub fn new_with_resp_time(
158 msg_header: M::FixedHeader,
159 max_resp_time: M::MaxRespTime,
160 ) -> IgmpPacketBuilder<B, M> {
161 IgmpPacketBuilder { max_resp_time, message_header: msg_header, _marker: PhantomData }
162 }
163}
164
165impl<B, M: MessageType<B>> IgmpPacketBuilder<B, M> {
166 fn serialize_headers<BB: packet::Fragment>(
167 &self,
168 mut headers_buff: &mut [u8],
169 body: FragmentedByteSlice<'_, BB>,
170 ) {
171 use packet::BufferViewMut;
172 let mut bytes = &mut headers_buff;
173 let mut header_prefix =
176 bytes.take_obj_front_zero::<HeaderPrefix>().expect("too few bytes for IGMP message");
177 header_prefix.set_msg_type(M::TYPE);
178 header_prefix.max_resp_code = self.max_resp_time.as_code();
179
180 let mut header =
181 bytes.take_obj_front_zero::<M::FixedHeader>().expect("too few bytes for IGMP message");
182 *header = self.message_header;
183
184 let checksum = compute_checksum_fragmented(&header_prefix, &Ref::bytes(&header), &body);
185 header_prefix.checksum = checksum;
186 }
187}
188
189const fn total_header_size<F>() -> usize {
190 mem::size_of::<HeaderPrefix>() + mem::size_of::<F>()
191}
192
193impl<B, M: MessageType<B, VariableBody = ()>> InnerPacketBuilder for IgmpPacketBuilder<B, M> {
196 fn bytes_len(&self) -> usize {
197 total_header_size::<M::FixedHeader>()
198 }
199
200 fn serialize(&self, buffer: &mut [u8]) {
201 let empty = FragmentedByteSlice::<&'static [u8]>::new_empty();
202 self.serialize_headers(buffer, empty);
203 }
204}
205
206pub struct IgmpEnvelope;
208
209pub trait IgmpSerializationContext: SerializationContext {
211 fn envelope_to_state(envelope: IgmpEnvelope) -> Self::ContextState;
213}
214
215impl IgmpSerializationContext for NoOpSerializationContext {
216 fn envelope_to_state(_envelope: IgmpEnvelope) -> Self::ContextState {
217 ()
218 }
219}
220
221impl<B, M: MessageType<B>> NestablePacketBuilder for IgmpPacketBuilder<B, M>
222where
223 M::VariableBody: IgmpNonEmptyBody,
224{
225 fn constraints(&self) -> PacketConstraints {
226 PacketConstraints::new(total_header_size::<M::FixedHeader>(), 0, 0, core::usize::MAX)
227 }
228}
229
230impl<B, M: MessageType<B>, C: IgmpSerializationContext> PacketBuilder<C> for IgmpPacketBuilder<B, M>
231where
232 M::VariableBody: IgmpNonEmptyBody,
233{
234 fn context_state(&self) -> C::ContextState {
235 C::envelope_to_state(IgmpEnvelope)
236 }
237
238 fn serialize(
239 &self,
240 _context: &mut C,
241 target: &mut SerializeTarget<'_>,
242 message_body: FragmentedBytesMut<'_, '_>,
243 ) {
244 self.serialize_headers(target.header, message_body);
245 }
246}
247
248#[derive(Default, Debug, IntoBytes, KnownLayout, FromBytes, Immutable, Unaligned)]
256#[repr(C)]
257pub struct HeaderPrefix {
258 msg_type: u8,
259 max_resp_code: u8,
266 checksum: [u8; 2],
267}
268
269impl HeaderPrefix {
270 fn set_msg_type<T: Into<u8>>(&mut self, msg_type: T) {
271 self.msg_type = msg_type.into();
272 }
273}
274
275#[derive(Debug)]
281pub struct IgmpMessage<B: SplitByteSlice, M: MessageType<B>> {
282 prefix: Ref<B, HeaderPrefix>,
283 header: Ref<B, M::FixedHeader>,
284 body: M::VariableBody,
285}
286
287impl<B: SplitByteSlice, M: MessageType<B>> IgmpMessage<B, M> {
288 pub fn builder(&self) -> IgmpPacketBuilder<B, M> {
290 IgmpPacketBuilder::new_with_resp_time(*self.header, self.max_response_time())
291 }
292
293 pub fn max_response_time(&self) -> M::MaxRespTime {
295 M::MaxRespTime::from_code(self.prefix.max_resp_code)
296 }
297
298 pub fn header(&self) -> &M::FixedHeader {
300 &self.header
301 }
302
303 pub fn body(&self) -> &M::VariableBody {
305 &self.body
306 }
307}
308
309fn compute_checksum_fragmented<BB: packet::Fragment>(
310 header_prefix: &HeaderPrefix,
311 header: &[u8],
312 body: &FragmentedByteSlice<'_, BB>,
313) -> [u8; 2] {
314 let mut c = Checksum::new();
315 c.add_bytes(&[header_prefix.msg_type, header_prefix.max_resp_code]);
316 c.add_bytes(&header_prefix.checksum);
317 c.add_bytes(header);
318 for p in body.iter_fragments() {
319 c.add_bytes(p);
320 }
321 c.checksum()
322}
323
324impl<B: SplitByteSlice, M: MessageType<B>> IgmpMessage<B, M> {
325 fn compute_checksum(header_prefix: &HeaderPrefix, header: &[u8], body: &[u8]) -> [u8; 2] {
326 let mut body = [body];
327 compute_checksum_fragmented(header_prefix, header, &body.as_fragmented_byte_slice())
328 }
329}
330
331impl<B: SplitByteSlice, M: MessageType<B, FixedHeader = Ipv4Addr>> IgmpMessage<B, M> {
332 pub fn group_addr(&self) -> Ipv4Addr {
334 *self.header
335 }
336}
337
338impl<B: SplitByteSlice, M: MessageType<B>> ParsablePacket<B, ()> for IgmpMessage<B, M> {
339 type Error = ParseError;
340
341 fn parse_metadata(&self) -> ParseMetadata {
342 let header_len = Ref::bytes(&self.prefix).len() + Ref::bytes(&self.header).len();
343 ParseMetadata::from_packet(header_len, M::body_bytes(&self.body).len(), 0)
344 }
345
346 fn parse<BV: BufferView<B>>(mut buffer: BV, _args: ()) -> Result<Self, ParseError> {
347 let prefix = buffer
348 .take_obj_front::<HeaderPrefix>()
349 .ok_or_else(debug_err_fn!(ParseError::Format, "too few bytes for header prefix"))?;
350
351 let header = buffer
352 .take_obj_front::<M::FixedHeader>()
353 .ok_or_else(debug_err_fn!(ParseError::Format, "too few bytes for header"))?;
354
355 let checksum = Self::compute_checksum(&prefix, &Ref::bytes(&header), buffer.as_ref());
356 if checksum != [0, 0] {
357 return debug_err!(
358 Err(ParseError::Checksum),
359 "invalid checksum, got 0x{:x?}",
360 prefix.checksum
361 );
362 }
363
364 if prefix.msg_type != M::TYPE.into() {
365 return debug_err!(Err(ParseError::NotExpected), "unexpected message type");
366 }
367
368 let body = M::parse_body(&header, buffer)?;
369
370 Ok(IgmpMessage { prefix, header, body })
371 }
372}
373
374pub fn peek_message_type<MessageType: TryFrom<u8>>(
392 bytes: &[u8],
393) -> Result<(MessageType, bool), ParseError> {
394 let long_message =
397 bytes.len() > (core::mem::size_of::<HeaderPrefix>() + core::mem::size_of::<Ipv4Addr>());
398 let (header, _) = Ref::<_, HeaderPrefix>::from_prefix(bytes).map_err(Into::into).map_err(
399 |_: zerocopy::SizeError<_, _>| debug_err!(ParseError::Format, "too few bytes for header"),
400 )?;
401 let msg_type = MessageType::try_from(header.msg_type).map_err(|_| {
402 debug_err!(ParseError::NotSupported, "unrecognized message type: {:x}", header.msg_type,)
403 })?;
404 Ok((msg_type, long_message))
405}
406
407#[cfg(test)]
408mod tests {
409
410 use packet::{NestablePacketBuilder as _, NoOpSerializationContext, ParseBuffer, Serializer};
411
412 use super::*;
413 use crate::igmp::messages::*;
414 use crate::ip::Ipv4Proto;
415 use crate::ipv4::options::Ipv4Option;
416 use crate::ipv4::{Ipv4Header, Ipv4Packet, Ipv4PacketBuilder, Ipv4PacketBuilderWithOptions};
417
418 fn serialize_to_bytes<
419 B: SplitByteSlice + Debug,
420 M: MessageType<B, VariableBody = ()> + Debug,
421 >(
422 igmp: &IgmpMessage<B, M>,
423 src_ip: Ipv4Addr,
424 dst_ip: Ipv4Addr,
425 ) -> Vec<u8> {
426 let ipv4 = Ipv4PacketBuilder::new(src_ip, dst_ip, 1, Ipv4Proto::Igmp);
427
428 Ipv4PacketBuilderWithOptions::new(ipv4, &[Ipv4Option::RouterAlert { data: 0 }])
429 .unwrap()
430 .wrap_body(igmp.builder().into_serializer())
431 .serialize_vec_outer(&mut NoOpSerializationContext)
432 .unwrap()
433 .as_ref()
434 .to_vec()
435 }
436
437 fn test_parse_and_serialize<
438 M: for<'a> MessageType<&'a [u8], VariableBody = ()> + Debug,
439 F: for<'a> FnOnce(&Ipv4Packet<&'a [u8]>),
440 G: for<'a> FnOnce(&IgmpMessage<&'a [u8], M>),
441 >(
442 mut pkt: &[u8],
443 check_ip: F,
444 check_igmp: G,
445 ) {
446 let orig_req = pkt;
447
448 let ip = pkt.parse_with::<_, Ipv4Packet<_>>(()).unwrap();
449 let src_ip = ip.src_ip();
450 let dst_ip = ip.dst_ip();
451 check_ip(&ip);
452 let mut req: &[u8] = pkt;
453 let igmp = req.parse_with::<_, IgmpMessage<_, M>>(()).unwrap();
454 check_igmp(&igmp);
455
456 let data = serialize_to_bytes(&igmp, src_ip, dst_ip);
457 assert_eq!(&data[..], orig_req);
458 }
459
460 #[test]
466 fn test_parse_and_serialize_igmpv2_report_with_options() {
467 use crate::testdata::igmpv2_membership::report::*;
468 test_parse_and_serialize::<IgmpMembershipReportV2, _, _>(
469 IP_PACKET_BYTES,
470 |ip| {
471 assert_eq!(ip.ttl(), 1);
472 assert_eq!(ip.iter_options().count(), 1);
473 let option = ip.iter_options().next().unwrap();
474 assert_eq!(option, Ipv4Option::RouterAlert { data: 0 });
475 assert_eq!(ip.header_len(), 24);
476 assert_eq!(ip.src_ip(), SOURCE);
477 assert_eq!(ip.dst_ip(), MULTICAST);
478 },
479 |igmp| {
480 assert_eq!(*igmp.header, MULTICAST);
481 },
482 )
483 }
484
485 #[test]
486 fn test_parse_and_serialize_igmpv2_query_with_options() {
487 use crate::testdata::igmpv2_membership::query::*;
488 test_parse_and_serialize::<IgmpMembershipQueryV2, _, _>(
489 IP_PACKET_BYTES,
490 |ip| {
491 assert_eq!(ip.ttl(), 1);
492 assert_eq!(ip.iter_options().count(), 1);
493 let option = ip.iter_options().next().unwrap();
494 assert_eq!(option, Ipv4Option::RouterAlert { data: 0 });
495 assert_eq!(ip.header_len(), 24);
496 assert_eq!(ip.src_ip(), SOURCE);
497 assert_eq!(ip.dst_ip(), MULTICAST);
498 },
499 |igmp| {
500 assert_eq!(*igmp.header, MULTICAST);
501 },
502 )
503 }
504
505 #[test]
506 fn test_parse_and_serialize_igmpv2_leave_with_options() {
507 use crate::testdata::igmpv2_membership::leave::*;
508 test_parse_and_serialize::<IgmpLeaveGroup, _, _>(
509 IP_PACKET_BYTES,
510 |ip| {
511 assert_eq!(ip.ttl(), 1);
512 assert_eq!(ip.iter_options().count(), 1);
513 let option = ip.iter_options().next().unwrap();
514 assert_eq!(option, Ipv4Option::RouterAlert { data: 0 });
515 assert_eq!(ip.header_len(), 24);
516 assert_eq!(ip.src_ip(), SOURCE);
517 assert_eq!(ip.dst_ip(), DESTINATION);
518 },
519 |igmp| {
520 assert_eq!(*igmp.header, MULTICAST);
521 },
522 )
523 }
524}