ibc_types_identifier/
lib.rs1#![no_std]
3#![cfg_attr(docsrs, feature(doc_auto_cfg))]
5
6extern crate alloc;
7#[cfg(any(test, feature = "std"))]
8extern crate std;
9
10mod prelude;
11use prelude::*;
12
13use displaydoc::Display;
14
15#[derive(Debug, Display)]
16#[cfg_attr(feature = "with_serde", derive(serde::Serialize))]
17pub enum IdentifierError {
18 ContainSeparator { id: String },
20 InvalidLength {
22 id: String,
23 length: usize,
24 min: usize,
25 max: usize,
26 },
27 InvalidCharacter { id: String },
29 Empty,
31 InvalidCounterpartyChannelId,
33}
34
35#[cfg(feature = "std")]
36impl std::error::Error for IdentifierError {}
37
38const PATH_SEPARATOR: char = '/';
40const VALID_SPECIAL_CHARS: &str = "._+-#[]<>";
41
42pub fn validate_identifier(id: &str, min: usize, max: usize) -> Result<(), IdentifierError> {
47 assert!(max >= min);
48
49 if id.is_empty() {
51 return Err(IdentifierError::Empty);
52 }
53
54 if id.contains(PATH_SEPARATOR) {
56 return Err(IdentifierError::ContainSeparator { id: id.into() });
57 }
58
59 if id.len() < min || id.len() > max {
61 return Err(IdentifierError::InvalidLength {
62 id: id.into(),
63 length: id.len(),
64 min,
65 max,
66 });
67 }
68
69 if !id
74 .chars()
75 .all(|c| c.is_alphanumeric() || VALID_SPECIAL_CHARS.contains(c))
76 {
77 return Err(IdentifierError::InvalidCharacter { id: id.into() });
78 }
79
80 Ok(())
82}
83
84pub fn validate_client_identifier(id: &str) -> Result<(), IdentifierError> {
89 validate_identifier(id, 9, 64)
90}
91
92pub fn validate_connection_identifier(id: &str) -> Result<(), IdentifierError> {
97 validate_identifier(id, 10, 64)
98}
99
100pub fn validate_port_identifier(id: &str) -> Result<(), IdentifierError> {
105 validate_identifier(id, 2, 128)
106}
107
108pub fn validate_channel_identifier(id: &str) -> Result<(), IdentifierError> {
113 validate_identifier(id, 8, 64)
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119 use test_log::test;
120
121 #[test]
122 fn parse_invalid_port_id_min() {
123 let id = validate_port_identifier("p");
125 assert!(id.is_err())
126 }
127
128 #[test]
129 fn parse_invalid_port_id_max() {
130 let id = validate_port_identifier(
132 "9anxkcme6je544d5lnj46zqiiiygfqzf8w4bjecbnyj4lj6s7zlpst67yln64tixp9anxkcme6je544d5lnj46zqiiiygfqzf8w4bjecbnyj4lj6s7zlpst67yln64tixp",
133 );
134 assert!(id.is_err())
135 }
136
137 #[test]
138 fn parse_invalid_connection_id_min() {
139 let id = validate_connection_identifier("connect01");
141 assert!(id.is_err())
142 }
143
144 #[test]
145 fn parse_connection_id_max() {
146 let id = validate_connection_identifier(
148 "ihhankr30iy4nna65hjl2wjod7182io1t2s7u3ip3wqtbbn1sl0rgcntqc540r36r",
149 );
150 assert!(id.is_err())
151 }
152
153 #[test]
154 fn parse_invalid_channel_id_min() {
155 let id = validate_channel_identifier("channel");
157 assert!(id.is_err())
158 }
159
160 #[test]
161 fn parse_channel_id_max() {
162 let id = validate_channel_identifier(
164 "ihhankr30iy4nna65hjl2wjod7182io1t2s7u3ip3wqtbbn1sl0rgcntqc540r36r",
165 );
166 assert!(id.is_err())
167 }
168
169 #[test]
170 fn parse_invalid_client_id_min() {
171 let id = validate_client_identifier("client");
173 assert!(id.is_err())
174 }
175
176 #[test]
177 fn parse_client_id_max() {
178 let id = validate_client_identifier(
180 "f0isrs5enif9e4td3r2jcbxoevhz6u1fthn4aforq7ams52jn5m48eiesfht9ckpn",
181 );
182 assert!(id.is_err())
183 }
184
185 #[test]
186 fn parse_invalid_id_chars() {
187 let id = validate_identifier("channel@01", 1, 10);
189 assert!(id.is_err())
190 }
191
192 #[test]
193 fn parse_invalid_id_empty() {
194 let id = validate_identifier("", 1, 10);
196 assert!(id.is_err())
197 }
198
199 #[test]
200 fn parse_invalid_id_path_separator() {
201 let id = validate_identifier("id/1", 1, 10);
203 assert!(id.is_err())
204 }
205}