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

izumi.fundamentals.platform.uuid.IzUUIDImpl.scala Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 izumi.fundamentals.platform.uuid

import java.net.{InetAddress, NetworkInterface}
import java.nio.ByteBuffer
import java.security.{MessageDigest, SecureRandom}
import java.util
import java.util.{Collections, Random, UUID}

/**
  * The goods are here: www.ietf.org/rfc/rfc4122.txt.
  */
trait IzUUIDImpl extends IzUUID {
  // A grand day! millis at 00:00:00.000 15 Oct 1582.
  private val START_EPOCH: Long = -12219292800000L

  private val clockSeqAndNode: Long = makeClockSeqAndNode()

  /*
   * The min and max possible lsb for a UUID.
   * Note that his is not 0 and all 1's because Cassandra TimeUUIDType
   * compares the lsb parts as a signed byte array comparison. So the min
   * value is 8 times -128 and the max is 8 times +127.
   *
   * Note that we ignore the uuid variant (namely, MIN_CLOCK_SEQ_AND_NODE
   * have variant 2 as it should, but MAX_CLOCK_SEQ_AND_NODE have variant 0).
   * I don't think that has any practical consequence and is more robust in
   * case someone provides a UUID with a broken variant.
   */

  private val MIN_CLOCK_SEQ_AND_NODE: Long = 0x8080808080808080L

  private val MAX_CLOCK_SEQ_AND_NODE: Long = 0x7F7F7F7F7F7F7F7FL

  private val secureRandom: SecureRandom = new SecureRandom()

  private var lastNanos: Long = _

  // make sure someone didn't whack the clockSeqAndNode by changing the order of instantiation.
  if (clockSeqAndNode == 0)
    throw new RuntimeException("singleton instantiation is misplaced.")

  // we can generate at most 10k UUIDs per ms.
  private def createTimeSafe(): Long = synchronized {
    var nanosSince: Long = (System.currentTimeMillis() - START_EPOCH) * 10000
    if (nanosSince > lastNanos) {
      lastNanos = nanosSince
    } else {
      lastNanos += 1
      nanosSince = lastNanos
    }
    createTime(nanosSince)
  }

  /**
    * @param when time in milliseconds
    */
  private def createTimeUnsafe(when: Long): Long = createTimeUnsafe(when, 0)

  private def createTimeUnsafe(when: Long, nanos: Int): Long = {
    val nanosSince: Long = ((when - START_EPOCH) * 10000) + nanos
    createTime(nanosSince)
  }

  /**
    * Creates a type 1 UUID (time-based UUID).
    *
    * @return a UUID instance
    */
  def generateTimeUUID(): UUID =
    new UUID(createTimeSafe(), clockSeqAndNode)

  /**
    * Creates a type 1 UUID (time-based UUID) with the timestamp of @param when, in milliseconds.
    *
    * @return a UUID instance
    */
  def getTimeUUID(when: Long): UUID =
    new UUID(createTime(fromUnixTimestamp(when)), clockSeqAndNode)

