diff --git a/CHANGELOG.md b/CHANGELOG.md index 524b7b0..caaa23f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## `6.x` +- 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 71fc3dd..b38d758 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)`). @@ -70,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. + +`Darsyn\IP\Contracts\FactoryInterface` provides 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 @@ -173,7 +231,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..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 { @@ -46,14 +47,85 @@ 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 * @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 9a48700..b28ab30 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; /** @@ -53,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. @@ -73,6 +75,81 @@ 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); + } + $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 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/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 5e333bb..4825e6c 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; @@ -38,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); @@ -46,23 +48,25 @@ public function testImplementsCapabilityInterfaces(): void $this->assertInstanceOf(Output4Interface::class, $ip); $this->assertInstanceOf(ClassificationInterface::class, $ip); $this->assertInstanceOf(Classification4Interface::class, $ip); + $this->assertInstanceOf(FactoryInterface::class, $ip); } /** * @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] @@ -75,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] @@ -88,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] @@ -107,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()); } @@ -229,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()); } @@ -241,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()); } @@ -253,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)); } @@ -266,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); } @@ -276,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); } @@ -290,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)); } @@ -299,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()); } @@ -361,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()); } @@ -373,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()); } @@ -385,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()); } @@ -398,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()); } @@ -410,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()); } @@ -422,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()); } @@ -434,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()); } @@ -446,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()); } @@ -458,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()); } @@ -470,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()); } @@ -482,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); } @@ -502,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())); } @@ -510,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()); } @@ -519,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)); } @@ -527,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()); @@ -540,11 +546,220 @@ 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); }); $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 + * @deprecated Deliberately contrasts the deprecated factory() against fromProtocol(). + */ + #[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()); + $this->assertSame($expectedDot, $ip->getDotAddress()); + } + + /** @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..3a5b13f 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; @@ -42,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); @@ -50,23 +52,25 @@ public function testImplementsCapabilityInterfaces(): void $this->assertInstanceOf(Output6Interface::class, $ip); $this->assertInstanceOf(ClassificationInterface::class, $ip); $this->assertInstanceOf(Classification6Interface::class, $ip); + $this->assertInstanceOf(FactoryInterface::class, $ip); } /** * @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] @@ -79,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] @@ -92,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] @@ -112,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) { } @@ -130,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()); @@ -139,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()); } @@ -169,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()); } @@ -273,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()); } @@ -285,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()); } @@ -297,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)); } @@ -306,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); } @@ -320,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)); } @@ -342,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); } @@ -356,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()); } @@ -368,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()); } @@ -380,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()); } @@ -404,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()); } @@ -416,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()); } @@ -428,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()); } @@ -441,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()); } @@ -453,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()); } @@ -465,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()); } @@ -477,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()); } @@ -489,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()); } @@ -501,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()); } @@ -513,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()); } @@ -525,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); } @@ -545,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())); } @@ -553,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()); @@ -563,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)); } @@ -571,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()); @@ -579,4 +606,207 @@ 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 + * @deprecated Deliberately contrasts the deprecated factory() against fromProtocol(). + */ + #[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()); + $this->assertSame($expanded, $ip->getExpandedAddress()); + $this->assertSame($compacted, $ip->getCompactedAddress()); + } + + /** @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..e49e340 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; @@ -49,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); @@ -59,17 +62,18 @@ public function testImplementsCapabilityInterfaces(): void $this->assertInstanceOf(ClassificationInterface::class, $ip); $this->assertInstanceOf(Classification4Interface::class, $ip); $this->assertInstanceOf(Classification6Interface::class, $ip); + $this->assertInstanceOf(FactoryInterface::class, $ip); } /** * @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); @@ -78,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 */ @@ -99,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] @@ -117,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] @@ -130,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] @@ -148,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()); } @@ -178,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)); @@ -217,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()); } @@ -235,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()); } @@ -247,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()); } @@ -259,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)); } @@ -268,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)); } @@ -277,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); } @@ -291,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)); } @@ -300,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); } @@ -314,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()); } @@ -326,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()); } @@ -338,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) @@ -357,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()); } @@ -369,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()); } @@ -381,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()); } @@ -394,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()); } @@ -406,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()); } @@ -418,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()); } @@ -430,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()); } @@ -442,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()); } @@ -454,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()); } @@ -467,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()); } @@ -480,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()); } @@ -493,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()); } @@ -506,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()); } @@ -519,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); @@ -542,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())); } @@ -550,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()); } @@ -559,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)); } @@ -567,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()); @@ -580,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())); } @@ -588,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()); } @@ -597,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)); } @@ -605,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()); @@ -621,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 { @@ -633,4 +641,203 @@ 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 + * @deprecated Deliberately contrasts the deprecated factory() against fromProtocol(). + */ + #[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()); + $this->assertSame($expanded, $ip->getExpandedAddress()); + $this->assertSame($compacted, $ip->getCompactedAddress()); + if (null !== $dot) { + $this->assertSame($dot, $ip->getDotAddress()); + } + } + + /** @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)); + } }