# Erratum — draft-fossil-sync-protocol-00 §5.1
| | |
|---|---|
| **Document** | `draft-fossil-sync-protocol-00` |
| **Section** | 5.1 — Message Structure (byte-counted payloads) |
| **Type** | Compatibility note (Fossil accepts a payload cut short by the end of the body; the draft's framing is kept and made explicit) |
| **Status** | Applied in `draft-fossil-sync-protocol-01` (2026-10-12); the applied text was then revised by `sync-protocol-01-s5.1-truncated-payload-receiver.md` |
| **Reported** | 2026-10-11 |
| **Submitter** | Dan Mestas |
| **Found via** | go-libfossil's conformance suite against Fossil 2.28 (scope note SP-5.1-1); confirmed by reading Fossil's source and by pushing it a short file card |
## 1. Summary
§5.1 says a byte-counted payload ends "only at the declared count", but says
nothing about a body that ends first. Fossil reads whatever bytes remain and
carries on. For a full-content `file`, those bytes are then hash-checked, so a
card that declares more bytes than the body holds is stored whenever the bytes
that are there hash to HASH.
## 2. Location
§5.1 (~lines 935-940):
> Bodies are sequences of LF-terminated lines. Byte-counted payloads
> begin immediately after the line LF and may contain LF. They end
> only at the declared count. …
## 3. The divergence
Source, Fossil 2.28:
- `blob_extract()` (`src/blob.c:675-692`) clamps the requested count to the
bytes left in the input and returns the shorter blob; it reports the actual
length but does not fail.
- `xfer_accept_file()` (`src/xfer.c:160`) ignores that return value. It
hash-checks the extracted bytes (line 211) and stores them (line 214). The
`cfile` handler (line 280) and `uvfile` handler (line 349) extract the same
way.
Run: a `fossil new` repository, `nobody` given `gioz`, sent through
`fossil http` as `application/x-fossil-debug`:
```
push 0000 PROJECTCODE
file 13febeeb…5615 1000
truncated payload test\n (23 bytes, then end of body)
```
The reply is `# timestamp … errors 0`, and `blob` holds `13febeeb…` with size
23. With bytes that do not hash to HASH, the request fails as any wrong-hash
`file` does.
## 4. Impact
- **Integrity.** None for `file`: the hash check still decides what is
stored, so nothing false is accepted.
- **Framing.** A sender that miscounts can still get its last artifact
stored by Fossil, and will be refused by a receiver that enforces the count.
Seen from the sender, the two look inconsistent.
- **Conformance.** A test that a short payload is rejected fails against
Fossil; go-libfossil leaves it out.
- **Not checked here:** truncated `cfile` and `uvfile` payloads, and
truncated deltas. They take the same extraction path, but what happens
next (decompression, delta application, UV hash checks) was not run.
## 5. Correction
State the rule the draft implies and record Fossil's leniency:
```diff
Bodies are sequences of LF-terminated lines. Byte-counted payloads
begin immediately after the line LF and may contain LF. They end
- only at the declared count. A cfile sender appends one further LF
+ only at the declared count. A body that ends before a payload's
+ declared count is malformed; senders MUST NOT produce one. (Fossil
+ instead uses the bytes that are present, and stores a full-content
+ file whose shorter payload still hashes to HASH.) A cfile sender
+ appends one further LF
```
## 6. Rationale
- **The count is the frame.** "End only at the declared count" already makes
a short payload a framing error; saying so closes the gap the scope note
found.
- **No receiver requirement Fossil cannot meet.** The sender rule is MUST
NOT; receivers are told what Fossil does rather than being required to
reject, so Fossil stays conforming and a strict receiver is not wrong.
- **The hash makes the leniency safe,** which is why a note is enough.
## 7. Verification
- Source: Fossil 2.28 `src/blob.c:675-692` (`blob_extract`); `src/xfer.c:160`,
`:211-214`, `:280`, `:349`.
- Run: the short `file` card in §3 against a fresh repository through
`fossil http`; stored with size 23 and `errors 0`.
---
*Prepared against `draft-fossil-sync-protocol-00`. The RFC remains the oracle;
the reference implementation is used only to confirm the observable behavior.*