[Debug]
Some checks failed
CI / checks (push) Failing after 2m17s

This commit is contained in:
Alex Emmet 2026-08-20 20:37:25 +02:00
commit bd660b2afb
No known key found for this signature in database
4 changed files with 66 additions and 33 deletions

View file

@ -217,6 +217,12 @@ impl HandshakeEngine {
_authentication_context: &AuthenticationContext, _authentication_context: &AuthenticationContext,
) -> Result<HandshakeResult, AcceptError> { ) -> Result<HandshakeResult, AcceptError> {
let mut first_msg = receiver.receive().await.map_err(AcceptError::Receive)?; let mut first_msg = receiver.receive().await.map_err(AcceptError::Receive)?;
tracing::debug!(
message_type = ?first_msg.get_type(),
version = ?first_msg.get_str(DataType::Version),
client_id = ?first_msg.get_data(DataType::Id),
"received MTP opening message"
);
let version_str = match first_msg.get_data(DataType::Version) { let version_str = match first_msg.get_data(DataType::Version) {
Some(DataValue::Str(s)) => s.clone(), Some(DataValue::Str(s)) => s.clone(),
@ -271,6 +277,11 @@ impl HandshakeEngine {
return Err(AcceptError::UnsupportedVersion(client_version)); return Err(AcceptError::UnsupportedVersion(client_version));
} }
}; };
tracing::debug!(
client_version = %client_version,
negotiated_version = %negotiated,
"MTP protocol version negotiated"
);
let codec = VersionedCodec::for_version(self.registry.clone(), negotiated.clone()) let codec = VersionedCodec::for_version(self.registry.clone(), negotiated.clone())
.ok_or_else(|| AcceptError::UnsupportedVersion(negotiated.clone()))?; .ok_or_else(|| AcceptError::UnsupportedVersion(negotiated.clone()))?;
@ -1105,6 +1116,12 @@ async fn send_rejection_generic<S: HandshakeSender>(
.add_typed_default(DataType::Connected, DataValue::BoolFalse) .add_typed_default(DataType::Connected, DataValue::BoolFalse)
.add_typed_default(DataType::ErrorMessage, DataValue::Str(reason.to_string())), .add_typed_default(DataType::ErrorMessage, DataValue::Str(reason.to_string())),
}; };
tracing::debug!(
reason = %reason,
response_type = ?response.get_type(),
has_version = response.get_data(DataType::Version).is_some(),
"sending MTP handshake rejection"
);
let _ = sender.send(&response).await; let _ = sender.send(&response).await;
} }
@ -1138,6 +1155,11 @@ async fn send_accepted_generic<S: HandshakeSender>(
if let Some(id) = assigned_id { if let Some(id) = assigned_id {
response = response.add_typed_default(DataType::Id, DataValue::UnsignedNumber(id as u128)); response = response.add_typed_default(DataType::Id, DataValue::UnsignedNumber(id as u128));
} }
tracing::debug!(
version = %version,
assigned_id = ?assigned_id,
"sending accepted MTP handshake response"
);
sender.send(&response).await?; sender.send(&response).await?;
sender.finish_stream().await sender.finish_stream().await
} }

View file

@ -88,6 +88,7 @@ impl TransportRecvStream for H3TransportReceiver {
tracing::debug!( tracing::debug!(
remote = %self.quinn.remote_address(), remote = %self.quinn.remote_address(),
bytes = buf.len(), bytes = buf.len(),
header = ?buf,
"received first bytes from WebTransport MTP stream" "received first bytes from WebTransport MTP stream"
); );
} }

View file

@ -423,6 +423,12 @@ impl<C: TransportConnection> GenericReceiver<C> {
break; break;
} }
}; };
tracing::debug!(
frames,
frame_len,
message_type = ?message.get_type(),
"decoded MTP receive frame"
);
let negotiated_type_map = type_map.read().await.clone(); let negotiated_type_map = type_map.read().await.clone();
message.set_type_map(&negotiated_type_map); message.set_type_map(&negotiated_type_map);

View file

