From 3fc5d183d41fc6956aea317b74d376bd102441c5 Mon Sep 17 00:00:00 2001 From: Pierre Avital Date: Thu, 18 Jan 2024 16:25:25 +0100 Subject: [PATCH 1/3] implement key expression namespaces --- commons/zenoh-keyexpr/src/key_expr/include.rs | 30 ++++++++++++++-- .../src/key_expr/intersect/mod.rs | 34 ++++++++++++++++--- commons/zenoh-keyexpr/src/key_expr/tests.rs | 21 ++++++++++++ 3 files changed, 78 insertions(+), 7 deletions(-) diff --git a/commons/zenoh-keyexpr/src/key_expr/include.rs b/commons/zenoh-keyexpr/src/key_expr/include.rs index 46a4e40d69..a8dbdaf4cd 100644 --- a/commons/zenoh-keyexpr/src/key_expr/include.rs +++ b/commons/zenoh-keyexpr/src/key_expr/include.rs @@ -22,9 +22,33 @@ pub trait Includer { 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"**" { + let mut left = left.as_bytes(); + let mut right = right.as_bytes(); + if left == right { + return true; + } + + if unsafe { *left.get_unchecked(0) == b'@' || *right.get_unchecked(0) == b'@' } { + let mut end = left.len().min(right.len()); + for i in 0..end { + if left[i] != right[i] { + return false; + } + if left[i] == DELIMITER { + end = i; + break; + } + } + if left.len() == end { + return false; + } + if right.len() == end { + return left.get(end..) == Some(b"/**"); + } + left = &left[(end + 1)..]; + right = &right[(end + 1)..]; + } + if left == b"**" { return true; } self.includes(left, right) diff --git a/commons/zenoh-keyexpr/src/key_expr/intersect/mod.rs b/commons/zenoh-keyexpr/src/key_expr/intersect/mod.rs index bda0677404..cebc194d16 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; @@ -88,13 +90,37 @@ impl< > Intersector<&keyexpr, &keyexpr> for T { fn intersect(&self, left: &keyexpr, right: &keyexpr) -> bool { - let left_bytes = left.as_bytes(); - let right_bytes = right.as_bytes(); + let mut left_bytes = left.as_bytes(); + let mut right_bytes = right.as_bytes(); if left_bytes == right_bytes { return true; } - match left.match_complexity() as u8 | right.match_complexity() as u8 { - 0 => false, + let complexity = left.match_complexity() as u8 | right.match_complexity() as u8; + if complexity == 0 { + return false; + } + if unsafe { *left_bytes.get_unchecked(0) == b'@' || *right_bytes.get_unchecked(0) == b'@' } + { + let mut end = left_bytes.len().min(right_bytes.len()); + for i in 0..end { + if left_bytes[i] != right_bytes[i] { + return false; + } + if left_bytes[i] == DELIMITER { + end = i; + break; + } + } + if left_bytes.len() == end { + return right_bytes.get(end..) == Some(b"/**"); + } + if right_bytes.len() == end { + return left_bytes.get(end..) == Some(b"/**"); + } + left_bytes = &left_bytes[(end + 1)..]; + right_bytes = &right_bytes[(end + 1)..]; + } + match complexity { 1 => self.intersect(NoSubWilds(left_bytes), NoSubWilds(right_bytes)), _ => self.intersect(left_bytes, right_bytes), } diff --git a/commons/zenoh-keyexpr/src/key_expr/tests.rs b/commons/zenoh-keyexpr/src/key_expr/tests.rs index ccba9b1ff6..002977255b 100644 --- a/commons/zenoh-keyexpr/src/key_expr/tests.rs +++ b/commons/zenoh-keyexpr/src/key_expr/tests.rs @@ -84,6 +84,16 @@ 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")); } fn includes< @@ -146,6 +156,17 @@ 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")); } #[test] From 1d109ad6a1c2e5db5f667108e59f0f3431e85e40 Mon Sep 17 00:00:00 2001 From: Pierre Avital Date: Fri, 19 Jan 2024 15:19:46 +0100 Subject: [PATCH 2/3] implement verbatim chunks --- commons/zenoh-keyexpr/src/key_expr/include.rs | 43 +++++---------- .../src/key_expr/intersect/classical.rs | 23 +++++--- .../src/key_expr/intersect/mod.rs | 53 +++++++++---------- commons/zenoh-keyexpr/src/key_expr/tests.rs | 14 +++++ 4 files changed, 69 insertions(+), 64 deletions(-) diff --git a/commons/zenoh-keyexpr/src/key_expr/include.rs b/commons/zenoh-keyexpr/src/key_expr/include.rs index a8dbdaf4cd..07b20964d4 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; @@ -22,35 +22,11 @@ pub trait Includer { impl Includer<&'a [u8], &'a [u8]>> Includer<&keyexpr, &keyexpr> for T { fn includes(&self, left: &keyexpr, right: &keyexpr) -> bool { - let mut left = left.as_bytes(); - let mut right = right.as_bytes(); + let left = left.as_bytes(); + let right = right.as_bytes(); if left == right { return true; } - - if unsafe { *left.get_unchecked(0) == b'@' || *right.get_unchecked(0) == b'@' } { - let mut end = left.len().min(right.len()); - for i in 0..end { - if left[i] != right[i] { - return false; - } - if left[i] == DELIMITER { - end = i; - break; - } - } - if left.len() == end { - return false; - } - if right.len() == end { - return left.get(end..) == Some(b"/**"); - } - left = &left[(end + 1)..]; - right = &right[(end + 1)..]; - } - if left == b"**" { - return true; - } self.includes(left, right) } } @@ -62,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()) || 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; @@ -90,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 cebc194d16..f5d7735d9e 100644 --- a/commons/zenoh-keyexpr/src/key_expr/intersect/mod.rs +++ b/commons/zenoh-keyexpr/src/key_expr/intersect/mod.rs @@ -90,39 +90,36 @@ impl< > Intersector<&keyexpr, &keyexpr> for T { fn intersect(&self, left: &keyexpr, right: &keyexpr) -> bool { - let mut left_bytes = left.as_bytes(); - let mut right_bytes = right.as_bytes(); + let left_bytes = left.as_bytes(); + let right_bytes = right.as_bytes(); if left_bytes == right_bytes { return true; } - let complexity = left.match_complexity() as u8 | right.match_complexity() as u8; - if complexity == 0 { - return false; - } - if unsafe { *left_bytes.get_unchecked(0) == b'@' || *right_bytes.get_unchecked(0) == b'@' } - { - let mut end = left_bytes.len().min(right_bytes.len()); - for i in 0..end { - if left_bytes[i] != right_bytes[i] { - return false; - } - if left_bytes[i] == DELIMITER { - end = i; - break; - } - } - if left_bytes.len() == end { - return right_bytes.get(end..) == Some(b"/**"); - } - if right_bytes.len() == end { - return left_bytes.get(end..) == Some(b"/**"); - } - left_bytes = &left_bytes[(end + 1)..]; - right_bytes = &right_bytes[(end + 1)..]; - } - match complexity { + match left.match_complexity() as u8 | right.match_complexity() as u8 { + 0 => false, 1 => self.intersect(NoSubWilds(left_bytes), NoSubWilds(right_bytes)), _ => self.intersect(left_bytes, right_bytes), } } } + +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 002977255b..2d24f2bad1 100644 --- a/commons/zenoh-keyexpr/src/key_expr/tests.rs +++ b/commons/zenoh-keyexpr/src/key_expr/tests.rs @@ -94,6 +94,16 @@ fn intersections() { 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")); } fn includes< @@ -167,6 +177,10 @@ fn inclusions() { 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] From a499d562f03bc8c4b066c88105e8f573466de616 Mon Sep 17 00:00:00 2001 From: Pierre Avital Date: Mon, 22 Jan 2024 18:19:20 +0100 Subject: [PATCH 3/3] fix bug in includes --- commons/zenoh-keyexpr/src/key_expr/include.rs | 2 +- commons/zenoh-keyexpr/src/key_expr/tests.rs | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/commons/zenoh-keyexpr/src/key_expr/include.rs b/commons/zenoh-keyexpr/src/key_expr/include.rs index 07b20964d4..f58d5b6e0e 100644 --- a/commons/zenoh-keyexpr/src/key_expr/include.rs +++ b/commons/zenoh-keyexpr/src/key_expr/include.rs @@ -38,7 +38,7 @@ impl Includer<&[u8], &[u8]> for LTRIncluder { let (lchunk, lrest) = left.split_once(&DELIMITER); let lempty = lrest.is_empty(); if lchunk == DOUBLE_WILD { - if (lempty && !right.has_verbatim()) || self.includes(lrest, right) { + if (lempty && !right.has_verbatim()) || (!lempty && self.includes(lrest, right)) { return true; } if unsafe { right.has_direct_verbatim_non_empty() } { diff --git a/commons/zenoh-keyexpr/src/key_expr/tests.rs b/commons/zenoh-keyexpr/src/key_expr/tests.rs index 2d24f2bad1..6d9e64896e 100644 --- a/commons/zenoh-keyexpr/src/key_expr/tests.rs +++ b/commons/zenoh-keyexpr/src/key_expr/tests.rs @@ -104,6 +104,10 @@ fn intersections() { 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<