diff --git a/phpunit.xml b/phpunit.xml index 4cac405..e29cd51 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -12,6 +12,7 @@ stopOnRisky="true" failOnRisky="true" failOnEmptyTestSuite="true" + displayDetailsOnTestsThatTriggerWarnings="true" displayDetailsOnIncompleteTests="true" > @@ -26,7 +27,8 @@ + + - \ No newline at end of file diff --git a/src/Abstracts/AbstractResourceRecordType.php b/src/Abstracts/AbstractResourceRecordType.php index 8bfbcd7..e30a567 100644 --- a/src/Abstracts/AbstractResourceRecordType.php +++ b/src/Abstracts/AbstractResourceRecordType.php @@ -125,7 +125,6 @@ protected function parseMessage(): void 'Response header length is invalid' ); } - [ 'type' => $type, 'class' => $class, @@ -201,11 +200,11 @@ public function getHeader(): string } /** - * @return string + * @inheritdoc */ - public function getMessage(): string + public function getMessage(): PacketMessageInterface { - return $this->getHeader() . $this->getRData(); + return $this->message; } /** @@ -315,7 +314,7 @@ public function toArray(): array 'host' => $this->getName(), 'class' => $this->getClass()->getName(), 'ttl' => $this->getTTL(), - 'type' => $this->getType(), + 'type' => $this->getType()->getName(), 'value' => $this->getValue(), ]; } diff --git a/src/Interfaces/Packet/PacketHeaderInterface.php b/src/Interfaces/Packet/PacketHeaderInterface.php index 37d33f4..0f931aa 100644 --- a/src/Interfaces/Packet/PacketHeaderInterface.php +++ b/src/Interfaces/Packet/PacketHeaderInterface.php @@ -304,6 +304,14 @@ public function withARCount(int $ar) : static; */ public function withANCount(int $an) : static; + /** + * With NS count + * + * @param int $ns + * @return $this + */ + public function withNSCount(int $ns) : static; + /** * With QD Count * @param int $qd diff --git a/src/Interfaces/Packet/PacketResourceRecordsInterface.php b/src/Interfaces/Packet/PacketResourceRecordsInterface.php index 1c1685e..6e9d0e2 100644 --- a/src/Interfaces/Packet/PacketResourceRecordsInterface.php +++ b/src/Interfaces/Packet/PacketResourceRecordsInterface.php @@ -46,6 +46,13 @@ public function getRecords(): array; */ public function getFilteredType(string $type, bool $single = false) : null|array|ResourceRecordTypeInterface; + /** + * Return array records + * @uses ResourceRecordTypeInterface::toArray() + * @return array + */ + public function toArray() : array; + /** * @return Traversable */ diff --git a/src/Interfaces/ResourceRecord/ResourceRecordTypeInterface.php b/src/Interfaces/ResourceRecord/ResourceRecordTypeInterface.php index 092cf40..56cee37 100644 --- a/src/Interfaces/ResourceRecord/ResourceRecordTypeInterface.php +++ b/src/Interfaces/ResourceRecord/ResourceRecordTypeInterface.php @@ -74,11 +74,11 @@ public function getOffsetPosition() : int; public function getHeader() : string; /** - * Get message raw response answer data + * Get message raw all response answer data * - * @return string + * @return PacketMessageInterface */ - public function getMessage(): string; + public function getMessage(): PacketMessageInterface; /** * The dns name @@ -141,7 +141,8 @@ public function getQueryMessage() : string; /** * Return array data * - * @return array + * @return array like dns_get_record() + * @see \dns_get_record() */ public function toArray() : array; } diff --git a/src/Packet/Header.php b/src/Packet/Header.php index 6654e43..c3acf4c 100644 --- a/src/Packet/Header.php +++ b/src/Packet/Header.php @@ -340,6 +340,17 @@ public function withANCount(int $an): static return $obj; } + /** + * @inheritdoc + */ + public function withNSCount(int $ns): static + { + $obj = clone $this; + $obj->nscount = $ns; + $obj->message = null; + return $obj; + } + /** * @inheritdoc */ diff --git a/src/Packet/Question.php b/src/Packet/Question.php index 0b0f8b6..dc46899 100644 --- a/src/Packet/Question.php +++ b/src/Packet/Question.php @@ -194,6 +194,7 @@ public function __serialize(): array * @param int $type * @param int $class * @return Question + * @noinspection PhpDocMissingThrowsInspection */ public static function fromFilteredResponse( string $name, diff --git a/src/Packet/Records.php b/src/Packet/Records.php index 1a40380..8a0916f 100644 --- a/src/Packet/Records.php +++ b/src/Packet/Records.php @@ -7,6 +7,8 @@ use ArrayAccess\DnsRecord\Interfaces\ResourceRecord\ResourceRecordTypeInterface; use ArrayIterator; use Traversable; +use function array_map; +use function array_values; use function md5; use function serialize; use function strtoupper; @@ -86,6 +88,14 @@ public function getFilteredType(string $type, bool $single = false) : null|array return $result === [] ? null : $result; } + /** + * @inheritdoc + */ + public function toArray(): array + { + return array_map(static fn ($e) => $e->toArray(), array_values($this->getRecords())); + } + /** * @inheritdoc */ diff --git a/src/Resolver.php b/src/Resolver.php index ba45cfb..195192f 100644 --- a/src/Resolver.php +++ b/src/Resolver.php @@ -152,7 +152,8 @@ public function createQueryOpcode( $class = trim($class?:IN::NAME)?:IN::NAME; $class = Lookup::resourceClass($class); $type = Lookup::resourceType($type); - $isOpt = $type->getName() === OPT::TYPE; + $typeName = $type->getName(); + $isOpt = $typeName === OPT::TYPE; if ($isOpt) { // if is OPT fallback to A $type = 'A'; } @@ -165,12 +166,13 @@ public function createQueryOpcode( } $dns = new DnsServerStorage(...$ss); } + + $header = Header::createQueryHeader($opcode, null, $adFlag, $cdFlag, $recurse); $requestData = new RequestData( - Header::createQueryHeader($opcode, null, $adFlag, $cdFlag, $recurse), + $header, $dns, $question ); - if ($isOpt || $dnsSec) { $requestData ->getAdditionalRecords() diff --git a/src/ResourceRecord/RRTypes/CERT.php b/src/ResourceRecord/RRTypes/CERT.php index 66befa6..c7cb8bb 100644 --- a/src/ResourceRecord/RRTypes/CERT.php +++ b/src/ResourceRecord/RRTypes/CERT.php @@ -5,6 +5,7 @@ use ArrayAccess\DnsRecord\Abstracts\AbstractResourceRecordType; use function array_values; +use function base64_encode; use function substr; /** @@ -56,26 +57,27 @@ protected function parseRData(string $message, int $rdataOffset): void // // copy the certificate // - $this->certificate = substr($this->rData, 5, $this->rdLength - 5); + $this->certificate = base64_encode(substr($this->rData, 5, $this->rdLength - 5)); } - public function getFormat(): ?int + public function getFormat(): int { - return $this->format??null; + return $this->format; } - public function getKeyTag(): ?int + public function getKeyTag(): int { - return $this->keyTag??null; + return $this->keyTag; } - public function getAlgorithm(): ?string + public function getAlgorithm(): string { - return $this->algorithm??null; + return $this->algorithm; } - public function getCertificate(): ?string + public function getCertificate(): string { - return $this->certificate??null; + return $this->certificate; } } +// @todo add toArray() diff --git a/src/ResourceRecord/RRTypes/CNAME.php b/src/ResourceRecord/RRTypes/CNAME.php index dbfc4ad..4cce7c3 100644 --- a/src/ResourceRecord/RRTypes/CNAME.php +++ b/src/ResourceRecord/RRTypes/CNAME.php @@ -20,18 +20,25 @@ class CNAME extends AbstractResourceRecordType { const TYPE = 'CNAME'; - protected string $cname; - /** * @inheritdoc */ protected function parseRData(string $message, int $rdataOffset): void { - $this->cname = Buffer::readLabel($message, $rdataOffset); + $this->value = Buffer::readLabel($message, $rdataOffset); } - public function getCname(): string + /** + * @inheritdoc + */ + public function toArray(): array { - return $this->cname; + return [ + 'host' => $this->getName(), + 'class' => $this->getClass()->getName(), + 'ttl' => $this->getTTL(), + 'type' => $this->getType()->getName(), + 'target' => $this->getValue(), + ]; } } diff --git a/src/ResourceRecord/RRTypes/DNSKEY.php b/src/ResourceRecord/RRTypes/DNSKEY.php index 4633e6b..d44683b 100644 --- a/src/ResourceRecord/RRTypes/DNSKEY.php +++ b/src/ResourceRecord/RRTypes/DNSKEY.php @@ -113,3 +113,4 @@ protected function parseRData(string $message, int $rdataOffset): void $this->zoneSep = ((int)$flags[15]) === 1; } } +// @todo add toArray() diff --git a/src/ResourceRecord/RRTypes/HINFO.php b/src/ResourceRecord/RRTypes/HINFO.php index 80d649c..0038c7e 100644 --- a/src/ResourceRecord/RRTypes/HINFO.php +++ b/src/ResourceRecord/RRTypes/HINFO.php @@ -40,4 +40,29 @@ protected function parseRData(string $message, int $rdataOffset): void $this->rdLength - strlen($this->cpu) ); } + + public function getCpu(): string + { + return $this->cpu; + } + + public function getOs(): string + { + return $this->os; + } + + /** + * @inheritdoc + */ + public function toArray(): array + { + return [ + 'host' => $this->getName(), + 'class' => $this->getClass()->getName(), + 'ttl' => $this->getTTL(), + 'type' => $this->getType()->getName(), + 'cpu' => $this->getCpu(), + 'os' => $this->getOs(), + ]; + } } diff --git a/src/ResourceRecord/RRTypes/IXFR.php b/src/ResourceRecord/RRTypes/IXFR.php new file mode 100644 index 0000000..e84e065 --- /dev/null +++ b/src/ResourceRecord/RRTypes/IXFR.php @@ -0,0 +1,49 @@ + | + * +---------------------------------------------------+ + * Authority | JAIN.AD.JP. IN SOA serial=1 | + * +---------------------------------------------------+ + * Additional | | + * +---------------------------------------------------+ + * + * IXFR Response Format + * + * +---------------------------------------------------+ + * Header | OPCODE=SQUERY, RESPONSE | + * +---------------------------------------------------+ + * Question | QNAME=JAIN.AD.JP., QCLASS=IN, QTYPE=IXFR | + * +---------------------------------------------------+ + * Answer | JAIN.AD.JP. IN SOA serial=3 | + * | JAIN.AD.JP. IN NS NS.JAIN.AD.JP. | + * | NS.JAIN.AD.JP. IN A 133.69.136.1 | + * | JAIN-BB.JAIN.AD.JP. IN A 133.69.136.3 | + * | JAIN-BB.JAIN.AD.JP. IN A 192.41.197.2 | + * | JAIN.AD.JP. IN SOA serial=3 | + * +---------------------------------------------------+ + * Authority | | + * +---------------------------------------------------+ + * Additional | | + * +---------------------------------------------------+ + * + * @link https://datatracker.ietf.org/doc/html/rfc1995#section-7 + */ +class IXFR extends AbstractResourceRecordType implements ResourceRecordMetaTypeInterface +{ + const TYPE = 'IXFR'; +} +// @todo add completion() diff --git a/src/ResourceRecord/RRTypes/MG.php b/src/ResourceRecord/RRTypes/MG.php index 07bb76e..1967893 100644 --- a/src/ResourceRecord/RRTypes/MG.php +++ b/src/ResourceRecord/RRTypes/MG.php @@ -31,3 +31,4 @@ protected function parseRData(string $message, int $rdataOffset): void $this->value = Buffer::readLabel($message, $rdataOffset); } } +// @todo add toArray() diff --git a/src/ResourceRecord/RRTypes/MR.php b/src/ResourceRecord/RRTypes/MR.php index e5d361f..ac22d21 100644 --- a/src/ResourceRecord/RRTypes/MR.php +++ b/src/ResourceRecord/RRTypes/MR.php @@ -31,3 +31,4 @@ protected function parseRData(string $message, int $rdataOffset): void $this->value = Buffer::readLabel($message, $rdataOffset); } } +// @todo add toArray() diff --git a/src/ResourceRecord/RRTypes/MX.php b/src/ResourceRecord/RRTypes/MX.php index e4d84ac..14bdc10 100644 --- a/src/ResourceRecord/RRTypes/MX.php +++ b/src/ResourceRecord/RRTypes/MX.php @@ -55,4 +55,19 @@ public function getExchange(): string { return $this->exchange; } + + /** + * @inheritdoc + */ + public function toArray(): array + { + return [ + 'host' => $this->getName(), + 'class' => $this->getClass()->getName(), + 'ttl' => $this->getTTL(), + 'type' => $this->getType()->getName(), + 'pri' => $this->getPreference(), + 'target' => $this->getExchange(), + ]; + } } diff --git a/src/ResourceRecord/RRTypes/NS.php b/src/ResourceRecord/RRTypes/NS.php index ca03dbf..c3c9493 100644 --- a/src/ResourceRecord/RRTypes/NS.php +++ b/src/ResourceRecord/RRTypes/NS.php @@ -30,4 +30,18 @@ protected function parseRData(string $message, int $rdataOffset): void { $this->value = Buffer::readLabel($message, $rdataOffset); } + + /** + * @inheritdoc + */ + public function toArray(): array + { + return [ + 'host' => $this->getName(), + 'class' => $this->getClass()->getName(), + 'ttl' => $this->getTTL(), + 'type' => $this->getType()->getName(), + 'target' => $this->getValue(), + ]; + } } diff --git a/src/ResourceRecord/RRTypes/OPT.php b/src/ResourceRecord/RRTypes/OPT.php index 50bdfc8..f3aa9e6 100644 --- a/src/ResourceRecord/RRTypes/OPT.php +++ b/src/ResourceRecord/RRTypes/OPT.php @@ -115,7 +115,7 @@ public function getQueryMessage(): string // build the TTL value based on the local values // return pack( - 'CCCC', + 'C4', $this->extended_rcode, $this->version, ($this->do << 7), @@ -123,3 +123,4 @@ public function getQueryMessage(): string ); } } +// @todo add toArray() diff --git a/src/ResourceRecord/RRTypes/PTR.php b/src/ResourceRecord/RRTypes/PTR.php index 3768a10..34b821c 100644 --- a/src/ResourceRecord/RRTypes/PTR.php +++ b/src/ResourceRecord/RRTypes/PTR.php @@ -29,4 +29,18 @@ protected function parseRData(string $message, int $rdataOffset): void // read domain name space $this->value = Buffer::readLabel($message, $rdataOffset); } + + /** + * @inheritdoc + */ + public function toArray(): array + { + return [ + 'host' => $this->getName(), + 'class' => $this->getClass()->getName(), + 'ttl' => $this->getTTL(), + 'type' => $this->getType()->getName(), + 'target' => $this->getValue(), + ]; + } } diff --git a/src/ResourceRecord/RRTypes/RRSIG.php b/src/ResourceRecord/RRTypes/RRSIG.php index 22bedc2..389f166 100644 --- a/src/ResourceRecord/RRTypes/RRSIG.php +++ b/src/ResourceRecord/RRTypes/RRSIG.php @@ -14,12 +14,19 @@ class RRSIG extends AbstractResourceRecordType const TYPE = 'RRSIG'; protected int $sigType; + protected int $algorithm; + protected int $labels; + protected int $originalttl; + protected int $expiration; + protected int $inception; + protected int $keyTag; + protected string $signer; protected string $signature; @@ -30,7 +37,6 @@ class RRSIG extends AbstractResourceRecordType protected function parseRData(string $message, int $rdataOffset): void { $stuff = Buffer::read($message, $rdataOffset, 18); - //$length = $ans_header['length'] - 18; [ 'type' => $this->sigType, 'algorithm' => $this->algorithm, @@ -49,11 +55,6 @@ protected function parseRData(string $message, int $rdataOffset): void ); } - public function getType(): string - { - return $this->type; - } - public function getSigType(): int { return $this->sigType; @@ -98,4 +99,22 @@ public function getSignature(): string { return $this->signature; } + public function toArray(): array + { + return [ + 'host' => $this->getName(), + 'ttl' => $this->getTTL(), + 'class' => $this->getClass()->getName(), + 'type' => $this->getType()->getName(), + 'labels' => $this->getLabels(), + 'sigtype' => $this->getSigType(), + 'originalttl' => $this->getOriginalttl(), + 'expiration' => $this->getExpiration(), + 'inception' => $this->getInception(), + 'keytag' => $this->getKeyTag(), + 'algorithm' => $this->getAlgorithm(), + 'signer' => $this->getSigner(), + 'signature' => $this->getSignature(), + ]; + } } diff --git a/src/ResourceRecord/RRTypes/SOA.php b/src/ResourceRecord/RRTypes/SOA.php index a31da96..f1a9bcc 100644 --- a/src/ResourceRecord/RRTypes/SOA.php +++ b/src/ResourceRecord/RRTypes/SOA.php @@ -43,6 +43,7 @@ protected function parseRData($message, int $rdataOffset): void "Nserial/Nrefresh/Nretry/Nexpire/NminTTL", Buffer::read($message, $rdataOffset, 20) ); + $this->value = sprintf( '%s. %s. %d %d %d %d %d', $this->mName, @@ -55,25 +56,6 @@ protected function parseRData($message, int $rdataOffset): void ); } - /** - * @return string - * @link https://datatracker.ietf.org/doc/rfc1995/ - */ -// public function getQueryMessage(): string -// { -// $query = Lookup::compressLabel($this->mName); -// $query .= Lookup::compressLabel($this->rName); -// $query .= pack('N*', $this->serial, $this->refresh, $this->retry, $this->expire, $this->minimumTTL); -// return $query; -// return Lookup::compressLabel( -// sprintf( -// '%s. IN SOA serial=%d', -// $this->name, -// $this->serial -// ) -// ); -// } - public function getMinimumTTL(): int { return $this->minimumTTL; @@ -108,4 +90,24 @@ public function getRetry(): int { return $this->retry; } + + /** + * @inheritdoc + */ + public function toArray(): array + { + return [ + 'host' => $this->getName(), + 'class' => $this->getClass()->getName(), + 'ttl' => $this->getTTL(), + 'type' => $this->getType()->getName(), + 'mname' => $this->getMName(), + 'rname' => $this->getRName(), + 'serial' => $this->getSerial(), + 'refresh' => $this->getRefresh(), + 'retry' => $this->getRetry(), + 'expire' => $this->getExpire(), + 'minimum-ttl' => $this->getMinimumTTL(), + ]; + } } diff --git a/src/ResourceRecord/RRTypes/SRV.php b/src/ResourceRecord/RRTypes/SRV.php index be46371..36dfe87 100644 --- a/src/ResourceRecord/RRTypes/SRV.php +++ b/src/ResourceRecord/RRTypes/SRV.php @@ -21,8 +21,6 @@ class SRV extends AbstractResourceRecordType protected int $port; - protected string $target; - /** * @inheritdoc */ @@ -38,6 +36,35 @@ protected function parseRData($message, int $rdataOffset): void Buffer::read($this->rData, $offset, 6) ); - $this->target = Buffer::readLabel($this->rData, $offset); + $this->value = Buffer::readLabel($this->rData, $offset); + } + + public function getPriority(): int + { + return $this->priority; + } + + public function getWeight(): int + { + return $this->weight; + } + + public function getPort(): int + { + return $this->port; + } + + public function toArray(): array + { + return [ + 'host' => $this->getName(), + 'class' => $this->getClass()->getName(), + 'ttl' => $this->getTTL(), + 'type' => $this->getType()->getName(), + 'pri' => $this->getPriority(), + 'weight' => $this->getWeight(), + 'port' => $this->getPort(), + 'target' => $this->getValue(), + ]; } } diff --git a/src/Utils/Buffer.php b/src/Utils/Buffer.php index 2375c4f..01d3830 100644 --- a/src/Utils/Buffer.php +++ b/src/Utils/Buffer.php @@ -53,21 +53,33 @@ public static function read(string $buffer, int &$offset, int $length): string public static function readLabel(string $buffer, int &$offset, string $delimiter = '.'): string { + $bufferLength = strlen($buffer); + if ($bufferLength <= $offset) { + return ''; + } $out = []; - while (($length = ord(self::read($buffer, $offset, 1))) > 0) { - if ($length < 64) { - $out[] = self::read($buffer, $offset, $length); - continue; + while (true) { + if ($bufferLength <= $offset) { + break; } - // 0x3f = 63 - $currentPosition = (($length & 63) << 8) + ord(self::read($buffer, $offset, 1)); - while (($len = ord(substr($buffer, $currentPosition, 1))) && $len > 0) { - $out[] = substr($buffer, $currentPosition + 1, $len); - $currentPosition += $len + 1; + $length = ord($buffer[$offset]); + if ($length === 0) { + ++$offset; + break; + } elseif (($length & 0xc0) === 0xc0) { + $pointer = ord($buffer[$offset]) << 8 | ord($buffer[$offset+1]); + $pointer = $pointer & 0x3fff; + $name2 = self::readLabel($buffer, $pointer); + $out[] = $name2; + $offset += 2; + break; + } else { + ++$offset; + $elem = substr($buffer, $offset, $length); + $out[] = $elem; + $offset += $length; } - break; } - return implode($delimiter, $out); }