diff --git a/gradle.properties b/gradle.properties index a14b8c2..424ade3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ -jwo.version = 2024.02.02 +jwo.version = 2024.02.09 lys.version = 2024.02.01 guice.version = 5.0.1 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1af9e09..a80b22c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/src/main/java/net/woggioni/jwo/Hash.java b/src/main/java/net/woggioni/jwo/Hash.java index 57329ad..0e48cb0 100644 --- a/src/main/java/net/woggioni/jwo/Hash.java +++ b/src/main/java/net/woggioni/jwo/Hash.java @@ -5,12 +5,15 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; +import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.security.DigestInputStream; import java.security.DigestOutputStream; import java.security.MessageDigest; +import static net.woggioni.jwo.JWO.newThrowable; + @EqualsAndHashCode @RequiredArgsConstructor public class Hash { @@ -87,6 +90,33 @@ public class Hash { return new String(hexChars); } + public static byte[] hexToBytes(String hexString) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + if (hexString.length() % 2 != 0) { + throw newThrowable(IllegalArgumentException.class, "Hex string length must be even," + + " string has length '%d' instead", hexString.length()); + } + int lim = hexString.length() / 2; + for(int i = 0; i < lim; i++) { + int tmp = 0; + for (int j = 0; j < 2; j++) { + int c = hexString.charAt(i * 2 + j); + if (c >= '0' && c <= '9') { + c -= '0'; + } else if (c >= 'A' && c <= 'F') { + c -= 'A' - 10; + } else if(c >= 'a' && c <= 'f') { + c -= 'a' - 10; + } else { + throw newThrowable(IllegalArgumentException.class, "Illegal hex char '%c'", c); + } + tmp |= (c << 4 * (1 - j)); + } + baos.write(tmp); + } + return baos.toByteArray(); + } + @Override public String toString() { return bytesToHex(bytes); diff --git a/src/main/java/net/woggioni/jwo/JWO.java b/src/main/java/net/woggioni/jwo/JWO.java index aebd449..d427394 100644 --- a/src/main/java/net/woggioni/jwo/JWO.java +++ b/src/main/java/net/woggioni/jwo/JWO.java @@ -233,16 +233,8 @@ public class JWO { } } - final private static char[] hexArray = "0123456789ABCDEF".toCharArray(); - public static String bytesToHex(byte[] bytes) { - char[] hexChars = new char[bytes.length * 2]; - for (int j = 0; j < bytes.length; j++) { - int v = bytes[j] & 0xFF; - hexChars[j * 2] = hexArray[v >>> 4]; - hexChars[j * 2 + 1] = hexArray[v & 0x0F]; - } - return new String(hexChars); + return Hash.bytesToHex(bytes); } @SneakyThrows diff --git a/src/test/java/net/woggioni/jwo/HashTest.java b/src/test/java/net/woggioni/jwo/HashTest.java index dcacb46..f0fd53b 100644 --- a/src/test/java/net/woggioni/jwo/HashTest.java +++ b/src/test/java/net/woggioni/jwo/HashTest.java @@ -3,9 +3,15 @@ package net.woggioni.jwo; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; import java.io.ByteArrayInputStream; import java.util.Random; +import java.util.stream.Stream; public class HashTest { @@ -42,4 +48,34 @@ public class HashTest { Assertions.assertNotEquals(hash1, hash2); Assertions.assertNotEquals(hash1.toString(), hash2.toString()); } + + + private static class HexTestArguments implements ArgumentsProvider { + @Override + public Stream provideArguments( + ExtensionContext extensionContext + ) { + return Stream.of( + Arguments.of("A41D767EEF6084823F250E954BDD48CF", null), + Arguments.of("A41D767eeF6084823f250E954bdD48CF", null), + Arguments.of("A41D767EEF6084823F250E954BDD48C", IllegalArgumentException.class), + Arguments.of("A41D767xEF6084823F250E954BDD48C", IllegalArgumentException.class), + Arguments.of("A41D767ZeF6084823f250E954bdD48C", IllegalArgumentException.class), + Arguments.of("A41D767eeF6084823f250E954bdD4x", IllegalArgumentException.class) + ); + } + } + @ArgumentsSource(HexTestArguments.class) + @ParameterizedTest + public void hexTest(String sourceString, Class t) { + if(t != null) { + Assertions.assertThrows(t, () -> + Hash.hexToBytes(sourceString) + ); + } else { + byte[] bytes = Hash.hexToBytes(sourceString); + Assertions.assertEquals(sourceString.length() / 2, bytes.length); + Assertions.assertEquals(sourceString.toUpperCase(), Hash.bytesToHex(bytes)); + } + } }