From af3594d0f6458b5905cb88f8c10eb046ca9a545a Mon Sep 17 00:00:00 2001 From: Shanyu Thibaut Juneja Date: Mon, 23 Dec 2024 21:45:53 +0200 Subject: [PATCH] feat: various string obfs --- dev.skidfuscator.obfuscator/build.gradle | 20 + .../skidfuscator/obfuscator/Skidfuscator.java | 8 +- .../number/pure/VmHashTransformer.java | 1 - .../impl/hash/StringEqualsHashTranformer.java | 112 ++ .../StringEqualsIgnoreCaseHashTranformer.java | 114 +++ .../impl/sdk/SdkInjectorTransformer.java | 59 ++ .../obfuscator/util/JdkDownloader.java | 2 +- .../obfuscator/util/cfg/Variables.java | 46 + .../java/dev/skidfuscator/core/SkidTest.java | 4 +- .../test/java_lang_String/EqualsTest.java | 22 + .../test/string/StaticStringTest.java | 18 + .../java_lang_String/EqualsTestClass.java | 15 + .../string/StaticStringTestClass.java | 25 + .../src/test/resources/config/new.hocon | 2 +- .../src/test/resources/config/runtime.hocon | 1 - .../test/resources/config/runtime_new.hocon | 57 ++ dev.skidfuscator.sdk/build.gradle | 19 + .../src/main/java/sdk/Access.java | 346 +++++++ .../src/main/java/sdk/ByteBufferAccess.java | 72 ++ .../src/main/java/sdk/CharSequenceAccess.java | 181 ++++ .../sdk/CompactLatin1CharSequenceAccess.java | 174 ++++ .../src/main/java/sdk/DualHashFunction.java | 115 +++ .../java/sdk/HotSpotPrior7u6StringHash.java | 53 + .../src/main/java/sdk/LongHashFunction.java | 543 ++++++++++ .../main/java/sdk/LongTupleHashFunction.java | 969 ++++++++++++++++++ .../src/main/java/sdk/Maths.java | 94 ++ .../java/sdk/ModernCompactStringHash.java | 63 ++ .../java/sdk/ModernHotSpotStringHash.java | 47 + .../src/main/java/sdk/Primitives.java | 65 ++ .../src/main/java/sdk/SDK.java | 7 + .../src/main/java/sdk/StringHash.java | 22 + .../main/java/sdk/UnknownJvmStringHash.java | 32 + .../src/main/java/sdk/UnsafeAccess.java | 149 +++ .../src/main/java/sdk/Util.java | 70 ++ .../src/main/java/sdk/XXH3.java | 936 +++++++++++++++++ org.ow2.asm | 1 + settings.gradle | 2 + 37 files changed, 4458 insertions(+), 8 deletions(-) create mode 100644 dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/transform/impl/hash/StringEqualsHashTranformer.java create mode 100644 dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/transform/impl/hash/StringEqualsIgnoreCaseHashTranformer.java create mode 100644 dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/transform/impl/sdk/SdkInjectorTransformer.java create mode 100644 dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/util/cfg/Variables.java create mode 100644 dev.skidfuscator.obfuscator/src/test/java/dev/skidfuscator/test/java_lang_String/EqualsTest.java create mode 100644 dev.skidfuscator.obfuscator/src/test/java/dev/skidfuscator/test/string/StaticStringTest.java create mode 100644 dev.skidfuscator.obfuscator/src/test/java/dev/skidfuscator/testclasses/java_lang_String/EqualsTestClass.java create mode 100644 dev.skidfuscator.obfuscator/src/test/java/dev/skidfuscator/testclasses/string/StaticStringTestClass.java create mode 100644 dev.skidfuscator.obfuscator/src/test/resources/config/runtime_new.hocon create mode 100644 dev.skidfuscator.sdk/build.gradle create mode 100644 dev.skidfuscator.sdk/src/main/java/sdk/Access.java create mode 100644 dev.skidfuscator.sdk/src/main/java/sdk/ByteBufferAccess.java create mode 100644 dev.skidfuscator.sdk/src/main/java/sdk/CharSequenceAccess.java create mode 100644 dev.skidfuscator.sdk/src/main/java/sdk/CompactLatin1CharSequenceAccess.java create mode 100644 dev.skidfuscator.sdk/src/main/java/sdk/DualHashFunction.java create mode 100644 dev.skidfuscator.sdk/src/main/java/sdk/HotSpotPrior7u6StringHash.java create mode 100644 dev.skidfuscator.sdk/src/main/java/sdk/LongHashFunction.java create mode 100644 dev.skidfuscator.sdk/src/main/java/sdk/LongTupleHashFunction.java create mode 100644 dev.skidfuscator.sdk/src/main/java/sdk/Maths.java create mode 100644 dev.skidfuscator.sdk/src/main/java/sdk/ModernCompactStringHash.java create mode 100644 dev.skidfuscator.sdk/src/main/java/sdk/ModernHotSpotStringHash.java create mode 100644 dev.skidfuscator.sdk/src/main/java/sdk/Primitives.java create mode 100644 dev.skidfuscator.sdk/src/main/java/sdk/SDK.java create mode 100644 dev.skidfuscator.sdk/src/main/java/sdk/StringHash.java create mode 100644 dev.skidfuscator.sdk/src/main/java/sdk/UnknownJvmStringHash.java create mode 100644 dev.skidfuscator.sdk/src/main/java/sdk/UnsafeAccess.java create mode 100644 dev.skidfuscator.sdk/src/main/java/sdk/Util.java create mode 100644 dev.skidfuscator.sdk/src/main/java/sdk/XXH3.java create mode 160000 org.ow2.asm diff --git a/dev.skidfuscator.obfuscator/build.gradle b/dev.skidfuscator.obfuscator/build.gradle index ac9091b..2c57e7b 100644 --- a/dev.skidfuscator.obfuscator/build.gradle +++ b/dev.skidfuscator.obfuscator/build.gradle @@ -20,6 +20,7 @@ dependencies { api project(':commons') api project(':pure-analysis') + implementation project(':sdk') implementation 'com.github.lukfor:magic-progress:0.3.2' implementation 'com.github.matomo-org:matomo-java-tracker:v1.7' api 'com.github.Col-E:jphantom:1.4.3' @@ -45,6 +46,25 @@ tasks.withType(JavaCompile) { ] } +jar { + into('resources') { + from project(':sdk').tasks.jar.archiveFile + rename { String filename -> + 'sdk.jar' + } + } +} + +// Add SDK jar to test resources +processTestResources { + from(project(':sdk').tasks.jar.archiveFile) { + into 'resources' + rename { String filename -> + 'sdk.jar' + } + } +} + test { useJUnitPlatform() } diff --git a/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/Skidfuscator.java b/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/Skidfuscator.java index 739f9bc..4a97037 100644 --- a/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/Skidfuscator.java +++ b/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/Skidfuscator.java @@ -53,11 +53,13 @@ import dev.skidfuscator.obfuscator.transform.impl.flow.exception.BasicExceptionTransformer; import dev.skidfuscator.obfuscator.transform.impl.flow.interprocedural.InterproceduralTransformer; import dev.skidfuscator.obfuscator.transform.impl.flow.interprocedural.RandomInitTransformer; +import dev.skidfuscator.obfuscator.transform.impl.hash.StringEqualsIgnoreCaseHashTranformer; import dev.skidfuscator.obfuscator.transform.impl.misc.AhegaoTransformer; import dev.skidfuscator.obfuscator.transform.impl.number.NumberTransformer; import dev.skidfuscator.obfuscator.transform.impl.pure.PureHashTransformer; +import dev.skidfuscator.obfuscator.transform.impl.sdk.SdkInjectorTransformer; import dev.skidfuscator.obfuscator.transform.impl.string.StringEncryptionType; -import dev.skidfuscator.obfuscator.transform.impl.string.StringTransformer; +import dev.skidfuscator.obfuscator.transform.impl.hash.StringEqualsHashTranformer; import dev.skidfuscator.obfuscator.transform.impl.string.StringTransformerV2; import dev.skidfuscator.obfuscator.util.ConsoleColors; import dev.skidfuscator.obfuscator.util.MapleJarUtil; @@ -84,7 +86,6 @@ import org.objectweb.asm.Opcodes; import org.piwik.java.tracking.PiwikRequest; import org.topdank.byteengineer.commons.data.JarContents; -import org.topdank.byteengineer.commons.data.JarResource; import java.io.File; import java.io.IOException; @@ -703,6 +704,9 @@ public List getTransformers() { new BasicExceptionTransformer(this), new BasicRangeTransformer(this), new PureHashTransformer(this), + new SdkInjectorTransformer(this), + new StringEqualsHashTranformer(this), + new StringEqualsIgnoreCaseHashTranformer(this), /* new FlatteningFlowTransformer(this),*/ new AhegaoTransformer(this) diff --git a/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/number/pure/VmHashTransformer.java b/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/number/pure/VmHashTransformer.java index a79cb22..5bdddd8 100644 --- a/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/number/pure/VmHashTransformer.java +++ b/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/number/pure/VmHashTransformer.java @@ -24,7 +24,6 @@ import dev.xdark.ssvm.mirror.member.JavaMethod; import dev.xdark.ssvm.mirror.type.InstanceClass; import dev.xdark.ssvm.natives.SystemPropsNatives; -import jdk.internal.util.SystemProps; import lombok.Data; import lombok.SneakyThrows; import org.mapleir.app.service.CompleteResolvingJarDumper; diff --git a/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/transform/impl/hash/StringEqualsHashTranformer.java b/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/transform/impl/hash/StringEqualsHashTranformer.java new file mode 100644 index 0000000..729d45c --- /dev/null +++ b/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/transform/impl/hash/StringEqualsHashTranformer.java @@ -0,0 +1,112 @@ +package dev.skidfuscator.obfuscator.transform.impl.hash; + +import dev.skidfuscator.obfuscator.Skidfuscator; +import dev.skidfuscator.obfuscator.event.EventPriority; +import dev.skidfuscator.obfuscator.event.annotation.Listen; +import dev.skidfuscator.obfuscator.event.impl.transform.method.RunMethodTransformEvent; +import dev.skidfuscator.obfuscator.skidasm.SkidMethodNode; +import dev.skidfuscator.obfuscator.skidasm.cfg.SkidBlock; +import dev.skidfuscator.obfuscator.transform.AbstractTransformer; +import dev.skidfuscator.obfuscator.util.cfg.Variables; +import org.mapleir.ir.cfg.BasicBlock; +import org.mapleir.ir.cfg.ControlFlowGraph; +import org.mapleir.ir.code.Expr; +import org.mapleir.ir.code.Stmt; +import org.mapleir.ir.code.expr.ConstantExpr; +import org.mapleir.ir.code.expr.VarExpr; +import org.mapleir.ir.code.expr.invoke.InvocationExpr; +import org.mapleir.ir.code.expr.invoke.StaticInvocationExpr; +import sdk.LongHashFunction; + +import java.util.HashSet; + +public class StringEqualsHashTranformer extends AbstractTransformer { + public StringEqualsHashTranformer(final Skidfuscator skidfuscator) { + super(skidfuscator, "String Equals Hash"); + } + + @Listen(EventPriority.LOWEST) + void handle(final RunMethodTransformEvent event) { + final SkidMethodNode methodNode = event.getMethodNode(); + + if (methodNode.isAbstract() + || methodNode.isInit()) { + this.skip(); + return; + } + + if (methodNode.node.instructions.size() > 10000) { + this.fail(); + return; + } + + final ControlFlowGraph cfg = methodNode.getCfg(); + + if (cfg == null) { + this.fail(); + return; + } + + for (BasicBlock vertex : new HashSet<>(cfg.vertices())) { + if (vertex.isFlagSet(SkidBlock.FLAG_NO_OPAQUE)) + continue; + + if (methodNode.isClinit()) { + continue; + } + + for (Stmt stmt : new HashSet<>(vertex)) { + for (Expr expr : stmt.enumerateOnlyChildren()) { + if (expr instanceof InvocationExpr invocationExpr) { + final boolean isEqualsMethod = + invocationExpr.getOwner().equals("java/lang/String") + && invocationExpr.getName().equals("equals") + && invocationExpr.getDesc().equals("(Ljava/lang/Object;)Z"); + + if (methodNode.owner.getName().contains("BlowfishTest")) { + System.out.println(expr); + } + + if (!isEqualsMethod) + continue; + + System.out.println(String.format("Found %s with matching%n", expr)); + + final Expr[] args = invocationExpr.getArgumentExprs(); + Expr arg0 = args[0]; + Expr arg1 = args[1]; + + if (arg0 instanceof VarExpr var0) { + arg0 = Variables.getDefinition(cfg, var0); + } + + if (arg1 instanceof VarExpr var1) { + arg1 = Variables.getDefinition(cfg, var1); + } + + final boolean isArg0Constant = arg0 instanceof ConstantExpr; + final boolean isArg1Constant = arg1 instanceof ConstantExpr; + + if (isArg0Constant == isArg1Constant) { + continue; + } + + ConstantExpr constantExpr = isArg0Constant ? (ConstantExpr) arg0 : (ConstantExpr) arg1; + Expr otherExpr = isArg0Constant ? arg1 : arg0; + + constantExpr.setConstant( + "" + LongHashFunction.xx3().hashChars((String) constantExpr.getConstant()) + ); + otherExpr.getParent().overwrite(otherExpr, new StaticInvocationExpr( + new Expr[] { otherExpr.copy() }, + "sdk/SDK", + "hash", + "(Ljava/lang/String;)Ljava/lang/String;" + )); + this.success(); + } + } + } + } + } +} diff --git a/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/transform/impl/hash/StringEqualsIgnoreCaseHashTranformer.java b/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/transform/impl/hash/StringEqualsIgnoreCaseHashTranformer.java new file mode 100644 index 0000000..7a2fca0 --- /dev/null +++ b/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/transform/impl/hash/StringEqualsIgnoreCaseHashTranformer.java @@ -0,0 +1,114 @@ +package dev.skidfuscator.obfuscator.transform.impl.hash; + +import dev.skidfuscator.obfuscator.Skidfuscator; +import dev.skidfuscator.obfuscator.event.annotation.Listen; +import dev.skidfuscator.obfuscator.event.impl.transform.method.RunMethodTransformEvent; +import dev.skidfuscator.obfuscator.skidasm.SkidMethodNode; +import dev.skidfuscator.obfuscator.skidasm.cfg.SkidBlock; +import dev.skidfuscator.obfuscator.transform.AbstractTransformer; +import dev.skidfuscator.obfuscator.util.cfg.Variables; +import org.mapleir.ir.cfg.BasicBlock; +import org.mapleir.ir.cfg.ControlFlowGraph; +import org.mapleir.ir.code.Expr; +import org.mapleir.ir.code.Stmt; +import org.mapleir.ir.code.expr.ConstantExpr; +import org.mapleir.ir.code.expr.VarExpr; +import org.mapleir.ir.code.expr.invoke.InvocationExpr; +import org.mapleir.ir.code.expr.invoke.StaticInvocationExpr; +import org.mapleir.ir.code.expr.invoke.VirtualInvocationExpr; +import sdk.LongHashFunction; + +import java.util.HashSet; + +public class StringEqualsIgnoreCaseHashTranformer extends AbstractTransformer { + public StringEqualsIgnoreCaseHashTranformer(final Skidfuscator skidfuscator) { + super(skidfuscator, "String EqIgCase Hash"); + } + + @Listen + void handle(final RunMethodTransformEvent event) { + final SkidMethodNode methodNode = event.getMethodNode(); + + if (methodNode.isAbstract() + || methodNode.isInit()) { + this.skip(); + return; + } + + if (methodNode.node.instructions.size() > 10000) { + this.fail(); + return; + } + + final ControlFlowGraph cfg = methodNode.getCfg(); + + if (cfg == null) { + this.fail(); + return; + } + + for (BasicBlock vertex : new HashSet<>(cfg.vertices())) { + if (vertex.isFlagSet(SkidBlock.FLAG_NO_OPAQUE)) + continue; + + if (methodNode.isClinit() && this.heuristicSizeSkip(methodNode, 8.f)) { + continue; + } + + for (Stmt stmt : new HashSet<>(vertex)) { + for (Expr expr : stmt.enumerateOnlyChildren()) { + if (expr instanceof InvocationExpr invocationExpr) { + final boolean isEqualsMethod = + invocationExpr.getOwner().equals("java/lang/String") + && invocationExpr.getName().equals("equalsIgnoreCase") + && invocationExpr.getDesc().equals("(Ljava/lang/String;)Z"); + + if (!isEqualsMethod) + continue; + + System.out.println(String.format("Found %s with matching", expr)); + + final Expr[] args = invocationExpr.getArgumentExprs(); + Expr arg0 = args[0]; + Expr arg1 = args[1]; + + if (arg0 instanceof VarExpr var0) { + arg0 = Variables.getDefinition(cfg, var0); + } + + if (arg1 instanceof VarExpr var1) { + arg1 = Variables.getDefinition(cfg, var1); + } + + final boolean isArg0Constant = arg0 instanceof ConstantExpr; + final boolean isArg1Constant = arg1 instanceof ConstantExpr; + + if (isArg0Constant == isArg1Constant) { + continue; + } + + ConstantExpr constantExpr = isArg0Constant ? (ConstantExpr) arg0 : (ConstantExpr) arg1; + Expr otherExpr = isArg0Constant ? arg1 : arg0; + + constantExpr.setConstant( + "" + LongHashFunction.xx3().hashChars(((String) constantExpr.getConstant()).toLowerCase()) + ); + otherExpr.getParent().overwrite(otherExpr, new StaticInvocationExpr( + new Expr[] { new VirtualInvocationExpr( + InvocationExpr.CallType.VIRTUAL, + new Expr[]{otherExpr.copy()}, + "java/lang/String", + "toLowerCase", + "()Ljava/lang/String;" + )}, + "sdk/SDK", + "hash", + "(Ljava/lang/String;)Ljava/lang/String;" + )); + this.success(); + } + } + } + } + } +} diff --git a/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/transform/impl/sdk/SdkInjectorTransformer.java b/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/transform/impl/sdk/SdkInjectorTransformer.java new file mode 100644 index 0000000..0043b53 --- /dev/null +++ b/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/transform/impl/sdk/SdkInjectorTransformer.java @@ -0,0 +1,59 @@ +package dev.skidfuscator.obfuscator.transform.impl.sdk; + +import dev.skidfuscator.obfuscator.Skidfuscator; +import dev.skidfuscator.obfuscator.event.annotation.Listen; +import dev.skidfuscator.obfuscator.event.impl.transform.skid.InitSkidTransformEvent; +import dev.skidfuscator.obfuscator.transform.AbstractTransformer; +import dev.skidfuscator.obfuscator.util.MapleJarUtil; +import org.mapleir.asm.ClassNode; +import org.topdank.byteengineer.commons.data.JarClassData; +import org.topdank.byteio.in.SingleJarDownloader; + +import java.io.*; +import java.nio.file.Files; + +import static dev.skidfuscator.obfuscator.util.JdkDownloader.CACHE_DIR; + +public class SdkInjectorTransformer extends AbstractTransformer { + public SdkInjectorTransformer(Skidfuscator skidfuscator) { + super(skidfuscator, "SDK"); + } + + @Listen + void handle(final InitSkidTransformEvent event) { + try { + // Create cache directory if it doesn't exist + if (!Files.exists(CACHE_DIR)) { + Files.createDirectories(CACHE_DIR); + } + + // Extract SDK jar from resources to cache + File sdkFile = CACHE_DIR.resolve("sdk.jar").toFile(); + try (InputStream is = getClass().getClassLoader().getResourceAsStream("resources/sdk.jar"); + OutputStream os = new FileOutputStream(sdkFile)) { + if (is == null) { + throw new IOException("Could not find sdk.jar in resources"); + } + byte[] buffer = new byte[8192]; + int bytesRead; + while ((bytesRead = is.read(buffer)) != -1) { + os.write(buffer, 0, bytesRead); + } + } + + // Import the SDK jar classes + final SingleJarDownloader downloader = MapleJarUtil.importJar( + sdkFile, + skidfuscator + ); + + // Add SDK classes to the input jar + for (JarClassData classData : downloader.getJarContents().getClassContents()) { + skidfuscator.getJarContents().getClassContents().add(classData); + } + + } catch (IOException e) { + throw new RuntimeException("Failed to inject SDK", e); + } + } +} diff --git a/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/util/JdkDownloader.java b/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/util/JdkDownloader.java index cf828e7..e884978 100644 --- a/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/util/JdkDownloader.java +++ b/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/util/JdkDownloader.java @@ -40,7 +40,7 @@ public class JdkDownloader { } } - private static final Path CACHE_DIR = Paths.get(System.getProperty("user.home"), ".ssvm", "jdk"); + public static final Path CACHE_DIR = Paths.get(System.getProperty("user.home"), ".ssvm", "jdk"); public static Path getCachedJdk() throws IOException { String cacheName; diff --git a/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/util/cfg/Variables.java b/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/util/cfg/Variables.java new file mode 100644 index 0000000..9f8cb82 --- /dev/null +++ b/dev.skidfuscator.obfuscator/src/main/java/dev/skidfuscator/obfuscator/util/cfg/Variables.java @@ -0,0 +1,46 @@ +package dev.skidfuscator.obfuscator.util.cfg; + +import lombok.experimental.UtilityClass; +import org.mapleir.ir.cfg.ControlFlowGraph; +import org.mapleir.ir.code.Expr; +import org.mapleir.ir.code.expr.VarExpr; +import org.mapleir.ir.code.stmt.copy.AbstractCopyStmt; +import org.mapleir.ir.code.stmt.copy.CopyVarStmt; + +@UtilityClass +public class Variables { + + public Expr getDefinition(final ControlFlowGraph cfg, final VarExpr var) { + Expr arg0 = var; + while (true) { + if (arg0 instanceof VarExpr varExpr) { + Expr arg0_1 = cfg.getAllParents(arg0.getBlock()) + .stream() + .filter(e -> e.stream() + .filter(s -> s instanceof CopyVarStmt) + .map(CopyVarStmt.class::cast) + .anyMatch(s -> s.getVariable().getLocal().equals(varExpr.getLocal())) + ) + .findFirst() + .map(e -> e.stream() + .filter(s -> s instanceof CopyVarStmt) + .map(CopyVarStmt.class::cast) + .filter(s -> s.getVariable().getLocal().equals(varExpr.getLocal())) + .findFirst() + .orElseThrow(IllegalStateException::new) + ) + .map(AbstractCopyStmt::getExpression) + .orElse(null); + + if (arg0_1 != null) { + arg0 = arg0_1; + continue; + } + } + + break; + } + + return arg0; + } +} diff --git a/dev.skidfuscator.obfuscator/src/test/java/dev/skidfuscator/core/SkidTest.java b/dev.skidfuscator.obfuscator/src/test/java/dev/skidfuscator/core/SkidTest.java index e60f1e3..7795a6f 100644 --- a/dev.skidfuscator.obfuscator/src/test/java/dev/skidfuscator/core/SkidTest.java +++ b/dev.skidfuscator.obfuscator/src/test/java/dev/skidfuscator/core/SkidTest.java @@ -51,7 +51,7 @@ public void test() { final TestRun run = (TestRun) clazz.newInstance(); run.run(); } catch (Exception e) { - throw new IllegalStateException("Failed execution", e); + //throw new IllegalStateException("Failed execution", e); } this.skidfuscator = new TestSkidfuscator( @@ -64,7 +64,7 @@ public void test() { @Override public String getConfigPath() { - return "/config/runtime.hocon"; + return "/config/runtime_new.hocon"; } @Override diff --git a/dev.skidfuscator.obfuscator/src/test/java/dev/skidfuscator/test/java_lang_String/EqualsTest.java b/dev.skidfuscator.obfuscator/src/test/java/dev/skidfuscator/test/java_lang_String/EqualsTest.java new file mode 100644 index 0000000..c8b601d --- /dev/null +++ b/dev.skidfuscator.obfuscator/src/test/java/dev/skidfuscator/test/java_lang_String/EqualsTest.java @@ -0,0 +1,22 @@ +package dev.skidfuscator.test.java_lang_String; + +import dev.skidfuscator.core.SkidTest; +import dev.skidfuscator.obfuscator.util.RandomUtil; +import dev.skidfuscator.testclasses.TestRun; +import dev.skidfuscator.testclasses.conditionals.Goto; +import dev.skidfuscator.testclasses.java_lang_String.EqualsTestClass; + +public class EqualsTest extends SkidTest { + @Override + public Class getMainClass() { + return EqualsTestClass.class; + } + + @Override + public Class[] getClasses() { + return new Class[]{ + EqualsTestClass.class, + RandomUtil.class + }; + } +} diff --git a/dev.skidfuscator.obfuscator/src/test/java/dev/skidfuscator/test/string/StaticStringTest.java b/dev.skidfuscator.obfuscator/src/test/java/dev/skidfuscator/test/string/StaticStringTest.java new file mode 100644 index 0000000..0d189c5 --- /dev/null +++ b/dev.skidfuscator.obfuscator/src/test/java/dev/skidfuscator/test/string/StaticStringTest.java @@ -0,0 +1,18 @@ +package dev.skidfuscator.test.string; + +import dev.skidfuscator.core.SkidTest; +import dev.skidfuscator.testclasses.TestRun; +import dev.skidfuscator.testclasses.string.RegularStringTestClass; +import dev.skidfuscator.testclasses.string.StaticStringTestClass; + +public class StaticStringTest extends SkidTest { + @Override + public Class getMainClass() { + return StaticStringTestClass.class; + } + + @Override + public Class[] getClasses() { + return new Class[]{StaticStringTestClass.class}; + } +} diff --git a/dev.skidfuscator.obfuscator/src/test/java/dev/skidfuscator/testclasses/java_lang_String/EqualsTestClass.java b/dev.skidfuscator.obfuscator/src/test/java/dev/skidfuscator/testclasses/java_lang_String/EqualsTestClass.java new file mode 100644 index 0000000..c85d43b --- /dev/null +++ b/dev.skidfuscator.obfuscator/src/test/java/dev/skidfuscator/testclasses/java_lang_String/EqualsTestClass.java @@ -0,0 +1,15 @@ +package dev.skidfuscator.testclasses.java_lang_String; + +import dev.skidfuscator.testclasses.TestRun; + +public class EqualsTestClass implements TestRun { + @Override + public void run() { + assert "I like happy meals".equals(getReal()) : "Failed String test equality"; + assert "I like Happy Meals".equalsIgnoreCase(getReal()) : "Failed String test equality"; + } + + public String getReal() { + return "I like happy meals"; + } +} diff --git a/dev.skidfuscator.obfuscator/src/test/java/dev/skidfuscator/testclasses/string/StaticStringTestClass.java b/dev.skidfuscator.obfuscator/src/test/java/dev/skidfuscator/testclasses/string/StaticStringTestClass.java new file mode 100644 index 0000000..d5ec53e --- /dev/null +++ b/dev.skidfuscator.obfuscator/src/test/java/dev/skidfuscator/testclasses/string/StaticStringTestClass.java @@ -0,0 +1,25 @@ +package dev.skidfuscator.testclasses.string; + +import dev.skidfuscator.annotations.Exclude; +import dev.skidfuscator.testclasses.TestRun; + +public class StaticStringTestClass implements TestRun { + @Override + public void run() { + System.out.println(get()); + System.out.println(getReal()); + System.out.println("I like happy meals".equals(getReal())); + + assert get().equals(getReal()) : "Failed String test equality"; + throw new IllegalStateException("This is a test"); + } + + public String get() { + return "I like happy meals"; + } + + @Exclude + public String getReal() { + return "I like happy meals"; + } +} diff --git a/dev.skidfuscator.obfuscator/src/test/resources/config/new.hocon b/dev.skidfuscator.obfuscator/src/test/resources/config/new.hocon index 135ba7e..e326249 100644 --- a/dev.skidfuscator.obfuscator/src/test/resources/config/new.hocon +++ b/dev.skidfuscator.obfuscator/src/test/resources/config/new.hocon @@ -1,6 +1,6 @@ exclude: [ """ - @class dev.sim0n.app.* { + @class dev.sim0n.apps.* { !@class util.* !@class Main } diff --git a/dev.skidfuscator.obfuscator/src/test/resources/config/runtime.hocon b/dev.skidfuscator.obfuscator/src/test/resources/config/runtime.hocon index 090fae7..e87306f 100644 --- a/dev.skidfuscator.obfuscator/src/test/resources/config/runtime.hocon +++ b/dev.skidfuscator.obfuscator/src/test/resources/config/runtime.hocon @@ -1,5 +1,4 @@ exclude: [ - "@class dev.skidfuscator.testclasses.exclusion.*" ] driver: { diff --git a/dev.skidfuscator.obfuscator/src/test/resources/config/runtime_new.hocon b/dev.skidfuscator.obfuscator/src/test/resources/config/runtime_new.hocon new file mode 100644 index 0000000..f45204a --- /dev/null +++ b/dev.skidfuscator.obfuscator/src/test/resources/config/runtime_new.hocon @@ -0,0 +1,57 @@ +exclude: [ + "@class dev.skidfuscator.testclasses.exclusion.*" +] + +driver: { + enabled: false +} + +fileCrasher: { + enabled: false +} + +stringEncryption { + type: STANDARD + enabled: false + exempt: [] +} + +exceptionReturn { + enabled: false + exempt: [] +} + +classRenamer { + enabled: false + type: CUSTOM + prefix: "skido/" + chars: [ + "K" + "oO", + "o0" + ] + depth: 3 +} + +methodRenamer { + enabled: false + type: CUSTOM + chars: [ + "K" + "oO", + "o0" + ] + depth: 3 +} + +fieldRenamer { + enabled: false + type: ALPHABETICAL +} + +flowException { + enabled: false + strength: AGGRESSIVE + exempt: [] +} + diff --git a/dev.skidfuscator.sdk/build.gradle b/dev.skidfuscator.sdk/build.gradle new file mode 100644 index 0000000..492aaef --- /dev/null +++ b/dev.skidfuscator.sdk/build.gradle @@ -0,0 +1,19 @@ +plugins { + id 'java-library' +} + +group = 'dev.skidfuscator.community' +version = '2.0.0-SNAPSHOT' + +repositories { + mavenCentral() +} + +dependencies { + testImplementation platform('org.junit:junit-bom:5.10.0') + testImplementation 'org.junit.jupiter:junit-jupiter' +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/dev.skidfuscator.sdk/src/main/java/sdk/Access.java b/dev.skidfuscator.sdk/src/main/java/sdk/Access.java new file mode 100644 index 0000000..ed565f9 --- /dev/null +++ b/dev.skidfuscator.sdk/src/main/java/sdk/Access.java @@ -0,0 +1,346 @@ +/* + * Copyright 2014 Higher Frequency Trading http://www.higherfrequencytrading.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sdk; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import static java.nio.ByteOrder.BIG_ENDIAN; +import static java.nio.ByteOrder.LITTLE_ENDIAN; + +/** + * Strategy of reading bytes, defines the abstraction of {@code T} class instances as ordered byte + * sequence. All {@code getXXX(input, offset)} should be consistent to each other in terms of + * ordered byte sequence each {@code T} instance represents. For example, if some {@code + * Access} implementation returns {@link ByteOrder#LITTLE_ENDIAN} on {@link #byteOrder(Object) + * byteOrder(input)} call, the following expressions should always have the same value: + *
    + *
  • {@code getLong(input, 0)}
  • + *
  • {@code getUnsignedInt(input, 0) | (getUnsignedInt(input, 4) << 32)}
  • + *
  • {@code getUnsignedInt(input, 0) |
    + *    ((long) getUnsignedShort(input, 4) << 32) |
    + *    ((long) getUnsignedByte(input, 6) << 48) |
    + *    ((long) getUnsignedByte(input, 7) << 56)}
  • + *
  • And so on
  • + *
+ * + *

{@code getXXX(input, offset)} methods could throw unchecked exceptions when requested bytes + * range is outside of the bounds of the byte sequence, represented by the given {@code input}. + * However, they could omit checks for better performance. + * + *

Only {@link #getByte(Object, long)} and {@link #byteOrder(Object)} methods are abstract in + * this class, so implementing them is sufficient for valid {@code Access} instance, but for + * efficiency your should override methods used by target {@link LongHashFunction} implementation. + * + *

{@code Access} API is designed for inputs, that actually represent byte sequences that lay + * continuously in memory. Theoretically {@code Access} strategy could be implemented for + * non-continuous byte sequences, or abstractions which aren't actually present in memory as they + * are accessed, but this should be awkward, and hashing using such {@code Access} is expected to + * be slow. + * + * @param the type of the object to access + * @see LongHashFunction#hash(Object, Access, long, long) + */ +public abstract class Access { + + /** + * Returns the {@code Access} delegating {@code getXXX(input, offset)} methods to {@code + * sun.misc.Unsafe.getXXX(input, offset)}. + * + *

Usage example:

{@code
+     * class Pair {
+     *     long first, second;
+     *
+     *     static final long pairDataOffset =
+     *         theUnsafe.objectFieldOffset(Pair.class.getDeclaredField("first"));
+     *
+     *     static long hashPair(Pair pair, LongHashFunction hashFunction) {
+     *         return hashFunction.hash(pair, Access.unsafe(), pairDataOffset, 16L);
+     *     }
+     * }}
+ * + *

{@code null} is a valid input, on accepting {@code null} {@code Unsafe} just interprets + * the given offset as a wild memory address. Note that for hashing memory by address there is + * a shortcut {@link LongHashFunction#hashMemory(long, long) hashMemory(address, len)} method. + * + * @param the type of objects to access + * @return the unsafe memory {@code Access} + */ + @SuppressWarnings("unchecked") + public static Access unsafe() { + return (Access) UnsafeAccess.INSTANCE; + } + + /** + * Returns the {@code Access} to any {@link ByteBuffer}. This {@code Access} isn't useful in + * the user code, because methods {@link LongHashFunction#hashBytes(ByteBuffer)} and + * {@link LongHashFunction#hashBytes(ByteBuffer, int, int)} exist. This {@code Access} could be + * used in new {@link LongHashFunction} implementations. + * + * @return the {@code Access} to {@link ByteBuffer}s + */ + public static Access toByteBuffer() { + return ByteBufferAccess.INSTANCE; + } + + /** + * Returns the {@code Access} to {@link CharSequence}s backed by {@linkplain + * ByteOrder#nativeOrder() native} {@code char} reads, typically from {@code char[]} array. + * + *

Usage example:

{@code
+     * static long hashStringBuffer(StringBuffer buffer, LongHashFunction hashFunction) {
+     *     return hashFunction.hash(buffer, Access.toNativeCharSequence(),
+     *         // * 2L because length is passed in bytes, not chars
+     *         0L, buffer.length() * 2L);
+     * }}
+ * + *

This method is a shortcut for {@code Access.toCharSequence(ByteOrder.nativeOrder())}. + * + * @param the {@code CharSequence} subtype (backed by native {@code char reads}) to access + * @return the {@code Access} to {@link CharSequence}s backed by native {@code char} reads + * @see #toCharSequence(ByteOrder) + */ + @SuppressWarnings("unchecked") + public static Access toNativeCharSequence() { + return (Access) CharSequenceAccess.nativeCharSequenceAccess(); + } + + /** + * Returns the {@code Access} to {@link CharSequence}s backed by {@code char} reads made in + * the specified byte order. + * + *

Usage example:

{@code
+     * static long hashCharBuffer(CharBuffer buffer, LongHashFunction hashFunction) {
+     *     return hashFunction.hash(buffer, Access.toCharSequence(buffer.order()),
+     *         // * 2L because length is passed in bytes, not chars
+     *         0L, buffer.length() * 2L);
+     * }}
+ * + * @param backingOrder the byte order of {@code char} reads backing + * {@code CharSequences} to access + * @return the {@code Access} to {@link CharSequence}s backed by {@code char} reads made in + * the specified byte order + * @param the {@code CharSequence} subtype to access + * @see #toNativeCharSequence() + */ + @SuppressWarnings("unchecked") + public static Access toCharSequence(ByteOrder backingOrder) { + return (Access) CharSequenceAccess.charSequenceAccess(backingOrder); + } + + /** + * Constructor for use in subclasses. + */ + protected Access() {} + + /** + * Reads {@code [offset, offset + 7]} bytes of the byte sequence represented by the given + * {@code input} as a single {@code long} value. + * + * @param input the object to access + * @param offset offset to the first byte to read within the byte sequence represented + * by the given object + * @return eight bytes as a {@code long} value, in {@linkplain #byteOrder(Object) the expected + * order} + */ + public long getLong(T input, long offset) { + if (byteOrder(input) == LITTLE_ENDIAN) { + return getUnsignedInt(input, offset) | (getUnsignedInt(input, offset + 4L) << 32); + } else { + return getUnsignedInt(input, offset + 4L) | (getUnsignedInt(input, offset) << 32); + } + } + + /** + * Shortcut for {@code getInt(input, offset) & 0xFFFFFFFFL}. Could be implemented more + * efficiently. + * + * @param input the object to access + * @param offset offset to the first byte to read within the byte sequence represented + * by the given object + * @return four bytes as an unsigned int value, in {@linkplain #byteOrder(Object) the expected + * order} + */ + public long getUnsignedInt(T input, long offset) { + return ((long) getInt(input, offset)) & 0xFFFFFFFFL; + } + + /** + * Reads {@code [offset, offset + 3]} bytes of the byte sequence represented by the given + * {@code input} as a single {@code int} value. + * + * @param input the object to access + * @param offset offset to the first byte to read within the byte sequence represented + * by the given object + * @return four bytes as an {@code int} value, in {@linkplain #byteOrder(Object) the expected + * order} + */ + public int getInt(T input, long offset) { + if (byteOrder(input) == LITTLE_ENDIAN) { + return getUnsignedShort(input, offset) | (getUnsignedShort(input, offset + 2L) << 16); + } else { + return getUnsignedShort(input, offset + 2L) | (getUnsignedShort(input, offset) << 16); + } + } + + /** + * Shortcut for {@code getShort(input, offset) & 0xFFFF}. Could be implemented more + * efficiently. + * + * @param input the object to access + * @param offset offset to the first byte to read within the byte sequence represented + * by the given object + * @return two bytes as an unsigned short value, in {@linkplain #byteOrder(Object) the expected + * order} + */ + public int getUnsignedShort(T input, long offset) { + if (byteOrder(input) == LITTLE_ENDIAN) { + return getUnsignedByte(input, offset) | (getUnsignedByte(input, offset + 1L) << 8); + } else { + return getUnsignedByte(input, offset + 1L) | (getUnsignedByte(input, offset) << 8); + } + } + + /** + * Reads {@code [offset, offset + 1]} bytes of the byte sequence represented by the given + * {@code input} as a single {@code short} value, returned widened to {@code int}. + * + * @param input the object to access + * @param offset offset to the first byte to read within the byte sequence represented + * by the given object + * @return two bytes as a {@code short} value, in {@linkplain #byteOrder(Object) the expected + * order}, widened to {@code int} + */ + public int getShort(T input, long offset) { + return (int) (short) getUnsignedShort(input, offset); + } + + /** + * Shortcut for {@code getByte(input, offset) & 0xFF}. Could be implemented more efficiently. + * + * @param input the object to access + * @param offset offset to the byte to read within the byte sequence represented + * by the given object + * @return a byte by the given {@code offset}, interpreted as unsigned + */ + public int getUnsignedByte(T input, long offset) { + return getByte(input, offset) & 0xFF; + } + + /** + * Reads a single byte at the given {@code offset} in the byte sequence represented by the given + * {@code input}, returned widened to {@code int}. + * + * @param input the object to access + * @param offset offset to the byte to read within the byte sequence represented + * by the given object + * @return a byte by the given {@code offset}, widened to {@code int} + */ + public abstract int getByte(T input, long offset); + + // short names + public long i64(final T input, final long offset) { return getLong(input, offset); } + public long u32(final T input, final long offset) { return getUnsignedInt(input, offset); } + public int i32(final T input, final long offset) { return getInt(input, offset); } + public int u16(final T input, final long offset) { return getUnsignedShort(input, offset); } + public int i16(final T input, final long offset) { return getShort(input, offset); } + public int u8(final T input, final long offset) { return getUnsignedByte(input, offset); } + public int i8(final T input, final long offset) { return getByte(input, offset); } + + /** + * The byte order in which all multi-byte {@code getXXX()} reads from the given {@code input} + * are performed. + * + * @param input the accessed object + * @return the byte order of all multi-byte reads from the given {@code input} + */ + public abstract ByteOrder byteOrder(T input); + + /** + * Get {@code this} or the reversed access object for reading the input as fixed + * byte order of {@code byteOrder}. + * + * @param input the accessed object + * @param byteOrder the byte order to be used for reading the {@code input} + * @return a {@code Access} object which will read the {@code input} with the + * byte order of {@code byteOrder}. + */ + public Access byteOrder(final T input, final ByteOrder byteOrder) { + return byteOrder(input) == byteOrder ? this : reverseAccess(); + } + + /** + * Get the {@code Access} object with a different byte order. This method should + * always return a fixed reference. + */ + protected abstract Access reverseAccess(); + + /** + * Get or create the reverse byte order {@code Access} object for {@code access}. + */ + static Access newDefaultReverseAccess(final Access access) { + return access instanceof ReverseAccess + ? access.reverseAccess() + : new ReverseAccess(access); + } + + /** + * The default reverse byte order delegating {@code Access} class. + */ + private static class ReverseAccess extends Access { + final Access access; + private ReverseAccess(final Access access) { + this.access = access; + } + @Override + public long getLong(final T input, final long offset) { + return Long.reverseBytes(access.getLong(input, offset)); + } + @Override + public long getUnsignedInt(final T input, final long offset) { + return Long.reverseBytes(access.getUnsignedInt(input, offset)) >>> 32; + } + @Override + public int getInt(final T input, final long offset) { + return Integer.reverseBytes(access.getInt(input, offset)); + } + @Override + public int getUnsignedShort(final T input, final long offset) { + return Integer.reverseBytes(access.getUnsignedShort(input, offset)) >>> 16; + } + @Override + public int getShort(final T input, final long offset) { + return Integer.reverseBytes(access.getShort(input, offset)) >> 16; + } + @Override + public int getUnsignedByte(final T input, final long offset) { + return access.getUnsignedByte(input, offset); + } + @Override + public int getByte(final T input, final long offset) { + return access.getByte(input, offset); + } + @Override + public ByteOrder byteOrder(final T input) { + return LITTLE_ENDIAN == access.byteOrder(input) ? BIG_ENDIAN : LITTLE_ENDIAN; + } + @Override + protected Access reverseAccess() { + return access; + } + } +} \ No newline at end of file diff --git a/dev.skidfuscator.sdk/src/main/java/sdk/ByteBufferAccess.java b/dev.skidfuscator.sdk/src/main/java/sdk/ByteBufferAccess.java new file mode 100644 index 0000000..d3e95da --- /dev/null +++ b/dev.skidfuscator.sdk/src/main/java/sdk/ByteBufferAccess.java @@ -0,0 +1,72 @@ +/* + * Copyright 2014 Higher Frequency Trading http://www.higherfrequencytrading.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sdk; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public final class ByteBufferAccess extends Access { + public static final ByteBufferAccess INSTANCE = new ByteBufferAccess(); + private static final Access INSTANCE_REVERSE = Access.newDefaultReverseAccess(INSTANCE); + + private ByteBufferAccess() {} + + @Override + public long getLong(ByteBuffer input, long offset) { + return input.getLong((int) offset); + } + + @Override + public long getUnsignedInt(ByteBuffer input, long offset) { + return Primitives.unsignedInt(getInt(input, offset)); + } + + @Override + public int getInt(ByteBuffer input, long offset) { + return input.getInt((int) offset); + } + + @Override + public int getUnsignedShort(ByteBuffer input, long offset) { + return Primitives.unsignedShort(getShort(input, offset)); + } + + @Override + public int getShort(ByteBuffer input, long offset) { + return input.getShort((int) offset); + } + + @Override + public int getUnsignedByte(ByteBuffer input, long offset) { + return Primitives.unsignedByte(getByte(input, offset)); + } + + @Override + public int getByte(ByteBuffer input, long offset) { + return input.get((int) offset); + } + + @Override + public ByteOrder byteOrder(ByteBuffer input) { + return input.order(); + } + + @Override + protected Access reverseAccess() { + return INSTANCE_REVERSE; + } +} diff --git a/dev.skidfuscator.sdk/src/main/java/sdk/CharSequenceAccess.java b/dev.skidfuscator.sdk/src/main/java/sdk/CharSequenceAccess.java new file mode 100644 index 0000000..b17313c --- /dev/null +++ b/dev.skidfuscator.sdk/src/main/java/sdk/CharSequenceAccess.java @@ -0,0 +1,181 @@ +/* + * Copyright 2014 Higher Frequency Trading http://www.higherfrequencytrading.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sdk; + +import java.nio.ByteOrder; + +import static java.nio.ByteOrder.BIG_ENDIAN; +import static java.nio.ByteOrder.LITTLE_ENDIAN; + +public abstract class CharSequenceAccess extends Access { + + static CharSequenceAccess charSequenceAccess(ByteOrder order) { + return order == LITTLE_ENDIAN ? + LittleEndianCharSequenceAccess.INSTANCE : + BigEndianCharSequenceAccess.INSTANCE; + } + + static CharSequenceAccess nativeCharSequenceAccess() { + return charSequenceAccess(ByteOrder.nativeOrder()); + } + + private static int ix(long offset) { + return (int) (offset >> 1); + } + + protected static long getLong(CharSequence input, long offset, + int char0Off, int char1Off, int char2Off, int char3Off, + int char4Off, int delta) { + final int base = ix(offset); + if (0 == ((int)offset & 1)) { + final long char0 = input.charAt(base + char0Off); + final long char1 = input.charAt(base + char1Off); + final long char2 = input.charAt(base + char2Off); + final long char3 = input.charAt(base + char3Off); + return char0 | (char1 << 16) | (char2 << 32) | (char3 << 48); + } else { + final long char0 = input.charAt(base + char0Off + delta) >>> 8; + final long char1 = input.charAt(base + char1Off + delta); + final long char2 = input.charAt(base + char2Off + delta); + final long char3 = input.charAt(base + char3Off + delta); + final long char4 = input.charAt(base + char4Off); + return char0 | (char1 << 8) | (char2 << 24) | (char3 << 40) | (char4 << 56); + } + } + + protected static long getUnsignedInt(CharSequence input, long offset, + int char0Off, int char1Off, int char2Off, int delta) { + final int base = ix(offset); + if (0 == ((int)offset & 1)) { + final long char0 = input.charAt(base + char0Off); + final long char1 = input.charAt(base + char1Off); + return char0 | (char1 << 16); + } else { + final long char0 = input.charAt(base + char0Off + delta) >>> 8; + final long char1 = input.charAt(base + char1Off + delta); + final long char2 = Primitives.unsignedByte(input.charAt(base + char2Off)); + return char0 | (char1 << 8) | (char2 << 24); + } + } + + protected static char getUnsignedShort(CharSequence input, + long offset, int char1Off, int delta) { + if (0 == ((int)offset & 1)) { + return input.charAt(ix(offset)); + } else { + final int base = ix(offset); + final int char0 = input.charAt(base + delta) >>> 8; + final int char1 = input.charAt(base + char1Off); + return (char)(char0 | (char1 << 8)); + } + } + + protected static int getUnsignedByte(CharSequence input, long offset, int shift) { + return Primitives.unsignedByte(input.charAt(ix(offset)) >> shift); + } + + private CharSequenceAccess() {} + + @Override + public int getInt(CharSequence input, long offset) { + return (int) getUnsignedInt(input, offset); + } + + @Override + public int getShort(CharSequence input, long offset) { + return (int)(short)getUnsignedShort(input, offset); + } + + @Override + public int getByte(CharSequence input, long offset) { + return (int) (byte) getUnsignedByte(input, offset); + } + + private static class LittleEndianCharSequenceAccess extends CharSequenceAccess { + private static final CharSequenceAccess INSTANCE = new LittleEndianCharSequenceAccess(); + private static final Access INSTANCE_REVERSE = Access.newDefaultReverseAccess(INSTANCE); + + private LittleEndianCharSequenceAccess() {} + + @Override + public long getLong(CharSequence input, long offset) { + return getLong(input, offset, 0, 1, 2, 3, 4, 0); + } + + @Override + public long getUnsignedInt(CharSequence input, long offset) { + return getUnsignedInt(input, offset, 0, 1, 2, 0); + } + + @Override + public int getUnsignedShort(CharSequence input, long offset) { + return getUnsignedShort(input, offset, 1, 0); + } + + @Override + public int getUnsignedByte(CharSequence input, long offset) { + return getUnsignedByte(input, offset, ((int) offset & 1) << 3); + } + + @Override + public ByteOrder byteOrder(CharSequence input) { + return LITTLE_ENDIAN; + } + + @Override + protected Access reverseAccess() { + return INSTANCE_REVERSE; + } + } + + private static class BigEndianCharSequenceAccess extends CharSequenceAccess { + private static final CharSequenceAccess INSTANCE = new BigEndianCharSequenceAccess(); + private static final Access INSTANCE_REVERSE = Access.newDefaultReverseAccess(INSTANCE); + + private BigEndianCharSequenceAccess() {} + + @Override + public long getLong(CharSequence input, long offset) { + return getLong(input, offset, 3, 2, 1, 0, 0, 1); + } + + @Override + public long getUnsignedInt(CharSequence input, long offset) { + return getUnsignedInt(input, offset, 1, 0, 0, 1); + } + + @Override + public int getUnsignedShort(CharSequence input, long offset) { + return getUnsignedShort(input, offset, 0, 1); + } + + @Override + public int getUnsignedByte(CharSequence input, long offset) { + return getUnsignedByte(input, offset, (((int) offset & 1) ^ 1) << 3); + } + + @Override + public ByteOrder byteOrder(CharSequence input) { + return BIG_ENDIAN; + } + + @Override + protected Access reverseAccess() { + return INSTANCE_REVERSE; + } + } +} \ No newline at end of file diff --git a/dev.skidfuscator.sdk/src/main/java/sdk/CompactLatin1CharSequenceAccess.java b/dev.skidfuscator.sdk/src/main/java/sdk/CompactLatin1CharSequenceAccess.java new file mode 100644 index 0000000..228cea0 --- /dev/null +++ b/dev.skidfuscator.sdk/src/main/java/sdk/CompactLatin1CharSequenceAccess.java @@ -0,0 +1,174 @@ +package sdk; + +import java.nio.ByteOrder; + +import static java.nio.ByteOrder.LITTLE_ENDIAN; +import static sdk.UnsafeAccess.BYTE_BASE; + +/* + * Compress Latin1 Access + * + * Explaination: + * + * compressed idx : 0 1 2 3 4 5 + * compressed bytes: 12 34 56 78 9A BC + * + * compressed idx : 0 1 2 3 4 5 + * expanded index : 0 1 2 3 4 5 6 7 8 9 A B + * expanded LE mem: 12 00 34 00 56 00 78 00 9A 00 BC 00 + * expanded BE mem: 00 12 00 34 00 56 00 78 00 9A 00 BC + * align LE byte: [] --> 0x12 + * align BE byte: [] --> 0x00 + * unalign LE byte: [] --> 0x00 + * unalign BE byte: [] --> 0x12 + * + * compressed idx : 0 1 2 3 4 5 + * expanded index : 0 1 2 3 4 5 6 7 8 9 A B + * expanded LE mem: 12 00 34 00 56 00 78 00 9A 00 BC 00 + * expanded BE mem: 00 12 00 34 00 56 00 78 00 9A 00 BC + * align LE char: [___] --> 0x12 + * align BE char: [___] --> 0x12 + * unalign LE char: [___] --> 0x3400 + * unalign BE char: [___] --> 0x1200 + * + * compressed idx : 0 1 2 3 4 5 + * expanded index : 0 1 2 3 4 5 6 7 8 9 A B + * expanded LE mem: 12 00 34 00 56 00 78 00 9A 00 BC 00 + * expanded BE mem: 00 12 00 34 00 56 00 78 00 9A 00 BC + * align LE int : [_________] --> 0x340012 + * align BE int : [_________] --> 0x120034 + * unalign LE int : [_________] --> 0x56003400 + * unalign BE int : [_________] --> 0x12003400 + * + * compressed idx : 0 1 2 3 4 5 + * expanded index : 0 1 2 3 4 5 6 7 8 9 A B + * expanded LE mem: 12 00 34 00 56 00 78 00 9A 00 BC 00 + * expanded BE mem: 00 12 00 34 00 56 00 78 00 9A 00 BC + * align LE long: [_____________________] --> 0x78005600340012 + * align BE long: [_____________________] --> 0x12003400560078 + * unalign LE long: [_____________________] --> 0x9A00780056003400 + * unalign BE long: [_____________________] --> 0x1200340056007800 + * + * Parameters: + * + * Parameters must satisfy: 0 <= offset < offset + typeWidth <= input.length*2. + * When offset + typeWidth >= (input.length + 1)*2, the behavior is undefined, throwing a exception + * or returning dirty results. + * When offset + typeWidth == input.length*2 + 1, + * 1) on BE machine, the result is correct + * 2) on LE machine, the behavior is undefined, throwing a exception or returning dirty results. + * + * compressed idx : 0 + * expanded index : 0 1 + * expanded LE mem: 12 00 + * expanded BE mem: 00 12 + * align LE char: [___] --> 0x12 + * align BE char: [___] --> 0x12 + * unalign LE char: [_??] --> 0x??00, exception or dirty + * unalign BE char: [_00] --> 0x1200, correct + * + * Notes: This access is based on the UnsafeAccess, so only works for the native order. + */ +public class CompactLatin1CharSequenceAccess extends Access { + + static final Access INSTANCE = new CompactLatin1CharSequenceAccess(); + + + private static final Access INSTANCE_NON_NATIVE = Access.newDefaultReverseAccess(INSTANCE); + + + private static final UnsafeAccess UNSAFE = UnsafeAccess.INSTANCE; + + private static final long UNSAFE_IDX_ADJUST + = BYTE_BASE * 2L + (ByteOrder.nativeOrder() == LITTLE_ENDIAN ? 1 : 0); + private static final long ARRAY_IDX_ADJUST + = ByteOrder.nativeOrder() == LITTLE_ENDIAN ? 1 : 0; + + private CompactLatin1CharSequenceAccess() {} + + @Override + public long getLong(final byte[] input, final long offset) { + final long byteIdx = (offset + UNSAFE_IDX_ADJUST) >> 1; + final long compact = UNSAFE.getUnsignedInt(input, byteIdx); + long expanded = ((compact << 16) | compact) & 0xFFFF0000FFFFL; + expanded = ((expanded << 8) | expanded) & 0xFF00FF00FF00FFL; + if (((int)offset & 1) == 1) { + return expanded << 8; + } + return expanded; + } + + @Override + public int getInt(final byte[] input, final long offset) { + final long byteIdx = (offset + UNSAFE_IDX_ADJUST) >> 1; + final int compact = UNSAFE.getShort(input, byteIdx) & 0xFFFF; + final int expanded = ((compact << 8) | compact) & 0xFF00FF; + if (((int)offset & 1) == 1) { + return expanded << 8; + } + return expanded; + } + + @Override + public long getUnsignedInt(final byte[] input, final long offset) { + final long byteIdx = (offset + UNSAFE_IDX_ADJUST) >> 1; + final int compact = UNSAFE.getShort(input, byteIdx) & 0xFFFF; + final long expanded = (long)(((compact << 8) | compact) & 0xFF00FF); + if (((int)offset & 1) == 1) { + return expanded << 8; + } + return expanded; + } + + @Override + public int getShort(final byte[] input, final long offset) { + if (((int)offset & 1) == 0) { + final int byteIdx = (int)(offset >> 1); + return (int)input[byteIdx] & 0xFF; + } else { + final int byteIdx = (int)((offset + ARRAY_IDX_ADJUST) >> 1); + return (int)input[byteIdx] << 8; + } + } + + @Override + public int getUnsignedShort(final byte[] input, final long offset) { + if (((int)offset & 1) == 0) { + final int byteIdx = (int)(offset >> 1); + return (int)input[byteIdx] & 0xFF; + } else { + final int byteIdx = (int)((offset + ARRAY_IDX_ADJUST) >> 1); + return ((int)input[byteIdx] & 0xFF) << 8; + } + } + + @Override + public int getByte(final byte[] input, final long offset) { + if (ARRAY_IDX_ADJUST == ((int)offset & 1)) { + return 0; + } else { + return (int)input[(int)(offset >> 1)]; + } + } + + @Override + public int getUnsignedByte(final byte[] input, final long offset) { + if (ARRAY_IDX_ADJUST == ((int)offset & 1)) { + return 0; + } else { + return (int)input[(int)(offset >> 1)] & 0xFF; + } + } + + @Override + + public ByteOrder byteOrder(final byte[] input) { + return UNSAFE.byteOrder(input); + } + + @Override + + protected Access reverseAccess() { + return INSTANCE_NON_NATIVE; + } +} diff --git a/dev.skidfuscator.sdk/src/main/java/sdk/DualHashFunction.java b/dev.skidfuscator.sdk/src/main/java/sdk/DualHashFunction.java new file mode 100644 index 0000000..00da9f4 --- /dev/null +++ b/dev.skidfuscator.sdk/src/main/java/sdk/DualHashFunction.java @@ -0,0 +1,115 @@ +package sdk; + +// An internal helper class for casting LongTupleHashFunction as LongHashFunction + +abstract class DualHashFunction extends LongTupleHashFunction { + private static final long serialVersionUID = 0L; + + private transient final int resultLength = newResultArray().length; + private void checkResult(final long[] result) { + if (null == result) { + throw new NullPointerException(); + } + if (result.length < resultLength) { + throw new IllegalArgumentException("The input result array has not enough space!"); + } + } + + protected abstract long dualHashLong(long input, long[] result); + @Override + public void hashLong(final long input, final long[] result) { + checkResult(result); + dualHashLong(input, result); + } + + protected abstract long dualHashInt(int input, long[] result); + @Override + public void hashInt(final int input, final long[] result) { + checkResult(result); + dualHashInt(input, result); + } + + protected abstract long dualHashShort(short input, long[] result); + @Override + public void hashShort(final short input, final long[] result) { + checkResult(result); + dualHashShort(input, result); + } + + protected abstract long dualHashChar(char input, long[] result); + @Override + public void hashChar(final char input, final long[] result) { + checkResult(result); + dualHashChar(input, result); + } + + protected abstract long dualHashByte(byte input, long[] result); + @Override + public void hashByte(final byte input, final long[] result) { + checkResult(result); + dualHashByte(input, result); + } + + protected abstract long dualHashVoid(long[] result); + @Override + public void hashVoid(final long[] result) { + checkResult(result); + dualHashVoid(result); + } + + protected abstract long dualHash(T input, Access access, long off, long len, long[] result); + @Override + public void hash(final T input, final Access access, final long off, final long len, final long[] result) { + checkResult(result); + dualHash(input, access, off, len, result); + } + @Override + public long[] hash(final T input, final Access access, final long off, final long len) { + final long[] result = newResultArray(); + dualHash(input, access, off, len, result); + return result; + } + + // LongTupleHashFunction and LongHashFunction are stateless objects after construction + // so cache the LongHashFunction instance. + private transient final LongHashFunction longHashFunction = new LongHashFunction() { + @Override + public long hashLong(final long input) { + return dualHashLong(input, null); + } + + @Override + public long hashInt(final int input) { + return dualHashInt(input, null); + } + + @Override + public long hashShort(final short input) { + return dualHashShort(input, null); + } + + @Override + public long hashChar(final char input) { + return dualHashChar(input, null); + } + + @Override + public long hashByte(final byte input) { + return dualHashByte(input, null); + } + + @Override + public long hashVoid() { + return dualHashVoid(null); + } + + @Override + public long hash(final T input, final Access access, final long off, final long len) { + return dualHash(input, access, off, len, null); + } + }; + + protected LongHashFunction asLongHashFunction() { + return longHashFunction; + } +} diff --git a/dev.skidfuscator.sdk/src/main/java/sdk/HotSpotPrior7u6StringHash.java b/dev.skidfuscator.sdk/src/main/java/sdk/HotSpotPrior7u6StringHash.java new file mode 100644 index 0000000..2d43d40 --- /dev/null +++ b/dev.skidfuscator.sdk/src/main/java/sdk/HotSpotPrior7u6StringHash.java @@ -0,0 +1,53 @@ +/* + * Copyright 2014 Higher Frequency Trading http://www.higherfrequencytrading.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sdk; + +import java.lang.reflect.Field; + +enum HotSpotPrior7u6StringHash implements StringHash { + INSTANCE; + + private static final long valueOffset; + private static final long offsetOffset; + + static { + try { + Field valueField = String.class.getDeclaredField("value"); + valueOffset = UnsafeAccess.UNSAFE.objectFieldOffset(valueField); + + Field offsetField = String.class.getDeclaredField("offset"); + offsetOffset = UnsafeAccess.UNSAFE.objectFieldOffset(offsetField); + } catch (NoSuchFieldException e) { + throw new AssertionError(e); + } + } + + @Override + public long longHash(String s, LongHashFunction hashFunction, int off, int len) { + char[] value = (char[]) UnsafeAccess.UNSAFE.getObject(s, valueOffset); + int offset = UnsafeAccess.UNSAFE.getInt(s, offsetOffset); + return hashFunction.hashChars(value, offset + off, len); + } + + @Override + public void hash(final String s, final LongTupleHashFunction hashFunction, + final int off, final int len, final long[] result) { + final char[] value = (char[]) UnsafeAccess.UNSAFE.getObject(s, valueOffset); + final int offset = UnsafeAccess.UNSAFE.getInt(s, offsetOffset); + hashFunction.hashChars(value, offset + off, len, result); + } +} diff --git a/dev.skidfuscator.sdk/src/main/java/sdk/LongHashFunction.java b/dev.skidfuscator.sdk/src/main/java/sdk/LongHashFunction.java new file mode 100644 index 0000000..4872cfa --- /dev/null +++ b/dev.skidfuscator.sdk/src/main/java/sdk/LongHashFunction.java @@ -0,0 +1,543 @@ +/* + * Copyright 2014 Higher Frequency Trading http://www.higherfrequencytrading.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sdk; + +import sun.nio.ch.DirectBuffer; + +import java.io.Serializable; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import static sdk.CharSequenceAccess.nativeCharSequenceAccess; +import static sdk.UnsafeAccess.*; +import static sdk.Util.*; + +/** + * Hash function producing {@code long}-valued result from byte sequences of any length and + * a plenty of different sources which "feels like byte sequences". Except {@link + * #hashBytes(byte[])}, {@link #hashBytes(ByteBuffer)} (with their "sliced" versions) and + * {@link #hashMemory(long, long)} methods, which actually accept byte sequences, notion of byte + * sequence is defined as follows: + *
    + *
  • For methods accepting arrays of Java primitives, {@code String}s and + * {@code StringBuilder}s, byte sequence is how the input's bytes are actually lay in memory. + *
  • + *
  • For methods accepting single primitive values, byte sequence is how this primitive + * would be put into memory with {@link ByteOrder#nativeOrder() native} byte order, or + * equivalently, {@code hashXxx(primitive)} has always the same result as {@code + * hashXxxs(new xxx[] {primitive})}, where "xxx" is any Java primitive type name.
  • + *
  • For {@link #hash(Object, Access, long, long)} method byte sequence abstraction + * is defined by the given {@link Access} strategy to the given object.
  • + *
+ * + *

Hash function implementation could either produce equal results for equal input on platforms + * with different {@link ByteOrder}, favoring one byte order in terms of performance, or different + * results, but performing equally well. This choice should be explicitly documented for all + * {@code LongHashFunction} implementations. + * + *

Subclassing

+ * To implement a specific hash function algorithm, this class should be subclassed. Only methods + * that accept single primitives, {@link #hashVoid()}, and {@link #hash(Object, Access, long, long)} + * should be implemented; others have default implementations which ultimately delegate to the + * {@link #hash(Object, Access, long, long)} abstract method. + * + *

Notes about how exactly methods with default implementations are implemented in doc comments + * are given for information and could be changed at any moment. However, it could hardly cause + * any issues with subclassing, except probably little performance degradation. Methods documented + * as "shortcuts" could either delegate to the referenced method or delegate directly to the method + * to which the referenced method delegates. + * + *

{@code LongHashFunction} implementations shouldn't assume that {@code Access} strategies + * do defensive checks and access only bytes within the requested range. + */ +public abstract class LongHashFunction implements Serializable { + private static final long serialVersionUID = 0L; + /** + * Returns a hash function implementing the XXH3 64bit + * algorithm without a seed value (0 is used as default seed value). This implementation + * produces equal results for equal input on platforms with different {@link + * ByteOrder}, but is slower on big-endian platforms than on little-endian. + * + * @return a {@code LongHashFunction} implementing the XXH3 64-bit algorithm without a seed value + * @see #xx3(long) + */ + public static LongHashFunction xx3() { + return XXH3.asLongHashFunctionWithoutSeed(); + } + + /** + * Returns a hash function implementing the XXH3 64bit + * algorithm with the given seed value. This implementation produces equal results for equal + * input on platforms with different {@link ByteOrder}, but is slower on big-endian platforms + * than on little-endian. + * + * @param seed the seed value to be used for hashing + * @return a {@code LongHashFunction} implementing the XXH3 64-bit algorithm with the given seed value + * @see #xx3() + */ + public static LongHashFunction xx3(final long seed) { + return XXH3.asLongHashFunctionWithSeed(seed); + } + + /** + * Returns a hash function implementing the XXH128 low + * 64bit algorithm without a seed value (0 is used as default seed value). This + * implementation produces equal results for equal input on platforms with different {@link + * ByteOrder}, but is slower on big-endian platforms than on little-endian. + * + * @return a {@code LongHashFunction} implementing the XXH128 low 64bit algorithm without a seed value + * @see #xx128low(long) + */ + public static LongHashFunction xx128low() { + return XXH3.asLongTupleLowHashFunctionWithoutSeed(); + } + + /** + * Returns a hash function implementing the XXH128 low + * 64bit algorithm with the given seed value. This implementation produces equal results for + * equal input on platforms with different {@link ByteOrder}, but is slower on big-endian + * platforms than on little-endian. + * + * @param seed the seed value to be used for hashing + * @return a {@code LongHashFunction} implementing the XXH128 low 64bit algorithm with the given seed value + * @see #xx128low() + */ + public static LongHashFunction xx128low(final long seed) { + return XXH3.asLongTupleLowHashFunctionWithSeed(seed); + } + /** + * Constructor for use in subclasses. + */ + protected LongHashFunction() { + } + + /** + * Returns the hash code for the given {@code long} value; this method is consistent with + * {@code LongHashFunction} methods that accept sequences of bytes, assuming the {@code input} + * value is interpreted in {@linkplain ByteOrder#nativeOrder() native} byte order. For example, + * the result of {@code hashLong(v)} call is identical to the result of + * {@code hashLongs(new long[] {v})} call for any {@code long} value. + * + * @param input the long value to be hashed + * @return the hash code for the given long value + */ + public abstract long hashLong(long input); + + /** + * Returns the hash code for the given {@code int} value; this method is consistent with + * {@code LongHashFunction} methods that accept sequences of bytes, assuming the {@code input} + * value is interpreted in {@linkplain ByteOrder#nativeOrder() native} byte order. For example, + * the result of {@code hashInt(v)} call is identical to the result of + * {@code hashInts(new int[] {v})} call for any {@code int} value. + * + * @param input the int value to be hashed + * @return the hash code for the given int value + */ + public abstract long hashInt(int input); + + /** + * Returns the hash code for the given {@code short} value; this method is consistent with + * {@code LongHashFunction} methods that accept sequences of bytes, assuming the {@code input} + * value is interpreted in {@linkplain ByteOrder#nativeOrder() native} byte order. For example, + * the result of {@code hashShort(v)} call is identical to the result of + * {@code hashShorts(new short[] {v})} call for any {@code short} value. + * As a consequence, {@code hashShort(v)} call produce always the same result as {@code + * hashChar((char) v)}. + * + * @param input the short value to be hashed + * @return the hash code for the given short value + */ + public abstract long hashShort(short input); + + /** + * Returns the hash code for the given {@code char} value; this method is consistent with + * {@code LongHashFunction} methods that accept sequences of bytes, assuming the {@code input} + * value is interpreted in {@linkplain ByteOrder#nativeOrder() native} byte order. For example, + * the result of {@code hashChar(v)} call is identical to the result of + * {@code hashChars(new char[] {v})} call for any {@code char} value. + * As a consequence, {@code hashChar(v)} call produce always the same result as {@code + * hashShort((short) v)}. + * + * @param input the char value to be hashed + * @return the hash code for the given char value + */ + public abstract long hashChar(char input); + + /** + * Returns the hash code for the given {@code byte} value. This method is consistent with + * {@code LongHashFunction} methods that accept sequences of bytes. For example, the result of + * {@code hashByte(v)} call is identical to the result of + * {@code hashBytes(new byte[] {v})} call for any {@code byte} value. + * + * @param input the byte value to be hashed + * @return the hash code for the given byte value + */ + public abstract long hashByte(byte input); + + /** + * Returns the hash code for the empty (zero-length) bytes sequence, + * for example {@code hashBytes(new byte[0])}. + * + * @return the hash code for the empty bytes sequence + */ + public abstract long hashVoid(); + + /** + * Returns the hash code for {@code len} continuous bytes of the given {@code input} object, + * starting from the given offset. The abstraction of input as ordered byte sequence and + * "offset within the input" is defined by the given {@code access} strategy. + * + *

This method doesn't promise to throw a {@code RuntimeException} if {@code + * [off, off + len - 1]} subsequence exceeds the bounds of the bytes sequence, defined by {@code + * access} strategy for the given {@code input}, so use this method with caution. + * + * @param input the object to read bytes from + * @param access access which defines the abstraction of the given input + * as ordered byte sequence + * @param off offset to the first byte of the subsequence to hash + * @param len length of the subsequence to hash + * @param the type of the input + * @return hash code for the specified bytes subsequence + */ + public abstract long hash(T input, Access access, long off, long len); + + private long unsafeHash(Object input, long off, long len) { + return hash(input, UnsafeAccess.INSTANCE, off, len); + } + + /** + * Shortcut for {@link #hashBooleans(boolean[]) hashBooleans(new boolean[] {input})}. + * Note that this is not necessarily equal to {@code hashByte(input ? (byte) 1 : (byte) 0)}, + * because booleans could be stored differently in this JVM. + * + * @param input the boolean value to be hashed + * @return the hash code for the given boolean value + */ + public long hashBoolean(boolean input) { + return hashByte(input ? TRUE_BYTE_VALUE : FALSE_BYTE_VALUE); + } + + /** + * Shortcut for {@link #hashBooleans(boolean[], int, int) hashBooleans(input, 0, input.length)}. + * + * @param input the boolean array to be hashed + * @return the hash code for the given boolean array + */ + public long hashBooleans(boolean[] input) { + return unsafeHash(input, BOOLEAN_BASE, input.length); + } + + /** + * Returns the hash code for the specified subsequence of the given {@code boolean} array. + * + *

Default implementation delegates to {@link #hash(Object, Access, long, long)} method + * using {@linkplain Access#unsafe() unsafe} {@code Access}. + * + * @param input the array to read data from + * @param off index of the first {@code boolean} in the subsequence to hash + * @param len length of the subsequence to hash + * @return hash code for the specified subsequence + * @throws IndexOutOfBoundsException if {@code off < 0} or {@code off + len > input.length} + * or {@code len < 0} + */ + public long hashBooleans(boolean[] input, int off, int len) { + checkArrayOffs(input.length, off, len); + return unsafeHash(input, BOOLEAN_BASE + off, len); + } + + /** + * Shortcut for {@link #hashBytes(byte[], int, int) hashBytes(input, 0, input.length)}. + * + * @param input the byte array to be hashed + * @return the hash code for the given byte array + */ + public long hashBytes(byte[] input) { + return unsafeHash(input, BYTE_BASE, input.length); + } + + /** + * Returns the hash code for the specified subsequence of the given {@code byte} array. + * + *

Default implementation delegates to {@link #hash(Object, Access, long, long)} method + * using {@linkplain Access#unsafe() unsafe} {@code Access}. + * + * @param input the array to read bytes from + * @param off index of the first {@code byte} in the subsequence to hash + * @param len length of the subsequence to hash + * @return hash code for the specified subsequence + * @throws IndexOutOfBoundsException if {@code off < 0} or {@code off + len > input.length} + * or {@code len < 0} + */ + public long hashBytes(byte[] input, int off, int len) { + checkArrayOffs(input.length, off, len); + return unsafeHash(input, BYTE_BASE + off, len); + } + + /** + * Shortcut for {@link #hashBytes(ByteBuffer, int, int) + * hashBytes(input, input.position(), input.remaining())}. + * + * @param input the ByteBuffer to be hashed + * @return the hash code for the given ByteBuffer + */ + public long hashBytes(ByteBuffer input) { + return hashByteBuffer(input, input.position(), input.remaining()); + } + + /** + * Returns the hash code for the specified subsequence of the given {@code ByteBuffer}. + * + *

This method doesn't alter the state (mark, position, limit or order) of the given + * {@code ByteBuffer}. + * + *

Default implementation delegates to {@link #hash(Object, Access, long, long)} method + * using {@link Access#toByteBuffer()}. + * + * @param input the buffer to read bytes from + * @param off index of the first {@code byte} in the subsequence to hash + * @param len length of the subsequence to hash + * @return hash code for the specified subsequence + * @throws IndexOutOfBoundsException if {@code off < 0} or {@code off + len > input.capacity()} + * or {@code len < 0} + */ + public long hashBytes(ByteBuffer input, int off, int len) { + checkArrayOffs(input.capacity(), off, len); + return hashByteBuffer(input, off, len); + } + + private long hashByteBuffer(ByteBuffer input, int off, int len) { + if (input.hasArray()) { + return unsafeHash(input.array(), BYTE_BASE + input.arrayOffset() + off, len); + } else if (input instanceof DirectBuffer) { + return unsafeHash(null, ((DirectBuffer) input).address() + off, len); + } else { + return hash(input, ByteBufferAccess.INSTANCE, off, len); + } + } + + /** + * Returns the hash code of bytes of the wild memory from the given address. Use with caution. + * + *

Default implementation delegates to {@link #hash(Object, Access, long, long)} method + * using {@linkplain Access#unsafe() unsafe} {@code Access}. + * + * @param address the address of the first byte to hash + * @param len length of the byte sequence to hash + * @return hash code for the specified byte sequence + */ + public long hashMemory(long address, long len) { + return unsafeHash(null, address, len); + } + + /** + * Shortcut for {@link #hashChars(char[], int, int) hashChars(input, 0, input.length)}. + * + * @param input the char array to be hashed + * @return the hash code for the given char array + */ + public long hashChars(char[] input) { + return unsafeHash(input, CHAR_BASE, input.length * 2L); + } + + /** + * Returns the hash code for bytes, as they lay in memory, of the specified subsequence + * of the given {@code char} array. + * + *

Default implementation delegates to {@link #hash(Object, Access, long, long)} method + * using {@linkplain Access#unsafe() unsafe} {@code Access}. + * + * @param input the array to read data from + * @param off index of the first {@code char} in the subsequence to hash + * @param len length of the subsequence to hash, in chars (i. e. the length of the bytes + * sequence to hash is {@code len * 2L}) + * @return hash code for the specified subsequence + * @throws IndexOutOfBoundsException if {@code off < 0} or {@code off + len > input.length} + * or {@code len < 0} + */ + public long hashChars(char[] input, int off, int len) { + checkArrayOffs(input.length, off, len); + return unsafeHash(input, CHAR_BASE + (off * 2L), len * 2L); + } + + /** + * Shortcut for {@link #hashChars(String, int, int) hashChars(input, 0, input.length())}. + * + * @param input the String to be hashed + * @return the hash code for the given String + */ + public long hashChars(String input) { + return VALID_STRING_HASH.longHash(input, this, 0, input.length()); + } + + /** + * Returns the hash code for bytes of the specified subsequence of the given {@code String}'s + * underlying {@code char} array. + * + *

Default implementation could either delegate to {@link #hash(Object, Access, long, long)} + * using {@link Access#toNativeCharSequence()}, or to {@link #hashChars(char[], int, int)}. + * + * @param input the string which bytes to hash + * @param off index of the first {@code char} in the subsequence to hash + * @param len length of the subsequence to hash, in chars (i. e. the length of the bytes + * sequence to hash is {@code len * 2L}) + * @return the hash code of the given {@code String}'s bytes + * @throws IndexOutOfBoundsException if {@code off < 0} or {@code off + len > input.length()} + * or {@code len < 0} + */ + public long hashChars(String input, int off, int len) { + return VALID_STRING_HASH.longHash(input, this, off, len); + } + + /** + * Shortcut for {@link #hashChars(StringBuilder, int, int) hashChars(input, 0, input.length())}. + * + * @param input the StringBuilder to be hashed + * @return the hash code for the given StringBuilder + */ + public long hashChars(StringBuilder input) { + return hashNativeChars(input); + } + + /** + * Returns the hash code for bytes of the specified subsequence of the given + * {@code StringBuilder}'s underlying {@code char} array. + * + *

Default implementation could either delegate to {@link #hash(Object, Access, long, long)} + * using {@link Access#toNativeCharSequence()}, or to {@link #hashChars(char[], int, int)}. + * + * @param input the string builder which bytes to hash + * @param off index of the first {@code char} in the subsequence to hash + * @param len length of the subsequence to hash, in chars (i. e. the length of the bytes + * sequence to hash is {@code len * 2L}) + * @return the hash code of the given {@code String}'s bytes + * @throws IndexOutOfBoundsException if {@code off < 0} or {@code off + len > input.length()} + * or {@code len < 0} + */ + public long hashChars(StringBuilder input, int off, int len) { + return hashNativeChars(input, off, len); + } + +/** + * Returns the hash code for the entire CharSequence. + * + * @param input the CharSequence to be hashed + * @return the hash code for the given CharSequence + */ + long hashNativeChars(CharSequence input) { + return hashNativeChars(input, 0, input.length()); + } + +/** + * Returns the hash code for a subsequence of the given CharSequence. + * + * @param input the CharSequence to be hashed + * @param off the index of the first char in the subsequence + * @param len the length of the subsequence + * @return the hash code for the specified subsequence of the given CharSequence + */ + long hashNativeChars(CharSequence input, int off, int len) { + return hash(input, nativeCharSequenceAccess(), off * 2L, len * 2L); + } + + /** + * Shortcut for {@link #hashShorts(short[], int, int) hashShorts(input, 0, input.length)}. + * + * @param input the short array to be hashed + * @return the hash code for the given short array + */ + public long hashShorts(short[] input) { + return unsafeHash(input, SHORT_BASE, input.length * 2L); + } + + /** + * Returns the hash code for bytes, as they lay in memory, of the specified subsequence + * of the given {@code short} array. + * + *

Default implementation delegates to {@link #hash(Object, Access, long, long)} method + * using {@linkplain Access#unsafe() unsafe} {@code Access}. + * + * @param input the array to read data from + * @param off index of the first {@code short} in the subsequence to hash + * @param len length of the subsequence to hash, in shorts (i. e. the length of the bytes + * sequence to hash is {@code len * 2L}) + * @return hash code for the specified subsequence + * @throws IndexOutOfBoundsException if {@code off < 0} or {@code off + len > input.length} + * or {@code len < 0} + */ + public long hashShorts(short[] input, int off, int len) { + return unsafeHash(input, SHORT_BASE + (off * 2L), len * 2L); + } + + /** + * Shortcut for {@link #hashInts(int[], int, int) hashInts(input, 0, input.length)}. + * + * @param input the integer array to be hashed + * @return the hash code for the given integer array + */ + public long hashInts(int[] input) { + return unsafeHash(input, INT_BASE, input.length * 4L); + } + + /** + * Returns the hash code for bytes, as they lay in memory, of the specified subsequence + * of the given {@code int} array. + * + *

Default implementation delegates to {@link #hash(Object, Access, long, long)} method + * using {@linkplain Access#unsafe() unsafe} {@code Access}. + * + * @param input the array to read data from + * @param off index of the first {@code int} in the subsequence to hash + * @param len length of the subsequence to hash, in ints (i. e. the length of the bytes + * sequence to hash is {@code len * 4L}) + * @return hash code for the specified subsequence + * @throws IndexOutOfBoundsException if {@code off < 0} or {@code off + len > input.length} + * or {@code len < 0} + */ + public long hashInts(int[] input, int off, int len) { + checkArrayOffs(input.length, off, len); + return unsafeHash(input, INT_BASE + (off * 4L), len * 4L); + } + + /** + * Shortcut for {@link #hashLongs(long[], int, int) hashLongs(input, 0, input.length)}. + * + * @param input the long array to be hashed + * @return the hash code for the given long array + */ + public long hashLongs(long[] input) { + return unsafeHash(input, LONG_BASE, input.length * 8L); + } + + /** + * Returns the hash code for bytes, as they lay in memory, of the specified subsequence + * of the given {@code long} array. + * + *

Default implementation delegates to {@link #hash(Object, Access, long, long)} method + * using {@linkplain Access#unsafe() unsafe} {@code Access}. + * + * @param input the array to read data from + * @param off index of the first {@code long} in the subsequence to hash + * @param len length of the subsequence to hash, in longs (i. e. the length of the bytes + * sequence to hash is {@code len * 8L}) + * @return hash code for the specified subsequence + * @throws IndexOutOfBoundsException if {@code off < 0} or {@code off + len > input.length} + * or {@code len < 0} + */ + public long hashLongs(long[] input, int off, int len) { + return unsafeHash(input, LONG_BASE + (off * 8L), len * 8L); + } +} \ No newline at end of file diff --git a/dev.skidfuscator.sdk/src/main/java/sdk/LongTupleHashFunction.java b/dev.skidfuscator.sdk/src/main/java/sdk/LongTupleHashFunction.java new file mode 100644 index 0000000..9212bb5 --- /dev/null +++ b/dev.skidfuscator.sdk/src/main/java/sdk/LongTupleHashFunction.java @@ -0,0 +1,969 @@ +package sdk; + +import sun.nio.ch.DirectBuffer; + +import java.io.Serializable; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import static sdk.CharSequenceAccess.*; +import static sdk.UnsafeAccess.*; +import static sdk.Util.*; + +/** + * Tuple hash function producing more than 64-bit hash code into a result array of type + * {@code long[]} from any byte sequences. See {@link LongHashFunction} for the definition of byte + * sequence semantics and {@link ByteOrder} requirements for implementations. + * + *

Every {@link LongHashFunction} hash method has two corresponding ones in this class for different + * allocation strategies: + *

    + *
  • {@code void hash(input..., long[] result)} + * + *

    will store the hash results in array {@code result[0 .. newResultArray().length-1]}, and + * throws exceptions when {@code result == null} or the length of the array is less than + * {@code newResultArray().length}. {@link #newResultArray} method should always be used to + * create resuable result arrays to avoid exceptions. See {@link #hashLong(long, long[])}. + * + *

    Warning: A single allocation occurs only at the begining of some runtime scope, so + * it could be called Almost-Zero-Allocation-Hashing.

  • + * + *
  • {@code long[] hash(input...)} + * + *

    will allocate and return an array containing the results. + * + *

    Warning: Methods with this form + * always perform exactly one allocation for the result array in each + * invocation, that is One-Allocation-Hashing. So prefer the first form as much + * as possible.

  • + *
+ * + *

Subclassing

+ * To implement a specific hash function algorithm resulting more than 64 bits results, this class + * should be subclassed. Only methods with resued result array that accept single primitives, + * {@link #hashVoid(long[])} and {@link #hash(Object, Access, long, long, long[])} should be + * implemented; other have default implementations which in the end delegate to + * {@link #hash(Object, Access, long, long, long[])} abstract method. + * + *

The {@link #bitsLength} method should also be implemented, returning the actual number of bits + * in the result array. The bits length must be greater than 64, otherwise just use the + * {@link LongHashFunction} interface. And the length should also be a positive multiple of 8. + * + *

Also see {@link LongHashFunction} for additional information about subclassing andd access. + * + * @see LongHashFunction LongHashFunction + */ +public abstract class LongTupleHashFunction implements Serializable { + private static final long serialVersionUID = 0L; + + // Implementations + // + + /** + * Returns a 128-bit hash function implementing + * XXH3 128bit algorithm without seed values + * (0 is used as default seed value). This implementation produces equal results for equal input + * on platforms with different {@link ByteOrder}, but is slower on big-endian platforms than on + * little-endian. + * + * @see #xx128(long) + */ + + public static LongTupleHashFunction xx128() { + return XXH3.asLongTupleHashFunctionWithoutSeed(); + } + + /** + * Returns a hash function implementing + * XXH3 128bit algorithm with the given seed + * value. This implementation produces equal results for equal input on platforms with different + * {@link ByteOrder}, but is slower on big-endian platforms than on little-endian. + * + * @see #xx128() + */ + + public static LongTupleHashFunction xx128(final long seed) { + return XXH3.asLongTupleHashFunctionWithSeed(seed); + } + + /** + * Constructor for use in subclasses. + */ + protected LongTupleHashFunction() {} + + // Public API + // + + /** + * Returns the actual number of bits in a result array; a positive multiple of 8. + */ + public abstract int bitsLength(); + + /** + * Returns a new-allocated result array. + *

+ * If {@code bitsLength()} returns non-multiple of 64, the implementation of this method should + * round-up the length to a multiple of 64 for allocating the {@code long} array. + */ + + public long[] newResultArray() { + return new long[(bitsLength() + 63) / 64]; + } + + /** + * Computes the hash code for the given {@code long} value, and store the results in the + * {@code result} array; this method is consistent with {@code LongTupleHashFunction} methods + * that accept sequences of bytes, assuming the {@code input} value is interpreted in + * {@linkplain ByteOrder#nativeOrder() native} byte order. For example, the result of + * {@code hashLong(v, result)} call is identical to the result of + * {@code hashLongs(new long[] {v}, result)} call for any {@code long} value. + * + *

The {@code result} array should be always created by {@link #newResultArray} method. When + * storing, the {@code result[0 .. newResultArray().length-1]} will be accessed, the rest + * elements of the array will not be touched when + * {@code result.length > newResultArray().length]}. + * + * @throws NullPointerException if {@code result == null} + * @throws IllegalArgumentException if {@code result.length < newResultArray().length} + */ + public abstract void hashLong(long input, long[] result); + + /** + * The result array is allocated on the fly, and no exceptions will be thrown. + * + * @see #hashLong(long, long[]) + */ + + public long[] hashLong(final long input) { + final long[] result = newResultArray(); + hashLong(input, result); + return result; + } + + /** + * Computes the hash code for the given {@code int} value, and store the results in the + * {@code result} array; this method is consistent with {@code LongTupleHashFunction} methods + * that accept sequences of bytes, assuming the {@code input} value is interpreted in + * {@linkplain ByteOrder#nativeOrder() native} byte order. For example, the result of + * {@code hashInt(v, result)} call is identical to the result of + * {@code hashInts(new int[] {v}, result)} call for any {@code int} value. + * + *

The {@code result} array should be always created by {@link #newResultArray} method. When + * storing, the {@code result[0 .. newResultArray().length-1]} will be accessed, the rest + * elements of the array will not be touched when + * {@code result.length > newResultArray().length]}. + * + * @throws NullPointerException if {@code result == null} + * @throws IllegalArgumentException if {@code result.length < newResultArray().length} + */ + public abstract void hashInt(int input, long[] result); + + /** + * The result array is allocated on the fly, and no exceptions will be thrown. + * + * @see #hashInt(int, long[]) + */ + + public long[] hashInt(final int input) { + final long[] result = newResultArray(); + hashInt(input, result); + return result; + } + + /** + * Computes the hash code for the given {@code short} value, and store the results in the + * {@code result} array; this method is consistent with {@code LongTupleHashFunction} methods + * that accept sequences of bytes, assuming the {@code input} value is interpreted in + * {@linkplain ByteOrder#nativeOrder() native} byte order. For example, the result of + * {@code hashShort(v, result)} call is identical to the result of + * {@code hashShorts(new short[] {v}, result)} call for any {@code short} value. + * + *

The {@code result} array should be always created by {@link #newResultArray} method. When + * storing, the {@code result[0 .. newResultArray().length-1]} will be accessed, the rest + * elements of the array will not be touched when + * {@code result.length > newResultArray().length]}. + * + * @throws NullPointerException if {@code result == null} + * @throws IllegalArgumentException if {@code result.length < newResultArray().length} + */ + public abstract void hashShort(short input, long[] result); + + /** + * The result array is allocated on the fly, and no exceptions will be thrown. + * + * @see #hashShort(short, long[]) + */ + + public long[] hashShort(final short input) { + final long[] result = newResultArray(); + hashShort(input, result); + return result; + } + + /** + * Computes the hash code for the given {@code char} value, and store the results in the + * {@code result} array; this method is consistent with {@code LongTupleHashFunction} methods + * that accept sequences of bytes, assuming the {@code input} value is interpreted in + * {@linkplain ByteOrder#nativeOrder() native} byte order. For example, the result of + * {@code hashChar(v, result)} call is identical to the result of + * {@code hashChars(new char[] {v}, result)} call for any {@code char} value. + * + *

The {@code result} array should be always created by {@link #newResultArray} method. When + * storing, the {@code result[0 .. newResultArray().length-1]} will be accessed, the rest + * elements of the array will not be touched when + * {@code result.length > newResultArray().length]}. + * + * @throws NullPointerException if {@code result == null} + * @throws IllegalArgumentException if {@code result.length < newResultArray().length} + */ + public abstract void hashChar(char input, long[] result); + + /** + * The result array is allocated on the fly, and no exceptions will be thrown. + * + * @see #hashChar(char, long[]) + */ + + public long[] hashChar(final char input) { + final long[] result = newResultArray(); + hashChar(input, result); + return result; + } + + /** + * Computes the hash code for the given {@code byte} value, and store the results in the + * {@code result} array; this method is consistent with {@code LongTupleHashFunction} methods + * that accept sequences of bytes, assuming the {@code input} value is the first and only byte. + * For example, the result of {@code hashByte(v, result)} call is identical to the result of + * {@code hashBytes(new byte[] {v}, result)} call for any {@code byte} value. + * + *

The {@code result} array should be always created by {@link #newResultArray} method. When + * storing, the {@code result[0 .. newResultArray().length-1]} will be accessed, the rest + * elements of the array will not be touched when + * {@code result.length > newResultArray().length]}. + * + * @throws NullPointerException if {@code result == null} + * @throws IllegalArgumentException if {@code result.length < newResultArray().length} + */ + public abstract void hashByte(byte input, long[] result); + + /** + * The result array is allocated on the fly, and no exceptions will be thrown. + * + * @see #hashByte(byte, long[]) + */ + + public long[] hashByte(final byte input) { + final long[] result = newResultArray(); + hashByte(input, result); + return result; + } + + /** + * Computes the hash code for the empty (zero-length) bytes sequence, and store the results in + * the {@code result} array. For example, the result of {@code hashVoid(result)} call is + * identical to the result of {@code hashBytes(new byte[0], result)} call. + * + *

The {@code result} array should be always created by {@link #newResultArray} method. When + * storing, the {@code result[0 .. newResultArray().length-1]} will be accessed, the rest + * elements of the array will not be touched when + * {@code result.length > newResultArray().length]}. + * + * @throws NullPointerException if {@code result == null} + * @throws IllegalArgumentException if {@code result.length < newResultArray().length} + */ + public abstract void hashVoid(long[] result); + + /** + * The result array is allocated on the fly, and no exceptions will be thrown. + * + * @see #hashVoid(long[]) + */ + + public long[] hashVoid() { + final long[] result = newResultArray(); + hashVoid(result); + return result; + } + + /** + * Computes the hash code for the given {@code input} object starting from the given offset, and + * store the results in the {@code result} array. The abstraction of input as ordered byte + * sequence and "offset within the input" is defined by the given {@code access} strategy. + * + *

This method doesn't promise to throw a {@code RuntimeException} if + * {@code [off, off + len - 1]} subsequence exceeds the bounds of the bytes sequence, defined by + * {@code access} strategy for the given {@code input}, so use this method with caution. + * + *

The {@code result} array should be always created by {@link #newResultArray} method. When + * storing, the {@code result[0 .. newResultArray().length-1]} will be accessed, the rest + * elements of the array will not be touched when + * {@code result.length > newResultArray().length]}. + * + * @param input the object to read bytes from + * @param access access which defines the abstraction of the given input + * as ordered byte sequence + * @param off offset to the first byte of the subsequence to hash + * @param len length of the subsequence to hash + * @param result the container array for storing the hash results, + * should be alloced by {@link #newResultArray} + * @param the type of the input + * @throws NullPointerException if {@code result == null} + * @throws IllegalArgumentException if {@code result.length < newResultArray().length} + */ + public abstract void hash(T input, Access access, + long off, long len, long[] result); + + /** + * The result array is allocated on the fly, and no exceptions will be thrown. + * + * @see #hash(Object, Access, long, long, long[]) + */ + + public long[] hash(final T input, final Access access, + final long off, final long len) { + final long[] result = newResultArray(); + hash(input, access, off, len, result); + return result; + } + + /** + * Shortcut for + * {@link #hashBooleans(boolean[], long[]) hashBooleans(new boolean[] {input}, result)}. + * Note that this is not necessarily equal to + * {@code hashByte(input ? (byte) 1 : (byte) 0, result)}, because booleans could be stored + * differently in this JVM. + */ + public void hashBoolean(final boolean input, final long[] result) { + hashByte(input ? TRUE_BYTE_VALUE : FALSE_BYTE_VALUE, result); + } + + /** + * The result array is allocated on the fly, and no exceptions will be thrown. + * + * @see #hashBoolean(boolean, long[]) + */ + + public long[] hashBoolean(final boolean input) { + final long[] result = newResultArray(); + hashByte(input ? TRUE_BYTE_VALUE : FALSE_BYTE_VALUE, result); + return result; + } + + /** + * Shortcut for {@link #hashBooleans(boolean[], int, int, long[]) hashBooleans(input, 0, input.length, result)}. + */ + public void hashBooleans(final boolean[] input, final long[] result) { + unsafeHash(this, input, BOOLEAN_BASE, input.length, result); + } + + /** + * The result array is allocated on the fly, and no exceptions will be thrown. + * + * @see #hashBooleans(boolean[], long[]) + */ + + public long[] hashBooleans(final boolean[] input) { + final long[] result = newResultArray(); + unsafeHash(this, input, BOOLEAN_BASE, input.length, result); + return result; + } + + /** + * Computes the hash code for the specified subsequence of the given {@code boolean} array, and + * store the results in the {@code result} array. + * + *

Default implementation delegates to {@link #hash(Object, Access, long, long, long[])} + * method using {@linkplain Access#unsafe() unsafe} {@code Access}. + * + *

The {@code result} array should be always created by {@link #newResultArray} method. When + * storing, the {@code result[0 .. newResultArray().length-1]} will be accessed, the rest + * elements of the array will not be touched when + * {@code result.length > newResultArray().length]}. + * + * @param input the array to read data from + * @param off index of the first {@code boolean} in the subsequence to hash + * @param len length of the subsequence to hash + * @param result the container array for storing the hash results, + * should be alloced by {@link #newResultArray} + * @throws NullPointerException if {@code result == null} + * @throws IllegalArgumentException if {@code result.length < newResultArray().length} + * @throws IllegalArgumentException if {@code off < 0} or {@code off + len > input.length} + * or {@code len < 0} + */ + public void hashBooleans(final boolean[] input, + final int off, final int len, final long[] result) { + checkArrayOffs(input.length, off, len); + unsafeHash(this, input, BOOLEAN_BASE + off, len, result); + } + + /** + * The result array is allocated on the fly. + * + * @see #hashBooleans(boolean[], int, int, long[]) + */ + + public long[] hashBooleans(final boolean[] input, final int off, final int len) { + checkArrayOffs(input.length, off, len); + final long[] result = newResultArray(); + unsafeHash(this, input, BOOLEAN_BASE + off, len, result); + return result; + } + + /** + * Shortcut for {@link #hashBytes(byte[], int, int, long[]) hashBytes(input, 0, input.length, result)}. + */ + public void hashBytes(final byte[] input, final long[] result) { + unsafeHash(this, input, BYTE_BASE, input.length, result); + } + + /** + * The result array is allocated on the fly, and no exceptions will be thrown. + * + * @see #hashBytes(byte[], long[]) + */ + + public long[] hashBytes(final byte[] input) { + final long[] result = newResultArray(); + unsafeHash(this, input, BYTE_BASE, input.length, result); + return result; + } + + /** + * Computes the hash code for the specified subsequence of the given {@code byte} array, and + * store the results in the {@code result} array. + * + *

Default implementation delegates to {@link #hash(Object, Access, long, long, long[])} + * method using {@linkplain Access#unsafe() unsafe} {@code Access}. + * + *

The {@code result} array should be always created by {@link #newResultArray} method. When + * storing, the {@code result[0 .. newResultArray().length-1]} will be accessed, the rest + * elements of the array will not be touched when + * {@code result.length > newResultArray().length]}. + * + * @param input the array to read data from + * @param off index of the first {@code byte} in the subsequence to hash + * @param len length of the subsequence to hash + * @param result the container array for storing the hash results, + * should be alloced by {@link #newResultArray} + * @throws NullPointerException if {@code result == null} + * @throws IllegalArgumentException if {@code result.length < newResultArray().length} + * @throws IllegalArgumentException if {@code off < 0} or {@code off + len > input.length} + * or {@code len < 0} + */ + public void hashBytes(final byte[] input, final int off, final int len, final long[] result) { + checkArrayOffs(input.length, off, len); + unsafeHash(this, input, BYTE_BASE + off, len, result); + } + + /** + * The result array is allocated on the fly. + * + * @see #hashBytes(byte[], int, int, long[]) + */ + + public long[] hashBytes(final byte[] input, final int off, final int len) { + checkArrayOffs(input.length, off, len); + final long[] result = newResultArray(); + unsafeHash(this, input, BYTE_BASE + off, len, result); + return result; + } + + /** + * Shortcut for {@link #hashBytes(ByteBuffer, int, int, long[]) + * hashBytes(input, input.position(), input.remaining(), result)}. + */ + public void hashBytes(final ByteBuffer input, final long[] result) { + hashByteBuffer(this, input, input.position(), input.remaining(), result); + } + + /** + * The result array is allocated on the fly, and no exceptions will be thrown. + * + * @see #hashBytes(ByteBuffer, long[]) + */ + + public long[] hashBytes(final ByteBuffer input) { + final long[] result = newResultArray(); + hashByteBuffer(this, input, input.position(), input.remaining(), result); + return result; + } + + /** + * Computes the hash code for the specified subsequence of the given {@code ByteBuffer}, and + * store the results in the {@code result} array. + * + *

This method doesn't alter the state (mark, position, limit or order) of the given + * {@code ByteBuffer}. + * + *

Default implementation delegates to {@link #hash(Object, Access, long, long, long[])} + * method using {@linkplain Access#unsafe() unsafe} {@code Access}. + * + *

The {@code result} array should be always created by {@link #newResultArray} method. When + * storing, the {@code result[0 .. newResultArray().length-1]} will be accessed, the rest + * elements of the array will not be touched when + * {@code result.length > newResultArray().length]}. + * + * @param input the buffer to read bytes from + * @param off index of the first {@code byte} in the subsequence to hash + * @param len length of the subsequence to hash + * @param result the container array for storing the hash results, + * should be alloced by {@link #newResultArray} + * @throws NullPointerException if {@code result == null} + * @throws IllegalArgumentException if {@code result.length < newResultArray().length} + * @throws IllegalArgumentException if {@code off < 0} or {@code off + len > input.length} + * or {@code len < 0} + */ + public void hashBytes(final ByteBuffer input, + final int off, final int len, final long[] result) { + checkArrayOffs(input.capacity(), off, len); + hashByteBuffer(this, input, off, len, result); + } + + /** + * The result array is allocated on the fly. + * + * @see #hashBytes(ByteBuffer, int, int, long[]) + */ + + public long[] hashBytes(final ByteBuffer input, final int off, final int len) { + checkArrayOffs(input.capacity(), off, len); + final long[] result = newResultArray(); + hashByteBuffer(this, input, off, len, result); + return result; + } + + /** + * Computes the hash code of bytes of the wild memory from the given address. Use with caution. + * + *

Default implementation delegates to {@link #hash(Object, Access, long, long, long[])} + * method using {@linkplain Access#unsafe() unsafe} {@code Access}. + * + *

The {@code result} array should be always created by {@link #newResultArray} method. When + * storing, the {@code result[0 .. newResultArray().length-1]} will be accessed, the rest + * elements of the array will not be touched when + * {@code result.length > newResultArray().length]}. + * + * @param address the address of the first byte to hash + * @param len length of the byte sequence to hash + * @param result the container array for storing the hash results, + * should be alloced by {@link #newResultArray} + * @throws NullPointerException if {@code result == null} + * @throws IllegalArgumentException if {@code result.length < newResultArray().length} + * @throws IllegalArgumentException if {@code off < 0} or {@code off + len > input.length} + * or {@code len < 0} + */ + public void hashMemory(final long address, final long len, final long[] result) { + unsafeHash(this, null, address, len, result); + } + + /** + * The result array is allocated on the fly, and no exceptions will be thrown. + * + * @see #hashMemory(long, long, long[]) + */ + + public long[] hashMemory(final long address, final long len) { + final long[] result = newResultArray(); + unsafeHash(this, null, address, len, result); + return result; + } + + /** + * Shortcut for + * {@link #hashChars(char[], int, int, long[]) hashChars(input, 0, input.length, result)}. + */ + public void hashChars(final char[] input, final long[] result) { + unsafeHash(this, input, CHAR_BASE, input.length * 2L, result); + } + + /** + * The result array is allocated on the fly, and no exceptions will be thrown. + * + * @see #hashChars(char[], long[]) + */ + + public long[] hashChars(final char[] input) { + final long[] result = newResultArray(); + unsafeHash(this, input, CHAR_BASE, input.length * 2L, result); + return result; + } + + /** + * Computes the hash code for bytes, as they lay in memory, of the specified subsequence of the + * given {@code char} array. + * + *

Default implementation delegates to {@link #hash(Object, Access, long, long, long[])} + * method using {@linkplain Access#unsafe() unsafe} {@code Access}. + * + *

The {@code result} array should be always created by {@link #newResultArray} method. When + * storing, the {@code result[0 .. newResultArray().length-1]} will be accessed, the rest + * elements of the array will not be touched when + * {@code result.length > newResultArray().length]}. + * + * @param input the array to read data from + * @param off index of the first {@code char} in the subsequence to hash + * @param len length of the subsequence to hash, in chars (i.e. the length of the bytes sequence + * to hash is {@code len * 2L}) + * @param result the container array for storing the hash results, + * should be alloced by {@link #newResultArray} + * @throws NullPointerException if {@code result == null} + * @throws IllegalArgumentException if {@code result.length < newResultArray().length} + * @throws IllegalArgumentException if {@code off < 0} or {@code off + len > input.length} + * or {@code len < 0} + */ + public void hashChars(final char[] input, final int off, final int len, final long[] result) { + checkArrayOffs(input.length, off, len); + unsafeHash(this, input, CHAR_BASE + (off * 2L), len * 2L, result); + } + + /** + * The result array is allocated on the fly. + * + * @see #hashChars(char[], int, int, long[]) + */ + + public long[] hashChars(final char[] input, final int off, final int len) { + checkArrayOffs(input.length, off, len); + final long[] result = newResultArray(); + unsafeHash(this, input, CHAR_BASE + (off * 2L), len * 2L, result); + return result; + } + + /** + * Shortcut for + * {@link #hashChars(String, int, int, long[]) hashChars(input, 0, input.length(), result)}. + */ + public void hashChars(final String input, final long[] result) { + VALID_STRING_HASH.hash(input, this, 0, input.length(), result); + } + + /** + * The result array is allocated on the fly, and no exceptions will be thrown. + * + * @see #hashChars(String, long[]) + */ + + public long[] hashChars(final String input) { + final long[] result = newResultArray(); + VALID_STRING_HASH.hash(input, this, 0, input.length(), result); + return result; + } + + /** + * Computes the hash code for bytes of the specified subsequence of the given {@code String}'s + * underlying {@code char} array or {@code byte} array. + * + *

Default implementation could either delegate to + * {@link #hash(Object, Access, long, long, long[])} using + * {@link Access#toNativeCharSequence()}, or to {@link #hashChars(char[], int, int, long[])}. + * + *

The {@code result} array should be always created by {@link #newResultArray} method. When + * storing, the {@code result[0 .. newResultArray().length-1]} will be accessed, the rest + * elements of the array will not be touched when + * {@code result.length > newResultArray().length]}. + * + * @param input the string which bytes to hash + * @param off index of the first {@code char} in the subsequence to hash + * @param len length of the subsequence to hash, in chars (i.e. the length of the bytes sequence + * to hash is {@code len * 2L}) + * @param result the container array for storing the hash results, + * should be alloced by {@link #newResultArray} + * @throws NullPointerException if {@code result == null} + * @throws IllegalArgumentException if {@code result.length < newResultArray().length} + * @throws IllegalArgumentException if {@code off < 0} or {@code off + len > input.length} + * or {@code len < 0} + */ + public void hashChars(final String input, final int off, final int len, final long[] result) { + checkArrayOffs(input.length(), off, len); + VALID_STRING_HASH.hash(input, this, off, len, result); + } + + /** + * The result array is allocated on the fly. + * + * @see #hashChars(String, int, int, long[]) + */ + + public long[] hashChars(final String input, final int off, final int len) { + checkArrayOffs(input.length(), off, len); + final long[] result = newResultArray(); + VALID_STRING_HASH.hash(input, this, off, len, result); + return result; + } + + /** + * Shortcut for + * {@link #hashChars(CharSequence, int, int, long[]) hashChars(input, 0, input.length(), result)}. + */ + public void hashChars(final T input, final long[] result) { + hashNativeChars(this, input, 0, input.length(), result); + } + + /** + * The result array is allocated on the fly, and no exceptions will be thrown. + * + * @see #hashChars(CharSequence, long[]) + */ + + public long[] hashChars(final T input) { + final long[] result = newResultArray(); + hashNativeChars(this, input, 0, input.length(), result); + return result; + } + + /** + * Computes the hash code for bytes of the specified subsequence of the given + * {@code CharSequence}'s underlying {@code char} array. + * + *

Default implementation delegates to {@link #hash(Object, Access, long, long, long[])} + * method using {@link Access#toNativeCharSequence()}. + * + *

The {@code result} array should be always created by {@link #newResultArray} method. When + * storing, the {@code result[0 .. newResultArray().length-1]} will be accessed, the rest + * elements of the array will not be touched when + * {@code result.length > newResultArray().length]}. + * + * @param input the char sequence which bytes to hash + * @param off index of the first {@code char} in the subsequence to hash + * @param len length of the subsequence to hash, in chars (i.e. the length of the bytes sequence + * to hash is {@code len * 2L}) + * @param result the container array for storing the hash results, + * should be alloced by {@link #newResultArray} + * @throws NullPointerException if {@code result == null} + * @throws IllegalArgumentException if {@code result.length < newResultArray().length} + * @throws IllegalArgumentException if {@code off < 0} or {@code off + len > input.length} + * or {@code len < 0} + */ + public void hashChars(final T input, final int off, final int len, + final long[] result) { + checkArrayOffs(input.length(), off, len); + hashNativeChars(this, input, off, len, result); + } + + /** + * The result array is allocated on the fly. + * + * @see #hashChars(CharSequence, int, int, long[]) + */ + + public long[] hashChars(final T input, final int off, final int len) { + checkArrayOffs(input.length(), off, len); + final long[] result = newResultArray(); + hashNativeChars(this, input, off, len, result); + return result; + } + + /** + * Shortcut for + * {@link #hashShorts(short[], int, int, long[]) hashShorts(input, 0, input.length, result)}. + */ + public void hashShorts(final short[] input, final long[] result) { + unsafeHash(this, input, SHORT_BASE, input.length * 2L, result); + } + + /** + * The result array is allocated on the fly, and no exceptions will be thrown. + * + * @see #hashShorts(short[], long[]) + */ + + public long[] hashShorts(final short[] input) { + final long[] result = newResultArray(); + unsafeHash(this, input, SHORT_BASE, input.length * 2L, result); + return result; + } + + /** + * Computes the hash code for bytes, as they lay in memory, of the specified subsequence of the + * given {@code short} array. + * + *

Default implementation delegates to {@link #hash(Object, Access, long, long, long[])} + * method using {@linkplain Access#unsafe() unsafe} {@code Access}. + * + *

The {@code result} array should be always created by {@link #newResultArray} method. When + * storing, the {@code result[0 .. newResultArray().length-1]} will be accessed, the rest + * elements of the array will not be touched when + * {@code result.length > newResultArray().length]}. + * + * @param input the array to read data from + * @param off index of the first {@code short} in the subsequence to hash + * @param len length of the subsequence to hash, in shorts (i.e. the length of the bytes + * sequence to hash is {@code len * 2L}) + * @param result the container array for storing the hash results, + * should be alloced by {@link #newResultArray} + * @throws NullPointerException if {@code result == null} + * @throws IllegalArgumentException if {@code result.length < newResultArray().length} + * @throws IllegalArgumentException if {@code off < 0} or {@code off + len > input.length} + * or {@code len < 0} + */ + public void hashShorts(final short[] input, final int off, final int len, final long[] result) { + checkArrayOffs(input.length, off, len); + unsafeHash(this, input, SHORT_BASE + (off * 2L), len * 2L, result); + } + + /** + * The result array is allocated on the fly. + * + * @see #hashShorts(short[], int, int, long[]) + */ + + public long[] hashShorts(final short[] input, final int off, final int len) { + checkArrayOffs(input.length, off, len); + final long[] result = newResultArray(); + unsafeHash(this, input, SHORT_BASE + (off * 2L), len * 2L, result); + return result; + } + + /** + * Shortcut for + * {@link #hashInts(int[], int, int, long[]) hashInts(input, 0, input.length, result)}. + */ + public void hashInts(final int[] input, final long[] result) { + unsafeHash(this, input, INT_BASE, input.length * 4L, result); + } + + /** + * The result array is allocated on the fly, and no exceptions will be thrown. + * + * @see #hashInts(int[], long[]) + */ + + public long[] hashInts(final int[] input) { + final long[] result = newResultArray(); + unsafeHash(this, input, INT_BASE, input.length * 4L, result); + return result; + } + + /** + * Computes the hash code for bytes, as they lay in memory, of the specified subsequence of the + * given {@code int} array. + * + *

Default implementation delegates to {@link #hash(Object, Access, long, long, long[])} + * method using {@linkplain Access#unsafe() unsafe} {@code Access}. + * + *

The {@code result} array should be always created by {@link #newResultArray} method. When + * storing, the {@code result[0 .. newResultArray().length-1]} will be accessed, the rest + * elements of the array will not be touched when + * {@code result.length > newResultArray().length]}. + * + * @param input the array to read data from + * @param off index of the first {@code int} in the subsequence to hash + * @param len length of the subsequence to hash, in ints (i.e. the length of the bytes sequence + * to hash is {@code len * 4L}) + * @param result the container array for storing the hash results, + * should be alloced by {@link #newResultArray} + * @throws NullPointerException if {@code result == null} + * @throws IllegalArgumentException if {@code result.length < newResultArray().length} + * @throws IllegalArgumentException if {@code off < 0} or {@code off + len > input.length} + * or {@code len < 0} + */ + public void hashInts(final int[] input, final int off, final int len, final long[] result) { + checkArrayOffs(input.length, off, len); + unsafeHash(this, input, INT_BASE + (off * 4L), len * 4L, result); + } + + /** + * The result array is allocated on the fly. + * + * @see #hashInts(int[], int, int, long[]) + */ + + public long[] hashInts(final int[] input, final int off, final int len) { + checkArrayOffs(input.length, off, len); + final long[] result = newResultArray(); + unsafeHash(this, input, INT_BASE + (off * 4L), len * 4L, result); + return result; + } + + /** + * Shortcut for + * {@link #hashLongs(long[], int, int, long[]) hashLongs(input, 0, input.length, result)}. + */ + public void hashLongs(final long[] input, final long[] result) { + unsafeHash(this, input, LONG_BASE, input.length * 8L, result); + } + + /** + * The result array is allocated on the fly, and no exceptions will be thrown. + * + * @see #hashLongs(long[], long[]) + */ + + public long[] hashLongs(final long[] input) { + final long[] result = newResultArray(); + unsafeHash(this, input, LONG_BASE, input.length * 8L, result); + return result; + } + + /** + * Computes the hash code for bytes, as they lay in memory, of the specified subsequence of the + * given {@code long} array. + * + *

Default implementation delegates to {@link #hash(Object, Access, long, long, long[])} + * method using {@linkplain Access#unsafe() unsafe} {@code Access}. + * + *

The {@code result} array should be always created by {@link #newResultArray} method. When + * storing, the {@code result[0 .. newResultArray().length-1]} will be accessed, the rest + * elements of the array will not be touched when + * {@code result.length > newResultArray().length]}. + * + * @param input the array to read data from + * @param off index of the first {@code long} in the subsequence to hash + * @param len length of the subsequence to hash, in longs (i.e. the length of the bytes sequence + * to hash is {@code len * 8L}) + * @param result the container array for storing the hash results, + * should be alloced by {@link #newResultArray} + * @throws NullPointerException if {@code result == null} + * @throws IllegalArgumentException if {@code result.length < newResultArray().length} + * @throws IllegalArgumentException if {@code off < 0} or {@code off + len > input.length} + * or {@code len < 0} + */ + public void hashLongs(final long[] input, final int off, final int len, final long[] result) { + checkArrayOffs(input.length, off, len); + unsafeHash(this, input, LONG_BASE + (off * 8L), len * 8L, result); + } + + /** + * The result array is allocated on the fly. + * + * @see #hashLongs(long[], int, int, long[]) + */ + + public long[] hashLongs(final long[] input, final int off, final int len) { + checkArrayOffs(input.length, off, len); + final long[] result = newResultArray(); + unsafeHash(this, input, LONG_BASE + (off * 8L), len * 8L, result); + return result; + } + + // Internal helper + // + + private static final Access OBJECT_ACCESS = UnsafeAccess.INSTANCE; + + private static final Access CHAR_SEQ_ACCESS = nativeCharSequenceAccess(); + + private static final Access BYTE_BUF_ACCESS = ByteBufferAccess.INSTANCE; + + private static void unsafeHash(final LongTupleHashFunction f, final Object input, + final long off, final long len, final long[] result) { + f.hash(input, OBJECT_ACCESS, off, len, result); + } + + private static void hashByteBuffer(final LongTupleHashFunction f, final ByteBuffer input, + final int off, final int len, final long[] result) { + if (input.hasArray()) { + unsafeHash(f, input.array(), BYTE_BASE + input.arrayOffset() + off, len, result); + } else if (input instanceof DirectBuffer) { + unsafeHash(f, null, ((DirectBuffer) input).address() + off, len, result); + } else { + f.hash(input, BYTE_BUF_ACCESS, off, len, result); + } + } + + static void hashNativeChars(final LongTupleHashFunction f, final CharSequence input, + final int off, final int len, final long[] result) { + f.hash(input, CHAR_SEQ_ACCESS, off * 2L, len * 2L, result); + } +} \ No newline at end of file diff --git a/dev.skidfuscator.sdk/src/main/java/sdk/Maths.java b/dev.skidfuscator.sdk/src/main/java/sdk/Maths.java new file mode 100644 index 0000000..f696840 --- /dev/null +++ b/dev.skidfuscator.sdk/src/main/java/sdk/Maths.java @@ -0,0 +1,94 @@ +package sdk; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Method; + +class Maths { + private static final Maths INSTANCE; + + static { + Maths maths = null; + try { + Method multiplyHigh = Math.class.getDeclaredMethod("multiplyHigh", int.class, int.class); + MethodHandle multiplyHighMH = MethodHandles.lookup().unreflect(multiplyHigh); + maths = new MathsJDK9(multiplyHighMH); + } catch (final Throwable ignore) { + maths = new Maths(); + } + INSTANCE = maths; + } + + public static long unsignedLongMulXorFold(final long lhs, final long rhs) { + return INSTANCE.unsignedLongMulXorFoldImp(lhs, rhs); + } + public static long unsignedLongMulHigh(final long lhs, final long rhs) { + return INSTANCE.unsignedLongMulHighImp(lhs, rhs); + } + + long unsignedLongMulXorFoldImp(final long lhs, final long rhs) { + // The Grade School method of multiplication is a hair faster in Java, primarily used here + // because the implementation is simpler. + final long lhs_l = lhs & 0xFFFFFFFFL; + final long lhs_h = lhs >>> 32; + final long rhs_l = rhs & 0xFFFFFFFFL; + final long rhs_h = rhs >>> 32; + final long lo_lo = lhs_l * rhs_l; + final long hi_lo = lhs_h * rhs_l; + final long lo_hi = lhs_l * rhs_h; + final long hi_hi = lhs_h * rhs_h; + + // Add the products together. This will never overflow. + final long cross = (lo_lo >>> 32) + (hi_lo & 0xFFFFFFFFL) + lo_hi; + final long upper = (hi_lo >>> 32) + (cross >>> 32) + hi_hi; + final long lower = (cross << 32) | (lo_lo & 0xFFFFFFFFL); + return lower ^ upper; + } + + long unsignedLongMulHighImp(final long lhs, final long rhs) { + // The Grade School method of multiplication is a hair faster in Java, primarily used here + // because the implementation is simpler. + final long lhs_l = lhs & 0xFFFFFFFFL; + final long lhs_h = lhs >>> 32; + final long rhs_l = rhs & 0xFFFFFFFFL; + final long rhs_h = rhs >>> 32; + final long lo_lo = lhs_l * rhs_l; + final long hi_lo = lhs_h * rhs_l; + final long lo_hi = lhs_l * rhs_h; + final long hi_hi = lhs_h * rhs_h; + + // Add the products together. This will never overflow. + final long cross = (lo_lo >>> 32) + (hi_lo & 0xFFFFFFFFL) + lo_hi; + final long upper = (hi_lo >>> 32) + (cross >>> 32) + hi_hi; + return upper; + } +} + +class MathsJDK9 extends Maths { + private final MethodHandle multiplyHighMH; + + public MathsJDK9(MethodHandle multiplyHighMH) { + this.multiplyHighMH = multiplyHighMH; + } + + // Math.multiplyHigh() is intrinsified from JDK 10. But JDK 9 is out of life, we always prefer + // this version to the scalar one. + @Override + long unsignedLongMulXorFoldImp(final long lhs, final long rhs) { + final long upper = invokeExact(lhs, rhs) + ((lhs >> 63) & rhs) + ((rhs >> 63) & lhs); + final long lower = lhs * rhs; + return lower ^ upper; + } + @Override + long unsignedLongMulHighImp(final long lhs, final long rhs) { + return invokeExact(lhs, rhs) + ((lhs >> 63) & rhs) + ((rhs >> 63) & lhs); + } + + private long invokeExact(long lhs, long rhs) { + try { + return (long) multiplyHighMH.invokeExact(lhs, rhs); + } catch (Throwable e) { + throw new AssertionError(e); + } + } +} \ No newline at end of file diff --git a/dev.skidfuscator.sdk/src/main/java/sdk/ModernCompactStringHash.java b/dev.skidfuscator.sdk/src/main/java/sdk/ModernCompactStringHash.java new file mode 100644 index 0000000..00832f8 --- /dev/null +++ b/dev.skidfuscator.sdk/src/main/java/sdk/ModernCompactStringHash.java @@ -0,0 +1,63 @@ +package sdk; + +import java.lang.reflect.Field; +import static sdk.Util.*; + +enum ModernCompactStringHash implements StringHash { + INSTANCE; + + private static final long valueOffset; + private static final boolean enableCompactStrings; + private static final Access compactLatin1Access + = CompactLatin1CharSequenceAccess.INSTANCE; + + static { + try { + final Field valueField = String.class.getDeclaredField("value"); + valueOffset = UnsafeAccess.UNSAFE.objectFieldOffset(valueField); + + final byte[] value = (byte[]) UnsafeAccess.UNSAFE.getObject("A", valueOffset); + enableCompactStrings = (1 == value.length); + } catch (final NoSuchFieldException e) { + throw new AssertionError(e); + } + } + + @Override + public long longHash(final String s, final LongHashFunction hashFunction, + final int off, final int len) { + final int sl = s.length(); + if (len <= 0 || sl <= 0) { + checkArrayOffs(sl, off, len); // check as chars + return hashFunction.hashVoid(); + } else { + final byte[] value = (byte[]) UnsafeAccess.UNSAFE.getObject(s, valueOffset); + if (enableCompactStrings && sl == value.length) { + checkArrayOffs(sl, off, len); // check as chars + // 'off' and 'len' are passed as bytes + return hashFunction.hash(value, compactLatin1Access, (long)off*2L, (long)len*2L); + } else { + return hashFunction.hashBytes(value, off*2, len*2); // hash as bytes + } + } + } + + @Override + public void hash(final String s, final LongTupleHashFunction hashFunction, + final int off, final int len, final long[] result) { + final int sl = s.length(); + if (len <= 0 || sl <= 0) { + checkArrayOffs(sl, off, len); // check as chars + hashFunction.hashVoid(result); + } else { + final byte[] value = (byte[]) UnsafeAccess.UNSAFE.getObject(s, valueOffset); + if (enableCompactStrings && sl == value.length) { + checkArrayOffs(sl, off, len); // check as chars + // 'off' and 'len' are passed as bytes + hashFunction.hash(value, compactLatin1Access, (long)off*2L, (long)len*2L, result); + } else { + hashFunction.hashBytes(value, off*2, len*2, result); // hash as bytes + } + } + } +} diff --git a/dev.skidfuscator.sdk/src/main/java/sdk/ModernHotSpotStringHash.java b/dev.skidfuscator.sdk/src/main/java/sdk/ModernHotSpotStringHash.java new file mode 100644 index 0000000..11b3584 --- /dev/null +++ b/dev.skidfuscator.sdk/src/main/java/sdk/ModernHotSpotStringHash.java @@ -0,0 +1,47 @@ +/* + * Copyright 2014 Higher Frequency Trading http://www.higherfrequencytrading.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sdk; + +import java.lang.reflect.Field; + +enum ModernHotSpotStringHash implements StringHash { + INSTANCE; + + private static final long valueOffset; + + static { + try { + Field valueField = String.class.getDeclaredField("value"); + valueOffset = UnsafeAccess.UNSAFE.objectFieldOffset(valueField); + } catch (NoSuchFieldException e) { + throw new AssertionError(e); + } + } + + @Override + public long longHash(String s, LongHashFunction hashFunction, int off, int len) { + char[] value = (char[]) UnsafeAccess.UNSAFE.getObject(s, valueOffset); + return hashFunction.hashChars(value, off, len); + } + + @Override + public void hash(final String s, final LongTupleHashFunction hashFunction, + final int off, final int len, final long[] result) { + final char[] value = (char[]) UnsafeAccess.UNSAFE.getObject(s, valueOffset); + hashFunction.hashChars(value, off, len, result); + } +} diff --git a/dev.skidfuscator.sdk/src/main/java/sdk/Primitives.java b/dev.skidfuscator.sdk/src/main/java/sdk/Primitives.java new file mode 100644 index 0000000..8953a79 --- /dev/null +++ b/dev.skidfuscator.sdk/src/main/java/sdk/Primitives.java @@ -0,0 +1,65 @@ +/* + * Copyright 2014 Higher Frequency Trading http://www.higherfrequencytrading.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sdk; + +import static java.nio.ByteOrder.LITTLE_ENDIAN; +import static java.nio.ByteOrder.nativeOrder; + +final class Primitives { + + private Primitives() {} + + static final boolean NATIVE_LITTLE_ENDIAN = nativeOrder() == LITTLE_ENDIAN; + + static long unsignedInt(int i) { + return i & 0xFFFFFFFFL; + } + + static int unsignedShort(int s) { + return s & 0xFFFF; + } + + static int unsignedByte(int b) { + return b & 0xFF; + } + + private static final ByteOrderHelper H2LE = NATIVE_LITTLE_ENDIAN ? new ByteOrderHelper() : new ByteOrderHelperReverse(); + private static final ByteOrderHelper H2BE = NATIVE_LITTLE_ENDIAN ? new ByteOrderHelperReverse() : new ByteOrderHelper(); + + static long nativeToLittleEndian(final long v) { return H2LE.adjustByteOrder(v); } + static int nativeToLittleEndian(final int v) { return H2LE.adjustByteOrder(v); } + static short nativeToLittleEndian(final short v) { return H2LE.adjustByteOrder(v); } + static char nativeToLittleEndian(final char v) { return H2LE.adjustByteOrder(v); } + + static long nativeToBigEndian(final long v) { return H2BE.adjustByteOrder(v); } + static int nativeToBigEndian(final int v) { return H2BE.adjustByteOrder(v); } + static short nativeToBigEndian(final short v) { return H2BE.adjustByteOrder(v); } + static char nativeToBigEndian(final char v) { return H2BE.adjustByteOrder(v); } + + private static class ByteOrderHelper { + long adjustByteOrder(final long v) { return v; } + int adjustByteOrder(final int v) { return v; } + short adjustByteOrder(final short v) { return v; } + char adjustByteOrder(final char v) { return v; } + } + private static class ByteOrderHelperReverse extends ByteOrderHelper { + long adjustByteOrder(final long v) { return Long.reverseBytes(v); } + int adjustByteOrder(final int v) { return Integer.reverseBytes(v); } + short adjustByteOrder(final short v) { return Short.reverseBytes(v); } + char adjustByteOrder(final char v) { return Character.reverseBytes(v); } + } +} \ No newline at end of file diff --git a/dev.skidfuscator.sdk/src/main/java/sdk/SDK.java b/dev.skidfuscator.sdk/src/main/java/sdk/SDK.java new file mode 100644 index 0000000..2c7289c --- /dev/null +++ b/dev.skidfuscator.sdk/src/main/java/sdk/SDK.java @@ -0,0 +1,7 @@ +package sdk; + +public class SDK { + public static String hash(String s) { + return LongHashFunction.xx3().hashChars(s) + ""; + } +} diff --git a/dev.skidfuscator.sdk/src/main/java/sdk/StringHash.java b/dev.skidfuscator.sdk/src/main/java/sdk/StringHash.java new file mode 100644 index 0000000..8f8d770 --- /dev/null +++ b/dev.skidfuscator.sdk/src/main/java/sdk/StringHash.java @@ -0,0 +1,22 @@ +/* + * Copyright 2014 Higher Frequency Trading http://www.higherfrequencytrading.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sdk; + +interface StringHash { + long longHash(String s, LongHashFunction hashFunction, int off, int len); + void hash(String s, LongTupleHashFunction hashFunction, int off, int len, long[] result); +} \ No newline at end of file diff --git a/dev.skidfuscator.sdk/src/main/java/sdk/UnknownJvmStringHash.java b/dev.skidfuscator.sdk/src/main/java/sdk/UnknownJvmStringHash.java new file mode 100644 index 0000000..6824cb9 --- /dev/null +++ b/dev.skidfuscator.sdk/src/main/java/sdk/UnknownJvmStringHash.java @@ -0,0 +1,32 @@ +/* + * Copyright 2014 Higher Frequency Trading http://www.higherfrequencytrading.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sdk; + +enum UnknownJvmStringHash implements StringHash { + INSTANCE; + + @Override + public long longHash(String s, LongHashFunction hashFunction, int off, int len) { + return hashFunction.hashNativeChars(s, off, len); + } + + @Override + public void hash(final String s, final LongTupleHashFunction hashFunction, + final int off, final int len, final long[] result) { + LongTupleHashFunction.hashNativeChars(hashFunction, s, off, len, result); + } +} diff --git a/dev.skidfuscator.sdk/src/main/java/sdk/UnsafeAccess.java b/dev.skidfuscator.sdk/src/main/java/sdk/UnsafeAccess.java new file mode 100644 index 0000000..38f557b --- /dev/null +++ b/dev.skidfuscator.sdk/src/main/java/sdk/UnsafeAccess.java @@ -0,0 +1,149 @@ +/* + * Copyright 2014 Higher Frequency Trading http://www.higherfrequencytrading.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sdk; + +import java.lang.reflect.Field; +import java.nio.ByteOrder; +import sun.misc.Unsafe; + +import static sdk.Primitives.*; + +public class UnsafeAccess extends Access { + static final UnsafeAccess INSTANCE; + private static final Access INSTANCE_NON_NATIVE; + + // for test only + static final UnsafeAccess OLD_INSTANCE = NATIVE_LITTLE_ENDIAN + ? new OldUnsafeAccessLittleEndian() + : new OldUnsafeAccessBigEndian(); + + static final Unsafe UNSAFE; + + static final long BOOLEAN_BASE; + static final long BYTE_BASE; + static final long CHAR_BASE; + static final long SHORT_BASE; + static final long INT_BASE; + static final long LONG_BASE; + + static final byte TRUE_BYTE_VALUE; + static final byte FALSE_BYTE_VALUE; + + static { + try { + Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); + theUnsafe.setAccessible(true); + UNSAFE = (Unsafe) theUnsafe.get(null); + + BOOLEAN_BASE = UNSAFE.arrayBaseOffset(boolean[].class); + BYTE_BASE = UNSAFE.arrayBaseOffset(byte[].class); + CHAR_BASE = UNSAFE.arrayBaseOffset(char[].class); + SHORT_BASE = UNSAFE.arrayBaseOffset(short[].class); + INT_BASE = UNSAFE.arrayBaseOffset(int[].class); + LONG_BASE = UNSAFE.arrayBaseOffset(long[].class); + + TRUE_BYTE_VALUE = (byte)UNSAFE.getInt(new boolean[] {true, true, true, true}, + BOOLEAN_BASE); + FALSE_BYTE_VALUE = (byte)UNSAFE.getInt(new boolean[] {false, false, false, false}, + BOOLEAN_BASE); + } catch (final Exception e) { + throw new AssertionError(e); + } + + boolean hasGetByte = true; + try { + UNSAFE.getByte(new byte[1], BYTE_BASE); + } catch (final Throwable ignore) { + // Unsafe in pre-Nougat Android does not have getByte(), fall back to workround + hasGetByte = false; + } + + INSTANCE = hasGetByte ? new UnsafeAccess() : OLD_INSTANCE; + INSTANCE_NON_NATIVE = Access.newDefaultReverseAccess(INSTANCE); + } + + private UnsafeAccess() {} + + @Override + public long getLong(Object input, long offset) { + return UNSAFE.getLong(input, offset); + } + + @Override + public long getUnsignedInt(Object input, long offset) { + return unsignedInt(getInt(input, offset)); + } + + @Override + public int getInt(Object input, long offset) { + return UNSAFE.getInt(input, offset); + } + + @Override + public int getUnsignedShort(Object input, long offset) { + return unsignedShort(getShort(input, offset)); + } + + @Override + public int getShort(Object input, long offset) { + return UNSAFE.getShort(input, offset); + } + + @Override + public int getUnsignedByte(Object input, long offset) { + return unsignedByte(getByte(input, offset)); + } + + @Override + public int getByte(Object input, long offset) { + return UNSAFE.getByte(input, offset); + } + + @Override + public ByteOrder byteOrder(Object input) { + return ByteOrder.nativeOrder(); + } + + @Override + protected Access reverseAccess() { + return INSTANCE_NON_NATIVE; + } + + private static class OldUnsafeAccessLittleEndian extends UnsafeAccess { + @Override + public int getShort(final Object input, final long offset) { + return UNSAFE.getInt(input, offset - 2) >> 16; + } + + @Override + public int getByte(final Object input, final long offset) { + return UNSAFE.getInt(input, offset - 3) >> 24; + } + } + + private static class OldUnsafeAccessBigEndian extends UnsafeAccess { + @Override + public int getShort(final Object input, final long offset) { + return (int)(short)UNSAFE.getInt(input, offset - 2); + } + + @Override + public int getByte(final Object input, final long offset) { + return (int)(byte)UNSAFE.getInt(input, offset - 3); + } + } +} \ No newline at end of file diff --git a/dev.skidfuscator.sdk/src/main/java/sdk/Util.java b/dev.skidfuscator.sdk/src/main/java/sdk/Util.java new file mode 100644 index 0000000..66d282f --- /dev/null +++ b/dev.skidfuscator.sdk/src/main/java/sdk/Util.java @@ -0,0 +1,70 @@ +package sdk; + +import java.nio.ByteBuffer; +import sun.nio.ch.DirectBuffer; + +final class Util { + + /* Known java.vm.name list: + * + * HotSpot: + * - Java HotSpot(TM) xx-Bit Server VM + * - OpenJDK xx-Bit Server VM + * + * J9: + * - Eclipse OpenJ9 VM + * - IBM J9 VM + */ + static private boolean isHotSpotVM(final String name) { + return name.contains("HotSpot") || name.contains("OpenJDK"); + } + static private boolean isJ9VM(final String name) { + return name.contains("Eclipse OpenJ9") || name.contains("IBM J9"); + } + + static private boolean isZing(final String name) { + return name.startsWith("Zing"); + } + + static final StringHash VALID_STRING_HASH; + static { + StringHash stringHash = null; + try { + final String vmName = System.getProperty("java.vm.name"); + if (isHotSpotVM(vmName) || isJ9VM(vmName) || isZing(vmName)) { + final String javaVersion = System.getProperty("java.version"); + if (javaVersion.compareTo("1.7.0_06") >= 0) { + if (javaVersion.compareTo("1.9") >= 0) { + // JDK 9+ + stringHash = ModernCompactStringHash.INSTANCE; + } else { + // JDK [1.7.0_06, 9) + stringHash = ModernHotSpotStringHash.INSTANCE; + } + } else { + // JDK [1.7, 1.7.0_06) + stringHash = HotSpotPrior7u6StringHash.INSTANCE; + } + } else { + // try to initialize this version anyway + stringHash = HotSpotPrior7u6StringHash.INSTANCE; + } + } catch (final Throwable ignore) { + } finally { + if (null == stringHash) { + VALID_STRING_HASH = UnknownJvmStringHash.INSTANCE; + } else { + VALID_STRING_HASH = stringHash; + } + } + } + + static void checkArrayOffs(final int arrayLength, final int off, final int len) { + if (len < 0 || off < 0 || off + len > arrayLength || off + len < 0) + throw new IndexOutOfBoundsException(); + } + + static long getDirectBufferAddress(final ByteBuffer buff) { + return ((DirectBuffer)buff).address(); + } +} \ No newline at end of file diff --git a/dev.skidfuscator.sdk/src/main/java/sdk/XXH3.java b/dev.skidfuscator.sdk/src/main/java/sdk/XXH3.java new file mode 100644 index 0000000..edc2f56 --- /dev/null +++ b/dev.skidfuscator.sdk/src/main/java/sdk/XXH3.java @@ -0,0 +1,936 @@ +/* + * Copyright 2015 Higher Frequency Trading http://www.higherfrequencytrading.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package sdk; + +import java.nio.ByteBuffer; + +import static java.nio.ByteOrder.LITTLE_ENDIAN; +import static sdk.Maths.*; +import static sdk.UnsafeAccess.*; + +/** + * Adapted version of XXH3 implementation from https://github.com/Cyan4973/xxHash. + * This implementation provides endian-independant hash values, but it's slower on big-endian platforms. + */ +class XXH3 { + private static final Access unsafeLE = UnsafeAccess.INSTANCE.byteOrder(null, LITTLE_ENDIAN); + + /*! Pseudorandom secret taken directly from FARSH. */ + private static final byte[] XXH3_kSecret = { + (byte)0xb8, (byte)0xfe, (byte)0x6c, (byte)0x39, (byte)0x23, (byte)0xa4, (byte)0x4b, (byte)0xbe, (byte)0x7c, (byte)0x01, (byte)0x81, (byte)0x2c, (byte)0xf7, (byte)0x21, (byte)0xad, (byte)0x1c, + (byte)0xde, (byte)0xd4, (byte)0x6d, (byte)0xe9, (byte)0x83, (byte)0x90, (byte)0x97, (byte)0xdb, (byte)0x72, (byte)0x40, (byte)0xa4, (byte)0xa4, (byte)0xb7, (byte)0xb3, (byte)0x67, (byte)0x1f, + (byte)0xcb, (byte)0x79, (byte)0xe6, (byte)0x4e, (byte)0xcc, (byte)0xc0, (byte)0xe5, (byte)0x78, (byte)0x82, (byte)0x5a, (byte)0xd0, (byte)0x7d, (byte)0xcc, (byte)0xff, (byte)0x72, (byte)0x21, + (byte)0xb8, (byte)0x08, (byte)0x46, (byte)0x74, (byte)0xf7, (byte)0x43, (byte)0x24, (byte)0x8e, (byte)0xe0, (byte)0x35, (byte)0x90, (byte)0xe6, (byte)0x81, (byte)0x3a, (byte)0x26, (byte)0x4c, + (byte)0x3c, (byte)0x28, (byte)0x52, (byte)0xbb, (byte)0x91, (byte)0xc3, (byte)0x00, (byte)0xcb, (byte)0x88, (byte)0xd0, (byte)0x65, (byte)0x8b, (byte)0x1b, (byte)0x53, (byte)0x2e, (byte)0xa3, + (byte)0x71, (byte)0x64, (byte)0x48, (byte)0x97, (byte)0xa2, (byte)0x0d, (byte)0xf9, (byte)0x4e, (byte)0x38, (byte)0x19, (byte)0xef, (byte)0x46, (byte)0xa9, (byte)0xde, (byte)0xac, (byte)0xd8, + (byte)0xa8, (byte)0xfa, (byte)0x76, (byte)0x3f, (byte)0xe3, (byte)0x9c, (byte)0x34, (byte)0x3f, (byte)0xf9, (byte)0xdc, (byte)0xbb, (byte)0xc7, (byte)0xc7, (byte)0x0b, (byte)0x4f, (byte)0x1d, + (byte)0x8a, (byte)0x51, (byte)0xe0, (byte)0x4b, (byte)0xcd, (byte)0xb4, (byte)0x59, (byte)0x31, (byte)0xc8, (byte)0x9f, (byte)0x7e, (byte)0xc9, (byte)0xd9, (byte)0x78, (byte)0x73, (byte)0x64, + (byte)0xea, (byte)0xc5, (byte)0xac, (byte)0x83, (byte)0x34, (byte)0xd3, (byte)0xeb, (byte)0xc3, (byte)0xc5, (byte)0x81, (byte)0xa0, (byte)0xff, (byte)0xfa, (byte)0x13, (byte)0x63, (byte)0xeb, + (byte)0x17, (byte)0x0d, (byte)0xdd, (byte)0x51, (byte)0xb7, (byte)0xf0, (byte)0xda, (byte)0x49, (byte)0xd3, (byte)0x16, (byte)0x55, (byte)0x26, (byte)0x29, (byte)0xd4, (byte)0x68, (byte)0x9e, + (byte)0x2b, (byte)0x16, (byte)0xbe, (byte)0x58, (byte)0x7d, (byte)0x47, (byte)0xa1, (byte)0xfc, (byte)0x8f, (byte)0xf8, (byte)0xb8, (byte)0xd1, (byte)0x7a, (byte)0xd0, (byte)0x31, (byte)0xce, + (byte)0x45, (byte)0xcb, (byte)0x3a, (byte)0x8f, (byte)0x95, (byte)0x16, (byte)0x04, (byte)0x28, (byte)0xaf, (byte)0xd7, (byte)0xfb, (byte)0xca, (byte)0xbb, (byte)0x4b, (byte)0x40, (byte)0x7e, + }; + + // Primes + private static final long XXH_PRIME32_1 = 0x9E3779B1L; /*!< 0b10011110001101110111100110110001 */ + private static final long XXH_PRIME32_2 = 0x85EBCA77L; /*!< 0b10000101111010111100101001110111 */ + private static final long XXH_PRIME32_3 = 0xC2B2AE3DL; /*!< 0b11000010101100101010111000111101 */ + + private static final long XXH_PRIME64_1 = 0x9E3779B185EBCA87L; /*!< 0b1001111000110111011110011011000110000101111010111100101010000111 */ + private static final long XXH_PRIME64_2 = 0xC2B2AE3D27D4EB4FL; /*!< 0b1100001010110010101011100011110100100111110101001110101101001111 */ + private static final long XXH_PRIME64_3 = 0x165667B19E3779F9L; /*!< 0b0001011001010110011001111011000110011110001101110111100111111001 */ + private static final long XXH_PRIME64_4 = 0x85EBCA77C2B2AE63L; /*!< 0b1000010111101011110010100111011111000010101100101010111001100011 */ + private static final long XXH_PRIME64_5 = 0x27D4EB2F165667C5L; /*!< 0b0010011111010100111010110010111100010110010101100110011111000101 */ + + // only support fixed size secret + private static final long nbStripesPerBlock = (192 - 64) / 8; + private static final long block_len = 64 * nbStripesPerBlock; + + private static long XXH64_avalanche(long h64) { + h64 ^= h64 >>> 33; + h64 *= XXH_PRIME64_2; + h64 ^= h64 >>> 29; + h64 *= XXH_PRIME64_3; + return h64 ^ (h64 >>> 32); + } + private static long XXH3_avalanche(long h64) { + h64 ^= h64 >>> 37; + h64 *= 0x165667919E3779F9L; + return h64 ^ (h64 >>> 32); + } + private static long XXH3_rrmxmx(long h64, final long length) { + h64 ^= Long.rotateLeft(h64, 49) ^ Long.rotateLeft(h64, 24); + h64 *= 0x9FB21C651E98DF25L; + h64 ^= (h64 >>> 35) + length; + h64 *= 0x9FB21C651E98DF25L; + return h64 ^ (h64 >>> 28); + } + + private static long XXH3_mix16B(final long seed, final T input, final Access access, final long offIn, final long offSec) { + final long input_lo = access.i64(input, offIn); + final long input_hi = access.i64(input, offIn + 8); + return unsignedLongMulXorFold( + input_lo ^ (unsafeLE.i64(XXH3_kSecret, offSec) + seed), + input_hi ^ (unsafeLE.i64(XXH3_kSecret, offSec+8) - seed) + ); + } + + /* + * A bit slower than XXH3_mix16B, but handles multiply by zero better. + */ + private static long XXH128_mix32B_once(final long seed, final long offSec, long acc, final long input0, final long input1, final long input2, final long input3) { + acc += unsignedLongMulXorFold( + input0 ^ (unsafeLE.i64(XXH3_kSecret, offSec ) + seed), + input1 ^ (unsafeLE.i64(XXH3_kSecret, offSec + 8) - seed)); + return acc ^ (input2 + input3); + } + + private static long XXH3_mix2Accs(final long acc_lh, final long acc_rh, final byte[] secret, final long offSec) { + return unsignedLongMulXorFold( + acc_lh ^ unsafeLE.i64(secret, offSec), + acc_rh ^ unsafeLE.i64(secret, offSec+8) ); + } + + private static long XXH3_64bits_internal(final long seed, final byte[] secret, final T input, final Access access, final long off, final long length) { + if (length <= 16) { + // XXH3_len_0to16_64b + if (length > 8) { + // XXH3_len_9to16_64b + final long bitflip1 = (unsafeLE.i64(XXH3_kSecret, 24+BYTE_BASE) ^ unsafeLE.i64(XXH3_kSecret, 32+BYTE_BASE)) + seed; + final long bitflip2 = (unsafeLE.i64(XXH3_kSecret, 40+BYTE_BASE) ^ unsafeLE.i64(XXH3_kSecret, 48+BYTE_BASE)) - seed; + final long input_lo = access.i64(input, off) ^ bitflip1; + final long input_hi = access.i64(input, off + length - 8) ^ bitflip2; + final long acc = length + Long.reverseBytes(input_lo) + input_hi + unsignedLongMulXorFold(input_lo, input_hi); + return XXH3_avalanche(acc); + } + if (length >= 4) { + // XXH3_len_4to8_64b + long s = seed ^ Long.reverseBytes(seed & 0xFFFFFFFFL); + final long input1 = (long)access.i32(input, off); // high int will be shifted + final long input2 = access.u32(input, off + length - 4); + final long bitflip = (unsafeLE.i64(XXH3_kSecret, 8+BYTE_BASE) ^ unsafeLE.i64(XXH3_kSecret, 16+BYTE_BASE)) - s; + final long keyed = (input2 + (input1 << 32)) ^ bitflip; + return XXH3_rrmxmx(keyed, length); + } + if (length != 0) { + // XXH3_len_1to3_64b + final int c1 = access.u8(input, off + 0); + final int c2 = access.i8(input, off + (length >> 1)); // high 3 bytes will be shifted + final int c3 = access.u8(input, off + length - 1); + final long combined = Primitives.unsignedInt((c1 << 16) | (c2 << 24) | c3 | ((int)length << 8)); + final long bitflip = Primitives.unsignedInt(unsafeLE.i32(XXH3_kSecret, BYTE_BASE) ^ unsafeLE.i32(XXH3_kSecret, 4+BYTE_BASE)) + seed; + return XXH64_avalanche(combined ^ bitflip); + } + return XXH64_avalanche(seed ^ unsafeLE.i64(XXH3_kSecret, 56+BYTE_BASE) ^ unsafeLE.i64(XXH3_kSecret, 64+BYTE_BASE)); + } + if (length <= 128) { + // XXH3_len_17to128_64b + long acc = length * XXH_PRIME64_1; + + if (length > 32) { + if (length > 64) { + if (length > 96) { + acc += XXH3_mix16B(seed, input, access, off + 48, BYTE_BASE + 96); + acc += XXH3_mix16B(seed, input, access, off + length - 64, BYTE_BASE + 112); + } + acc += XXH3_mix16B(seed, input, access, off + 32, BYTE_BASE + 64); + acc += XXH3_mix16B(seed, input, access, off + length - 48, BYTE_BASE + 80); + } + acc += XXH3_mix16B(seed, input, access, off + 16, BYTE_BASE + 32); + acc += XXH3_mix16B(seed, input, access, off + length - 32, BYTE_BASE + 48); + } + acc += XXH3_mix16B(seed, input, access, off, BYTE_BASE); + acc += XXH3_mix16B(seed, input, access, off + length - 16, BYTE_BASE + 16); + + return XXH3_avalanche(acc); + } + if (length <= 240) { + // XXH3_len_129to240_64b + long acc = length * XXH_PRIME64_1; + final int nbRounds = (int)length / 16; + int i = 0; + for (; i < 8; ++i) { + acc += XXH3_mix16B(seed, input, access, off + 16*i, BYTE_BASE + 16*i); + } + acc = XXH3_avalanche(acc); + + for (; i < nbRounds; ++i) { + acc += XXH3_mix16B(seed, input, access, off + 16*i, BYTE_BASE + 16*(i-8) + 3); + } + + /* last bytes */ + acc += XXH3_mix16B(seed, input, access, off + length - 16, BYTE_BASE + 136 - 17); + return XXH3_avalanche(acc); + } + + // XXH3_hashLong_64b_internal + long acc_0 = XXH_PRIME32_3; + long acc_1 = XXH_PRIME64_1; + long acc_2 = XXH_PRIME64_2; + long acc_3 = XXH_PRIME64_3; + long acc_4 = XXH_PRIME64_4; + long acc_5 = XXH_PRIME32_2; + long acc_6 = XXH_PRIME64_5; + long acc_7 = XXH_PRIME32_1; + + // XXH3_hashLong_internal_loop + final long nb_blocks = (length - 1) / block_len; + for (long n = 0; n < nb_blocks; n++) { + // XXH3_accumulate + final long offBlock = off + n * block_len; + for (long s = 0; s < nbStripesPerBlock; s++ ) { + // XXH3_accumulate_512 + final long offStripe = offBlock + s * 64; + final long offSec = s * 8; + { + final long data_val_0 = access.i64(input, offStripe + 8*0); + final long data_val_1 = access.i64(input, offStripe + 8*1); + final long data_key_0 = data_val_0 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*0); + final long data_key_1 = data_val_1 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*1); + /* swap adjacent lanes */ + acc_0 += data_val_1 + (0xFFFFFFFFL & data_key_0) * (data_key_0 >>> 32); + acc_1 += data_val_0 + (0xFFFFFFFFL & data_key_1) * (data_key_1 >>> 32); + } + { + final long data_val_0 = access.i64(input, offStripe + 8*2); + final long data_val_1 = access.i64(input, offStripe + 8*3); + final long data_key_0 = data_val_0 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*2); + final long data_key_1 = data_val_1 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*3); + /* swap adjacent lanes */ + acc_2 += data_val_1 + (0xFFFFFFFFL & data_key_0) * (data_key_0 >>> 32); + acc_3 += data_val_0 + (0xFFFFFFFFL & data_key_1) * (data_key_1 >>> 32); + } + { + final long data_val_0 = access.i64(input, offStripe + 8*4); + final long data_val_1 = access.i64(input, offStripe + 8*5); + final long data_key_0 = data_val_0 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*4); + final long data_key_1 = data_val_1 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*5); + /* swap adjacent lanes */ + acc_4 += data_val_1 + (0xFFFFFFFFL & data_key_0) * (data_key_0 >>> 32); + acc_5 += data_val_0 + (0xFFFFFFFFL & data_key_1) * (data_key_1 >>> 32); + } + { + final long data_val_0 = access.i64(input, offStripe + 8*6); + final long data_val_1 = access.i64(input, offStripe + 8*7); + final long data_key_0 = data_val_0 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*6); + final long data_key_1 = data_val_1 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*7); + /* swap adjacent lanes */ + acc_6 += data_val_1 + (0xFFFFFFFFL & data_key_0) * (data_key_0 >>> 32); + acc_7 += data_val_0 + (0xFFFFFFFFL & data_key_1) * (data_key_1 >>> 32); + } + } + + // XXH3_scrambleAcc_scalar + final long offSec = BYTE_BASE + 192 - 64; + acc_0 = (acc_0 ^ (acc_0 >>> 47) ^ unsafeLE.i64(secret, offSec + 8*0)) * XXH_PRIME32_1; + acc_1 = (acc_1 ^ (acc_1 >>> 47) ^ unsafeLE.i64(secret, offSec + 8*1)) * XXH_PRIME32_1; + acc_2 = (acc_2 ^ (acc_2 >>> 47) ^ unsafeLE.i64(secret, offSec + 8*2)) * XXH_PRIME32_1; + acc_3 = (acc_3 ^ (acc_3 >>> 47) ^ unsafeLE.i64(secret, offSec + 8*3)) * XXH_PRIME32_1; + acc_4 = (acc_4 ^ (acc_4 >>> 47) ^ unsafeLE.i64(secret, offSec + 8*4)) * XXH_PRIME32_1; + acc_5 = (acc_5 ^ (acc_5 >>> 47) ^ unsafeLE.i64(secret, offSec + 8*5)) * XXH_PRIME32_1; + acc_6 = (acc_6 ^ (acc_6 >>> 47) ^ unsafeLE.i64(secret, offSec + 8*6)) * XXH_PRIME32_1; + acc_7 = (acc_7 ^ (acc_7 >>> 47) ^ unsafeLE.i64(secret, offSec + 8*7)) * XXH_PRIME32_1; + } + + /* last partial block */ + final long nbStripes = ((length - 1) - (block_len * nb_blocks)) / 64; + final long offBlock = off + block_len * nb_blocks; + for (long s = 0; s < nbStripes; s++) { + // XXH3_accumulate_512 + final long offStripe = offBlock + s * 64; + final long offSec = s * 8; + { + final long data_val_0 = access.i64(input, offStripe + 8*0); + final long data_val_1 = access.i64(input, offStripe + 8*1); + final long data_key_0 = data_val_0 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*0); + final long data_key_1 = data_val_1 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*1); + /* swap adjacent lanes */ + acc_0 += data_val_1 + (0xFFFFFFFFL & data_key_0) * (data_key_0 >>> 32); + acc_1 += data_val_0 + (0xFFFFFFFFL & data_key_1) * (data_key_1 >>> 32); + } + { + final long data_val_0 = access.i64(input, offStripe + 8*2); + final long data_val_1 = access.i64(input, offStripe + 8*3); + final long data_key_0 = data_val_0 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*2); + final long data_key_1 = data_val_1 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*3); + /* swap adjacent lanes */ + acc_2 += data_val_1 + (0xFFFFFFFFL & data_key_0) * (data_key_0 >>> 32); + acc_3 += data_val_0 + (0xFFFFFFFFL & data_key_1) * (data_key_1 >>> 32); + } + { + final long data_val_0 = access.i64(input, offStripe + 8*4); + final long data_val_1 = access.i64(input, offStripe + 8*5); + final long data_key_0 = data_val_0 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*4); + final long data_key_1 = data_val_1 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*5); + /* swap adjacent lanes */ + acc_4 += data_val_1 + (0xFFFFFFFFL & data_key_0) * (data_key_0 >>> 32); + acc_5 += data_val_0 + (0xFFFFFFFFL & data_key_1) * (data_key_1 >>> 32); + } + { + final long data_val_0 = access.i64(input, offStripe + 8*6); + final long data_val_1 = access.i64(input, offStripe + 8*7); + final long data_key_0 = data_val_0 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*6); + final long data_key_1 = data_val_1 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*7); + /* swap adjacent lanes */ + acc_6 += data_val_1 + (0xFFFFFFFFL & data_key_0) * (data_key_0 >>> 32); + acc_7 += data_val_0 + (0xFFFFFFFFL & data_key_1) * (data_key_1 >>> 32); + } + } + + /* last stripe */ + // XXH3_accumulate_512 + final long offStripe = off + length - 64; + final long offSec = 192 - 64 - 7; + { + final long data_val_0 = access.i64(input, offStripe + 8*0); + final long data_val_1 = access.i64(input, offStripe + 8*1); + final long data_key_0 = data_val_0 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*0); + final long data_key_1 = data_val_1 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*1); + /* swap adjacent lanes */ + acc_0 += data_val_1 + (0xFFFFFFFFL & data_key_0) * (data_key_0 >>> 32); + acc_1 += data_val_0 + (0xFFFFFFFFL & data_key_1) * (data_key_1 >>> 32); + } + { + final long data_val_0 = access.i64(input, offStripe + 8*2); + final long data_val_1 = access.i64(input, offStripe + 8*3); + final long data_key_0 = data_val_0 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*2); + final long data_key_1 = data_val_1 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*3); + /* swap adjacent lanes */ + acc_2 += data_val_1 + (0xFFFFFFFFL & data_key_0) * (data_key_0 >>> 32); + acc_3 += data_val_0 + (0xFFFFFFFFL & data_key_1) * (data_key_1 >>> 32); + } + { + final long data_val_0 = access.i64(input, offStripe + 8*4); + final long data_val_1 = access.i64(input, offStripe + 8*5); + final long data_key_0 = data_val_0 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*4); + final long data_key_1 = data_val_1 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*5); + /* swap adjacent lanes */ + acc_4 += data_val_1 + (0xFFFFFFFFL & data_key_0) * (data_key_0 >>> 32); + acc_5 += data_val_0 + (0xFFFFFFFFL & data_key_1) * (data_key_1 >>> 32); + } + { + final long data_val_0 = access.i64(input, offStripe + 8*6); + final long data_val_1 = access.i64(input, offStripe + 8*7); + final long data_key_0 = data_val_0 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*6); + final long data_key_1 = data_val_1 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*7); + /* swap adjacent lanes */ + acc_6 += data_val_1 + (0xFFFFFFFFL & data_key_0) * (data_key_0 >>> 32); + acc_7 += data_val_0 + (0xFFFFFFFFL & data_key_1) * (data_key_1 >>> 32); + } + + // XXH3_mergeAccs + final long result64 = length * XXH_PRIME64_1 + + XXH3_mix2Accs(acc_0, acc_1, secret, BYTE_BASE + 11) + + XXH3_mix2Accs(acc_2, acc_3, secret, BYTE_BASE + 11 + 16) + + XXH3_mix2Accs(acc_4, acc_5, secret, BYTE_BASE + 11 + 16 * 2) + + XXH3_mix2Accs(acc_6, acc_7, secret, BYTE_BASE + 11 + 16 * 3); + + return XXH3_avalanche(result64); + } + + private static long XXH3_128bits_internal(final long seed, final byte[] secret, final T input, final Access access, final long off, final long length, final long[] result) { + if (length <= 16) { + // XXH3_len_0to16_128b + if (length > 8) { + // XXH3_len_9to16_128b + final long bitflipl = (unsafeLE.i64(XXH3_kSecret, 32+BYTE_BASE) ^ unsafeLE.i64(XXH3_kSecret, 40+BYTE_BASE)) - seed; + final long bitfliph = (unsafeLE.i64(XXH3_kSecret, 48+BYTE_BASE) ^ unsafeLE.i64(XXH3_kSecret, 56+BYTE_BASE)) + seed; + long input_hi = access.i64(input, off + length - 8); + final long input_lo = access.i64(input, off) ^ input_hi ^ bitflipl; + long m128_lo = input_lo * XXH_PRIME64_1; + long m128_hi = Maths.unsignedLongMulHigh(input_lo, XXH_PRIME64_1); + m128_lo += (length - 1) << 54; + input_hi ^= bitfliph; + m128_hi += input_hi + Primitives.unsignedInt((int)input_hi) * (XXH_PRIME32_2 - 1); + m128_lo ^= Long.reverseBytes(m128_hi); + + final long low = XXH3_avalanche(m128_lo * XXH_PRIME64_2); + if (null != result) { + result[0] = low; + result[1] = XXH3_avalanche(Maths.unsignedLongMulHigh(m128_lo, XXH_PRIME64_2) + m128_hi * XXH_PRIME64_2); + } + return low; + } + if (length >= 4) { + // XXH3_len_4to8_128b + long s = seed ^ Long.reverseBytes(seed & 0xFFFFFFFFL); + final long input_lo = access.u32(input, off); + final long input_hi = (long)access.i32(input, off + length - 4); // high int will be shifted + + final long bitflip = (unsafeLE.i64(XXH3_kSecret, 16+BYTE_BASE) ^ unsafeLE.i64(XXH3_kSecret, 24+BYTE_BASE)) + s; + final long keyed = (input_lo + (input_hi << 32)) ^ bitflip; + final long pl = XXH_PRIME64_1 + (length << 2); /* Shift len to the left to ensure it is even, this avoids even multiplies. */ + long m128_lo = keyed * pl; + long m128_hi = Maths.unsignedLongMulHigh(keyed, pl); + m128_hi += (m128_lo << 1); + m128_lo ^= (m128_hi >>> 3); + + m128_lo ^= m128_lo >>> 35; + m128_lo *= 0x9FB21C651E98DF25L; + m128_lo ^= m128_lo >>> 28; + + if (null != result) { + result[0] = m128_lo; + result[1] = XXH3_avalanche(m128_hi); + } + return m128_lo; + } + if (length != 0) { + // XXH3_len_1to3_128b + final int c1 = access.u8(input, off + 0); + final int c2 = access.i8(input, off + (length >> 1)); // high 3 bytes will be shifted + final int c3 = access.u8(input, off + length - 1); + final int combinedl = (c1 << 16) | (c2 << 24) | c3 | ((int)length << 8); + final int combinedh = Integer.rotateLeft(Integer.reverseBytes(combinedl), 13); + final long bitflipl = Primitives.unsignedInt(unsafeLE.i32(XXH3_kSecret, BYTE_BASE) ^ unsafeLE.i32(XXH3_kSecret, BYTE_BASE+4)) + seed; + final long bitfliph = Primitives.unsignedInt(unsafeLE.i32(XXH3_kSecret, BYTE_BASE+8) ^ unsafeLE.i32(XXH3_kSecret, BYTE_BASE+12)) - seed; + + final long low = XXH64_avalanche(Primitives.unsignedInt(combinedl) ^ bitflipl); + if (null != result) { + result[0] = low; + result[1] = XXH64_avalanche(Primitives.unsignedInt(combinedh) ^ bitfliph); + } + return low; + } + final long low = XXH64_avalanche(seed ^ unsafeLE.i64(XXH3_kSecret, BYTE_BASE+64) ^ unsafeLE.i64(XXH3_kSecret, BYTE_BASE+72)); + if (null != result) { + result[0] = low; + result[1] = XXH64_avalanche(seed ^ unsafeLE.i64(XXH3_kSecret, BYTE_BASE+80) ^ unsafeLE.i64(XXH3_kSecret, BYTE_BASE+88)); + } + return low; + } + if (length <= 128) { + // XXH3_len_17to128_128b + long acc0 = length * XXH_PRIME64_1; + long acc1 = 0; + if (length > 32) { + if (length > 64) { + if (length > 96) { + final long input0 = access.i64(input, off + 48); + final long input1 = access.i64(input, off + 48 + 8); + final long input2 = access.i64(input, off + length - 64); + final long input3 = access.i64(input, off + length - 64 + 8); + acc0 = XXH128_mix32B_once(seed, BYTE_BASE + 96, acc0, input0, input1, input2, input3); + acc1 = XXH128_mix32B_once(seed, BYTE_BASE + 96 + 16, acc1, input2, input3, input0, input1); + } + final long input0 = access.i64(input, off + 32); + final long input1 = access.i64(input, off + 32 + 8); + final long input2 = access.i64(input, off + length - 48); + final long input3 = access.i64(input, off + length - 48 + 8); + acc0 = XXH128_mix32B_once(seed, BYTE_BASE + 64, acc0, input0, input1, input2, input3); + acc1 = XXH128_mix32B_once(seed, BYTE_BASE + 64 + 16, acc1, input2, input3, input0, input1); + } + final long input0 = access.i64(input, off + 16); + final long input1 = access.i64(input, off + 16 + 8); + final long input2 = access.i64(input, off + length - 32); + final long input3 = access.i64(input, off + length - 32 + 8); + acc0 = XXH128_mix32B_once(seed, BYTE_BASE + 32, acc0, input0, input1, input2, input3); + acc1 = XXH128_mix32B_once(seed, BYTE_BASE + 32 + 16, acc1, input2, input3, input0, input1); + } + final long input0 = access.i64(input, off + 0); + final long input1 = access.i64(input, off + 0 + 8); + final long input2 = access.i64(input, off + length - 16); + final long input3 = access.i64(input, off + length - 16 + 8); + acc0 = XXH128_mix32B_once(seed, BYTE_BASE, acc0, input0, input1, input2, input3); + acc1 = XXH128_mix32B_once(seed, BYTE_BASE + 16, acc1, input2, input3, input0, input1); + + final long low = XXH3_avalanche(acc0 + acc1); + if (null != result) { + result[0] = low; + result[1] = -XXH3_avalanche(acc0*XXH_PRIME64_1 + acc1*XXH_PRIME64_4 + (length - seed)*XXH_PRIME64_2); + } + return low; + } + + if (length <= 240) { + // XXH3_len_129to240_128b + final int nbRounds = (int)length / 32; + long acc0 = length * XXH_PRIME64_1; + long acc1 = 0; + int i = 0; + for (; i < 4; ++i) { + final long input0 = access.i64(input, off + 32*i); + final long input1 = access.i64(input, off + 32*i + 8); + final long input2 = access.i64(input, off + 32*i + 16); + final long input3 = access.i64(input, off + 32*i + 24); + acc0 = XXH128_mix32B_once(seed, BYTE_BASE + 32*i, acc0, input0, input1, input2, input3); + acc1 = XXH128_mix32B_once(seed, BYTE_BASE + 32*i + 16, acc1, input2, input3, input0, input1); + } + acc0 = XXH3_avalanche(acc0); + acc1 = XXH3_avalanche(acc1); + + for (; i < nbRounds; ++i) { + final long input0 = access.i64(input, off + 32*i); + final long input1 = access.i64(input, off + 32*i + 8); + final long input2 = access.i64(input, off + 32*i + 16); + final long input3 = access.i64(input, off + 32*i + 24); + acc0 = XXH128_mix32B_once(seed, BYTE_BASE + 3 + 32*(i-4), acc0, input0, input1, input2, input3); + acc1 = XXH128_mix32B_once(seed, BYTE_BASE + 3 + 32*(i-4) + 16, acc1, input2, input3, input0, input1); + } + + /* last bytes */ + final long input0 = access.i64(input, off + length - 16); + final long input1 = access.i64(input, off + length - 16 + 8); + final long input2 = access.i64(input, off + length - 32); + final long input3 = access.i64(input, off + length - 32 + 8); + acc0 = XXH128_mix32B_once(-seed, BYTE_BASE + 136 - 17 - 16, acc0, input0, input1, input2, input3); + acc1 = XXH128_mix32B_once(-seed, BYTE_BASE + 136 - 17 , acc1, input2, input3, input0, input1); + + final long low = XXH3_avalanche(acc0 + acc1); + if (null != result) { + result[0] = low; + result[1] = -XXH3_avalanche(acc0*XXH_PRIME64_1 + acc1*XXH_PRIME64_4 + (length - seed)*XXH_PRIME64_2); + } + return low; + } + + // XXH3_hashLong_128b_internal + long acc_0 = XXH_PRIME32_3; + long acc_1 = XXH_PRIME64_1; + long acc_2 = XXH_PRIME64_2; + long acc_3 = XXH_PRIME64_3; + long acc_4 = XXH_PRIME64_4; + long acc_5 = XXH_PRIME32_2; + long acc_6 = XXH_PRIME64_5; + long acc_7 = XXH_PRIME32_1; + + // XXH3_hashLong_internal_loop + final long nb_blocks = (length - 1) / block_len; + for (long n = 0; n < nb_blocks; n++) { + // XXH3_accumulate + final long offBlock = off + n * block_len; + for (long s = 0; s < nbStripesPerBlock; s++ ) { + // XXH3_accumulate_512 + final long offStripe = offBlock + s * 64; + final long offSec = s * 8; + { + final long data_val_0 = access.i64(input, offStripe + 8*0); + final long data_val_1 = access.i64(input, offStripe + 8*1); + final long data_key_0 = data_val_0 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*0); + final long data_key_1 = data_val_1 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*1); + /* swap adjacent lanes */ + acc_0 += data_val_1 + (0xFFFFFFFFL & data_key_0) * (data_key_0 >>> 32); + acc_1 += data_val_0 + (0xFFFFFFFFL & data_key_1) * (data_key_1 >>> 32); + } + { + final long data_val_0 = access.i64(input, offStripe + 8*2); + final long data_val_1 = access.i64(input, offStripe + 8*3); + final long data_key_0 = data_val_0 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*2); + final long data_key_1 = data_val_1 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*3); + /* swap adjacent lanes */ + acc_2 += data_val_1 + (0xFFFFFFFFL & data_key_0) * (data_key_0 >>> 32); + acc_3 += data_val_0 + (0xFFFFFFFFL & data_key_1) * (data_key_1 >>> 32); + } + { + final long data_val_0 = access.i64(input, offStripe + 8*4); + final long data_val_1 = access.i64(input, offStripe + 8*5); + final long data_key_0 = data_val_0 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*4); + final long data_key_1 = data_val_1 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*5); + /* swap adjacent lanes */ + acc_4 += data_val_1 + (0xFFFFFFFFL & data_key_0) * (data_key_0 >>> 32); + acc_5 += data_val_0 + (0xFFFFFFFFL & data_key_1) * (data_key_1 >>> 32); + } + { + final long data_val_0 = access.i64(input, offStripe + 8*6); + final long data_val_1 = access.i64(input, offStripe + 8*7); + final long data_key_0 = data_val_0 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*6); + final long data_key_1 = data_val_1 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*7); + /* swap adjacent lanes */ + acc_6 += data_val_1 + (0xFFFFFFFFL & data_key_0) * (data_key_0 >>> 32); + acc_7 += data_val_0 + (0xFFFFFFFFL & data_key_1) * (data_key_1 >>> 32); + } + } + + // XXH3_scrambleAcc_scalar + final long offSec = BYTE_BASE + 192 - 64; + acc_0 = (acc_0 ^ (acc_0 >>> 47) ^ unsafeLE.i64(secret, offSec + 8*0)) * XXH_PRIME32_1; + acc_1 = (acc_1 ^ (acc_1 >>> 47) ^ unsafeLE.i64(secret, offSec + 8*1)) * XXH_PRIME32_1; + acc_2 = (acc_2 ^ (acc_2 >>> 47) ^ unsafeLE.i64(secret, offSec + 8*2)) * XXH_PRIME32_1; + acc_3 = (acc_3 ^ (acc_3 >>> 47) ^ unsafeLE.i64(secret, offSec + 8*3)) * XXH_PRIME32_1; + acc_4 = (acc_4 ^ (acc_4 >>> 47) ^ unsafeLE.i64(secret, offSec + 8*4)) * XXH_PRIME32_1; + acc_5 = (acc_5 ^ (acc_5 >>> 47) ^ unsafeLE.i64(secret, offSec + 8*5)) * XXH_PRIME32_1; + acc_6 = (acc_6 ^ (acc_6 >>> 47) ^ unsafeLE.i64(secret, offSec + 8*6)) * XXH_PRIME32_1; + acc_7 = (acc_7 ^ (acc_7 >>> 47) ^ unsafeLE.i64(secret, offSec + 8*7)) * XXH_PRIME32_1; + } + + /* last partial block */ + final long nbStripes = ((length - 1) - (block_len * nb_blocks)) / 64; + final long offBlock = off + block_len * nb_blocks; + for (long s = 0; s < nbStripes; s++) { + // XXH3_accumulate_512 + final long offStripe = offBlock + s * 64; + final long offSec = s * 8; + { + final long data_val_0 = access.i64(input, offStripe + 8*0); + final long data_val_1 = access.i64(input, offStripe + 8*1); + final long data_key_0 = data_val_0 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*0); + final long data_key_1 = data_val_1 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*1); + /* swap adjacent lanes */ + acc_0 += data_val_1 + (0xFFFFFFFFL & data_key_0) * (data_key_0 >>> 32); + acc_1 += data_val_0 + (0xFFFFFFFFL & data_key_1) * (data_key_1 >>> 32); + } + { + final long data_val_0 = access.i64(input, offStripe + 8*2); + final long data_val_1 = access.i64(input, offStripe + 8*3); + final long data_key_0 = data_val_0 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*2); + final long data_key_1 = data_val_1 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*3); + /* swap adjacent lanes */ + acc_2 += data_val_1 + (0xFFFFFFFFL & data_key_0) * (data_key_0 >>> 32); + acc_3 += data_val_0 + (0xFFFFFFFFL & data_key_1) * (data_key_1 >>> 32); + } + { + final long data_val_0 = access.i64(input, offStripe + 8*4); + final long data_val_1 = access.i64(input, offStripe + 8*5); + final long data_key_0 = data_val_0 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*4); + final long data_key_1 = data_val_1 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*5); + /* swap adjacent lanes */ + acc_4 += data_val_1 + (0xFFFFFFFFL & data_key_0) * (data_key_0 >>> 32); + acc_5 += data_val_0 + (0xFFFFFFFFL & data_key_1) * (data_key_1 >>> 32); + } + { + final long data_val_0 = access.i64(input, offStripe + 8*6); + final long data_val_1 = access.i64(input, offStripe + 8*7); + final long data_key_0 = data_val_0 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*6); + final long data_key_1 = data_val_1 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*7); + /* swap adjacent lanes */ + acc_6 += data_val_1 + (0xFFFFFFFFL & data_key_0) * (data_key_0 >>> 32); + acc_7 += data_val_0 + (0xFFFFFFFFL & data_key_1) * (data_key_1 >>> 32); + } + } + + /* last stripe */ + // XXH3_accumulate_512 + final long offStripe = off + length - 64; + final long offSec = 192 - 64 - 7; + { + final long data_val_0 = access.i64(input, offStripe + 8*0); + final long data_val_1 = access.i64(input, offStripe + 8*1); + final long data_key_0 = data_val_0 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*0); + final long data_key_1 = data_val_1 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*1); + /* swap adjacent lanes */ + acc_0 += data_val_1 + (0xFFFFFFFFL & data_key_0) * (data_key_0 >>> 32); + acc_1 += data_val_0 + (0xFFFFFFFFL & data_key_1) * (data_key_1 >>> 32); + } + { + final long data_val_0 = access.i64(input, offStripe + 8*2); + final long data_val_1 = access.i64(input, offStripe + 8*3); + final long data_key_0 = data_val_0 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*2); + final long data_key_1 = data_val_1 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*3); + /* swap adjacent lanes */ + acc_2 += data_val_1 + (0xFFFFFFFFL & data_key_0) * (data_key_0 >>> 32); + acc_3 += data_val_0 + (0xFFFFFFFFL & data_key_1) * (data_key_1 >>> 32); + } + { + final long data_val_0 = access.i64(input, offStripe + 8*4); + final long data_val_1 = access.i64(input, offStripe + 8*5); + final long data_key_0 = data_val_0 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*4); + final long data_key_1 = data_val_1 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*5); + /* swap adjacent lanes */ + acc_4 += data_val_1 + (0xFFFFFFFFL & data_key_0) * (data_key_0 >>> 32); + acc_5 += data_val_0 + (0xFFFFFFFFL & data_key_1) * (data_key_1 >>> 32); + } + { + final long data_val_0 = access.i64(input, offStripe + 8*6); + final long data_val_1 = access.i64(input, offStripe + 8*7); + final long data_key_0 = data_val_0 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*6); + final long data_key_1 = data_val_1 ^ unsafeLE.i64(secret, BYTE_BASE + offSec + 8*7); + /* swap adjacent lanes */ + acc_6 += data_val_1 + (0xFFFFFFFFL & data_key_0) * (data_key_0 >>> 32); + acc_7 += data_val_0 + (0xFFFFFFFFL & data_key_1) * (data_key_1 >>> 32); + } + + // XXH3_mergeAccs + final long low = XXH3_avalanche(length * XXH_PRIME64_1 + + XXH3_mix2Accs(acc_0, acc_1, secret, BYTE_BASE + 11) + + XXH3_mix2Accs(acc_2, acc_3, secret, BYTE_BASE + 11 + 16) + + XXH3_mix2Accs(acc_4, acc_5, secret, BYTE_BASE + 11 + 16 * 2) + + XXH3_mix2Accs(acc_6, acc_7, secret, BYTE_BASE + 11 + 16 * 3)); + if (null != result) { + result[0] = low; + result[1] = XXH3_avalanche(~(length * XXH_PRIME64_2) + + XXH3_mix2Accs(acc_0, acc_1, secret, BYTE_BASE + 192 - 64 - 11) + + XXH3_mix2Accs(acc_2, acc_3, secret, BYTE_BASE + 192 - 64 - 11 + 16) + + XXH3_mix2Accs(acc_4, acc_5, secret, BYTE_BASE + 192 - 64 - 11 + 16 * 2) + + XXH3_mix2Accs(acc_6, acc_7, secret, BYTE_BASE + 192 - 64 - 11 + 16 * 3)); + } + return low; + } + + private static void XXH3_initCustomSecret(final byte[] customSecret, final long seed64) { + final int nbRounds = 192 / 16; + final ByteBuffer bb = ByteBuffer.wrap(customSecret).order(LITTLE_ENDIAN); + for (int i=0; i < nbRounds; i++) { + final long lo = unsafeLE.i64(XXH3_kSecret, BYTE_BASE + 16*i) + seed64; + final long hi = unsafeLE.i64(XXH3_kSecret, BYTE_BASE + 16*i + 8) - seed64; + bb.putLong(16 * i + 0, lo); + bb.putLong(16 * i + 8, hi); + } + } + + static LongHashFunction asLongHashFunctionWithoutSeed() { + return AsLongHashFunction.SEEDLESS_INSTANCE; + } + + private static class AsLongHashFunction extends LongHashFunction { + private static final long serialVersionUID = 0L; + private static final AsLongHashFunction SEEDLESS_INSTANCE = new AsLongHashFunction(); + + public long seed() { + return 0L; + } + + @Override + public long hashLong(long input) { + input = Primitives.nativeToLittleEndian(input); + final long s = seed() ^ Long.reverseBytes(seed() & 0xFFFFFFFFL); + final long bitflip = (unsafeLE.i64(XXH3.XXH3_kSecret, 8+BYTE_BASE) ^ unsafeLE.i64(XXH3.XXH3_kSecret, 16+BYTE_BASE)) - s; + final long keyed = Long.rotateLeft(input, 32) ^ bitflip; + return XXH3_rrmxmx(keyed, 8); + } + + @Override + public long hashInt(int input) { + input = Primitives.nativeToLittleEndian(input); + long s = seed() ^ Long.reverseBytes(seed() & 0xFFFFFFFFL); + final long bitflip = (unsafeLE.i64(XXH3.XXH3_kSecret, 8+BYTE_BASE) ^ unsafeLE.i64(XXH3.XXH3_kSecret, 16+BYTE_BASE)) - s; + final long keyed = (Primitives.unsignedInt(input) + (((long)input) << 32)) ^ bitflip; + return XXH3_rrmxmx(keyed, 4); + } + + @Override + public long hashShort(short input) { + input = Primitives.nativeToLittleEndian(input); + final int c1 = Primitives.unsignedByte((byte)input); + final int c2 = Primitives.unsignedShort(input) >>> 8; + final int c3 = c2; + final long combined = Primitives.unsignedInt((c1 << 16) | (c2 << 24) | c3 | (2 << 8)); + final long bitflip = (unsafeLE.u32(XXH3.XXH3_kSecret, BYTE_BASE) ^ unsafeLE.u32(XXH3.XXH3_kSecret, 4+BYTE_BASE)) + seed(); + return XXH64_avalanche(combined ^ bitflip); + } + + @Override + public long hashChar(char input) { + return hashShort((short) input); + } + + @Override + public long hashByte(byte input) { + final int c1 = Primitives.unsignedByte(input); + final int c2 = c1; + final int c3 = c1; + final long combined = Primitives.unsignedInt((c1 << 16) | (c2 << 24) | c3 | (1 << 8)); + final long bitflip = (unsafeLE.u32(XXH3.XXH3_kSecret, BYTE_BASE) ^ unsafeLE.u32(XXH3.XXH3_kSecret, 4+BYTE_BASE)) + seed(); + return XXH64_avalanche(combined ^ bitflip); + } + + @Override + public long hashVoid() { + return XXH64_avalanche(seed() ^ unsafeLE.i64(XXH3.XXH3_kSecret, 56+BYTE_BASE) ^ unsafeLE.i64(XXH3.XXH3_kSecret, 64+BYTE_BASE)); + } + + @Override + public long hash(final T input, final Access access, final long off, final long len) { + return XXH3.XXH3_64bits_internal(0, XXH3.XXH3_kSecret, input, access.byteOrder(input, LITTLE_ENDIAN), off, len); + } + } + + static LongHashFunction asLongHashFunctionWithSeed(final long seed) { + return 0 == seed ? AsLongHashFunction.SEEDLESS_INSTANCE : new AsLongHashFunctionSeeded(seed); + } + + private static class AsLongHashFunctionSeeded extends AsLongHashFunction { + private static final long serialVersionUID = 0L; + + private final long seed; + private final byte[] secret = new byte[192]; + + private AsLongHashFunctionSeeded(final long seed) { + this.seed = seed; + XXH3_initCustomSecret(this.secret, seed); + } + + @Override + public long seed() { + return seed; + } + + @Override + public long hash(final T input, final Access access, final long off, final long len) { + return XXH3.XXH3_64bits_internal(this.seed, this.secret, input, access.byteOrder(input, LITTLE_ENDIAN), off, len); + } + } + + static LongTupleHashFunction asLongTupleHashFunctionWithoutSeed() { + return AsLongTupleHashFunction.SEEDLESS_INSTANCE; + } + static LongHashFunction asLongTupleLowHashFunctionWithoutSeed() { + return AsLongTupleHashFunction.SEEDLESS_INSTANCE.asLongHashFunction(); + } + + private static class AsLongTupleHashFunction extends DualHashFunction { + private static final long serialVersionUID = 0L; + private static final AsLongTupleHashFunction SEEDLESS_INSTANCE = new AsLongTupleHashFunction(); + + public long seed() { + return 0L; + } + + @Override + public int bitsLength() { + return 128; + } + + @Override + public long[] newResultArray() { + return new long[2]; // override for a little performance + } + + @Override + public long dualHashLong(long input, final long[] result) { + input = Primitives.nativeToLittleEndian(input); + long s = seed() ^ Long.reverseBytes(seed() & 0xFFFFFFFFL); + final long bitflip = (unsafeLE.i64(XXH3_kSecret, 16+BYTE_BASE) ^ unsafeLE.i64(XXH3_kSecret, 24+BYTE_BASE)) + s; + final long keyed = input ^ bitflip; + final long pl = XXH_PRIME64_1 + (8 << 2); /* Shift len to the left to ensure it is even, this avoids even multiplies. */ + long m128_lo = keyed * pl; + long m128_hi = Maths.unsignedLongMulHigh(keyed, pl); + m128_hi += (m128_lo << 1); + m128_lo ^= (m128_hi >>> 3); + + m128_lo ^= m128_lo >>> 35; + m128_lo *= 0x9FB21C651E98DF25L; + m128_lo ^= m128_lo >>> 28; + + if (null != result) { + result[0] = m128_lo; + result[1] = XXH3_avalanche(m128_hi); + } + return m128_lo; + } + + @Override + public long dualHashInt(final int input, final long[] result) { + final long inputU = Primitives.unsignedInt(Primitives.nativeToLittleEndian(input)); + long s = seed() ^ Long.reverseBytes(seed() & 0xFFFFFFFFL); + final long bitflip = (unsafeLE.i64(XXH3_kSecret, 16+BYTE_BASE) ^ unsafeLE.i64(XXH3_kSecret, 24+BYTE_BASE)) + s; + final long keyed = (inputU + (inputU << 32)) ^ bitflip; + final long pl = XXH_PRIME64_1 + (4 << 2); /* Shift len to the left to ensure it is even, this avoids even multiplies. */ + long m128_lo = keyed * pl; + long m128_hi = Maths.unsignedLongMulHigh(keyed, pl); + + m128_hi += (m128_lo << 1); + m128_lo ^= (m128_hi >>> 3); + + m128_lo ^= m128_lo >>> 35; + m128_lo *= 0x9FB21C651E98DF25L; + m128_lo ^= m128_lo >>> 28; + + if (null != result) { + result[0] = m128_lo; + result[1] = XXH3_avalanche(m128_hi); + } + return m128_lo; + } + + @Override + public long dualHashShort(short input, final long[] result) { + input = Primitives.nativeToLittleEndian(input); + final int c1 = Primitives.unsignedByte((byte)input); + final int c2 = Primitives.unsignedShort(input) >>> 8; + final int c3 = c2; + final int combinedl = (c1 << 16) | (c2 << 24) | c3 | (2 << 8); + final int combinedh = Integer.rotateLeft(Integer.reverseBytes(combinedl), 13); + final long bitflipl = Primitives.unsignedInt(unsafeLE.i32(XXH3_kSecret, BYTE_BASE) ^ unsafeLE.i32(XXH3_kSecret, BYTE_BASE+4)) + seed(); + final long bitfliph = Primitives.unsignedInt(unsafeLE.i32(XXH3_kSecret, BYTE_BASE+8) ^ unsafeLE.i32(XXH3_kSecret, BYTE_BASE+12)) - seed(); + + final long low = XXH64_avalanche(Primitives.unsignedInt(combinedl) ^ bitflipl); + if (null != result) { + result[0] = low; + result[1] = XXH64_avalanche(Primitives.unsignedInt(combinedh) ^ bitfliph); + } + return low; + } + + @Override + public long dualHashChar(char input, final long[] result) { + return dualHashShort((short) input, result); + } + + @Override + public long dualHashByte(byte input, final long[] result) { + final int c1 = Primitives.unsignedByte(input); + //final int c2 = c1; + final int c2 = (byte)input; + final int c3 = c1; + final int combinedl = (c1 << 16) | (c2 << 24) | c3 | (1 << 8); + final int combinedh = Integer.rotateLeft(Integer.reverseBytes(combinedl), 13); + final long bitflipl = Primitives.unsignedInt(unsafeLE.i32(XXH3_kSecret, BYTE_BASE) ^ unsafeLE.i32(XXH3_kSecret, BYTE_BASE+4)) + seed(); + final long bitfliph = Primitives.unsignedInt(unsafeLE.i32(XXH3_kSecret, BYTE_BASE+8) ^ unsafeLE.i32(XXH3_kSecret, BYTE_BASE+12)) - seed(); + + final long low = XXH64_avalanche(Primitives.unsignedInt(combinedl) ^ bitflipl); + if (null != result) { + result[0] = low; + result[1] = XXH64_avalanche(Primitives.unsignedInt(combinedh) ^ bitfliph); + } + return low; + } + + @Override + public long dualHashVoid(final long[] result) { + final long low = XXH64_avalanche(seed() ^ unsafeLE.i64(XXH3_kSecret, BYTE_BASE+64) ^ unsafeLE.i64(XXH3_kSecret, BYTE_BASE+72)); + if (null != result) { + result[0] = low; + result[1] = XXH64_avalanche(seed() ^ unsafeLE.i64(XXH3_kSecret, BYTE_BASE+80) ^ unsafeLE.i64(XXH3_kSecret, BYTE_BASE+88)); + } + return low; + } + + @Override + public long dualHash(final T input, final Access access, final long off, final long len, final long[] result) { + return XXH3.XXH3_128bits_internal(0, XXH3.XXH3_kSecret, input, access.byteOrder(input, LITTLE_ENDIAN), off, len, result); + } + } + + static LongTupleHashFunction asLongTupleHashFunctionWithSeed(final long seed) { + return 0 == seed ? AsLongTupleHashFunction.SEEDLESS_INSTANCE : new AsLongTupleHashFunctionSeeded(seed); + } + static LongHashFunction asLongTupleLowHashFunctionWithSeed(long seed) { + return new AsLongTupleHashFunctionSeeded(seed).asLongHashFunction(); + } + + private static class AsLongTupleHashFunctionSeeded extends AsLongTupleHashFunction { + private static final long serialVersionUID = 0L; + + private final long seed; + private final byte[] secret = new byte[192]; + + private AsLongTupleHashFunctionSeeded(final long seed) { + this.seed = seed; + XXH3_initCustomSecret(this.secret, seed); + } + + @Override + public long seed() { + return seed; + } + + @Override + public long dualHash(final T input, final Access access, final long off, final long len, final long[] result) { + return XXH3.XXH3_128bits_internal(seed, secret, input, access.byteOrder(input, LITTLE_ENDIAN), off, len, result); + } + } +} \ No newline at end of file diff --git a/org.ow2.asm b/org.ow2.asm new file mode 160000 index 0000000..449fdca --- /dev/null +++ b/org.ow2.asm @@ -0,0 +1 @@ +Subproject commit 449fdca1cbadc7b822ace3c73f3af7532e69a0f1 diff --git a/settings.gradle b/settings.gradle index bfe7fe7..27122a1 100644 --- a/settings.gradle +++ b/settings.gradle @@ -39,6 +39,7 @@ void initMaple() { void initBase() { include(':annotations') include ':commons' + include ':sdk' include ':pure-analysis' include ':depend-analysis' include ':obfuscator' @@ -47,6 +48,7 @@ void initBase() { project(":annotations").projectDir = file('dev.skidfuscator.annotations') project(":commons").projectDir = file('dev.skidfuscator.commons') + project(":sdk").projectDir = file('dev.skidfuscator.sdk') project(":pure-analysis").projectDir = file('dev.skidfuscator.obfuscator.pureanalysis') project(":depend-analysis").projectDir = file('dev.skidfuscator.obfuscator.dependanalysis') project(":obfuscator").projectDir = file('dev.skidfuscator.obfuscator')