{
 "number": 36135,
 "repo": "bitcoin/bitcoin",
 "url": "https://github.com/bitcoin/bitcoin/pull/36135",
 "title": "fuzz: test HTTPRequest state machine in http_request",
 "author": "frankomosh",
 "author_association": "CONTRIBUTOR",
 "created_at": "2026-09-01T04:35:20Z",
 "updated_at": "2026-09-07T00:37:38Z",
 "age_days": 16,
 "draft": false,
 "labels": [
  "Fuzzing"
 ],
 "milestone": null,
 "base": "master",
 "head_sha": "fca8fef8f903d841df693d18e4b9078f0867e07f",
 "head_ref": "2026-08-fuzz-http-request-state",
 "head_repo": "frankomosh/bitcoin",
 "head_history": [
  {
   "t": "2026-09-01T08:04:17Z",
   "sha": "b7bab44231d75326e4c8a20c95673e42c51691ec"
  },
  {
   "t": "2026-09-01T16:11:25Z",
   "sha": "1a004e4cebffac58f767aec7f37ec182c1b3a892"
  },
  {
   "t": "2026-09-03T16:24:11Z",
   "sha": "1837de9d2d8994738d8f4ea8be085db05979d064"
  },
  {
   "t": "2026-09-04T07:20:52Z",
   "sha": "fca8fef8f903d841df693d18e4b9078f0867e07f"
  }
 ],
 "additions": 171,
 "deletions": 27,
 "changed_files": 1,
 "commit_count": 4,
 "size_bucket": "M",
 "mergeable_state": "clean",
 "bot": {
  "drahtbot": {
   "present": true,
   "reviews": {
    "ack": [
     {
      "login": "hodlinator",
      "url": "https://github.com/bitcoin/bitcoin/pull/36135#pullrequestreview-5112314014"
     },
     {
      "login": "jeanpablojp",
      "url": "https://github.com/bitcoin/bitcoin/pull/36135#issuecomment-5541357454"
     }
    ]
   },
   "conflicts": [
    {
     "number": 36124,
     "title": "http: Make `HTTPRequest` update state internally",
     "author": "hodlinator"
    },
    {
     "number": 35964,
     "title": "fuzz: add coverage for GetQueryParameterFromUri",
     "author": "laxmanacharya8"
    }
   ]
  }
 },
 "acks_parsed": {
  "jeanpablojp": {
   "kind": "concept_ack",
   "hash": null,
   "t": "2026-09-01T22:08:20Z",
   "stale": false
  },
  "hodlinator": {
   "kind": "ack",
   "hash": "fca8fef8f903d841df693d18e4b9078f0867e07f",
   "t": "2026-09-04T11:40:09Z",
   "stale": false
  }
 },
 "acks_tally": {
  "ack": 1,
  "stale_ack": 0,
  "concept_ack": 1,
  "approach_ack": 0,
  "nack": 0,
  "concept_nack": 0,
  "approach_nack": 0
 },
 "reviews": {
  "approved": 1,
  "changes_requested": 0,
  "distinct_reviewers": [
   "fanquake",
   "hodlinator",
   "janb84",
   "jeanpablojp"
  ]
 },
 "signals": {
  "needs_rebase": false,
  "ci_failed": false,
  "mergeable_state": "clean",
  "last_author_activity": "2026-09-04T07:28:57Z",
  "last_reviewer_activity": "2026-09-04T13:46:52Z",
  "last_reviewer": "jeanpablojp",
  "author_silent_days": 13,
  "waiting_on_author_days": 13,
  "days_since_update": 10
 },
 "refs": {
  "mentioned": [
   35735,
   35759,
   36159,
   36160
  ],
  "depends_on": [
   35735
  ],
  "fixes": [],
  "linked_issues": [],
  "references": [
   {
    "number": 35735,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-08-17",
    "title": "Add state to HTTPRequest"
   },
   {
    "number": 35759,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-08-06",
    "title": "fuzz: check http_request body matches framing"
   },
   {
    "number": 36159,
    "type": "pull",
    "state": "open",
    "merged": false,
    "merged_at": null,
    "title": "http: Improve HTTPRemoteClient::MaybeDisconnect()"
   },
   {
    "number": 36160,
    "type": "pull",
    "state": "closed",
    "merged": true,
    "merged_at": "2026-09-16",
    "title": "refactor: Minor improvements to HTTP unit tests"
   }
  ],
  "conflicts": [
   36124,
   35964
  ]
 },
 "stack": {
  "shares_commits_with": [],
  "based_on": [],
  "base_for": []
 },
 "review_paths": [
  "src/test/fuzz/http_request.cpp"
 ],
 "body": "Follow-up to #35735, adding a test to the state machine added there. As it is, the target parses the whole input in one pass, so the paths that resume a partly-read request are unreachable from it.\n\nThis target now parses each input twice, once whole and once in random slices with a parse attempt after each, and requires both runs to produce the same request. HTTP framing is defined by the byte stream, so how it was split must not change the result.\n\nAlso raises the input cap from 4096, which sat below MAX_HEADERS_SIZE and kept the header size limit out of reach.",
 "commits": [
  {
   "sha": "56af5e072a0d1056bcf6a650c6057b44ab7f9756",
   "date": "2026-08-31T09:24:54Z",
   "message": "fuzz: extract http_request parse and framing check into helpers\n\nPure refactor with no behaviour change. Moving the single-shot parse into a\nnamed helper lets later commits add checks after it without its early returns\nskipping them, and lets the framing invariant from #35759 apply to requests\nparsed by other means."
  },
  {
   "sha": "d23b53e0cfbc8fdb2e5b820e2db875cf6cde7d55",
   "date": "2026-08-31T16:53:18Z",
   "message": "fuzz: allow http_request inputs to reach MAX_HEADERS_SIZE\n\nThe input was capped at 4096 bytes, which is below the 8192-byte\nMAX_HEADERS_SIZE enforced in HTTPHeaders::Read(), so no input this target\ngenerates could reach the headers size limit. The cap dates back to the\noriginal libevent harness and predates the parser the target now exercises."
  },
  {
   "sha": "c368de3eec9c381ce69936a83f8024e2c7f8c0f1",
   "date": "2026-09-03T15:37:07Z",
   "message": "fuzz: exercise the HTTPRequest state machine\n\nThe target parses each input in one pass: a single LineReader over the whole\nbuffer, each Load* called once. A request that arrives over several I/O cycles\ntakes a different path, which resumes where it left off, and the target could\nnot reach it.\n\nFeed the same bytes through a client a slice at a time, so parsing has to\nresume across cycle boundaries, and check what must hold at each boundary. A\ncompleted request is always handed back, parsing only advances or fails, chunk\nprogress never exceeds the declared chunk size, a failed request consumes\nnothing further, and nothing is parsed or consumed while a request is out with\na worker."
  },
  {
   "sha": "fca8fef8f903d841df693d18e4b9078f0867e07f",
   "date": "2026-09-04T07:00:33Z",
   "message": "fuzz: assert HTTP framing is independent of stream segmentation\n\nFraming is defined by the byte stream, so how TCP happened to split it must\nnot change what the parser produces. Run the same input twice, once delivered\nwhole and once in arbitrary slices, and require both runs to agree on the\nrequests parsed, their order, and whether parsing failed.\n\nThe leftover receive buffer is only compared when neither run failed: the\nwhole buffer is discarded on a parse error, so how much had arrived by then\nlegitimately differs between the two runs."
  }
 ],
 "timeline": [
  {
   "t": "2026-09-01T08:04:17Z",
   "kind": "force_push",
   "who": "frankomosh",
   "commit": "b7bab44231d75326e4c8a20c95673e42c51691ec"
  },
  {
   "t": "2026-09-01T08:10:09Z",
   "kind": "comment",
   "who": "frankomosh",
   "assoc": "CONTRIBUTOR",
   "text": "Drafting to check CI issues"
  },
  {
   "t": "2026-09-01T11:12:16Z",
   "kind": "comment",
   "who": "fanquake",
   "assoc": "MEMBER",
   "text": "```bash\nIn file included from /home/runner/work/_temp/src/httpserver.h:21,\n                 from /home/runner/work/_temp/src/test/fuzz/http_request.cpp:5:\n/home/runner/work/_temp/src/util/sock.h:67:19: error: 'virtual Sock& Sock::operator=(Sock&&)' was hidden [-Werror=overloaded-virtual=]\n   67 |     virtual Sock& operator=(Sock&& other);\n      |                   ^~~~~~~~\n/home/runner/work/_temp/src/test/fuzz/http_request.cpp:78:7: note:   by '{anonymous}::NoopSock& {anonymous}::NoopSock::operator=(const {anonymous}::NoopSock&)'\n   78 | class NoopSock : public Sock\n      |       ^~~~~~~~\n```"
  },
  {
   "t": "2026-09-01T16:11:25Z",
   "kind": "force_push",
   "who": "frankomosh",
   "commit": "1a004e4cebffac58f767aec7f37ec182c1b3a892"
  },
  {
   "t": "2026-09-01T17:22:27Z",
   "kind": "comment",
   "who": "frankomosh",
   "assoc": "CONTRIBUTOR",
   "text": "[quoted text omitted]\n\nThanks. Switched to `ZeroSock` (from test/util/net.h) instead of trying to use a custom mock. It already handles the operator GCC flagged here."
  },
  {
   "t": "2026-09-01T22:08:20Z",
   "kind": "review",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "state": "COMMENTED",
   "commit": "1a004e4cebffac58f767aec7f37ec182c1b3a892",
   "text": "Concept ACK\n\nBuilt with ASan and UBSan, replayed the qa-assets corpus and fuzzed on top of it without a failure."
  },
  {
   "t": "2026-09-01T22:08:20Z",
   "kind": "review_comment",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/fuzz/http_request.cpp",
   "commit": "fca8fef8f903d841df693d18e4b9078f0867e07f",
   "in_reply_to": null,
   "text": "No headers in here, so a header parsed differently by the one-shot and sliced runs is invisible unless it also changes something the comparison does look at. The `m_consumed` resume path in `HTTPHeaders::Read` is new ground for this target, and its output is what gets left out. Worth adding headers? I tried one extra field populated by `GetHeader(\"Host\")`, and today's qa-assets corpus then catches a mutation that changes a header only on the resumed path."
  },
  {
   "t": "2026-09-01T22:08:20Z",
   "kind": "review_comment",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/fuzz/http_request.cpp",
   "commit": "fca8fef8f903d841df693d18e4b9078f0867e07f",
   "in_reply_to": null,
   "text": "nit: nothing in `test_runner.py` passes `-max_len`, so libFuzzer caps generated inputs at 4128 bytes for this target, the largest file in the corpus today. That leaves `MAX_HEADERS_SIZE` out of reach. A bigger seed fixes it. I tried an 8387-byte one, which survives the corpus merge and adds coverage."
  },
  {
   "t": "2026-09-01T22:08:20Z",
   "kind": "review_comment",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/fuzz/http_request.cpp",
   "commit": "fca8fef8f903d841df693d18e4b9078f0867e07f",
   "in_reply_to": null,
   "text": "No qa-assets input reaches this assertion. I probed the three resume points separately. Header resumption shows up in every qa-assets corpus I tried, chunk resumption in none, and body resumption in one of the 146 inputs, but that request never completes. A mutation that drops a byte on body resume goes unnoticed by the current corpus. Worth sending two seeds, one chunked and one Content-Length request with a complete body?"
  },
  {
   "t": "2026-09-03T13:16:52Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/test/fuzz/http_request.cpp",
   "commit": "1a004e4cebffac58f767aec7f37ec182c1b3a892",
   "in_reply_to": null,
   "text": "Please don't replicate lowercase methods:\n```suggestion\n    void Receive(std::string_view s) { MutateRecvBuffer().append(s); }\n```\nAttempt at making the unit test conform to developer-notes.md: [`e69331c` (#36160)](https://github.com/bitcoin/bitcoin/pull/36160/changes/e69331c329877f824ac06a92b00abff116125bad)"
  },
  {
   "t": "2026-09-03T13:29:19Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/test/fuzz/http_request.cpp",
   "commit": "1a004e4cebffac58f767aec7f37ec182c1b3a892",
   "in_reply_to": null,
   "text": "Seems like we can skip this dance through:\n```diff\n--- a/src/test/fuzz/http_request.cpp\n+++ b/src/test/fuzz/http_request.cpp\n@@ -78,7 +78,7 @@ void SingleShotParse(const std::string& http_buffer, FuzzedDataProvider& provide\n class FuzzClient : public HTTPRemoteClient\n {\n public:\n-    FuzzClient() : HTTPRemoteClient{/*id=*/0, /*addr=*/CService(), /*socket=*/CreateSock(0, 0, 0)} {}\n+    FuzzClient() : HTTPRemoteClient{/*id=*/0, /*addr=*/CService(), /*socket=*/std::make_unique<ZeroSock>()} {}\n     void receive(std::string_view s) { MutateRecvBuffer().append(s); }\n };\n\n@@ -164,12 +164,6 @@ void CheckSegmentationIndependence(const std::string& input, FuzzedDataProvider&\n     FakeNodeClock clock{1610000000s};\n     FakeSteadyClock steady_clock;\n\n-    // fuzz.cpp points CreateSock at std::terminate(), and a client needs a socket\n-    // even though nothing here connects to anything. Keep it deterministic: a\n-    // FuzzedSock would consume fuzz data and desynchronise the two runs below.\n-    auto CreateSockOrig{CreateSock};\n-    CreateSock = [](int, int, int) { return std::make_unique<ZeroSock>(); };\n-\n     // The whole stream arrives in one I/O cycle.\n     RunResult one_shot;\n     {\n@@ -194,8 +188,6 @@ void CheckSegmentationIndependence(const std::string& input, FuzzedDataProvider&\n         sliced.remainder = client->GetRecvBuffer();\n     }\n\n-    CreateSock = CreateSockOrig;\n-\n     assert(one_shot.requests == sliced.requests);\n     assert(one_shot.errored == sliced.errored);\n     // The whole receive buffer is discarded on a parse error, so how much is\n```"
  },
  {
   "t": "2026-09-03T13:49:50Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/test/fuzz/http_request.cpp",
   "commit": "1a004e4cebffac58f767aec7f37ec182c1b3a892",
   "in_reply_to": null,
   "text": "```suggestion\n    // The whole receive buffer is discarded on a parse error, so in the sliced case\n    // what is left over depends on how much had arrived when the error fired.\n```"
  },
  {
   "t": "2026-09-03T13:54:17Z",
   "kind": "review",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "1a004e4cebffac58f767aec7f37ec182c1b3a892",
   "text": "Concept ACK 1a004e4cebffac58f767aec7f37ec182c1b3a892"
  },
  {
   "t": "2026-09-03T16:09:21Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/test/fuzz/http_request.cpp",
   "commit": "1a004e4cebffac58f767aec7f37ec182c1b3a892",
   "in_reply_to": 3924936916,
   "text": "Hm.. although @janb84 pointed out the the base class already has a `Receive()` method.. so a different name might be preferable.. then again I think the name is fine here and the base class method could rather be called something more specific like `ReceiveFromSocket()`. :\\"
  },
  {
   "t": "2026-09-03T16:24:11Z",
   "kind": "force_push",
   "who": "frankomosh",
   "commit": "1837de9d2d8994738d8f4ea8be085db05979d064"
  },
  {
   "t": "2026-09-03T16:26:53Z",
   "kind": "review_comment",
   "who": "frankomosh",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/fuzz/http_request.cpp",
   "commit": "fca8fef8f903d841df693d18e4b9078f0867e07f",
   "in_reply_to": 3908861899,
   "text": "Added the Host. Thanks for catching it"
  },
  {
   "t": "2026-09-03T16:29:31Z",
   "kind": "review_comment",
   "who": "janb84",
   "assoc": "MEMBER",
   "path": "src/test/fuzz/http_request.cpp",
   "commit": "1a004e4cebffac58f767aec7f37ec182c1b3a892",
   "in_reply_to": 3924936916,
   "text": "Agree that renaming the base class is better option ( suggestion, rename the base class to `ReceiveBytesFromSocket()` to maximizes symmetry with MaybeSendBytesFromBuffer() )"
  },
  {
   "t": "2026-09-03T16:30:06Z",
   "kind": "review_comment",
   "who": "frankomosh",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/fuzz/http_request.cpp",
   "commit": "1a004e4cebffac58f767aec7f37ec182c1b3a892",
   "in_reply_to": 3925227628,
   "text": "Taken. thanks"
  },
  {
   "t": "2026-09-03T16:30:37Z",
   "kind": "review_comment",
   "who": "frankomosh",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/fuzz/http_request.cpp",
   "commit": "1a004e4cebffac58f767aec7f37ec182c1b3a892",
   "in_reply_to": 3925051325,
   "text": "Taken. thanks"
  },
  {
   "t": "2026-09-03T16:52:39Z",
   "kind": "review_comment",
   "who": "frankomosh",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/fuzz/http_request.cpp",
   "commit": "fca8fef8f903d841df693d18e4b9078f0867e07f",
   "in_reply_to": 3908861907,
   "text": "Agreed, and it matches what I also measured. no `-max_len` means libFuzzer derives its limit from the corpus max (4128 today), so corpus is bounded and thus generation. Raising the cap removes one half of that problem; the other half needs a larger seed in qa-assets. I am not completely sure(or aware of precedent) if adding seeds manually is recommendable but if yes then that would much better be done separately from this PR."
  },
  {
   "t": "2026-09-03T16:57:50Z",
   "kind": "review_comment",
   "who": "frankomosh",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/fuzz/http_request.cpp",
   "commit": "fca8fef8f903d841df693d18e4b9078f0867e07f",
   "in_reply_to": 3908861915,
   "text": "This also matches my coverage runs: the chunked branch of LoadBody() gets zero hits from the qa-assets corpus, and none of the inputs I generated contains Transfer-Encoding either. A manual seed injection kills the mutant, as with the case above. I'd be interested if there's a way to reach it without hand-writing one. Trying to see around if thats possible."
  },
  {
   "t": "2026-09-03T19:09:02Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/test/fuzz/http_request.cpp",
   "commit": "1a004e4cebffac58f767aec7f37ec182c1b3a892",
   "in_reply_to": 3924936916,
   "text": "re https://github.com/bitcoin/bitcoin/pull/36135#discussion_r3924936916:\n\n\"bytes\" in `MaybeSendBytesFromBuffer()` seems redundant... itching for another rename to `MaybeSendFromBuffer()` or `MaybeSendToSocket()` or `MaybeSendBufferToSocket()`. :)\nAlso itching to rename `GetRecvBuffer()` to `PeekRecvBuffer()` and `GetRequest()` to `PeekRequest()`, especially to disambiguate the latter one further from `TryReadRequest()`...\n\nBut with all HTTP PRs in flight let's defer those renames for now - except for the horrible case of `MaybeDisconnect()` which really really doesn't do what it sounds like (#36159).\n\nWill try not to derail too much for now, grateful the PR author applied my suggestion at the start of the thread."
  },
  {
   "t": "2026-09-03T19:22:12Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/test/fuzz/http_request.cpp",
   "commit": "1837de9d2d8994738d8f4ea8be085db05979d064",
   "in_reply_to": null,
   "text": "nanonit: Have a slight preference for more `string_view`s - it decreases arithmetic but makes L183 loong. Feel free to ignore.\n```suggestion\n        std::string_view remaining{input};\n        while (!remaining.empty()) {\n            const std::string_view chunk{remaining.substr(0, provider.ConsumeIntegralInRange<size_t>(1, remaining.size()))};\n            client->Receive(chunk);\n            remaining = remaining.substr(chunk.size());\n```"
  },
  {
   "t": "2026-09-03T19:55:58Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/test/fuzz/http_request.cpp",
   "commit": "1837de9d2d8994738d8f4ea8be085db05979d064",
   "in_reply_to": null,
   "text": "Could elaborate:\n```suggestion\n    // The whole stream arrives in one I/O cycle. This is similar to\n    // SingleShotParse(), although here we pipe it through the client and also\n    // parse multiple requests.\n```"
  },
  {
   "t": "2026-09-03T19:57:09Z",
   "kind": "review",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "state": "COMMENTED",
   "commit": "1837de9d2d8994738d8f4ea8be085db05979d064",
   "text": "Reviewed 1837de9d2d8994738d8f4ea8be085db05979d064"
  },
  {
   "t": "2026-09-03T21:11:55Z",
   "kind": "review_comment",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/fuzz/http_request.cpp",
   "commit": "fca8fef8f903d841df693d18e4b9078f0867e07f",
   "in_reply_to": 3908861907,
   "text": "Contributing corpus to qa-assets is routine and you have done it yourself. I could not find a case that was clearly a hand-written seed though, so that part I cannot settle. What I can say is that the small chunked input I wrote by hand survives the same `set_cover_merge` the corpus is maintained with, and the merge reported far more new coverage from it than from a large generated input."
  },
  {
   "t": "2026-09-03T21:11:57Z",
   "kind": "review_comment",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/fuzz/http_request.cpp",
   "commit": "fca8fef8f903d841df693d18e4b9078f0867e07f",
   "in_reply_to": 3908861915,
   "text": "I tried. Two ten-minute value-profile runs never produced even the header name, which fits `CaseInsensitiveEqual` not going through `memcmp`, so libFuzzer never learns it. Nothing I ran reached this assertion without a hand-written input, so a seed still looks like the way here. Short runs on one box, so treat the negative half as evidence rather than proof."
  },
  {
   "t": "2026-09-04T07:20:52Z",
   "kind": "force_push",
   "who": "frankomosh",
   "commit": "fca8fef8f903d841df693d18e4b9078f0867e07f"
  },
  {
   "t": "2026-09-04T07:22:45Z",
   "kind": "comment",
   "who": "frankomosh",
   "assoc": "CONTRIBUTOR",
   "text": "New update mainly on style nits."
  },
  {
   "t": "2026-09-04T07:23:17Z",
   "kind": "review_comment",
   "who": "frankomosh",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/fuzz/http_request.cpp",
   "commit": "1837de9d2d8994738d8f4ea8be085db05979d064",
   "in_reply_to": 3928185187,
   "text": "updated. Thanks"
  },
  {
   "t": "2026-09-04T07:24:31Z",
   "kind": "review_comment",
   "who": "frankomosh",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/fuzz/http_request.cpp",
   "commit": "1837de9d2d8994738d8f4ea8be085db05979d064",
   "in_reply_to": 3927918735,
   "text": "Taken, with a small tweak (slice length pulled into a named variable so the line stays under 100 characters)."
  },
  {
   "t": "2026-09-04T07:28:57Z",
   "kind": "review_comment",
   "who": "frankomosh",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/fuzz/http_request.cpp",
   "commit": "fca8fef8f903d841df693d18e4b9078f0867e07f",
   "in_reply_to": 3908861915,
   "text": "Aah..Just seen that there is already a dictionary for this harness in qa-assets (https://github.com/bitcoin-core/qa-assets/blob/main/fuzz_dicts/http_request.dict). So I think the best course of action is that we could do a follow up there to add \"chunked\" and \"Transfer-Encoding: chunked\"....."
  },
  {
   "t": "2026-09-04T11:23:00Z",
   "kind": "review_comment",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "path": "src/test/fuzz/http_request.cpp",
   "commit": "1837de9d2d8994738d8f4ea8be085db05979d064",
   "in_reply_to": 3927918735,
   "text": "Neat variant!"
  },
  {
   "t": "2026-09-04T11:40:09Z",
   "kind": "review",
   "who": "hodlinator",
   "assoc": "MEMBER",
   "state": "APPROVED",
   "commit": "fca8fef8f903d841df693d18e4b9078f0867e07f",
   "text": "ACK fca8fef8f903d841df693d18e4b9078f0867e07f\n\nShould\u2122\ufe0f provide more coverage of `HTTPRemoteClient` as well as adding comparison of the resulting requests when the input is sliced up and when it's not (the slice points being defined by the fuzz input itself).\n\nhttps://corecheck.dev/bitcoin/bitcoin/pulls/36135 doesn't show any significant difference in coverage, probably it needs an updated fuzz corpus."
  },
  {
   "t": "2026-09-04T12:31:53Z",
   "kind": "review_comment",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "path": "src/test/fuzz/http_request.cpp",
   "commit": "fca8fef8f903d841df693d18e4b9078f0867e07f",
   "in_reply_to": 3908861915,
   "text": "Since that exchange I went at the corpus side and opened bitcoin-core/qa-assets#293 with it. The dictionary I left untouched, so that follow-up is yours. Happy to open it if you'd rather not. Either way it has to be a separate pull request, since the qa-assets lint rejects one that mixes `fuzz_corpora` with files outside it.\n\nOne number from those runs, in case it saves you time: `\"Transfer-Encoding\"` is already an entry there, and adding `\"chunked\"` beside it stayed out of the branch over 1.79M executions. A small test dictionary that included the whole `\"Transfer-Encoding: chunked\"` did get in, and that is the entry I have not tried against the real one."
  },
  {
   "t": "2026-09-04T13:46:52Z",
   "kind": "comment",
   "who": "jeanpablojp",
   "assoc": "CONTRIBUTOR",
   "text": "tACK fca8fef8f903d841df693d18e4b9078f0867e07f\n\nRebuilt with ASan and UBSan, replayed the corpus and fuzzed on top of it without a failure. Mutating the resume paths trips the new assertions."
  }
 ],
 "labels_log": [
  {
   "t": "2026-09-01T04:35:24Z",
   "action": "labeled",
   "label": "Fuzzing",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-01T06:09:42Z",
   "action": "labeled",
   "label": "CI failed",
   "who": "DrahtBot"
  },
  {
   "t": "2026-09-01T17:17:21Z",
   "action": "unlabeled",
   "label": "CI failed",
   "who": "DrahtBot"
  }
 ],
 "state_log": [
  {
   "t": "2026-09-01T08:09:50Z",
   "kind": "convert_to_draft",
   "who": "frankomosh"
  },
  {
   "t": "2026-09-01T17:22:43Z",
   "kind": "ready_for_review",
   "who": "frankomosh"
  }
 ],
 "text_chars": 11959,
 "text_tokens_estimate": 2989,
 "changed_paths": [
  "src/test/fuzz/http_request.cpp"
 ],
 "files": [
  {
   "path": "src/test/fuzz/http_request.cpp",
   "add": 171,
   "del": 27
  }
 ],
 "test_lines": 198,
 "git": {
  "head": "fca8fef8f903d841df693d18e4b9078f0867e07f",
  "head_matches_backup": true,
  "base": "d2e24e951de45e7e8d328ef36b80c055c90a6fdd",
  "commits": [
   {
    "sha": "56af5e072a",
    "subject": "fuzz: extract http_request parse and framing check into helpers",
    "files": 1,
    "add": 39,
    "del": 28
   },
   {
    "sha": "d23b53e0cf",
    "subject": "fuzz: allow http_request inputs to reach MAX_HEADERS_SIZE",
    "files": 1,
    "add": 3,
    "del": 1
   },
   {
    "sha": "c368de3eec",
    "subject": "fuzz: exercise the HTTPRequest state machine",
    "files": 1,
    "add": 98,
    "del": 0
   },
   {
    "sha": "fca8fef8f9",
    "subject": "fuzz: assert HTTP framing is independent of stream segmentation",
    "files": 1,
    "add": 47,
    "del": 14
   }
  ],
  "patch_truncated": false
 },
 "input_hash": "86ed5399ae03aba5",
 "extracted_at": "2026-09-17T16:15:31+00:00"
}