diff --git a/src/db.rs b/src/db.rs index 53bfedc..8f3f53e 100644 --- a/src/db.rs +++ b/src/db.rs @@ -3,8 +3,11 @@ use crate::config::SETTINGS; use crate::error::Error; use crate::error::Result; use crate::event::Event; +use crate::hexrange::hex_range; +use crate::hexrange::HexSearch; use crate::nip05; use crate::subscription::Subscription; +use crate::utils::is_hex; use governor::clock::Clock; use governor::{Quota, RateLimiter}; use hex; @@ -560,86 +563,6 @@ pub struct QueryResult { pub event: String, } -/// Check if a string contains only hex characters. -fn is_hex(s: &str) -> bool { - s.chars().all(|x| char::is_ascii_hexdigit(&x)) -} - -/// Check if a string contains only f chars -fn is_all_fs(s: &str) -> bool { - s.chars().all(|x| x == 'f' || x == 'F') -} - -/// Types of hexadecimal queries. -#[derive(PartialEq, Debug, Clone)] -enum HexSearch { - // when no range is needed, exact 32-byte - Exact(Vec), - // lower (inclusive) and upper range (exclusive) - Range(Vec, Vec), - // lower bound only, upper bound is MAX inclusive - LowerOnly(Vec), -} - -/// Find the next hex sequence greater than the argument. -fn hex_range(s: &str) -> Option { - // handle special cases - if !is_hex(s) || s.len() > 64 { - return None; - } - if s.len() == 64 { - return Some(HexSearch::Exact(hex::decode(s).ok()?)); - } - // if s is odd, add a zero - let mut hash_base = s.to_owned(); - let mut odd = hash_base.len() % 2 != 0; - if odd { - // extend the string to make it even - hash_base.push('0'); - } - let base = hex::decode(hash_base).ok()?; - // check for all ff's - if is_all_fs(s) { - // there is no higher bound, we only want to search for blobs greater than this. - return Some(HexSearch::LowerOnly(base)); - } - - // return a range - let mut upper = base.clone(); - let mut byte_len = upper.len(); - - // for odd strings, we made them longer, but we want to increment the upper char (+16). - // we know we can do this without overflowing because we explicitly set the bottom half to 0's. - while byte_len > 0 { - byte_len -= 1; - // check if byte can be incremented, or if we need to carry. - let b = upper[byte_len]; - if b == u8::MAX { - // reset and carry - upper[byte_len] = 0; - } else if odd { - // check if first char in this byte is NOT 'f' - if b < 240 { - upper[byte_len] = b + 16; // bump up the first character in this byte - // increment done, stop iterating through the vec - break; - } else { - // if it is 'f', reset the byte to 0 and do a carry - // reset and carry - upper[byte_len] = 0; - } - // done with odd logic, so don't repeat this - odd = false; - } else { - // bump up the first character in this byte - upper[byte_len] = b + 1; - // increment done, stop iterating - break; - } - } - Some(HexSearch::Range(base, upper)) -} - /// Produce a arbitrary list of '?' parameters. fn repeat_vars(count: usize) -> String { if count == 0 { @@ -683,7 +606,6 @@ fn query_from_sub(sub: &Subscription) -> (String, Vec>) { params.push(Box::new(upper)); } Some(HexSearch::LowerOnly(lower)) => { - // info!("{:?} => lower; {:?} ", auth, hex::encode(lower)); auth_searches.push("author>?".to_owned()); params.push(Box::new(lower)); } @@ -836,84 +758,3 @@ pub async fn db_query( ok }); } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn hex_range_exact() -> Result<()> { - let hex = "abcdef00abcdef00abcdef00abcdef00abcdef00abcdef00abcdef00abcdef00"; - let r = hex_range(hex); - assert_eq!( - r, - Some(HexSearch::Exact(hex::decode(hex).expect("invalid hex"))) - ); - Ok(()) - } - #[test] - fn hex_full_range() -> Result<()> { - //let hex = "abcdef00abcdef00abcdef00abcdef00abcdef00abcdef00abcdef00abcdef00"; - let hex = "aaaa"; - let hex_upper = "aaab"; - let r = hex_range(hex); - assert_eq!( - r, - Some(HexSearch::Range( - hex::decode(hex).expect("invalid hex"), - hex::decode(hex_upper).expect("invalid hex") - )) - ); - Ok(()) - } - - #[test] - fn hex_full_range_odd() -> Result<()> { - let r = hex_range("abc"); - assert_eq!( - r, - Some(HexSearch::Range( - hex::decode("abc0").expect("invalid hex"), - hex::decode("abd0").expect("invalid hex") - )) - ); - Ok(()) - } - - #[test] - fn hex_full_range_odd_end_f() -> Result<()> { - let r = hex_range("abf"); - assert_eq!( - r, - Some(HexSearch::Range( - hex::decode("abf0").expect("invalid hex"), - hex::decode("ac00").expect("invalid hex") - )) - ); - Ok(()) - } - - #[test] - fn hex_no_upper() -> Result<()> { - let r = hex_range("ffff"); - assert_eq!( - r, - Some(HexSearch::LowerOnly( - hex::decode("ffff").expect("invalid hex") - )) - ); - Ok(()) - } - - #[test] - fn hex_no_upper_odd() -> Result<()> { - let r = hex_range("fff"); - assert_eq!( - r, - Some(HexSearch::LowerOnly( - hex::decode("fff0").expect("invalid hex") - )) - ); - Ok(()) - } -} diff --git a/src/event.rs b/src/event.rs index a12dcf6..c118850 100644 --- a/src/event.rs +++ b/src/event.rs @@ -3,6 +3,7 @@ use crate::config; use crate::error::Error::*; use crate::error::Result; use crate::nip05; +use crate::utils::unix_time; use bitcoin_hashes::{sha256, Hash}; use lazy_static::lazy_static; use log::*; @@ -13,7 +14,6 @@ use serde_json::Number; use std::collections::HashMap; use std::collections::HashSet; use std::str::FromStr; -use std::time::SystemTime; lazy_static! { /// Secp256k1 verification instance. @@ -72,14 +72,6 @@ impl From for Result { } } -/// Seconds since 1970. -pub fn unix_time() -> u64 { - SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .map(|x| x.as_secs()) - .unwrap_or(0) -} - impl Event { pub fn is_kind_metadata(&self) -> bool { self.kind == 0 diff --git a/src/hexrange.rs b/src/hexrange.rs new file mode 100644 index 0000000..4d809f0 --- /dev/null +++ b/src/hexrange.rs @@ -0,0 +1,158 @@ +//! Utilities for searching hexadecimal +use crate::utils::is_hex; +use hex; + +/// Types of hexadecimal queries. +#[derive(PartialEq, Debug, Clone)] +pub enum HexSearch { + // when no range is needed, exact 32-byte + Exact(Vec), + // lower (inclusive) and upper range (exclusive) + Range(Vec, Vec), + // lower bound only, upper bound is MAX inclusive + LowerOnly(Vec), +} + +/// Check if a string contains only f chars +fn is_all_fs(s: &str) -> bool { + s.chars().all(|x| x == 'f' || x == 'F') +} + +/// Find the next hex sequence greater than the argument. +pub fn hex_range(s: &str) -> Option { + // handle special cases + if !is_hex(s) || s.len() > 64 { + return None; + } + if s.len() == 64 { + return Some(HexSearch::Exact(hex::decode(s).ok()?)); + } + // if s is odd, add a zero + let mut hash_base = s.to_owned(); + let mut odd = hash_base.len() % 2 != 0; + if odd { + // extend the string to make it even + hash_base.push('0'); + } + let base = hex::decode(hash_base).ok()?; + // check for all ff's + if is_all_fs(s) { + // there is no higher bound, we only want to search for blobs greater than this. + return Some(HexSearch::LowerOnly(base)); + } + + // return a range + let mut upper = base.clone(); + let mut byte_len = upper.len(); + + // for odd strings, we made them longer, but we want to increment the upper char (+16). + // we know we can do this without overflowing because we explicitly set the bottom half to 0's. + while byte_len > 0 { + byte_len -= 1; + // check if byte can be incremented, or if we need to carry. + let b = upper[byte_len]; + if b == u8::MAX { + // reset and carry + upper[byte_len] = 0; + } else if odd { + // check if first char in this byte is NOT 'f' + if b < 240 { + upper[byte_len] = b + 16; // bump up the first character in this byte + // increment done, stop iterating through the vec + break; + } else { + // if it is 'f', reset the byte to 0 and do a carry + // reset and carry + upper[byte_len] = 0; + } + // done with odd logic, so don't repeat this + odd = false; + } else { + // bump up the first character in this byte + upper[byte_len] = b + 1; + // increment done, stop iterating + break; + } + } + Some(HexSearch::Range(base, upper)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn hex_range_exact() -> Result<()> { + let hex = "abcdef00abcdef00abcdef00abcdef00abcdef00abcdef00abcdef00abcdef00"; + let r = hex_range(hex); + assert_eq!( + r, + Some(HexSearch::Exact(hex::decode(hex).expect("invalid hex"))) + ); + Ok(()) + } + #[test] + fn hex_full_range() -> Result<()> { + let hex = "aaaa"; + let hex_upper = "aaab"; + let r = hex_range(hex); + assert_eq!( + r, + Some(HexSearch::Range( + hex::decode(hex).expect("invalid hex"), + hex::decode(hex_upper).expect("invalid hex") + )) + ); + Ok(()) + } + + #[test] + fn hex_full_range_odd() -> Result<()> { + let r = hex_range("abc"); + assert_eq!( + r, + Some(HexSearch::Range( + hex::decode("abc0").expect("invalid hex"), + hex::decode("abd0").expect("invalid hex") + )) + ); + Ok(()) + } + + #[test] + fn hex_full_range_odd_end_f() -> Result<()> { + let r = hex_range("abf"); + assert_eq!( + r, + Some(HexSearch::Range( + hex::decode("abf0").expect("invalid hex"), + hex::decode("ac00").expect("invalid hex") + )) + ); + Ok(()) + } + + #[test] + fn hex_no_upper() -> Result<()> { + let r = hex_range("ffff"); + assert_eq!( + r, + Some(HexSearch::LowerOnly( + hex::decode("ffff").expect("invalid hex") + )) + ); + Ok(()) + } + + #[test] + fn hex_no_upper_odd() -> Result<()> { + let r = hex_range("fff"); + assert_eq!( + r, + Some(HexSearch::LowerOnly( + hex::decode("fff0").expect("invalid hex") + )) + ); + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index fad4d12..589eafc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,9 @@ pub mod conn; pub mod db; pub mod error; pub mod event; +pub mod hexrange; pub mod info; pub mod nip05; pub mod protostream; pub mod subscription; +pub mod utils; diff --git a/src/nip05.rs b/src/nip05.rs index 6788a00..ed023f2 100644 --- a/src/nip05.rs +++ b/src/nip05.rs @@ -7,7 +7,8 @@ use crate::config::SETTINGS; use crate::db; use crate::error::{Error, Result}; -use crate::event::{unix_time, Event}; +use crate::event::Event; +use crate::utils::unix_time; use hyper::body::HttpBody; use hyper::client::connect::HttpConnector; use hyper::Client; diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..2b4a3db --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,15 @@ +//! Common utility functions +use std::time::SystemTime; + +/// Seconds since 1970. +pub fn unix_time() -> u64 { + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .map(|x| x.as_secs()) + .unwrap_or(0) +} + +/// Check if a string contains only hex characters. +pub fn is_hex(s: &str) -> bool { + s.chars().all(|x| char::is_ascii_hexdigit(&x)) +}