use super::{
ContinuationReplayAuthority, ContinuationReplayLimits, ContinuationReplayMiddleware, Cx,
IDENTITY_BYTES, Identity, Instant, Journal, JsonRpcRequest, LimitedWriter, McpContext,
McpResult, ProcessGenerationGuard, SnapshotCloneStance, Value, Write, check_context,
sha256_bounded, unavailable,
};
use serde::ser::SerializeMap;
use serde::{Serialize, Serializer};
const CHAIN_IDENTITY_BYTES: usize = IDENTITY_BYTES + 128;
const RETIRED_IDENTITY_BYTES: usize = IDENTITY_BYTES + 32;
pub(super) struct SuccessorEntry {
operation: [u8; 32],
pub(super) successor: Option<[u8; 32]>,
pub(super) superseded: bool,
}
impl SuccessorEntry {
pub(super) fn new(operation: [u8; 32]) -> Self {
Self {
operation,
successor: None,
superseded: false,
}
}
}
impl ContinuationReplayMiddleware {
pub fn new_with_successor_recovery<F>(
cx: &Cx,
guard: &ProcessGenerationGuard,
stance: SnapshotCloneStance,
limits: ContinuationReplayLimits,
authorize: F,
) -> McpResult<Self>
where
F: Fn(&McpContext, &JsonRpcRequest) -> McpResult<ContinuationReplayAuthority>
+ Send
+ Sync
+ 'static,
{
let mut middleware = Self::new(cx, guard, stance, limits, authorize)?;
middleware.recover_successors = true;
Ok(middleware)
}
pub(super) fn identity_bytes(&self) -> usize {
if self.recover_successors {
CHAIN_IDENTITY_BYTES
} else {
IDENTITY_BYTES
}
}
pub(super) fn slot(
&self,
authority: &ContinuationReplayAuthority,
method: &str,
state: &str,
) -> McpResult<[u8; 32]> {
let maximum = self.limits.request_bytes + 256;
let mut selector = LimitedWriter::new(maximum);
selector
.write_all(b"fastmcp/mrtr-terminal-slot/v1\0")
.map_err(|_| unavailable())?;
selector
.write_all(authority.key.as_bytes())
.map_err(|_| unavailable())?;
selector
.write_all(authority.authorization.as_bytes())
.map_err(|_| unavailable())?;
serde_json::to_writer(&mut selector, &(method, state)).map_err(|_| unavailable())?;
Ok(sha256_bounded(&selector.bytes, maximum)
.map_err(|_| unavailable())?
.into_bytes())
}
}
impl Journal {
pub(super) fn predecessor(
&self,
ctx: &McpContext,
identity: &Identity,
) -> McpResult<Option<([u8; 32], usize)>> {
let Some(parent) = self.successors.get(&identity.slot) else {
return Ok(None);
};
let entry = self.entries.get(parent).ok_or_else(unavailable)?;
let transition = entry.transition.as_ref().ok_or_else(unavailable)?;
check_context(ctx)?;
if transition.superseded
|| transition.successor != Some(identity.slot)
|| identity.operation != Some(transition.operation)
|| entry.lifetime.is_cancel_requested()
|| identity.lifetime.is_cancel_requested()
|| Instant::now() >= entry.expires_at
|| entry.result.is_none()
{
return Err(unavailable());
}
let released = entry
.charge
.checked_sub(RETIRED_IDENTITY_BYTES)
.ok_or_else(unavailable)?;
Ok(Some((*parent, released)))
}
pub(super) fn retire_predecessor(&mut self, parent: [u8; 32], successor: [u8; 32]) {
let entry = self
.entries
.get_mut(&parent)
.expect("admitted predecessor remains under lock");
let transition = entry.transition.as_mut().expect("admitted successor mode");
transition.superseded = true;
transition.successor = None;
entry.result = None;
entry.charge = RETIRED_IDENTITY_BYTES;
self.successors.remove(&successor);
}
pub(super) fn admit_successor(&self, parent: [u8; 32], successor: [u8; 32]) -> McpResult<()> {
if parent == successor
|| self.entries.contains_key(&successor)
|| self.successors.contains_key(&successor)
{
return Err(unavailable());
}
Ok(())
}
pub(super) fn prune_finished(&mut self, now: Instant) -> usize {
let stale: Vec<_> = self
.entries
.iter()
.filter_map(|(slot, entry)| {
let finished = entry.result.is_some()
|| entry
.transition
.as_ref()
.is_some_and(|transition| transition.superseded);
(finished && (now >= entry.expires_at || entry.lifetime.is_cancel_requested()))
.then_some(*slot)
})
.collect();
for slot in &stale {
let entry = self
.entries
.remove(slot)
.expect("selected entry remains under lock");
if let Some(next) = entry.transition.and_then(|transition| transition.successor) {
self.successors.remove(&next);
}
self.retained_bytes -= entry.charge;
}
stale.len()
}
}
struct StableParams<'a>(&'a serde_json::Map<String, Value>);
impl Serialize for StableParams<'_> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let count = self
.0
.keys()
.filter(|key| !matches!(key.as_str(), "requestState" | "inputResponses"))
.count();
let mut map = serializer.serialize_map(Some(count))?;
for (key, value) in self.0 {
if !matches!(key.as_str(), "requestState" | "inputResponses") {
map.serialize_entry(key, value)?;
}
}
map.end()
}
}
pub(super) fn operation_fingerprint(
method: &str,
params: &Value,
maximum: usize,
) -> McpResult<[u8; 32]> {
let object = params.as_object().ok_or_else(unavailable)?;
let mut encoded = LimitedWriter::new(maximum);
serde_json::to_writer(&mut encoded, &(method, StableParams(object)))
.map_err(|_| unavailable())?;
Ok(sha256_bounded(&encoded.bytes, maximum)
.map_err(|_| unavailable())?
.into_bytes())
}
#[cfg(test)]
mod tests;