gemachain_client/
rpc_filter.rs

1use thiserror::Error;
2
3#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
4#[serde(rename_all = "camelCase")]
5pub enum RpcFilterType {
6    DataSize(u64),
7    Memcmp(Memcmp),
8}
9
10impl RpcFilterType {
11    pub fn verify(&self) -> Result<(), RpcFilterError> {
12        match self {
13            RpcFilterType::DataSize(_) => Ok(()),
14            RpcFilterType::Memcmp(compare) => {
15                let encoding = compare.encoding.as_ref().unwrap_or(&MemcmpEncoding::Binary);
16                match encoding {
17                    MemcmpEncoding::Binary => {
18                        let MemcmpEncodedBytes::Binary(bytes) = &compare.bytes;
19
20                        if bytes.len() > 128 {
21                            Err(RpcFilterError::Base58DataTooLarge)
22                        } else {
23                            bs58::decode(&bytes)
24                                .into_vec()
25                                .map(|_| ())
26                                .map_err(|e| e.into())
27                        }
28                    }
29                }
30            }
31        }
32    }
33}
34
35#[derive(Error, PartialEq, Debug)]
36pub enum RpcFilterError {
37    #[error("bs58 decode error")]
38    DecodeError(#[from] bs58::decode::Error),
39    #[error("encoded binary (base 58) data should be less than 129 bytes")]
40    Base58DataTooLarge,
41}
42
43#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
44#[serde(rename_all = "camelCase")]
45pub enum MemcmpEncoding {
46    Binary,
47}
48
49#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
50#[serde(rename_all = "camelCase", untagged)]
51pub enum MemcmpEncodedBytes {
52    Binary(String),
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
56pub struct Memcmp {
57    /// Data offset to begin match
58    pub offset: usize,
59    /// Bytes, encoded with specified encoding, or default Binary
60    pub bytes: MemcmpEncodedBytes,
61    /// Optional encoding specification
62    pub encoding: Option<MemcmpEncoding>,
63}
64
65impl Memcmp {
66    pub fn bytes_match(&self, data: &[u8]) -> bool {
67        match &self.bytes {
68            MemcmpEncodedBytes::Binary(bytes) => {
69                let bytes = bs58::decode(bytes).into_vec();
70                if bytes.is_err() {
71                    return false;
72                }
73                let bytes = bytes.unwrap();
74                if self.offset > data.len() {
75                    return false;
76                }
77                if data[self.offset..].len() < bytes.len() {
78                    return false;
79                }
80                data[self.offset..self.offset + bytes.len()] == bytes[..]
81            }
82        }
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn test_bytes_match() {
92        let data = vec![1, 2, 3, 4, 5];
93
94        // Exact match of data succeeds
95        assert!(Memcmp {
96            offset: 0,
97            bytes: MemcmpEncodedBytes::Binary(bs58::encode(vec![1, 2, 3, 4, 5]).into_string()),
98            encoding: None,
99        }
100        .bytes_match(&data));
101
102        // Partial match of data succeeds
103        assert!(Memcmp {
104            offset: 0,
105            bytes: MemcmpEncodedBytes::Binary(bs58::encode(vec![1, 2]).into_string()),
106            encoding: None,
107        }
108        .bytes_match(&data));
109
110        // Offset partial match of data succeeds
111        assert!(Memcmp {
112            offset: 2,
113            bytes: MemcmpEncodedBytes::Binary(bs58::encode(vec![3, 4]).into_string()),
114            encoding: None,
115        }
116        .bytes_match(&data));
117
118        // Incorrect partial match of data fails
119        assert!(!Memcmp {
120            offset: 0,
121            bytes: MemcmpEncodedBytes::Binary(bs58::encode(vec![2]).into_string()),
122            encoding: None,
123        }
124        .bytes_match(&data));
125
126        // Bytes overrun data fails
127        assert!(!Memcmp {
128            offset: 2,
129            bytes: MemcmpEncodedBytes::Binary(bs58::encode(vec![3, 4, 5, 6]).into_string()),
130            encoding: None,
131        }
132        .bytes_match(&data));
133
134        // Offset outside data fails
135        assert!(!Memcmp {
136            offset: 6,
137            bytes: MemcmpEncodedBytes::Binary(bs58::encode(vec![5]).into_string()),
138            encoding: None,
139        }
140        .bytes_match(&data));
141
142        // Invalid base-58 fails
143        assert!(!Memcmp {
144            offset: 0,
145            bytes: MemcmpEncodedBytes::Binary("III".to_string()),
146            encoding: None,
147        }
148        .bytes_match(&data));
149    }
150
151    #[test]
152    fn test_verify_memcmp() {
153        let base58_bytes = "\
154            1111111111111111111111111111111111111111111111111111111111111111\
155            1111111111111111111111111111111111111111111111111111111111111111";
156        assert_eq!(base58_bytes.len(), 128);
157        assert_eq!(
158            RpcFilterType::Memcmp(Memcmp {
159                offset: 0,
160                bytes: MemcmpEncodedBytes::Binary(base58_bytes.to_string()),
161                encoding: None,
162            })
163            .verify(),
164            Ok(())
165        );
166
167        let base58_bytes = "\
168            1111111111111111111111111111111111111111111111111111111111111111\
169            1111111111111111111111111111111111111111111111111111111111111111\
170            1";
171        assert_eq!(base58_bytes.len(), 129);
172        assert_eq!(
173            RpcFilterType::Memcmp(Memcmp {
174                offset: 0,
175                bytes: MemcmpEncodedBytes::Binary(base58_bytes.to_string()),
176                encoding: None,
177            })
178            .verify(),
179            Err(RpcFilterError::Base58DataTooLarge)
180        );
181    }
182}