package io.andersonsp.ulid import scala.util.{Failure, Success, Try} import java.security.SecureRandom // The components are encoded as 16 octets. Each component is encoded with the Most Significant Byte first (network byte order). // 0 1 2 3 // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | 32_bit_uint_time_high | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | 16_bit_uint_time_low | 16_bit_uint_random | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | 32_bit_uint_random | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // | 32_bit_uint_random | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ class ULIDGenerator { private val random = SecureRandom.getInstance("NativePRNGNonBlocking") def generate: ULID = { val time = System.currentTimeMillis() ULID((time << 16) | (random.nextLong & 0xFFFF), random.nextLong) } } case class ULID(msb: Long, lsb: Long) extends Ordered[ULID] { def toBytes: Array[Byte] = java.nio.ByteBuffer.allocate(ULID.Length).putLong(msb).putLong(lsb).array() def timestamp: Long = msb >>> 16 override def compare(that: ULID): Int = { if (msb == that.msb) { lsb.compareTo(that.lsb) } else { msb.compareTo(that.msb) } } } object ULID { val Length = 16 def fromBytes(bytes: Array[Byte]): Try[ULID] = { if (bytes.length != Length) { Failure(new IllegalArgumentException("Binary ULID must be 16 bytes long")) } else { val buf = java.nio.ByteBuffer.wrap(bytes) val msb = buf.getLong val lsb = buf.getLong Success(ULID(msb, lsb)) } } }