From 4151e547618c5ef5ccea1877736d6b2cc1396e3b Mon Sep 17 00:00:00 2001 From: Zan Baldwin Date: Tue, 16 Jun 2026 14:27:47 +0200 Subject: [PATCH 1/3] =?UTF-8?q?feature(contracts):=20=E2=9C=A8=20add=20str?= =?UTF-8?q?ict=20parsing=20factory=20interface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 2 + docs/03-overview.md | 82 ++++++++- src/Contracts/FactoryInterface.php | 64 +++++++ src/Exception/InvalidBinaryException.php | 7 + src/Version/IPv4.php | 70 ++++++++ src/Version/IPv6.php | 67 ++++++++ src/Version/Multi.php | 73 ++++++++ src/Version/Version4Interface.php | 3 +- src/Version/Version6Interface.php | 3 +- tests/Version/IPv4Test.php | 208 +++++++++++++++++++++++ tests/Version/IPv6Test.php | 201 ++++++++++++++++++++++ tests/Version/MultiTest.php | 195 +++++++++++++++++++++ 12 files changed, 971 insertions(+), 4 deletions(-) create mode 100644 src/Contracts/FactoryInterface.php create mode 100644 src/Exception/InvalidBinaryException.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 524b7b0..fd3864d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## `6.x` +- Add strict parsing methods to the new `Contracts\FactoryInterface` interface: + try/from protocol, binary, hex. - Introduce `@experimental` capability interfaces under `Darsyn\IP\Contracts\`. Their shape may change before `7.0`, but remains backwards compatible for `6.x` - Allow overriding the global formatter per call by passing a diff --git a/docs/03-overview.md b/docs/03-overview.md index 71fc3dd..b2f31ea 100644 --- a/docs/03-overview.md +++ b/docs/03-overview.md @@ -1,5 +1,8 @@ # Overview +> The `::factory()` static method is now deprecated. See the strict-parsing +> named constructors below. + IP addresses get automatically validated on creation through the static factory method; if the IP address supplied is invalid an `InvalidIpAddressException` will be thrown. @@ -55,7 +58,7 @@ try { Each class has methods for determining the version: - `$ip->getVersion()` returns the IP address version (either `int(4)` or - `int(6)`). + `int(6)`). - `$ip->isVersion($version)` returns a boolean value on whether the `$ip` object is the version specified in `$version` (which must be either `int(4)` or `int(6)`). @@ -91,6 +94,81 @@ try { > processes the constructor does not perform any input validation. Because of > this the constructor method has been kept private. +## Parsing Strictly + +`factory()` is deliberately permissive: it accepts _either_ protocol notation +_or_ a raw binary sequence (4 bytes for `IPv4`, 16 bytes for `IPv6`/`Multi`). + +> Any 4-character string is silently accepted as an address: `IPv4::factory('abcd')` +> returns the IP `97.98.99.100`. + +This is not an issue for version 4 addresses (valid IPv4 protcol strings range 7 +to 15 characters), but can cause problems when a 16-byte binary sequence happens +to be parsed as a valid IPv6 address. + +> The version 6 protocol address `2001:db8::70:734` could also be interpreted as +> `TODO` when intended as a binary sequence. + +When the input is user-supplied (a query string, a header, a form field), a +string that was never meant to be an IP address slips through validation. This +matters most for SSRF defences, where untrusted text must never be coerced into +an address. + +For these cases the version classes implement `Darsyn\IP\Contracts\FactoryInterface`, +which separates the two concerns into strict, single-purpose entry points: + +- `fromProtocol()` parses protocol notation **only**; a raw binary sequence is + rejected with an `InvalidIpAddressException`. +- `fromBinary()` accepts a raw binary sequence **only**, of exactly the right + length, throwing an `InvalidBinaryException` otherwise. +- `fromHex()` accepts a hexadecimal string (no `0x` prefix, case-insensitive). + +```php + **Note:** `InvalidBinaryException` extends `InvalidIpAddressException`, so +> existing `catch` blocks keep working unchanged. + ## Return Formats Once an IP object has been initialised, the IP address value can be returned in @@ -173,7 +251,7 @@ $ip->getProtocolAppropriateAddress(); // string("127.0.0.1") `getBinary()` returns the 16 byte (4 bytes if using `IPv4`) binary string of the IP address. This will most likely contain non-printable characters, so is not -appropriate for displaying. +appropriate for displaying. ```php pton($ip); + } catch (Exception\IpException $e) { + throw new Exception\InvalidIpAddressException($ip, $e); + } + // pton() returns a raw 4/16-byte string verbatim as a permissive + // fallback (the behaviour the old factory relies on); strict protocol + // parsing must reject anything not actually parsed from protocol notation. + // TODO: This _really_ needs to change before the next major version bump. + if ($binary === $ip) { + throw new Exception\InvalidIpAddressException($ip); + } + if (4 !== MbString::getLength($binary)) { + throw new Exception\WrongVersionException(4, 6, $ip); + } + return new static($binary); + } + + public static function tryFromProtocol(string $ip) + { + try { + return static::fromProtocol($ip); + } catch (Exception\InvalidIpAddressException $e) { + return null; + } + } + + public static function fromBinary(string $binary) + { + if (4 !== MbString::getLength($binary)) { + throw new Exception\InvalidBinaryException($binary); + } + return new static($binary); + } + + public static function tryFromBinary(string $binary) + { + try { + return static::fromBinary($binary); + } catch (Exception\InvalidIpAddressException $e) { + return null; + } + } + + public static function fromHex(string $hex) + { + try { + $binary = Binary::fromHex($hex); + } catch (\InvalidArgumentException $e) { + throw new Exception\InvalidIpAddressException($hex, $e); + } + return static::fromBinary($binary); + } + + public static function tryFromHex(string $hex) + { + try { + return static::fromHex($hex); + } catch (Exception\InvalidIpAddressException $e) { + return null; + } + } + + public static function isValid(string $ip): bool + { + return null !== static::tryFromProtocol($ip); + } + public function getDotAddress(/* ?ProtocolFormatterInterface $formatter = null */): string { try { diff --git a/src/Version/IPv6.php b/src/Version/IPv6.php index 4e0d6c5..c675bb6 100644 --- a/src/Version/IPv6.php +++ b/src/Version/IPv6.php @@ -46,6 +46,73 @@ public static function factory(string $ip) return new static($binary); } + public static function fromProtocol(string $ip) + { + try { + $binary = self::getProtocolFormatter()->pton($ip); + } catch (Exception\IpException $e) { + throw new Exception\InvalidIpAddressException($ip, $e); + } + // (see rant in IPv4::fromProtocol). + if ($binary === $ip) { + throw new Exception\InvalidIpAddressException($ip); + } + if (16 !== MbString::getLength($binary)) { + throw new Exception\WrongVersionException(6, 4, $ip); + } + return new static($binary); + } + + public static function tryFromProtocol(string $ip) + { + try { + return static::fromProtocol($ip); + } catch (Exception\InvalidIpAddressException $e) { + return null; + } + } + + public static function fromBinary(string $binary) + { + if (16 !== MbString::getLength($binary)) { + throw new Exception\InvalidBinaryException($binary); + } + return new static($binary); + } + + public static function tryFromBinary(string $binary) + { + try { + return static::fromBinary($binary); + } catch (Exception\InvalidIpAddressException $e) { + return null; + } + } + + public static function fromHex(string $hex) + { + try { + $binary = Binary::fromHex($hex); + } catch (\InvalidArgumentException $e) { + throw new Exception\InvalidIpAddressException($hex, $e); + } + return static::fromBinary($binary); + } + + public static function tryFromHex(string $hex) + { + try { + return static::fromHex($hex); + } catch (Exception\InvalidIpAddressException $e) { + return null; + } + } + + public static function isValid(string $ip): bool + { + return null !== static::tryFromProtocol($ip); + } + /** * @throws \Darsyn\IP\Exception\InvalidIpAddressException * @throws \Darsyn\IP\Exception\WrongVersionException diff --git a/src/Version/Multi.php b/src/Version/Multi.php index 9a48700..f7b1117 100644 --- a/src/Version/Multi.php +++ b/src/Version/Multi.php @@ -8,6 +8,7 @@ use Darsyn\IP\IpInterface; use Darsyn\IP\Strategy\EmbeddingStrategyInterface; use Darsyn\IP\Strategy\Mapped as MappedEmbeddingStrategy; +use Darsyn\IP\Util\Binary; use Darsyn\IP\Util\MbString; /** @@ -73,6 +74,78 @@ public static function factory(string $ip, ?EmbeddingStrategyInterface $strategy return new static($binary, $strategy); } + public static function fromProtocol(string $ip, ?EmbeddingStrategyInterface $strategy = null) + { + $strategy = $strategy ?: self::getDefaultEmbeddingStrategy(); + try { + $binary = self::getProtocolFormatter()->pton($ip); + } catch (Exception\IpException $e) { + throw new Exception\InvalidIpAddressException($ip, $e); + } + // (see rant in IPv4::fromProtocol). + if ($binary === $ip) { + throw new Exception\InvalidIpAddressException($ip); + } + if (4 === MbString::getLength($binary)) { + $binary = $strategy->pack($binary); + } + return new static($binary, $strategy); + } + + public static function tryFromProtocol(string $ip, ?EmbeddingStrategyInterface $strategy = null) + { + try { + return static::fromProtocol($ip, $strategy); + } catch (Exception\InvalidIpAddressException $e) { + return null; + } + } + + public static function fromBinary(string $binary, ?EmbeddingStrategyInterface $strategy = null) + { + $strategy = $strategy ?: self::getDefaultEmbeddingStrategy(); + $length = MbString::getLength($binary); + if (4 === $length) { + $binary = $strategy->pack($binary); + } elseif (16 !== $length) { + throw new Exception\InvalidBinaryException($binary); + } + return new static($binary, $strategy); + } + + public static function tryFromBinary(string $binary, ?EmbeddingStrategyInterface $strategy = null) + { + try { + return static::fromBinary($binary, $strategy); + } catch (Exception\InvalidIpAddressException $e) { + return null; + } + } + + public static function fromHex(string $hex, ?EmbeddingStrategyInterface $strategy = null) + { + try { + $binary = Binary::fromHex($hex); + } catch (\InvalidArgumentException $e) { + throw new Exception\InvalidIpAddressException($hex, $e); + } + return static::fromBinary($binary, $strategy); + } + + public static function tryFromHex(string $hex, ?EmbeddingStrategyInterface $strategy = null) + { + try { + return static::fromHex($hex, $strategy); + } catch (Exception\InvalidIpAddressException $e) { + return null; + } + } + + public static function isValid(string $ip, ?EmbeddingStrategyInterface $strategy = null): bool + { + return null !== static::tryFromProtocol($ip, $strategy); + } + protected function __construct(string $ip, ?EmbeddingStrategyInterface $strategy = null) { // Fallback to default in case this instance was created from static in diff --git a/src/Version/Version4Interface.php b/src/Version/Version4Interface.php index 06d1d67..8e569d5 100644 --- a/src/Version/Version4Interface.php +++ b/src/Version/Version4Interface.php @@ -5,7 +5,8 @@ namespace Darsyn\IP\Version; use Darsyn\IP\Contracts\Classification4Interface; +use Darsyn\IP\Contracts\FactoryInterface; use Darsyn\IP\Contracts\Output4Interface; use Darsyn\IP\IpInterface; -interface Version4Interface extends IpInterface, Classification4Interface, Output4Interface {} +interface Version4Interface extends IpInterface, Classification4Interface, Output4Interface, FactoryInterface {} diff --git a/src/Version/Version6Interface.php b/src/Version/Version6Interface.php index 872c76b..ee1aafe 100644 --- a/src/Version/Version6Interface.php +++ b/src/Version/Version6Interface.php @@ -5,7 +5,8 @@ namespace Darsyn\IP\Version; use Darsyn\IP\Contracts\Classification6Interface; +use Darsyn\IP\Contracts\FactoryInterface; use Darsyn\IP\Contracts\Output6Interface; use Darsyn\IP\IpInterface; -interface Version6Interface extends IpInterface, Classification6Interface, Output6Interface {} +interface Version6Interface extends IpInterface, Classification6Interface, Output6Interface, FactoryInterface {} diff --git a/tests/Version/IPv4Test.php b/tests/Version/IPv4Test.php index 5e333bb..c08a476 100644 --- a/tests/Version/IPv4Test.php +++ b/tests/Version/IPv4Test.php @@ -8,9 +8,11 @@ use Darsyn\IP\Contracts\Classification4Interface; use Darsyn\IP\Contracts\ClassificationInterface; use Darsyn\IP\Contracts\ComparisonInterface; +use Darsyn\IP\Contracts\FactoryInterface; use Darsyn\IP\Contracts\Output4Interface; use Darsyn\IP\Contracts\OutputInterface; use Darsyn\IP\Contracts\VersionIdentityInterface; +use Darsyn\IP\Exception\InvalidBinaryException; use Darsyn\IP\Exception\InvalidCidrException; use Darsyn\IP\Exception\InvalidIpAddressException; use Darsyn\IP\Exception\WrongVersionException; @@ -46,6 +48,7 @@ public function testImplementsCapabilityInterfaces(): void $this->assertInstanceOf(Output4Interface::class, $ip); $this->assertInstanceOf(ClassificationInterface::class, $ip); $this->assertInstanceOf(Classification4Interface::class, $ip); + $this->assertInstanceOf(FactoryInterface::class, $ip); } /** @@ -547,4 +550,209 @@ public function testInvalidScalarPerCallFormatterMentionsTypeInDeprecation(): vo $this->assertNotNull($message); $this->assertStringContainsString('integer', (string) $message); } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidProtocolIpAddresses() + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidProtocolIpAddresses')] + public function testFromProtocolAcceptsProtocolNotation(string $value, string $expectedHex, string $expectedDot): void + { + $ip = IP::fromProtocol($value); + $this->assertInstanceOf(Version4Interface::class, $ip); + $actualHex = \unpack('H*hex', $ip->getBinary()); + $this->assertSame($expectedHex, \is_array($actualHex) ? $actualHex['hex'] : null); + } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidBinarySequences() + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidBinarySequences')] + public function testFromProtocolRejectsRawBinarySequences(string $value, string $expectedHex, string $expectedDot): void + { + $this->expectException(InvalidIpAddressException::class); + IP::fromProtocol($value); + } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getInvalidIpAddresses() + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getInvalidIpAddresses')] + public function testFromProtocolThrowsOnInvalidAddresses(string $value): void + { + $this->expectException(InvalidIpAddressException::class); + IP::fromProtocol($value); + } + + /** @test */ + #[PHPUnit\Test] + public function testFromProtocolRejectsWhatFactoryAcceptsAsBinary(): void + { + // factory() permissively turns a raw 4-byte string into an address ... + $this->assertInstanceOf(Version4Interface::class, IP::factory('abcd')); + // ... but strict protocol parsing must not (the SSRF footgun this closes). + $this->expectException(InvalidIpAddressException::class); + IP::fromProtocol('abcd'); + } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidBinarySequences() + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidBinarySequences')] + public function testFromBinaryAcceptsRawBinarySequences(string $value, string $expectedHex, string $expectedDot): void + { + $ip = IP::fromBinary($value); + $this->assertInstanceOf(Version4Interface::class, $ip); + $this->assertSame($value, $ip->getBinary()); + } + + /** @test */ + #[PHPUnit\Test] + public function testFromBinaryThrowsOnWrongLength(): void + { + $this->expectException(InvalidBinaryException::class); + try { + IP::fromBinary('abc'); + } catch (InvalidBinaryException $e) { + $this->assertSame('abc', $e->getSuppliedIp()); + throw $e; + } + $this->fail(); + } + + /** @test */ + #[PHPUnit\Test] + public function testInvalidBinaryExceptionIsCatchableAsInvalidIpAddress(): void + { + try { + IP::fromBinary('abc'); + } catch (InvalidIpAddressException $e) { + $this->assertInstanceOf(InvalidBinaryException::class, $e); + return; + } + $this->fail(); + } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidBinarySequences() + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidBinarySequences')] + public function testFromHexRoundTripsWithBinary(string $value, string $expectedHex, string $expectedDot): void + { + $ip = IP::fromHex($expectedHex); + $this->assertSame($value, $ip->getBinary()); + $this->assertSame($expectedHex, Binary::toHex($ip->getBinary())); + } + + /** @test */ + #[PHPUnit\Test] + public function testFromHexIsCaseInsensitive(): void + { + $this->assertSame(IP::fromHex('7f000001')->getBinary(), IP::fromHex('7F000001')->getBinary()); + } + + /** @test */ + #[PHPUnit\Test] + public function testFromHexThrowsOnNonHexadecimal(): void + { + $this->expectException(InvalidIpAddressException::class); + IP::fromHex('zzzzzzzz'); + } + + /** @test */ + #[PHPUnit\Test] + public function testFromHexThrowsOnWrongWidth(): void + { + $this->expectException(InvalidBinaryException::class); + IP::fromHex('7f0000'); + } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidProtocolIpAddresses() + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidProtocolIpAddresses')] + public function testTryFromProtocolReturnsInstanceForValid(string $value, string $expectedHex, string $expectedDot): void + { + $this->assertInstanceOf(Version4Interface::class, IP::tryFromProtocol($value)); + } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidBinarySequences() + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidBinarySequences')] + public function testTryFromProtocolReturnsNullForRawBinary(string $value, string $expectedHex, string $expectedDot): void + { + $this->assertNull(IP::tryFromProtocol($value)); + } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidBinarySequences() + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidBinarySequences')] + public function testTryFromBinaryReturnsInstanceForValid(string $value, string $expectedHex, string $expectedDot): void + { + $this->assertInstanceOf(Version4Interface::class, IP::tryFromBinary($value)); + } + + /** @test */ + #[PHPUnit\Test] + public function testTryFromBinaryReturnsNullForWrongLength(): void + { + $this->assertNull(IP::tryFromBinary('abc')); + } + + /** @test */ + #[PHPUnit\Test] + public function testTryFromHexReturnsNullForInvalid(): void + { + $this->assertNull(IP::tryFromHex('zzzz')); + } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidProtocolIpAddresses() + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidProtocolIpAddresses')] + public function testIsValidReturnsTrueForProtocolNotation(string $value, string $expectedHex, string $expectedDot): void + { + $this->assertTrue(IP::isValid($value)); + } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidBinarySequences() + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidBinarySequences')] + public function testIsValidReturnsFalseForRawBinary(string $value, string $expectedHex, string $expectedDot): void + { + $this->assertFalse(IP::isValid($value)); + } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getInvalidIpAddresses() + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getInvalidIpAddresses')] + public function testIsValidReturnsFalseForInvalid(string $value): void + { + $this->assertFalse(IP::isValid($value)); + } } diff --git a/tests/Version/IPv6Test.php b/tests/Version/IPv6Test.php index 2c8481c..bfe278d 100644 --- a/tests/Version/IPv6Test.php +++ b/tests/Version/IPv6Test.php @@ -8,9 +8,11 @@ use Darsyn\IP\Contracts\Classification6Interface; use Darsyn\IP\Contracts\ClassificationInterface; use Darsyn\IP\Contracts\ComparisonInterface; +use Darsyn\IP\Contracts\FactoryInterface; use Darsyn\IP\Contracts\Output6Interface; use Darsyn\IP\Contracts\OutputInterface; use Darsyn\IP\Contracts\VersionIdentityInterface; +use Darsyn\IP\Exception\InvalidBinaryException; use Darsyn\IP\Exception\InvalidCidrException; use Darsyn\IP\Exception\InvalidIpAddressException; use Darsyn\IP\Exception\WrongVersionException; @@ -50,6 +52,7 @@ public function testImplementsCapabilityInterfaces(): void $this->assertInstanceOf(Output6Interface::class, $ip); $this->assertInstanceOf(ClassificationInterface::class, $ip); $this->assertInstanceOf(Classification6Interface::class, $ip); + $this->assertInstanceOf(FactoryInterface::class, $ip); } /** @@ -579,4 +582,202 @@ public function testInvalidPerCallFormatterTriggersDeprecationAndFallsBack(): vo $this->assertNotNull($message); $this->assertSame('2001:db8::a60:8a2e:370:7334', $result); } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getValidProtocolIpAddresses() + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getValidProtocolIpAddresses')] + public function testFromProtocolAcceptsProtocolNotation(string $value, string $hex, string $expanded, string $compacted): void + { + $ip = IP::fromProtocol($value); + $this->assertInstanceOf(Version6Interface::class, $ip); + $this->assertSame($hex, Binary::toHex($ip->getBinary())); + } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getValidBinarySequences() + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getValidBinarySequences')] + public function testFromProtocolRejectsRawBinarySequences(string $value, string $hex, string $expanded, string $compacted): void + { + $this->expectException(InvalidIpAddressException::class); + IP::fromProtocol($value); + } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getInvalidIpAddresses() + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getInvalidIpAddresses')] + public function testFromProtocolThrowsOnInvalidAddresses(string $value): void + { + $this->expectException(InvalidIpAddressException::class); + IP::fromProtocol($value); + } + + /** @test */ + #[PHPUnit\Test] + public function testFromProtocolRejectsWhatFactoryAcceptsAsBinary(): void + { + $this->assertInstanceOf(Version6Interface::class, IP::factory('1234567890123456')); + $this->expectException(InvalidIpAddressException::class); + IP::fromProtocol('1234567890123456'); + } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getValidBinarySequences() + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getValidBinarySequences')] + public function testFromBinaryAcceptsRawBinarySequences(string $value, string $hex, string $expanded, string $compacted): void + { + $ip = IP::fromBinary($value); + $this->assertInstanceOf(Version6Interface::class, $ip); + $this->assertSame($value, $ip->getBinary()); + } + + /** @test */ + #[PHPUnit\Test] + public function testFromBinaryThrowsOnWrongLength(): void + { + $this->expectException(InvalidBinaryException::class); + try { + IP::fromBinary('abc'); + } catch (InvalidBinaryException $e) { + $this->assertSame('abc', $e->getSuppliedIp()); + throw $e; + } + $this->fail(); + } + + /** @test */ + #[PHPUnit\Test] + public function testFromBinaryThrowsOnFourByteSequence(): void + { + $this->expectException(InvalidBinaryException::class); + IP::fromBinary('abcd'); + } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getValidBinarySequences() + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getValidBinarySequences')] + public function testFromHexRoundTripsWithBinary(string $value, string $hex, string $expanded, string $compacted): void + { + $ip = IP::fromHex($hex); + $this->assertSame($value, $ip->getBinary()); + $this->assertSame($hex, Binary::toHex($ip->getBinary())); + } + + /** @test */ + #[PHPUnit\Test] + public function testFromHexIsCaseInsensitive(): void + { + $lower = '00000000000000000000000000000001'; + $this->assertSame(IP::fromHex($lower)->getBinary(), IP::fromHex(\strtoupper($lower))->getBinary()); + } + + /** @test */ + #[PHPUnit\Test] + public function testFromHexThrowsOnNonHexadecimal(): void + { + $this->expectException(InvalidIpAddressException::class); + IP::fromHex('zz000000000000000000000000000000'); + } + + /** @test */ + #[PHPUnit\Test] + public function testFromHexThrowsOnWrongWidth(): void + { + $this->expectException(InvalidBinaryException::class); + IP::fromHex('0000000000000001'); + } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getValidProtocolIpAddresses() + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getValidProtocolIpAddresses')] + public function testTryFromProtocolReturnsInstanceForValid(string $value, string $hex, string $expanded, string $compacted): void + { + $this->assertInstanceOf(Version6Interface::class, IP::tryFromProtocol($value)); + } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getValidBinarySequences() + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getValidBinarySequences')] + public function testTryFromProtocolReturnsNullForRawBinary(string $value, string $hex, string $expanded, string $compacted): void + { + $this->assertNull(IP::tryFromProtocol($value)); + } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getValidBinarySequences() + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getValidBinarySequences')] + public function testTryFromBinaryReturnsInstanceForValid(string $value, string $hex, string $expanded, string $compacted): void + { + $this->assertInstanceOf(Version6Interface::class, IP::tryFromBinary($value)); + } + + /** @test */ + #[PHPUnit\Test] + public function testTryFromBinaryReturnsNullForWrongLength(): void + { + $this->assertNull(IP::tryFromBinary('abc')); + } + + /** @test */ + #[PHPUnit\Test] + public function testTryFromHexReturnsNullForInvalid(): void + { + $this->assertNull(IP::tryFromHex('zzzz')); + } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getValidProtocolIpAddresses() + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getValidProtocolIpAddresses')] + public function testIsValidReturnsTrueForProtocolNotation(string $value, string $hex, string $expanded, string $compacted): void + { + $this->assertTrue(IP::isValid($value)); + } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getValidBinarySequences() + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getValidBinarySequences')] + public function testIsValidReturnsFalseForRawBinary(string $value, string $hex, string $expanded, string $compacted): void + { + $this->assertFalse(IP::isValid($value)); + } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getInvalidIpAddresses() + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getInvalidIpAddresses')] + public function testIsValidReturnsFalseForInvalid(string $value): void + { + $this->assertFalse(IP::isValid($value)); + } } diff --git a/tests/Version/MultiTest.php b/tests/Version/MultiTest.php index ea8f0d1..dddb35f 100644 --- a/tests/Version/MultiTest.php +++ b/tests/Version/MultiTest.php @@ -9,10 +9,12 @@ use Darsyn\IP\Contracts\Classification6Interface; use Darsyn\IP\Contracts\ClassificationInterface; use Darsyn\IP\Contracts\ComparisonInterface; +use Darsyn\IP\Contracts\FactoryInterface; use Darsyn\IP\Contracts\Output4Interface; use Darsyn\IP\Contracts\Output6Interface; use Darsyn\IP\Contracts\OutputInterface; use Darsyn\IP\Contracts\VersionIdentityInterface; +use Darsyn\IP\Exception\InvalidBinaryException; use Darsyn\IP\Exception\InvalidIpAddressException; use Darsyn\IP\Exception\WrongVersionException; use Darsyn\IP\Formatter\ConsistentFormatter; @@ -21,6 +23,7 @@ use Darsyn\IP\Tests\DataProvider\Multi as MultiDataProvider; use Darsyn\IP\Tests\Stub\StubFormatter; use Darsyn\IP\Tests\TestCase; +use Darsyn\IP\Util\Binary; use Darsyn\IP\Version\IPv4; use Darsyn\IP\Version\IPv6; use Darsyn\IP\Version\Multi as IP; @@ -59,6 +62,7 @@ public function testImplementsCapabilityInterfaces(): void $this->assertInstanceOf(ClassificationInterface::class, $ip); $this->assertInstanceOf(Classification4Interface::class, $ip); $this->assertInstanceOf(Classification6Interface::class, $ip); + $this->assertInstanceOf(FactoryInterface::class, $ip); } /** @@ -633,4 +637,195 @@ public function testInvalidPerCallFormatterTriggersDeprecationForDotAddressOnNon $this->assertNotNull($message); $this->assertInstanceOf(WrongVersionException::class, $thrown); } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\Multi::getValidProtocolIpAddresses() + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getValidProtocolIpAddresses')] + public function testFromProtocolAcceptsProtocolNotation(string $value, string $hex, string $expanded, string $compacted, ?string $dot): void + { + $ip = IP::fromProtocol($value); + $this->assertInstanceOf(MultiVersionInterface::class, $ip); + $this->assertSame($hex, Binary::toHex($ip->getBinary())); + } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\Multi::getValidBinarySequences() + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getValidBinarySequences')] + public function testFromProtocolRejectsRawBinarySequences(string $value, string $hex, string $expanded, string $compacted, ?string $dot): void + { + $this->expectException(InvalidIpAddressException::class); + IP::fromProtocol($value); + } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\Multi::getInvalidIpAddresses() + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getInvalidIpAddresses')] + public function testFromProtocolThrowsOnInvalidAddresses(string $value): void + { + $this->expectException(InvalidIpAddressException::class); + IP::fromProtocol($value); + } + + /** @test */ + #[PHPUnit\Test] + public function testFromProtocolRejectsWhatFactoryAcceptsAsBinary(): void + { + $this->assertInstanceOf(MultiVersionInterface::class, IP::factory('1234567890123456')); + $this->expectException(InvalidIpAddressException::class); + IP::fromProtocol('1234567890123456'); + } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\Multi::getValidBinarySequences() + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getValidBinarySequences')] + public function testFromBinaryAcceptsRawBinarySequences(string $value, string $hex, string $expanded, string $compacted, ?string $dot): void + { + $ip = IP::fromBinary($value); + $this->assertInstanceOf(MultiVersionInterface::class, $ip); + $this->assertSame($value, $ip->getBinary()); + } + + /** @test */ + #[PHPUnit\Test] + public function testFromBinaryPacksFourByteSequenceWithDefaultStrategy(): void + { + $ip = IP::fromBinary(Binary::fromHex('0c22384e')); + $this->assertInstanceOf(MultiVersionInterface::class, $ip); + $this->assertTrue($ip->isVersion4()); + $this->assertSame('12.34.56.78', $ip->getDotAddress()); + } + + /** @test */ + #[PHPUnit\Test] + public function testFromBinaryThrowsOnWrongLength(): void + { + $this->expectException(InvalidBinaryException::class); + try { + IP::fromBinary('abcde'); + } catch (InvalidBinaryException $e) { + $this->assertSame('abcde', $e->getSuppliedIp()); + throw $e; + } + $this->fail(); + } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\Multi::getEmbeddingStrategyIpAddresses() + * @param class-string $strategyClass + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getEmbeddingStrategyIpAddresses')] + public function testFromProtocolUsesExplicitStrategy(string $strategyClass, string $expandedAddress, string $v4address): void + { + $ip = IP::fromProtocol($v4address, new $strategyClass()); + $this->assertSame($expandedAddress, $ip->getExpandedAddress()); + } + + /** @test */ + #[PHPUnit\Test] + public function testFromBinaryUsesExplicitStrategy(): void + { + $ip = IP::fromBinary(Binary::fromHex('0c22384e'), new Strategy\Derived()); + $this->assertSame('2002:0c22:384e:0000:0000:0000:0000:0000', $ip->getExpandedAddress()); + } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\Multi::getValidBinarySequences() + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getValidBinarySequences')] + public function testFromHexRoundTripsWithBinary(string $value, string $hex, string $expanded, string $compacted, ?string $dot): void + { + $ip = IP::fromHex($hex); + $this->assertSame($value, $ip->getBinary()); + $this->assertSame($hex, Binary::toHex($ip->getBinary())); + } + + /** @test */ + #[PHPUnit\Test] + public function testFromHexThrowsOnWrongWidth(): void + { + $this->expectException(InvalidBinaryException::class); + IP::fromHex('0c22384e0c22'); + } + + /** @test */ + #[PHPUnit\Test] + public function testFromHexThrowsOnNonHexadecimal(): void + { + $this->expectException(InvalidIpAddressException::class); + IP::fromHex('zz000000000000000000000000000000'); + } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\Multi::getValidProtocolIpAddresses() + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getValidProtocolIpAddresses')] + public function testTryFromProtocolReturnsInstanceForValid(string $value, string $hex, string $expanded, string $compacted, ?string $dot): void + { + $this->assertInstanceOf(MultiVersionInterface::class, IP::tryFromProtocol($value)); + } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\Multi::getValidBinarySequences() + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getValidBinarySequences')] + public function testTryFromProtocolReturnsNullForRawBinary(string $value, string $hex, string $expanded, string $compacted, ?string $dot): void + { + $this->assertNull(IP::tryFromProtocol($value)); + } + + /** @test */ + #[PHPUnit\Test] + public function testTryFromBinaryReturnsNullForWrongLength(): void + { + $this->assertNull(IP::tryFromBinary('abcde')); + } + + /** @test */ + #[PHPUnit\Test] + public function testTryFromHexReturnsNullForInvalid(): void + { + $this->assertNull(IP::tryFromHex('zzzz')); + } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\Multi::getValidProtocolIpAddresses() + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getValidProtocolIpAddresses')] + public function testIsValidReturnsTrueForProtocolNotation(string $value, string $hex, string $expanded, string $compacted, ?string $dot): void + { + $this->assertTrue(IP::isValid($value)); + } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\Multi::getInvalidIpAddresses() + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getInvalidIpAddresses')] + public function testIsValidReturnsFalseForInvalid(string $value): void + { + $this->assertFalse(IP::isValid($value)); + } } From 1334bb065dca06e42bf49b9e9996a835dc3505df Mon Sep 17 00:00:00 2001 From: Zan Baldwin Date: Tue, 16 Jun 2026 17:51:26 +0200 Subject: [PATCH 2/3] =?UTF-8?q?refactor(factory):=20=F0=9F=94=A8=20depreca?= =?UTF-8?q?te=20factory=20method=20for=20strict=20named=20constructors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 2 + docs/03-overview.md | 44 +++------- src/IpInterface.php | 1 + src/Strategy/Nat64.php | 4 +- src/Version/IPv4.php | 1 + src/Version/IPv6.php | 7 +- src/Version/Multi.php | 1 + tests/DataProvider/Multi.php | 41 +++++++-- tests/Strategy/Nat64Test.php | 20 ++--- tests/Version/IPv4Test.php | 145 +++++++++++++++++--------------- tests/Version/IPv6Test.php | 159 +++++++++++++++++++++-------------- tests/Version/MultiTest.php | 138 ++++++++++++++++-------------- 12 files changed, 315 insertions(+), 248 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd3864d..caaa23f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ - Add strict parsing methods to the new `Contracts\FactoryInterface` interface: try/from protocol, binary, hex. +- Deprecate `factory()` in favour of the strict named constructors + `fromProtocol()`/`fromBinary()`. - Introduce `@experimental` capability interfaces under `Darsyn\IP\Contracts\`. Their shape may change before `7.0`, but remains backwards compatible for `6.x` - Allow overriding the global formatter per call by passing a diff --git a/docs/03-overview.md b/docs/03-overview.md index b2f31ea..b38d758 100644 --- a/docs/03-overview.md +++ b/docs/03-overview.md @@ -73,9 +73,11 @@ Each class has methods for determining the version: ## Instantiation -All classes are instantiated using the `factory()` static method. This method -validates the input and converts it into binary. In the case of the `Multi` -class it also packs any version 4 addresses into a version 6 address. +Many instances are constructed for all [helper](./04-helpers.md) and +[type](./07-types.md) methods. Validating the input every time a new instance +is constructed slows things down considerably, so to speed up internal +processes the constructor does not perform any input validation. Because of +this the constructor method has been kept private. ```php Many instances are constructed for all [helper](./04-helpers.md) and -> [type](./07-types.md) methods. Validating the input every time a new instance -> is constructed slows things down considerably, so to speed up internal -> processes the constructor does not perform any input validation. Because of -> this the constructor method has been kept private. +> **Deprecated:** `factory()` is deprecated because it accepts raw binary +> sequences as well as protocol notation. Prefer the strict named constructors: +> `fromProtocol()` for protocol notation, or `fromBinary()` for a raw binary +> sequence. -## Parsing Strictly - -`factory()` is deliberately permissive: it accepts _either_ protocol notation -_or_ a raw binary sequence (4 bytes for `IPv4`, 16 bytes for `IPv6`/`Multi`). - -> Any 4-character string is silently accepted as an address: `IPv4::factory('abcd')` -> returns the IP `97.98.99.100`. - -This is not an issue for version 4 addresses (valid IPv4 protcol strings range 7 -to 15 characters), but can cause problems when a 16-byte binary sequence happens -to be parsed as a valid IPv6 address. - -> The version 6 protocol address `2001:db8::70:734` could also be interpreted as -> `TODO` when intended as a binary sequence. - -When the input is user-supplied (a query string, a header, a form field), a -string that was never meant to be an IP address slips through validation. This -matters most for SSRF defences, where untrusted text must never be coerced into -an address. - -For these cases the version classes implement `Darsyn\IP\Contracts\FactoryInterface`, -which separates the two concerns into strict, single-purpose entry points: +`Darsyn\IP\Contracts\FactoryInterface` provides strict, single-purpose entry points: - `fromProtocol()` parses protocol notation **only**; a raw binary sequence is rejected with an `InvalidIpAddressException`. @@ -128,7 +108,7 @@ which separates the two concerns into strict, single-purpose entry points: use Darsyn\IP\Version\IPv4; use Darsyn\IP\Exception; -IPv4::factory('abcd'); // Accepted: the raw bytes become "97.98.99.100". +IPv4::factory('abcd'); // Accepted: the raw bytes become "97.98.99.100". try { IPv4::fromProtocol('abcd'); diff --git a/src/IpInterface.php b/src/IpInterface.php index cd81c8a..f80fd52 100644 --- a/src/IpInterface.php +++ b/src/IpInterface.php @@ -16,6 +16,7 @@ interface IpInterface extends ArithmeticInterface, ClassificationInterface, Comp * @throws \Darsyn\IP\Exception\InvalidIpAddressException * @throws \Darsyn\IP\Exception\WrongVersionException * @return static + * @deprecated Use fromProtocol() for protocol notation, or fromBinary() for a raw binary sequence. */ public static function factory(string $ip); diff --git a/src/Strategy/Nat64.php b/src/Strategy/Nat64.php index b626858..91bcc2e 100644 --- a/src/Strategy/Nat64.php +++ b/src/Strategy/Nat64.php @@ -77,8 +77,8 @@ public static function wellKnown(): self } /** - * Named constructor for a Network-Specific Prefix from an operator's own - * unicast space, eg `Nat64::networkSpecific(IPv6::factory('2001:db8:122:344::'), 64)`. + * Named constructor for a Network-Specific Prefix from an operator's own unicast + * space, eg `Nat64::networkSpecific(IPv6::fromProtocol('2001:db8:122:344::'), 64)`. * * @throws \InvalidArgumentException */ diff --git a/src/Version/IPv4.php b/src/Version/IPv4.php index e1c1c37..516ac5a 100644 --- a/src/Version/IPv4.php +++ b/src/Version/IPv4.php @@ -28,6 +28,7 @@ */ class IPv4 extends AbstractIP implements Version4Interface { + /** @deprecated Use fromProtocol() or fromBinary() instead. */ public static function factory(string $ip) { try { diff --git a/src/Version/IPv6.php b/src/Version/IPv6.php index c675bb6..112bf75 100644 --- a/src/Version/IPv6.php +++ b/src/Version/IPv6.php @@ -30,6 +30,7 @@ */ class IPv6 extends AbstractIP implements Version6Interface { + /** @deprecated Use fromProtocol() or fromBinary() instead. */ public static function factory(string $ip) { try { @@ -117,10 +118,14 @@ public static function isValid(string $ip): bool * @throws \Darsyn\IP\Exception\InvalidIpAddressException * @throws \Darsyn\IP\Exception\WrongVersionException * @return static + * TODO: Deprecate method after I've decided on the whole + * canonical/non-canonical packing situation. Will replace with + * IPv6::fromEmbed(Version4Interface, ?EmbeddingStrategyInterface). */ public static function fromEmbedded(string $ip, ?EmbeddingStrategyInterface $strategy = null) { - return new static(Multi::factory($ip, $strategy)->getBinary()); + $multi = Multi::tryFromProtocol($ip, $strategy) ?? Multi::fromBinary($ip, $strategy); + return new static($multi->getBinary()); } public function getExpandedAddress(): string diff --git a/src/Version/Multi.php b/src/Version/Multi.php index f7b1117..b97f633 100644 --- a/src/Version/Multi.php +++ b/src/Version/Multi.php @@ -54,6 +54,7 @@ private static function getDefaultEmbeddingStrategy(): EmbeddingStrategyInterfac return self::$defaultEmbeddingStrategy ?: new MappedEmbeddingStrategy(); } + /** @deprecated Use fromProtocol() or fromBinary() instead. */ public static function factory(string $ip, ?EmbeddingStrategyInterface $strategy = null): self { // We need a strategy to pack version 4 addresses. diff --git a/tests/DataProvider/Multi.php b/tests/DataProvider/Multi.php index 5d32788..04322d7 100644 --- a/tests/DataProvider/Multi.php +++ b/tests/DataProvider/Multi.php @@ -110,6 +110,35 @@ public static function getIpAddressVersions() ); } + /** @return list */ + public static function getValidProtocolIpVersion4Addresses() + { + return \array_values(\array_filter(self::getValidProtocolIpAddresses(), static function (array $row) { + return \is_string($row[4]); + })); + } + + /** @return list */ + public static function getValidProtocolIpVersion6Addresses() + { + return \array_values(\array_filter(self::getValidProtocolIpAddresses(), static function (array $row) { + return !\is_string($row[4]); + })); + } + + /** @return list */ + public static function getProtocolIpAddressVersions() + { + return \array_merge( + \array_map(static function ($row) { + return [$row[0], 4]; + }, self::getValidProtocolIpVersion4Addresses()), + \array_map(static function ($row) { + return [$row[0], 6]; + }, self::getValidProtocolIpVersion6Addresses()) + ); + } + /** @return list */ public static function getValidCidrValues() { @@ -290,7 +319,7 @@ public static function getUniqueLocalIpAddresses() return \array_merge( \array_map(static function ($testData) { return [$testData[0], false, true]; - }, self::getValidIpVersion4Addresses()), + }, self::getValidProtocolIpVersion4Addresses()), \array_map(static function ($testData) { $testData[] = false; return $testData; @@ -304,7 +333,7 @@ public static function getUnicastIpAddresses() return \array_merge( \array_map(static function ($testData) { return [$testData[0], false, true]; - }, self::getValidIpVersion4Addresses()), + }, self::getValidProtocolIpVersion4Addresses()), \array_map(static function ($testData) { $testData[] = false; return $testData; @@ -318,7 +347,7 @@ public static function getUnicastGlobalIpAddresses() return \array_merge( \array_map(static function ($testData) { return [$testData[0], false, true]; - }, self::getValidIpVersion4Addresses()), + }, self::getValidProtocolIpVersion4Addresses()), \array_map(static function ($testData) { $testData[] = false; return $testData; @@ -336,7 +365,7 @@ public static function getIsBroadcastIpAddresses() }, IPv4::getIsBroadcastIpAddresses()), \array_map(static function ($testData) { return [$testData[0], false, true]; - }, self::getValidIpVersion6Addresses()) + }, self::getValidProtocolIpVersion6Addresses()) ); } @@ -350,7 +379,7 @@ public static function getSharedIpAddresses() }, IPv4::getSharedIpAddresses()), \array_map(static function ($testData) { return [$testData[0], false, true]; - }, self::getValidIpVersion6Addresses()) + }, self::getValidProtocolIpVersion6Addresses()) ); } @@ -364,7 +393,7 @@ public static function getFutureReservedIpAddresses() }, IPv4::getFutureReservedIpAddresses()), \array_map(static function ($testData) { return [$testData[0], false, true]; - }, self::getValidIpVersion6Addresses()) + }, self::getValidProtocolIpVersion6Addresses()) ); } diff --git a/tests/Strategy/Nat64Test.php b/tests/Strategy/Nat64Test.php index 36759b7..559f3ae 100644 --- a/tests/Strategy/Nat64Test.php +++ b/tests/Strategy/Nat64Test.php @@ -98,7 +98,7 @@ public function testSequenceCorrectlyPackedIntoIpBinaryFromIpBinary(string $ipv6 #[PHPUnit\DataProviderExternal(Nat64DataProvider::class, 'getNetworkSpecificSequences')] public function testSequencesEmbedExtractAndPackWithNetworkSpecificPrefixes(string $prefixAddress, int $length, string $ipv6, string $ipv4): void { - $strategy = Nat64::networkSpecific(IPv6::factory($prefixAddress), $length); + $strategy = Nat64::networkSpecific(IPv6::fromProtocol($prefixAddress), $length); $this->assertTrue($strategy->isEmbedded($ipv6)); $this->assertSame($ipv4, $strategy->extract($ipv6)); $this->assertSame($ipv6, $strategy->pack($ipv4)); @@ -112,7 +112,7 @@ public function testSequencesEmbedExtractAndPackWithNetworkSpecificPrefixes(stri #[PHPUnit\DataProviderExternal(Nat64DataProvider::class, 'getNonMatchingNetworkSpecificSequences')] public function testIsEmbeddedReturnsFalseForSequencesNotMatchingNetworkSpecificPrefix(string $prefixAddress, int $length, string $ipv6): void { - $this->assertFalse(Nat64::networkSpecific(IPv6::factory($prefixAddress), $length)->isEmbedded($ipv6)); + $this->assertFalse(Nat64::networkSpecific(IPv6::fromProtocol($prefixAddress), $length)->isEmbedded($ipv6)); } /** @@ -123,7 +123,7 @@ public function testIsEmbeddedReturnsFalseForSequencesNotMatchingNetworkSpecific #[PHPUnit\DataProviderExternal(Nat64DataProvider::class, 'getNonCanonicalNetworkSpecificSequences')] public function testNonCanonicalSequencesRoundTripToCanonicalFormWithNetworkSpecificPrefixes(string $prefixAddress, int $length, string $nonCanonical, string $ipv4, string $canonical): void { - $strategy = Nat64::networkSpecific(IPv6::factory($prefixAddress), $length); + $strategy = Nat64::networkSpecific(IPv6::fromProtocol($prefixAddress), $length); $this->assertTrue($strategy->isEmbedded($nonCanonical)); $this->assertSame($ipv4, $strategy->extract($nonCanonical)); $this->assertSame($canonical, $strategy->pack($strategy->extract($nonCanonical))); @@ -137,10 +137,10 @@ public function testNonCanonicalSequencesRoundTripToCanonicalFormWithNetworkSpec #[PHPUnit\DataProviderExternal(Nat64DataProvider::class, 'getValidNetworkSpecificArguments')] public function testNetworkSpecificStrategyCorrectlyConstructed(string $prefixAddress, int $length, string $expectedPrefixHex): void { - $strategy = Nat64::networkSpecific(IPv6::factory($prefixAddress), $length); + $strategy = Nat64::networkSpecific(IPv6::fromProtocol($prefixAddress), $length); $this->assertSame(\pack('H*', $expectedPrefixHex), $strategy->getPrefix()); $this->assertSame($length, $strategy->getPrefixLength()); - $this->assertSame($prefixAddress, IPv6::factory($strategy->getPrefix())->getCompactedAddress()); + $this->assertSame($prefixAddress, IPv6::fromBinary($strategy->getPrefix())->getCompactedAddress()); } /** @@ -152,7 +152,7 @@ public function testNetworkSpecificStrategyCorrectlyConstructed(string $prefixAd public function testExceptionIsThrownForInvalidNetworkSpecificArguments(string $prefixAddress, int $length): void { $this->expectException(\InvalidArgumentException::class); - Nat64::networkSpecific(IPv6::factory($prefixAddress), $length); + Nat64::networkSpecific(IPv6::fromProtocol($prefixAddress), $length); } /** @@ -163,7 +163,7 @@ public function testExceptionIsThrownForInvalidNetworkSpecificArguments(string $ #[PHPUnit\DataProviderExternal(Nat64DataProvider::class, 'getZeroedNetworkSpecificArguments')] public function testBitsSetAfterPrefixLengthAreZeroedForNetworkSpecificPrefixes(string $prefixAddress, int $length, string $expectedPrefixHex): void { - $strategy = Nat64::networkSpecific(IPv6::factory($prefixAddress), $length); + $strategy = Nat64::networkSpecific(IPv6::fromProtocol($prefixAddress), $length); $this->assertSame(\pack('H*', $expectedPrefixHex), $strategy->getPrefix()); $this->assertSame($length, $strategy->getPrefixLength()); } @@ -175,9 +175,9 @@ public function testWellKnownNamedConstructor(): void $wellKnown = Nat64::wellKnown(); $this->assertSame(\pack('H*', Nat64::WELL_KNOWN_PREFIX), $wellKnown->getPrefix()); $this->assertSame(96, $wellKnown->getPrefixLength()); - $this->assertSame('64:ff9b::', IPv6::factory($wellKnown->getPrefix())->getCompactedAddress()); + $this->assertSame('64:ff9b::', IPv6::fromBinary($wellKnown->getPrefix())->getCompactedAddress()); $this->assertSame( - Nat64::networkSpecific(IPv6::factory('64:ff9b::'), 96)->pack(\pack('H*', 'c0000221')), + Nat64::networkSpecific(IPv6::fromProtocol('64:ff9b::'), 96)->pack(\pack('H*', 'c0000221')), $wellKnown->pack(\pack('H*', 'c0000221')) ); } @@ -189,7 +189,7 @@ public function testLocalUseNamedConstructor(): void $localUse = Nat64::localUse(); $this->assertSame(\pack('H*', Nat64::LOCAL_USE_PREFIX), $localUse->getPrefix()); $this->assertSame(48, $localUse->getPrefixLength()); - $this->assertSame('64:ff9b:1::', IPv6::factory($localUse->getPrefix())->getCompactedAddress()); + $this->assertSame('64:ff9b:1::', IPv6::fromBinary($localUse->getPrefix())->getCompactedAddress()); } /** diff --git a/tests/Version/IPv4Test.php b/tests/Version/IPv4Test.php index c08a476..4825e6c 100644 --- a/tests/Version/IPv4Test.php +++ b/tests/Version/IPv4Test.php @@ -40,7 +40,7 @@ public function resetProtocolFormatter(): void #[PHPUnit\Test] public function testImplementsCapabilityInterfaces(): void { - $ip = IP::factory('127.0.0.1'); + $ip = IP::fromProtocol('127.0.0.1'); $this->assertInstanceOf(VersionIdentityInterface::class, $ip); $this->assertInstanceOf(ComparisonInterface::class, $ip); $this->assertInstanceOf(ArithmeticInterface::class, $ip); @@ -53,19 +53,20 @@ public function testImplementsCapabilityInterfaces(): void /** * @test - * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidIpAddresses() + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidProtocolIpAddresses() */ #[PHPUnit\Test] - #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidIpAddresses')] + #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidProtocolIpAddresses')] public function testInstantiationWithValidAddresses(string $value, string $expectedHex, string $expectedDot): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertInstanceOf(IpInterface::class, $ip); $this->assertInstanceOf(Version4Interface::class, $ip); } /** * @test + * @deprecated Retains coverage of the deprecated factory() raw-binary path. * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidBinarySequences() */ #[PHPUnit\Test] @@ -78,6 +79,7 @@ public function testBinarySequenceIsTheSameOnceInstantiated(string $value, strin /** * @test + * @deprecated Retains coverage of the deprecated factory() protocol path. * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidProtocolIpAddresses() */ #[PHPUnit\Test] @@ -91,6 +93,7 @@ public function testProtocolNotationConvertsToCorrectBinarySequence(string $valu /** * @test + * @deprecated Retains coverage of the deprecated factory() validation path. * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getInvalidIpAddresses() */ #[PHPUnit\Test] @@ -110,85 +113,85 @@ public function testExceptionIsThrownOnInstantiationWithInvalidAddresses(string /** * @test - * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidIpAddresses() + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidProtocolIpAddresses() */ #[PHPUnit\Test] - #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidIpAddresses')] + #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidProtocolIpAddresses')] public function testGetBinaryAlwaysReturnsA4ByteString(string $value, string $expectedHex, string $expectedDot): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame(4, \strlen(\bin2hex($ip->getBinary())) / 2); } /** * @test - * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidIpAddresses() + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidProtocolIpAddresses() */ #[PHPUnit\Test] - #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidIpAddresses')] + #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidProtocolIpAddresses')] public function testDotAddressReturnsCorrectString(string $value, string $expectedHex, string $expectedDot): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($expectedDot, $ip->getDotAddress()); } /** * @test - * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidIpAddresses() + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidProtocolIpAddresses() */ #[PHPUnit\Test] - #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidIpAddresses')] + #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidProtocolIpAddresses')] public function testGetVersionAlwaysReturns4(string $value, string $expectedHex, string $expectedDot): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame(4, $ip->getVersion()); } /** * @test - * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidIpAddresses() + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidProtocolIpAddresses() */ #[PHPUnit\Test] - #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidIpAddresses')] + #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidProtocolIpAddresses')] public function testIsVersionOnlyReturnsTrueFor4(string $value, string $expectedHex, string $expectedDot): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertTrue($ip->isVersion(4)); } /** * @test - * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidIpAddresses() + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidProtocolIpAddresses() */ #[PHPUnit\Test] - #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidIpAddresses')] + #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidProtocolIpAddresses')] public function testIsVersionOnlyReturnsFalseFor6(string $value, string $expectedHex, string $expectedDot): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertFalse($ip->isVersion(6)); } /** * @test - * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidIpAddresses() + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidProtocolIpAddresses() */ #[PHPUnit\Test] - #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidIpAddresses')] + #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidProtocolIpAddresses')] public function testIsVersion4AlwaysReturnsTrue(string $value, string $expectedHex, string $expectedDot): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertTrue($ip->isVersion4()); } /** * @test - * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidIpAddresses() + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidProtocolIpAddresses() */ #[PHPUnit\Test] - #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidIpAddresses')] + #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidProtocolIpAddresses')] public function testIsVersion6AlwaysReturnsFalse(string $value, string $expectedHex, string $expectedDot): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertFalse($ip->isVersion6()); } @@ -232,7 +235,7 @@ public function testExceptionIsThrownFromOutOfRangeCidrValues(int $cidr): void #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getNetworkIpAddresses')] public function testNetworkIp(string $expected, int $cidr): void { - $ip = IP::factory('12.34.56.78'); + $ip = IP::fromProtocol('12.34.56.78'); $this->assertSame($expected, $ip->getNetworkIp($cidr)->getDotAddress()); } @@ -244,7 +247,7 @@ public function testNetworkIp(string $expected, int $cidr): void #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getBroadcastIpAddresses')] public function testBroadcastIp(string $expected, int $cidr): void { - $ip = IP::factory('12.34.56.78'); + $ip = IP::fromProtocol('12.34.56.78'); $this->assertSame($expected, $ip->getBroadcastIp($cidr)->getDotAddress()); } @@ -256,8 +259,8 @@ public function testBroadcastIp(string $expected, int $cidr): void #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidInRangeIpAddresses')] public function testInRange(string $first, string $second, int $cidr): void { - $first = IP::factory($first); - $second = IP::factory($second); + $first = IP::fromProtocol($first); + $second = IP::fromProtocol($second); $this->assertTrue($first->inRange($second, $cidr)); } @@ -269,8 +272,8 @@ public function testInRange(string $first, string $second, int $cidr): void #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getOutOfRangeCidrValues')] public function testInRangeThrowsExceptionOnOutOfRangeCidr(int $cidr): void { - $first = IP::factory('12.34.56.78'); - $second = IP::factory('12.34.56.78'); + $first = IP::fromProtocol('12.34.56.78'); + $second = IP::fromProtocol('12.34.56.78'); $this->expectException(InvalidCidrException::class); $first->inRange($second, $cidr); } @@ -279,8 +282,8 @@ public function testInRangeThrowsExceptionOnOutOfRangeCidr(int $cidr): void #[PHPUnit\Test] public function testDifferentVersionsAreNotInRange(): void { - $ip = IP::factory('12.34.56.78'); - $other = IPv6::factory('::12.34.56.78'); + $ip = IP::fromProtocol('12.34.56.78'); + $other = IPv6::fromProtocol('::12.34.56.78'); $this->expectException(WrongVersionException::class); $ip->inRange($other, 0); } @@ -293,8 +296,8 @@ public function testDifferentVersionsAreNotInRange(): void #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getCommonCidrValues')] public function testCommonCidr(string $first, string $second, int $expectedCidr): void { - $first = IP::factory($first); - $second = IP::factory($second); + $first = IP::fromProtocol($first); + $second = IP::fromProtocol($second); $this->assertSame($expectedCidr, $first->getCommonCidr($second)); } @@ -302,57 +305,57 @@ public function testCommonCidr(string $first, string $second, int $expectedCidr) #[PHPUnit\Test] public function testCommonCidrThrowsException(): void { - $first = IP::factory('12.34.56.78'); - $second = IPv6::factory('2001:db8::a60:8a2e:370:7334'); + $first = IP::fromProtocol('12.34.56.78'); + $second = IPv6::fromProtocol('2001:db8::a60:8a2e:370:7334'); $this->expectException(WrongVersionException::class); $first->getCommonCidr($second); } /** * @test - * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidIpAddresses() + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidProtocolIpAddresses() */ #[PHPUnit\Test] - #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidIpAddresses')] + #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidProtocolIpAddresses')] public function testIsMappedAlwaysReturnsFalse(string $value, string $expectedHex, string $expectedDot): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertFalse($ip->isMapped()); } /** * @test - * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidIpAddresses() + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidProtocolIpAddresses() */ #[PHPUnit\Test] - #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidIpAddresses')] + #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidProtocolIpAddresses')] public function testIsDerivedAlwaysReturnsFalse(string $value, string $expectedHex, string $expectedDot): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertFalse($ip->isDerived()); } /** * @test - * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidIpAddresses() + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidProtocolIpAddresses() */ #[PHPUnit\Test] - #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidIpAddresses')] + #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidProtocolIpAddresses')] public function testIsCompatibleAlwaysReturnsFalse(string $value, string $expectedHex, string $expectedDot): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertFalse($ip->isCompatible()); } /** * @test - * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidIpAddresses() + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidProtocolIpAddresses() */ #[PHPUnit\Test] - #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidIpAddresses')] + #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidProtocolIpAddresses')] public function testIsEmbeddedAlwaysReturnsFalse(string $value, string $expectedHex, string $expectedDot): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertFalse($ip->isEmbedded()); } @@ -364,7 +367,7 @@ public function testIsEmbeddedAlwaysReturnsFalse(string $value, string $expected #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getLinkLocalIpAddresses')] public function testIsLinkLocal(string $value, bool $isLinkLocal): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($isLinkLocal, $ip->isLinkLocal()); } @@ -376,7 +379,7 @@ public function testIsLinkLocal(string $value, bool $isLinkLocal): void #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getLoopbackIpAddresses')] public function testIsLoopback(string $value, bool $isLoopback): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($isLoopback, $ip->isLoopback()); } @@ -388,7 +391,7 @@ public function testIsLoopback(string $value, bool $isLoopback): void #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getMulticastIpAddresses')] public function testIsMulticast(string $value, bool $isMulticast): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($isMulticast, $ip->isMulticast()); } @@ -401,7 +404,7 @@ public function testIsMulticast(string $value, bool $isMulticast): void #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getPrivateUseIpAddresses')] public function testIsPrivateUse(string $value, bool $isPrivateUse): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($isPrivateUse, $ip->isPrivateUse()); } @@ -413,7 +416,7 @@ public function testIsPrivateUse(string $value, bool $isPrivateUse): void #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getUnspecifiedIpAddresses')] public function testIsUnspecified(string $value, bool $isUnspecified): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($isUnspecified, $ip->isUnspecified()); } @@ -425,7 +428,7 @@ public function testIsUnspecified(string $value, bool $isUnspecified): void #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getBenchmarkingIpAddresses')] public function testIsBenchmarking(string $value, bool $isBenchmarking): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($isBenchmarking, $ip->isBenchmarking()); } @@ -437,7 +440,7 @@ public function testIsBenchmarking(string $value, bool $isBenchmarking): void #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getDocumentationIpAddresses')] public function testIsDocumentation(string $value, bool $isDocumentation): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($isDocumentation, $ip->isDocumentation()); } @@ -449,7 +452,7 @@ public function testIsDocumentation(string $value, bool $isDocumentation): void #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getGloballyReachableIpAddresses')] public function testIsGloballyReachable(string $value, bool $isGloballyReachable): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($isGloballyReachable, $ip->isGloballyReachable()); } @@ -461,7 +464,7 @@ public function testIsGloballyReachable(string $value, bool $isGloballyReachable #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getIsBroadcastIpAddresses')] public function testIsBroadcast(string $value, bool $isBroadcast): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($isBroadcast, $ip->isBroadcast()); } @@ -473,7 +476,7 @@ public function testIsBroadcast(string $value, bool $isBroadcast): void #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getSharedIpAddresses')] public function testIsShared(string $value, bool $isShared): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($isShared, $ip->isShared()); } @@ -485,19 +488,19 @@ public function testIsShared(string $value, bool $isShared): void #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getFutureReservedIpAddresses')] public function testIsFutureReserved(string $value, bool $isFutureReserved): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($isFutureReserved, $ip->isFutureReserved()); } /** * @test - * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidIpAddresses() + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv4::getValidProtocolIpAddresses() */ #[PHPUnit\Test] - #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidIpAddresses')] + #[PHPUnit\DataProviderExternal(IPv4DataProvider::class, 'getValidProtocolIpAddresses')] public function testStringCasting(string $value, string $expectedHex, string $expectedDot): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($expectedDot, (string) $ip); } @@ -505,7 +508,7 @@ public function testStringCasting(string $value, string $expectedHex, string $ex #[PHPUnit\Test] public function testPerCallFormatterOverridesGlobal(): void { - $ip = IP::factory('12.34.56.78'); + $ip = IP::fromProtocol('12.34.56.78'); $this->assertSame(StubFormatter::SENTINEL, $ip->getDotAddress(new StubFormatter())); } @@ -513,7 +516,7 @@ public function testPerCallFormatterOverridesGlobal(): void #[PHPUnit\Test] public function testPerCallFormatterDoesNotMutateGlobal(): void { - $ip = IP::factory('12.34.56.78'); + $ip = IP::fromProtocol('12.34.56.78'); $this->assertSame(StubFormatter::SENTINEL, $ip->getDotAddress(new StubFormatter())); $this->assertSame('12.34.56.78', $ip->getDotAddress()); } @@ -522,7 +525,7 @@ public function testPerCallFormatterDoesNotMutateGlobal(): void #[PHPUnit\Test] public function testExplicitNullPerCallFormatterFallsBackToGlobal(): void { - $ip = IP::factory('12.34.56.78'); + $ip = IP::fromProtocol('12.34.56.78'); $this->assertSame('12.34.56.78', $ip->getDotAddress(null)); } @@ -530,7 +533,7 @@ public function testExplicitNullPerCallFormatterFallsBackToGlobal(): void #[PHPUnit\Test] public function testInvalidPerCallFormatterTriggersDeprecationAndFallsBack(): void { - $ip = IP::factory('12.34.56.78'); + $ip = IP::fromProtocol('12.34.56.78'); $result = null; $message = $this->captureDeprecation(static function () use ($ip, &$result): void { $result = $ip->getDotAddress(new \stdClass()); @@ -543,7 +546,7 @@ public function testInvalidPerCallFormatterTriggersDeprecationAndFallsBack(): vo #[PHPUnit\Test] public function testInvalidScalarPerCallFormatterMentionsTypeInDeprecation(): void { - $ip = IP::factory('12.34.56.78'); + $ip = IP::fromProtocol('12.34.56.78'); $message = $this->captureDeprecation(static function () use ($ip): void { $ip->getDotAddress(42); }); @@ -589,7 +592,10 @@ public function testFromProtocolThrowsOnInvalidAddresses(string $value): void IP::fromProtocol($value); } - /** @test */ + /** + * @test + * @deprecated Deliberately contrasts the deprecated factory() against fromProtocol(). + */ #[PHPUnit\Test] public function testFromProtocolRejectsWhatFactoryAcceptsAsBinary(): void { @@ -611,6 +617,7 @@ public function testFromBinaryAcceptsRawBinarySequences(string $value, string $e $ip = IP::fromBinary($value); $this->assertInstanceOf(Version4Interface::class, $ip); $this->assertSame($value, $ip->getBinary()); + $this->assertSame($expectedDot, $ip->getDotAddress()); } /** @test */ diff --git a/tests/Version/IPv6Test.php b/tests/Version/IPv6Test.php index bfe278d..3a5b13f 100644 --- a/tests/Version/IPv6Test.php +++ b/tests/Version/IPv6Test.php @@ -44,7 +44,7 @@ public function resetProtocolFormatter(): void #[PHPUnit\Test] public function testImplementsCapabilityInterfaces(): void { - $ip = IP::factory('::1'); + $ip = IP::fromProtocol('::1'); $this->assertInstanceOf(VersionIdentityInterface::class, $ip); $this->assertInstanceOf(ComparisonInterface::class, $ip); $this->assertInstanceOf(ArithmeticInterface::class, $ip); @@ -57,19 +57,20 @@ public function testImplementsCapabilityInterfaces(): void /** * @test - * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getValidIpAddresses() + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getValidProtocolIpAddresses() */ #[PHPUnit\Test] - #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getValidIpAddresses')] + #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getValidProtocolIpAddresses')] public function testInstantiationWithValidAddresses(string $value, string $hex, string $expanded, string $compacted): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertInstanceOf(IpInterface::class, $ip); $this->assertInstanceOf(Version6Interface::class, $ip); } /** * @test + * @deprecated Retains coverage of the deprecated factory() raw-binary path. * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getValidBinarySequences() */ #[PHPUnit\Test] @@ -82,6 +83,7 @@ public function testBinarySequenceIsTheSameOnceInstantiated(string $value, strin /** * @test + * @deprecated Retains coverage of the deprecated factory() protocol path. * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getValidProtocolIpAddresses() */ #[PHPUnit\Test] @@ -95,6 +97,7 @@ public function testProtocolNotationConvertsToCorrectBinarySequence(string $valu /** * @test + * @deprecated Retains coverage of the deprecated factory() validation path. * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getInvalidIpAddresses() */ #[PHPUnit\Test] @@ -115,15 +118,16 @@ public function testExceptionIsThrownOnInstantiationWithInvalidAddresses(string /** * @test * @covers \Darsyn\IP\Version\IPv6::fromEmbedded() - * @covers \Darsyn\IP\Version\Multi::factory() + * @covers \Darsyn\IP\Version\Multi::tryFromProtocol() + * @covers \Darsyn\IP\Version\Multi::fromProtocol() * @covers \Darsyn\IP\Version\Multi::getBinary() */ #[PHPUnit\Test] public function testInstantiationFromEmbeddedIpAddress(): void { try { - $ip = IP::factory('12.34.56.78'); - $this->fail('IPv6 factory should not accept IPv4 addresses.'); + $ip = IP::fromProtocol('12.34.56.78'); + $this->fail('IPv6 fromProtocol() should not accept IPv4 addresses.'); } catch (InvalidIpAddressException $e) { } @@ -133,7 +137,7 @@ public function testInstantiationFromEmbeddedIpAddress(): void $this->assertSame('0000:1fff:ffff:ffff:ffff:ffff:ffff:ffff', $embedded->getBroadcastIp(19)->getExpandedAddress()); // Multi objects understand both IPv4 and IPv6 addresses. - $multi = Multi::factory('12.34.56.78', new Mapped()); + $multi = Multi::fromProtocol('12.34.56.78', new Mapped()); // So therefore, if a Multi object detects that it holds an embedded IPv4 address it will attempt to work with // the IPv4 address before falling back on the full IPv6 address. $this->assertSame('0000:0000:0000:0000:0000:ffff:0c22:3fff', $multi->getBroadcastIp(19)->getExpandedAddress()); @@ -142,25 +146,45 @@ public function testInstantiationFromEmbeddedIpAddress(): void /** * @test - * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getValidIpAddresses() + * @covers \Darsyn\IP\Version\IPv6::fromEmbedded() + * @covers \Darsyn\IP\Version\Multi::tryFromProtocol() + * @covers \Darsyn\IP\Version\Multi::fromBinary() */ #[PHPUnit\Test] - #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getValidIpAddresses')] + public function testFromEmbeddedAcceptsBinarySequence(): void + { + // fromEmbedded() accepts a raw binary sequence as well as protocol notation, remaining + // backwards compatible with the deprecated factory() it used to delegate to. + $fromProtocol = IP::fromEmbedded('12.34.56.78', new Mapped()); + $fromBinary = IP::fromEmbedded("\x0c\x22\x38\x4e", new Mapped()); + $this->assertSame($fromProtocol->getBinary(), $fromBinary->getBinary()); + $this->assertSame( + $fromProtocol->getBinary(), + IP::fromEmbedded($fromProtocol->getBinary(), new Mapped())->getBinary() + ); + } + + /** + * @test + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getValidProtocolIpAddresses() + */ + #[PHPUnit\Test] + #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getValidProtocolIpAddresses')] public function testGetBinaryAlwaysReturnsA16ByteString(string $value, string $hex, string $expanded, string $compacted): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame(16, \strlen(\bin2hex($ip->getBinary())) / 2); } /** * @test - * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getValidIpAddresses() + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getValidProtocolIpAddresses() */ #[PHPUnit\Test] - #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getValidIpAddresses')] + #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getValidProtocolIpAddresses')] public function testGetCompactedAddressReturnsCorrectString(string $value, string $hex, string $expanded, string $compacted): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($compacted, $ip->getCompactedAddress()); } @@ -172,67 +196,67 @@ public function testGetCompactedAddressReturnsCorrectString(string $value, strin #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getValidProtocolIpAddresses')] public function testGetExpandedAddressReturnsCorrectString(string $value, string $hex, string $expanded, string $compacted): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($expanded, $ip->getExpandedAddress()); } /** * @test - * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getValidIpAddresses() + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getValidProtocolIpAddresses() */ #[PHPUnit\Test] - #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getValidIpAddresses')] + #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getValidProtocolIpAddresses')] public function testGetVersionAlwaysReturns6(string $value, string $hex, string $expanded, string $compacted): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame(6, $ip->getVersion()); } /** * @test - * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getValidIpAddresses() + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getValidProtocolIpAddresses() */ #[PHPUnit\Test] - #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getValidIpAddresses')] + #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getValidProtocolIpAddresses')] public function testIsVersionOnlyReturnsTrueFor6(string $value, string $hex, string $expanded, string $compacted): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertTrue($ip->isVersion(6)); } /** * @test - * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getValidIpAddresses() + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getValidProtocolIpAddresses() */ #[PHPUnit\Test] - #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getValidIpAddresses')] + #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getValidProtocolIpAddresses')] public function testIsVersionOnlyReturnsFalseFor4(string $value, string $hex, string $expanded, string $compacted): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertFalse($ip->isVersion(4)); } /** * @test - * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getValidIpAddresses() + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getValidProtocolIpAddresses() */ #[PHPUnit\Test] - #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getValidIpAddresses')] + #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getValidProtocolIpAddresses')] public function testIsVersion6AlwaysReturnsTrue(string $value, string $hex, string $expanded, string $compacted): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertTrue($ip->isVersion6()); } /** * @test - * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getValidIpAddresses() + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getValidProtocolIpAddresses() */ #[PHPUnit\Test] - #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getValidIpAddresses')] + #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getValidProtocolIpAddresses')] public function testIsVersion4AlwaysReturnsFalse(string $value, string $hex, string $expanded, string $compacted): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertFalse($ip->isVersion4()); } @@ -276,7 +300,7 @@ public function testExceptionIsThrownFromOutOfRangeCidrValues(int $cidr): void #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getNetworkIpAddresses')] public function testNetworkIp(string $expected, int $cidr): void { - $ip = IP::factory('2001:db8::a60:8a2e:370:7334'); + $ip = IP::fromProtocol('2001:db8::a60:8a2e:370:7334'); $this->assertSame($expected, $ip->getNetworkIp($cidr)->getCompactedAddress()); } @@ -288,7 +312,7 @@ public function testNetworkIp(string $expected, int $cidr): void #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getBroadcastIpAddresses')] public function testBroadcastIp(string $expected, int $cidr): void { - $ip = IP::factory('2001:db8::a60:8a2e:370:7334'); + $ip = IP::fromProtocol('2001:db8::a60:8a2e:370:7334'); $this->assertSame($expected, $ip->getBroadcastIp($cidr)->getCompactedAddress()); } @@ -300,8 +324,8 @@ public function testBroadcastIp(string $expected, int $cidr): void #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getValidInRangeIpAddresses')] public function testInRange(string $first, string $second, int $cidr): void { - $first = IP::factory($first); - $second = IP::factory($second); + $first = IP::fromProtocol($first); + $second = IP::fromProtocol($second); $this->assertTrue($first->inRange($second, $cidr)); } @@ -309,8 +333,8 @@ public function testInRange(string $first, string $second, int $cidr): void #[PHPUnit\Test] public function testDifferentVersionsAreNotInRange(): void { - $ip = IP::factory('::12.34.56.78'); - $other = IPv4::factory('12.34.56.78'); + $ip = IP::fromProtocol('::12.34.56.78'); + $other = IPv4::fromProtocol('12.34.56.78'); $this->expectException(WrongVersionException::class); $ip->inRange($other, 0); } @@ -323,8 +347,8 @@ public function testDifferentVersionsAreNotInRange(): void #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getCommonCidrValues')] public function testCommonCidr(string $first, string $second, int $expectedCidr): void { - $first = IP::factory($first); - $second = IP::factory($second); + $first = IP::fromProtocol($first); + $second = IP::fromProtocol($second); $this->assertSame($expectedCidr, $first->getCommonCidr($second)); } @@ -345,8 +369,8 @@ public function testEmbeddedCommonCidr(string $first, string $second, int $expec #[PHPUnit\Test] public function testCommonCidrThrowsException(): void { - $first = IP::factory('2001:db8::a60:8a2e:370:7334'); - $second = IPv4::factory('12.34.56.78'); + $first = IP::fromProtocol('2001:db8::a60:8a2e:370:7334'); + $second = IPv4::fromProtocol('12.34.56.78'); $this->expectException(WrongVersionException::class); $first->getCommonCidr($second); } @@ -359,7 +383,7 @@ public function testCommonCidrThrowsException(): void #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getMappedIpAddresses')] public function testIsMapped(string $value, bool $isMapped): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($isMapped, $ip->isMapped()); } @@ -371,7 +395,7 @@ public function testIsMapped(string $value, bool $isMapped): void #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getDerivedIpAddresses')] public function testIsDerived(string $value, bool $isDerived): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($isDerived, $ip->isDerived()); } @@ -383,19 +407,19 @@ public function testIsDerived(string $value, bool $isDerived): void #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getCompatibleIpAddresses')] public function testIsCompatible(string $value, bool $isCompatible): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($isCompatible, $ip->isCompatible()); } /** * @test - * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getValidIpAddresses() + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getValidProtocolIpAddresses() */ #[PHPUnit\Test] - #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getValidIpAddresses')] + #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getValidProtocolIpAddresses')] public function testIsEmbeddedAlwaysReturnsFalse(string $value, string $hex, string $expanded, string $compacted): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertFalse($ip->isEmbedded()); } @@ -407,7 +431,7 @@ public function testIsEmbeddedAlwaysReturnsFalse(string $value, string $hex, str #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getLinkLocalIpAddresses')] public function testIsLinkLocal(string $value, bool $isLinkLocal): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($isLinkLocal, $ip->isLinkLocal()); } @@ -419,7 +443,7 @@ public function testIsLinkLocal(string $value, bool $isLinkLocal): void #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getLoopbackIpAddresses')] public function testIsLoopback(string $value, bool $isLoopback): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($isLoopback, $ip->isLoopback()); } @@ -431,7 +455,7 @@ public function testIsLoopback(string $value, bool $isLoopback): void #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getMulticastIpAddresses')] public function testIsMulticast(string $value, bool $isMulticast): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($isMulticast, $ip->isMulticast()); } @@ -444,7 +468,7 @@ public function testIsMulticast(string $value, bool $isMulticast): void #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getPrivateUseIpAddresses')] public function testIsPrivateUse(string $value, bool $isPrivateUse): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($isPrivateUse, $ip->isPrivateUse()); } @@ -456,7 +480,7 @@ public function testIsPrivateUse(string $value, bool $isPrivateUse): void #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getUnspecifiedIpAddresses')] public function testIsUnspecified(string $value, bool $isUnspecified): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($isUnspecified, $ip->isUnspecified()); } @@ -468,7 +492,7 @@ public function testIsUnspecified(string $value, bool $isUnspecified): void #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getBenchmarkingIpAddresses')] public function testIsBenchmarking(string $value, bool $isBenchmarking): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($isBenchmarking, $ip->isBenchmarking()); } @@ -480,7 +504,7 @@ public function testIsBenchmarking(string $value, bool $isBenchmarking): void #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getDocumentationIpAddresses')] public function testIsDocumentation(string $value, bool $isDocumentation): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($isDocumentation, $ip->isDocumentation()); } @@ -492,7 +516,7 @@ public function testIsDocumentation(string $value, bool $isDocumentation): void #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getGloballyReachableIpAddresses')] public function testIsGloballyReachable(string $value, bool $isGloballyReachable): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($isGloballyReachable, $ip->isGloballyReachable()); } @@ -504,7 +528,7 @@ public function testIsGloballyReachable(string $value, bool $isGloballyReachable #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getUniqueLocalIpAddresses')] public function testIsUniqueLocal(string $value, bool $isUniqueLocal): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($isUniqueLocal, $ip->isUniqueLocal()); } @@ -516,7 +540,7 @@ public function testIsUniqueLocal(string $value, bool $isUniqueLocal): void #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getUnicastIpAddresses')] public function testIsUnicast(string $value, bool $isUnicast): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($isUnicast, $ip->isUnicast()); } @@ -528,19 +552,19 @@ public function testIsUnicast(string $value, bool $isUnicast): void #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getUnicastGlobalIpAddresses')] public function testIsUnicastGlobal(string $value, bool $isUnicastGlobal): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($isUnicastGlobal, $ip->isUnicastGlobal()); } /** * @test - * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getValidIpAddresses() + * @dataProvider \Darsyn\IP\Tests\DataProvider\IPv6::getValidProtocolIpAddresses() */ #[PHPUnit\Test] - #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getValidIpAddresses')] + #[PHPUnit\DataProviderExternal(IPv6DataProvider::class, 'getValidProtocolIpAddresses')] public function testStringCasting(string $value, string $hex, string $expanded, string $compacted): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($compacted, (string) $ip); } @@ -548,7 +572,7 @@ public function testStringCasting(string $value, string $hex, string $expanded, #[PHPUnit\Test] public function testPerCallFormatterOverridesGlobal(): void { - $ip = IP::factory('2001:db8::a60:8a2e:370:7334'); + $ip = IP::fromProtocol('2001:db8::a60:8a2e:370:7334'); $this->assertSame(StubFormatter::SENTINEL, $ip->getCompactedAddress(new StubFormatter())); } @@ -556,7 +580,7 @@ public function testPerCallFormatterOverridesGlobal(): void #[PHPUnit\Test] public function testPerCallNativeFormatterProducesNativeOutput(): void { - $ip = IP::factory('::ffff:c22:384e'); + $ip = IP::fromProtocol('::ffff:c22:384e'); $this->assertSame('::ffff:c22:384e', $ip->getCompactedAddress()); $this->assertSame('::ffff:12.34.56.78', $ip->getCompactedAddress(new NativeFormatter())); $this->assertSame('::ffff:c22:384e', $ip->getCompactedAddress()); @@ -566,7 +590,7 @@ public function testPerCallNativeFormatterProducesNativeOutput(): void #[PHPUnit\Test] public function testExplicitNullPerCallFormatterFallsBackToGlobal(): void { - $ip = IP::factory('::ffff:c22:384e'); + $ip = IP::fromProtocol('::ffff:c22:384e'); $this->assertSame('::ffff:c22:384e', $ip->getCompactedAddress(null)); } @@ -574,7 +598,7 @@ public function testExplicitNullPerCallFormatterFallsBackToGlobal(): void #[PHPUnit\Test] public function testInvalidPerCallFormatterTriggersDeprecationAndFallsBack(): void { - $ip = IP::factory('2001:db8::a60:8a2e:370:7334'); + $ip = IP::fromProtocol('2001:db8::a60:8a2e:370:7334'); $result = null; $message = $this->captureDeprecation(static function () use ($ip, &$result): void { $result = $ip->getCompactedAddress(new \stdClass()); @@ -620,7 +644,10 @@ public function testFromProtocolThrowsOnInvalidAddresses(string $value): void IP::fromProtocol($value); } - /** @test */ + /** + * @test + * @deprecated Deliberately contrasts the deprecated factory() against fromProtocol(). + */ #[PHPUnit\Test] public function testFromProtocolRejectsWhatFactoryAcceptsAsBinary(): void { @@ -640,6 +667,8 @@ public function testFromBinaryAcceptsRawBinarySequences(string $value, string $h $ip = IP::fromBinary($value); $this->assertInstanceOf(Version6Interface::class, $ip); $this->assertSame($value, $ip->getBinary()); + $this->assertSame($expanded, $ip->getExpandedAddress()); + $this->assertSame($compacted, $ip->getCompactedAddress()); } /** @test */ diff --git a/tests/Version/MultiTest.php b/tests/Version/MultiTest.php index dddb35f..e49e340 100644 --- a/tests/Version/MultiTest.php +++ b/tests/Version/MultiTest.php @@ -52,7 +52,7 @@ public function resetProtocolFormatter(): void #[PHPUnit\Test] public function testImplementsCapabilityInterfaces(): void { - $ip = IP::factory('127.0.0.1'); + $ip = IP::fromProtocol('127.0.0.1'); $this->assertInstanceOf(VersionIdentityInterface::class, $ip); $this->assertInstanceOf(ComparisonInterface::class, $ip); $this->assertInstanceOf(ArithmeticInterface::class, $ip); @@ -67,13 +67,13 @@ public function testImplementsCapabilityInterfaces(): void /** * @test - * @dataProvider \Darsyn\IP\Tests\DataProvider\Multi::getValidIpAddresses() + * @dataProvider \Darsyn\IP\Tests\DataProvider\Multi::getValidProtocolIpAddresses() */ #[PHPUnit\Test] - #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getValidIpAddresses')] + #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getValidProtocolIpAddresses')] public function testInstantiationWithValidAddresses(string $value, string $hex, string $expanded, string $compacted, ?string $dot): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertInstanceOf(IpInterface::class, $ip); $this->assertInstanceOf(Version4Interface::class, $ip); $this->assertInstanceOf(Version6Interface::class, $ip); @@ -82,6 +82,7 @@ public function testInstantiationWithValidAddresses(string $value, string $hex, /** * @test + * @deprecated Retains coverage of the deprecated factory() with an explicit embedding strategy. * @dataProvider \Darsyn\IP\Tests\DataProvider\Multi::getEmbeddingStrategyIpAddresses() * @param class-string $strategyClass */ @@ -103,12 +104,13 @@ public function testEmbeddingStrategy(string $strategyClass, string $expandedAdd public function testDefaufltEmbeddingStrategy(string $strategyClass, string $expandedAddress, string $v4address): void { IP::setDefaultEmbeddingStrategy(new $strategyClass()); - $ip = IP::factory($v4address); + $ip = IP::fromProtocol($v4address); $this->assertSame($expandedAddress, $ip->getExpandedAddress()); } /** * @test + * @deprecated Retains coverage of the deprecated factory() raw-binary path. * @dataProvider \Darsyn\IP\Tests\DataProvider\Multi::getValidBinarySequences() */ #[PHPUnit\Test] @@ -121,6 +123,7 @@ public function testBinarySequenceIsTheSameOnceInstantiated(string $value, strin /** * @test + * @deprecated Retains coverage of the deprecated factory() protocol path. * @dataProvider \Darsyn\IP\Tests\DataProvider\Multi::getValidProtocolIpAddresses() */ #[PHPUnit\Test] @@ -134,6 +137,7 @@ public function testProtocolNotationConvertsToCorrectBinarySequence(string $valu /** * @test + * @deprecated Retains coverage of the deprecated factory() validation path. * @dataProvider \Darsyn\IP\Tests\DataProvider\Multi::getInvalidIpAddresses() */ #[PHPUnit\Test] @@ -152,25 +156,25 @@ public function testExceptionIsThrownOnInstantiationWithInvalidAddresses(string /** * @test - * @dataProvider \Darsyn\IP\Tests\DataProvider\Multi::getValidIpAddresses() + * @dataProvider \Darsyn\IP\Tests\DataProvider\Multi::getValidProtocolIpAddresses() */ #[PHPUnit\Test] - #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getValidIpAddresses')] + #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getValidProtocolIpAddresses')] public function testGetBinaryAlwaysReturnsA16ByteString(string $value, string $hex, string $expanded, string $compacted, ?string $dot): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame(16, \strlen(\bin2hex($ip->getBinary())) / 2); } /** * @test - * @dataProvider \Darsyn\IP\Tests\DataProvider\Multi::getValidIpAddresses() + * @dataProvider \Darsyn\IP\Tests\DataProvider\Multi::getValidProtocolIpAddresses() */ #[PHPUnit\Test] - #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getValidIpAddresses')] + #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getValidProtocolIpAddresses')] public function testGetCompactedAddressReturnsCorrectString(string $value, string $hex, string $expanded, string $compacted, ?string $dot): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($compacted, $ip->getCompactedAddress()); } @@ -182,33 +186,33 @@ public function testGetCompactedAddressReturnsCorrectString(string $value, strin #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getValidProtocolIpAddresses')] public function testGetExpandedAddressReturnsCorrectString(string $value, string $hex, string $expanded, string $compacted, ?string $dot): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($expanded, $ip->getExpandedAddress()); } /** * @test - * @dataProvider \Darsyn\IP\Tests\DataProvider\Multi::getValidIpVersion4Addresses() + * @dataProvider \Darsyn\IP\Tests\DataProvider\Multi::getValidProtocolIpVersion4Addresses() */ #[PHPUnit\Test] - #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getValidIpVersion4Addresses')] + #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getValidProtocolIpVersion4Addresses')] public function testDotAddressReturnsCorrectString(string $value, string $hex, string $expanded, string $compacted, ?string $dot): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($dot, $ip->getDotAddress()); } /** * @test - * @dataProvider \Darsyn\IP\Tests\DataProvider\Multi::getValidIpVersion6Addresses() + * @dataProvider \Darsyn\IP\Tests\DataProvider\Multi::getValidProtocolIpVersion6Addresses() */ #[PHPUnit\Test] - #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getValidIpVersion6Addresses')] + #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getValidProtocolIpVersion6Addresses')] public function testDotAddressThrowsExceptionForNonVersion4Addresses(string $value, string $hex, string $expanded, string $compacted, ?string $dot): void { $this->expectException(\Darsyn\IP\Exception\WrongVersionException::class); try { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $ip->getDotAddress(); } catch (WrongVersionException $e) { $this->assertTrue(isset($ip)); @@ -221,13 +225,13 @@ public function testDotAddressThrowsExceptionForNonVersion4Addresses(string $val /** * @test - * @dataProvider \Darsyn\IP\Tests\DataProvider\Multi::getIpAddressVersions() + * @dataProvider \Darsyn\IP\Tests\DataProvider\Multi::getProtocolIpAddressVersions() */ #[PHPUnit\Test] - #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getIpAddressVersions')] + #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getProtocolIpAddressVersions')] public function testVersion(string $value, int $version): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($version, $ip->getVersion()); } @@ -239,7 +243,7 @@ public function testVersion(string $value, int $version): void #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getNetworkIpAddresses')] public function testNetworkIp(string $initial, string $expected, int $cidr): void { - $ip = IP::factory($initial); + $ip = IP::fromProtocol($initial); $this->assertSame($expected, $ip->getNetworkIp($cidr)->getProtocolAppropriateAddress()); } @@ -251,7 +255,7 @@ public function testNetworkIp(string $initial, string $expected, int $cidr): voi #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getBroadcastIpAddresses')] public function testBroadcastIp(string $initial, string $expected, int $cidr): void { - $ip = IP::factory($initial); + $ip = IP::fromProtocol($initial); $this->assertSame($expected, $ip->getBroadcastIp($cidr)->getProtocolAppropriateAddress()); } @@ -263,8 +267,8 @@ public function testBroadcastIp(string $initial, string $expected, int $cidr): v #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getValidInRangeIpAddresses')] public function testInRange(string $first, string $second, int $cidr): void { - $first = IP::factory($first); - $second = IP::factory($second); + $first = IP::fromProtocol($first); + $second = IP::fromProtocol($second); $this->assertTrue($first->inRange($second, $cidr)); } @@ -272,8 +276,8 @@ public function testInRange(string $first, string $second, int $cidr): void #[PHPUnit\Test] public function testDifferentVersionsAreInRange(): void { - $first = IP::factory('127.0.0.1', new Strategy\Mapped()); - $second = IPv6::factory('::1234:5678:abcd:90ef'); + $first = IP::fromProtocol('127.0.0.1', new Strategy\Mapped()); + $second = IPv6::fromProtocol('::1234:5678:abcd:90ef'); $this->assertTrue($first->inRange($second, 0)); } @@ -281,8 +285,8 @@ public function testDifferentVersionsAreInRange(): void #[PHPUnit\Test] public function testDifferentByteLengthsAreNotInRange(): void { - $first = IP::factory('127.0.0.1'); - $second = IPv4::factory('127.0.0.1'); + $first = IP::fromProtocol('127.0.0.1'); + $second = IPv4::fromProtocol('127.0.0.1'); $this->expectException(WrongVersionException::class); $first->inRange($second, 0); } @@ -295,8 +299,8 @@ public function testDifferentByteLengthsAreNotInRange(): void #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getCommonCidrValues')] public function testCommonCidr(string $first, string $second, int $expectedCidr): void { - $first = IP::factory($first); - $second = IP::factory($second); + $first = IP::fromProtocol($first); + $second = IP::fromProtocol($second); $this->assertSame($expectedCidr, $first->getCommonCidr($second)); } @@ -304,8 +308,8 @@ public function testCommonCidr(string $first, string $second, int $expectedCidr) #[PHPUnit\Test] public function testCommonCidrThrowsException(): void { - $first = IP::factory('12.34.56.78'); - $second = IPv4::factory('12.34.56.78'); + $first = IP::fromProtocol('12.34.56.78'); + $second = IPv4::fromProtocol('12.34.56.78'); $this->expectException(WrongVersionException::class); $first->getCommonCidr($second); } @@ -318,7 +322,7 @@ public function testCommonCidrThrowsException(): void #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getLinkLocalIpAddresses')] public function testIsLinkLocal(string $value, bool $isLinkLocal): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($isLinkLocal, $ip->isLinkLocal()); } @@ -330,7 +334,7 @@ public function testIsLinkLocal(string $value, bool $isLinkLocal): void #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getMappedLoopbackIpAddresses')] public function testIsLoopbackMapped(string $value, bool $isLoopback): void { - $ip = IP::factory($value, new Strategy\Mapped()); + $ip = IP::fromProtocol($value, new Strategy\Mapped()); $this->assertSame($isLoopback, $ip->isLoopback()); } @@ -342,7 +346,7 @@ public function testIsLoopbackMapped(string $value, bool $isLoopback): void #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getCompatibleLoopbackIpAddresses')] public function testIsLoopbackCompatible(string $value, bool $isLoopback): void { - $ip = IP::factory($value, new Strategy\Compatible()); + $ip = IP::fromProtocol($value, new Strategy\Compatible()); if ('0000:0000:0000:0000:0000:0000:0000:0001' === $ip->getExpandedAddress()) { // Special case that I can't figure out a solution for. // The address 0.0.0.1 (when using the compatible embedding strategy) @@ -361,7 +365,7 @@ public function testIsLoopbackCompatible(string $value, bool $isLoopback): void #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getDerivedLoopbackIpAddresses')] public function testIsLoopbackDerived(string $value, bool $isLoopback): void { - $ip = IP::factory($value, new Strategy\Derived()); + $ip = IP::fromProtocol($value, new Strategy\Derived()); $this->assertSame($isLoopback, $ip->isLoopback()); } @@ -373,7 +377,7 @@ public function testIsLoopbackDerived(string $value, bool $isLoopback): void #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getTeredoLoopbackIpAddresses')] public function testIsLoopbackTeredo(string $value, bool $isLoopback): void { - $ip = IP::factory($value, new Strategy\Teredo()); + $ip = IP::fromProtocol($value, new Strategy\Teredo()); $this->assertSame($isLoopback, $ip->isLoopback()); } @@ -385,7 +389,7 @@ public function testIsLoopbackTeredo(string $value, bool $isLoopback): void #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getMulticastIpAddresses')] public function testIsMulticast(string $value, bool $isMulticast): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($isMulticast, $ip->isMulticast()); } @@ -398,7 +402,7 @@ public function testIsMulticast(string $value, bool $isMulticast): void #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getPrivateUseIpAddresses')] public function testIsPrivateUse(string $value, bool $isPrivateUse): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($isPrivateUse, $ip->isPrivateUse()); } @@ -410,7 +414,7 @@ public function testIsPrivateUse(string $value, bool $isPrivateUse): void #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getUnspecifiedIpAddresses')] public function testIsUnspecified(string $value, bool $isUnspecified): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($isUnspecified, $ip->isUnspecified()); } @@ -422,7 +426,7 @@ public function testIsUnspecified(string $value, bool $isUnspecified): void #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getBenchmarkingIpAddresses')] public function testIsBenchmarking(string $value, bool $isBenchmarking): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($isBenchmarking, $ip->isBenchmarking()); } @@ -434,7 +438,7 @@ public function testIsBenchmarking(string $value, bool $isBenchmarking): void #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getDocumentationIpAddresses')] public function testIsDocumentation(string $value, bool $isDocumentation): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $this->assertSame($isDocumentation, $ip->isDocumentation()); } @@ -446,7 +450,7 @@ public function testIsDocumentation(string $value, bool $isDocumentation): void #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getGloballyReachableIpAddresses')] public function testIsGloballyReachable(string $value, bool $isGloballyReachable): void { - $ip = IP::factory($value, new Strategy\Mapped()); + $ip = IP::fromProtocol($value, new Strategy\Mapped()); $this->assertSame($isGloballyReachable, $ip->isGloballyReachable()); } @@ -458,7 +462,7 @@ public function testIsGloballyReachable(string $value, bool $isGloballyReachable #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getUniqueLocalIpAddresses')] public function testIsUniqueLocal(string $value, bool $isUniqueLocal, bool $willThrowException): void { - $ip = IP::factory($value, new Strategy\Mapped()); + $ip = IP::fromProtocol($value, new Strategy\Mapped()); $willThrowException && $this->expectException(WrongVersionException::class); $this->assertSame($isUniqueLocal, $ip->isUniqueLocal()); } @@ -471,7 +475,7 @@ public function testIsUniqueLocal(string $value, bool $isUniqueLocal, bool $will #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getUnicastIpAddresses')] public function testIsUnicast(string $value, bool $isUnicast, bool $willThrowException): void { - $ip = IP::factory($value, new Strategy\Mapped()); + $ip = IP::fromProtocol($value, new Strategy\Mapped()); $willThrowException && $this->expectException(WrongVersionException::class); $this->assertSame($isUnicast, $ip->isUnicast()); } @@ -484,7 +488,7 @@ public function testIsUnicast(string $value, bool $isUnicast, bool $willThrowExc #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getUnicastGlobalIpAddresses')] public function testIsUnicastGlobal(string $value, bool $isUnicastGlobal, bool $willThrowException): void { - $ip = IP::factory($value, new Strategy\Mapped()); + $ip = IP::fromProtocol($value, new Strategy\Mapped()); $willThrowException && $this->expectException(WrongVersionException::class); $this->assertSame($isUnicastGlobal, $ip->isUnicastGlobal()); } @@ -497,7 +501,7 @@ public function testIsUnicastGlobal(string $value, bool $isUnicastGlobal, bool $ #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getIsBroadcastIpAddresses')] public function testIsBroadcast(string $value, bool $isBroadcast, bool $willThrowException): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $willThrowException && $this->expectException(WrongVersionException::class); $this->assertSame($isBroadcast, $ip->isBroadcast()); } @@ -510,7 +514,7 @@ public function testIsBroadcast(string $value, bool $isBroadcast, bool $willThro #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getSharedIpAddresses')] public function testIsShared(string $value, bool $isShared, bool $willThrowException): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $willThrowException && $this->expectException(WrongVersionException::class); $this->assertSame($isShared, $ip->isShared()); } @@ -523,20 +527,20 @@ public function testIsShared(string $value, bool $isShared, bool $willThrowExcep #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getFutureReservedIpAddresses')] public function testIsFutureReserved(string $value, bool $isFutureReserved, bool $willThrowException): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); $willThrowException && $this->expectException(WrongVersionException::class); $this->assertSame($isFutureReserved, $ip->isFutureReserved()); } /** * @test - * @dataProvider \Darsyn\IP\Tests\DataProvider\Multi::getValidIpAddresses() + * @dataProvider \Darsyn\IP\Tests\DataProvider\Multi::getValidProtocolIpAddresses() */ #[PHPUnit\Test] - #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getValidIpAddresses')] + #[PHPUnit\DataProviderExternal(MultiDataProvider::class, 'getValidProtocolIpAddresses')] public function testStringCasting(string $value, string $hex, string $expanded, string $compacted, ?string $dot): void { - $ip = IP::factory($value); + $ip = IP::fromProtocol($value); null !== $dot ? $this->assertSame($dot, (string) $ip) : $this->assertSame($compacted, (string) $ip); @@ -546,7 +550,7 @@ public function testStringCasting(string $value, string $hex, string $expanded, #[PHPUnit\Test] public function testPerCallFormatterOverridesGlobalForProtocolAppropriateAddress(): void { - $ip = IP::factory('12.34.56.78'); + $ip = IP::fromProtocol('12.34.56.78'); $this->assertSame(StubFormatter::SENTINEL, $ip->getProtocolAppropriateAddress(new StubFormatter())); } @@ -554,7 +558,7 @@ public function testPerCallFormatterOverridesGlobalForProtocolAppropriateAddress #[PHPUnit\Test] public function testPerCallFormatterDoesNotMutateGlobalForProtocolAppropriateAddress(): void { - $ip = IP::factory('12.34.56.78'); + $ip = IP::fromProtocol('12.34.56.78'); $this->assertSame(StubFormatter::SENTINEL, $ip->getProtocolAppropriateAddress(new StubFormatter())); $this->assertSame('12.34.56.78', $ip->getProtocolAppropriateAddress()); } @@ -563,7 +567,7 @@ public function testPerCallFormatterDoesNotMutateGlobalForProtocolAppropriateAdd #[PHPUnit\Test] public function testExplicitNullPerCallFormatterFallsBackToGlobalForProtocolAppropriateAddress(): void { - $ip = IP::factory('12.34.56.78'); + $ip = IP::fromProtocol('12.34.56.78'); $this->assertSame('12.34.56.78', $ip->getProtocolAppropriateAddress(null)); } @@ -571,7 +575,7 @@ public function testExplicitNullPerCallFormatterFallsBackToGlobalForProtocolAppr #[PHPUnit\Test] public function testInvalidPerCallFormatterTriggersDeprecationForProtocolAppropriateAddress(): void { - $ip = IP::factory('12.34.56.78'); + $ip = IP::fromProtocol('12.34.56.78'); $result = null; $message = $this->captureDeprecation(static function () use ($ip, &$result): void { $result = $ip->getProtocolAppropriateAddress(new \stdClass()); @@ -584,7 +588,7 @@ public function testInvalidPerCallFormatterTriggersDeprecationForProtocolAppropr #[PHPUnit\Test] public function testPerCallFormatterOverridesGlobalForDotAddress(): void { - $ip = IP::factory('12.34.56.78'); + $ip = IP::fromProtocol('12.34.56.78'); $this->assertSame(StubFormatter::SENTINEL, $ip->getDotAddress(new StubFormatter())); } @@ -592,7 +596,7 @@ public function testPerCallFormatterOverridesGlobalForDotAddress(): void #[PHPUnit\Test] public function testPerCallFormatterDoesNotMutateGlobalForDotAddress(): void { - $ip = IP::factory('12.34.56.78'); + $ip = IP::fromProtocol('12.34.56.78'); $this->assertSame(StubFormatter::SENTINEL, $ip->getDotAddress(new StubFormatter())); $this->assertSame('12.34.56.78', $ip->getDotAddress()); } @@ -601,7 +605,7 @@ public function testPerCallFormatterDoesNotMutateGlobalForDotAddress(): void #[PHPUnit\Test] public function testExplicitNullPerCallFormatterFallsBackToGlobalForDotAddress(): void { - $ip = IP::factory('12.34.56.78'); + $ip = IP::fromProtocol('12.34.56.78'); $this->assertSame('12.34.56.78', $ip->getDotAddress(null)); } @@ -609,7 +613,7 @@ public function testExplicitNullPerCallFormatterFallsBackToGlobalForDotAddress() #[PHPUnit\Test] public function testInvalidPerCallFormatterTriggersDeprecationForDotAddress(): void { - $ip = IP::factory('12.34.56.78'); + $ip = IP::fromProtocol('12.34.56.78'); $result = null; $message = $this->captureDeprecation(static function () use ($ip, &$result): void { $result = $ip->getDotAddress(new \stdClass()); @@ -625,7 +629,7 @@ public function testInvalidPerCallFormatterTriggersDeprecationForDotAddressOnNon // The formatter argument is validated before the version check, so the // deprecation fires even though the IPv6 address ultimately rejects // dotted notation with a WrongVersionException. - $ip = IP::factory('2001:db8::1'); + $ip = IP::fromProtocol('2001:db8::1'); $thrown = null; $message = $this->captureDeprecation(static function () use ($ip, &$thrown): void { try { @@ -675,7 +679,10 @@ public function testFromProtocolThrowsOnInvalidAddresses(string $value): void IP::fromProtocol($value); } - /** @test */ + /** + * @test + * @deprecated Deliberately contrasts the deprecated factory() against fromProtocol(). + */ #[PHPUnit\Test] public function testFromProtocolRejectsWhatFactoryAcceptsAsBinary(): void { @@ -695,6 +702,11 @@ public function testFromBinaryAcceptsRawBinarySequences(string $value, string $h $ip = IP::fromBinary($value); $this->assertInstanceOf(MultiVersionInterface::class, $ip); $this->assertSame($value, $ip->getBinary()); + $this->assertSame($expanded, $ip->getExpandedAddress()); + $this->assertSame($compacted, $ip->getCompactedAddress()); + if (null !== $dot) { + $this->assertSame($dot, $ip->getDotAddress()); + } } /** @test */ From f6a3eb78da86bebe8cd739cbd4b0c5effdca658f Mon Sep 17 00:00:00 2001 From: Zan Baldwin Date: Tue, 16 Jun 2026 18:47:31 +0200 Subject: [PATCH 3/3] =?UTF-8?q?fix(factory):=20=F0=9F=A9=B9=20reject=20inv?= =?UTF-8?q?alid-length=20binary=20in=20Multi::fromProtocol?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Greptile caught this one! Good one, Greptile! --- src/Version/Multi.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Version/Multi.php b/src/Version/Multi.php index b97f633..b28ab30 100644 --- a/src/Version/Multi.php +++ b/src/Version/Multi.php @@ -87,8 +87,11 @@ public static function fromProtocol(string $ip, ?EmbeddingStrategyInterface $str if ($binary === $ip) { throw new Exception\InvalidIpAddressException($ip); } - if (4 === MbString::getLength($binary)) { + $length = MbString::getLength($binary); + if (4 === $length) { $binary = $strategy->pack($binary); + } elseif (16 !== $length) { + throw new Exception\InvalidBinaryException($binary); } return new static($binary, $strategy); }