Check clock sequence and local identifier boundaries

This commit is contained in:
Ben Ramsey
2020-03-06 17:00:08 -06:00
parent 8a8d5d4ba8
commit 4a53d4d33d
3 changed files with 173 additions and 18 deletions
+44 -9
View File
@@ -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);
+1 -1
View File
@@ -32,7 +32,7 @@ class FunctionsTest extends TestCase
Uuid::DCE_DOMAIN_PERSON,
new IntegerObject('1004'),
new Hexadecimal('aabbccdd0011'),
1234
63
);
/** @var FieldsInterface $fields */
+128 -8
View File
@@ -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'));
}
}