Change NodeProviderInterface::getNode() to return Hexadecimal

This commit is contained in:
Ben Ramsey
2020-02-29 22:35:45 -06:00
parent 4ffd156a84
commit 86c37eff4c
13 changed files with 196 additions and 135 deletions
+24
View File
@@ -0,0 +1,24 @@
<?php
/**
* This file is part of the ramsey/uuid library
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @copyright Copyright (c) Ben Ramsey <ben@benramsey.com>
* @license http://opensource.org/licenses/MIT MIT
*/
declare(strict_types=1);
namespace Ramsey\Uuid\Exception;
use RuntimeException as PhpRuntimeException;
/**
* Thrown to indicate that attempting to fetch or create a node ID encountered an error
*/
class NodeException extends PhpRuntimeException
{
}
+16 -7
View File
@@ -14,7 +14,9 @@ declare(strict_types=1);
namespace Ramsey\Uuid\Provider\Node;
use Ramsey\Uuid\Exception\NodeException;
use Ramsey\Uuid\Provider\NodeProviderInterface;
use Ramsey\Uuid\Type\Hexadecimal;
/**
* FallbackNodeProvider retrieves the system node ID by stepping through a list
@@ -35,18 +37,25 @@ class FallbackNodeProvider implements NodeProviderInterface
$this->nodeProviders = $providers;
}
/**
* @inheritDoc
*/
public function getNode()
public function getNode(): Hexadecimal
{
$lastProviderException = null;
/** @var NodeProviderInterface $provider */
foreach ($this->nodeProviders as $provider) {
if ($node = $provider->getNode()) {
return $node;
try {
return $provider->getNode();
} catch (NodeException $exception) {
$lastProviderException = $exception;
continue;
}
}
return null;
throw new NodeException(
'Unable to find a suitable node provider',
0,
$lastProviderException
);
}
}
+3 -5
View File
@@ -16,6 +16,7 @@ namespace Ramsey\Uuid\Provider\Node;
use Ramsey\Uuid\Exception\RandomSourceException;
use Ramsey\Uuid\Provider\NodeProviderInterface;
use Ramsey\Uuid\Type\Hexadecimal;
use function bin2hex;
use function dechex;
@@ -33,10 +34,7 @@ use const STR_PAD_LEFT;
*/
class RandomNodeProvider implements NodeProviderInterface
{
/**
* @inheritDoc
*/
public function getNode()
public function getNode(): Hexadecimal
{
try {
$nodeBytes = random_bytes(6);
@@ -65,6 +63,6 @@ class RandomNodeProvider implements NodeProviderInterface
// Recombine the node bytes.
$node = $nodeMsb . $nodeLsb;
return str_pad(bin2hex($node), 12, '0', STR_PAD_LEFT);
return new Hexadecimal(str_pad(bin2hex($node), 12, '0', STR_PAD_LEFT));
}
}
+2 -5
View File
@@ -51,12 +51,9 @@ class StaticNodeProvider implements NodeProviderInterface
$this->node = $this->setMulticastBit($node);
}
/**
* @inheritDoc
*/
public function getNode()
public function getNode(): Hexadecimal
{
return $this->node->toString();
return $this->node;
}
/**
+46 -29
View File
@@ -14,13 +14,14 @@ declare(strict_types=1);
namespace Ramsey\Uuid\Provider\Node;
use Ramsey\Uuid\Exception\NodeException;
use Ramsey\Uuid\Provider\NodeProviderInterface;
use Ramsey\Uuid\Type\Hexadecimal;
use function array_filter;
use function array_map;
use function array_walk;
use function count;
use function is_array;
use function ob_get_clean;
use function ob_start;
use function preg_match;
@@ -44,37 +45,48 @@ use const PREG_PATTERN_ORDER;
class SystemNodeProvider implements NodeProviderInterface
{
/**
* @inheritDoc
* Pattern to match nodes in ifconfig and ipconfig output.
*/
public function getNode()
private const IFCONFIG_PATTERN = '/[^:]([0-9a-f]{2}([:-])[0-9a-f]{2}(\2[0-9a-f]{2}){4})[^:]/i';
/**
* Pattern to match nodes in sysfs stream output.
*/
private const SYSFS_PATTERN = '/^([0-9a-f]{2}:){5}[0-9a-f]{2}$/i';
public function getNode(): Hexadecimal
{
$node = $this->getNodeFromSystem();
if ($node === '') {
throw new NodeException(
'Unable to fetch a node for this system'
);
}
return new Hexadecimal($node);
}
/**
* Returns the system node, if it can find it
*/
protected function getNodeFromSystem(): string
{
static $node = null;
if ($node !== null) {
return $node;
return (string) $node;
}
$pattern = '/[^:]([0-9A-Fa-f]{2}([:-])[0-9A-Fa-f]{2}(\2[0-9A-Fa-f]{2}){4})[^:]/';
$matches = [];
// First, try a Linux-specific approach.
$node = $this->getSysfs();
// Search the ifconfig output for all MAC addresses and return
// the first one found.
if ($node === false) {
if (preg_match_all($pattern, $this->getIfconfig(), $matches, PREG_PATTERN_ORDER)) {
$node = $matches[1][0] ?? false;
}
if ($node === '') {
// Search ifconfig output for MAC addresses & return the first one.
$node = $this->getIfconfig();
}
if ($node !== false) {
$node = str_replace([':', '-'], '', $node);
if (is_array($node)) {
$node = $node[0] ?? false;
}
}
$node = str_replace([':', '-'], '', $node);
return $node;
}
@@ -84,7 +96,7 @@ class SystemNodeProvider implements NodeProviderInterface
*
* @codeCoverageIgnore
*/
private function getIfconfig(): string
protected function getIfconfig(): string
{
$disabledFunctions = strtolower((string) ini_get('disable_functions'));
@@ -113,23 +125,28 @@ class SystemNodeProvider implements NodeProviderInterface
break;
}
return (string) ob_get_clean();
$ifconfig = (string) ob_get_clean();
$node = '';
if (preg_match_all(self::IFCONFIG_PATTERN, $ifconfig, $matches, PREG_PATTERN_ORDER)) {
$node = $matches[1][0] ?? '';
}
return (string) $node;
}
/**
* Returns MAC address from the first system interface via the sysfs interface
*
* @return string|bool
*/
protected function getSysfs()
protected function getSysfs(): string
{
$mac = false;
$mac = '';
if (strtoupper(constant('PHP_OS')) === 'LINUX') {
$addressPaths = glob('/sys/class/net/*/address', GLOB_NOSORT);
if ($addressPaths === false || count($addressPaths) === 0) {
return false;
return '';
}
$macs = [];
@@ -145,12 +162,12 @@ class SystemNodeProvider implements NodeProviderInterface
// Remove invalid entries.
$macs = array_filter($macs, function (string $address) {
return $address !== '00:00:00:00:00:00'
&& preg_match('/^([0-9a-f]{2}:){5}[0-9a-f]{2}$/i', $address);
&& preg_match(self::SYSFS_PATTERN, $address);
});
$mac = reset($macs);
}
return $mac;
return (string) $mac;
}
}
+6 -7
View File
@@ -14,18 +14,17 @@ declare(strict_types=1);
namespace Ramsey\Uuid\Provider;
use Ramsey\Uuid\Type\Hexadecimal;
/**
* A node provider retrieves the system node ID
*
* The system node ID, or host ID, is often the same as the MAC address for a
* network interface on the host.
* A node provider retrieves or generates a node ID
*/
interface NodeProviderInterface
{
/**
* Returns the system node ID
* Returns a node ID
*
* @return string|false|null System node ID as a hexadecimal string
* @return Hexadecimal The node ID as a hexadecimal string
*/
public function getNode();
public function getNode(): Hexadecimal;
}
+1 -1
View File
@@ -526,7 +526,7 @@ class ExpectedBehaviorTest extends TestCase
public function testUsingDefaultTimeGeneratorWithCustomProviders()
{
$nodeProvider = \Mockery::mock('Ramsey\Uuid\Provider\NodeProviderInterface', [
'getNode' => '0123456789ab',
'getNode' => new Hexadecimal('0123456789ab'),
]);
$timeConverter = \Mockery::mock('Ramsey\Uuid\Converter\TimeConverterInterface');
+1 -1
View File
@@ -56,7 +56,7 @@ class DceSecurityGeneratorTest extends TestCase
]);
$nodeProvider = Mockery::mock(NodeProviderInterface::class, [
'getNode' => $node,
'getNode' => new Hexadecimal($node),
]);
$timeProvider = new FixedTimeProvider(new Time($seconds, $microseconds));
+1 -1
View File
@@ -87,7 +87,7 @@ class DefaultTimeGeneratorTest extends TestCase
{
$this->nodeProvider->expects($this->once())
->method('getNode')
->willReturn('122f80ca9e06');
->willReturn(new Hexadecimal('122f80ca9e06'));
$this->timeConverter->expects($this->once())
->method('calculateTime')
->with($this->currentTime['sec'], $this->currentTime['usec'])
@@ -4,10 +4,12 @@ declare(strict_types=1);
namespace Ramsey\Uuid\Test\Provider\Node;
use Ramsey\Uuid\Exception\NodeException;
use Ramsey\Uuid\Provider\Node\FallbackNodeProvider;
use Ramsey\Uuid\Provider\Node\NodeProviderCollection;
use Ramsey\Uuid\Provider\NodeProviderInterface;
use Ramsey\Uuid\Test\TestCase;
use Ramsey\Uuid\Type\Hexadecimal;
class FallbackNodeProviderTest extends TestCase
{
@@ -16,11 +18,11 @@ class FallbackNodeProviderTest extends TestCase
$providerWithNode = $this->getMockBuilder(NodeProviderInterface::class)->getMock();
$providerWithNode->expects($this->once())
->method('getNode')
->willReturn('57764a07f756');
->willReturn(new Hexadecimal('57764a07f756'));
$providerWithoutNode = $this->getMockBuilder(NodeProviderInterface::class)->getMock();
$providerWithoutNode->expects($this->once())
->method('getNode')
->willReturn(null);
->willThrowException(new NodeException());
$provider = new FallbackNodeProvider(new NodeProviderCollection([$providerWithoutNode, $providerWithNode]));
$provider->getNode();
@@ -31,11 +33,11 @@ class FallbackNodeProviderTest extends TestCase
$providerWithoutNode = $this->getMockBuilder(NodeProviderInterface::class)->getMock();
$providerWithoutNode->expects($this->once())
->method('getNode')
->willReturn(null);
->willThrowException(new NodeException());
$providerWithNode = $this->getMockBuilder(NodeProviderInterface::class)->getMock();
$providerWithNode->expects($this->once())
->method('getNode')
->willReturn('57764a07f756');
->willReturn(new Hexadecimal('57764a07f756'));
$anotherProviderWithoutNode = $this->getMockBuilder(NodeProviderInterface::class)->getMock();
$anotherProviderWithoutNode->expects($this->never())
->method('getNode');
@@ -48,15 +50,19 @@ class FallbackNodeProviderTest extends TestCase
$this->assertEquals('57764a07f756', $node);
}
public function testGetNodeReturnsNullWhenNoNodesFound(): void
public function testGetNodeThrowsExceptionWhenNoNodesFound(): void
{
$providerWithoutNode = $this->getMockBuilder(NodeProviderInterface::class)->getMock();
$providerWithoutNode->method('getNode')
->willReturn(null);
->willThrowException(new NodeException());
$provider = new FallbackNodeProvider(new NodeProviderCollection([$providerWithoutNode]));
$node = $provider->getNode();
$this->assertNull($node);
$this->expectException(NodeException::class);
$this->expectExceptionMessage(
'Unable to find a suitable node provider'
);
$provider->getNode();
}
}
@@ -37,7 +37,7 @@ class RandomNodeProviderTest extends TestCase
$provider = new RandomNodeProvider();
$node = $provider->getNode();
$this->assertSame($expectedNode, $node);
$this->assertSame($expectedNode, $node->toString());
$randomBytes->verifyInvoked([6]);
}
@@ -55,7 +55,7 @@ class RandomNodeProviderTest extends TestCase
$randomBytes = AspectMock::func('Ramsey\Uuid\Provider\Node', 'random_bytes', $bytes);
$provider = new RandomNodeProvider();
$this->assertSame($expectedNode, $provider->getNode());
$this->assertSame($expectedNode, $provider->getNode()->toString());
$randomBytes->verifyInvoked([6]);
}
@@ -74,7 +74,7 @@ class RandomNodeProviderTest extends TestCase
$randomBytes = AspectMock::func('Ramsey\Uuid\Provider\Node', 'random_bytes', $bytes);
$provider = new RandomNodeProvider();
$this->assertSame($expectedNode, $provider->getNode());
$this->assertSame($expectedNode, $provider->getNode()->toString());
$randomBytes->verifyInvoked([6]);
}
@@ -90,7 +90,7 @@ class RandomNodeProviderTest extends TestCase
$randomBytes = AspectMock::func('Ramsey\Uuid\Provider\Node', 'random_bytes', $bytes);
$provider = new RandomNodeProvider();
$this->assertSame($expectedNode, $provider->getNode());
$this->assertSame($expectedNode, $provider->getNode()->toString());
$randomBytes->verifyInvoked([6]);
}
@@ -18,7 +18,7 @@ class StaticNodeProviderTest extends TestCase
{
$staticNode = new StaticNodeProvider($node);
$this->assertSame($expectedNode, $staticNode->getNode());
$this->assertSame($expectedNode, $staticNode->getNode()->toString());
}
/**
+77 -66
View File
@@ -6,8 +6,8 @@ namespace Ramsey\Uuid\Test\Provider\Node;
use AspectMock\Proxy\FuncProxy;
use AspectMock\Test as AspectMock;
use Mockery;
use Ramsey\Uuid\Exception\InvalidArgumentException;
use Ramsey\Uuid\Exception\NodeException;
use Ramsey\Uuid\Provider\Node\SystemNodeProvider;
use Ramsey\Uuid\Test\TestCase;
@@ -84,13 +84,13 @@ class SystemNodeProviderTest extends TestCase
/* Assert the result match expectations */
$this->assertMockFunctions(null, null, ['netstat -ie 2>&1'], ['PHP_OS'], ['disable_functions']);
$this->assertSame($expected, $node);
$this->assertSame($expected, $node->toString());
$message = vsprintf(
'Node should be a hexadecimal string of 12 characters. Actual node: %s (length: %s)',
[$node, strlen($node),]
[$node->toString(), strlen($node->toString()),]
);
$this->assertRegExp('/^[A-Fa-f0-9]{12}$/', $node, $message);
$this->assertRegExp('/^[A-Fa-f0-9]{12}$/', $node->toString(), $message);
}
/**
@@ -112,13 +112,18 @@ class SystemNodeProviderTest extends TestCase
);
/* Act */
$exception = null;
$provider = new SystemNodeProvider();
$node = $provider->getNode();
try {
$node = $provider->getNode();
} catch (NodeException $exception) {
// do nothing
}
/* Assert */
$this->assertMockFunctions(null, null, ['netstat -ie 2>&1'], ['PHP_OS'], ['disable_functions']);
$this->assertFalse($node);
$this->assertInstanceOf(NodeException::class, $exception);
}
/**
@@ -146,7 +151,7 @@ class SystemNodeProviderTest extends TestCase
/* Assert */
$this->assertMockFunctions(null, null, ['netstat -ie 2>&1'], ['PHP_OS'], ['disable_functions']);
$this->assertEquals($expected, $node);
$this->assertEquals($expected, $node->toString());
}
/**
@@ -168,13 +173,18 @@ class SystemNodeProviderTest extends TestCase
);
/* Act */
$exception = null;
$provider = new SystemNodeProvider();
$node = $provider->getNode();
try {
$node = $provider->getNode();
} catch (NodeException $exception) {
// do nothing
}
/* Assert */
$this->assertMockFunctions(null, null, ['netstat -ie 2>&1'], ['PHP_OS'], ['disable_functions']);
$this->assertEquals(false, $node);
$this->assertInstanceOf(NodeException::class, $exception);
}
/**
@@ -201,7 +211,7 @@ class SystemNodeProviderTest extends TestCase
/* Assert */
$this->assertMockFunctions(null, null, ['netstat -ie 2>&1'], ['PHP_OS'], ['disable_functions']);
$this->assertEquals('AABBCCDDEEFF', $node);
$this->assertEquals('aabbccddeeff', $node->toString());
}
/**
@@ -222,13 +232,18 @@ class SystemNodeProviderTest extends TestCase
);
/* Act */
$exception = null;
$provider = new SystemNodeProvider();
$node = $provider->getNode();
try {
$node = $provider->getNode();
} catch (NodeException $exception) {
// do nothing
}
/* Assert */
$this->assertMockFunctions(null, null, ['netstat -ie 2>&1'], ['PHP_OS'], ['disable_functions']);
$this->assertFalse($node);
$this->assertInstanceOf(NodeException::class, $exception);
}
/**
@@ -249,14 +264,26 @@ class SystemNodeProviderTest extends TestCase
);
/* Act */
$exception1 = null;
$exception2 = null;
$provider = new SystemNodeProvider();
$provider->getNode();
$node = $provider->getNode();
try {
$provider->getNode();
} catch (NodeException $exception1) {
// do nothing
}
try {
$provider->getNode();
} catch (NodeException $exception2) {
// do nothing
}
/* Assert */
$this->assertMockFunctions(null, null, ['netstat -ie 2>&1'], ['PHP_OS'], ['disable_functions']);
$this->assertFalse($node);
$this->assertInstanceOf(NodeException::class, $exception1);
$this->assertInstanceOf(NodeException::class, $exception2);
}
/**
@@ -277,8 +304,14 @@ class SystemNodeProviderTest extends TestCase
);
/* Act */
$exception = null;
$provider = new SystemNodeProvider();
$node = $provider->getNode();
try {
$node = $provider->getNode();
} catch (NodeException $exception) {
// do nothing
}
/* Assert */
$globBodyAssert = null;
@@ -298,7 +331,7 @@ class SystemNodeProviderTest extends TestCase
$isReadableAssert
);
$this->assertFalse($node);
$this->assertInstanceOf(NodeException::class, $exception);
}
/**
@@ -326,7 +359,7 @@ class SystemNodeProviderTest extends TestCase
/* Assert */
$this->assertMockFunctions(null, null, ['netstat -ie 2>&1'], ['PHP_OS'], ['disable_functions']);
$this->assertEquals($node, $node2);
$this->assertEquals($node->toString(), $node2->toString());
}
/**
@@ -354,7 +387,7 @@ class SystemNodeProviderTest extends TestCase
/* Assert */
$this->assertMockFunctions(null, null, ['netstat -ie 2>&1'], ['PHP_OS'], ['disable_functions']);
$this->assertEquals($node, $node2);
$this->assertEquals($node->toString(), $node2->toString());
}
/**
@@ -409,7 +442,7 @@ class SystemNodeProviderTest extends TestCase
$isReadableAssert
);
$this->assertEquals('010203040506', $node);
$this->assertEquals('010203040506', $node->toString());
}
/**
@@ -442,7 +475,7 @@ class SystemNodeProviderTest extends TestCase
['disable_functions']
);
$this->assertEquals('010203040506', $node);
$this->assertEquals('010203040506', $node->toString());
}
/**
@@ -475,7 +508,7 @@ class SystemNodeProviderTest extends TestCase
['disable_functions']
);
$this->assertEquals('010203040506', $node);
$this->assertEquals('010203040506', $node->toString());
}
/**
@@ -510,7 +543,7 @@ class SystemNodeProviderTest extends TestCase
['mock address path 1', 'mock address path 2']
);
$this->assertEquals('010203040506', $node);
$this->assertEquals('010203040506', $node->toString());
}
/**
@@ -529,8 +562,14 @@ class SystemNodeProviderTest extends TestCase
);
/* Act */
$exception = null;
$provider = new SystemNodeProvider();
$node = $provider->getNode();
try {
$node = $provider->getNode();
} catch (NodeException $exception) {
// do nothing
}
/* Assert */
$this->assertMockFunctions(
@@ -541,7 +580,7 @@ class SystemNodeProviderTest extends TestCase
['disable_functions']
);
$this->assertFalse($node);
$this->assertInstanceOf(NodeException::class, $exception);
}
/**
@@ -845,7 +884,7 @@ TXT
DHCP Enabled. . . . . . . . . . . : No
Autoconfiguration Enabled . . . . : Yes
TXT
, '080027B842C6',
, '080027b842c6',
],
'Full output - FreeBSD' => [<<<'TXT'
Name Mtu Network Address Ipkts Ierrs Idrop Opkts Oerrs Coll
@@ -859,51 +898,23 @@ TXT
/* The single line that is relevant */
'Linux - single line' => ["\ndocker0 Link encap:Ethernet HWaddr 01:23:45:67:89:ab\n", '0123456789ab'],
'MacOS - Single line ' => ["\nether 10:dd:b1:b4:e4:8e\n", '10ddb1b4e48e'],
'Window - single line' => ["\nPhysical Address. . . . . . . . . : 08-00-27-B8-42-C6\n", '080027B842C6'],
'Window - single line' => ["\nPhysical Address. . . . . . . . . : 08-00-27-B8-42-C6\n", '080027b842c6'],
/* Minimal subsets of the single line to show the differences */
'with colon, with linebreak, with space' => ["\n : AA-BB-CC-DD-EE-FF\n", 'AABBCCDDEEFF'],
'without colon, with linebreak, with space' => ["\n AA-BB-CC-DD-EE-FF \n", 'AABBCCDDEEFF'],
'without colon, with linebreak, without space' => ["\nAA-BB-CC-DD-EE-FF\n", 'AABBCCDDEEFF'],
'without colon, without linebreak, with space' => [' AA-BB-CC-DD-EE-FF ', 'AABBCCDDEEFF'],
'with colon, with linebreak, with space' => ["\n : AA-BB-CC-DD-EE-FF\n", 'aabbccddeeff'],
'without colon, with linebreak, with space' => ["\n AA-BB-CC-DD-EE-FF \n", 'aabbccddeeff'],
'without colon, with linebreak, without space' => ["\nAA-BB-CC-DD-EE-FF\n", 'aabbccddeeff'],
'without colon, without linebreak, with space' => [' AA-BB-CC-DD-EE-FF ', 'aabbccddeeff'],
/* Other accepted variations */
'Actual mac - 1' => ["\n52:54:00:14:91:69\n", '525400149169'],
'Actual mac - 2' => ["\n00:16:3e:a9:73:f0\n", '00163ea973f0'],
'FF:FF:FF:FF:FF:FF' => ["\nFF:FF:FF:FF:FF:FF\n", 'FFFFFFFFFFFF'],
'FF:FF:FF:FF:FF:FF' => ["\nFF:FF:FF:FF:FF:FF\n", 'ffffffffffff'],
/* Incorrect variations that are also accepted */
'Local host' => ["\n00:00:00:00:00:00\n", '000000000000'],
'Too long -- extra character' => ["\nABC-01-23-45-67-89\n", 'BC0123456789'],
'Too long -- extra tuple' => ["\n01-AA-BB-CC-DD-EE-FF\n", '01AABBCCDDEE'],
'Too long -- extra character' => ["\nABC-01-23-45-67-89\n", 'bc0123456789'],
'Too long -- extra tuple' => ["\n01-AA-BB-CC-DD-EE-FF\n", '01aabbccddee'],
];
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testInternalNodeValueIsArray(): void
{
$provider = Mockery::mock(SystemNodeProvider::class);
$provider->shouldAllowMockingProtectedMethods();
$provider->shouldReceive('getSysfs')->andReturn(['foo:bar']);
$provider->shouldReceive('getNode')->passthru();
$this->assertSame('foobar', $provider->getNode());
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testInternalNodeValueIsArrayWithNoElements(): void
{
$provider = Mockery::mock(SystemNodeProvider::class);
$provider->shouldAllowMockingProtectedMethods();
$provider->shouldReceive('getSysfs')->andReturn([]);
$provider->shouldReceive('getNode')->passthru();
$this->assertFalse($provider->getNode());
}
}