All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.codahale.timeid.IdGenerator Maven / Gradle / Ivy

There is a newer version: 0.4.5
Show newest version
/*
 * Copyright © 2019 Coda Hale ([email protected])
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.codahale.timeid;

import java.io.Serializable;
import java.security.SecureRandom;
import java.time.Clock;

/**
 * {@link IdGenerator} generates 27-character, time-ordered, k-sortable, URL-safe, globally unique
 * identifiers.
 *
 * 

The identifiers are encoded with Radix-64, using an alphabet which is both URL-safe and which * preserves lexical ordering. Each ID consists of a 32-bit, big-endian timestamp (the number of * seconds since 1.4e9 seconds after the Unix epoch), plus 128 bits of random data. * *

Random data is produced via ChaCha20 in a fast-key-erasure construction. */ public class IdGenerator implements Serializable { private static final char[] ALPHABET = "$0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".toCharArray(); private static final long EPOCH_OFFSET = 1_400_000_000L; private static final long serialVersionUID = 5133358267293287137L; private final SecureRandom random; private final Clock clock; private final byte[] id; private final char[] out; private transient PRNG prng; /** Creates a new {@link IdGenerator}. */ public IdGenerator() { this(new SecureRandom(), Clock.systemUTC()); } IdGenerator(SecureRandom random, Clock clock) { this.random = random; this.clock = clock; // The buffer is an extra byte long to make it divisible by three, which simplifies the // Radix-64 encoding. this.id = new byte[21]; // Similarly, this is 28 bytes, despite the last character always being "zero". this.out = new char[28]; checkState(); } /** * Generates a new ID. * * @return a new 27-character ID */ public synchronized String generate() { checkState(); // Calculate the timestamp — number of seconds since 1.4e9 seconds past the Unix epoch. final int timestamp = (int) ((clock.millis() / 1000) - EPOCH_OFFSET); // Encode the timestamp as the first 4 big-endian bytes of the ID. PRNG.intToBytes(timestamp, id, 0); // Append 16 bytes of random data. prng.append(id); // Encode the data with Radix-64. return encode(id); } private void checkState() { if (prng == null) { // Initialize the PRNG. this.prng = new PRNG(random); } } private String encode(byte[] b) { // Encode a 21-byte array using Radix-64. // Split data into 24-bit blocks. for (int i = 0, j = 0; i < b.length - 1; ) { // Load 24-bit integer from big-endian data. final int v = (Byte.toUnsignedInt(b[i++]) << 16) | (Byte.toUnsignedInt(b[i++]) << 8) | Byte.toUnsignedInt(b[i++]); // Encode the 24 bits over 4 characters. out[j++] = ALPHABET[(v >> 18) & 63]; out[j++] = ALPHABET[(v >> 12) & 63]; out[j++] = ALPHABET[(v >> 6) & 63]; out[j++] = ALPHABET[v & 63]; } // The underlying data is only 20 bytes, but that's padded out to 21 bytes to make the above // algorithm simpler. As a result, we can skip the 28th character, since the lower 6 bits of the // last chunk of input will always be zero. This also means the 27th character technically only // encodes 4 bits of information, not 6. return new String(out, 0, 27); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy