use crate::{
database::Database,
error::HyperscanRuntimeError,
hs,
matchers::{
contiguous_slice::{match_slice, Match, SliceMatcher},
stream::match_slice_stream,
vectored_slice::{match_slice_vectored, VectoredMatch, VectoredMatcher},
ByteSlice, MatchResult, VectoredByteSlices,
},
stream::StreamSink,
};
#[cfg(feature = "async")]
use crate::{
error::ScanError, matchers::stream::scan::scan_slice_stream, stream::channel::StreamSinkChannel,
};
#[cfg(feature = "async")]
use {
async_stream::try_stream,
futures_core::stream::Stream,
tokio::{sync::mpsc, task},
};
use std::{
mem, ops,
ptr::{self, NonNull},
};
pub type NativeScratch = hs::hs_scratch;
#[derive(Debug)]
#[repr(transparent)]
pub struct Scratch(Option<NonNull<NativeScratch>>);
impl Scratch {
pub const fn new() -> Self { Self(None) }
pub fn setup_for_db(&mut self, db: &Database) -> Result<(), HyperscanRuntimeError> {
let mut scratch_ptr = self.0.map(|p| p.as_ptr()).unwrap_or(ptr::null_mut());
HyperscanRuntimeError::from_native(unsafe {
hs::hs_alloc_scratch(db.as_ref_native(), &mut scratch_ptr)
})?;
self.0 = NonNull::new(scratch_ptr);
Ok(())
}
pub fn as_ref_native(&self) -> Option<&NativeScratch> { self.0.map(|p| unsafe { p.as_ref() }) }
pub fn as_mut_native(&mut self) -> Option<&mut NativeScratch> {
self.0.map(|mut p| unsafe { p.as_mut() })
}
#[cfg(feature = "async")]
fn into_db(db: &Database) -> usize {
let db: *const Database = db;
db as usize
}
#[cfg(feature = "async")]
fn from_db<'a>(db: usize) -> &'a Database { unsafe { &*(db as *const Database) } }
#[cfg(feature = "async")]
fn into_scratch(scratch: &mut Scratch) -> usize {
let scratch: *mut Scratch = scratch;
scratch as usize
}
#[cfg(feature = "async")]
fn from_scratch<'a>(scratch: usize) -> &'a mut Scratch {
unsafe { &mut *(scratch as *mut Scratch) }
}
pub fn scan_sync<'data>(
&mut self,
db: &Database,
data: ByteSlice<'data>,
mut f: impl FnMut(Match<'data>) -> MatchResult,
) -> Result<(), HyperscanRuntimeError> {
let mut matcher = SliceMatcher::new(data, &mut f);
HyperscanRuntimeError::from_native(unsafe {
hs::hs_scan(
db.as_ref_native(),
matcher.parent_slice().as_ptr(),
matcher.parent_slice().native_len(),
0,
self.as_mut_native().unwrap(),
Some(match_slice),
mem::transmute(&mut matcher),
)
})
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn scan_channel<'data>(
&mut self,
db: &Database,
data: ByteSlice<'data>,
mut f: impl FnMut(&Match<'data>) -> MatchResult+Send+Sync,
) -> impl Stream<Item=Result<Match<'data>, ScanError>> {
let scratch = Self::into_scratch(self);
let db = Self::into_db(db);
let data: ByteSlice<'static> = unsafe { mem::transmute(data) };
let f: &mut (dyn FnMut(&Match<'data>) -> MatchResult+Send+Sync) = &mut f;
let (matches_tx, mut matches_rx) = mpsc::unbounded_channel();
let f: &'static mut (dyn FnMut(&Match<'static>) -> MatchResult+Send+Sync) =
unsafe { mem::transmute(f) };
let matches_tx: mpsc::UnboundedSender<Match<'static>> = unsafe { mem::transmute(matches_tx) };
let scan_task = task::spawn_blocking(move || {
let scratch: &mut Self = Self::from_scratch(scratch);
let db: &Database = Self::from_db(db);
scratch.scan_sync(db, data, |m| {
let result = f(&m);
matches_tx.send(m).unwrap();
result
})
});
try_stream! {
while let Some(m) = matches_rx.recv().await {
yield m;
}
scan_task.await??;
}
}
pub fn scan_sync_vectored<'data>(
&mut self,
db: &Database,
data: VectoredByteSlices<'data>,
mut f: impl FnMut(VectoredMatch<'data>) -> MatchResult,
) -> Result<(), HyperscanRuntimeError> {
let mut matcher = VectoredMatcher::new(data, &mut f);
let (data_pointers, lengths) = matcher.parent_slices().pointers_and_lengths();
HyperscanRuntimeError::from_native(unsafe {
hs::hs_scan_vector(
db.as_ref_native(),
data_pointers.as_ptr(),
lengths.as_ptr(),
matcher.parent_slices().native_len(),
0,
self.as_mut_native().unwrap(),
Some(match_slice_vectored),
mem::transmute(&mut matcher),
)
})
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn scan_channel_vectored<'data>(
&mut self,
db: &Database,
data: VectoredByteSlices<'data>,
mut f: impl FnMut(&VectoredMatch<'data>) -> MatchResult+Send+Sync,
) -> impl Stream<Item=Result<VectoredMatch<'data>, ScanError>> {
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 scratch = Self::into_scratch(self);
let db = Self::into_db(db);
let data: VectoredByteSlices<'static> = unsafe { mem::transmute(data) };
let f: &mut (dyn FnMut(&VectoredMatch<'data>) -> MatchResult+Send+Sync) = &mut f;
let (matches_tx, mut matches_rx) = mpsc::unbounded_channel();
let f: &'static mut (dyn FnMut(&VectoredMatch<'static>) -> MatchResult+Send+Sync) =
unsafe { mem::transmute(f) };
let matches_tx: mpsc::UnboundedSender<VectoredMatch<'static>> =
unsafe { mem::transmute(matches_tx) };
let scan_task = task::spawn_blocking(move || {
let scratch: &mut Self = Self::from_scratch(scratch);
let db: &Database = Self::from_db(db);
scratch.scan_sync_vectored(db, data, |m| {
let result = f(&m);
matches_tx.send(m).unwrap();
result
})
});
try_stream! {
while let Some(m) = matches_rx.recv().await {
yield m;
}
scan_task.await??;
}
}
pub fn scan_sync_stream<'data>(
&mut self,
data: ByteSlice<'data>,
sink: &mut StreamSink,
) -> Result<(), HyperscanRuntimeError> {
HyperscanRuntimeError::from_native(unsafe {
hs::hs_scan_stream(
sink.live.as_mut_native(),
data.as_ptr(),
data.native_len(),
0,
self.as_mut_native().unwrap(),
Some(match_slice_stream),
mem::transmute(&mut sink.matcher),
)
})
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn scan_stream<'data>(
&mut self,
data: ByteSlice<'data>,
sink: &mut StreamSinkChannel,
) -> Result<(), ScanError> {
let s: &'static mut Self = unsafe { mem::transmute(self) };
let data: ByteSlice<'static> = unsafe { mem::transmute(data) };
let sink: &'static mut StreamSinkChannel = unsafe { mem::transmute(sink) };
Ok(
task::spawn_blocking(move || {
HyperscanRuntimeError::from_native(unsafe {
hs::hs_scan_stream(
sink.live.as_mut_native(),
data.as_ptr(),
data.native_len(),
0,
s.as_mut_native().unwrap(),
Some(scan_slice_stream),
mem::transmute(&mut sink.matcher),
)
})
})
.await??,
)
}
pub fn flush_eod_sync(&mut self, sink: &mut StreamSink) -> Result<(), HyperscanRuntimeError> {
HyperscanRuntimeError::from_native(unsafe {
hs::hs_direct_flush_stream(
sink.live.as_mut_native(),
self.as_mut_native().unwrap(),
Some(match_slice_stream),
mem::transmute(&mut sink.matcher),
)
})
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn flush_eod(&mut self, sink: &mut StreamSinkChannel) -> Result<(), ScanError> {
let s: &'static mut Self = unsafe { mem::transmute(self) };
let sink: &'static mut StreamSinkChannel = unsafe { mem::transmute(sink) };
Ok(
task::spawn_blocking(move || {
HyperscanRuntimeError::from_native(unsafe {
hs::hs_direct_flush_stream(
sink.live.as_mut_native(),
s.as_mut_native().unwrap(),
Some(scan_slice_stream),
mem::transmute(&mut sink.matcher),
)
})
})
.await??,
)
}
pub fn get_size(&self) -> Result<usize, HyperscanRuntimeError> {
match self.as_ref_native() {
None => Ok(0),
Some(p) => {
let mut n: usize = 0;
HyperscanRuntimeError::from_native(unsafe { hs::hs_scratch_size(p, &mut n) })?;
Ok(n)
},
}
}
pub fn try_clone(&self) -> Result<Self, HyperscanRuntimeError> {
match self.as_ref_native() {
None => Ok(Self::new()),
Some(p) => {
let mut scratch_ptr = ptr::null_mut();
HyperscanRuntimeError::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<(), HyperscanRuntimeError> {
if let Some(p) = self.as_mut_native() {
HyperscanRuntimeError::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(all(test, feature = "compiler", feature = "async"))]
mod test {
use crate::{
expression::Expression,
flags::{Flags, Mode},
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_channel(&db, "asdf".into(), |_| MatchResult::Continue)
.and_then(|m| async move { Ok(unsafe { 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_channel(&db, "asdf".into(), |_| MatchResult::Continue)
.and_then(|m| async move { Ok(unsafe { m.source.as_str() }) })
.try_collect()
.await?;
assert_eq!(&matches, &["asdf"]);
Ok(())
}
}
#[cfg(feature = "chimera")]
#[cfg_attr(docsrs, doc(cfg(feature = "chimera")))]
pub mod chimera {
use super::*;
use crate::{database::chimera::ChimeraDb, error::chimera::*, matchers::chimera::*};
#[cfg(feature = "async")]
use async_stream::stream;
pub type NativeChimeraScratch = hs::ch_scratch;
#[derive(Debug)]
#[repr(transparent)]
pub struct ChimeraScratch(Option<NonNull<NativeChimeraScratch>>);
impl ChimeraScratch {
pub const fn new() -> Self { Self(None) }
pub fn setup_for_db(&mut self, db: &ChimeraDb) -> Result<(), ChimeraRuntimeError> {
let mut scratch_ptr = self.0.map(|p| p.as_ptr()).unwrap_or(ptr::null_mut());
ChimeraRuntimeError::from_native(unsafe {
hs::ch_alloc_scratch(db.as_ref_native(), &mut scratch_ptr)
})?;
self.0 = NonNull::new(scratch_ptr);
Ok(())
}
pub fn as_ref_native(&self) -> Option<&NativeChimeraScratch> {
self.0.map(|p| unsafe { p.as_ref() })
}
pub fn as_mut_native(&mut self) -> Option<&mut NativeChimeraScratch> {
self.0.map(|mut p| unsafe { p.as_mut() })
}
fn into_db(db: &ChimeraDb) -> usize {
let db: *const ChimeraDb = db;
db as usize
}
fn from_db<'a>(db: usize) -> &'a ChimeraDb { unsafe { &*(db as *const ChimeraDb) } }
fn into_scratch(scratch: &mut ChimeraScratch) -> usize {
let scratch: *mut ChimeraScratch = scratch;
scratch as usize
}
fn from_scratch<'a>(scratch: usize) -> &'a mut ChimeraScratch {
unsafe { &mut *(scratch as *mut ChimeraScratch) }
}
pub fn scan_sync<'data>(
&mut self,
db: &ChimeraDb,
data: ByteSlice<'data>,
mut m: impl FnMut(ChimeraMatch<'data>) -> ChimeraMatchResult,
mut e: impl FnMut(ChimeraMatchError) -> ChimeraMatchResult,
) -> Result<(), ChimeraRuntimeError> {
let mut matcher = ChimeraSyncSliceMatcher::new(data, &mut m, &mut e);
ChimeraRuntimeError::from_native(unsafe {
hs::ch_scan(
db.as_ref_native(),
matcher.parent_slice().as_ptr(),
matcher.parent_slice().native_len(),
0,
self.as_mut_native().unwrap(),
Some(match_chimera_slice),
Some(error_callback_chimera),
mem::transmute(&mut matcher),
)
})
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn scan_channel<'data>(
&mut self,
db: &ChimeraDb,
data: ByteSlice<'data>,
mut m: impl FnMut(&ChimeraMatch<'data>) -> ChimeraMatchResult+Send+Sync,
mut e: impl FnMut(&ChimeraMatchError) -> ChimeraMatchResult+Send+Sync,
) -> impl Stream<Item=Result<ChimeraMatch<'data>, ChimeraScanError>> {
let scratch = Self::into_scratch(self);
let db = Self::into_db(db);
let data: ByteSlice<'static> = unsafe { mem::transmute(data) };
let m: &mut (dyn FnMut(&ChimeraMatch<'data>) -> ChimeraMatchResult+Send+Sync) = &mut m;
let e: &mut (dyn FnMut(&ChimeraMatchError) -> ChimeraMatchResult+Send+Sync) = &mut e;
let (matches_tx, mut matches_rx) = mpsc::unbounded_channel();
let (errors_tx, mut errors_rx) = mpsc::unbounded_channel();
let m: &'static mut (dyn FnMut(&ChimeraMatch<'static>) -> ChimeraMatchResult+Send+Sync) =
unsafe { mem::transmute(m) };
let e: &'static mut (dyn FnMut(&ChimeraMatchError) -> ChimeraMatchResult+Send+Sync) =
unsafe { mem::transmute(e) };
let matches_tx: mpsc::UnboundedSender<ChimeraMatch<'static>> =
unsafe { mem::transmute(matches_tx) };
let scan_task = task::spawn_blocking(move || {
let scratch: &mut Self = Self::from_scratch(scratch);
let db: &ChimeraDb = Self::from_db(db);
scratch.scan_sync(
db,
data,
|cm| {
let result = m(&cm);
matches_tx.send(cm).unwrap();
result
},
|ce| {
let result = e(&ce);
errors_tx.send(ce).unwrap();
result
},
)
});
stream! {
while tokio::select! {
biased;
Some(e) = errors_rx.recv() => { yield Err(e.into()); true },
Some(m) = matches_rx.recv() => { yield Ok(m); true },
else => false,
} {}
match scan_task.await {
Err(e) => {
yield Err(e.into());
},
Ok(Err(e)) => {
yield Err(e.into());
},
Ok(Ok(())) => (),
}
}
}
pub fn get_size(&self) -> Result<usize, ChimeraRuntimeError> {
match self.as_ref_native() {
None => Ok(0),
Some(p) => {
let mut n: usize = 0;
ChimeraRuntimeError::from_native(unsafe { hs::ch_scratch_size(p, &mut n) })?;
Ok(n)
},
}
}
pub fn try_clone(&self) -> Result<Self, ChimeraRuntimeError> {
match self.as_ref_native() {
None => Ok(Self::new()),
Some(p) => {
let mut scratch_ptr = ptr::null_mut();
ChimeraRuntimeError::from_native(unsafe { hs::ch_clone_scratch(p, &mut scratch_ptr) })?;
Ok(Self(NonNull::new(scratch_ptr)))
},
}
}
pub unsafe fn try_drop(&mut self) -> Result<(), ChimeraRuntimeError> {
if let Some(p) = self.as_mut_native() {
ChimeraRuntimeError::from_native(unsafe { hs::ch_free_scratch(p) })?;
}
Ok(())
}
}
impl Clone for ChimeraScratch {
fn clone(&self) -> Self { self.try_clone().unwrap() }
}
impl ops::Drop for ChimeraScratch {
fn drop(&mut self) {
unsafe {
self.try_drop().unwrap();
}
}
}
}