1<?php 2 3/** 4 * This file is part of the ramsey/uuid library 5 * 6 * For the full copyright and license information, please view the LICENSE 7 * file that was distributed with this source code. 8 * 9 * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com> 10 * @license http://opensource.org/licenses/MIT MIT 11 */ 12 13declare(strict_types=1); 14 15namespace Ramsey\Uuid\Generator; 16 17use Ramsey\Uuid\Converter\TimeConverterInterface; 18use Ramsey\Uuid\Exception\InvalidArgumentException; 19use Ramsey\Uuid\Exception\RandomSourceException; 20use Ramsey\Uuid\Exception\TimeSourceException; 21use Ramsey\Uuid\Provider\NodeProviderInterface; 22use Ramsey\Uuid\Provider\TimeProviderInterface; 23use Ramsey\Uuid\Type\Hexadecimal; 24use Throwable; 25 26use function ctype_xdigit; 27use function dechex; 28use function hex2bin; 29use function is_int; 30use function pack; 31use function sprintf; 32use function str_pad; 33use function strlen; 34 35use const STR_PAD_LEFT; 36 37/** 38 * DefaultTimeGenerator generates strings of binary data based on a node ID, 39 * clock sequence, and the current time 40 */ 41class DefaultTimeGenerator implements TimeGeneratorInterface 42{ 43 /** 44 * @var NodeProviderInterface 45 */ 46 private $nodeProvider; 47 48 /** 49 * @var TimeConverterInterface 50 */ 51 private $timeConverter; 52 53 /** 54 * @var TimeProviderInterface 55 */ 56 private $timeProvider; 57 58 public function __construct( 59 NodeProviderInterface $nodeProvider, 60 TimeConverterInterface $timeConverter, 61 TimeProviderInterface $timeProvider 62 ) { 63 $this->nodeProvider = $nodeProvider; 64 $this->timeConverter = $timeConverter; 65 $this->timeProvider = $timeProvider; 66 } 67 68 /** 69 * @throws InvalidArgumentException if the parameters contain invalid values 70 * @throws RandomSourceException if random_int() throws an exception/error 71 * 72 * @inheritDoc 73 */ 74 public function generate($node = null, ?int $clockSeq = null): string 75 { 76 if ($node instanceof Hexadecimal) { 77 $node = $node->toString(); 78 } 79 80 $node = $this->getValidNode($node); 81 82 if ($clockSeq === null) { 83 try { 84 // This does not use "stable storage"; see RFC 4122, Section 4.2.1.1. 85 $clockSeq = random_int(0, 0x3fff); 86 } catch (Throwable $exception) { 87 throw new RandomSourceException( 88 $exception->getMessage(), 89 (int) $exception->getCode(), 90 $exception 91 ); 92 } 93 } 94 95 $time = $this->timeProvider->getTime(); 96 97 $uuidTime = $this->timeConverter->calculateTime( 98 $time->getSeconds()->toString(), 99 $time->getMicroseconds()->toString() 100 ); 101 102 $timeHex = str_pad($uuidTime->toString(), 16, '0', STR_PAD_LEFT); 103 104 if (strlen($timeHex) !== 16) { 105 throw new TimeSourceException(sprintf( 106 'The generated time of \'%s\' is larger than expected', 107 $timeHex 108 )); 109 } 110 111 $timeBytes = (string) hex2bin($timeHex); 112 113 return $timeBytes[4] . $timeBytes[5] . $timeBytes[6] . $timeBytes[7] 114 . $timeBytes[2] . $timeBytes[3] 115 . $timeBytes[0] . $timeBytes[1] 116 . pack('n*', $clockSeq) 117 . $node; 118 } 119 120 /** 121 * Uses the node provider given when constructing this instance to get 122 * the node ID (usually a MAC address) 123 * 124 * @param string|int|null $node A node value that may be used to override the node provider 125 * 126 * @return string 6-byte binary string representation of the node 127 * 128 * @throws InvalidArgumentException 129 */ 130 private function getValidNode($node): string 131 { 132 if ($node === null) { 133 $node = $this->nodeProvider->getNode(); 134 } 135 136 // Convert the node to hex, if it is still an integer. 137 if (is_int($node)) { 138 $node = dechex($node); 139 } 140 141 if (!ctype_xdigit((string) $node) || strlen((string) $node) > 12) { 142 throw new InvalidArgumentException('Invalid node value'); 143 } 144 145 return (string) hex2bin(str_pad((string) $node, 12, '0', STR_PAD_LEFT)); 146 } 147} 148