From f7a26e25d600e98f8719001230f2982f79110997 Mon Sep 17 00:00:00 2001 From: snehilzs Date: Thu, 8 Feb 2024 09:23:29 +0100 Subject: [PATCH] WIP:Modified ACL for attributes --- Cargo.lock | 1 + zenoh/Cargo.toml | 2 + zenoh/src/net/routing/dispatcher/resource.rs | 7 +- .../routing/interceptor/accessintercept.rs | 86 ++-- zenoh/src/net/routing/interceptor/authz.rs | 379 +++++++++++------- zenoh/src/net/routing/interceptor/mod.rs | 8 +- zenoh/src/net/routing/mod.rs | 9 +- 7 files changed, 285 insertions(+), 207 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8bb241d50d..9b4291712f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4594,6 +4594,7 @@ dependencies = [ "petgraph", "rand 0.8.5", "regex", + "rustc-hash", "rustc_version 0.4.0", "serde", "serde_json", diff --git a/zenoh/Cargo.toml b/zenoh/Cargo.toml index d87cfb1e66..1866ebffa4 100644 --- a/zenoh/Cargo.toml +++ b/zenoh/Cargo.toml @@ -105,11 +105,13 @@ zenoh-sync = { workspace = true } zenoh-transport = { workspace = true } zenoh-util = { workspace = true } fr-trie = "*" +rustc-hash = "1.1.0" [build-dependencies] rustc_version = { workspace = true } [dependencies.bitflags] version = "2.4.2" + [lib] name = "zenoh" diff --git a/zenoh/src/net/routing/dispatcher/resource.rs b/zenoh/src/net/routing/dispatcher/resource.rs index 8a183088d6..84b5679471 100644 --- a/zenoh/src/net/routing/dispatcher/resource.rs +++ b/zenoh/src/net/routing/dispatcher/resource.rs @@ -214,7 +214,12 @@ impl Resource { pub fn expr(&self) -> String { match &self.parent { - Some(parent) => parent.expr() + &self.suffix, + // Some(parent) => parent.expr() + &self.suffix, + Some(parent) => { + let mut string = parent.expr(); + string.push_str(&self.suffix); + string + } None => String::from(""), } } diff --git a/zenoh/src/net/routing/interceptor/accessintercept.rs b/zenoh/src/net/routing/interceptor/accessintercept.rs index 8942a07fd7..d9030424a4 100644 --- a/zenoh/src/net/routing/interceptor/accessintercept.rs +++ b/zenoh/src/net/routing/interceptor/accessintercept.rs @@ -7,16 +7,14 @@ use zenoh_protocol::{ }; use zenoh_transport::{multicast::TransportMulticast, unicast::TransportUnicast}; -use crate::net::routing::{interceptor::authz::PolicyEnforcer, RoutingContext}; +use crate::net::routing::RoutingContext; use super::{ - authz::{self, NewCtx}, + authz::{ActionFlag, NewCtx, NewPolicyEnforcer}, EgressInterceptor, IngressInterceptor, InterceptorFactoryTrait, InterceptorTrait, }; -use authz::{Action, Request, Subject}; - pub(crate) struct AclEnforcer { - pub(crate) e: Arc, + pub(crate) e: Arc, } impl InterceptorFactoryTrait for AclEnforcer { @@ -28,10 +26,10 @@ impl InterceptorFactoryTrait for AclEnforcer { ( Some(Box::new(IngressAclEnforcer { e: self.e.clone(), - zid: Some(uid), + zid: uid, })), Some(Box::new(EgressAclEnforcer { - zid: Some(uid), + zid: uid, e: self.e.clone(), })), ) @@ -44,7 +42,7 @@ impl InterceptorFactoryTrait for AclEnforcer { let e = &self.e; Some(Box::new(EgressAclEnforcer { e: e.clone(), - zid: None, + zid: ZenohId::default(), })) } @@ -52,14 +50,14 @@ impl InterceptorFactoryTrait for AclEnforcer { let e = &self.e; Some(Box::new(IngressAclEnforcer { e: e.clone(), - zid: None, + zid: ZenohId::default(), })) } } struct IngressAclEnforcer { - e: Arc, - zid: Option, + e: Arc, + zid: ZenohId, } impl InterceptorTrait for IngressAclEnforcer { @@ -74,23 +72,21 @@ impl InterceptorTrait for IngressAclEnforcer { }) = &ctx.msg.body { let e = &self.e; - let ke: &str = ctx.full_expr().unwrap(); - let new_ctx = NewCtx { ke, zid: self.zid }; //how to get the zid here - let decision = e.policy_enforcement_point(new_ctx, Action::Write).unwrap(); - - // let sub = Subject { - // id: self.zid.unwrap(), - // attributes: None, - // }; - // let request = Request { - // sub, - // obj: ke.to_owned(), - // action: Action::Write, - // }; - // let decision = e.policy_decision_point(request).unwrap(); - if !decision { - return None; + let ke: String = ctx.full_expr().unwrap().to_owned(); + // let ke: String = "test/thr".to_owned(); //for testing + let new_ctx = NewCtx { + ke: &ke, + zid: self.zid, + attributes: None, + }; + match e.policy_enforcement_point(new_ctx, ActionFlag::Write) { + Ok(decision) => { + if !decision { + return None; + } + } + Err(_) => return None, } } @@ -99,8 +95,8 @@ impl InterceptorTrait for IngressAclEnforcer { } struct EgressAclEnforcer { - e: Arc, - zid: Option, + e: Arc, + zid: ZenohId, } impl InterceptorTrait for EgressAclEnforcer { @@ -115,23 +111,21 @@ impl InterceptorTrait for EgressAclEnforcer { }) = &ctx.msg.body { let e = &self.e; - let ke: &str = ctx.full_expr().unwrap(); - let new_ctx = NewCtx { ke, zid: self.zid }; - let decision = e.policy_enforcement_point(new_ctx, Action::Read).unwrap(); - - // let sub = Subject { - // id: self.zid.unwrap(), - // attributes: None, - // }; - // let request = Request { - // sub, - // obj: ke.to_owned(), - // action: Action::Read, - // }; - // let decision = e.policy_decision_point(request).unwrap(); - - if !decision { - return None; + let ke: String = ctx.full_expr().unwrap().to_owned(); + + // let ke: String = "test/thr".to_owned(); //for testing + let new_ctx = NewCtx { + ke: &ke, + zid: self.zid, + attributes: None, + }; + match e.policy_enforcement_point(new_ctx, ActionFlag::Read) { + Ok(decision) => { + if !decision { + return None; + } + } + Err(_) => return None, } } diff --git a/zenoh/src/net/routing/interceptor/authz.rs b/zenoh/src/net/routing/interceptor/authz.rs index c408356384..de62cf77bf 100644 --- a/zenoh/src/net/routing/interceptor/authz.rs +++ b/zenoh/src/net/routing/interceptor/authz.rs @@ -1,26 +1,32 @@ +use std::collections::HashMap; use std::fs::File; -use std::hash::Hash; +use std::hash::{Hash, Hasher}; use std::io::Read; +use std::net::Ipv4Addr; +use std::str::FromStr; +use fr_trie::glob::GlobMatcher; +//use serde::ser::SerializeStruct; //use super::RoutingContext; -use fr_trie::key::ValueMerge; use serde::{Deserialize, Serialize}; use zenoh_config::ZenohId; //use zenoh_protocol::network::NetworkMessage; -use zenoh_result::ZResult; - use fr_trie::glob::acl::Acl; -use fr_trie::glob::GlobMatcher; +use rustc_hash::FxHashMap; +use zenoh_result::ZResult; +//use fr_trie::glob::GlobMatcher; use fr_trie::trie::Trie; -use std::collections::HashMap; +//use std::collections::HashMap; use bitflags::bitflags; +#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)] +pub struct ActionFlag(u8); + bitflags! { - #[derive(Clone,PartialEq)] - pub struct ActionFlag: u8 { + impl ActionFlag: u8 { const None = 0b00000000; const Read = 0b00000001; const Write = 0b00000010; @@ -42,50 +48,76 @@ pub enum Action { pub struct NewCtx<'a> { pub(crate) ke: &'a str, - pub(crate) zid: Option, + pub(crate) zid: ZenohId, + pub(crate) attributes: Option, } #[derive(Debug, Serialize, Deserialize)] pub struct Request { pub(crate) sub: Subject, pub(crate) obj: String, - pub(crate) action: Action, + pub(crate) action: ActionFlag, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NewRequest { + pub(crate) sub: PolicySubject, + pub(crate) obj: String, + pub(crate) action: ActionFlag, } pub struct RequestBuilder { sub: Option, obj: Option, - action: Option, + action: Option, } -#[derive(Clone, Debug)] +//#[derive(Clone, Debug)] -pub enum Permissions { - Deny, - Allow, -} +// pub enum Permissions { +// Deny, +// Allow, +// } + +type KeTreeFast = Trie; -impl ValueMerge for Permissions { - fn merge(&self, _other: &Self) -> Self { - self.clone() +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] + +pub struct Attributes(HashMap); + +// impl Into for Option { +// fn into(self) -> Attributes { +// self::Attributes +// } +// } + +impl std::hash::Hash for Attributes { + fn hash(&self, state: &mut H) { + let mut pairs: Vec<_> = self.0.iter().collect(); + pairs.sort_by_key(|i| i.0); + Hash::hash(&pairs, state); } - fn merge_mut(&mut self, _other: &Self) {} + fn hash_slice(data: &[Self], state: &mut H) + where + Self: Sized, + { + for piece in data { + piece.hash(state) + } + } } -//type KeTree = Trie; -type KeTreeFast = Trie; - #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)] pub struct Subject { pub(crate) id: ZenohId, - pub(crate) attributes: Option>, //might be mapped to other types eventually + pub(crate) attributes: Option, //might be mapped to other types eventually } //subject_builder (add ID, attributes, roles) pub struct SubjectBuilder { id: Option, - attributes: Option>, + attributes: Option, } //request_builder (add subject, resource, action) //do we need one? @@ -105,17 +137,17 @@ impl RequestBuilder { pub fn sub(&mut self, sub: impl Into) -> &mut Self { //adds subject - self.sub.insert(sub.into()); + let _ = self.sub.insert(sub.into()); self } pub fn obj(&mut self, obj: impl Into) -> &mut Self { - self.obj.insert(obj.into()); + let _ = self.obj.insert(obj.into()); self } - pub fn action(&mut self, action: impl Into) -> &mut Self { - self.action.insert(action.into()); + pub fn action(&mut self, action: impl Into) -> &mut Self { + let _ = self.action.insert(action.into()); self } @@ -139,73 +171,102 @@ impl SubjectBuilder { pub fn id(&mut self, id: impl Into) -> &mut Self { //adds subject - self.id.insert(id.into()); + let _ = self.id.insert(id.into()); self } - pub fn attributes(&mut self, attributes: impl Into>) -> &mut Self { - self.attributes.insert(attributes.into()); + pub fn attributes(&mut self, attributes: impl Into) -> &mut Self { + let _ = self.attributes.insert(attributes.into()); self } pub fn build(&mut self) -> ZResult { let id = self.id.unwrap(); - let attr = &self.attributes; + let attr = self.attributes.as_ref(); Ok(Subject { id, - attributes: self.attributes.clone(), + attributes: attr.cloned(), }) } } -//struct that defines a single rule in the access-control policy -#[derive(Serialize, Deserialize, Clone)] -pub struct Rule { - sub: Subject, - ke: String, - action: Action, - permission: bool, +#[derive(Debug, Clone, Serialize, Deserialize, Eq, Hash, PartialEq)] +pub enum PolicySubject { + Id(ZenohId), + Attribute(Attribute), } -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct SubAct(Subject, Action); +//single attribute per policy check +#[derive(Eq, Clone, Hash, PartialEq, Serialize, Debug, Deserialize)] +pub enum Attribute { + IPRange(Ipv4Addr), + Networktype(u8), //1 for wifi,2 for lan etc +} +//#[derive(Eq, Hash, PartialEq)] +type SinglePolicy = FxHashMap; +pub struct NewPolicyEnforcer( + u8, //stores types of policies (for now just 1,2,3 for userid,attribute,both) + pub Vec, //stores +); -#[derive(Clone)] -//pub struct PolicyEnforcer(HashMap); -pub struct PolicyEnforcer(pub HashMap); #[derive(Clone, PartialEq, Eq, Hash)] - pub struct KeTrie {} -impl PolicyEnforcer { +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub struct PolicyRule { + sub: PolicySubject, + ke: String, + action: Action, + permission: bool, +} +impl NewPolicyEnforcer { + pub fn new() -> ZResult { + // PolicyEnforcer + Ok(NewPolicyEnforcer(0, Vec::new())) + } + pub fn init(&mut self) -> ZResult<()> { /* - Initializs the policy for the control logic + Initializes the policy for the control logic loads policy into memory from file/network path creates the policy hashmap with the ke-tries for ke matching - should have polic-type in the mix here...need to verify + can have policy-type in the mix here...need to verify */ - // let rule_set = Self::policy_resource_point("rules_test_thr.json5").unwrap(); - // let pe = Self::build_policy_map_with_sub(rule_set).expect("policy not established"); + //set the policy type to 1,2,3 depending on the user input in the "rules" file - let rule_set = self.policy_resource_point("rules_test_thr.json5").unwrap(); - self.build_policy_map_with(rule_set) + let (rule_type, rule_set) = self.policy_resource_point("rules_test_thr.json5").unwrap(); + self.build_policy_map(rule_type, rule_set) .expect("policy not established"); + /* setup a temporary variable here to hold all the values */ //also should start the logger here Ok(()) } - pub fn build_policy_map_with(&mut self, rule_set: Vec) -> ZResult<()> { + pub fn build_policy_map(&mut self, rule_type: u8, rule_set: Vec) -> ZResult<()> { //convert vector of rules to a hashmap mapping subact to ketrie /* - representaiton of policy list as a hashmap of trees - tried KeTrees, didn't work + representaiton of policy list as a vector of hashmap of trees + each hashmap maps a subject (ID/atttribute) to a trie of allowed values using fr-trie for now as a placeholder for key-matching */ - let mut policy = self; - //let mut policy = PolicyEnforcer(HashMap::new()); - //create a hashmap for ketries ((sub,action)->ketrie) from the vector of rules + self.0 = rule_type; + let map_subject_policy = &mut self.1; + match rule_type { + 1 => map_subject_policy.push(Self::build_id_map(rule_set)), + + 2 => map_subject_policy.push(Self::build_attribute_map(rule_set)), + 3 | 4 => { + map_subject_policy.push(Self::build_id_map(rule_set.clone())); + map_subject_policy.push(Self::build_attribute_map(rule_set)); + } + _ => bail!("bad entry for type"), + } + Ok(()) + } + + pub fn build_id_map(rule_set: Vec) -> SinglePolicy { + let mut policy: SinglePolicy = FxHashMap::default(); for v in rule_set { // for now permission being false means this ke will not be inserted into the trie of allowed ke's let perm = v.permission; @@ -223,137 +284,149 @@ impl PolicyEnforcer { Action::DeclareQuery => ActionFlag::DeclareQuery, }; - //create subact - //let subact = SubAct(sub, action); - //match subact in the policy hashmap + //match subject to the policy hashmap #[allow(clippy::map_entry)] - if !policy.0.contains_key(&sub) { - //create new entry for subact + ketree + if !policy.contains_key(&sub) { + //create new entry for subject + ke-tree let mut ketree = KeTreeFast::new(); - //ketree.insert(Acl::new(&ke), Permissionssions::READ); ketree.insert(Acl::new(&ke), action_flag); - policy.0.insert(sub, ketree); + policy.insert(sub, ketree); } else { - let ketree = policy.0.get_mut(&sub).unwrap(); - //ketree.insert(Acl::new(&ke), Permissionssions::READ); - ketree.insert(Acl::new(&ke), action_flag); + let ketree = policy.get_mut(&sub).unwrap(); + let old_flag = ketree.get::(&Acl::new(&ke)).unwrap(); + ketree.insert(Acl::new(&ke), action_flag | old_flag); //update old flag } } - Ok(()) + policy } - // pub fn build_policy_map(rule_set: Vec) -> ZResult { - // //convert vector of rules to a hashmap mapping subact to ketrie - // /* - // representaiton of policy list as a hashmap of trees - // tried KeTrees, didn't work - // using fr-trie for now as a placeholder for key-matching - // */ - // let mut policy = PolicyEnforcer(HashMap::new()); - // //create a hashmap for ketries ((sub,action)->ketrie) from the vector of rules - // for v in rule_set { - // // for now permission being false means this ke will not be inserted into the trie of allowed ke's - // let perm = v.permission; - // if !perm { - // continue; - // } - // let sub = v.sub; - // // let action = v.action; - // let action_flag = v.action as isize; - // let ke = v.ke; - - // //create subact - // // let subact = SubAct(sub, action); - // //match subact in the policy hashmap - // #[allow(clippy::map_entry)] - // if !policy.0.contains_key(&sub) { - // //create new entry for subact + ketree - // let mut ketree = KeTree::new(); - // //ketree.insert(Acl::new(&ke), Permissionssions::READ); - // ketree.insert(Acl::new(&ke), ActionFlag::); - // policy.0.insert(subact, ketree); - // } else { - // let ketree = policy.0.get_mut(&sub).unwrap(); - // //ketree.insert(Acl::new(&ke), Permissionssions::READ); - // ketree.insert(Acl::new(&ke), Permissions::Allow); - // } - // } - // Ok(policy) - // } - - pub fn policy_enforcement_point(&self, new_ctx: NewCtx, action: Action) -> ZResult { + fn build_attribute_map(rule_set: Vec) -> SinglePolicy { + let x: SinglePolicy = FxHashMap::default(); + return x; + } + + pub fn policy_enforcement_point(&self, new_ctx: NewCtx, action: ActionFlag) -> ZResult { /* input: new_context and action (sub,act for now but will need attribute values later) - output: allow/deny + output: allow/denyca function: depending on the msg, builds the subject, builds the request, passes the request to policy_decision_point() collects result from PDP and then uses that allow/deny output to block or pass the msg to routing table */ //get keyexpression and zid for the request; attributes will be added at this point (phase 2) + let ke = new_ctx.ke; - let zid = new_ctx.zid.unwrap(); - //build subject - let subject = SubjectBuilder::new().id(zid).build()?; + let zid = new_ctx.zid; + let attribute = new_ctx.attributes; + // if let Some(value) = new_ctx.attributes { + // attribute = value; + // } + // let subject = SubjectBuilder::new().id(zid).build()?; //build request - let request = RequestBuilder::new() - .sub(subject) - .obj(ke) - .action(action) - .build()?; - - //call PDP - let decision = self.policy_decision_point(request)?; + // let request = RequestBuilder::new() + // .sub(subject) + // .obj(ke) + // .action(action) + // .build()?; + + let subject = PolicySubject::Id(zid); + let request = NewRequest { + sub: subject, + obj: ke.to_owned(), + action, + }; + let decision = self.policy_decision_point(request); Ok(decision) } - pub fn policy_decision_point(&self, request: Request) -> ZResult { + + pub fn policy_decision_point(&self, request: NewRequest) -> bool { /* input: (request) output: true(allow)/false(deny) function: process the request received from PEP against the policy (self) - policy list is be a hashmap of (subject,action)->ketries (test and discuss) + the policy list is chosen based on the policy-type specified in the rules file + policy list is be a hashmap of subject->ketries (test and discuss) */ - //get subject and action from request and create subact [this will be our key for hashmap] - // let subact = SubAct(request.sub, request.action); - let action_flag = match request.action { - Action::Read => ActionFlag::Read, - Action::Write => ActionFlag::Write, - Action::None => ActionFlag::None, - Action::DeclareSub => ActionFlag::DeclareSub, - Action::Delete => ActionFlag::Delete, - Action::DeclareQuery => ActionFlag::DeclareQuery, + //compare the request to the vec of values...matching depends on the value of the policy type + match self.0 { + 1 => { + //check the id map (value 0) + return self.matching_algo(0, request); + } + 2 => { + //check the attribute map (value 1) + return self.matching_algo(1, request); + } + 3 => { + //check both the maps and do an OR (either ID or attribute should match for allow) + return self.matching_algo(0, request.clone()) || self.matching_algo(1, request); + } + 4 => { + //check both the maps and do AND (both ID and attribute should match for allow) + return self.matching_algo(0, request.clone()) && self.matching_algo(1, request); + } + _ => { + //wrong value; deny request + false + } }; + + false + } + + pub fn matching_algo(&self, matching_type: usize, request: NewRequest) -> bool { + // return true; let ke = request.obj; - match self.0.get(&request.sub) { + let sub = request.sub; + let action = request.action; + + match self.1[matching_type].get(&sub) { Some(ktrie) => { - // check if request ke has a match in ke-trie; if ke in ketrie, then Ok(true) else Ok(false) - //let result = ktrie.get_merge::(&Acl::new(&ke)); + // check if request ke has a match in ke-trie; if ke in ketrie, then true (allow) else false (deny) let result = ktrie.get::(&Acl::new(&ke)); - if let Some(value) = result { - if (value & action_flag) != ActionFlag::None { - return Ok(true); - } + match result { + Some(value) => (value & action) != ActionFlag::None, + None => false, } } - None => return Ok(false), + None => false, } - Ok(false) } - pub fn policy_resource_point(&self, file_path: &str) -> ZResult> { + pub fn policy_resource_point(&self, file_path: &str) -> ZResult<(u8, Vec)> { /* input: path to rules.json file - output: loads the appropriate policy into the memory and returns back a vector of rules; + output: loads the appropriate policy into the memory and returns back a vector of rules and the policy type as specified in the file; * might also be the point to select AC type (ACL, ABAC etc)?? * */ - #[derive(Serialize, Deserialize, Clone)] - struct Rules(Vec); - - let mut file = File::open(file_path).unwrap(); - let mut buff = String::new(); - file.read_to_string(&mut buff).unwrap(); - let rulevec: Rules = serde_json::from_str(&buff).unwrap(); - Ok(rulevec.0) + + let policytype: u8 = 1; + + let vec_ids = [ + "aaa3b411006ad57868988f9fec672a31", + "bbb3b411006ad57868988f9fec672a31", + "aaabbb11006ad57868988f9fec672a31", + "aaabbb11006ad57868988f9fec672a31", + ]; + let vec_actions: Vec = + vec![Action::Write, Action::Read, Action::Write, Action::Read]; + //let vec_perms = []; + let mut policyrules: Vec = Vec::new(); + + for i in 0..4 { + policyrules.push(PolicyRule { + sub: PolicySubject::Id(ZenohId::from_str(vec_ids[i]).unwrap()), + ke: "test/thr".to_string(), + action: vec_actions.get(i).unwrap().clone(), + permission: true, + }); + } + //let policyruleset: PolicyRuleSet; + + println!("policy rules : {:?}", policyrules); + + Ok((policytype, policyrules)) } } diff --git a/zenoh/src/net/routing/interceptor/mod.rs b/zenoh/src/net/routing/interceptor/mod.rs index c9777ae0c5..30f68cfb34 100644 --- a/zenoh/src/net/routing/interceptor/mod.rs +++ b/zenoh/src/net/routing/interceptor/mod.rs @@ -20,11 +20,11 @@ //! mod accessintercept; mod authz; -use std::{collections::HashMap, sync::Arc}; +use std::sync::Arc; use super::RoutingContext; //use crate::net::routing::interceptor::authz; -use crate::net::routing::interceptor::{accessintercept::AclEnforcer, authz::PolicyEnforcer}; +use crate::net::routing::interceptor::{accessintercept::AclEnforcer, authz::NewPolicyEnforcer}; use zenoh_config::Config; use zenoh_protocol::network::NetworkMessage; use zenoh_transport::{multicast::TransportMulticast, unicast::TransportUnicast}; @@ -62,9 +62,9 @@ pub(crate) fn interceptor_factories(_config: &Config) -> Vec */ /* if config condition is selected this will be initialiased; putting true for now */ - if true { + if false { println!("the interceptor is initialized"); - let mut policy_enforcer = PolicyEnforcer(HashMap::new()); + let mut policy_enforcer = NewPolicyEnforcer::new().unwrap(); //(HashMap::new()); match policy_enforcer.init() { Ok(_) => res.push(Box::new(AclEnforcer { e: Arc::new(policy_enforcer), diff --git a/zenoh/src/net/routing/mod.rs b/zenoh/src/net/routing/mod.rs index 0b069c1337..06340f7e12 100644 --- a/zenoh/src/net/routing/mod.rs +++ b/zenoh/src/net/routing/mod.rs @@ -140,9 +140,12 @@ impl RoutingContext { } } if let Some(prefix) = self.prefix.get().cloned() { - let _ = self - .full_expr - .set(prefix.expr() + wire_expr.suffix.as_ref()); + // let _ = self + // .full_expr + // .set(prefix.expr() + wire_expr.suffix.as_ref()); + let mut full_expr = prefix.expr(); + full_expr.push_str(wire_expr.suffix.as_ref()); + let _ = self.full_expr.set(full_expr); return Some(self.full_expr.get().as_ref().unwrap()); } }