@ -128,8 +128,6 @@ pub struct WasmTransport {
buffer: Rc<RefCell<Vec<u8>>>, buffer: Rc<RefCell<Vec<u8>>>,
/// Set to `true` when `open_next_stream` succeeds; cleared after the first frame is parsed. /// Set to `true` when `open_next_stream` succeeds; cleared after the first frame is parsed.
new_stream_frame: Rc<Cell<bool>>, new_stream_frame: Rc<Cell<bool>>,
/// A single ordered browser send stream shared by all cloned transports.
outgoing_writer: Rc<RefCell<Option<JsValue>>>,
/// Serializes stream creation and writes across concurrent callers. /// Serializes stream creation and writes across concurrent callers.
send_lock: Rc<AsyncMutex<()>>, send_lock: Rc<AsyncMutex<()>>,
type_map: Rc<RefCell<TypeMap>>, type_map: Rc<RefCell<TypeMap>>,
@ -209,7 +207,6 @@ impl WasmTransport {
stream_reader: Rc::new(RefCell::new(None)), stream_reader: Rc::new(RefCell::new(None)),
buffer: Rc::new(RefCell::new(Vec::new())), buffer: Rc::new(RefCell::new(Vec::new())),
new_stream_frame: Rc::new(Cell::new(false)), new_stream_frame: Rc::new(Cell::new(false)),
outgoing_writer: Rc::new(RefCell::new(None)),
send_lock: Rc::new(AsyncMutex::new(())), send_lock: Rc::new(AsyncMutex::new(())),
type_map: Rc::new(RefCell::new(TypeMap::latest())), type_map: Rc::new(RefCell::new(TypeMap::latest())),
decode_limits: Rc::new(RefCell::new(decode_limits)), decode_limits: Rc::new(RefCell::new(decode_limits)),
@ -247,9 +244,12 @@ impl WasmTransport {
return Err(js_error("message too large")); return Err(js_error("message too large"));
} }
let writer_val = if let Some(writer) = self.outgoing_writer.borrow().clone() { // Use one WebTransport uni-stream per MTP frame. Chromium reliably
writer // publishes a browser-created uni-stream to the peer when it is
} else { // closed; leaving a shared stream open can leave the server waiting
// in accept_uni() until the authentication deadline. The bytes are
// already the canonical MTP self-framed value, so no extra stream
// length prefix is added here.
let create_stream = js_sys::Reflect::get( let create_stream = js_sys::Reflect::get(
&self.inner, &self.inner,
&JsValue::from_str("createUnidirectionalStream"), &JsValue::from_str("createUnidirectionalStream"),
@ -262,15 +262,12 @@ impl WasmTransport {
.map_err(|_| js_error("createUnidirectionalStream did not return a Promise"))?; .map_err(|_| js_error("createUnidirectionalStream did not return a Promise"))?;
let stream = JsFuture::from(stream_promise).await?; let stream = JsFuture::from(stream_promise).await?;
let writable_or_stream = resolve_stream_writable(&stream)?; let writable_or_stream = resolve_stream_writable(&stream)?;
let writer = js_sys::Reflect::get(&writable_or_stream, &JsValue::from_str("getWriter")) let writer_val = js_sys::Reflect::get(&writable_or_stream, &JsValue::from_str("getWriter"))
.map_err(|_| js_error("missing getWriter"))? .map_err(|_| js_error("missing getWriter"))?
.dyn_into::<js_sys::Function>() .dyn_into::<js_sys::Function>()
.map_err(|_| js_error("getWriter not a function"))? .map_err(|_| js_error("getWriter not a function"))?
.call0(&writable_or_stream) .call0(&writable_or_stream)
.map_err(|_| js_error("getWriter call failed"))?; .map_err(|_| js_error("getWriter call failed"))?;
*self.outgoing_writer.borrow_mut() = Some(writer.clone());
writer
};
let chunk = js_sys::Uint8Array::from(frame); let chunk = js_sys::Uint8Array::from(frame);
@ -283,11 +280,24 @@ impl WasmTransport {
.map_err(|e| js_error(format!("write failed: {:?}", e)))?; .map_err(|e| js_error(format!("write failed: {:?}", e)))?;
if let Err(e) = JsFuture::from(write_promise.unchecked_into::<js_sys::Promise>()).await { if let Err(e) = JsFuture::from(write_promise.unchecked_into::<js_sys::Promise>()).await {
log_stream_error_code(&e, "send_frame write"); log_stream_error_code(&e, "send_frame write");
self.outgoing_writer.borrow_mut().take();
release_writer_lock(&writer_val); release_writer_lock(&writer_val);
return Err(e); return Err(e);
} }
let close_fn = js_sys::Reflect::get(&writer_val, &JsValue::from_str("close"))
.map_err(|_| js_error("missing close"))?
.dyn_into::<js_sys::Function>()
.map_err(|_| js_error("close not a function"))?;
let close_promise = close_fn
.call0(&writer_val)
.map_err(|e| js_error(format!("close failed: {:?}", e)))?;
if let Err(e) = JsFuture::from(close_promise.unchecked_into::<js_sys::Promise>()).await {
// The frame was already written; do not retry it merely because
// FIN failed, as that would duplicate the MTP frame.
log_stream_error_code(&e, "send_frame close");
}
release_writer_lock(&writer_val);
Ok(()) Ok(())
} }
@ -677,12 +687,6 @@ impl WasmTransport {
} }
pub fn close(&self) { pub fn close(&self) {
if let Some(writer) = self.outgoing_writer.borrow_mut().take() {
// The WebTransport session close below terminates the stream. The
// lock must be released first so dropping it is not interpreted as
// an application abort.
release_writer_lock(&writer);
}
// Release reader locks before closing so they aren't treated as cancels. // Release reader locks before closing so they aren't treated as cancels.
if let Some(reader) = self.stream_reader.borrow_mut().take() { if let Some(reader) = self.stream_reader.borrow_mut().take() {
release_reader_lock(&reader); release_reader_lock(&reader);