1use url::Url;
8
9use opcua_types::{constants::DEFAULT_OPC_UA_SERVER_PORT, status_code::StatusCode};
10
11pub const OPC_TCP_SCHEME: &str = "opc.tcp";
12
13fn opc_url_from_str(s: &str) -> Result<Url, ()> {
15 Url::parse(s)
16 .map(|mut url| {
17 if url.port().is_none() {
18 let _ = url.set_port(Some(DEFAULT_OPC_UA_SERVER_PORT));
20 }
21 url
22 })
23 .map_err(|err| {
24 error!("Cannot parse url \"{}\", error = {:?}", s, err);
25 })
26}
27
28pub fn url_with_replaced_hostname(url: &str, hostname: &str) -> Result<String, ()> {
30 let mut url = opc_url_from_str(url)?;
31 let _ = url.set_host(Some(hostname));
32 Ok(url.into_string())
33}
34
35pub fn url_matches_except_host(url1: &str, url2: &str) -> bool {
38 if let Ok(mut url1) = opc_url_from_str(url1) {
39 if let Ok(mut url2) = opc_url_from_str(url2) {
40 if url1.set_host(Some("xxxx")).is_ok() && url2.set_host(Some("xxxx")).is_ok() {
43 return url1 == url2;
44 }
45 } else {
46 error!("Cannot parse url \"{}\"", url2);
47 }
48 } else {
49 error!("Cannot parse url \"{}\"", url1);
50 }
51 false
52}
53
54pub fn server_url_from_endpoint_url(endpoint_url: &str) -> std::result::Result<String, ()> {
56 opc_url_from_str(endpoint_url).map(|mut url| {
57 url.set_query(None);
58 if let Some(port) = url.port() {
59 if port == DEFAULT_OPC_UA_SERVER_PORT {
61 let _ = url.set_port(None);
62 }
63 }
64 url.into_string()
65 })
66}
67
68pub fn is_valid_opc_ua_url(url: &str) -> bool {
69 is_opc_ua_binary_url(url)
70}
71
72pub fn is_opc_ua_binary_url(url: &str) -> bool {
73 if let Ok(url) = opc_url_from_str(url) {
74 url.scheme() == OPC_TCP_SCHEME
75 } else {
76 false
77 }
78}
79
80pub fn hostname_from_url(url: &str) -> Result<String, ()> {
81 if let Ok(url) = Url::parse(url) {
83 if let Some(host) = url.host_str() {
84 Ok(host.to_string())
85 } else {
86 Err(())
87 }
88 } else {
89 Err(())
90 }
91}
92
93pub fn hostname_port_from_url(url: &str, default_port: u16) -> Result<(String, u16), StatusCode> {
94 let url = Url::parse(url).map_err(|_| StatusCode::BadTcpEndpointUrlInvalid)?;
96
97 if url.scheme() != OPC_TCP_SCHEME || !url.has_host() {
98 Err(StatusCode::BadTcpEndpointUrlInvalid)
99 } else {
100 let host = url.host_str().unwrap();
101 let port = url.port().unwrap_or(default_port);
102 Ok((host.to_string(), port))
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn url_scheme() {
112 assert!(is_opc_ua_binary_url("opc.tcp://foo/xyz"));
113 assert!(is_opc_ua_binary_url(
114 "opc.tcp://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/xyz"
115 ));
116 assert!(!is_opc_ua_binary_url("http://foo/xyz"));
117 }
118
119 #[test]
120 fn url_matches_test() {
121 assert!(url_matches_except_host(
122 "opc.tcp://localhost/xyz",
123 "opc.tcp://127.0.0.1/xyz"
124 ));
125 assert!(!url_matches_except_host(
126 "opc.tcp://localhost/xyz",
127 "opc.tcp://127.0.0.1/abc"
128 ));
129 }
130
131 #[test]
132 fn server_url_from_endpoint_url_test() {
133 assert_eq!(
134 "opc.tcp://localhost/",
135 server_url_from_endpoint_url("opc.tcp://localhost").unwrap()
136 );
137 assert_eq!(
138 "opc.tcp://localhost/",
139 server_url_from_endpoint_url("opc.tcp://localhost:4840").unwrap()
140 );
141 assert_eq!(
142 "opc.tcp://localhost:4841/",
143 server_url_from_endpoint_url("opc.tcp://localhost:4841").unwrap()
144 );
145 assert_eq!(
146 "opc.tcp://localhost/xyz/abc",
147 server_url_from_endpoint_url("opc.tcp://localhost/xyz/abc?1").unwrap()
148 );
149 assert_eq!(
150 "opc.tcp://localhost:999/xyz/abc",
151 server_url_from_endpoint_url("opc.tcp://localhost:999/xyz/abc?1").unwrap()
152 );
153 }
154
155 #[test]
156 fn url_with_replaced_hostname_test() {
157 assert_eq!(
158 url_with_replaced_hostname("opc.tcp://foo:123/x", "foo").unwrap(),
159 "opc.tcp://foo:123/x"
160 );
161 assert_eq!(
162 url_with_replaced_hostname("opc.tcp://foo:123/x", "bar").unwrap(),
163 "opc.tcp://bar:123/x"
164 );
165 assert_eq!(
166 url_with_replaced_hostname("opc.tcp://localhost:123/x", "127.0.0.1").unwrap(),
167 "opc.tcp://127.0.0.1:123/x"
168 );
169 }
170}