scapy icon indicating copy to clipboard operation
scapy copied to clipboard

Provide a diff feature that refine packet comparison

Open alxroyer-thales opened this issue 5 months ago • 3 comments

Draft PR for #4739.

Initiated as a draft PR for preliminary discussions before an official delivery (not squashed yet).

To be discussed:

  • Chaged currently based on v2.6.1 => shall I align on master? or useful to deliver for a version before?

Checklist:

  • [x] If you are new to Scapy: I have checked CONTRIBUTING.md (esp. section submitting-pull-requests)
  • [ ] I squashed commits belonging together
  • [x] I added unit tests or explained why they are not relevant
  • [ ] I executed the regression tests (using cd test && ./run_tests or tox)
  • [x] If the PR is still not finished, please create a Draft Pull Request

alxroyer-thales avatar Jun 26 '25 10:06 alxroyer-thales

Could you show a demo of what it does in action? Thanks.

gpotter2 avatar Jun 26 '25 14:06 gpotter2

Could you show a demo of what it does in action? Thanks.

Sure. It would be worth adding faithful documentation in the end if you validate this proposal.

To reply quickly, here are extracts of outputs from the 'diff.uts' unit test I've proposed.

Comparison of packet with exact match

###(005)=[passed] 005) Compare a1 with a2 => PacketCmp.compare() returns ``True``.

>>> cmp = PacketCmp(a1, a2)
>>> assert cmp.compare(log_success_level=logging.INFO) is True
A.k: 1 (compared) == 1 (expected)
A.t: 1751014222 (compared) == 1751014222 (expected)


###(006)=[passed] 006) The comparison instance holds 2 successful exact comparisons, and no errors.

>>> print(f"diffs: {cmp.diffs!r}")
diffs: [<PacketCmp.Diff 'A.k: 1 (compared) == 1 (expected)'>, <PacketCmp.Diff 'A.t: 1751014222 (compared) == 1751014222 (expected)'>]
>>> assert len(cmp.diffs) == 2
>>> check_diff(cmp.diffs[0], "k", error=False, approx=False)
>>> check_diff(cmp.diffs[1], "t", error=False, approx=False)
>>> print(f"errors: {cmp.errors!r}")
errors: []
>>> assert len(cmp.errors) == 0

Memo: The k field is a simple ByteField, and t is an ApproximateField (see step 002).

The lines after the .compare() (step 005) are loggings activated by the log_success_level=logging.INFO configuration.

The PacketCmp object holds two lists in the end:

  • diffs: All PacketCmp.Diff objects, successful comparisons included.
  • errors: diffs` filtered on errors.

Successful comparison with approximation

###(009)=[passed] 009) Compare a1 with a2 => PacketCmp.compare() returns ``True``.

>>> cmp = PacketCmp(a1, a2)
>>> assert cmp.compare(log_success_level=logging.INFO) is True
A.t: 1751014222 (compared) ~= (delta: 1.0 <= tolerance: 2.0) 1751014221 (expected) -- comparison restarted
A.k: 1 (compared) == 1 (expected)
A.t: 1751014222 (compared) == 1751014222 (expected)


###(010)=[passed] 010) The comparison instance holds 3 diffs, the 1st being the approximation that restarted the comparison, and no errors.

>>> print(f"diffs: {cmp.diffs!r}")
diffs: [<PacketCmp.Diff 'A.t: 1751014222 (compared) ~= (delta: 1.0 <= tolerance: 2.0) 1751014221 (expected) -- comparison restarted'>, <PacketCmp.Diff 'A.k: 1 (compared) == 1 (expected)'>, <PacketCmp.Diff 'A.t: 1751014222 (compared) == 1751014222 (expected)'>]
>>> assert len(cmp.diffs) == 3
>>> check_diff(cmp.diffs[0], "t", error=False, approx=True)
>>> check_diff(cmp.diffs[1], "k", error=False, approx=False)
>>> check_diff(cmp.diffs[2], "t", error=False, approx=False)
>>> print(f"errors: {cmp.errors!r}")
errors: []
>>> assert len(cmp.errors) == 0

Comparison failure with aproximate field out of tolerance

###(013)=[passed] 013) Compare a1 with a2 => PacketCmp.compare() returns ``False``.

>>> cmp = PacketCmp(a1, a2)
>>> assert cmp.compare(log_success_level=logging.INFO) is False
A.k: 1 (compared) == 1 (expected)
A.t: 1751014222 (compared) != (delta: 3.0 > tolerance: 2.0) 1751014219 (expected) -- Mismatching values


###(014)=[passed] 014) The comparison instance holds 2 diffs, 1 error among them.

>>> print(f"diffs: {cmp.diffs!r}")
diffs: [<PacketCmp.Diff 'A.k: 1 (compared) == 1 (expected)'>, <PacketCmp.Diff 'A.t: 1751014222 (compared) != (delta: 3.0 > tolerance: 2.0) 1751014219 (expected) -- Mismatching values'>]
>>> assert len(cmp.diffs) == 2
>>> check_diff(cmp.diffs[0], "k", error=False, approx=False)
>>> check_diff(cmp.diffs[1], "t", error=True, approx=True)
>>> print(f"errors: {cmp.errors!r}")
errors: [<PacketCmp.Diff 'A.t: 1751014222 (compared) != (delta: 3.0 > tolerance: 2.0) 1751014219 (expected) -- Mismatching values'>]
>>> check_diff(cmp.errors[0], "t", error=True, approx=True)

alxroyer-thales avatar Jun 27 '25 09:06 alxroyer-thales

Codecov Report

Attention: Patch coverage is 78.22581% with 54 lines in your changes missing coverage. Please review.

Project coverage is 81.02%. Comparing base (8e08cbf) to head (768e66b). Report is 127 commits behind head on master.

Files with missing lines Patch % Lines
scapy/diff.py 77.29% 52 Missing :warning:
scapy/fields.py 88.88% 2 Missing :warning:
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #4779      +/-   ##
==========================================
- Coverage   81.62%   81.02%   -0.61%     
==========================================
  Files         358      366       +8     
  Lines       85652    89275    +3623     
==========================================
+ Hits        69915    72336    +2421     
- Misses      15737    16939    +1202     
Files with missing lines Coverage Δ
scapy/all.py 100.00% <100.00%> (ø)
scapy/fields.py 92.79% <88.88%> (+0.02%) :arrow_up:
scapy/diff.py 77.29% <77.29%> (ø)

... and 102 files with indirect coverage changes

:rocket: New features to boost your workflow:
  • :snowflake: Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

codecov[bot] avatar Jul 01 '25 07:07 codecov[bot]