diff --git a/src/Protocol/Parser.php b/src/Protocol/Parser.php index 968bb022..011a6e7f 100644 --- a/src/Protocol/Parser.php +++ b/src/Protocol/Parser.php @@ -23,29 +23,28 @@ final class Parser */ public function parseMessage($data) { - // create empty message with two additional, temporary properties for parser - $message = new Message(); - $message->data = $data; - $message->consumed = null; - - if ($this->parse($data, $message) !== $message) { + $message = $this->parse($data, 0); + if ($message === null) { throw new InvalidArgumentException('Unable to parse binary message'); } - unset($message->data, $message->consumed); - return $message; } - private function parse($data, Message $message) + /** + * @param string $data + * @param int $consumed + * @return ?Message + */ + private function parse($data, $consumed) { - if (!isset($message->data[12 - 1])) { - return; + if (!isset($data[12 - 1])) { + return null; } - list($id, $fields, $qdCount, $anCount, $nsCount, $arCount) = array_values(unpack('n*', substr($message->data, 0, 12))); - $message->consumed += 12; + list($id, $fields, $qdCount, $anCount, $nsCount, $arCount) = array_values(unpack('n*', substr($data, 0, 12))); + $message = new Message(); $message->id = $id; $message->rcode = $fields & 0xf; $message->ra = (($fields >> 7) & 1) === 1; @@ -54,12 +53,13 @@ private function parse($data, Message $message) $message->aa = (($fields >> 10) & 1) === 1; $message->opcode = ($fields >> 11) & 0xf; $message->qr = (($fields >> 15) & 1) === 1; + $consumed += 12; // parse all questions for ($i = $qdCount; $i > 0; --$i) { - $question = $this->parseQuestion($message); + list($question, $consumed) = $this->parseQuestion($data, $consumed); if ($question === null) { - return; + return null; } else { $message->questions[] = $question; } @@ -67,9 +67,9 @@ private function parse($data, Message $message) // parse all answer records for ($i = $anCount; $i > 0; --$i) { - $record = $this->parseRecord($message); + list($record, $consumed) = $this->parseRecord($data, $consumed); if ($record === null) { - return; + return null; } else { $message->answers[] = $record; } @@ -77,9 +77,9 @@ private function parse($data, Message $message) // parse all authority records for ($i = $nsCount; $i > 0; --$i) { - $record = $this->parseRecord($message); + list($record, $consumed) = $this->parseRecord($data, $consumed); if ($record === null) { - return; + return null; } else { $message->authority[] = $record; } @@ -87,9 +87,9 @@ private function parse($data, Message $message) // parse all additional records for ($i = $arCount; $i > 0; --$i) { - $record = $this->parseRecord($message); + list($record, $consumed) = $this->parseRecord($data, $consumed); if ($record === null) { - return; + return null; } else { $message->additional[] = $record; } @@ -99,49 +99,48 @@ private function parse($data, Message $message) } /** - * @param Message $message - * @return ?Query + * @param string $data + * @param int $consumed + * @return array */ - private function parseQuestion(Message $message) + private function parseQuestion($data, $consumed) { - $consumed = $message->consumed; - - list($labels, $consumed) = $this->readLabels($message->data, $consumed); + list($labels, $consumed) = $this->readLabels($data, $consumed); - if ($labels === null || !isset($message->data[$consumed + 4 - 1])) { - return; + if ($labels === null || !isset($data[$consumed + 4 - 1])) { + return array(null, null); } - list($type, $class) = array_values(unpack('n*', substr($message->data, $consumed, 4))); + list($type, $class) = array_values(unpack('n*', substr($data, $consumed, 4))); $consumed += 4; - $message->consumed = $consumed; - - return new Query( - implode('.', $labels), - $type, - $class + return array( + new Query( + implode('.', $labels), + $type, + $class + ), + $consumed ); } /** - * @param Message $message - * @return ?Record returns parsed Record on success or null if data is invalid/incomplete + * @param string $data + * @param int $consumed + * @return array An array with a parsed Record on success or array with null if data is invalid/incomplete */ - private function parseRecord(Message $message) + private function parseRecord($data, $consumed) { - $consumed = $message->consumed; - - list($name, $consumed) = $this->readDomain($message->data, $consumed); + list($name, $consumed) = $this->readDomain($data, $consumed); - if ($name === null || !isset($message->data[$consumed + 10 - 1])) { - return null; + if ($name === null || !isset($data[$consumed + 10 - 1])) { + return array(null, null); } - list($type, $class) = array_values(unpack('n*', substr($message->data, $consumed, 4))); + list($type, $class) = array_values(unpack('n*', substr($data, $consumed, 4))); $consumed += 4; - list($ttl) = array_values(unpack('N', substr($message->data, $consumed, 4))); + list($ttl) = array_values(unpack('N', substr($data, $consumed, 4))); $consumed += 4; // TTL is a UINT32 that must not have most significant bit set for BC reasons @@ -149,11 +148,11 @@ private function parseRecord(Message $message) $ttl = 0; } - list($rdLength) = array_values(unpack('n', substr($message->data, $consumed, 2))); + list($rdLength) = array_values(unpack('n', substr($data, $consumed, 2))); $consumed += 2; - if (!isset($message->data[$consumed + $rdLength - 1])) { - return null; + if (!isset($data[$consumed + $rdLength - 1])) { + return array(null, null); } $rdata = null; @@ -161,27 +160,27 @@ private function parseRecord(Message $message) if (Message::TYPE_A === $type) { if ($rdLength === 4) { - $rdata = inet_ntop(substr($message->data, $consumed, $rdLength)); + $rdata = inet_ntop(substr($data, $consumed, $rdLength)); $consumed += $rdLength; } } elseif (Message::TYPE_AAAA === $type) { if ($rdLength === 16) { - $rdata = inet_ntop(substr($message->data, $consumed, $rdLength)); + $rdata = inet_ntop(substr($data, $consumed, $rdLength)); $consumed += $rdLength; } } elseif (Message::TYPE_CNAME === $type || Message::TYPE_PTR === $type || Message::TYPE_NS === $type) { - list($rdata, $consumed) = $this->readDomain($message->data, $consumed); + list($rdata, $consumed) = $this->readDomain($data, $consumed); } elseif (Message::TYPE_TXT === $type || Message::TYPE_SPF === $type) { $rdata = array(); while ($consumed < $expected) { - $len = ord($message->data[$consumed]); - $rdata[] = (string)substr($message->data, $consumed + 1, $len); + $len = ord($data[$consumed]); + $rdata[] = (string)substr($data, $consumed + 1, $len); $consumed += $len + 1; } } elseif (Message::TYPE_MX === $type) { if ($rdLength > 2) { - list($priority) = array_values(unpack('n', substr($message->data, $consumed, 2))); - list($target, $consumed) = $this->readDomain($message->data, $consumed + 2); + list($priority) = array_values(unpack('n', substr($data, $consumed, 2))); + list($target, $consumed) = $this->readDomain($data, $consumed + 2); $rdata = array( 'priority' => $priority, @@ -190,8 +189,8 @@ private function parseRecord(Message $message) } } elseif (Message::TYPE_SRV === $type) { if ($rdLength > 6) { - list($priority, $weight, $port) = array_values(unpack('n*', substr($message->data, $consumed, 6))); - list($target, $consumed) = $this->readDomain($message->data, $consumed + 6); + list($priority, $weight, $port) = array_values(unpack('n*', substr($data, $consumed, 6))); + list($target, $consumed) = $this->readDomain($data, $consumed + 6); $rdata = array( 'priority' => $priority, @@ -202,8 +201,8 @@ private function parseRecord(Message $message) } } elseif (Message::TYPE_SSHFP === $type) { if ($rdLength > 2) { - list($algorithm, $hash) = \array_values(\unpack('C*', \substr($message->data, $consumed, 2))); - $fingerprint = \bin2hex(\substr($message->data, $consumed + 2, $rdLength - 2)); + list($algorithm, $hash) = \array_values(\unpack('C*', \substr($data, $consumed, 2))); + $fingerprint = \bin2hex(\substr($data, $consumed + 2, $rdLength - 2)); $consumed += $rdLength; $rdata = array( @@ -213,11 +212,11 @@ private function parseRecord(Message $message) ); } } elseif (Message::TYPE_SOA === $type) { - list($mname, $consumed) = $this->readDomain($message->data, $consumed); - list($rname, $consumed) = $this->readDomain($message->data, $consumed); + list($mname, $consumed) = $this->readDomain($data, $consumed); + list($rname, $consumed) = $this->readDomain($data, $consumed); - if ($mname !== null && $rname !== null && isset($message->data[$consumed + 20 - 1])) { - list($serial, $refresh, $retry, $expire, $minimum) = array_values(unpack('N*', substr($message->data, $consumed, 20))); + if ($mname !== null && $rname !== null && isset($data[$consumed + 20 - 1])) { + list($serial, $refresh, $retry, $expire, $minimum) = array_values(unpack('N*', substr($data, $consumed, 20))); $consumed += 20; $rdata = array( @@ -232,9 +231,9 @@ private function parseRecord(Message $message) } } elseif (Message::TYPE_OPT === $type) { $rdata = array(); - while (isset($message->data[$consumed + 4 - 1])) { - list($code, $length) = array_values(unpack('n*', substr($message->data, $consumed, 4))); - $value = (string) substr($message->data, $consumed + 4, $length); + while (isset($data[$consumed + 4 - 1])) { + list($code, $length) = array_values(unpack('n*', substr($data, $consumed, 4))); + $value = (string) substr($data, $consumed + 4, $length); if ($code === Message::OPT_TCP_KEEPALIVE && $value === '') { $value = null; } elseif ($code === Message::OPT_TCP_KEEPALIVE && $length === 2) { @@ -248,11 +247,11 @@ private function parseRecord(Message $message) } } elseif (Message::TYPE_CAA === $type) { if ($rdLength > 3) { - list($flag, $tagLength) = array_values(unpack('C*', substr($message->data, $consumed, 2))); + list($flag, $tagLength) = array_values(unpack('C*', substr($data, $consumed, 2))); if ($tagLength > 0 && $rdLength - 2 - $tagLength > 0) { - $tag = substr($message->data, $consumed + 2, $tagLength); - $value = substr($message->data, $consumed + 2 + $tagLength, $rdLength - 2 - $tagLength); + $tag = substr($data, $consumed + 2, $tagLength); + $value = substr($data, $consumed + 2 + $tagLength, $rdLength - 2 - $tagLength); $consumed += $rdLength; $rdata = array( @@ -264,18 +263,19 @@ private function parseRecord(Message $message) } } else { // unknown types simply parse rdata as an opaque binary string - $rdata = substr($message->data, $consumed, $rdLength); + $rdata = substr($data, $consumed, $rdLength); $consumed += $rdLength; } // ensure parsing record data consumes expact number of bytes indicated in record length if ($consumed !== $expected || $rdata === null) { - return null; + return array(null, null); } - $message->consumed = $consumed; - - return new Record($name, $type, $class, $ttl, $rdata); + return array( + new Record($name, $type, $class, $ttl, $rdata), + $consumed + ); } private function readDomain($data, $consumed)