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\Guid; 16 17use Ramsey\Uuid\Exception\InvalidArgumentException; 18use Ramsey\Uuid\Fields\SerializableFieldsTrait; 19use Ramsey\Uuid\Rfc4122\FieldsInterface; 20use Ramsey\Uuid\Rfc4122\NilTrait; 21use Ramsey\Uuid\Rfc4122\VariantTrait; 22use Ramsey\Uuid\Rfc4122\VersionTrait; 23use Ramsey\Uuid\Type\Hexadecimal; 24use Ramsey\Uuid\Uuid; 25 26use function bin2hex; 27use function dechex; 28use function hexdec; 29use function pack; 30use function sprintf; 31use function str_pad; 32use function strlen; 33use function substr; 34use function unpack; 35 36use const STR_PAD_LEFT; 37 38/** 39 * GUIDs are comprised of a set of named fields, according to RFC 4122 40 * 41 * @see Guid 42 * 43 * @psalm-immutable 44 */ 45final class Fields implements FieldsInterface 46{ 47 use NilTrait; 48 use SerializableFieldsTrait; 49 use VariantTrait; 50 use VersionTrait; 51 52 /** 53 * @var string 54 */ 55 private $bytes; 56 57 /** 58 * @param string $bytes A 16-byte binary string representation of a UUID 59 * 60 * @throws InvalidArgumentException if the byte string is not exactly 16 bytes 61 * @throws InvalidArgumentException if the byte string does not represent a GUID 62 * @throws InvalidArgumentException if the byte string does not contain a valid version 63 */ 64 public function __construct(string $bytes) 65 { 66 if (strlen($bytes) !== 16) { 67 throw new InvalidArgumentException( 68 'The byte string must be 16 bytes long; ' 69 . 'received ' . strlen($bytes) . ' bytes' 70 ); 71 } 72 73 $this->bytes = $bytes; 74 75 if (!$this->isCorrectVariant()) { 76 throw new InvalidArgumentException( 77 'The byte string received does not conform to the RFC ' 78 . '4122 or Microsoft Corporation variants' 79 ); 80 } 81 82 if (!$this->isCorrectVersion()) { 83 throw new InvalidArgumentException( 84 'The byte string received does not contain a valid version' 85 ); 86 } 87 } 88 89 public function getBytes(): string 90 { 91 return $this->bytes; 92 } 93 94 public function getTimeLow(): Hexadecimal 95 { 96 // Swap the bytes from little endian to network byte order. 97 $hex = unpack( 98 'H*', 99 pack( 100 'v*', 101 hexdec(bin2hex(substr($this->bytes, 2, 2))), 102 hexdec(bin2hex(substr($this->bytes, 0, 2))) 103 ) 104 ); 105 106 return new Hexadecimal((string) ($hex[1] ?? '')); 107 } 108 109 public function getTimeMid(): Hexadecimal 110 { 111 // Swap the bytes from little endian to network byte order. 112 $hex = unpack( 113 'H*', 114 pack( 115 'v', 116 hexdec(bin2hex(substr($this->bytes, 4, 2))) 117 ) 118 ); 119 120 return new Hexadecimal((string) ($hex[1] ?? '')); 121 } 122 123 public function getTimeHiAndVersion(): Hexadecimal 124 { 125 // Swap the bytes from little endian to network byte order. 126 $hex = unpack( 127 'H*', 128 pack( 129 'v', 130 hexdec(bin2hex(substr($this->bytes, 6, 2))) 131 ) 132 ); 133 134 return new Hexadecimal((string) ($hex[1] ?? '')); 135 } 136 137 public function getTimestamp(): Hexadecimal 138 { 139 return new Hexadecimal(sprintf( 140 '%03x%04s%08s', 141 hexdec($this->getTimeHiAndVersion()->toString()) & 0x0fff, 142 $this->getTimeMid()->toString(), 143 $this->getTimeLow()->toString() 144 )); 145 } 146 147 public function getClockSeq(): Hexadecimal 148 { 149 $clockSeq = hexdec(bin2hex(substr($this->bytes, 8, 2))) & 0x3fff; 150 151 return new Hexadecimal(str_pad(dechex($clockSeq), 4, '0', STR_PAD_LEFT)); 152 } 153 154 public function getClockSeqHiAndReserved(): Hexadecimal 155 { 156 return new Hexadecimal(bin2hex(substr($this->bytes, 8, 1))); 157 } 158 159 public function getClockSeqLow(): Hexadecimal 160 { 161 return new Hexadecimal(bin2hex(substr($this->bytes, 9, 1))); 162 } 163 164 public function getNode(): Hexadecimal 165 { 166 return new Hexadecimal(bin2hex(substr($this->bytes, 10))); 167 } 168 169 public function getVersion(): ?int 170 { 171 if ($this->isNil()) { 172 return null; 173 } 174 175 $parts = unpack('n*', $this->bytes); 176 177 return ((int) $parts[4] >> 4) & 0x00f; 178 } 179 180 private function isCorrectVariant(): bool 181 { 182 if ($this->isNil()) { 183 return true; 184 } 185 186 $variant = $this->getVariant(); 187 188 return $variant === Uuid::RFC_4122 || $variant === Uuid::RESERVED_MICROSOFT; 189 } 190} 191