diff --git a/Cargo.toml b/Cargo.toml index c438b9b..398ad0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "obfstr" -version = "0.2.5" +version = "0.2.6" edition = "2018" license = "MIT" diff --git a/src/lib.rs b/src/lib.rs index 167a06e..a709dd5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,9 +2,9 @@ Compiletime string constant obfuscation. */ -#![no_std] +#![cfg_attr(not(test), no_std)] -use core::{char, fmt, ops, ptr, str}; +use core::{char, fmt, ptr, str}; #[doc(hidden)] pub mod wide; @@ -15,6 +15,9 @@ pub mod cfo; mod murmur3; pub use self::murmur3::murmur3; +mod pos; +pub use self::pos::position; + //---------------------------------------------------------------- /// Compiletime random number generator. @@ -115,56 +118,6 @@ pub const SEED: u64 = splitmix(hash(env!("OBFSTR_SEED")) as u64); //---------------------------------------------------------------- -/// Finds the position of the needle in the haystack at compiletime. -/// -/// Produces a const-eval error if the needle is not a substring of the haystack. -/// -/// ``` -/// assert_eq!(obfstr::position!("haystack", "st"), 3..5); -///# assert_eq!(obfstr::position!("haystack", "haystack"), 0..8); -///# assert_eq!(obfstr::position!("haystack", "ck"), 6..8); -/// ``` -#[macro_export] -macro_rules! position { - ($haystack:expr, $needle:expr) => {{ const _POSITION_RANGE: ::core::ops::Range = $crate::position($haystack, $needle); _POSITION_RANGE }}; -} - -/// Finds the position of the needle in the haystack at compiletime. -/// -/// Produces a const-eval error if the needle is not a substring of the haystack. -/// -/// ``` -/// const POSITION: std::ops::Range = obfstr::position("haystack", "st"); -/// assert_eq!(POSITION, 3..5); -/// ``` -#[inline(always)] -pub const fn position(haystack: &str, needle: &str) -> ops::Range { - const fn check(haystack: &[u8], needle: &[u8], offset: usize) -> bool { - let mut i = 0; - while i < needle.len() { - if haystack[offset + i] != needle[i] { - return false; - } - i += 1; - } - return true; - } - let mut offset = 0; - let haystack = haystack.as_bytes(); - let needle = needle.as_bytes(); - while offset + needle.len() <= haystack.len() { - if check(haystack, needle, offset) { - return offset..offset + needle.len(); - } - offset += 1; - } - // Compile error if substring not found - let _ = haystack[haystack.len()]; - return 0..0; -} - -//---------------------------------------------------------------- - /// Obfuscated string constant data. /// /// This type represents the data baked in the binary and holds the key and obfuscated string. diff --git a/src/pos.rs b/src/pos.rs new file mode 100644 index 0000000..8139047 --- /dev/null +++ b/src/pos.rs @@ -0,0 +1,129 @@ +use core::{ops, str}; + +/// Finds the position of the needle in the haystack at compiletime. +/// +/// Produces a const-eval error if the needle is not a substring of the haystack. +/// +/// # Examples +/// +/// ``` +/// assert_eq!(obfstr::position!("haystack", "st"), 3..5); +///# assert_eq!(obfstr::position!("haystack", "haystack"), 0..8); +///# assert_eq!(obfstr::position!("haystack", "ck"), 6..8); +/// ``` +/// +/// Use this API when pooling strings in a single obfstr: +/// +/// ``` +/// const POOL: &str = concat!("Foo", "Bar", "Baz"); +/// +/// obfstr::obfstr! { let pool = POOL; } +/// +/// // Later, read strings from the pool +/// let foo = &pool[obfstr::position!(POOL, "Foo")]; +/// let bar = &pool[obfstr::position!(POOL, "Bar")]; +/// let baz = &pool[obfstr::position!(POOL, "Baz")]; +/// ``` +#[macro_export] +macro_rules! position { + ($haystack:expr, $needle:expr) => {{ const POSITION: ::core::ops::Range = $crate::position($haystack, $needle); POSITION }}; +} + +/// Finds the position of the needle in the haystack at compiletime. +/// +/// Produces a const-eval error if the needle is not a substring of the haystack. +/// +/// ``` +/// const POSITION: std::ops::Range = obfstr::position("haystack", "st"); +/// assert_eq!(POSITION, 3..5); +/// ``` +#[inline(always)] +pub const fn position(haystack: &str, needle: &str) -> ops::Range { + let start = search(haystack, needle); + // Panic if substring not found + if start < 0 { + let _ = haystack.as_bytes()[haystack.len()]; + } + let start = start as usize; + start..start + needle.len() +} + +const fn search(haystack: &str, needle: &str) -> isize { + // Short-circuit empty needles + if needle.len() == 0 { + return 0; + } + + let haystack = haystack.as_bytes(); + let needle = needle.as_bytes(); + + // Avoid overflow checks later + if needle.len() <= haystack.len() { + // Special case for needle length of 1 + if needle.len() == 1 { + let needle = needle[0]; + let mut offset = 0; + while offset <= haystack.len() { + if haystack[offset] == needle { + return offset as isize; + } + offset += 1; + } + } + // Full blown quicksearch + else { + // assumed: + // needle.len() >= 2 + // needle.len() <= haystack.len() + + // Initialize the jump table + let mut jumps = [max(needle.len()); 256]; + let tail = needle.len() - 1; + let mut i = 0; + while i < tail { + jumps[needle[i] as usize] = max(tail - i); + i += 1; + } + // Find the needle + let sentinel = needle[tail]; + let mut offset = 0; + while offset < haystack.len() - tail { + let chr = haystack[offset + tail]; + if chr == sentinel && check(haystack, needle, offset) { + return offset as isize; + } + offset += jumps[chr as usize] as usize; + } + } + } + return -1; +} + + +#[inline(always)] +const fn check(haystack: &[u8], needle: &[u8], offset: usize) -> bool { + let mut i = 0; + while i < needle.len() { + if haystack[offset + i] != needle[i] { + return false; + } + i += 1; + } + return true; +} +#[inline(always)] +const fn max(a: usize) -> u8 { + if a > 255 { 255 } else { a as u8 } +} + +#[test] +fn test_position() { + assert_eq!(position("ABCBC", "CBC"), 2..5); + assert_eq!(position("ABCBC", "ABCBC"), 0..5); +} + +#[test] +#[should_panic] +fn test_position_needle_longer_than_haystack() { + let _ = position("haystack", "needleneedleneedle"); +}