[multistream-select] Reduce roundtrips in protocol negotiation. #3
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Overview & Motivation
This is another attempt at libp2p#659, superseding libp2p#1121 and sitting on top of libp2p#1203. The following are the main conceptual changes:
The multistream-select header is always sent together with the first negotiation message.
Enable 0-RTT: If the dialer only supports a single protocol, it can send protocol data (e.g. the actual application request) together with the multistream-select header and protocol proposal. Similarly, if the listener supports a proposed protocol, it can send protocol data (e.g. the actual application response) together with the multistream-select header and protocol confirmation.
In general, the dialer "settles on" an expected protocol as soon as it runs out of alternatives. Furthermore, both dialer and listener do not immediately flush the final protocol confirmation, allowing it to be sent together with application protocol data. Attempts to read from an incompletely negotiated I/O stream implicitly flushes any pending data.
A clean / graceful shutdown of an I/O stream always tries to complete protocol negotiation, so even if a request does not expect a response, as long as the I/O stream is shut down cleanly, an error will occur if the remote did not support the expected protocol after all.
If desired in specific cases, it is possible to explicitly wait for full completion of the negotiation before sending or receiving data by waiting on the future returned by
Negotiated::complete()
.Overall, the hope is that this especially improves the situation for the substream-per-request scenario.
The public API of multistream-select changed slightly, requiring both
AsyncRead
andAsyncWrite
bounds for async reading and writing due to the implicit buffering and "lazy" negotiation. The error types have also been changed, but they were not previously fully exported anyway.This implementation comes with some general refactoring and simplifications and some more tests, e.g. there was an edge case relating to a possible ambiguity when parsing multistream-select protocol messages.
Examples
0-RTT Negotiation
Logs of a 0-RTT negotiation; The dialer supports a single protocol also supported by the listener:
A corresponding packet exchange excerpt (Wireshark) of 0-RTT negotiation where
ping
andpong
are "application protocol data" and e:Other Examples
Logs from a 1-RTT negotiation; The dialer supports two protocols, the second of which is supported by the listener.
Since protocol negotiation "nests", the following may also be observed as a single packet (again a simplified Wireshark excerpt):
Multistream-select 2.0
Given the status of the spec and the apparent lack of any implementations that I'm aware of, I didn't actually bother trying to implement it. Instead, these changes incorporate minimal compatibility with the currently proposed upgrade path by having the listener (aka responder) always answer to
/multistream/2.0.0
withna
. The implementation could be continued once the spec becomes "actionable".