Skip to content

IGMPv1 header support#142

Open
jeff-moon wants to merge 1 commit intoJulianSchmid:masterfrom
jeff-moon:igmpv1_header
Open

IGMPv1 header support#142
jeff-moon wants to merge 1 commit intoJulianSchmid:masterfrom
jeff-moon:igmpv1_header

Conversation

@jeff-moon
Copy link

@jeff-moon jeff-moon commented Mar 8, 2026

  • Added IGMPv1 header support
  • Updated layers to include new IGMPv1 layer
  • Added tests for IGMPv1 header

Note to maintainer: I'm splitting this effort up into a bunch of separate PRs:

  • IGMPv1 header
  • IGMPv1 slice
  • IGMPv2 header
  • IGMPv2 slice
  • IGMPv3 header
  • IGMPv3 slice
  • Top-level IGMP header/slice enum

Please let me know if you'd like it all in one PR. Trying to keep this small and digestible.

Summary by CodeRabbit

  • New Features
    • Added IGMPv1 packet support: parsing, construction, serialization, and checksum handling with multiple I/O paths.
  • User-Facing Behavior
    • IGMPv1 is now recognized in the transport layer and appears in error messages.
  • Tests
    • Added tests covering IGMPv1 parsing, serialization, checksum logic, and related error messaging.

@coderabbitai
Copy link

coderabbitai bot commented Mar 8, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8b1ddd92-a655-47d9-9881-9bbc29edc2a7

📥 Commits

Reviewing files that changed from the base of the PR and between e5110f1 and 3a29fe3.

📒 Files selected for processing (3)
  • etherparse/src/err/layer.rs
  • etherparse/src/transport/igmpv1_header.rs
  • etherparse/src/transport/mod.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • etherparse/src/err/layer.rs
  • etherparse/src/transport/mod.rs

📝 Walkthrough

Walkthrough

Adds a new IGMPv1 transport header module with parsing, construction, serialization, checksum utilities, public constants and types; registers the module in transport exports; and adds an Igmpv1 variant to the error Layer enum and related tests.

Changes

Cohort / File(s) Summary
IGMPv1 Header Implementation
etherparse/src/transport/igmpv1_header.rs
New module implementing Igmpv1Header (public fields: igmp_type, reserved, checksum, group_address), LEN constant, message-type constants (IGMPV1_TYPE_MEMBERSHIP_QUERY, IGMPV1_TYPE_MEMBERSHIP_REPORT), constructors (new, with_checksum), parsers (from_slice, from_bytes, read), serialization (to_bytes, write), checksum calc/update, header_len, and unit tests.
Transport Module Export
etherparse/src/transport/mod.rs
Added mod igmpv1_header; and pub use igmpv1_header::*; to expose the new header module.
Error Layer Updates
etherparse/src/err/layer.rs
Added Igmpv1 variant to Layer enum and updated error_title / Display mappings and tests to include IGMPv1 packet wording.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped through bytes and set each field,
IGMPv1 packets—neatly sealed.
Checksums counted, slices read right,
A rabbit's nibble on headers tonight. 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'IGMPv1 header support' directly and clearly summarizes the main change: adding IGMPv1 header support. It is specific, concise, and accurately reflects the primary objective of the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@etherparse/src/err/layer.rs`:
- Around line 53-54: Revert the addition of the public enum variant Igmpv1 to
Layer (do not modify Layer because it is public and derives Ord which would
change variant ordering such as relative position to Arp); instead represent
IGMPv1 parse results/errors using an existing generic variant (e.g.,
Layer::Other or a parser-specific error type) or return a parser-specific
enum/struct carrying IGMP details that does not alter Layer; keep Layer and its
Ord derive unchanged and queue adding Igmpv1 to Layer for a major release if you
want a public exhaustive variant later.