  /**
    * Returns a version 1 UUID using the provided timestamp and the local clock and sequence.
    * 

* Note that this method is generally only safe to use if you can guarantee that the provided * parameter is unique across calls (otherwise the returned UUID won't be unique accross calls). * * @param whenInMicros a unix time in microseconds. * @return a new UUID { @code id} such that { @code microsTimestamp(id) == whenInMicros}. Please not that * multiple calls to this method with the same value of { @code whenInMicros} will return the same * UUID. */ def getTimeUUIDFromMicros(whenInMicros: Long): UUID = { val whenInMillis: Long = whenInMicros / 1000 val nanos: Long = (whenInMicros - (whenInMillis * 1000)) * 10 getTimeUUID(whenInMillis, nanos) } /** * Similar to {@link getTimeUUIDFromMicros}, but randomize (using SecureRandom) the clock and sequence. *

* If you can guarantee that the {@code whenInMicros} argument is unique (for this JVM instance) for * every call, then you should prefer {@link getTimeUUIDFromMicros} which is faster. If you can't * guarantee this however, this method will ensure the returned UUID are still unique (accross calls) * through randomization. * * @param whenInMicros a unix time in microseconds. * @return a new UUID { @code id} such that { @code microsTimestamp(id) == whenInMicros}. The UUID returned * by different calls will be unique even if { @code whenInMicros} is not. */ def getRandomTimeUUIDFromMicros(whenInMicros: Long): UUID = { val whenInMillis: Long = whenInMicros / 1000 val nanos: Long = (whenInMicros - (whenInMillis * 1000)) * 10 new UUID(createTime(fromUnixTimestamp(whenInMillis, nanos)), secureRandom.nextLong()) } def getTimeUUID(when: Long, nanos: Long): UUID = new UUID(createTime(fromUnixTimestamp(when, nanos)), clockSeqAndNode) def getTimeUUID(when: Long, nanos: Long, clockSeqAndNode: Long): UUID = new UUID(createTime(fromUnixTimestamp(when, nanos)), clockSeqAndNode) /** * creates a type 1 uuid from raw bytes. */ def getUUID(raw: ByteBuffer): UUID = new UUID(raw.getLong(raw.position()), raw.getLong(raw.position() + 8)) /** * decomposes a uuid into raw bytes. */ def decompose(uuid: UUID): Array[Byte] = { val most: Long = uuid.getMostSignificantBits val least: Long = uuid.getLeastSignificantBits val b: Array[Byte] = Array.ofDim[Byte](16) for (i <- 0.until(8)) { b(i) = (most >>> ((7 - i) * 8)).toByte b(8 + i) = (least >>> ((7 - i) * 8)).toByte } b } /** * Returns a 16 byte representation of a type 1 UUID (a time-based UUID), * based on the current system time. * * @return a type 1 UUID represented as a byte[] */ def generateTimeUUIDBytes(): Array[Byte] = createTimeUUIDBytes(createTimeSafe()) /** * Returns the smaller possible type 1 UUID having the provided timestamp. * * Warning: this method should only be used for querying as this * doesn't at all guarantee the uniqueness of the resulting UUID. */ def minTimeUUID(timestamp: Long): UUID = new UUID(createTime(fromUnixTimestamp(timestamp)), MIN_CLOCK_SEQ_AND_NODE) /** * Returns the biggest possible type 1 UUID having the provided timestamp. * * Warning: this method should only be used for querying as this * doesn't at all guarantee the uniqueness of the resulting UUID. */ def maxTimeUUID(timestamp: Long): UUID = { // precision by taking 10000, but rather 19999. val uuidTstamp: Long = fromUnixTimestamp(timestamp + 1) - 1 new UUID(createTime(uuidTstamp), MAX_CLOCK_SEQ_AND_NODE) } // unix timestamp are milliseconds precision, uuid timestamp are 100's // nanoseconds precision. If we ask for the biggest uuid have unix // timestamp 1ms, then we should not extend 100's nanoseconds // unix timestamp are milliseconds precision, uuid timestamp are 100's // nanoseconds precision. If we ask for the biggest uuid have unix // timestamp 1ms, then we should not extend 100's nanoseconds /** * @param uuid * @return milliseconds since Unix epoch */ def unixTimestamp(uuid: UUID): Long = (uuid.timestamp() / 10000) + START_EPOCH /** * @param uuid * @return microseconds since Unix epoch */ def microsTimestamp(uuid: UUID): Long = (uuid.timestamp() / 10) + START_EPOCH * 1000 /** * @param timestamp milliseconds since Unix epoch * @return */ private def fromUnixTimestamp(timestamp: Long): Long = fromUnixTimestamp(timestamp, 0L) private def fromUnixTimestamp(timestamp: Long, nanos: Long): Long = ((timestamp - START_EPOCH) * 10000) + nanos /** * Converts a milliseconds-since-epoch timestamp into the 16 byte representation * of a type 1 UUID (a time-based UUID). * *

Deprecated: This method goes again the principle of a time * UUID and should not be used. For queries based on timestamp, minTimeUUID() and * maxTimeUUID() can be used but this method has questionable usefulness. This is * only kept because CQL2 uses it (see TimeUUID.fromStringCQL2) and we * don't want to break compatibility.

* *

Warning: This method is not guaranteed to return unique UUIDs; Multiple * invocations using identical timestamps will result in identical UUIDs.

* * @param timeMillis * @return a type 1 UUID represented as a byte[] */ def getTimeUUIDBytes(timeMillis: Long): Array[Byte] = createTimeUUIDBytes(createTimeUnsafe(timeMillis)) /** * Converts a 100-nanoseconds precision timestamp into the 16 byte representation * of a type 1 UUID (a time-based UUID). * * To specify a 100-nanoseconds precision timestamp, one should provide a milliseconds timestamp and * a number 0 <= n < 10000 such that n*100 is the number of nanoseconds within that millisecond. * *

Warning: This method is not guaranteed to return unique UUIDs; Multiple * invocations using identical timestamps will result in identical UUIDs.

* * @return a type 1 UUID represented as a byte[] */ def getTimeUUIDBytes(timeMillis: Long, nanos: Int): Array[Byte] = { if (nanos >= 10000) throw new IllegalArgumentException() createTimeUUIDBytes(createTimeUnsafe(timeMillis, nanos)) } private def createTimeUUIDBytes(msb: Long): Array[Byte] = { val lsb: Long = clockSeqAndNode val uuidBytes: Array[Byte] = Array.ofDim[Byte](16) for (i <- 0.until(8)) uuidBytes(i) = (msb >>> 8 * (7 - i)).toByte for (i <- 8.until(16)) uuidBytes(i) = (lsb >>> 8 * (7 - i)).toByte uuidBytes } /** * Returns a milliseconds-since-epoch value for a type-1 UUID. * * @param uuid a type-1 (time-based) UUID * @return the number of milliseconds since the unix epoch * @throws IllegalArgumentException if the UUID is not version 1 */ def getAdjustedTimestamp(uuid: UUID): Long = { if (uuid.version() != 1) throw new IllegalArgumentException("incompatible with uuid version: " + uuid.version()) (uuid.timestamp() / 10000) + START_EPOCH } private def makeClockSeqAndNode(): Long = { val clock: Long = new Random(System.currentTimeMillis()).nextLong() var lsb: Long = 0 // variant (2 bits) lsb |= 0x8000000000000000L // clock sequence (14 bits) lsb |= (clock & 0x0000000000003FFFL) << 48 // 6 bytes lsb |= makeNode() lsb } private def createTime(nanosSince: Long): Long = { var msb: Long = 0L msb |= (0x00000000FFFFFFFFL & nanosSince) << 32 msb |= (0x0000FFFF00000000L & nanosSince) >>> 16 msb |= (0xFFFF000000000000L & nanosSince) >>> 48 // sets the version to 1. msb |= 0x0000000000001000L msb } private def getAllLocalAddresses(): util.Collection[InetAddress] = { val localAddresses = new util.HashSet[InetAddress]() val nets = NetworkInterface.getNetworkInterfaces while (nets.hasMoreElements) localAddresses.addAll(Collections.list(nets.nextElement().getInetAddresses)) localAddresses } private def makeNode(): Long = { /* * We don't have access to the MAC address but need to generate a node part * that identify this host as uniquely as possible. * The spec says that one option is to take as many source that identify * this node as possible and hash them together. That's what we do here by * gathering all the ip of this host. * Note that FBUtilities.getBroadcastAddress() should be enough to uniquely * identify the node *in the cluster* but it triggers DatabaseDescriptor * instanciation and the UUID generator is used in Stress for instance, * where we don't want to require the yaml. */ val localAddresses: util.Collection[InetAddress] = getAllLocalAddresses() if (localAddresses.isEmpty) throw new RuntimeException("Cannot generate the node component of the UUID because cannot retrieve any IP addresses.") // ideally, we'd use the MAC address, but java doesn't expose that. val hash: Array[Byte] = doHash(localAddresses) var node: Long = 0 for (i <- 0 until Math.min(6, hash.length)) node |= (0x00000000000000FF & hash(i).toLong) << (5 - i) * 8 assert((0xFF00000000000000L & node) == 0) // bit (least significant bit of the first octet of the node ID) must be 1. node | 0x0000010000000000L } // Since we don't use the mac address, the spec says that multicast // Since we don't use the mac address, the spec says that multicast private def doHash(data: util.Collection[InetAddress]): Array[Byte] = { import scala.jdk.CollectionConverters.* val messageDigest: MessageDigest = MessageDigest.getInstance("MD5") for (addr <- data.asScala) messageDigest.update(addr.getAddress) messageDigest.digest() } } // for the curious, here is how I generated START_EPOCH // Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT-0")); // c.set(Calendar.YEAR, 1582); // c.set(Calendar.MONTH, Calendar.OCTOBER); // c.set(Calendar.DAY_OF_MONTH, 15); // c.set(Calendar.HOUR_OF_DAY, 0); // c.set(Calendar.MINUTE, 0); // c.set(Calendar.SECOND, 0); // c.set(Calendar.MILLISECOND, 0); // long START_EPOCH = c.getTimeInMillis();




© 2015 - 2024 Weber Informatics LLC | Privacy Policy