From 1bee3ba042cda0eca4dfc7e4d7c87e22307e3c3e Mon Sep 17 00:00:00 2001 From: Fabio Lima Date: Mon, 9 Nov 2020 03:57:12 -0300 Subject: [PATCH] Optimization of the ULID string validation It's not needed to parse the whole ULID timestamp. All is needed is to check the first two bits. They are extra bits added by the base-32 encoding. Both bits must be ZERO. --- README.md | 2 +- .../f4b6a3/ulid/util/UlidValidator.java | 47 ++++--------------- .../github/f4b6a3/ulid/util/UlidUtilTest.java | 22 +++++---- 3 files changed, 22 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 5f86065..37c17ab 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Add these lines to your `pom.xml`. com.github.f4b6a3 ulid-creator - 2.3.1 + 2.3.2 ``` See more options in [maven.org](https://search.maven.org/artifact/com.github.f4b6a3/ulid-creator). diff --git a/src/main/java/com/github/f4b6a3/ulid/util/UlidValidator.java b/src/main/java/com/github/f4b6a3/ulid/util/UlidValidator.java index 846de8a..04b32e5 100644 --- a/src/main/java/com/github/f4b6a3/ulid/util/UlidValidator.java +++ b/src/main/java/com/github/f4b6a3/ulid/util/UlidValidator.java @@ -30,9 +30,6 @@ import static com.github.f4b6a3.ulid.util.internal.UlidStruct.BASE32_VALUES; public final class UlidValidator { - // Date: 10889-08-02T05:31:50.655Z: 281474976710655 (2^48-1) - private static final long TIMESTAMP_MAX = 0xffffffffffffL; - protected static final int ULID_LENGTH = 26; private UlidValidator() { @@ -58,11 +55,7 @@ public final class UlidValidator { * @return boolean true if valid */ public static boolean isValid(String ulid) { - if (ulid == null) { - return false; - } - char[] chars = ulid.toCharArray(); - return isValidString(chars) && isValidTimestamp(chars); + return (ulid != null && ulid.length() != 0 && isValidString(ulid.toCharArray())); } /** @@ -74,13 +67,9 @@ public final class UlidValidator { * @throws InvalidUlidException if invalid */ public static void validate(String ulid) { - if(ulid != null) { - final char[] chars = ulid.toCharArray(); - if(isValidString(chars) && isValidTimestamp(chars)) { - return; // valid - } + if (ulid == null || ulid.length() == 0 || !isValidString(ulid.toCharArray())) { + throw new InvalidUlidException(String.format("Invalid ULID: %s.", ulid)); } - throw new InvalidUlidException(String.format("Invalid ULID: %s.", ulid)); } /** @@ -101,6 +90,12 @@ public final class UlidValidator { * @return boolean true if valid */ protected static boolean isValidString(final char[] c) { + + // the two extra bits added by base-32 encoding must be zero + if ((BASE32_VALUES[c[0]] & 0b11000) != 0) { + return false; // overflow + } + int hyphen = 0; for (int i = 0; i < c.length; i++) { if (c[i] == '-') { @@ -117,28 +112,4 @@ public final class UlidValidator { } return (c.length - hyphen) == ULID_LENGTH; } - - /** - * Checks if the timestamp is between 0 and 2^48-1 - * - * @param chars a char array - * @return false if invalid. - */ - protected static boolean isValidTimestamp(char[] chars) { - - long time = 0; - - time |= BASE32_VALUES[chars[0x00]] << 45; - time |= BASE32_VALUES[chars[0x01]] << 40; - time |= BASE32_VALUES[chars[0x02]] << 35; - time |= BASE32_VALUES[chars[0x03]] << 30; - time |= BASE32_VALUES[chars[0x04]] << 25; - time |= BASE32_VALUES[chars[0x05]] << 20; - time |= BASE32_VALUES[chars[0x06]] << 15; - time |= BASE32_VALUES[chars[0x07]] << 10; - time |= BASE32_VALUES[chars[0x08]] << 5; - time |= BASE32_VALUES[chars[0x09]]; - - return time >= 0 && time <= TIMESTAMP_MAX; - } } diff --git a/src/test/java/com/github/f4b6a3/ulid/util/UlidUtilTest.java b/src/test/java/com/github/f4b6a3/ulid/util/UlidUtilTest.java index c17e847..e017393 100644 --- a/src/test/java/com/github/f4b6a3/ulid/util/UlidUtilTest.java +++ b/src/test/java/com/github/f4b6a3/ulid/util/UlidUtilTest.java @@ -55,9 +55,19 @@ public class UlidUtilTest { ulid = "7ZZZZZZZZZ" + EXAMPLE_RANDOMNESS; milliseconds = extractUnixMilliseconds(ulid); assertEquals(TIMESTAMP_MAX, milliseconds); - + try { - ulid = "8ZZZZZZZZZ" + EXAMPLE_RANDOMNESS; + // Test the first extra bit added by the base32 encoding + ulid = "G0000000000000000000000000"; + extractUnixMilliseconds(ulid); + fail("Should throw an InvalidUlidException"); + } catch (InvalidUlidException e) { + // success + } + + try { + // Test the second extra bit added by the base32 encoding + ulid = "80000000000000000000000000"; extractUnixMilliseconds(ulid); fail("Should throw an InvalidUlidException"); } catch (InvalidUlidException e) { @@ -77,14 +87,6 @@ public class UlidUtilTest { ulid = UlidConverter.fromString(string); milliseconds = extractUnixMilliseconds(ulid); assertEquals(TIMESTAMP_MAX, milliseconds); - - try { - string = "8ZZZZZZZZZ" + EXAMPLE_RANDOMNESS; - ulid = UlidConverter.fromString(string); - fail("Should throw an InvalidUlidException"); - } catch (InvalidUlidException e) { - // success - } } @Test