Fields.php 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. <?php
  2. /**
  3. * This file is part of the ramsey/uuid library
  4. *
  5. * For the full copyright and license information, please view the LICENSE
  6. * file that was distributed with this source code.
  7. *
  8. * @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
  9. * @license http://opensource.org/licenses/MIT MIT
  10. */
  11. declare(strict_types=1);
  12. namespace Ramsey\Uuid\Rfc4122;
  13. use Ramsey\Uuid\Exception\InvalidArgumentException;
  14. use Ramsey\Uuid\Fields\SerializableFieldsTrait;
  15. use Ramsey\Uuid\Type\Hexadecimal;
  16. use Ramsey\Uuid\Uuid;
  17. use function bin2hex;
  18. use function dechex;
  19. use function hexdec;
  20. use function sprintf;
  21. use function str_pad;
  22. use function strlen;
  23. use function substr;
  24. use function unpack;
  25. use const STR_PAD_LEFT;
  26. /**
  27. * RFC 4122 variant UUIDs are comprised of a set of named fields
  28. *
  29. * Internally, this class represents the fields together as a 16-byte binary
  30. * string.
  31. *
  32. * @psalm-immutable
  33. */
  34. final class Fields implements FieldsInterface
  35. {
  36. use NilTrait;
  37. use SerializableFieldsTrait;
  38. use VariantTrait;
  39. use VersionTrait;
  40. /**
  41. * @var string
  42. */
  43. private $bytes;
  44. /**
  45. * @param string $bytes A 16-byte binary string representation of a UUID
  46. *
  47. * @throws InvalidArgumentException if the byte string is not exactly 16 bytes
  48. * @throws InvalidArgumentException if the byte string does not represent an RFC 4122 UUID
  49. * @throws InvalidArgumentException if the byte string does not contain a valid version
  50. */
  51. public function __construct(string $bytes)
  52. {
  53. if (strlen($bytes) !== 16) {
  54. throw new InvalidArgumentException(
  55. 'The byte string must be 16 bytes long; '
  56. . 'received ' . strlen($bytes) . ' bytes'
  57. );
  58. }
  59. $this->bytes = $bytes;
  60. if (!$this->isCorrectVariant()) {
  61. throw new InvalidArgumentException(
  62. 'The byte string received does not conform to the RFC 4122 variant'
  63. );
  64. }
  65. if (!$this->isCorrectVersion()) {
  66. throw new InvalidArgumentException(
  67. 'The byte string received does not contain a valid RFC 4122 version'
  68. );
  69. }
  70. }
  71. public function getBytes(): string
  72. {
  73. return $this->bytes;
  74. }
  75. public function getClockSeq(): Hexadecimal
  76. {
  77. $clockSeq = hexdec(bin2hex(substr($this->bytes, 8, 2))) & 0x3fff;
  78. return new Hexadecimal(str_pad(dechex($clockSeq), 4, '0', STR_PAD_LEFT));
  79. }
  80. public function getClockSeqHiAndReserved(): Hexadecimal
  81. {
  82. return new Hexadecimal(bin2hex(substr($this->bytes, 8, 1)));
  83. }
  84. public function getClockSeqLow(): Hexadecimal
  85. {
  86. return new Hexadecimal(bin2hex(substr($this->bytes, 9, 1)));
  87. }
  88. public function getNode(): Hexadecimal
  89. {
  90. return new Hexadecimal(bin2hex(substr($this->bytes, 10)));
  91. }
  92. public function getTimeHiAndVersion(): Hexadecimal
  93. {
  94. return new Hexadecimal(bin2hex(substr($this->bytes, 6, 2)));
  95. }
  96. public function getTimeLow(): Hexadecimal
  97. {
  98. return new Hexadecimal(bin2hex(substr($this->bytes, 0, 4)));
  99. }
  100. public function getTimeMid(): Hexadecimal
  101. {
  102. return new Hexadecimal(bin2hex(substr($this->bytes, 4, 2)));
  103. }
  104. /**
  105. * Returns the full 60-bit timestamp, without the version
  106. *
  107. * For version 2 UUIDs, the time_low field is the local identifier and
  108. * should not be returned as part of the time. For this reason, we set the
  109. * bottom 32 bits of the timestamp to 0's. As a result, there is some loss
  110. * of fidelity of the timestamp, for version 2 UUIDs. The timestamp can be
  111. * off by a range of 0 to 429.4967295 seconds (or 7 minutes, 9 seconds, and
  112. * 496730 microseconds).
  113. *
  114. * For version 6 UUIDs, the timestamp order is reversed from the typical RFC
  115. * 4122 order (the time bits are in the correct bit order, so that it is
  116. * monotonically increasing). In returning the timestamp value, we put the
  117. * bits in the order: time_low + time_mid + time_hi.
  118. */
  119. public function getTimestamp(): Hexadecimal
  120. {
  121. switch ($this->getVersion()) {
  122. case Uuid::UUID_TYPE_DCE_SECURITY:
  123. $timestamp = sprintf(
  124. '%03x%04s%08s',
  125. hexdec($this->getTimeHiAndVersion()->toString()) & 0x0fff,
  126. $this->getTimeMid()->toString(),
  127. ''
  128. );
  129. break;
  130. case Uuid::UUID_TYPE_PEABODY:
  131. $timestamp = sprintf(
  132. '%08s%04s%03x',
  133. $this->getTimeLow()->toString(),
  134. $this->getTimeMid()->toString(),
  135. hexdec($this->getTimeHiAndVersion()->toString()) & 0x0fff
  136. );
  137. break;
  138. default:
  139. $timestamp = sprintf(
  140. '%03x%04s%08s',
  141. hexdec($this->getTimeHiAndVersion()->toString()) & 0x0fff,
  142. $this->getTimeMid()->toString(),
  143. $this->getTimeLow()->toString()
  144. );
  145. }
  146. return new Hexadecimal($timestamp);
  147. }
  148. public function getVersion(): ?int
  149. {
  150. if ($this->isNil()) {
  151. return null;
  152. }
  153. /** @var array $parts */
  154. $parts = unpack('n*', $this->bytes);
  155. return (int) $parts[4] >> 12;
  156. }
  157. private function isCorrectVariant(): bool
  158. {
  159. if ($this->isNil()) {
  160. return true;
  161. }
  162. return $this->getVariant() === Uuid::RFC_4122;
  163. }
  164. }