mirror of
https://github.com/ramsey/uuid.git
synced 2026-06-15 16:07:55 +03:00
Check clock sequence and local identifier boundaries
This commit is contained in:
@@ -15,7 +15,7 @@ declare(strict_types=1);
|
||||
namespace Ramsey\Uuid\Generator;
|
||||
|
||||
use Ramsey\Uuid\Converter\NumberConverterInterface;
|
||||
use Ramsey\Uuid\Exception\InvalidArgumentException;
|
||||
use Ramsey\Uuid\Exception\DceSecurityException;
|
||||
use Ramsey\Uuid\Provider\DceSecurityProviderInterface;
|
||||
use Ramsey\Uuid\Type\Hexadecimal;
|
||||
use Ramsey\Uuid\Type\Integer as IntegerObject;
|
||||
@@ -25,6 +25,7 @@ use function hex2bin;
|
||||
use function in_array;
|
||||
use function pack;
|
||||
use function str_pad;
|
||||
use function strlen;
|
||||
use function substr_replace;
|
||||
|
||||
use const STR_PAD_LEFT;
|
||||
@@ -41,6 +42,16 @@ class DceSecurityGenerator implements DceSecurityGeneratorInterface
|
||||
Uuid::DCE_DOMAIN_ORG,
|
||||
];
|
||||
|
||||
/**
|
||||
* Upper bounds for the clock sequence in DCE Security UUIDs.
|
||||
*/
|
||||
private const CLOCK_SEQ_HIGH = 63;
|
||||
|
||||
/**
|
||||
* Lower bounds for the clock sequence in DCE Security UUIDs.
|
||||
*/
|
||||
private const CLOCK_SEQ_LOW = 0;
|
||||
|
||||
/**
|
||||
* @var NumberConverterInterface
|
||||
*/
|
||||
@@ -73,15 +84,27 @@ class DceSecurityGenerator implements DceSecurityGeneratorInterface
|
||||
?int $clockSeq = null
|
||||
): string {
|
||||
if (!in_array($localDomain, self::DOMAINS)) {
|
||||
throw new InvalidArgumentException(
|
||||
throw new DceSecurityException(
|
||||
'Local domain must be a valid DCE Security domain'
|
||||
);
|
||||
}
|
||||
|
||||
if ($localIdentifier && $localIdentifier->isNegative()) {
|
||||
throw new DceSecurityException(
|
||||
'Local identifier out of bounds; it must be a value between 0 and 4294967295'
|
||||
);
|
||||
}
|
||||
|
||||
if ($clockSeq > self::CLOCK_SEQ_HIGH || $clockSeq < self::CLOCK_SEQ_LOW) {
|
||||
throw new DceSecurityException(
|
||||
'Clock sequence out of bounds; it must be a value between 0 and 63'
|
||||
);
|
||||
}
|
||||
|
||||
switch ($localDomain) {
|
||||
case Uuid::DCE_DOMAIN_ORG:
|
||||
if ($localIdentifier === null) {
|
||||
throw new InvalidArgumentException(
|
||||
throw new DceSecurityException(
|
||||
'A local identifier must be provided for the org domain'
|
||||
);
|
||||
}
|
||||
@@ -102,18 +125,30 @@ class DceSecurityGenerator implements DceSecurityGeneratorInterface
|
||||
break;
|
||||
}
|
||||
|
||||
$identifierHex = $this->numberConverter->toHex($localIdentifier->toString());
|
||||
|
||||
// The maximum value for the local identifier is 0xffffffff, or
|
||||
// 4294967295. This is 8 hexadecimal digits, so if the length of
|
||||
// hexadecimal digits is greater than 8, we know the value is greater
|
||||
// than 0xffffffff.
|
||||
if (strlen($identifierHex) > 8) {
|
||||
throw new DceSecurityException(
|
||||
'Local identifier out of bounds; it must be a value between 0 and 4294967295'
|
||||
);
|
||||
}
|
||||
|
||||
$domainByte = pack('n', $localDomain)[1];
|
||||
$identifierBytes = hex2bin(str_pad(
|
||||
$this->numberConverter->toHex($localIdentifier->toString()),
|
||||
8,
|
||||
'0',
|
||||
STR_PAD_LEFT
|
||||
));
|
||||
$identifierBytes = hex2bin(str_pad($identifierHex, 8, '0', STR_PAD_LEFT));
|
||||
|
||||
if ($node instanceof Hexadecimal) {
|
||||
$node = $node->toString();
|
||||
}
|
||||
|
||||
// Shift the clock sequence 8 bits to the left, so it matches 0x3f00.
|
||||
if ($clockSeq !== null) {
|
||||
$clockSeq = $clockSeq << 8;
|
||||
}
|
||||
|
||||
/** @var string $bytes */
|
||||
$bytes = $this->timeGenerator->generate($node, $clockSeq);
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ class FunctionsTest extends TestCase
|
||||
Uuid::DCE_DOMAIN_PERSON,
|
||||
new IntegerObject('1004'),
|
||||
new Hexadecimal('aabbccdd0011'),
|
||||
1234
|
||||
63
|
||||
);
|
||||
|
||||
/** @var FieldsInterface $fields */
|
||||
|
||||
@@ -8,7 +8,7 @@ use Mockery;
|
||||
use Ramsey\Uuid\Converter\Number\GenericNumberConverter;
|
||||
use Ramsey\Uuid\Converter\NumberConverterInterface;
|
||||
use Ramsey\Uuid\Converter\Time\GenericTimeConverter;
|
||||
use Ramsey\Uuid\Exception\InvalidArgumentException;
|
||||
use Ramsey\Uuid\Exception\DceSecurityException;
|
||||
use Ramsey\Uuid\Generator\DceSecurityGenerator;
|
||||
use Ramsey\Uuid\Generator\DefaultTimeGenerator;
|
||||
use Ramsey\Uuid\Generator\TimeGeneratorInterface;
|
||||
@@ -44,7 +44,6 @@ class DceSecurityGeneratorTest extends TestCase
|
||||
int $providedDomain,
|
||||
?IntegerObject $providedId,
|
||||
?Hexadecimal $providedNode,
|
||||
?int $providedClockSeq,
|
||||
string $expectedId,
|
||||
string $expectedDomain,
|
||||
string $expectedNode,
|
||||
@@ -68,7 +67,7 @@ class DceSecurityGeneratorTest extends TestCase
|
||||
|
||||
$dceSecurityGenerator = new DceSecurityGenerator($numberConverter, $timeGenerator, $dceSecurityProvider);
|
||||
|
||||
$bytes = $dceSecurityGenerator->generate($providedDomain, $providedId, $providedNode, $providedClockSeq);
|
||||
$bytes = $dceSecurityGenerator->generate($providedDomain, $providedId, $providedNode);
|
||||
|
||||
$this->assertSame($expectedId, bin2hex(substr($bytes, 0, 4)));
|
||||
$this->assertSame($expectedDomain, bin2hex(substr($bytes, 9, 1)));
|
||||
@@ -91,7 +90,6 @@ class DceSecurityGeneratorTest extends TestCase
|
||||
'providedDomain' => Uuid::DCE_DOMAIN_PERSON,
|
||||
'providedId' => null,
|
||||
'providedNode' => null,
|
||||
'providedClockSeq' => null,
|
||||
'expectedId' => '000003e9',
|
||||
'expectedDomain' => '00',
|
||||
'expectedNode' => '001122334455',
|
||||
@@ -106,7 +104,6 @@ class DceSecurityGeneratorTest extends TestCase
|
||||
'providedDomain' => Uuid::DCE_DOMAIN_GROUP,
|
||||
'providedId' => null,
|
||||
'providedNode' => null,
|
||||
'providedClockSeq' => null,
|
||||
'expectedId' => '000007d1',
|
||||
'expectedDomain' => '01',
|
||||
'expectedNode' => '001122334455',
|
||||
@@ -121,7 +118,6 @@ class DceSecurityGeneratorTest extends TestCase
|
||||
'providedDomain' => Uuid::DCE_DOMAIN_ORG,
|
||||
'providedId' => new IntegerObject('4294967295'),
|
||||
'providedNode' => null,
|
||||
'providedClockSeq' => null,
|
||||
'expectedId' => 'ffffffff',
|
||||
'expectedDomain' => '02',
|
||||
'expectedNode' => '001122334455',
|
||||
@@ -138,7 +134,7 @@ class DceSecurityGeneratorTest extends TestCase
|
||||
|
||||
$generator = new DceSecurityGenerator($numberConverter, $timeGenerator, $dceSecurityProvider);
|
||||
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->expectException(DceSecurityException::class);
|
||||
$this->expectExceptionMessage('Local domain must be a valid DCE Security domain');
|
||||
|
||||
$generator->generate(42);
|
||||
@@ -152,9 +148,133 @@ class DceSecurityGeneratorTest extends TestCase
|
||||
|
||||
$generator = new DceSecurityGenerator($numberConverter, $timeGenerator, $dceSecurityProvider);
|
||||
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->expectException(DceSecurityException::class);
|
||||
$this->expectExceptionMessage('A local identifier must be provided for the org domain');
|
||||
|
||||
$generator->generate(Uuid::DCE_DOMAIN_ORG);
|
||||
}
|
||||
|
||||
public function testClockSequenceLowerBounds(): void
|
||||
{
|
||||
$dceSecurityProvider = Mockery::mock(DceSecurityProviderInterface::class);
|
||||
$nodeProvider = Mockery::mock(NodeProviderInterface::class);
|
||||
$timeProvider = new FixedTimeProvider(new Time(1583527677, 111984));
|
||||
|
||||
$calculator = new BrickMathCalculator();
|
||||
$numberConverter = new GenericNumberConverter($calculator);
|
||||
$timeConverter = new GenericTimeConverter($calculator);
|
||||
$timeGenerator = new DefaultTimeGenerator($nodeProvider, $timeConverter, $timeProvider);
|
||||
|
||||
$dceSecurityGenerator = new DceSecurityGenerator($numberConverter, $timeGenerator, $dceSecurityProvider);
|
||||
|
||||
$bytes = $dceSecurityGenerator->generate(
|
||||
Uuid::DCE_DOMAIN_ORG,
|
||||
new IntegerObject(1001),
|
||||
new Hexadecimal('0123456789ab'),
|
||||
0
|
||||
);
|
||||
|
||||
$hex = bin2hex($bytes);
|
||||
|
||||
$this->assertSame('000003e9', substr($hex, 0, 8));
|
||||
$this->assertSame('5feb01ea', substr($hex, 8, 8));
|
||||
$this->assertSame('00', substr($hex, 16, 2));
|
||||
$this->assertSame('02', substr($hex, 18, 2));
|
||||
$this->assertSame('0123456789ab', substr($hex, 20));
|
||||
}
|
||||
|
||||
public function testClockSequenceUpperBounds(): void
|
||||
{
|
||||
$dceSecurityProvider = Mockery::mock(DceSecurityProviderInterface::class);
|
||||
$nodeProvider = Mockery::mock(NodeProviderInterface::class);
|
||||
$timeProvider = new FixedTimeProvider(new Time(1583527677, 111984));
|
||||
|
||||
$calculator = new BrickMathCalculator();
|
||||
$numberConverter = new GenericNumberConverter($calculator);
|
||||
$timeConverter = new GenericTimeConverter($calculator);
|
||||
$timeGenerator = new DefaultTimeGenerator($nodeProvider, $timeConverter, $timeProvider);
|
||||
|
||||
$dceSecurityGenerator = new DceSecurityGenerator($numberConverter, $timeGenerator, $dceSecurityProvider);
|
||||
|
||||
$bytes = $dceSecurityGenerator->generate(
|
||||
Uuid::DCE_DOMAIN_ORG,
|
||||
new IntegerObject(1001),
|
||||
new Hexadecimal('0123456789ab'),
|
||||
63
|
||||
);
|
||||
|
||||
$hex = bin2hex($bytes);
|
||||
|
||||
$this->assertSame('000003e9', substr($hex, 0, 8));
|
||||
$this->assertSame('5feb01ea', substr($hex, 8, 8));
|
||||
$this->assertSame('3f', substr($hex, 16, 2));
|
||||
$this->assertSame('02', substr($hex, 18, 2));
|
||||
$this->assertSame('0123456789ab', substr($hex, 20));
|
||||
}
|
||||
|
||||
public function testExceptionThrownWhenClockSequenceTooLow(): void
|
||||
{
|
||||
$dceSecurityProvider = Mockery::mock(DceSecurityProviderInterface::class);
|
||||
$timeGenerator = Mockery::mock(TimeGeneratorInterface::class);
|
||||
$numberConverter = Mockery::mock(NumberConverterInterface::class);
|
||||
|
||||
$dceSecurityGenerator = new DceSecurityGenerator($numberConverter, $timeGenerator, $dceSecurityProvider);
|
||||
|
||||
$this->expectException(DceSecurityException::class);
|
||||
$this->expectExceptionMessage(
|
||||
'Clock sequence out of bounds; it must be a value between 0 and 63'
|
||||
);
|
||||
|
||||
$dceSecurityGenerator->generate(Uuid::DCE_DOMAIN_ORG, null, null, -1);
|
||||
}
|
||||
|
||||
public function testExceptionThrownWhenClockSequenceTooHigh(): void
|
||||
{
|
||||
$dceSecurityProvider = Mockery::mock(DceSecurityProviderInterface::class);
|
||||
$timeGenerator = Mockery::mock(TimeGeneratorInterface::class);
|
||||
$numberConverter = Mockery::mock(NumberConverterInterface::class);
|
||||
|
||||
$dceSecurityGenerator = new DceSecurityGenerator($numberConverter, $timeGenerator, $dceSecurityProvider);
|
||||
|
||||
$this->expectException(DceSecurityException::class);
|
||||
$this->expectExceptionMessage(
|
||||
'Clock sequence out of bounds; it must be a value between 0 and 63'
|
||||
);
|
||||
|
||||
$dceSecurityGenerator->generate(Uuid::DCE_DOMAIN_ORG, null, null, 64);
|
||||
}
|
||||
|
||||
public function testExceptionThrownWhenLocalIdTooLow(): void
|
||||
{
|
||||
$dceSecurityProvider = Mockery::mock(DceSecurityProviderInterface::class);
|
||||
$timeGenerator = Mockery::mock(TimeGeneratorInterface::class);
|
||||
$numberConverter = Mockery::mock(NumberConverterInterface::class);
|
||||
|
||||
$dceSecurityGenerator = new DceSecurityGenerator($numberConverter, $timeGenerator, $dceSecurityProvider);
|
||||
|
||||
$this->expectException(DceSecurityException::class);
|
||||
$this->expectExceptionMessage(
|
||||
'Local identifier out of bounds; it must be a value between 0 and 4294967295'
|
||||
);
|
||||
|
||||
$dceSecurityGenerator->generate(Uuid::DCE_DOMAIN_ORG, new IntegerObject(-1));
|
||||
}
|
||||
|
||||
public function testExceptionThrownWhenLocalIdTooHigh(): void
|
||||
{
|
||||
$dceSecurityProvider = Mockery::mock(DceSecurityProviderInterface::class);
|
||||
$timeGenerator = Mockery::mock(TimeGeneratorInterface::class);
|
||||
|
||||
$calculator = new BrickMathCalculator();
|
||||
$numberConverter = new GenericNumberConverter($calculator);
|
||||
|
||||
$dceSecurityGenerator = new DceSecurityGenerator($numberConverter, $timeGenerator, $dceSecurityProvider);
|
||||
|
||||
$this->expectException(DceSecurityException::class);
|
||||
$this->expectExceptionMessage(
|
||||
'Local identifier out of bounds; it must be a value between 0 and 4294967295'
|
||||
);
|
||||
|
||||
$dceSecurityGenerator->generate(Uuid::DCE_DOMAIN_ORG, new IntegerObject('4294967296'));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user