1 /*
2  * Copyright (c) 2015-2019, Intel Corporation
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  *  * Redistributions of source code must retain the above copyright notice,
8  *    this list of conditions and the following disclaimer.
9  *  * Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *  * Neither the name of Intel Corporation nor the names of its contributors
13  *    may be used to endorse or promote products derived from this software
14  *    without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  * POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 /** \file
30  * \brief Runtime functions.
31  */
32 
33 #include <stdlib.h>
34 #include <string.h>
35 
36 #include "allocator.h"
37 #include "hs_compile.h" /* for HS_MODE_* flags */
38 #include "hs_runtime.h"
39 #include "hs_internal.h"
40 #include "hwlm/hwlm.h"
41 #include "nfa/mcclellan.h"
42 #include "nfa/nfa_api.h"
43 #include "nfa/nfa_api_util.h"
44 #include "nfa/nfa_internal.h"
45 #include "nfa/nfa_rev_api.h"
46 #include "nfa/sheng.h"
47 #include "smallwrite/smallwrite_internal.h"
48 #include "rose/rose.h"
49 #include "rose/runtime.h"
50 #include "database.h"
51 #include "report.h"
52 #include "scratch.h"
53 #include "som/som_runtime.h"
54 #include "som/som_stream.h"
55 #include "state.h"
56 #include "stream_compress.h"
57 #include "ue2common.h"
58 #include "util/exhaust.h"
59 #include "util/multibit.h"
60 
61 static really_inline
prefetch_data(const char * data,unsigned length)62 void prefetch_data(const char *data, unsigned length) {
63     __builtin_prefetch(data);
64     __builtin_prefetch(data + length/2);
65     __builtin_prefetch(data + length - 24);
66 }
67 
68 /** dummy event handler for use when user does not provide one */
69 static
null_onEvent(UNUSED unsigned id,UNUSED unsigned long long from,UNUSED unsigned long long to,UNUSED unsigned flags,UNUSED void * ctxt)70 int HS_CDECL null_onEvent(UNUSED unsigned id, UNUSED unsigned long long from,
71                           UNUSED unsigned long long to, UNUSED unsigned flags,
72                           UNUSED void *ctxt) {
73     return 0;
74 }
75 
76 static really_inline
getHistoryAmount(const struct RoseEngine * t,u64a offset)77 u32 getHistoryAmount(const struct RoseEngine *t, u64a offset) {
78     return MIN(t->historyRequired, offset);
79 }
80 
81 static really_inline
getHistory(char * state,const struct RoseEngine * t,u64a offset)82 u8 *getHistory(char *state, const struct RoseEngine *t, u64a offset) {
83     return (u8 *)state + t->stateOffsets.history + t->historyRequired
84         - MIN(t->historyRequired, offset);
85 }
86 
87 /** \brief Sanity checks for scratch space.
88  *
89  * Although more at home in scratch.c, it is located here to be closer to its
90  * callers.
91  */
92 static really_inline
validScratch(const struct RoseEngine * t,const struct hs_scratch * s)93 char validScratch(const struct RoseEngine *t, const struct hs_scratch *s) {
94     if (!ISALIGNED_CL(s)) {
95         DEBUG_PRINTF("bad alignment %p\n", s);
96         return 0;
97     }
98 
99     if (s->magic != SCRATCH_MAGIC) {
100         DEBUG_PRINTF("bad magic 0x%x\n", s->magic);
101         return 0;
102     }
103 
104     if (t->mode == HS_MODE_BLOCK && t->stateOffsets.end > s->bStateSize) {
105         DEBUG_PRINTF("bad state size\n");
106         return 0;
107     }
108 
109     if (t->queueCount > s->queueCount) {
110         DEBUG_PRINTF("bad queue count\n");
111         return 0;
112     }
113 
114     /* TODO: add quick rose sanity checks */
115 
116     return 1;
117 }
118 
119 static really_inline
populateCoreInfo(struct hs_scratch * s,const struct RoseEngine * rose,char * state,match_event_handler onEvent,void * userCtx,const char * data,size_t length,const u8 * history,size_t hlen,u64a offset,u8 status,UNUSED unsigned int flags)120 void populateCoreInfo(struct hs_scratch *s, const struct RoseEngine *rose,
121                       char *state, match_event_handler onEvent, void *userCtx,
122                       const char *data, size_t length, const u8 *history,
123                       size_t hlen, u64a offset, u8 status,
124                       UNUSED unsigned int flags) {
125     assert(rose);
126     s->core_info.userContext = userCtx;
127     s->core_info.userCallback = onEvent ? onEvent : null_onEvent;
128     s->core_info.rose = rose;
129     s->core_info.state = state; /* required for chained queues + evec */
130 
131     s->core_info.exhaustionVector = state + rose->stateOffsets.exhausted;
132     s->core_info.status = status;
133     s->core_info.buf = (const u8 *)data;
134     s->core_info.len = length;
135     s->core_info.hbuf = history;
136     s->core_info.hlen = hlen;
137     s->core_info.buf_offset = offset;
138 
139     /* and some stuff not actually in core info */
140     s->som_set_now_offset = ~0ULL;
141     s->deduper.current_report_offset = ~0ULL;
142     s->deduper.som_log_dirty = 1; /* som logs have not been cleared */
143     s->fdr_conf = NULL;
144 
145     // Rose program execution (used for some report paths) depends on these
146     // values being initialised.
147     s->tctxt.lastMatchOffset = 0;
148     s->tctxt.minMatchOffset = offset;
149     s->tctxt.minNonMpvMatchOffset = offset;
150 }
151 
152 #define STATUS_VALID_BITS                                                      \
153     (STATUS_TERMINATED | STATUS_EXHAUSTED | STATUS_DELAY_DIRTY | STATUS_ERROR)
154 
155 /** \brief Retrieve status bitmask from stream state. */
156 static really_inline
getStreamStatus(const char * state)157 u8 getStreamStatus(const char *state) {
158     u8 status = *(const u8 *)(state + ROSE_STATE_OFFSET_STATUS_FLAGS);
159     assert((status & ~STATUS_VALID_BITS) == 0);
160     return status;
161 }
162 
163 /** \brief Store status bitmask to stream state. */
164 static really_inline
setStreamStatus(char * state,u8 status)165 void setStreamStatus(char *state, u8 status) {
166     assert((status & ~STATUS_VALID_BITS) == 0);
167     *(u8 *)(state + ROSE_STATE_OFFSET_STATUS_FLAGS) = status;
168 }
169 
170 /** \brief Initialise SOM state. Used in both block and streaming mode. */
171 static really_inline
initSomState(const struct RoseEngine * rose,char * state)172 void initSomState(const struct RoseEngine *rose, char *state) {
173     assert(rose && state);
174     const u32 somCount = rose->somLocationCount;
175     mmbit_clear((u8 *)state + rose->stateOffsets.somValid, somCount);
176     mmbit_clear((u8 *)state + rose->stateOffsets.somWritable, somCount);
177 }
178 
179 static really_inline
rawBlockExec(const struct RoseEngine * rose,struct hs_scratch * scratch)180 void rawBlockExec(const struct RoseEngine *rose, struct hs_scratch *scratch) {
181     assert(rose);
182     assert(scratch);
183 
184     initSomState(rose, scratch->core_info.state);
185 
186     DEBUG_PRINTF("blockmode scan len=%zu\n", scratch->core_info.len);
187 
188     roseBlockExec(rose, scratch);
189 }
190 
191 static really_inline
pureLiteralInitScratch(struct hs_scratch * scratch,u64a offset)192 void pureLiteralInitScratch(struct hs_scratch *scratch, u64a offset) {
193     // Some init has already been done.
194     assert(offset == scratch->core_info.buf_offset);
195 
196     scratch->tctxt.lit_offset_adjust = offset + 1;
197     scratch->tctxt.lastEndOffset = offset;
198     scratch->tctxt.delayLastEndOffset = offset;
199     scratch->tctxt.filledDelayedSlots = 0;
200     scratch->al_log_sum = 0;
201 }
202 
203 static really_inline
pureLiteralBlockExec(const struct RoseEngine * rose,struct hs_scratch * scratch)204 void pureLiteralBlockExec(const struct RoseEngine *rose,
205                           struct hs_scratch *scratch) {
206     assert(rose);
207     assert(scratch);
208 
209     const struct HWLM *ftable = getFLiteralMatcher(rose);
210     initSomState(rose, scratch->core_info.state);
211     const u8 *buffer = scratch->core_info.buf;
212     size_t length = scratch->core_info.len;
213     DEBUG_PRINTF("rose engine %d\n", rose->runtimeImpl);
214 
215     pureLiteralInitScratch(scratch, 0);
216     scratch->tctxt.groups = rose->initialGroups;
217 
218     hwlmExec(ftable, buffer, length, 0, roseCallback, scratch,
219              rose->initialGroups & rose->floating_group_mask);
220 }
221 
222 static really_inline
initOutfixQueue(struct mq * q,u32 qi,const struct RoseEngine * t,struct hs_scratch * scratch)223 void initOutfixQueue(struct mq *q, u32 qi, const struct RoseEngine *t,
224                      struct hs_scratch *scratch) {
225     const struct NfaInfo *info = getNfaInfoByQueue(t, qi);
226     q->nfa = getNfaByInfo(t, info);
227     q->end = 0;
228     q->cur = 0;
229     q->state = scratch->fullState + info->fullStateOffset;
230     q->streamState = (char *)scratch->core_info.state + info->stateOffset;
231     q->offset = scratch->core_info.buf_offset;
232     q->buffer = scratch->core_info.buf;
233     q->length = scratch->core_info.len;
234     q->history = scratch->core_info.hbuf;
235     q->hlength = scratch->core_info.hlen;
236     q->cb = roseReportAdaptor;
237     q->context = scratch;
238     q->report_current = 0;
239 
240     DEBUG_PRINTF("qi=%u, offset=%llu, fullState=%u, streamState=%u, "
241                  "state=%u\n", qi, q->offset, info->fullStateOffset,
242                  info->stateOffset, *(u32 *)q->state);
243 }
244 
245 static never_inline
soleOutfixBlockExec(const struct RoseEngine * t,struct hs_scratch * scratch)246 void soleOutfixBlockExec(const struct RoseEngine *t,
247                          struct hs_scratch *scratch) {
248     assert(t);
249     assert(scratch);
250 
251     initSomState(t, scratch->core_info.state);
252     assert(t->outfixEndQueue == 1);
253     assert(!t->amatcherOffset);
254     assert(!t->ematcherOffset);
255     assert(!t->fmatcherOffset);
256 
257     const struct NFA *nfa = getNfaByQueue(t, 0);
258 
259     size_t len = nfaRevAccelCheck(nfa, scratch->core_info.buf,
260                                   scratch->core_info.len);
261     if (!len) {
262         return;
263     }
264 
265     struct mq *q = scratch->queues;
266     initOutfixQueue(q, 0, t, scratch);
267     q->length = len; /* adjust for rev_accel */
268     nfaQueueInitState(nfa, q);
269     pushQueueAt(q, 0, MQE_START, 0);
270     pushQueueAt(q, 1, MQE_TOP, 0);
271     pushQueueAt(q, 2, MQE_END, scratch->core_info.len);
272 
273     char rv = nfaQueueExec(q->nfa, q, scratch->core_info.len);
274 
275     if (rv && nfaAcceptsEod(nfa) && len == scratch->core_info.len) {
276         nfaCheckFinalState(nfa, q->state, q->streamState, q->length, q->cb,
277                            scratch);
278     }
279 }
280 
281 static rose_inline
runSmallWriteEngine(const struct SmallWriteEngine * smwr,struct hs_scratch * scratch)282 void runSmallWriteEngine(const struct SmallWriteEngine *smwr,
283                          struct hs_scratch *scratch) {
284     assert(smwr);
285     assert(scratch);
286 
287     const u8 *buffer = scratch->core_info.buf;
288     size_t length = scratch->core_info.len;
289 
290     DEBUG_PRINTF("USING SMALL WRITE\n");
291 
292     if (length <= smwr->start_offset) {
293         DEBUG_PRINTF("too short\n");
294         return;
295     }
296 
297     const struct NFA *nfa = getSmwrNfa(smwr);
298 
299     size_t local_alen = length - smwr->start_offset;
300     const u8 *local_buffer = buffer + smwr->start_offset;
301 
302     assert(isDfaType(nfa->type));
303     if (nfa->type == MCCLELLAN_NFA_8) {
304         nfaExecMcClellan8_B(nfa, smwr->start_offset, local_buffer,
305                             local_alen, roseReportAdaptor, scratch);
306     } else if (nfa->type == MCCLELLAN_NFA_16) {
307         nfaExecMcClellan16_B(nfa, smwr->start_offset, local_buffer,
308                              local_alen, roseReportAdaptor, scratch);
309     } else {
310         nfaExecSheng_B(nfa, smwr->start_offset, local_buffer,
311                        local_alen, roseReportAdaptor, scratch);
312     }
313 }
314 
315 HS_PUBLIC_API
hs_scan(const hs_database_t * db,const char * data,unsigned length,unsigned flags,hs_scratch_t * scratch,match_event_handler onEvent,void * userCtx)316 hs_error_t HS_CDECL hs_scan(const hs_database_t *db, const char *data,
317                             unsigned length, unsigned flags,
318                             hs_scratch_t *scratch, match_event_handler onEvent,
319                             void *userCtx) {
320     if (unlikely(!scratch || !data)) {
321         return HS_INVALID;
322     }
323 
324     hs_error_t err = validDatabase(db);
325     if (unlikely(err != HS_SUCCESS)) {
326         return err;
327     }
328 
329     const struct RoseEngine *rose = hs_get_bytecode(db);
330     if (unlikely(!ISALIGNED_16(rose))) {
331         return HS_INVALID;
332     }
333 
334     if (unlikely(rose->mode != HS_MODE_BLOCK)) {
335         return HS_DB_MODE_ERROR;
336     }
337 
338     if (unlikely(!validScratch(rose, scratch))) {
339         return HS_INVALID;
340     }
341 
342     if (unlikely(markScratchInUse(scratch))) {
343         return HS_SCRATCH_IN_USE;
344     }
345 
346     if (rose->minWidth > length) {
347         DEBUG_PRINTF("minwidth=%u > length=%u\n", rose->minWidth, length);
348         unmarkScratchInUse(scratch);
349         return HS_SUCCESS;
350     }
351 
352     prefetch_data(data, length);
353 
354     /* populate core info in scratch */
355     populateCoreInfo(scratch, rose, scratch->bstate, onEvent, userCtx, data,
356                      length, NULL, 0, 0, 0, flags);
357 
358     clearEvec(rose, scratch->core_info.exhaustionVector);
359     if (rose->ckeyCount) {
360         scratch->core_info.logicalVector = scratch->bstate +
361                                            rose->stateOffsets.logicalVec;
362         scratch->core_info.combVector = scratch->bstate +
363                                         rose->stateOffsets.combVec;
364         scratch->tctxt.lastCombMatchOffset = 0;
365         clearLvec(rose, scratch->core_info.logicalVector,
366                   scratch->core_info.combVector);
367     }
368 
369     if (!length) {
370         if (rose->boundary.reportZeroEodOffset) {
371             roseRunBoundaryProgram(rose, rose->boundary.reportZeroEodOffset, 0,
372                                    scratch);
373         }
374         goto set_retval;
375     }
376 
377     if (rose->boundary.reportZeroOffset) {
378         int rv = roseRunBoundaryProgram(rose, rose->boundary.reportZeroOffset,
379                                         0, scratch);
380         if (rv == MO_HALT_MATCHING) {
381             goto set_retval;
382         }
383     }
384 
385     if (rose->minWidthExcludingBoundaries > length) {
386         DEBUG_PRINTF("minWidthExcludingBoundaries=%u > length=%u\n",
387                      rose->minWidthExcludingBoundaries, length);
388         goto done_scan;
389     }
390 
391     // Similarly, we may have a maximum width (for engines constructed entirely
392     // of bi-anchored patterns).
393     if (rose->maxBiAnchoredWidth != ROSE_BOUND_INF
394         && length > rose->maxBiAnchoredWidth) {
395         DEBUG_PRINTF("block len=%u longer than maxBAWidth=%u\n", length,
396                      rose->maxBiAnchoredWidth);
397         goto done_scan;
398     }
399 
400     // Is this a small write case?
401     if (rose->smallWriteOffset) {
402         const struct SmallWriteEngine *smwr = getSmallWrite(rose);
403         assert(smwr);
404 
405         // Apply the small write engine if and only if the block (buffer) is
406         // small enough. Otherwise, we allow rose &co to deal with it.
407         if (length < smwr->largestBuffer) {
408             DEBUG_PRINTF("Attempting small write of block %u bytes long.\n",
409                          length);
410             runSmallWriteEngine(smwr, scratch);
411             goto done_scan;
412         }
413     }
414 
415     switch (rose->runtimeImpl) {
416     default:
417         assert(0);
418     case ROSE_RUNTIME_FULL_ROSE:
419         rawBlockExec(rose, scratch);
420         break;
421     case ROSE_RUNTIME_PURE_LITERAL:
422         pureLiteralBlockExec(rose, scratch);
423         break;
424     case ROSE_RUNTIME_SINGLE_OUTFIX:
425         soleOutfixBlockExec(rose, scratch);
426         break;
427     }
428 
429 done_scan:
430     if (unlikely(internal_matching_error(scratch))) {
431         unmarkScratchInUse(scratch);
432         return HS_UNKNOWN_ERROR;
433     } else if (told_to_stop_matching(scratch)) {
434         unmarkScratchInUse(scratch);
435         return HS_SCAN_TERMINATED;
436     }
437 
438     if (rose->hasSom) {
439         int halt = flushStoredSomMatches(scratch, ~0ULL);
440         if (halt) {
441             unmarkScratchInUse(scratch);
442             return HS_SCAN_TERMINATED;
443         }
444     }
445 
446     if (rose->boundary.reportEodOffset) {
447         roseRunBoundaryProgram(rose, rose->boundary.reportEodOffset, length,
448                                scratch);
449     }
450 
451 set_retval:
452     if (unlikely(internal_matching_error(scratch))) {
453         unmarkScratchInUse(scratch);
454         return HS_UNKNOWN_ERROR;
455     }
456 
457     if (rose->lastFlushCombProgramOffset) {
458         if (roseRunLastFlushCombProgram(rose, scratch, length)
459             == MO_HALT_MATCHING) {
460             if (unlikely(internal_matching_error(scratch))) {
461                 unmarkScratchInUse(scratch);
462                 return HS_UNKNOWN_ERROR;
463             }
464             unmarkScratchInUse(scratch);
465             return HS_SCAN_TERMINATED;
466         }
467     }
468 
469     DEBUG_PRINTF("done. told_to_stop_matching=%d\n",
470                  told_to_stop_matching(scratch));
471     hs_error_t rv = told_to_stop_matching(scratch) ? HS_SCAN_TERMINATED
472                                                    : HS_SUCCESS;
473     unmarkScratchInUse(scratch);
474     return rv;
475 }
476 
477 static really_inline
maintainHistoryBuffer(const struct RoseEngine * rose,char * state,const char * buffer,size_t length)478 void maintainHistoryBuffer(const struct RoseEngine *rose, char *state,
479                            const char *buffer, size_t length) {
480     if (!rose->historyRequired) {
481         return;
482     }
483 
484     // Hopefully few of our users are scanning no data.
485     if (unlikely(length == 0)) {
486         DEBUG_PRINTF("zero-byte scan\n");
487         return;
488     }
489 
490     char *his_state = state + rose->stateOffsets.history;
491 
492     if (length < rose->historyRequired) {
493         size_t shortfall = rose->historyRequired - length;
494         memmove(his_state, his_state + rose->historyRequired - shortfall,
495                 shortfall);
496     }
497     size_t amount = MIN(rose->historyRequired, length);
498 
499     memcpy(his_state + rose->historyRequired - amount, buffer + length - amount,
500            amount);
501 #ifdef DEBUG_HISTORY
502     printf("History [%u] : ", rose->historyRequired);
503     for (size_t i = 0; i < rose->historyRequired; i++) {
504         printf(" %02hhx", his_state[i]);
505     }
506     printf("\n");
507 #endif
508 }
509 
510 static really_inline
init_stream(struct hs_stream * s,const struct RoseEngine * rose,char init_history)511 void init_stream(struct hs_stream *s, const struct RoseEngine *rose,
512                  char init_history) {
513     char *state = getMultiState(s);
514 
515     if (init_history) {
516         // Make absolutely sure that the 16 bytes leading up to the end of the
517         // history buffer are initialised, as we rely on this (regardless of the
518         // actual values used) in FDR.
519         char *hist_end =
520             state + rose->stateOffsets.history + rose->historyRequired;
521         assert(hist_end - 16 >= (const char *)s);
522         memset(hist_end - 16, 0x5a, 16);
523     }
524 
525     s->rose = rose;
526     s->offset = 0;
527 
528     setStreamStatus(state, 0);
529     roseInitState(rose, state);
530 
531     clearEvec(rose, state + rose->stateOffsets.exhausted);
532     if (rose->ckeyCount) {
533         clearLvec(rose, state + rose->stateOffsets.logicalVec,
534                   state + rose->stateOffsets.combVec);
535     }
536 
537     // SOM state multibit structures.
538     initSomState(rose, state);
539 }
540 
541 HS_PUBLIC_API
hs_open_stream(const hs_database_t * db,UNUSED unsigned flags,hs_stream_t ** stream)542 hs_error_t HS_CDECL hs_open_stream(const hs_database_t *db,
543                                    UNUSED unsigned flags,
544                                    hs_stream_t **stream) {
545     if (unlikely(!stream)) {
546         return HS_INVALID;
547     }
548 
549     *stream = NULL;
550 
551     hs_error_t err = validDatabase(db);
552     if (unlikely(err != HS_SUCCESS)) {
553         return err;
554     }
555 
556     const struct RoseEngine *rose = hs_get_bytecode(db);
557     if (unlikely(!ISALIGNED_16(rose))) {
558         return HS_INVALID;
559     }
560 
561     if (unlikely(rose->mode != HS_MODE_STREAM)) {
562         return HS_DB_MODE_ERROR;
563     }
564 
565     size_t stateSize = rose->stateOffsets.end;
566     struct hs_stream *s = hs_stream_alloc(sizeof(struct hs_stream) + stateSize);
567     if (unlikely(!s)) {
568         return HS_NOMEM;
569     }
570 
571     init_stream(s, rose, 1);
572 
573     *stream = s;
574     return HS_SUCCESS;
575 }
576 
577 
578 static really_inline
rawEodExec(hs_stream_t * id,hs_scratch_t * scratch)579 void rawEodExec(hs_stream_t *id, hs_scratch_t *scratch) {
580     const struct RoseEngine *rose = id->rose;
581 
582     if (can_stop_matching(scratch)) {
583         DEBUG_PRINTF("stream already broken\n");
584         return;
585     }
586 
587     if (isAllExhausted(rose, scratch->core_info.exhaustionVector)) {
588         DEBUG_PRINTF("stream exhausted\n");
589         return;
590     }
591 
592     roseStreamEodExec(rose, id->offset, scratch);
593 }
594 
595 static never_inline
soleOutfixEodExec(hs_stream_t * id,hs_scratch_t * scratch)596 void soleOutfixEodExec(hs_stream_t *id, hs_scratch_t *scratch) {
597     const struct RoseEngine *t = id->rose;
598 
599     if (can_stop_matching(scratch)) {
600         DEBUG_PRINTF("stream already broken\n");
601         return;
602     }
603 
604     if (isAllExhausted(t, scratch->core_info.exhaustionVector)) {
605         DEBUG_PRINTF("stream exhausted\n");
606         return;
607     }
608 
609     assert(t->outfixEndQueue == 1);
610     assert(!t->amatcherOffset);
611     assert(!t->ematcherOffset);
612     assert(!t->fmatcherOffset);
613 
614     const struct NFA *nfa = getNfaByQueue(t, 0);
615 
616     struct mq *q = scratch->queues;
617     initOutfixQueue(q, 0, t, scratch);
618     if (!scratch->core_info.buf_offset) {
619         DEBUG_PRINTF("buf_offset is zero\n");
620         return; /* no vacuous engines */
621     }
622 
623     nfaExpandState(nfa, q->state, q->streamState, q->offset,
624                    queue_prev_byte(q, 0));
625 
626     assert(nfaAcceptsEod(nfa));
627     nfaCheckFinalState(nfa, q->state, q->streamState, q->offset, q->cb,
628                        scratch);
629 }
630 
631 static really_inline
report_eod_matches(hs_stream_t * id,hs_scratch_t * scratch,match_event_handler onEvent,void * context)632 void report_eod_matches(hs_stream_t *id, hs_scratch_t *scratch,
633                         match_event_handler onEvent, void *context) {
634     DEBUG_PRINTF("--- report eod matches at offset %llu\n", id->offset);
635     assert(onEvent);
636 
637     const struct RoseEngine *rose = id->rose;
638     char *state = getMultiState(id);
639     u8 status = getStreamStatus(state);
640 
641     if (status & (STATUS_TERMINATED | STATUS_EXHAUSTED | STATUS_ERROR)) {
642         DEBUG_PRINTF("stream is broken, just freeing storage\n");
643         return;
644     }
645 
646     populateCoreInfo(scratch, rose, state, onEvent, context, NULL, 0,
647                      getHistory(state, rose, id->offset),
648                      getHistoryAmount(rose, id->offset), id->offset, status, 0);
649 
650     if (rose->ckeyCount) {
651         scratch->core_info.logicalVector = state +
652                                            rose->stateOffsets.logicalVec;
653         scratch->core_info.combVector = state + rose->stateOffsets.combVec;
654         if (!id->offset) {
655             scratch->tctxt.lastCombMatchOffset = id->offset;
656         }
657     }
658 
659     if (rose->somLocationCount) {
660         loadSomFromStream(scratch, id->offset);
661     }
662 
663     if (!id->offset) {
664         if (rose->boundary.reportZeroEodOffset) {
665             int rv = roseRunBoundaryProgram(
666                 rose, rose->boundary.reportZeroEodOffset, 0, scratch);
667             if (rv == MO_HALT_MATCHING) {
668                 return;
669             }
670         }
671     } else {
672         if (rose->boundary.reportEodOffset) {
673             int rv = roseRunBoundaryProgram(
674                 rose, rose->boundary.reportEodOffset, id->offset, scratch);
675             if (rv == MO_HALT_MATCHING) {
676                 return;
677             }
678         }
679 
680         if (rose->requiresEodCheck) {
681             switch (rose->runtimeImpl) {
682             default:
683             case ROSE_RUNTIME_PURE_LITERAL:
684                 assert(0);
685             case ROSE_RUNTIME_FULL_ROSE:
686                 rawEodExec(id, scratch);
687                 break;
688             case ROSE_RUNTIME_SINGLE_OUTFIX:
689                 soleOutfixEodExec(id, scratch);
690                 break;
691             }
692         }
693     }
694 
695     if (rose->hasSom && !told_to_stop_matching(scratch)) {
696         int halt = flushStoredSomMatches(scratch, ~0ULL);
697         if (halt) {
698             DEBUG_PRINTF("told to stop matching\n");
699             scratch->core_info.status |= STATUS_TERMINATED;
700         }
701     }
702 
703     if (rose->lastFlushCombProgramOffset && !told_to_stop_matching(scratch)) {
704         if (roseRunLastFlushCombProgram(rose, scratch, id->offset)
705             == MO_HALT_MATCHING) {
706             DEBUG_PRINTF("told to stop matching\n");
707             scratch->core_info.status |= STATUS_TERMINATED;
708         }
709     }
710 }
711 
712 HS_PUBLIC_API
hs_copy_stream(hs_stream_t ** to_id,const hs_stream_t * from_id)713 hs_error_t HS_CDECL hs_copy_stream(hs_stream_t **to_id,
714                                    const hs_stream_t *from_id) {
715     if (!to_id) {
716         return HS_INVALID;
717     }
718 
719     *to_id = NULL;
720 
721     if (!from_id || !from_id->rose) {
722         return HS_INVALID;
723     }
724 
725     const struct RoseEngine *rose = from_id->rose;
726     size_t stateSize = sizeof(struct hs_stream) + rose->stateOffsets.end;
727 
728     struct hs_stream *s = hs_stream_alloc(stateSize);
729     if (!s) {
730         return HS_NOMEM;
731     }
732 
733     memcpy(s, from_id, stateSize);
734 
735     *to_id = s;
736 
737     return HS_SUCCESS;
738 }
739 
740 HS_PUBLIC_API
hs_reset_and_copy_stream(hs_stream_t * to_id,const hs_stream_t * from_id,hs_scratch_t * scratch,match_event_handler onEvent,void * context)741 hs_error_t HS_CDECL hs_reset_and_copy_stream(hs_stream_t *to_id,
742                                              const hs_stream_t *from_id,
743                                              hs_scratch_t *scratch,
744                                              match_event_handler onEvent,
745                                              void *context) {
746     if (!from_id || !from_id->rose) {
747         return HS_INVALID;
748     }
749 
750     if (!to_id || to_id->rose != from_id->rose) {
751         return HS_INVALID;
752     }
753 
754     if (to_id == from_id) {
755         return HS_INVALID;
756     }
757 
758     if (onEvent) {
759         if (!scratch || !validScratch(to_id->rose, scratch)) {
760             return HS_INVALID;
761         }
762         if (unlikely(markScratchInUse(scratch))) {
763             return HS_SCRATCH_IN_USE;
764         }
765         report_eod_matches(to_id, scratch, onEvent, context);
766         if (unlikely(internal_matching_error(scratch))) {
767             unmarkScratchInUse(scratch);
768             return HS_UNKNOWN_ERROR;
769         }
770         unmarkScratchInUse(scratch);
771     }
772 
773     size_t stateSize
774         = sizeof(struct hs_stream) + from_id->rose->stateOffsets.end;
775 
776     memcpy(to_id, from_id, stateSize);
777 
778     return HS_SUCCESS;
779 }
780 
781 static really_inline
rawStreamExec(struct hs_stream * stream_state,struct hs_scratch * scratch)782 void rawStreamExec(struct hs_stream *stream_state, struct hs_scratch *scratch) {
783     assert(stream_state);
784     assert(scratch);
785     assert(!can_stop_matching(scratch));
786 
787     DEBUG_PRINTF("::: streaming rose ::: offset = %llu len = %zu\n",
788                  stream_state->offset, scratch->core_info.len);
789 
790     const struct RoseEngine *rose = stream_state->rose;
791     assert(rose);
792     roseStreamExec(rose, scratch);
793 
794     if (!told_to_stop_matching(scratch) &&
795         isAllExhausted(rose, scratch->core_info.exhaustionVector)) {
796         DEBUG_PRINTF("stream exhausted\n");
797         scratch->core_info.status |= STATUS_EXHAUSTED;
798     }
799 }
800 
801 static really_inline
pureLiteralStreamExec(struct hs_stream * stream_state,struct hs_scratch * scratch)802 void pureLiteralStreamExec(struct hs_stream *stream_state,
803                            struct hs_scratch *scratch) {
804     assert(stream_state);
805     assert(scratch);
806     assert(!can_stop_matching(scratch));
807 
808     const struct RoseEngine *rose = stream_state->rose;
809     const struct HWLM *ftable = getFLiteralMatcher(rose);
810 
811     size_t len2 = scratch->core_info.len;
812 
813     DEBUG_PRINTF("::: streaming rose ::: offset = %llu len = %zu\n",
814                  stream_state->offset, scratch->core_info.len);
815 
816     pureLiteralInitScratch(scratch, stream_state->offset);
817     scratch->tctxt.groups = loadGroups(rose, scratch->core_info.state);
818 
819     // Pure literal cases don't have floatingMinDistance set, so we always
820     // start the match region at zero.
821     const size_t start = 0;
822 
823     hwlmExecStreaming(ftable, len2, start, roseCallback, scratch,
824                       rose->initialGroups & rose->floating_group_mask);
825 
826     if (!told_to_stop_matching(scratch) &&
827         isAllExhausted(rose, scratch->core_info.exhaustionVector)) {
828         DEBUG_PRINTF("stream exhausted\n");
829         scratch->core_info.status |= STATUS_EXHAUSTED;
830     }
831 }
832 
833 static never_inline
soleOutfixStreamExec(struct hs_stream * stream_state,struct hs_scratch * scratch)834 void soleOutfixStreamExec(struct hs_stream *stream_state,
835                           struct hs_scratch *scratch) {
836     assert(stream_state);
837     assert(scratch);
838     assert(!can_stop_matching(scratch));
839 
840     const struct RoseEngine *t = stream_state->rose;
841     assert(t->outfixEndQueue == 1);
842     assert(!t->amatcherOffset);
843     assert(!t->ematcherOffset);
844     assert(!t->fmatcherOffset);
845 
846     const struct NFA *nfa = getNfaByQueue(t, 0);
847 
848     struct mq *q = scratch->queues;
849     initOutfixQueue(q, 0, t, scratch);
850     if (!scratch->core_info.buf_offset) {
851         nfaQueueInitState(nfa, q);
852         pushQueueAt(q, 0, MQE_START, 0);
853         pushQueueAt(q, 1, MQE_TOP, 0);
854         pushQueueAt(q, 2, MQE_END, scratch->core_info.len);
855     } else {
856         nfaExpandState(nfa, q->state, q->streamState, q->offset,
857                        queue_prev_byte(q, 0));
858         pushQueueAt(q, 0, MQE_START, 0);
859         pushQueueAt(q, 1, MQE_END, scratch->core_info.len);
860     }
861 
862     if (nfaQueueExec(q->nfa, q, scratch->core_info.len)) {
863         nfaQueueCompressState(nfa, q, scratch->core_info.len);
864     } else if (!told_to_stop_matching(scratch)) {
865         scratch->core_info.status |= STATUS_EXHAUSTED;
866     }
867 }
868 
869 static inline
hs_scan_stream_internal(hs_stream_t * id,const char * data,unsigned length,UNUSED unsigned flags,hs_scratch_t * scratch,match_event_handler onEvent,void * context)870 hs_error_t hs_scan_stream_internal(hs_stream_t *id, const char *data,
871                                    unsigned length, UNUSED unsigned flags,
872                                    hs_scratch_t *scratch,
873                                    match_event_handler onEvent, void *context) {
874     assert(id);
875     assert(scratch);
876 
877     if (unlikely(!data)) {
878         return HS_INVALID;
879     }
880 
881     const struct RoseEngine *rose = id->rose;
882     char *state = getMultiState(id);
883 
884     u8 status = getStreamStatus(state);
885     if (status & (STATUS_TERMINATED | STATUS_EXHAUSTED | STATUS_ERROR)) {
886         DEBUG_PRINTF("stream is broken, halting scan\n");
887         if (status & STATUS_ERROR) {
888             return HS_UNKNOWN_ERROR;
889         } else if (status & STATUS_TERMINATED) {
890             return HS_SCAN_TERMINATED;
891         } else {
892             return HS_SUCCESS;
893         }
894     }
895 
896     // We avoid doing any work if the user has given us zero bytes of data to
897     // scan. Arguably we should define some semantics for how we treat vacuous
898     // cases here.
899     if (unlikely(length == 0)) {
900         DEBUG_PRINTF("zero length block\n");
901         return HS_SUCCESS;
902     }
903 
904     u32 historyAmount = getHistoryAmount(rose, id->offset);
905     populateCoreInfo(scratch, rose, state, onEvent, context, data, length,
906                      getHistory(state, rose, id->offset), historyAmount,
907                      id->offset, status, flags);
908     if (rose->ckeyCount) {
909         scratch->core_info.logicalVector = state +
910                                            rose->stateOffsets.logicalVec;
911         scratch->core_info.combVector = state + rose->stateOffsets.combVec;
912         if (!id->offset) {
913             scratch->tctxt.lastCombMatchOffset = id->offset;
914         }
915     }
916     assert(scratch->core_info.hlen <= id->offset
917            && scratch->core_info.hlen <= rose->historyRequired);
918 
919     prefetch_data(data, length);
920 
921     if (rose->somLocationCount) {
922         loadSomFromStream(scratch, id->offset);
923     }
924 
925     if (!id->offset && rose->boundary.reportZeroOffset) {
926         DEBUG_PRINTF("zero reports\n");
927         int rv = roseRunBoundaryProgram(rose, rose->boundary.reportZeroOffset,
928                                         0, scratch);
929         if (rv == MO_HALT_MATCHING) {
930             DEBUG_PRINTF("halting scan\n");
931             setStreamStatus(state, scratch->core_info.status);
932             if (told_to_stop_matching(scratch)) {
933                 return HS_SCAN_TERMINATED;
934             } else {
935                 assert(scratch->core_info.status & STATUS_EXHAUSTED);
936                 return HS_SUCCESS;
937             }
938         }
939     }
940 
941     switch (rose->runtimeImpl) {
942     default:
943         assert(0);
944     case ROSE_RUNTIME_FULL_ROSE:
945         rawStreamExec(id, scratch);
946         break;
947     case ROSE_RUNTIME_PURE_LITERAL:
948         pureLiteralStreamExec(id, scratch);
949         break;
950     case ROSE_RUNTIME_SINGLE_OUTFIX:
951         soleOutfixStreamExec(id, scratch);
952     }
953 
954     if (rose->hasSom && !told_to_stop_matching(scratch)) {
955         int halt = flushStoredSomMatches(scratch, ~0ULL);
956         if (halt) {
957             scratch->core_info.status |= STATUS_TERMINATED;
958         }
959     }
960 
961     setStreamStatus(state, scratch->core_info.status);
962 
963     if (unlikely(internal_matching_error(scratch))) {
964         return HS_UNKNOWN_ERROR;
965     } else if (likely(!can_stop_matching(scratch))) {
966         maintainHistoryBuffer(rose, state, data, length);
967         id->offset += length; /* maintain offset */
968 
969         if (rose->somLocationCount) {
970             storeSomToStream(scratch, id->offset);
971         }
972     } else if (told_to_stop_matching(scratch)) {
973         return HS_SCAN_TERMINATED;
974     }
975 
976     return HS_SUCCESS;
977 }
978 
979 HS_PUBLIC_API
hs_scan_stream(hs_stream_t * id,const char * data,unsigned length,unsigned flags,hs_scratch_t * scratch,match_event_handler onEvent,void * context)980 hs_error_t HS_CDECL hs_scan_stream(hs_stream_t *id, const char *data,
981                                    unsigned length, unsigned flags,
982                                    hs_scratch_t *scratch,
983                                    match_event_handler onEvent, void *context) {
984     if (unlikely(!id || !scratch || !data ||
985                  !validScratch(id->rose, scratch))) {
986         return HS_INVALID;
987     }
988 
989     if (unlikely(markScratchInUse(scratch))) {
990         return HS_SCRATCH_IN_USE;
991     }
992     hs_error_t rv = hs_scan_stream_internal(id, data, length, flags, scratch,
993                                             onEvent, context);
994     unmarkScratchInUse(scratch);
995     return rv;
996 }
997 
998 HS_PUBLIC_API
hs_close_stream(hs_stream_t * id,hs_scratch_t * scratch,match_event_handler onEvent,void * context)999 hs_error_t HS_CDECL hs_close_stream(hs_stream_t *id, hs_scratch_t *scratch,
1000                                     match_event_handler onEvent,
1001                                     void *context) {
1002     if (!id) {
1003         return HS_INVALID;
1004     }
1005 
1006     if (onEvent) {
1007         if (!scratch || !validScratch(id->rose, scratch)) {
1008             return HS_INVALID;
1009         }
1010         if (unlikely(markScratchInUse(scratch))) {
1011             return HS_SCRATCH_IN_USE;
1012         }
1013         report_eod_matches(id, scratch, onEvent, context);
1014         if (unlikely(internal_matching_error(scratch))) {
1015             unmarkScratchInUse(scratch);
1016             return HS_UNKNOWN_ERROR;
1017         }
1018         unmarkScratchInUse(scratch);
1019     }
1020 
1021     hs_stream_free(id);
1022 
1023     return HS_SUCCESS;
1024 }
1025 
1026 HS_PUBLIC_API
hs_reset_stream(hs_stream_t * id,UNUSED unsigned int flags,hs_scratch_t * scratch,match_event_handler onEvent,void * context)1027 hs_error_t HS_CDECL hs_reset_stream(hs_stream_t *id, UNUSED unsigned int flags,
1028                                     hs_scratch_t *scratch,
1029                                     match_event_handler onEvent,
1030                                     void *context) {
1031     if (!id) {
1032         return HS_INVALID;
1033     }
1034 
1035     if (onEvent) {
1036         if (!scratch || !validScratch(id->rose, scratch)) {
1037             return HS_INVALID;
1038         }
1039         if (unlikely(markScratchInUse(scratch))) {
1040             return HS_SCRATCH_IN_USE;
1041         }
1042         report_eod_matches(id, scratch, onEvent, context);
1043         if (unlikely(internal_matching_error(scratch))) {
1044             unmarkScratchInUse(scratch);
1045             return HS_UNKNOWN_ERROR;
1046         }
1047         unmarkScratchInUse(scratch);
1048     }
1049 
1050     // history already initialised
1051     init_stream(id, id->rose, 0);
1052 
1053     return HS_SUCCESS;
1054 }
1055 
1056 HS_PUBLIC_API
hs_stream_size(const hs_database_t * db,size_t * stream_size)1057 hs_error_t HS_CDECL hs_stream_size(const hs_database_t *db,
1058                                    size_t *stream_size) {
1059     if (!stream_size) {
1060         return HS_INVALID;
1061     }
1062 
1063     hs_error_t ret = validDatabase(db);
1064     if (ret != HS_SUCCESS) {
1065         return ret;
1066     }
1067 
1068     const struct RoseEngine *rose = hs_get_bytecode(db);
1069     if (!ISALIGNED_16(rose)) {
1070         return HS_INVALID;
1071     }
1072 
1073     if (rose->mode != HS_MODE_STREAM) {
1074         return HS_DB_MODE_ERROR;
1075     }
1076 
1077     u32 base_stream_size = rose->stateOffsets.end;
1078 
1079     // stream state plus the hs_stream struct itself
1080     *stream_size = base_stream_size + sizeof(struct hs_stream);
1081 
1082     return HS_SUCCESS;
1083 }
1084 
1085 #if defined(DEBUG) || defined(DUMP_SUPPORT)
1086 #include "util/compare.h"
1087 // A debugging crutch: print a hex-escaped version of the match for our
1088 // perusal.
1089 static UNUSED
dumpData(const char * data,size_t len)1090 void dumpData(const char *data, size_t len) {
1091     DEBUG_PRINTF("BUFFER:");
1092     for (size_t i = 0; i < len; i++) {
1093         u8 c = data[i];
1094         if (ourisprint(c) && c != '\'') {
1095             printf("%c", c);
1096         } else {
1097             printf("\\x%02x", c);
1098         }
1099     }
1100     printf("\n");
1101 }
1102 #endif
1103 
1104 HS_PUBLIC_API
hs_scan_vector(const hs_database_t * db,const char * const * data,const unsigned int * length,unsigned int count,UNUSED unsigned int flags,hs_scratch_t * scratch,match_event_handler onEvent,void * context)1105 hs_error_t HS_CDECL hs_scan_vector(const hs_database_t *db,
1106                                    const char * const * data,
1107                                    const unsigned int *length,
1108                                    unsigned int count,
1109                                    UNUSED unsigned int flags,
1110                                    hs_scratch_t *scratch,
1111                                    match_event_handler onEvent, void *context) {
1112     if (unlikely(!scratch || !data || !length)) {
1113         return HS_INVALID;
1114     }
1115 
1116     hs_error_t err = validDatabase(db);
1117     if (unlikely(err != HS_SUCCESS)) {
1118         return err;
1119     }
1120 
1121     const struct RoseEngine *rose = hs_get_bytecode(db);
1122     if (unlikely(!ISALIGNED_16(rose))) {
1123         return HS_INVALID;
1124     }
1125 
1126     if (unlikely(rose->mode != HS_MODE_VECTORED)) {
1127         return HS_DB_MODE_ERROR;
1128     }
1129 
1130     if (unlikely(!validScratch(rose, scratch))) {
1131         return HS_INVALID;
1132     }
1133 
1134     if (unlikely(markScratchInUse(scratch))) {
1135         return HS_SCRATCH_IN_USE;
1136     }
1137 
1138     hs_stream_t *id = (hs_stream_t *)(scratch->bstate);
1139 
1140     init_stream(id, rose, 1); /* open stream */
1141 
1142     for (u32 i = 0; i < count; i++) {
1143         DEBUG_PRINTF("block %u/%u offset=%llu len=%u\n", i, count, id->offset,
1144                      length[i]);
1145 #ifdef DEBUG
1146         dumpData(data[i], length[i]);
1147 #endif
1148         hs_error_t ret
1149             = hs_scan_stream_internal(id, data[i], length[i], 0, scratch,
1150                                       onEvent, context);
1151         if (ret != HS_SUCCESS) {
1152             unmarkScratchInUse(scratch);
1153             return ret;
1154         }
1155     }
1156 
1157     /* close stream */
1158     if (onEvent) {
1159         report_eod_matches(id, scratch, onEvent, context);
1160 
1161         if (unlikely(internal_matching_error(scratch))) {
1162             unmarkScratchInUse(scratch);
1163             return HS_UNKNOWN_ERROR;
1164         } else if (told_to_stop_matching(scratch)) {
1165             unmarkScratchInUse(scratch);
1166             return HS_SCAN_TERMINATED;
1167         }
1168     }
1169 
1170     unmarkScratchInUse(scratch);
1171 
1172     return HS_SUCCESS;
1173 }
1174 
1175 HS_PUBLIC_API
hs_compress_stream(const hs_stream_t * stream,char * buf,size_t buf_space,size_t * used_space)1176 hs_error_t HS_CDECL hs_compress_stream(const hs_stream_t *stream, char *buf,
1177                                        size_t buf_space, size_t *used_space) {
1178     if (unlikely(!stream || !used_space)) {
1179         return HS_INVALID;
1180     }
1181 
1182     if (unlikely(buf_space && !buf)) {
1183         return HS_INVALID;
1184     }
1185 
1186     const struct RoseEngine *rose = stream->rose;
1187 
1188     size_t stream_size = size_compress_stream(rose, stream);
1189 
1190     DEBUG_PRINTF("require %zu [orig %zu]\n", stream_size,
1191                  rose->stateOffsets.end + sizeof(struct hs_stream));
1192     *used_space = stream_size;
1193 
1194     if (buf_space < stream_size) {
1195         return HS_INSUFFICIENT_SPACE;
1196     }
1197     compress_stream(buf, stream_size, rose, stream);
1198 
1199     return HS_SUCCESS;
1200 }
1201 
1202 HS_PUBLIC_API
hs_expand_stream(const hs_database_t * db,hs_stream_t ** stream,const char * buf,size_t buf_size)1203 hs_error_t HS_CDECL hs_expand_stream(const hs_database_t *db,
1204                                      hs_stream_t **stream,
1205                                      const char *buf, size_t buf_size) {
1206     if (unlikely(!stream || !buf)) {
1207         return HS_INVALID;
1208     }
1209 
1210     *stream = NULL;
1211 
1212     hs_error_t err = validDatabase(db);
1213     if (unlikely(err != HS_SUCCESS)) {
1214         return err;
1215     }
1216 
1217     const struct RoseEngine *rose = hs_get_bytecode(db);
1218     if (unlikely(!ISALIGNED_16(rose))) {
1219         return HS_INVALID;
1220     }
1221 
1222     if (unlikely(rose->mode != HS_MODE_STREAM)) {
1223         return HS_DB_MODE_ERROR;
1224     }
1225 
1226     size_t stream_size = rose->stateOffsets.end + sizeof(struct hs_stream);
1227 
1228     struct hs_stream *s = hs_stream_alloc(stream_size);
1229     if (unlikely(!s)) {
1230         return HS_NOMEM;
1231     }
1232 
1233     if (!expand_stream(s, rose, buf, buf_size)) {
1234         hs_stream_free(s);
1235         return HS_INVALID;
1236     }
1237 
1238     *stream = s;
1239     return HS_SUCCESS;
1240 }
1241 
1242 HS_PUBLIC_API
hs_reset_and_expand_stream(hs_stream_t * to_stream,const char * buf,size_t buf_size,hs_scratch_t * scratch,match_event_handler onEvent,void * context)1243 hs_error_t HS_CDECL hs_reset_and_expand_stream(hs_stream_t *to_stream,
1244                                                const char *buf, size_t buf_size,
1245                                                hs_scratch_t *scratch,
1246                                                match_event_handler onEvent,
1247                                                void *context) {
1248     if (unlikely(!to_stream || !buf)) {
1249         return HS_INVALID;
1250     }
1251 
1252     const struct RoseEngine *rose = to_stream->rose;
1253 
1254     if (onEvent) {
1255         if (!scratch || !validScratch(to_stream->rose, scratch)) {
1256             return HS_INVALID;
1257         }
1258         if (unlikely(markScratchInUse(scratch))) {
1259             return HS_SCRATCH_IN_USE;
1260         }
1261         report_eod_matches(to_stream, scratch, onEvent, context);
1262         if (unlikely(internal_matching_error(scratch))) {
1263             unmarkScratchInUse(scratch);
1264             return HS_UNKNOWN_ERROR;
1265         }
1266         unmarkScratchInUse(scratch);
1267     }
1268 
1269     if (expand_stream(to_stream, rose, buf, buf_size)) {
1270         return HS_SUCCESS;
1271     } else {
1272         return HS_INVALID;
1273     }
1274 }
1275