use crate::error::{Error, ErrorCode, Result, TransportError};
use crate::shared::http_constants::{CONTENT_TYPE, TEXT_EVENT_STREAM};
use crate::shared::sse_parser::{take_utf8_prefix, SseParser};
use crate::shared::StreamableHttpTransport;
use crate::types::jsonrpc::RequestId;
use crate::types::mrtr::META_KEY;
use crate::types::notifications::ServerNotification;
use crate::types::subscriptions::{
request_id_value, SubscriptionAcknowledgedParams, ACKNOWLEDGED_METHOD, SUBSCRIPTION_ID_META_KEY,
};
use async_trait::async_trait;
use futures::{Stream, StreamExt};
use http_body_util::BodyExt;
use serde_json::Value;
use std::collections::VecDeque;
use std::pin::Pin;
use std::task::{Context, Poll};
const MAX_ECHOED_FRAME: usize = 200;
const MAX_LISTEN_LINE_BYTES: usize = 256 * 1024;
pub type SubscriptionFrameStream = Pin<Box<dyn Stream<Item = Result<String>> + Send>>;
#[async_trait]
pub trait EventStreamTransport {
async fn open_event_stream(&self, body: Vec<u8>) -> Result<SubscriptionFrameStream>;
}
#[async_trait]
impl EventStreamTransport for StreamableHttpTransport {
async fn open_event_stream(&self, body: Vec<u8>) -> Result<SubscriptionFrameStream> {
let response = self.post_streaming(body).await?;
let status = response.status();
let is_event_stream = response
.headers()
.get(CONTENT_TYPE)
.and_then(|value| value.to_str().ok())
.is_some_and(|value| value.contains(TEXT_EVENT_STREAM));
if !is_event_stream || !status.is_success() {
let collected = match self.collect_capped_body(response).await {
Ok(bytes) => bytes,
Err(error) => return Err(error),
};
return Err(rejection_error(status, &collected));
}
Ok(Box::pin(sse_payload_stream(response.into_body())))
}
}
fn rejection_error(status: hyper::StatusCode, collected: &[u8]) -> Error {
if let Ok(value) = serde_json::from_slice::<Value>(collected) {
let is_jsonrpc = value.get("jsonrpc").and_then(Value::as_str) == Some("2.0");
if let (true, Some(error)) = (is_jsonrpc, value.get("error")) {
if let Ok(error) =
serde_json::from_value::<crate::types::jsonrpc::JSONRPCError>(error.clone())
{
return Error::from_jsonrpc_error(error);
}
}
}
Error::Transport(TransportError::Request(format!(
"subscriptions/listen did not open a stream (HTTP {status}): {}",
truncate(&String::from_utf8_lossy(collected))
)))
}
struct PayloadState {
body: hyper::body::Incoming,
parser: SseParser,
bytes: Vec<u8>,
pending: VecDeque<String>,
done: bool,
}
fn sse_payload_stream(body: hyper::body::Incoming) -> impl Stream<Item = Result<String>> + Send {
let state = PayloadState {
body,
parser: SseParser::with_max_buffer_size(MAX_LISTEN_LINE_BYTES),
bytes: Vec::new(),
pending: VecDeque::new(),
done: false,
};
futures::stream::unfold(state, |mut state| async move {
loop {
if let Some(payload) = state.pending.pop_front() {
return Some((Ok(payload), state));
}
if state.done {
return None;
}
if let Some(error) = read_next_frame(&mut state).await {
return Some((Err(error), state));
}
}
})
}
async fn read_next_frame(state: &mut PayloadState) -> Option<Error> {
match state.body.frame().await {
None => {
state.done = true;
None
},
Some(Err(e)) => {
state.done = true;
Some(Error::Transport(TransportError::Request(e.to_string())))
},
Some(Ok(frame)) => {
if let Some(chunk) = frame.data_ref() {
state.bytes.extend_from_slice(chunk);
let text = take_utf8_prefix(&mut state.bytes);
state
.pending
.extend(drain_sse_payloads(&mut state.parser, &text));
if let Some(error) = listen_overflow(&state.parser) {
state.done = true;
return Some(error);
}
}
None
},
}
}
fn drain_sse_payloads(parser: &mut SseParser, chunk: &str) -> Vec<String> {
parser
.feed(chunk)
.into_iter()
.filter(|event| event.event.as_deref().is_none_or(|name| name == "message"))
.map(|event| event.data)
.collect()
}
fn listen_overflow(parser: &SseParser) -> Option<Error> {
if !parser.overflowed() {
return None;
}
Some(Error::protocol(
ErrorCode::INVALID_REQUEST,
format!(
"a subscriptions/listen chunk pushed the buffered stream state past the \
{}-byte parser bound; the buffered bytes were discarded and the stream \
was ended",
parser.max_buffer_size()
),
))
}
fn truncate(text: &str) -> String {
let mut boundary = None;
for (index, (offset, _)) in text.char_indices().enumerate() {
if index == MAX_ECHOED_FRAME {
boundary = Some(offset);
break;
}
}
let Some(boundary) = boundary else {
return text.to_string();
};
let mut out = String::with_capacity(boundary + '…'.len_utf8());
out.push_str(&text[..boundary]);
out.push('…');
out
}
enum FrameOutcome {
Notification(Box<ServerNotification>),
Terminal,
Failed(Box<Error>),
}
pub struct SubscriptionStream {
subscription_id: RequestId,
acknowledged: SubscriptionAcknowledgedParams,
frames: SubscriptionFrameStream,
finished: bool,
}
impl std::fmt::Debug for SubscriptionStream {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SubscriptionStream")
.field("subscription_id", &self.subscription_id)
.field("acknowledged", &self.acknowledged)
.field("finished", &self.finished)
.finish_non_exhaustive()
}
}
impl SubscriptionStream {
pub(crate) async fn open(
subscription_id: RequestId,
mut frames: SubscriptionFrameStream,
) -> Result<Self> {
let Some(first) = frames.next().await else {
return Err(Error::protocol_msg(
"subscriptions/listen stream ended before the mandatory acknowledgement",
));
};
let payload = first?;
let frame = serde_json::from_str::<Value>(&payload).map_err(|e| {
Error::parse(format!(
"subscriptions/listen acknowledgement is not JSON ({e}): {}",
truncate(&payload)
))
})?;
if frame.get("method").and_then(Value::as_str) != Some(ACKNOWLEDGED_METHOD) {
return Err(Error::protocol(
ErrorCode::INVALID_REQUEST,
format!(
"spec MUST: the first message on a subscriptions/listen stream is \
{ACKNOWLEDGED_METHOD}; got {}",
truncate(&payload)
),
));
}
verify_subscription_id(&frame, &subscription_id)?;
let acknowledged = frame
.get("params")
.cloned()
.ok_or_else(|| {
Error::parse("the subscriptions/listen acknowledgement carries no params")
})
.and_then(|params| {
serde_json::from_value::<SubscriptionAcknowledgedParams>(params)
.map_err(|e| Error::parse(format!("invalid acknowledgement params: {e}")))
})?;
Ok(Self {
subscription_id,
acknowledged,
frames,
finished: false,
})
}
#[must_use]
pub fn subscription_id(&self) -> &RequestId {
&self.subscription_id
}
#[must_use]
pub fn acknowledged(&self) -> &SubscriptionAcknowledgedParams {
&self.acknowledged
}
}
impl Stream for SubscriptionStream {
type Item = Result<ServerNotification>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
if this.finished {
return Poll::Ready(None);
}
match this.frames.as_mut().poll_next(cx) {
Poll::Pending => Poll::Pending,
Poll::Ready(None) => {
this.finished = true;
Poll::Ready(None)
},
Poll::Ready(Some(Err(e))) => {
this.finished = true;
Poll::Ready(Some(Err(e)))
},
Poll::Ready(Some(Ok(payload))) => {
match classify_frame(&payload, &this.subscription_id) {
FrameOutcome::Notification(notification) => {
Poll::Ready(Some(Ok(*notification)))
},
FrameOutcome::Terminal => {
this.finished = true;
Poll::Ready(None)
},
FrameOutcome::Failed(e) => Poll::Ready(Some(Err(*e))),
}
},
}
}
}
fn verify_subscription_id(frame: &Value, expected: &RequestId) -> Result<()> {
let observed = ["params", "result"].into_iter().find_map(|section| {
frame
.get(section)?
.get(META_KEY)?
.get(SUBSCRIPTION_ID_META_KEY)
});
let expected_value = request_id_value(expected);
if observed == Some(&expected_value) {
return Ok(());
}
Err(Error::protocol(
ErrorCode::INVALID_REQUEST,
format!(
"subscriptions/listen frame carries subscriptionId {} but this stream is {expected_value}; \
refusing to deliver another subscription's frame",
observed.map_or_else(|| "<absent>".to_string(), ToString::to_string),
),
))
}
fn classify_frame(payload: &str, subscription_id: &RequestId) -> FrameOutcome {
let Ok(frame) = serde_json::from_str::<Value>(payload) else {
return FrameOutcome::Failed(Box::new(Error::parse(format!(
"subscriptions/listen frame is not JSON: {}",
truncate(payload)
))));
};
if let Some(error) = frame.get("error") {
let error = serde_json::from_value::<crate::types::jsonrpc::JSONRPCError>(error.clone())
.map_or_else(
|_| Error::protocol_msg(format!("subscriptions/listen error frame: {error}")),
Error::from_jsonrpc_error,
);
return FrameOutcome::Failed(Box::new(error));
}
if let Err(e) = verify_subscription_id(&frame, subscription_id) {
return FrameOutcome::Failed(Box::new(e));
}
if frame.get("result").is_some() {
return FrameOutcome::Terminal;
}
if frame.get("method").and_then(Value::as_str) == Some(ACKNOWLEDGED_METHOD) {
return FrameOutcome::Failed(Box::new(Error::protocol(
ErrorCode::INVALID_REQUEST,
"spec MUST: a subscriptions/listen stream is acknowledged exactly once, and a second \
acknowledgement arrived",
)));
}
match decode_notification(frame, payload) {
Ok(notification) => FrameOutcome::Notification(Box::new(notification)),
Err(e) => FrameOutcome::Failed(Box::new(e)),
}
}
fn decode_notification(mut cleaned: Value, payload: &str) -> Result<ServerNotification> {
let Some(object) = cleaned.as_object_mut() else {
return Err(Error::parse("subscriptions/listen frame is not an object"));
};
object.remove("jsonrpc");
object.remove("id");
let drop_params = match object.get_mut("params") {
Some(Value::Object(params)) => {
params.remove(META_KEY);
params.is_empty()
},
_ => false,
};
if drop_params {
object.remove("params");
}
serde_json::from_value::<ServerNotification>(cleaned).map_err(|e| {
Error::parse(format!(
"subscriptions/listen frame is not a known server notification ({e}): {}",
truncate(payload)
))
})
}
#[cfg(any(feature = "fuzzing", test))]
#[doc(hidden)]
#[must_use]
pub fn decode_listen_chunks_for_fuzz(
chunks: &[&[u8]],
subscription_id: &str,
max_buffer_size: usize,
) -> (
Vec<std::result::Result<ServerNotification, String>>,
Vec<bool>,
Vec<usize>,
) {
let id = RequestId::String(subscription_id.to_string());
let mut parser = SseParser::with_max_buffer_size(max_buffer_size);
let mut bytes: Vec<u8> = Vec::new();
let mut outcomes = Vec::new();
let mut overflowed = Vec::with_capacity(chunks.len());
let mut peak_buffered_bytes = Vec::with_capacity(chunks.len());
for chunk in chunks {
bytes.extend_from_slice(chunk);
let text = take_utf8_prefix(&mut bytes);
outcomes.extend(
drain_sse_payloads(&mut parser, &text)
.into_iter()
.filter_map(|payload| match classify_frame(&payload, &id) {
FrameOutcome::Notification(notification) => Some(Ok(*notification)),
FrameOutcome::Failed(e) => Some(Err(e.to_string())),
FrameOutcome::Terminal => None,
}),
);
overflowed.push(listen_overflow(&parser).is_some());
peak_buffered_bytes.push(parser.buffered_bytes());
}
(outcomes, overflowed, peak_buffered_bytes)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::subscriptions::{subscription_id_meta, SubscriptionFilter};
use serde_json::json;
fn stream_over(
subscription_id: RequestId,
payloads: Vec<Result<String>>,
) -> SubscriptionStream {
SubscriptionStream {
subscription_id,
acknowledged: SubscriptionAcknowledgedParams::default(),
frames: Box::pin(futures::stream::iter(payloads)),
finished: false,
}
}
fn id() -> RequestId {
RequestId::Number(11)
}
fn ack_payload(subscription_id: &RequestId, filter: &Value) -> String {
json!({
"jsonrpc": "2.0",
"method": ACKNOWLEDGED_METHOD,
"params": {
"notifications": filter,
"_meta": subscription_id_meta(subscription_id),
},
})
.to_string()
}
fn tools_changed(subscription_id: &RequestId) -> String {
json!({
"jsonrpc": "2.0",
"method": "notifications/tools/list_changed",
"params": { "_meta": subscription_id_meta(subscription_id) },
})
.to_string()
}
fn resource_updated(subscription_id: &RequestId, uri: &str) -> String {
json!({
"jsonrpc": "2.0",
"method": "notifications/resources/updated",
"params": { "uri": uri, "_meta": subscription_id_meta(subscription_id) },
})
.to_string()
}
fn terminal(subscription_id: &RequestId) -> String {
json!({
"jsonrpc": "2.0",
"id": request_id_value(subscription_id),
"result": { "_meta": subscription_id_meta(subscription_id) },
})
.to_string()
}
#[tokio::test]
async fn open_consumes_the_acknowledgement_and_exposes_the_agreed_filter() {
let frames: SubscriptionFrameStream = Box::pin(futures::stream::iter(vec![Ok(
ack_payload(&id(), &json!({ "toolsListChanged": true })),
)]));
let stream = SubscriptionStream::open(id(), frames)
.await
.expect("the ack opens the stream");
assert_eq!(stream.subscription_id(), &id());
assert_eq!(
stream.acknowledged().notifications,
SubscriptionFilter {
tools_list_changed: Some(true),
..SubscriptionFilter::default()
},
"the agreed filter is readable BEFORE the first poll"
);
}
#[tokio::test]
async fn a_non_acknowledgement_first_frame_is_refused() {
let frames: SubscriptionFrameStream =
Box::pin(futures::stream::iter(vec![Ok(tools_changed(&id()))]));
let error = SubscriptionStream::open(id(), frames)
.await
.expect_err("a notification cannot precede the acknowledgement");
assert!(
error.to_string().contains(ACKNOWLEDGED_METHOD),
"the error names the spec MUST: {error}"
);
}
#[tokio::test]
async fn an_acknowledgement_for_another_subscription_is_refused() {
let frames: SubscriptionFrameStream = Box::pin(futures::stream::iter(vec![Ok(
ack_payload(&RequestId::Number(999), &json!({})),
)]));
let error = SubscriptionStream::open(id(), frames)
.await
.expect_err("a cross-tagged ack must not open a stream");
assert!(
error.to_string().contains("subscriptionId"),
"the error names the mismatch: {error}"
);
}
#[tokio::test]
async fn an_empty_stream_is_refused() {
let frames: SubscriptionFrameStream = Box::pin(futures::stream::iter(Vec::new()));
let error = SubscriptionStream::open(id(), frames)
.await
.expect_err("no frame at all is not an acknowledgement");
assert!(error.to_string().contains("acknowledgement"), "{error}");
}
#[tokio::test]
async fn a_tagged_unit_notification_is_decoded() {
let mut stream = stream_over(id(), vec![Ok(tools_changed(&id()))]);
let item = stream.next().await.expect("one frame").expect("decodes");
assert!(matches!(item, ServerNotification::ToolsChanged));
assert!(stream.next().await.is_none(), "then the stream ends");
}
#[tokio::test]
async fn a_tagged_struct_notification_is_decoded() {
let mut stream = stream_over(id(), vec![Ok(resource_updated(&id(), "mem://greeting"))]);
let item = stream.next().await.expect("one frame").expect("decodes");
match item {
ServerNotification::ResourceUpdated(params) => {
assert_eq!(params.uri, "mem://greeting");
},
other => panic!("expected a resources/updated notification, got {other:?}"),
}
}
#[tokio::test]
async fn a_frame_tagged_with_another_subscription_id_yields_an_error() {
let mut stream = stream_over(
id(),
vec![
Ok(tools_changed(&RequestId::Number(999))),
Ok(tools_changed(&id())),
],
);
let error = stream
.next()
.await
.expect("an item")
.expect_err("a cross-tagged frame is never forwarded as the caller's own");
assert!(
error.to_string().contains("999"),
"the error names the foreign id: {error}"
);
let recovered = stream
.next()
.await
.expect("the stream did NOT terminate")
.expect("the correctly tagged frame still arrives");
assert!(matches!(recovered, ServerNotification::ToolsChanged));
}
#[tokio::test]
async fn an_untagged_frame_yields_an_error() {
let payload = json!({
"jsonrpc": "2.0",
"method": "notifications/tools/list_changed",
})
.to_string();
let mut stream = stream_over(id(), vec![Ok(payload)]);
let error = stream.next().await.expect("an item").expect_err("untagged");
assert!(error.to_string().contains("<absent>"), "{error}");
}
#[tokio::test]
async fn a_malformed_frame_yields_an_error_without_ending_the_stream() {
let mut stream = stream_over(
id(),
vec![Ok("{not json at all".to_string()), Ok(tools_changed(&id()))],
);
let error = stream
.next()
.await
.expect("an item")
.expect_err("garbage is an error");
assert!(error.to_string().contains("not JSON"), "{error}");
let recovered = stream
.next()
.await
.expect("the stream survived the malformed frame")
.expect("and the next good frame decodes");
assert!(matches!(recovered, ServerNotification::ToolsChanged));
}
#[tokio::test]
async fn an_unknown_notification_method_yields_an_error_without_ending_the_stream() {
let payload = json!({
"jsonrpc": "2.0",
"method": "notifications/from/the/future",
"params": { "_meta": subscription_id_meta(&id()) },
})
.to_string();
let mut stream = stream_over(id(), vec![Ok(payload), Ok(tools_changed(&id()))]);
assert!(
stream.next().await.expect("an item").is_err(),
"an unmodelled method is surfaced, not silently dropped"
);
assert!(
stream.next().await.expect("still live").is_ok(),
"and the stream keeps going"
);
}
#[tokio::test]
async fn a_second_acknowledgement_yields_an_error() {
let mut stream = stream_over(id(), vec![Ok(ack_payload(&id(), &json!({})))]);
let error = stream
.next()
.await
.expect("an item")
.expect_err("exactly one acknowledgement is allowed");
assert!(error.to_string().contains("exactly once"), "{error}");
}
#[tokio::test]
async fn the_terminal_result_ends_the_stream() {
let mut stream = stream_over(
id(),
vec![
Ok(tools_changed(&id())),
Ok(terminal(&id())),
Ok(tools_changed(&id())),
],
);
assert!(stream.next().await.expect("the notification").is_ok());
assert!(
stream.next().await.is_none(),
"the terminal SubscriptionsListenResult ends the stream gracefully"
);
assert!(
stream.next().await.is_none(),
"and it stays ended — nothing after it is delivered"
);
}
#[tokio::test]
async fn a_transport_error_ends_the_stream() {
let mut stream = stream_over(
id(),
vec![
Err(Error::Transport(TransportError::ConnectionClosed)),
Ok(tools_changed(&id())),
],
);
assert!(stream.next().await.expect("an item").is_err());
assert!(
stream.next().await.is_none(),
"a transport failure is terminal, unlike a bad frame"
);
}
#[test]
fn keep_alive_comments_are_skipped() {
let mut parser = SseParser::new();
assert!(
drain_sse_payloads(&mut parser, ": keep-alive\n\n").is_empty(),
"a comment line is not a payload"
);
assert!(
drain_sse_payloads(&mut parser, ":\n\n").is_empty(),
"an empty comment is not a payload either"
);
assert_eq!(
drain_sse_payloads(&mut parser, "event: message\ndata: {\"a\":1}\n\n"),
vec!["{\"a\":1}".to_string()],
"and the real event still arrives"
);
}
#[test]
fn a_payload_split_across_chunks_is_reassembled() {
let mut parser = SseParser::new();
assert!(drain_sse_payloads(&mut parser, "data: {\"a\"").is_empty());
assert_eq!(
drain_sse_payloads(&mut parser, ":1}\n\n"),
vec!["{\"a\":1}".to_string()]
);
}
#[test]
fn a_multibyte_character_split_across_chunks_survives() {
let text = "data: \u{2602}\n\n";
let bytes = text.as_bytes();
let mut buffer = Vec::new();
let mut parser = SseParser::new();
let mut payloads = Vec::new();
buffer.extend_from_slice(&bytes[..7]); let prefix = take_utf8_prefix(&mut buffer);
payloads.extend(drain_sse_payloads(&mut parser, &prefix));
assert!(!buffer.is_empty(), "the incomplete tail is retained");
buffer.extend_from_slice(&bytes[7..]);
let rest = take_utf8_prefix(&mut buffer);
payloads.extend(drain_sse_payloads(&mut parser, &rest));
assert_eq!(payloads, vec!["\u{2602}".to_string()]);
}
#[test]
fn invalid_bytes_do_not_wedge_the_decoder() {
let mut buffer = vec![0xff, 0xfe, b'a'];
let text = take_utf8_prefix(&mut buffer);
assert!(
buffer.is_empty(),
"genuinely invalid bytes are consumed, not retained forever"
);
assert!(text.contains('a'), "the valid remainder survives: {text:?}");
}
#[test]
fn a_line_past_the_bound_latches_the_parser_and_ends_the_stream() {
let mut parser = SseParser::with_max_buffer_size(64);
assert!(
listen_overflow(&parser).is_none(),
"a fresh parser has lost nothing, so the stream keeps reading"
);
assert!(
drain_sse_payloads(&mut parser, &"x".repeat(256)).is_empty(),
"an unterminated line completes no payload"
);
let error = listen_overflow(&parser).expect("a discarded line ends the stream");
assert!(
error.to_string().contains("64"),
"the error names the limit the parser actually enforced: {error}"
);
match error {
Error::Protocol { code, .. } => {
assert_eq!(code, ErrorCode::INVALID_REQUEST);
},
other => panic!("expected a structured protocol error, got {other:?}"),
}
}
#[test]
fn a_newline_carrying_flood_ends_the_stream_too() {
let mut parser = SseParser::with_max_buffer_size(64);
let mut error = None;
for _ in 0..1_000 {
assert!(
drain_sse_payloads(&mut parser, "data: AAAAAAAA\n").is_empty(),
"a `data:` line with no blank line after it completes no payload"
);
if let Some(seen) = listen_overflow(&parser) {
error = Some(seen);
break;
}
}
let error = error.expect("accumulated `data:` lines must trip the bound");
assert!(
error.to_string().contains("64"),
"the error names the limit the parser actually enforced: {error}"
);
assert!(
!error.to_string().contains('A'),
"no fed byte is echoed into the message: {error}"
);
}
#[test]
fn a_normal_listen_stream_never_trips_the_bound() {
let mut parser = SseParser::with_max_buffer_size(MAX_LISTEN_LINE_BYTES);
let payloads = drain_sse_payloads(
&mut parser,
&format!("event: message\ndata: {}\n\n", tools_changed(&id())),
);
assert_eq!(payloads.len(), 1);
assert!(listen_overflow(&parser).is_none());
}
#[test]
fn the_listen_bound_is_tighter_than_the_shared_default() {
let shared = crate::shared::sse_parser::SseConfig::default().max_buffer_size;
assert!(
MAX_LISTEN_LINE_BYTES < shared,
"{MAX_LISTEN_LINE_BYTES} must be tighter than the shared {shared}"
);
}
#[test]
fn the_chunked_fuzz_seam_trips_the_bound_partway_through() {
let chunk = [b'x'; 16];
let chunks: Vec<&[u8]> = vec![&chunk[..]; 8];
let (outcomes, overflowed, peak_buffered_bytes) =
decode_listen_chunks_for_fuzz(&chunks, "sub-1", 64);
assert!(
outcomes.is_empty(),
"an unterminated line completes no frame"
);
assert_eq!(overflowed.len(), chunks.len(), "one observation per chunk");
assert!(
!overflowed[0],
"the bound is not tripped by the first chunk"
);
assert!(
overflowed.last().copied().unwrap_or_default(),
"the bound is tripped by the end: {overflowed:?}"
);
assert_eq!(
peak_buffered_bytes.len(),
chunks.len(),
"one retention sample per chunk"
);
assert!(
peak_buffered_bytes.iter().all(|held| *held <= 64),
"retention stays inside the bound even while it is being approached: \
{peak_buffered_bytes:?}"
);
}
#[test]
fn the_chunked_fuzz_seam_carries_a_frame_split_across_chunks() {
let id = RequestId::String("sub-1".to_string());
let body = format!("event: message\ndata: {}\n\n", tools_changed(&id));
let bytes = body.as_bytes();
let chunks: Vec<&[u8]> = bytes.chunks(5).collect();
assert!(chunks.len() > 1, "the frame must actually be split");
let (outcomes, overflowed, peak_buffered_bytes) =
decode_listen_chunks_for_fuzz(&chunks, "sub-1", MAX_LISTEN_LINE_BYTES);
assert_eq!(outcomes.len(), 1, "exactly one frame is reassembled");
assert!(outcomes[0].is_ok(), "and it is delivered: {outcomes:?}");
assert!(
overflowed.iter().all(|seen| !*seen),
"a legitimate frame never trips the bound"
);
assert!(
peak_buffered_bytes
.iter()
.all(|held| *held <= MAX_LISTEN_LINE_BYTES),
"a legitimate frame stays inside the bound: {peak_buffered_bytes:?}"
);
}
#[test]
fn the_seam_reports_retention_that_stays_inside_a_tiny_bound_while_reaching_it() {
const BOUND: usize = 64;
let line = b"data: AAAAAAAA\n";
let chunks: Vec<&[u8]> = vec![&line[..]; 20];
let (outcomes, overflowed, peak_buffered_bytes) =
decode_listen_chunks_for_fuzz(&chunks, "sub-1", BOUND);
assert!(
outcomes.is_empty(),
"a `data:` line with no blank line after it completes no frame: {outcomes:?}"
);
assert_eq!(
peak_buffered_bytes.len(),
chunks.len(),
"one retention sample per chunk"
);
assert!(
peak_buffered_bytes.iter().all(|held| *held <= BOUND),
"the campaign's invariant: retention never exceeds the bound, \
{peak_buffered_bytes:?} under {BOUND}"
);
assert!(
overflowed.iter().any(|seen| *seen),
"non-vacuity: the flood must actually REACH the bound, or the \
invariant above holds for an uninteresting reason: {overflowed:?}"
);
assert!(
peak_buffered_bytes.iter().any(|held| *held > line.len()),
"non-vacuity: retention must ACCUMULATE across lines, not merely \
hold one chunk: {peak_buffered_bytes:?}"
);
}
#[test]
fn an_untrusted_frame_is_truncated_in_error_messages() {
let huge = "x".repeat(MAX_ECHOED_FRAME * 4);
let truncated = truncate(&huge);
assert!(truncated.chars().count() <= MAX_ECHOED_FRAME + 1);
assert!(truncated.ends_with('…'), "the elision is visible");
let exact = "y".repeat(MAX_ECHOED_FRAME);
assert_eq!(truncate(&exact), exact);
let umbrellas = "\u{2602}".repeat(MAX_ECHOED_FRAME * 2);
let cut = truncate(&umbrellas);
assert_eq!(cut.chars().count(), MAX_ECHOED_FRAME + 1);
}
#[test]
fn a_jsonrpc_error_envelope_is_surfaced_unchanged() {
let body = json!({
"jsonrpc": "2.0",
"id": 1,
"error": { "code": -32601, "message": "Method not found" },
})
.to_string();
let error = rejection_error(hyper::StatusCode::NOT_FOUND, body.as_bytes());
match error {
Error::Protocol { code, .. } => assert_eq!(code.as_i32(), -32601),
other => panic!("expected the server's own protocol error, got {other:?}"),
}
}
#[test]
fn a_non_envelope_body_becomes_a_truncated_transport_error() {
let body = "<html>".to_string() + &"z".repeat(MAX_ECHOED_FRAME * 10);
let error = rejection_error(hyper::StatusCode::BAD_GATEWAY, body.as_bytes());
let rendered = error.to_string();
assert!(matches!(error, Error::Transport(_)), "{rendered}");
assert!(rendered.contains("502"), "the status is named: {rendered}");
assert!(
rendered.matches('z').count() < MAX_ECHOED_FRAME + 1,
"the untrusted body is bounded in the message: {} echoed bytes",
rendered.matches('z').count()
);
}
const METHODS: [&str; 4] = [
"notifications/tools/list_changed",
"notifications/prompts/list_changed",
"notifications/resources/list_changed",
"notifications/from/the/future",
];
proptest::proptest! {
#[test]
fn a_frame_is_delivered_only_when_its_tag_matches_this_stream(
stream_id in 0i64..8,
frame_id in 0i64..8,
method_index in 0usize..4,
) {
let frame = json!({
"jsonrpc": "2.0",
"method": METHODS[method_index],
"params": {
"_meta": subscription_id_meta(&RequestId::Number(frame_id)),
},
});
let delivered = matches!(
classify_frame(&frame.to_string(), &RequestId::Number(stream_id)),
FrameOutcome::Notification(_)
);
proptest::prop_assert_eq!(
delivered,
stream_id == frame_id && method_index < 3,
"delivery must be exactly (matching tag AND known method); \
stream={} frame={} method={}",
stream_id,
frame_id,
METHODS[method_index],
);
}
#[test]
fn arbitrary_bytes_never_panic_the_decoder(
bytes in proptest::collection::vec(proptest::prelude::any::<u8>(), 0..512),
) {
let _ = decode_listen_chunks_for_fuzz(&[&bytes], "prop-subscription", MAX_LISTEN_LINE_BYTES);
}
#[test]
fn arbitrary_sse_shaped_text_never_panics_the_decoder(
body in "(data|event|id|:|\\{|\\}|\"|a|1|\n){0,200}",
) {
let _ = decode_listen_chunks_for_fuzz(&[body.as_bytes()], "prop-subscription", MAX_LISTEN_LINE_BYTES);
}
#[test]
fn chunked_arbitrary_bytes_never_panic_the_decoder(
bytes in proptest::collection::vec(proptest::prelude::any::<u8>(), 0..512),
) {
let chunks: Vec<&[u8]> = bytes.chunks(16).collect();
let _ = decode_listen_chunks_for_fuzz(&chunks, "prop-subscription", 64);
}
}
mod era_gate {
use super::*;
use crate::shared::{Transport, TransportMessage};
use crate::types::protocol::{ProtocolVersion, PROTOCOL_VERSION_2026_07_28};
use crate::{Client, ClientBuilder};
use std::sync::{Arc, Mutex};
#[derive(Debug, Default, Clone)]
struct CountingStubTransport {
opened: Arc<Mutex<Vec<Vec<u8>>>>,
payloads: Arc<Mutex<Vec<String>>>,
}
#[async_trait]
impl Transport for CountingStubTransport {
async fn send(&mut self, _message: TransportMessage) -> Result<()> {
Ok(())
}
async fn receive(&mut self) -> Result<TransportMessage> {
Err(Error::protocol_msg("no responses"))
}
async fn close(&mut self) -> Result<()> {
Ok(())
}
fn transport_type(&self) -> &'static str {
"counting-stub"
}
fn supports_negotiated_protocol_version(&self) -> bool {
true
}
}
#[async_trait]
impl EventStreamTransport for CountingStubTransport {
async fn open_event_stream(&self, body: Vec<u8>) -> Result<SubscriptionFrameStream> {
self.opened.lock().unwrap().push(body);
let payloads: Vec<Result<String>> = self
.payloads
.lock()
.unwrap()
.iter()
.cloned()
.map(Ok)
.collect();
Ok(Box::pin(futures::stream::iter(payloads)))
}
}
fn v2_version() -> ProtocolVersion {
ProtocolVersion(PROTOCOL_VERSION_2026_07_28.to_string())
}
fn client_with(
transport: CountingStubTransport,
v2: bool,
) -> Client<CountingStubTransport> {
let builder = ClientBuilder::new(transport);
if v2 {
builder
.with_protocol_version(v2_version())
.expect("2026-07-28 is selectable")
.build()
} else {
builder.build()
}
}
#[tokio::test]
async fn a_non_v2_client_refuses_without_opening_a_stream() {
let transport = CountingStubTransport::default();
let opened = transport.opened.clone();
let client = client_with(transport, false);
let error = client
.subscriptions_listen(SubscriptionFilter::default())
.await
.expect_err("subscriptions/listen does not exist on v1");
assert_eq!(
opened.lock().unwrap().len(),
0,
"a v1 client must not put a request on the wire that cannot succeed"
);
assert!(
error.to_string().contains("with_protocol_version"),
"the error names the opt-in: {error}"
);
}
#[tokio::test]
async fn a_v2_client_sends_the_listen_frame_and_consumes_the_ack() {
let transport = CountingStubTransport::default();
let opened = transport.opened.clone();
let client = client_with(transport, true);
let error = client
.subscriptions_listen(SubscriptionFilter {
tools_list_changed: Some(true),
..SubscriptionFilter::default()
})
.await
.expect_err("the stub sends no acknowledgement at all");
assert!(error.to_string().contains("acknowledgement"), "{error}");
let opened = opened.lock().unwrap();
assert_eq!(opened.len(), 1, "exactly one stream open");
let frame = serde_json::from_slice::<Value>(&opened[0]).expect("a JSON-RPC frame");
assert_eq!(frame["method"], json!("subscriptions/listen"));
assert_eq!(
frame["params"]["notifications"],
json!({ "toolsListChanged": true }),
"the requested filter travels under the REQUIRED `notifications` field"
);
assert_eq!(
frame["params"]["_meta"]["io.modelcontextprotocol/protocolVersion"],
json!(PROTOCOL_VERSION_2026_07_28),
"the v2 era signal is stamped like on every other v2 request"
);
}
#[tokio::test]
async fn a_v2_client_returns_the_servers_own_error_unchanged() {
#[derive(Debug, Default, Clone)]
struct RejectingTransport;
#[async_trait]
impl Transport for RejectingTransport {
async fn send(&mut self, _message: TransportMessage) -> Result<()> {
Ok(())
}
async fn receive(&mut self) -> Result<TransportMessage> {
Err(Error::protocol_msg("no responses"))
}
async fn close(&mut self) -> Result<()> {
Ok(())
}
fn supports_negotiated_protocol_version(&self) -> bool {
true
}
}
#[async_trait]
impl EventStreamTransport for RejectingTransport {
async fn open_event_stream(
&self,
_body: Vec<u8>,
) -> Result<SubscriptionFrameStream> {
Err(Error::from_jsonrpc_error(
crate::types::jsonrpc::JSONRPCError {
code: crate::types::protocol::error_codes::METHOD_NOT_FOUND,
message: "Method not found: subscriptions/listen".to_string(),
data: None,
},
))
}
}
let client = ClientBuilder::new(RejectingTransport)
.with_protocol_version(v2_version())
.expect("2026-07-28 is selectable")
.build();
let error = client
.subscriptions_listen(SubscriptionFilter::default())
.await
.expect_err("a non-advertising server answers -32601");
match error {
Error::Protocol { code, .. } => assert_eq!(
code.as_i32(),
crate::types::protocol::error_codes::METHOD_NOT_FOUND,
"the server's own error code reaches the caller unchanged"
),
other => panic!("expected a structured protocol error, got {other:?}"),
}
}
}
}