|
| 1 | +# TODO2 |
| 2 | + |
| 3 | +Gap analysis of the codebase against SPEC.md. |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +## P0 — Wire Format / Encoding (defs.go) |
| 8 | + |
| 9 | +These are foundational: the header hash (used for challenge verification and pid |
| 10 | +references) and message hash (used for duplicate detection, challenge response, |
| 11 | +and pid) are both wrong until Encode() is complete. |
| 12 | + |
| 13 | +### ~~1. Encode() must include size and attachment headers~~ DONE |
| 14 | +**File:** `defs.go` `Encode()` |
| 15 | +Spec defines "message header" as all fields through the attachment headers. |
| 16 | +Encode() currently stops after the type field — it omits: |
| 17 | + - size (uint32 LE) |
| 18 | + - attachment count (uint8) + attachment headers (flags, type, filename, size) |
| 19 | + |
| 20 | +The header hash (SHA-256 of the encoded header) will be incorrect without these |
| 21 | +fields, which breaks challenge verification and pid references. |
| 22 | + |
| 23 | +### 2. Encode() must omit topic when pid is set |
| 24 | +**File:** `defs.go` `Encode()` |
| 25 | +Spec: "When pid exists the entire topic field MUST NOT be included on the wire." |
| 26 | +Encode() always writes the topic. It must only write topic when pid is absent. |
| 27 | + |
| 28 | +### 3. Encode() must support common-type encoding for type field |
| 29 | +**File:** `defs.go` `Encode()` |
| 30 | +When common-type flag (bit 2) is set, type on the wire is a single uint8 index, |
| 31 | +not a length-prefixed string. Encode() always writes length-prefixed. |
| 32 | + |
| 33 | +### ~~4. Add Attachments field to FMsgHeader~~ DONE |
| 34 | +**File:** `defs.go` struct |
| 35 | +Add `Attachments []FMsgAttachmentHeader` to store parsed attachment headers. |
| 36 | +Required before Encode() can include attachment headers, before attachment |
| 37 | +parsing/validation/download, and before hash computation is correct. |
| 38 | + |
| 39 | +### ~~5. Complete FMsgAttachmentHeader struct~~ DONE |
| 40 | +**File:** `defs.go` struct |
| 41 | +Currently only has Filename, Size, Filepath. Missing per-spec fields: |
| 42 | + - Flags (uint8) — including per-attachment common-type (bit 0) and deflate (bit 1) |
| 43 | + - Type (string) — the attachment's media type |
| 44 | + |
| 45 | +### 6. Add ChallengeCompleted flag to FMsgHeader |
| 46 | +**File:** `defs.go` struct |
| 47 | +Add `ChallengeCompleted bool` to distinguish "challenge was completed and |
| 48 | +ChallengeHash is valid" from "challenge was not performed." Without this, the |
| 49 | +hash check in downloadMessage erroneously fails when the challenge was skipped. |
| 50 | + |
| 51 | +### ~~7. GetMessageHash() must include attachment data~~ DONE |
| 52 | +**File:** `defs.go` `GetMessageHash()` |
| 53 | +Spec: message hash is SHA-256 of the entire message — header + data + |
| 54 | +attachment data. Currently attachment data (sequential byte sequences following |
| 55 | +the message body) is not included. |
| 56 | + |
| 57 | +--- |
| 58 | + |
| 59 | +## P1 — Receiving: Header Exchange (host.go readHeader) |
| 60 | + |
| 61 | +### 8. Generalise first-byte version/challenge detection |
| 62 | +**File:** `host.go` `readHeader()` |
| 63 | +Spec step 1.3: 1..127 = version, 129..255 = CHALLENGE (version = 256 − value), |
| 64 | +0 and 128 are undefined. Currently only v==255 is handled as a challenge. Must |
| 65 | +handle any value > 128 where (256 − value) is a supported version. |
| 66 | + |
| 67 | +### 9. Send reject code 2 for unsupported version |
| 68 | +**File:** `host.go` `readHeader()` |
| 69 | +Spec 1.3.iii: Send code 2 on the connection before closing. Currently just |
| 70 | +returns an error without sending any code. |
| 71 | + |
| 72 | +### 10. Validate at least one "to" recipient |
| 73 | +**File:** `host.go` `readHeader()` |
| 74 | +Spec 1.4.i.a: If to count is 0, reject code 1 (invalid). Currently no check. |
| 75 | + |
| 76 | +### 11. Make topic conditional on pid absence |
| 77 | +**File:** `host.go` `readHeader()` |
| 78 | +Spec: topic field is only present when pid is NOT set. Currently topic is |
| 79 | +read unconditionally regardless of pid. |
| 80 | + |
| 81 | +### 12. Handle common-type flag for type field |
| 82 | +**File:** `host.go` `readHeader()` |
| 83 | +When common-type flag (bit 2) is set, type is a single uint8 index into the |
| 84 | +Common Media Types table. If the value has no mapping → reject code 1. |
| 85 | +Currently always reads type as a length-prefixed string. |
| 86 | + |
| 87 | +### 13. Parse and validate attachment headers |
| 88 | +**File:** `host.go` `readHeader()` |
| 89 | +Currently rejects any non-zero attachment count. Must: |
| 90 | + - Parse each header: flags (uint8), type, filename (uint8 + UTF-8), size (uint32). |
| 91 | + - Validate per-attachment common-type flag mapping (reject code 1 if unmapped). |
| 92 | + - Validate filenames per spec (Unicode letters/numbers, limited special chars, |
| 93 | + no consecutive dots, not at start/end, unique case-insensitive, < 256 bytes). |
| 94 | + - Include all attachment sizes in MAX_SIZE check (data size + Σ attachment sizes). |
| 95 | + - Store parsed headers on FMsgHeader.Attachments. |
| 96 | + |
| 97 | +### 14. DNS-verify sender IP during header exchange |
| 98 | +**File:** `host.go` `readHeader()` |
| 99 | +Spec 1.4.ii / "Sender IP Verification": Host B MUST resolve |
| 100 | +`_fmsg.<sender-domain>` and verify the incoming connection's IP is in the |
| 101 | +authorised set. If not authorised → TERMINATE (no reject code, no challenge). |
| 102 | +Currently this check only happens inside challenge() and is skipped when |
| 103 | +challenge is not performed. |
| 104 | + |
| 105 | +### 15. Perform pid verification during header exchange |
| 106 | +**File:** `host.go` `readHeader()` |
| 107 | +Spec 1.4.v.c: When pid exists and add to does not: |
| 108 | + a. pid must be verified stored per "Verifying Message Stored"; else code 6. |
| 109 | + b. Parent time must be before message time; else code 9 (time travel). |
| 110 | +Currently deferred to downloadMessage(). |
| 111 | + |
| 112 | +### 16. Distinguish code-200 vs code-11 parents for add-to header-only path |
| 113 | +**File:** `host.go` `readHeader()` + `store.go` |
| 114 | +Spec 1.4.v.b.a (no add-to recipients for our domain): pid MUST match a message |
| 115 | +originally accepted with code 200 (not code 11), preventing add-to chaining. |
| 116 | +Currently lookupMsgIdByHash does not distinguish how the message was accepted. |
| 117 | + |
| 118 | +### 17. Verify sender participated in parent message |
| 119 | +**File:** `host.go` `readHeader()` |
| 120 | +Spec invariant: "A sender (from) MUST have been a participant in the message |
| 121 | +referenced by pid." Not currently checked. |
| 122 | + |
| 123 | +--- |
| 124 | + |
| 125 | +## P2 — Receiving: Challenge (host.go challenge) |
| 126 | + |
| 127 | +### 18. Connection 2 must target same IP as Connection 1 |
| 128 | +**File:** `host.go` `challenge()` |
| 129 | +Spec 2.1: Dial conn.RemoteAddr() IP, not h.From.Domain. Dialling the domain |
| 130 | +may resolve to a different IP. |
| 131 | + |
| 132 | +### 19. Make challenge mode configurable |
| 133 | +**File:** `host.go` `challenge()` / `handleConn()` |
| 134 | +Spec defines challenge modes (NEVER, ALWAYS, HAS_NOT_PARTICIPATED, |
| 135 | +DIFFERENT_DOMAIN) as Host B's implementation choice. Currently always challenges. |
| 136 | +At minimum, support skipping the challenge so the ChallengeCompleted guard |
| 137 | +(item 6) works correctly. |
| 138 | + |
| 139 | +--- |
| 140 | + |
| 141 | +## P3 — Receiving: Download and Response (host.go downloadMessage) |
| 142 | + |
| 143 | +### 20. Pre-download duplicate check via challenge hash |
| 144 | +**File:** `host.go` `downloadMessage()` |
| 145 | +Spec 3.1: BEFORE downloading data, if challenge was completed, use the message |
| 146 | +hash to check for duplicate → code 10. Currently the duplicate check is after |
| 147 | +download, wasting bandwidth. |
| 148 | + |
| 149 | +### 21. Guard hash verification behind ChallengeCompleted |
| 150 | +**File:** `host.go` `downloadMessage()` |
| 151 | +Spec 3.3: Hash verification must only run when challenge was completed. Currently |
| 152 | +ChallengeHash is zero-valued when skipped, causing false mismatch on every |
| 153 | +non-challenged message. |
| 154 | + |
| 155 | +### 22. Download attachment data |
| 156 | +**File:** `host.go` `downloadMessage()` |
| 157 | +Spec 3.2: Download sequential attachment byte sequences after message body, |
| 158 | +bounded by attachment header sizes. Currently only message body is downloaded. |
| 159 | + |
| 160 | +### 23. Per-recipient user-duplicate check (code 103) |
| 161 | +**File:** `host.go` `downloadMessage()` / `validateMsgRecvForAddr()` |
| 162 | +Spec 3.4.i ordering: user duplicate (103) → unknown (100) → full (101) → |
| 163 | +not accepting (102) → accept (200). Currently there is no check for whether |
| 164 | +the message was already received for a specific recipient (code 103). |
| 165 | + |
| 166 | +--- |
| 167 | + |
| 168 | +## P4 — Sending (sender.go) |
| 169 | + |
| 170 | +### 24. Write actual attachment headers |
| 171 | +**File:** `sender.go` `deliverMessage()` |
| 172 | +Attachment count is hardcoded to 0. Must write attachment count + each |
| 173 | +attachment header (flags, type, filename, size) from FMsgHeader.Attachments. |
| 174 | + |
| 175 | +### 25. Send attachment data after message body |
| 176 | +**File:** `sender.go` `deliverMessage()` |
| 177 | +Spec: sequential attachment byte sequences following data, bounded by header |
| 178 | +sizes. Currently not sent. |
| 179 | + |
| 180 | +### 26. Implement exponential back-off for retries |
| 181 | +**File:** `sender.go` `findPendingTargets()` |
| 182 | +Spec says "SHOULD apply a back-off strategy." Currently uses a fixed |
| 183 | +RetryInterval. |
| 184 | + |
| 185 | +### 27. Add code 101 (user full) to retryable set |
| 186 | +**File:** `sender.go` `findPendingTargets()` |
| 187 | +Per-user code 101 is analogous to global code 5 and is likely transient. |
| 188 | +Spec says MAY warrant retry. |
| 189 | + |
| 190 | +--- |
| 191 | + |
| 192 | +## P5 — Storage (store.go / dd.sql) |
| 193 | + |
| 194 | +### 28. Store and load attachment metadata |
| 195 | +**Files:** `store.go` `storeMsgDetail()` / `loadMsg()`, `dd.sql` msg_attachment |
| 196 | + - dd.sql: msg_attachment is missing `flags` (uint8) and `type` (varchar) |
| 197 | + columns needed for wire-format reconstruction and hash computation. |
| 198 | + - storeMsgDetail: insert attachment headers into msg_attachment. |
| 199 | + - loadMsg: load attachment rows into FMsgHeader.Attachments. |
| 200 | + |
| 201 | +### 29. Distinguish acceptance mode in stored messages |
| 202 | +**File:** `store.go` / `dd.sql` |
| 203 | +For item 16 (preventing add-to chaining), need a way to distinguish messages |
| 204 | +accepted via code 200 (full message) from code 11 (header-only add-to |
| 205 | +notification). Options: boolean column, separate lookup, or filepath="" check. |
| 206 | + |
| 207 | +--- |
| 208 | + |
| 209 | +## P6 — Address Validation (host.go) |
| 210 | + |
| 211 | +### 30. Support Unicode in address recipient part |
| 212 | +**File:** `host.go` `isValidUser()` |
| 213 | +Spec says recipient may contain Unicode letters/numbers (`\p{L}`, `\p{N}`). |
| 214 | +Currently only ASCII a-z, A-Z, 0-9 are accepted. Must use Unicode-aware |
| 215 | +checks (e.g. `unicode.IsLetter`, `unicode.IsNumber`). |
| 216 | + |
| 217 | +### 31. Enforce dot placement rules in address recipient part |
| 218 | +**File:** `host.go` `isValidUser()` |
| 219 | +Spec says dot `.` must not be consecutive and not at start/end. Currently dots |
| 220 | +are allowed anywhere with no positional checks. |
| 221 | + |
| 222 | +--- |
| 223 | + |
| 224 | +## P7 — DNS / DNSSEC (dns.go) |
| 225 | + |
| 226 | +### 32. Perform DNSSEC validation |
| 227 | +**Files:** `dns.go` `lookupAuthorisedIPs()`, `sender.go` |
| 228 | +Spec: DNSSEC validation SHOULD be performed. If validation fails → connection |
| 229 | +MUST terminate (no retry). Currently not performed or reported. |
| 230 | + |
| 231 | +--- |
| 232 | + |
| 233 | +## P8 — Handling a Challenge (host.go) |
| 234 | + |
| 235 | +### 33. Incoming challenge must send reject code 2 for unsupported version |
| 236 | +**File:** `host.go` `readHeader()` / `handleChallenge()` |
| 237 | +Spec "Handling a Challenge" step 1: if the first byte is not a supported |
| 238 | +version and not a valid challenge, send reject code 2 (unsupported version) |
| 239 | +and close. Currently handleChallenge is only entered for v==255 and other |
| 240 | +unsupported values return an error without sending a code. |
0 commit comments