Hand the SASL scratch buffers to the caller, and redo the gap survey
Surveying the gaps against the code rather than against the last list
turned up something the list did not have, because I had put it there:
the AUTH paths each held about 27 KB on the stack. `max_sasl_message` was
8192, base64 makes the encoded form 10924, and three such buffers were
live in one frame on both sides. Fine on a main thread; not fine on a
server handing each connection a 64 KB stack.
Both sides now take the scratch from the caller, for the same reason
`reply_buffer` is the caller's: how much room a mechanism needs is the
caller's to know, and the range is wide -- a few hundred bytes for the
classic mechanisms, several kilobytes for an OAuth token. An absent or
undersized one is `error.SaslBufferTooSmall` rather than a hidden
allocation or an array the caller cannot see.
Three buffers became two, and the two take turns. The split is
four-to-three, which is base64's expansion exactly, so the coded half
always holds the encoding of a full plaintext half. A challenge decodes
into the coded half; the mechanism consumes it while writing its answer
into the plain half; the answer encodes back over the challenge, which is
finished with. There is a test that runs a real CRAM-MD5 exchange through
the 896-byte minimum, where those halves are 512 and 384.
The survey also found that `Extensions.auth` is the one field of
`Extensions` that borrows -- it points into the reply buffer -- where
zig-pop3 answered the same question with a bounded copy. That one is
written down rather than fixed: the two libraries disagree on purpose
until one of them gives way.
And the README's code samples said `zig-smtp.Client`, which is not an
identifier. The rename replaced the name everywhere including inside the
examples; they say `smtp.` now, matching `@import("smtp")`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SDrB41sGu5k1ubD1ufbxqC