use crate::{
database::Database,
error::HyperscanError,
flags::{CpuFeatures, ScanFlags, TuneFamily},
hs,
matchers::{
contiguous_slice::{match_slice_ref, Match, SliceMatcher},
vectored_slice::{match_slice_vectored_ref, VectoredMatch, VectoredSliceMatcher},
ByteSlice, MatchResult, VectoredByteSlices,
},
};
use async_stream::try_stream;
use futures_core::stream::Stream;
use once_cell::sync::Lazy;
use tokio::task;
use std::{
mem, ops,
pin::Pin,
ptr::{self, NonNull},
};
#[derive(Debug, Copy, Clone)]
#[repr(transparent)]
pub struct Platform(hs::hs_platform_info);
static CACHED_PLATFORM: Lazy<Platform> = Lazy::new(|| Platform::populate().unwrap());
impl Platform {
#[inline]
pub fn tune(&self) -> TuneFamily { TuneFamily::from_native(self.0.tune) }
#[inline]
pub fn set_tune(&mut self, tune: TuneFamily) { self.0.tune = tune.into_native(); }
#[inline]
pub fn cpu_features(&self) -> CpuFeatures { CpuFeatures::from_native(self.0.cpu_features) }
#[inline]
pub fn set_cpu_features(&mut self, cpu_features: CpuFeatures) {
self.0.cpu_features = cpu_features.into_native();
}
#[inline]
fn populate() -> Result<Self, HyperscanError> {
let mut s = mem::MaybeUninit::<hs::hs_platform_info>::uninit();
HyperscanError::from_native(unsafe { hs::hs_populate_platform(s.as_mut_ptr()) })?;
Ok(unsafe { Self(s.assume_init()) })
}
#[inline]
pub fn get() -> &'static Self { &CACHED_PLATFORM }
#[inline]
pub(crate) fn as_ref_native(&self) -> &hs::hs_platform_info { &self.0 }
}
#[derive(Debug)]
#[repr(transparent)]
pub struct Scratch(Option<NonNull<hs::hs_scratch>>);
impl Scratch {
#[inline]
pub const fn new() -> Self { Self(None) }
pub fn setup_for_db(&mut self, db: &Database) -> Result<(), HyperscanError> {
let mut scratch_ptr = self.0.map(|p| p.as_ptr()).unwrap_or(ptr::null_mut());
HyperscanError::from_native(unsafe {
hs::hs_alloc_scratch(db.as_ref_native(), &mut scratch_ptr)
})?;
self.0 = NonNull::new(scratch_ptr);
Ok(())
}
#[inline]
pub(crate) fn as_ref_native(&self) -> Option<&hs::hs_scratch> {
self.0.map(|p| unsafe { p.as_ref() })
}
#[inline]
pub(crate) fn as_mut_native(&mut self) -> Option<&mut hs::hs_scratch> {
self.0.map(|mut p| unsafe { p.as_mut() })
}
pub fn get_size(&self) -> Result<usize, HyperscanError> {
match self.as_ref_native() {
None => Ok(0),
Some(p) => {
let mut n = mem::MaybeUninit::<usize>::uninit();
HyperscanError::from_native(unsafe { hs::hs_scratch_size(p, n.as_mut_ptr()) })?;
Ok(unsafe { n.assume_init() })
},
}
}
fn into_slice_ctx(m: SliceMatcher) -> usize {
let ctx: *mut SliceMatcher = Box::into_raw(Box::new(m));
ctx as usize
}
fn from_slice_ctx<'data, 'code>(ctx: usize) -> Pin<Box<SliceMatcher<'data, 'code>>> {
Box::into_pin(unsafe { Box::from_raw(ctx as *mut SliceMatcher) })
}
fn into_vectored_ctx(m: VectoredSliceMatcher) -> usize {
let ctx: *mut VectoredSliceMatcher = Box::into_raw(Box::new(m));
ctx as usize
}
fn from_vectored_ctx<'data, 'code>(ctx: usize) -> Pin<Box<VectoredSliceMatcher<'data, 'code>>> {
Box::into_pin(unsafe { Box::from_raw(ctx as *mut VectoredSliceMatcher) })
}
fn into_db(db: &Database) -> usize {
let db: *const Database = db;
db as usize
}
fn from_db<'a>(db: usize) -> &'a Database { unsafe { &*(db as *const Database) } }
fn into_scratch(scratch: &mut Scratch) -> usize {
let scratch: *mut Scratch = scratch;
scratch as usize
}
fn from_scratch<'a>(scratch: usize) -> &'a mut Scratch {
unsafe { &mut *(scratch as *mut Scratch) }
}
pub fn scan<'data, F: FnMut(&Match<'data>) -> MatchResult+'data>(
&mut self,
db: &Database,
data: ByteSlice<'data>,
flags: ScanFlags,
mut f: F,
) -> impl Stream<Item=Result<Match<'data>, HyperscanError>>+'data {
let (matcher, mut matches_rx) = SliceMatcher::new(data, &mut f);
let ctx = Self::into_slice_ctx(matcher);
let scratch = Self::into_scratch(self);
let db = Self::into_db(db);
let scan_task = task::spawn_blocking(move || {
let scratch: &mut Self = Self::from_scratch(scratch);
let db: &Database = Self::from_db(db);
let mut matcher: Pin<Box<SliceMatcher>> = Self::from_slice_ctx(ctx);
let parent_slice = matcher.parent_slice();
HyperscanError::from_native(unsafe {
hs::hs_scan(
db.as_ref_native(),
parent_slice.as_ptr(),
parent_slice.native_len(),
flags.into_native(),
scratch.as_mut_native().unwrap(),
Some(match_slice_ref),
mem::transmute(matcher.as_mut().get_mut()),
)
})
});
try_stream! {
while let Some(m) = matches_rx.recv().await {
yield m;
}
scan_task.await.unwrap()?;
}
}
pub fn scan_vectored<'data, F: FnMut(&VectoredMatch<'data>) -> MatchResult+'data>(
&mut self,
db: &Database,
data: VectoredByteSlices<'data>,
flags: ScanFlags,
mut f: F,
) -> impl Stream<Item=Result<VectoredMatch<'data>, HyperscanError>>+'data {
static_assertions::assert_eq_size!([u8; 4], u32);
static_assertions::assert_eq_size!(&u8, *const u8);
static_assertions::assert_eq_size!(&[u8; 4], *const u8);
static_assertions::const_assert!(mem::size_of::<&[u8]>() > mem::size_of::<*const u8>());
let (matcher, mut matches_rx) = VectoredSliceMatcher::new(data, &mut f);
let ctx = Self::into_vectored_ctx(matcher);
let scratch = Self::into_scratch(self);
let db = Self::into_db(db);
let scan_task = task::spawn_blocking(move || {
let scratch: &mut Self = Self::from_scratch(scratch);
let db: &Database = Self::from_db(db);
let mut matcher: Pin<Box<VectoredSliceMatcher>> = Self::from_vectored_ctx(ctx);
let parent_slices = matcher.parent_slices();
let (data_pointers, lengths) = parent_slices.pointers_and_lengths();
HyperscanError::from_native(unsafe {
hs::hs_scan_vector(
db.as_ref_native(),
data_pointers.as_ptr(),
lengths.as_ptr(),
parent_slices.native_len(),
flags.into_native(),
scratch.as_mut_native().unwrap(),
Some(match_slice_vectored_ref),
mem::transmute(matcher.as_mut().get_mut()),
)
})
});
try_stream! {
while let Some(m) = matches_rx.recv().await {
yield m;
}
scan_task.await.unwrap()?;
}
}
pub fn try_clone(&self) -> Result<Self, HyperscanError> {
match self.as_ref_native() {
None => Ok(Self::new()),
Some(p) => {
let mut scratch_ptr = ptr::null_mut();
HyperscanError::from_native(unsafe { hs::hs_clone_scratch(p, &mut scratch_ptr) })?;
Ok(Self(NonNull::new(scratch_ptr)))
},
}
}
pub unsafe fn try_drop(&mut self) -> Result<(), HyperscanError> {
if let Some(p) = self.as_mut_native() {
HyperscanError::from_native(unsafe { hs::hs_free_scratch(p) })?;
}
Ok(())
}
}
impl Clone for Scratch {
fn clone(&self) -> Self { self.try_clone().unwrap() }
}
impl ops::Drop for Scratch {
fn drop(&mut self) {
unsafe {
self.try_drop().unwrap();
}
}
}
unsafe impl Send for Scratch {}
unsafe impl Sync for Scratch {}
#[cfg(test)]
mod test {
use crate::{
expression::Expression,
flags::{Flags, Mode, ScanFlags},
matchers::MatchResult,
};
use futures_util::TryStreamExt;
use std::{mem::ManuallyDrop, sync::Arc};
#[tokio::test]
async fn try_clone_still_valid() -> Result<(), eyre::Report> {
let a_expr: Expression = "asdf$".parse()?;
let db = a_expr.compile(Flags::UTF8, Mode::BLOCK)?;
let mut scratch = ManuallyDrop::new(db.allocate_scratch()?);
let mut s2 = ManuallyDrop::new(scratch.try_clone()?);
unsafe {
scratch.try_drop()?;
}
let matches: Vec<&str> = s2
.scan(&db, "asdf".into(), ScanFlags::default(), |_| {
MatchResult::Continue
})
.and_then(|m| async move { Ok(m.source.as_str()) })
.try_collect()
.await?;
assert_eq!(&matches, &["asdf"]);
unsafe {
s2.try_drop()?;
}
Ok(())
}
#[tokio::test]
async fn make_mut() -> Result<(), eyre::Report> {
let a_expr: Expression = "asdf$".parse()?;
let db = a_expr.compile(Flags::UTF8, Mode::BLOCK)?;
let scratch = Arc::new(db.allocate_scratch()?);
let mut s2 = Arc::clone(&scratch);
let matches: Vec<&str> = Arc::make_mut(&mut s2)
.scan(&db, "asdf".into(), ScanFlags::default(), |_| {
MatchResult::Continue
})
.and_then(|m| async move { Ok(m.source.as_str()) })
.try_collect()
.await?;
assert_eq!(&matches, &["asdf"]);
Ok(())
}
}