diff --git a/CHANGELOG.md b/CHANGELOG.md index 77bff1e..96e6e88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. a date/time value. * Introduce `Ramsey\Uuid\Variant` enum to represent the variant field. * Introduce `Ramsey\Uuid\Rfc4122\Version` enum to represent the version field. +* Add new static method `Ramsey\Uuid\BinaryUtils::applyVersionAndVariant()`. ### Changed diff --git a/src/BinaryUtils.php b/src/BinaryUtils.php index 0ae8e88..16fcbec 100644 --- a/src/BinaryUtils.php +++ b/src/BinaryUtils.php @@ -16,6 +16,11 @@ namespace Ramsey\Uuid; use Ramsey\Uuid\Rfc4122\Version; +use function pack; +use function substr; +use function substr_replace; +use function unpack; + /** * Provides binary math utilities */ @@ -64,4 +69,38 @@ class BinaryUtils return $timeHi; } + + /** + * Applies the RFC 4122 version number and variant field to the 128-bit + * integer (as a 16-byte string) provided + * + * @param non-empty-string $bytes A 128-bit integer (16-byte string) to + * which the RFC 4122 version number and variant field will be applied, + * making the number a valid UUID + * @param Version $version The RFC 4122 version to apply + * + * @return non-empty-string A 16-byte string with the UUID version and variant applied + * + * @psalm-pure + */ + public static function applyVersionAndVariant( + string $bytes, + Version $version, + Variant $variant = Variant::Rfc4122 + ): string { + /** @var array $unpackedTime */ + $unpackedTime = unpack('n*', substr($bytes, 6, 2)); + $timeHi = (int) $unpackedTime[1]; + $timeHiAndVersion = pack('n*', self::applyVersion($timeHi, $version)); + + /** @var array $unpackedClockSeq */ + $unpackedClockSeq = unpack('n*', substr($bytes, 8, 2)); + $clockSeqHi = (int) $unpackedClockSeq[1]; + $clockSeqHiAndReserved = pack('n*', self::applyVariant($clockSeqHi, $variant)); + + $bytes = substr_replace($bytes, $timeHiAndVersion, 6, 2); + + /** @var non-empty-string */ + return substr_replace($bytes, $clockSeqHiAndReserved, 8, 2); + } } diff --git a/src/UuidFactory.php b/src/UuidFactory.php index a16e52e..98ade95 100644 --- a/src/UuidFactory.php +++ b/src/UuidFactory.php @@ -35,12 +35,9 @@ use Ramsey\Uuid\Validator\ValidatorInterface; use function bin2hex; use function hex2bin; -use function pack; use function str_pad; use function strtolower; use function substr; -use function substr_replace; -use function unpack; use const STR_PAD_LEFT; @@ -484,25 +481,12 @@ class UuidFactory implements UuidFactoryInterface */ private function uuidFromBytesAndVersion(string $bytes, Version $version): UuidInterface { - /** @var array $unpackedTime */ - $unpackedTime = unpack('n*', substr($bytes, 6, 2)); - $timeHi = (int) $unpackedTime[1]; - $timeHiAndVersion = pack('n*', BinaryUtils::applyVersion($timeHi, $version)); - - /** @var array $unpackedClockSeq */ - $unpackedClockSeq = unpack('n*', substr($bytes, 8, 2)); - $clockSeqHi = (int) $unpackedClockSeq[1]; - $clockSeqHiAndReserved = pack('n*', BinaryUtils::applyVariant($clockSeqHi)); - - $bytes = substr_replace($bytes, $timeHiAndVersion, 6, 2); - - /** @var non-empty-string $bytes */ - $bytes = substr_replace($bytes, $clockSeqHiAndReserved, 8, 2); + $bytesWithVariantAndVersion = BinaryUtils::applyVersionAndVariant($bytes, $version); if ($this->isDefaultFeatureSet) { - return LazyUuidFromString::fromBytes($bytes); + return LazyUuidFromString::fromBytes($bytesWithVariantAndVersion); } - return $this->uuid($bytes); + return $this->uuid($bytesWithVariantAndVersion); } } diff --git a/tests/BinaryUtilsTest.php b/tests/BinaryUtilsTest.php index 0ff183d..9ffa32f 100644 --- a/tests/BinaryUtilsTest.php +++ b/tests/BinaryUtilsTest.php @@ -8,7 +8,9 @@ use Ramsey\Uuid\BinaryUtils; use Ramsey\Uuid\Rfc4122\Version; use Ramsey\Uuid\Variant; +use function bin2hex; use function dechex; +use function hex2bin; class BinaryUtilsTest extends TestCase { @@ -30,6 +32,19 @@ class BinaryUtilsTest extends TestCase $this->assertSame($expectedHex, dechex(BinaryUtils::applyVariant($clockSeq, $variant))); } + /** + * @param non-empty-string $bytes + * + * @dataProvider provideVersionAndVariantTestValues + */ + public function testApplyVersionAndVariant( + string $bytes, + Version $version, + string $expectedHex + ): void { + $this->assertSame($expectedHex, bin2hex(BinaryUtils::applyVersionAndVariant($bytes, $version))); + } + /** * @return array */ @@ -66,6 +81,24 @@ class BinaryUtilsTest extends TestCase 'expectedInt' => 21481, 'expectedHex' => '53e9', ], + [ + 'timeHi' => 1001, + 'version' => Version::ReorderedTime, + 'expectedInt' => 25577, + 'expectedHex' => '63e9', + ], + [ + 'timeHi' => 1001, + 'version' => Version::UnixTime, + 'expectedInt' => 29673, + 'expectedHex' => '73e9', + ], + [ + 'timeHi' => 1001, + 'version' => Version::Custom, + 'expectedInt' => 33769, + 'expectedHex' => '83e9', + ], [ 'timeHi' => 65535, 'version' => Version::Time, @@ -96,6 +129,24 @@ class BinaryUtilsTest extends TestCase 'expectedInt' => 24575, 'expectedHex' => '5fff', ], + [ + 'timeHi' => 65535, + 'version' => Version::ReorderedTime, + 'expectedInt' => 28671, + 'expectedHex' => '6fff', + ], + [ + 'timeHi' => 65535, + 'version' => Version::UnixTime, + 'expectedInt' => 32767, + 'expectedHex' => '7fff', + ], + [ + 'timeHi' => 65535, + 'version' => Version::Custom, + 'expectedInt' => 36863, + 'expectedHex' => '8fff', + ], [ 'timeHi' => 0, 'version' => Version::Time, @@ -126,6 +177,24 @@ class BinaryUtilsTest extends TestCase 'expectedInt' => 20480, 'expectedHex' => '5000', ], + [ + 'timeHi' => 0, + 'version' => Version::ReorderedTime, + 'expectedInt' => 24576, + 'expectedHex' => '6000', + ], + [ + 'timeHi' => 0, + 'version' => Version::UnixTime, + 'expectedInt' => 28672, + 'expectedHex' => '7000', + ], + [ + 'timeHi' => 0, + 'version' => Version::Custom, + 'expectedInt' => 32768, + 'expectedHex' => '8000', + ], ]; } @@ -257,4 +326,56 @@ class BinaryUtilsTest extends TestCase ], ]; } + + /** + * @return array + */ + public function provideVersionAndVariantTestValues(): array + { + /** @var non-empty-string $bytes */ + $bytes = hex2bin('426a25b74e975e743af1700fa4e42455'); + + return [ + [ + 'bytes' => $bytes, + 'version' => Version::Time, + 'expectedHex' => '426a25b74e971e74baf1700fa4e42455', + ], + [ + 'bytes' => $bytes, + 'version' => Version::DceSecurity, + 'expectedHex' => '426a25b74e972e74baf1700fa4e42455', + ], + [ + 'bytes' => $bytes, + 'version' => Version::HashMd5, + 'expectedHex' => '426a25b74e973e74baf1700fa4e42455', + ], + [ + 'bytes' => $bytes, + 'version' => Version::Random, + 'expectedHex' => '426a25b74e974e74baf1700fa4e42455', + ], + [ + 'bytes' => $bytes, + 'version' => Version::HashSha1, + 'expectedHex' => '426a25b74e975e74baf1700fa4e42455', + ], + [ + 'bytes' => $bytes, + 'version' => Version::ReorderedTime, + 'expectedHex' => '426a25b74e976e74baf1700fa4e42455', + ], + [ + 'bytes' => $bytes, + 'version' => Version::UnixTime, + 'expectedHex' => '426a25b74e977e74baf1700fa4e42455', + ], + [ + 'bytes' => $bytes, + 'version' => Version::Custom, + 'expectedHex' => '426a25b74e978e74baf1700fa4e42455', + ], + ]; + } }