-
Notifications
You must be signed in to change notification settings - Fork 27
webrtc: update str0m, fix multistream message decoding and encoding #441
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -220,6 +220,10 @@ impl Message { | |
|
|
||
| // Skip ahead to the next protocol. | ||
| remaining = &tail[len..]; | ||
| if remaining.is_empty() { | ||
| // During negotiation the remote may not append a trailing newline. | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| Ok(Message::Protocols(protocols)) | ||
|
|
@@ -230,7 +234,7 @@ impl Message { | |
| /// | ||
| /// # Note | ||
| /// | ||
| /// This is implementation is not compliant with the multistream-select protocol spec. | ||
| /// This implementation may not be compliant with the multistream-select protocol spec. | ||
| /// The only purpose of this was to get the `multistream-select` protocol working with smoldot. | ||
| pub fn webrtc_encode_multistream_message( | ||
| messages: impl IntoIterator<Item = Message>, | ||
|
|
@@ -249,9 +253,6 @@ pub fn webrtc_encode_multistream_message( | |
| header.append(&mut proto_bytes); | ||
| } | ||
|
|
||
| // For the `Message::Protocols` to be interpreted correctly, it must be followed by a newline. | ||
| header.push(b'\n'); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could reach out to https://github.com/smol-dot/smoldot to align on the spec, then we could remove/simplify these functions. We have diverged a bit from the spec, I wonder if this may break things down the line. Would the next message be interpreted correctly?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have @freddyli7 working on utilizing smoldot as a client to test against the libp2p server, as well as the litep2p server. We plan on getting it into
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @lexnv I made a quick attempt to remove this function and it was easy for This is my (simplified?) implementation of pub fn propose(
protocol: ProtocolName,
fallback_names: Vec<ProtocolName>,
) -> crate::Result<(Self, Vec<u8>)> {
let header = Protocol::try_from(&b"/multistream/1.0.0"[..])
.expect("valid multitstream-select header");
let mut protocols = vec![header];
protocols.extend(std::iter::once(protocol.clone())
.chain(fallback_names.clone())
.filter_map(|protocol| Protocol::try_from(protocol.as_ref()).ok())
);
let mut buf = BytesMut::with_capacity(64);
Message::Protocols(protocols).encode(&mut buf).map_err(|_| Error::InvalidData)?;
Ok((
Self {
protocol,
fallback_names,
state: HandshakeState::WaitingResponse,
},
buf.freeze().to_vec(),
))
}There are two differences between an
Number 1 is not added by
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar to https://github.com/paritytech/litep2p/pull/441/files#r2498425079, I believe the path forward is to align both litep2p <-> smoldot to the expected multistream-select behavior. From the litep2p perspective, it would mean removing this The WebRTC code was written quickly as a PoC to validate litep2p interoperability with smoldot; however, it used a few hackish methods that deviated from the spec, which should not be present in production environments (unless technically impossible to avoid). From the smoldot perspective, we'd need several patches to align with the WebSocket behavior
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What concretely would you like to see changed on the smoldot side? I believe its current behaviour is in line with the spec. IMHO, the custom code in litep2p is necessary due to the protobuf framing used in webrtc transport. I agree that it would be nice to handle this in a cleaner way, but that shouldn't require changes in smoldot. |
||
|
|
||
| Ok(BytesMut::from(&header[..])) | ||
| } | ||
|
|
||
|
|
@@ -542,4 +543,21 @@ mod tests { | |
| ProtocolError::InvalidMessage | ||
| ); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_decode_multiple_protocols_no_trailing_newline() { | ||
| let raw: [u8; 38] = [ | ||
| 19, 47, 109, 117, 108, 116, 105, 115, 116, 114, 101, 97, 109, 47, 49, 46, 48, 46, 48, | ||
| 10, 17, 47, 105, 112, 102, 115, 47, 112, 105, 110, 103, 47, 49, 46, 48, 46, 48, 10, | ||
| ]; | ||
| let bytes = Bytes::copy_from_slice(&raw); | ||
|
|
||
| assert_eq!( | ||
| Message::decode(bytes).unwrap(), | ||
| Message::Protocols(vec![ | ||
| Protocol::try_from(Bytes::from_static(b"/multistream/1.0.0")).unwrap(), | ||
| Protocol::try_from(Bytes::from_static(b"/ipfs/ping/1.0.0")).unwrap(), | ||
| ]) | ||
| ); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -631,6 +631,8 @@ impl WebRtcConnection { | |
| protocol: protocol.to_string(), | ||
| }); | ||
|
|
||
| self.rtc.channel(channel_id).unwrap().set_buffered_amount_low_threshold(1024); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When testing smoldot against a minimal litep2p-server, this caused a panic for me because |
||
|
|
||
| tracing::trace!( | ||
| target: LOG_TARGET, | ||
| peer = ?self.peer, | ||
|
|
@@ -742,6 +744,20 @@ impl WebRtcConnection { | |
|
|
||
| continue; | ||
| } | ||
| Event::ChannelBufferedAmountLow(channel_id) => { | ||
| if let Some(ChannelState::Closing) = self.channels.get(&channel_id) { | ||
| tracing::trace!( | ||
| target: LOG_TARGET, | ||
| peer = ?self.peer, | ||
| ?channel_id, | ||
| "buffer drained, closing channel", | ||
| ); | ||
| self.rtc.direct_api().close_data_channel(channel_id); | ||
| self.handles.remove(&channel_id); | ||
| } | ||
|
|
||
| continue; | ||
| } | ||
| event => { | ||
| tracing::debug!( | ||
| target: LOG_TARGET, | ||
|
|
@@ -794,10 +810,7 @@ impl WebRtcConnection { | |
| ?channel_id, | ||
| "channel closed", | ||
| ); | ||
|
|
||
| self.rtc.direct_api().close_data_channel(channel_id); | ||
| self.channels.insert(channel_id, ChannelState::Closing); | ||
| self.handles.remove(&channel_id); | ||
| } | ||
| Some((channel_id, Some(SubstreamEvent::Message(data)))) => { | ||
| if let Err(error) = self.on_outbound_data(channel_id, data) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was this observed with smoldot integration?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was observed when testing against the libp2p native WebRTC transport. During negotiation, the libp2p native WebRTC transport doesn't append an extra newline.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem is that
Message::decode()conflateslsresponses with protocol requests and confirmation messages. As I mentioned in another comment, there are small differences.lsresponse:protocol request with fallback protocols:
protocol confirmation:
When using webrtc transport, there is another length prefix for the protobuf frame. But that is independent of whether the payload is a multistream-select message or not.
edit: @timwu20 pointed me towards #75 which in turn links to this spec for multistream-select that specifies a back-and-forth process for providing fallback protocols. I've been working off this spec, which glosses over this. So I have to correct my example above: The "protocol request with fallback protocols" does not exist in this form, which means that
Message::decode()does not need to support it. :)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would not add this code due to possible incompatibilities for current transports.
I see rust-libp2p is using a similar code path to what we did before:
Instead, we should get in touch with smoldot to adjust any multistream-select inconsistencies (or impl
lssimilarly to litep2p/libp2p 🙏 )Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to Tim's comment, he added this when testing webrtc transport between litep2p and rust-libp2p.
I just tested litep2p <-> smoldot without this change and it doesn't work either. The reason why rust-libp2p <-> smoldot works without this change is that
MessageIOyields oneMessageat a time, even if the remote sent both header and protocol name in a single webrtc protobuf frame.@lexnv I completely agree with your concern that we must not break backwards compatibility for other transports. I see two alternatives to including this change:
webrtc_encode_multistream_message.MessageIO,DialerSelectFuture,ListenerSelectFuture, etc.I know that you prefer option 2 based on your comment here and I'm happy to work on that. But I'm also expected to make progress on litep2p <-> smoldot interop and don't have a clear picture of how to implement option 2 yet. 🫤
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a third option: Adding the trailing newline in
webrtc_listener_negotiate()before passing it toMessage::decode().@lexnv Is this acceptable to you as a short-term solution if we implement option 2 once we have achieved interoperability?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The third option is adding the quick workaround without modifying the MultiStream Select core logic.
I'm mostly concerned about subtle changes that will lead to litep2p <-> old litep2p validators misbehaving and causing wide connectivity issues. When this happens, usually a higher level starts to manifest undefined behaviors. The load we are experiencing in kusama / polkadot is not reproducible by our testing setups yet, and spawning large test nets is unsustainable budget-wise 🙏