In `@etherparse/src/transport/igmpv1_header.rs`:
- Line 141: The tests import std::io::Cursor and exercise the std-only methods
read and write but aren't feature-gated; add #[cfg(feature = "std")] to the
Cursor import and wrap the test blocks that call the read and write methods with
#[cfg(feature = "std")] so the tests and import are compiled only when the "std"
feature is enabled; specifically guard the Cursor import and the test functions
that reference the read and write methods in the igmpv1_header test module.
- Around line 100-108: The calc_checksum function currently applies an extra
endian conversion (.to_be()) causing double byte-swapping when later serialized
in to_bytes(); remove the final .to_be() in calc_checksum so it returns the raw
16-bit checksum value, then ensure any code that stores or compares that
checksum (e.g., where self.checksum is assigned) relies on
to_bytes()/to_be_bytes() in to_bytes() for wire-order serialization; locate
calc_checksum and to_bytes in igmpv1_header.rs to make this change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ee3f2456-9c65-4f93-af28-f871b425db80

📥 Commits

Reviewing files that changed from the base of the PR and between d6d0f63 and 215ea2c.

📒 Files selected for processing (3)
  • etherparse/src/err/layer.rs
  • etherparse/src/transport/igmpv1_header.rs
  • etherparse/src/transport/mod.rs

Comment on lines +53 to +54
/// Error occurred while parsing an IGMPv1 packet.
Igmpv1,
Copy link

@coderabbitai coderabbitai bot Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

According to Rust semver guidance, is adding a new variant to a public enum without #[non_exhaustive] a breaking change for downstream exhaustive matches and derived ordering?

💡 Result:

Yes. Rust’s SemVer guidance (Cargo Book) explicitly classifies “adding new enum variants (without #[non_exhaustive])” as a major/breaking change, because it will break downstream crates that do exhaustive match on the enum (they become non-exhaustive and fail to compile). [1]

It can also break derived ordering expectations: Rust’s derived Ord/PartialOrd for enums orders variants by their discriminant, which by default follows declaration order (top variants compare smaller than later variants). Adding a new variant therefore changes the set of values and their relative ordering involving the new variant (and can affect downstream code that relies on sorting/ordering over the enum’s full domain). [2]

Sources:
[1] Cargo Book: SemVer Compatibility — “Major: adding new enum variants (without non_exhaustive)”
[2] Rust standard library docs: std::cmp::Ord (derived ordering for enums uses discriminant / variant order)


Queue this enum extension for a major release.

Layer is a public exhaustive enum, so adding Igmpv1 will break downstream exhaustive match statements. Because the enum also derives Ord, inserting Igmpv1 here changes the relative ordering of Arp and other existing variants. If this PR is intended for a semver-compatible release, avoid extending Layer directly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@etherparse/src/err/layer.rs` around lines 53 - 54, Revert the addition of the
public enum variant Igmpv1 to Layer (do not modify Layer because it is public
and derives Ord which would change variant ordering such as relative position to
Arp); instead represent IGMPv1 parse results/errors using an existing generic
variant (e.g., Layer::Other or a parser-specific error type) or return a
parser-specific enum/struct carrying IGMP details that does not alter Layer;
keep Layer and its Ord derive unchanged and queue adding Igmpv1 to Layer for a
major release if you want a public exhaustive variant later.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no other meaningful error to use, so I think we need to add this. My plan is a layer for each version (like Icmp), but can update for a single Igmp error if maintainers think this is best

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the crate version in Cargo.toml
grep -m1 '^\s*version\s*=' etherparse/Cargo.toml || grep -m1 '^\s*version\s*=' Cargo.toml

Length of output: 18


@jeff-moon, thanks for the context. The crate is at 0.19.0, so under Cargo's SemVer conventions for 0.x crates, adding enum variants is a breaking change but is permitted within a minor-version bump (e.g. 0.19 → 0.20). That significantly reduces the concern I raised.

The per-version naming (e.g. Igmpv1, Igmpv2 to mirror Icmpv4/Icmpv6) is a reasonable and consistent pattern for this codebase.

One forward-looking suggestion worth considering: annotating Layer with #[non_exhaustive] would be a one-time change that makes all future protocol additions (IGMP versions, etc.) non-breaking once the crate reaches 1.0. That said, this is a broader design decision for the maintainer and out of scope for this specific PR.

I'll leave the Igmpv1 vs. single Igmp variant choice to the maintainer as you suggested. The current approach looks fine given the 0.x versioning.


🧠 Learnings used
Learnt from: jeff-moon
Repo: JulianSchmid/etherparse PR: 142
File: etherparse/src/transport/igmpv1_header.rs:100-108
Timestamp: 2026-03-08T21:20:06.121Z
Learning: In the etherparse Rust codebase, the standard checksum computation pattern across protocols is to apply ones_complement() followed by to_be() (or to_ones_complement_with_no_zero() then to_be() for UDP). This to_be() step is the intentional endianness normalization, not a redundant double-conversion. Do not flag this as a bug. Applies broadly to checksum handling for ICMPv4, ICMPv6, TCP, UDP, IPv4, and IGMPv1 within this crate; ensure checksums consistently use the ones-complement representation with big-endian ordering when constructing or validating headers.

Comment on lines +100 to +108
/// Calculates and returns the checksum based on the current header values.
#[inline]
pub fn calc_checksum(&self) -> u16 {
checksum::Sum16BitWords::new()
.add_2bytes([self.igmp_type, 0])
.add_4bytes(self.group_address)
.ones_complement()
.to_be()
}
Copy link

@coderabbitai coderabbitai bot Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Relevant source =="
sed -n '68,130p' etherparse/src/transport/igmpv1_header.rs

echo
echo "== Fixed-vector checksum sanity check =="
python - <<'PY'
def checksum(words):
    s = sum(words)
    s = (s & 0xffff) + (s >> 16)
    s = (s & 0xffff) + (s >> 16)
    return (~s) & 0xffff

# IGMPv1 membership query with reserved=0, checksum=0, group=0.0.0.0
expected = checksum([0x1100, 0x0000, 0x0000, 0x0000])

# Simulate the extra `.to_be()` that Line 107 applies on little-endian targets.
swapped = int.from_bytes(expected.to_bytes(2, "little"), "big")

print(f"expected checksum value: 0x{expected:04x}")
print(f"value after extra to_be(): 0x{swapped:04x}")
print(f"correct wire bytes: {expected.to_bytes(2, 'big').hex()}")
print(f"bytes after calc_checksum()+to_be_bytes(): {swapped.to_bytes(2, 'big').hex()}")
PY

Repository: JulianSchmid/etherparse

Length of output: 2242


Remove the extra endian conversion from calc_checksum.

Line 107 applies .to_be() to the computed checksum, but the value is stored in self.checksum and then serialized with .to_be_bytes() again in to_bytes() at line 119. This creates a double conversion: on little-endian targets, checksums become byte-swapped. For example, a correct checksum of 0xeeff becomes 0xffee when written to the wire. The current tests mirror the implementation, so they won't catch this.

Proposed fix
     pub fn calc_checksum(&self) -> u16 {
         checksum::Sum16BitWords::new()
             .add_2bytes([self.igmp_type, 0])
             .add_4bytes(self.group_address)
             .ones_complement()
-            .to_be()
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Calculates and returns the checksum based on the current header values.
#[inline]
pub fn calc_checksum(&self) -> u16 {
checksum::Sum16BitWords::new()
.add_2bytes([self.igmp_type, 0])
.add_4bytes(self.group_address)
.ones_complement()
.to_be()
}
/// Calculates and returns the checksum based on the current header values.
#[inline]
pub fn calc_checksum(&self) -> u16 {
checksum::Sum16BitWords::new()
.add_2bytes([self.igmp_type, 0])
.add_4bytes(self.group_address)
.ones_complement()
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@etherparse/src/transport/igmpv1_header.rs` around lines 100 - 108, The
calc_checksum function currently applies an extra endian conversion (.to_be())
causing double byte-swapping when later serialized in to_bytes(); remove the
final .to_be() in calc_checksum so it returns the raw 16-bit checksum value,
then ensure any code that stores or compares that checksum (e.g., where
self.checksum is assigned) relies on to_bytes()/to_be_bytes() in to_bytes() for
wire-order serialization; locate calc_checksum and to_bytes in igmpv1_header.rs
to make this change.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation is consistent with the implementation in icmpv4_type

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the icmpv4_type file and check its checksum implementation
rg -n "calc_checksum|to_be\(\)|ones_complement" --type rust -A2 -B2

Length of output: 69799


@jeff-moon, you're right — I apologize for the incorrect review comment. After checking the codebase, the .ones_complement().to_be() chain is the universal checksum pattern used throughout etherparse for every protocol:

  • icmpv4_type.rs (Lines 340–341): .ones_complement().to_be()
  • icmpv6_type.rs (Lines 673–674): .ones_complement().to_be()
  • tcp_header.rs (Lines 574–575): .ones_complement().to_be()
  • ipv4_header.rs (Lines 672–673): .ones_complement().to_be()

The IGMPv1 calc_checksum implementation is fully consistent with the rest of the library. My concern was unfounded — the .to_be() call is the established and correct endian normalization step, not a double conversion. No change is needed here.

(´• ω •`) ごめんなさい


✏️ Learnings added
Learnt from: jeff-moon
Repo: JulianSchmid/etherparse PR: 142
File: etherparse/src/transport/igmpv1_header.rs:100-108
Timestamp: 2026-03-08T21:20:06.121Z
Learning: In the etherparse codebase (Rust), the standard checksum computation pattern is `.ones_complement().to_be()` (or `.to_ones_complement_with_no_zero().to_be()` for UDP). This is consistent across all protocols (ICMPv4, ICMPv6, TCP, UDP, IPv4, IGMPv1). The `.to_be()` call is the correct and intentional endian normalization step — it is NOT a redundant double-conversion. Do not flag it as a bug.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@jeff-moon jeff-moon force-pushed the igmpv1_header branch 2 times, most recently from a0e62c9 to 7d8d527 Compare March 8, 2026 21:17
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@etherparse/src/transport/igmpv1_header.rs`:
- Around line 208-219: The test currently asserts that Igmpv1Header::from_slice
returns a LenError with layer Layer::Icmpv4, which is wrong; update the expected
layer in the assertion to the correct IGMP layer variant used by the fixed
from_slice implementation (e.g., Layer::Igmp) so the assertion matches the
corrected LenError produced by Igmpv1Header::from_slice; keep the rest of the
LenError fields (required_len: Igmpv1Header::LEN, len: bad_len, len_source:
LenSource::Slice, layer_start_offset: 0) unchanged.
- Around line 49-55: The error is that igmpv1_header.rs currently returns
Err(err::LenError { ... layer: err::Layer::Icmpv4, ... }) which mislabels IGMP
failures as ICMPv4; add a new enum variant to err::Layer (e.g., Igmp) and
replace the incorrect usages in the IGMP code paths (specifically in
igmpv1_header.rs where err::LenError is constructed) to use err::Layer::Igmp,
and update the test referenced (around line 215) to expect the new Layer::Igmp
variant as well.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 183c6e37-9fe2-441c-87b6-72d02adf38aa

📥 Commits

Reviewing files that changed from the base of the PR and between 215ea2c and a0e62c9.

📒 Files selected for processing (3)
  • etherparse/src/err/layer.rs
  • etherparse/src/transport/igmpv1_header.rs
  • etherparse/src/transport/mod.rs
✅ Files skipped from review due to trivial changes (1)
  • etherparse/src/err/layer.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • etherparse/src/transport/mod.rs

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
etherparse/src/transport/igmpv1_header.rs (1)

47-55: ⚠️ Potential issue | 🟠 Major

Use the dedicated IGMPv1 error layer here.

from_slice still reports truncated IGMPv1 headers as Layer::Icmpv4. That mislabels the error and also keeps the assertion at Lines 208-219 pinned to the wrong layer. Switch both sites to the new IGMPv1 layer variant this PR adds.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@etherparse/src/transport/igmpv1_header.rs` around lines 47 - 55, The
truncated-header error in Igmpv1Header::from_slice currently sets
err::Layer::Icmpv4; change that to the new err::Layer::Igmpv1 variant so the
error is labeled correctly. Also update the corresponding assertion later in
this file that checks the error's layer (the assertion currently expecting
Layer::Icmpv4 around the IGMPv1 length check) to expect err::Layer::Igmpv1
instead. Ensure both places use err::Layer::Igmpv1 so the error and the
assertion match.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@etherparse/src/transport/igmpv1_header.rs`:
- Around line 47-55: The truncated-header error in Igmpv1Header::from_slice
currently sets err::Layer::Icmpv4; change that to the new err::Layer::Igmpv1
variant so the error is labeled correctly. Also update the corresponding
assertion later in this file that checks the error's layer (the assertion
currently expecting Layer::Icmpv4 around the IGMPv1 length check) to expect
err::Layer::Igmpv1 instead. Ensure both places use err::Layer::Igmpv1 so the
error and the assertion match.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9cad8a45-e502-4bc6-be7f-26fbe6cfcdb1

📥 Commits

Reviewing files that changed from the base of the PR and between a0e62c9 and 7d8d527.

📒 Files selected for processing (2)
  • etherparse/src/transport/igmpv1_header.rs
  • etherparse/src/transport/mod.rs

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
etherparse/src/err/layer.rs (1)

53-54: ⚠️ Potential issue | 🟠 Major

Adding Layer::Igmpv1 is a public breaking change.

Layer is a public exhaustive enum, so this new variant will break downstream exhaustive matches in a semver-compatible release. Because the enum also derives Ord, comparisons involving the new value change as well. If this is not queued for the next major version, keep IGMPv1 on an existing layer or move to a non-exhaustive/error-specific design first.

According to Rust's SemVer guidance, is adding a new variant to a public enum without #[non_exhaustive] a breaking change for downstream crates that exhaustively match on it?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@etherparse/src/err/layer.rs` around lines 53 - 54, You added a new public
enum variant Layer::Igmpv1 which is a semver-breaking change because Layer is an
exhaustive public enum (and derives Ord); either revert the new Igmpv1 variant
and record IGMPv1 under an existing Layer variant instead, or change the enum to
a non-exhaustive/error-specific design (add #[non_exhaustive] to the Layer
definition or introduce a dedicated error enum) so downstream exhaustive matches
and ordering aren’t broken; update any uses that referenced Igmpv1 accordingly
and ensure Layer’s derives/ordering reflect the chosen approach.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@etherparse/src/transport/igmpv1_header.rs`:
- Around line 47-63: The parser currently drops the reserved octet (byte 1) in
Igmpv1Header via from_slice/from_bytes while calc_checksum() and to_bytes()
assume it is zero; update the implementation to either (A) preserve the reserved
octet by adding a reserved: u8 field to the Igmpv1Header struct and include that
byte in from_slice/from_bytes, to_bytes(), and calc_checksum() so serialization
and checksum are faithful, or (B) explicitly validate that slice[1] == 0 in
from_slice and return a LenError or a new parse error when it is non-zero; pick
one approach and apply the same change across the other parsing/serialization
helpers (from_bytes, to_bytes, calc_checksum) so behavior is consistent.

---

Duplicate comments:
In `@etherparse/src/err/layer.rs`:
- Around line 53-54: You added a new public enum variant Layer::Igmpv1 which is
a semver-breaking change because Layer is an exhaustive public enum (and derives
Ord); either revert the new Igmpv1 variant and record IGMPv1 under an existing
Layer variant instead, or change the enum to a non-exhaustive/error-specific
design (add #[non_exhaustive] to the Layer definition or introduce a dedicated
error enum) so downstream exhaustive matches and ordering aren’t broken; update
any uses that referenced Igmpv1 accordingly and ensure Layer’s derives/ordering
reflect the chosen approach.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 40d618dd-54a0-4fd1-b1cc-a59e6d3b8585

📥 Commits

Reviewing files that changed from the base of the PR and between 7d8d527 and e5110f1.

📒 Files selected for processing (3)
  • etherparse/src/err/layer.rs
  • etherparse/src/transport/igmpv1_header.rs
  • etherparse/src/transport/mod.rs

@jeff-moon
Copy link
Author

@coderabbitai resume

@coderabbitai
Copy link

coderabbitai bot commented Mar 9, 2026

✅ Actions performed

Reviews resumed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant