Skip to main content
Drupal API
User account menu
  • Log in

Breadcrumb

  1. Drupal Core 11.1.x

UnixTimeGenerator.php

Namespace

Ramsey\Uuid\Generator

File

vendor/ramsey/uuid/src/Generator/UnixTimeGenerator.php

View source
<?php


/**
 * This file is part of the ramsey/uuid library
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
 * @license http://opensource.org/licenses/MIT MIT
 */
declare (strict_types=1);
namespace Ramsey\Uuid\Generator;

use Brick\Math\BigInteger;
use DateTimeImmutable;
use DateTimeInterface;
use Ramsey\Uuid\Type\Hexadecimal;
use function hash;
use function pack;
use function str_pad;
use function strlen;
use function substr;
use function substr_replace;
use function unpack;
use const PHP_INT_SIZE;
use const STR_PAD_LEFT;

/**
 * UnixTimeGenerator generates bytes that combine a 48-bit timestamp in
 * milliseconds since the Unix Epoch with 80 random bits
 *
 * Code and concepts within this class are borrowed from the symfony/uid package
 * and are used under the terms of the MIT license distributed with symfony/uid.
 *
 * symfony/uid is copyright (c) Fabien Potencier.
 *
 * @link https://symfony.com/components/Uid Symfony Uid component
 * @link https://github.com/symfony/uid/blob/4f9f537e57261519808a7ce1d941490736522bbc/UuidV7.php Symfony UuidV7 class
 * @link https://github.com/symfony/uid/blob/6.2/LICENSE MIT License
 */
class UnixTimeGenerator implements TimeGeneratorInterface {
    private static string $time = '';
    private static ?string $seed = null;
    private static int $seedIndex = 0;
    
    /** @var int[] */
    private static array $rand = [];
    
    /** @var int[] */
    private static array $seedParts;
    public function __construct(RandomGeneratorInterface $randomGenerator, int $intSize = PHP_INT_SIZE) {
    }
    
    /**
     * @param Hexadecimal|int|string|null $node Unused in this generator
     * @param int|null $clockSeq Unused in this generator
     * @param DateTimeInterface $dateTime A date-time instance to use when
     *     generating bytes
     *
     * @inheritDoc
     */
    public function generate($node = null, ?int $clockSeq = null, ?DateTimeInterface $dateTime = null) : string {
        $time = ($dateTime ?? new DateTimeImmutable('now'))->format('Uv');
        if ($time > self::$time || $dateTime !== null && $time !== self::$time) {
            $this->randomize($time);
        }
        else {
            $time = $this->increment();
        }
        if ($this->intSize >= 8) {
            $time = substr(pack('J', (int) $time), -6);
        }
        else {
            $time = str_pad(BigInteger::of($time)->toBytes(false), 6, "\x00", STR_PAD_LEFT);
        }
        
        /** @var non-empty-string */
        return $time . pack('n*', self::$rand[1], self::$rand[2], self::$rand[3], self::$rand[4], self::$rand[5]);
    }
    private function randomize(string $time) : void {
        if (self::$seed === null) {
            $seed = $this->randomGenerator
                ->generate(16);
            self::$seed = $seed;
        }
        else {
            $seed = $this->randomGenerator
                ->generate(10);
        }
        
        /** @var int[] $rand */
        $rand = unpack('n*', $seed);
        $rand[1] &= 0x3ff;
        self::$rand = $rand;
        self::$time = $time;
    }
    
    /**
     * Special thanks to Nicolas Grekas for sharing the following information:
     *
     * Within the same ms, we increment the rand part by a random 24-bit number.
     *
     * Instead of getting this number from random_bytes(), which is slow, we get
     * it by sha512-hashing self::$seed. This produces 64 bytes of entropy,
     * which we need to split in a list of 24-bit numbers. unpack() first splits
     * them into 16 x 32-bit numbers; we take the first byte of each of these
     * numbers to get 5 extra 24-bit numbers. Then, we consume those numbers
     * one-by-one and run this logic every 21 iterations.
     *
     * self::$rand holds the random part of the UUID, split into 5 x 16-bit
     * numbers for x86 portability. We increment this random part by the next
     * 24-bit number in the self::$seedParts list and decrement
     * self::$seedIndex.
     *
     * @link https://twitter.com/nicolasgrekas/status/1583356938825261061 Tweet from Nicolas Grekas
     */
    private function increment() : string {
        if (self::$seedIndex === 0 && self::$seed !== null) {
            self::$seed = hash('sha512', self::$seed, true);
            
            /** @var int[] $s */
            $s = unpack('l*', self::$seed);
            $s[] = $s[1] >> 8 & 0xff0000 | $s[2] >> 16 & 0xff00 | $s[3] >> 24 & 0xff;
            $s[] = $s[4] >> 8 & 0xff0000 | $s[5] >> 16 & 0xff00 | $s[6] >> 24 & 0xff;
            $s[] = $s[7] >> 8 & 0xff0000 | $s[8] >> 16 & 0xff00 | $s[9] >> 24 & 0xff;
            $s[] = $s[10] >> 8 & 0xff0000 | $s[11] >> 16 & 0xff00 | $s[12] >> 24 & 0xff;
            $s[] = $s[13] >> 8 & 0xff0000 | $s[14] >> 16 & 0xff00 | $s[15] >> 24 & 0xff;
            self::$seedParts = $s;
            self::$seedIndex = 21;
        }
        self::$rand[5] = 0xffff & ($carry = self::$rand[5] + 1 + (self::$seedParts[self::$seedIndex--] & 0xffffff));
        self::$rand[4] = 0xffff & ($carry = self::$rand[4] + ($carry >> 16));
        self::$rand[3] = 0xffff & ($carry = self::$rand[3] + ($carry >> 16));
        self::$rand[2] = 0xffff & ($carry = self::$rand[2] + ($carry >> 16));
        self::$rand[1] += $carry >> 16;
        if (0xfc00 & self::$rand[1]) {
            $time = self::$time;
            $mtime = (int) substr($time, -9);
            if ($this->intSize >= 8 || strlen($time) < 10) {
                $time = (string) ((int) $time + 1);
            }
            elseif ($mtime === 999999999) {
                $time = 1 + (int) substr($time, 0, -9) . '000000000';
            }
            else {
                $mtime++;
                $time = substr_replace($time, str_pad((string) $mtime, 9, '0', STR_PAD_LEFT), -9);
            }
            $this->randomize($time);
        }
        return self::$time;
    }

}

Classes

Title Deprecated Summary
UnixTimeGenerator UnixTimeGenerator generates bytes that combine a 48-bit timestamp in milliseconds since the Unix Epoch with 80 random bits

API Navigation

  • Drupal Core 11.1.x
  • Topics
  • Classes
  • Functions
  • Constants
  • Globals
  • Files
  • Namespaces
  • Deprecated
  • Services
RSS feed
Powered by Drupal