assertTrue($ip->isIpv4()); $this->assertSame('203.0.113.42', $ip->text()); $this->assertSame(16, strlen($ip->binary())); $this->assertSame( "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xcb\x00\x71\x2a", $ip->binary() ); } public function testParsesIpv4Zero(): void { $ip = IpAddress::fromString('0.0.0.0'); $this->assertTrue($ip->isIpv4()); $this->assertSame('0.0.0.0', $ip->text()); $this->assertSame( "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00", $ip->binary() ); } public function testParsesIpv4Max(): void { $ip = IpAddress::fromString('255.255.255.255'); $this->assertTrue($ip->isIpv4()); $this->assertSame('255.255.255.255', $ip->text()); } public function testParsesFullIpv6(): void { $ip = IpAddress::fromString('2001:0db8:0000:0000:0000:0000:0000:0001'); $this->assertFalse($ip->isIpv4()); $this->assertSame('2001:db8::1', $ip->text()); $this->assertSame(16, strlen($ip->binary())); } public function testParsesZeroCompressedIpv6(): void { $ip = IpAddress::fromString('2001:db8::1'); $this->assertFalse($ip->isIpv4()); $this->assertSame('2001:db8::1', $ip->text()); } public function testParsesIpv6Loopback(): void { $ip = IpAddress::fromString('::1'); $this->assertFalse($ip->isIpv4()); $this->assertSame('::1', $ip->text()); } public function testParsesIpv6Unspecified(): void { $ip = IpAddress::fromString('::'); $this->assertFalse($ip->isIpv4()); $this->assertSame('::', $ip->text()); } public function testParsesV4MappedIpv6KeepsIpv6Form(): void { $ip = IpAddress::fromString('::ffff:1.2.3.4'); $this->assertFalse($ip->isIpv4(), 'v4-mapped passed in IPv6 form should report as IPv6'); $this->assertSame( "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x01\x02\x03\x04", $ip->binary() ); } public function testIpv4MapsIntoSameBinaryAsV4MappedIpv6(): void { $v4 = IpAddress::fromString('1.2.3.4'); $v6 = IpAddress::fromString('::ffff:1.2.3.4'); $this->assertSame($v4->binary(), $v6->binary()); } public function testIpv6IsLowercased(): void { $ip = IpAddress::fromString('2001:DB8:ABCD::FFFF'); $this->assertSame('2001:db8:abcd::ffff', $ip->text()); } public function testIpv6FullyExpandedCanonicalizes(): void { $ip = IpAddress::fromString('fe80:0000:0000:0000:0000:0000:0000:0001'); $this->assertSame('fe80::1', $ip->text()); } public function testIpv6LinkLocalLowercased(): void { $ip = IpAddress::fromString('FE80::1'); $this->assertSame('fe80::1', $ip->text()); } public function testFromBinaryRoundtripIpv4(): void { $original = IpAddress::fromString('10.20.30.40'); $copy = IpAddress::fromBinary($original->binary()); $this->assertTrue($copy->isIpv4()); $this->assertSame('10.20.30.40', $copy->text()); } public function testFromBinaryRoundtripIpv6(): void { $original = IpAddress::fromString('2001:db8::dead:beef'); $copy = IpAddress::fromBinary($original->binary()); $this->assertFalse($copy->isIpv4()); $this->assertSame('2001:db8::dead:beef', $copy->text()); } public function testFromBinaryRejectsWrongLength(): void { $this->expectException(InvalidIpException::class); IpAddress::fromBinary("\x01\x02\x03"); } public function testEqualsCompareByBinary(): void { $a = IpAddress::fromString('1.2.3.4'); $b = IpAddress::fromString('::ffff:1.2.3.4'); $c = IpAddress::fromString('1.2.3.5'); $this->assertTrue($a->equals($b)); $this->assertFalse($a->equals($c)); } /** * @return iterable */ public static function invalidProvider(): iterable { yield 'empty' => ['']; yield 'whitespace only' => [' ']; yield 'leading space' => [' 1.2.3.4']; yield 'trailing space' => ['1.2.3.4 ']; yield 'inner whitespace' => ['1.2. 3.4']; yield 'integer-as-string' => ['1234567890']; yield 'three octets' => ['1.2.3']; yield 'five octets' => ['1.2.3.4.5']; yield 'octet > 255' => ['1.2.3.256']; yield 'negative octet' => ['1.2.3.-1']; yield 'leading zero v4' => ['010.0.0.1']; yield 'leading zero in last octet' => ['10.0.0.01']; yield 'empty octet' => ['10..0.1']; yield 'hex octet' => ['0x10.0.0.1']; yield 'alpha octet' => ['a.b.c.d']; yield 'just dots' => ['...']; yield 'incomplete v6' => ['2001:db8']; yield 'too many groups v6' => ['1:2:3:4:5:6:7:8:9']; yield 'invalid hex group v6' => ['ggggg::1']; yield 'double :: v6' => ['1::2::3']; yield 'bracketed v6' => ['[::1]']; yield 'zone id v6' => ['fe80::1%eth0']; yield 'random garbage' => ['hello world']; yield 'sql injection-ish' => ["1.2.3.4'; DROP TABLE--"]; yield 'newline embedded' => ["1.2.3.4\n"]; yield 'tab embedded' => ["1.2.3.4\t"]; yield 'just slash' => ['/']; yield 'with prefix' => ['1.2.3.4/24']; yield 'just colon' => [':']; yield 'just dot' => ['.']; yield 'numeric overflow octet' => ['1.2.3.99999']; } #[DataProvider('invalidProvider')] public function testRejectsInvalidInput(string $input): void { $this->expectException(InvalidIpException::class); IpAddress::fromString($input); } }