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.
This commit is contained in:
Fabio Lima 2020-11-09 03:57:12 -03:00
parent 1b47994150
commit 1bee3ba042
3 changed files with 22 additions and 49 deletions

View File

@ -33,7 +33,7 @@ Add these lines to your `pom.xml`.
<dependency> <dependency>
<groupId>com.github.f4b6a3</groupId> <groupId>com.github.f4b6a3</groupId>
<artifactId>ulid-creator</artifactId> <artifactId>ulid-creator</artifactId>
<version>2.3.1</version> <version>2.3.2</version>
</dependency> </dependency>
``` ```
See more options in [maven.org](https://search.maven.org/artifact/com.github.f4b6a3/ulid-creator). See more options in [maven.org](https://search.maven.org/artifact/com.github.f4b6a3/ulid-creator).

View File

@ -30,9 +30,6 @@ import static com.github.f4b6a3.ulid.util.internal.UlidStruct.BASE32_VALUES;
public final class UlidValidator { 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; protected static final int ULID_LENGTH = 26;
private UlidValidator() { private UlidValidator() {
@ -58,11 +55,7 @@ public final class UlidValidator {
* @return boolean true if valid * @return boolean true if valid
*/ */
public static boolean isValid(String ulid) { public static boolean isValid(String ulid) {
if (ulid == null) { return (ulid != null && ulid.length() != 0 && isValidString(ulid.toCharArray()));
return false;
}
char[] chars = ulid.toCharArray();
return isValidString(chars) && isValidTimestamp(chars);
} }
/** /**
@ -74,14 +67,10 @@ public final class UlidValidator {
* @throws InvalidUlidException if invalid * @throws InvalidUlidException if invalid
*/ */
public static void validate(String ulid) { public static void validate(String ulid) {
if(ulid != null) { if (ulid == null || ulid.length() == 0 || !isValidString(ulid.toCharArray())) {
final char[] chars = ulid.toCharArray();
if(isValidString(chars) && isValidTimestamp(chars)) {
return; // valid
}
}
throw new InvalidUlidException(String.format("Invalid ULID: %s.", ulid)); throw new InvalidUlidException(String.format("Invalid ULID: %s.", ulid));
} }
}
/** /**
* Checks if the string is a valid ULID. * Checks if the string is a valid ULID.
@ -101,6 +90,12 @@ public final class UlidValidator {
* @return boolean true if valid * @return boolean true if valid
*/ */
protected static boolean isValidString(final char[] c) { 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; int hyphen = 0;
for (int i = 0; i < c.length; i++) { for (int i = 0; i < c.length; i++) {
if (c[i] == '-') { if (c[i] == '-') {
@ -117,28 +112,4 @@ public final class UlidValidator {
} }
return (c.length - hyphen) == ULID_LENGTH; 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;
}
} }

View File

@ -57,7 +57,17 @@ public class UlidUtilTest {
assertEquals(TIMESTAMP_MAX, milliseconds); assertEquals(TIMESTAMP_MAX, milliseconds);
try { 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); extractUnixMilliseconds(ulid);
fail("Should throw an InvalidUlidException"); fail("Should throw an InvalidUlidException");
} catch (InvalidUlidException e) { } catch (InvalidUlidException e) {
@ -77,14 +87,6 @@ public class UlidUtilTest {
ulid = UlidConverter.fromString(string); ulid = UlidConverter.fromString(string);
milliseconds = extractUnixMilliseconds(ulid); milliseconds = extractUnixMilliseconds(ulid);
assertEquals(TIMESTAMP_MAX, milliseconds); assertEquals(TIMESTAMP_MAX, milliseconds);
try {
string = "8ZZZZZZZZZ" + EXAMPLE_RANDOMNESS;
ulid = UlidConverter.fromString(string);
fail("Should throw an InvalidUlidException");
} catch (InvalidUlidException e) {
// success
}
} }
@Test @Test