diff --git a/commons/zenoh-keyexpr/src/key_expr/include.rs b/commons/zenoh-keyexpr/src/key_expr/include.rs index 46a4e40d69..f58d5b6e0e 100644 --- a/commons/zenoh-keyexpr/src/key_expr/include.rs +++ b/commons/zenoh-keyexpr/src/key_expr/include.rs @@ -11,7 +11,7 @@ // Contributors: // ZettaScale Zenoh Team, // -use super::{keyexpr, utils::Split, DELIMITER, DOUBLE_WILD, STAR_DSL}; +use super::{intersect::MayHaveVerbatim, keyexpr, utils::Split, DELIMITER, DOUBLE_WILD, STAR_DSL}; pub const DEFAULT_INCLUDER: LTRIncluder = LTRIncluder; @@ -24,7 +24,7 @@ impl Includer<&'a [u8], &'a [u8]>> Includer<&keyexpr, &keyexpr> for T fn includes(&self, left: &keyexpr, right: &keyexpr) -> bool { let left = left.as_bytes(); let right = right.as_bytes(); - if left == right || left == b"**" { + if left == right { return true; } self.includes(left, right) @@ -38,9 +38,12 @@ impl Includer<&[u8], &[u8]> for LTRIncluder { let (lchunk, lrest) = left.split_once(&DELIMITER); let lempty = lrest.is_empty(); if lchunk == DOUBLE_WILD { - if lempty || self.includes(lrest, right) { + if (lempty && !right.has_verbatim()) || (!lempty && self.includes(lrest, right)) { return true; } + if unsafe { right.has_direct_verbatim_non_empty() } { + return false; + } right = right.split_once(&DELIMITER).1; if right.is_empty() { return false; @@ -66,7 +69,13 @@ impl Includer<&[u8], &[u8]> for LTRIncluder { impl LTRIncluder { fn non_double_wild_chunk_includes(&self, lchunk: &[u8], rchunk: &[u8]) -> bool { - if lchunk == b"*" || lchunk == rchunk { + if lchunk == rchunk { + true + } else if unsafe { + lchunk.has_direct_verbatim_non_empty() || rchunk.has_direct_verbatim_non_empty() + } { + false + } else if lchunk == b"*" { true } else if lchunk.contains(&b'$') { let mut spleft = lchunk.splitter(STAR_DSL); diff --git a/commons/zenoh-keyexpr/src/key_expr/intersect/classical.rs b/commons/zenoh-keyexpr/src/key_expr/intersect/classical.rs index e5ffd9d52b..fa346a2d4a 100644 --- a/commons/zenoh-keyexpr/src/key_expr/intersect/classical.rs +++ b/commons/zenoh-keyexpr/src/key_expr/intersect/classical.rs @@ -66,6 +66,9 @@ fn chunk_intersect(c1: &[u8], c2: &[u8]) -> bool { if c1 == c2 { return true; } + if c1.has_direct_verbatim() || c2.has_direct_verbatim() { + return false; + } chunk_it_intersect::(c1, c2) } @@ -83,14 +86,20 @@ fn it_intersect(mut it1: &[u8], mut it2: &[u8]) -> bool { let (current2, advanced2) = next(it2); match (current1, current2) { (b"**", _) => { - return advanced1.is_empty() - || it_intersect::(advanced1, it2) - || it_intersect::(it1, advanced2); + if advanced1.is_empty() { + return !it2.has_verbatim(); + } + return (!unsafe { current2.has_direct_verbatim_non_empty() } + && it_intersect::(it1, advanced2)) + || it_intersect::(advanced1, it2); } (_, b"**") => { - return advanced2.is_empty() - || it_intersect::(it1, advanced2) - || it_intersect::(advanced1, it2); + if advanced2.is_empty() { + return !it1.has_verbatim(); + } + return (!unsafe { current1.has_direct_verbatim_non_empty() } + && it_intersect::(advanced1, it2)) + || it_intersect::(it1, advanced2); } (sub1, sub2) if chunk_intersect::(sub1, sub2) => { it1 = advanced1; @@ -111,7 +120,7 @@ pub fn intersect(s1: &[u8], s2: &[u8]) -> bool { } use super::restiction::NoSubWilds; -use super::Intersector; +use super::{Intersector, MayHaveVerbatim}; pub struct ClassicIntersector; impl Intersector, NoSubWilds<&[u8]>> for ClassicIntersector { diff --git a/commons/zenoh-keyexpr/src/key_expr/intersect/mod.rs b/commons/zenoh-keyexpr/src/key_expr/intersect/mod.rs index bda0677404..f5d7735d9e 100644 --- a/commons/zenoh-keyexpr/src/key_expr/intersect/mod.rs +++ b/commons/zenoh-keyexpr/src/key_expr/intersect/mod.rs @@ -12,6 +12,8 @@ // ZettaScale Zenoh Team, // +use crate::DELIMITER; + use super::keyexpr; mod classical; @@ -100,3 +102,24 @@ impl< } } } + +pub(crate) trait MayHaveVerbatim { + fn has_verbatim(&self) -> bool; + fn has_direct_verbatim(&self) -> bool; + unsafe fn has_direct_verbatim_non_empty(&self) -> bool { + self.has_direct_verbatim() + } +} + +impl MayHaveVerbatim for [u8] { + fn has_direct_verbatim(&self) -> bool { + matches!(self, [b'@', ..]) + } + fn has_verbatim(&self) -> bool { + self.split(|c| *c == DELIMITER) + .any(MayHaveVerbatim::has_direct_verbatim) + } + unsafe fn has_direct_verbatim_non_empty(&self) -> bool { + unsafe { *self.get_unchecked(0) == b'@' } + } +} diff --git a/commons/zenoh-keyexpr/src/key_expr/tests.rs b/commons/zenoh-keyexpr/src/key_expr/tests.rs index ccba9b1ff6..6d9e64896e 100644 --- a/commons/zenoh-keyexpr/src/key_expr/tests.rs +++ b/commons/zenoh-keyexpr/src/key_expr/tests.rs @@ -84,6 +84,30 @@ fn intersections() { assert!(intersect("x/a$*d$*e", "x/ade")); assert!(!intersect("x/c$*", "x/abc$*")); assert!(!intersect("x/$*d", "x/$*e")); + + assert!(intersect("@a", "@a")); + assert!(!intersect("@a", "@ab")); + assert!(!intersect("@a", "@a/b")); + assert!(!intersect("@a", "@a/*")); + assert!(!intersect("@a", "@a/*/**")); + assert!(!intersect("@a", "@a$*/**")); + assert!(intersect("@a", "@a/**")); + assert!(!intersect("**/xyz$*xyz", "@a/b/xyzdefxyz")); + assert!(intersect("@a/**/c/**/e", "@a/b/b/b/c/d/d/d/e")); + assert!(!intersect("@a/**/c/**/e", "@a/@b/b/b/c/d/d/d/e")); + assert!(intersect("@a/**/@c/**/e", "@a/b/b/b/@c/d/d/d/e")); + assert!(intersect("@a/**/e", "@a/b/b/d/d/d/e")); + assert!(intersect("@a/**/e", "@a/b/b/b/d/d/d/e")); + assert!(intersect("@a/**/e", "@a/b/b/c/d/d/d/e")); + assert!(!intersect("@a/**/e", "@a/b/b/@c/b/d/d/d/e")); + assert!(!intersect("@a/*", "@a/@b")); + assert!(!intersect("@a/**", "@a/@b")); + assert!(intersect("@a/**/@b", "@a/@b")); + assert!(intersect("@a/@b/**", "@a/@b")); + assert!(intersect("@a/**/@c/**/@b", "@a/**/@c/@b")); + assert!(intersect("@a/**/@c/**/@b", "@a/@c/**/@b")); + assert!(intersect("@a/**/@c/@b", "@a/@c/**/@b")); + assert!(!intersect("@a/**/@b", "@a/**/@c/**/@b")); } fn includes< @@ -146,6 +170,21 @@ fn inclusions() { assert!(!includes("x/c$*", "x/abc$*")); assert!(includes("x/$*c$*", "x/abc$*")); assert!(!includes("x/$*d", "x/$*e")); + + assert!(includes("@a", "@a")); + assert!(!includes("@a", "@ab")); + assert!(!includes("@a", "@a/b")); + assert!(!includes("@a", "@a/*")); + assert!(!includes("@a", "@a/*/**")); + assert!(!includes("@a$*/**", "@a")); + assert!(!includes("@a", "@a/**")); + assert!(includes("@a/**", "@a")); + assert!(!includes("**/xyz$*xyz", "@a/b/xyzdefxyz")); + assert!(includes("@a/**/c/**/e", "@a/b/b/b/c/d/d/d/e")); + assert!(!includes("@a/*", "@a/@b")); + assert!(!includes("@a/**", "@a/@b")); + assert!(includes("@a/**/@b", "@a/@b")); + assert!(includes("@a/@b/**", "@a/@b")); } #[test]