#![allow(deprecated)] use {std::borrow::Cow, thiserror::Error}; const MAX_DATA_SIZE: usize = 128; const MAX_DATA_BASE58_SIZE: usize = 175; const MAX_DATA_BASE64_SIZE: usize = 172; #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum RpcFilterType { DataSize(u64), Memcmp(Memcmp), } impl RpcFilterType { pub fn verify(&self) -> Result<(), RpcFilterError> { match self { RpcFilterType::DataSize(_) => Ok(()), RpcFilterType::Memcmp(compare) => { let encoding = compare.encoding.as_ref().unwrap_or(&MemcmpEncoding::Binary); match encoding { MemcmpEncoding::Binary => { use MemcmpEncodedBytes::*; match &compare.bytes { // DEPRECATED Binary(bytes) => { if bytes.len() > MAX_DATA_BASE58_SIZE { return Err(RpcFilterError::Base58DataTooLarge); } let bytes = bs58::decode(&bytes) .into_vec() .map_err(RpcFilterError::DecodeError)?; if bytes.len() > MAX_DATA_SIZE { Err(RpcFilterError::Base58DataTooLarge) } else { Ok(()) } } Base58(bytes) => { if bytes.len() > MAX_DATA_BASE58_SIZE { return Err(RpcFilterError::DataTooLarge); } let bytes = bs58::decode(&bytes).into_vec()?; if bytes.len() > MAX_DATA_SIZE { Err(RpcFilterError::DataTooLarge) } else { Ok(()) } } Base64(bytes) => { if bytes.len() > MAX_DATA_BASE64_SIZE { return Err(RpcFilterError::DataTooLarge); } let bytes = base64::decode(&bytes)?; if bytes.len() > MAX_DATA_SIZE { Err(RpcFilterError::DataTooLarge) } else { Ok(()) } } Bytes(bytes) => { if bytes.len() > MAX_DATA_SIZE { return Err(RpcFilterError::DataTooLarge); } Ok(()) } } } } } } } } #[derive(Error, PartialEq, Debug)] pub enum RpcFilterError { #[error("encoded binary data should be less than 129 bytes")] DataTooLarge, #[deprecated( since = "1.8.1", note = "Error for MemcmpEncodedBytes::Binary which is deprecated" )] #[error("encoded binary (base 58) data should be less than 129 bytes")] Base58DataTooLarge, #[deprecated( since = "1.8.1", note = "Error for MemcmpEncodedBytes::Binary which is deprecated" )] #[error("bs58 decode error")] DecodeError(bs58::decode::Error), #[error("base58 decode error")] Base58DecodeError(#[from] bs58::decode::Error), #[error("base64 decode error")] Base64DecodeError(#[from] base64::DecodeError), } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum MemcmpEncoding { Binary, } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] #[serde(rename_all = "camelCase", untagged)] pub enum MemcmpEncodedBytes { #[deprecated( since = "1.8.1", note = "Please use MemcmpEncodedBytes::Base58 instead" )] Binary(String), Base58(String), Base64(String), Bytes(Vec), } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct Memcmp { /// Data offset to begin match pub offset: usize, /// Bytes, encoded with specified encoding, or default Binary pub bytes: MemcmpEncodedBytes, /// Optional encoding specification pub encoding: Option, } impl Memcmp { pub fn bytes(&self) -> Option>> { use MemcmpEncodedBytes::*; match &self.bytes { Binary(bytes) | Base58(bytes) => bs58::decode(bytes).into_vec().ok().map(Cow::Owned), Base64(bytes) => base64::decode(bytes).ok().map(Cow::Owned), Bytes(bytes) => Some(Cow::Borrowed(bytes)), } } pub fn bytes_match(&self, data: &[u8]) -> bool { match self.bytes() { Some(bytes) => { if self.offset > data.len() { return false; } if data[self.offset..].len() < bytes.len() { return false; } data[self.offset..self.offset + bytes.len()] == bytes[..] } None => false, } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_worst_case_encoded_tx_goldens() { let ff_data = vec![0xffu8; MAX_DATA_SIZE]; let data58 = bs58::encode(&ff_data).into_string(); assert_eq!(data58.len(), MAX_DATA_BASE58_SIZE); let data64 = base64::encode(&ff_data); assert_eq!(data64.len(), MAX_DATA_BASE64_SIZE); } #[test] fn test_bytes_match() { let data = vec![1, 2, 3, 4, 5]; // Exact match of data succeeds assert!(Memcmp { offset: 0, bytes: MemcmpEncodedBytes::Base58(bs58::encode(vec![1, 2, 3, 4, 5]).into_string()), encoding: None, } .bytes_match(&data)); // Partial match of data succeeds assert!(Memcmp { offset: 0, bytes: MemcmpEncodedBytes::Base58(bs58::encode(vec![1, 2]).into_string()), encoding: None, } .bytes_match(&data)); // Offset partial match of data succeeds assert!(Memcmp { offset: 2, bytes: MemcmpEncodedBytes::Base58(bs58::encode(vec![3, 4]).into_string()), encoding: None, } .bytes_match(&data)); // Incorrect partial match of data fails assert!(!Memcmp { offset: 0, bytes: MemcmpEncodedBytes::Base58(bs58::encode(vec![2]).into_string()), encoding: None, } .bytes_match(&data)); // Bytes overrun data fails assert!(!Memcmp { offset: 2, bytes: MemcmpEncodedBytes::Base58(bs58::encode(vec![3, 4, 5, 6]).into_string()), encoding: None, } .bytes_match(&data)); // Offset outside data fails assert!(!Memcmp { offset: 6, bytes: MemcmpEncodedBytes::Base58(bs58::encode(vec![5]).into_string()), encoding: None, } .bytes_match(&data)); // Invalid base-58 fails assert!(!Memcmp { offset: 0, bytes: MemcmpEncodedBytes::Base58("III".to_string()), encoding: None, } .bytes_match(&data)); } #[test] fn test_verify_memcmp() { let base58_bytes = "\ 1111111111111111111111111111111111111111111111111111111111111111\ 1111111111111111111111111111111111111111111111111111111111111111"; assert_eq!(base58_bytes.len(), 128); assert_eq!( RpcFilterType::Memcmp(Memcmp { offset: 0, bytes: MemcmpEncodedBytes::Base58(base58_bytes.to_string()), encoding: None, }) .verify(), Ok(()) ); let base58_bytes = "\ 1111111111111111111111111111111111111111111111111111111111111111\ 1111111111111111111111111111111111111111111111111111111111111111\ 1"; assert_eq!(base58_bytes.len(), 129); assert_eq!( RpcFilterType::Memcmp(Memcmp { offset: 0, bytes: MemcmpEncodedBytes::Base58(base58_bytes.to_string()), encoding: None, }) .verify(), Err(RpcFilterError::DataTooLarge) ); } }