Implementing an AsyncWrite wrapper with stateful encoding

I am attempting to write an implementation of tokio::io::AsyncWrite that wraps another AsyncWrite and encodes (well, poorly encrypts) bytes passed to the wrapper's poll_write() before they are passed on to the inner poll_write(). The problem is that the encoding mechanism is stateful, and so just passing the input through the encoder on every call would break as soon as the inner poll_write() returned Pending, as that would cause the encoded bytes to be discarded, and then when the same input is passed again on the next poll_write() call, it'd be encoded differently (i.e., wrong).

The simplest solution I can think of is to store the encoded bytes in the wrapper whenever the inner poll_write() returns Pending, and then when poll_write() is called again while the storage is nonempty, another attempt is made to pass the stored bytes to the inner poll_write(). However, this only works as long as the outer poll_write() is always called with the same bytes until Ready is returned — an assumption that is broken by cancellation of writes, and maybe some other things? (I don't think my codebase would continue using a writer after cancelling a write, but I'm not about to audit it right now.) A variation would be to store both the raw and encoded bytes and compare the stored raw bytes against the input to poll_write(), which would presumably work as long as there are no occurrences of "try to write bytes → cancel write → try to write the same bytes again."

Is there a (more) robust way to pull this off?

Another approach I just thought of: Defer the actual writing to poll_flush(). poll_write() would just encode the data, store it, and return Ready, and then once poll_flush() iss called, the stored data would be passed to the inner poll_write() until it either returned Pending or there was nothing left (at which point the inner poll_flush() would be called). I'm not sure how reliable or efficient this would be in general, but I believe my codebase flushes after writing each message anyway.

I ended up going with the "defer writing to poll_flush()" approach, and it seems to be working fine.

1 Like