@@ -10,28 +10,27 @@ import kotlin.jvm.JvmInline
1010import kotlin.math.absoluteValue
1111import kotlin.random.Random
1212
13- // some of these are marked as internal so that tests can be conducted easier
13+ // these are marked as internal so that tests can be conducted easier
1414// ideally, they would all be private unless there's a compelling reason to expose these
1515
16- private const val TIMESTAMP_BIT_SIZE = 48
16+ internal const val TIMESTAMP_BIT_SIZE = 48
1717
18- // this is really just used for testing
1918internal const val TIMESTAMP_BYTE_SIZE = TIMESTAMP_BIT_SIZE / 8
2019internal const val MAX_TIME = (1L shl TIMESTAMP_BIT_SIZE ) - 1 // 2^48 - 1
21- private const val RANDOM_BIT_SIZE = 80
22- private const val ULID_BIT_SIZE = TIMESTAMP_BIT_SIZE + RANDOM_BIT_SIZE
20+ internal const val RANDOM_BIT_SIZE = 80
21+ internal const val ULID_BIT_SIZE = TIMESTAMP_BIT_SIZE + RANDOM_BIT_SIZE
2322
2423internal const val ULID_BYTE_SIZE = ULID_BIT_SIZE / 8
25- private const val RANDOM_BYTE_SIZE = RANDOM_BIT_SIZE / 8
24+ internal const val RANDOM_BYTE_SIZE = RANDOM_BIT_SIZE / 8
2625
27- private const val ENCODED_ULID_BYTE_SIZE = 26
26+ internal const val ENCODED_ULID_BYTE_SIZE = 26
2827
2928// ULIDs don't saturate the entirety of their encoding space, and zero-pad the bits they don't use at the front
3029// as a result, we need to shift around the bit we're looking at a little bit more when placing it
31- private const val ULID_ENCODING_FRONT_PADDING = ENCODED_ULID_BYTE_SIZE * 5 - ULID_BIT_SIZE
30+ internal const val ULID_ENCODING_FRONT_PADDING = ENCODED_ULID_BYTE_SIZE * 5 - ULID_BIT_SIZE
3231
3332// Crockford's base32 alphabet
34- private val ENCODING_CHARS = " 0123456789ABCDEFGHJKMNPQRSTVWXYZ" .toCharArray()
33+ internal val ENCODING_CHARS = " 0123456789ABCDEFGHJKMNPQRSTVWXYZ" .toCharArray()
3534
3635/* *
3736 * A Universally Unique Lexicographically Sortable Identifier (ULID).
@@ -90,22 +89,27 @@ public value class ULID private constructor(
9089 */
9190 override fun toString (): String = encodeCrockfordBase32(value)
9291
93- // we actually do need a class bc we have to keep track of the generator's state
94- // ... then again, do we really? This at least will make things faster than ripping it out
95- public class MonotonicGenerator (
96- private val secureRandom : Boolean = true ,
92+ public class MonotonicGenerator internal constructor(
93+ private val randomProvider : (ByteArray ) -> Unit ,
94+ private val clock : () -> Long ,
9795 ) {
96+ public constructor (secureRandom: Boolean = true ) : this (
97+ randomProvider = { randomBytes(secureRandom, it) },
98+ clock = { Clock .System .now().toEpochMilliseconds() },
99+ )
100+
101+ // these are internal so that tests can access them
98102 private var lastTimestamp = 0L
99103 private var lastRandom = ByteArray (RANDOM_BYTE_SIZE )
100104
101105 public fun next (): ULID {
102- val timestamp = Clock . System .now().toEpochMilliseconds ()
106+ val timestamp = clock ()
103107
104108 if (timestamp == lastTimestamp) {
105109 incrementRandom()
106110 } else {
107111 lastTimestamp = timestamp
108- randomBytes(secureRandom, lastRandom)
112+ randomProvider( lastRandom)
109113 }
110114
111115 return ULID (lastTimestamp, lastRandom)
@@ -273,20 +277,20 @@ public value class ULID private constructor(
273277 }
274278 }
275279 }
280+ }
281+ }
276282
277- private fun randomBytes (
278- secure : Boolean ,
279- buf : ByteArray? = null,
280- ): ByteArray {
281- val buffer = buf ? : ByteArray (RANDOM_BYTE_SIZE )
282- if (secure) {
283- CryptoRand .Default .nextBytes(
284- buffer,
285- )
286- } else {
287- Random .nextBytes(buffer)
288- }
289- return buffer
290- }
283+ internal fun randomBytes (
284+ secure : Boolean ,
285+ buf : ByteArray? = null,
286+ ): ByteArray {
287+ val buffer = buf ? : ByteArray (RANDOM_BYTE_SIZE )
288+ if (secure) {
289+ CryptoRand .Default .nextBytes(
290+ buffer,
291+ )
292+ } else {
293+ Random .nextBytes(buffer)
291294 }
295+ return buffer
292296}
0 commit comments