Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 98 additions & 9 deletions src/main/java/com/gliwka/hyperscan/wrapper/Database.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
*/
public class Database implements Closeable {
private final Map<Integer, Expression> expressions;
private final Expression[] expressionsById;
private final int expressionCount;

private NativeDatabase database;
Expand All @@ -30,7 +31,14 @@ void registerDeallocator() {
}
}

private Database(NativeDatabase database, List<Expression> expressions) {
private static final Map<Integer, ExpressionFlag> BITMASK_TO_FLAG =
Collections.unmodifiableMap(Arrays.stream(ExpressionFlag.values())
.collect(Collectors.toMap(ExpressionFlag::getBits, identity())));

private final Mode mode;

private Database(NativeDatabase database, List<Expression> expressions, Mode mode) {
this.mode = mode;
this.database = database;
this.expressionCount = expressions.size();
database.registerDeallocator();
Expand All @@ -49,6 +57,40 @@ private Database(NativeDatabase database, List<Expression> expressions) {
this.expressions.put(i++, expression);
}
}

this.expressionsById = buildExpressionsById(expressions, hasIds);
}

private static Expression[] buildExpressionsById(List<Expression> expressions, boolean hasIds) {
int maxId = -1;
if (hasIds) {
for (Expression expression : expressions) {
Integer id = expression.getId();
if (id != null && id > maxId) {
maxId = id;
}
}
} else {
maxId = expressions.size() - 1;
}
if (maxId < 0 || maxId > Math.max(4 * expressions.size(), 1024)) {
return null;
}
Expression[] byId = new Expression[maxId + 1];
if (hasIds) {
for (Expression expression : expressions) {
Integer id = expression.getId();
if (id != null) {
byId[id] = expression;
}
}
} else {
int i = 0;
for (Expression expression : expressions) {
byId[i++] = expression;
}
}
return byId;
}

private static void handleErrors(int hsError, hs_compile_error_t compileError, List<Expression> expressions) throws CompileErrorException {
Expand Down Expand Up @@ -94,6 +136,21 @@ public static Database compile(Expression... expressions) throws CompileErrorExc
* @throws CompileErrorException If any of the expressions cannot be compiled
*/
public static Database compile(List<Expression> expressions) throws CompileErrorException {
return compile(expressions, Mode.BLOCK);
}

/**
* Compiles a list of expressions into a database in the given mode.
* Block-mode databases work with the block scanning methods, stream-mode
* databases with {@link Scanner#openStream(Database)}, and vectored-mode
* databases with the vectored scanning methods.
*
* @param expressions List of expressions to compile
* @param mode Compilation mode
* @return Compiled database
* @throws CompileErrorException If any of the expressions cannot be compiled
*/
public static Database compile(List<Expression> expressions, Mode mode) throws CompileErrorException {
try (
NativeExpressionCollection nativeExpressions = new NativeExpressionCollection(expressions);
hs_compile_error_t errorT = new hs_compile_error_t();
Expand All @@ -106,17 +163,44 @@ public static Database compile(List<Expression> expressions) throws CompileError
nativeExpressions.getNativeFlags(),
nativeExpressions.getNativeIds(),
nativeExpressions.getSize(),
HS_MODE_BLOCK,
nativeMode(mode),
null,
database,
error);

handleErrors(hsError, error.get(hs_compile_error_t.class), expressions);

return new Database(database.get(NativeDatabase.class), expressions);
return new Database(database.get(NativeDatabase.class), expressions, mode);
}
}

/**
* compile an expression into a database in the given mode to use for scanning
*
* @param expression Expression to compile
* @param mode Compilation mode
* @return Compiled database
* @throws CompileErrorException If the expression cannot be compiled
*/
public static Database compile(Expression expression, Mode mode) throws CompileErrorException {
return compile(singletonList(expression), mode);
}

private static int nativeMode(Mode mode) {
switch (mode) {
case STREAM:
return HS_MODE_STREAM;
case VECTORED:
return HS_MODE_VECTORED;
default:
return HS_MODE_BLOCK;
}
}

Mode getMode() {
return mode;
}

NativeDatabase getDatabase() {
return database;
}
Expand All @@ -139,6 +223,13 @@ public long getSize() {
}

Expression getExpression(int id) {
Expression[] byId = expressionsById;
if (byId != null && id >= 0 && id < byId.length) {
Expression expression = byId[id];
if (expression != null) {
return expression;
}
}
return expressions.get(id);
}

Expand Down Expand Up @@ -249,18 +340,14 @@ public static Database load(InputStream expressionsIn, InputStream databaseIn) t
int expressionCount = expressionsDataIn.readInt();
List<Expression> expressions = new ArrayList<>(expressionCount);

// Setup a lookup map for expression flags
Map<Integer, ExpressionFlag> bitmaskToFlag = Arrays.stream(ExpressionFlag.values())
.collect(Collectors.toMap(ExpressionFlag::getBits, identity()));

for (int i = 0; i < expressionCount; i++) {
int id = expressionsDataIn.readInt();
String pattern = expressionsDataIn.readUTF();
int flagCount = expressionsDataIn.readInt();
EnumSet<ExpressionFlag> flags = EnumSet.noneOf(ExpressionFlag.class);
for (int j = 0; j < flagCount; j++) {
int bitmask = expressionsDataIn.readInt();
flags.add(bitmaskToFlag.get(bitmask));
flags.add(BITMASK_TO_FLAG.get(bitmask));

}
expressions.add(new Expression(pattern, flags, id == -1 ? null : id));
Expand All @@ -281,7 +368,9 @@ public static Database load(InputStream expressionsIn, InputStream databaseIn) t
throw HyperscanException.hsErrorToException(hsError);
}

return new Database(database, expressions);
// The mode is not recoverable from the serialized form; leave it
// unknown so API-level mode validation is skipped for loaded databases.
return new Database(database, expressions, null);
}

@Override
Expand Down
23 changes: 23 additions & 0 deletions src/main/java/com/gliwka/hyperscan/wrapper/Mode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.gliwka.hyperscan.wrapper;

/**
* Database compilation mode, determining which scanning APIs the compiled
* database can be used with.
*/
public enum Mode {
/**
* Block mode: the whole input is scanned in one call via
* {@link Scanner#scan(Database, byte[], ByteMatchEventHandler)} and friends.
*/
BLOCK,
/**
* Streaming mode: input is fed in chunks via
* {@link Scanner#openStream(Database)}.
*/
STREAM,
/**
* Vectored mode: input is presented as a list of segments via
* {@link Scanner#scanVector(Database, byte[][], ByteMatchEventHandler)}.
*/
VECTORED
}
Loading