diff --git a/examples/src/lib.rs b/examples/src/lib.rs index 0da43cfcab..0be3809cf2 100644 --- a/examples/src/lib.rs +++ b/examples/src/lib.rs @@ -48,7 +48,6 @@ impl From<&CommonArgs> for Config { Some(path) => Config::from_file(path).unwrap(), None => Config::default(), }; - println!("ARGS mode: {:?} ", value.mode); match value.mode { Some(Wai::Peer) => config.set_mode(Some(zenoh::scouting::WhatAmI::Peer)), Some(Wai::Client) => config.set_mode(Some(zenoh::scouting::WhatAmI::Client)), diff --git a/io/zenoh-transport/src/lib.rs b/io/zenoh-transport/src/lib.rs index 5432394756..5e00bed2e7 100644 --- a/io/zenoh-transport/src/lib.rs +++ b/io/zenoh-transport/src/lib.rs @@ -20,7 +20,6 @@ pub mod common; pub mod manager; pub mod multicast; -pub mod primitives; pub mod unicast; #[cfg(feature = "stats")] diff --git a/io/zenoh-transport/src/primitives/demux.rs b/io/zenoh-transport/src/primitives/demux.rs deleted file mode 100644 index 260fffa11d..0000000000 --- a/io/zenoh-transport/src/primitives/demux.rs +++ /dev/null @@ -1,58 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// -use super::Primitives; -use crate::TransportPeerEventHandler; -use std::any::Any; -use zenoh_link::Link; -use zenoh_protocol::network::{NetworkBody, NetworkMessage}; -use zenoh_result::ZResult; - -pub struct DeMux { - primitives: P, -} - -impl DeMux

{ - pub fn new(primitives: P) -> DeMux

{ - DeMux { primitives } - } -} - -impl TransportPeerEventHandler for DeMux

{ - fn handle_message(&self, msg: NetworkMessage) -> ZResult<()> { - match msg.body { - NetworkBody::Declare(m) => self.primitives.send_declare(m), - NetworkBody::Push(m) => self.primitives.send_push(m), - NetworkBody::Request(m) => self.primitives.send_request(m), - NetworkBody::Response(m) => self.primitives.send_response(m), - NetworkBody::ResponseFinal(m) => self.primitives.send_response_final(m), - NetworkBody::OAM(_m) => (), - } - - Ok(()) - } - - fn new_link(&self, _link: Link) {} - - fn del_link(&self, _link: Link) {} - - fn closing(&self) { - self.primitives.send_close(); - } - - fn closed(&self) {} - - fn as_any(&self) -> &dyn Any { - self - } -} diff --git a/io/zenoh-transport/src/primitives/mux.rs b/io/zenoh-transport/src/primitives/mux.rs deleted file mode 100644 index 8783b8ff40..0000000000 --- a/io/zenoh-transport/src/primitives/mux.rs +++ /dev/null @@ -1,130 +0,0 @@ -// -// Copyright (c) 2023 ZettaScale Technology -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License 2.0 which is available at -// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// -// Contributors: -// ZettaScale Zenoh Team, -// -use super::super::{TransportMulticast, TransportUnicast}; -use super::Primitives; -use zenoh_protocol::network::{ - Declare, NetworkBody, NetworkMessage, Push, Request, Response, ResponseFinal, -}; - -pub struct Mux { - handler: TransportUnicast, -} - -impl Mux { - pub fn new(handler: TransportUnicast) -> Mux { - Mux { handler } - } -} - -impl Primitives for Mux { - fn send_declare(&self, msg: Declare) { - let _ = self.handler.schedule(NetworkMessage { - body: NetworkBody::Declare(msg), - #[cfg(feature = "stats")] - size: None, - }); - } - - fn send_push(&self, msg: Push) { - let _ = self.handler.schedule(NetworkMessage { - body: NetworkBody::Push(msg), - #[cfg(feature = "stats")] - size: None, - }); - } - - fn send_request(&self, msg: Request) { - let _ = self.handler.schedule(NetworkMessage { - body: NetworkBody::Request(msg), - #[cfg(feature = "stats")] - size: None, - }); - } - - fn send_response(&self, msg: Response) { - let _ = self.handler.schedule(NetworkMessage { - body: NetworkBody::Response(msg), - #[cfg(feature = "stats")] - size: None, - }); - } - - fn send_response_final(&self, msg: ResponseFinal) { - let _ = self.handler.schedule(NetworkMessage { - body: NetworkBody::ResponseFinal(msg), - #[cfg(feature = "stats")] - size: None, - }); - } - - fn send_close(&self) { - // self.handler.closing().await; - } -} - -pub struct McastMux { - handler: TransportMulticast, -} - -impl McastMux { - pub fn new(handler: TransportMulticast) -> McastMux { - McastMux { handler } - } -} - -impl Primitives for McastMux { - fn send_declare(&self, msg: Declare) { - let _ = self.handler.handle_message(NetworkMessage { - body: NetworkBody::Declare(msg), - #[cfg(feature = "stats")] - size: None, - }); - } - - fn send_push(&self, msg: Push) { - let _ = self.handler.handle_message(NetworkMessage { - body: NetworkBody::Push(msg), - #[cfg(feature = "stats")] - size: None, - }); - } - - fn send_request(&self, msg: Request) { - let _ = self.handler.handle_message(NetworkMessage { - body: NetworkBody::Request(msg), - #[cfg(feature = "stats")] - size: None, - }); - } - - fn send_response(&self, msg: Response) { - let _ = self.handler.handle_message(NetworkMessage { - body: NetworkBody::Response(msg), - #[cfg(feature = "stats")] - size: None, - }); - } - - fn send_response_final(&self, msg: ResponseFinal) { - let _ = self.handler.handle_message(NetworkMessage { - body: NetworkBody::ResponseFinal(msg), - #[cfg(feature = "stats")] - size: None, - }); - } - - fn send_close(&self) { - // self.handler.closing().await; - } -} diff --git a/zenoh-ext/examples/z_pub_cache.rs b/zenoh-ext/examples/z_pub_cache.rs index 74f2ada1f8..882764b8f9 100644 --- a/zenoh-ext/examples/z_pub_cache.rs +++ b/zenoh-ext/examples/z_pub_cache.rs @@ -23,7 +23,7 @@ async fn main() { // Initiate logging env_logger::init(); - let (config, key_expr, value, history, prefix) = parse_args(); + let (config, key_expr, value, history, prefix, complete) = parse_args(); println!("Opening session..."); let session = zenoh::open(config).res().await.unwrap(); @@ -31,7 +31,8 @@ async fn main() { println!("Declaring PublicationCache on {}", &key_expr); let mut publication_cache_builder = session .declare_publication_cache(&key_expr) - .history(history); + .history(history) + .queryable_complete(complete); if let Some(prefix) = prefix { publication_cache_builder = publication_cache_builder.queryable_prefix(prefix); } @@ -45,7 +46,7 @@ async fn main() { } } -fn parse_args() -> (Config, String, String, usize, Option) { +fn parse_args() -> (Config, String, String, usize, Option, bool) { let args = Command::new("zenoh-ext pub cache example") .arg( arg!(-m --mode [MODE] "The zenoh session mode (peer by default)") @@ -59,11 +60,12 @@ fn parse_args() -> (Config, String, String, usize, Option) { ) .arg(arg!(-v --value [VALUE] "The value to publish.").default_value("Pub from Rust!")) .arg( - arg!(-h --history [SIZE] "The number of publications to keep in cache") + arg!(-i --history [SIZE] "The number of publications to keep in cache") .default_value("1"), ) .arg(arg!(-x --prefix [STRING] "An optional queryable prefix")) .arg(arg!(-c --config [FILE] "A configuration file.")) + .arg(arg!(-o --complete "Set `complete` option to true. This means that this queryable is ulitmate data source, no need to scan other queryables.")) .arg(arg!(--"no-multicast-scouting" "Disable the multicast-based scouting mechanism.")) .get_matches(); @@ -101,6 +103,7 @@ fn parse_args() -> (Config, String, String, usize, Option) { let value = args.get_one::("value").unwrap().to_string(); let history: usize = args.get_one::("history").unwrap().parse().unwrap(); let prefix = args.get_one::("prefix").map(|s| (*s).to_owned()); + let complete = args.get_flag("complete"); - (config, key_expr, value, history, prefix) + (config, key_expr, value, history, prefix, complete) } diff --git a/zenoh-ext/src/lib.rs b/zenoh-ext/src/lib.rs index dca488ba80..7440d80a53 100644 --- a/zenoh-ext/src/lib.rs +++ b/zenoh-ext/src/lib.rs @@ -20,7 +20,7 @@ pub use publication_cache::{PublicationCache, PublicationCacheBuilder}; pub use querying_subscriber::{ FetchingSubscriber, FetchingSubscriberBuilder, QueryingSubscriberBuilder, }; -pub use session_ext::{ArcSessionExt, SessionExt}; +pub use session_ext::SessionExt; pub use subscriber_ext::SubscriberBuilderExt; pub use subscriber_ext::SubscriberForward; diff --git a/zenoh-ext/src/publication_cache.rs b/zenoh-ext/src/publication_cache.rs index 7ae440b02c..cd5ed964ad 100644 --- a/zenoh-ext/src/publication_cache.rs +++ b/zenoh-ext/src/publication_cache.rs @@ -32,7 +32,8 @@ pub struct PublicationCacheBuilder<'a, 'b, 'c> { session: SessionRef<'a>, pub_key_expr: ZResult>, queryable_prefix: Option>>, - queryable_origin: Locality, + queryable_origin: Option, + complete: Option, history: usize, resources_limit: Option, } @@ -46,7 +47,8 @@ impl<'a, 'b, 'c> PublicationCacheBuilder<'a, 'b, 'c> { session, pub_key_expr, queryable_prefix: None, - queryable_origin: Locality::default(), + queryable_origin: None, + complete: None, history: 1, resources_limit: None, } @@ -67,7 +69,13 @@ impl<'a, 'b, 'c> PublicationCacheBuilder<'a, 'b, 'c> { #[zenoh_macros::unstable] #[inline] pub fn queryable_allowed_origin(mut self, origin: Locality) -> Self { - self.queryable_origin = origin; + self.queryable_origin = Some(origin); + self + } + + /// Set completeness option for the queryable. + pub fn queryable_complete(mut self, complete: bool) -> Self { + self.complete = Some(complete); self } @@ -137,28 +145,21 @@ impl<'a> PublicationCache<'a> { } // declare the local subscriber that will store the local publications - let (local_sub, queryable) = match conf.session.clone() { - SessionRef::Borrow(session) => ( - session - .declare_subscriber(&key_expr) - .allowed_origin(Locality::SessionLocal) - .res_sync()?, - session - .declare_queryable(&queryable_key_expr) - .allowed_origin(conf.queryable_origin) - .res_sync()?, - ), - SessionRef::Shared(session) => ( - session - .declare_subscriber(&key_expr) - .allowed_origin(Locality::SessionLocal) - .res_sync()?, - session - .declare_queryable(&queryable_key_expr) - .allowed_origin(conf.queryable_origin) - .res_sync()?, - ), - }; + let local_sub = conf + .session + .declare_subscriber(&key_expr) + .allowed_origin(Locality::SessionLocal) + .res_sync()?; + + // declare the queryable which returns the cached publications + let mut queryable = conf.session.declare_queryable(&queryable_key_expr); + if let Some(origin) = conf.queryable_origin { + queryable = queryable.allowed_origin(origin); + } + if let Some(complete) = conf.complete { + queryable = queryable.complete(complete); + } + let queryable = queryable.res_sync()?; // take local ownership of stuff to be moved into task let sub_recv = local_sub.receiver.clone(); diff --git a/zenoh-ext/src/querying_subscriber.rs b/zenoh-ext/src/querying_subscriber.rs index 1083c111c4..4a7c4f2ded 100644 --- a/zenoh-ext/src/querying_subscriber.rs +++ b/zenoh-ext/src/querying_subscriber.rs @@ -680,33 +680,20 @@ impl<'a, Receiver> FetchingSubscriber<'a, Receiver> { // register fetch handler let handler = register_handler(state.clone(), callback.clone()); // declare subscriber - let subscriber = match conf.session.clone() { - SessionRef::Borrow(session) => match conf.key_space.into() { - crate::KeySpace::User => session - .declare_subscriber(&key_expr) - .callback(sub_callback) - .reliability(conf.reliability) - .allowed_origin(conf.origin) - .res_sync()?, - crate::KeySpace::Liveliness => session - .liveliness() - .declare_subscriber(&key_expr) - .callback(sub_callback) - .res_sync()?, - }, - SessionRef::Shared(session) => match conf.key_space.into() { - crate::KeySpace::User => session - .declare_subscriber(&key_expr) - .callback(sub_callback) - .reliability(conf.reliability) - .allowed_origin(conf.origin) - .res_sync()?, - crate::KeySpace::Liveliness => session - .liveliness() - .declare_subscriber(&key_expr) - .callback(sub_callback) - .res_sync()?, - }, + let subscriber = match conf.key_space.into() { + crate::KeySpace::User => conf + .session + .declare_subscriber(&key_expr) + .callback(sub_callback) + .reliability(conf.reliability) + .allowed_origin(conf.origin) + .res_sync()?, + crate::KeySpace::Liveliness => conf + .session + .liveliness() + .declare_subscriber(&key_expr) + .callback(sub_callback) + .res_sync()?, }; let fetch_subscriber = FetchingSubscriber { diff --git a/zenoh-ext/src/session_ext.rs b/zenoh-ext/src/session_ext.rs index 3f9a428293..2a2c1df97b 100644 --- a/zenoh-ext/src/session_ext.rs +++ b/zenoh-ext/src/session_ext.rs @@ -18,67 +18,49 @@ use zenoh::prelude::KeyExpr; use zenoh::{Session, SessionRef}; /// Some extensions to the [`zenoh::Session`](zenoh::Session) -pub trait SessionExt { - type PublicationCacheBuilder<'a, 'b, 'c> - where - Self: 'a; - fn declare_publication_cache<'a, 'b, 'c, TryIntoKeyExpr>( - &'a self, +pub trait SessionExt<'s, 'a> { + fn declare_publication_cache<'b, 'c, TryIntoKeyExpr>( + &'s self, pub_key_expr: TryIntoKeyExpr, - ) -> Self::PublicationCacheBuilder<'a, 'b, 'c> + ) -> PublicationCacheBuilder<'a, 'b, 'c> where TryIntoKeyExpr: TryInto>, >>::Error: Into; } -impl SessionExt for Session { - type PublicationCacheBuilder<'a, 'b, 'c> = PublicationCacheBuilder<'a, 'b, 'c>; - fn declare_publication_cache<'a, 'b, 'c, TryIntoKeyExpr>( - &'a self, +impl<'s, 'a> SessionExt<'s, 'a> for SessionRef<'a> { + fn declare_publication_cache<'b, 'c, TryIntoKeyExpr>( + &'s self, pub_key_expr: TryIntoKeyExpr, ) -> PublicationCacheBuilder<'a, 'b, 'c> where TryIntoKeyExpr: TryInto>, >>::Error: Into, { - PublicationCacheBuilder::new( - SessionRef::Borrow(self), - pub_key_expr.try_into().map_err(Into::into), - ) + PublicationCacheBuilder::new(self.clone(), pub_key_expr.try_into().map_err(Into::into)) } } -impl SessionExt for T { - type PublicationCacheBuilder<'a, 'b, 'c> = PublicationCacheBuilder<'static, 'b, 'c>; - fn declare_publication_cache<'a, 'b, 'c, TryIntoKeyExpr>( +impl<'a> SessionExt<'a, 'a> for Session { + fn declare_publication_cache<'b, 'c, TryIntoKeyExpr>( &'a self, pub_key_expr: TryIntoKeyExpr, - ) -> Self::PublicationCacheBuilder<'a, 'b, 'c> + ) -> PublicationCacheBuilder<'a, 'b, 'c> where TryIntoKeyExpr: TryInto>, >>::Error: Into, { - ArcSessionExt::declare_publication_cache(self, pub_key_expr) + SessionRef::Borrow(self).declare_publication_cache(pub_key_expr) } } -pub trait ArcSessionExt { - fn declare_publication_cache<'b, 'c, TryIntoKeyExpr>( - &self, - pub_key_expr: TryIntoKeyExpr, - ) -> PublicationCacheBuilder<'static, 'b, 'c> - where - TryIntoKeyExpr: TryInto>, - >>::Error: Into; -} - -impl ArcSessionExt for Arc { +impl<'s> SessionExt<'s, 'static> for Arc { /// Examples: /// ``` /// # async_std::task::block_on(async { /// use zenoh::prelude::r#async::*; /// use zenoh::config::ModeDependentValue::Unique; - /// use zenoh_ext::ArcSessionExt; + /// use zenoh_ext::SessionExt; /// /// let mut config = config::default(); /// config.timestamping.set_enabled(Some(Unique(true))); @@ -90,16 +72,13 @@ impl ArcSessionExt for Arc { /// # }) /// ``` fn declare_publication_cache<'b, 'c, TryIntoKeyExpr>( - &self, + &'s self, pub_key_expr: TryIntoKeyExpr, ) -> PublicationCacheBuilder<'static, 'b, 'c> where TryIntoKeyExpr: TryInto>, >>::Error: Into, { - PublicationCacheBuilder::new( - SessionRef::Shared(self.clone()), - pub_key_expr.try_into().map_err(Into::into), - ) + SessionRef::Shared(self.clone()).declare_publication_cache(pub_key_expr) } } diff --git a/zenoh/src/key_expr.rs b/zenoh/src/key_expr.rs index 7f7c7f8880..5c60407baf 100644 --- a/zenoh/src/key_expr.rs +++ b/zenoh/src/key_expr.rs @@ -26,9 +26,8 @@ use zenoh_protocol::{ network::{declare, DeclareBody, Mapping, UndeclareKeyExpr}, }; use zenoh_result::ZResult; -use zenoh_transport::primitives::Primitives; -use crate::{prelude::Selector, Session, Undeclarable}; +use crate::{net::primitives::Primitives, prelude::Selector, Session, Undeclarable}; #[derive(Clone, Debug)] pub(crate) enum KeyExprInner<'a> { diff --git a/zenoh/src/net/mod.rs b/zenoh/src/net/mod.rs index b0b4be3f14..346426a630 100644 --- a/zenoh/src/net/mod.rs +++ b/zenoh/src/net/mod.rs @@ -20,13 +20,13 @@ #[doc(hidden)] pub(crate) mod codec; #[doc(hidden)] +pub(crate) mod primitives; +#[doc(hidden)] pub(crate) mod protocol; #[doc(hidden)] pub(crate) mod routing; #[doc(hidden)] pub mod runtime; -#[doc(hidden)] -pub(crate) use zenoh_transport as transport; #[cfg(test)] pub(crate) mod tests; diff --git a/zenoh/src/net/primitives/demux.rs b/zenoh/src/net/primitives/demux.rs new file mode 100644 index 0000000000..95b89268df --- /dev/null +++ b/zenoh/src/net/primitives/demux.rs @@ -0,0 +1,95 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// +use super::Primitives; +use crate::net::routing::{ + dispatcher::face::Face, + interceptor::{InterceptorTrait, InterceptorsChain}, + RoutingContext, +}; +use std::any::Any; +use zenoh_link::Link; +use zenoh_protocol::network::{NetworkBody, NetworkMessage}; +use zenoh_result::ZResult; +use zenoh_transport::unicast::TransportUnicast; +use zenoh_transport::TransportPeerEventHandler; + +pub struct DeMux { + face: Face, + pub(crate) transport: Option, + pub(crate) interceptor: InterceptorsChain, +} + +impl DeMux { + pub(crate) fn new( + face: Face, + transport: Option, + interceptor: InterceptorsChain, + ) -> Self { + Self { + face, + transport, + interceptor, + } + } +} + +impl TransportPeerEventHandler for DeMux { + #[inline] + fn handle_message(&self, mut msg: NetworkMessage) -> ZResult<()> { + if !self.interceptor.interceptors.is_empty() { + let ctx = RoutingContext::new_in(msg, self.face.clone()); + let ctx = match self.interceptor.intercept(ctx) { + Some(ctx) => ctx, + None => return Ok(()), + }; + msg = ctx.msg; + } + + match msg.body { + NetworkBody::Push(m) => self.face.send_push(m), + NetworkBody::Declare(m) => self.face.send_declare(m), + NetworkBody::Request(m) => self.face.send_request(m), + NetworkBody::Response(m) => self.face.send_response(m), + NetworkBody::ResponseFinal(m) => self.face.send_response_final(m), + NetworkBody::OAM(m) => { + if let Some(transport) = self.transport.as_ref() { + let ctrl_lock = zlock!(self.face.tables.ctrl_lock); + let mut tables = zwrite!(self.face.tables.tables); + ctrl_lock.handle_oam(&mut tables, &self.face.tables, m, transport)? + } + } + } + + Ok(()) + } + + fn new_link(&self, _link: Link) {} + + fn del_link(&self, _link: Link) {} + + fn closing(&self) { + self.face.send_close(); + if let Some(transport) = self.transport.as_ref() { + let ctrl_lock = zlock!(self.face.tables.ctrl_lock); + let mut tables = zwrite!(self.face.tables.tables); + let _ = ctrl_lock.closing(&mut tables, &self.face.tables, transport); + } + } + + fn closed(&self) {} + + fn as_any(&self) -> &dyn Any { + self + } +} diff --git a/io/zenoh-transport/src/primitives/mod.rs b/zenoh/src/net/primitives/mod.rs similarity index 61% rename from io/zenoh-transport/src/primitives/mod.rs rename to zenoh/src/net/primitives/mod.rs index b79682790f..cbfa2e3716 100644 --- a/io/zenoh-transport/src/primitives/mod.rs +++ b/zenoh/src/net/primitives/mod.rs @@ -18,6 +18,8 @@ pub use demux::*; pub use mux::*; use zenoh_protocol::network::{Declare, Push, Request, Response, ResponseFinal}; +use super::routing::RoutingContext; + pub trait Primitives: Send + Sync { fn send_declare(&self, msg: Declare); @@ -32,15 +34,23 @@ pub trait Primitives: Send + Sync { fn send_close(&self); } -#[derive(Default)] -pub struct DummyPrimitives; +pub(crate) trait EPrimitives: Send + Sync { + fn send_declare(&self, ctx: RoutingContext); + + fn send_push(&self, msg: Push); + + fn send_request(&self, ctx: RoutingContext); -impl DummyPrimitives { - pub fn new() -> Self { - Self - } + fn send_response(&self, ctx: RoutingContext); + + fn send_response_final(&self, ctx: RoutingContext); + + fn send_close(&self); } +#[derive(Default)] +pub struct DummyPrimitives; + impl Primitives for DummyPrimitives { fn send_declare(&self, _msg: Declare) {} @@ -54,3 +64,17 @@ impl Primitives for DummyPrimitives { fn send_close(&self) {} } + +impl EPrimitives for DummyPrimitives { + fn send_declare(&self, _ctx: RoutingContext) {} + + fn send_push(&self, _msg: Push) {} + + fn send_request(&self, _ctx: RoutingContext) {} + + fn send_response(&self, _ctx: RoutingContext) {} + + fn send_response_final(&self, _ctx: RoutingContext) {} + + fn send_close(&self) {} +} diff --git a/zenoh/src/net/primitives/mux.rs b/zenoh/src/net/primitives/mux.rs new file mode 100644 index 0000000000..17aad11311 --- /dev/null +++ b/zenoh/src/net/primitives/mux.rs @@ -0,0 +1,432 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// +use super::{EPrimitives, Primitives}; +use crate::net::routing::{ + dispatcher::face::Face, + interceptor::{InterceptorTrait, InterceptorsChain}, + RoutingContext, +}; +use std::sync::OnceLock; +use zenoh_protocol::network::{ + Declare, NetworkBody, NetworkMessage, Push, Request, Response, ResponseFinal, +}; +use zenoh_transport::{multicast::TransportMulticast, unicast::TransportUnicast}; + +pub struct Mux { + pub handler: TransportUnicast, + pub(crate) face: OnceLock, + pub(crate) interceptor: InterceptorsChain, +} + +impl Mux { + pub(crate) fn new(handler: TransportUnicast, interceptor: InterceptorsChain) -> Mux { + Mux { + handler, + face: OnceLock::new(), + interceptor, + } + } +} + +impl Primitives for Mux { + fn send_declare(&self, msg: Declare) { + let msg = NetworkMessage { + body: NetworkBody::Declare(msg), + #[cfg(feature = "stats")] + size: None, + }; + if self.interceptor.interceptors.is_empty() { + let _ = self.handler.schedule(msg); + } else if let Some(face) = self.face.get() { + let ctx = RoutingContext::new_out(msg, face.clone()); + if let Some(ctx) = self.interceptor.intercept(ctx) { + let _ = self.handler.schedule(ctx.msg); + } + } else { + log::error!("Uninitialized multiplexer!"); + } + } + + fn send_push(&self, msg: Push) { + let msg = NetworkMessage { + body: NetworkBody::Push(msg), + #[cfg(feature = "stats")] + size: None, + }; + if self.interceptor.interceptors.is_empty() { + let _ = self.handler.schedule(msg); + } else if let Some(face) = self.face.get() { + let ctx = RoutingContext::new_out(msg, face.clone()); + if let Some(ctx) = self.interceptor.intercept(ctx) { + let _ = self.handler.schedule(ctx.msg); + } + } else { + log::error!("Uninitialized multiplexer!"); + } + } + + fn send_request(&self, msg: Request) { + let msg = NetworkMessage { + body: NetworkBody::Request(msg), + #[cfg(feature = "stats")] + size: None, + }; + if self.interceptor.interceptors.is_empty() { + let _ = self.handler.schedule(msg); + } else if let Some(face) = self.face.get() { + let ctx = RoutingContext::new_out(msg, face.clone()); + if let Some(ctx) = self.interceptor.intercept(ctx) { + let _ = self.handler.schedule(ctx.msg); + } + } else { + log::error!("Uninitialized multiplexer!"); + } + } + + fn send_response(&self, msg: Response) { + let msg = NetworkMessage { + body: NetworkBody::Response(msg), + #[cfg(feature = "stats")] + size: None, + }; + if self.interceptor.interceptors.is_empty() { + let _ = self.handler.schedule(msg); + } else if let Some(face) = self.face.get() { + let ctx = RoutingContext::new_out(msg, face.clone()); + if let Some(ctx) = self.interceptor.intercept(ctx) { + let _ = self.handler.schedule(ctx.msg); + } + } else { + log::error!("Uninitialized multiplexer!"); + } + } + + fn send_response_final(&self, msg: ResponseFinal) { + let msg = NetworkMessage { + body: NetworkBody::ResponseFinal(msg), + #[cfg(feature = "stats")] + size: None, + }; + if self.interceptor.interceptors.is_empty() { + let _ = self.handler.schedule(msg); + } else if let Some(face) = self.face.get() { + let ctx = RoutingContext::new_out(msg, face.clone()); + if let Some(ctx) = self.interceptor.intercept(ctx) { + let _ = self.handler.schedule(ctx.msg); + } + } else { + log::error!("Uninitialized multiplexer!"); + } + } + + fn send_close(&self) { + // self.handler.closing().await; + } +} + +impl EPrimitives for Mux { + fn send_declare(&self, ctx: RoutingContext) { + let ctx = RoutingContext { + msg: NetworkMessage { + body: NetworkBody::Declare(ctx.msg), + #[cfg(feature = "stats")] + size: None, + }, + inface: ctx.inface, + outface: ctx.outface, + prefix: ctx.prefix, + full_expr: ctx.full_expr, + }; + if let Some(ctx) = self.interceptor.intercept(ctx) { + let _ = self.handler.schedule(ctx.msg); + } + } + + fn send_push(&self, msg: Push) { + let msg = NetworkMessage { + body: NetworkBody::Push(msg), + #[cfg(feature = "stats")] + size: None, + }; + if self.interceptor.interceptors.is_empty() { + let _ = self.handler.schedule(msg); + } else if let Some(face) = self.face.get() { + let ctx = RoutingContext::new_out(msg, face.clone()); + if let Some(ctx) = self.interceptor.intercept(ctx) { + let _ = self.handler.schedule(ctx.msg); + } + } else { + log::error!("Uninitialized multiplexer!"); + } + } + + fn send_request(&self, ctx: RoutingContext) { + let ctx = RoutingContext { + msg: NetworkMessage { + body: NetworkBody::Request(ctx.msg), + #[cfg(feature = "stats")] + size: None, + }, + inface: ctx.inface, + outface: ctx.outface, + prefix: ctx.prefix, + full_expr: ctx.full_expr, + }; + if let Some(ctx) = self.interceptor.intercept(ctx) { + let _ = self.handler.schedule(ctx.msg); + } + } + + fn send_response(&self, ctx: RoutingContext) { + let ctx = RoutingContext { + msg: NetworkMessage { + body: NetworkBody::Response(ctx.msg), + #[cfg(feature = "stats")] + size: None, + }, + inface: ctx.inface, + outface: ctx.outface, + prefix: ctx.prefix, + full_expr: ctx.full_expr, + }; + if let Some(ctx) = self.interceptor.intercept(ctx) { + let _ = self.handler.schedule(ctx.msg); + } + } + + fn send_response_final(&self, ctx: RoutingContext) { + let ctx = RoutingContext { + msg: NetworkMessage { + body: NetworkBody::ResponseFinal(ctx.msg), + #[cfg(feature = "stats")] + size: None, + }, + inface: ctx.inface, + outface: ctx.outface, + prefix: ctx.prefix, + full_expr: ctx.full_expr, + }; + if let Some(ctx) = self.interceptor.intercept(ctx) { + let _ = self.handler.schedule(ctx.msg); + } + } + + fn send_close(&self) { + // self.handler.closing().await; + } +} + +pub struct McastMux { + pub handler: TransportMulticast, + pub(crate) face: OnceLock, + pub(crate) interceptor: InterceptorsChain, +} + +impl McastMux { + pub(crate) fn new(handler: TransportMulticast, interceptor: InterceptorsChain) -> McastMux { + McastMux { + handler, + face: OnceLock::new(), + interceptor, + } + } +} + +impl Primitives for McastMux { + fn send_declare(&self, msg: Declare) { + let msg = NetworkMessage { + body: NetworkBody::Declare(msg), + #[cfg(feature = "stats")] + size: None, + }; + if self.interceptor.interceptors.is_empty() { + let _ = self.handler.schedule(msg); + } else if let Some(face) = self.face.get() { + let ctx = RoutingContext::new_out(msg, face.clone()); + if let Some(ctx) = self.interceptor.intercept(ctx) { + let _ = self.handler.schedule(ctx.msg); + } + } else { + log::error!("Uninitialized multiplexer!"); + } + } + + fn send_push(&self, msg: Push) { + let msg = NetworkMessage { + body: NetworkBody::Push(msg), + #[cfg(feature = "stats")] + size: None, + }; + if self.interceptor.interceptors.is_empty() { + let _ = self.handler.schedule(msg); + } else if let Some(face) = self.face.get() { + let ctx = RoutingContext::new_out(msg, face.clone()); + if let Some(ctx) = self.interceptor.intercept(ctx) { + let _ = self.handler.schedule(ctx.msg); + } + } else { + log::error!("Uninitialized multiplexer!"); + } + } + + fn send_request(&self, msg: Request) { + let msg = NetworkMessage { + body: NetworkBody::Request(msg), + #[cfg(feature = "stats")] + size: None, + }; + if self.interceptor.interceptors.is_empty() { + let _ = self.handler.schedule(msg); + } else if let Some(face) = self.face.get() { + let ctx = RoutingContext::new_out(msg, face.clone()); + if let Some(ctx) = self.interceptor.intercept(ctx) { + let _ = self.handler.schedule(ctx.msg); + } + } else { + log::error!("Uninitialized multiplexer!"); + } + } + + fn send_response(&self, msg: Response) { + let msg = NetworkMessage { + body: NetworkBody::Response(msg), + #[cfg(feature = "stats")] + size: None, + }; + if self.interceptor.interceptors.is_empty() { + let _ = self.handler.schedule(msg); + } else if let Some(face) = self.face.get() { + let ctx = RoutingContext::new_out(msg, face.clone()); + if let Some(ctx) = self.interceptor.intercept(ctx) { + let _ = self.handler.schedule(ctx.msg); + } + } else { + log::error!("Uninitialized multiplexer!"); + } + } + + fn send_response_final(&self, msg: ResponseFinal) { + let msg = NetworkMessage { + body: NetworkBody::ResponseFinal(msg), + #[cfg(feature = "stats")] + size: None, + }; + if self.interceptor.interceptors.is_empty() { + let _ = self.handler.schedule(msg); + } else if let Some(face) = self.face.get() { + let ctx = RoutingContext::new_out(msg, face.clone()); + if let Some(ctx) = self.interceptor.intercept(ctx) { + let _ = self.handler.schedule(ctx.msg); + } + } else { + log::error!("Uninitialized multiplexer!"); + } + } + + fn send_close(&self) { + // self.handler.closing().await; + } +} + +impl EPrimitives for McastMux { + fn send_declare(&self, ctx: RoutingContext) { + let ctx = RoutingContext { + msg: NetworkMessage { + body: NetworkBody::Declare(ctx.msg), + #[cfg(feature = "stats")] + size: None, + }, + inface: ctx.inface, + outface: ctx.outface, + prefix: ctx.prefix, + full_expr: ctx.full_expr, + }; + if let Some(ctx) = self.interceptor.intercept(ctx) { + let _ = self.handler.schedule(ctx.msg); + } + } + + fn send_push(&self, msg: Push) { + let msg = NetworkMessage { + body: NetworkBody::Push(msg), + #[cfg(feature = "stats")] + size: None, + }; + if self.interceptor.interceptors.is_empty() { + let _ = self.handler.schedule(msg); + } else if let Some(face) = self.face.get() { + let ctx = RoutingContext::new_out(msg, face.clone()); + if let Some(ctx) = self.interceptor.intercept(ctx) { + let _ = self.handler.schedule(ctx.msg); + } + } else { + log::error!("Uninitialized multiplexer!"); + } + } + + fn send_request(&self, ctx: RoutingContext) { + let ctx = RoutingContext { + msg: NetworkMessage { + body: NetworkBody::Request(ctx.msg), + #[cfg(feature = "stats")] + size: None, + }, + inface: ctx.inface, + outface: ctx.outface, + prefix: ctx.prefix, + full_expr: ctx.full_expr, + }; + if let Some(ctx) = self.interceptor.intercept(ctx) { + let _ = self.handler.schedule(ctx.msg); + } + } + + fn send_response(&self, ctx: RoutingContext) { + let ctx = RoutingContext { + msg: NetworkMessage { + body: NetworkBody::Response(ctx.msg), + #[cfg(feature = "stats")] + size: None, + }, + inface: ctx.inface, + outface: ctx.outface, + prefix: ctx.prefix, + full_expr: ctx.full_expr, + }; + if let Some(ctx) = self.interceptor.intercept(ctx) { + let _ = self.handler.schedule(ctx.msg); + } + } + + fn send_response_final(&self, ctx: RoutingContext) { + let ctx = RoutingContext { + msg: NetworkMessage { + body: NetworkBody::ResponseFinal(ctx.msg), + #[cfg(feature = "stats")] + size: None, + }, + inface: ctx.inface, + outface: ctx.outface, + prefix: ctx.prefix, + full_expr: ctx.full_expr, + }; + if let Some(ctx) = self.interceptor.intercept(ctx) { + let _ = self.handler.schedule(ctx.msg); + } + } + + fn send_close(&self) { + // self.handler.closing().await; + } +} diff --git a/zenoh/src/net/routing/dispatcher/face.rs b/zenoh/src/net/routing/dispatcher/face.rs new file mode 100644 index 0000000000..fcf6d3c302 --- /dev/null +++ b/zenoh/src/net/routing/dispatcher/face.rs @@ -0,0 +1,234 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// +use super::super::router::*; +use super::tables::TablesLock; +use super::{resource::*, tables}; +use crate::net::primitives::Primitives; +use std::any::Any; +use std::collections::HashMap; +use std::fmt; +use std::sync::Arc; +use zenoh_protocol::zenoh::RequestBody; +use zenoh_protocol::{ + core::{ExprId, WhatAmI, ZenohId}, + network::{Mapping, Push, Request, RequestId, Response, ResponseFinal}, +}; +use zenoh_transport::multicast::TransportMulticast; +#[cfg(feature = "stats")] +use zenoh_transport::stats::TransportStats; + +pub struct FaceState { + pub(crate) id: usize, + pub(crate) zid: ZenohId, + pub(crate) whatami: WhatAmI, + #[cfg(feature = "stats")] + pub(crate) stats: Option>, + pub(crate) primitives: Arc, + pub(crate) local_mappings: HashMap>, + pub(crate) remote_mappings: HashMap>, + pub(crate) next_qid: RequestId, + pub(crate) pending_queries: HashMap>, + pub(crate) mcast_group: Option, + pub(crate) hat: Box, +} + +impl FaceState { + pub(crate) fn new( + id: usize, + zid: ZenohId, + whatami: WhatAmI, + #[cfg(feature = "stats")] stats: Option>, + primitives: Arc, + mcast_group: Option, + hat: Box, + ) -> Arc { + Arc::new(FaceState { + id, + zid, + whatami, + #[cfg(feature = "stats")] + stats, + primitives, + local_mappings: HashMap::new(), + remote_mappings: HashMap::new(), + next_qid: 0, + pending_queries: HashMap::new(), + mcast_group, + hat, + }) + } + + #[inline] + pub(crate) fn get_mapping( + &self, + prefixid: &ExprId, + mapping: Mapping, + ) -> Option<&std::sync::Arc> { + match mapping { + Mapping::Sender => self.remote_mappings.get(prefixid), + Mapping::Receiver => self.local_mappings.get(prefixid), + } + } + + #[inline] + pub(crate) fn get_sent_mapping( + &self, + prefixid: &ExprId, + mapping: Mapping, + ) -> Option<&std::sync::Arc> { + match mapping { + Mapping::Sender => self.local_mappings.get(prefixid), + Mapping::Receiver => self.remote_mappings.get(prefixid), + } + } + + pub(crate) fn get_next_local_id(&self) -> ExprId { + let mut id = 1; + while self.local_mappings.get(&id).is_some() || self.remote_mappings.get(&id).is_some() { + id += 1; + } + id + } +} + +impl fmt::Display for FaceState { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Face{{{}, {}}}", self.id, self.zid) + } +} + +#[derive(Clone)] +pub struct Face { + pub(crate) tables: Arc, + pub(crate) state: Arc, +} + +impl Primitives for Face { + fn send_declare(&self, msg: zenoh_protocol::network::Declare) { + let ctrl_lock = zlock!(self.tables.ctrl_lock); + match msg.body { + zenoh_protocol::network::DeclareBody::DeclareKeyExpr(m) => { + register_expr(&self.tables, &mut self.state.clone(), m.id, &m.wire_expr); + } + zenoh_protocol::network::DeclareBody::UndeclareKeyExpr(m) => { + unregister_expr(&self.tables, &mut self.state.clone(), m.id); + } + zenoh_protocol::network::DeclareBody::DeclareSubscriber(m) => { + declare_subscription( + ctrl_lock.as_ref(), + &self.tables, + &mut self.state.clone(), + &m.wire_expr, + &m.ext_info, + msg.ext_nodeid.node_id, + ); + } + zenoh_protocol::network::DeclareBody::UndeclareSubscriber(m) => { + undeclare_subscription( + ctrl_lock.as_ref(), + &self.tables, + &mut self.state.clone(), + &m.ext_wire_expr.wire_expr, + msg.ext_nodeid.node_id, + ); + } + zenoh_protocol::network::DeclareBody::DeclareQueryable(m) => { + declare_queryable( + ctrl_lock.as_ref(), + &self.tables, + &mut self.state.clone(), + &m.wire_expr, + &m.ext_info, + msg.ext_nodeid.node_id, + ); + } + zenoh_protocol::network::DeclareBody::UndeclareQueryable(m) => { + undeclare_queryable( + ctrl_lock.as_ref(), + &self.tables, + &mut self.state.clone(), + &m.ext_wire_expr.wire_expr, + msg.ext_nodeid.node_id, + ); + } + zenoh_protocol::network::DeclareBody::DeclareToken(_m) => todo!(), + zenoh_protocol::network::DeclareBody::UndeclareToken(_m) => todo!(), + zenoh_protocol::network::DeclareBody::DeclareInterest(_m) => todo!(), + zenoh_protocol::network::DeclareBody::FinalInterest(_m) => todo!(), + zenoh_protocol::network::DeclareBody::UndeclareInterest(_m) => todo!(), + } + drop(ctrl_lock); + } + + #[inline] + fn send_push(&self, msg: Push) { + full_reentrant_route_data( + &self.tables, + &self.state, + &msg.wire_expr, + msg.ext_qos, + msg.payload, + msg.ext_nodeid.node_id, + ); + } + + fn send_request(&self, msg: Request) { + match msg.payload { + RequestBody::Query(_) => { + route_query( + &self.tables, + &self.state, + &msg.wire_expr, + // parameters, + msg.id, + msg.ext_target, + // consolidation, + msg.payload, + msg.ext_nodeid.node_id, + ); + } + RequestBody::Pull(_) => { + pull_data(&self.tables.tables, &self.state.clone(), msg.wire_expr); + } + _ => { + log::error!("Unsupported request"); + } + } + } + + fn send_response(&self, msg: Response) { + route_send_response( + &self.tables, + &mut self.state.clone(), + msg.rid, + msg.ext_respid, + msg.wire_expr, + msg.payload, + ); + } + + fn send_response_final(&self, msg: ResponseFinal) { + route_send_response_final(&self.tables, &mut self.state.clone(), msg.rid); + } + + fn send_close(&self) { + tables::close_face(&self.tables, &Arc::downgrade(&self.state)); + } +} + +impl fmt::Display for Face { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.state.fmt(f) + } +} diff --git a/zenoh/src/net/routing/dispatcher/mod.rs b/zenoh/src/net/routing/dispatcher/mod.rs new file mode 100644 index 0000000000..53c32fb5ff --- /dev/null +++ b/zenoh/src/net/routing/dispatcher/mod.rs @@ -0,0 +1,24 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +//! ⚠️ WARNING ⚠️ +//! +//! This module is intended for Zenoh's internal use. +//! +//! [Click here for Zenoh's documentation](../zenoh/index.html) +pub mod face; +pub mod pubsub; +pub mod queries; +pub mod resource; +pub mod tables; diff --git a/zenoh/src/net/routing/dispatcher/pubsub.rs b/zenoh/src/net/routing/dispatcher/pubsub.rs new file mode 100644 index 0000000000..da6ae0c371 --- /dev/null +++ b/zenoh/src/net/routing/dispatcher/pubsub.rs @@ -0,0 +1,620 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// +use super::face::FaceState; +use super::resource::{DataRoutes, Direction, PullCaches, Resource}; +use super::tables::{NodeId, Route, RoutingExpr, Tables, TablesLock}; +use crate::net::routing::hat::HatTrait; +use std::borrow::Cow; +use std::collections::HashMap; +use std::sync::Arc; +use std::sync::RwLock; +use zenoh_core::zread; +use zenoh_protocol::core::key_expr::{keyexpr, OwnedKeyExpr}; +use zenoh_protocol::network::declare::subscriber::ext::SubscriberInfo; +use zenoh_protocol::network::declare::Mode; +use zenoh_protocol::{ + core::{WhatAmI, WireExpr}, + network::{declare::ext, Push}, + zenoh::PushBody, +}; +use zenoh_sync::get_mut_unchecked; + +pub(crate) fn declare_subscription( + hat_code: &(dyn HatTrait + Send + Sync), + tables: &TablesLock, + face: &mut Arc, + expr: &WireExpr, + sub_info: &SubscriberInfo, + node_id: NodeId, +) { + log::debug!("Declare subscription {}", face); + let rtables = zread!(tables.tables); + match rtables + .get_mapping(face, &expr.scope, expr.mapping) + .cloned() + { + Some(mut prefix) => { + let res = Resource::get_resource(&prefix, &expr.suffix); + let (mut res, mut wtables) = + if res.as_ref().map(|r| r.context.is_some()).unwrap_or(false) { + drop(rtables); + let wtables = zwrite!(tables.tables); + (res.unwrap(), wtables) + } else { + let mut fullexpr = prefix.expr(); + fullexpr.push_str(expr.suffix.as_ref()); + let mut matches = keyexpr::new(fullexpr.as_str()) + .map(|ke| Resource::get_matches(&rtables, ke)) + .unwrap_or_default(); + drop(rtables); + let mut wtables = zwrite!(tables.tables); + let mut res = + Resource::make_resource(&mut wtables, &mut prefix, expr.suffix.as_ref()); + matches.push(Arc::downgrade(&res)); + Resource::match_resource(&wtables, &mut res, matches); + (res, wtables) + }; + + hat_code.declare_subscription(&mut wtables, face, &mut res, sub_info, node_id); + + disable_matches_data_routes(&mut wtables, &mut res); + drop(wtables); + + let rtables = zread!(tables.tables); + let matches_data_routes = compute_matches_data_routes(&rtables, &res); + drop(rtables); + + let wtables = zwrite!(tables.tables); + for (mut res, data_routes, matching_pulls) in matches_data_routes { + get_mut_unchecked(&mut res) + .context_mut() + .update_data_routes(data_routes); + get_mut_unchecked(&mut res) + .context_mut() + .update_matching_pulls(matching_pulls); + } + drop(wtables); + } + None => log::error!("Declare subscription for unknown scope {}!", expr.scope), + } +} + +pub(crate) fn undeclare_subscription( + hat_code: &(dyn HatTrait + Send + Sync), + tables: &TablesLock, + face: &mut Arc, + expr: &WireExpr, + node_id: NodeId, +) { + log::debug!("Undeclare subscription {}", face); + let rtables = zread!(tables.tables); + match rtables.get_mapping(face, &expr.scope, expr.mapping) { + Some(prefix) => match Resource::get_resource(prefix, expr.suffix.as_ref()) { + Some(mut res) => { + drop(rtables); + let mut wtables = zwrite!(tables.tables); + + hat_code.undeclare_subscription(&mut wtables, face, &mut res, node_id); + + disable_matches_data_routes(&mut wtables, &mut res); + drop(wtables); + + let rtables = zread!(tables.tables); + let matches_data_routes = compute_matches_data_routes(&rtables, &res); + drop(rtables); + + let wtables = zwrite!(tables.tables); + for (mut res, data_routes, matching_pulls) in matches_data_routes { + get_mut_unchecked(&mut res) + .context_mut() + .update_data_routes(data_routes); + get_mut_unchecked(&mut res) + .context_mut() + .update_matching_pulls(matching_pulls); + } + Resource::clean(&mut res); + drop(wtables); + } + None => log::error!("Undeclare unknown subscription!"), + }, + None => log::error!("Undeclare subscription with unknown scope!"), + } +} + +fn compute_data_routes_(tables: &Tables, routes: &mut DataRoutes, expr: &mut RoutingExpr) { + let indexes = tables.hat_code.get_data_routes_entries(tables); + + let max_idx = indexes.routers.iter().max().unwrap(); + routes + .routers + .resize_with((*max_idx as usize) + 1, || Arc::new(HashMap::new())); + + for idx in indexes.routers { + routes.routers[idx as usize] = + tables + .hat_code + .compute_data_route(tables, expr, idx, WhatAmI::Router); + } + + let max_idx = indexes.peers.iter().max().unwrap(); + routes + .peers + .resize_with((*max_idx as usize) + 1, || Arc::new(HashMap::new())); + + for idx in indexes.peers { + routes.peers[idx as usize] = + tables + .hat_code + .compute_data_route(tables, expr, idx, WhatAmI::Peer); + } + + let max_idx = indexes.clients.iter().max().unwrap(); + routes + .clients + .resize_with((*max_idx as usize) + 1, || Arc::new(HashMap::new())); + + for idx in indexes.clients { + routes.clients[idx as usize] = + tables + .hat_code + .compute_data_route(tables, expr, idx, WhatAmI::Client); + } +} + +pub(crate) fn compute_data_routes(tables: &Tables, expr: &mut RoutingExpr) -> DataRoutes { + let mut routes = DataRoutes::default(); + compute_data_routes_(tables, &mut routes, expr); + routes +} + +pub(crate) fn update_data_routes(tables: &Tables, res: &mut Arc) { + if res.context.is_some() { + let mut res_mut = res.clone(); + let res_mut = get_mut_unchecked(&mut res_mut); + compute_data_routes_( + tables, + &mut res_mut.context_mut().data_routes, + &mut RoutingExpr::new(res, ""), + ); + } +} + +pub(crate) fn update_data_routes_from(tables: &mut Tables, res: &mut Arc) { + update_data_routes(tables, res); + update_matching_pulls(tables, res); + let res = get_mut_unchecked(res); + for child in res.childs.values_mut() { + update_data_routes_from(tables, child); + } +} + +pub(crate) fn compute_matches_data_routes<'a>( + tables: &'a Tables, + res: &'a Arc, +) -> Vec<(Arc, DataRoutes, Arc)> { + let mut routes = vec![]; + if res.context.is_some() { + let mut expr = RoutingExpr::new(res, ""); + routes.push(( + res.clone(), + compute_data_routes(tables, &mut expr), + compute_matching_pulls(tables, &mut expr), + )); + for match_ in &res.context().matches { + let match_ = match_.upgrade().unwrap(); + if !Arc::ptr_eq(&match_, res) { + let mut expr = RoutingExpr::new(&match_, ""); + let match_routes = compute_data_routes(tables, &mut expr); + let matching_pulls = compute_matching_pulls(tables, &mut expr); + routes.push((match_, match_routes, matching_pulls)); + } + } + } + routes +} + +pub(crate) fn update_matches_data_routes<'a>(tables: &'a mut Tables, res: &'a mut Arc) { + if res.context.is_some() { + update_data_routes(tables, res); + update_matching_pulls(tables, res); + for match_ in &res.context().matches { + let mut match_ = match_.upgrade().unwrap(); + if !Arc::ptr_eq(&match_, res) { + update_data_routes(tables, &mut match_); + update_matching_pulls(tables, &mut match_); + } + } + } +} + +pub(crate) fn disable_matches_data_routes(_tables: &mut Tables, res: &mut Arc) { + if res.context.is_some() { + get_mut_unchecked(res).context_mut().disable_data_routes(); + for match_ in &res.context().matches { + let mut match_ = match_.upgrade().unwrap(); + if !Arc::ptr_eq(&match_, res) { + get_mut_unchecked(&mut match_) + .context_mut() + .disable_data_routes(); + get_mut_unchecked(&mut match_) + .context_mut() + .disable_matching_pulls(); + } + } + } +} + +macro_rules! treat_timestamp { + ($hlc:expr, $payload:expr, $drop:expr) => { + // if an HLC was configured (via Config.add_timestamp), + // check DataInfo and add a timestamp if there isn't + if let Some(hlc) = $hlc { + if let PushBody::Put(data) = &mut $payload { + if let Some(ref ts) = data.timestamp { + // Timestamp is present; update HLC with it (possibly raising error if delta exceed) + match hlc.update_with_timestamp(ts) { + Ok(()) => (), + Err(e) => { + if $drop { + log::error!( + "Error treating timestamp for received Data ({}). Drop it!", + e + ); + return; + } else { + data.timestamp = Some(hlc.new_timestamp()); + log::error!( + "Error treating timestamp for received Data ({}). Replace timestamp: {:?}", + e, + data.timestamp); + } + } + } + } else { + // Timestamp not present; add one + data.timestamp = Some(hlc.new_timestamp()); + log::trace!("Adding timestamp to DataInfo: {:?}", data.timestamp); + } + } + } + } +} + +#[inline] +fn get_data_route( + tables: &Tables, + face: &FaceState, + res: &Option>, + expr: &mut RoutingExpr, + routing_context: NodeId, +) -> Arc { + let local_context = tables + .hat_code + .map_routing_context(tables, face, routing_context); + res.as_ref() + .and_then(|res| res.data_route(face.whatami, local_context)) + .unwrap_or_else(|| { + tables + .hat_code + .compute_data_route(tables, expr, local_context, face.whatami) + }) +} + +#[zenoh_macros::unstable] +#[inline] +pub(crate) fn get_local_data_route( + tables: &Tables, + res: &Option>, + expr: &mut RoutingExpr, +) -> Arc { + res.as_ref() + .and_then(|res| res.data_route(WhatAmI::Client, 0)) + .unwrap_or_else(|| { + tables + .hat_code + .compute_data_route(tables, expr, 0, WhatAmI::Client) + }) +} + +fn compute_matching_pulls_(tables: &Tables, pull_caches: &mut PullCaches, expr: &mut RoutingExpr) { + let ke = if let Ok(ke) = OwnedKeyExpr::try_from(expr.full_expr()) { + ke + } else { + return; + }; + let res = Resource::get_resource(expr.prefix, expr.suffix); + let matches = res + .as_ref() + .and_then(|res| res.context.as_ref()) + .map(|ctx| Cow::from(&ctx.matches)) + .unwrap_or_else(|| Cow::from(Resource::get_matches(tables, &ke))); + + for mres in matches.iter() { + let mres = mres.upgrade().unwrap(); + for context in mres.session_ctxs.values() { + if let Some(subinfo) = &context.subs { + if subinfo.mode == Mode::Pull { + pull_caches.push(context.clone()); + } + } + } + } +} + +pub(crate) fn compute_matching_pulls(tables: &Tables, expr: &mut RoutingExpr) -> Arc { + let mut pull_caches = PullCaches::default(); + compute_matching_pulls_(tables, &mut pull_caches, expr); + Arc::new(pull_caches) +} + +pub(crate) fn update_matching_pulls(tables: &Tables, res: &mut Arc) { + if res.context.is_some() { + let mut res_mut = res.clone(); + let res_mut = get_mut_unchecked(&mut res_mut); + if res_mut.context_mut().matching_pulls.is_none() { + res_mut.context_mut().matching_pulls = Some(Arc::new(PullCaches::default())); + } + compute_matching_pulls_( + tables, + get_mut_unchecked(res_mut.context_mut().matching_pulls.as_mut().unwrap()), + &mut RoutingExpr::new(res, ""), + ); + } +} + +#[inline] +fn get_matching_pulls( + tables: &Tables, + res: &Option>, + expr: &mut RoutingExpr, +) -> Arc { + res.as_ref() + .and_then(|res| res.context.as_ref()) + .and_then(|ctx| ctx.matching_pulls.clone()) + .unwrap_or_else(|| compute_matching_pulls(tables, expr)) +} + +macro_rules! cache_data { + ( + $matching_pulls:expr, + $expr:expr, + $payload:expr + ) => { + for context in $matching_pulls.iter() { + get_mut_unchecked(&mut context.clone()) + .last_values + .insert($expr.full_expr().to_string(), $payload.clone()); + } + }; +} + +#[cfg(feature = "stats")] +macro_rules! inc_stats { + ( + $face:expr, + $txrx:ident, + $space:ident, + $body:expr + ) => { + paste::paste! { + if let Some(stats) = $face.stats.as_ref() { + use zenoh_buffers::buffer::Buffer; + match &$body { + PushBody::Put(p) => { + stats.[<$txrx _z_put_msgs>].[](1); + stats.[<$txrx _z_put_pl_bytes>].[](p.payload.len()); + } + PushBody::Del(_) => { + stats.[<$txrx _z_del_msgs>].[](1); + } + } + } + } + }; +} + +pub fn full_reentrant_route_data( + tables_ref: &Arc, + face: &FaceState, + expr: &WireExpr, + ext_qos: ext::QoSType, + mut payload: PushBody, + routing_context: NodeId, +) { + let tables = zread!(tables_ref.tables); + match tables.get_mapping(face, &expr.scope, expr.mapping).cloned() { + Some(prefix) => { + log::trace!( + "Route data for res {}{}", + prefix.expr(), + expr.suffix.as_ref() + ); + let mut expr = RoutingExpr::new(&prefix, expr.suffix.as_ref()); + + #[cfg(feature = "stats")] + let admin = expr.full_expr().starts_with("@/"); + #[cfg(feature = "stats")] + if !admin { + inc_stats!(face, rx, user, payload) + } else { + inc_stats!(face, rx, admin, payload) + } + + if tables.hat_code.ingress_filter(&tables, face, &mut expr) { + let res = Resource::get_resource(&prefix, expr.suffix); + + let route = get_data_route(&tables, face, &res, &mut expr, routing_context); + + let matching_pulls = get_matching_pulls(&tables, &res, &mut expr); + + if !(route.is_empty() && matching_pulls.is_empty()) { + treat_timestamp!(&tables.hlc, payload, tables.drop_future_timestamp); + + if route.len() == 1 && matching_pulls.len() == 0 { + let (outface, key_expr, context) = route.values().next().unwrap(); + if tables + .hat_code + .egress_filter(&tables, face, outface, &mut expr) + { + drop(tables); + #[cfg(feature = "stats")] + if !admin { + inc_stats!(face, tx, user, payload) + } else { + inc_stats!(face, tx, admin, payload) + } + + outface.primitives.send_push(Push { + wire_expr: key_expr.into(), + ext_qos, + ext_tstamp: None, + ext_nodeid: ext::NodeIdType { node_id: *context }, + payload, + }) + } + } else { + if !matching_pulls.is_empty() { + let lock = zlock!(tables.pull_caches_lock); + cache_data!(matching_pulls, expr, payload); + drop(lock); + } + + if tables.whatami == WhatAmI::Router { + let route = route + .values() + .filter(|(outface, _key_expr, _context)| { + tables + .hat_code + .egress_filter(&tables, face, outface, &mut expr) + }) + .cloned() + .collect::>(); + + drop(tables); + for (outface, key_expr, context) in route { + #[cfg(feature = "stats")] + if !admin { + inc_stats!(face, tx, user, payload) + } else { + inc_stats!(face, tx, admin, payload) + } + + outface.primitives.send_push(Push { + wire_expr: key_expr, + ext_qos, + ext_tstamp: None, + ext_nodeid: ext::NodeIdType { node_id: context }, + payload: payload.clone(), + }) + } + } else { + drop(tables); + for (outface, key_expr, context) in route.values() { + if face.id != outface.id + && match ( + face.mcast_group.as_ref(), + outface.mcast_group.as_ref(), + ) { + (Some(l), Some(r)) => l != r, + _ => true, + } + { + #[cfg(feature = "stats")] + if !admin { + inc_stats!(face, tx, user, payload) + } else { + inc_stats!(face, tx, admin, payload) + } + + outface.primitives.send_push(Push { + wire_expr: key_expr.into(), + ext_qos, + ext_tstamp: None, + ext_nodeid: ext::NodeIdType { node_id: *context }, + payload: payload.clone(), + }) + } + } + } + } + } + } + } + None => { + log::error!("Route data with unknown scope {}!", expr.scope); + } + } +} + +pub fn pull_data(tables_ref: &RwLock, face: &Arc, expr: WireExpr) { + let tables = zread!(tables_ref); + match tables.get_mapping(face, &expr.scope, expr.mapping) { + Some(prefix) => match Resource::get_resource(prefix, expr.suffix.as_ref()) { + Some(mut res) => { + let res = get_mut_unchecked(&mut res); + match res.session_ctxs.get_mut(&face.id) { + Some(ctx) => match &ctx.subs { + Some(_subinfo) => { + // let reliability = subinfo.reliability; + let lock = zlock!(tables.pull_caches_lock); + let route = get_mut_unchecked(ctx) + .last_values + .drain() + .map(|(name, sample)| { + ( + Resource::get_best_key(&tables.root_res, &name, face.id) + .to_owned(), + sample, + ) + }) + .collect::>(); + drop(lock); + drop(tables); + for (key_expr, payload) in route { + face.primitives.send_push(Push { + wire_expr: key_expr, + ext_qos: ext::QoSType::push_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + payload, + }); + } + } + None => { + log::error!( + "Pull data for unknown subscription {} (no info)!", + prefix.expr() + expr.suffix.as_ref() + ); + } + }, + None => { + log::error!( + "Pull data for unknown subscription {} (no context)!", + prefix.expr() + expr.suffix.as_ref() + ); + } + } + } + None => { + log::error!( + "Pull data for unknown subscription {} (no resource)!", + prefix.expr() + expr.suffix.as_ref() + ); + } + }, + None => { + log::error!("Pull data with unknown scope {}!", expr.scope); + } + }; +} diff --git a/zenoh/src/net/routing/dispatcher/queries.rs b/zenoh/src/net/routing/dispatcher/queries.rs new file mode 100644 index 0000000000..9645af0f74 --- /dev/null +++ b/zenoh/src/net/routing/dispatcher/queries.rs @@ -0,0 +1,805 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// +use super::face::FaceState; +use super::resource::{QueryRoute, QueryRoutes, QueryTargetQablSet, Resource}; +use super::tables::NodeId; +use super::tables::{RoutingExpr, Tables, TablesLock}; +use crate::net::routing::hat::HatTrait; +use crate::net::routing::RoutingContext; +use async_trait::async_trait; +use std::collections::HashMap; +use std::sync::{Arc, Weak}; +use zenoh_config::WhatAmI; +use zenoh_protocol::core::key_expr::keyexpr; +use zenoh_protocol::network::declare::queryable::ext::QueryableInfo; +use zenoh_protocol::{ + core::{Encoding, WireExpr}, + network::{ + declare::ext, + request::{ext::TargetType, Request, RequestId}, + response::{self, ext::ResponderIdType, Response, ResponseFinal}, + }, + zenoh::{reply::ext::ConsolidationType, Reply, RequestBody, ResponseBody}, +}; +use zenoh_sync::get_mut_unchecked; +use zenoh_util::Timed; + +pub(crate) struct Query { + src_face: Arc, + src_qid: RequestId, +} + +pub(crate) fn declare_queryable( + hat_code: &(dyn HatTrait + Send + Sync), + tables: &TablesLock, + face: &mut Arc, + expr: &WireExpr, + qabl_info: &QueryableInfo, + node_id: NodeId, +) { + log::debug!("Register queryable {}", face); + let rtables = zread!(tables.tables); + match rtables + .get_mapping(face, &expr.scope, expr.mapping) + .cloned() + { + Some(mut prefix) => { + let res = Resource::get_resource(&prefix, &expr.suffix); + let (mut res, mut wtables) = + if res.as_ref().map(|r| r.context.is_some()).unwrap_or(false) { + drop(rtables); + let wtables = zwrite!(tables.tables); + (res.unwrap(), wtables) + } else { + let mut fullexpr = prefix.expr(); + fullexpr.push_str(expr.suffix.as_ref()); + let mut matches = keyexpr::new(fullexpr.as_str()) + .map(|ke| Resource::get_matches(&rtables, ke)) + .unwrap_or_default(); + drop(rtables); + let mut wtables = zwrite!(tables.tables); + let mut res = + Resource::make_resource(&mut wtables, &mut prefix, expr.suffix.as_ref()); + matches.push(Arc::downgrade(&res)); + Resource::match_resource(&wtables, &mut res, matches); + (res, wtables) + }; + + hat_code.declare_queryable(&mut wtables, face, &mut res, qabl_info, node_id); + + disable_matches_query_routes(&mut wtables, &mut res); + drop(wtables); + + let rtables = zread!(tables.tables); + let matches_query_routes = compute_matches_query_routes(&rtables, &res); + drop(rtables); + + let wtables = zwrite!(tables.tables); + for (mut res, query_routes) in matches_query_routes { + get_mut_unchecked(&mut res) + .context_mut() + .update_query_routes(query_routes); + } + drop(wtables); + } + None => log::error!("Declare queryable for unknown scope {}!", expr.scope), + } +} + +pub(crate) fn undeclare_queryable( + hat_code: &(dyn HatTrait + Send + Sync), + tables: &TablesLock, + face: &mut Arc, + expr: &WireExpr, + node_id: NodeId, +) { + let rtables = zread!(tables.tables); + match rtables.get_mapping(face, &expr.scope, expr.mapping) { + Some(prefix) => match Resource::get_resource(prefix, expr.suffix.as_ref()) { + Some(mut res) => { + drop(rtables); + let mut wtables = zwrite!(tables.tables); + + hat_code.undeclare_queryable(&mut wtables, face, &mut res, node_id); + + disable_matches_query_routes(&mut wtables, &mut res); + drop(wtables); + + let rtables = zread!(tables.tables); + let matches_query_routes = compute_matches_query_routes(&rtables, &res); + drop(rtables); + + let wtables = zwrite!(tables.tables); + for (mut res, query_routes) in matches_query_routes { + get_mut_unchecked(&mut res) + .context_mut() + .update_query_routes(query_routes); + } + Resource::clean(&mut res); + drop(wtables); + } + None => log::error!("Undeclare unknown queryable!"), + }, + None => log::error!("Undeclare queryable with unknown scope!"), + } +} + +fn compute_query_routes_(tables: &Tables, routes: &mut QueryRoutes, expr: &mut RoutingExpr) { + let indexes = tables.hat_code.get_query_routes_entries(tables); + + let max_idx = indexes.routers.iter().max().unwrap(); + routes.routers.resize_with((*max_idx as usize) + 1, || { + Arc::new(QueryTargetQablSet::new()) + }); + + for idx in indexes.routers { + routes.routers[idx as usize] = + tables + .hat_code + .compute_query_route(tables, expr, idx, WhatAmI::Router); + } + + let max_idx = indexes.peers.iter().max().unwrap(); + routes.peers.resize_with((*max_idx as usize) + 1, || { + Arc::new(QueryTargetQablSet::new()) + }); + + for idx in indexes.peers { + routes.peers[idx as usize] = + tables + .hat_code + .compute_query_route(tables, expr, idx, WhatAmI::Peer); + } + + let max_idx = indexes.clients.iter().max().unwrap(); + routes.clients.resize_with((*max_idx as usize) + 1, || { + Arc::new(QueryTargetQablSet::new()) + }); + + for idx in indexes.clients { + routes.clients[idx as usize] = + tables + .hat_code + .compute_query_route(tables, expr, idx, WhatAmI::Client); + } +} + +pub(crate) fn compute_query_routes(tables: &Tables, res: &Arc) -> QueryRoutes { + let mut routes = QueryRoutes::default(); + compute_query_routes_(tables, &mut routes, &mut RoutingExpr::new(res, "")); + routes +} + +pub(crate) fn update_query_routes(tables: &Tables, res: &Arc) { + if res.context.is_some() { + let mut res_mut = res.clone(); + let res_mut = get_mut_unchecked(&mut res_mut); + compute_query_routes_( + tables, + &mut res_mut.context_mut().query_routes, + &mut RoutingExpr::new(res, ""), + ); + } +} + +pub(crate) fn update_query_routes_from(tables: &mut Tables, res: &mut Arc) { + update_query_routes(tables, res); + let res = get_mut_unchecked(res); + for child in res.childs.values_mut() { + update_query_routes_from(tables, child); + } +} + +pub(crate) fn compute_matches_query_routes( + tables: &Tables, + res: &Arc, +) -> Vec<(Arc, QueryRoutes)> { + let mut routes = vec![]; + if res.context.is_some() { + routes.push((res.clone(), compute_query_routes(tables, res))); + for match_ in &res.context().matches { + let match_ = match_.upgrade().unwrap(); + if !Arc::ptr_eq(&match_, res) { + let match_routes = compute_query_routes(tables, &match_); + routes.push((match_, match_routes)); + } + } + } + routes +} + +pub(crate) fn update_matches_query_routes(tables: &Tables, res: &Arc) { + if res.context.is_some() { + update_query_routes(tables, res); + for match_ in &res.context().matches { + let match_ = match_.upgrade().unwrap(); + if !Arc::ptr_eq(&match_, res) { + update_query_routes(tables, &match_); + } + } + } +} + +#[inline] +fn insert_pending_query(outface: &mut Arc, query: Arc) -> RequestId { + let outface_mut = get_mut_unchecked(outface); + outface_mut.next_qid += 1; + let qid = outface_mut.next_qid; + outface_mut.pending_queries.insert(qid, query); + qid +} + +#[inline] +fn compute_final_route( + tables: &Tables, + qabls: &Arc, + src_face: &Arc, + expr: &mut RoutingExpr, + target: &TargetType, + query: Arc, +) -> QueryRoute { + match target { + TargetType::All => { + let mut route = HashMap::new(); + for qabl in qabls.iter() { + if tables + .hat_code + .egress_filter(tables, src_face, &qabl.direction.0, expr) + { + #[cfg(feature = "complete_n")] + { + route.entry(qabl.direction.0.id).or_insert_with(|| { + let mut direction = qabl.direction.clone(); + let qid = insert_pending_query(&mut direction.0, query.clone()); + (direction, qid, *target) + }); + } + #[cfg(not(feature = "complete_n"))] + { + route.entry(qabl.direction.0.id).or_insert_with(|| { + let mut direction = qabl.direction.clone(); + let qid = insert_pending_query(&mut direction.0, query.clone()); + (direction, qid) + }); + } + } + } + route + } + TargetType::AllComplete => { + let mut route = HashMap::new(); + for qabl in qabls.iter() { + if qabl.complete > 0 + && tables + .hat_code + .egress_filter(tables, src_face, &qabl.direction.0, expr) + { + #[cfg(feature = "complete_n")] + { + route.entry(qabl.direction.0.id).or_insert_with(|| { + let mut direction = qabl.direction.clone(); + let qid = insert_pending_query(&mut direction.0, query.clone()); + (direction, qid, *target) + }); + } + #[cfg(not(feature = "complete_n"))] + { + route.entry(qabl.direction.0.id).or_insert_with(|| { + let mut direction = qabl.direction.clone(); + let qid = insert_pending_query(&mut direction.0, query.clone()); + (direction, qid) + }); + } + } + } + route + } + #[cfg(feature = "complete_n")] + TargetType::Complete(n) => { + let mut route = HashMap::new(); + let mut remaining = *n; + for qabl in qabls.iter() { + if qabl.complete > 0 + && tables + .hat_code + .egress_filter(tables, src_face, &qabl.direction.0, expr) + { + let nb = std::cmp::min(qabl.complete, remaining); + route.entry(qabl.direction.0.id).or_insert_with(|| { + let mut direction = qabl.direction.clone(); + let qid = insert_pending_query(&mut direction.0, query.clone()); + (direction, qid, TargetType::Complete(nb)) + }); + remaining -= nb; + if remaining == 0 { + break; + } + } + } + route + } + TargetType::BestMatching => { + if let Some(qabl) = qabls + .iter() + .find(|qabl| qabl.direction.0.id != src_face.id && qabl.complete > 0) + { + let mut route = HashMap::new(); + #[cfg(feature = "complete_n")] + { + let mut direction = qabl.direction.clone(); + let qid = insert_pending_query(&mut direction.0, query); + route.insert(direction.0.id, (direction, qid, *target)); + } + #[cfg(not(feature = "complete_n"))] + { + let mut direction = qabl.direction.clone(); + let qid = insert_pending_query(&mut direction.0, query); + route.insert(direction.0.id, (direction, qid)); + } + route + } else { + compute_final_route(tables, qabls, src_face, expr, &TargetType::All, query) + } + } + } +} + +#[derive(Clone)] +struct QueryCleanup { + tables: Arc, + face: Weak, + qid: RequestId, +} + +#[async_trait] +impl Timed for QueryCleanup { + async fn run(&mut self) { + if let Some(mut face) = self.face.upgrade() { + let tables_lock = zwrite!(self.tables.tables); + if let Some(query) = get_mut_unchecked(&mut face) + .pending_queries + .remove(&self.qid) + { + drop(tables_lock); + log::warn!( + "Didn't receive final reply {}:{} from {}: Timeout!", + query.src_face, + self.qid, + face + ); + finalize_pending_query(query); + } + } + } +} + +pub(crate) fn disable_matches_query_routes(_tables: &mut Tables, res: &mut Arc) { + if res.context.is_some() { + get_mut_unchecked(res).context_mut().disable_query_routes(); + for match_ in &res.context().matches { + let mut match_ = match_.upgrade().unwrap(); + if !Arc::ptr_eq(&match_, res) { + get_mut_unchecked(&mut match_) + .context_mut() + .disable_query_routes(); + } + } + } +} + +#[inline] +fn get_query_route( + tables: &Tables, + face: &FaceState, + res: &Option>, + expr: &mut RoutingExpr, + routing_context: NodeId, +) -> Arc { + let local_context = tables + .hat_code + .map_routing_context(tables, face, routing_context); + res.as_ref() + .and_then(|res| res.query_route(face.whatami, local_context)) + .unwrap_or_else(|| { + tables + .hat_code + .compute_query_route(tables, expr, local_context, face.whatami) + }) +} + +#[cfg(feature = "stats")] +macro_rules! inc_req_stats { + ( + $face:expr, + $txrx:ident, + $space:ident, + $body:expr + ) => { + paste::paste! { + if let Some(stats) = $face.stats.as_ref() { + use zenoh_buffers::buffer::Buffer; + match &$body { + RequestBody::Put(p) => { + stats.[<$txrx _z_put_msgs>].[](1); + stats.[<$txrx _z_put_pl_bytes>].[](p.payload.len()); + } + RequestBody::Del(_) => { + stats.[<$txrx _z_del_msgs>].[](1); + } + RequestBody::Query(q) => { + stats.[<$txrx _z_query_msgs>].[](1); + stats.[<$txrx _z_query_pl_bytes>].[]( + q.ext_body.as_ref().map(|b| b.payload.len()).unwrap_or(0), + ); + } + RequestBody::Pull(_) => (), + } + } + } + }; +} + +#[cfg(feature = "stats")] +macro_rules! inc_res_stats { + ( + $face:expr, + $txrx:ident, + $space:ident, + $body:expr + ) => { + paste::paste! { + if let Some(stats) = $face.stats.as_ref() { + use zenoh_buffers::buffer::Buffer; + match &$body { + ResponseBody::Put(p) => { + stats.[<$txrx _z_put_msgs>].[](1); + stats.[<$txrx _z_put_pl_bytes>].[](p.payload.len()); + } + ResponseBody::Reply(r) => { + stats.[<$txrx _z_reply_msgs>].[](1); + stats.[<$txrx _z_reply_pl_bytes>].[](r.payload.len()); + } + ResponseBody::Err(e) => { + stats.[<$txrx _z_reply_msgs>].[](1); + stats.[<$txrx _z_reply_pl_bytes>].[]( + e.ext_body.as_ref().map(|b| b.payload.len()).unwrap_or(0), + ); + } + ResponseBody::Ack(_) => (), + } + } + } + }; +} + +pub fn route_query( + tables_ref: &Arc, + face: &Arc, + expr: &WireExpr, + qid: RequestId, + target: TargetType, + body: RequestBody, + routing_context: NodeId, +) { + let rtables = zread!(tables_ref.tables); + match rtables.get_mapping(face, &expr.scope, expr.mapping) { + Some(prefix) => { + log::debug!( + "Route query {}:{} for res {}{}", + face, + qid, + prefix.expr(), + expr.suffix.as_ref(), + ); + let prefix = prefix.clone(); + let mut expr = RoutingExpr::new(&prefix, expr.suffix.as_ref()); + + #[cfg(feature = "stats")] + let admin = expr.full_expr().starts_with("@/"); + #[cfg(feature = "stats")] + if !admin { + inc_req_stats!(face, rx, user, body) + } else { + inc_req_stats!(face, rx, admin, body) + } + + if rtables.hat_code.ingress_filter(&rtables, face, &mut expr) { + let res = Resource::get_resource(&prefix, expr.suffix); + + let route = get_query_route(&rtables, face, &res, &mut expr, routing_context); + + let query = Arc::new(Query { + src_face: face.clone(), + src_qid: qid, + }); + + let queries_lock = zwrite!(tables_ref.queries_lock); + let route = compute_final_route(&rtables, &route, face, &mut expr, &target, query); + let local_replies = + rtables + .hat_code + .compute_local_replies(&rtables, &prefix, expr.suffix, face); + let zid = rtables.zid; + + drop(queries_lock); + drop(rtables); + + for (wexpr, payload) in local_replies { + let payload = ResponseBody::Reply(Reply { + timestamp: None, + encoding: Encoding::default(), + ext_sinfo: None, + ext_consolidation: ConsolidationType::default(), + #[cfg(feature = "shared-memory")] + ext_shm: None, + ext_attachment: None, // @TODO: expose it in the API + ext_unknown: vec![], + payload, + }); + #[cfg(feature = "stats")] + if !admin { + inc_res_stats!(face, tx, user, payload) + } else { + inc_res_stats!(face, tx, admin, payload) + } + + face.primitives + .clone() + .send_response(RoutingContext::with_expr( + Response { + rid: qid, + wire_expr: wexpr, + payload, + ext_qos: response::ext::QoSType::declare_default(), + ext_tstamp: None, + ext_respid: Some(response::ext::ResponderIdType { + zid, + eid: 0, // @TODO use proper ResponderId (#703) + }), + }, + expr.full_expr().to_string(), + )); + } + + if route.is_empty() { + log::debug!( + "Send final reply {}:{} (no matching queryables or not master)", + face, + qid + ); + face.primitives + .clone() + .send_response_final(RoutingContext::with_expr( + ResponseFinal { + rid: qid, + ext_qos: response::ext::QoSType::response_final_default(), + ext_tstamp: None, + }, + expr.full_expr().to_string(), + )); + } else { + // let timer = tables.timer.clone(); + // let timeout = tables.queries_default_timeout; + #[cfg(feature = "complete_n")] + { + for ((outface, key_expr, context), qid, t) in route.values() { + // timer.add(TimedEvent::once( + // Instant::now() + timeout, + // QueryCleanup { + // tables: tables_ref.clone(), + // face: Arc::downgrade(&outface), + // *qid, + // }, + // )); + #[cfg(feature = "stats")] + if !admin { + inc_req_stats!(outface, tx, user, body) + } else { + inc_req_stats!(outface, tx, admin, body) + } + + log::trace!("Propagate query {}:{} to {}", face, qid, outface); + outface.primitives.send_request(RoutingContext::with_expr( + Request { + id: *qid, + wire_expr: key_expr.into(), + ext_qos: ext::QoSType::request_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType { node_id: *context }, + ext_target: *t, + ext_budget: None, + ext_timeout: None, + payload: body.clone(), + }, + expr.full_expr().to_string(), + )); + } + } + + #[cfg(not(feature = "complete_n"))] + { + for ((outface, key_expr, context), qid) in route.values() { + // timer.add(TimedEvent::once( + // Instant::now() + timeout, + // QueryCleanup { + // tables: tables_ref.clone(), + // face: Arc::downgrade(&outface), + // *qid, + // }, + // )); + #[cfg(feature = "stats")] + if !admin { + inc_req_stats!(outface, tx, user, body) + } else { + inc_req_stats!(outface, tx, admin, body) + } + + log::trace!("Propagate query {}:{} to {}", face, qid, outface); + outface.primitives.send_request(RoutingContext::with_expr( + Request { + id: *qid, + wire_expr: key_expr.into(), + ext_qos: ext::QoSType::request_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType { node_id: *context }, + ext_target: target, + ext_budget: None, + ext_timeout: None, + payload: body.clone(), + }, + expr.full_expr().to_string(), + )); + } + } + } + } else { + log::debug!("Send final reply {}:{} (not master)", face, qid); + drop(rtables); + face.primitives + .clone() + .send_response_final(RoutingContext::with_expr( + ResponseFinal { + rid: qid, + ext_qos: response::ext::QoSType::response_final_default(), + ext_tstamp: None, + }, + expr.full_expr().to_string(), + )); + } + } + None => { + log::error!( + "Route query with unknown scope {}! Send final reply.", + expr.scope + ); + drop(rtables); + face.primitives + .clone() + .send_response_final(RoutingContext::with_expr( + ResponseFinal { + rid: qid, + ext_qos: response::ext::QoSType::response_final_default(), + ext_tstamp: None, + }, + "".to_string(), + )); + } + } +} + +pub(crate) fn route_send_response( + tables_ref: &Arc, + face: &mut Arc, + qid: RequestId, + ext_respid: Option, + key_expr: WireExpr, + body: ResponseBody, +) { + let queries_lock = zread!(tables_ref.queries_lock); + #[cfg(feature = "stats")] + let admin = key_expr.as_str().starts_with("@/"); + #[cfg(feature = "stats")] + if !admin { + inc_res_stats!(face, rx, user, body) + } else { + inc_res_stats!(face, rx, admin, body) + } + + match face.pending_queries.get(&qid) { + Some(query) => { + drop(queries_lock); + + #[cfg(feature = "stats")] + if !admin { + inc_res_stats!(query.src_face, tx, user, body) + } else { + inc_res_stats!(query.src_face, tx, admin, body) + } + + query + .src_face + .primitives + .clone() + .send_response(RoutingContext::with_expr( + Response { + rid: query.src_qid, + wire_expr: key_expr.to_owned(), + payload: body, + ext_qos: response::ext::QoSType::response_default(), + ext_tstamp: None, + ext_respid, + }, + "".to_string(), // @TODO provide the proper key expression of the response for interceptors + )); + } + None => log::warn!( + "Route reply {}:{} from {}: Query nof found!", + face, + qid, + face + ), + } +} + +pub(crate) fn route_send_response_final( + tables_ref: &Arc, + face: &mut Arc, + qid: RequestId, +) { + let queries_lock = zwrite!(tables_ref.queries_lock); + match get_mut_unchecked(face).pending_queries.remove(&qid) { + Some(query) => { + drop(queries_lock); + log::debug!( + "Received final reply {}:{} from {}", + query.src_face, + qid, + face + ); + finalize_pending_query(query); + } + None => log::warn!( + "Route final reply {}:{} from {}: Query nof found!", + face, + qid, + face + ), + } +} + +pub(crate) fn finalize_pending_queries(tables_ref: &TablesLock, face: &mut Arc) { + let queries_lock = zwrite!(tables_ref.queries_lock); + for (_, query) in get_mut_unchecked(face).pending_queries.drain() { + finalize_pending_query(query); + } + drop(queries_lock); +} + +pub(crate) fn finalize_pending_query(query: Arc) { + if let Some(query) = Arc::into_inner(query) { + log::debug!("Propagate final reply {}:{}", query.src_face, query.src_qid); + query + .src_face + .primitives + .clone() + .send_response_final(RoutingContext::with_expr( + ResponseFinal { + rid: query.src_qid, + ext_qos: response::ext::QoSType::response_final_default(), + ext_tstamp: None, + }, + "".to_string(), + )); + } +} diff --git a/zenoh/src/net/routing/resource.rs b/zenoh/src/net/routing/dispatcher/resource.rs similarity index 70% rename from zenoh/src/net/routing/resource.rs rename to zenoh/src/net/routing/dispatcher/resource.rs index 00189b85c9..7fc71c623d 100644 --- a/zenoh/src/net/routing/resource.rs +++ b/zenoh/src/net/routing/dispatcher/resource.rs @@ -12,17 +12,20 @@ // ZettaScale Zenoh Team, // use super::face::FaceState; -use super::router::{Tables, TablesLock}; -use std::collections::{HashMap, HashSet}; +use super::tables::{Tables, TablesLock}; +use crate::net::routing::RoutingContext; +use std::any::Any; +use std::collections::HashMap; use std::convert::TryInto; use std::hash::{Hash, Hasher}; use std::sync::{Arc, Weak}; +use zenoh_config::WhatAmI; #[cfg(feature = "complete_n")] use zenoh_protocol::network::request::ext::TargetType; use zenoh_protocol::network::RequestId; use zenoh_protocol::zenoh::PushBody; use zenoh_protocol::{ - core::{key_expr::keyexpr, ExprId, WireExpr, ZenohId}, + core::{key_expr::keyexpr, ExprId, WireExpr}, network::{ declare::{ ext, queryable::ext::QueryableInfo, subscriber::ext::SubscriberInfo, Declare, @@ -33,114 +36,143 @@ use zenoh_protocol::{ }; use zenoh_sync::get_mut_unchecked; -pub(super) type RoutingContext = u16; +pub(crate) type NodeId = u16; -pub(super) type Direction = (Arc, WireExpr<'static>, Option); -pub(super) type Route = HashMap; +pub(crate) type Direction = (Arc, WireExpr<'static>, NodeId); +pub(crate) type Route = HashMap; #[cfg(feature = "complete_n")] -pub(super) type QueryRoute = HashMap; +pub(crate) type QueryRoute = HashMap; #[cfg(not(feature = "complete_n"))] -pub(super) type QueryRoute = HashMap; -pub(super) struct QueryTargetQabl { - pub(super) direction: Direction, - pub(super) complete: u64, - pub(super) distance: f64, +pub(crate) type QueryRoute = HashMap; +pub(crate) struct QueryTargetQabl { + pub(crate) direction: Direction, + pub(crate) complete: u64, + pub(crate) distance: f64, } -pub(super) type QueryTargetQablSet = Vec; -pub(super) type PullCaches = Vec>; - -pub(super) struct SessionContext { - pub(super) face: Arc, - pub(super) local_expr_id: Option, - pub(super) remote_expr_id: Option, - pub(super) subs: Option, - pub(super) qabl: Option, - pub(super) last_values: HashMap, +pub(crate) type QueryTargetQablSet = Vec; +pub(crate) type PullCaches = Vec>; + +pub(crate) struct SessionContext { + pub(crate) face: Arc, + pub(crate) local_expr_id: Option, + pub(crate) remote_expr_id: Option, + pub(crate) subs: Option, + pub(crate) qabl: Option, + pub(crate) last_values: HashMap, } -pub(super) struct DataRoutes { - pub(super) matching_pulls: Option>, - pub(super) routers_data_routes: Vec>, - pub(super) peers_data_routes: Vec>, - pub(super) peer_data_route: Option>, - pub(super) client_data_route: Option>, +#[derive(Default)] +pub(crate) struct RoutesIndexes { + pub(crate) routers: Vec, + pub(crate) peers: Vec, + pub(crate) clients: Vec, } -pub(super) struct QueryRoutes { - pub(super) routers_query_routes: Vec>, - pub(super) peers_query_routes: Vec>, - pub(super) peer_query_route: Option>, - pub(super) client_query_route: Option>, +#[derive(Default)] +pub(crate) struct DataRoutes { + pub(crate) routers: Vec>, + pub(crate) peers: Vec>, + pub(crate) clients: Vec>, } -pub(super) struct ResourceContext { - pub(super) router_subs: HashSet, - pub(super) peer_subs: HashSet, - pub(super) router_qabls: HashMap, - pub(super) peer_qabls: HashMap, - pub(super) matches: Vec>, - pub(super) matching_pulls: Arc, - pub(super) valid_data_routes: bool, - pub(super) routers_data_routes: Vec>, - pub(super) peers_data_routes: Vec>, - pub(super) peer_data_route: Option>, - pub(super) client_data_route: Option>, - pub(super) valid_query_routes: bool, - pub(super) routers_query_routes: Vec>, - pub(super) peers_query_routes: Vec>, - pub(super) peer_query_route: Option>, - pub(super) client_query_route: Option>, +impl DataRoutes { + #[inline] + pub(crate) fn get_route(&self, whatami: WhatAmI, context: NodeId) -> Option> { + match whatami { + WhatAmI::Router => (self.routers.len() > context as usize) + .then(|| self.routers[context as usize].clone()), + WhatAmI::Peer => { + (self.peers.len() > context as usize).then(|| self.peers[context as usize].clone()) + } + WhatAmI::Client => (self.clients.len() > context as usize) + .then(|| self.clients[context as usize].clone()), + } + } +} + +#[derive(Default)] +pub(crate) struct QueryRoutes { + pub(crate) routers: Vec>, + pub(crate) peers: Vec>, + pub(crate) clients: Vec>, +} + +impl QueryRoutes { + #[inline] + pub(crate) fn get_route( + &self, + whatami: WhatAmI, + context: NodeId, + ) -> Option> { + match whatami { + WhatAmI::Router => (self.routers.len() > context as usize) + .then(|| self.routers[context as usize].clone()), + WhatAmI::Peer => { + (self.peers.len() > context as usize).then(|| self.peers[context as usize].clone()) + } + WhatAmI::Client => (self.clients.len() > context as usize) + .then(|| self.clients[context as usize].clone()), + } + } +} + +pub(crate) struct ResourceContext { + pub(crate) matches: Vec>, + pub(crate) matching_pulls: Option>, + pub(crate) hat: Box, + pub(crate) valid_data_routes: bool, + pub(crate) data_routes: DataRoutes, + pub(crate) valid_query_routes: bool, + pub(crate) query_routes: QueryRoutes, } impl ResourceContext { - fn new() -> ResourceContext { + fn new(hat: Box) -> ResourceContext { ResourceContext { - router_subs: HashSet::new(), - peer_subs: HashSet::new(), - router_qabls: HashMap::new(), - peer_qabls: HashMap::new(), matches: Vec::new(), - matching_pulls: Arc::new(Vec::new()), + matching_pulls: None, + hat, valid_data_routes: false, - routers_data_routes: Vec::new(), - peers_data_routes: Vec::new(), - peer_data_route: None, - client_data_route: None, + data_routes: DataRoutes::default(), valid_query_routes: false, - routers_query_routes: Vec::new(), - peers_query_routes: Vec::new(), - peer_query_route: None, - client_query_route: None, + query_routes: QueryRoutes::default(), } } - pub(super) fn update_data_routes(&mut self, data_routes: DataRoutes) { + pub(crate) fn update_data_routes(&mut self, data_routes: DataRoutes) { self.valid_data_routes = true; - if let Some(matching_pulls) = data_routes.matching_pulls { - self.matching_pulls = matching_pulls; - } - self.routers_data_routes = data_routes.routers_data_routes; - self.peers_data_routes = data_routes.peers_data_routes; - self.peer_data_route = data_routes.peer_data_route; - self.client_data_route = data_routes.client_data_route; + self.data_routes = data_routes; + } + + pub(crate) fn disable_data_routes(&mut self) { + self.valid_data_routes = false; } - pub(super) fn update_query_routes(&mut self, query_routes: QueryRoutes) { + pub(crate) fn update_query_routes(&mut self, query_routes: QueryRoutes) { self.valid_query_routes = true; - self.routers_query_routes = query_routes.routers_query_routes; - self.peers_query_routes = query_routes.peers_query_routes; - self.peer_query_route = query_routes.peer_query_route; - self.client_query_route = query_routes.client_query_route; + self.query_routes = query_routes + } + + pub(crate) fn disable_query_routes(&mut self) { + self.valid_query_routes = false; + } + + pub(crate) fn update_matching_pulls(&mut self, pulls: Arc) { + self.matching_pulls = Some(pulls); + } + + pub(crate) fn disable_matching_pulls(&mut self) { + self.matching_pulls = None; } } pub struct Resource { - pub(super) parent: Option>, - pub(super) suffix: String, - pub(super) nonwild_prefix: Option<(Arc, String)>, - pub(super) childs: HashMap>, - pub(super) context: Option, - pub(super) session_ctxs: HashMap>, + pub(crate) parent: Option>, + pub(crate) suffix: String, + pub(crate) nonwild_prefix: Option<(Arc, String)>, + pub(crate) childs: HashMap>, + pub(crate) context: Option, + pub(crate) session_ctxs: HashMap>, } impl PartialEq for Resource { @@ -187,12 +219,12 @@ impl Resource { } #[inline(always)] - pub(super) fn context(&self) -> &ResourceContext { + pub(crate) fn context(&self) -> &ResourceContext { self.context.as_ref().unwrap() } #[inline(always)] - pub(super) fn context_mut(&mut self) -> &mut ResourceContext { + pub(crate) fn context_mut(&mut self) -> &mut ResourceContext { self.context.as_mut().unwrap() } @@ -209,115 +241,31 @@ impl Resource { } } - #[inline(always)] - pub fn routers_data_route(&self, context: usize) -> Option> { - match &self.context { - Some(ctx) => { - if ctx.valid_data_routes { - (ctx.routers_data_routes.len() > context) - .then(|| ctx.routers_data_routes[context].clone()) - } else { - None - } - } - - None => None, - } - } - - #[inline(always)] - pub fn peers_data_route(&self, context: usize) -> Option> { - match &self.context { - Some(ctx) => { - if ctx.valid_data_routes { - (ctx.peers_data_routes.len() > context) - .then(|| ctx.peers_data_routes[context].clone()) - } else { - None - } - } - None => None, - } - } - - #[inline(always)] - pub fn peer_data_route(&self) -> Option> { - match &self.context { - Some(ctx) => { - if ctx.valid_data_routes { - ctx.peer_data_route.clone() - } else { - None - } - } - None => None, - } - } - - #[inline(always)] - pub fn client_data_route(&self) -> Option> { + #[inline] + pub(crate) fn data_route(&self, whatami: WhatAmI, context: NodeId) -> Option> { match &self.context { Some(ctx) => { if ctx.valid_data_routes { - ctx.client_data_route.clone() + ctx.data_routes.get_route(whatami, context) } else { None } } - None => None, - } - } - #[inline(always)] - pub(super) fn routers_query_route(&self, context: usize) -> Option> { - match &self.context { - Some(ctx) => { - if ctx.valid_query_routes { - (ctx.routers_query_routes.len() > context) - .then(|| ctx.routers_query_routes[context].clone()) - } else { - None - } - } None => None, } } #[inline(always)] - pub(super) fn peers_query_route(&self, context: usize) -> Option> { + pub(crate) fn query_route( + &self, + whatami: WhatAmI, + context: NodeId, + ) -> Option> { match &self.context { Some(ctx) => { if ctx.valid_query_routes { - (ctx.peers_query_routes.len() > context) - .then(|| ctx.peers_query_routes[context].clone()) - } else { - None - } - } - None => None, - } - } - - #[inline(always)] - pub(super) fn peer_query_route(&self) -> Option> { - match &self.context { - Some(ctx) => { - if ctx.valid_query_routes { - ctx.peer_query_route.clone() - } else { - None - } - } - None => None, - } - } - - #[inline(always)] - pub(super) fn client_query_route(&self) -> Option> { - match &self.context { - Some(ctx) => { - if ctx.valid_query_routes { - ctx.client_query_route.clone() + ctx.query_routes.get_route(whatami, context) } else { None } @@ -374,12 +322,12 @@ impl Resource { } pub fn make_resource( - _tables: &mut Tables, + tables: &mut Tables, from: &mut Arc, suffix: &str, ) -> Arc { if suffix.is_empty() { - Resource::upgrade_resource(from); + Resource::upgrade_resource(from, tables.hat_code.new_resource()); from.clone() } else if let Some(stripped_suffix) = suffix.strip_prefix('/') { let (chunk, rest) = match stripped_suffix.find('/') { @@ -388,13 +336,13 @@ impl Resource { }; match get_mut_unchecked(from).childs.get_mut(chunk) { - Some(res) => Resource::make_resource(_tables, res, rest), + Some(res) => Resource::make_resource(tables, res, rest), None => { let mut new = Arc::new(Resource::new(from, chunk, None)); if log::log_enabled!(log::Level::Debug) && rest.is_empty() { log::debug!("Register resource {}", new.expr()); } - let res = Resource::make_resource(_tables, &mut new, rest); + let res = Resource::make_resource(tables, &mut new, rest); get_mut_unchecked(from) .childs .insert(String::from(chunk), new); @@ -404,7 +352,7 @@ impl Resource { } else { match from.parent.clone() { Some(mut parent) => { - Resource::make_resource(_tables, &mut parent, &[&from.suffix, suffix].concat()) + Resource::make_resource(tables, &mut parent, &[&from.suffix, suffix].concat()) } None => { let (chunk, rest) = match suffix[1..].find('/') { @@ -413,13 +361,13 @@ impl Resource { }; match get_mut_unchecked(from).childs.get_mut(chunk) { - Some(res) => Resource::make_resource(_tables, res, rest), + Some(res) => Resource::make_resource(tables, res, rest), None => { let mut new = Arc::new(Resource::new(from, chunk, None)); if log::log_enabled!(log::Level::Debug) && rest.is_empty() { log::debug!("Register resource {}", new.expr()); } - let res = Resource::make_resource(_tables, &mut new, rest); + let res = Resource::make_resource(tables, &mut new, rest); get_mut_unchecked(from) .childs .insert(String::from(chunk), new); @@ -516,15 +464,18 @@ impl Resource { get_mut_unchecked(face) .local_mappings .insert(expr_id, nonwild_prefix.clone()); - face.primitives.send_declare(Declare { - ext_qos: ext::QoSType::declare_default(), - ext_tstamp: None, - ext_nodeid: ext::NodeIdType::default(), - body: DeclareBody::DeclareKeyExpr(DeclareKeyExpr { - id: expr_id, - wire_expr: nonwild_prefix.expr().into(), - }), - }); + face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::DeclareKeyExpr(DeclareKeyExpr { + id: expr_id, + wire_expr: nonwild_prefix.expr().into(), + }), + }, + nonwild_prefix.expr(), + )); WireExpr { scope: expr_id, suffix: wildsuffix.into(), @@ -675,9 +626,9 @@ impl Resource { } } - pub fn upgrade_resource(res: &mut Arc) { + pub fn upgrade_resource(res: &mut Arc, hat: Box) { if res.context.is_none() { - get_mut_unchecked(res).context = Some(ResourceContext::new()); + get_mut_unchecked(res).context = Some(ResourceContext::new(hat)); } } } @@ -742,7 +693,7 @@ pub fn register_expr( get_mut_unchecked(face) .remote_mappings .insert(expr_id, res.clone()); - wtables.compute_matches_routes(&mut res); + wtables.update_matches_routes(&mut res); drop(wtables); } }, diff --git a/zenoh/src/net/routing/dispatcher/tables.rs b/zenoh/src/net/routing/dispatcher/tables.rs new file mode 100644 index 0000000000..274b600024 --- /dev/null +++ b/zenoh/src/net/routing/dispatcher/tables.rs @@ -0,0 +1,182 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// +use super::face::FaceState; +pub use super::pubsub::*; +pub use super::queries::*; +pub use super::resource::*; +use crate::net::routing::hat; +use crate::net::routing::hat::HatTrait; +use crate::net::routing::interceptor::interceptor_factories; +use crate::net::routing::interceptor::InterceptorFactory; +use std::any::Any; +use std::collections::HashMap; +use std::sync::{Arc, Weak}; +use std::sync::{Mutex, RwLock}; +use uhlc::HLC; +use zenoh_config::unwrap_or_default; +use zenoh_config::Config; +use zenoh_protocol::core::{ExprId, WhatAmI, ZenohId}; +use zenoh_protocol::network::Mapping; +// use zenoh_collections::Timer; +use zenoh_sync::get_mut_unchecked; + +pub(crate) struct RoutingExpr<'a> { + pub(crate) prefix: &'a Arc, + pub(crate) suffix: &'a str, + full: Option, +} + +impl<'a> RoutingExpr<'a> { + #[inline] + pub(crate) fn new(prefix: &'a Arc, suffix: &'a str) -> Self { + RoutingExpr { + prefix, + suffix, + full: None, + } + } + + #[inline] + pub(crate) fn full_expr(&mut self) -> &str { + if self.full.is_none() { + self.full = Some(self.prefix.expr() + self.suffix); + } + self.full.as_ref().unwrap() + } +} + +pub struct Tables { + pub(crate) zid: ZenohId, + pub(crate) whatami: WhatAmI, + pub(crate) face_counter: usize, + #[allow(dead_code)] + pub(crate) hlc: Option>, + pub(crate) drop_future_timestamp: bool, + // pub(crate) timer: Timer, + // pub(crate) queries_default_timeout: Duration, + pub(crate) root_res: Arc, + pub(crate) faces: HashMap>, + pub(crate) mcast_groups: Vec>, + pub(crate) mcast_faces: Vec>, + pub(crate) interceptors: Vec, + pub(crate) pull_caches_lock: Mutex<()>, + pub(crate) hat: Box, + pub(crate) hat_code: Arc, // @TODO make this a Box +} + +impl Tables { + pub fn new(zid: ZenohId, whatami: WhatAmI, hlc: Option>, config: &Config) -> Self { + let drop_future_timestamp = + unwrap_or_default!(config.timestamping().drop_future_timestamp()); + let router_peers_failover_brokering = + unwrap_or_default!(config.routing().router().peers_failover_brokering()); + // let queries_default_timeout = + // Duration::from_millis(unwrap_or_default!(config.queries_default_timeout())); + let hat_code = hat::new_hat(whatami, config); + Tables { + zid, + whatami, + face_counter: 0, + hlc, + drop_future_timestamp, + // timer: Timer::new(true), + // queries_default_timeout, + root_res: Resource::root(), + faces: HashMap::new(), + mcast_groups: vec![], + mcast_faces: vec![], + interceptors: interceptor_factories(config), + pull_caches_lock: Mutex::new(()), + hat: hat_code.new_tables(router_peers_failover_brokering), + hat_code: hat_code.into(), + } + } + + #[doc(hidden)] + pub fn _get_root(&self) -> &Arc { + &self.root_res + } + + #[cfg(test)] + pub fn print(&self) -> String { + Resource::print_tree(&self.root_res) + } + + #[inline] + pub(crate) fn get_mapping<'a>( + &'a self, + face: &'a FaceState, + expr_id: &ExprId, + mapping: Mapping, + ) -> Option<&'a Arc> { + match expr_id { + 0 => Some(&self.root_res), + expr_id => face.get_mapping(expr_id, mapping), + } + } + + #[inline] + pub(crate) fn get_sent_mapping<'a>( + &'a self, + face: &'a FaceState, + expr_id: &ExprId, + mapping: Mapping, + ) -> Option<&'a Arc> { + match expr_id { + 0 => Some(&self.root_res), + expr_id => face.get_sent_mapping(expr_id, mapping), + } + } + + #[inline] + pub(crate) fn get_face(&self, zid: &ZenohId) -> Option<&Arc> { + self.faces.values().find(|face| face.zid == *zid) + } + + fn update_routes(&mut self, res: &mut Arc) { + update_data_routes(self, res); + update_query_routes(self, res); + } + + pub(crate) fn update_matches_routes(&mut self, res: &mut Arc) { + if res.context.is_some() { + self.update_routes(res); + + let resclone = res.clone(); + for match_ in &mut get_mut_unchecked(res).context_mut().matches { + let match_ = &mut match_.upgrade().unwrap(); + if !Arc::ptr_eq(match_, &resclone) && match_.context.is_some() { + self.update_routes(match_); + } + } + } + } +} + +pub fn close_face(tables: &TablesLock, face: &Weak) { + match face.upgrade() { + Some(mut face) => { + log::debug!("Close {}", face); + finalize_pending_queries(tables, &mut face); + zlock!(tables.ctrl_lock).close_face(tables, &mut face); + } + None => log::error!("Face already closed!"), + } +} + +pub struct TablesLock { + pub tables: RwLock, + pub(crate) ctrl_lock: Mutex>, + pub queries_lock: RwLock<()>, +} diff --git a/zenoh/src/net/routing/hat/client/mod.rs b/zenoh/src/net/routing/hat/client/mod.rs new file mode 100644 index 0000000000..aa83c34f5d --- /dev/null +++ b/zenoh/src/net/routing/hat/client/mod.rs @@ -0,0 +1,319 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +//! ⚠️ WARNING ⚠️ +//! +//! This module is intended for Zenoh's internal use. +//! +//! [Click here for Zenoh's documentation](../zenoh/index.html) +use crate::{ + net::routing::{ + dispatcher::face::Face, + router::{ + compute_data_routes, compute_matching_pulls, compute_query_routes, RoutesIndexes, + }, + }, + runtime::Runtime, +}; + +use self::{ + pubsub::{pubsub_new_face, undeclare_client_subscription}, + queries::{queries_new_face, undeclare_client_queryable}, +}; +use super::{ + super::dispatcher::{ + face::FaceState, + tables::{NodeId, Resource, RoutingExpr, Tables, TablesLock}, + }, + HatBaseTrait, HatTrait, +}; +use std::{ + any::Any, + collections::{HashMap, HashSet}, + sync::Arc, +}; +use zenoh_config::WhatAmI; +use zenoh_protocol::network::declare::queryable::ext::QueryableInfo; +use zenoh_protocol::network::Oam; +use zenoh_result::ZResult; +use zenoh_sync::get_mut_unchecked; +use zenoh_transport::unicast::TransportUnicast; + +mod pubsub; +mod queries; + +macro_rules! face_hat { + ($f:expr) => { + $f.hat.downcast_ref::().unwrap() + }; +} +use face_hat; + +macro_rules! face_hat_mut { + ($f:expr) => { + get_mut_unchecked($f).hat.downcast_mut::().unwrap() + }; +} +use face_hat_mut; + +struct HatTables {} + +impl HatTables { + fn new() -> Self { + Self {} + } +} + +pub(crate) struct HatCode {} + +impl HatBaseTrait for HatCode { + fn init(&self, _tables: &mut Tables, _runtime: Runtime) {} + + fn new_tables(&self, _router_peers_failover_brokering: bool) -> Box { + Box::new(HatTables::new()) + } + + fn new_face(&self) -> Box { + Box::new(HatFace::new()) + } + + fn new_resource(&self) -> Box { + Box::new(HatContext::new()) + } + + fn new_local_face( + &self, + tables: &mut Tables, + _tables_ref: &Arc, + face: &mut Face, + ) -> ZResult<()> { + pubsub_new_face(tables, &mut face.state); + queries_new_face(tables, &mut face.state); + Ok(()) + } + + fn new_transport_unicast_face( + &self, + tables: &mut Tables, + _tables_ref: &Arc, + face: &mut Face, + _transport: &TransportUnicast, + ) -> ZResult<()> { + pubsub_new_face(tables, &mut face.state); + queries_new_face(tables, &mut face.state); + Ok(()) + } + + fn close_face(&self, tables: &TablesLock, face: &mut Arc) { + let mut wtables = zwrite!(tables.tables); + let mut face_clone = face.clone(); + let face = get_mut_unchecked(face); + for res in face.remote_mappings.values_mut() { + get_mut_unchecked(res).session_ctxs.remove(&face.id); + Resource::clean(res); + } + face.remote_mappings.clear(); + for res in face.local_mappings.values_mut() { + get_mut_unchecked(res).session_ctxs.remove(&face.id); + Resource::clean(res); + } + face.local_mappings.clear(); + + let mut subs_matches = vec![]; + for mut res in face + .hat + .downcast_mut::() + .unwrap() + .remote_subs + .drain() + { + get_mut_unchecked(&mut res).session_ctxs.remove(&face.id); + undeclare_client_subscription(&mut wtables, &mut face_clone, &mut res); + + if res.context.is_some() { + for match_ in &res.context().matches { + let mut match_ = match_.upgrade().unwrap(); + if !Arc::ptr_eq(&match_, &res) { + get_mut_unchecked(&mut match_) + .context_mut() + .disable_data_routes(); + subs_matches.push(match_); + } + } + get_mut_unchecked(&mut res) + .context_mut() + .disable_data_routes(); + subs_matches.push(res); + } + } + + let mut qabls_matches = vec![]; + for mut res in face + .hat + .downcast_mut::() + .unwrap() + .remote_qabls + .drain() + { + get_mut_unchecked(&mut res).session_ctxs.remove(&face.id); + undeclare_client_queryable(&mut wtables, &mut face_clone, &mut res); + + if res.context.is_some() { + for match_ in &res.context().matches { + let mut match_ = match_.upgrade().unwrap(); + if !Arc::ptr_eq(&match_, &res) { + get_mut_unchecked(&mut match_) + .context_mut() + .disable_query_routes(); + qabls_matches.push(match_); + } + } + get_mut_unchecked(&mut res) + .context_mut() + .disable_query_routes(); + qabls_matches.push(res); + } + } + drop(wtables); + + let mut matches_data_routes = vec![]; + let mut matches_query_routes = vec![]; + let rtables = zread!(tables.tables); + for _match in subs_matches.drain(..) { + let mut expr = RoutingExpr::new(&_match, ""); + matches_data_routes.push(( + _match.clone(), + compute_data_routes(&rtables, &mut expr), + compute_matching_pulls(&rtables, &mut expr), + )); + } + for _match in qabls_matches.drain(..) { + matches_query_routes.push((_match.clone(), compute_query_routes(&rtables, &_match))); + } + drop(rtables); + + let mut wtables = zwrite!(tables.tables); + for (mut res, data_routes, matching_pulls) in matches_data_routes { + get_mut_unchecked(&mut res) + .context_mut() + .update_data_routes(data_routes); + get_mut_unchecked(&mut res) + .context_mut() + .update_matching_pulls(matching_pulls); + Resource::clean(&mut res); + } + for (mut res, query_routes) in matches_query_routes { + get_mut_unchecked(&mut res) + .context_mut() + .update_query_routes(query_routes); + Resource::clean(&mut res); + } + wtables.faces.remove(&face.id); + drop(wtables); + } + + fn handle_oam( + &self, + _tables: &mut Tables, + _tables_ref: &Arc, + _oam: Oam, + _transport: &TransportUnicast, + ) -> ZResult<()> { + Ok(()) + } + + #[inline] + fn map_routing_context( + &self, + _tables: &Tables, + _face: &FaceState, + _routing_context: NodeId, + ) -> NodeId { + 0 + } + + fn closing( + &self, + _tables: &mut Tables, + _tables_ref: &Arc, + _transport: &TransportUnicast, + ) -> ZResult<()> { + Ok(()) + } + + fn as_any(&self) -> &dyn Any { + self + } + + #[inline] + fn ingress_filter(&self, _tables: &Tables, _face: &FaceState, _expr: &mut RoutingExpr) -> bool { + true + } + + #[inline] + fn egress_filter( + &self, + _tables: &Tables, + src_face: &FaceState, + out_face: &Arc, + _expr: &mut RoutingExpr, + ) -> bool { + src_face.id != out_face.id + && match (src_face.mcast_group.as_ref(), out_face.mcast_group.as_ref()) { + (Some(l), Some(r)) => l != r, + _ => true, + } + } + + fn info(&self, _tables: &Tables, _kind: WhatAmI) -> String { + "graph {}".to_string() + } +} + +struct HatContext {} + +impl HatContext { + fn new() -> Self { + Self {} + } +} + +struct HatFace { + local_subs: HashSet>, + remote_subs: HashSet>, + local_qabls: HashMap, QueryableInfo>, + remote_qabls: HashSet>, +} + +impl HatFace { + fn new() -> Self { + Self { + local_subs: HashSet::new(), + remote_subs: HashSet::new(), + local_qabls: HashMap::new(), + remote_qabls: HashSet::new(), + } + } +} + +impl HatTrait for HatCode {} + +#[inline] +fn get_routes_entries() -> RoutesIndexes { + RoutesIndexes { + routers: vec![0], + peers: vec![0], + clients: vec![0], + } +} diff --git a/zenoh/src/net/routing/hat/client/pubsub.rs b/zenoh/src/net/routing/hat/client/pubsub.rs new file mode 100644 index 0000000000..7becff4b4d --- /dev/null +++ b/zenoh/src/net/routing/hat/client/pubsub.rs @@ -0,0 +1,353 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// +use super::{face_hat, face_hat_mut, get_routes_entries}; +use super::{HatCode, HatFace}; +use crate::net::routing::dispatcher::face::FaceState; +use crate::net::routing::dispatcher::resource::{NodeId, Resource, SessionContext}; +use crate::net::routing::dispatcher::tables::Tables; +use crate::net::routing::dispatcher::tables::{Route, RoutingExpr}; +use crate::net::routing::hat::HatPubSubTrait; +use crate::net::routing::router::RoutesIndexes; +use crate::net::routing::{RoutingContext, PREFIX_LIVELINESS}; +use std::borrow::Cow; +use std::collections::{HashMap, HashSet}; +use std::sync::Arc; +use zenoh_protocol::core::key_expr::OwnedKeyExpr; +use zenoh_protocol::{ + core::{Reliability, WhatAmI}, + network::declare::{ + common::ext::WireExprType, ext, subscriber::ext::SubscriberInfo, Declare, DeclareBody, + DeclareSubscriber, Mode, UndeclareSubscriber, + }, +}; +use zenoh_sync::get_mut_unchecked; + +#[inline] +fn propagate_simple_subscription_to( + _tables: &mut Tables, + dst_face: &mut Arc, + res: &Arc, + sub_info: &SubscriberInfo, + src_face: &mut Arc, +) { + if (src_face.id != dst_face.id || res.expr().starts_with(PREFIX_LIVELINESS)) + && !face_hat!(dst_face).local_subs.contains(res) + && (src_face.whatami == WhatAmI::Client || dst_face.whatami == WhatAmI::Client) + { + face_hat_mut!(dst_face).local_subs.insert(res.clone()); + let key_expr = Resource::decl_key(res, dst_face); + dst_face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::DeclareSubscriber(DeclareSubscriber { + id: 0, // @TODO use proper SubscriberId (#703) + wire_expr: key_expr, + ext_info: *sub_info, + }), + }, + res.expr(), + )); + } +} + +fn propagate_simple_subscription( + tables: &mut Tables, + res: &Arc, + sub_info: &SubscriberInfo, + src_face: &mut Arc, +) { + for mut dst_face in tables + .faces + .values() + .cloned() + .collect::>>() + { + propagate_simple_subscription_to(tables, &mut dst_face, res, sub_info, src_face); + } +} + +fn register_client_subscription( + _tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + sub_info: &SubscriberInfo, +) { + // Register subscription + { + let res = get_mut_unchecked(res); + log::debug!("Register subscription {} for {}", res.expr(), face); + match res.session_ctxs.get_mut(&face.id) { + Some(ctx) => match &ctx.subs { + Some(info) => { + if Mode::Pull == info.mode { + get_mut_unchecked(ctx).subs = Some(*sub_info); + } + } + None => { + get_mut_unchecked(ctx).subs = Some(*sub_info); + } + }, + None => { + res.session_ctxs.insert( + face.id, + Arc::new(SessionContext { + face: face.clone(), + local_expr_id: None, + remote_expr_id: None, + subs: Some(*sub_info), + qabl: None, + last_values: HashMap::new(), + }), + ); + } + } + } + face_hat_mut!(face).remote_subs.insert(res.clone()); +} + +fn declare_client_subscription( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + sub_info: &SubscriberInfo, +) { + register_client_subscription(tables, face, res, sub_info); + let mut propa_sub_info = *sub_info; + propa_sub_info.mode = Mode::Push; + + propagate_simple_subscription(tables, res, &propa_sub_info, face); + // This introduced a buffer overflow on windows + // @TODO: Let's deactivate this on windows until Fixed + #[cfg(not(windows))] + for mcast_group in &tables.mcast_groups { + mcast_group + .primitives + .send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::DeclareSubscriber(DeclareSubscriber { + id: 0, // @TODO use proper SubscriberId (#703) + wire_expr: res.expr().into(), + ext_info: *sub_info, + }), + }, + res.expr(), + )) + } +} + +#[inline] +fn client_subs(res: &Arc) -> Vec> { + res.session_ctxs + .values() + .filter_map(|ctx| { + if ctx.subs.is_some() { + Some(ctx.face.clone()) + } else { + None + } + }) + .collect() +} + +fn propagate_forget_simple_subscription(tables: &mut Tables, res: &Arc) { + for face in tables.faces.values_mut() { + if face_hat!(face).local_subs.contains(res) { + let wire_expr = Resource::get_best_key(res, "", face.id); + face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::UndeclareSubscriber(UndeclareSubscriber { + id: 0, // @TODO use proper SubscriberId (#703) + ext_wire_expr: WireExprType { wire_expr }, + }), + }, + res.expr(), + )); + face_hat_mut!(face).local_subs.remove(res); + } + } +} + +pub(super) fn undeclare_client_subscription( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, +) { + log::debug!("Unregister client subscription {} for {}", res.expr(), face); + if let Some(ctx) = get_mut_unchecked(res).session_ctxs.get_mut(&face.id) { + get_mut_unchecked(ctx).subs = None; + } + face_hat_mut!(face).remote_subs.remove(res); + + let mut client_subs = client_subs(res); + if client_subs.is_empty() { + propagate_forget_simple_subscription(tables, res); + } + if client_subs.len() == 1 { + let face = &mut client_subs[0]; + if face_hat!(face).local_subs.contains(res) + && !(face.whatami == WhatAmI::Client && res.expr().starts_with(PREFIX_LIVELINESS)) + { + let wire_expr = Resource::get_best_key(res, "", face.id); + face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::UndeclareSubscriber(UndeclareSubscriber { + id: 0, // @TODO use proper SubscriberId (#703) + ext_wire_expr: WireExprType { wire_expr }, + }), + }, + res.expr(), + )); + + face_hat_mut!(face).local_subs.remove(res); + } + } +} +fn forget_client_subscription( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, +) { + undeclare_client_subscription(tables, face, res); +} + +pub(super) fn pubsub_new_face(tables: &mut Tables, face: &mut Arc) { + let sub_info = SubscriberInfo { + reliability: Reliability::Reliable, // @TODO compute proper reliability to propagate from reliability of known subscribers + mode: Mode::Push, + }; + for src_face in tables + .faces + .values() + .cloned() + .collect::>>() + { + for sub in &face_hat!(src_face).remote_subs { + propagate_simple_subscription_to(tables, face, sub, &sub_info, &mut src_face.clone()); + } + } +} + +impl HatPubSubTrait for HatCode { + fn declare_subscription( + &self, + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + sub_info: &SubscriberInfo, + _node_id: NodeId, + ) { + declare_client_subscription(tables, face, res, sub_info); + } + + fn undeclare_subscription( + &self, + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + _node_id: NodeId, + ) { + forget_client_subscription(tables, face, res); + } + + fn get_subscriptions(&self, tables: &Tables) -> Vec> { + let mut subs = HashSet::new(); + for src_face in tables.faces.values() { + for sub in &face_hat!(src_face).remote_subs { + subs.insert(sub.clone()); + } + } + Vec::from_iter(subs) + } + + fn compute_data_route( + &self, + tables: &Tables, + expr: &mut RoutingExpr, + source: NodeId, + source_type: WhatAmI, + ) -> Arc { + let mut route = HashMap::new(); + let key_expr = expr.full_expr(); + if key_expr.ends_with('/') { + return Arc::new(route); + } + log::trace!( + "compute_data_route({}, {:?}, {:?})", + key_expr, + source, + source_type + ); + let key_expr = match OwnedKeyExpr::try_from(key_expr) { + Ok(ke) => ke, + Err(e) => { + log::warn!("Invalid KE reached the system: {}", e); + return Arc::new(route); + } + }; + let res = Resource::get_resource(expr.prefix, expr.suffix); + let matches = res + .as_ref() + .and_then(|res| res.context.as_ref()) + .map(|ctx| Cow::from(&ctx.matches)) + .unwrap_or_else(|| Cow::from(Resource::get_matches(tables, &key_expr))); + + for mres in matches.iter() { + let mres = mres.upgrade().unwrap(); + + for (sid, context) in &mres.session_ctxs { + if let Some(subinfo) = &context.subs { + if match tables.whatami { + WhatAmI::Router => context.face.whatami != WhatAmI::Router, + _ => { + source_type == WhatAmI::Client + || context.face.whatami == WhatAmI::Client + } + } && subinfo.mode == Mode::Push + { + route.entry(*sid).or_insert_with(|| { + let key_expr = Resource::get_best_key(expr.prefix, expr.suffix, *sid); + (context.face.clone(), key_expr.to_owned(), NodeId::default()) + }); + } + } + } + } + for mcast_group in &tables.mcast_groups { + route.insert( + mcast_group.id, + ( + mcast_group.clone(), + expr.full_expr().to_string().into(), + NodeId::default(), + ), + ); + } + Arc::new(route) + } + + fn get_data_routes_entries(&self, _tables: &Tables) -> RoutesIndexes { + get_routes_entries() + } +} diff --git a/zenoh/src/net/routing/hat/client/queries.rs b/zenoh/src/net/routing/hat/client/queries.rs new file mode 100644 index 0000000000..35a10557dc --- /dev/null +++ b/zenoh/src/net/routing/hat/client/queries.rs @@ -0,0 +1,378 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// +use super::{face_hat, face_hat_mut, get_routes_entries}; +use super::{HatCode, HatFace}; +use crate::net::routing::dispatcher::face::FaceState; +use crate::net::routing::dispatcher::resource::{NodeId, Resource, SessionContext}; +use crate::net::routing::dispatcher::tables::Tables; +use crate::net::routing::dispatcher::tables::{QueryTargetQabl, QueryTargetQablSet, RoutingExpr}; +use crate::net::routing::hat::HatQueriesTrait; +use crate::net::routing::router::RoutesIndexes; +use crate::net::routing::{RoutingContext, PREFIX_LIVELINESS}; +use ordered_float::OrderedFloat; +use std::borrow::Cow; +use std::collections::{HashMap, HashSet}; +use std::sync::Arc; +use zenoh_buffers::ZBuf; +use zenoh_protocol::core::key_expr::include::{Includer, DEFAULT_INCLUDER}; +use zenoh_protocol::core::key_expr::OwnedKeyExpr; +use zenoh_protocol::{ + core::{WhatAmI, WireExpr}, + network::declare::{ + common::ext::WireExprType, ext, queryable::ext::QueryableInfo, Declare, DeclareBody, + DeclareQueryable, UndeclareQueryable, + }, +}; +use zenoh_sync::get_mut_unchecked; + +#[cfg(feature = "complete_n")] +#[inline] +fn merge_qabl_infos(mut this: QueryableInfo, info: &QueryableInfo) -> QueryableInfo { + this.complete += info.complete; + this.distance = std::cmp::min(this.distance, info.distance); + this +} + +#[cfg(not(feature = "complete_n"))] +#[inline] +fn merge_qabl_infos(mut this: QueryableInfo, info: &QueryableInfo) -> QueryableInfo { + this.complete = u8::from(this.complete != 0 || info.complete != 0); + this.distance = std::cmp::min(this.distance, info.distance); + this +} + +fn local_qabl_info(_tables: &Tables, res: &Arc, face: &Arc) -> QueryableInfo { + res.session_ctxs + .values() + .fold(None, |accu, ctx| { + if ctx.face.id != face.id { + if let Some(info) = ctx.qabl.as_ref() { + Some(match accu { + Some(accu) => merge_qabl_infos(accu, info), + None => *info, + }) + } else { + accu + } + } else { + accu + } + }) + .unwrap_or(QueryableInfo { + complete: 0, + distance: 0, + }) +} + +fn propagate_simple_queryable( + tables: &mut Tables, + res: &Arc, + src_face: Option<&mut Arc>, +) { + let faces = tables.faces.values().cloned(); + for mut dst_face in faces { + let info = local_qabl_info(tables, res, &dst_face); + let current_info = face_hat!(dst_face).local_qabls.get(res); + if (src_face.is_none() || src_face.as_ref().unwrap().id != dst_face.id) + && (current_info.is_none() || *current_info.unwrap() != info) + && (src_face.is_none() + || src_face.as_ref().unwrap().whatami == WhatAmI::Client + || dst_face.whatami == WhatAmI::Client) + { + face_hat_mut!(&mut dst_face) + .local_qabls + .insert(res.clone(), info); + let key_expr = Resource::decl_key(res, &mut dst_face); + dst_face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::DeclareQueryable(DeclareQueryable { + id: 0, // @TODO use proper QueryableId (#703) + wire_expr: key_expr, + ext_info: info, + }), + }, + res.expr(), + )); + } + } +} + +fn register_client_queryable( + _tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + qabl_info: &QueryableInfo, +) { + // Register queryable + { + let res = get_mut_unchecked(res); + log::debug!("Register queryable {} (face: {})", res.expr(), face,); + get_mut_unchecked(res.session_ctxs.entry(face.id).or_insert_with(|| { + Arc::new(SessionContext { + face: face.clone(), + local_expr_id: None, + remote_expr_id: None, + subs: None, + qabl: None, + last_values: HashMap::new(), + }) + })) + .qabl = Some(*qabl_info); + } + face_hat_mut!(face).remote_qabls.insert(res.clone()); +} + +fn declare_client_queryable( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + qabl_info: &QueryableInfo, +) { + register_client_queryable(tables, face, res, qabl_info); + propagate_simple_queryable(tables, res, Some(face)); +} + +#[inline] +fn client_qabls(res: &Arc) -> Vec> { + res.session_ctxs + .values() + .filter_map(|ctx| { + if ctx.qabl.is_some() { + Some(ctx.face.clone()) + } else { + None + } + }) + .collect() +} + +fn propagate_forget_simple_queryable(tables: &mut Tables, res: &mut Arc) { + for face in tables.faces.values_mut() { + if face_hat!(face).local_qabls.contains_key(res) { + let wire_expr = Resource::get_best_key(res, "", face.id); + face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::UndeclareQueryable(UndeclareQueryable { + id: 0, // @TODO use proper QueryableId (#703) + ext_wire_expr: WireExprType { wire_expr }, + }), + }, + res.expr(), + )); + + face_hat_mut!(face).local_qabls.remove(res); + } + } +} + +pub(super) fn undeclare_client_queryable( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, +) { + log::debug!("Unregister client queryable {} for {}", res.expr(), face); + if let Some(ctx) = get_mut_unchecked(res).session_ctxs.get_mut(&face.id) { + get_mut_unchecked(ctx).qabl = None; + if ctx.qabl.is_none() { + face_hat_mut!(face).remote_qabls.remove(res); + } + } + + let mut client_qabls = client_qabls(res); + if client_qabls.is_empty() { + propagate_forget_simple_queryable(tables, res); + } else { + propagate_simple_queryable(tables, res, None); + } + if client_qabls.len() == 1 { + let face = &mut client_qabls[0]; + if face_hat!(face).local_qabls.contains_key(res) { + let wire_expr = Resource::get_best_key(res, "", face.id); + face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::UndeclareQueryable(UndeclareQueryable { + id: 0, // @TODO use proper QueryableId (#703) + ext_wire_expr: WireExprType { wire_expr }, + }), + }, + res.expr(), + )); + + face_hat_mut!(face).local_qabls.remove(res); + } + } +} + +fn forget_client_queryable( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, +) { + undeclare_client_queryable(tables, face, res); +} + +pub(super) fn queries_new_face(tables: &mut Tables, _face: &mut Arc) { + for face in tables + .faces + .values() + .cloned() + .collect::>>() + { + for qabl in face_hat!(face).remote_qabls.iter() { + propagate_simple_queryable(tables, qabl, Some(&mut face.clone())); + } + } +} + +lazy_static::lazy_static! { + static ref EMPTY_ROUTE: Arc = Arc::new(Vec::new()); +} + +impl HatQueriesTrait for HatCode { + fn declare_queryable( + &self, + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + qabl_info: &QueryableInfo, + _node_id: NodeId, + ) { + declare_client_queryable(tables, face, res, qabl_info); + } + + fn undeclare_queryable( + &self, + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + _node_id: NodeId, + ) { + forget_client_queryable(tables, face, res); + } + + fn get_queryables(&self, tables: &Tables) -> Vec> { + let mut qabls = HashSet::new(); + for src_face in tables.faces.values() { + for qabl in &face_hat!(src_face).remote_qabls { + qabls.insert(qabl.clone()); + } + } + Vec::from_iter(qabls) + } + + fn compute_query_route( + &self, + tables: &Tables, + expr: &mut RoutingExpr, + source: NodeId, + source_type: WhatAmI, + ) -> Arc { + let mut route = QueryTargetQablSet::new(); + let key_expr = expr.full_expr(); + if key_expr.ends_with('/') { + return EMPTY_ROUTE.clone(); + } + log::trace!( + "compute_query_route({}, {:?}, {:?})", + key_expr, + source, + source_type + ); + let key_expr = match OwnedKeyExpr::try_from(key_expr) { + Ok(ke) => ke, + Err(e) => { + log::warn!("Invalid KE reached the system: {}", e); + return EMPTY_ROUTE.clone(); + } + }; + let res = Resource::get_resource(expr.prefix, expr.suffix); + let matches = res + .as_ref() + .and_then(|res| res.context.as_ref()) + .map(|ctx| Cow::from(&ctx.matches)) + .unwrap_or_else(|| Cow::from(Resource::get_matches(tables, &key_expr))); + + for mres in matches.iter() { + let mres = mres.upgrade().unwrap(); + let complete = DEFAULT_INCLUDER.includes(mres.expr().as_bytes(), key_expr.as_bytes()); + for (sid, context) in &mres.session_ctxs { + let key_expr = Resource::get_best_key(expr.prefix, expr.suffix, *sid); + if let Some(qabl_info) = context.qabl.as_ref() { + route.push(QueryTargetQabl { + direction: (context.face.clone(), key_expr.to_owned(), NodeId::default()), + complete: if complete { + qabl_info.complete as u64 + } else { + 0 + }, + distance: 0.5, + }); + } + } + } + route.sort_by_key(|qabl| OrderedFloat(qabl.distance)); + Arc::new(route) + } + + #[inline] + fn compute_local_replies( + &self, + tables: &Tables, + prefix: &Arc, + suffix: &str, + face: &Arc, + ) -> Vec<(WireExpr<'static>, ZBuf)> { + let mut result = vec![]; + // Only the first routing point in the query route + // should return the liveliness tokens + if face.whatami == WhatAmI::Client { + let key_expr = prefix.expr() + suffix; + let key_expr = match OwnedKeyExpr::try_from(key_expr) { + Ok(ke) => ke, + Err(e) => { + log::warn!("Invalid KE reached the system: {}", e); + return result; + } + }; + if key_expr.starts_with(PREFIX_LIVELINESS) { + let res = Resource::get_resource(prefix, suffix); + let matches = res + .as_ref() + .and_then(|res| res.context.as_ref()) + .map(|ctx| Cow::from(&ctx.matches)) + .unwrap_or_else(|| Cow::from(Resource::get_matches(tables, &key_expr))); + for mres in matches.iter() { + let mres = mres.upgrade().unwrap(); + if mres.session_ctxs.values().any(|ctx| ctx.subs.is_some()) { + result.push((Resource::get_best_key(&mres, "", face.id), ZBuf::default())); + } + } + } + } + result + } + + fn get_query_routes_entries(&self, _tables: &Tables) -> RoutesIndexes { + get_routes_entries() + } +} diff --git a/zenoh/src/net/routing/hat/linkstate_peer/mod.rs b/zenoh/src/net/routing/hat/linkstate_peer/mod.rs new file mode 100644 index 0000000000..a655d2f0a3 --- /dev/null +++ b/zenoh/src/net/routing/hat/linkstate_peer/mod.rs @@ -0,0 +1,536 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +//! ⚠️ WARNING ⚠️ +//! +//! This module is intended for Zenoh's internal use. +//! +//! [Click here for Zenoh's documentation](../zenoh/index.html) +use self::{ + network::Network, + pubsub::{pubsub_new_face, pubsub_remove_node, undeclare_client_subscription}, + queries::{queries_new_face, queries_remove_node, undeclare_client_queryable}, +}; +use super::{ + super::dispatcher::{ + face::FaceState, + tables::{NodeId, Resource, RoutingExpr, Tables, TablesLock}, + }, + HatBaseTrait, HatTrait, +}; +use crate::{ + net::{ + codec::Zenoh080Routing, + protocol::linkstate::LinkStateList, + routing::{ + dispatcher::face::Face, + hat::TREES_COMPUTATION_DELAY_MS, + router::{ + compute_data_routes, compute_matching_pulls, compute_query_routes, RoutesIndexes, + }, + }, + }, + runtime::Runtime, +}; +use async_std::task::JoinHandle; +use std::{ + any::Any, + collections::{HashMap, HashSet}, + sync::Arc, +}; +use zenoh_config::{unwrap_or_default, ModeDependent, WhatAmI, WhatAmIMatcher, ZenohId}; +use zenoh_protocol::{ + common::ZExtBody, + network::{declare::queryable::ext::QueryableInfo, oam::id::OAM_LINKSTATE, Oam}, +}; +use zenoh_result::ZResult; +use zenoh_sync::get_mut_unchecked; +use zenoh_transport::unicast::TransportUnicast; + +mod network; +mod pubsub; +mod queries; + +macro_rules! hat { + ($t:expr) => { + $t.hat.downcast_ref::().unwrap() + }; +} +use hat; + +macro_rules! hat_mut { + ($t:expr) => { + $t.hat.downcast_mut::().unwrap() + }; +} +use hat_mut; + +macro_rules! res_hat { + ($r:expr) => { + $r.context().hat.downcast_ref::().unwrap() + }; +} +use res_hat; + +macro_rules! res_hat_mut { + ($r:expr) => { + get_mut_unchecked($r) + .context_mut() + .hat + .downcast_mut::() + .unwrap() + }; +} +use res_hat_mut; + +macro_rules! face_hat { + ($f:expr) => { + $f.hat.downcast_ref::().unwrap() + }; +} +use face_hat; + +macro_rules! face_hat_mut { + ($f:expr) => { + get_mut_unchecked($f).hat.downcast_mut::().unwrap() + }; +} +use face_hat_mut; + +struct HatTables { + peer_subs: HashSet>, + peer_qabls: HashSet>, + peers_net: Option, + peers_trees_task: Option>, +} + +impl HatTables { + fn new() -> Self { + Self { + peer_subs: HashSet::new(), + peer_qabls: HashSet::new(), + peers_net: None, + peers_trees_task: None, + } + } + + fn schedule_compute_trees(&mut self, tables_ref: Arc) { + log::trace!("Schedule computations"); + if self.peers_trees_task.is_none() { + let task = Some(async_std::task::spawn(async move { + async_std::task::sleep(std::time::Duration::from_millis( + *TREES_COMPUTATION_DELAY_MS, + )) + .await; + let mut tables = zwrite!(tables_ref.tables); + + log::trace!("Compute trees"); + let new_childs = hat_mut!(tables).peers_net.as_mut().unwrap().compute_trees(); + + log::trace!("Compute routes"); + pubsub::pubsub_tree_change(&mut tables, &new_childs); + queries::queries_tree_change(&mut tables, &new_childs); + + log::trace!("Computations completed"); + hat_mut!(tables).peers_trees_task = None; + })); + self.peers_trees_task = task; + } + } +} + +pub(crate) struct HatCode {} + +impl HatBaseTrait for HatCode { + fn init(&self, tables: &mut Tables, runtime: Runtime) { + let config = runtime.config().lock(); + let whatami = tables.whatami; + let gossip = unwrap_or_default!(config.scouting().gossip().enabled()); + let gossip_multihop = unwrap_or_default!(config.scouting().gossip().multihop()); + let autoconnect = if gossip { + *unwrap_or_default!(config.scouting().gossip().autoconnect().get(whatami)) + } else { + WhatAmIMatcher::empty() + }; + + let peer_full_linkstate = whatami != WhatAmI::Client + && unwrap_or_default!(config.routing().peer().mode()) == *"linkstate"; + let router_peers_failover_brokering = + unwrap_or_default!(config.routing().router().peers_failover_brokering()); + drop(config); + + hat_mut!(tables).peers_net = Some(Network::new( + "[Peers network]".to_string(), + tables.zid, + runtime, + peer_full_linkstate, + router_peers_failover_brokering, + gossip, + gossip_multihop, + autoconnect, + )); + } + + fn new_tables(&self, _router_peers_failover_brokering: bool) -> Box { + Box::new(HatTables::new()) + } + + fn new_face(&self) -> Box { + Box::new(HatFace::new()) + } + + fn new_resource(&self) -> Box { + Box::new(HatContext::new()) + } + + fn new_local_face( + &self, + tables: &mut Tables, + _tables_ref: &Arc, + face: &mut Face, + ) -> ZResult<()> { + pubsub_new_face(tables, &mut face.state); + queries_new_face(tables, &mut face.state); + Ok(()) + } + + fn new_transport_unicast_face( + &self, + tables: &mut Tables, + tables_ref: &Arc, + face: &mut Face, + transport: &TransportUnicast, + ) -> ZResult<()> { + let link_id = if face.state.whatami != WhatAmI::Client { + if let Some(net) = hat_mut!(tables).peers_net.as_mut() { + net.add_link(transport.clone()) + } else { + 0 + } + } else { + 0 + }; + + face_hat_mut!(&mut face.state).link_id = link_id; + pubsub_new_face(tables, &mut face.state); + queries_new_face(tables, &mut face.state); + + if face.state.whatami != WhatAmI::Client { + hat_mut!(tables).schedule_compute_trees(tables_ref.clone()); + } + Ok(()) + } + + fn close_face(&self, tables: &TablesLock, face: &mut Arc) { + let mut wtables = zwrite!(tables.tables); + let mut face_clone = face.clone(); + let face = get_mut_unchecked(face); + for res in face.remote_mappings.values_mut() { + get_mut_unchecked(res).session_ctxs.remove(&face.id); + Resource::clean(res); + } + face.remote_mappings.clear(); + for res in face.local_mappings.values_mut() { + get_mut_unchecked(res).session_ctxs.remove(&face.id); + Resource::clean(res); + } + face.local_mappings.clear(); + + let mut subs_matches = vec![]; + for mut res in face + .hat + .downcast_mut::() + .unwrap() + .remote_subs + .drain() + { + get_mut_unchecked(&mut res).session_ctxs.remove(&face.id); + undeclare_client_subscription(&mut wtables, &mut face_clone, &mut res); + + if res.context.is_some() { + for match_ in &res.context().matches { + let mut match_ = match_.upgrade().unwrap(); + if !Arc::ptr_eq(&match_, &res) { + get_mut_unchecked(&mut match_) + .context_mut() + .disable_data_routes(); + subs_matches.push(match_); + } + } + get_mut_unchecked(&mut res) + .context_mut() + .disable_data_routes(); + subs_matches.push(res); + } + } + + let mut qabls_matches = vec![]; + for mut res in face + .hat + .downcast_mut::() + .unwrap() + .remote_qabls + .drain() + { + get_mut_unchecked(&mut res).session_ctxs.remove(&face.id); + undeclare_client_queryable(&mut wtables, &mut face_clone, &mut res); + + if res.context.is_some() { + for match_ in &res.context().matches { + let mut match_ = match_.upgrade().unwrap(); + if !Arc::ptr_eq(&match_, &res) { + get_mut_unchecked(&mut match_) + .context_mut() + .disable_query_routes(); + qabls_matches.push(match_); + } + } + get_mut_unchecked(&mut res) + .context_mut() + .disable_query_routes(); + qabls_matches.push(res); + } + } + drop(wtables); + + let mut matches_data_routes = vec![]; + let mut matches_query_routes = vec![]; + let rtables = zread!(tables.tables); + for _match in subs_matches.drain(..) { + let mut expr = RoutingExpr::new(&_match, ""); + matches_data_routes.push(( + _match.clone(), + compute_data_routes(&rtables, &mut expr), + compute_matching_pulls(&rtables, &mut expr), + )); + } + for _match in qabls_matches.drain(..) { + matches_query_routes.push((_match.clone(), compute_query_routes(&rtables, &_match))); + } + drop(rtables); + + let mut wtables = zwrite!(tables.tables); + for (mut res, data_routes, matching_pulls) in matches_data_routes { + get_mut_unchecked(&mut res) + .context_mut() + .update_data_routes(data_routes); + get_mut_unchecked(&mut res) + .context_mut() + .update_matching_pulls(matching_pulls); + Resource::clean(&mut res); + } + for (mut res, query_routes) in matches_query_routes { + get_mut_unchecked(&mut res) + .context_mut() + .update_query_routes(query_routes); + Resource::clean(&mut res); + } + wtables.faces.remove(&face.id); + drop(wtables); + } + + fn handle_oam( + &self, + tables: &mut Tables, + tables_ref: &Arc, + oam: Oam, + transport: &TransportUnicast, + ) -> ZResult<()> { + if oam.id == OAM_LINKSTATE { + if let ZExtBody::ZBuf(buf) = oam.body { + if let Ok(zid) = transport.get_zid() { + use zenoh_buffers::reader::HasReader; + use zenoh_codec::RCodec; + let codec = Zenoh080Routing::new(); + let mut reader = buf.reader(); + let list: LinkStateList = codec.read(&mut reader).unwrap(); + + let whatami = transport.get_whatami()?; + if whatami != WhatAmI::Client { + if let Some(net) = hat_mut!(tables).peers_net.as_mut() { + let changes = net.link_states(list.link_states, zid); + + for (_, removed_node) in changes.removed_nodes { + pubsub_remove_node(tables, &removed_node.zid); + queries_remove_node(tables, &removed_node.zid); + } + + hat_mut!(tables).schedule_compute_trees(tables_ref.clone()); + } + }; + } + } + } + + Ok(()) + } + + #[inline] + fn map_routing_context( + &self, + tables: &Tables, + face: &FaceState, + routing_context: NodeId, + ) -> NodeId { + hat!(tables) + .peers_net + .as_ref() + .unwrap() + .get_local_context(routing_context, face_hat!(face).link_id) + } + + fn closing( + &self, + tables: &mut Tables, + tables_ref: &Arc, + transport: &TransportUnicast, + ) -> ZResult<()> { + match (transport.get_zid(), transport.get_whatami()) { + (Ok(zid), Ok(whatami)) => { + if whatami != WhatAmI::Client { + for (_, removed_node) in hat_mut!(tables) + .peers_net + .as_mut() + .unwrap() + .remove_link(&zid) + { + pubsub_remove_node(tables, &removed_node.zid); + queries_remove_node(tables, &removed_node.zid); + } + + hat_mut!(tables).schedule_compute_trees(tables_ref.clone()); + }; + } + (_, _) => log::error!("Closed transport in session closing!"), + } + Ok(()) + } + + fn as_any(&self) -> &dyn Any { + self + } + + #[inline] + fn ingress_filter(&self, _tables: &Tables, _face: &FaceState, _expr: &mut RoutingExpr) -> bool { + true + } + + #[inline] + fn egress_filter( + &self, + _tables: &Tables, + src_face: &FaceState, + out_face: &Arc, + _expr: &mut RoutingExpr, + ) -> bool { + src_face.id != out_face.id + && match (src_face.mcast_group.as_ref(), out_face.mcast_group.as_ref()) { + (Some(l), Some(r)) => l != r, + _ => true, + } + } + + fn info(&self, tables: &Tables, kind: WhatAmI) -> String { + match kind { + WhatAmI::Peer => hat!(tables) + .peers_net + .as_ref() + .map(|net| net.dot()) + .unwrap_or_else(|| "graph {}".to_string()), + _ => "graph {}".to_string(), + } + } +} + +struct HatContext { + router_subs: HashSet, + peer_subs: HashSet, + peer_qabls: HashMap, +} + +impl HatContext { + fn new() -> Self { + Self { + router_subs: HashSet::new(), + peer_subs: HashSet::new(), + peer_qabls: HashMap::new(), + } + } +} + +struct HatFace { + link_id: usize, + local_subs: HashSet>, + remote_subs: HashSet>, + local_qabls: HashMap, QueryableInfo>, + remote_qabls: HashSet>, +} + +impl HatFace { + fn new() -> Self { + Self { + link_id: 0, + local_subs: HashSet::new(), + remote_subs: HashSet::new(), + local_qabls: HashMap::new(), + remote_qabls: HashSet::new(), + } + } +} + +fn get_peer(tables: &Tables, face: &Arc, nodeid: NodeId) -> Option { + match hat!(tables) + .peers_net + .as_ref() + .unwrap() + .get_link(face_hat!(face).link_id) + { + Some(link) => match link.get_zid(&(nodeid as u64)) { + Some(router) => Some(*router), + None => { + log::error!( + "Received peer declaration with unknown routing context id {}", + nodeid + ); + None + } + }, + None => { + log::error!( + "Could not find corresponding link in peers network for {}", + face + ); + None + } + } +} + +impl HatTrait for HatCode {} + +#[inline] +fn get_routes_entries(tables: &Tables) -> RoutesIndexes { + let indexes = hat!(tables) + .peers_net + .as_ref() + .unwrap() + .graph + .node_indices() + .map(|i| i.index() as NodeId) + .collect::>(); + RoutesIndexes { + routers: indexes.clone(), + peers: indexes, + clients: vec![0], + } +} diff --git a/zenoh/src/net/routing/hat/linkstate_peer/network.rs b/zenoh/src/net/routing/hat/linkstate_peer/network.rs new file mode 100644 index 0000000000..ac610a808b --- /dev/null +++ b/zenoh/src/net/routing/hat/linkstate_peer/network.rs @@ -0,0 +1,980 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// +use crate::net::codec::Zenoh080Routing; +use crate::net::protocol::linkstate::{LinkState, LinkStateList}; +use crate::net::routing::dispatcher::tables::NodeId; +use crate::net::runtime::Runtime; +use async_std::task; +use petgraph::graph::NodeIndex; +use petgraph::visit::{VisitMap, Visitable}; +use std::convert::TryInto; +use vec_map::VecMap; +use zenoh_buffers::writer::{DidntWrite, HasWriter}; +use zenoh_buffers::ZBuf; +use zenoh_codec::WCodec; +use zenoh_link::Locator; +use zenoh_protocol::common::ZExtBody; +use zenoh_protocol::core::{WhatAmI, WhatAmIMatcher, ZenohId}; +use zenoh_protocol::network::oam::id::OAM_LINKSTATE; +use zenoh_protocol::network::{oam, NetworkBody, NetworkMessage, Oam}; +use zenoh_transport::unicast::TransportUnicast; + +#[derive(Clone)] +struct Details { + zid: bool, + locators: bool, + links: bool, +} + +#[derive(Clone)] +pub(super) struct Node { + pub(super) zid: ZenohId, + pub(super) whatami: Option, + pub(super) locators: Option>, + pub(super) sn: u64, + pub(super) links: Vec, +} + +impl std::fmt::Debug for Node { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.zid) + } +} + +pub(super) struct Link { + pub(super) transport: TransportUnicast, + zid: ZenohId, + mappings: VecMap, + local_mappings: VecMap, +} + +impl Link { + fn new(transport: TransportUnicast) -> Self { + let zid = transport.get_zid().unwrap(); + Link { + transport, + zid, + mappings: VecMap::new(), + local_mappings: VecMap::new(), + } + } + + #[inline] + pub(super) fn set_zid_mapping(&mut self, psid: u64, zid: ZenohId) { + self.mappings.insert(psid.try_into().unwrap(), zid); + } + + #[inline] + pub(super) fn get_zid(&self, psid: &u64) -> Option<&ZenohId> { + self.mappings.get((*psid).try_into().unwrap()) + } + + #[inline] + pub(super) fn set_local_psid_mapping(&mut self, psid: u64, local_psid: u64) { + self.local_mappings + .insert(psid.try_into().unwrap(), local_psid); + } + + #[inline] + pub(super) fn get_local_psid(&self, psid: &u64) -> Option<&u64> { + self.local_mappings.get((*psid).try_into().unwrap()) + } +} + +pub(super) struct Changes { + pub(super) updated_nodes: Vec<(NodeIndex, Node)>, + pub(super) removed_nodes: Vec<(NodeIndex, Node)>, +} + +#[derive(Clone)] +pub(super) struct Tree { + pub(super) parent: Option, + pub(super) childs: Vec, + pub(super) directions: Vec>, +} + +pub(super) struct Network { + pub(super) name: String, + pub(super) full_linkstate: bool, + pub(super) router_peers_failover_brokering: bool, + pub(super) gossip: bool, + pub(super) gossip_multihop: bool, + pub(super) autoconnect: WhatAmIMatcher, + pub(super) idx: NodeIndex, + pub(super) links: VecMap, + pub(super) trees: Vec, + pub(super) distances: Vec, + pub(super) graph: petgraph::stable_graph::StableUnGraph, + pub(super) runtime: Runtime, +} + +impl Network { + #[allow(clippy::too_many_arguments)] + pub(super) fn new( + name: String, + zid: ZenohId, + runtime: Runtime, + full_linkstate: bool, + router_peers_failover_brokering: bool, + gossip: bool, + gossip_multihop: bool, + autoconnect: WhatAmIMatcher, + ) -> Self { + let mut graph = petgraph::stable_graph::StableGraph::default(); + log::debug!("{} Add node (self) {}", name, zid); + let idx = graph.add_node(Node { + zid, + whatami: Some(runtime.whatami()), + locators: None, + sn: 1, + links: vec![], + }); + Network { + name, + full_linkstate, + router_peers_failover_brokering, + gossip, + gossip_multihop, + autoconnect, + idx, + links: VecMap::new(), + trees: vec![Tree { + parent: None, + childs: vec![], + directions: vec![None], + }], + distances: vec![0.0], + graph, + runtime, + } + } + + pub(super) fn dot(&self) -> String { + std::format!( + "{:?}", + petgraph::dot::Dot::with_config(&self.graph, &[petgraph::dot::Config::EdgeNoLabel]) + ) + } + + #[inline] + pub(super) fn get_idx(&self, zid: &ZenohId) -> Option { + self.graph + .node_indices() + .find(|idx| self.graph[*idx].zid == *zid) + } + + #[inline] + pub(super) fn get_link(&self, id: usize) -> Option<&Link> { + self.links.get(id) + } + + #[inline] + pub(super) fn get_link_from_zid(&self, zid: &ZenohId) -> Option<&Link> { + self.links.values().find(|link| link.zid == *zid) + } + + #[inline] + pub(super) fn get_local_context(&self, context: NodeId, link_id: usize) -> NodeId { + match self.get_link(link_id) { + Some(link) => match link.get_local_psid(&(context as u64)) { + Some(psid) => (*psid).try_into().unwrap_or(0), + None => { + log::error!( + "Cannot find local psid for context {} on link {}", + context, + link_id + ); + 0 + } + }, + None => { + log::error!("Cannot find link {}", link_id); + 0 + } + } + } + + fn add_node(&mut self, node: Node) -> NodeIndex { + let zid = node.zid; + let idx = self.graph.add_node(node); + for link in self.links.values_mut() { + if let Some((psid, _)) = link.mappings.iter().find(|(_, p)| **p == zid) { + link.local_mappings.insert(psid, idx.index() as u64); + } + } + idx + } + + fn make_link_state(&self, idx: NodeIndex, details: Details) -> LinkState { + let links = if details.links { + self.graph[idx] + .links + .iter() + .filter_map(|zid| { + if let Some(idx2) = self.get_idx(zid) { + Some(idx2.index().try_into().unwrap()) + } else { + log::error!( + "{} Internal error building link state: cannot get index of {}", + self.name, + zid + ); + None + } + }) + .collect() + } else { + vec![] + }; + LinkState { + psid: idx.index().try_into().unwrap(), + sn: self.graph[idx].sn, + zid: if details.zid { + Some(self.graph[idx].zid) + } else { + None + }, + whatami: self.graph[idx].whatami, + locators: if details.locators { + if idx == self.idx { + Some(self.runtime.get_locators()) + } else { + self.graph[idx].locators.clone() + } + } else { + None + }, + links, + } + } + + fn make_msg(&self, idxs: Vec<(NodeIndex, Details)>) -> Result { + let mut link_states = vec![]; + for (idx, details) in idxs { + link_states.push(self.make_link_state(idx, details)); + } + let codec = Zenoh080Routing::new(); + let mut buf = ZBuf::empty(); + codec.write(&mut buf.writer(), &LinkStateList { link_states })?; + Ok(NetworkBody::OAM(Oam { + id: OAM_LINKSTATE, + body: ZExtBody::ZBuf(buf), + ext_qos: oam::ext::QoSType::oam_default(), + ext_tstamp: None, + }) + .into()) + } + + fn send_on_link(&self, idxs: Vec<(NodeIndex, Details)>, transport: &TransportUnicast) { + if let Ok(msg) = self.make_msg(idxs) { + log::trace!("{} Send to {:?} {:?}", self.name, transport.get_zid(), msg); + if let Err(e) = transport.schedule(msg) { + log::debug!("{} Error sending LinkStateList: {}", self.name, e); + } + } else { + log::error!("Failed to encode Linkstate message"); + } + } + + fn send_on_links

(&self, idxs: Vec<(NodeIndex, Details)>, mut parameters: P) + where + P: FnMut(&Link) -> bool, + { + if let Ok(msg) = self.make_msg(idxs) { + for link in self.links.values() { + if parameters(link) { + log::trace!("{} Send to {} {:?}", self.name, link.zid, msg); + if let Err(e) = link.transport.schedule(msg.clone()) { + log::debug!("{} Error sending LinkStateList: {}", self.name, e); + } + } + } + } else { + log::error!("Failed to encode Linkstate message"); + } + } + + // Indicates if locators should be included when propagating Linkstate message + // from the given node. + // Returns true if gossip is enabled and if multihop gossip is enabled or + // the node is one of self neighbours. + fn propagate_locators(&self, idx: NodeIndex) -> bool { + self.gossip + && (self.gossip_multihop + || idx == self.idx + || self.links.values().any(|link| { + self.graph + .node_weight(idx) + .map(|node| link.zid == node.zid) + .unwrap_or(true) + })) + } + + fn update_edge(&mut self, idx1: NodeIndex, idx2: NodeIndex) { + use std::hash::Hasher; + let mut hasher = std::collections::hash_map::DefaultHasher::default(); + if self.graph[idx1].zid > self.graph[idx2].zid { + hasher.write(&self.graph[idx2].zid.to_le_bytes()); + hasher.write(&self.graph[idx1].zid.to_le_bytes()); + } else { + hasher.write(&self.graph[idx1].zid.to_le_bytes()); + hasher.write(&self.graph[idx2].zid.to_le_bytes()); + } + let weight = 100.0 + ((hasher.finish() as u32) as f64) / u32::MAX as f64; + self.graph.update_edge(idx1, idx2, weight); + } + + pub(super) fn link_states(&mut self, link_states: Vec, src: ZenohId) -> Changes { + log::trace!("{} Received from {} raw: {:?}", self.name, src, link_states); + + let graph = &self.graph; + let links = &mut self.links; + + let src_link = match links.values_mut().find(|link| link.zid == src) { + Some(link) => link, + None => { + log::error!( + "{} Received LinkStateList from unknown link {}", + self.name, + src + ); + return Changes { + updated_nodes: vec![], + removed_nodes: vec![], + }; + } + }; + + // register psid<->zid mappings & apply mapping to nodes + let link_states = link_states + .into_iter() + .filter_map(|link_state| { + if let Some(zid) = link_state.zid { + src_link.set_zid_mapping(link_state.psid, zid); + if let Some(idx) = graph.node_indices().find(|idx| graph[*idx].zid == zid) { + src_link.set_local_psid_mapping(link_state.psid, idx.index() as u64); + } + Some(( + zid, + link_state.whatami.unwrap_or(WhatAmI::Router), + link_state.locators, + link_state.sn, + link_state.links, + )) + } else { + match src_link.get_zid(&link_state.psid) { + Some(zid) => Some(( + *zid, + link_state.whatami.unwrap_or(WhatAmI::Router), + link_state.locators, + link_state.sn, + link_state.links, + )), + None => { + log::error!( + "Received LinkState from {} with unknown node mapping {}", + src, + link_state.psid + ); + None + } + } + } + }) + .collect::>(); + + // apply psid<->zid mapping to links + let src_link = self.get_link_from_zid(&src).unwrap(); + let link_states = link_states + .into_iter() + .map(|(zid, wai, locs, sn, links)| { + let links: Vec = links + .iter() + .filter_map(|l| { + if let Some(zid) = src_link.get_zid(l) { + Some(*zid) + } else { + log::error!( + "{} Received LinkState from {} with unknown link mapping {}", + self.name, + src, + l + ); + None + } + }) + .collect(); + (zid, wai, locs, sn, links) + }) + .collect::>(); + + // log::trace!( + // "{} Received from {} mapped: {:?}", + // self.name, + // src, + // link_states + // ); + for link_state in &link_states { + log::trace!( + "{} Received from {} mapped: {:?}", + self.name, + src, + link_state + ); + } + + if !self.full_linkstate { + let mut changes = Changes { + updated_nodes: vec![], + removed_nodes: vec![], + }; + for (zid, whatami, locators, sn, links) in link_states.into_iter() { + let idx = match self.get_idx(&zid) { + None => { + let idx = self.add_node(Node { + zid, + whatami: Some(whatami), + locators: locators.clone(), + sn, + links, + }); + changes.updated_nodes.push((idx, self.graph[idx].clone())); + locators.is_some().then_some(idx) + } + Some(idx) => { + let node = &mut self.graph[idx]; + let oldsn = node.sn; + (oldsn < sn) + .then(|| { + node.sn = sn; + node.links = links.clone(); + changes.updated_nodes.push((idx, node.clone())); + (node.locators != locators && locators.is_some()).then(|| { + node.locators = locators.clone(); + idx + }) + }) + .flatten() + } + }; + + if self.gossip { + if let Some(idx) = idx { + if self.gossip_multihop || self.links.values().any(|link| link.zid == zid) { + self.send_on_links( + vec![( + idx, + Details { + zid: true, + locators: true, + links: false, + }, + )], + |link| link.zid != zid, + ); + } + + if !self.autoconnect.is_empty() { + // Connect discovered peers + if task::block_on(self.runtime.manager().get_transport_unicast(&zid)) + .is_none() + && self.autoconnect.matches(whatami) + { + if let Some(locators) = locators { + let runtime = self.runtime.clone(); + self.runtime.spawn(async move { + // random backoff + async_std::task::sleep(std::time::Duration::from_millis( + rand::random::() % 100, + )) + .await; + runtime.connect_peer(&zid, &locators).await; + }); + } + } + } + } + } + } + return changes; + } + + // Add nodes to graph & filter out up to date states + let mut link_states = link_states + .into_iter() + .filter_map( + |(zid, whatami, locators, sn, links)| match self.get_idx(&zid) { + Some(idx) => { + let node = &mut self.graph[idx]; + let oldsn = node.sn; + if oldsn < sn { + node.sn = sn; + node.links = links.clone(); + if locators.is_some() { + node.locators = locators; + } + if oldsn == 0 { + Some((links, idx, true)) + } else { + Some((links, idx, false)) + } + } else { + None + } + } + None => { + let node = Node { + zid, + whatami: Some(whatami), + locators, + sn, + links: links.clone(), + }; + log::debug!("{} Add node (state) {}", self.name, zid); + let idx = self.add_node(node); + Some((links, idx, true)) + } + }, + ) + .collect::, NodeIndex, bool)>>(); + + // Add/remove edges from graph + let mut reintroduced_nodes = vec![]; + for (links, idx1, _) in &link_states { + for link in links { + if let Some(idx2) = self.get_idx(link) { + if self.graph[idx2].links.contains(&self.graph[*idx1].zid) { + log::trace!( + "{} Update edge (state) {} {}", + self.name, + self.graph[*idx1].zid, + self.graph[idx2].zid + ); + self.update_edge(*idx1, idx2); + } + } else { + let node = Node { + zid: *link, + whatami: None, + locators: None, + sn: 0, + links: vec![], + }; + log::debug!("{} Add node (reintroduced) {}", self.name, link.clone()); + let idx = self.add_node(node); + reintroduced_nodes.push((vec![], idx, true)); + } + } + let mut edges = vec![]; + let mut neighbors = self.graph.neighbors_undirected(*idx1).detach(); + while let Some(edge) = neighbors.next(&self.graph) { + edges.push(edge); + } + for (eidx, idx2) in edges { + if !links.contains(&self.graph[idx2].zid) { + log::trace!( + "{} Remove edge (state) {} {}", + self.name, + self.graph[*idx1].zid, + self.graph[idx2].zid + ); + self.graph.remove_edge(eidx); + } + } + } + link_states.extend(reintroduced_nodes); + + let removed = self.remove_detached_nodes(); + let link_states = link_states + .into_iter() + .filter(|ls| !removed.iter().any(|(idx, _)| idx == &ls.1)) + .collect::, NodeIndex, bool)>>(); + + if !self.autoconnect.is_empty() { + // Connect discovered peers + for (_, idx, _) in &link_states { + let node = &self.graph[*idx]; + if let Some(whatami) = node.whatami { + if task::block_on(self.runtime.manager().get_transport_unicast(&node.zid)) + .is_none() + && self.autoconnect.matches(whatami) + { + if let Some(locators) = &node.locators { + let runtime = self.runtime.clone(); + let zid = node.zid; + let locators = locators.clone(); + self.runtime.spawn(async move { + // random backoff + async_std::task::sleep(std::time::Duration::from_millis( + rand::random::() % 100, + )) + .await; + runtime.connect_peer(&zid, &locators).await; + }); + } + } + } + } + } + + // Propagate link states + // Note: we need to send all states at once for each face + // to avoid premature node deletion on the other side + #[allow(clippy::type_complexity)] // This is only used here + if !link_states.is_empty() { + let (new_idxs, updated_idxs): ( + Vec<(Vec, NodeIndex, bool)>, + Vec<(Vec, NodeIndex, bool)>, + ) = link_states.into_iter().partition(|(_, _, new)| *new); + let new_idxs = new_idxs + .into_iter() + .map(|(_, idx1, _new_node)| { + ( + idx1, + Details { + zid: true, + locators: self.propagate_locators(idx1), + links: true, + }, + ) + }) + .collect::>(); + for link in self.links.values() { + if link.zid != src { + let updated_idxs: Vec<(NodeIndex, Details)> = updated_idxs + .clone() + .into_iter() + .filter_map(|(_, idx1, _)| { + if link.zid != self.graph[idx1].zid { + Some(( + idx1, + Details { + zid: false, + locators: self.propagate_locators(idx1), + links: true, + }, + )) + } else { + None + } + }) + .collect(); + if !new_idxs.is_empty() || !updated_idxs.is_empty() { + self.send_on_link( + [&new_idxs[..], &updated_idxs[..]].concat(), + &link.transport, + ); + } + } else if !new_idxs.is_empty() { + self.send_on_link(new_idxs.clone(), &link.transport); + } + } + } + Changes { + updated_nodes: vec![], + removed_nodes: removed, + } + } + + pub(super) fn add_link(&mut self, transport: TransportUnicast) -> usize { + let free_index = { + let mut i = 0; + while self.links.contains_key(i) { + i += 1; + } + i + }; + self.links.insert(free_index, Link::new(transport.clone())); + + let zid = transport.get_zid().unwrap(); + let whatami = transport.get_whatami().unwrap(); + + if self.full_linkstate || self.router_peers_failover_brokering { + let (idx, new) = match self.get_idx(&zid) { + Some(idx) => (idx, false), + None => { + log::debug!("{} Add node (link) {}", self.name, zid); + ( + self.add_node(Node { + zid, + whatami: Some(whatami), + locators: None, + sn: 0, + links: vec![], + }), + true, + ) + } + }; + if self.full_linkstate && self.graph[idx].links.contains(&self.graph[self.idx].zid) { + log::trace!("Update edge (link) {} {}", self.graph[self.idx].zid, zid); + self.update_edge(self.idx, idx); + } + self.graph[self.idx].links.push(zid); + self.graph[self.idx].sn += 1; + + // Send updated self linkstate on all existing links except new one + self.links + .values() + .filter(|link| { + link.zid != zid + && (self.full_linkstate + || link.transport.get_whatami().unwrap_or(WhatAmI::Peer) + == WhatAmI::Router) + }) + .for_each(|link| { + self.send_on_link( + if new || (!self.full_linkstate && !self.gossip_multihop) { + vec![ + ( + idx, + Details { + zid: true, + locators: false, + links: false, + }, + ), + ( + self.idx, + Details { + zid: false, + locators: self.propagate_locators(idx), + links: true, + }, + ), + ] + } else { + vec![( + self.idx, + Details { + zid: false, + locators: self.propagate_locators(idx), + links: true, + }, + )] + }, + &link.transport, + ) + }); + } + + // Send all nodes linkstate on new link + let idxs = self + .graph + .node_indices() + .filter_map(|idx| { + (self.full_linkstate + || self.gossip_multihop + || self.links.values().any(|link| link.zid == zid) + || (self.router_peers_failover_brokering + && idx == self.idx + && whatami == WhatAmI::Router)) + .then(|| { + ( + idx, + Details { + zid: true, + locators: self.propagate_locators(idx), + links: self.full_linkstate + || (self.router_peers_failover_brokering + && idx == self.idx + && whatami == WhatAmI::Router), + }, + ) + }) + }) + .collect(); + self.send_on_link(idxs, &transport); + free_index + } + + pub(super) fn remove_link(&mut self, zid: &ZenohId) -> Vec<(NodeIndex, Node)> { + log::trace!("{} remove_link {}", self.name, zid); + self.links.retain(|_, link| link.zid != *zid); + self.graph[self.idx].links.retain(|link| *link != *zid); + + if self.full_linkstate { + if let Some((edge, _)) = self + .get_idx(zid) + .and_then(|idx| self.graph.find_edge_undirected(self.idx, idx)) + { + self.graph.remove_edge(edge); + } + let removed = self.remove_detached_nodes(); + + self.graph[self.idx].sn += 1; + + self.send_on_links( + vec![( + self.idx, + Details { + zid: false, + locators: self.gossip, + links: true, + }, + )], + |_| true, + ); + + removed + } else { + if let Some(idx) = self.get_idx(zid) { + self.graph.remove_node(idx); + } + if self.router_peers_failover_brokering { + self.send_on_links( + vec![( + self.idx, + Details { + zid: false, + locators: self.gossip, + links: true, + }, + )], + |link| { + link.zid != *zid + && link.transport.get_whatami().unwrap_or(WhatAmI::Peer) + == WhatAmI::Router + }, + ); + } + vec![] + } + } + + fn remove_detached_nodes(&mut self) -> Vec<(NodeIndex, Node)> { + let mut dfs_stack = vec![self.idx]; + let mut visit_map = self.graph.visit_map(); + while let Some(node) = dfs_stack.pop() { + if visit_map.visit(node) { + for succzid in &self.graph[node].links { + if let Some(succ) = self.get_idx(succzid) { + if !visit_map.is_visited(&succ) { + dfs_stack.push(succ); + } + } + } + } + } + + let mut removed = vec![]; + for idx in self.graph.node_indices().collect::>() { + if !visit_map.is_visited(&idx) { + log::debug!("Remove node {}", &self.graph[idx].zid); + removed.push((idx, self.graph.remove_node(idx).unwrap())); + } + } + removed + } + + pub(super) fn compute_trees(&mut self) -> Vec> { + let indexes = self.graph.node_indices().collect::>(); + let max_idx = indexes.iter().max().unwrap(); + + let old_childs: Vec> = self.trees.iter().map(|t| t.childs.clone()).collect(); + + self.trees.clear(); + self.trees.resize_with(max_idx.index() + 1, || Tree { + parent: None, + childs: vec![], + directions: vec![], + }); + + for tree_root_idx in &indexes { + let paths = petgraph::algo::bellman_ford(&self.graph, *tree_root_idx).unwrap(); + + if tree_root_idx.index() == 0 { + self.distances = paths.distances; + } + + if log::log_enabled!(log::Level::Debug) { + let ps: Vec> = paths + .predecessors + .iter() + .enumerate() + .map(|(is, o)| { + o.map(|ip| { + format!( + "{} <- {}", + self.graph[ip].zid, + self.graph[NodeIndex::new(is)].zid + ) + }) + }) + .collect(); + log::debug!("Tree {} {:?}", self.graph[*tree_root_idx].zid, ps); + } + + self.trees[tree_root_idx.index()].parent = paths.predecessors[self.idx.index()]; + + for idx in &indexes { + if let Some(parent_idx) = paths.predecessors[idx.index()] { + if parent_idx == self.idx { + self.trees[tree_root_idx.index()].childs.push(*idx); + } + } + } + + self.trees[tree_root_idx.index()] + .directions + .resize_with(max_idx.index() + 1, || None); + let mut dfs = petgraph::algo::DfsSpace::new(&self.graph); + for destination in &indexes { + if self.idx != *destination + && petgraph::algo::has_path_connecting( + &self.graph, + self.idx, + *destination, + Some(&mut dfs), + ) + { + let mut direction = None; + let mut current = *destination; + while let Some(parent) = paths.predecessors[current.index()] { + if parent == self.idx { + direction = Some(current); + break; + } else { + current = parent; + } + } + + self.trees[tree_root_idx.index()].directions[destination.index()] = + match direction { + Some(direction) => Some(direction), + None => self.trees[tree_root_idx.index()].parent, + }; + } + } + } + + let mut new_childs = Vec::with_capacity(self.trees.len()); + new_childs.resize(self.trees.len(), vec![]); + + for i in 0..new_childs.len() { + new_childs[i] = if i < old_childs.len() { + self.trees[i] + .childs + .iter() + .filter(|idx| !old_childs[i].contains(idx)) + .cloned() + .collect() + } else { + self.trees[i].childs.clone() + }; + } + + new_childs + } +} diff --git a/zenoh/src/net/routing/hat/linkstate_peer/pubsub.rs b/zenoh/src/net/routing/hat/linkstate_peer/pubsub.rs new file mode 100644 index 0000000000..a83d72de20 --- /dev/null +++ b/zenoh/src/net/routing/hat/linkstate_peer/pubsub.rs @@ -0,0 +1,693 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// +use super::network::Network; +use super::{face_hat, face_hat_mut, get_routes_entries, hat, hat_mut, res_hat, res_hat_mut}; +use super::{get_peer, HatCode, HatContext, HatFace, HatTables}; +use crate::net::routing::dispatcher::face::FaceState; +use crate::net::routing::dispatcher::pubsub::*; +use crate::net::routing::dispatcher::resource::{NodeId, Resource, SessionContext}; +use crate::net::routing::dispatcher::tables::Tables; +use crate::net::routing::dispatcher::tables::{Route, RoutingExpr}; +use crate::net::routing::hat::HatPubSubTrait; +use crate::net::routing::router::RoutesIndexes; +use crate::net::routing::{RoutingContext, PREFIX_LIVELINESS}; +use petgraph::graph::NodeIndex; +use std::borrow::Cow; +use std::collections::{HashMap, HashSet}; +use std::sync::Arc; +use zenoh_protocol::core::key_expr::OwnedKeyExpr; +use zenoh_protocol::{ + core::{Reliability, WhatAmI, ZenohId}, + network::declare::{ + common::ext::WireExprType, ext, subscriber::ext::SubscriberInfo, Declare, DeclareBody, + DeclareSubscriber, Mode, UndeclareSubscriber, + }, +}; +use zenoh_sync::get_mut_unchecked; + +#[inline] +fn send_sourced_subscription_to_net_childs( + tables: &Tables, + net: &Network, + childs: &[NodeIndex], + res: &Arc, + src_face: Option<&Arc>, + sub_info: &SubscriberInfo, + routing_context: NodeId, +) { + for child in childs { + if net.graph.contains_node(*child) { + match tables.get_face(&net.graph[*child].zid).cloned() { + Some(mut someface) => { + if src_face.is_none() || someface.id != src_face.unwrap().id { + let key_expr = Resource::decl_key(res, &mut someface); + + log::debug!("Send subscription {} on {}", res.expr(), someface); + + someface.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType { + node_id: routing_context, + }, + body: DeclareBody::DeclareSubscriber(DeclareSubscriber { + id: 0, // TODO + wire_expr: key_expr, + ext_info: *sub_info, + }), + }, + res.expr(), + )); + } + } + None => log::trace!("Unable to find face for zid {}", net.graph[*child].zid), + } + } + } +} + +#[inline] +fn propagate_simple_subscription_to( + _tables: &mut Tables, + dst_face: &mut Arc, + res: &Arc, + sub_info: &SubscriberInfo, + src_face: &mut Arc, +) { + if (src_face.id != dst_face.id || res.expr().starts_with(PREFIX_LIVELINESS)) + && !face_hat!(dst_face).local_subs.contains(res) + && dst_face.whatami == WhatAmI::Client + { + face_hat_mut!(dst_face).local_subs.insert(res.clone()); + let key_expr = Resource::decl_key(res, dst_face); + dst_face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::DeclareSubscriber(DeclareSubscriber { + id: 0, // TODO + wire_expr: key_expr, + ext_info: *sub_info, + }), + }, + res.expr(), + )); + } +} + +fn propagate_simple_subscription( + tables: &mut Tables, + res: &Arc, + sub_info: &SubscriberInfo, + src_face: &mut Arc, +) { + for mut dst_face in tables + .faces + .values() + .cloned() + .collect::>>() + { + propagate_simple_subscription_to(tables, &mut dst_face, res, sub_info, src_face); + } +} + +fn propagate_sourced_subscription( + tables: &Tables, + res: &Arc, + sub_info: &SubscriberInfo, + src_face: Option<&Arc>, + source: &ZenohId, +) { + let net = hat!(tables).peers_net.as_ref().unwrap(); + match net.get_idx(source) { + Some(tree_sid) => { + if net.trees.len() > tree_sid.index() { + send_sourced_subscription_to_net_childs( + tables, + net, + &net.trees[tree_sid.index()].childs, + res, + src_face, + sub_info, + tree_sid.index() as NodeId, + ); + } else { + log::trace!( + "Propagating sub {}: tree for node {} sid:{} not yet ready", + res.expr(), + tree_sid.index(), + source + ); + } + } + None => log::error!( + "Error propagating sub {}: cannot get index of {}!", + res.expr(), + source + ), + } +} + +fn register_peer_subscription( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + sub_info: &SubscriberInfo, + peer: ZenohId, +) { + if !res_hat!(res).peer_subs.contains(&peer) { + // Register peer subscription + { + log::debug!("Register peer subscription {} (peer: {})", res.expr(), peer); + res_hat_mut!(res).peer_subs.insert(peer); + hat_mut!(tables).peer_subs.insert(res.clone()); + } + + // Propagate subscription to peers + propagate_sourced_subscription(tables, res, sub_info, Some(face), &peer); + } + + if tables.whatami == WhatAmI::Peer { + // Propagate subscription to clients + propagate_simple_subscription(tables, res, sub_info, face); + } +} + +fn declare_peer_subscription( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + sub_info: &SubscriberInfo, + peer: ZenohId, +) { + register_peer_subscription(tables, face, res, sub_info, peer); +} + +fn register_client_subscription( + _tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + sub_info: &SubscriberInfo, +) { + // Register subscription + { + let res = get_mut_unchecked(res); + log::debug!("Register subscription {} for {}", res.expr(), face); + match res.session_ctxs.get_mut(&face.id) { + Some(ctx) => match &ctx.subs { + Some(info) => { + if Mode::Pull == info.mode { + get_mut_unchecked(ctx).subs = Some(*sub_info); + } + } + None => { + get_mut_unchecked(ctx).subs = Some(*sub_info); + } + }, + None => { + res.session_ctxs.insert( + face.id, + Arc::new(SessionContext { + face: face.clone(), + local_expr_id: None, + remote_expr_id: None, + subs: Some(*sub_info), + qabl: None, + last_values: HashMap::new(), + }), + ); + } + } + } + face_hat_mut!(face).remote_subs.insert(res.clone()); +} + +fn declare_client_subscription( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + sub_info: &SubscriberInfo, +) { + register_client_subscription(tables, face, res, sub_info); + let mut propa_sub_info = *sub_info; + propa_sub_info.mode = Mode::Push; + let zid = tables.zid; + register_peer_subscription(tables, face, res, &propa_sub_info, zid); +} + +#[inline] +fn remote_peer_subs(tables: &Tables, res: &Arc) -> bool { + res.context.is_some() + && res_hat!(res) + .peer_subs + .iter() + .any(|peer| peer != &tables.zid) +} + +#[inline] +fn client_subs(res: &Arc) -> Vec> { + res.session_ctxs + .values() + .filter_map(|ctx| { + if ctx.subs.is_some() { + Some(ctx.face.clone()) + } else { + None + } + }) + .collect() +} + +#[inline] +fn send_forget_sourced_subscription_to_net_childs( + tables: &Tables, + net: &Network, + childs: &[NodeIndex], + res: &Arc, + src_face: Option<&Arc>, + routing_context: Option, +) { + for child in childs { + if net.graph.contains_node(*child) { + match tables.get_face(&net.graph[*child].zid).cloned() { + Some(mut someface) => { + if src_face.is_none() || someface.id != src_face.unwrap().id { + let wire_expr = Resource::decl_key(res, &mut someface); + + log::debug!("Send forget subscription {} on {}", res.expr(), someface); + + someface.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType { + node_id: routing_context.unwrap_or(0), + }, + body: DeclareBody::UndeclareSubscriber(UndeclareSubscriber { + id: 0, // TODO + ext_wire_expr: WireExprType { wire_expr }, + }), + }, + res.expr(), + )); + } + } + None => log::trace!("Unable to find face for zid {}", net.graph[*child].zid), + } + } + } +} + +fn propagate_forget_simple_subscription(tables: &mut Tables, res: &Arc) { + for face in tables.faces.values_mut() { + if face_hat!(face).local_subs.contains(res) { + let wire_expr = Resource::get_best_key(res, "", face.id); + face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::UndeclareSubscriber(UndeclareSubscriber { + id: 0, // TODO + ext_wire_expr: WireExprType { wire_expr }, + }), + }, + res.expr(), + )); + face_hat_mut!(face).local_subs.remove(res); + } + } +} + +fn propagate_forget_sourced_subscription( + tables: &Tables, + res: &Arc, + src_face: Option<&Arc>, + source: &ZenohId, +) { + let net = hat!(tables).peers_net.as_ref().unwrap(); + match net.get_idx(source) { + Some(tree_sid) => { + if net.trees.len() > tree_sid.index() { + send_forget_sourced_subscription_to_net_childs( + tables, + net, + &net.trees[tree_sid.index()].childs, + res, + src_face, + Some(tree_sid.index() as NodeId), + ); + } else { + log::trace!( + "Propagating forget sub {}: tree for node {} sid:{} not yet ready", + res.expr(), + tree_sid.index(), + source + ); + } + } + None => log::error!( + "Error propagating forget sub {}: cannot get index of {}!", + res.expr(), + source + ), + } +} + +fn unregister_peer_subscription(tables: &mut Tables, res: &mut Arc, peer: &ZenohId) { + log::debug!( + "Unregister peer subscription {} (peer: {})", + res.expr(), + peer + ); + res_hat_mut!(res).peer_subs.retain(|sub| sub != peer); + + if res_hat!(res).peer_subs.is_empty() { + hat_mut!(tables) + .peer_subs + .retain(|sub| !Arc::ptr_eq(sub, res)); + + if tables.whatami == WhatAmI::Peer { + propagate_forget_simple_subscription(tables, res); + } + } +} + +fn undeclare_peer_subscription( + tables: &mut Tables, + face: Option<&Arc>, + res: &mut Arc, + peer: &ZenohId, +) { + if res_hat!(res).peer_subs.contains(peer) { + unregister_peer_subscription(tables, res, peer); + propagate_forget_sourced_subscription(tables, res, face, peer); + } +} + +fn forget_peer_subscription( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + peer: &ZenohId, +) { + undeclare_peer_subscription(tables, Some(face), res, peer); +} + +pub(super) fn undeclare_client_subscription( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, +) { + log::debug!("Unregister client subscription {} for {}", res.expr(), face); + if let Some(ctx) = get_mut_unchecked(res).session_ctxs.get_mut(&face.id) { + get_mut_unchecked(ctx).subs = None; + } + face_hat_mut!(face).remote_subs.remove(res); + + let mut client_subs = client_subs(res); + let peer_subs = remote_peer_subs(tables, res); + if client_subs.is_empty() { + undeclare_peer_subscription(tables, None, res, &tables.zid.clone()); + } + if client_subs.len() == 1 && !peer_subs { + let face = &mut client_subs[0]; + if face_hat!(face).local_subs.contains(res) + && !(face.whatami == WhatAmI::Client && res.expr().starts_with(PREFIX_LIVELINESS)) + { + let wire_expr = Resource::get_best_key(res, "", face.id); + face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::UndeclareSubscriber(UndeclareSubscriber { + id: 0, // TODO + ext_wire_expr: WireExprType { wire_expr }, + }), + }, + res.expr(), + )); + + face_hat_mut!(face).local_subs.remove(res); + } + } +} + +fn forget_client_subscription( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, +) { + undeclare_client_subscription(tables, face, res); +} + +pub(super) fn pubsub_new_face(tables: &mut Tables, face: &mut Arc) { + let sub_info = SubscriberInfo { + reliability: Reliability::Reliable, // @TODO + mode: Mode::Push, + }; + + if face.whatami == WhatAmI::Client { + for sub in &hat!(tables).peer_subs { + face_hat_mut!(face).local_subs.insert(sub.clone()); + let key_expr = Resource::decl_key(sub, face); + face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::DeclareSubscriber(DeclareSubscriber { + id: 0, // TODO + wire_expr: key_expr, + ext_info: sub_info, + }), + }, + sub.expr(), + )); + } + } +} + +pub(super) fn pubsub_remove_node(tables: &mut Tables, node: &ZenohId) { + for mut res in hat!(tables) + .peer_subs + .iter() + .filter(|res| res_hat!(res).peer_subs.contains(node)) + .cloned() + .collect::>>() + { + unregister_peer_subscription(tables, &mut res, node); + + update_matches_data_routes(tables, &mut res); + Resource::clean(&mut res) + } +} + +pub(super) fn pubsub_tree_change(tables: &mut Tables, new_childs: &[Vec]) { + // propagate subs to new childs + for (tree_sid, tree_childs) in new_childs.iter().enumerate() { + if !tree_childs.is_empty() { + let net = hat!(tables).peers_net.as_ref().unwrap(); + let tree_idx = NodeIndex::new(tree_sid); + if net.graph.contains_node(tree_idx) { + let tree_id = net.graph[tree_idx].zid; + + let subs_res = &hat!(tables).peer_subs; + + for res in subs_res { + let subs = &res_hat!(res).peer_subs; + for sub in subs { + if *sub == tree_id { + let sub_info = SubscriberInfo { + reliability: Reliability::Reliable, // @TODO + mode: Mode::Push, + }; + send_sourced_subscription_to_net_childs( + tables, + net, + tree_childs, + res, + None, + &sub_info, + tree_sid as NodeId, + ); + } + } + } + } + } + } + + // recompute routes + update_data_routes_from(tables, &mut tables.root_res.clone()); +} + +#[inline] +fn insert_faces_for_subs( + route: &mut Route, + expr: &RoutingExpr, + tables: &Tables, + net: &Network, + source: NodeId, + subs: &HashSet, +) { + if net.trees.len() > source as usize { + for sub in subs { + if let Some(sub_idx) = net.get_idx(sub) { + if net.trees[source as usize].directions.len() > sub_idx.index() { + if let Some(direction) = net.trees[source as usize].directions[sub_idx.index()] + { + if net.graph.contains_node(direction) { + if let Some(face) = tables.get_face(&net.graph[direction].zid) { + route.entry(face.id).or_insert_with(|| { + let key_expr = + Resource::get_best_key(expr.prefix, expr.suffix, face.id); + (face.clone(), key_expr.to_owned(), source) + }); + } + } + } + } + } + } + } else { + log::trace!("Tree for node sid:{} not yet ready", source); + } +} + +impl HatPubSubTrait for HatCode { + fn declare_subscription( + &self, + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + sub_info: &SubscriberInfo, + node_id: NodeId, + ) { + if face.whatami != WhatAmI::Client { + if let Some(peer) = get_peer(tables, face, node_id) { + declare_peer_subscription(tables, face, res, sub_info, peer) + } + } else { + declare_client_subscription(tables, face, res, sub_info) + } + } + + fn undeclare_subscription( + &self, + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + node_id: NodeId, + ) { + if face.whatami != WhatAmI::Client { + if let Some(peer) = get_peer(tables, face, node_id) { + forget_peer_subscription(tables, face, res, &peer); + } + } else { + forget_client_subscription(tables, face, res); + } + } + + fn get_subscriptions(&self, tables: &Tables) -> Vec> { + hat!(tables).peer_subs.iter().cloned().collect() + } + + fn compute_data_route( + &self, + tables: &Tables, + expr: &mut RoutingExpr, + source: NodeId, + source_type: WhatAmI, + ) -> Arc { + let mut route = HashMap::new(); + let key_expr = expr.full_expr(); + if key_expr.ends_with('/') { + return Arc::new(route); + } + log::trace!( + "compute_data_route({}, {:?}, {:?})", + key_expr, + source, + source_type + ); + let key_expr = match OwnedKeyExpr::try_from(key_expr) { + Ok(ke) => ke, + Err(e) => { + log::warn!("Invalid KE reached the system: {}", e); + return Arc::new(route); + } + }; + let res = Resource::get_resource(expr.prefix, expr.suffix); + let matches = res + .as_ref() + .and_then(|res| res.context.as_ref()) + .map(|ctx| Cow::from(&ctx.matches)) + .unwrap_or_else(|| Cow::from(Resource::get_matches(tables, &key_expr))); + + for mres in matches.iter() { + let mres = mres.upgrade().unwrap(); + + let net = hat!(tables).peers_net.as_ref().unwrap(); + let peer_source = match source_type { + WhatAmI::Router | WhatAmI::Peer => source, + _ => net.idx.index() as NodeId, + }; + insert_faces_for_subs( + &mut route, + expr, + tables, + net, + peer_source, + &res_hat!(mres).peer_subs, + ); + + for (sid, context) in &mres.session_ctxs { + if let Some(subinfo) = &context.subs { + if match tables.whatami { + WhatAmI::Router => context.face.whatami != WhatAmI::Router, + _ => { + source_type == WhatAmI::Client + || context.face.whatami == WhatAmI::Client + } + } && subinfo.mode == Mode::Push + { + route.entry(*sid).or_insert_with(|| { + let key_expr = Resource::get_best_key(expr.prefix, expr.suffix, *sid); + (context.face.clone(), key_expr.to_owned(), NodeId::default()) + }); + } + } + } + } + for mcast_group in &tables.mcast_groups { + route.insert( + mcast_group.id, + ( + mcast_group.clone(), + expr.full_expr().to_string().into(), + NodeId::default(), + ), + ); + } + Arc::new(route) + } + + fn get_data_routes_entries(&self, tables: &Tables) -> RoutesIndexes { + get_routes_entries(tables) + } +} diff --git a/zenoh/src/net/routing/hat/linkstate_peer/queries.rs b/zenoh/src/net/routing/hat/linkstate_peer/queries.rs new file mode 100644 index 0000000000..6281993c93 --- /dev/null +++ b/zenoh/src/net/routing/hat/linkstate_peer/queries.rs @@ -0,0 +1,799 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// +use super::network::Network; +use super::{face_hat, face_hat_mut, get_routes_entries, hat, hat_mut, res_hat, res_hat_mut}; +use super::{get_peer, HatCode, HatContext, HatFace, HatTables}; +use crate::net::routing::dispatcher::face::FaceState; +use crate::net::routing::dispatcher::queries::*; +use crate::net::routing::dispatcher::resource::{NodeId, Resource, SessionContext}; +use crate::net::routing::dispatcher::tables::Tables; +use crate::net::routing::dispatcher::tables::{QueryTargetQabl, QueryTargetQablSet, RoutingExpr}; +use crate::net::routing::hat::HatQueriesTrait; +use crate::net::routing::router::RoutesIndexes; +use crate::net::routing::{RoutingContext, PREFIX_LIVELINESS}; +use ordered_float::OrderedFloat; +use petgraph::graph::NodeIndex; +use std::borrow::Cow; +use std::collections::HashMap; +use std::sync::Arc; +use zenoh_buffers::ZBuf; +use zenoh_protocol::core::key_expr::include::{Includer, DEFAULT_INCLUDER}; +use zenoh_protocol::core::key_expr::OwnedKeyExpr; +use zenoh_protocol::{ + core::{WhatAmI, WireExpr, ZenohId}, + network::declare::{ + common::ext::WireExprType, ext, queryable::ext::QueryableInfo, Declare, DeclareBody, + DeclareQueryable, UndeclareQueryable, + }, +}; +use zenoh_sync::get_mut_unchecked; + +#[cfg(feature = "complete_n")] +#[inline] +fn merge_qabl_infos(mut this: QueryableInfo, info: &QueryableInfo) -> QueryableInfo { + this.complete += info.complete; + this.distance = std::cmp::min(this.distance, info.distance); + this +} + +#[cfg(not(feature = "complete_n"))] +#[inline] +fn merge_qabl_infos(mut this: QueryableInfo, info: &QueryableInfo) -> QueryableInfo { + this.complete = u8::from(this.complete != 0 || info.complete != 0); + this.distance = std::cmp::min(this.distance, info.distance); + this +} + +fn local_peer_qabl_info(_tables: &Tables, res: &Arc) -> QueryableInfo { + res.session_ctxs + .values() + .fold(None, |accu, ctx| { + if let Some(info) = ctx.qabl.as_ref() { + Some(match accu { + Some(accu) => merge_qabl_infos(accu, info), + None => *info, + }) + } else { + accu + } + }) + .unwrap_or(QueryableInfo { + complete: 0, + distance: 0, + }) +} + +fn local_qabl_info(tables: &Tables, res: &Arc, face: &Arc) -> QueryableInfo { + let info = if res.context.is_some() { + res_hat!(res) + .peer_qabls + .iter() + .fold(None, |accu, (zid, info)| { + if *zid != tables.zid { + Some(match accu { + Some(accu) => merge_qabl_infos(accu, info), + None => *info, + }) + } else { + accu + } + }) + } else { + None + }; + res.session_ctxs + .values() + .fold(info, |accu, ctx| { + if ctx.face.id != face.id && ctx.face.whatami != WhatAmI::Peer + || face.whatami != WhatAmI::Peer + { + if let Some(info) = ctx.qabl.as_ref() { + Some(match accu { + Some(accu) => merge_qabl_infos(accu, info), + None => *info, + }) + } else { + accu + } + } else { + accu + } + }) + .unwrap_or(QueryableInfo { + complete: 0, + distance: 0, + }) +} + +#[inline] +fn send_sourced_queryable_to_net_childs( + tables: &Tables, + net: &Network, + childs: &[NodeIndex], + res: &Arc, + qabl_info: &QueryableInfo, + src_face: Option<&mut Arc>, + routing_context: NodeId, +) { + for child in childs { + if net.graph.contains_node(*child) { + match tables.get_face(&net.graph[*child].zid).cloned() { + Some(mut someface) => { + if src_face.is_none() || someface.id != src_face.as_ref().unwrap().id { + let key_expr = Resource::decl_key(res, &mut someface); + + log::debug!("Send queryable {} on {}", res.expr(), someface); + + someface.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType { + node_id: routing_context, + }, + body: DeclareBody::DeclareQueryable(DeclareQueryable { + id: 0, // @TODO use proper QueryableId (#703) + wire_expr: key_expr, + ext_info: *qabl_info, + }), + }, + res.expr(), + )); + } + } + None => log::trace!("Unable to find face for zid {}", net.graph[*child].zid), + } + } + } +} + +fn propagate_simple_queryable( + tables: &mut Tables, + res: &Arc, + src_face: Option<&mut Arc>, +) { + let faces = tables.faces.values().cloned(); + for mut dst_face in faces { + let info = local_qabl_info(tables, res, &dst_face); + let current_info = face_hat!(dst_face).local_qabls.get(res); + if (src_face.is_none() || src_face.as_ref().unwrap().id != dst_face.id) + && (current_info.is_none() || *current_info.unwrap() != info) + && dst_face.whatami == WhatAmI::Client + { + face_hat_mut!(&mut dst_face) + .local_qabls + .insert(res.clone(), info); + let key_expr = Resource::decl_key(res, &mut dst_face); + dst_face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::DeclareQueryable(DeclareQueryable { + id: 0, // @TODO use proper QueryableId (#703) + wire_expr: key_expr, + ext_info: info, + }), + }, + res.expr(), + )); + } + } +} + +fn propagate_sourced_queryable( + tables: &Tables, + res: &Arc, + qabl_info: &QueryableInfo, + src_face: Option<&mut Arc>, + source: &ZenohId, +) { + let net = hat!(tables).peers_net.as_ref().unwrap(); + match net.get_idx(source) { + Some(tree_sid) => { + if net.trees.len() > tree_sid.index() { + send_sourced_queryable_to_net_childs( + tables, + net, + &net.trees[tree_sid.index()].childs, + res, + qabl_info, + src_face, + tree_sid.index() as NodeId, + ); + } else { + log::trace!( + "Propagating qabl {}: tree for node {} sid:{} not yet ready", + res.expr(), + tree_sid.index(), + source + ); + } + } + None => log::error!( + "Error propagating qabl {}: cannot get index of {}!", + res.expr(), + source + ), + } +} + +fn register_peer_queryable( + tables: &mut Tables, + mut face: Option<&mut Arc>, + res: &mut Arc, + qabl_info: &QueryableInfo, + peer: ZenohId, +) { + let current_info = res_hat!(res).peer_qabls.get(&peer); + if current_info.is_none() || current_info.unwrap() != qabl_info { + // Register peer queryable + { + log::debug!("Register peer queryable {} (peer: {})", res.expr(), peer,); + res_hat_mut!(res).peer_qabls.insert(peer, *qabl_info); + hat_mut!(tables).peer_qabls.insert(res.clone()); + } + + // Propagate queryable to peers + propagate_sourced_queryable(tables, res, qabl_info, face.as_deref_mut(), &peer); + } + + if tables.whatami == WhatAmI::Peer { + // Propagate queryable to clients + propagate_simple_queryable(tables, res, face); + } +} + +fn declare_peer_queryable( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + qabl_info: &QueryableInfo, + peer: ZenohId, +) { + let face = Some(face); + register_peer_queryable(tables, face, res, qabl_info, peer); +} + +fn register_client_queryable( + _tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + qabl_info: &QueryableInfo, +) { + // Register queryable + { + let res = get_mut_unchecked(res); + log::debug!("Register queryable {} (face: {})", res.expr(), face,); + get_mut_unchecked(res.session_ctxs.entry(face.id).or_insert_with(|| { + Arc::new(SessionContext { + face: face.clone(), + local_expr_id: None, + remote_expr_id: None, + subs: None, + qabl: None, + last_values: HashMap::new(), + }) + })) + .qabl = Some(*qabl_info); + } + face_hat_mut!(face).remote_qabls.insert(res.clone()); +} + +fn declare_client_queryable( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + qabl_info: &QueryableInfo, +) { + register_client_queryable(tables, face, res, qabl_info); + + let local_details = local_peer_qabl_info(tables, res); + let zid = tables.zid; + register_peer_queryable(tables, Some(face), res, &local_details, zid); +} + +#[inline] +fn remote_peer_qabls(tables: &Tables, res: &Arc) -> bool { + res.context.is_some() + && res_hat!(res) + .peer_qabls + .keys() + .any(|peer| peer != &tables.zid) +} + +#[inline] +fn client_qabls(res: &Arc) -> Vec> { + res.session_ctxs + .values() + .filter_map(|ctx| { + if ctx.qabl.is_some() { + Some(ctx.face.clone()) + } else { + None + } + }) + .collect() +} + +#[inline] +fn send_forget_sourced_queryable_to_net_childs( + tables: &Tables, + net: &Network, + childs: &[NodeIndex], + res: &Arc, + src_face: Option<&Arc>, + routing_context: NodeId, +) { + for child in childs { + if net.graph.contains_node(*child) { + match tables.get_face(&net.graph[*child].zid).cloned() { + Some(mut someface) => { + if src_face.is_none() || someface.id != src_face.unwrap().id { + let wire_expr = Resource::decl_key(res, &mut someface); + + log::debug!("Send forget queryable {} on {}", res.expr(), someface); + + someface.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType { + node_id: routing_context, + }, + body: DeclareBody::UndeclareQueryable(UndeclareQueryable { + id: 0, // @TODO use proper QueryableId (#703) + ext_wire_expr: WireExprType { wire_expr }, + }), + }, + res.expr(), + )); + } + } + None => log::trace!("Unable to find face for zid {}", net.graph[*child].zid), + } + } + } +} + +fn propagate_forget_simple_queryable(tables: &mut Tables, res: &mut Arc) { + for face in tables.faces.values_mut() { + if face_hat!(face).local_qabls.contains_key(res) { + let wire_expr = Resource::get_best_key(res, "", face.id); + face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::UndeclareQueryable(UndeclareQueryable { + id: 0, // @TODO use proper QueryableId (#703) + ext_wire_expr: WireExprType { wire_expr }, + }), + }, + res.expr(), + )); + + face_hat_mut!(face).local_qabls.remove(res); + } + } +} + +fn propagate_forget_sourced_queryable( + tables: &mut Tables, + res: &mut Arc, + src_face: Option<&Arc>, + source: &ZenohId, +) { + let net = hat!(tables).peers_net.as_ref().unwrap(); + match net.get_idx(source) { + Some(tree_sid) => { + if net.trees.len() > tree_sid.index() { + send_forget_sourced_queryable_to_net_childs( + tables, + net, + &net.trees[tree_sid.index()].childs, + res, + src_face, + tree_sid.index() as NodeId, + ); + } else { + log::trace!( + "Propagating forget qabl {}: tree for node {} sid:{} not yet ready", + res.expr(), + tree_sid.index(), + source + ); + } + } + None => log::error!( + "Error propagating forget qabl {}: cannot get index of {}!", + res.expr(), + source + ), + } +} + +fn unregister_peer_queryable(tables: &mut Tables, res: &mut Arc, peer: &ZenohId) { + log::debug!("Unregister peer queryable {} (peer: {})", res.expr(), peer,); + res_hat_mut!(res).peer_qabls.remove(peer); + + if res_hat!(res).peer_qabls.is_empty() { + hat_mut!(tables) + .peer_qabls + .retain(|qabl| !Arc::ptr_eq(qabl, res)); + + if tables.whatami == WhatAmI::Peer { + propagate_forget_simple_queryable(tables, res); + } + } +} + +fn undeclare_peer_queryable( + tables: &mut Tables, + face: Option<&Arc>, + res: &mut Arc, + peer: &ZenohId, +) { + if res_hat!(res).peer_qabls.contains_key(peer) { + unregister_peer_queryable(tables, res, peer); + propagate_forget_sourced_queryable(tables, res, face, peer); + } +} + +fn forget_peer_queryable( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + peer: &ZenohId, +) { + undeclare_peer_queryable(tables, Some(face), res, peer); +} + +pub(super) fn undeclare_client_queryable( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, +) { + log::debug!("Unregister client queryable {} for {}", res.expr(), face); + if let Some(ctx) = get_mut_unchecked(res).session_ctxs.get_mut(&face.id) { + get_mut_unchecked(ctx).qabl = None; + if ctx.qabl.is_none() { + face_hat_mut!(face).remote_qabls.remove(res); + } + } + + let mut client_qabls = client_qabls(res); + let peer_qabls = remote_peer_qabls(tables, res); + + if client_qabls.is_empty() { + undeclare_peer_queryable(tables, None, res, &tables.zid.clone()); + } else { + let local_info = local_peer_qabl_info(tables, res); + register_peer_queryable(tables, None, res, &local_info, tables.zid); + } + + if client_qabls.len() == 1 && !peer_qabls { + let face = &mut client_qabls[0]; + if face_hat!(face).local_qabls.contains_key(res) { + let wire_expr = Resource::get_best_key(res, "", face.id); + face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::UndeclareQueryable(UndeclareQueryable { + id: 0, // @TODO use proper QueryableId (#703) + ext_wire_expr: WireExprType { wire_expr }, + }), + }, + res.expr(), + )); + + face_hat_mut!(face).local_qabls.remove(res); + } + } +} + +fn forget_client_queryable( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, +) { + undeclare_client_queryable(tables, face, res); +} + +pub(super) fn queries_new_face(tables: &mut Tables, face: &mut Arc) { + if face.whatami == WhatAmI::Client { + for qabl in &hat!(tables).peer_qabls { + if qabl.context.is_some() { + let info = local_qabl_info(tables, qabl, face); + face_hat_mut!(face).local_qabls.insert(qabl.clone(), info); + let key_expr = Resource::decl_key(qabl, face); + face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::DeclareQueryable(DeclareQueryable { + id: 0, // @TODO use proper QueryableId (#703) + wire_expr: key_expr, + ext_info: info, + }), + }, + qabl.expr(), + )); + } + } + } +} + +pub(super) fn queries_remove_node(tables: &mut Tables, node: &ZenohId) { + let mut qabls = vec![]; + for res in hat!(tables).peer_qabls.iter() { + for qabl in res_hat!(res).peer_qabls.keys() { + if qabl == node { + qabls.push(res.clone()); + } + } + } + for mut res in qabls { + unregister_peer_queryable(tables, &mut res, node); + + update_matches_query_routes(tables, &res); + Resource::clean(&mut res) + } +} + +pub(super) fn queries_tree_change(tables: &mut Tables, new_childs: &[Vec]) { + // propagate qabls to new childs + for (tree_sid, tree_childs) in new_childs.iter().enumerate() { + if !tree_childs.is_empty() { + let net = hat!(tables).peers_net.as_ref().unwrap(); + let tree_idx = NodeIndex::new(tree_sid); + if net.graph.contains_node(tree_idx) { + let tree_id = net.graph[tree_idx].zid; + + let qabls_res = &hat!(tables).peer_qabls; + + for res in qabls_res { + let qabls = &res_hat!(res).peer_qabls; + if let Some(qabl_info) = qabls.get(&tree_id) { + send_sourced_queryable_to_net_childs( + tables, + net, + tree_childs, + res, + qabl_info, + None, + tree_sid as NodeId, + ); + } + } + } + } + } + + // recompute routes + update_query_routes_from(tables, &mut tables.root_res.clone()); +} + +#[inline] +fn insert_target_for_qabls( + route: &mut QueryTargetQablSet, + expr: &mut RoutingExpr, + tables: &Tables, + net: &Network, + source: NodeId, + qabls: &HashMap, + complete: bool, +) { + if net.trees.len() > source as usize { + for (qabl, qabl_info) in qabls { + if let Some(qabl_idx) = net.get_idx(qabl) { + if net.trees[source as usize].directions.len() > qabl_idx.index() { + if let Some(direction) = net.trees[source as usize].directions[qabl_idx.index()] + { + if net.graph.contains_node(direction) { + if let Some(face) = tables.get_face(&net.graph[direction].zid) { + if net.distances.len() > qabl_idx.index() { + let key_expr = + Resource::get_best_key(expr.prefix, expr.suffix, face.id); + route.push(QueryTargetQabl { + direction: (face.clone(), key_expr.to_owned(), source), + complete: if complete { + qabl_info.complete as u64 + } else { + 0 + }, + distance: net.distances[qabl_idx.index()], + }); + } + } + } + } + } + } + } + } else { + log::trace!("Tree for node sid:{} not yet ready", source); + } +} + +lazy_static::lazy_static! { + static ref EMPTY_ROUTE: Arc = Arc::new(Vec::new()); +} + +impl HatQueriesTrait for HatCode { + fn declare_queryable( + &self, + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + qabl_info: &QueryableInfo, + node_id: NodeId, + ) { + if face.whatami != WhatAmI::Client { + if let Some(peer) = get_peer(tables, face, node_id) { + declare_peer_queryable(tables, face, res, qabl_info, peer); + } + } else { + declare_client_queryable(tables, face, res, qabl_info); + } + } + + fn undeclare_queryable( + &self, + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + node_id: NodeId, + ) { + if face.whatami != WhatAmI::Client { + if let Some(peer) = get_peer(tables, face, node_id) { + forget_peer_queryable(tables, face, res, &peer); + } + } else { + forget_client_queryable(tables, face, res); + } + } + + fn get_queryables(&self, tables: &Tables) -> Vec> { + hat!(tables).peer_qabls.iter().cloned().collect() + } + + fn compute_query_route( + &self, + tables: &Tables, + expr: &mut RoutingExpr, + source: NodeId, + source_type: WhatAmI, + ) -> Arc { + let mut route = QueryTargetQablSet::new(); + let key_expr = expr.full_expr(); + if key_expr.ends_with('/') { + return EMPTY_ROUTE.clone(); + } + log::trace!( + "compute_query_route({}, {:?}, {:?})", + key_expr, + source, + source_type + ); + let key_expr = match OwnedKeyExpr::try_from(key_expr) { + Ok(ke) => ke, + Err(e) => { + log::warn!("Invalid KE reached the system: {}", e); + return EMPTY_ROUTE.clone(); + } + }; + let res = Resource::get_resource(expr.prefix, expr.suffix); + let matches = res + .as_ref() + .and_then(|res| res.context.as_ref()) + .map(|ctx| Cow::from(&ctx.matches)) + .unwrap_or_else(|| Cow::from(Resource::get_matches(tables, &key_expr))); + + for mres in matches.iter() { + let mres = mres.upgrade().unwrap(); + let complete = DEFAULT_INCLUDER.includes(mres.expr().as_bytes(), key_expr.as_bytes()); + + let net = hat!(tables).peers_net.as_ref().unwrap(); + let peer_source = match source_type { + WhatAmI::Router | WhatAmI::Peer => source, + _ => net.idx.index() as NodeId, + }; + insert_target_for_qabls( + &mut route, + expr, + tables, + net, + peer_source, + &res_hat!(mres).peer_qabls, + complete, + ); + + for (sid, context) in &mres.session_ctxs { + if match tables.whatami { + WhatAmI::Router => context.face.whatami != WhatAmI::Router, + _ => source_type == WhatAmI::Client || context.face.whatami == WhatAmI::Client, + } { + let key_expr = Resource::get_best_key(expr.prefix, expr.suffix, *sid); + if let Some(qabl_info) = context.qabl.as_ref() { + route.push(QueryTargetQabl { + direction: ( + context.face.clone(), + key_expr.to_owned(), + NodeId::default(), + ), + complete: if complete { + qabl_info.complete as u64 + } else { + 0 + }, + distance: 0.5, + }); + } + } + } + } + route.sort_by_key(|qabl| OrderedFloat(qabl.distance)); + Arc::new(route) + } + + #[inline] + fn compute_local_replies( + &self, + tables: &Tables, + prefix: &Arc, + suffix: &str, + face: &Arc, + ) -> Vec<(WireExpr<'static>, ZBuf)> { + let mut result = vec![]; + // Only the first routing point in the query route + // should return the liveliness tokens + if face.whatami == WhatAmI::Client { + let key_expr = prefix.expr() + suffix; + let key_expr = match OwnedKeyExpr::try_from(key_expr) { + Ok(ke) => ke, + Err(e) => { + log::warn!("Invalid KE reached the system: {}", e); + return result; + } + }; + if key_expr.starts_with(PREFIX_LIVELINESS) { + let res = Resource::get_resource(prefix, suffix); + let matches = res + .as_ref() + .and_then(|res| res.context.as_ref()) + .map(|ctx| Cow::from(&ctx.matches)) + .unwrap_or_else(|| Cow::from(Resource::get_matches(tables, &key_expr))); + for mres in matches.iter() { + let mres = mres.upgrade().unwrap(); + if (mres.context.is_some() + && (!res_hat!(mres).router_subs.is_empty() + || !res_hat!(mres).peer_subs.is_empty())) + || mres.session_ctxs.values().any(|ctx| ctx.subs.is_some()) + { + result.push((Resource::get_best_key(&mres, "", face.id), ZBuf::default())); + } + } + } + } + result + } + + fn get_query_routes_entries(&self, tables: &Tables) -> RoutesIndexes { + get_routes_entries(tables) + } +} diff --git a/zenoh/src/net/routing/hat/mod.rs b/zenoh/src/net/routing/hat/mod.rs new file mode 100644 index 0000000000..4fbf9c9e5d --- /dev/null +++ b/zenoh/src/net/routing/hat/mod.rs @@ -0,0 +1,195 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +//! ⚠️ WARNING ⚠️ +//! +//! This module is intended for Zenoh's internal use. +//! +//! [Click here for Zenoh's documentation](../zenoh/index.html) +use super::{ + dispatcher::{ + face::{Face, FaceState}, + tables::{NodeId, QueryTargetQablSet, Resource, Route, RoutingExpr, Tables, TablesLock}, + }, + router::RoutesIndexes, +}; +use crate::runtime::Runtime; +use std::{any::Any, sync::Arc}; +use zenoh_buffers::ZBuf; +use zenoh_config::{unwrap_or_default, Config, WhatAmI}; +use zenoh_protocol::{ + core::WireExpr, + network::{ + declare::{queryable::ext::QueryableInfo, subscriber::ext::SubscriberInfo}, + Oam, + }, +}; +use zenoh_result::ZResult; +use zenoh_transport::unicast::TransportUnicast; + +mod client; +mod linkstate_peer; +mod p2p_peer; +mod router; + +zconfigurable! { + pub static ref TREES_COMPUTATION_DELAY_MS: u64 = 100; +} + +pub(crate) trait HatTrait: HatBaseTrait + HatPubSubTrait + HatQueriesTrait {} + +pub(crate) trait HatBaseTrait { + fn as_any(&self) -> &dyn Any; + + fn init(&self, tables: &mut Tables, runtime: Runtime); + + fn new_tables(&self, router_peers_failover_brokering: bool) -> Box; + + fn new_face(&self) -> Box; + + fn new_resource(&self) -> Box; + + fn new_local_face( + &self, + tables: &mut Tables, + tables_ref: &Arc, + face: &mut Face, + ) -> ZResult<()>; + + fn new_transport_unicast_face( + &self, + tables: &mut Tables, + tables_ref: &Arc, + face: &mut Face, + transport: &TransportUnicast, + ) -> ZResult<()>; + + fn handle_oam( + &self, + tables: &mut Tables, + tables_ref: &Arc, + oam: Oam, + transport: &TransportUnicast, + ) -> ZResult<()>; + + fn map_routing_context( + &self, + tables: &Tables, + face: &FaceState, + routing_context: NodeId, + ) -> NodeId; + + fn ingress_filter(&self, tables: &Tables, face: &FaceState, expr: &mut RoutingExpr) -> bool; + + fn egress_filter( + &self, + tables: &Tables, + src_face: &FaceState, + out_face: &Arc, + expr: &mut RoutingExpr, + ) -> bool; + + fn info(&self, tables: &Tables, kind: WhatAmI) -> String; + + fn closing( + &self, + tables: &mut Tables, + tables_ref: &Arc, + transport: &TransportUnicast, + ) -> ZResult<()>; + + fn close_face(&self, tables: &TablesLock, face: &mut Arc); +} + +pub(crate) trait HatPubSubTrait { + fn declare_subscription( + &self, + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + sub_info: &SubscriberInfo, + node_id: NodeId, + ); + fn undeclare_subscription( + &self, + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + node_id: NodeId, + ); + + fn get_subscriptions(&self, tables: &Tables) -> Vec>; + + fn compute_data_route( + &self, + tables: &Tables, + expr: &mut RoutingExpr, + source: NodeId, + source_type: WhatAmI, + ) -> Arc; + + fn get_data_routes_entries(&self, tables: &Tables) -> RoutesIndexes; +} + +pub(crate) trait HatQueriesTrait { + fn declare_queryable( + &self, + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + qabl_info: &QueryableInfo, + node_id: NodeId, + ); + fn undeclare_queryable( + &self, + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + node_id: NodeId, + ); + + fn get_queryables(&self, tables: &Tables) -> Vec>; + + fn compute_query_route( + &self, + tables: &Tables, + expr: &mut RoutingExpr, + source: NodeId, + source_type: WhatAmI, + ) -> Arc; + + fn get_query_routes_entries(&self, tables: &Tables) -> RoutesIndexes; + + fn compute_local_replies( + &self, + tables: &Tables, + prefix: &Arc, + suffix: &str, + face: &Arc, + ) -> Vec<(WireExpr<'static>, ZBuf)>; +} + +pub(crate) fn new_hat(whatami: WhatAmI, config: &Config) -> Box { + match whatami { + WhatAmI::Client => Box::new(client::HatCode {}), + WhatAmI::Peer => { + if unwrap_or_default!(config.routing().peer().mode()) == *"linkstate" { + Box::new(linkstate_peer::HatCode {}) + } else { + Box::new(p2p_peer::HatCode {}) + } + } + WhatAmI::Router => Box::new(router::HatCode {}), + } +} diff --git a/zenoh/src/net/routing/hat/p2p_peer/gossip.rs b/zenoh/src/net/routing/hat/p2p_peer/gossip.rs new file mode 100644 index 0000000000..ae3fda51a7 --- /dev/null +++ b/zenoh/src/net/routing/hat/p2p_peer/gossip.rs @@ -0,0 +1,562 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// +use crate::net::codec::Zenoh080Routing; +use crate::net::protocol::linkstate::{LinkState, LinkStateList}; +use crate::net::runtime::Runtime; +use async_std::task; +use petgraph::graph::NodeIndex; +use std::convert::TryInto; +use vec_map::VecMap; +use zenoh_buffers::writer::{DidntWrite, HasWriter}; +use zenoh_buffers::ZBuf; +use zenoh_codec::WCodec; +use zenoh_link::Locator; +use zenoh_protocol::common::ZExtBody; +use zenoh_protocol::core::{WhatAmI, WhatAmIMatcher, ZenohId}; +use zenoh_protocol::network::oam::id::OAM_LINKSTATE; +use zenoh_protocol::network::{oam, NetworkBody, NetworkMessage, Oam}; +use zenoh_transport::unicast::TransportUnicast; + +#[derive(Clone)] +struct Details { + zid: bool, + locators: bool, + links: bool, +} + +#[derive(Clone)] +pub(super) struct Node { + pub(super) zid: ZenohId, + pub(super) whatami: Option, + pub(super) locators: Option>, + pub(super) sn: u64, + pub(super) links: Vec, +} + +impl std::fmt::Debug for Node { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.zid) + } +} + +pub(super) struct Link { + pub(super) transport: TransportUnicast, + zid: ZenohId, + mappings: VecMap, + local_mappings: VecMap, +} + +impl Link { + fn new(transport: TransportUnicast) -> Self { + let zid = transport.get_zid().unwrap(); + Link { + transport, + zid, + mappings: VecMap::new(), + local_mappings: VecMap::new(), + } + } + + #[inline] + pub(super) fn set_zid_mapping(&mut self, psid: u64, zid: ZenohId) { + self.mappings.insert(psid.try_into().unwrap(), zid); + } + + #[inline] + pub(super) fn get_zid(&self, psid: &u64) -> Option<&ZenohId> { + self.mappings.get((*psid).try_into().unwrap()) + } + + #[inline] + pub(super) fn set_local_psid_mapping(&mut self, psid: u64, local_psid: u64) { + self.local_mappings + .insert(psid.try_into().unwrap(), local_psid); + } +} + +pub(super) struct Network { + pub(super) name: String, + pub(super) router_peers_failover_brokering: bool, + pub(super) gossip: bool, + pub(super) gossip_multihop: bool, + pub(super) autoconnect: WhatAmIMatcher, + pub(super) idx: NodeIndex, + pub(super) links: VecMap, + pub(super) graph: petgraph::stable_graph::StableUnGraph, + pub(super) runtime: Runtime, +} + +impl Network { + pub(super) fn new( + name: String, + zid: ZenohId, + runtime: Runtime, + router_peers_failover_brokering: bool, + gossip: bool, + gossip_multihop: bool, + autoconnect: WhatAmIMatcher, + ) -> Self { + let mut graph = petgraph::stable_graph::StableGraph::default(); + log::debug!("{} Add node (self) {}", name, zid); + let idx = graph.add_node(Node { + zid, + whatami: Some(runtime.whatami()), + locators: None, + sn: 1, + links: vec![], + }); + Network { + name, + router_peers_failover_brokering, + gossip, + gossip_multihop, + autoconnect, + idx, + links: VecMap::new(), + graph, + runtime, + } + } + + //noinspection ALL + // pub(super) fn dot(&self) -> String { + // std::format!( + // "{:?}", + // petgraph::dot::Dot::with_config(&self.graph, &[petgraph::dot::Config::EdgeNoLabel]) + // ) + // } + + #[inline] + pub(super) fn get_idx(&self, zid: &ZenohId) -> Option { + self.graph + .node_indices() + .find(|idx| self.graph[*idx].zid == *zid) + } + + #[inline] + pub(super) fn get_link_from_zid(&self, zid: &ZenohId) -> Option<&Link> { + self.links.values().find(|link| link.zid == *zid) + } + + fn add_node(&mut self, node: Node) -> NodeIndex { + let zid = node.zid; + let idx = self.graph.add_node(node); + for link in self.links.values_mut() { + if let Some((psid, _)) = link.mappings.iter().find(|(_, p)| **p == zid) { + link.local_mappings.insert(psid, idx.index() as u64); + } + } + idx + } + + fn make_link_state(&self, idx: NodeIndex, details: Details) -> LinkState { + let links = if details.links { + self.graph[idx] + .links + .iter() + .filter_map(|zid| { + if let Some(idx2) = self.get_idx(zid) { + Some(idx2.index().try_into().unwrap()) + } else { + log::error!( + "{} Internal error building link state: cannot get index of {}", + self.name, + zid + ); + None + } + }) + .collect() + } else { + vec![] + }; + LinkState { + psid: idx.index().try_into().unwrap(), + sn: self.graph[idx].sn, + zid: if details.zid { + Some(self.graph[idx].zid) + } else { + None + }, + whatami: self.graph[idx].whatami, + locators: if details.locators { + if idx == self.idx { + Some(self.runtime.get_locators()) + } else { + self.graph[idx].locators.clone() + } + } else { + None + }, + links, + } + } + + fn make_msg(&self, idxs: Vec<(NodeIndex, Details)>) -> Result { + let mut link_states = vec![]; + for (idx, details) in idxs { + link_states.push(self.make_link_state(idx, details)); + } + let codec = Zenoh080Routing::new(); + let mut buf = ZBuf::empty(); + codec.write(&mut buf.writer(), &LinkStateList { link_states })?; + Ok(NetworkBody::OAM(Oam { + id: OAM_LINKSTATE, + body: ZExtBody::ZBuf(buf), + ext_qos: oam::ext::QoSType::oam_default(), + ext_tstamp: None, + }) + .into()) + } + + fn send_on_link(&self, idxs: Vec<(NodeIndex, Details)>, transport: &TransportUnicast) { + if let Ok(msg) = self.make_msg(idxs) { + log::trace!("{} Send to {:?} {:?}", self.name, transport.get_zid(), msg); + if let Err(e) = transport.schedule(msg) { + log::debug!("{} Error sending LinkStateList: {}", self.name, e); + } + } else { + log::error!("Failed to encode Linkstate message"); + } + } + + fn send_on_links

(&self, idxs: Vec<(NodeIndex, Details)>, mut parameters: P) + where + P: FnMut(&Link) -> bool, + { + if let Ok(msg) = self.make_msg(idxs) { + for link in self.links.values() { + if parameters(link) { + log::trace!("{} Send to {} {:?}", self.name, link.zid, msg); + if let Err(e) = link.transport.schedule(msg.clone()) { + log::debug!("{} Error sending LinkStateList: {}", self.name, e); + } + } + } + } else { + log::error!("Failed to encode Linkstate message"); + } + } + + // Indicates if locators should be included when propagating Linkstate message + // from the given node. + // Returns true if gossip is enabled and if multihop gossip is enabled or + // the node is one of self neighbours. + fn propagate_locators(&self, idx: NodeIndex) -> bool { + self.gossip + && (self.gossip_multihop + || idx == self.idx + || self.links.values().any(|link| { + self.graph + .node_weight(idx) + .map(|node| link.zid == node.zid) + .unwrap_or(true) + })) + } + + pub(super) fn link_states(&mut self, link_states: Vec, src: ZenohId) { + log::trace!("{} Received from {} raw: {:?}", self.name, src, link_states); + + let graph = &self.graph; + let links = &mut self.links; + + let src_link = match links.values_mut().find(|link| link.zid == src) { + Some(link) => link, + None => { + log::error!( + "{} Received LinkStateList from unknown link {}", + self.name, + src + ); + return; + } + }; + + // register psid<->zid mappings & apply mapping to nodes + let link_states = link_states + .into_iter() + .filter_map(|link_state| { + if let Some(zid) = link_state.zid { + src_link.set_zid_mapping(link_state.psid, zid); + if let Some(idx) = graph.node_indices().find(|idx| graph[*idx].zid == zid) { + src_link.set_local_psid_mapping(link_state.psid, idx.index() as u64); + } + Some(( + zid, + link_state.whatami.unwrap_or(WhatAmI::Router), + link_state.locators, + link_state.sn, + link_state.links, + )) + } else { + match src_link.get_zid(&link_state.psid) { + Some(zid) => Some(( + *zid, + link_state.whatami.unwrap_or(WhatAmI::Router), + link_state.locators, + link_state.sn, + link_state.links, + )), + None => { + log::error!( + "Received LinkState from {} with unknown node mapping {}", + src, + link_state.psid + ); + None + } + } + } + }) + .collect::>(); + + // apply psid<->zid mapping to links + let src_link = self.get_link_from_zid(&src).unwrap(); + let link_states = link_states + .into_iter() + .map(|(zid, wai, locs, sn, links)| { + let links: Vec = links + .iter() + .filter_map(|l| { + if let Some(zid) = src_link.get_zid(l) { + Some(*zid) + } else { + log::error!( + "{} Received LinkState from {} with unknown link mapping {}", + self.name, + src, + l + ); + None + } + }) + .collect(); + (zid, wai, locs, sn, links) + }) + .collect::>(); + + // log::trace!( + // "{} Received from {} mapped: {:?}", + // self.name, + // src, + // link_states + // ); + for link_state in &link_states { + log::trace!( + "{} Received from {} mapped: {:?}", + self.name, + src, + link_state + ); + } + + for (zid, whatami, locators, sn, links) in link_states.into_iter() { + let idx = match self.get_idx(&zid) { + None => { + let idx = self.add_node(Node { + zid, + whatami: Some(whatami), + locators: locators.clone(), + sn, + links, + }); + locators.is_some().then_some(idx) + } + Some(idx) => { + let node = &mut self.graph[idx]; + let oldsn = node.sn; + (oldsn < sn) + .then(|| { + node.sn = sn; + node.links = links.clone(); + (node.locators != locators && locators.is_some()).then(|| { + node.locators = locators.clone(); + idx + }) + }) + .flatten() + } + }; + + if self.gossip { + if let Some(idx) = idx { + if self.gossip_multihop || self.links.values().any(|link| link.zid == zid) { + self.send_on_links( + vec![( + idx, + Details { + zid: true, + locators: true, + links: false, + }, + )], + |link| link.zid != zid, + ); + } + + if !self.autoconnect.is_empty() { + // Connect discovered peers + if task::block_on(self.runtime.manager().get_transport_unicast(&zid)) + .is_none() + && self.autoconnect.matches(whatami) + { + if let Some(locators) = locators { + let runtime = self.runtime.clone(); + self.runtime.spawn(async move { + // random backoff + async_std::task::sleep(std::time::Duration::from_millis( + rand::random::() % 100, + )) + .await; + runtime.connect_peer(&zid, &locators).await; + }); + } + } + } + } + } + } + } + + pub(super) fn add_link(&mut self, transport: TransportUnicast) -> usize { + let free_index = { + let mut i = 0; + while self.links.contains_key(i) { + i += 1; + } + i + }; + self.links.insert(free_index, Link::new(transport.clone())); + + let zid = transport.get_zid().unwrap(); + let whatami = transport.get_whatami().unwrap(); + + if self.router_peers_failover_brokering { + let (idx, new) = match self.get_idx(&zid) { + Some(idx) => (idx, false), + None => { + log::debug!("{} Add node (link) {}", self.name, zid); + ( + self.add_node(Node { + zid, + whatami: Some(whatami), + locators: None, + sn: 0, + links: vec![], + }), + true, + ) + } + }; + self.graph[self.idx].links.push(zid); + self.graph[self.idx].sn += 1; + + // Send updated self linkstate on all existing links except new one + self.links + .values() + .filter(|link| { + link.zid != zid + && link.transport.get_whatami().unwrap_or(WhatAmI::Peer) == WhatAmI::Router + }) + .for_each(|link| { + self.send_on_link( + if new || (!self.gossip_multihop) { + vec![ + ( + idx, + Details { + zid: true, + locators: false, + links: false, + }, + ), + ( + self.idx, + Details { + zid: false, + locators: self.propagate_locators(idx), + links: true, + }, + ), + ] + } else { + vec![( + self.idx, + Details { + zid: false, + locators: self.propagate_locators(idx), + links: true, + }, + )] + }, + &link.transport, + ) + }); + } + + // Send all nodes linkstate on new link + let idxs = self + .graph + .node_indices() + .filter_map(|idx| { + (self.gossip_multihop + || self.links.values().any(|link| link.zid == zid) + || (self.router_peers_failover_brokering + && idx == self.idx + && whatami == WhatAmI::Router)) + .then(|| { + ( + idx, + Details { + zid: true, + locators: self.propagate_locators(idx), + links: (self.router_peers_failover_brokering + && idx == self.idx + && whatami == WhatAmI::Router), + }, + ) + }) + }) + .collect(); + self.send_on_link(idxs, &transport); + free_index + } + + pub(super) fn remove_link(&mut self, zid: &ZenohId) -> Vec<(NodeIndex, Node)> { + log::trace!("{} remove_link {}", self.name, zid); + self.links.retain(|_, link| link.zid != *zid); + self.graph[self.idx].links.retain(|link| *link != *zid); + + if let Some(idx) = self.get_idx(zid) { + self.graph.remove_node(idx); + } + if self.router_peers_failover_brokering { + self.send_on_links( + vec![( + self.idx, + Details { + zid: false, + locators: self.gossip, + links: true, + }, + )], + |link| { + link.zid != *zid + && link.transport.get_whatami().unwrap_or(WhatAmI::Peer) == WhatAmI::Router + }, + ); + } + vec![] + } +} diff --git a/zenoh/src/net/routing/hat/p2p_peer/mod.rs b/zenoh/src/net/routing/hat/p2p_peer/mod.rs new file mode 100644 index 0000000000..8dc4f15ada --- /dev/null +++ b/zenoh/src/net/routing/hat/p2p_peer/mod.rs @@ -0,0 +1,392 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +//! ⚠️ WARNING ⚠️ +//! +//! This module is intended for Zenoh's internal use. +//! +//! [Click here for Zenoh's documentation](../zenoh/index.html) +use crate::{ + net::{ + codec::Zenoh080Routing, + protocol::linkstate::LinkStateList, + routing::{ + dispatcher::face::Face, + router::{ + compute_data_routes, compute_matching_pulls, compute_query_routes, RoutesIndexes, + }, + }, + }, + runtime::Runtime, +}; + +use self::{ + gossip::Network, + pubsub::{pubsub_new_face, undeclare_client_subscription}, + queries::{queries_new_face, undeclare_client_queryable}, +}; +use super::{ + super::dispatcher::{ + face::FaceState, + tables::{NodeId, Resource, RoutingExpr, Tables, TablesLock}, + }, + HatBaseTrait, HatTrait, +}; +use std::{ + any::Any, + collections::{HashMap, HashSet}, + sync::Arc, +}; +use zenoh_config::{unwrap_or_default, ModeDependent, WhatAmI, WhatAmIMatcher}; +use zenoh_protocol::network::Oam; +use zenoh_protocol::{ + common::ZExtBody, + network::{declare::queryable::ext::QueryableInfo, oam::id::OAM_LINKSTATE}, +}; +use zenoh_result::ZResult; +use zenoh_sync::get_mut_unchecked; +use zenoh_transport::unicast::TransportUnicast; + +mod gossip; +mod pubsub; +mod queries; + +macro_rules! hat_mut { + ($t:expr) => { + $t.hat.downcast_mut::().unwrap() + }; +} +use hat_mut; + +macro_rules! face_hat { + ($f:expr) => { + $f.hat.downcast_ref::().unwrap() + }; +} +use face_hat; + +macro_rules! face_hat_mut { + ($f:expr) => { + get_mut_unchecked($f).hat.downcast_mut::().unwrap() + }; +} +use face_hat_mut; + +struct HatTables { + gossip: Option, +} + +impl HatTables { + fn new() -> Self { + Self { gossip: None } + } +} + +pub(crate) struct HatCode {} + +impl HatBaseTrait for HatCode { + fn init(&self, tables: &mut Tables, runtime: Runtime) { + let config = runtime.config().lock(); + let whatami = tables.whatami; + let gossip = unwrap_or_default!(config.scouting().gossip().enabled()); + let gossip_multihop = unwrap_or_default!(config.scouting().gossip().multihop()); + let autoconnect = if gossip { + *unwrap_or_default!(config.scouting().gossip().autoconnect().get(whatami)) + } else { + WhatAmIMatcher::empty() + }; + let router_peers_failover_brokering = + unwrap_or_default!(config.routing().router().peers_failover_brokering()); + drop(config); + + hat_mut!(tables).gossip = Some(Network::new( + "[Gossip]".to_string(), + tables.zid, + runtime, + router_peers_failover_brokering, + gossip, + gossip_multihop, + autoconnect, + )); + } + + fn new_tables(&self, _router_peers_failover_brokering: bool) -> Box { + Box::new(HatTables::new()) + } + + fn new_face(&self) -> Box { + Box::new(HatFace::new()) + } + + fn new_resource(&self) -> Box { + Box::new(HatContext::new()) + } + + fn new_local_face( + &self, + tables: &mut Tables, + _tables_ref: &Arc, + face: &mut Face, + ) -> ZResult<()> { + pubsub_new_face(tables, &mut face.state); + queries_new_face(tables, &mut face.state); + Ok(()) + } + + fn new_transport_unicast_face( + &self, + tables: &mut Tables, + _tables_ref: &Arc, + face: &mut Face, + transport: &TransportUnicast, + ) -> ZResult<()> { + if face.state.whatami != WhatAmI::Client { + if let Some(net) = hat_mut!(tables).gossip.as_mut() { + net.add_link(transport.clone()); + } + } + pubsub_new_face(tables, &mut face.state); + queries_new_face(tables, &mut face.state); + Ok(()) + } + + fn close_face(&self, tables: &TablesLock, face: &mut Arc) { + let mut wtables = zwrite!(tables.tables); + let mut face_clone = face.clone(); + let face = get_mut_unchecked(face); + for res in face.remote_mappings.values_mut() { + get_mut_unchecked(res).session_ctxs.remove(&face.id); + Resource::clean(res); + } + face.remote_mappings.clear(); + for res in face.local_mappings.values_mut() { + get_mut_unchecked(res).session_ctxs.remove(&face.id); + Resource::clean(res); + } + face.local_mappings.clear(); + + let mut subs_matches = vec![]; + for mut res in face + .hat + .downcast_mut::() + .unwrap() + .remote_subs + .drain() + { + get_mut_unchecked(&mut res).session_ctxs.remove(&face.id); + undeclare_client_subscription(&mut wtables, &mut face_clone, &mut res); + + if res.context.is_some() { + for match_ in &res.context().matches { + let mut match_ = match_.upgrade().unwrap(); + if !Arc::ptr_eq(&match_, &res) { + get_mut_unchecked(&mut match_) + .context_mut() + .disable_data_routes(); + subs_matches.push(match_); + } + } + get_mut_unchecked(&mut res) + .context_mut() + .disable_data_routes(); + subs_matches.push(res); + } + } + + let mut qabls_matches = vec![]; + for mut res in face + .hat + .downcast_mut::() + .unwrap() + .remote_qabls + .drain() + { + get_mut_unchecked(&mut res).session_ctxs.remove(&face.id); + undeclare_client_queryable(&mut wtables, &mut face_clone, &mut res); + + if res.context.is_some() { + for match_ in &res.context().matches { + let mut match_ = match_.upgrade().unwrap(); + if !Arc::ptr_eq(&match_, &res) { + get_mut_unchecked(&mut match_) + .context_mut() + .disable_query_routes(); + qabls_matches.push(match_); + } + } + get_mut_unchecked(&mut res) + .context_mut() + .disable_query_routes(); + qabls_matches.push(res); + } + } + drop(wtables); + + let mut matches_data_routes = vec![]; + let mut matches_query_routes = vec![]; + let rtables = zread!(tables.tables); + for _match in subs_matches.drain(..) { + let mut expr = RoutingExpr::new(&_match, ""); + matches_data_routes.push(( + _match.clone(), + compute_data_routes(&rtables, &mut expr), + compute_matching_pulls(&rtables, &mut expr), + )); + } + for _match in qabls_matches.drain(..) { + matches_query_routes.push((_match.clone(), compute_query_routes(&rtables, &_match))); + } + drop(rtables); + + let mut wtables = zwrite!(tables.tables); + for (mut res, data_routes, matching_pulls) in matches_data_routes { + get_mut_unchecked(&mut res) + .context_mut() + .update_data_routes(data_routes); + get_mut_unchecked(&mut res) + .context_mut() + .update_matching_pulls(matching_pulls); + Resource::clean(&mut res); + } + for (mut res, query_routes) in matches_query_routes { + get_mut_unchecked(&mut res) + .context_mut() + .update_query_routes(query_routes); + Resource::clean(&mut res); + } + wtables.faces.remove(&face.id); + drop(wtables); + } + + fn handle_oam( + &self, + tables: &mut Tables, + _tables_ref: &Arc, + oam: Oam, + transport: &TransportUnicast, + ) -> ZResult<()> { + if oam.id == OAM_LINKSTATE { + if let ZExtBody::ZBuf(buf) = oam.body { + if let Ok(zid) = transport.get_zid() { + use zenoh_buffers::reader::HasReader; + use zenoh_codec::RCodec; + let codec = Zenoh080Routing::new(); + let mut reader = buf.reader(); + let list: LinkStateList = codec.read(&mut reader).unwrap(); + + let whatami = transport.get_whatami()?; + if whatami != WhatAmI::Client { + if let Some(net) = hat_mut!(tables).gossip.as_mut() { + net.link_states(list.link_states, zid); + } + }; + } + } + } + + Ok(()) + } + + #[inline] + fn map_routing_context( + &self, + _tables: &Tables, + _face: &FaceState, + _routing_context: NodeId, + ) -> NodeId { + 0 + } + + fn closing( + &self, + tables: &mut Tables, + _tables_ref: &Arc, + transport: &TransportUnicast, + ) -> ZResult<()> { + match (transport.get_zid(), transport.get_whatami()) { + (Ok(zid), Ok(whatami)) => { + if whatami != WhatAmI::Client { + hat_mut!(tables).gossip.as_mut().unwrap().remove_link(&zid); + }; + } + (_, _) => log::error!("Closed transport in session closing!"), + } + Ok(()) + } + + fn as_any(&self) -> &dyn Any { + self + } + + #[inline] + fn ingress_filter(&self, _tables: &Tables, _face: &FaceState, _expr: &mut RoutingExpr) -> bool { + true + } + + #[inline] + fn egress_filter( + &self, + _tables: &Tables, + src_face: &FaceState, + out_face: &Arc, + _expr: &mut RoutingExpr, + ) -> bool { + src_face.id != out_face.id + && match (src_face.mcast_group.as_ref(), out_face.mcast_group.as_ref()) { + (Some(l), Some(r)) => l != r, + _ => true, + } + } + + fn info(&self, _tables: &Tables, _kind: WhatAmI) -> String { + "graph {}".to_string() + } +} + +struct HatContext {} + +impl HatContext { + fn new() -> Self { + Self {} + } +} + +struct HatFace { + local_subs: HashSet>, + remote_subs: HashSet>, + local_qabls: HashMap, QueryableInfo>, + remote_qabls: HashSet>, +} + +impl HatFace { + fn new() -> Self { + Self { + local_subs: HashSet::new(), + remote_subs: HashSet::new(), + local_qabls: HashMap::new(), + remote_qabls: HashSet::new(), + } + } +} + +impl HatTrait for HatCode {} + +#[inline] +fn get_routes_entries() -> RoutesIndexes { + RoutesIndexes { + routers: vec![0], + peers: vec![0], + clients: vec![0], + } +} diff --git a/zenoh/src/net/routing/hat/p2p_peer/pubsub.rs b/zenoh/src/net/routing/hat/p2p_peer/pubsub.rs new file mode 100644 index 0000000000..8f91335f0a --- /dev/null +++ b/zenoh/src/net/routing/hat/p2p_peer/pubsub.rs @@ -0,0 +1,354 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// +use super::{face_hat, face_hat_mut, get_routes_entries}; +use super::{HatCode, HatFace}; +use crate::net::routing::dispatcher::face::FaceState; +use crate::net::routing::dispatcher::resource::{NodeId, Resource, SessionContext}; +use crate::net::routing::dispatcher::tables::Tables; +use crate::net::routing::dispatcher::tables::{Route, RoutingExpr}; +use crate::net::routing::hat::HatPubSubTrait; +use crate::net::routing::router::RoutesIndexes; +use crate::net::routing::{RoutingContext, PREFIX_LIVELINESS}; +use std::borrow::Cow; +use std::collections::{HashMap, HashSet}; +use std::sync::Arc; +use zenoh_protocol::core::key_expr::OwnedKeyExpr; +use zenoh_protocol::{ + core::{Reliability, WhatAmI}, + network::declare::{ + common::ext::WireExprType, ext, subscriber::ext::SubscriberInfo, Declare, DeclareBody, + DeclareSubscriber, Mode, UndeclareSubscriber, + }, +}; +use zenoh_sync::get_mut_unchecked; + +#[inline] +fn propagate_simple_subscription_to( + _tables: &mut Tables, + dst_face: &mut Arc, + res: &Arc, + sub_info: &SubscriberInfo, + src_face: &mut Arc, +) { + if (src_face.id != dst_face.id || res.expr().starts_with(PREFIX_LIVELINESS)) + && !face_hat!(dst_face).local_subs.contains(res) + && (src_face.whatami == WhatAmI::Client || dst_face.whatami == WhatAmI::Client) + { + face_hat_mut!(dst_face).local_subs.insert(res.clone()); + let key_expr = Resource::decl_key(res, dst_face); + dst_face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::DeclareSubscriber(DeclareSubscriber { + id: 0, // @TODO use proper SubscriberId (#703) + wire_expr: key_expr, + ext_info: *sub_info, + }), + }, + res.expr(), + )); + } +} + +fn propagate_simple_subscription( + tables: &mut Tables, + res: &Arc, + sub_info: &SubscriberInfo, + src_face: &mut Arc, +) { + for mut dst_face in tables + .faces + .values() + .cloned() + .collect::>>() + { + propagate_simple_subscription_to(tables, &mut dst_face, res, sub_info, src_face); + } +} + +fn register_client_subscription( + _tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + sub_info: &SubscriberInfo, +) { + // Register subscription + { + let res = get_mut_unchecked(res); + log::debug!("Register subscription {} for {}", res.expr(), face); + match res.session_ctxs.get_mut(&face.id) { + Some(ctx) => match &ctx.subs { + Some(info) => { + if Mode::Pull == info.mode { + get_mut_unchecked(ctx).subs = Some(*sub_info); + } + } + None => { + get_mut_unchecked(ctx).subs = Some(*sub_info); + } + }, + None => { + res.session_ctxs.insert( + face.id, + Arc::new(SessionContext { + face: face.clone(), + local_expr_id: None, + remote_expr_id: None, + subs: Some(*sub_info), + qabl: None, + last_values: HashMap::new(), + }), + ); + } + } + } + face_hat_mut!(face).remote_subs.insert(res.clone()); +} + +fn declare_client_subscription( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + sub_info: &SubscriberInfo, +) { + register_client_subscription(tables, face, res, sub_info); + let mut propa_sub_info = *sub_info; + propa_sub_info.mode = Mode::Push; + + propagate_simple_subscription(tables, res, &propa_sub_info, face); + // This introduced a buffer overflow on windows + // TODO: Let's deactivate this on windows until Fixed + #[cfg(not(windows))] + for mcast_group in &tables.mcast_groups { + mcast_group + .primitives + .send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::DeclareSubscriber(DeclareSubscriber { + id: 0, // @TODO use proper SubscriberId (#703) + wire_expr: res.expr().into(), + ext_info: *sub_info, + }), + }, + res.expr(), + )) + } +} + +#[inline] +fn client_subs(res: &Arc) -> Vec> { + res.session_ctxs + .values() + .filter_map(|ctx| { + if ctx.subs.is_some() { + Some(ctx.face.clone()) + } else { + None + } + }) + .collect() +} + +fn propagate_forget_simple_subscription(tables: &mut Tables, res: &Arc) { + for face in tables.faces.values_mut() { + if face_hat!(face).local_subs.contains(res) { + let wire_expr = Resource::get_best_key(res, "", face.id); + face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::UndeclareSubscriber(UndeclareSubscriber { + id: 0, // @TODO use proper SubscriberId (#703) + ext_wire_expr: WireExprType { wire_expr }, + }), + }, + res.expr(), + )); + face_hat_mut!(face).local_subs.remove(res); + } + } +} + +pub(super) fn undeclare_client_subscription( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, +) { + log::debug!("Unregister client subscription {} for {}", res.expr(), face); + if let Some(ctx) = get_mut_unchecked(res).session_ctxs.get_mut(&face.id) { + get_mut_unchecked(ctx).subs = None; + } + face_hat_mut!(face).remote_subs.remove(res); + + let mut client_subs = client_subs(res); + if client_subs.is_empty() { + propagate_forget_simple_subscription(tables, res); + } + if client_subs.len() == 1 { + let face = &mut client_subs[0]; + if face_hat!(face).local_subs.contains(res) + && !(face.whatami == WhatAmI::Client && res.expr().starts_with(PREFIX_LIVELINESS)) + { + let wire_expr = Resource::get_best_key(res, "", face.id); + face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::UndeclareSubscriber(UndeclareSubscriber { + id: 0, // @TODO use proper SubscriberId (#703) + ext_wire_expr: WireExprType { wire_expr }, + }), + }, + res.expr(), + )); + + face_hat_mut!(face).local_subs.remove(res); + } + } +} + +fn forget_client_subscription( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, +) { + undeclare_client_subscription(tables, face, res); +} + +pub(super) fn pubsub_new_face(tables: &mut Tables, face: &mut Arc) { + let sub_info = SubscriberInfo { + reliability: Reliability::Reliable, // @TODO compute proper reliability to propagate from reliability of known subscribers + mode: Mode::Push, + }; + for src_face in tables + .faces + .values() + .cloned() + .collect::>>() + { + for sub in &face_hat!(src_face).remote_subs { + propagate_simple_subscription_to(tables, face, sub, &sub_info, &mut src_face.clone()); + } + } +} + +impl HatPubSubTrait for HatCode { + fn declare_subscription( + &self, + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + sub_info: &SubscriberInfo, + _node_id: NodeId, + ) { + declare_client_subscription(tables, face, res, sub_info); + } + + fn undeclare_subscription( + &self, + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + _node_id: NodeId, + ) { + forget_client_subscription(tables, face, res); + } + + fn get_subscriptions(&self, tables: &Tables) -> Vec> { + let mut subs = HashSet::new(); + for src_face in tables.faces.values() { + for sub in &face_hat!(src_face).remote_subs { + subs.insert(sub.clone()); + } + } + Vec::from_iter(subs) + } + + fn compute_data_route( + &self, + tables: &Tables, + expr: &mut RoutingExpr, + source: NodeId, + source_type: WhatAmI, + ) -> Arc { + let mut route = HashMap::new(); + let key_expr = expr.full_expr(); + if key_expr.ends_with('/') { + return Arc::new(route); + } + log::trace!( + "compute_data_route({}, {:?}, {:?})", + key_expr, + source, + source_type + ); + let key_expr = match OwnedKeyExpr::try_from(key_expr) { + Ok(ke) => ke, + Err(e) => { + log::warn!("Invalid KE reached the system: {}", e); + return Arc::new(route); + } + }; + let res = Resource::get_resource(expr.prefix, expr.suffix); + let matches = res + .as_ref() + .and_then(|res| res.context.as_ref()) + .map(|ctx| Cow::from(&ctx.matches)) + .unwrap_or_else(|| Cow::from(Resource::get_matches(tables, &key_expr))); + + for mres in matches.iter() { + let mres = mres.upgrade().unwrap(); + + for (sid, context) in &mres.session_ctxs { + if let Some(subinfo) = &context.subs { + if match tables.whatami { + WhatAmI::Router => context.face.whatami != WhatAmI::Router, + _ => { + source_type == WhatAmI::Client + || context.face.whatami == WhatAmI::Client + } + } && subinfo.mode == Mode::Push + { + route.entry(*sid).or_insert_with(|| { + let key_expr = Resource::get_best_key(expr.prefix, expr.suffix, *sid); + (context.face.clone(), key_expr.to_owned(), NodeId::default()) + }); + } + } + } + } + for mcast_group in &tables.mcast_groups { + route.insert( + mcast_group.id, + ( + mcast_group.clone(), + expr.full_expr().to_string().into(), + NodeId::default(), + ), + ); + } + Arc::new(route) + } + + fn get_data_routes_entries(&self, _tables: &Tables) -> RoutesIndexes { + get_routes_entries() + } +} diff --git a/zenoh/src/net/routing/hat/p2p_peer/queries.rs b/zenoh/src/net/routing/hat/p2p_peer/queries.rs new file mode 100644 index 0000000000..35a10557dc --- /dev/null +++ b/zenoh/src/net/routing/hat/p2p_peer/queries.rs @@ -0,0 +1,378 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// +use super::{face_hat, face_hat_mut, get_routes_entries}; +use super::{HatCode, HatFace}; +use crate::net::routing::dispatcher::face::FaceState; +use crate::net::routing::dispatcher::resource::{NodeId, Resource, SessionContext}; +use crate::net::routing::dispatcher::tables::Tables; +use crate::net::routing::dispatcher::tables::{QueryTargetQabl, QueryTargetQablSet, RoutingExpr}; +use crate::net::routing::hat::HatQueriesTrait; +use crate::net::routing::router::RoutesIndexes; +use crate::net::routing::{RoutingContext, PREFIX_LIVELINESS}; +use ordered_float::OrderedFloat; +use std::borrow::Cow; +use std::collections::{HashMap, HashSet}; +use std::sync::Arc; +use zenoh_buffers::ZBuf; +use zenoh_protocol::core::key_expr::include::{Includer, DEFAULT_INCLUDER}; +use zenoh_protocol::core::key_expr::OwnedKeyExpr; +use zenoh_protocol::{ + core::{WhatAmI, WireExpr}, + network::declare::{ + common::ext::WireExprType, ext, queryable::ext::QueryableInfo, Declare, DeclareBody, + DeclareQueryable, UndeclareQueryable, + }, +}; +use zenoh_sync::get_mut_unchecked; + +#[cfg(feature = "complete_n")] +#[inline] +fn merge_qabl_infos(mut this: QueryableInfo, info: &QueryableInfo) -> QueryableInfo { + this.complete += info.complete; + this.distance = std::cmp::min(this.distance, info.distance); + this +} + +#[cfg(not(feature = "complete_n"))] +#[inline] +fn merge_qabl_infos(mut this: QueryableInfo, info: &QueryableInfo) -> QueryableInfo { + this.complete = u8::from(this.complete != 0 || info.complete != 0); + this.distance = std::cmp::min(this.distance, info.distance); + this +} + +fn local_qabl_info(_tables: &Tables, res: &Arc, face: &Arc) -> QueryableInfo { + res.session_ctxs + .values() + .fold(None, |accu, ctx| { + if ctx.face.id != face.id { + if let Some(info) = ctx.qabl.as_ref() { + Some(match accu { + Some(accu) => merge_qabl_infos(accu, info), + None => *info, + }) + } else { + accu + } + } else { + accu + } + }) + .unwrap_or(QueryableInfo { + complete: 0, + distance: 0, + }) +} + +fn propagate_simple_queryable( + tables: &mut Tables, + res: &Arc, + src_face: Option<&mut Arc>, +) { + let faces = tables.faces.values().cloned(); + for mut dst_face in faces { + let info = local_qabl_info(tables, res, &dst_face); + let current_info = face_hat!(dst_face).local_qabls.get(res); + if (src_face.is_none() || src_face.as_ref().unwrap().id != dst_face.id) + && (current_info.is_none() || *current_info.unwrap() != info) + && (src_face.is_none() + || src_face.as_ref().unwrap().whatami == WhatAmI::Client + || dst_face.whatami == WhatAmI::Client) + { + face_hat_mut!(&mut dst_face) + .local_qabls + .insert(res.clone(), info); + let key_expr = Resource::decl_key(res, &mut dst_face); + dst_face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::DeclareQueryable(DeclareQueryable { + id: 0, // @TODO use proper QueryableId (#703) + wire_expr: key_expr, + ext_info: info, + }), + }, + res.expr(), + )); + } + } +} + +fn register_client_queryable( + _tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + qabl_info: &QueryableInfo, +) { + // Register queryable + { + let res = get_mut_unchecked(res); + log::debug!("Register queryable {} (face: {})", res.expr(), face,); + get_mut_unchecked(res.session_ctxs.entry(face.id).or_insert_with(|| { + Arc::new(SessionContext { + face: face.clone(), + local_expr_id: None, + remote_expr_id: None, + subs: None, + qabl: None, + last_values: HashMap::new(), + }) + })) + .qabl = Some(*qabl_info); + } + face_hat_mut!(face).remote_qabls.insert(res.clone()); +} + +fn declare_client_queryable( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + qabl_info: &QueryableInfo, +) { + register_client_queryable(tables, face, res, qabl_info); + propagate_simple_queryable(tables, res, Some(face)); +} + +#[inline] +fn client_qabls(res: &Arc) -> Vec> { + res.session_ctxs + .values() + .filter_map(|ctx| { + if ctx.qabl.is_some() { + Some(ctx.face.clone()) + } else { + None + } + }) + .collect() +} + +fn propagate_forget_simple_queryable(tables: &mut Tables, res: &mut Arc) { + for face in tables.faces.values_mut() { + if face_hat!(face).local_qabls.contains_key(res) { + let wire_expr = Resource::get_best_key(res, "", face.id); + face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::UndeclareQueryable(UndeclareQueryable { + id: 0, // @TODO use proper QueryableId (#703) + ext_wire_expr: WireExprType { wire_expr }, + }), + }, + res.expr(), + )); + + face_hat_mut!(face).local_qabls.remove(res); + } + } +} + +pub(super) fn undeclare_client_queryable( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, +) { + log::debug!("Unregister client queryable {} for {}", res.expr(), face); + if let Some(ctx) = get_mut_unchecked(res).session_ctxs.get_mut(&face.id) { + get_mut_unchecked(ctx).qabl = None; + if ctx.qabl.is_none() { + face_hat_mut!(face).remote_qabls.remove(res); + } + } + + let mut client_qabls = client_qabls(res); + if client_qabls.is_empty() { + propagate_forget_simple_queryable(tables, res); + } else { + propagate_simple_queryable(tables, res, None); + } + if client_qabls.len() == 1 { + let face = &mut client_qabls[0]; + if face_hat!(face).local_qabls.contains_key(res) { + let wire_expr = Resource::get_best_key(res, "", face.id); + face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::UndeclareQueryable(UndeclareQueryable { + id: 0, // @TODO use proper QueryableId (#703) + ext_wire_expr: WireExprType { wire_expr }, + }), + }, + res.expr(), + )); + + face_hat_mut!(face).local_qabls.remove(res); + } + } +} + +fn forget_client_queryable( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, +) { + undeclare_client_queryable(tables, face, res); +} + +pub(super) fn queries_new_face(tables: &mut Tables, _face: &mut Arc) { + for face in tables + .faces + .values() + .cloned() + .collect::>>() + { + for qabl in face_hat!(face).remote_qabls.iter() { + propagate_simple_queryable(tables, qabl, Some(&mut face.clone())); + } + } +} + +lazy_static::lazy_static! { + static ref EMPTY_ROUTE: Arc = Arc::new(Vec::new()); +} + +impl HatQueriesTrait for HatCode { + fn declare_queryable( + &self, + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + qabl_info: &QueryableInfo, + _node_id: NodeId, + ) { + declare_client_queryable(tables, face, res, qabl_info); + } + + fn undeclare_queryable( + &self, + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + _node_id: NodeId, + ) { + forget_client_queryable(tables, face, res); + } + + fn get_queryables(&self, tables: &Tables) -> Vec> { + let mut qabls = HashSet::new(); + for src_face in tables.faces.values() { + for qabl in &face_hat!(src_face).remote_qabls { + qabls.insert(qabl.clone()); + } + } + Vec::from_iter(qabls) + } + + fn compute_query_route( + &self, + tables: &Tables, + expr: &mut RoutingExpr, + source: NodeId, + source_type: WhatAmI, + ) -> Arc { + let mut route = QueryTargetQablSet::new(); + let key_expr = expr.full_expr(); + if key_expr.ends_with('/') { + return EMPTY_ROUTE.clone(); + } + log::trace!( + "compute_query_route({}, {:?}, {:?})", + key_expr, + source, + source_type + ); + let key_expr = match OwnedKeyExpr::try_from(key_expr) { + Ok(ke) => ke, + Err(e) => { + log::warn!("Invalid KE reached the system: {}", e); + return EMPTY_ROUTE.clone(); + } + }; + let res = Resource::get_resource(expr.prefix, expr.suffix); + let matches = res + .as_ref() + .and_then(|res| res.context.as_ref()) + .map(|ctx| Cow::from(&ctx.matches)) + .unwrap_or_else(|| Cow::from(Resource::get_matches(tables, &key_expr))); + + for mres in matches.iter() { + let mres = mres.upgrade().unwrap(); + let complete = DEFAULT_INCLUDER.includes(mres.expr().as_bytes(), key_expr.as_bytes()); + for (sid, context) in &mres.session_ctxs { + let key_expr = Resource::get_best_key(expr.prefix, expr.suffix, *sid); + if let Some(qabl_info) = context.qabl.as_ref() { + route.push(QueryTargetQabl { + direction: (context.face.clone(), key_expr.to_owned(), NodeId::default()), + complete: if complete { + qabl_info.complete as u64 + } else { + 0 + }, + distance: 0.5, + }); + } + } + } + route.sort_by_key(|qabl| OrderedFloat(qabl.distance)); + Arc::new(route) + } + + #[inline] + fn compute_local_replies( + &self, + tables: &Tables, + prefix: &Arc, + suffix: &str, + face: &Arc, + ) -> Vec<(WireExpr<'static>, ZBuf)> { + let mut result = vec![]; + // Only the first routing point in the query route + // should return the liveliness tokens + if face.whatami == WhatAmI::Client { + let key_expr = prefix.expr() + suffix; + let key_expr = match OwnedKeyExpr::try_from(key_expr) { + Ok(ke) => ke, + Err(e) => { + log::warn!("Invalid KE reached the system: {}", e); + return result; + } + }; + if key_expr.starts_with(PREFIX_LIVELINESS) { + let res = Resource::get_resource(prefix, suffix); + let matches = res + .as_ref() + .and_then(|res| res.context.as_ref()) + .map(|ctx| Cow::from(&ctx.matches)) + .unwrap_or_else(|| Cow::from(Resource::get_matches(tables, &key_expr))); + for mres in matches.iter() { + let mres = mres.upgrade().unwrap(); + if mres.session_ctxs.values().any(|ctx| ctx.subs.is_some()) { + result.push((Resource::get_best_key(&mres, "", face.id), ZBuf::default())); + } + } + } + } + result + } + + fn get_query_routes_entries(&self, _tables: &Tables) -> RoutesIndexes { + get_routes_entries() + } +} diff --git a/zenoh/src/net/routing/hat/router/mod.rs b/zenoh/src/net/routing/hat/router/mod.rs new file mode 100644 index 0000000000..24c837e8f5 --- /dev/null +++ b/zenoh/src/net/routing/hat/router/mod.rs @@ -0,0 +1,877 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +//! ⚠️ WARNING ⚠️ +//! +//! This module is intended for Zenoh's internal use. +//! +//! [Click here for Zenoh's documentation](../zenoh/index.html) +use self::{ + network::{shared_nodes, Network}, + pubsub::{ + pubsub_linkstate_change, pubsub_new_face, pubsub_remove_node, undeclare_client_subscription, + }, + queries::{ + queries_linkstate_change, queries_new_face, queries_remove_node, undeclare_client_queryable, + }, +}; +use super::{ + super::dispatcher::{ + face::FaceState, + tables::{NodeId, Resource, RoutingExpr, Tables, TablesLock}, + }, + HatBaseTrait, HatTrait, +}; +use crate::{ + net::{ + codec::Zenoh080Routing, + protocol::linkstate::LinkStateList, + routing::{ + dispatcher::face::Face, + hat::TREES_COMPUTATION_DELAY_MS, + router::{ + compute_data_routes, compute_matching_pulls, compute_query_routes, RoutesIndexes, + }, + }, + }, + runtime::Runtime, +}; +use async_std::task::JoinHandle; +use std::{ + any::Any, + collections::{hash_map::DefaultHasher, HashMap, HashSet}, + hash::Hasher, + sync::Arc, +}; +use zenoh_config::{unwrap_or_default, ModeDependent, WhatAmI, WhatAmIMatcher, ZenohId}; +use zenoh_protocol::{ + common::ZExtBody, + network::{declare::queryable::ext::QueryableInfo, oam::id::OAM_LINKSTATE, Oam}, +}; +use zenoh_result::ZResult; +use zenoh_sync::get_mut_unchecked; +use zenoh_transport::unicast::TransportUnicast; + +mod network; +mod pubsub; +mod queries; + +macro_rules! hat { + ($t:expr) => { + $t.hat.downcast_ref::().unwrap() + }; +} +use hat; + +macro_rules! hat_mut { + ($t:expr) => { + $t.hat.downcast_mut::().unwrap() + }; +} +use hat_mut; + +macro_rules! res_hat { + ($r:expr) => { + $r.context().hat.downcast_ref::().unwrap() + }; +} +use res_hat; + +macro_rules! res_hat_mut { + ($r:expr) => { + get_mut_unchecked($r) + .context_mut() + .hat + .downcast_mut::() + .unwrap() + }; +} +use res_hat_mut; + +macro_rules! face_hat { + ($f:expr) => { + $f.hat.downcast_ref::().unwrap() + }; +} +use face_hat; + +macro_rules! face_hat_mut { + ($f:expr) => { + get_mut_unchecked($f).hat.downcast_mut::().unwrap() + }; +} +use face_hat_mut; + +struct HatTables { + router_subs: HashSet>, + peer_subs: HashSet>, + router_qabls: HashSet>, + peer_qabls: HashSet>, + routers_net: Option, + peers_net: Option, + shared_nodes: Vec, + routers_trees_task: Option>, + peers_trees_task: Option>, + router_peers_failover_brokering: bool, +} + +impl HatTables { + fn new(router_peers_failover_brokering: bool) -> Self { + Self { + router_subs: HashSet::new(), + peer_subs: HashSet::new(), + router_qabls: HashSet::new(), + peer_qabls: HashSet::new(), + routers_net: None, + peers_net: None, + shared_nodes: vec![], + routers_trees_task: None, + peers_trees_task: None, + router_peers_failover_brokering, + } + } + + #[inline] + fn get_net(&self, net_type: WhatAmI) -> Option<&Network> { + match net_type { + WhatAmI::Router => self.routers_net.as_ref(), + WhatAmI::Peer => self.peers_net.as_ref(), + _ => None, + } + } + + #[inline] + fn full_net(&self, net_type: WhatAmI) -> bool { + match net_type { + WhatAmI::Router => self + .routers_net + .as_ref() + .map(|net| net.full_linkstate) + .unwrap_or(false), + WhatAmI::Peer => self + .peers_net + .as_ref() + .map(|net| net.full_linkstate) + .unwrap_or(false), + _ => false, + } + } + + #[inline] + fn get_router_links(&self, peer: ZenohId) -> impl Iterator + '_ { + self.peers_net + .as_ref() + .unwrap() + .get_links(peer) + .iter() + .filter(move |nid| { + if let Some(node) = self.routers_net.as_ref().unwrap().get_node(nid) { + node.whatami.unwrap_or(WhatAmI::Router) == WhatAmI::Router + } else { + false + } + }) + } + + #[inline] + fn elect_router<'a>( + &'a self, + self_zid: &'a ZenohId, + key_expr: &str, + mut routers: impl Iterator, + ) -> &'a ZenohId { + match routers.next() { + None => self_zid, + Some(router) => { + let hash = |r: &ZenohId| { + let mut hasher = DefaultHasher::new(); + for b in key_expr.as_bytes() { + hasher.write_u8(*b); + } + for b in &r.to_le_bytes()[..r.size()] { + hasher.write_u8(*b); + } + hasher.finish() + }; + let mut res = router; + let mut h = None; + for router2 in routers { + let h2 = hash(router2); + if h2 > *h.get_or_insert_with(|| hash(res)) { + res = router2; + h = Some(h2); + } + } + res + } + } + } + + #[inline] + fn failover_brokering_to(source_links: &[ZenohId], dest: ZenohId) -> bool { + // if source_links is empty then gossip is probably disabled in source peer + !source_links.is_empty() && !source_links.contains(&dest) + } + + #[inline] + fn failover_brokering(&self, peer1: ZenohId, peer2: ZenohId) -> bool { + self.router_peers_failover_brokering + && self + .peers_net + .as_ref() + .map(|net| { + let links = net.get_links(peer1); + log::debug!("failover_brokering {} {} ({:?})", peer1, peer2, links); + HatTables::failover_brokering_to(links, peer2) + }) + .unwrap_or(false) + } + + fn schedule_compute_trees(&mut self, tables_ref: Arc, net_type: WhatAmI) { + log::trace!("Schedule computations"); + if (net_type == WhatAmI::Router && self.routers_trees_task.is_none()) + || (net_type == WhatAmI::Peer && self.peers_trees_task.is_none()) + { + let task = Some(async_std::task::spawn(async move { + async_std::task::sleep(std::time::Duration::from_millis( + *TREES_COMPUTATION_DELAY_MS, + )) + .await; + let mut tables = zwrite!(tables_ref.tables); + + log::trace!("Compute trees"); + let new_childs = match net_type { + WhatAmI::Router => hat_mut!(tables) + .routers_net + .as_mut() + .unwrap() + .compute_trees(), + _ => hat_mut!(tables).peers_net.as_mut().unwrap().compute_trees(), + }; + + log::trace!("Compute routes"); + pubsub::pubsub_tree_change(&mut tables, &new_childs, net_type); + queries::queries_tree_change(&mut tables, &new_childs, net_type); + + log::trace!("Computations completed"); + match net_type { + WhatAmI::Router => hat_mut!(tables).routers_trees_task = None, + _ => hat_mut!(tables).peers_trees_task = None, + }; + })); + match net_type { + WhatAmI::Router => self.routers_trees_task = task, + _ => self.peers_trees_task = task, + }; + } + } +} + +pub(crate) struct HatCode {} + +impl HatBaseTrait for HatCode { + fn init(&self, tables: &mut Tables, runtime: Runtime) { + let config = runtime.config().lock(); + let whatami = tables.whatami; + let gossip = unwrap_or_default!(config.scouting().gossip().enabled()); + let gossip_multihop = unwrap_or_default!(config.scouting().gossip().multihop()); + let autoconnect = if gossip { + *unwrap_or_default!(config.scouting().gossip().autoconnect().get(whatami)) + } else { + WhatAmIMatcher::empty() + }; + + let router_full_linkstate = whatami == WhatAmI::Router; + let peer_full_linkstate = whatami != WhatAmI::Client + && unwrap_or_default!(config.routing().peer().mode()) == *"linkstate"; + let router_peers_failover_brokering = + unwrap_or_default!(config.routing().router().peers_failover_brokering()); + drop(config); + + if router_full_linkstate | gossip { + hat_mut!(tables).routers_net = Some(Network::new( + "[Routers network]".to_string(), + tables.zid, + runtime.clone(), + router_full_linkstate, + router_peers_failover_brokering, + gossip, + gossip_multihop, + autoconnect, + )); + } + if peer_full_linkstate | gossip { + hat_mut!(tables).peers_net = Some(Network::new( + "[Peers network]".to_string(), + tables.zid, + runtime, + peer_full_linkstate, + router_peers_failover_brokering, + gossip, + gossip_multihop, + autoconnect, + )); + } + if router_full_linkstate && peer_full_linkstate { + hat_mut!(tables).shared_nodes = shared_nodes( + hat!(tables).routers_net.as_ref().unwrap(), + hat!(tables).peers_net.as_ref().unwrap(), + ); + } + } + + fn new_tables(&self, router_peers_failover_brokering: bool) -> Box { + Box::new(HatTables::new(router_peers_failover_brokering)) + } + + fn new_face(&self) -> Box { + Box::new(HatFace::new()) + } + + fn new_resource(&self) -> Box { + Box::new(HatContext::new()) + } + + fn new_local_face( + &self, + tables: &mut Tables, + _tables_ref: &Arc, + face: &mut Face, + ) -> ZResult<()> { + pubsub_new_face(tables, &mut face.state); + queries_new_face(tables, &mut face.state); + Ok(()) + } + + fn new_transport_unicast_face( + &self, + tables: &mut Tables, + tables_ref: &Arc, + face: &mut Face, + transport: &TransportUnicast, + ) -> ZResult<()> { + let link_id = match face.state.whatami { + WhatAmI::Router => hat_mut!(tables) + .routers_net + .as_mut() + .unwrap() + .add_link(transport.clone()), + WhatAmI::Peer => { + if let Some(net) = hat_mut!(tables).peers_net.as_mut() { + net.add_link(transport.clone()) + } else { + 0 + } + } + _ => 0, + }; + + if hat!(tables).full_net(WhatAmI::Router) && hat!(tables).full_net(WhatAmI::Peer) { + hat_mut!(tables).shared_nodes = shared_nodes( + hat!(tables).routers_net.as_ref().unwrap(), + hat!(tables).peers_net.as_ref().unwrap(), + ); + } + + face_hat_mut!(&mut face.state).link_id = link_id; + pubsub_new_face(tables, &mut face.state); + queries_new_face(tables, &mut face.state); + + match face.state.whatami { + WhatAmI::Router => { + hat_mut!(tables).schedule_compute_trees(tables_ref.clone(), WhatAmI::Router); + } + WhatAmI::Peer => { + if hat_mut!(tables).full_net(WhatAmI::Peer) { + hat_mut!(tables).schedule_compute_trees(tables_ref.clone(), WhatAmI::Peer); + } + } + _ => (), + } + Ok(()) + } + + fn close_face(&self, tables: &TablesLock, face: &mut Arc) { + let mut wtables = zwrite!(tables.tables); + let mut face_clone = face.clone(); + let face = get_mut_unchecked(face); + for res in face.remote_mappings.values_mut() { + get_mut_unchecked(res).session_ctxs.remove(&face.id); + Resource::clean(res); + } + face.remote_mappings.clear(); + for res in face.local_mappings.values_mut() { + get_mut_unchecked(res).session_ctxs.remove(&face.id); + Resource::clean(res); + } + face.local_mappings.clear(); + + let mut subs_matches = vec![]; + for mut res in face + .hat + .downcast_mut::() + .unwrap() + .remote_subs + .drain() + { + get_mut_unchecked(&mut res).session_ctxs.remove(&face.id); + undeclare_client_subscription(&mut wtables, &mut face_clone, &mut res); + + if res.context.is_some() { + for match_ in &res.context().matches { + let mut match_ = match_.upgrade().unwrap(); + if !Arc::ptr_eq(&match_, &res) { + get_mut_unchecked(&mut match_) + .context_mut() + .disable_data_routes(); + subs_matches.push(match_); + } + } + get_mut_unchecked(&mut res) + .context_mut() + .disable_data_routes(); + subs_matches.push(res); + } + } + + let mut qabls_matches = vec![]; + for mut res in face + .hat + .downcast_mut::() + .unwrap() + .remote_qabls + .drain() + { + get_mut_unchecked(&mut res).session_ctxs.remove(&face.id); + undeclare_client_queryable(&mut wtables, &mut face_clone, &mut res); + + if res.context.is_some() { + for match_ in &res.context().matches { + let mut match_ = match_.upgrade().unwrap(); + if !Arc::ptr_eq(&match_, &res) { + get_mut_unchecked(&mut match_) + .context_mut() + .disable_query_routes(); + qabls_matches.push(match_); + } + } + get_mut_unchecked(&mut res) + .context_mut() + .disable_query_routes(); + qabls_matches.push(res); + } + } + drop(wtables); + + let mut matches_data_routes = vec![]; + let mut matches_query_routes = vec![]; + let rtables = zread!(tables.tables); + for _match in subs_matches.drain(..) { + let mut expr = RoutingExpr::new(&_match, ""); + matches_data_routes.push(( + _match.clone(), + compute_data_routes(&rtables, &mut expr), + compute_matching_pulls(&rtables, &mut expr), + )); + } + for _match in qabls_matches.drain(..) { + matches_query_routes.push((_match.clone(), compute_query_routes(&rtables, &_match))); + } + drop(rtables); + + let mut wtables = zwrite!(tables.tables); + for (mut res, data_routes, matching_pulls) in matches_data_routes { + get_mut_unchecked(&mut res) + .context_mut() + .update_data_routes(data_routes); + get_mut_unchecked(&mut res) + .context_mut() + .update_matching_pulls(matching_pulls); + Resource::clean(&mut res); + } + for (mut res, query_routes) in matches_query_routes { + get_mut_unchecked(&mut res) + .context_mut() + .update_query_routes(query_routes); + Resource::clean(&mut res); + } + wtables.faces.remove(&face.id); + drop(wtables); + } + + fn handle_oam( + &self, + tables: &mut Tables, + tables_ref: &Arc, + oam: Oam, + transport: &TransportUnicast, + ) -> ZResult<()> { + if oam.id == OAM_LINKSTATE { + if let ZExtBody::ZBuf(buf) = oam.body { + if let Ok(zid) = transport.get_zid() { + use zenoh_buffers::reader::HasReader; + use zenoh_codec::RCodec; + let codec = Zenoh080Routing::new(); + let mut reader = buf.reader(); + let list: LinkStateList = codec.read(&mut reader).unwrap(); + + let whatami = transport.get_whatami()?; + match whatami { + WhatAmI::Router => { + for (_, removed_node) in hat_mut!(tables) + .routers_net + .as_mut() + .unwrap() + .link_states(list.link_states, zid) + .removed_nodes + { + pubsub_remove_node(tables, &removed_node.zid, WhatAmI::Router); + queries_remove_node(tables, &removed_node.zid, WhatAmI::Router); + } + + if hat!(tables).full_net(WhatAmI::Peer) { + hat_mut!(tables).shared_nodes = shared_nodes( + hat!(tables).routers_net.as_ref().unwrap(), + hat!(tables).peers_net.as_ref().unwrap(), + ); + } + + hat_mut!(tables) + .schedule_compute_trees(tables_ref.clone(), WhatAmI::Router); + } + WhatAmI::Peer => { + if let Some(net) = hat_mut!(tables).peers_net.as_mut() { + let changes = net.link_states(list.link_states, zid); + if hat!(tables).full_net(WhatAmI::Peer) { + for (_, removed_node) in changes.removed_nodes { + pubsub_remove_node( + tables, + &removed_node.zid, + WhatAmI::Peer, + ); + queries_remove_node( + tables, + &removed_node.zid, + WhatAmI::Peer, + ); + } + + hat_mut!(tables).shared_nodes = shared_nodes( + hat!(tables).routers_net.as_ref().unwrap(), + hat!(tables).peers_net.as_ref().unwrap(), + ); + + hat_mut!(tables) + .schedule_compute_trees(tables_ref.clone(), WhatAmI::Peer); + } else { + for (_, updated_node) in changes.updated_nodes { + pubsub_linkstate_change( + tables, + &updated_node.zid, + &updated_node.links, + ); + queries_linkstate_change( + tables, + &updated_node.zid, + &updated_node.links, + ); + } + } + } + } + _ => (), + }; + } + } + } + + Ok(()) + } + + #[inline] + fn map_routing_context( + &self, + tables: &Tables, + face: &FaceState, + routing_context: NodeId, + ) -> NodeId { + match face.whatami { + WhatAmI::Router => hat!(tables) + .routers_net + .as_ref() + .unwrap() + .get_local_context(routing_context, face_hat!(face).link_id), + WhatAmI::Peer => { + if hat!(tables).full_net(WhatAmI::Peer) { + hat!(tables) + .peers_net + .as_ref() + .unwrap() + .get_local_context(routing_context, face_hat!(face).link_id) + } else { + 0 + } + } + _ => 0, + } + } + + fn closing( + &self, + tables: &mut Tables, + tables_ref: &Arc, + transport: &TransportUnicast, + ) -> ZResult<()> { + match (transport.get_zid(), transport.get_whatami()) { + (Ok(zid), Ok(whatami)) => { + match whatami { + WhatAmI::Router => { + for (_, removed_node) in hat_mut!(tables) + .routers_net + .as_mut() + .unwrap() + .remove_link(&zid) + { + pubsub_remove_node(tables, &removed_node.zid, WhatAmI::Router); + queries_remove_node(tables, &removed_node.zid, WhatAmI::Router); + } + + if hat!(tables).full_net(WhatAmI::Peer) { + hat_mut!(tables).shared_nodes = shared_nodes( + hat!(tables).routers_net.as_ref().unwrap(), + hat!(tables).peers_net.as_ref().unwrap(), + ); + } + + hat_mut!(tables) + .schedule_compute_trees(tables_ref.clone(), WhatAmI::Router); + } + WhatAmI::Peer => { + if hat!(tables).full_net(WhatAmI::Peer) { + for (_, removed_node) in hat_mut!(tables) + .peers_net + .as_mut() + .unwrap() + .remove_link(&zid) + { + pubsub_remove_node(tables, &removed_node.zid, WhatAmI::Peer); + queries_remove_node(tables, &removed_node.zid, WhatAmI::Peer); + } + + hat_mut!(tables).shared_nodes = shared_nodes( + hat!(tables).routers_net.as_ref().unwrap(), + hat!(tables).peers_net.as_ref().unwrap(), + ); + + hat_mut!(tables) + .schedule_compute_trees(tables_ref.clone(), WhatAmI::Peer); + } else if let Some(net) = hat_mut!(tables).peers_net.as_mut() { + net.remove_link(&zid); + } + } + _ => (), + }; + } + (_, _) => log::error!("Closed transport in session closing!"), + } + Ok(()) + } + + fn as_any(&self) -> &dyn Any { + self + } + + #[inline] + fn ingress_filter(&self, tables: &Tables, face: &FaceState, expr: &mut RoutingExpr) -> bool { + face.whatami != WhatAmI::Peer + || hat!(tables).peers_net.is_none() + || tables.zid + == *hat!(tables).elect_router( + &tables.zid, + expr.full_expr(), + hat!(tables).get_router_links(face.zid), + ) + } + + #[inline] + fn egress_filter( + &self, + tables: &Tables, + src_face: &FaceState, + out_face: &Arc, + expr: &mut RoutingExpr, + ) -> bool { + if src_face.id != out_face.id + && match (src_face.mcast_group.as_ref(), out_face.mcast_group.as_ref()) { + (Some(l), Some(r)) => l != r, + _ => true, + } + { + let dst_master = out_face.whatami != WhatAmI::Peer + || hat!(tables).peers_net.is_none() + || tables.zid + == *hat!(tables).elect_router( + &tables.zid, + expr.full_expr(), + hat!(tables).get_router_links(out_face.zid), + ); + + return dst_master + && (src_face.whatami != WhatAmI::Peer + || out_face.whatami != WhatAmI::Peer + || hat!(tables).full_net(WhatAmI::Peer) + || hat!(tables).failover_brokering(src_face.zid, out_face.zid)); + } + false + } + + fn info(&self, tables: &Tables, kind: WhatAmI) -> String { + match kind { + WhatAmI::Router => hat!(tables) + .routers_net + .as_ref() + .map(|net| net.dot()) + .unwrap_or_else(|| "graph {}".to_string()), + WhatAmI::Peer => hat!(tables) + .peers_net + .as_ref() + .map(|net| net.dot()) + .unwrap_or_else(|| "graph {}".to_string()), + _ => "graph {}".to_string(), + } + } +} + +struct HatContext { + router_subs: HashSet, + peer_subs: HashSet, + router_qabls: HashMap, + peer_qabls: HashMap, +} + +impl HatContext { + fn new() -> Self { + Self { + router_subs: HashSet::new(), + peer_subs: HashSet::new(), + router_qabls: HashMap::new(), + peer_qabls: HashMap::new(), + } + } +} + +struct HatFace { + link_id: usize, + local_subs: HashSet>, + remote_subs: HashSet>, + local_qabls: HashMap, QueryableInfo>, + remote_qabls: HashSet>, +} + +impl HatFace { + fn new() -> Self { + Self { + link_id: 0, + local_subs: HashSet::new(), + remote_subs: HashSet::new(), + local_qabls: HashMap::new(), + remote_qabls: HashSet::new(), + } + } +} + +fn get_router(tables: &Tables, face: &Arc, nodeid: NodeId) -> Option { + match hat!(tables) + .routers_net + .as_ref() + .unwrap() + .get_link(face_hat!(face).link_id) + { + Some(link) => match link.get_zid(&(nodeid as u64)) { + Some(router) => Some(*router), + None => { + log::error!( + "Received router declaration with unknown routing context id {}", + nodeid + ); + None + } + }, + None => { + log::error!( + "Could not find corresponding link in routers network for {}", + face + ); + None + } + } +} + +fn get_peer(tables: &Tables, face: &Arc, nodeid: NodeId) -> Option { + match hat!(tables) + .peers_net + .as_ref() + .unwrap() + .get_link(face_hat!(face).link_id) + { + Some(link) => match link.get_zid(&(nodeid as u64)) { + Some(router) => Some(*router), + None => { + log::error!( + "Received peer declaration with unknown routing context id {}", + nodeid + ); + None + } + }, + None => { + log::error!( + "Could not find corresponding link in peers network for {}", + face + ); + None + } + } +} + +impl HatTrait for HatCode {} + +#[inline] +fn get_routes_entries(tables: &Tables) -> RoutesIndexes { + let routers_indexes = hat!(tables) + .routers_net + .as_ref() + .unwrap() + .graph + .node_indices() + .map(|i| i.index() as NodeId) + .collect::>(); + let peers_indexes = if hat!(tables).full_net(WhatAmI::Peer) { + hat!(tables) + .peers_net + .as_ref() + .unwrap() + .graph + .node_indices() + .map(|i| i.index() as NodeId) + .collect::>() + } else { + vec![0] + }; + RoutesIndexes { + routers: routers_indexes, + peers: peers_indexes, + clients: vec![0], + } +} diff --git a/zenoh/src/net/routing/network.rs b/zenoh/src/net/routing/hat/router/network.rs similarity index 94% rename from zenoh/src/net/routing/network.rs rename to zenoh/src/net/routing/hat/router/network.rs index 627a472522..ccc8a55850 100644 --- a/zenoh/src/net/routing/network.rs +++ b/zenoh/src/net/routing/hat/router/network.rs @@ -13,6 +13,7 @@ // use crate::net::codec::Zenoh080Routing; use crate::net::protocol::linkstate::{LinkState, LinkStateList}; +use crate::net::routing::dispatcher::tables::NodeId; use crate::net::runtime::Runtime; use async_std::task; use petgraph::graph::NodeIndex; @@ -37,12 +38,12 @@ struct Details { } #[derive(Clone)] -pub(crate) struct Node { - pub(crate) zid: ZenohId, - pub(crate) whatami: Option, - pub(crate) locators: Option>, - pub(crate) sn: u64, - pub(crate) links: Vec, +pub(super) struct Node { + pub(super) zid: ZenohId, + pub(super) whatami: Option, + pub(super) locators: Option>, + pub(super) sn: u64, + pub(super) links: Vec, } impl std::fmt::Debug for Node { @@ -51,8 +52,8 @@ impl std::fmt::Debug for Node { } } -pub(crate) struct Link { - pub(crate) transport: TransportUnicast, +pub(super) struct Link { + pub(super) transport: TransportUnicast, zid: ZenohId, mappings: VecMap, local_mappings: VecMap, @@ -70,57 +71,57 @@ impl Link { } #[inline] - pub(crate) fn set_zid_mapping(&mut self, psid: u64, zid: ZenohId) { + pub(super) fn set_zid_mapping(&mut self, psid: u64, zid: ZenohId) { self.mappings.insert(psid.try_into().unwrap(), zid); } #[inline] - pub(crate) fn get_zid(&self, psid: &u64) -> Option<&ZenohId> { + pub(super) fn get_zid(&self, psid: &u64) -> Option<&ZenohId> { self.mappings.get((*psid).try_into().unwrap()) } #[inline] - pub(crate) fn set_local_psid_mapping(&mut self, psid: u64, local_psid: u64) { + pub(super) fn set_local_psid_mapping(&mut self, psid: u64, local_psid: u64) { self.local_mappings .insert(psid.try_into().unwrap(), local_psid); } #[inline] - pub(crate) fn get_local_psid(&self, psid: &u64) -> Option<&u64> { + pub(super) fn get_local_psid(&self, psid: &u64) -> Option<&u64> { self.local_mappings.get((*psid).try_into().unwrap()) } } -pub(crate) struct Changes { - pub(crate) updated_nodes: Vec<(NodeIndex, Node)>, - pub(crate) removed_nodes: Vec<(NodeIndex, Node)>, +pub(super) struct Changes { + pub(super) updated_nodes: Vec<(NodeIndex, Node)>, + pub(super) removed_nodes: Vec<(NodeIndex, Node)>, } #[derive(Clone)] -pub(crate) struct Tree { - pub(crate) parent: Option, - pub(crate) childs: Vec, - pub(crate) directions: Vec>, +pub(super) struct Tree { + pub(super) parent: Option, + pub(super) childs: Vec, + pub(super) directions: Vec>, } -pub(crate) struct Network { - pub(crate) name: String, - pub(crate) full_linkstate: bool, - pub(crate) router_peers_failover_brokering: bool, - pub(crate) gossip: bool, - pub(crate) gossip_multihop: bool, - pub(crate) autoconnect: WhatAmIMatcher, - pub(crate) idx: NodeIndex, - pub(crate) links: VecMap, - pub(crate) trees: Vec, - pub(crate) distances: Vec, - pub(crate) graph: petgraph::stable_graph::StableUnGraph, - pub(crate) runtime: Runtime, +pub(super) struct Network { + pub(super) name: String, + pub(super) full_linkstate: bool, + pub(super) router_peers_failover_brokering: bool, + pub(super) gossip: bool, + pub(super) gossip_multihop: bool, + pub(super) autoconnect: WhatAmIMatcher, + pub(super) idx: NodeIndex, + pub(super) links: VecMap, + pub(super) trees: Vec, + pub(super) distances: Vec, + pub(super) graph: petgraph::stable_graph::StableUnGraph, + pub(super) runtime: Runtime, } impl Network { #[allow(clippy::too_many_arguments)] - pub(crate) fn new( + pub(super) fn new( name: String, zid: ZenohId, runtime: Runtime, @@ -159,8 +160,7 @@ impl Network { } } - //noinspection ALL - pub(crate) fn dot(&self) -> String { + pub(super) fn dot(&self) -> String { std::format!( "{:?}", petgraph::dot::Dot::with_config(&self.graph, &[petgraph::dot::Config::EdgeNoLabel]) @@ -168,31 +168,31 @@ impl Network { } #[inline] - pub(crate) fn get_node(&self, zid: &ZenohId) -> Option<&Node> { + pub(super) fn get_node(&self, zid: &ZenohId) -> Option<&Node> { self.graph.node_weights().find(|weight| weight.zid == *zid) } #[inline] - pub(crate) fn get_idx(&self, zid: &ZenohId) -> Option { + pub(super) fn get_idx(&self, zid: &ZenohId) -> Option { self.graph .node_indices() .find(|idx| self.graph[*idx].zid == *zid) } #[inline] - pub(crate) fn get_link(&self, id: usize) -> Option<&Link> { + pub(super) fn get_link(&self, id: usize) -> Option<&Link> { self.links.get(id) } #[inline] - pub(crate) fn get_link_from_zid(&self, zid: &ZenohId) -> Option<&Link> { + pub(super) fn get_link_from_zid(&self, zid: &ZenohId) -> Option<&Link> { self.links.values().find(|link| link.zid == *zid) } #[inline] - pub(crate) fn get_local_context(&self, context: u64, link_id: usize) -> usize { + pub(super) fn get_local_context(&self, context: NodeId, link_id: usize) -> NodeId { match self.get_link(link_id) { - Some(link) => match link.get_local_psid(&context) { + Some(link) => match link.get_local_psid(&(context as u64)) { Some(psid) => (*psid).try_into().unwrap_or(0), None => { log::error!( @@ -340,7 +340,7 @@ impl Network { self.graph.update_edge(idx1, idx2, weight); } - pub(crate) fn link_states(&mut self, link_states: Vec, src: ZenohId) -> Changes { + pub(super) fn link_states(&mut self, link_states: Vec, src: ZenohId) -> Changes { log::trace!("{} Received from {} raw: {:?}", self.name, src, link_states); let graph = &self.graph; @@ -362,7 +362,6 @@ impl Network { }; // register psid<->zid mappings & apply mapping to nodes - #[allow(clippy::needless_collect)] // need to release borrow on self let link_states = link_states .into_iter() .filter_map(|link_state| { @@ -637,7 +636,7 @@ impl Network { // Propagate link states // Note: we need to send all states at once for each face // to avoid premature node deletion on the other side - #[allow(clippy::type_complexity)] + #[allow(clippy::type_complexity)] // This is only used here if !link_states.is_empty() { let (new_idxs, updated_idxs): ( Vec<(Vec, NodeIndex, bool)>, @@ -693,7 +692,7 @@ impl Network { } } - pub(crate) fn add_link(&mut self, transport: TransportUnicast) -> usize { + pub(super) fn add_link(&mut self, transport: TransportUnicast) -> usize { let free_index = { let mut i = 0; while self.links.contains_key(i) { @@ -805,7 +804,7 @@ impl Network { free_index } - pub(crate) fn remove_link(&mut self, zid: &ZenohId) -> Vec<(NodeIndex, Node)> { + pub(super) fn remove_link(&mut self, zid: &ZenohId) -> Vec<(NodeIndex, Node)> { log::trace!("{} remove_link {}", self.name, zid); self.links.retain(|_, link| link.zid != *zid); self.graph[self.idx].links.retain(|link| *link != *zid); @@ -884,7 +883,7 @@ impl Network { removed } - pub(crate) fn compute_trees(&mut self) -> Vec> { + pub(super) fn compute_trees(&mut self) -> Vec> { let indexes = self.graph.node_indices().collect::>(); let max_idx = indexes.iter().max().unwrap(); diff --git a/zenoh/src/net/routing/hat/router/pubsub.rs b/zenoh/src/net/routing/hat/router/pubsub.rs new file mode 100644 index 0000000000..d8b3220116 --- /dev/null +++ b/zenoh/src/net/routing/hat/router/pubsub.rs @@ -0,0 +1,1030 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// +use super::network::Network; +use super::{face_hat, face_hat_mut, get_routes_entries, hat, hat_mut, res_hat, res_hat_mut}; +use super::{get_peer, get_router, HatCode, HatContext, HatFace, HatTables}; +use crate::net::routing::dispatcher::face::FaceState; +use crate::net::routing::dispatcher::pubsub::*; +use crate::net::routing::dispatcher::resource::{NodeId, Resource, SessionContext}; +use crate::net::routing::dispatcher::tables::Tables; +use crate::net::routing::dispatcher::tables::{Route, RoutingExpr}; +use crate::net::routing::hat::HatPubSubTrait; +use crate::net::routing::router::RoutesIndexes; +use crate::net::routing::{RoutingContext, PREFIX_LIVELINESS}; +use petgraph::graph::NodeIndex; +use std::borrow::Cow; +use std::collections::{HashMap, HashSet}; +use std::sync::Arc; +use zenoh_protocol::core::key_expr::OwnedKeyExpr; +use zenoh_protocol::{ + core::{Reliability, WhatAmI, ZenohId}, + network::declare::{ + common::ext::WireExprType, ext, subscriber::ext::SubscriberInfo, Declare, DeclareBody, + DeclareSubscriber, Mode, UndeclareSubscriber, + }, +}; +use zenoh_sync::get_mut_unchecked; + +#[inline] +fn send_sourced_subscription_to_net_childs( + tables: &Tables, + net: &Network, + childs: &[NodeIndex], + res: &Arc, + src_face: Option<&Arc>, + sub_info: &SubscriberInfo, + routing_context: NodeId, +) { + for child in childs { + if net.graph.contains_node(*child) { + match tables.get_face(&net.graph[*child].zid).cloned() { + Some(mut someface) => { + if src_face.is_none() || someface.id != src_face.unwrap().id { + let key_expr = Resource::decl_key(res, &mut someface); + + log::debug!("Send subscription {} on {}", res.expr(), someface); + + someface.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType { + node_id: routing_context, + }, + body: DeclareBody::DeclareSubscriber(DeclareSubscriber { + id: 0, // @TODO use proper SubscriberId (#703) + wire_expr: key_expr, + ext_info: *sub_info, + }), + }, + res.expr(), + )); + } + } + None => log::trace!("Unable to find face for zid {}", net.graph[*child].zid), + } + } + } +} + +#[inline] +fn propagate_simple_subscription_to( + tables: &mut Tables, + dst_face: &mut Arc, + res: &Arc, + sub_info: &SubscriberInfo, + src_face: &mut Arc, + full_peer_net: bool, +) { + if (src_face.id != dst_face.id || res.expr().starts_with(PREFIX_LIVELINESS)) + && !face_hat!(dst_face).local_subs.contains(res) + && if full_peer_net { + dst_face.whatami == WhatAmI::Client + } else { + dst_face.whatami != WhatAmI::Router + && (src_face.whatami != WhatAmI::Peer + || dst_face.whatami != WhatAmI::Peer + || hat!(tables).failover_brokering(src_face.zid, dst_face.zid)) + } + { + face_hat_mut!(dst_face).local_subs.insert(res.clone()); + let key_expr = Resource::decl_key(res, dst_face); + dst_face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::DeclareSubscriber(DeclareSubscriber { + id: 0, // @TODO use proper SubscriberId (#703) + wire_expr: key_expr, + ext_info: *sub_info, + }), + }, + res.expr(), + )); + } +} + +fn propagate_simple_subscription( + tables: &mut Tables, + res: &Arc, + sub_info: &SubscriberInfo, + src_face: &mut Arc, +) { + let full_peer_net = hat!(tables).full_net(WhatAmI::Peer); + for mut dst_face in tables + .faces + .values() + .cloned() + .collect::>>() + { + propagate_simple_subscription_to( + tables, + &mut dst_face, + res, + sub_info, + src_face, + full_peer_net, + ); + } +} + +fn propagate_sourced_subscription( + tables: &Tables, + res: &Arc, + sub_info: &SubscriberInfo, + src_face: Option<&Arc>, + source: &ZenohId, + net_type: WhatAmI, +) { + let net = hat!(tables).get_net(net_type).unwrap(); + match net.get_idx(source) { + Some(tree_sid) => { + if net.trees.len() > tree_sid.index() { + send_sourced_subscription_to_net_childs( + tables, + net, + &net.trees[tree_sid.index()].childs, + res, + src_face, + sub_info, + tree_sid.index() as NodeId, + ); + } else { + log::trace!( + "Propagating sub {}: tree for node {} sid:{} not yet ready", + res.expr(), + tree_sid.index(), + source + ); + } + } + None => log::error!( + "Error propagating sub {}: cannot get index of {}!", + res.expr(), + source + ), + } +} + +fn register_router_subscription( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + sub_info: &SubscriberInfo, + router: ZenohId, +) { + if !res_hat!(res).router_subs.contains(&router) { + // Register router subscription + { + log::debug!( + "Register router subscription {} (router: {})", + res.expr(), + router + ); + res_hat_mut!(res).router_subs.insert(router); + hat_mut!(tables).router_subs.insert(res.clone()); + } + + // Propagate subscription to routers + propagate_sourced_subscription(tables, res, sub_info, Some(face), &router, WhatAmI::Router); + } + // Propagate subscription to peers + if hat!(tables).full_net(WhatAmI::Peer) && face.whatami != WhatAmI::Peer { + register_peer_subscription(tables, face, res, sub_info, tables.zid) + } + + // Propagate subscription to clients + propagate_simple_subscription(tables, res, sub_info, face); +} + +fn declare_router_subscription( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + sub_info: &SubscriberInfo, + router: ZenohId, +) { + register_router_subscription(tables, face, res, sub_info, router); +} + +fn register_peer_subscription( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + sub_info: &SubscriberInfo, + peer: ZenohId, +) { + if !res_hat!(res).peer_subs.contains(&peer) { + // Register peer subscription + { + log::debug!("Register peer subscription {} (peer: {})", res.expr(), peer); + res_hat_mut!(res).peer_subs.insert(peer); + hat_mut!(tables).peer_subs.insert(res.clone()); + } + + // Propagate subscription to peers + propagate_sourced_subscription(tables, res, sub_info, Some(face), &peer, WhatAmI::Peer); + } +} + +fn declare_peer_subscription( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + sub_info: &SubscriberInfo, + peer: ZenohId, +) { + register_peer_subscription(tables, face, res, sub_info, peer); + let mut propa_sub_info = *sub_info; + propa_sub_info.mode = Mode::Push; + let zid = tables.zid; + register_router_subscription(tables, face, res, &propa_sub_info, zid); +} + +fn register_client_subscription( + _tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + sub_info: &SubscriberInfo, +) { + // Register subscription + { + let res = get_mut_unchecked(res); + log::debug!("Register subscription {} for {}", res.expr(), face); + match res.session_ctxs.get_mut(&face.id) { + Some(ctx) => match &ctx.subs { + Some(info) => { + if Mode::Pull == info.mode { + get_mut_unchecked(ctx).subs = Some(*sub_info); + } + } + None => { + get_mut_unchecked(ctx).subs = Some(*sub_info); + } + }, + None => { + res.session_ctxs.insert( + face.id, + Arc::new(SessionContext { + face: face.clone(), + local_expr_id: None, + remote_expr_id: None, + subs: Some(*sub_info), + qabl: None, + last_values: HashMap::new(), + }), + ); + } + } + } + face_hat_mut!(face).remote_subs.insert(res.clone()); +} + +fn declare_client_subscription( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + sub_info: &SubscriberInfo, +) { + register_client_subscription(tables, face, res, sub_info); + let mut propa_sub_info = *sub_info; + propa_sub_info.mode = Mode::Push; + let zid = tables.zid; + register_router_subscription(tables, face, res, &propa_sub_info, zid); +} + +#[inline] +fn remote_router_subs(tables: &Tables, res: &Arc) -> bool { + res.context.is_some() + && res_hat!(res) + .router_subs + .iter() + .any(|peer| peer != &tables.zid) +} + +#[inline] +fn remote_peer_subs(tables: &Tables, res: &Arc) -> bool { + res.context.is_some() + && res_hat!(res) + .peer_subs + .iter() + .any(|peer| peer != &tables.zid) +} + +#[inline] +fn client_subs(res: &Arc) -> Vec> { + res.session_ctxs + .values() + .filter_map(|ctx| { + if ctx.subs.is_some() { + Some(ctx.face.clone()) + } else { + None + } + }) + .collect() +} + +#[inline] +fn send_forget_sourced_subscription_to_net_childs( + tables: &Tables, + net: &Network, + childs: &[NodeIndex], + res: &Arc, + src_face: Option<&Arc>, + routing_context: Option, +) { + for child in childs { + if net.graph.contains_node(*child) { + match tables.get_face(&net.graph[*child].zid).cloned() { + Some(mut someface) => { + if src_face.is_none() || someface.id != src_face.unwrap().id { + let wire_expr = Resource::decl_key(res, &mut someface); + + log::debug!("Send forget subscription {} on {}", res.expr(), someface); + + someface.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType { + node_id: routing_context.unwrap_or(0), + }, + body: DeclareBody::UndeclareSubscriber(UndeclareSubscriber { + id: 0, // @TODO use proper SubscriberId (#703) + ext_wire_expr: WireExprType { wire_expr }, + }), + }, + res.expr(), + )); + } + } + None => log::trace!("Unable to find face for zid {}", net.graph[*child].zid), + } + } + } +} + +fn propagate_forget_simple_subscription(tables: &mut Tables, res: &Arc) { + for face in tables.faces.values_mut() { + if face_hat!(face).local_subs.contains(res) { + let wire_expr = Resource::get_best_key(res, "", face.id); + face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::UndeclareSubscriber(UndeclareSubscriber { + id: 0, // @TODO use proper SubscriberId (#703) + ext_wire_expr: WireExprType { wire_expr }, + }), + }, + res.expr(), + )); + face_hat_mut!(face).local_subs.remove(res); + } + } +} + +fn propagate_forget_simple_subscription_to_peers(tables: &mut Tables, res: &Arc) { + if !hat!(tables).full_net(WhatAmI::Peer) + && res_hat!(res).router_subs.len() == 1 + && res_hat!(res).router_subs.contains(&tables.zid) + { + for mut face in tables + .faces + .values() + .cloned() + .collect::>>() + { + if face.whatami == WhatAmI::Peer + && face_hat!(face).local_subs.contains(res) + && !res.session_ctxs.values().any(|s| { + face.zid != s.face.zid + && s.subs.is_some() + && (s.face.whatami == WhatAmI::Client + || (s.face.whatami == WhatAmI::Peer + && hat!(tables).failover_brokering(s.face.zid, face.zid))) + }) + { + let wire_expr = Resource::get_best_key(res, "", face.id); + face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::UndeclareSubscriber(UndeclareSubscriber { + id: 0, // @TODO use proper SubscriberId (#703) + ext_wire_expr: WireExprType { wire_expr }, + }), + }, + res.expr(), + )); + + face_hat_mut!(&mut face).local_subs.remove(res); + } + } + } +} + +fn propagate_forget_sourced_subscription( + tables: &Tables, + res: &Arc, + src_face: Option<&Arc>, + source: &ZenohId, + net_type: WhatAmI, +) { + let net = hat!(tables).get_net(net_type).unwrap(); + match net.get_idx(source) { + Some(tree_sid) => { + if net.trees.len() > tree_sid.index() { + send_forget_sourced_subscription_to_net_childs( + tables, + net, + &net.trees[tree_sid.index()].childs, + res, + src_face, + Some(tree_sid.index() as NodeId), + ); + } else { + log::trace!( + "Propagating forget sub {}: tree for node {} sid:{} not yet ready", + res.expr(), + tree_sid.index(), + source + ); + } + } + None => log::error!( + "Error propagating forget sub {}: cannot get index of {}!", + res.expr(), + source + ), + } +} + +fn unregister_router_subscription(tables: &mut Tables, res: &mut Arc, router: &ZenohId) { + log::debug!( + "Unregister router subscription {} (router: {})", + res.expr(), + router + ); + res_hat_mut!(res).router_subs.retain(|sub| sub != router); + + if res_hat!(res).router_subs.is_empty() { + hat_mut!(tables) + .router_subs + .retain(|sub| !Arc::ptr_eq(sub, res)); + + if hat_mut!(tables).full_net(WhatAmI::Peer) { + undeclare_peer_subscription(tables, None, res, &tables.zid.clone()); + } + propagate_forget_simple_subscription(tables, res); + } + + propagate_forget_simple_subscription_to_peers(tables, res); +} + +fn undeclare_router_subscription( + tables: &mut Tables, + face: Option<&Arc>, + res: &mut Arc, + router: &ZenohId, +) { + if res_hat!(res).router_subs.contains(router) { + unregister_router_subscription(tables, res, router); + propagate_forget_sourced_subscription(tables, res, face, router, WhatAmI::Router); + } +} + +fn forget_router_subscription( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + router: &ZenohId, +) { + undeclare_router_subscription(tables, Some(face), res, router); +} + +fn unregister_peer_subscription(tables: &mut Tables, res: &mut Arc, peer: &ZenohId) { + log::debug!( + "Unregister peer subscription {} (peer: {})", + res.expr(), + peer + ); + res_hat_mut!(res).peer_subs.retain(|sub| sub != peer); + + if res_hat!(res).peer_subs.is_empty() { + hat_mut!(tables) + .peer_subs + .retain(|sub| !Arc::ptr_eq(sub, res)); + } +} + +fn undeclare_peer_subscription( + tables: &mut Tables, + face: Option<&Arc>, + res: &mut Arc, + peer: &ZenohId, +) { + if res_hat!(res).peer_subs.contains(peer) { + unregister_peer_subscription(tables, res, peer); + propagate_forget_sourced_subscription(tables, res, face, peer, WhatAmI::Peer); + } +} + +fn forget_peer_subscription( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + peer: &ZenohId, +) { + undeclare_peer_subscription(tables, Some(face), res, peer); + let client_subs = res.session_ctxs.values().any(|ctx| ctx.subs.is_some()); + let peer_subs = remote_peer_subs(tables, res); + let zid = tables.zid; + if !client_subs && !peer_subs { + undeclare_router_subscription(tables, None, res, &zid); + } +} + +pub(super) fn undeclare_client_subscription( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, +) { + log::debug!("Unregister client subscription {} for {}", res.expr(), face); + if let Some(ctx) = get_mut_unchecked(res).session_ctxs.get_mut(&face.id) { + get_mut_unchecked(ctx).subs = None; + } + face_hat_mut!(face).remote_subs.remove(res); + + let mut client_subs = client_subs(res); + let router_subs = remote_router_subs(tables, res); + let peer_subs = remote_peer_subs(tables, res); + if client_subs.is_empty() && !peer_subs { + undeclare_router_subscription(tables, None, res, &tables.zid.clone()); + } else { + propagate_forget_simple_subscription_to_peers(tables, res); + } + if client_subs.len() == 1 && !router_subs && !peer_subs { + let face = &mut client_subs[0]; + if face_hat!(face).local_subs.contains(res) + && !(face.whatami == WhatAmI::Client && res.expr().starts_with(PREFIX_LIVELINESS)) + { + let wire_expr = Resource::get_best_key(res, "", face.id); + face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::UndeclareSubscriber(UndeclareSubscriber { + id: 0, // @TODO use proper SubscriberId (#703) + ext_wire_expr: WireExprType { wire_expr }, + }), + }, + res.expr(), + )); + + face_hat_mut!(face).local_subs.remove(res); + } + } +} + +fn forget_client_subscription( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, +) { + undeclare_client_subscription(tables, face, res); +} + +pub(super) fn pubsub_new_face(tables: &mut Tables, face: &mut Arc) { + let sub_info = SubscriberInfo { + reliability: Reliability::Reliable, // @TODO compute proper reliability to propagate from reliability of known subscribers + mode: Mode::Push, + }; + + if face.whatami == WhatAmI::Client { + for sub in &hat!(tables).router_subs { + face_hat_mut!(face).local_subs.insert(sub.clone()); + let key_expr = Resource::decl_key(sub, face); + face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::DeclareSubscriber(DeclareSubscriber { + id: 0, // @TODO use proper SubscriberId (#703) + wire_expr: key_expr, + ext_info: sub_info, + }), + }, + sub.expr(), + )); + } + } else if face.whatami == WhatAmI::Peer && !hat!(tables).full_net(WhatAmI::Peer) { + for sub in &hat!(tables).router_subs { + if sub.context.is_some() + && (res_hat!(sub).router_subs.iter().any(|r| *r != tables.zid) + || sub.session_ctxs.values().any(|s| { + s.subs.is_some() + && (s.face.whatami == WhatAmI::Client + || (s.face.whatami == WhatAmI::Peer + && hat!(tables).failover_brokering(s.face.zid, face.zid))) + })) + { + face_hat_mut!(face).local_subs.insert(sub.clone()); + let key_expr = Resource::decl_key(sub, face); + face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::DeclareSubscriber(DeclareSubscriber { + id: 0, // @TODO use proper SubscriberId (#703) + wire_expr: key_expr, + ext_info: sub_info, + }), + }, + sub.expr(), + )); + } + } + } +} + +pub(super) fn pubsub_remove_node(tables: &mut Tables, node: &ZenohId, net_type: WhatAmI) { + match net_type { + WhatAmI::Router => { + for mut res in hat!(tables) + .router_subs + .iter() + .filter(|res| res_hat!(res).router_subs.contains(node)) + .cloned() + .collect::>>() + { + unregister_router_subscription(tables, &mut res, node); + + update_matches_data_routes(tables, &mut res); + Resource::clean(&mut res) + } + } + WhatAmI::Peer => { + for mut res in hat!(tables) + .peer_subs + .iter() + .filter(|res| res_hat!(res).peer_subs.contains(node)) + .cloned() + .collect::>>() + { + unregister_peer_subscription(tables, &mut res, node); + let client_subs = res.session_ctxs.values().any(|ctx| ctx.subs.is_some()); + let peer_subs = remote_peer_subs(tables, &res); + if !client_subs && !peer_subs { + undeclare_router_subscription(tables, None, &mut res, &tables.zid.clone()); + } + + update_matches_data_routes(tables, &mut res); + Resource::clean(&mut res) + } + } + _ => (), + } +} + +pub(super) fn pubsub_tree_change( + tables: &mut Tables, + new_childs: &[Vec], + net_type: WhatAmI, +) { + // propagate subs to new childs + for (tree_sid, tree_childs) in new_childs.iter().enumerate() { + if !tree_childs.is_empty() { + let net = hat!(tables).get_net(net_type).unwrap(); + let tree_idx = NodeIndex::new(tree_sid); + if net.graph.contains_node(tree_idx) { + let tree_id = net.graph[tree_idx].zid; + + let subs_res = match net_type { + WhatAmI::Router => &hat!(tables).router_subs, + _ => &hat!(tables).peer_subs, + }; + + for res in subs_res { + let subs = match net_type { + WhatAmI::Router => &res_hat!(res).router_subs, + _ => &res_hat!(res).peer_subs, + }; + for sub in subs { + if *sub == tree_id { + let sub_info = SubscriberInfo { + reliability: Reliability::Reliable, // @TODO compute proper reliability to propagate from reliability of known subscribers + mode: Mode::Push, + }; + send_sourced_subscription_to_net_childs( + tables, + net, + tree_childs, + res, + None, + &sub_info, + tree_sid as NodeId, + ); + } + } + } + } + } + } + + // recompute routes + update_data_routes_from(tables, &mut tables.root_res.clone()); +} + +pub(super) fn pubsub_linkstate_change(tables: &mut Tables, zid: &ZenohId, links: &[ZenohId]) { + if let Some(src_face) = tables.get_face(zid).cloned() { + if hat!(tables).router_peers_failover_brokering && src_face.whatami == WhatAmI::Peer { + for res in &face_hat!(src_face).remote_subs { + let client_subs = res + .session_ctxs + .values() + .any(|ctx| ctx.face.whatami == WhatAmI::Client && ctx.subs.is_some()); + if !remote_router_subs(tables, res) && !client_subs { + for ctx in get_mut_unchecked(&mut res.clone()) + .session_ctxs + .values_mut() + { + let dst_face = &mut get_mut_unchecked(ctx).face; + if dst_face.whatami == WhatAmI::Peer && src_face.zid != dst_face.zid { + if face_hat!(dst_face).local_subs.contains(res) { + let forget = !HatTables::failover_brokering_to(links, dst_face.zid) + && { + let ctx_links = hat!(tables) + .peers_net + .as_ref() + .map(|net| net.get_links(dst_face.zid)) + .unwrap_or_else(|| &[]); + res.session_ctxs.values().any(|ctx2| { + ctx2.face.whatami == WhatAmI::Peer + && ctx2.subs.is_some() + && HatTables::failover_brokering_to( + ctx_links, + ctx2.face.zid, + ) + }) + }; + if forget { + let wire_expr = Resource::get_best_key(res, "", dst_face.id); + dst_face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::UndeclareSubscriber( + UndeclareSubscriber { + id: 0, // @TODO use proper SubscriberId (#703) + ext_wire_expr: WireExprType { wire_expr }, + }, + ), + }, + res.expr(), + )); + + face_hat_mut!(dst_face).local_subs.remove(res); + } + } else if HatTables::failover_brokering_to(links, ctx.face.zid) { + let dst_face = &mut get_mut_unchecked(ctx).face; + face_hat_mut!(dst_face).local_subs.insert(res.clone()); + let key_expr = Resource::decl_key(res, dst_face); + let sub_info = SubscriberInfo { + reliability: Reliability::Reliable, // @TODO compute proper reliability to propagate from reliability of known subscribers + mode: Mode::Push, + }; + dst_face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::DeclareSubscriber(DeclareSubscriber { + id: 0, // @TODO use proper SubscriberId (#703) + wire_expr: key_expr, + ext_info: sub_info, + }), + }, + res.expr(), + )); + } + } + } + } + } + } + } +} + +#[inline] +fn insert_faces_for_subs( + route: &mut Route, + expr: &RoutingExpr, + tables: &Tables, + net: &Network, + source: NodeId, + subs: &HashSet, +) { + if net.trees.len() > source as usize { + for sub in subs { + if let Some(sub_idx) = net.get_idx(sub) { + if net.trees[source as usize].directions.len() > sub_idx.index() { + if let Some(direction) = net.trees[source as usize].directions[sub_idx.index()] + { + if net.graph.contains_node(direction) { + if let Some(face) = tables.get_face(&net.graph[direction].zid) { + route.entry(face.id).or_insert_with(|| { + let key_expr = + Resource::get_best_key(expr.prefix, expr.suffix, face.id); + (face.clone(), key_expr.to_owned(), source) + }); + } + } + } + } + } + } + } else { + log::trace!("Tree for node sid:{} not yet ready", source); + } +} + +impl HatPubSubTrait for HatCode { + fn declare_subscription( + &self, + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + sub_info: &SubscriberInfo, + node_id: NodeId, + ) { + match face.whatami { + WhatAmI::Router => { + if let Some(router) = get_router(tables, face, node_id) { + declare_router_subscription(tables, face, res, sub_info, router) + } + } + WhatAmI::Peer => { + if hat!(tables).full_net(WhatAmI::Peer) { + if let Some(peer) = get_peer(tables, face, node_id) { + declare_peer_subscription(tables, face, res, sub_info, peer) + } + } else { + declare_client_subscription(tables, face, res, sub_info) + } + } + _ => declare_client_subscription(tables, face, res, sub_info), + } + } + + fn undeclare_subscription( + &self, + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + node_id: NodeId, + ) { + match face.whatami { + WhatAmI::Router => { + if let Some(router) = get_router(tables, face, node_id) { + forget_router_subscription(tables, face, res, &router) + } + } + WhatAmI::Peer => { + if hat!(tables).full_net(WhatAmI::Peer) { + if let Some(peer) = get_peer(tables, face, node_id) { + forget_peer_subscription(tables, face, res, &peer) + } + } else { + forget_client_subscription(tables, face, res) + } + } + _ => forget_client_subscription(tables, face, res), + } + } + + fn get_subscriptions(&self, tables: &Tables) -> Vec> { + hat!(tables).router_subs.iter().cloned().collect() + } + + fn compute_data_route( + &self, + tables: &Tables, + expr: &mut RoutingExpr, + source: NodeId, + source_type: WhatAmI, + ) -> Arc { + let mut route = HashMap::new(); + let key_expr = expr.full_expr(); + if key_expr.ends_with('/') { + return Arc::new(route); + } + log::trace!( + "compute_data_route({}, {:?}, {:?})", + key_expr, + source, + source_type + ); + let key_expr = match OwnedKeyExpr::try_from(key_expr) { + Ok(ke) => ke, + Err(e) => { + log::warn!("Invalid KE reached the system: {}", e); + return Arc::new(route); + } + }; + let res = Resource::get_resource(expr.prefix, expr.suffix); + let matches = res + .as_ref() + .and_then(|res| res.context.as_ref()) + .map(|ctx| Cow::from(&ctx.matches)) + .unwrap_or_else(|| Cow::from(Resource::get_matches(tables, &key_expr))); + + let master = !hat!(tables).full_net(WhatAmI::Peer) + || *hat!(tables).elect_router(&tables.zid, &key_expr, hat!(tables).shared_nodes.iter()) + == tables.zid; + + for mres in matches.iter() { + let mres = mres.upgrade().unwrap(); + + if master || source_type == WhatAmI::Router { + let net = hat!(tables).routers_net.as_ref().unwrap(); + let router_source = match source_type { + WhatAmI::Router => source, + _ => net.idx.index() as NodeId, + }; + insert_faces_for_subs( + &mut route, + expr, + tables, + net, + router_source, + &res_hat!(mres).router_subs, + ); + } + + if (master || source_type != WhatAmI::Router) && hat!(tables).full_net(WhatAmI::Peer) { + let net = hat!(tables).peers_net.as_ref().unwrap(); + let peer_source = match source_type { + WhatAmI::Peer => source, + _ => net.idx.index() as NodeId, + }; + insert_faces_for_subs( + &mut route, + expr, + tables, + net, + peer_source, + &res_hat!(mres).peer_subs, + ); + } + + if master || source_type == WhatAmI::Router { + for (sid, context) in &mres.session_ctxs { + if let Some(subinfo) = &context.subs { + if context.face.whatami != WhatAmI::Router && subinfo.mode == Mode::Push { + route.entry(*sid).or_insert_with(|| { + let key_expr = + Resource::get_best_key(expr.prefix, expr.suffix, *sid); + (context.face.clone(), key_expr.to_owned(), NodeId::default()) + }); + } + } + } + } + } + for mcast_group in &tables.mcast_groups { + route.insert( + mcast_group.id, + ( + mcast_group.clone(), + expr.full_expr().to_string().into(), + NodeId::default(), + ), + ); + } + Arc::new(route) + } + + fn get_data_routes_entries(&self, tables: &Tables) -> RoutesIndexes { + get_routes_entries(tables) + } +} diff --git a/zenoh/src/net/routing/hat/router/queries.rs b/zenoh/src/net/routing/hat/router/queries.rs new file mode 100644 index 0000000000..90944a524f --- /dev/null +++ b/zenoh/src/net/routing/hat/router/queries.rs @@ -0,0 +1,1223 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// +use super::network::Network; +use super::{face_hat, face_hat_mut, get_routes_entries, hat, hat_mut, res_hat, res_hat_mut}; +use super::{get_peer, get_router, HatCode, HatContext, HatFace, HatTables}; +use crate::net::routing::dispatcher::face::FaceState; +use crate::net::routing::dispatcher::queries::*; +use crate::net::routing::dispatcher::resource::{NodeId, Resource, SessionContext}; +use crate::net::routing::dispatcher::tables::Tables; +use crate::net::routing::dispatcher::tables::{QueryTargetQabl, QueryTargetQablSet, RoutingExpr}; +use crate::net::routing::hat::HatQueriesTrait; +use crate::net::routing::router::RoutesIndexes; +use crate::net::routing::{RoutingContext, PREFIX_LIVELINESS}; +use ordered_float::OrderedFloat; +use petgraph::graph::NodeIndex; +use std::borrow::Cow; +use std::collections::HashMap; +use std::sync::Arc; +use zenoh_buffers::ZBuf; +use zenoh_protocol::core::key_expr::include::{Includer, DEFAULT_INCLUDER}; +use zenoh_protocol::core::key_expr::OwnedKeyExpr; +use zenoh_protocol::{ + core::{WhatAmI, WireExpr, ZenohId}, + network::declare::{ + common::ext::WireExprType, ext, queryable::ext::QueryableInfo, Declare, DeclareBody, + DeclareQueryable, UndeclareQueryable, + }, +}; +use zenoh_sync::get_mut_unchecked; + +#[cfg(feature = "complete_n")] +#[inline] +fn merge_qabl_infos(mut this: QueryableInfo, info: &QueryableInfo) -> QueryableInfo { + this.complete += info.complete; + this.distance = std::cmp::min(this.distance, info.distance); + this +} + +#[cfg(not(feature = "complete_n"))] +#[inline] +fn merge_qabl_infos(mut this: QueryableInfo, info: &QueryableInfo) -> QueryableInfo { + this.complete = u8::from(this.complete != 0 || info.complete != 0); + this.distance = std::cmp::min(this.distance, info.distance); + this +} + +fn local_router_qabl_info(tables: &Tables, res: &Arc) -> QueryableInfo { + let info = if hat!(tables).full_net(WhatAmI::Peer) { + res.context.as_ref().and_then(|_| { + res_hat!(res) + .peer_qabls + .iter() + .fold(None, |accu, (zid, info)| { + if *zid != tables.zid { + Some(match accu { + Some(accu) => merge_qabl_infos(accu, info), + None => *info, + }) + } else { + accu + } + }) + }) + } else { + None + }; + res.session_ctxs + .values() + .fold(info, |accu, ctx| { + if let Some(info) = ctx.qabl.as_ref() { + Some(match accu { + Some(accu) => merge_qabl_infos(accu, info), + None => *info, + }) + } else { + accu + } + }) + .unwrap_or(QueryableInfo { + complete: 0, + distance: 0, + }) +} + +fn local_peer_qabl_info(tables: &Tables, res: &Arc) -> QueryableInfo { + let info = if res.context.is_some() { + res_hat!(res) + .router_qabls + .iter() + .fold(None, |accu, (zid, info)| { + if *zid != tables.zid { + Some(match accu { + Some(accu) => merge_qabl_infos(accu, info), + None => *info, + }) + } else { + accu + } + }) + } else { + None + }; + res.session_ctxs + .values() + .fold(info, |accu, ctx| { + if let Some(info) = ctx.qabl.as_ref() { + Some(match accu { + Some(accu) => merge_qabl_infos(accu, info), + None => *info, + }) + } else { + accu + } + }) + .unwrap_or(QueryableInfo { + complete: 0, + distance: 0, + }) +} + +fn local_qabl_info(tables: &Tables, res: &Arc, face: &Arc) -> QueryableInfo { + let mut info = if res.context.is_some() { + res_hat!(res) + .router_qabls + .iter() + .fold(None, |accu, (zid, info)| { + if *zid != tables.zid { + Some(match accu { + Some(accu) => merge_qabl_infos(accu, info), + None => *info, + }) + } else { + accu + } + }) + } else { + None + }; + if res.context.is_some() && hat!(tables).full_net(WhatAmI::Peer) { + info = res_hat!(res) + .peer_qabls + .iter() + .fold(info, |accu, (zid, info)| { + if *zid != tables.zid { + Some(match accu { + Some(accu) => merge_qabl_infos(accu, info), + None => *info, + }) + } else { + accu + } + }) + } + res.session_ctxs + .values() + .fold(info, |accu, ctx| { + if ctx.face.id != face.id && ctx.face.whatami != WhatAmI::Peer + || face.whatami != WhatAmI::Peer + || hat!(tables).failover_brokering(ctx.face.zid, face.zid) + { + if let Some(info) = ctx.qabl.as_ref() { + Some(match accu { + Some(accu) => merge_qabl_infos(accu, info), + None => *info, + }) + } else { + accu + } + } else { + accu + } + }) + .unwrap_or(QueryableInfo { + complete: 0, + distance: 0, + }) +} + +#[inline] +fn send_sourced_queryable_to_net_childs( + tables: &Tables, + net: &Network, + childs: &[NodeIndex], + res: &Arc, + qabl_info: &QueryableInfo, + src_face: Option<&mut Arc>, + routing_context: NodeId, +) { + for child in childs { + if net.graph.contains_node(*child) { + match tables.get_face(&net.graph[*child].zid).cloned() { + Some(mut someface) => { + if src_face.is_none() || someface.id != src_face.as_ref().unwrap().id { + let key_expr = Resource::decl_key(res, &mut someface); + + log::debug!("Send queryable {} on {}", res.expr(), someface); + + someface.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType { + node_id: routing_context, + }, + body: DeclareBody::DeclareQueryable(DeclareQueryable { + id: 0, // @TODO use proper QueryableId (#703) + wire_expr: key_expr, + ext_info: *qabl_info, + }), + }, + res.expr(), + )); + } + } + None => log::trace!("Unable to find face for zid {}", net.graph[*child].zid), + } + } + } +} + +fn propagate_simple_queryable( + tables: &mut Tables, + res: &Arc, + src_face: Option<&mut Arc>, +) { + let full_peers_net = hat!(tables).full_net(WhatAmI::Peer); + let faces = tables.faces.values().cloned(); + for mut dst_face in faces { + let info = local_qabl_info(tables, res, &dst_face); + let current_info = face_hat!(dst_face).local_qabls.get(res); + if (src_face.is_none() || src_face.as_ref().unwrap().id != dst_face.id) + && (current_info.is_none() || *current_info.unwrap() != info) + && if full_peers_net { + dst_face.whatami == WhatAmI::Client + } else { + dst_face.whatami != WhatAmI::Router + && (src_face.is_none() + || src_face.as_ref().unwrap().whatami != WhatAmI::Peer + || dst_face.whatami != WhatAmI::Peer + || hat!(tables) + .failover_brokering(src_face.as_ref().unwrap().zid, dst_face.zid)) + } + { + face_hat_mut!(&mut dst_face) + .local_qabls + .insert(res.clone(), info); + let key_expr = Resource::decl_key(res, &mut dst_face); + dst_face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::DeclareQueryable(DeclareQueryable { + id: 0, // @TODO use proper QueryableId (#703) + wire_expr: key_expr, + ext_info: info, + }), + }, + res.expr(), + )); + } + } +} + +fn propagate_sourced_queryable( + tables: &Tables, + res: &Arc, + qabl_info: &QueryableInfo, + src_face: Option<&mut Arc>, + source: &ZenohId, + net_type: WhatAmI, +) { + let net = hat!(tables).get_net(net_type).unwrap(); + match net.get_idx(source) { + Some(tree_sid) => { + if net.trees.len() > tree_sid.index() { + send_sourced_queryable_to_net_childs( + tables, + net, + &net.trees[tree_sid.index()].childs, + res, + qabl_info, + src_face, + tree_sid.index() as NodeId, + ); + } else { + log::trace!( + "Propagating qabl {}: tree for node {} sid:{} not yet ready", + res.expr(), + tree_sid.index(), + source + ); + } + } + None => log::error!( + "Error propagating qabl {}: cannot get index of {}!", + res.expr(), + source + ), + } +} + +fn register_router_queryable( + tables: &mut Tables, + mut face: Option<&mut Arc>, + res: &mut Arc, + qabl_info: &QueryableInfo, + router: ZenohId, +) { + let current_info = res_hat!(res).router_qabls.get(&router); + if current_info.is_none() || current_info.unwrap() != qabl_info { + // Register router queryable + { + log::debug!( + "Register router queryable {} (router: {})", + res.expr(), + router, + ); + res_hat_mut!(res).router_qabls.insert(router, *qabl_info); + hat_mut!(tables).router_qabls.insert(res.clone()); + } + + // Propagate queryable to routers + propagate_sourced_queryable( + tables, + res, + qabl_info, + face.as_deref_mut(), + &router, + WhatAmI::Router, + ); + } + + if hat!(tables).full_net(WhatAmI::Peer) { + // Propagate queryable to peers + if face.is_none() || face.as_ref().unwrap().whatami != WhatAmI::Peer { + let local_info = local_peer_qabl_info(tables, res); + register_peer_queryable(tables, face.as_deref_mut(), res, &local_info, tables.zid) + } + } + + // Propagate queryable to clients + propagate_simple_queryable(tables, res, face); +} + +fn declare_router_queryable( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + qabl_info: &QueryableInfo, + router: ZenohId, +) { + register_router_queryable(tables, Some(face), res, qabl_info, router); +} + +fn register_peer_queryable( + tables: &mut Tables, + face: Option<&mut Arc>, + res: &mut Arc, + qabl_info: &QueryableInfo, + peer: ZenohId, +) { + let current_info = res_hat!(res).peer_qabls.get(&peer); + if current_info.is_none() || current_info.unwrap() != qabl_info { + // Register peer queryable + { + log::debug!("Register peer queryable {} (peer: {})", res.expr(), peer,); + res_hat_mut!(res).peer_qabls.insert(peer, *qabl_info); + hat_mut!(tables).peer_qabls.insert(res.clone()); + } + + // Propagate queryable to peers + propagate_sourced_queryable(tables, res, qabl_info, face, &peer, WhatAmI::Peer); + } +} + +fn declare_peer_queryable( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + qabl_info: &QueryableInfo, + peer: ZenohId, +) { + let mut face = Some(face); + register_peer_queryable(tables, face.as_deref_mut(), res, qabl_info, peer); + let local_info = local_router_qabl_info(tables, res); + let zid = tables.zid; + register_router_queryable(tables, face, res, &local_info, zid); +} + +fn register_client_queryable( + _tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + qabl_info: &QueryableInfo, +) { + // Register queryable + { + let res = get_mut_unchecked(res); + log::debug!("Register queryable {} (face: {})", res.expr(), face,); + get_mut_unchecked(res.session_ctxs.entry(face.id).or_insert_with(|| { + Arc::new(SessionContext { + face: face.clone(), + local_expr_id: None, + remote_expr_id: None, + subs: None, + qabl: None, + last_values: HashMap::new(), + }) + })) + .qabl = Some(*qabl_info); + } + face_hat_mut!(face).remote_qabls.insert(res.clone()); +} + +fn declare_client_queryable( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + qabl_info: &QueryableInfo, +) { + register_client_queryable(tables, face, res, qabl_info); + let local_details = local_router_qabl_info(tables, res); + let zid = tables.zid; + register_router_queryable(tables, Some(face), res, &local_details, zid); +} + +#[inline] +fn remote_router_qabls(tables: &Tables, res: &Arc) -> bool { + res.context.is_some() + && res_hat!(res) + .router_qabls + .keys() + .any(|router| router != &tables.zid) +} + +#[inline] +fn remote_peer_qabls(tables: &Tables, res: &Arc) -> bool { + res.context.is_some() + && res_hat!(res) + .peer_qabls + .keys() + .any(|peer| peer != &tables.zid) +} + +#[inline] +fn client_qabls(res: &Arc) -> Vec> { + res.session_ctxs + .values() + .filter_map(|ctx| { + if ctx.qabl.is_some() { + Some(ctx.face.clone()) + } else { + None + } + }) + .collect() +} + +#[inline] +fn send_forget_sourced_queryable_to_net_childs( + tables: &Tables, + net: &Network, + childs: &[NodeIndex], + res: &Arc, + src_face: Option<&Arc>, + routing_context: NodeId, +) { + for child in childs { + if net.graph.contains_node(*child) { + match tables.get_face(&net.graph[*child].zid).cloned() { + Some(mut someface) => { + if src_face.is_none() || someface.id != src_face.unwrap().id { + let wire_expr = Resource::decl_key(res, &mut someface); + + log::debug!("Send forget queryable {} on {}", res.expr(), someface); + + someface.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType { + node_id: routing_context, + }, + body: DeclareBody::UndeclareQueryable(UndeclareQueryable { + id: 0, // @TODO use proper QueryableId (#703) + ext_wire_expr: WireExprType { wire_expr }, + }), + }, + res.expr(), + )); + } + } + None => log::trace!("Unable to find face for zid {}", net.graph[*child].zid), + } + } + } +} + +fn propagate_forget_simple_queryable(tables: &mut Tables, res: &mut Arc) { + for face in tables.faces.values_mut() { + if face_hat!(face).local_qabls.contains_key(res) { + let wire_expr = Resource::get_best_key(res, "", face.id); + face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::UndeclareQueryable(UndeclareQueryable { + id: 0, // @TODO use proper QueryableId (#703) + ext_wire_expr: WireExprType { wire_expr }, + }), + }, + res.expr(), + )); + + face_hat_mut!(face).local_qabls.remove(res); + } + } +} + +fn propagate_forget_simple_queryable_to_peers(tables: &mut Tables, res: &mut Arc) { + if !hat!(tables).full_net(WhatAmI::Peer) + && res_hat!(res).router_qabls.len() == 1 + && res_hat!(res).router_qabls.contains_key(&tables.zid) + { + for mut face in tables + .faces + .values() + .cloned() + .collect::>>() + { + if face.whatami == WhatAmI::Peer + && face_hat!(face).local_qabls.contains_key(res) + && !res.session_ctxs.values().any(|s| { + face.zid != s.face.zid + && s.qabl.is_some() + && (s.face.whatami == WhatAmI::Client + || (s.face.whatami == WhatAmI::Peer + && hat!(tables).failover_brokering(s.face.zid, face.zid))) + }) + { + let wire_expr = Resource::get_best_key(res, "", face.id); + face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::UndeclareQueryable(UndeclareQueryable { + id: 0, // @TODO use proper QueryableId (#703) + ext_wire_expr: WireExprType { wire_expr }, + }), + }, + res.expr(), + )); + + face_hat_mut!(&mut face).local_qabls.remove(res); + } + } + } +} + +fn propagate_forget_sourced_queryable( + tables: &mut Tables, + res: &mut Arc, + src_face: Option<&Arc>, + source: &ZenohId, + net_type: WhatAmI, +) { + let net = hat!(tables).get_net(net_type).unwrap(); + match net.get_idx(source) { + Some(tree_sid) => { + if net.trees.len() > tree_sid.index() { + send_forget_sourced_queryable_to_net_childs( + tables, + net, + &net.trees[tree_sid.index()].childs, + res, + src_face, + tree_sid.index() as NodeId, + ); + } else { + log::trace!( + "Propagating forget qabl {}: tree for node {} sid:{} not yet ready", + res.expr(), + tree_sid.index(), + source + ); + } + } + None => log::error!( + "Error propagating forget qabl {}: cannot get index of {}!", + res.expr(), + source + ), + } +} + +fn unregister_router_queryable(tables: &mut Tables, res: &mut Arc, router: &ZenohId) { + log::debug!( + "Unregister router queryable {} (router: {})", + res.expr(), + router, + ); + res_hat_mut!(res).router_qabls.remove(router); + + if res_hat!(res).router_qabls.is_empty() { + hat_mut!(tables) + .router_qabls + .retain(|qabl| !Arc::ptr_eq(qabl, res)); + + if hat!(tables).full_net(WhatAmI::Peer) { + undeclare_peer_queryable(tables, None, res, &tables.zid.clone()); + } + propagate_forget_simple_queryable(tables, res); + } + + propagate_forget_simple_queryable_to_peers(tables, res); +} + +fn undeclare_router_queryable( + tables: &mut Tables, + face: Option<&Arc>, + res: &mut Arc, + router: &ZenohId, +) { + if res_hat!(res).router_qabls.contains_key(router) { + unregister_router_queryable(tables, res, router); + propagate_forget_sourced_queryable(tables, res, face, router, WhatAmI::Router); + } +} + +fn forget_router_queryable( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + router: &ZenohId, +) { + undeclare_router_queryable(tables, Some(face), res, router); +} + +fn unregister_peer_queryable(tables: &mut Tables, res: &mut Arc, peer: &ZenohId) { + log::debug!("Unregister peer queryable {} (peer: {})", res.expr(), peer,); + res_hat_mut!(res).peer_qabls.remove(peer); + + if res_hat!(res).peer_qabls.is_empty() { + hat_mut!(tables) + .peer_qabls + .retain(|qabl| !Arc::ptr_eq(qabl, res)); + } +} + +fn undeclare_peer_queryable( + tables: &mut Tables, + face: Option<&Arc>, + res: &mut Arc, + peer: &ZenohId, +) { + if res_hat!(res).peer_qabls.contains_key(peer) { + unregister_peer_queryable(tables, res, peer); + propagate_forget_sourced_queryable(tables, res, face, peer, WhatAmI::Peer); + } +} + +fn forget_peer_queryable( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + peer: &ZenohId, +) { + undeclare_peer_queryable(tables, Some(face), res, peer); + + let client_qabls = res.session_ctxs.values().any(|ctx| ctx.qabl.is_some()); + let peer_qabls = remote_peer_qabls(tables, res); + let zid = tables.zid; + if !client_qabls && !peer_qabls { + undeclare_router_queryable(tables, None, res, &zid); + } else { + let local_info = local_router_qabl_info(tables, res); + register_router_queryable(tables, None, res, &local_info, zid); + } +} + +pub(super) fn undeclare_client_queryable( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, +) { + log::debug!("Unregister client queryable {} for {}", res.expr(), face); + if let Some(ctx) = get_mut_unchecked(res).session_ctxs.get_mut(&face.id) { + get_mut_unchecked(ctx).qabl = None; + if ctx.qabl.is_none() { + face_hat_mut!(face).remote_qabls.remove(res); + } + } + + let mut client_qabls = client_qabls(res); + let router_qabls = remote_router_qabls(tables, res); + let peer_qabls = remote_peer_qabls(tables, res); + + if client_qabls.is_empty() && !peer_qabls { + undeclare_router_queryable(tables, None, res, &tables.zid.clone()); + } else { + let local_info = local_router_qabl_info(tables, res); + register_router_queryable(tables, None, res, &local_info, tables.zid); + propagate_forget_simple_queryable_to_peers(tables, res); + } + + if client_qabls.len() == 1 && !router_qabls && !peer_qabls { + let face = &mut client_qabls[0]; + if face_hat!(face).local_qabls.contains_key(res) { + let wire_expr = Resource::get_best_key(res, "", face.id); + face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::UndeclareQueryable(UndeclareQueryable { + id: 0, // @TODO use proper QueryableId (#703) + ext_wire_expr: WireExprType { wire_expr }, + }), + }, + res.expr(), + )); + + face_hat_mut!(face).local_qabls.remove(res); + } + } +} + +fn forget_client_queryable( + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, +) { + undeclare_client_queryable(tables, face, res); +} + +pub(super) fn queries_new_face(tables: &mut Tables, face: &mut Arc) { + if face.whatami == WhatAmI::Client { + for qabl in hat!(tables).router_qabls.iter() { + if qabl.context.is_some() { + let info = local_qabl_info(tables, qabl, face); + face_hat_mut!(face).local_qabls.insert(qabl.clone(), info); + let key_expr = Resource::decl_key(qabl, face); + face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::DeclareQueryable(DeclareQueryable { + id: 0, // @TODO use proper QueryableId (#703) + wire_expr: key_expr, + ext_info: info, + }), + }, + qabl.expr(), + )); + } + } + } else if face.whatami == WhatAmI::Peer && !hat!(tables).full_net(WhatAmI::Peer) { + for qabl in hat!(tables).router_qabls.iter() { + if qabl.context.is_some() + && (res_hat!(qabl).router_qabls.keys().any(|r| *r != tables.zid) + || qabl.session_ctxs.values().any(|s| { + s.qabl.is_some() + && (s.face.whatami == WhatAmI::Client + || (s.face.whatami == WhatAmI::Peer + && hat!(tables).failover_brokering(s.face.zid, face.zid))) + })) + { + let info = local_qabl_info(tables, qabl, face); + face_hat_mut!(face).local_qabls.insert(qabl.clone(), info); + let key_expr = Resource::decl_key(qabl, face); + face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::DeclareQueryable(DeclareQueryable { + id: 0, // @TODO use proper QueryableId (#703) + wire_expr: key_expr, + ext_info: info, + }), + }, + qabl.expr(), + )); + } + } + } +} + +pub(super) fn queries_remove_node(tables: &mut Tables, node: &ZenohId, net_type: WhatAmI) { + match net_type { + WhatAmI::Router => { + let mut qabls = vec![]; + for res in hat!(tables).router_qabls.iter() { + for qabl in res_hat!(res).router_qabls.keys() { + if qabl == node { + qabls.push(res.clone()); + } + } + } + for mut res in qabls { + unregister_router_queryable(tables, &mut res, node); + + update_matches_query_routes(tables, &res); + Resource::clean(&mut res); + } + } + WhatAmI::Peer => { + let mut qabls = vec![]; + for res in hat!(tables).router_qabls.iter() { + for qabl in res_hat!(res).router_qabls.keys() { + if qabl == node { + qabls.push(res.clone()); + } + } + } + for mut res in qabls { + unregister_peer_queryable(tables, &mut res, node); + + let client_qabls = res.session_ctxs.values().any(|ctx| ctx.qabl.is_some()); + let peer_qabls = remote_peer_qabls(tables, &res); + if !client_qabls && !peer_qabls { + undeclare_router_queryable(tables, None, &mut res, &tables.zid.clone()); + } else { + let local_info = local_router_qabl_info(tables, &res); + register_router_queryable(tables, None, &mut res, &local_info, tables.zid); + } + + update_matches_query_routes(tables, &res); + Resource::clean(&mut res) + } + } + _ => (), + } +} + +pub(super) fn queries_linkstate_change(tables: &mut Tables, zid: &ZenohId, links: &[ZenohId]) { + if let Some(src_face) = tables.get_face(zid) { + if hat!(tables).router_peers_failover_brokering && src_face.whatami == WhatAmI::Peer { + for res in &face_hat!(src_face).remote_qabls { + let client_qabls = res + .session_ctxs + .values() + .any(|ctx| ctx.face.whatami == WhatAmI::Client && ctx.qabl.is_some()); + if !remote_router_qabls(tables, res) && !client_qabls { + for ctx in get_mut_unchecked(&mut res.clone()) + .session_ctxs + .values_mut() + { + let dst_face = &mut get_mut_unchecked(ctx).face; + if dst_face.whatami == WhatAmI::Peer && src_face.zid != dst_face.zid { + if face_hat!(dst_face).local_qabls.contains_key(res) { + let forget = !HatTables::failover_brokering_to(links, dst_face.zid) + && { + let ctx_links = hat!(tables) + .peers_net + .as_ref() + .map(|net| net.get_links(dst_face.zid)) + .unwrap_or_else(|| &[]); + res.session_ctxs.values().any(|ctx2| { + ctx2.face.whatami == WhatAmI::Peer + && ctx2.qabl.is_some() + && HatTables::failover_brokering_to( + ctx_links, + ctx2.face.zid, + ) + }) + }; + if forget { + let wire_expr = Resource::get_best_key(res, "", dst_face.id); + dst_face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::UndeclareQueryable( + UndeclareQueryable { + id: 0, // @TODO use proper QueryableId (#703) + ext_wire_expr: WireExprType { wire_expr }, + }, + ), + }, + res.expr(), + )); + + face_hat_mut!(dst_face).local_qabls.remove(res); + } + } else if HatTables::failover_brokering_to(links, ctx.face.zid) { + let dst_face = &mut get_mut_unchecked(ctx).face; + let info = local_qabl_info(tables, res, dst_face); + face_hat_mut!(dst_face) + .local_qabls + .insert(res.clone(), info); + let key_expr = Resource::decl_key(res, dst_face); + dst_face.primitives.send_declare(RoutingContext::with_expr( + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::DeclareQueryable(DeclareQueryable { + id: 0, // @TODO use proper QueryableId (#703) + wire_expr: key_expr, + ext_info: info, + }), + }, + res.expr(), + )); + } + } + } + } + } + } + } +} + +pub(super) fn queries_tree_change( + tables: &mut Tables, + new_childs: &[Vec], + net_type: WhatAmI, +) { + // propagate qabls to new childs + for (tree_sid, tree_childs) in new_childs.iter().enumerate() { + if !tree_childs.is_empty() { + let net = hat!(tables).get_net(net_type).unwrap(); + let tree_idx = NodeIndex::new(tree_sid); + if net.graph.contains_node(tree_idx) { + let tree_id = net.graph[tree_idx].zid; + + let qabls_res = match net_type { + WhatAmI::Router => &hat!(tables).router_qabls, + _ => &hat!(tables).peer_qabls, + }; + + for res in qabls_res { + let qabls = match net_type { + WhatAmI::Router => &res_hat!(res).router_qabls, + _ => &res_hat!(res).peer_qabls, + }; + if let Some(qabl_info) = qabls.get(&tree_id) { + send_sourced_queryable_to_net_childs( + tables, + net, + tree_childs, + res, + qabl_info, + None, + tree_sid as NodeId, + ); + } + } + } + } + } + + // recompute routes + update_query_routes_from(tables, &mut tables.root_res.clone()); +} + +#[inline] +fn insert_target_for_qabls( + route: &mut QueryTargetQablSet, + expr: &mut RoutingExpr, + tables: &Tables, + net: &Network, + source: NodeId, + qabls: &HashMap, + complete: bool, +) { + if net.trees.len() > source as usize { + for (qabl, qabl_info) in qabls { + if let Some(qabl_idx) = net.get_idx(qabl) { + if net.trees[source as usize].directions.len() > qabl_idx.index() { + if let Some(direction) = net.trees[source as usize].directions[qabl_idx.index()] + { + if net.graph.contains_node(direction) { + if let Some(face) = tables.get_face(&net.graph[direction].zid) { + if net.distances.len() > qabl_idx.index() { + let key_expr = + Resource::get_best_key(expr.prefix, expr.suffix, face.id); + route.push(QueryTargetQabl { + direction: (face.clone(), key_expr.to_owned(), source), + complete: if complete { + qabl_info.complete as u64 + } else { + 0 + }, + distance: net.distances[qabl_idx.index()], + }); + } + } + } + } + } + } + } + } else { + log::trace!("Tree for node sid:{} not yet ready", source); + } +} + +lazy_static::lazy_static! { + static ref EMPTY_ROUTE: Arc = Arc::new(Vec::new()); +} + +impl HatQueriesTrait for HatCode { + fn declare_queryable( + &self, + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + qabl_info: &QueryableInfo, + node_id: NodeId, + ) { + match face.whatami { + WhatAmI::Router => { + if let Some(router) = get_router(tables, face, node_id) { + declare_router_queryable(tables, face, res, qabl_info, router) + } + } + WhatAmI::Peer => { + if hat!(tables).full_net(WhatAmI::Peer) { + if let Some(peer) = get_peer(tables, face, node_id) { + declare_peer_queryable(tables, face, res, qabl_info, peer) + } + } else { + declare_client_queryable(tables, face, res, qabl_info) + } + } + _ => declare_client_queryable(tables, face, res, qabl_info), + } + } + + fn undeclare_queryable( + &self, + tables: &mut Tables, + face: &mut Arc, + res: &mut Arc, + node_id: NodeId, + ) { + match face.whatami { + WhatAmI::Router => { + if let Some(router) = get_router(tables, face, node_id) { + forget_router_queryable(tables, face, res, &router) + } + } + WhatAmI::Peer => { + if hat!(tables).full_net(WhatAmI::Peer) { + if let Some(peer) = get_peer(tables, face, node_id) { + forget_peer_queryable(tables, face, res, &peer) + } + } else { + forget_client_queryable(tables, face, res) + } + } + _ => forget_client_queryable(tables, face, res), + } + } + + fn get_queryables(&self, tables: &Tables) -> Vec> { + hat!(tables).router_qabls.iter().cloned().collect() + } + + fn compute_query_route( + &self, + tables: &Tables, + expr: &mut RoutingExpr, + source: NodeId, + source_type: WhatAmI, + ) -> Arc { + let mut route = QueryTargetQablSet::new(); + let key_expr = expr.full_expr(); + if key_expr.ends_with('/') { + return EMPTY_ROUTE.clone(); + } + log::trace!( + "compute_query_route({}, {:?}, {:?})", + key_expr, + source, + source_type + ); + let key_expr = match OwnedKeyExpr::try_from(key_expr) { + Ok(ke) => ke, + Err(e) => { + log::warn!("Invalid KE reached the system: {}", e); + return EMPTY_ROUTE.clone(); + } + }; + let res = Resource::get_resource(expr.prefix, expr.suffix); + let matches = res + .as_ref() + .and_then(|res| res.context.as_ref()) + .map(|ctx| Cow::from(&ctx.matches)) + .unwrap_or_else(|| Cow::from(Resource::get_matches(tables, &key_expr))); + + let master = !hat!(tables).full_net(WhatAmI::Peer) + || *hat!(tables).elect_router(&tables.zid, &key_expr, hat!(tables).shared_nodes.iter()) + == tables.zid; + + for mres in matches.iter() { + let mres = mres.upgrade().unwrap(); + let complete = DEFAULT_INCLUDER.includes(mres.expr().as_bytes(), key_expr.as_bytes()); + if master || source_type == WhatAmI::Router { + let net = hat!(tables).routers_net.as_ref().unwrap(); + let router_source = match source_type { + WhatAmI::Router => source, + _ => net.idx.index() as NodeId, + }; + insert_target_for_qabls( + &mut route, + expr, + tables, + net, + router_source, + &res_hat!(mres).router_qabls, + complete, + ); + } + + if (master || source_type != WhatAmI::Router) && hat!(tables).full_net(WhatAmI::Peer) { + let net = hat!(tables).peers_net.as_ref().unwrap(); + let peer_source = match source_type { + WhatAmI::Peer => source, + _ => net.idx.index() as NodeId, + }; + insert_target_for_qabls( + &mut route, + expr, + tables, + net, + peer_source, + &res_hat!(mres).peer_qabls, + complete, + ); + } + + if master || source_type == WhatAmI::Router { + for (sid, context) in &mres.session_ctxs { + if context.face.whatami != WhatAmI::Router { + let key_expr = Resource::get_best_key(expr.prefix, expr.suffix, *sid); + if let Some(qabl_info) = context.qabl.as_ref() { + route.push(QueryTargetQabl { + direction: ( + context.face.clone(), + key_expr.to_owned(), + NodeId::default(), + ), + complete: if complete { + qabl_info.complete as u64 + } else { + 0 + }, + distance: 0.5, + }); + } + } + } + } + } + route.sort_by_key(|qabl| OrderedFloat(qabl.distance)); + Arc::new(route) + } + + #[inline] + fn compute_local_replies( + &self, + tables: &Tables, + prefix: &Arc, + suffix: &str, + face: &Arc, + ) -> Vec<(WireExpr<'static>, ZBuf)> { + let mut result = vec![]; + // Only the first routing point in the query route + // should return the liveliness tokens + if face.whatami == WhatAmI::Client { + let key_expr = prefix.expr() + suffix; + let key_expr = match OwnedKeyExpr::try_from(key_expr) { + Ok(ke) => ke, + Err(e) => { + log::warn!("Invalid KE reached the system: {}", e); + return result; + } + }; + if key_expr.starts_with(PREFIX_LIVELINESS) { + let res = Resource::get_resource(prefix, suffix); + let matches = res + .as_ref() + .and_then(|res| res.context.as_ref()) + .map(|ctx| Cow::from(&ctx.matches)) + .unwrap_or_else(|| Cow::from(Resource::get_matches(tables, &key_expr))); + for mres in matches.iter() { + let mres = mres.upgrade().unwrap(); + if (mres.context.is_some() + && (!res_hat!(mres).router_subs.is_empty() + || !res_hat!(mres).peer_subs.is_empty())) + || mres.session_ctxs.values().any(|ctx| ctx.subs.is_some()) + { + result.push((Resource::get_best_key(&mres, "", face.id), ZBuf::default())); + } + } + } + } + result + } + + fn get_query_routes_entries(&self, tables: &Tables) -> RoutesIndexes { + get_routes_entries(tables) + } +} diff --git a/zenoh/src/net/routing/interceptor/mod.rs b/zenoh/src/net/routing/interceptor/mod.rs new file mode 100644 index 0000000000..7503580405 --- /dev/null +++ b/zenoh/src/net/routing/interceptor/mod.rs @@ -0,0 +1,144 @@ +// +// Copyright (c) 2023 ZettaScale Technology +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// +// Contributors: +// ZettaScale Zenoh Team, +// + +//! ⚠️ WARNING ⚠️ +//! +//! This module is intended for Zenoh's internal use. +//! +//! [Click here for Zenoh's documentation](../zenoh/index.html) +use super::RoutingContext; +use zenoh_config::Config; +use zenoh_protocol::network::NetworkMessage; +use zenoh_transport::{multicast::TransportMulticast, unicast::TransportUnicast}; + +pub(crate) trait InterceptorTrait { + fn intercept( + &self, + ctx: RoutingContext, + ) -> Option>; +} + +pub(crate) type Interceptor = Box; +pub(crate) type IngressInterceptor = Interceptor; +pub(crate) type EgressInterceptor = Interceptor; + +pub(crate) trait InterceptorFactoryTrait { + fn new_transport_unicast( + &self, + transport: &TransportUnicast, + ) -> (Option, Option); + fn new_transport_multicast(&self, transport: &TransportMulticast) -> Option; + fn new_peer_multicast(&self, transport: &TransportMulticast) -> Option; +} + +pub(crate) type InterceptorFactory = Box; + +pub(crate) fn interceptor_factories(_config: &Config) -> Vec { + // Add interceptors here + // @TODO build the list of intercetors with the correct order from the config + // vec![Box::new(LoggerInterceptor {})] + vec![] +} + +pub(crate) struct InterceptorsChain { + pub(crate) interceptors: Vec, +} + +impl InterceptorsChain { + #[allow(dead_code)] + pub(crate) fn empty() -> Self { + Self { + interceptors: vec![], + } + } +} + +impl From> for InterceptorsChain { + fn from(interceptors: Vec) -> Self { + InterceptorsChain { interceptors } + } +} + +impl InterceptorTrait for InterceptorsChain { + fn intercept( + &self, + mut ctx: RoutingContext, + ) -> Option> { + for interceptor in &self.interceptors { + match interceptor.intercept(ctx) { + Some(newctx) => ctx = newctx, + None => { + log::trace!("Msg intercepted!"); + return None; + } + } + } + Some(ctx) + } +} + +pub(crate) struct IngressMsgLogger {} + +impl InterceptorTrait for IngressMsgLogger { + fn intercept( + &self, + ctx: RoutingContext, + ) -> Option> { + log::debug!( + "Recv {} {} Expr:{:?}", + ctx.inface() + .map(|f| f.to_string()) + .unwrap_or("None".to_string()), + ctx.msg, + ctx.full_expr(), + ); + Some(ctx) + } +} +pub(crate) struct EgressMsgLogger {} + +impl InterceptorTrait for EgressMsgLogger { + fn intercept( + &self, + ctx: RoutingContext, + ) -> Option> { + log::debug!("Send {} Expr:{:?}", ctx.msg, ctx.full_expr()); + Some(ctx) + } +} + +pub(crate) struct LoggerInterceptor {} + +impl InterceptorFactoryTrait for LoggerInterceptor { + fn new_transport_unicast( + &self, + transport: &TransportUnicast, + ) -> (Option, Option) { + log::debug!("New transport unicast {:?}", transport); + ( + Some(Box::new(IngressMsgLogger {})), + Some(Box::new(EgressMsgLogger {})), + ) + } + + fn new_transport_multicast(&self, transport: &TransportMulticast) -> Option { + log::debug!("New transport multicast {:?}", transport); + Some(Box::new(EgressMsgLogger {})) + } + + fn new_peer_multicast(&self, transport: &TransportMulticast) -> Option { + log::debug!("New peer multicast {:?}", transport); + Some(Box::new(IngressMsgLogger {})) + } +} diff --git a/zenoh/src/net/routing/mod.rs b/zenoh/src/net/routing/mod.rs index 59c2a7cd06..f6d00051ca 100644 --- a/zenoh/src/net/routing/mod.rs +++ b/zenoh/src/net/routing/mod.rs @@ -26,6 +26,150 @@ pub(crate) mod resource; // ignore_tagging pub(crate) mod router; +use std::{cell::OnceCell, sync::Arc}; + +use zenoh_protocol::{core::WireExpr, network::NetworkMessage}; + +use self::{dispatcher::face::Face, router::Resource}; + use super::runtime; pub(crate) static PREFIX_LIVELINESS: &str = "@/liveliness"; + +pub(crate) struct RoutingContext { + pub(crate) msg: Msg, + pub(crate) inface: OnceCell, + pub(crate) outface: OnceCell, + pub(crate) prefix: OnceCell>, + pub(crate) full_expr: OnceCell, +} + +impl RoutingContext { + #[allow(dead_code)] + pub(crate) fn new(msg: Msg) -> Self { + Self { + msg, + inface: OnceCell::new(), + outface: OnceCell::new(), + prefix: OnceCell::new(), + full_expr: OnceCell::new(), + } + } + + #[allow(dead_code)] + pub(crate) fn new_in(msg: Msg, inface: Face) -> Self { + Self { + msg, + inface: OnceCell::from(inface), + outface: OnceCell::new(), + prefix: OnceCell::new(), + full_expr: OnceCell::new(), + } + } + + #[allow(dead_code)] + pub(crate) fn new_out(msg: Msg, outface: Face) -> Self { + Self { + msg, + inface: OnceCell::new(), + outface: OnceCell::from(outface), + prefix: OnceCell::new(), + full_expr: OnceCell::new(), + } + } + + #[allow(dead_code)] + pub(crate) fn with_expr(msg: Msg, expr: String) -> Self { + Self { + msg, + inface: OnceCell::new(), + outface: OnceCell::new(), + prefix: OnceCell::new(), + full_expr: OnceCell::from(expr), + } + } + + #[allow(dead_code)] + pub(crate) fn inface(&self) -> Option<&Face> { + self.inface.get() + } + + #[allow(dead_code)] + pub(crate) fn outface(&self) -> Option<&Face> { + self.outface.get() + } +} + +impl RoutingContext { + #[inline] + pub(crate) fn wire_expr(&self) -> Option<&WireExpr> { + use zenoh_protocol::network::DeclareBody; + use zenoh_protocol::network::NetworkBody; + match &self.msg.body { + NetworkBody::Push(m) => Some(&m.wire_expr), + NetworkBody::Request(m) => Some(&m.wire_expr), + NetworkBody::Response(m) => Some(&m.wire_expr), + NetworkBody::ResponseFinal(_) => None, + NetworkBody::Declare(m) => match &m.body { + DeclareBody::DeclareKeyExpr(m) => Some(&m.wire_expr), + DeclareBody::UndeclareKeyExpr(_) => None, + DeclareBody::DeclareSubscriber(m) => Some(&m.wire_expr), + DeclareBody::UndeclareSubscriber(m) => Some(&m.ext_wire_expr.wire_expr), + DeclareBody::DeclareQueryable(m) => Some(&m.wire_expr), + DeclareBody::UndeclareQueryable(m) => Some(&m.ext_wire_expr.wire_expr), + DeclareBody::DeclareToken(m) => Some(&m.wire_expr), + DeclareBody::UndeclareToken(m) => Some(&m.ext_wire_expr.wire_expr), + DeclareBody::DeclareInterest(m) => Some(&m.wire_expr), + DeclareBody::FinalInterest(_) => None, + DeclareBody::UndeclareInterest(m) => Some(&m.ext_wire_expr.wire_expr), + }, + NetworkBody::OAM(_) => None, + } + } + + #[inline] + pub(crate) fn full_expr(&self) -> Option<&str> { + if self.full_expr.get().is_some() { + return Some(self.full_expr.get().as_ref().unwrap()); + } + if let Some(face) = self.outface.get() { + if let Some(wire_expr) = self.wire_expr() { + let wire_expr = wire_expr.to_owned(); + if self.prefix.get().is_none() { + if let Some(prefix) = zread!(face.tables.tables) + .get_sent_mapping(&face.state, &wire_expr.scope, wire_expr.mapping) + .cloned() + { + let _ = self.prefix.set(prefix); + } + } + if let Some(prefix) = self.prefix.get().cloned() { + let _ = self + .full_expr + .set(prefix.expr() + wire_expr.suffix.as_ref()); + return Some(self.full_expr.get().as_ref().unwrap()); + } + } + } + if let Some(face) = self.inface.get() { + if let Some(wire_expr) = self.wire_expr() { + let wire_expr = wire_expr.to_owned(); + if self.prefix.get().is_none() { + if let Some(prefix) = zread!(face.tables.tables) + .get_mapping(&face.state, &wire_expr.scope, wire_expr.mapping) + .cloned() + { + let _ = self.prefix.set(prefix); + } + } + if let Some(prefix) = self.prefix.get().cloned() { + let _ = self + .full_expr + .set(prefix.expr() + wire_expr.suffix.as_ref()); + return Some(self.full_expr.get().as_ref().unwrap()); + } + } + } + None + } +} diff --git a/zenoh/src/net/routing/router.rs b/zenoh/src/net/routing/router.rs index 933bd7339c..26c9d36185 100644 --- a/zenoh/src/net/routing/router.rs +++ b/zenoh/src/net/routing/router.rs @@ -11,296 +11,119 @@ // Contributors: // ZettaScale Zenoh Team, // -use super::face::{Face, FaceState}; -use super::network::{shared_nodes, Network}; -pub use super::pubsub::*; -pub use super::queries::*; -pub use super::resource::*; +use super::dispatcher::face::{Face, FaceState}; +pub use super::dispatcher::pubsub::*; +pub use super::dispatcher::queries::*; +pub use super::dispatcher::resource::*; +use super::dispatcher::tables::Tables; +use super::dispatcher::tables::TablesLock; +use super::hat; +use super::interceptor::EgressInterceptor; +use super::interceptor::InterceptorsChain; use super::runtime::Runtime; -use crate::net::codec::Zenoh080Routing; -use crate::net::protocol::linkstate::LinkStateList; -use async_std::task::JoinHandle; -use std::any::Any; -use std::collections::hash_map::DefaultHasher; -use std::collections::{HashMap, HashSet}; -use std::hash::Hasher; +use crate::net::primitives::DeMux; +use crate::net::primitives::DummyPrimitives; +use crate::net::primitives::EPrimitives; +use crate::net::primitives::McastMux; +use crate::net::primitives::Mux; +use crate::net::routing::interceptor::IngressInterceptor; use std::str::FromStr; -use std::sync::{Arc, Weak}; +use std::sync::Arc; use std::sync::{Mutex, RwLock}; -use std::time::Duration; use uhlc::HLC; -use zenoh_link::Link; -use zenoh_protocol::common::ZExtBody; -use zenoh_protocol::core::{ExprId, WhatAmI, WhatAmIMatcher, ZenohId}; -use zenoh_protocol::network::oam::id::OAM_LINKSTATE; -use zenoh_protocol::network::{Mapping, NetworkBody, NetworkMessage}; -#[cfg(feature = "stats")] -use zenoh_transport::stats::TransportStats; -use zenoh_transport::{ - multicast::TransportMulticast, - primitives::{DeMux, DummyPrimitives, McastMux, Mux, Primitives}, - unicast::TransportUnicast, - TransportPeer, TransportPeerEventHandler, -}; +use zenoh_config::Config; +use zenoh_protocol::core::{WhatAmI, ZenohId}; +use zenoh_transport::multicast::TransportMulticast; +use zenoh_transport::unicast::TransportUnicast; +use zenoh_transport::TransportPeer; // use zenoh_collections::Timer; -use zenoh_core::zconfigurable; use zenoh_result::ZResult; -use zenoh_sync::get_mut_unchecked; -zconfigurable! { - static ref TREES_COMPUTATION_DELAY: u64 = 100; -} - -pub(crate) struct RoutingExpr<'a> { - pub(crate) prefix: &'a Arc, - pub(crate) suffix: &'a str, - full: Option, -} - -impl<'a> RoutingExpr<'a> { - #[inline] - pub(crate) fn new(prefix: &'a Arc, suffix: &'a str) -> Self { - RoutingExpr { - prefix, - suffix, - full: None, - } - } - - #[inline] - pub(crate) fn full_expr(&mut self) -> &str { - if self.full.is_none() { - self.full = Some(self.prefix.expr() + self.suffix); - } - self.full.as_ref().unwrap() - } -} - -pub struct Tables { - pub(crate) zid: ZenohId, - pub(crate) whatami: WhatAmI, - face_counter: usize, - #[allow(dead_code)] - pub(crate) hlc: Option>, - pub(crate) drop_future_timestamp: bool, - pub(crate) router_peers_failover_brokering: bool, - // pub(crate) timer: Timer, - // pub(crate) queries_default_timeout: Duration, - pub(crate) root_res: Arc, - pub(crate) faces: HashMap>, - pub(crate) mcast_groups: Vec>, - pub(crate) mcast_faces: Vec>, - pub(crate) pull_caches_lock: Mutex<()>, - pub(crate) router_subs: HashSet>, - pub(crate) peer_subs: HashSet>, - pub(crate) router_qabls: HashSet>, - pub(crate) peer_qabls: HashSet>, - pub(crate) routers_net: Option, - pub(crate) peers_net: Option, - pub(crate) shared_nodes: Vec, - pub(crate) routers_trees_task: Option>, - pub(crate) peers_trees_task: Option>, +pub struct Router { + // whatami: WhatAmI, + pub tables: Arc, } -impl Tables { - pub fn new( - zid: ZenohId, - whatami: WhatAmI, - hlc: Option>, - drop_future_timestamp: bool, - router_peers_failover_brokering: bool, - _queries_default_timeout: Duration, - ) -> Self { - Tables { - zid, - whatami, - face_counter: 0, - hlc, - drop_future_timestamp, - router_peers_failover_brokering, - // timer: Timer::new(true), - // queries_default_timeout, - root_res: Resource::root(), - faces: HashMap::new(), - mcast_groups: vec![], - mcast_faces: vec![], - pull_caches_lock: Mutex::new(()), - router_subs: HashSet::new(), - peer_subs: HashSet::new(), - router_qabls: HashSet::new(), - peer_qabls: HashSet::new(), - routers_net: None, - peers_net: None, - shared_nodes: vec![], - routers_trees_task: None, - peers_trees_task: None, - } - } - - #[doc(hidden)] - pub fn _get_root(&self) -> &Arc { - &self.root_res - } - - #[cfg(test)] - pub fn print(&self) -> String { - Resource::print_tree(&self.root_res) - } - - #[inline] - #[allow(clippy::trivially_copy_pass_by_ref)] - pub(crate) fn get_mapping<'a>( - &'a self, - face: &'a FaceState, - expr_id: &ExprId, - mapping: Mapping, - ) -> Option<&'a Arc> { - match expr_id { - 0 => Some(&self.root_res), - expr_id => face.get_mapping(expr_id, mapping), - } - } - - #[inline] - pub(crate) fn get_net(&self, net_type: WhatAmI) -> Option<&Network> { - match net_type { - WhatAmI::Router => self.routers_net.as_ref(), - WhatAmI::Peer => self.peers_net.as_ref(), - _ => None, - } - } - - #[inline] - pub(crate) fn full_net(&self, net_type: WhatAmI) -> bool { - match net_type { - WhatAmI::Router => self - .routers_net - .as_ref() - .map(|net| net.full_linkstate) - .unwrap_or(false), - WhatAmI::Peer => self - .peers_net - .as_ref() - .map(|net| net.full_linkstate) - .unwrap_or(false), - _ => false, - } - } - - #[inline] - pub(crate) fn get_face(&self, zid: &ZenohId) -> Option<&Arc> { - self.faces.values().find(|face| face.zid == *zid) - } - - #[inline] - pub(crate) fn get_router_links(&self, peer: ZenohId) -> impl Iterator + '_ { - self.peers_net - .as_ref() - .unwrap() - .get_links(peer) - .iter() - .filter(move |nid| { - if let Some(node) = self.routers_net.as_ref().unwrap().get_node(nid) { - node.whatami.unwrap_or(WhatAmI::Router) == WhatAmI::Router - } else { - false - } - }) - } - - #[inline] - pub(crate) fn elect_router<'a>( - &'a self, - key_expr: &str, - mut routers: impl Iterator, - ) -> &'a ZenohId { - match routers.next() { - None => &self.zid, - Some(router) => { - let hash = |r: &ZenohId| { - let mut hasher = DefaultHasher::new(); - for b in key_expr.as_bytes() { - hasher.write_u8(*b); - } - for b in &r.to_le_bytes()[..r.size()] { - hasher.write_u8(*b); - } - hasher.finish() - }; - let mut res = router; - let mut h = None; - for router2 in routers { - let h2 = hash(router2); - if h2 > *h.get_or_insert_with(|| hash(res)) { - res = router2; - h = Some(h2); - } - } - res - } +impl Router { + pub fn new(zid: ZenohId, whatami: WhatAmI, hlc: Option>, config: &Config) -> Self { + Router { + // whatami, + tables: Arc::new(TablesLock { + tables: RwLock::new(Tables::new(zid, whatami, hlc, config)), + ctrl_lock: Mutex::new(hat::new_hat(whatami, config)), + queries_lock: RwLock::new(()), + }), } } - #[inline] - pub(crate) fn failover_brokering_to(source_links: &[ZenohId], dest: ZenohId) -> bool { - // if source_links is empty then gossip is probably disabled in source peer - !source_links.is_empty() && !source_links.contains(&dest) + #[allow(clippy::too_many_arguments)] + pub fn init_link_state(&mut self, runtime: Runtime) { + let ctrl_lock = zlock!(self.tables.ctrl_lock); + let mut tables = zwrite!(self.tables.tables); + ctrl_lock.init(&mut tables, runtime) } - #[inline] - pub(crate) fn failover_brokering(&self, peer1: ZenohId, peer2: ZenohId) -> bool { - self.router_peers_failover_brokering - && self - .peers_net - .as_ref() - .map(|net| Tables::failover_brokering_to(net.get_links(peer1), peer2)) - .unwrap_or(false) - } + pub(crate) fn new_primitives( + &self, + primitives: Arc, + ) -> Arc { + let ctrl_lock = zlock!(self.tables.ctrl_lock); + let mut tables = zwrite!(self.tables.tables); - fn open_net_face( - &mut self, - zid: ZenohId, - whatami: WhatAmI, - #[cfg(feature = "stats")] stats: Arc, - primitives: Arc, - link_id: usize, - ) -> Weak { - let fid = self.face_counter; - self.face_counter += 1; - let mut newface = self + let zid = tables.zid; + let fid = tables.face_counter; + tables.face_counter += 1; + let newface = tables .faces .entry(fid) .or_insert_with(|| { FaceState::new( fid, zid, - whatami, - false, + WhatAmI::Client, #[cfg(feature = "stats")] - Some(stats), + None, primitives.clone(), - link_id, None, + ctrl_lock.new_face(), ) }) .clone(); log::debug!("New {}", newface); - queries_new_face(self, &mut newface); - pubsub_new_face(self, &mut newface); - - Arc::downgrade(&newface) + let mut face = Face { + tables: self.tables.clone(), + state: newface, + }; + ctrl_lock + .new_local_face(&mut tables, &self.tables, &mut face) + .unwrap(); + drop(tables); + drop(ctrl_lock); + Arc::new(face) } - pub fn open_face( - &mut self, - zid: ZenohId, - whatami: WhatAmI, - primitives: Arc, - ) -> Weak { - let fid = self.face_counter; - self.face_counter += 1; - let mut newface = self + pub fn new_transport_unicast(&self, transport: TransportUnicast) -> ZResult> { + let ctrl_lock = zlock!(self.tables.ctrl_lock); + let mut tables = zwrite!(self.tables.tables); + + let whatami = transport.get_whatami()?; + let fid = tables.face_counter; + tables.face_counter += 1; + let zid = transport.get_zid()?; + #[cfg(feature = "stats")] + let stats = transport.get_stats()?; + let (ingress, egress): (Vec<_>, Vec<_>) = tables + .interceptors + .iter() + .map(|itor| itor.new_transport_unicast(&transport)) + .unzip(); + let (ingress, egress) = ( + InterceptorsChain::from(ingress.into_iter().flatten().collect::>()), + InterceptorsChain::from(egress.into_iter().flatten().collect::>()), + ); + let mux = Arc::new(Mux::new(transport.clone(), egress)); + let newface = tables .faces .entry(fid) .or_insert_with(|| { @@ -308,363 +131,60 @@ impl Tables { fid, zid, whatami, - true, #[cfg(feature = "stats")] + Some(stats), + mux.clone(), None, - primitives.clone(), - 0, - None, + ctrl_lock.new_face(), ) }) .clone(); log::debug!("New {}", newface); - queries_new_face(self, &mut newface); - pubsub_new_face(self, &mut newface); - - Arc::downgrade(&newface) - } - - fn compute_routes(&mut self, res: &mut Arc) { - compute_data_routes(self, res); - compute_query_routes(self, res); - } - - pub(crate) fn compute_matches_routes(&mut self, res: &mut Arc) { - if res.context.is_some() { - self.compute_routes(res); - - let resclone = res.clone(); - for match_ in &mut get_mut_unchecked(res).context_mut().matches { - let match_ = &mut match_.upgrade().unwrap(); - if !Arc::ptr_eq(match_, &resclone) && match_.context.is_some() { - self.compute_routes(match_); - } - } - } - } - - pub(crate) fn schedule_compute_trees( - &mut self, - tables_ref: Arc, - net_type: WhatAmI, - ) { - log::trace!("Schedule computations"); - if (net_type == WhatAmI::Router && self.routers_trees_task.is_none()) - || (net_type == WhatAmI::Peer && self.peers_trees_task.is_none()) - { - let task = Some(async_std::task::spawn(async move { - async_std::task::sleep(std::time::Duration::from_millis(*TREES_COMPUTATION_DELAY)) - .await; - let mut tables = zwrite!(tables_ref.tables); - - log::trace!("Compute trees"); - let new_childs = match net_type { - WhatAmI::Router => tables.routers_net.as_mut().unwrap().compute_trees(), - _ => tables.peers_net.as_mut().unwrap().compute_trees(), - }; - - log::trace!("Compute routes"); - queries_tree_change(&mut tables, &new_childs, net_type); - pubsub_tree_change(&mut tables, &new_childs, net_type); - - log::trace!("Computations completed"); - match net_type { - WhatAmI::Router => tables.routers_trees_task = None, - _ => tables.peers_trees_task = None, - }; - })); - match net_type { - WhatAmI::Router => self.routers_trees_task = task, - _ => self.peers_trees_task = task, - }; - } - } -} - -pub fn close_face(tables: &TablesLock, face: &Weak) { - match face.upgrade() { - Some(mut face) => { - log::debug!("Close {}", face); - finalize_pending_queries(tables, &mut face); - - let ctrl_lock = zlock!(tables.ctrl_lock); - let mut wtables = zwrite!(tables.tables); - let mut face_clone = face.clone(); - let face = get_mut_unchecked(&mut face); - for res in face.remote_mappings.values_mut() { - get_mut_unchecked(res).session_ctxs.remove(&face.id); - Resource::clean(res); - } - face.remote_mappings.clear(); - for res in face.local_mappings.values_mut() { - get_mut_unchecked(res).session_ctxs.remove(&face.id); - Resource::clean(res); - } - face.local_mappings.clear(); - - let mut subs_matches = vec![]; - for mut res in face.remote_subs.drain() { - get_mut_unchecked(&mut res).session_ctxs.remove(&face.id); - undeclare_client_subscription(&mut wtables, &mut face_clone, &mut res); - - if res.context.is_some() { - for match_ in &res.context().matches { - let mut match_ = match_.upgrade().unwrap(); - if !Arc::ptr_eq(&match_, &res) { - get_mut_unchecked(&mut match_) - .context_mut() - .valid_data_routes = false; - subs_matches.push(match_); - } - } - get_mut_unchecked(&mut res).context_mut().valid_data_routes = false; - subs_matches.push(res); - } - } - - let mut qabls_matches = vec![]; - for mut res in face.remote_qabls.drain() { - get_mut_unchecked(&mut res).session_ctxs.remove(&face.id); - undeclare_client_queryable(&mut wtables, &mut face_clone, &mut res); - - if res.context.is_some() { - for match_ in &res.context().matches { - let mut match_ = match_.upgrade().unwrap(); - if !Arc::ptr_eq(&match_, &res) { - get_mut_unchecked(&mut match_) - .context_mut() - .valid_query_routes = false; - qabls_matches.push(match_); - } - } - get_mut_unchecked(&mut res).context_mut().valid_query_routes = false; - qabls_matches.push(res); - } - } - drop(wtables); - - let mut matches_data_routes = vec![]; - let mut matches_query_routes = vec![]; - let rtables = zread!(tables.tables); - for _match in subs_matches.drain(..) { - matches_data_routes.push((_match.clone(), compute_data_routes_(&rtables, &_match))); - } - for _match in qabls_matches.drain(..) { - matches_query_routes - .push((_match.clone(), compute_query_routes_(&rtables, &_match))); - } - drop(rtables); - - let mut wtables = zwrite!(tables.tables); - for (mut res, data_routes) in matches_data_routes { - get_mut_unchecked(&mut res) - .context_mut() - .update_data_routes(data_routes); - Resource::clean(&mut res); - } - for (mut res, query_routes) in matches_query_routes { - get_mut_unchecked(&mut res) - .context_mut() - .update_query_routes(query_routes); - Resource::clean(&mut res); - } - wtables.faces.remove(&face.id); - drop(wtables); - drop(ctrl_lock); - } - None => log::error!("Face already closed!"), - } -} - -pub struct TablesLock { - pub tables: RwLock, - pub ctrl_lock: Mutex<()>, - pub queries_lock: RwLock<()>, -} - -pub struct Router { - whatami: WhatAmI, - pub tables: Arc, -} - -impl Router { - pub fn new( - zid: ZenohId, - whatami: WhatAmI, - hlc: Option>, - drop_future_timestamp: bool, - router_peers_failover_brokering: bool, - queries_default_timeout: Duration, - ) -> Self { - Router { - whatami, - tables: Arc::new(TablesLock { - tables: RwLock::new(Tables::new( - zid, - whatami, - hlc, - drop_future_timestamp, - router_peers_failover_brokering, - queries_default_timeout, - )), - ctrl_lock: Mutex::new(()), - queries_lock: RwLock::new(()), - }), - } - } - - #[allow(clippy::too_many_arguments)] - pub fn init_link_state( - &mut self, - runtime: Runtime, - router_full_linkstate: bool, - peer_full_linkstate: bool, - router_peers_failover_brokering: bool, - gossip: bool, - gossip_multihop: bool, - autoconnect: WhatAmIMatcher, - ) { - let mut tables = zwrite!(self.tables.tables); - if router_full_linkstate | gossip { - tables.routers_net = Some(Network::new( - "[Routers network]".to_string(), - tables.zid, - runtime.clone(), - router_full_linkstate, - router_peers_failover_brokering, - gossip, - gossip_multihop, - autoconnect, - )); - } - if peer_full_linkstate | gossip { - tables.peers_net = Some(Network::new( - "[Peers network]".to_string(), - tables.zid, - runtime, - peer_full_linkstate, - router_peers_failover_brokering, - gossip, - gossip_multihop, - autoconnect, - )); - } - if router_full_linkstate && peer_full_linkstate { - tables.shared_nodes = shared_nodes( - tables.routers_net.as_ref().unwrap(), - tables.peers_net.as_ref().unwrap(), - ); - } - } - - pub fn new_primitives(&self, primitives: Arc) -> Arc { - Arc::new(Face { + let mut face = Face { tables: self.tables.clone(), - state: { - let ctrl_lock = zlock!(self.tables.ctrl_lock); - let mut tables = zwrite!(self.tables.tables); - let zid = tables.zid; - let face = tables - .open_face(zid, WhatAmI::Client, primitives) - .upgrade() - .unwrap(); - drop(tables); - drop(ctrl_lock); - face - }, - }) - } - - pub fn new_transport_unicast( - &self, - transport: TransportUnicast, - ) -> ZResult> { - let ctrl_lock = zlock!(self.tables.ctrl_lock); - let mut tables = zwrite!(self.tables.tables); - let whatami = transport.get_whatami()?; - - let link_id = match (self.whatami, whatami) { - (WhatAmI::Router, WhatAmI::Router) => tables - .routers_net - .as_mut() - .unwrap() - .add_link(transport.clone()), - (WhatAmI::Router, WhatAmI::Peer) - | (WhatAmI::Peer, WhatAmI::Router) - | (WhatAmI::Peer, WhatAmI::Peer) => { - if let Some(net) = tables.peers_net.as_mut() { - net.add_link(transport.clone()) - } else { - 0 - } - } - _ => 0, + state: newface, }; - if tables.full_net(WhatAmI::Router) && tables.full_net(WhatAmI::Peer) { - tables.shared_nodes = shared_nodes( - tables.routers_net.as_ref().unwrap(), - tables.peers_net.as_ref().unwrap(), - ); - } + let _ = mux.face.set(face.clone()); - let handler = Arc::new(LinkStateInterceptor::new( - transport.clone(), - self.tables.clone(), - Face { - tables: self.tables.clone(), - state: tables - .open_net_face( - transport.get_zid().unwrap(), - whatami, - #[cfg(feature = "stats")] - transport.get_stats().unwrap(), - Arc::new(Mux::new(transport)), - link_id, - ) - .upgrade() - .unwrap(), - }, - )); + ctrl_lock.new_transport_unicast_face(&mut tables, &self.tables, &mut face, &transport)?; - match (self.whatami, whatami) { - (WhatAmI::Router, WhatAmI::Router) => { - tables.schedule_compute_trees(self.tables.clone(), WhatAmI::Router); - } - (WhatAmI::Router, WhatAmI::Peer) - | (WhatAmI::Peer, WhatAmI::Router) - | (WhatAmI::Peer, WhatAmI::Peer) => { - if tables.full_net(WhatAmI::Peer) { - tables.schedule_compute_trees(self.tables.clone(), WhatAmI::Peer); - } - } - _ => (), - } - drop(tables); - drop(ctrl_lock); - Ok(handler) + Ok(Arc::new(DeMux::new(face, Some(transport), ingress))) } pub fn new_transport_multicast(&self, transport: TransportMulticast) -> ZResult<()> { + let ctrl_lock = zlock!(self.tables.ctrl_lock); let mut tables = zwrite!(self.tables.tables); let fid = tables.face_counter; tables.face_counter += 1; - tables.mcast_groups.push(FaceState::new( + let interceptor = InterceptorsChain::from( + tables + .interceptors + .iter() + .filter_map(|itor| itor.new_transport_multicast(&transport)) + .collect::>(), + ); + let mux = Arc::new(McastMux::new(transport.clone(), interceptor)); + let face = FaceState::new( fid, ZenohId::from_str("1").unwrap(), WhatAmI::Peer, - false, #[cfg(feature = "stats")] None, - Arc::new(McastMux::new(transport.clone())), - 0, + mux.clone(), Some(transport), - )); + ctrl_lock.new_face(), + ); + let _ = mux.face.set(Face { + state: face.clone(), + tables: self.tables.clone(), + }); + tables.mcast_groups.push(face); // recompute routes let mut root_res = tables.root_res.clone(); - compute_data_routes_from(&mut tables, &mut root_res); + update_data_routes_from(&mut tables, &mut root_res); Ok(()) } @@ -672,225 +192,40 @@ impl Router { &self, transport: TransportMulticast, peer: TransportPeer, - ) -> ZResult>> { + ) -> ZResult> { + let ctrl_lock = zlock!(self.tables.ctrl_lock); let mut tables = zwrite!(self.tables.tables); let fid = tables.face_counter; tables.face_counter += 1; + let interceptor = InterceptorsChain::from( + tables + .interceptors + .iter() + .filter_map(|itor| itor.new_peer_multicast(&transport)) + .collect::>(), + ); let face_state = FaceState::new( fid, peer.zid, WhatAmI::Client, // Quick hack - false, #[cfg(feature = "stats")] Some(transport.get_stats().unwrap()), Arc::new(DummyPrimitives), - 0, Some(transport), + ctrl_lock.new_face(), ); tables.mcast_faces.push(face_state.clone()); // recompute routes let mut root_res = tables.root_res.clone(); - compute_data_routes_from(&mut tables, &mut root_res); - Ok(Arc::new(DeMux::new(Face { - tables: self.tables.clone(), - state: face_state, - }))) - } -} - -pub struct LinkStateInterceptor { - pub(crate) transport: TransportUnicast, - pub(crate) tables: Arc, - pub(crate) face: Face, - pub(crate) demux: DeMux, -} - -impl LinkStateInterceptor { - fn new(transport: TransportUnicast, tables: Arc, face: Face) -> Self { - LinkStateInterceptor { - transport, - tables, - face: face.clone(), - demux: DeMux::new(face), - } - } -} - -impl TransportPeerEventHandler for LinkStateInterceptor { - fn handle_message(&self, msg: NetworkMessage) -> ZResult<()> { - log::trace!("Recv {:?}", msg); - match msg.body { - NetworkBody::OAM(oam) => { - if oam.id == OAM_LINKSTATE { - if let ZExtBody::ZBuf(buf) = oam.body { - if let Ok(zid) = self.transport.get_zid() { - use zenoh_buffers::reader::HasReader; - use zenoh_codec::RCodec; - let codec = Zenoh080Routing::new(); - let mut reader = buf.reader(); - let list: LinkStateList = codec.read(&mut reader).unwrap(); - - let ctrl_lock = zlock!(self.tables.ctrl_lock); - let mut tables = zwrite!(self.tables.tables); - let whatami = self.transport.get_whatami()?; - match (tables.whatami, whatami) { - (WhatAmI::Router, WhatAmI::Router) => { - for (_, removed_node) in tables - .routers_net - .as_mut() - .unwrap() - .link_states(list.link_states, zid) - .removed_nodes - { - queries_remove_node( - &mut tables, - &removed_node.zid, - WhatAmI::Router, - ); - pubsub_remove_node( - &mut tables, - &removed_node.zid, - WhatAmI::Router, - ); - } - - if tables.full_net(WhatAmI::Peer) { - tables.shared_nodes = shared_nodes( - tables.routers_net.as_ref().unwrap(), - tables.peers_net.as_ref().unwrap(), - ); - } - - tables.schedule_compute_trees( - self.tables.clone(), - WhatAmI::Router, - ); - } - (WhatAmI::Router, WhatAmI::Peer) - | (WhatAmI::Peer, WhatAmI::Router) - | (WhatAmI::Peer, WhatAmI::Peer) => { - if let Some(net) = tables.peers_net.as_mut() { - let changes = net.link_states(list.link_states, zid); - if tables.full_net(WhatAmI::Peer) { - for (_, removed_node) in changes.removed_nodes { - pubsub_remove_node( - &mut tables, - &removed_node.zid, - WhatAmI::Peer, - ); - queries_remove_node( - &mut tables, - &removed_node.zid, - WhatAmI::Peer, - ); - } - - if tables.whatami == WhatAmI::Router { - tables.shared_nodes = shared_nodes( - tables.routers_net.as_ref().unwrap(), - tables.peers_net.as_ref().unwrap(), - ); - } - - tables.schedule_compute_trees( - self.tables.clone(), - WhatAmI::Peer, - ); - } else { - for (_, updated_node) in changes.updated_nodes { - queries_linkstate_change( - &mut tables, - &updated_node.zid, - &updated_node.links, - ); - pubsub_linkstate_change( - &mut tables, - &updated_node.zid, - &updated_node.links, - ); - } - } - } - } - _ => (), - }; - drop(tables); - drop(ctrl_lock); - } - } - } - - Ok(()) - } - _ => self.demux.handle_message(msg), - } - } - - fn new_link(&self, _link: Link) {} - - fn del_link(&self, _link: Link) {} - - fn closing(&self) { - self.demux.closing(); - let tables_ref = self.tables.clone(); - match (self.transport.get_zid(), self.transport.get_whatami()) { - (Ok(zid), Ok(whatami)) => { - let ctrl_lock = zlock!(tables_ref.ctrl_lock); - let mut tables = zwrite!(tables_ref.tables); - match (tables.whatami, whatami) { - (WhatAmI::Router, WhatAmI::Router) => { - for (_, removed_node) in - tables.routers_net.as_mut().unwrap().remove_link(&zid) - { - queries_remove_node(&mut tables, &removed_node.zid, WhatAmI::Router); - pubsub_remove_node(&mut tables, &removed_node.zid, WhatAmI::Router); - } - - if tables.full_net(WhatAmI::Peer) { - tables.shared_nodes = shared_nodes( - tables.routers_net.as_ref().unwrap(), - tables.peers_net.as_ref().unwrap(), - ); - } - - tables.schedule_compute_trees(tables_ref.clone(), WhatAmI::Router); - } - (WhatAmI::Router, WhatAmI::Peer) - | (WhatAmI::Peer, WhatAmI::Router) - | (WhatAmI::Peer, WhatAmI::Peer) => { - if tables.full_net(WhatAmI::Peer) { - for (_, removed_node) in - tables.peers_net.as_mut().unwrap().remove_link(&zid) - { - queries_remove_node(&mut tables, &removed_node.zid, WhatAmI::Peer); - pubsub_remove_node(&mut tables, &removed_node.zid, WhatAmI::Peer); - } - - if tables.whatami == WhatAmI::Router { - tables.shared_nodes = shared_nodes( - tables.routers_net.as_ref().unwrap(), - tables.peers_net.as_ref().unwrap(), - ); - } - - tables.schedule_compute_trees(tables_ref.clone(), WhatAmI::Peer); - } else if let Some(net) = tables.peers_net.as_mut() { - net.remove_link(&zid); - } - } - _ => (), - }; - drop(tables); - drop(ctrl_lock); - } - (_, _) => log::error!("Closed transport in session closing!"), - } - } - - fn closed(&self) {} - - fn as_any(&self) -> &dyn Any { - self + update_data_routes_from(&mut tables, &mut root_res); + Ok(Arc::new(DeMux::new( + Face { + tables: self.tables.clone(), + state: face_state, + }, + None, + interceptor, + ))) } } diff --git a/zenoh/src/net/runtime/adminspace.rs b/zenoh/src/net/runtime/adminspace.rs index b109774054..f6fb13e76e 100644 --- a/zenoh/src/net/runtime/adminspace.rs +++ b/zenoh/src/net/runtime/adminspace.rs @@ -10,9 +10,10 @@ // // Contributors: // ZettaScale Zenoh Team, -use super::routing::face::Face; +use super::routing::dispatcher::face::Face; use super::Runtime; use crate::key_expr::KeyExpr; +use crate::net::primitives::Primitives; use crate::plugins::sealed::{self as plugins}; use crate::prelude::sync::{Sample, SyncResolve}; use crate::queryable::Query; @@ -27,7 +28,7 @@ use std::convert::TryInto; use std::sync::Arc; use std::sync::Mutex; use zenoh_buffers::buffer::SplitBuffer; -use zenoh_config::{ConfigValidator, ValidatedMap}; +use zenoh_config::{ConfigValidator, ValidatedMap, WhatAmI}; use zenoh_plugin_trait::{PluginControl, PluginStatus}; use zenoh_protocol::core::key_expr::keyexpr; use zenoh_protocol::{ @@ -40,7 +41,7 @@ use zenoh_protocol::{ zenoh::{PushBody, RequestBody}, }; use zenoh_result::ZResult; -use zenoh_transport::{primitives::Primitives, unicast::TransportUnicast}; +use zenoh_transport::unicast::TransportUnicast; pub struct AdminContext { runtime: Runtime, @@ -273,7 +274,7 @@ impl AdminSpace { ext_tstamp: None, ext_nodeid: ext::NodeIdType::default(), body: DeclareBody::DeclareQueryable(DeclareQueryable { - id: 0, // TODO + id: 0, // @TODO use proper QueryableId (#703) wire_expr: [&root_key, "/**"].concat().into(), ext_info: QueryableInfo { complete: 0, @@ -287,7 +288,7 @@ impl AdminSpace { ext_tstamp: None, ext_nodeid: ext::NodeIdType::default(), body: DeclareBody::DeclareSubscriber(DeclareSubscriber { - id: 0, // TODO + id: 0, // @TODO use proper SubscriberId (#703) wire_expr: [&root_key, "/config/**"].concat().into(), ext_info: SubscriberInfo::default(), }), @@ -449,6 +450,38 @@ impl Primitives for AdminSpace { } } +impl crate::net::primitives::EPrimitives for AdminSpace { + #[inline] + fn send_declare(&self, ctx: crate::net::routing::RoutingContext) { + (self as &dyn Primitives).send_declare(ctx.msg) + } + + #[inline] + fn send_push(&self, msg: Push) { + (self as &dyn Primitives).send_push(msg) + } + + #[inline] + fn send_request(&self, ctx: crate::net::routing::RoutingContext) { + (self as &dyn Primitives).send_request(ctx.msg) + } + + #[inline] + fn send_response(&self, ctx: crate::net::routing::RoutingContext) { + (self as &dyn Primitives).send_response(ctx.msg) + } + + #[inline] + fn send_response_final(&self, ctx: crate::net::routing::RoutingContext) { + (self as &dyn Primitives).send_response_final(ctx.msg) + } + + #[inline] + fn send_close(&self) { + (self as &dyn Primitives).send_close() + } +} + fn router_data(context: &AdminContext, query: Query) { let reply_key: OwnedKeyExpr = format!("@/router/{}", context.zid_str).try_into().unwrap(); @@ -582,10 +615,8 @@ fn routers_linkstate_data(context: &AdminContext, query: Query) { reply_key, Value::from( tables - .routers_net - .as_ref() - .map(|net| net.dot()) - .unwrap_or_else(|| "graph {}".to_string()) + .hat_code + .info(&tables, WhatAmI::Router) .as_bytes() .to_vec(), ) @@ -609,10 +640,8 @@ fn peers_linkstate_data(context: &AdminContext, query: Query) { reply_key, Value::from( tables - .peers_net - .as_ref() - .map(|net| net.dot()) - .unwrap_or_else(|| "graph {}".to_string()) + .hat_code + .info(&tables, WhatAmI::Peer) .as_bytes() .to_vec(), ) @@ -626,7 +655,7 @@ fn peers_linkstate_data(context: &AdminContext, query: Query) { fn subscribers_data(context: &AdminContext, query: Query) { let tables = zread!(context.runtime.state.router.tables.tables); - for sub in tables.router_subs.iter() { + for sub in tables.hat_code.get_subscriptions(&tables) { let key = KeyExpr::try_from(format!( "@/router/{}/subscriber/{}", context.zid_str, @@ -643,7 +672,7 @@ fn subscribers_data(context: &AdminContext, query: Query) { fn queryables_data(context: &AdminContext, query: Query) { let tables = zread!(context.runtime.state.router.tables.tables); - for qabl in tables.router_qabls.iter() { + for qabl in tables.hat_code.get_queryables(&tables) { let key = KeyExpr::try_from(format!( "@/router/{}/queryable/{}", context.zid_str, @@ -683,7 +712,7 @@ fn plugins_status(context: &AdminContext, query: Query) { for plugin in guard.started_plugins_iter() { with_extended_string(&mut root_key, &[plugin.name()], |plugin_key| { - // TODO: response to "__version__", this need not to be implemented by each plugin + // @TODO: response to "__version__", this need not to be implemented by each plugin with_extended_string(plugin_key, &["/__path__"], |plugin_path_key| { if let Ok(key_expr) = KeyExpr::try_from(plugin_path_key.clone()) { if query.key_expr().intersects(&key_expr) { diff --git a/zenoh/src/net/runtime/mod.rs b/zenoh/src/net/runtime/mod.rs index 77b76fa5ff..61e36aafb7 100644 --- a/zenoh/src/net/runtime/mod.rs +++ b/zenoh/src/net/runtime/mod.rs @@ -23,10 +23,9 @@ mod adminspace; // ignore_tagging pub(crate) mod orchestrator; +use super::primitives::DeMux; use super::routing; -use super::routing::face::Face; -use super::routing::pubsub::full_reentrant_route_data; -use super::routing::router::{LinkStateInterceptor, Router}; +use super::routing::router::Router; use crate::config::{unwrap_or_default, Config, ModeDependent, Notifier}; use crate::GIT_VERSION; pub use adminspace::AdminSpace; @@ -35,20 +34,18 @@ use futures::stream::StreamExt; use futures::Future; use std::any::Any; use std::sync::Arc; -use std::time::Duration; use stop_token::future::FutureExt; use stop_token::{StopSource, TimedOutError}; use uhlc::{HLCBuilder, HLC}; use zenoh_link::{EndPoint, Link}; use zenoh_plugin_trait::{PluginStartArgs, StructVersion}; -use zenoh_protocol::core::{whatami::WhatAmIMatcher, Locator, WhatAmI, ZenohId}; -use zenoh_protocol::network::{NetworkBody, NetworkMessage}; +use zenoh_protocol::core::{Locator, WhatAmI, ZenohId}; +use zenoh_protocol::network::NetworkMessage; use zenoh_result::{bail, ZResult}; use zenoh_sync::get_mut_unchecked; use zenoh_transport::{ - multicast::TransportMulticast, primitives::DeMux, unicast::TransportUnicast, - TransportEventHandler, TransportManager, TransportMulticastEventHandler, TransportPeer, - TransportPeerEventHandler, + multicast::TransportMulticast, unicast::TransportUnicast, TransportEventHandler, + TransportManager, TransportMulticastEventHandler, TransportPeer, TransportPeerEventHandler, }; struct RuntimeState { @@ -102,33 +99,8 @@ impl Runtime { let metadata = config.metadata().clone(); let hlc = (*unwrap_or_default!(config.timestamping().enabled().get(whatami))) .then(|| Arc::new(HLCBuilder::new().with_id(uhlc::ID::from(&zid)).build())); - let drop_future_timestamp = - unwrap_or_default!(config.timestamping().drop_future_timestamp()); - - let gossip = unwrap_or_default!(config.scouting().gossip().enabled()); - let gossip_multihop = unwrap_or_default!(config.scouting().gossip().multihop()); - let autoconnect = if gossip { - *unwrap_or_default!(config.scouting().gossip().autoconnect().get(whatami)) - } else { - WhatAmIMatcher::empty() - }; - let router_link_state = whatami == WhatAmI::Router; - let peer_link_state = whatami != WhatAmI::Client - && unwrap_or_default!(config.routing().peer().mode()) == *"linkstate"; - let router_peers_failover_brokering = - unwrap_or_default!(config.routing().router().peers_failover_brokering()); - let queries_default_timeout = - Duration::from_millis(unwrap_or_default!(config.queries_default_timeout())); - - let router = Arc::new(Router::new( - zid, - whatami, - hlc.clone(), - drop_future_timestamp, - router_peers_failover_brokering, - queries_default_timeout, - )); + let router = Arc::new(Router::new(zid, whatami, hlc.clone(), &config)); let handler = Arc::new(RuntimeTransportEventHandler { runtime: std::sync::RwLock::new(None), @@ -158,15 +130,7 @@ impl Runtime { }), }; *handler.runtime.write().unwrap() = Some(runtime.clone()); - get_mut_unchecked(&mut runtime.state.router.clone()).init_link_state( - runtime.clone(), - router_link_state, - peer_link_state, - router_peers_failover_brokering, - gossip, - gossip_multihop, - autoconnect, - ); + get_mut_unchecked(&mut runtime.state.router.clone()).init_link_state(runtime.clone()); let receiver = config.subscribe(); runtime.spawn({ @@ -316,27 +280,12 @@ impl TransportEventHandler for RuntimeTransportEventHandler { pub(super) struct RuntimeSession { pub(super) runtime: Runtime, pub(super) endpoint: std::sync::RwLock>, - pub(super) main_handler: Arc, + pub(super) main_handler: Arc, pub(super) slave_handlers: Vec>, } impl TransportPeerEventHandler for RuntimeSession { fn handle_message(&self, msg: NetworkMessage) -> ZResult<()> { - // critical path shortcut - if let NetworkBody::Push(data) = msg.body { - let face = &self.main_handler.face.state; - - full_reentrant_route_data( - &self.main_handler.tables.tables, - face, - &data.wire_expr, - data.ext_qos, - data.payload, - data.ext_nodeid.node_id.into(), - ); - return Ok(()); - } - self.main_handler.handle_message(msg) } @@ -415,7 +364,7 @@ impl TransportMulticastEventHandler for RuntimeMuticastGroup { } pub(super) struct RuntimeMuticastSession { - pub(super) main_handler: Arc>, + pub(super) main_handler: Arc, pub(super) slave_handlers: Vec>, } diff --git a/zenoh/src/net/tests/tables.rs b/zenoh/src/net/tests/tables.rs index 4e959c3c4f..c12f90ba99 100644 --- a/zenoh/src/net/tests/tables.rs +++ b/zenoh/src/net/tests/tables.rs @@ -11,14 +11,15 @@ // Contributors: // ZettaScale Zenoh Team, // -use crate::net::routing::pubsub::*; -use crate::net::routing::router::{self, *}; +use crate::net::primitives::{DummyPrimitives, EPrimitives, Primitives}; +use crate::net::routing::dispatcher::tables::{self, Tables}; +use crate::net::routing::router::*; +use crate::net::routing::RoutingContext; use std::convert::{TryFrom, TryInto}; -use std::sync::{Arc, Mutex, RwLock}; -use std::time::Duration; +use std::sync::Arc; use uhlc::HLC; use zenoh_buffers::ZBuf; -use zenoh_config::defaults::queries_default_timeout; +use zenoh_config::Config; use zenoh_core::zlock; use zenoh_protocol::core::Encoding; use zenoh_protocol::core::{ @@ -28,29 +29,20 @@ use zenoh_protocol::network::declare::subscriber::ext::SubscriberInfo; use zenoh_protocol::network::declare::Mode; use zenoh_protocol::network::{ext, Declare, DeclareBody, DeclareKeyExpr}; use zenoh_protocol::zenoh::{PushBody, Put}; -use zenoh_transport::primitives::{DummyPrimitives, Primitives}; #[test] fn base_test() { - let tables = TablesLock { - tables: RwLock::new(Tables::new( - ZenohId::try_from([1]).unwrap(), - WhatAmI::Client, - Some(Arc::new(HLC::default())), - false, - true, - Duration::from_millis(queries_default_timeout), - )), - ctrl_lock: Mutex::new(()), - queries_lock: RwLock::new(()), - }; - - let primitives = Arc::new(DummyPrimitives::new()); - let face = zwrite!(tables.tables).open_face( + let config = Config::default(); + let router = Router::new( ZenohId::try_from([1]).unwrap(), WhatAmI::Client, - primitives, + Some(Arc::new(HLC::default())), + &config, ); + let tables = router.tables.clone(); + + let primitives = Arc::new(DummyPrimitives {}); + let face = Arc::downgrade(&router.new_primitives(primitives).state); register_expr( &tables, &mut face.upgrade().unwrap(), @@ -68,12 +60,14 @@ fn base_test() { reliability: Reliability::Reliable, mode: Mode::Push, }; - declare_client_subscription( + + declare_subscription( + zlock!(tables.ctrl_lock).as_ref(), &tables, - zread!(tables.tables), &mut face.upgrade().unwrap(), &WireExpr::from(1).with_suffix("four/five"), &sub_info, + NodeId::default(), ); Tables::print(&zread!(tables.tables)); @@ -133,24 +127,17 @@ fn match_test() { ] .map(|s| keyexpr::new(s).unwrap()); - let tables = TablesLock { - tables: RwLock::new(Tables::new( - ZenohId::try_from([1]).unwrap(), - WhatAmI::Client, - Some(Arc::new(HLC::default())), - false, - true, - Duration::from_millis(queries_default_timeout), - )), - ctrl_lock: Mutex::new(()), - queries_lock: RwLock::new(()), - }; - let primitives = Arc::new(DummyPrimitives::new()); - let face = zwrite!(tables.tables).open_face( + let config = Config::default(); + let router = Router::new( ZenohId::try_from([1]).unwrap(), WhatAmI::Client, - primitives, + Some(Arc::new(HLC::default())), + &config, ); + let tables = router.tables.clone(); + + let primitives = Arc::new(DummyPrimitives {}); + let face = Arc::downgrade(&router.new_primitives(primitives).state); for (i, key_expr) in key_exprs.iter().enumerate() { register_expr( &tables, @@ -179,25 +166,17 @@ fn match_test() { #[test] fn clean_test() { - let tables = TablesLock { - tables: RwLock::new(Tables::new( - ZenohId::try_from([1]).unwrap(), - WhatAmI::Client, - Some(Arc::new(HLC::default())), - false, - true, - Duration::from_millis(queries_default_timeout), - )), - ctrl_lock: Mutex::new(()), - queries_lock: RwLock::new(()), - }; - - let primitives = Arc::new(DummyPrimitives::new()); - let face0 = zwrite!(tables.tables).open_face( + let config = Config::default(); + let router = Router::new( ZenohId::try_from([1]).unwrap(), WhatAmI::Client, - primitives, + Some(Arc::new(HLC::default())), + &config, ); + let tables = router.tables.clone(); + + let primitives = Arc::new(DummyPrimitives {}); + let face0 = Arc::downgrade(&router.new_primitives(primitives).state); assert!(face0.upgrade().is_some()); // -------------- @@ -255,12 +234,13 @@ fn clean_test() { mode: Mode::Push, }; - declare_client_subscription( + declare_subscription( + zlock!(tables.ctrl_lock).as_ref(), &tables, - zread!(tables.tables), &mut face0.upgrade().unwrap(), &"todrop1/todrop11".into(), &sub_info, + NodeId::default(), ); let optres2 = Resource::get_resource(zread!(tables.tables)._get_root(), "todrop1/todrop11") .map(|res| Arc::downgrade(&res)); @@ -268,34 +248,41 @@ fn clean_test() { let res2 = optres2.unwrap(); assert!(res2.upgrade().is_some()); - declare_client_subscription( + declare_subscription( + zlock!(tables.ctrl_lock).as_ref(), &tables, - zread!(tables.tables), &mut face0.upgrade().unwrap(), &WireExpr::from(1).with_suffix("/todrop12"), &sub_info, + NodeId::default(), ); let optres3 = Resource::get_resource(zread!(tables.tables)._get_root(), "todrop1/todrop12") .map(|res| Arc::downgrade(&res)); assert!(optres3.is_some()); let res3 = optres3.unwrap(); + println!("COUNT: {}", res3.strong_count()); assert!(res3.upgrade().is_some()); - forget_client_subscription( + undeclare_subscription( + zlock!(tables.ctrl_lock).as_ref(), &tables, - zread!(tables.tables), &mut face0.upgrade().unwrap(), &WireExpr::from(1).with_suffix("/todrop12"), + NodeId::default(), ); + + println!("COUNT2: {}", res3.strong_count()); + assert!(res1.upgrade().is_some()); assert!(res2.upgrade().is_some()); assert!(res3.upgrade().is_none()); - forget_client_subscription( + undeclare_subscription( + zlock!(tables.ctrl_lock).as_ref(), &tables, - zread!(tables.tables), &mut face0.upgrade().unwrap(), &"todrop1/todrop11".into(), + NodeId::default(), ); assert!(res1.upgrade().is_some()); assert!(res2.upgrade().is_none()); @@ -308,12 +295,13 @@ fn clean_test() { // -------------- register_expr(&tables, &mut face0.upgrade().unwrap(), 2, &"todrop3".into()); - declare_client_subscription( + declare_subscription( + zlock!(tables.ctrl_lock).as_ref(), &tables, - zread!(tables.tables), &mut face0.upgrade().unwrap(), &"todrop3".into(), &sub_info, + NodeId::default(), ); let optres1 = Resource::get_resource(zread!(tables.tables)._get_root(), "todrop3") .map(|res| Arc::downgrade(&res)); @@ -321,11 +309,12 @@ fn clean_test() { let res1 = optres1.unwrap(); assert!(res1.upgrade().is_some()); - forget_client_subscription( + undeclare_subscription( + zlock!(tables.ctrl_lock).as_ref(), &tables, - zread!(tables.tables), &mut face0.upgrade().unwrap(), &"todrop3".into(), + NodeId::default(), ); assert!(res1.upgrade().is_some()); @@ -335,19 +324,21 @@ fn clean_test() { // -------------- register_expr(&tables, &mut face0.upgrade().unwrap(), 3, &"todrop4".into()); register_expr(&tables, &mut face0.upgrade().unwrap(), 4, &"todrop5".into()); - declare_client_subscription( + declare_subscription( + zlock!(tables.ctrl_lock).as_ref(), &tables, - zread!(tables.tables), &mut face0.upgrade().unwrap(), &"todrop5".into(), &sub_info, + NodeId::default(), ); - declare_client_subscription( + declare_subscription( + zlock!(tables.ctrl_lock).as_ref(), &tables, - zread!(tables.tables), &mut face0.upgrade().unwrap(), &"todrop6".into(), &sub_info, + NodeId::default(), ); let optres1 = Resource::get_resource(zread!(tables.tables)._get_root(), "todrop4") @@ -367,7 +358,7 @@ fn clean_test() { assert!(res2.upgrade().is_some()); assert!(res3.upgrade().is_some()); - router::close_face(&tables, &face0); + tables::close_face(&tables, &face0); assert!(face0.upgrade().is_none()); assert!(res1.upgrade().is_none()); assert!(res2.upgrade().is_none()); @@ -452,20 +443,43 @@ impl Primitives for ClientPrimitives { fn send_close(&self) {} } +impl EPrimitives for ClientPrimitives { + fn send_declare(&self, ctx: RoutingContext) { + match ctx.msg.body { + DeclareBody::DeclareKeyExpr(d) => { + let name = self.get_name(&d.wire_expr); + zlock!(self.mapping).insert(d.id, name); + } + DeclareBody::UndeclareKeyExpr(u) => { + zlock!(self.mapping).remove(&u.id); + } + _ => (), + } + } + + fn send_push(&self, msg: zenoh_protocol::network::Push) { + *zlock!(self.data) = Some(msg.wire_expr.to_owned()); + } + + fn send_request(&self, _ctx: RoutingContext) {} + + fn send_response(&self, _ctx: RoutingContext) {} + + fn send_response_final(&self, _ctx: RoutingContext) {} + + fn send_close(&self) {} +} + #[test] fn client_test() { - let tables = TablesLock { - tables: RwLock::new(Tables::new( - ZenohId::try_from([1]).unwrap(), - WhatAmI::Client, - Some(Arc::new(HLC::default())), - false, - true, - Duration::from_millis(queries_default_timeout), - )), - ctrl_lock: Mutex::new(()), - queries_lock: RwLock::new(()), - }; + let config = Config::default(); + let router = Router::new( + ZenohId::try_from([1]).unwrap(), + WhatAmI::Client, + Some(Arc::new(HLC::default())), + &config, + ); + let tables = router.tables.clone(); let sub_info = SubscriberInfo { reliability: Reliability::Reliable, @@ -473,33 +487,32 @@ fn client_test() { }; let primitives0 = Arc::new(ClientPrimitives::new()); - - let face0 = zwrite!(tables.tables).open_face( - ZenohId::try_from([1]).unwrap(), - WhatAmI::Client, - primitives0.clone(), - ); + let face0 = Arc::downgrade(&router.new_primitives(primitives0.clone()).state); register_expr( &tables, &mut face0.upgrade().unwrap(), 11, &"test/client".into(), ); - primitives0.send_declare(Declare { - ext_qos: ext::QoSType::declare_default(), - ext_tstamp: None, - ext_nodeid: ext::NodeIdType::default(), - body: DeclareBody::DeclareKeyExpr(DeclareKeyExpr { - id: 11, - wire_expr: "test/client".into(), - }), - }); - declare_client_subscription( + Primitives::send_declare( + primitives0.as_ref(), + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::DeclareKeyExpr(DeclareKeyExpr { + id: 11, + wire_expr: "test/client".into(), + }), + }, + ); + declare_subscription( + zlock!(tables.ctrl_lock).as_ref(), &tables, - zread!(tables.tables), &mut face0.upgrade().unwrap(), &WireExpr::from(11).with_suffix("/**"), &sub_info, + NodeId::default(), ); register_expr( &tables, @@ -507,43 +520,46 @@ fn client_test() { 12, &WireExpr::from(11).with_suffix("/z1_pub1"), ); - primitives0.send_declare(Declare { - ext_qos: ext::QoSType::declare_default(), - ext_tstamp: None, - ext_nodeid: ext::NodeIdType::default(), - body: DeclareBody::DeclareKeyExpr(DeclareKeyExpr { - id: 12, - wire_expr: WireExpr::from(11).with_suffix("/z1_pub1"), - }), - }); + Primitives::send_declare( + primitives0.as_ref(), + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::DeclareKeyExpr(DeclareKeyExpr { + id: 12, + wire_expr: WireExpr::from(11).with_suffix("/z1_pub1"), + }), + }, + ); let primitives1 = Arc::new(ClientPrimitives::new()); - let face1 = zwrite!(tables.tables).open_face( - ZenohId::try_from([1]).unwrap(), - WhatAmI::Client, - primitives1.clone(), - ); + let face1 = Arc::downgrade(&router.new_primitives(primitives1.clone()).state); register_expr( &tables, &mut face1.upgrade().unwrap(), 21, &"test/client".into(), ); - primitives1.send_declare(Declare { - ext_qos: ext::QoSType::declare_default(), - ext_tstamp: None, - ext_nodeid: ext::NodeIdType::default(), - body: DeclareBody::DeclareKeyExpr(DeclareKeyExpr { - id: 21, - wire_expr: "test/client".into(), - }), - }); - declare_client_subscription( + Primitives::send_declare( + primitives1.as_ref(), + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::DeclareKeyExpr(DeclareKeyExpr { + id: 21, + wire_expr: "test/client".into(), + }), + }, + ); + declare_subscription( + zlock!(tables.ctrl_lock).as_ref(), &tables, - zread!(tables.tables), &mut face1.upgrade().unwrap(), &WireExpr::from(21).with_suffix("/**"), &sub_info, + NodeId::default(), ); register_expr( &tables, @@ -551,43 +567,46 @@ fn client_test() { 22, &WireExpr::from(21).with_suffix("/z2_pub1"), ); - primitives1.send_declare(Declare { - ext_qos: ext::QoSType::declare_default(), - ext_tstamp: None, - ext_nodeid: ext::NodeIdType::default(), - body: DeclareBody::DeclareKeyExpr(DeclareKeyExpr { - id: 22, - wire_expr: WireExpr::from(21).with_suffix("/z2_pub1"), - }), - }); + Primitives::send_declare( + primitives1.as_ref(), + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::DeclareKeyExpr(DeclareKeyExpr { + id: 22, + wire_expr: WireExpr::from(21).with_suffix("/z2_pub1"), + }), + }, + ); let primitives2 = Arc::new(ClientPrimitives::new()); - let face2 = zwrite!(tables.tables).open_face( - ZenohId::try_from([1]).unwrap(), - WhatAmI::Client, - primitives2.clone(), - ); + let face2 = Arc::downgrade(&router.new_primitives(primitives2.clone()).state); register_expr( &tables, &mut face2.upgrade().unwrap(), 31, &"test/client".into(), ); - primitives2.send_declare(Declare { - ext_qos: ext::QoSType::declare_default(), - ext_tstamp: None, - ext_nodeid: ext::NodeIdType::default(), - body: DeclareBody::DeclareKeyExpr(DeclareKeyExpr { - id: 31, - wire_expr: "test/client".into(), - }), - }); - declare_client_subscription( + Primitives::send_declare( + primitives2.as_ref(), + Declare { + ext_qos: ext::QoSType::declare_default(), + ext_tstamp: None, + ext_nodeid: ext::NodeIdType::default(), + body: DeclareBody::DeclareKeyExpr(DeclareKeyExpr { + id: 31, + wire_expr: "test/client".into(), + }), + }, + ); + declare_subscription( + zlock!(tables.ctrl_lock).as_ref(), &tables, - zread!(tables.tables), &mut face2.upgrade().unwrap(), &WireExpr::from(31).with_suffix("/**"), &sub_info, + NodeId::default(), ); primitives0.clear_data(); @@ -595,7 +614,7 @@ fn client_test() { primitives2.clear_data(); full_reentrant_route_data( - &tables.tables, + &tables, &face0.upgrade().unwrap(), &"test/client/z1_wr1".into(), ext::QoSType::default(), @@ -605,9 +624,9 @@ fn client_test() { ext_sinfo: None, #[cfg(feature = "shared-memory")] ext_shm: None, - ext_attachment: None, ext_unknown: vec![], payload: ZBuf::empty(), + ext_attachment: None, }), 0, ); @@ -628,7 +647,7 @@ fn client_test() { primitives1.clear_data(); primitives2.clear_data(); full_reentrant_route_data( - &tables.tables, + &router.tables, &face0.upgrade().unwrap(), &WireExpr::from(11).with_suffix("/z1_wr2"), ext::QoSType::default(), @@ -638,9 +657,9 @@ fn client_test() { ext_sinfo: None, #[cfg(feature = "shared-memory")] ext_shm: None, - ext_attachment: None, ext_unknown: vec![], payload: ZBuf::empty(), + ext_attachment: None, }), 0, ); @@ -661,7 +680,7 @@ fn client_test() { primitives1.clear_data(); primitives2.clear_data(); full_reentrant_route_data( - &tables.tables, + &router.tables, &face1.upgrade().unwrap(), &"test/client/**".into(), ext::QoSType::default(), @@ -671,9 +690,9 @@ fn client_test() { ext_sinfo: None, #[cfg(feature = "shared-memory")] ext_shm: None, - ext_attachment: None, ext_unknown: vec![], payload: ZBuf::empty(), + ext_attachment: None, }), 0, ); @@ -694,7 +713,7 @@ fn client_test() { primitives1.clear_data(); primitives2.clear_data(); full_reentrant_route_data( - &tables.tables, + &router.tables, &face0.upgrade().unwrap(), &12.into(), ext::QoSType::default(), @@ -704,9 +723,9 @@ fn client_test() { ext_sinfo: None, #[cfg(feature = "shared-memory")] ext_shm: None, - ext_attachment: None, ext_unknown: vec![], payload: ZBuf::empty(), + ext_attachment: None, }), 0, ); @@ -727,7 +746,7 @@ fn client_test() { primitives1.clear_data(); primitives2.clear_data(); full_reentrant_route_data( - &tables.tables, + &router.tables, &face1.upgrade().unwrap(), &22.into(), ext::QoSType::default(), @@ -737,9 +756,9 @@ fn client_test() { ext_sinfo: None, #[cfg(feature = "shared-memory")] ext_shm: None, - ext_attachment: None, ext_unknown: vec![], payload: ZBuf::empty(), + ext_attachment: None, }), 0, ); diff --git a/zenoh/src/publication.rs b/zenoh/src/publication.rs index e175dd7622..d6949133f0 100644 --- a/zenoh/src/publication.rs +++ b/zenoh/src/publication.rs @@ -17,7 +17,7 @@ use crate::handlers::Callback; #[zenoh_macros::unstable] use crate::handlers::DefaultHandler; -use crate::net::transport::primitives::Primitives; +use crate::net::primitives::Primitives; use crate::prelude::*; #[zenoh_macros::unstable] use crate::sample::Attachment; diff --git a/zenoh/src/queryable.rs b/zenoh/src/queryable.rs index 3971f749ba..f830988cd4 100644 --- a/zenoh/src/queryable.rs +++ b/zenoh/src/queryable.rs @@ -15,6 +15,7 @@ //! Queryable primitives. use crate::handlers::{locked, DefaultHandler}; +use crate::net::primitives::Primitives; use crate::prelude::*; #[zenoh_macros::unstable] use crate::query::ReplyKeyExpr; @@ -35,7 +36,6 @@ use zenoh_protocol::zenoh::ext::ValueType; use zenoh_protocol::zenoh::reply::ext::ConsolidationType; use zenoh_protocol::zenoh::{self, ResponseBody}; use zenoh_result::ZResult; -use zenoh_transport::primitives::Primitives; pub(crate) struct QueryInner { /// The key expression of this Query. @@ -240,7 +240,7 @@ impl SyncResolve for ReplyBuilder<'_> { { Some(zenoh::reply::ext::SourceInfoType { zid: data_info.source_id.unwrap_or_default(), - eid: 0, // TODO + eid: 0, // @TODO use proper EntityId (#703) sn: data_info.source_sn.unwrap_or_default() as u32, }) } else { @@ -257,7 +257,7 @@ impl SyncResolve for ReplyBuilder<'_> { ext_tstamp: None, ext_respid: Some(response::ext::ResponderIdType { zid: self.query.inner.zid, - eid: 0, // TODO + eid: 0, // @TODO use proper EntityId (#703) }), }); Ok(()) @@ -287,7 +287,7 @@ impl SyncResolve for ReplyBuilder<'_> { ext_tstamp: None, ext_respid: Some(response::ext::ResponderIdType { zid: self.query.inner.zid, - eid: 0, // TODO + eid: 0, // @TODO use proper EntityId (#703) }), }); Ok(()) diff --git a/zenoh/src/session.rs b/zenoh/src/session.rs index f7a8dc25d9..d220fcb9c6 100644 --- a/zenoh/src/session.rs +++ b/zenoh/src/session.rs @@ -20,9 +20,9 @@ use crate::info::*; use crate::key_expr::KeyExprInner; #[zenoh_macros::unstable] use crate::liveliness::{Liveliness, LivelinessTokenState}; -use crate::net::routing::face::Face; +use crate::net::primitives::Primitives; +use crate::net::routing::dispatcher::face::Face; use crate::net::runtime::Runtime; -use crate::net::transport::primitives::Primitives; use crate::prelude::Locality; use crate::prelude::{KeyExpr, Parameters}; use crate::publication::*; @@ -278,7 +278,6 @@ impl Resource { } } - // ignore_tagging #[derive(Clone)] pub enum SessionRef<'a> { @@ -286,6 +285,69 @@ pub enum SessionRef<'a> { Shared(Arc), } +impl<'s, 'a> SessionDeclarations<'s, 'a> for SessionRef<'a> { + fn declare_subscriber<'b, TryIntoKeyExpr>( + &'s self, + key_expr: TryIntoKeyExpr, + ) -> SubscriberBuilder<'a, 'b, PushMode, DefaultHandler> + where + TryIntoKeyExpr: TryInto>, + >>::Error: Into, + { + SubscriberBuilder { + session: self.clone(), + key_expr: TryIntoKeyExpr::try_into(key_expr).map_err(Into::into), + reliability: Reliability::default(), + mode: PushMode, + origin: Locality::default(), + handler: DefaultHandler, + } + } + fn declare_queryable<'b, TryIntoKeyExpr>( + &'s self, + key_expr: TryIntoKeyExpr, + ) -> QueryableBuilder<'a, 'b, DefaultHandler> + where + TryIntoKeyExpr: TryInto>, + >>::Error: Into, + { + QueryableBuilder { + session: self.clone(), + key_expr: key_expr.try_into().map_err(Into::into), + complete: false, + origin: Locality::default(), + handler: DefaultHandler, + } + } + fn declare_publisher<'b, TryIntoKeyExpr>( + &'s self, + key_expr: TryIntoKeyExpr, + ) -> PublisherBuilder<'a, 'b> + where + TryIntoKeyExpr: TryInto>, + >>::Error: Into, + { + PublisherBuilder { + session: self.clone(), + key_expr: key_expr.try_into().map_err(Into::into), + congestion_control: CongestionControl::default(), + priority: Priority::default(), + destination: Locality::default(), + } + } + #[zenoh_macros::unstable] + fn liveliness(&'s self) -> Liveliness<'a> { + Liveliness { + session: self.clone(), + } + } + fn info(&'s self) -> SessionInfo<'a> { + SessionInfo { + session: self.clone(), + } + } +} + impl Deref for SessionRef<'_> { type Target = Session; @@ -511,45 +573,13 @@ impl Session { pub fn config(&self) -> &Notifier { self.runtime.config() } +} - /// Get informations about the zenoh [`Session`](Session). - /// - /// # Examples - /// ``` - /// # async_std::task::block_on(async { - /// use zenoh::prelude::r#async::*; - /// - /// let session = zenoh::open(config::peer()).res().await.unwrap(); - /// let info = session.info(); - /// # }) - /// ``` - // tags{} - pub fn info(&self) -> SessionInfo { - SessionInfo { - session: SessionRef::Borrow(self), - } +impl<'a> SessionDeclarations<'a, 'a> for Session { + fn info(&self) -> SessionInfo { + SessionRef::Borrow(self).info() } - - /// Create a [`Subscriber`](Subscriber) for the given key expression. - /// - /// # Arguments - /// - /// * `key_expr` - The key expression to subscribe to - /// - /// # Examples - /// ```no_run - /// # async_std::task::block_on(async { - /// use zenoh::prelude::r#async::*; - /// - /// let session = zenoh::open(config::peer()).res().await.unwrap(); - /// let subscriber = session.declare_subscriber("key/expression").res().await.unwrap(); - /// while let Ok(sample) = subscriber.recv_async().await { - /// println!("Received: {:?}", sample); - /// } - /// # }) - /// ``` - // tags{subscriber.create} - pub fn declare_subscriber<'a, 'b, TryIntoKeyExpr>( + fn declare_subscriber<'b, TryIntoKeyExpr>( &'a self, key_expr: TryIntoKeyExpr, ) -> SubscriberBuilder<'a, 'b, PushMode, DefaultHandler> @@ -557,40 +587,9 @@ impl Session { TryIntoKeyExpr: TryInto>, >>::Error: Into, { - SubscriberBuilder { - session: SessionRef::Borrow(self), - key_expr: TryIntoKeyExpr::try_into(key_expr).map_err(Into::into), - reliability: Reliability::default(), - mode: PushMode, - origin: Locality::default(), - handler: DefaultHandler, - } + SessionRef::Borrow(self).declare_subscriber(key_expr) } - - /// Create a [`Queryable`](Queryable) for the given key expression. - /// - /// # Arguments - /// - /// * `key_expr` - The key expression matching the queries the - /// [`Queryable`](Queryable) will reply to - /// - /// # Examples - /// ```no_run - /// # async_std::task::block_on(async { - /// use zenoh::prelude::r#async::*; - /// - /// let session = zenoh::open(config::peer()).res().await.unwrap(); - /// let queryable = session.declare_queryable("key/expression").res().await.unwrap(); - /// while let Ok(query) = queryable.recv_async().await { - /// query.reply(Ok(Sample::try_from( - /// "key/expression", - /// "value", - /// ).unwrap())).res().await.unwrap(); - /// } - /// # }) - /// ``` - // tags{queryable.create} - pub fn declare_queryable<'a, 'b, TryIntoKeyExpr>( + fn declare_queryable<'b, TryIntoKeyExpr>( &'a self, key_expr: TryIntoKeyExpr, ) -> QueryableBuilder<'a, 'b, DefaultHandler> @@ -598,36 +597,9 @@ impl Session { TryIntoKeyExpr: TryInto>, >>::Error: Into, { - QueryableBuilder { - session: SessionRef::Borrow(self), - key_expr: key_expr.try_into().map_err(Into::into), - complete: false, - origin: Locality::default(), - handler: DefaultHandler, - } + SessionRef::Borrow(self).declare_queryable(key_expr) } - - /// Create a [`Publisher`](crate::publication::Publisher) for the given key expression. - /// - /// # Arguments - /// - /// * `key_expr` - The key expression matching resources to write - /// - /// # Examples - /// ``` - /// # async_std::task::block_on(async { - /// use zenoh::prelude::r#async::*; - /// - /// let session = zenoh::open(config::peer()).res().await.unwrap(); - /// let publisher = session.declare_publisher("key/expression") - /// .res() - /// .await - /// .unwrap(); - /// publisher.put("value").res().await.unwrap(); - /// # }) - /// ``` - // tags{publisher.create} - pub fn declare_publisher<'a, 'b, TryIntoKeyExpr>( + fn declare_publisher<'b, TryIntoKeyExpr>( &'a self, key_expr: TryIntoKeyExpr, ) -> PublisherBuilder<'a, 'b> @@ -635,15 +607,15 @@ impl Session { TryIntoKeyExpr: TryInto>, >>::Error: Into, { - PublisherBuilder { - session: SessionRef::Borrow(self), - key_expr: key_expr.try_into().map_err(Into::into), - congestion_control: CongestionControl::default(), - priority: Priority::default(), - destination: Locality::default(), - } + SessionRef::Borrow(self).declare_publisher(key_expr) } + #[zenoh_macros::unstable] + fn liveliness(&'a self) -> Liveliness { + SessionRef::Borrow(self).liveliness() + } +} +impl Session { /// Informs Zenoh that you intend to use `key_expr` multiple times and that it should optimize its transmission. /// /// The returned `KeyExpr`'s internal structure may differ from what you would have obtained through a simple @@ -828,30 +800,6 @@ impl Session { handler: DefaultHandler, } } - - /// Obtain a [`Liveliness`] struct tied to this Zenoh [`Session`]. - /// - /// # Examples - /// ``` - /// # async_std::task::block_on(async { - /// use zenoh::prelude::r#async::*; - /// - /// let session = zenoh::open(config::peer()).res().await.unwrap(); - /// let liveliness = session - /// .liveliness() - /// .declare_token("key/expression") - /// .res() - /// .await - /// .unwrap(); - /// # }) - /// ``` - // tags{liveliness.create} - #[zenoh_macros::unstable] - pub fn liveliness(&self) -> Liveliness { - Liveliness { - session: SessionRef::Borrow(self), - } - } } impl Session { @@ -1195,7 +1143,7 @@ impl Session { ext_tstamp: None, ext_nodeid: ext::NodeIdType::default(), body: DeclareBody::UndeclareSubscriber(UndeclareSubscriber { - id: 0, // TODO + id: 0, // @TODO use proper SubscriberId (#703) ext_wire_expr: WireExprType { wire_expr }, }), }); @@ -1220,7 +1168,7 @@ impl Session { ext_tstamp: None, ext_nodeid: ext::NodeIdType::default(), body: DeclareBody::UndeclareSubscriber(UndeclareSubscriber { - id: 0, // TODO + id: 0, // @TODO use proper SubscriberId (#703) ext_wire_expr: WireExprType { wire_expr: key_expr.to_wire(self).to_owned(), }, @@ -1369,7 +1317,7 @@ impl Session { ext_tstamp: None, ext_nodeid: declare::ext::NodeIdType::default(), body: DeclareBody::DeclareQueryable(DeclareQueryable { - id: 0, // TODO + id: 0, // @TODO use proper QueryableId (#703) wire_expr: qable_state.key_expr.clone(), ext_info: qabl_info, }), @@ -1388,7 +1336,7 @@ impl Session { ext_tstamp: None, ext_nodeid: declare::ext::NodeIdType::default(), body: DeclareBody::DeclareQueryable(DeclareQueryable { - id: 0, // TODO + id: 0, // @TODO use proper QueryableId (#703) wire_expr: qable_state.key_expr.clone(), ext_info: qabl_info, }), @@ -1404,7 +1352,7 @@ impl Session { ext_tstamp: None, ext_nodeid: declare::ext::NodeIdType::default(), body: DeclareBody::UndeclareQueryable(UndeclareQueryable { - id: 0, // TODO + id: 0, // @TODO use proper QueryableId (#703) ext_wire_expr: WireExprType { wire_expr: qable_state.key_expr.clone(), }, @@ -1464,7 +1412,7 @@ impl Session { ext_tstamp: None, ext_nodeid: ext::NodeIdType::default(), body: DeclareBody::UndeclareSubscriber(UndeclareSubscriber { - id: 0, // TODO + id: 0, // @TODO use proper SubscriberId (#703) ext_wire_expr: WireExprType { wire_expr: key_expr.to_wire(self).to_owned(), }, @@ -1518,23 +1466,20 @@ impl Session { key_expr: &KeyExpr, destination: Locality, ) -> ZResult { - use crate::net::routing::router::RoutingExpr; - use zenoh_protocol::core::WhatAmI; + use crate::net::routing::dispatcher::tables::RoutingExpr; let router = self.runtime.router(); let tables = zread!(router.tables.tables); - let res = crate::net::routing::resource::Resource::get_resource( + let res = crate::net::routing::dispatcher::resource::Resource::get_resource( &tables.root_res, key_expr.as_str(), ); - let route = crate::net::routing::pubsub::get_data_route( + let route = crate::net::routing::dispatcher::pubsub::get_local_data_route( &tables, - WhatAmI::Client, - 0, &res, &mut RoutingExpr::new(&tables.root_res, key_expr.as_str()), - 0, ); + drop(tables); let matching = match destination { Locality::Any => !route.is_empty(), @@ -1766,7 +1711,7 @@ impl Session { let primitives = state.primitives.as_ref().unwrap().clone(); drop(state); primitives.send_request(Request { - id: 0, // TODO + id: 0, // @TODO compute a proper request ID wire_expr: key_expr.to_wire(self).to_owned(), ext_qos: ext::QoSType::request_default(), ext_tstamp: None, @@ -1994,7 +1939,7 @@ impl Session { } } -impl SessionDeclarations for Arc { +impl<'s> SessionDeclarations<'s, 'static> for Arc { /// Create a [`Subscriber`](Subscriber) for the given key expression. /// /// # Arguments @@ -2019,7 +1964,7 @@ impl SessionDeclarations for Arc { /// # }) /// ``` fn declare_subscriber<'b, TryIntoKeyExpr>( - &self, + &'s self, key_expr: TryIntoKeyExpr, ) -> SubscriberBuilder<'static, 'b, PushMode, DefaultHandler> where @@ -2064,7 +2009,7 @@ impl SessionDeclarations for Arc { /// # }) /// ``` fn declare_queryable<'b, TryIntoKeyExpr>( - &self, + &'s self, key_expr: TryIntoKeyExpr, ) -> QueryableBuilder<'static, 'b, DefaultHandler> where @@ -2100,7 +2045,7 @@ impl SessionDeclarations for Arc { /// # }) /// ``` fn declare_publisher<'b, TryIntoKeyExpr>( - &self, + &'s self, key_expr: TryIntoKeyExpr, ) -> PublisherBuilder<'static, 'b> where @@ -2133,11 +2078,17 @@ impl SessionDeclarations for Arc { /// # }) /// ``` #[zenoh_macros::unstable] - fn liveliness(&self) -> Liveliness<'static> { + fn liveliness(&'s self) -> Liveliness<'static> { Liveliness { session: SessionRef::Shared(self.clone()), } } + + fn info(&'s self) -> SessionInfo<'static> { + SessionInfo { + session: SessionRef::Shared(self.clone()), + } + } } impl Primitives for Session { @@ -2557,14 +2508,14 @@ impl fmt::Debug for Session { } } -/// Functions to create zenoh entities with `'static` lifetime. +/// Functions to create zenoh entities /// /// This trait contains functions to create zenoh entities like /// [`Subscriber`](crate::subscriber::Subscriber), and -/// [`Queryable`](crate::queryable::Queryable) with a `'static` lifetime. -/// This is useful to move zenoh entities to several threads and tasks. +/// [`Queryable`](crate::queryable::Queryable) /// -/// This trait is implemented for `Arc`. +/// This trait is implemented by [`Session`](crate::session::Session) itself and +/// by wrappers [`SessionRef`](crate::session::SessionRef) and [`Arc`](crate::session::Arc) /// /// # Examples /// ```no_run @@ -2583,8 +2534,7 @@ impl fmt::Debug for Session { /// }).await; /// # }) /// ``` -// tags{session} -pub trait SessionDeclarations { +pub trait SessionDeclarations<'s, 'a> { /// Create a [`Subscriber`](crate::subscriber::Subscriber) for the given key expression. /// /// # Arguments @@ -2608,14 +2558,13 @@ pub trait SessionDeclarations { /// }).await; /// # }) /// ``` - // tags{session.declare_subscriber} - fn declare_subscriber<'a, TryIntoKeyExpr>( - &self, + fn declare_subscriber<'b, TryIntoKeyExpr>( + &'s self, key_expr: TryIntoKeyExpr, - ) -> SubscriberBuilder<'static, 'a, PushMode, DefaultHandler> + ) -> SubscriberBuilder<'a, 'b, PushMode, DefaultHandler> where - TryIntoKeyExpr: TryInto>, - >>::Error: Into; + TryIntoKeyExpr: TryInto>, + >>::Error: Into; /// Create a [`Queryable`](crate::queryable::Queryable) for the given key expression. /// @@ -2644,14 +2593,13 @@ pub trait SessionDeclarations { /// }).await; /// # }) /// ``` - // tags{session.declare_queryable} - fn declare_queryable<'a, TryIntoKeyExpr>( - &self, + fn declare_queryable<'b, TryIntoKeyExpr>( + &'s self, key_expr: TryIntoKeyExpr, - ) -> QueryableBuilder<'static, 'a, DefaultHandler> + ) -> QueryableBuilder<'a, 'b, DefaultHandler> where - TryIntoKeyExpr: TryInto>, - >>::Error: Into; + TryIntoKeyExpr: TryInto>, + >>::Error: Into; /// Create a [`Publisher`](crate::publication::Publisher) for the given key expression. /// @@ -2672,14 +2620,13 @@ pub trait SessionDeclarations { /// publisher.put("value").res().await.unwrap(); /// # }) /// ``` - // tags{session.declare_publisher} - fn declare_publisher<'a, TryIntoKeyExpr>( - &self, + fn declare_publisher<'b, TryIntoKeyExpr>( + &'s self, key_expr: TryIntoKeyExpr, - ) -> PublisherBuilder<'static, 'a> + ) -> PublisherBuilder<'a, 'b> where - TryIntoKeyExpr: TryInto>, - >>::Error: Into; + TryIntoKeyExpr: TryInto>, + >>::Error: Into; /// Obtain a [`Liveliness`] struct tied to this Zenoh [`Session`]. /// @@ -2699,5 +2646,49 @@ pub trait SessionDeclarations { /// ``` // tags{liveliness.create} #[zenoh_macros::unstable] - fn liveliness(&self) -> Liveliness<'static>; + fn liveliness(&'s self) -> Liveliness<'a>; + /// Get informations about the zenoh [`Session`](Session). + /// + /// # Examples + /// ``` + /// # async_std::task::block_on(async { + /// use zenoh::prelude::r#async::*; + /// + /// let session = zenoh::open(config::peer()).res().await.unwrap(); + /// let info = session.info(); + /// # }) + /// ``` + fn info(&'s self) -> SessionInfo<'a>; +} + +impl crate::net::primitives::EPrimitives for Session { + #[inline] + fn send_declare(&self, ctx: crate::net::routing::RoutingContext) { + (self as &dyn Primitives).send_declare(ctx.msg) + } + + #[inline] + fn send_push(&self, msg: Push) { + (self as &dyn Primitives).send_push(msg) + } + + #[inline] + fn send_request(&self, ctx: crate::net::routing::RoutingContext) { + (self as &dyn Primitives).send_request(ctx.msg) + } + + #[inline] + fn send_response(&self, ctx: crate::net::routing::RoutingContext) { + (self as &dyn Primitives).send_response(ctx.msg) + } + + #[inline] + fn send_response_final(&self, ctx: crate::net::routing::RoutingContext) { + (self as &dyn Primitives).send_response_final(ctx.msg) + } + + #[inline] + fn send_close(&self) { + (self as &dyn Primitives).send_close() + } }