ibc_types_identifier/
lib.rs

1//! IBC identifier validation.
2#![no_std]
3// Requires nightly.
4#![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    /// identifier `{id}` cannot contain separator '/'
19    ContainSeparator { id: String },
20    /// identifier `{id}` has invalid length `{length}` must be between `{min}`-`{max}` characters
21    InvalidLength {
22        id: String,
23        length: usize,
24        min: usize,
25        max: usize,
26    },
27    /// identifier `{id}` must only contain alphanumeric characters or `.`, `_`, `+`, `-`, `#`, - `[`, `]`, `<`, `>`
28    InvalidCharacter { id: String },
29    /// identifier cannot be empty
30    Empty,
31    /// Invalid channel id in counterparty
32    InvalidCounterpartyChannelId,
33}
34
35#[cfg(feature = "std")]
36impl std::error::Error for IdentifierError {}
37
38/// Path separator (ie. forward slash '/')
39const PATH_SEPARATOR: char = '/';
40const VALID_SPECIAL_CHARS: &str = "._+-#[]<>";
41
42/// Default validator function for identifiers.
43///
44/// A valid identifier only contain lowercase alphabetic characters, and be of a given min and max
45/// length.
46pub fn validate_identifier(id: &str, min: usize, max: usize) -> Result<(), IdentifierError> {
47    assert!(max >= min);
48
49    // Check identifier is not empty
50    if id.is_empty() {
51        return Err(IdentifierError::Empty);
52    }
53
54    // Check identifier does not contain path separators
55    if id.contains(PATH_SEPARATOR) {
56        return Err(IdentifierError::ContainSeparator { id: id.into() });
57    }
58
59    // Check identifier length is between given min/max
60    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    // Check that the identifier comprises only valid characters:
70    // - Alphanumeric
71    // - `.`, `_`, `+`, `-`, `#`
72    // - `[`, `]`, `<`, `>`
73    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    // All good!
81    Ok(())
82}
83
84/// Default validator function for Client identifiers.
85///
86/// A valid identifier must be between 9-64 characters and only contain lowercase
87/// alphabetic characters,
88pub fn validate_client_identifier(id: &str) -> Result<(), IdentifierError> {
89    validate_identifier(id, 9, 64)
90}
91
92/// Default validator function for Connection identifiers.
93///
94/// A valid Identifier must be between 10-64 characters and only contain lowercase
95/// alphabetic characters,
96pub fn validate_connection_identifier(id: &str) -> Result<(), IdentifierError> {
97    validate_identifier(id, 10, 64)
98}
99
100/// Default validator function for Port identifiers.
101///
102/// A valid Identifier must be between 2-128 characters and only contain lowercase
103/// alphabetic characters,
104pub fn validate_port_identifier(id: &str) -> Result<(), IdentifierError> {
105    validate_identifier(id, 2, 128)
106}
107
108/// Default validator function for Channel identifiers.
109///
110/// A valid identifier must be between 8-64 characters and only contain
111/// alphabetic characters,
112pub 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        // invalid min port id
124        let id = validate_port_identifier("p");
125        assert!(id.is_err())
126    }
127
128    #[test]
129    fn parse_invalid_port_id_max() {
130        // invalid max port id (test string length is 130 chars)
131        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        // invalid min connection id
140        let id = validate_connection_identifier("connect01");
141        assert!(id.is_err())
142    }
143
144    #[test]
145    fn parse_connection_id_max() {
146        // invalid max connection id (test string length is 65)
147        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        // invalid channel id, must be at least 8 characters
156        let id = validate_channel_identifier("channel");
157        assert!(id.is_err())
158    }
159
160    #[test]
161    fn parse_channel_id_max() {
162        // invalid channel id (test string length is 65)
163        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        // invalid min client id
172        let id = validate_client_identifier("client");
173        assert!(id.is_err())
174    }
175
176    #[test]
177    fn parse_client_id_max() {
178        // invalid max client id (test string length is 65)
179        let id = validate_client_identifier(
180            "f0isrs5enif9e4td3r2jcbxoevhz6u1fthn4aforq7ams52jn5m48eiesfht9ckpn",
181        );
182        assert!(id.is_err())
183    }
184
185    #[test]
186    fn parse_invalid_id_chars() {
187        // invalid id chars
188        let id = validate_identifier("channel@01", 1, 10);
189        assert!(id.is_err())
190    }
191
192    #[test]
193    fn parse_invalid_id_empty() {
194        // invalid id empty
195        let id = validate_identifier("", 1, 10);
196        assert!(id.is_err())
197    }
198
199    #[test]
200    fn parse_invalid_id_path_separator() {
201        // invalid id with path separator
202        let id = validate_identifier("id/1", 1, 10);
203        assert!(id.is_err())
204    }
205}