1use std::collections::{BTreeMap, HashMap, VecDeque};
20use std::convert::Infallible;
21use std::io;
22use std::net::SocketAddr;
23use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
24use std::sync::{Arc, Mutex};
25use std::time::Duration;
26
27use http_body_util::{BodyExt, Full, Limited};
28use hyper::body::{Bytes, Incoming};
29use hyper::header::{AUTHORIZATION, CONTENT_TYPE};
30use hyper::service::service_fn;
31use hyper::{Method, Request, Response, StatusCode};
32use hyper_util::rt::TokioIo;
33use rustev_contract::Document;
34use rustev_contract::ids::ContentDigest;
35use rustev_contract::remote::{CancelSupport, MAX_TEXT_BYTES, RemoteCode, cut_to};
36use rustev_contract::remote_context::{
37 self as rc, AcquireCode, AcquireResult, CancelAnswer, CancelItem, ContextCode, ContextField,
38 ContextTerms, EndpointClass, FetchResult, HostedDescriptor, HostedSource, Invalidation,
39 InvalidationKind, InvalidationScope, InvalidationSupport, PageBytes,
40};
41use rustev_contract::remote_sink::{self as rs, DeliveryResult, RecordSchema};
42use rustev_contract::run::{Charge, RunRecord};
43use rustev_contract::time::DurationMs;
44use rustev_core::seams::{
45 AcquireCall, AcquisitionFailure, AcquisitionSource, CallContext, CancelSignal, ContextSource,
46 EvidenceSink,
47};
48use tokio::net::TcpListener;
49use tokio::sync::watch;
50use tokio::task::JoinHandle;
51use tokio::time::Instant;
52
53use crate::http::Secret;
54use crate::taxonomy::code_of_detail;
55use crate::wire::digest_bytes;
56
57pub const CONTEXT_DESCRIBE_PATH: &str = "/rustev/remote-context/1/describe";
58pub const CONTEXT_FETCH_PATH: &str = "/rustev/remote-context/1/fetch";
59pub const CONTEXT_CANCEL_PATH: &str = "/rustev/remote-context/1/cancel";
60pub const CONTEXT_INVALIDATIONS_PATH: &str = "/rustev/remote-context/1/invalidations";
61pub const CONTEXT_ACQUIRE_PATH: &str = "/rustev/remote-context/1/acquire";
62pub const SINK_DESCRIBE_PATH: &str = "/rustev/remote-sink/1/describe";
63pub const SINK_DELIVER_PATH: &str = "/rustev/remote-sink/1/deliver";
64
65#[derive(Clone)]
67pub struct CallbackConfig {
68 pub bearer: Option<Secret>,
70 pub idempotency_window_ms: u64,
71 pub max_request_bytes: u64,
72 pub max_response_bytes: u64,
73 pub cancellation: CancelSupport,
74 pub invalidation: InvalidationSupport,
75 pub max_in_flight: usize,
77 pub max_remembered: usize,
80 pub max_invalidations: usize,
83}
84
85impl Default for CallbackConfig {
86 fn default() -> Self {
87 CallbackConfig {
88 bearer: None,
89 idempotency_window_ms: 10 * 60 * 1000,
90 max_request_bytes: 1024 * 1024,
91 max_response_bytes: 4 * 1024 * 1024,
92 cancellation: CancelSupport::Confirmed,
93 invalidation: InvalidationSupport::None,
94 max_in_flight: 64,
95 max_remembered: 10_000,
96 max_invalidations: 10_000,
97 }
98 }
99}
100
101#[derive(Clone)]
103pub struct HostedContext {
104 pub source: Arc<dyn ContextSource>,
105 pub fields: Vec<ContextField>,
106}
107
108#[derive(Clone)]
110pub struct HostedSink {
111 pub sink: Arc<dyn EvidenceSink>,
112 pub record_schemas: Vec<RecordSchema>,
113 pub durability: String,
116 pub deduplication_window_ms: u64,
117 pub max_record_bytes: u64,
118}
119
120#[derive(Clone)]
121struct HostedAcquisition {
122 source: Arc<dyn AcquisitionSource>,
123 descriptor: HostedDescriptor,
124}
125
126struct Fetch {
128 content: String,
129 cancel: watch::Sender<bool>,
130 done: watch::Receiver<Option<Vec<u8>>>,
131 finished_at: Option<Instant>,
132}
133
134struct Delivery {
135 digest: ContentDigest,
136 done: watch::Receiver<Option<DeliveryResult>>,
137 finished_at: Option<Instant>,
138}
139
140#[derive(Default)]
141struct InvalidationLog {
142 last: u64,
143 items: VecDeque<Invalidation>,
144}
145
146struct State {
147 config: CallbackConfig,
148 contexts: Mutex<BTreeMap<String, HostedContext>>,
149 acquisitions: Mutex<BTreeMap<String, HostedAcquisition>>,
150 sink: Mutex<Option<HostedSink>>,
151 fetches: Mutex<HashMap<String, Fetch>>,
152 deliveries: Mutex<HashMap<String, Delivery>>,
153 invalidations: Mutex<InvalidationLog>,
154 in_flight: AtomicUsize,
155 dispatched: AtomicU64,
156}
157
158#[derive(Clone)]
160pub struct CallbackServer {
161 state: Arc<State>,
162}
163
164pub struct CallbackHandle {
166 pub addr: SocketAddr,
167 task: JoinHandle<()>,
168}
169
170impl Drop for CallbackHandle {
171 fn drop(&mut self) {
172 self.task.abort();
173 }
174}
175
176impl CallbackHandle {
177 pub fn url(&self) -> String {
179 format!("http://{}", self.addr)
180 }
181}
182
183fn lock<T>(m: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
184 m.lock().unwrap_or_else(|e| e.into_inner())
185}
186
187type Refusal = (StatusCode, String);
188
189fn bad(why: impl Into<String>) -> Refusal {
190 (StatusCode::BAD_REQUEST, why.into())
191}
192
193fn internal(why: impl ToString) -> Refusal {
194 (StatusCode::INTERNAL_SERVER_ERROR, why.to_string())
195}
196
197fn overloaded() -> Refusal {
198 (StatusCode::SERVICE_UNAVAILABLE, "overloaded".into())
199}
200
201impl CallbackServer {
202 pub fn new(config: CallbackConfig) -> Self {
203 CallbackServer {
204 state: Arc::new(State {
205 config,
206 contexts: Mutex::new(BTreeMap::new()),
207 acquisitions: Mutex::new(BTreeMap::new()),
208 sink: Mutex::new(None),
209 fetches: Mutex::new(HashMap::new()),
210 deliveries: Mutex::new(HashMap::new()),
211 invalidations: Mutex::new(InvalidationLog::default()),
212 in_flight: AtomicUsize::new(0),
213 dispatched: AtomicU64::new(0),
214 }),
215 }
216 }
217
218 pub fn host_context(&self, source_id: &str, hosted: HostedContext) -> Result<(), String> {
222 if source_id.is_empty() || source_id.len() > MAX_TEXT_BYTES {
223 return Err("source id must be 1 to 256 bytes".into());
224 }
225 lock(&self.state.contexts).insert(source_id.into(), hosted);
226 Ok(())
227 }
228
229 pub fn host_acquisition(
235 &self,
236 source_id: &str,
237 source: Arc<dyn AcquisitionSource>,
238 ) -> Result<(), String> {
239 if !lock(&self.state.contexts).contains_key(source_id) {
240 return Err("no context source is hosted under that id".into());
241 }
242 let descriptor = HostedDescriptor::new(source.descriptor()).map_err(|e| e.to_string())?;
243 if descriptor
244 .document
245 .document()
246 .map_err(|e| e.to_string())?
247 .operations
248 .is_none()
249 {
250 return Err("the source descriptor declares no operation".into());
251 }
252 let mut acquisitions = lock(&self.state.acquisitions);
253 if acquisitions
254 .iter()
255 .any(|(id, a)| id != source_id && a.descriptor.id == descriptor.id)
256 {
257 return Err("another hosted source carries that SourceId".into());
258 }
259 acquisitions.insert(source_id.into(), HostedAcquisition { source, descriptor });
260 Ok(())
261 }
262
263 pub fn host_sink(&self, hosted: HostedSink) -> Result<(), String> {
265 if hosted.record_schemas.is_empty()
266 || hosted.durability.is_empty()
267 || hosted.durability.len() > 128
268 || !hosted
269 .durability
270 .bytes()
271 .all(|c| c.is_ascii_alphanumeric() || b"_-".contains(&c))
272 || hosted.max_record_bytes == 0
273 {
274 return Err("sink needs a record schema, a durability state and a byte bound".into());
275 }
276 *lock(&self.state.sink) = Some(hosted);
277 Ok(())
278 }
279
280 pub fn dispatched(&self) -> u64 {
282 self.state.dispatched.load(Ordering::SeqCst)
283 }
284
285 pub fn describe_digest(&self) -> Result<ContentDigest, String> {
287 describe_response(&self.state)
288 .record_digest()
289 .map_err(|e| e.to_string())
290 }
291
292 pub fn publish_invalidation(
295 &self,
296 source_id: &str,
297 kind: InvalidationKind,
298 fields: Vec<String>,
299 scope: InvalidationScope,
300 ) -> Result<u64, String> {
301 if !lock(&self.state.contexts).contains_key(source_id) {
302 return Err("unknown source".into());
303 }
304 let mut log = lock(&self.state.invalidations);
305 let item = Invalidation {
306 schema: rc::INVALIDATION.into(),
307 source_id: source_id.into(),
308 sequence: log.last + 1,
309 kind,
310 fields,
311 scope,
312 };
313 Invalidation::parse(&item.record_canonical().map_err(|e| e.to_string())?)
314 .map_err(|e| e.to_string())?;
315 log.last = item.sequence;
316 log.items.push_back(item);
317 while log.items.len() > self.state.config.max_invalidations {
318 log.items.pop_front();
319 }
320 Ok(log.last)
321 }
322
323 pub fn serve(
326 &self,
327 listener: TcpListener,
328 tls: Option<Arc<rustls::ServerConfig>>,
329 ) -> io::Result<CallbackHandle> {
330 let addr = listener.local_addr()?;
331 if tls.is_none() && !addr.ip().is_loopback() {
332 return Err(io::Error::new(
333 io::ErrorKind::InvalidInput,
334 "TLS is required except for loopback",
335 ));
336 }
337 let acceptor = tls.map(tokio_rustls::TlsAcceptor::from);
338 let state = self.state.clone();
339 let task = tokio::spawn(async move {
340 loop {
341 let Ok((stream, _)) = listener.accept().await else {
342 continue;
343 };
344 let _ = stream.set_nodelay(true);
345 let state = state.clone();
346 let acceptor = acceptor.clone();
347 tokio::spawn(async move {
348 match acceptor {
349 None => serve_conn(state, stream).await,
350 Some(a) => {
351 if let Ok(tls) = a.accept(stream).await {
352 serve_conn(state, tls).await
353 }
354 }
355 }
356 });
357 }
358 });
359 Ok(CallbackHandle { addr, task })
360 }
361
362 #[cfg(unix)]
364 pub fn serve_unix(&self, listener: tokio::net::UnixListener) -> CallbackHandle {
365 let state = self.state.clone();
366 let task = tokio::spawn(async move {
367 loop {
368 let Ok((stream, _)) = listener.accept().await else {
369 continue;
370 };
371 tokio::spawn(serve_conn(state.clone(), stream));
372 }
373 });
374 CallbackHandle {
375 addr: SocketAddr::from(([127, 0, 0, 1], 0)),
376 task,
377 }
378 }
379
380 pub async fn serve_loopback(&self) -> io::Result<CallbackHandle> {
382 let listener = TcpListener::bind(("127.0.0.1", 0)).await?;
383 self.serve(listener, None)
384 }
385}
386
387async fn serve_conn<S>(state: Arc<State>, stream: S)
388where
389 S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send + 'static,
390{
391 let svc = service_fn(move |req| {
392 let state = state.clone();
393 async move { Ok::<_, Infallible>(handle(state, req).await) }
394 });
395 let _ = hyper::server::conn::http1::Builder::new()
396 .serve_connection(TokioIo::new(stream), svc)
397 .await;
398}
399
400fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
402 a.len() == b.len() && a.iter().zip(b).fold(0u8, |acc, (x, y)| acc | (x ^ y)) == 0
403}
404
405fn reply(status: StatusCode, body: Vec<u8>) -> Response<Full<Bytes>> {
406 let mut r = Response::new(Full::new(Bytes::from(body)));
407 *r.status_mut() = status;
408 r.headers_mut()
409 .insert(CONTENT_TYPE, "application/json".parse().expect("static"));
410 r
411}
412
413fn refuse(status: StatusCode, why: &str) -> Response<Full<Bytes>> {
414 let body = serde_json::json!({ "error": cut_to(why, MAX_TEXT_BYTES) });
415 reply(status, serde_json::to_vec(&body).unwrap_or_default())
416}
417
418struct InFlight<'a>(&'a AtomicUsize);
419
420impl Drop for InFlight<'_> {
421 fn drop(&mut self) {
422 self.0.fetch_sub(1, Ordering::SeqCst);
423 }
424}
425
426async fn handle(state: Arc<State>, req: Request<Incoming>) -> Response<Full<Bytes>> {
427 if let Some(b) = &state.config.bearer {
428 let expected = format!("Bearer {}", b.expose());
429 let ok = req
430 .headers()
431 .get(AUTHORIZATION)
432 .is_some_and(|v| constant_time_eq(v.as_bytes(), expected.as_bytes()));
433 if !ok {
434 return refuse(StatusCode::UNAUTHORIZED, "credentials refused");
435 }
436 }
437 if req.method() != Method::POST {
438 return refuse(StatusCode::METHOD_NOT_ALLOWED, "POST only");
439 }
440 let n = state.in_flight.fetch_add(1, Ordering::SeqCst) + 1;
441 let _guard = InFlight(&state.in_flight);
442 if n > state.config.max_in_flight {
443 return refuse(StatusCode::SERVICE_UNAVAILABLE, "overloaded");
444 }
445 let path = req.uri().path().to_string();
446 let max = match path.as_str() {
447 SINK_DELIVER_PATH => rs::DELIVER_LIMITS.max_bytes,
449 _ => state.config.max_request_bytes as usize,
450 };
451 let body = match Limited::new(req.into_body(), max).collect().await {
452 Ok(b) => b.to_bytes(),
453 Err(_) => return refuse(StatusCode::PAYLOAD_TOO_LARGE, "request too large"),
454 };
455 let r = match path.as_str() {
456 CONTEXT_DESCRIBE_PATH => context_describe(&state, &body),
457 CONTEXT_FETCH_PATH => fetch(&state, &body).await,
458 CONTEXT_CANCEL_PATH => cancel(&state, &body),
459 CONTEXT_INVALIDATIONS_PATH => invalidations(&state, &body),
460 CONTEXT_ACQUIRE_PATH => acquire(&state, &body).await,
461 SINK_DESCRIBE_PATH => sink_describe(&state, &body),
462 SINK_DELIVER_PATH => deliver(&state, &body).await,
463 _ => return refuse(StatusCode::NOT_FOUND, "no such document"),
464 };
465 match r {
466 Ok(bytes) => reply(StatusCode::OK, bytes),
467 Err((status, why)) => refuse(status, &why),
468 }
469}
470
471fn describe_response(state: &State) -> rc::DescribeResponse {
472 let c = &state.config;
473 let acquisitions = lock(&state.acquisitions);
474 rc::DescribeResponse {
475 schema: rc::DESCRIBE.into(),
476 version: 1,
477 sources: lock(&state.contexts)
478 .iter()
479 .map(|(id, h)| HostedSource {
480 source_id: id.clone(),
481 fields: h.fields.clone(),
482 terms: ContextTerms {
483 cancellation: c.cancellation,
484 idempotency_window_ms: c.idempotency_window_ms,
485 max_request_bytes: c.max_request_bytes,
486 max_response_bytes: c.max_response_bytes,
487 invalidation: c.invalidation.clone(),
488 endpoint_class: EndpointClass::HostOperated,
489 },
490 descriptor: acquisitions.get(id).map(|a| a.descriptor.clone()),
491 })
492 .collect(),
493 }
494}
495
496fn context_describe(state: &State, body: &[u8]) -> Result<Vec<u8>, Refusal> {
497 let req = rc::DescribeRequest::parse(body).map_err(|e| bad(e.to_string()))?;
498 if !req.versions.contains(&1) {
499 return Err(bad("no common protocol version"));
500 }
501 describe_response(state)
502 .record_canonical()
503 .map_err(internal)
504}
505
506fn fetch_failure(code: RemoteCode, detail: &str) -> Result<Vec<u8>, Refusal> {
507 rc::FetchResponse {
508 schema: rc::FETCH.into(),
509 result: FetchResult::Failure {
510 code: ContextCode(code),
511 detail: cut_to(detail, MAX_TEXT_BYTES),
512 },
513 }
514 .record_canonical()
515 .map_err(internal)
516}
517
518async fn fetch(state: &Arc<State>, body: &[u8]) -> Result<Vec<u8>, Refusal> {
519 let req = rc::FetchRequest::parse(body).map_err(|e| bad(e.to_string()))?;
520 if req.version != 1 {
521 return fetch_failure(RemoteCode::Capability, "unsupported version");
522 }
523 let Some(hosted) = lock(&state.contexts).get(&req.source_id).cloned() else {
524 return fetch_failure(RemoteCode::Capability, "unknown source");
525 };
526 let digest = describe_response(state).record_digest().map_err(internal)?;
527 if req.describe_digest != digest {
528 return fetch_failure(RemoteCode::IdentityMismatch, "describe digest differs");
529 }
530 let content = {
533 let mut keyed = req.clone();
534 keyed.budget_ms = 0;
535 digest_bytes(&keyed.record_canonical().map_err(internal)?)
536 };
537 let Some(principal) = req.principal.reference_bytes() else {
538 return Err(bad("invalid principal reference"));
539 };
540 let (attempt_id, budget_ms) = (req.attempt_id.clone(), req.budget_ms);
541 once(
542 state,
543 &attempt_id,
544 content,
545 budget_ms,
546 fetch_failure,
547 |cancel, tx| {
548 tokio::spawn(run_fetch(state.clone(), hosted, req, principal, cancel, tx));
549 },
550 )
551 .await
552}
553
554async fn once(
558 state: &Arc<State>,
559 attempt_id: &str,
560 content: String,
561 budget_ms: u64,
562 failure: fn(RemoteCode, &str) -> Result<Vec<u8>, Refusal>,
563 start: impl FnOnce(watch::Receiver<bool>, watch::Sender<Option<Vec<u8>>>),
564) -> Result<Vec<u8>, Refusal> {
565 let window = Duration::from_millis(state.config.idempotency_window_ms);
566 let mut done = {
567 let mut fetches = lock(&state.fetches);
568 let now = Instant::now();
569 fetches.retain(|_, e| e.finished_at.is_none_or(|t| now.duration_since(t) < window));
570 if let Some(e) = fetches.get(attempt_id) {
571 if e.content != content {
572 return failure(RemoteCode::MalformedRequest, "attempt_id_conflict");
573 }
574 e.done.clone()
575 } else {
576 if budget_ms == 0 {
577 return failure(RemoteCode::RemoteDeadline, "budget expired");
578 }
579 if fetches.len() >= state.config.max_remembered {
580 return Err(overloaded());
581 }
582 let (tx, rx) = watch::channel(None);
583 let (cancel_tx, cancel_rx) = watch::channel(false);
584 fetches.insert(
585 attempt_id.into(),
586 Fetch {
587 content,
588 cancel: cancel_tx,
589 done: rx.clone(),
590 finished_at: None,
591 },
592 );
593 state.dispatched.fetch_add(1, Ordering::SeqCst);
594 start(cancel_rx, tx);
595 rx
596 }
597 };
598 let answer = done
599 .wait_for(Option::is_some)
600 .await
601 .map_err(|_| internal("the attempt vanished"))?
602 .clone()
603 .expect("waited for Some");
604 Ok(answer)
605}
606
607fn finish(state: &State, attempt_id: &str, done: watch::Sender<Option<Vec<u8>>>, bytes: Vec<u8>) {
608 if let Some(e) = lock(&state.fetches).get_mut(attempt_id) {
609 e.finished_at = Some(Instant::now());
610 }
611 let _ = done.send(Some(bytes));
612}
613
614fn acquire_failure(code: AcquireCode, detail: &str, charge: Charge) -> Result<Vec<u8>, Refusal> {
615 rc::AcquireResponse {
616 schema: rc::ACQUIRE.into(),
617 result: AcquireResult::Failure {
618 code,
619 detail: cut_to(detail, MAX_TEXT_BYTES),
620 },
621 charge,
622 }
623 .record_canonical()
624 .map_err(internal)
625}
626
627fn acquire_refusal(code: RemoteCode, detail: &str) -> Result<Vec<u8>, Refusal> {
628 acquire_failure(code.into(), detail, Charge::Observed { units: 0 })
629}
630
631async fn acquire(state: &Arc<State>, body: &[u8]) -> Result<Vec<u8>, Refusal> {
632 let req = rc::AcquireRequest::parse(body).map_err(|e| bad(e.to_string()))?;
633 if req.version != 1 {
634 return acquire_refusal(RemoteCode::Capability, "unsupported version");
635 }
636 let hosted = lock(&state.acquisitions)
637 .values()
638 .find(|a| a.descriptor.id == req.source)
639 .cloned();
640 let Some(hosted) = hosted else {
641 return acquire_refusal(RemoteCode::Capability, "unknown source");
642 };
643 let declared = hosted
644 .source
645 .descriptor()
646 .operations
647 .iter()
648 .flatten()
649 .any(|o| o.name == req.operation);
650 if !declared {
651 return acquire_refusal(RemoteCode::Capability, "unknown operation");
652 }
653 let content = {
654 let mut keyed = req.clone();
655 keyed.budget_ms = 0;
656 digest_bytes(&keyed.record_canonical().map_err(internal)?)
657 };
658 let Some(principal) = req.principal.reference_bytes() else {
659 return Err(bad("invalid principal reference"));
660 };
661 let (attempt_id, budget_ms) = (req.attempt_id.clone(), req.budget_ms);
662 once(
663 state,
664 &attempt_id,
665 content,
666 budget_ms,
667 acquire_refusal,
668 |cancel, tx| {
669 tokio::spawn(run_acquire(
670 state.clone(),
671 hosted,
672 req,
673 principal,
674 cancel,
675 tx,
676 ));
677 },
678 )
679 .await
680}
681
682fn acquire_code(class: AcquisitionFailure, detail: &str) -> AcquireCode {
686 let default = match class {
687 AcquisitionFailure::Denied => return AcquireCode::Denied,
688 AcquisitionFailure::Transient => RemoteCode::RemoteError,
689 AcquisitionFailure::Overloaded => RemoteCode::Overloaded,
690 AcquisitionFailure::Permanent => RemoteCode::Capability,
691 };
692 let class_of = |code| crate::taxonomy::failure_class(code);
693 code_of_detail(detail)
694 .filter(|c| class_of(*c) == class_of(default) && !crate::taxonomy::is_local_only(*c))
695 .unwrap_or(default)
696 .into()
697}
698
699async fn run_acquire(
700 state: Arc<State>,
701 hosted: HostedAcquisition,
702 req: rc::AcquireRequest,
703 principal: Vec<u8>,
704 mut cancel: watch::Receiver<bool>,
705 done: watch::Sender<Option<Vec<u8>>>,
706) {
707 let cx = CallContext {
708 remaining_ms: DurationMs(req.budget_ms),
709 trace_id: req.trace_id.clone(),
710 principal_handle: principal,
711 };
712 let bytes = match req.request.document() {
713 Err(e) => acquire_failure(
714 RemoteCode::RemoteError.into(),
715 &e.to_string(),
716 Charge::Unknown,
717 ),
718 Ok(request) => {
719 let signal = CancelSignal::new();
720 let call = AcquireCall {
721 operation: &req.operation,
722 request: &request,
723 request_id: &req.request_id,
724 attempt_id: &req.attempt_id,
725 cancel: signal.child(),
726 cx: &cx,
727 };
728 let budget = Duration::from_millis(req.budget_ms);
729 let report = tokio::select! {
730 r = tokio::time::timeout(budget, hosted.source.acquire(call)) => r.ok(),
731 _ = cancel.wait_for(|c| *c) => {
732 signal.raise();
733 None
734 }
735 };
736 match report {
737 Some(report) => match report.result {
738 Ok(page) => match PageBytes::new(&page) {
739 Ok(page) => rc::AcquireResponse {
740 schema: rc::ACQUIRE.into(),
741 result: AcquireResult::Page(page),
742 charge: report.charge,
743 }
744 .record_canonical()
745 .map_err(internal)
746 .and_then(|b| {
747 if b.len() as u64 <= state.config.max_response_bytes {
748 Ok(b)
749 } else {
750 acquire_failure(
751 RemoteCode::RemoteError.into(),
752 "response exceeds its bound",
753 report.charge,
754 )
755 }
756 }),
757 Err(e) => acquire_failure(
758 RemoteCode::RemoteError.into(),
759 &e.to_string(),
760 report.charge,
761 ),
762 },
763 Err((class, detail)) => {
764 acquire_failure(acquire_code(class, &detail), &detail, report.charge)
765 }
766 },
767 None if *cancel.borrow() => {
768 acquire_failure(RemoteCode::Cancelled.into(), "cancelled", Charge::Unknown)
769 }
770 None => acquire_failure(
771 RemoteCode::RemoteDeadline.into(),
772 "budget expired",
773 Charge::Unknown,
774 ),
775 }
776 }
777 };
778 finish(&state, &req.attempt_id, done, bytes.unwrap_or_default());
779}
780
781async fn run_fetch(
782 state: Arc<State>,
783 hosted: HostedContext,
784 req: rc::FetchRequest,
785 principal: Vec<u8>,
786 mut cancel: watch::Receiver<bool>,
787 done: watch::Sender<Option<Vec<u8>>>,
788) {
789 let cx = CallContext {
790 remaining_ms: DurationMs(req.budget_ms),
791 trace_id: req.trace_id.clone(),
792 principal_handle: principal,
793 };
794 let budget = Duration::from_millis(req.budget_ms);
795 let result = tokio::select! {
796 r = tokio::time::timeout(budget, hosted.source.fetch(&req.fields, &cx)) => r.ok(),
797 _ = cancel.wait_for(|c| *c) => None,
798 };
799 let cancelled = *cancel.borrow();
800 let bytes = match result {
801 Some(Ok(snapshot)) => {
802 let bytes = rc::FetchResponse {
803 schema: rc::FETCH.into(),
804 result: FetchResult::Snapshot(snapshot),
805 }
806 .record_canonical();
807 match bytes {
808 Ok(b) if b.len() as u64 <= state.config.max_response_bytes => Ok(b),
809 Ok(_) => fetch_failure(RemoteCode::RemoteError, "response exceeds its bound"),
810 Err(e) => fetch_failure(RemoteCode::RemoteError, &e.to_string()),
811 }
812 }
813 Some(Err(detail)) => {
814 let code = code_of_detail(&detail).unwrap_or(RemoteCode::RemoteError);
815 fetch_failure(code, &detail)
816 }
817 None if cancelled => fetch_failure(RemoteCode::Cancelled, "cancelled"),
818 None => fetch_failure(RemoteCode::RemoteDeadline, "budget expired"),
819 };
820 finish(&state, &req.attempt_id, done, bytes.unwrap_or_default());
821}
822
823fn cancel(state: &State, body: &[u8]) -> Result<Vec<u8>, Refusal> {
824 let req = rc::CancelRequest::parse(body).map_err(|e| bad(e.to_string()))?;
825 if req.attempt_ids.len() > 1024 {
826 return Err(bad("too many attempt ids"));
827 }
828 let fetches = lock(&state.fetches);
829 let answers = req
830 .attempt_ids
831 .iter()
832 .map(|id| {
833 let answer = match fetches.get(id) {
834 None => CancelAnswer::UnknownAttempt,
835 Some(e) if e.finished_at.is_some() => CancelAnswer::Unconfirmed,
836 Some(e) => match state.config.cancellation {
837 CancelSupport::None => CancelAnswer::Unconfirmed,
838 CancelSupport::BestEffort => {
839 let _ = e.cancel.send(true);
840 CancelAnswer::Unconfirmed
841 }
842 CancelSupport::Confirmed => {
844 let _ = e.cancel.send(true);
845 CancelAnswer::Stopped
846 }
847 },
848 };
849 CancelItem {
850 attempt_id: id.clone(),
851 answer,
852 }
853 })
854 .collect();
855 rc::CancelResponse {
856 schema: rc::CANCEL.into(),
857 answers,
858 }
859 .record_canonical()
860 .map_err(internal)
861}
862
863fn invalidations(state: &State, body: &[u8]) -> Result<Vec<u8>, Refusal> {
864 let req = rc::InvalidationsRequest::parse(body).map_err(|e| bad(e.to_string()))?;
865 if !matches!(state.config.invalidation, InvalidationSupport::Poll { .. }) {
866 return Err(bad("invalidation polling is not offered"));
867 }
868 let log = lock(&state.invalidations);
869 let (sequence, items) = match req.sequence {
870 None => (log.last, vec![]),
872 Some(s) if s > log.last => return Err(bad("sequence beyond the last published")),
873 Some(s) => {
874 let items: Vec<_> = log
877 .items
878 .iter()
879 .filter(|i| i.sequence > s)
880 .take(256)
881 .cloned()
882 .collect();
883 (items.last().map_or(s, |i| i.sequence), items)
884 }
885 };
886 rc::InvalidationsResponse {
887 schema: rc::INVALIDATIONS.into(),
888 sequence,
889 items,
890 }
891 .record_canonical()
892 .map_err(internal)
893}
894
895fn sink_describe(state: &State, body: &[u8]) -> Result<Vec<u8>, Refusal> {
896 let req = rs::DescribeRequest::parse(body).map_err(|e| bad(e.to_string()))?;
897 if !req.versions.contains(&1) {
898 return Err(bad("no common protocol version"));
899 }
900 let Some(h) = lock(&state.sink).clone() else {
901 return Err((StatusCode::NOT_FOUND, "no sink is hosted".into()));
902 };
903 rs::DescribeResponse {
904 schema: rs::DESCRIBE.into(),
905 version: 1,
906 record_schemas: h.record_schemas,
907 durability_states: vec![h.durability],
908 deduplication_window_ms: h.deduplication_window_ms,
909 max_record_bytes: h.max_record_bytes,
910 }
911 .record_canonical()
912 .map_err(internal)
913}
914
915fn deliver_answer(result: DeliveryResult) -> Result<Vec<u8>, Refusal> {
916 rs::DeliverResponse {
917 schema: rs::DELIVER.into(),
918 result,
919 }
920 .record_canonical()
921 .map_err(internal)
922}
923
924async fn deliver(state: &Arc<State>, body: &[u8]) -> Result<Vec<u8>, Refusal> {
925 let Some(h) = lock(&state.sink).clone() else {
926 return Err((StatusCode::NOT_FOUND, "no sink is hosted".into()));
927 };
928 let req = rs::DeliverRequest::parse(body).map_err(|e| bad(e.to_string()))?;
929 if req.version != 1 || !h.record_schemas.contains(&req.record_schema) {
930 return deliver_answer(DeliveryResult::Rejected {
931 code: RemoteCode::Capability,
932 });
933 }
934 if req.record.len() as u64 > h.max_record_bytes {
935 return deliver_answer(DeliveryResult::Rejected {
936 code: RemoteCode::Capability,
937 });
938 }
939 let window = Duration::from_millis(h.deduplication_window_ms);
940 let key = req.decision_id.as_str().to_string();
941 enum Next {
942 Wait(watch::Receiver<Option<DeliveryResult>>),
943 Run(Box<RunRecord>, watch::Sender<Option<DeliveryResult>>),
944 }
945 let next = {
946 let mut deliveries = lock(&state.deliveries);
947 let now = Instant::now();
948 deliveries.retain(|_, e| e.finished_at.is_none_or(|t| now.duration_since(t) < window));
949 match deliveries.get(&key) {
950 Some(e) if e.digest != req.record_digest => {
951 return deliver_answer(DeliveryResult::Conflict);
952 }
953 Some(e) => Next::Wait(e.done.clone()),
954 None => {
955 if deliveries.len() >= state.config.max_remembered {
956 return Err(overloaded());
957 }
958 let record =
959 RunRecord::parse(req.record.as_bytes()).map_err(|e| bad(e.to_string()))?;
960 let (tx, rx) = watch::channel(None);
961 deliveries.insert(
962 key.clone(),
963 Delivery {
964 digest: req.record_digest.clone(),
965 done: rx,
966 finished_at: None,
967 },
968 );
969 Next::Run(Box::new(record), tx)
970 }
971 }
972 };
973 let mut done = match next {
974 Next::Wait(rx) => rx,
975 Next::Run(record, tx) => {
976 state.dispatched.fetch_add(1, Ordering::SeqCst);
977 let result = match h.sink.deliver(&record).await {
978 Ok(receipt) => DeliveryResult::Accepted {
979 durability: h.durability.clone(),
980 external_id: cut_to(&receipt, MAX_TEXT_BYTES),
981 accepted_digest: req.record_digest.clone(),
982 },
983 Err(detail) => DeliveryResult::Rejected {
984 code: code_of_detail(&detail).unwrap_or(RemoteCode::RemoteError),
985 },
986 };
987 {
988 let mut deliveries = lock(&state.deliveries);
989 if matches!(result, DeliveryResult::Rejected { .. }) {
990 deliveries.remove(&key);
992 } else if let Some(e) = deliveries.get_mut(&key) {
993 e.finished_at = Some(Instant::now());
994 }
995 }
996 let _ = tx.send(Some(result.clone()));
997 return deliver_answer(result);
998 }
999 };
1000 let first = done
1001 .wait_for(Option::is_some)
1002 .await
1003 .map_err(|_| internal("the delivery vanished"))?
1004 .clone()
1005 .expect("waited for Some");
1006 deliver_answer(match first {
1007 DeliveryResult::Accepted {
1008 accepted_digest, ..
1009 }
1010 | DeliveryResult::Duplicate { accepted_digest } => {
1011 DeliveryResult::Duplicate { accepted_digest }
1012 }
1013 other => other,
1014 })
1015}