From 66ab9a71d8f14bd31d02413ddb8073819b5c1331 Mon Sep 17 00:00:00 2001 From: Mark Mennell Date: Tue, 11 Aug 2026 07:15:18 +1000 Subject: [PATCH] Keep the per-recipient code stream valid on failures after 64/65 After responding code 64 or 65 the sender reads one byte per recipient, so per-recipient failure sites must write the per-recipient undisclosed code 105, not the header-level code 3 (SPEC SS8/SS10.4). Download or storage failures where the stream position is unknown now abort the connection instead of writing a header-level code into the stream; the sender records no response and retries. Co-Authored-By: Claude Fable 5 --- cmd/fmsgd/host.go | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/cmd/fmsgd/host.go b/cmd/fmsgd/host.go index 63825fc..8def06b 100644 --- a/cmd/fmsgd/host.go +++ b/cmd/fmsgd/host.go @@ -1456,9 +1456,11 @@ func storeAcceptedMessage(h *FMsgHeader, codes []byte, acceptedTo []FMsgAddress, h.Filepath = primaryFilepath if err := storeMsgDetail(h, localOutcome); err != nil { log.Printf("ERROR: storing message: %s", err) + // These bytes go into the per-recipient code stream, so the failure + // code must be the per-recipient 105, never the header-level 3. for i := range codes { if codes[i] == RejectCodeAccept { - codes[i] = RejectCodeUndisclosed + codes[i] = RejectCodeUserUndisclosed } } return false @@ -1556,7 +1558,7 @@ func downloadMessage(c net.Conn, r io.Reader, h *FMsgHeader, skipData bool) erro fp := uniqueFilepath(dirpath, uint32(h.Timestamp), ext) if err := copyMessagePayload(src, fp, h.Flags&FlagDeflate != 0, h.Size); err != nil { log.Printf("ERROR: copying downloaded message from: %s, to: %s", h.Filepath, fp) - codes[i] = RejectCodeUndisclosed + codes[i] = RejectCodeUserUndisclosed continue } @@ -1570,7 +1572,7 @@ func downloadMessage(c net.Conn, r io.Reader, h *FMsgHeader, skipData bool) erro primaryFilepath = fp if err := persistAttachmentPayloads(h, filepath.Dir(primaryFilepath)); err != nil { log.Printf("ERROR: copying attachment payloads for message storage: %s", err) - codes[i] = RejectCodeUndisclosed + codes[i] = RejectCodeUserUndisclosed primaryFilepath = "" acceptedTo = acceptedTo[:0] acceptedAddTo = acceptedAddTo[:0] @@ -1756,14 +1758,13 @@ func handleConn(c net.Conn) { } c.SetReadDeadline(time.Now().Add(calcNetIODuration(deadlineBytes, MinDownloadRate))) if err := downloadMessage(c, r, header, skipData); err != nil { - // if error was a protocal violation, abort; otherise let sender know there was an internal error + // After code 64/65 the sender reads one byte per recipient, so a + // header-level code here would be read as a recipient's code and + // desync the stream — abort instead; the sender records no response + // and retries later (§10.4). log.Printf("ERROR: Download failed from, %s: %s", c.RemoteAddr().String(), err) - if errors.Is(err, ErrProtocolViolation) { - abortConn(c) - return - } else { - _ = sendCode(c, RejectCodeUndisclosed) - } + abortConn(c) + return } // gracefully close 1st connection