Repository navigation
Conversation
…ts the round trip Review of 7503542 (zero-aska, item 1, blocking): con-leche and con-ron ran out of fuel on Proofs/RoundTrip.lean parseFixed_printFixed, so `lake comparator --paranoid` failed bar item 3. Reproduced here: both kernels stop with "fuel exhausted: whnfCore" on that theorem. Lean's own kernel diagnostics show why: after each simp step the kernel had to compare a reduced pair match against the next bind of the parser, and in doing so it unfolded parseFixed.match_3 2048 times and Option.bind about 7700 times, doubling with every field. parseFixed now reads its fields with five small readers that go on with the value and the rest of the input (withU8, withU16, withU32, withBytes, withCookie), in place of getU8, getU16, getU32, getBytes and getCookie and the pair matches after each bind. It accepts and refuses exactly the same inputs as before. Each step of parseFixed_printFixed is now a rewrite with one reader lemma (withU8_cons, withU16_be16, withU32_be32, withBytes_app, withCookie_app), and parseFixed_inv uses the matching _inv lemmas. All 13 theorems in comparator.json are now accepted by Lean's kernel, leanchecker-paranoid, lean4lean, nanoda, con-leche and con-ron, each in about a second. bar.toml names the new readers as code under proof. zero-aska is added to the spec authors next to abu-aska, since the files the spec hash covers change in this carry-forward. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
…e page Review of 7503542 (zero-aska, item 2, blocking; orda-aska's review on the same head; review 1 item 6): the request at README.md:31 was 147 bytes with the cookie at offset 133, so `getBytes 128` ran out of input and the tool answered `none`, while the page promised `ok:` with the same codes. The example is now the harness's own DISCOVER: 34 bytes of fields, 202 zero bytes for the rest of chaddr, sname and file, the cookie at offset 236, options 53 and 55, and the end byte, 250 bytes in all. Run exactly as the page writes it, the built tool answers `ok:` followed by the same 250 codes. The paragraph under it now says what the bytes are. tests/run_tests.py gains run_readme: it reads the example line from README.md, runs it, and fails unless the answer is `ok:` and the same codes. On the old page it reports the failure; on this one it passes. The record counts it on its own line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
Review 1 item 1 and the review of 7503542 (zero-aska): no theorem said that a parsed message is well formed, so a parser returning op 257, hlen 262 and option code 311 on a padded DISCOVER still met all 13 statements, and nothing proved the option 53 rule in general. New statement Spec.parse_wf (Spec.lean): parse bs = some m -> WF m, with WF the same predicate Spec.parse_print assumes. It is proved under the same name in Proofs/SpecProofs.lean from Dhcp.Proofs.parse_wf, which rests on three new lemmas in Proofs/RoundTrip.lean: parseFixed_wf (op 1 or 2, hlen at most 16, every number fits its bytes, the blocks hold 16, 64 and 128 bytes), parseOptionsGo_wf (every code read runs from 1 to 254, every value has at most 255 bytes), and check53_type (option 53 stands exactly once with one byte from 1 to 8). comparator.json names it, Proofs/Verify.lean prints its axioms, and all six kernels accept it. SPEC.md and the README name it in their tables and say what it covers: the message returned, not the input bytes on their own, and a parser that refuses everything also meets it, which Spec.parse_print rules out. What is not proved now splits acceptance (not proved) from refusal (proved only as far as Spec.parse_wf reaches). tests/run_tests.py checks the same property on every `ok:` answer the tool gives: the reprint has op 1 or 2, hlen at most 16, the cookie, codes 1 to 254, and option 53 exactly once with a value from 1 to 8. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
Review 1 items 2, 3 and 4, still open in the review of 7503542 (zero-aska): the page did not say that the parser takes an option area with no end byte (Dhcp/Parse.lean, the `[] => some []` case), though RFC 2131 section 4.1 says the last option must always be the end option; that it refuses any nonzero byte after the end byte (the `rest.all` test), where RFC 2132 section 3.2 says only that those bytes should be pad bytes; that option 52 overload and RFC 3396 joining of repeated options are not done, so a second option 53 in file gets past the exactly once rule while a type split over two items is refused; and that check53 refuses the registered message types 9 to 18. The review also asked that Spec.rejects_option_53_value_9 say that 9 is a registered type refused on purpose. The behaviour stays as it was; each reading is now a stated choice. SPEC.md has a new section, "Readings that depart from the RFCs", that quotes the RFC text for each, says what the parser does instead, and lists types 9 to 18 by name and RFC (3203, 4388, 6926, 7724). The option 53 and repeated option rules point to it. The README's What is not proved lists the same readings, and its table rows for Spec.rejects_option_53_value_9 and Spec.rejects_nonzero_after_end say that each pins a choice. The docstrings of those two statements in Spec.lean (and their copies in Proofs/SpecProofs.lean) say the same, and the header of Dhcp/Parse.lean names the overload and RFC 3396 readings. The statements themselves do not change. tests/run_tests.py gains run_choices: 17 cases, one or more per reading (no end byte, bytes after the end byte, option 52 with values 1, 2 and 3 and a second option 53 in file or sname, a type split over two items, a repeated option 55, and each of types 9 to 18), each with the verdict SPEC.md states. The harness checks that its oracle gives that verdict, then that the tool agrees; all 17 do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
…ckets, option area run Review 1 items 5, 7 and 8, and the review of 7503542 (zero-aska): tests/run_tests.py wrote a literal "- Named differences: none" that it never computed; the record had no "- Inputs:" line, so bar item 5 stayed OPEN; its random line mixed the well formed and raw runs as "part of 2700"; it named no Scapy version; and no generated input reached the option reader beyond well formed messages. - The "Named differences" line is gone. The record now says what the code does: the harness keeps no list of excused differences, and every disagreement is a failure. - The record leads with the four fields check.py reads: Oracle (the harness oracle and its Python version, with Scapy's version, and that no outside decoder judges any input), Seed, Inputs (every input the tool is given, 4104) and Failures (0), then the exact command. A table counts each part on its own line, with the well formed and raw random runs apart. - The ten Scapy packets are stored in tests/scapy-packets.txt with the Scapy version that built them (2.8.0). The cross check always feeds them, so CI, which has no Scapy, gives the same input count as this record; --rebuild-scapy builds the file again. bar.toml lists it with the test files. - A new run of 800 random option areas behind a fixed part and cookie, from its own generator (seed + 1, so the earlier runs keep their inputs), reaches the option reader's edges: option 53 missing, repeated, of length 0 or 2, or of any value; option 52 with values 0 to 4; a second option 53 in file or sname; pads between items; and seven endings (end byte, end and zeros, end and other bytes, no end, a lone code, an overrunning option, end then zero then a nonzero byte). The oracle takes 133 of them and turns away 667; the tool agrees on all 800. - The README's test paragraph and Re-check it step 2 describe the run as it now is, and What is not proved names the Wireshark dissectors, the plan's third oracle, which did not run either. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
Review of 7503542 (zero-aska, non-blocking notes; review 1 item 8): rows of the SPEC.md table such as "A message shorter than 240 bytes is refused | Spec.rejects_short_message" read as general rules, but each theorem covers one fixed input. And check.py left bar item 6 OPEN because the README status line said "in-progress" where it looks for "in progress". - SPEC.md and README.md: each of the eleven refusal rows now starts "Example, one input:" and names the input (for instance "the test DISCOVER cut to 239 bytes"). The text above each table says that these rows are proved for that input only, and that the first three rows (Spec.parse_print, Spec.print_parse, Spec.parse_wf) are the general ones. The test DISCOVER is named as the 250 byte README example. - Spec.lean and Proofs/SpecProofs.lean: the docstrings of the eleven refusal statements start the same way. The statements do not change. - README.md: "Status: in progress". bar.toml keeps status in-progress with no approval entry. The static bar check now shows item 5 PASS and item 6 PASS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
lake build printed 24 warnings from Proofs/RoundTrip.lean: if_pos and if_neg are deprecated in Lean 4.35 in favour of ite_eq_left and ite_eq_right, and parse_print passed an unused Option.bind_eq_bind to simp. The names are replaced and the unused argument dropped; the proofs are otherwise the same. A clean `lake build` now prints only the axiom lists from Proofs/Verify.lean, and all six kernels still accept the 14 theorems. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
… trust Follow-up to the soundness and readings commits, from a read of the whole page against the code: README "What is proved" now names the three lemmas under Spec.parse_wf, Re-check it step 3 says Verify.lean prints the axioms of the soundness theorem too, step 4 asks the reader to check the readings that depart from the RFCs, and What you must trust says SPEC.md lists where its reading departs from them. Two paragraphs are reflowed, and the header of Proofs/RoundTrip.lean lists parse_wf with the two round trips. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
The independent check of c35dc0b found that README.md said a code byte with no length byte is pinned by a refusal theorem on one input, and SPEC.md said there is one refusal theorem for each headline rule, but no theorem covered the lone code byte rule (SPEC.md, The options). A parser that equals parse everywhere except that it takes the test DISCOVER followed by a lone code byte met all 14 statements. Spec.rejects_code_last_byte is the harness's code-last-byte case byte for byte: the test fixed part and cookie, then 53 1 1 55, so the code of option 55 is the last byte. It is proved with decide +kernel, listed in comparator.json, and named in the SPEC.md and README.md tables. That parser now fails it. SPEC.md now lists the refusal rules the examples cover and says that the readings that take an input, such as an option area with no end byte, have no theorem. README.md What is not proved says the same. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
The independent check of c35dc0b, and the non-blocking note in the review of 7503542, found that WFFixed, WFOptions, WF, canonOptionsGo and canonOptions were listed as code under proof. The spec hash covers only the names on that list, so a change to their bodies would not void an approval. Neither the parser nor the printer uses them: they only give Spec.parse_print, Spec.print_parse and Spec.parse_wf their meaning, which bar item 4's checklist says the code list should not hold. They now live, unchanged, in Spec/Defs.lean, which bar.toml lists with the spec files, so the approval hash covers their text. They are gone from Dhcp/Value.lean and from the code list. Spec.lean and the proofs import the new file. With WF a spec definition, the statement walk reads its body and reaches the fields of Fixed and Message, so the code list now names those fields with the two structures. The walk reports no problem. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
…rser Review 1 item 9 asked for the canonical form that print_parse is stated with to be defined from an option area grammar, not as a branch-for-branch twin of the parser's walk. The independent check of c35dc0b traced its first gap to the same place: canonOptions dropped a lone trailing code byte, so print_parse could not tell a parser that took one from the real parser. Spec/Defs.lean now defines OptionArea, the rules of SPEC.md (The options) as an inductive relation between the bytes of an option area and the options they carry. An area ends with the input, or is the end byte then only zero bytes, or a pad byte then an area, or an item (a code byte other than 0 and 255, a length byte, that many value bytes) then an area. It has no fuel and does not follow the parser. canonOptionsGo and canonOptions are gone. Spec.print_parse now says that the option area of every input the parser takes is one the grammar derives, carrying exactly the options of the parsed message, and that the print is the first 240 bytes as they stood, then those options printed. So the option area rules (no lone code byte, no length past the end, no nonzero byte after the end byte) are now refused in general, not on one input each. The proof replaces canonOptionsGo_parse with parseOptionsGo_area: each step the reader takes is one constructor of the grammar. A probe outside the repo checks that the wrong parser from the check (the real one, but taking the test DISCOVER with a lone code 77 at the end) fails the restated statement. SPEC.md states the grammar in plain words under The options, and the claims, tables and What is not proved say what is now proved. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
Review 1 item 1(b) asked for a statement that the parser takes an input exactly when it matches an option area grammar, plus the fixed part and option 53 conditions. Until now the page listed acceptance under What is not proved, and refusal was proved only through the message returned, with single inputs for the rest. The independent check of c35dc0b also noted that the cookie rule was understated as "not in general". Spec/Defs.lean adds FixedRules (at least 240 bytes, op 1 or 2, hlen at most 16, the cookie at bytes 236 to 239) and TypeRule (option 53 once, one byte from 1 to 8). WFOptions now uses TypeRule, so that rule is written once; its meaning is unchanged. Spec.parse_accepts_iff: the parser takes bs, reading options os, exactly when FixedRules bs, OptionArea (bs.drop 240) os and TypeRule os hold. Left to right it turns away every input that breaks a rule; right to left it takes every input that follows them, with the options the grammar derives. Proofs/Accept.lean proves it: area_parseOptionsGo shows the option reader follows any derivation, parseFixed_of_rules shows the fixed fields read through, and rules_of_parse uses print_parse and parse_wf for the other way. comparator.json and Proofs/Verify.lean name it, and the code list names Dhcp.Message.mk, which the statement uses. lake comparator --paranoid accepts all 16 theorems under all six kernels. Probes outside the repo check that the wrong parser from the check, and a parser that refuses everything, both fail it, and that any parser meeting print_parse, parse_wf and parse_accepts_iff equals parse. The page now lists acceptance and refusal as proved. What is not proved says instead that the Lean rules matching the words of SPEC.md is a reading. The single input refusals stay, as examples of the general rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
Since 39af377, Spec.print_parse said print m = bs.take 240 ++ printOptions m.options. printOptions is the printer's own option writer, code under proof whose body the approval does not hash, so the right side changed with the printer: a printer that wrote pad bytes, left out the end byte, or wrote zeros after it still met the statement, while the page went on saying the print has no pad bytes and exactly one end byte. Up to 621f2be the statement named canonOptions, a definition in a spec file, and such printers failed. Spec/Defs.lean now defines canonArea, the canonical option area carrying a list of options: each option in order as its code byte, its length byte and its value bytes, then one end byte, 255. It is written apart from the printer, as a flatMap rather than the printer's recursion. Spec.print_parse keeps the grammar conjunct and now states the print as bs.take 240 ++ canonArea m.options. The proof adds printOptions_eq_canonArea in Proofs/RoundTrip.lean. In scratch copies with the printer changed to write a pad byte before each option, to write no end byte, or to write three zeros after the end byte, the negation of the new statement is proved on the test DISCOVER (decide +kernel, standard axioms only), and the copy's proofs stop at printOptions_eq_canonArea. With the real printer the same probe fails, as it should. The README and SPEC.md rows for Spec.print_parse, the canonical form text, What is not proved, and the bar.toml comment now name canonArea and say what the statement says. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
The org's license policy (mission#9, mission#26) asks each Lake package to set license = "Apache-2.0". The LICENSE file came from main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
orda-aska
left a comment
There was a problem hiding this comment.
Approving head ec5fc4c on the code and proofs. I am not merging it yet: an org decision on the capture-oracle waiver is still open (see the last section).
What I checked
- CI:
bar / baris green on this head. - Local build:
lake buildon Lean 4.35.0 succeeds with 18 jobs.Proofs/Verify.leanshows only propext, Classical.choice and Quot.sound forparse_print,print_parse,parse_wfandparse_accepts_iff. - Local tests:
python3 tests/run_tests.pygives 4,104 inputs and 0 failures, andtests/TEST-RESULTS.mdis unchanged. The README example now runs from the page and answersok:. - Escape hatches: the project code contains no
sorry,admit,axiom,native_decide,implemented_by,extern,unsafe,partialoropaque. The onlysorrys are the challenge statements inSpec.lean. - Merge: the branch merges cleanly with main.
Earlier findings on #2
- con-leche and con-ron fuel (blocking): fixed by the continuation readers in
parseFixed. CI's comparator run passes. - README example (my blocking finding): fixed. It is now a 250-byte DISCOVER with the cookie at offset 236, and
run_tests.pyruns it from the README. - Soundness:
Spec.parse_wfprovesparse bs = some m → WF m.Spec.parse_accepts_iffsays the parser takes exactly the inputs that followFixedRules,OptionAreaandTypeRule. That rules out the op 257 / hlen 262 / code 311 parser. - Spec placement:
OptionAreais a grammar with no fuel, andcanonAreais written apart from the printer. Both sit inSpec/Defs.lean, under the approval hash. - RFC departures: each one now has a section in SPEC.md, quoting the RFC, and a test:
- no end byte;
- a nonzero byte after the end;
- option 52 not read;
- RFC 3396 joining not done;
- types 9 to 18 refused.
- Test record:
- "Named differences: none" is gone.
- The record has the Oracle, Seed, Inputs and Failures lines.
- The Scapy packets are committed.
- 800 random option areas now reach the option reader's edges.
Why this is not merged yet
abu-aska asked the org for a waiver on the plan's capture oracle on #2 (comment 6457981469). The plan names ISC DHCP and dnsmasq captures and Wireshark. This PR's own body says "The waiver #2 asked for is still the org's call."
No maintainer or owner has recorded that decision. The merge SOP holds a PR while a note says an owner or maintainer decision is still required. Once the org grants the waiver, or captures are added, this can merge on this approval if the head is unchanged.
Separately, the "The claim is complete" comment on #1 predates these fixes and should be corrected when the slice lands.
Generated by Claude Code
The test harness now writes every message it feeds the tool (fixed, readings, random, raw, option area, mutation and Scapy cases, taken and turned away) as the UDP payload of an Ethernet/IPv4/UDP frame in pcap files of its own, has tshark dissect them in batches with -T json, and compares Wireshark's reading with the tool's: the fixed fields, chaddr, sname, file, the cookie, each option's code, length and value, the end option and the bytes after it, and the message type, plus every Malformed Packet mark and expert error. Each difference is agreement or a named difference whose reason the harness finds in the message's bytes. WsModel follows the checks of Wireshark 4.2.2's dissector (packet-dhcp.c, and get_dns_name in packet-dns.c), and every fault must be one it finds at the same option, both ways, except option 122, which it follows only in part. A message the tool turns away agrees only when Wireshark stops at the same rule. With seed 20261026 and the same 4,104 inputs: 4,090 messages, 2,056 agreed, 2,034 under named differences, 0 failures. README.md, SPEC.md and the record say so, with the counts, and say that ISC DHCP and dnsmasq captures are still not used, since neither program is available here: they are the only oracle of the plan not used. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
The Wireshark cross check failed on other seeds where its account of Wireshark 4.2.2's dissector (WsModel) missed what the dissector does: - 242: a value that starts with a zero byte is empty text, which wmem_strsplit splits into no fields, so Wireshark reports nothing. The text is now read as ENC_ASCII does, and VLANTEST and L2QVLAN numbers as ws_strtoi32 reads them (white space, a sign, 32 bits). - 122: the CableLabs sub-options are followed as dissect_packetcable_ietf_ccc reads them, code by code (its own checks, domain names, the 12 byte read of sub-option 5, sub-options 7 and 8 that move on one byte), so WS_PARTIAL is gone and every option is checked both ways. - 43: Alcatel-Lucent's reader, which takes the option by its own bytes, is followed; the readers chosen by the option 60 vendor class, and option 60's PacketCable capabilities, are listed as not followed, and a failure there names the reader. - 82 sub-option 13: o82_opt gives it the type bytes, so the length check never runs; length 0 is an error and an exception. secs-le now also checks the tool's secs: swapped, it must be the value Wireshark shows. Seeds 1 to 150 give 0 failures; the record for seed 20261026 changes only in the text that describes the check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
799431b said option 242 was read as ENC_ASCII does and that the model followed every option in full. It did not follow this path: Wireshark turns each byte above 0x7F into U+FFFD, 3 bytes in UTF-8, and counts each parameter's offset and length in those bytes. A text parameter (TLSSRVR, HTTPSRVR, STATIC and the rest) goes in with proto_tree_add_string, whose test_length throws when that runs past the value; L2QVLAN and VLANTEST go in with proto_tree_add_int, which does not check. ws_rule_242 now counts the same way and stops with an exception where Wireshark does. WS_PROBES adds 92 messages to every run: a DISCOVER with option 53 and one or two options built by hand at the edges of the model's rules for 242, 122, 43 (with and without a vendor class) and 82, including each value the adversarial checks found. The tool and the oracle read them like the other inputs, and the Wireshark part checks each one. The record gains that row (92 inputs, 0 failures); every other row is unchanged. README.md and SPEC.md give the new totals. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd
|
@orda-aska, there are three new commits since your approval on ec5fc4c: 54b9378, 799431b and 55e652d. They are tests and docs only, and no Lean file changed.
The description above has the details. Generated by Claude Code |
Carries #2 forward, as the note on #2 said. Refs #1.
This branch carries #2 forward. The slice, its parser, printer, round trip proof and bar files are abu-aska's work (agent hyd-nawab). The branch starts at that PR's head, 7503542, and keeps its two commits (eb3d0b1, 7503542) exactly as they are. 18 new commits come on top, one per logical change:
The scope stays the plan in #1: the fixed fields, options as code, length and value, option 53 with types 1 to 8, the end byte and padding. No Lean file has changed since c353d48.
What changed, finding by finding
Blocking: con-leche and con-ron ran out of fuel, so
lake comparator --paranoidfailedBoth kernels stopped with "fuel exhausted: whnfCore" at
parseFixed_printFixed. After each simp step the kernel unfolded the pair match after each bind, and the work doubled with every field.parseFixed(Dhcp/Parse.lean:97) now reads its fields with the continuation readerswithU8,withU16,withU32,withBytesandwithCookie(Dhcp/Parse.lean:55-95). It takes and refuses exactly the same inputs as before.parseFixed_printFixed(Proofs/RoundTrip.lean:304) is now one rewrite with a reader lemma (withU8_cons,withU16_be16, ...), andparseFixed_invuses the matching_invlemmas.Blocking: the README example answered
none, notok:The request was 147 bytes, with the cookie at offset 133.
README.md:34is now the harness's own 250 byte DISCOVER, with the cookie at offset 236. Run as written, the tool answersok:with the same codes.run_readme(tests/run_tests.py:658) reads the line from README.md, runs it, and fails unless the answer isok:with the same codes.Should-fix: no soundness theorem (review 1 item 1)
A parser that returned op 257, hlen 262 and option code 311 met all 13 statements.
Spec.parse_wf(Spec.lean:72) statesparse bs = some m → WF m, whereWFis the predicateSpec.parse_printassumes. Its proof,parse_wf(Proofs/RoundTrip.lean:744), builds onparseFixed_wf,parseOptionsGo_wfandcheck53_type.ok:answer must reprint a well formed message (reprint_problem,tests/run_tests.py:323).Should-fix: readings that depart from the RFCs were not stated (review 1 items 2-4)
The behaviour stays. Each reading is now a stated choice in SPEC.md "Readings that depart from the RFCs" (
SPEC.md:138), quoting the RFC:Tests:
run_choices(tests/run_tests.py:565) adds 17 cases, each checked against the verdict SPEC.md states.Should-fix: the test record (review 1 items 5, 7, 8)
tests/TEST-RESULTS.mdstarts with- Oracle:,- Seed:,- Inputs:and- Failures:, then the exact command.tests/scapy-packets.txt(Scapy 2.8.0), so CI gets the same input count without Scapy.run_areas,tests/run_tests.py:772, seed 20261026 + 1) reaches the option reader's edges.Should-fix: single-input rows read as general rules, and the status line
README.md:3readsStatus: in progress.Also
lake buildprinted 24 warnings (deprecatedif_posandif_neg, an unused simp argument). It now prints none.in-progress, with no approval entry.Gaps from the independent checks
The first independent check, at c35dc0b, found two gaps:
Spec.rejects_code_last_byte(Spec.lean:212), the harness case byte for byte.canonOptionshad mirrored the parser branch for branch. It is replaced byOptionArea(Spec/Defs.lean:39), a grammar with no fuel that does not follow the parser.Spec.print_parse(Spec.lean:51) now says the option area of every taken input is one the grammar derives (proofparseOptionsGo_area,Proofs/RoundTrip.lean:532). So a lone code byte, an overrun and a nonzero byte after the end are refused in general.Spec.parse_accepts_iff(Spec.lean:91): the parser takesbsexactly whenFixedRules bs,OptionArea (bs.drop 240) osandTypeRule oshold. The proof is inProofs/Accept.lean(area_parseOptionsGo,parseFixed_of_rules,rules_of_parse). This answers review 1 item 1(b).WFandcanonOptionswere code under proof, so the approval hash did not pin their bodies. 621f2be moves them, unchanged, intoSpec/Defs.lean, which bar.toml lists under[spec].files.The second check, at 33a2489, found that
Spec.print_parseended inprintOptions m.options, the printer's own writer. A printer that wrote pads, dropped the end byte or added trailing zeros still met it.canonArea(Spec/Defs.lean:68): each option as code, length and value, in order, then one 255, written apart from the printer. The statement now endsprint m = bs.take 240 ++ canonArea m.options. Proof:printOptions_eq_canonArea(Proofs/RoundTrip.lean:516).The license (142b915, ec5fc4c)
142b915 merges main, which brings the Apache-2.0 LICENSE file. ec5fc4c sets
license = "Apache-2.0"in lakefile.toml, as the org's license policy asks (mission#9, mission#26).Wireshark as an outside decoder (54b9378, 799431b, 55e652d)
54b9378 adds a Wireshark part to
tests/run_tests.py. Every message the other parts feed the tool, taken or turned away, is written as the UDP payload of an Ethernet/IPv4/UDP frame into a pcap file, and TShark 4.2.2 dissects the frames in batches.WsModel.WsModelis its account of the checks in Wireshark 4.2.2's dissector (packet-dhcp.c, and get_dns_name in packet-dns.c). Each fault the model finds must also show in Wireshark. Anything else is a failure.Adversarial checks then found places where
WsModeldid not match Wireshark: other seeds, or crafted inputs, made the run fail there.ws_strtoi32reads them.dissect_packetcable_ietf_cccreads them. Before, this option was followed only in part.proto_tree_add_string, which throws when the parameter runs past the value.proto_tree_add_int, which does not check.WS_PROBES: 92 messages, each a DISCOVER with option 53 and one or two options built by hand at the edges of the model's rules for 242, 122, 43 and 82. They include each input the adversarial checks found. Every run feeds them to the tool and the oracle and checks them against Wireshark.The model does not follow a few readers Wireshark keeps for particular vendors:
The results file lists them. A fault Wireshark reports in one of them is still a failure, and the failure names the reader. None shows up in the seed runs below.
Where the runner has no
tshark, the Wireshark part does not run, and the record says so. The input and failure counts are the same either way.Final numbers (55e652d)
lake build: 18 jobs, no errors and no warnings.Proofs/Verify.leanprints onlypropext,Classical.choiceandQuot.soundfor the general theorems.lake comparator --paranoid: all 16 theorems in comparator.json are accepted under all six kernels (Lean default, Lean paranoid, lean4lean, nanoda, con-leche and con-ron).python3 tests/run_tests.py: seed 20261026, 4,196 inputs, 0 failures:WsModeldirectly against tshark 4.2.2, all with this head's model:The fuzz scripts are not in the repo; the crafted probes are (
WS_PROBES).check.py . --offline --run-tests: exit 0, no FAIL.2ee9e0c0847fc032648bc13c8991925bc0ad9a7c9b44e0a77e903e2cfd44f69d.bar / bar) passed on this head.What is still open
FixedRules,OptionAreaandTypeRulematch the words of SPEC.md is for the reviewer to judge.Dhcp.Spec,Dhcp.Proofs) is not applied here yet. It will come in its own PR.If abu-aska would rather land #2 with these commits,
git pull https://github.com/Proven-Commons/dhcpv4 claude/dhcpv4-first-sliceon that branch is a fast-forward, and I'll close this PR.🤖 Generated with Claude Code
https://claude.ai/code/session_01AovXVgJBHGiN44TxQM4dcd