zenoh_protocol_core/
locators.rs

1//
2// Copyright (c) 2022 ZettaScale Technology
3//
4// This program and the accompanying materials are made available under the
5// terms of the Eclipse Public License 2.0 which is available at
6// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7// which is available at https://www.apache.org/licenses/LICENSE-2.0.
8//
9// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
10//
11// Contributors:
12//   ZettaScale Zenoh Team, <zenoh@zettascale.tech>
13//
14use std::{
15    collections::HashMap, convert::TryFrom, hash::Hash, iter::FromIterator, str::FromStr, sync::Arc,
16};
17
18use zenoh_core::bail;
19
20use crate::split_once;
21
22// Parsing chars
23pub const PROTO_SEPARATOR: char = '/';
24pub const METADATA_SEPARATOR: char = '?';
25pub const CONFIG_SEPARATOR: char = '#';
26pub const LIST_SEPARATOR: char = ';';
27pub const FIELD_SEPARATOR: char = '=';
28
29#[derive(Debug, Clone, Eq)]
30pub struct ArcProperties(pub Arc<HashMap<String, String>>);
31impl std::ops::Deref for ArcProperties {
32    type Target = Arc<HashMap<String, String>>;
33    fn deref(&self) -> &Self::Target {
34        &self.0
35    }
36}
37impl Hash for ArcProperties {
38    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
39        Arc::as_ptr(&self.0).hash(state);
40    }
41}
42impl std::ops::DerefMut for ArcProperties {
43    fn deref_mut(&mut self) -> &mut Self::Target {
44        &mut self.0
45    }
46}
47impl PartialEq for ArcProperties {
48    fn eq(&self, other: &Self) -> bool {
49        Arc::as_ptr(&self.0) == Arc::as_ptr(&other.0)
50    }
51}
52impl From<Arc<HashMap<String, String>>> for ArcProperties {
53    fn from(v: Arc<HashMap<String, String>>) -> Self {
54        ArcProperties(v)
55    }
56}
57impl From<HashMap<String, String>> for ArcProperties {
58    fn from(v: HashMap<String, String>) -> Self {
59        ArcProperties(Arc::new(v))
60    }
61}
62impl ArcProperties {
63    pub fn merge(&mut self, other: &Arc<HashMap<String, String>>) {
64        if other.is_empty() {
65            return;
66        }
67        if self.is_empty() {
68            self.0 = other.clone()
69        } else {
70            self.extend(other.iter().map(|(k, v)| (k.clone(), v.clone())))
71        }
72    }
73}
74impl FromStr for ArcProperties {
75    type Err = ();
76
77    fn from_str(s: &str) -> Result<Self, Self::Err> {
78        let this = HashMap::from_iter(s.split(LIST_SEPARATOR).filter_map(|s| {
79            let (k, v) = split_once(s, FIELD_SEPARATOR);
80            (!k.is_empty()).then(|| (k.to_owned(), v.to_owned()))
81        }));
82        match this.is_empty() {
83            true => Err(()),
84            false => Ok(this.into()),
85        }
86    }
87}
88impl Extend<(String, String)> for ArcProperties {
89    fn extend<T: IntoIterator<Item = (String, String)>>(&mut self, iter: T) {
90        let mut iter = iter.into_iter();
91        if let Some((k, v)) = iter.next() {
92            let (min, max) = iter.size_hint();
93            let extended = Arc::make_mut(&mut self.0);
94            extended.reserve(max.unwrap_or(min));
95            extended.insert(k, v);
96            for (k, v) in iter {
97                extended.insert(k, v);
98            }
99        };
100    }
101}
102
103/// A `String` that respects the [`Locator`] canon form: `<proto>/<address>?<metadata>`, such that `<metadata>` is of the form `<key1>=<value1>;...;<keyN>=<valueN>` where keys are alphabetically sorted.
104#[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
105#[serde(into = "String")]
106#[serde(try_from = "String")]
107pub struct Locator {
108    pub(crate) inner: String,
109    #[serde(skip)]
110    pub metadata: Option<ArcProperties>,
111}
112impl From<Locator> for String {
113    fn from(val: Locator) -> Self {
114        val.inner
115    }
116}
117impl core::fmt::Display for Locator {
118    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> std::fmt::Result {
119        f.write_str(&self.inner)?;
120        if let Some(meta) = &self.metadata {
121            let mut iter = meta.iter();
122            if let Some((k, v)) = iter.next() {
123                write!(f, "{}{}{}{}", METADATA_SEPARATOR, k, FIELD_SEPARATOR, v)?;
124            }
125            for (k, v) in iter {
126                write!(f, "{}{}{}{}", LIST_SEPARATOR, k, FIELD_SEPARATOR, v)?;
127            }
128        }
129        Ok(())
130    }
131}
132impl TryFrom<String> for Locator {
133    type Error = zenoh_core::Error;
134    fn try_from(mut inner: String) -> Result<Self, Self::Error> {
135        let (locator, meta) = split_once(&inner, METADATA_SEPARATOR);
136        if !locator.contains(PROTO_SEPARATOR) {
137            bail!("Missing protocol: locators must be of the form <proto>/<address>[?<metadata>]")
138        }
139        let metadata = meta.parse().ok();
140        let locator_len = locator.len();
141        inner.truncate(locator_len);
142        Ok(Locator { inner, metadata })
143    }
144}
145impl FromStr for Locator {
146    type Err = zenoh_core::Error;
147    fn from_str(s: &str) -> Result<Self, Self::Err> {
148        let (locator, meta) = split_once(s, METADATA_SEPARATOR);
149        if !locator.contains(PROTO_SEPARATOR) {
150            bail!("Missing protocol: locators must be of the form <proto>/<address>[?<metadata>]")
151        }
152        Ok(Locator {
153            inner: locator.to_owned(),
154            metadata: meta.parse().ok(),
155        })
156    }
157}
158
159impl Locator {
160    #[inline(always)]
161    pub fn new<Addr: std::fmt::Display>(protocol: &str, addr: &Addr) -> Self {
162        Locator::try_from(format!("{}{}{}", protocol, PROTO_SEPARATOR, addr)).unwrap()
163    }
164    #[must_use = "returns true if successful"]
165    pub fn set_addr(&mut self, addr: &str) -> bool {
166        let addr_start = self.inner.find(PROTO_SEPARATOR).unwrap() + 1;
167        let addr_end = self
168            .inner
169            .find(METADATA_SEPARATOR)
170            .unwrap_or(self.inner.len());
171        self.inner.replace_range(addr_start..addr_end, addr);
172        true
173    }
174}
175
176impl Locator {
177    pub fn split(
178        &self,
179    ) -> (
180        &str,
181        &str,
182        impl Iterator<Item = (&str, &str)> + DoubleEndedIterator + Clone,
183    ) {
184        let (protocol, rest) = split_once(&self.inner, PROTO_SEPARATOR);
185        let (address, properties) = split_once(rest, METADATA_SEPARATOR);
186        (
187            protocol,
188            address,
189            properties
190                .split(LIST_SEPARATOR)
191                .map(|prop| split_once(prop, FIELD_SEPARATOR)),
192        )
193    }
194
195    pub fn protocol(&self) -> &str {
196        let index = self.inner.find(PROTO_SEPARATOR).unwrap_or(self.inner.len());
197        &self.inner[..index]
198    }
199
200    pub fn address(&self) -> &str {
201        let index = self.inner.find(PROTO_SEPARATOR).unwrap_or(self.inner.len());
202        let rest = &self.inner[index + 1..];
203        let index = rest.find(METADATA_SEPARATOR).unwrap_or(rest.len());
204        &rest[..index]
205    }
206
207    pub fn clone_without_meta(&self) -> Self {
208        Locator {
209            inner: self.inner.clone(),
210            metadata: None,
211        }
212    }
213
214    pub fn metadata(&self) -> Option<&ArcProperties> {
215        self.metadata.as_ref()
216    }
217}
218
219pub(crate) trait HasCanonForm {
220    fn is_canon(&self) -> bool;
221    type Output;
222    fn canonicalize(self) -> Self::Output;
223}
224fn cmp(this: &str, than: &str) -> std::cmp::Ordering {
225    let is_longer = this.len().cmp(&than.len());
226    let this = this.chars();
227    let than = than.chars();
228    let zip = this.zip(than);
229    for (this, than) in zip {
230        match this.cmp(&than) {
231            std::cmp::Ordering::Equal => {}
232            o => return o,
233        }
234    }
235    is_longer
236}
237impl<'a, T: Iterator<Item = (&'a str, V)> + Clone, V> HasCanonForm for T {
238    fn is_canon(&self) -> bool {
239        let mut iter = self.clone();
240        let mut acc = if let Some((key, _)) = iter.next() {
241            key
242        } else {
243            return true;
244        };
245        for (key, _) in iter {
246            if cmp(key, acc) != std::cmp::Ordering::Greater {
247                return false;
248            }
249            acc = key;
250        }
251        true
252    }
253    type Output = Vec<(&'a str, V)>;
254    fn canonicalize(mut self) -> Self::Output {
255        let mut result = Vec::new();
256        if let Some(v) = self.next() {
257            result.push(v);
258        }
259        'outer: for (k, v) in self {
260            for (i, (x, _)) in result.iter().enumerate() {
261                match cmp(k, x) {
262                    std::cmp::Ordering::Less => {
263                        result.insert(i, (k, v));
264                        continue 'outer;
265                    }
266                    std::cmp::Ordering::Equal => {
267                        result[i].1 = v;
268                        continue 'outer;
269                    }
270                    std::cmp::Ordering::Greater => {}
271                }
272            }
273            result.push((k, v))
274        }
275        result
276    }
277}
278
279#[test]
280fn locators() {
281    Locator::from_str("udp/127.0.0.1").unwrap();
282    let locator = Locator::from_str("udp/127.0.0.1?hello=there;general=kenobi").unwrap();
283    assert_eq!(locator.protocol(), "udp");
284    assert_eq!(locator.address(), "127.0.0.1");
285    assert_eq!(
286        ***locator.metadata().unwrap(),
287        [("general", "kenobi"), ("hello", "there")]
288            .iter()
289            .map(|&(k, v)| (k.to_owned(), v.to_owned()))
290            .collect::<HashMap<String, String>>()
291    );
292}
293
294pub type LocatorProtocol = str;