1use std::collections::{BTreeMap, BTreeSet};
26use std::sync::{Arc, Mutex, MutexGuard};
27
28use futures::channel::mpsc::UnboundedSender;
29use serde::{Deserialize, Serialize};
30use vertep_core::datamodel::{Event, EventType};
31
32use crate::address::Address;
33use crate::json::JsonValue;
34use crate::mailbox::{Envelope, Mailbox, MailboxError};
35use crate::node::NodeHandle;
36use crate::transport::{Resolver, Transport, TransportError};
37
38#[cfg(feature = "websocket")]
39pub mod server;
40
41pub const WEBSOCKET_PROCESSOR: &str = "https://w3id.org/vertep/vt#WebSocketEventProcessor";
44
45pub const REFUSED_REASON: &str = "transport-refused";
47
48#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
50pub struct Message {
51 pub id: String,
53 pub target: String,
55 #[serde(default, skip_serializing_if = "Option::is_none")]
57 pub origin: Option<String>,
58 pub event: String,
60 #[serde(default, skip_serializing_if = "Option::is_none")]
62 pub data: Option<JsonValue>,
63 #[serde(default, skip_serializing_if = "Option::is_none")]
65 pub sendid: Option<String>,
66 #[serde(default, skip_serializing_if = "Option::is_none")]
68 pub correlation: Option<String>,
69}
70
71impl Message {
72 pub fn from_envelope(env: &Envelope) -> Self {
74 Message {
75 id: env.message_id.clone(),
76 target: env.to.clone(),
77 origin: env.event.origin.clone(),
78 event: env.event.name.clone(),
79 data: Some(JsonValue(env.event.data.clone())),
80 sendid: env.event.sendid.clone(),
81 correlation: env.correlation.clone(),
82 }
83 }
84
85 pub fn into_envelope(self) -> Envelope {
88 let mut event = Event::named(self.event, EventType::External);
89 event.origin = self.origin;
90 event.origintype = Some(WEBSOCKET_PROCESSOR.to_owned());
91 event.sendid = self.sendid;
92 if let Some(d) = self.data {
93 event.data = d.0;
94 }
95 Envelope {
96 message_id: self.id,
97 to: self.target,
98 event,
99 correlation: self.correlation,
100 timer: None,
101 }
102 }
103}
104
105#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
107#[serde(tag = "type", rename_all = "lowercase")]
108pub enum Frame {
109 Hello {
111 node: String,
113 #[serde(default, skip_serializing_if = "Option::is_none")]
115 credential: Option<String>,
116 #[serde(default)]
118 hosts: Vec<String>,
119 },
120 Welcome {
122 node: String,
124 #[serde(default)]
126 hosts: Vec<String>,
127 },
128 Refused {
130 reason: String,
132 },
133 Message(Message),
135 Ack {
137 id: String,
139 },
140}
141
142impl Frame {
143 pub fn to_text(&self) -> String {
145 serde_json::to_string(self).unwrap_or_default()
146 }
147
148 pub fn parse(text: &str) -> Result<Frame, String> {
154 serde_json::from_str(text).map_err(|e| format!("not a Vertep WebSocket frame: {e}"))
155 }
156}
157
158#[derive(Clone, Debug, PartialEq, Eq)]
160pub enum Offer {
161 Delivered,
163 Pending,
165 Refused(String),
167}
168
169#[derive(Clone, Debug, PartialEq)]
171pub enum Inbound {
172 Deliver(Envelope),
174 Acked {
176 id: String,
178 watched: bool,
180 },
181 Handshake(Frame),
183 Refused {
185 reason: String,
187 failed: Vec<String>,
189 },
190}
191
192#[derive(Debug)]
193struct Pending {
194 env: Envelope,
195 sent_on: Option<u64>,
196 order: u64,
197}
198
199#[derive(Default)]
200struct State {
201 connection: Option<(u64, UnboundedSender<String>)>,
202 pending: BTreeMap<String, Pending>,
203 acked: BTreeSet<String>,
204 watched: BTreeSet<String>,
205 settled: Vec<(String, Result<(), String>)>,
206 refused: Option<String>,
207 next_order: u64,
208 routes: BTreeSet<String>,
209}
210
211#[derive(Default)]
217pub struct Link {
218 state: Mutex<State>,
219}
220
221impl std::fmt::Debug for Link {
222 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
223 let s = self.lock();
224 f.debug_struct("Link")
225 .field("connected", &s.connection.as_ref().map(|c| c.0))
226 .field("pending", &s.pending.len())
227 .field("refused", &s.refused)
228 .finish()
229 }
230}
231
232impl Link {
233 pub fn new() -> Self {
235 Link::default()
236 }
237
238 fn lock(&self) -> MutexGuard<'_, State> {
239 self.state
240 .lock()
241 .unwrap_or_else(std::sync::PoisonError::into_inner)
242 }
243
244 fn send_pending(s: &mut State, id: &str) {
245 let Some((generation, tx)) = s.connection.clone() else {
246 return;
247 };
248 if let Some(p) = s.pending.get_mut(id)
249 && p.sent_on != Some(generation)
250 {
251 p.sent_on = Some(generation);
252 let _ = tx.unbounded_send(Frame::Message(Message::from_envelope(&p.env)).to_text());
253 }
254 }
255
256 pub fn offer(&self, env: Envelope) -> Offer {
260 let mut s = self.lock();
261 if s.acked.remove(&env.message_id) {
262 return Offer::Delivered;
263 }
264 if let Some(r) = &s.refused {
265 return Offer::Refused(r.clone());
266 }
267 let id = env.message_id.clone();
268 if !s.pending.contains_key(&id) {
269 let order = s.next_order;
270 s.next_order += 1;
271 s.pending.insert(
272 id.clone(),
273 Pending {
274 env,
275 sent_on: None,
276 order,
277 },
278 );
279 }
280 Link::send_pending(&mut s, &id);
281 Offer::Pending
282 }
283
284 pub fn watch(&self, id: &str) {
287 let mut s = self.lock();
288 if s.acked.remove(id) {
289 s.settled.push((id.to_owned(), Ok(())));
290 } else {
291 s.watched.insert(id.to_owned());
292 }
293 }
294
295 pub fn take_settled(&self) -> Vec<(String, Result<(), String>)> {
297 std::mem::take(&mut self.lock().settled)
298 }
299
300 pub fn is_pending(&self, id: &str) -> bool {
302 self.lock().pending.contains_key(id)
303 }
304
305 pub fn pending_len(&self) -> usize {
307 self.lock().pending.len()
308 }
309
310 pub fn connected(&self, generation: u64, tx: UnboundedSender<String>) {
314 let mut s = self.lock();
315 s.connection = Some((generation, tx));
316 s.refused = None;
317 let mut ids: Vec<(u64, String)> = s
318 .pending
319 .iter()
320 .map(|(k, p)| (p.order, k.clone()))
321 .collect();
322 ids.sort();
323 for (_, id) in ids {
324 Link::send_pending(&mut s, &id);
325 }
326 }
327
328 pub fn disconnected(&self, generation: u64) {
330 let mut s = self.lock();
331 if s.connection.as_ref().is_some_and(|c| c.0 == generation) {
332 s.connection = None;
333 }
334 }
335
336 pub fn is_connected(&self) -> bool {
338 self.lock().connection.is_some()
339 }
340
341 pub fn ack(&self, id: &str) {
343 let s = self.lock();
344 if let Some((_, tx)) = &s.connection {
345 let _ = tx.unbounded_send(Frame::Ack { id: id.to_owned() }.to_text());
346 }
347 }
348
349 pub fn send_frame(&self, f: &Frame) -> bool {
351 let s = self.lock();
352 match &s.connection {
353 Some((_, tx)) => tx.unbounded_send(f.to_text()).is_ok(),
354 None => false,
355 }
356 }
357
358 pub fn refused(&self, reason: &str) -> Vec<String> {
362 let mut s = self.lock();
363 s.refused = Some(reason.to_owned());
364 let ids: Vec<String> = s.pending.keys().cloned().collect();
365 s.pending.clear();
366 let mut failed = Vec::new();
367 for id in ids {
368 if s.watched.remove(&id) {
369 s.settled.push((id.clone(), Err(reason.to_owned())));
370 failed.push(id);
371 }
372 }
373 failed
374 }
375
376 pub fn refusal(&self) -> Option<String> {
378 self.lock().refused.clone()
379 }
380
381 pub fn learn_route(&self, iri: &str) {
384 self.lock().routes.insert(iri.to_owned());
385 }
386
387 pub fn routes_to(&self, iri: &str) -> bool {
389 self.lock().routes.contains(iri)
390 }
391
392 pub fn receive(&self, text: &str) -> Result<Inbound, String> {
398 Ok(match Frame::parse(text)? {
399 Frame::Message(m) => {
400 if let Some(o) = &m.origin {
401 self.learn_route(o);
402 }
403 Inbound::Deliver(m.into_envelope())
404 }
405 Frame::Ack { id } => {
406 let mut s = self.lock();
407 let known = s.pending.remove(&id).is_some();
408 let watched = s.watched.remove(&id);
409 if watched {
410 s.settled.push((id.clone(), Ok(())));
411 } else if known {
412 s.acked.insert(id.clone());
413 }
414 Inbound::Acked { id, watched }
415 }
416 Frame::Refused { reason } => {
417 let failed = self.refused(&reason);
418 Inbound::Refused { reason, failed }
419 }
420 f @ (Frame::Hello { .. } | Frame::Welcome { .. }) => Inbound::Handshake(f),
421 })
422 }
423}
424
425pub struct LinkMailbox {
431 link: Arc<Link>,
432 handle: Option<NodeHandle>,
433}
434
435impl std::fmt::Debug for LinkMailbox {
436 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
437 f.debug_struct("LinkMailbox")
438 .field("link", &self.link)
439 .finish_non_exhaustive()
440 }
441}
442
443impl LinkMailbox {
444 pub fn new(link: Arc<Link>, handle: Option<NodeHandle>) -> Self {
446 LinkMailbox { link, handle }
447 }
448}
449
450impl Mailbox for LinkMailbox {
451 fn enqueue(&self, env: Envelope) -> Result<(), MailboxError> {
452 let (to, copy) = (env.to.clone(), env.clone());
453 match self.link.offer(env) {
454 Offer::Delivered => Ok(()),
455 Offer::Pending => Err(MailboxError(
456 "awaiting the WebSocket peer's acknowledgement".into(),
457 )),
458 Offer::Refused(_) => {
459 if let (Some(h), Ok(a)) = (&self.handle, Address::parse(&to)) {
460 h.record_dead_letter(&a, ©, REFUSED_REASON);
461 }
462 Ok(())
463 }
464 }
465 }
466
467 fn receive(&self, _deferred: &[String]) -> Option<Envelope> {
468 None
469 }
470
471 fn ack(&self, _message_id: &str) -> Result<(), MailboxError> {
472 Ok(())
473 }
474
475 fn release(&self, _message_id: &str) {}
476
477 fn release_all(&self) {}
478
479 fn pending(&self) -> Vec<Envelope> {
480 Vec::new()
481 }
482
483 fn pending_deliverable(&self, _deferred: &[String]) -> usize {
484 0
485 }
486}
487
488pub struct ClientResolver {
492 link: Arc<Link>,
493 handle: Option<NodeHandle>,
494 targets: Vec<String>,
495}
496
497impl std::fmt::Debug for ClientResolver {
498 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
499 f.debug_struct("ClientResolver")
500 .field("targets", &self.targets)
501 .finish_non_exhaustive()
502 }
503}
504
505impl ClientResolver {
506 pub fn new(link: Arc<Link>, handle: Option<NodeHandle>, targets: Vec<String>) -> Self {
508 ClientResolver {
509 link,
510 handle,
511 targets,
512 }
513 }
514
515 pub fn claims(&self, address: &str) -> bool {
517 self.targets.is_empty() || self.targets.iter().any(|t| address.starts_with(t.as_str()))
518 }
519}
520
521impl Resolver for ClientResolver {
522 fn resolve(&self, address: &Address) -> Option<Arc<dyn Mailbox>> {
523 self.claims(address.as_str()).then(|| {
524 Arc::new(LinkMailbox::new(self.link.clone(), self.handle.clone())) as Arc<dyn Mailbox>
525 })
526 }
527}
528
529#[derive(Clone, Copy, Debug, Default)]
533pub struct WebSocketTransport;
534
535impl Transport for WebSocketTransport {
536 fn type_iri(&self) -> &str {
537 WEBSOCKET_PROCESSOR
538 }
539
540 fn aliases(&self) -> Vec<String> {
541 vec!["websocket".to_owned()]
542 }
543
544 fn deliver(&self, mailbox: &dyn Mailbox, env: Envelope) -> Result<(), TransportError> {
545 mailbox
546 .enqueue(env)
547 .map_err(|e| TransportError(e.to_string()))
548 }
549}
550
551#[cfg(test)]
552mod tests {
553 use super::*;
554 use futures::channel::mpsc::unbounded;
555 use vertep_core::datamodel::Value;
556
557 fn env(id: &str) -> Envelope {
558 let mut e = Event::named("ping", EventType::External);
559 e.origin = Some("https://client.example/a/1".into());
560 e.data = Value::Object(vec![("n".into(), Value::Number(1.0))]);
561 Envelope::new(
562 id,
563 &Address::parse("https://server.example/s/1").unwrap(),
564 e,
565 )
566 }
567
568 fn drain(rx: &mut futures::channel::mpsc::UnboundedReceiver<String>) -> Vec<Frame> {
569 let mut out = Vec::new();
570 while let Ok(t) = rx.try_recv() {
571 out.push(Frame::parse(&t).unwrap());
572 }
573 out
574 }
575
576 #[test]
577 fn frames_round_trip() {
578 let m = Message::from_envelope(&env("m1"));
579 let f = Frame::Message(m.clone());
580 assert_eq!(Frame::parse(&f.to_text()).unwrap(), f);
581 let delivered = m.into_envelope();
582 assert_eq!(
583 delivered.event.origintype.as_deref(),
584 Some(WEBSOCKET_PROCESSOR)
585 );
586 assert_eq!(
587 delivered.event.origin.as_deref(),
588 Some("https://client.example/a/1")
589 );
590 assert_eq!(delivered.event.data, env("m1").event.data);
591 assert!(Frame::parse(r#"{"type":"ack","id":"x"}"#).is_ok());
592 assert!(Frame::parse("nope").is_err());
593 }
594
595 #[test]
596 fn pending_until_acked_and_resent_after_reconnect() {
597 let link = Link::new();
598 assert_eq!(link.offer(env("m1")), Offer::Pending, "no connection yet");
599 let (tx, mut rx) = unbounded();
600 link.connected(1, tx);
601 assert_eq!(drain(&mut rx).len(), 1, "sent on connect");
602 assert_eq!(link.offer(env("m1")), Offer::Pending);
603 assert!(
604 drain(&mut rx).is_empty(),
605 "a retry does not resend on the same connection"
606 );
607 link.disconnected(1);
608 let (tx, mut rx) = unbounded();
609 link.connected(2, tx);
610 assert_eq!(drain(&mut rx).len(), 1, "resent after reconnect");
611 link.receive(r#"{"type":"ack","id":"m1"}"#).unwrap();
612 assert_eq!(link.offer(env("m1")), Offer::Delivered);
613 assert_eq!(link.pending_len(), 0);
614 }
615
616 #[test]
617 fn watched_messages_settle_and_fail_on_refusal() {
618 let link = Link::new();
619 let (tx, _rx) = unbounded();
620 link.connected(1, tx);
621 link.offer(env("a"));
622 link.watch("a");
623 link.offer(env("b"));
624 link.watch("b");
625 link.receive(r#"{"type":"ack","id":"a"}"#).unwrap();
626 assert_eq!(link.take_settled(), vec![("a".into(), Ok(()))]);
627 let r = link
628 .receive(r#"{"type":"refused","reason":"bad token"}"#)
629 .unwrap();
630 assert_eq!(
631 r,
632 Inbound::Refused {
633 reason: "bad token".into(),
634 failed: vec!["b".into()]
635 }
636 );
637 assert_eq!(
638 link.take_settled(),
639 vec![("b".into(), Err("bad token".into()))]
640 );
641 assert_eq!(link.offer(env("c")), Offer::Refused("bad token".into()));
642 let (tx, mut rx) = unbounded();
643 link.connected(2, tx);
644 assert_eq!(link.offer(env("c")), Offer::Pending, "accepted again");
645 assert!(rx.try_recv().is_ok(), "and sent");
646 }
647
648 #[test]
649 fn received_messages_teach_routes() {
650 let link = Link::new();
651 let text = Frame::Message(Message::from_envelope(&env("m"))).to_text();
652 assert!(matches!(link.receive(&text).unwrap(), Inbound::Deliver(_)));
653 assert!(link.routes_to("https://client.example/a/1"));
654 }
655}