001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.commons.utils;
018
019import java.security.*;
020import java.time.*;
021import java.util.*;
022
023/**
024 * Generator for
025 * <a class="doclink" href="https://www.rfc-editor.org/rfc/rfc9562.html#section-5.7">RFC&nbsp;9562 §5.7</a> version-7 UUIDs.
026 *
027 * <p>
028 * A v7 UUID is time-ordered: a 48-bit big-endian Unix-milliseconds timestamp in the most-significant bits, followed by
029 * the RFC&nbsp;9562 version (7) and variant nibbles and {@link SecureRandom}-filled random bits.  No JDK API produces v7
030 * natively as of the Java&nbsp;17 baseline this repo targets.
031 *
032 * <p>
033 * The {@link SecureRandom} used to fill the random bits is cached per thread ({@link ThreadLocal}) so minting is not a
034 * fresh {@link SecureRandom} construction (and potential entropy syscall) on the request path.
035 *
036 * <h5 class='section'>See Also:</h5><ul>
037 *    <li class='extlink'><a class="doclink" href="https://www.rfc-editor.org/rfc/rfc9562.html">RFC 9562 — Universally Unique IDentifiers (UUIDs)</a>
038 * </ul>
039 */
040public final class Uuid7 {
041
042   @SuppressWarnings({
043      "java:S5164" // Process-lifetime cached SecureRandom per thread is intentional; there is no scope boundary at which remove() would apply.
044   })
045   private static final ThreadLocal<SecureRandom> RANDOM = ThreadLocal.withInitial(SecureRandom::new);
046
047   private static final Clock SYSTEM_CLOCK = Clock.systemUTC();
048
049   private static final long VERSION_7 = 0x7000L;      // version nibble in the low 16 bits of the MSB long
050   private static final long RAND_A_MASK = 0x0FFFL;    // 12 random bits alongside the version nibble
051   private static final long VARIANT_IETF = 0x2L << 62;// binary 10 in the top 2 bits of the LSB long
052   private static final long RAND_B_MASK = 0x3FFFFFFFFFFFFFFFL; // 62 random bits
053
054   private Uuid7() {}
055
056   /**
057    * Generates a new version-7 UUID using the current wall-clock time.
058    *
059    * @return A new v7 UUID.  Never <jk>null</jk>.
060    */
061   public static UUID create() {
062      return create(SYSTEM_CLOCK);
063   }
064
065   /**
066    * Generates a new version-7 UUID whose timestamp is read from the specified clock.
067    *
068    * <p>
069    * Lets tests pin the encoded timestamp with {@link Clock#fixed(Instant, ZoneId)}.  The random bits are still
070    * {@link SecureRandom}-filled, so two UUIDs minted against the same fixed clock differ.
071    *
072    * @param clock The clock supplying the Unix-milliseconds timestamp.  Must not be <jk>null</jk>.
073    * @return A new v7 UUID.  Never <jk>null</jk>.
074    */
075   public static UUID create(Clock clock) {
076      var random = RANDOM.get();
077      var ts = clock.millis() & 0xFFFFFFFFFFFFL;
078      var randA = random.nextInt() & RAND_A_MASK;
079      var msb = (ts << 16) | VERSION_7 | randA;
080      var randB = random.nextLong() & RAND_B_MASK;
081      var lsb = VARIANT_IETF | randB;
082      return new UUID(msb, lsb);
083   }
084
085   /**
086    * Generates a new version-7 UUID and returns its canonical string form.
087    *
088    * @return The string form of a new v7 UUID.  Never <jk>null</jk>.
089    */
090   public static String createString() {
091      return create().toString();
092   }
093
094   /**
095    * Extracts the 48-bit Unix-milliseconds timestamp encoded in a v7 UUID's most-significant bits.
096    *
097    * @param uuid The UUID.  Must not be <jk>null</jk>.
098    * @return The encoded Unix-milliseconds timestamp.
099    */
100   public static long timestamp(UUID uuid) {
101      return uuid.getMostSignificantBits() >>> 16;
102   }
103
104   /**
105    * White-box accessor: the {@link SecureRandom} instance cached for the current thread.  For tests only.
106    *
107    * @return The current thread's cached {@link SecureRandom}.
108    */
109   static SecureRandom currentRandom() {
110      return RANDOM.get();
111   }
112}