<?php

// spl_autoload_register('Net_DNS2::autoload');

class Net_DNS2
{
    const VERSION = '1.4.2';
    const RESOLV_CONF = '/etc/resolv.conf';
    public $use_resolv_options = false;
    public $use_tcp = false;
    public $dns_port = 53;
    public $local_host = '';
    public $local_port = 0;
    public $timeout = 5;
    public $ns_random = false;
    public $domain = '';
    public $search_list = array();
    public $cache_type = 'none';
    public $cache_file = '/tmp/net_dns2.cache';
    public $cache_size = 50000;
    public $cache_serializer = 'serialize';
    public $strict_query_mode = false;
    public $recurse = true;
    public $dnssec = false;
    public $dnssec_ad_flag = false;
    public $dnssec_cd_flag = false;
    public $dnssec_payload_size = 4000;
    public $last_exception = null;
    public $last_exception_list = array();
    public $nameservers = array();
    protected $sock = array('udp' => array(), 'tcp' => array());
    protected $sockets_enabled = false;
    protected $auth_signature = null;
    protected $cache = null;
    protected $use_cache = false;

    public function __construct(array $options = null)
    {
        if (extension_loaded('sockets') && (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')) {
            $this->sockets_enabled = true;
        }

        if (!empty($options)) {
            foreach ($options as $key => $value) {
                if ($key == 'nameservers') {
                    $this->setServers($value);
                } else {
                    $this->$key = $value;
                }
            }
        }

        switch ($this->cache_type) {
            case 'shared':
                if (extension_loaded('shmop')) {
                    $this->cache = new Net_DNS2_Cache_Shm;
                    $this->use_cache = true;
                } else {
                    throw new Net_DNS2_Exception(
                        'shmop library is not available for cache',
                        Net_DNS2_Lookups::E_CACHE_SHM_UNAVAIL
                    );
                }
                break;
            case 'file':
                $this->cache = new Net_DNS2_Cache_File;
                $this->use_cache = true;
                break;
            case 'none':
                $this->use_cache = false;
                break;
            default:
                throw new Net_DNS2_Exception(
                    'un-supported cache type: ' . $this->cache_type,
                    Net_DNS2_Lookups::E_CACHE_UNSUPPORTED
                );
        }
    }

    public function setServers($nameservers)
    {
        if (is_array($nameservers)) {
            $this->nameservers = $nameservers;
        } else {
            $ns = array();

            if (is_readable($nameservers) === true) {
                $data = file_get_contents($nameservers);
                if ($data === false) {
                    throw new Net_DNS2_Exception(
                        'failed to read contents of file: ' . $nameservers,
                        Net_DNS2_Lookups::E_NS_INVALID_FILE
                    );
                }

                $lines = explode("\n", $data);

                foreach ($lines as $line) {
                    $line = trim($line);

                    if ((strlen($line) == 0)
                        || ($line[0] == '#')
                        || ($line[0] == ';')
                    ) {
                        continue;
                    }

                    if (!str_contains($line, ' ')) {
                        continue;
                    }

                    list($key, $value) = preg_split('/\s+/', $line, 2);

                    $key = trim(strtolower($key));
                    $value = trim(strtolower($value));

                    switch ($key) {
                        case 'nameserver':
                            if (self::isIPv4($value)
                                || self::isIPv6($value)
                            ) {
                                $ns[] = $value;
                            } else {
                                throw new Net_DNS2_Exception(
                                    'invalid nameserver entry: ' . $value,
                                    Net_DNS2_Lookups::E_NS_INVALID_ENTRY
                                );
                            }
                            break;

                        case 'domain':
                            $this->domain = $value;
                            break;

                        case 'search':
                            $this->search_list = preg_split('/\s+/', $value);
                            break;

                        case 'options':
                            $this->parseOptions($value);
                            break;

                        default:
                    }
                }

                if ((strlen($this->domain) == 0)
                    && (count($this->search_list) > 0)
                ) {
                    $this->domain = $this->search_list[0];
                }

            } else {
                throw new Net_DNS2_Exception(
                    'resolver file file provided is not readable: ' . $nameservers,
                    Net_DNS2_Lookups::E_NS_INVALID_FILE
                );
            }

            if (count($ns) > 0) {
                $this->nameservers = $ns;
            }
        }

        $this->nameservers = array_unique($this->nameservers);
        $this->checkServers();

        return true;
    }

    public static function isIPv4($_address): bool
    {
        if (extension_loaded('filter')) {
            if (!filter_var($_address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
                return false;
            }
        } else {
            if (inet_pton($_address) === false) {
                return false;
            }

            if (preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $_address) == 0) {
                return false;
            }
        }

        return true;
    }

    public static function isIPv6($_address): bool
    {
        if (extension_loaded('filter')) {
            if (!filter_var($_address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
                return false;
            }
        } else {
            if (inet_pton($_address) === false) {
                return false;
            }

            if (preg_match('/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/', $_address) == 1) {
                return false;
            }
        }

        return true;
    }

    private function parseOptions($value)
    {
        if (!$this->use_resolv_options || (strlen($value) == 0)) {
            return true;
        }

        $options = preg_split('/\s+/', strtolower($value));

        foreach ($options as $option) {

            if ((strncmp($option, 'timeout', 7) == 0) && (str_contains($option, ':'))) {

                list($key, $val) = explode(':', $option);

                if (($val > 0) && ($val <= 30)) {
                    $this->timeout = $val;
                }

            } else if (strncmp($option, 'rotate', 6) == 0) {
                $this->ns_random = true;
            }
        }

        return true;
    }

    protected function checkServers($default = null)
    {
        if (empty($this->nameservers)) {

            if (isset($default)) {
                $this->setServers($default);
            } else {
                throw new Net_DNS2_Exception(
                    'empty name servers list; you must provide a list of name ' .
                    'servers, or the path to a resolv.conf file.',
                    Net_DNS2_Lookups::E_NS_INVALID_ENTRY
                );
            }
        }

        return true;
    }

    static public function autoload($name)
    {
        if (strncmp($name, 'Net_DNS2', 8) == 0) {
            include str_replace('_', '/', $name) . '.php';
        }
    }

    public static function expandUint32($_int)
    {
        if (($_int < 0) && (PHP_INT_MAX == 2147483647)) {
            return sprintf('%u', $_int);
        } else {
            return $_int;
        }
    }

    public static function expandIPv6($_address)
    {
        if (str_contains($_address, '::')) {

            $part = explode('::', $_address);
            $part[0] = explode(':', $part[0]);
            $part[1] = explode(':', $part[1]);

            $missing = array();

            $x = (8 - (count($part[0]) + count($part[1])));
            for ($i = 0; $i < $x; $i++) {

                $missing[] = '0000';
            }

            $missing = array_merge($part[0], $missing);
            $part = array_merge($missing, $part[1]);

        } else {
            $part = explode(':', $_address);
        }

        foreach ($part as &$p) {
            while (strlen($p) < 4) {
                $p = '0' . $p;
            }
        }

        unset($p);

        $result = implode(':', $part);

        if (strlen($result) == 39) {
            return $result;
        } else {
            return false;
        }
    }

    public function signTSIG(
        $keyname, $signature = '', $algorithm = Net_DNS2_RR_TSIG::HMAC_MD5
    )
    {
        if ($keyname instanceof Net_DNS2_RR_TSIG) {
            $this->auth_signature = $keyname;
        } else {
            $this->auth_signature = Net_DNS2_RR::fromString(
                strtolower(trim($keyname)) .
                ' TSIG ' . $signature
            );

            $this->auth_signature->algorithm = $algorithm;
        }

        return true;
    }

    public function signSIG0($filename)
    {
        if (extension_loaded('openssl') === false) {
            throw new Net_DNS2_Exception(
                'the OpenSSL extension is required to use SIG(0).',
                Net_DNS2_Lookups::E_OPENSSL_UNAVAIL
            );
        }

        if ($filename instanceof Net_DNS2_RR_SIG) {
            $this->auth_signature = $filename;
        } else {
            $private = new Net_DNS2_PrivateKey($filename);
            $this->auth_signature = new Net_DNS2_RR_SIG();
            $this->auth_signature->name = $private->signname;
            $this->auth_signature->ttl = 0;
            $this->auth_signature->class = 'ANY';

            $this->auth_signature->algorithm = $private->algorithm;
            $this->auth_signature->keytag = $private->keytag;
            $this->auth_signature->signname = $private->signname;

            $this->auth_signature->typecovered = 'SIG0';
            $this->auth_signature->labels = 0;
            $this->auth_signature->origttl = 0;

            $t = time();

            $this->auth_signature->sigincep = gmdate('YmdHis', $t);
            $this->auth_signature->sigexp = gmdate('YmdHis', $t + 500);

            $this->auth_signature->private_key = $private;
        }

        switch ($this->auth_signature->algorithm) {
            case Net_DNS2_Lookups::DNSSEC_ALGORITHM_RSAMD5:
            case Net_DNS2_Lookups::DNSSEC_ALGORITHM_RSASHA1:
            case Net_DNS2_Lookups::DNSSEC_ALGORITHM_RSASHA256:
            case Net_DNS2_Lookups::DNSSEC_ALGORITHM_RSASHA512:
            case Net_DNS2_Lookups::DNSSEC_ALGORITHM_DSA:
                break;
            default:
                throw new Net_DNS2_Exception(
                    'only asymmetric algorithms work with SIG(0)!',
                    Net_DNS2_Lookups::E_OPENSSL_INV_ALGO
                );
        }

        return true;
    }

    public function cacheable($_type): bool
    {
        switch ($_type) {
            case 'AXFR':
            case 'OPT':
                return false;
        }

        return true;
    }

    protected function sendPacket(Net_DNS2_Packet $request, $use_tcp)
    {
        $data = $request->get();
        if (strlen($data) < Net_DNS2_Lookups::DNS_HEADER_SIZE) {
            throw new Net_DNS2_Exception(
                'invalid or empty packet for sending!',
                Net_DNS2_Lookups::E_PACKET_INVALID,
                null,
                $request
            );
        }

        reset($this->nameservers);

        if ($this->ns_random) {
            shuffle($this->nameservers);
        }

        $response = null;

        foreach ($this->nameservers as $key => $ns) {

            if ($key === false) {
                if (!is_null($this->last_exception)) {
                    throw $this->last_exception;
                } else {
                    throw new Net_DNS2_Exception(
                        'every name server provided has failed',
                        Net_DNS2_Lookups::E_NS_FAILED
                    );
                }
            }

            $max_udp_size = Net_DNS2_Lookups::DNS_MAX_UDP_SIZE;
            if ($this->dnssec) {
                $max_udp_size = $this->dnssec_payload_size;
            }

            if ($use_tcp || (strlen($data) > $max_udp_size)) {

                try {
                    $response = $this->sendTCPRequest($ns, $data, $request->question[0]->qtype == 'AXFR');

                } catch (Net_DNS2_Exception $e) {

                    $this->last_exception = $e;
                    $this->last_exception_list[$ns] = $e;

                    continue;
                }

            } else {

                try {
                    $response = $this->sendUDPRequest($ns, $data);

                    if ($response->header->tc == 1) {
                        $response = $this->sendTCPRequest($ns, $data);
                    }

                } catch (Net_DNS2_Exception $e) {

                    $this->last_exception = $e;
                    $this->last_exception_list[$ns] = $e;

                    continue;
                }
            }

            if ($request->header->id != $response->header->id) {

                $this->last_exception = new Net_DNS2_Exception(

                    'invalid header: the request and response id do not match.',
                    Net_DNS2_Lookups::E_HEADER_INVALID,
                    null,
                    $request,
                    $response
                );

                $this->last_exception_list[$ns] = $this->last_exception;
                continue;
            }

            if ($response->header->qr != Net_DNS2_Lookups::QR_RESPONSE) {

                $this->last_exception = new Net_DNS2_Exception(

                    'invalid header: the response provided is not a response packet.',
                    Net_DNS2_Lookups::E_HEADER_INVALID,
                    null,
                    $request,
                    $response
                );

                $this->last_exception_list[$ns] = $this->last_exception;
                continue;
            }

            if ($response->header->rcode != Net_DNS2_Lookups::RCODE_NOERROR) {

                $this->last_exception = new Net_DNS2_Exception(

                    'DNS request failed: ' .
                    Net_DNS2_Lookups::$result_code_messages[$response->header->rcode],
                    $response->header->rcode,
                    null,
                    $request,
                    $response
                );

                $this->last_exception_list[$ns] = $this->last_exception;
                continue;
            }

            break;
        }

        return $response;
    }

    private function sendTCPRequest($_ns, $_data, $_axfr = false)
    {
        $start_time = microtime(true);
        if ((!isset($this->sock['tcp'][$_ns]))
            || (!($this->sock['tcp'][$_ns] instanceof Net_DNS2_Socket))
        ) {

            if ($this->sockets_enabled === true) {

                $this->sock['tcp'][$_ns] = new Net_DNS2_Socket_Sockets(
                    Net_DNS2_Socket::SOCK_STREAM, $_ns, $this->dns_port, $this->timeout
                );

            } else {

                $this->sock['tcp'][$_ns] = new Net_DNS2_Socket_Streams(
                    Net_DNS2_Socket::SOCK_STREAM, $_ns, $this->dns_port, $this->timeout
                );
            }

            if (strlen($this->local_host) > 0) {

                $this->sock['tcp'][$_ns]->bindAddress(
                    $this->local_host, $this->local_port
                );
            }

            if ($this->sock['tcp'][$_ns]->open() === false) {

                throw new Net_DNS2_Exception(
                    $this->sock['tcp'][$_ns]->last_error, Net_DNS2_Lookups::E_NS_SOCKET_FAILED
                );
            }
        }

        if ($this->sock['tcp'][$_ns]->write($_data) === false) {

            throw new Net_DNS2_Exception(
                $this->sock['tcp'][$_ns]->last_error, Net_DNS2_Lookups::E_NS_SOCKET_FAILED
            );
        }

        $size = 0;
        $response = null;

        if ($_axfr) {

            $soa_count = 0;

            while (1) {

                $result = $this->sock['tcp'][$_ns]->read($size, $this->dnssec ? $this->dnssec_payload_size : Net_DNS2_Lookups::DNS_MAX_UDP_SIZE);
                if (($result === false) || ($size < Net_DNS2_Lookups::DNS_HEADER_SIZE)) {

                    throw new Net_DNS2_Exception(
                        $this->sock['tcp'][$_ns]->last_error, Net_DNS2_Lookups::E_NS_SOCKET_FAILED
                    );
                }

                $chunk = new Net_DNS2_Packet_Response($result, $size);

                if (is_null($response)) {

                    $response = clone $chunk;

                    if ($response->header->rcode != Net_DNS2_Lookups::RCODE_NOERROR) {
                        break;
                    }

                    foreach ($response->answer as $rr) {
                        if ($rr->type == 'SOA') {
                            $soa_count++;
                        }
                    }

                } else {

                    foreach ($chunk->answer as $rr) {

                        if ($rr->type == 'SOA') {
                            $soa_count++;
                        }

                        $response->answer[] = $rr;
                    }

                }
                if ($soa_count >= 2) {
                    break;
                }
            }

        } else {

            $result = $this->sock['tcp'][$_ns]->read($size, $this->dnssec ? $this->dnssec_payload_size : Net_DNS2_Lookups::DNS_MAX_UDP_SIZE);

            if (($result === false) || ($size < Net_DNS2_Lookups::DNS_HEADER_SIZE)) {
                throw new Net_DNS2_Exception(
                    $this->sock['tcp'][$_ns]->last_error, Net_DNS2_Lookups::E_NS_SOCKET_FAILED
                );
            }

            $response = new Net_DNS2_Packet_Response($result, $size);
        }

        $response->response_time = microtime(true) - $start_time;

        $response->answer_from = $_ns;
        $response->answer_socket_type = Net_DNS2_Socket::SOCK_STREAM;

        return $response;
    }

    private function sendUDPRequest($_ns, $_data)
    {
        $start_time = microtime(true);

        if ((!isset($this->sock['udp'][$_ns]))
            || (!($this->sock['udp'][$_ns] instanceof Net_DNS2_Socket))
        ) {

            if ($this->sockets_enabled === true) {

                $this->sock['udp'][$_ns] = new Net_DNS2_Socket_Sockets(
                    Net_DNS2_Socket::SOCK_DGRAM, $_ns, $this->dns_port, $this->timeout
                );

            } else {

                $this->sock['udp'][$_ns] = new Net_DNS2_Socket_Streams(
                    Net_DNS2_Socket::SOCK_DGRAM, $_ns, $this->dns_port, $this->timeout
                );
            }

            if (strlen($this->local_host) > 0) {

                $this->sock['udp'][$_ns]->bindAddress(
                    $this->local_host, $this->local_port
                );
            }

            if ($this->sock['udp'][$_ns]->open() === false) {

                throw new Net_DNS2_Exception(
                    $this->sock['udp'][$_ns]->last_error, Net_DNS2_Lookups::E_NS_SOCKET_FAILED
                );
            }
        }

        if ($this->sock['udp'][$_ns]->write($_data) === false) {

            throw new Net_DNS2_Exception(
                $this->sock['udp'][$_ns]->last_error, Net_DNS2_Lookups::E_NS_SOCKET_FAILED
            );
        }

        $size = 0;

        $result = $this->sock['udp'][$_ns]->read($size, $this->dnssec ? $this->dnssec_payload_size : Net_DNS2_Lookups::DNS_MAX_UDP_SIZE);
        if (($result === false) || ($size < Net_DNS2_Lookups::DNS_HEADER_SIZE)) {

            throw new Net_DNS2_Exception(
                $this->sock['udp'][$_ns]->last_error, Net_DNS2_Lookups::E_NS_SOCKET_FAILED
            );
        }

        $response = new Net_DNS2_Packet_Response($result, $size);
        $response->response_time = microtime(true) - $start_time;

        $response->answer_from = $_ns;
        $response->answer_socket_type = Net_DNS2_Socket::SOCK_DGRAM;

        return $response;
    }
}

class Net_DNS2_BitMap
{

    public static function bitMapToArray($data)
    {
        if (strlen($data) == 0) {
            return null;
        }

        $output = array();
        $offset = 0;
        $length = strlen($data);

        while ($offset < $length) {

            $x = unpack('@' . $offset . '/Cwindow/Clength', $data);
            $offset += 2;

            $bitmap = unpack('C*', substr($data, $offset, $x['length']));
            $offset += $x['length'];

            $bitstr = '';
            foreach ($bitmap as $r) {

                $bitstr .= sprintf('%08b', $r);
            }

            $blen = strlen($bitstr);
            for ($i = 0; $i < $blen; $i++) {

                if ($bitstr[$i] == '1') {

                    $type = $x['window'] * 256 + $i;

                    if (isset(Net_DNS2_Lookups::$rr_types_by_id[$type])) {

                        $output[] = Net_DNS2_Lookups::$rr_types_by_id[$type];
                    } else {

                        $output[] = 'TYPE' . $type;
                    }
                }
            }
        }

        return $output;
    }

    public static function arrayToBitMap(array $data)
    {
        if (count($data) == 0) {
            return null;
        }

        $max = 0;
        $bm = array();

        foreach ($data as $rr) {

            $rr = strtoupper($rr);
            $type = @Net_DNS2_Lookups::$rr_types_by_name[$rr];
            if (isset($type)) {

                if ((isset(Net_DNS2_Lookups::$rr_qtypes_by_id[$type]))
                    || (isset(Net_DNS2_Lookups::$rr_metatypes_by_id[$type]))
                ) {
                    continue;
                }

            } else {

                list($name, $type) = explode('TYPE', $rr);
                if (!isset($type)) {

                    continue;
                }
            }

            $current_window = (int)($type / 256);

            $val = $type - $current_window * 256.0;
            if ($val > $max) {
                $max = $val;
            }

            $bm[$current_window][$val] = 1;
            $bm[$current_window]['length'] = ceil(($max + 1) / 8);
        }

        $output = '';

        foreach ($bm as $window => $bitdata) {

            $bitstr = '';

            for ($i = 0; $i < $bitdata['length'] * 8; $i++) {
                if (isset($bitdata[$i])) {
                    $bitstr .= '1';
                } else {
                    $bitstr .= '0';
                }
            }

            $output .= pack('CC', $window, $bitdata['length']);
            $output .= pack('H*', self::bigBaseConvert($bitstr));
        }

        return $output;
    }

    public static function bigBaseConvert($number)
    {
        $result = '';

        $bin = substr(chunk_split(strrev($number), 4, '-'), 0, -1);
        $temp = preg_split('[-]', $bin, -1, PREG_SPLIT_DELIM_CAPTURE);

        for ($i = count($temp) - 1; $i >= 0; $i--) {

            $result = $result . base_convert(strrev($temp[$i]), 2, 16);
        }

        return strtoupper($result);
    }
}

class Net_DNS2_Cache
{

    protected $cache_file = '';
    protected $cache_data = array();
    protected $cache_size = 0;
    protected $cache_serializer;

    public function has($key)
    {
        return isset($this->cache_data[$key]);
    }

    public function get($key)
    {
        if (isset($this->cache_data[$key])) {

            if ($this->cache_serializer == 'json') {
                return json_decode($this->cache_data[$key]['object']);
            } else {
                return unserialize($this->cache_data[$key]['object']);
            }
        } else {

            return false;
        }
    }

    public function put($key, $data)
    {
        $ttl = 86400 * 365;


        $data->rdata = '';
        $data->rdlength = 0;


        foreach ($data->answer as $rr) {

            if ($rr->ttl < $ttl) {
                $ttl = $rr->ttl;
            }

            $rr->rdata = '';
            $rr->rdlength = 0;
        }
        foreach ($data->authority as $rr) {

            if ($rr->ttl < $ttl) {
                $ttl = $rr->ttl;
            }

            $rr->rdata = '';
            $rr->rdlength = 0;
        }
        foreach ($data->additional as $rr) {

            if ($rr->ttl < $ttl) {
                $ttl = $rr->ttl;
            }

            $rr->rdata = '';
            $rr->rdlength = 0;
        }

        $this->cache_data[$key] = array(

            'cache_date' => time(),
            'ttl' => $ttl
        );

        if ($this->cache_serializer == 'json') {
            $this->cache_data[$key]['object'] = json_encode($data);
        } else {
            $this->cache_data[$key]['object'] = serialize($data);
        }
    }

    protected function clean()
    {
        if (count($this->cache_data) > 0) {


            $now = time();

            foreach ($this->cache_data as $key => $data) {

                $diff = $now - $data['cache_date'];

                if ($data['ttl'] <= $diff) {

                    unset($this->cache_data[$key]);
                } else {

                    $this->cache_data[$key]['ttl'] -= $diff;
                    $this->cache_data[$key]['cache_date'] = $now;
                }
            }
        }
    }

    protected function resize()
    {
        if (count($this->cache_data) > 0) {


            if ($this->cache_serializer == 'json') {
                $cache = json_encode($this->cache_data);
            } else {
                $cache = serialize($this->cache_data);
            }


            if (strlen($cache) > $this->cache_size) {

                while (strlen($cache) > $this->cache_size) {


                    $smallest_ttl = time();
                    $smallest_key = null;

                    foreach ($this->cache_data as $key => $data) {

                        if ($data['ttl'] < $smallest_ttl) {

                            $smallest_ttl = $data['ttl'];
                            $smallest_key = $key;
                        }
                    }

                    unset($this->cache_data[$smallest_key]);

                    if ($this->cache_serializer == 'json') {
                        $cache = json_encode($this->cache_data);
                    } else {
                        $cache = serialize($this->cache_data);
                    }
                }
            }

            if (($cache == 'a:0:{}') || ($cache == '{}')) {
                return null;
            } else {
                return $cache;
            }
        }

        return null;
    }
}


class Net_DNS2_Exception extends Exception
{
    private $_request;
    private $_response;

    public function __construct(
        $message = '',
        $code = 0,
        $previous = null,
        Net_DNS2_Packet_Request $request = null,
        Net_DNS2_Packet_Response $response = null
    )
    {

        $this->_request = $request;
        $this->_response = $response;

        if (version_compare(PHP_VERSION, '5.3.0', '>=')) {

            parent::__construct($message, $code, $previous);
        } else {

            parent::__construct($message, $code);
        }
    }

    public function getRequest()
    {
        return $this->_request;
    }

    public function getResponse()
    {
        return $this->_response;
    }
}

class Net_DNS2_Header
{
    public $id;
    public $qr;
    public $opcode;
    public $aa;
    public $tc;
    public $rd;
    public $ra;
    public $z;
    public $ad;
    public $cd;
    public $rcode;
    public $qdcount;
    public $ancount;
    public $nscount;
    public $arcount;

    public function __construct(Net_DNS2_Packet &$packet = null)
    {
        if (!is_null($packet)) {

            $this->set($packet);
        } else {

            $this->id = $this->nextPacketId();
            $this->qr = Net_DNS2_Lookups::QR_QUERY;
            $this->opcode = Net_DNS2_Lookups::OPCODE_QUERY;
            $this->aa = 0;
            $this->tc = 0;
            $this->rd = 1;
            $this->ra = 0;
            $this->z = 0;
            $this->ad = 0;
            $this->cd = 0;
            $this->rcode = Net_DNS2_Lookups::RCODE_NOERROR;
            $this->qdcount = 1;
            $this->ancount = 0;
            $this->nscount = 0;
            $this->arcount = 0;
        }
    }

    public function set(Net_DNS2_Packet &$packet)
    {


        if ($packet->rdlength < Net_DNS2_Lookups::DNS_HEADER_SIZE) {

            throw new Net_DNS2_Exception(
                'invalid header data provided; to small',
                Net_DNS2_Lookups::E_HEADER_INVALID
            );
        }

        $offset = 0;


        $this->id = ord($packet->rdata[$offset]) << 8 |
            ord($packet->rdata[++$offset]);

        ++$offset;
        $this->qr = (ord($packet->rdata[$offset]) >> 7) & 0x1;
        $this->opcode = (ord($packet->rdata[$offset]) >> 3) & 0xf;
        $this->aa = (ord($packet->rdata[$offset]) >> 2) & 0x1;
        $this->tc = (ord($packet->rdata[$offset]) >> 1) & 0x1;
        $this->rd = ord($packet->rdata[$offset]) & 0x1;

        ++$offset;
        $this->ra = (ord($packet->rdata[$offset]) >> 7) & 0x1;
        $this->z = (ord($packet->rdata[$offset]) >> 6) & 0x1;
        $this->ad = (ord($packet->rdata[$offset]) >> 5) & 0x1;
        $this->cd = (ord($packet->rdata[$offset]) >> 4) & 0x1;
        $this->rcode = ord($packet->rdata[$offset]) & 0xf;

        $this->qdcount = ord($packet->rdata[++$offset]) << 8 |
            ord($packet->rdata[++$offset]);
        $this->ancount = ord($packet->rdata[++$offset]) << 8 |
            ord($packet->rdata[++$offset]);
        $this->nscount = ord($packet->rdata[++$offset]) << 8 |
            ord($packet->rdata[++$offset]);
        $this->arcount = ord($packet->rdata[++$offset]) << 8 |
            ord($packet->rdata[++$offset]);


        $packet->offset += Net_DNS2_Lookups::DNS_HEADER_SIZE;

        return true;
    }

    public function nextPacketId()
    {
        if (++Net_DNS2_Lookups::$next_packet_id > 65535) {

            Net_DNS2_Lookups::$next_packet_id = 1;
        }

        return Net_DNS2_Lookups::$next_packet_id;
    }

    public function __toString()
    {
        $output = ";;\n;; Header:\n";

        $output .= ";;\t id         = " . $this->id . "\n";
        $output .= ";;\t qr         = " . $this->qr . "\n";
        $output .= ";;\t opcode     = " . $this->opcode . "\n";
        $output .= ";;\t aa         = " . $this->aa . "\n";
        $output .= ";;\t tc         = " . $this->tc . "\n";
        $output .= ";;\t rd         = " . $this->rd . "\n";
        $output .= ";;\t ra         = " . $this->ra . "\n";
        $output .= ";;\t z          = " . $this->z . "\n";
        $output .= ";;\t ad         = " . $this->ad . "\n";
        $output .= ";;\t cd         = " . $this->cd . "\n";
        $output .= ";;\t rcode      = " . $this->rcode . "\n";
        $output .= ";;\t qdcount    = " . $this->qdcount . "\n";
        $output .= ";;\t ancount    = " . $this->ancount . "\n";
        $output .= ";;\t nscount    = " . $this->nscount . "\n";
        $output .= ";;\t arcount    = " . $this->arcount . "\n";

        return $output;
    }

    public function get(Net_DNS2_Packet &$packet)
    {
        $data = pack('n', $this->id) .
            chr(
                ($this->qr << 7) | ($this->opcode << 3) |
                ($this->aa << 2) | ($this->tc << 1) | ($this->rd)
            ) .
            chr(
                ($this->ra << 7) | ($this->ad << 5) | ($this->cd << 4) | $this->rcode
            ) .
            chr($this->qdcount << 8) . chr($this->qdcount) .
            chr($this->ancount << 8) . chr($this->ancount) .
            chr($this->nscount << 8) . chr($this->nscount) .
            chr($this->arcount << 8) . chr($this->arcount);

        $packet->offset += Net_DNS2_Lookups::DNS_HEADER_SIZE;

        return $data;
    }
}

Net_DNS2_Lookups::$next_packet_id = mt_rand(0, 65535);

Net_DNS2_Lookups::$rr_types_by_id = array_flip(Net_DNS2_Lookups::$rr_types_by_name);
Net_DNS2_Lookups::$classes_by_id = array_flip(Net_DNS2_Lookups::$classes_by_name);
Net_DNS2_Lookups::$rr_types_class_to_id = array_flip(Net_DNS2_Lookups::$rr_types_id_to_class);
Net_DNS2_Lookups::$algorithm_name_to_id = array_flip(Net_DNS2_Lookups::$algorithm_id_to_name);
Net_DNS2_Lookups::$digest_name_to_id = array_flip(Net_DNS2_Lookups::$digest_id_to_name);
Net_DNS2_Lookups::$rr_qtypes_by_id = array_flip(Net_DNS2_Lookups::$rr_qtypes_by_name);
Net_DNS2_Lookups::$rr_metatypes_by_id = array_flip(Net_DNS2_Lookups::$rr_metatypes_by_name);
Net_DNS2_Lookups::$protocol_by_id = array_flip(Net_DNS2_Lookups::$protocol_by_name);

class Net_DNS2_Lookups
{

    const DNS_HEADER_SIZE = 12;

    const DNS_MAX_UDP_SIZE = 512;

    const QR_QUERY = 0;
    const QR_RESPONSE = 1;

    const OPCODE_QUERY = 0;
    const OPCODE_IQUERY = 1;
    const OPCODE_STATUS = 2;
    const OPCODE_NOTIFY = 4;
    const OPCODE_UPDATE = 5;

    const RR_CLASS_IN = 1;
    const RR_CLASS_CH = 3;
    const RR_CLASS_HS = 4;
    const RR_CLASS_NONE = 254;
    const RR_CLASS_ANY = 255;

    const RCODE_NOERROR = 0;
    const RCODE_FORMERR = 1;
    const RCODE_SERVFAIL = 2;
    const RCODE_NXDOMAIN = 3;
    const RCODE_NOTIMP = 4;
    const RCODE_REFUSED = 5;
    const RCODE_YXDOMAIN = 6;
    const RCODE_YXRRSET = 7;
    const RCODE_NXRRSET = 8;
    const RCODE_NOTAUTH = 9;
    const RCODE_NOTZONE = 10;

    const RCODE_BADSIG = 16;
    const RCODE_BADKEY = 17;
    const RCODE_BADTIME = 18;
    const RCODE_BADMODE = 19;
    const RCODE_BADNAME = 20;
    const RCODE_BADALG = 21;
    const RCODE_BADTRUNC = 22;

    const E_NONE = 0;
    const E_DNS_FORMERR = self::RCODE_FORMERR;
    const E_DNS_SERVFAIL = self::RCODE_SERVFAIL;
    const E_DNS_NXDOMAIN = self::RCODE_NXDOMAIN;
    const E_DNS_NOTIMP = self::RCODE_NOTIMP;
    const E_DNS_REFUSED = self::RCODE_REFUSED;
    const E_DNS_YXDOMAIN = self::RCODE_YXDOMAIN;
    const E_DNS_YXRRSET = self::RCODE_YXRRSET;
    const E_DNS_NXRRSET = self::RCODE_NXRRSET;
    const E_DNS_NOTAUTH = self::RCODE_NOTAUTH;
    const E_DNS_NOTZONE = self::RCODE_NOTZONE;

    const E_DNS_BADSIG = self::RCODE_BADSIG;
    const E_DNS_BADKEY = self::RCODE_BADKEY;
    const E_DNS_BADTIME = self::RCODE_BADTIME;
    const E_DNS_BADMODE = self::RCODE_BADMODE;
    const E_DNS_BADNAME = self::RCODE_BADNAME;
    const E_DNS_BADALG = self::RCODE_BADALG;
    const E_DNS_BADTRUNC = self::RCODE_BADTRUNC;

    const E_NS_INVALID_FILE = 200;
    const E_NS_INVALID_ENTRY = 201;
    const E_NS_FAILED = 202;
    const E_NS_SOCKET_FAILED = 203;

    const E_PACKET_INVALID = 300;
    const E_PARSE_ERROR = 301;
    const E_HEADER_INVALID = 302;
    const E_QUESTION_INVALID = 303;
    const E_RR_INVALID = 304;

    const E_OPENSSL_ERROR = 400;
    const E_OPENSSL_UNAVAIL = 401;
    const E_OPENSSL_INV_PKEY = 402;
    const E_OPENSSL_INV_ALGO = 403;

    const E_CACHE_UNSUPPORTED = 500;
    const E_CACHE_SHM_FILE = 501;
    const E_CACHE_SHM_UNAVAIL = 502;

    const DNSSEC_ALGORITHM_RES = 0;
    const DNSSEC_ALGORITHM_RSAMD5 = 1;
    const DNSSEC_ALGORITHM_DH = 2;
    const DNSSEC_ALGORITHM_DSA = 3;
    const DNSSEC_ALGORITHM_ECC = 4;
    const DNSSEC_ALGORITHM_RSASHA1 = 5;
    const DNSSEC_ALGORITHM_DSANSEC3SHA1 = 6;
    const DSNSEC_ALGORITHM_RSASHA1NSEC3SHA1 = 7;
    const DNSSEC_ALGORITHM_RSASHA256 = 8;
    const DNSSEC_ALGORITHM_RSASHA512 = 10;
    const DNSSEC_ALGORITHM_ECCGOST = 12;
    const DNSSEC_ALGORITHM_INDIRECT = 252;
    const DNSSEC_ALGORITHM_PRIVATEDNS = 253;
    const DNSSEC_ALGORITHM_PRIVATEOID = 254;

    const DNSSEC_DIGEST_RES = 0;
    const DNSSEC_DIGEST_SHA1 = 1;

    public static $next_packet_id;

    public static $rr_types_by_id = array();
    public static $rr_types_by_name = array(

        'SIG0' => 0,
        'A' => 1,
        'NS' => 2,
        'MD' => 3,
        'MF' => 4,
        'CNAME' => 5,
        'SOA' => 6,
        'MB' => 7,
        'MG' => 8,
        'MR' => 9,
        'NULL' => 10,
        'WKS' => 11,
        'PTR' => 12,
        'HINFO' => 13,
        'MINFO' => 14,
        'MX' => 15,
        'TXT' => 16,
        'RP' => 17,
        'AFSDB' => 18,
        'X25' => 19,
        'ISDN' => 20,
        'RT' => 21,
        'NSAP' => 22,
        'NSAP_PTR' => 23,
        'SIG' => 24,
        'KEY' => 25,
        'PX' => 26,
        'GPOS' => 27,
        'AAAA' => 28,
        'LOC' => 29,
        'NXT' => 30,
        'EID' => 31,
        'NIMLOC' => 32,
        'SRV' => 33,
        'ATMA' => 34,
        'NAPTR' => 35,
        'KX' => 36,
        'CERT' => 37,
        'A6' => 38,
        'DNAME' => 39,
        'SINK' => 40,
        'OPT' => 41,
        'APL' => 42,
        'DS' => 43,
        'SSHFP' => 44,
        'IPSECKEY' => 45,
        'RRSIG' => 46,
        'NSEC' => 47,
        'DNSKEY' => 48,
        'DHCID' => 49,
        'NSEC3' => 50,
        'NSEC3PARAM' => 51,
        'TLSA' => 52,

        'HIP' => 55,
        'NINFO' => 56,
        'RKEY' => 57,
        'TALINK' => 58,
        'CDS' => 59,
        'CDNSKEY' => 60,
        'OPENPGPKEY' => 61,
        'CSYNC' => 62,

        'SPF' => 99,
        'UINFO' => 100,
        'UID' => 101,
        'GID' => 102,
        'UNSPEC' => 103,
        'NID' => 104,
        'L32' => 105,
        'L64' => 106,
        'LP' => 107,
        'EUI48' => 108,
        'EUI64' => 109,

        'TKEY' => 249,
        'TSIG' => 250,
        'IXFR' => 251,
        'AXFR' => 252,
        'MAILB' => 253,
        'MAILA' => 254,
        'ANY' => 255,
        'URI' => 256,
        'CAA' => 257,

        'TA' => 32768,
        'DLV' => 32769
    );

    public static $rr_qtypes_by_id = array();
    public static $rr_qtypes_by_name = array(

        'IXFR' => 251,
        'AXFR' => 252,
        'MAILB' => 253,
        'MAILA' => 254,
        'ANY' => 255
    );

    public static $rr_metatypes_by_id = array();
    public static $rr_metatypes_by_name = array(

        'OPT' => 41,
        'TKEY' => 249,
        'TSIG' => 250
    );

    public static $rr_types_class_to_id = array();
    public static $rr_types_id_to_class = array(

        1 => 'Net_DNS2_RR_A',
        2 => 'Net_DNS2_RR_NS',
        5 => 'Net_DNS2_RR_CNAME',
        6 => 'Net_DNS2_RR_SOA',
        11 => 'Net_DNS2_RR_WKS',
        12 => 'Net_DNS2_RR_PTR',
        13 => 'Net_DNS2_RR_HINFO',
        15 => 'Net_DNS2_RR_MX',
        16 => 'Net_DNS2_RR_TXT',
        17 => 'Net_DNS2_RR_RP',
        18 => 'Net_DNS2_RR_AFSDB',
        19 => 'Net_DNS2_RR_X25',
        20 => 'Net_DNS2_RR_ISDN',
        21 => 'Net_DNS2_RR_RT',
        22 => 'Net_DNS2_RR_NSAP',
        24 => 'Net_DNS2_RR_SIG',
        25 => 'Net_DNS2_RR_KEY',
        26 => 'Net_DNS2_RR_PX',
        28 => 'Net_DNS2_RR_AAAA',
        29 => 'Net_DNS2_RR_LOC',
        31 => 'Net_DNS2_RR_EID',
        32 => 'Net_DNS2_RR_NIMLOC',
        33 => 'Net_DNS2_RR_SRV',
        34 => 'Net_DNS2_RR_ATMA',
        35 => 'Net_DNS2_RR_NAPTR',
        36 => 'Net_DNS2_RR_KX',
        37 => 'Net_DNS2_RR_CERT',
        39 => 'Net_DNS2_RR_DNAME',
        41 => 'Net_DNS2_RR_OPT',
        42 => 'Net_DNS2_RR_APL',
        43 => 'Net_DNS2_RR_DS',
        44 => 'Net_DNS2_RR_SSHFP',
        45 => 'Net_DNS2_RR_IPSECKEY',
        46 => 'Net_DNS2_RR_RRSIG',
        47 => 'Net_DNS2_RR_NSEC',
        48 => 'Net_DNS2_RR_DNSKEY',
        49 => 'Net_DNS2_RR_DHCID',
        50 => 'Net_DNS2_RR_NSEC3',
        51 => 'Net_DNS2_RR_NSEC3PARAM',
        52 => 'Net_DNS2_RR_TLSA',
        55 => 'Net_DNS2_RR_HIP',
        58 => 'Net_DNS2_RR_TALINK',
        59 => 'Net_DNS2_RR_CDS',
        60 => 'Net_DNS2_RR_CDNSKEY',
        61 => 'Net_DNS2_RR_OPENPGPKEY',
        62 => 'Net_DNS2_RR_CSYNC',
        99 => 'Net_DNS2_RR_SPF',
        104 => 'Net_DNS2_RR_NID',
        105 => 'Net_DNS2_RR_L32',
        106 => 'Net_DNS2_RR_L64',
        107 => 'Net_DNS2_RR_LP',
        108 => 'Net_DNS2_RR_EUI48',
        109 => 'Net_DNS2_RR_EUI64',

        249 => 'Net_DNS2_RR_TKEY',
        250 => 'Net_DNS2_RR_TSIG',

        255 => 'Net_DNS2_RR_ANY',
        256 => 'Net_DNS2_RR_URI',
        257 => 'Net_DNS2_RR_CAA',
        32768 => 'Net_DNS2_RR_TA',
        32769 => 'Net_DNS2_RR_DLV'
    );

    public static $classes_by_id = array();
    public static $classes_by_name = array(

        'IN' => self::RR_CLASS_IN,
        'CH' => self::RR_CLASS_CH,
        'HS' => self::RR_CLASS_HS,
        'NONE' => self::RR_CLASS_NONE,
        'ANY' => self::RR_CLASS_ANY
    );

    public static $result_code_messages = array(

        self::RCODE_NOERROR => 'The request completed successfully.',
        self::RCODE_FORMERR => 'The name server was unable to interpret the query.',
        self::RCODE_SERVFAIL => 'The name server was unable to process this query due to a problem with the name server.',
        self::RCODE_NXDOMAIN => 'The domain name referenced in the query does not exist.',
        self::RCODE_NOTIMP => 'The name server does not support the requested kind of query.',
        self::RCODE_REFUSED => 'The name server refuses to perform the specified operation for policy reasons.',
        self::RCODE_YXDOMAIN => 'Name Exists when it should not.',
        self::RCODE_YXRRSET => 'RR Set Exists when it should not.',
        self::RCODE_NXRRSET => 'RR Set that should exist does not.',
        self::RCODE_NOTAUTH => 'Server Not Authoritative for zone.',
        self::RCODE_NOTZONE => 'Name not contained in zone.',

        self::RCODE_BADSIG => 'TSIG Signature Failure.',
        self::RCODE_BADKEY => 'Key not recognized.',
        self::RCODE_BADTIME => 'Signature out of time window.',
        self::RCODE_BADMODE => 'Bad TKEY Mode.',
        self::RCODE_BADNAME => 'Duplicate key name.',
        self::RCODE_BADALG => 'Algorithm not supported.',
        self::RCODE_BADTRUNC => 'Bad truncation.'
    );

    public static $algorithm_name_to_id = array();
    public static $algorithm_id_to_name = array(

        self::DNSSEC_ALGORITHM_RES => 'RES',
        self::DNSSEC_ALGORITHM_RSAMD5 => 'RSAMD5',
        self::DNSSEC_ALGORITHM_DH => 'DH',
        self::DNSSEC_ALGORITHM_DSA => 'DSA',
        self::DNSSEC_ALGORITHM_ECC => 'ECC',
        self::DNSSEC_ALGORITHM_RSASHA1 => 'RSASHA1',
        self::DNSSEC_ALGORITHM_DSANSEC3SHA1 => 'DSA-NSEC3-SHA1',
        self::DSNSEC_ALGORITHM_RSASHA1NSEC3SHA1 => 'RSASHA1-NSEC3-SHA1',
        self::DNSSEC_ALGORITHM_RSASHA256 => 'RSASHA256',
        self::DNSSEC_ALGORITHM_RSASHA512 => 'RSASHA512',
        self::DNSSEC_ALGORITHM_ECCGOST => 'ECC-GOST',
        self::DNSSEC_ALGORITHM_INDIRECT => 'INDIRECT',
        self::DNSSEC_ALGORITHM_PRIVATEDNS => 'PRIVATEDNS',
        self::DNSSEC_ALGORITHM_PRIVATEOID => 'PRIVATEOID'
    );

    public static $digest_name_to_id = array();
    public static $digest_id_to_name = array(

        self::DNSSEC_DIGEST_RES => 'RES',
        self::DNSSEC_DIGEST_SHA1 => 'SHA-1'
    );

    public static $protocol_by_id = array();
    public static $protocol_by_name = array(

        'ICMP' => 1,
        'IGMP' => 2,
        'GGP' => 3,
        'ST' => 5,
        'TCP' => 6,
        'UCL' => 7,
        'EGP' => 8,
        'IGP' => 9,
        'BBN-RCC-MON' => 10,
        'NVP-II' => 11,
        'PUP' => 12,
        'ARGUS' => 13,
        'EMCON' => 14,
        'XNET' => 15,
        'CHAOS' => 16,
        'UDP' => 17,
        'MUX' => 18,
        'DCN-MEAS' => 19,
        'HMP' => 20,
        'PRM' => 21,
        'XNS-IDP' => 22,
        'TRUNK-1' => 23,
        'TRUNK-2' => 24,
        'LEAF-1' => 25,
        'LEAF-2' => 26,
        'RDP' => 27,
        'IRTP' => 28,
        'ISO-TP4' => 29,
        'NETBLT' => 30,
        'MFE-NSP' => 31,
        'MERIT-INP' => 32,
        'SEP' => 33,

        'CFTP' => 62,

        'SAT-EXPAK' => 64,
        'MIT-SUBNET' => 65,
        'RVD' => 66,
        'IPPC' => 67,

        'SAT-MON' => 69,

        'IPCV' => 71,

        'BR-SAT-MON' => 76,

        'WB-MON' => 78,
        'WB-EXPAK' => 79

    );
}


class Net_DNS2_Packet
{

    public $rdata;
    public $rdlength;

    public $offset = 0;

    public $header;

    public $question = array();

    public $answer = array();

    public $authority = array();

    public $additional = array();

    private $_compressed = array();

    public static function pack($name)
    {
        $offset = 0;
        $names = explode('.', $name);
        $compname = '';

        while (!empty($names)) {

            $first = array_shift($names);
            $length = strlen($first);

            $compname .= pack('Ca*', $length, $first);
            $offset += $length + 1;
        }

        $compname .= "\0";
        $offset++;

        return $compname;
    }

    public static function expand(Net_DNS2_Packet &$packet, &$offset)
    {
        $name = '';

        while (1) {
            if ($packet->rdlength < ($offset + 1)) {
                return null;
            }

            $xlen = ord($packet->rdata[$offset]);
            if ($xlen == 0) {

                ++$offset;
                break;

            } else if (($xlen & 0xc0) == 0xc0) {
                if ($packet->rdlength < ($offset + 2)) {

                    return null;
                }

                $ptr = ord($packet->rdata[$offset]) << 8 |
                    ord($packet->rdata[$offset + 1]);
                $ptr = $ptr & 0x3fff;

                $name2 = Net_DNS2_Packet::expand($packet, $ptr);
                if (is_null($name2)) {

                    return null;
                }

                $name .= $name2;
                $offset += 2;

                break;
            } else {
                ++$offset;

                if ($packet->rdlength < ($offset + $xlen)) {

                    return null;
                }

                $elem = '';
                $elem = substr($packet->rdata, $offset, $xlen);
                $name .= $elem . '.';
                $offset += $xlen;
            }
        }

        return trim($name, '.');
    }

    public static function label(Net_DNS2_Packet &$packet, &$offset)
    {
        $name = '';

        if ($packet->rdlength < ($offset + 1)) {

            return null;
        }

        $xlen = ord($packet->rdata[$offset]);
        ++$offset;

        if (($xlen + $offset) > $packet->rdlength) {

            $name = substr($packet->rdata, $offset);
            $offset = $packet->rdlength;
        } else {

            $name = substr($packet->rdata, $offset, $xlen);
            $offset += $xlen;
        }

        return $name;
    }

    public static function formatIPv6($address)
    {
        return Net_DNS2::expandIPv6($address);
    }

    public function __toString()
    {
        $output = $this->header->__toString();

        foreach ($this->question as $x) {

            $output .= $x->__toString() . "\n";
        }
        foreach ($this->answer as $x) {

            $output .= $x->__toString() . "\n";
        }
        foreach ($this->authority as $x) {

            $output .= $x->__toString() . "\n";
        }
        foreach ($this->additional as $x) {

            $output .= $x->__toString() . "\n";
        }

        return $output;
    }

    public function get()
    {
        $data = $this->header->get($this);

        foreach ($this->question as $x) {

            $data .= $x->get($this);
        }
        foreach ($this->answer as $x) {

            $data .= $x->get($this);
        }
        foreach ($this->authority as $x) {

            $data .= $x->get($this);
        }
        foreach ($this->additional as $x) {

            $data .= $x->get($this);
        }

        return $data;
    }

    public function compress($name, &$offset)
    {
        $names = explode('.', $name);
        $compname = '';

        while (!empty($names)) {

            $dname = join('.', $names);

            if (isset($this->_compressed[$dname])) {

                $compname .= pack('n', 0xc000 | $this->_compressed[$dname]);
                $offset += 2;

                break;
            }

            $this->_compressed[$dname] = $offset;
            $first = array_shift($names);

            $length = strlen($first);
            if ($length <= 0) {
                continue;
            }


            if ($length > 63) {

                $length = 63;
                $first = substr($first, 0, $length);
            }

            $compname .= pack('Ca*', $length, $first);
            $offset += $length + 1;
        }

        if (empty($names)) {

            $compname .= pack('C', 0);
            $offset++;
        }

        return $compname;
    }

    public function copy(Net_DNS2_Packet $packet)
    {
        $this->header = $packet->header;
        $this->question = $packet->question;
        $this->answer = $packet->answer;
        $this->authority = $packet->authority;
        $this->additional = $packet->additional;

        return true;
    }

    public function reset()
    {
        $this->header->id = $this->header->nextPacketId();
        $this->rdata = '';
        $this->rdlength = 0;
        $this->offset = 0;
        $this->answer = array();
        $this->authority = array();
        $this->additional = array();
        $this->_compressed = array();

        return true;
    }
}


class Net_DNS2_PrivateKey
{

    public $filename;

    public $keytag;

    public $signname;

    public $algorithm;

    public $key_format;

    public $instance;
    public $prime;
    public $subprime;
    public $base;
    public $private_value;
    public $public_value;
    private $_modulus;
    private $_public_exponent;
    private $_private_exponent;
    private $_prime1;
    private $_prime2;
    private $_exponent1;
    private $_exponent2;
    private $_coefficient;

    public function __construct($file = null)
    {
        if (isset($file)) {
            $this->parseFile($file);
        }
    }

    public function parseFile($file)
    {


        if (extension_loaded('openssl') === false) {

            throw new Net_DNS2_Exception(
                'the OpenSSL extension is required to use parse private key.',
                Net_DNS2_Lookups::E_OPENSSL_UNAVAIL
            );
        }


        if (!is_readable($file)) {

            throw new Net_DNS2_Exception(
                'invalid private key file: ' . $file,
                Net_DNS2_Lookups::E_OPENSSL_INV_PKEY
            );
        }


        $keyname = basename($file);
        if (strlen($keyname) == 0) {

            throw new Net_DNS2_Exception(
                'failed to get basename() for: ' . $file,
                Net_DNS2_Lookups::E_OPENSSL_INV_PKEY
            );
        }


        if (preg_match("/K(.*)\.\+(\d{3})\+(\d*)\.private/", $keyname, $matches)) {

            $this->signname = $matches[1];
            $this->algorithm = intval($matches[2]);
            $this->keytag = intval($matches[3]);

        } else {

            throw new Net_DNS2_Exception(
                'file ' . $keyname . ' does not look like a private key file!',
                Net_DNS2_Lookups::E_OPENSSL_INV_PKEY
            );
        }


        $data = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        if (count($data) == 0) {

            throw new Net_DNS2_Exception(
                'file ' . $keyname . ' is empty!',
                Net_DNS2_Lookups::E_OPENSSL_INV_PKEY
            );
        }

        foreach ($data as $line) {

            list($key, $value) = explode(':', $line);

            $key = trim($key);
            $value = trim($value);

            switch (strtolower($key)) {

                case 'private-key-format':
                    $this->_key_format = $value;
                    break;

                case 'algorithm':
                    if ($this->algorithm != $value) {
                        throw new Net_DNS2_Exception(
                            'Algorithm mis-match! filename is ' . $this->algorithm .
                            ', contents say ' . $value,
                            Net_DNS2_Lookups::E_OPENSSL_INV_ALGO
                        );
                    }
                    break;


                case 'modulus':
                    $this->_modulus = $value;
                    break;

                case 'publicexponent':
                    $this->_public_exponent = $value;
                    break;

                case 'privateexponent':
                    $this->_private_exponent = $value;
                    break;

                case 'prime1':
                    $this->_prime1 = $value;
                    break;

                case 'prime2':
                    $this->_prime2 = $value;
                    break;

                case 'exponent1':
                    $this->_exponent1 = $value;
                    break;

                case 'exponent2':
                    $this->_exponent2 = $value;
                    break;

                case 'coefficient':
                    $this->_coefficient = $value;
                    break;


                case 'prime(p)':
                    $this->prime = $value;
                    break;

                case 'subprime(q)':
                    $this->subprime = $value;
                    break;

                case 'base(g)':
                    $this->base = $value;
                    break;

                case 'private_value(x)':
                    $this->private_value = $value;
                    break;

                case 'public_value(y)':
                    $this->public_value = $value;
                    break;

                default:
                    throw new Net_DNS2_Exception(
                        'unknown private key data: ' . $key . ': ' . $value,
                        Net_DNS2_Lookups::E_OPENSSL_INV_PKEY
                    );
            }
        }

        switch ($this->algorithm) {


            case Net_DNS2_Lookups::DNSSEC_ALGORITHM_RSAMD5:
            case Net_DNS2_Lookups::DNSSEC_ALGORITHM_RSASHA1:
            case Net_DNS2_Lookups::DNSSEC_ALGORITHM_RSASHA256:
            case Net_DNS2_Lookups::DNSSEC_ALGORITHM_RSASHA512:

                $args = array(

                    'rsa' => array(

                        'n' => base64_decode($this->_modulus),
                        'e' => base64_decode($this->_public_exponent),
                        'd' => base64_decode($this->_private_exponent),
                        'p' => base64_decode($this->_prime1),
                        'q' => base64_decode($this->_prime2),
                        'dmp1' => base64_decode($this->_exponent1),
                        'dmq1' => base64_decode($this->_exponent2),
                        'iqmp' => base64_decode($this->_coefficient)
                    )
                );

                break;


            case Net_DNS2_Lookups::DNSSEC_ALGORITHM_DSA:

                $args = array(

                    'dsa' => array(

                        'p' => base64_decode($this->prime),
                        'q' => base64_decode($this->subprime),
                        'g' => base64_decode($this->base),
                        'priv_key' => base64_decode($this->private_value),
                        'pub_key' => base64_decode($this->public_value)
                    )
                );

                break;

            default:
                throw new Net_DNS2_Exception(
                    'we only currently support RSAMD5 and RSASHA1 encryption.',
                    Net_DNS2_Lookups::E_OPENSSL_INV_PKEY
                );
        }


        $this->instance = openssl_pkey_new($args);
        if ($this->instance === false) {
            throw new Net_DNS2_Exception(
                openssl_error_string(),
                Net_DNS2_Lookups::E_OPENSSL_ERROR
            );
        }


        $this->filename = $file;

        return true;
    }
}


class Net_DNS2_Question
{

    public $qname;

    public $qtype;

    public $qclass;

    public function __construct(Net_DNS2_Packet &$packet = null)
    {
        if (!is_null($packet)) {

            $this->set($packet);
        } else {

            $this->qname = '';
            $this->qtype = 'A';
            $this->qclass = 'IN';
        }
    }

    public function set(Net_DNS2_Packet &$packet)
    {


        $this->qname = $packet->expand($packet, $packet->offset);
        if ($packet->rdlength < ($packet->offset + 4)) {

            throw new Net_DNS2_Exception(
                'invalid question section: to small',
                Net_DNS2_Lookups::E_QUESTION_INVALID
            );
        }


        $type = ord($packet->rdata[$packet->offset++]) << 8 |
            ord($packet->rdata[$packet->offset++]);
        $class = ord($packet->rdata[$packet->offset++]) << 8 |
            ord($packet->rdata[$packet->offset++]);


        $type_name = Net_DNS2_Lookups::$rr_types_by_id[$type];
        $class_name = Net_DNS2_Lookups::$classes_by_id[$class];

        if ((!isset($type_name)) || (!isset($class_name))) {

            throw new Net_DNS2_Exception(
                'invalid question section: invalid type (' . $type .
                ') or class (' . $class . ') specified.',
                Net_DNS2_Lookups::E_QUESTION_INVALID
            );
        }


        $this->qtype = $type_name;
        $this->qclass = $class_name;

        return true;
    }

    public function __toString()
    {
        return ";;\n;; Question:\n;;\t " . $this->qname . '. ' .
            $this->qtype . ' ' . $this->qclass . "\n";
    }

    public function get(Net_DNS2_Packet &$packet)
    {


        $type = Net_DNS2_Lookups::$rr_types_by_name[$this->qtype];
        $class = Net_DNS2_Lookups::$classes_by_name[$this->qclass];

        if ((!isset($type)) || (!isset($class))) {

            throw new Net_DNS2_Exception(
                'invalid question section: invalid type (' . $this->qtype .
                ') or class (' . $this->qclass . ') specified.',
                Net_DNS2_Lookups::E_QUESTION_INVALID
            );
        }

        $data = $packet->compress($this->qname, $packet->offset);

        $data .= chr($type << 8) . chr($type) . chr($class << 8) . chr($class);
        $packet->offset += 4;

        return $data;
    }
}


class Net_DNS2_Resolver extends Net_DNS2
{

    public function __construct(array $options = null)
    {
        parent::__construct($options);
    }

    public function query($name, $type = 'A', $class = 'IN')
    {


        $this->checkServers(Net_DNS2::RESOLV_CONF);


        if ($type == 'IXFR') {

            $type = 'AXFR';
        }


        if ((!str_contains($name, '.')) && ($type != 'PTR')) {

            $name .= '.' . strtolower($this->domain);
        }


        $packet = new Net_DNS2_Packet_Request($name, $type, $class);


        if (($this->auth_signature instanceof Net_DNS2_RR_TSIG)
            || ($this->auth_signature instanceof Net_DNS2_RR_SIG)
        ) {
            $packet->additional[] = $this->auth_signature;
            $packet->header->arcount = count($packet->additional);
        }


        if ($this->dnssec) {


            $opt = new Net_DNS2_RR_OPT();


            $opt->do = 1;
            $opt->class = $this->dnssec_payload_size;


            $packet->additional[] = $opt;
            $packet->header->arcount = count($packet->additional);
        }


        if ($this->dnssec_ad_flag) {

            $packet->header->ad = 1;
        }
        if ($this->dnssec_cd_flag) {

            $packet->header->cd = 1;
        }


        $packet_hash = '';

        if ($this->use_cache && $this->cacheable($type)) {


            $this->cache->open(
                $this->cache_file, $this->cache_size, $this->cache_serializer
            );


            $packet_hash = md5(
                $packet->question[0]->qname . '|' . $packet->question[0]->qtype
            );

            if ($this->cache->has($packet_hash)) {

                return $this->cache->get($packet_hash);
            }
        }


        if (!$this->recurse) {
            $packet->header->rd = 0;
        } else {
            $packet->header->rd = 1;
        }


        $response = $this->sendPacket(
            $packet, ($type == 'AXFR') ? true : $this->use_tcp
        );


        if ($this->strict_query_mode
            && ($response->header->ancount > 0)
        ) {

            $found = false;


            foreach ($response->answer as $index => $object) {

                if ((strcasecmp($object->name, $name) == 0)
                    && ($object->type == $type)
                    && ($object->class == $class)
                ) {
                    $found = true;
                    break;
                }
            }


            if (!$found) {

                $response->answer = array();
                $response->header->ancount = 0;
            }
        }


        if ($this->use_cache && $this->cacheable($type)) {

            $this->cache->put($packet_hash, $response);
        }

        return $response;
    }

    public function iquery(Net_DNS2_RR $rr)
    {


        $this->checkServers(Net_DNS2::RESOLV_CONF);


        $packet = new Net_DNS2_Packet_Request($rr->name, 'A', 'IN');


        $packet->question = array();
        $packet->header->qdcount = 0;


        $packet->header->opcode = Net_DNS2_Lookups::OPCODE_IQUERY;


        $packet->answer[] = $rr;
        $packet->header->ancount = 1;


        if (($this->auth_signature instanceof Net_DNS2_RR_TSIG)
            || ($this->auth_signature instanceof Net_DNS2_RR_SIG)
        ) {
            $packet->additional[] = $this->auth_signature;
            $packet->header->arcount = count($packet->additional);
        }


        return $this->sendPacket($packet, $this->use_tcp);
    }
}

abstract class Net_DNS2_RR
{
    public $name;
    public $type;
    public $class;
    public $ttl;
    public $rdlength;
    public $rdata;

    public function __construct(Net_DNS2_Packet &$packet = null, array $rr = null)
    {
        if ((!is_null($packet)) && (!is_null($rr))) {

            if (!$this->set($packet, $rr)) {

                throw new Net_DNS2_Exception(
                    'failed to generate resource record',
                    Net_DNS2_Lookups::E_RR_INVALID
                );
            }
        } else {

            $class = Net_DNS2_Lookups::$rr_types_class_to_id[get_class($this)];
            if (isset($class)) {

                $this->type = Net_DNS2_Lookups::$rr_types_by_id[$class];
            }

            $this->class = 'IN';
            $this->ttl = 86400;
        }
    }

    public function set(Net_DNS2_Packet &$packet, array $rr)
    {
        $this->name = $rr['name'];
        $this->type = Net_DNS2_Lookups::$rr_types_by_id[$rr['type']];


        if ($this->type == 'OPT') {
            $this->class = $rr['class'];
        } else {
            $this->class = Net_DNS2_Lookups::$classes_by_id[$rr['class']];
        }

        $this->ttl = $rr['ttl'];
        $this->rdlength = $rr['rdlength'];
        $this->rdata = substr($packet->rdata, $packet->offset, $rr['rdlength']);

        return $this->rrSet($packet);
    }

    abstract protected function rrSet(Net_DNS2_Packet &$packet);

    public static function parse(Net_DNS2_Packet &$packet)
    {
        $object = array();


        $object['name'] = $packet->expand($packet, $packet->offset);
        if (is_null($object['name'])) {

            throw new Net_DNS2_Exception(
                'failed to parse resource record: failed to expand name.',
                Net_DNS2_Lookups::E_PARSE_ERROR
            );
        }
        if ($packet->rdlength < ($packet->offset + 10)) {

            throw new Net_DNS2_Exception(
                'failed to parse resource record: packet too small.',
                Net_DNS2_Lookups::E_PARSE_ERROR
            );
        }


        $object['type'] = ord($packet->rdata[$packet->offset++]) << 8 |
            ord($packet->rdata[$packet->offset++]);
        $object['class'] = ord($packet->rdata[$packet->offset++]) << 8 |
            ord($packet->rdata[$packet->offset++]);

        $object['ttl'] = ord($packet->rdata[$packet->offset++]) << 24 |
            ord($packet->rdata[$packet->offset++]) << 16 |
            ord($packet->rdata[$packet->offset++]) << 8 |
            ord($packet->rdata[$packet->offset++]);

        $object['rdlength'] = ord($packet->rdata[$packet->offset++]) << 8 |
            ord($packet->rdata[$packet->offset++]);

        if ($packet->rdlength < ($packet->offset + $object['rdlength'])) {
            return null;
        }


        $o = null;
        $class = Net_DNS2_Lookups::$rr_types_id_to_class[$object['type']];

        if (isset($class)) {

            $o = new $class($packet, $object);
            if ($o) {

                $packet->offset += $object['rdlength'];
            }
        } else {

            throw new Net_DNS2_Exception(
                'un-implemented resource record type: ' . $object['type'],
                Net_DNS2_Lookups::E_RR_INVALID
            );
        }

        return $o;
    }

    public static function fromString($line)
    {
        if (strlen($line) == 0) {
            throw new Net_DNS2_Exception(
                'empty config line provided.',
                Net_DNS2_Lookups::E_PARSE_ERROR
            );
        }

        $name = '';
        $type = '';
        $class = 'IN';
        $ttl = 86400;


        $values = preg_split('/\s+/', $line);
        if (count($values) < 3) {

            throw new Net_DNS2_Exception(
                'failed to parse config: minimum of name, type and rdata required.',
                Net_DNS2_Lookups::E_PARSE_ERROR
            );
        }


        $name = trim(strtolower(array_shift($values)), '.');


        foreach ($values as $value) {

            switch ($value) {
                case is_numeric($value):

                    $ttl = array_shift($values);
                    break;


                case ($value === 0):
                    $ttl = array_shift($values);
                    break;

                case isset(Net_DNS2_Lookups::$classes_by_name[strtoupper($value)]):

                    $class = strtoupper(array_shift($values));
                    break;

                case isset(Net_DNS2_Lookups::$rr_types_by_name[strtoupper($value)]):

                    $type = strtoupper(array_shift($values));
                    break 2;
                default:

                    throw new Net_DNS2_Exception(
                        'invalid config line provided: unknown file: ' . $value,
                        Net_DNS2_Lookups::E_PARSE_ERROR
                    );
            }
        }


        $o = null;
        $class_name = Net_DNS2_Lookups::$rr_types_id_to_class[Net_DNS2_Lookups::$rr_types_by_name[$type]];

        if (isset($class_name)) {

            $o = new $class_name;
            if (!is_null($o)) {


                $o->name = $name;
                $o->class = $class;
                $o->ttl = $ttl;


                if ($o->rrFromString($values) === false) {

                    throw new Net_DNS2_Exception(
                        'failed to parse rdata for config: ' . $line,
                        Net_DNS2_Lookups::E_PARSE_ERROR
                    );
                }

            } else {

                throw new Net_DNS2_Exception(
                    'failed to create new RR record for type: ' . $type,
                    Net_DNS2_Lookups::E_RR_INVALID
                );
            }

        } else {

            throw new Net_DNS2_Exception(
                'un-implemented resource record type: ' . $type,
                Net_DNS2_Lookups::E_RR_INVALID
            );
        }

        return $o;
    }

    abstract protected function rrFromString(array $rdata);

    public function __toString()
    {
        return $this->name . '. ' . $this->ttl . ' ' . $this->class .
            ' ' . $this->type . ' ' . $this->rrToString();
    }

    abstract protected function rrToString();

    public function get(Net_DNS2_Packet &$packet)
    {
        $rdata = '';

        $data = $packet->compress($this->name, $packet->offset);

        if ($this->type == 'OPT') {

            $this->preBuild();

            $data .= pack(
                'nnN',
                Net_DNS2_Lookups::$rr_types_by_name[$this->type],
                $this->class,
                $this->ttl
            );
        } else {

            $data .= pack(
                'nnN',
                Net_DNS2_Lookups::$rr_types_by_name[$this->type],
                Net_DNS2_Lookups::$classes_by_name[$this->class],
                $this->ttl
            );
        }


        $packet->offset += 10;


        if ($this->rdlength != -1) {

            $rdata = $this->rrGet($packet);
        }


        $data .= pack('n', strlen($rdata)) . $rdata;

        return $data;
    }

    abstract protected function rrGet(Net_DNS2_Packet &$packet);

    public function cleanString($data)
    {
        return strtolower(rtrim($data, '.'));
    }

    protected function formatString($string)
    {
        return '"' . str_replace('"', '\"', trim($string, '"')) . '"';
    }

    protected function buildString(array $chunks)
    {
        $data = array();
        $c = 0;
        $in = false;

        foreach ($chunks as $r) {

            $r = trim($r);
            if (strlen($r) == 0) {
                continue;
            }

            if (($r[0] == '"')
                && ($r[strlen($r) - 1] == '"')
                && ($r[strlen($r) - 2] != '\\')
            ) {

                $data[$c] = $r;
                ++$c;
                $in = false;

            } else if ($r[0] == '"') {

                $data[$c] = $r;
                $in = true;

            } else if (($r[strlen($r) - 1] == '"')
                && ($r[strlen($r) - 2] != '\\')
            ) {

                $data[$c] .= ' ' . $r;
                ++$c;
                $in = false;

            } else {

                if ($in) {
                    $data[$c] .= ' ' . $r;
                } else {
                    $data[$c++] = $r;
                }
            }
        }

        foreach ($data as $index => $string) {

            $data[$index] = str_replace('\"', '"', trim($string, '"'));
        }

        return $data;
    }
}


if (!defined('SOCK_STREAM')) {
    define('SOCK_STREAM', 1);
}
if (!defined('SOCK_DGRAM')) {
    define('SOCK_DGRAM', 2);
}

abstract class Net_DNS2_Socket
{
    const SOCK_STREAM = SOCK_STREAM;
    const SOCK_DGRAM = SOCK_DGRAM;
    public $last_error;
    protected $sock;
    protected $type;
    protected $host;
    protected $port;
    protected $timeout;
    protected $local_host;
    protected $local_port;

    public function __construct($type, $host, $port, $timeout)
    {
        $this->type = $type;
        $this->host = $host;
        $this->port = $port;
        $this->timeout = $timeout;
    }

    public function __destruct()
    {
        $this->close();
    }

    abstract public function close();

    public function bindAddress($address, $port = 0)
    {
        $this->local_host = $address;
        $this->local_port = $port;

        return true;
    }

    abstract public function open();

    abstract public function write($data);

    abstract public function read(&$size, $max_size);
}


class Net_DNS2_Updater extends Net_DNS2
{

    private $_packet;

    public function __construct($zone, array $options = null)
    {
        parent::__construct($options);


        $this->_packet = new Net_DNS2_Packet_Request(
            strtolower(trim($zone, " \n\r\t.")), 'SOA', 'IN'
        );


        $this->_packet->header->opcode = Net_DNS2_Lookups::OPCODE_UPDATE;
    }

    public function signature($keyname, $signature)
    {
        return $this->signTSIG($keyname, $signature);
    }

    public function add(Net_DNS2_RR $rr)
    {
        $this->_checkName($rr->name);


        if (!in_array($rr, $this->_packet->authority)) {
            $this->_packet->authority[] = $rr;
        }

        return true;
    }

    private function _checkName($name)
    {
        if (!preg_match('/' . $this->_packet->question[0]->qname . '$/', $name)) {

            throw new Net_DNS2_Exception(
                'name provided (' . $name . ') does not match zone name (' .
                $this->_packet->question[0]->qname . ')',
                Net_DNS2_Lookups::E_PACKET_INVALID
            );
        }

        return true;
    }

    public function delete(Net_DNS2_RR $rr)
    {
        $this->_checkName($rr->name);

        $rr->ttl = 0;
        $rr->class = 'NONE';


        if (!in_array($rr, $this->_packet->authority)) {
            $this->_packet->authority[] = $rr;
        }

        return true;
    }

    public function deleteAny($name, $type)
    {
        $this->_checkName($name);

        $class = Net_DNS2_Lookups::$rr_types_id_to_class[Net_DNS2_Lookups::$rr_types_by_name[$type]];
        if (!isset($class)) {

            throw new Net_DNS2_Exception(
                'unknown or un-supported resource record type: ' . $type,
                Net_DNS2_Lookups::E_RR_INVALID
            );
        }

        $rr = new $class;

        $rr->name = $name;
        $rr->ttl = 0;
        $rr->class = 'ANY';
        $rr->rdlength = -1;
        $rr->rdata = '';


        if (!in_array($rr, $this->_packet->authority)) {
            $this->_packet->authority[] = $rr;
        }

        return true;
    }

    public function deleteAll($name)
    {
        $this->_checkName($name);


        $rr = new Net_DNS2_RR_ANY;

        $rr->name = $name;
        $rr->ttl = 0;
        $rr->type = 'ANY';
        $rr->class = 'ANY';
        $rr->rdlength = -1;
        $rr->rdata = '';


        if (!in_array($rr, $this->_packet->authority)) {
            $this->_packet->authority[] = $rr;
        }

        return true;
    }

    public function checkExists($name, $type)
    {
        $this->_checkName($name);

        $class = Net_DNS2_Lookups::$rr_types_id_to_class[Net_DNS2_Lookups::$rr_types_by_name[$type]];
        if (!isset($class)) {

            throw new Net_DNS2_Exception(
                'unknown or un-supported resource record type: ' . $type,
                Net_DNS2_Lookups::E_RR_INVALID
            );
        }

        $rr = new $class;

        $rr->name = $name;
        $rr->ttl = 0;
        $rr->class = 'ANY';
        $rr->rdlength = -1;
        $rr->rdata = '';


        if (!in_array($rr, $this->_packet->answer)) {
            $this->_packet->answer[] = $rr;
        }

        return true;
    }

    public function checkValueExists(Net_DNS2_RR $rr)
    {
        $this->_checkName($rr->name);

        $rr->ttl = 0;


        if (!in_array($rr, $this->_packet->answer)) {
            $this->_packet->answer[] = $rr;
        }

        return true;
    }

    public function checkNotExists($name, $type)
    {
        $this->_checkName($name);

        $class = Net_DNS2_Lookups::$rr_types_id_to_class[Net_DNS2_Lookups::$rr_types_by_name[$type]];
        if (!isset($class)) {

            throw new Net_DNS2_Exception(
                'unknown or un-supported resource record type: ' . $type,
                Net_DNS2_Lookups::E_RR_INVALID
            );
        }

        $rr = new $class;

        $rr->name = $name;
        $rr->ttl = 0;
        $rr->class = 'NONE';
        $rr->rdlength = -1;
        $rr->rdata = '';


        if (!in_array($rr, $this->_packet->answer)) {
            $this->_packet->answer[] = $rr;
        }

        return true;
    }

    public function checkNameInUse($name)
    {
        $this->_checkName($name);


        $rr = new Net_DNS2_RR_ANY;

        $rr->name = $name;
        $rr->ttl = 0;
        $rr->type = 'ANY';
        $rr->class = 'ANY';
        $rr->rdlength = -1;
        $rr->rdata = '';


        if (!in_array($rr, $this->_packet->answer)) {
            $this->_packet->answer[] = $rr;
        }

        return true;
    }

    public function checkNameNotInUse($name)
    {
        $this->_checkName($name);


        $rr = new Net_DNS2_RR_ANY;

        $rr->name = $name;
        $rr->ttl = 0;
        $rr->type = 'ANY';
        $rr->class = 'NONE';
        $rr->rdlength = -1;
        $rr->rdata = '';


        if (!in_array($rr, $this->_packet->answer)) {
            $this->_packet->answer[] = $rr;
        }

        return true;
    }

    public function packet()
    {


        $p = $this->_packet;


        if (($this->auth_signature instanceof Net_DNS2_RR_TSIG)
            || ($this->auth_signature instanceof Net_DNS2_RR_SIG)
        ) {
            $p->additional[] = $this->auth_signature;
        }


        $p->header->qdcount = count($p->question);
        $p->header->ancount = count($p->answer);
        $p->header->nscount = count($p->authority);
        $p->header->arcount = count($p->additional);

        return $p;
    }

    public function update(&$response = null)
    {


        $this->checkServers(Net_DNS2::RESOLV_CONF);


        if (($this->auth_signature instanceof Net_DNS2_RR_TSIG)
            || ($this->auth_signature instanceof Net_DNS2_RR_SIG)
        ) {
            $this->_packet->additional[] = $this->auth_signature;
        }


        $this->_packet->header->qdcount = count($this->_packet->question);
        $this->_packet->header->ancount = count($this->_packet->answer);
        $this->_packet->header->nscount = count($this->_packet->authority);
        $this->_packet->header->arcount = count($this->_packet->additional);


        if (($this->_packet->header->qdcount == 0)
            || ($this->_packet->header->nscount == 0)
        ) {
            throw new Net_DNS2_Exception(
                'empty headers- nothing to send!',
                Net_DNS2_Lookups::E_PACKET_INVALID
            );
        }


        $response = $this->sendPacket($this->_packet, $this->use_tcp);


        $this->_packet->reset();


        return true;
    }
}


class Net_DNS2_Cache_File extends Net_DNS2_Cache
{

    public function open($cache_file, $size, $serializer)
    {
        $this->cache_size = $size;
        $this->cache_file = $cache_file;
        $this->cache_serializer = $serializer;


        if (file_exists($this->cache_file)
            && (filesize($this->cache_file) > 0)
        ) {


            $fp = @fopen($this->cache_file, 'r');
            if ($fp !== false) {


                flock($fp, LOCK_EX);


                $data = fread($fp, filesize($this->cache_file));

                if ($this->cache_serializer == 'json') {

                    $decoded = json_decode($data, true);
                } else {

                    $decoded = unserialize($data);
                }

                if (is_array($decoded)) {

                    $this->cache_data = $decoded;
                } else {

                    $this->cache_data = array();
                }


                flock($fp, LOCK_UN);


                fclose($fp);


                $this->clean();
            }
        }
    }

    public function __destruct()
    {


        if (strlen($this->cache_file) == 0) {
            return;
        }


        $fp = fopen($this->cache_file, 'a+');
        if ($fp !== false) {


            flock($fp, LOCK_EX);


            fseek($fp, 0, SEEK_SET);


            $data = @fread($fp, filesize($this->cache_file));
            if (($data !== false) && (strlen($data) > 0)) {


                $c = $this->cache_data;

                if ($this->cache_serializer == 'json') {

                    $decoded = json_decode($data, true);
                } else {

                    $decoded = unserialize($data);
                }

                if (is_array($decoded)) {

                    $this->cache_data = array_merge($c, $decoded);
                }
            }


            ftruncate($fp, 0);


            $this->clean();


            $data = $this->resize();
            if (!is_null($data)) {


                fwrite($fp, $data);
            }


            flock($fp, LOCK_UN);


            fclose($fp);
        }
    }
}


class Net_DNS2_Cache_Shm extends Net_DNS2_Cache
{

    private $_cache_id = false;

    private $_cache_file_tok = -1;

    public function open($cache_file, $size, $serializer)
    {
        $this->cache_size = $size;
        $this->cache_file = $cache_file;
        $this->cache_serializer = $serializer;


        if (!file_exists($cache_file)) {

            if (file_put_contents($cache_file, '') === false) {

                throw new Net_DNS2_Exception(
                    'failed to create empty SHM file: ' . $cache_file,
                    Net_DNS2_Lookups::E_CACHE_SHM_FILE
                );
            }
        }


        $this->_cache_file_tok = ftok($cache_file, 't');
        if ($this->_cache_file_tok == -1) {

            throw new Net_DNS2_Exception(
                'failed on ftok() file: ' . $this->_cache_file_tok,
                Net_DNS2_Lookups::E_CACHE_SHM_FILE
            );
        }


        $this->_cache_id = @shmop_open($this->_cache_file_tok, 'w', 0, 0);
        if ($this->_cache_id !== false) {


            $allocated = shmop_size($this->_cache_id);
            if ($allocated > 0) {


                $data = trim(shmop_read($this->_cache_id, 0, $allocated));
                if (($data !== false) && (strlen($data) > 0)) {

                    if ($this->cache_serializer == 'json') {

                        $decoded = json_decode($data, true);
                    } else {

                        $decoded = unserialize($data);
                    }

                    if (is_array($decoded)) {

                        $this->cache_data = $decoded;
                    } else {

                        $this->cache_data = array();
                    }


                    $this->clean();
                }
            }
        }
    }

    public function __destruct()
    {


        if (strlen($this->cache_file) == 0) {
            return;
        }

        $fp = fopen($this->cache_file, 'r');
        if ($fp !== false) {


            flock($fp, LOCK_EX);


            if ($this->_cache_id === false) {


                $this->_cache_id = @shmop_open(
                    $this->_cache_file_tok, 'w', 0, 0
                );
                if ($this->_cache_id === false) {


                    $this->_cache_id = @shmop_open(
                        $this->_cache_file_tok, 'c', 0, $this->cache_size
                    );
                }
            }


            $allocated = shmop_size($this->_cache_id);


            $data = trim(shmop_read($this->_cache_id, 0, $allocated));


            if (($data !== false) && (strlen($data) > 0)) {

                $c = $this->cache_data;

                if ($this->cache_serializer == 'json') {

                    $decoded = json_decode($data, true);
                } else {

                    $decoded = unserialize($data);
                }

                if (is_array($decoded)) {

                    $this->cache_data = array_merge($c, $decoded);
                }
            }


            shmop_delete($this->_cache_id);


            $this->clean();


            $data = $this->resize();
            if (!is_null($data)) {


                $this->_cache_id = @shmop_open(
                    $this->_cache_file_tok, 'c', 0644, $this->cache_size
                );
                if ($this->_cache_id === false) {
                    return;
                }

                shmop_write($this->_cache_id, $data, 0);
            }


            shmop_close($this->_cache_id);


            flock($fp, LOCK_UN);


            fclose($fp);
        }
    }
}

class Net_DNS2_Packet_Request extends Net_DNS2_Packet
{

    public function __construct($name, $type = null, $class = null)
    {
        $this->set($name, $type, $class);
    }

    public function set($name, $type = 'A', $class = 'IN')
    {


        $this->header = new Net_DNS2_Header;


        $q = new Net_DNS2_Question();


        if ($name != '.') {
            $name = trim(strtolower($name), " \t\n\r\0\x0B.");
        }

        $type = strtoupper(trim($type));
        $class = strtoupper(trim($class));


        if (empty($name)) {

            throw new Net_DNS2_Exception(
                'empty query string provided',
                Net_DNS2_Lookups::E_PACKET_INVALID
            );
        }


        if ($type == '*') {

            $type = 'ANY';
        }


        if ((!isset(Net_DNS2_Lookups::$rr_types_by_name[$type]))
            || (!isset(Net_DNS2_Lookups::$classes_by_name[$class]))
        ) {
            throw new Net_DNS2_Exception(
                'invalid type (' . $type . ') or class (' . $class . ') specified.',
                Net_DNS2_Lookups::E_PACKET_INVALID
            );
        }

        if ($type == 'PTR') {


            if (Net_DNS2::isIPv4($name)) {


                $name = implode('.', array_reverse(explode('.', $name)));
                $name .= '.in-addr.arpa';

            } else if (Net_DNS2::isIPv6($name)) {


                $e = Net_DNS2::expandIPv6($name);
                if ($e !== false) {

                    $name = implode(
                        '.', array_reverse(str_split(str_replace(':', '', $e)))
                    );

                    $name .= '.ip6.arpa';

                } else {

                    throw new Net_DNS2_Exception(
                        'unsupported PTR value: ' . $name,
                        Net_DNS2_Lookups::E_PACKET_INVALID
                    );
                }
            }
        }


        $q->qname = $name;
        $q->qtype = $type;
        $q->qclass = $class;

        $this->question[] = $q;


        $this->answer = array();
        $this->authority = array();
        $this->additional = array();

        return true;
    }
}


class Net_DNS2_Packet_Response extends Net_DNS2_Packet
{

    public $answer_from;

    public $answer_socket_type;

    public $response_time = 0;

    public function __construct($data, $size)
    {
        $this->set($data, $size);
    }

    public function set($data, $size)
    {


        $this->rdata = $data;
        $this->rdlength = $size;


        $this->header = new Net_DNS2_Header($this);


        if ($this->header->tc == 1) {

            return false;
        }


        for ($x = 0; $x < $this->header->qdcount; ++$x) {

            $this->question[$x] = new Net_DNS2_Question($this);
        }


        for ($x = 0; $x < $this->header->ancount; ++$x) {

            $o = Net_DNS2_RR::parse($this);
            if (!is_null($o)) {

                $this->answer[] = $o;
            }
        }


        for ($x = 0; $x < $this->header->nscount; ++$x) {

            $o = Net_DNS2_RR::parse($this);
            if (!is_null($o)) {

                $this->authority[] = $o;
            }
        }


        for ($x = 0; $x < $this->header->arcount; ++$x) {

            $o = Net_DNS2_RR::parse($this);
            if (!is_null($o)) {

                $this->additional[] = $o;
            }
        }

        return true;
    }
}


class Net_DNS2_RR_A extends Net_DNS2_RR
{

    public $address;

    protected function rrToString()
    {
        return $this->address;
    }

    protected function rrFromString(array $rdata)
    {
        $value = array_shift($rdata);

        if (Net_DNS2::isIPv4($value)) {

            $this->address = $value;
            return true;
        }

        return false;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {

            $this->address = inet_ntop($this->rdata);
            if ($this->address !== false) {

                return true;
            }
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        $packet->offset += 4;
        return inet_pton($this->address);
    }
}


class Net_DNS2_RR_AAAA extends Net_DNS2_RR
{

    public $address;

    protected function rrToString()
    {
        return $this->address;
    }

    protected function rrFromString(array $rdata)
    {


        $value = array_shift($rdata);
        if (Net_DNS2::isIPv6($value)) {

            $this->address = $value;
            return true;
        }

        return false;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {


        if ($this->rdlength == 16) {


            $x = unpack('n8', $this->rdata);
            if (count($x) == 8) {

                $this->address = vsprintf('%x:%x:%x:%x:%x:%x:%x:%x', $x);
                return true;
            }
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        $packet->offset += 16;
        return inet_pton($this->address);
    }
}


class Net_DNS2_RR_AFSDB extends Net_DNS2_RR
{

    public $subtype;

    public $hostname;

    protected function rrToString()
    {
        return $this->subtype . ' ' . $this->cleanString($this->hostname) . '.';
    }

    protected function rrFromString(array $rdata)
    {
        $this->subtype = array_shift($rdata);
        $this->hostname = $this->cleanString(array_shift($rdata));

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {


            $x = unpack('nsubtype', $this->rdata);

            $this->subtype = $x['subtype'];
            $offset = $packet->offset + 2;

            $this->hostname = Net_DNS2_Packet::expand($packet, $offset);

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->hostname) > 0) {

            $data = pack('n', $this->subtype);
            $packet->offset += 2;

            $data .= $packet->compress($this->hostname, $packet->offset);

            return $data;
        }

        return null;
    }
}


class Net_DNS2_RR_ANY extends Net_DNS2_RR
{

    protected function rrToString()
    {
        return '';
    }

    protected function rrFromString(array $rdata)
    {
        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        return true;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        return '';
    }
}


class Net_DNS2_RR_APL extends Net_DNS2_RR
{

    public $apl_items = array();

    protected function rrToString()
    {
        $out = '';

        foreach ($this->apl_items as $item) {

            if ($item['n'] == 1) {

                $out .= '!';
            }

            $out .= $item['address_family'] . ':' .
                $item['afd_part'] . '/' . $item['prefix'] . ' ';
        }

        return trim($out);
    }

    protected function rrFromString(array $rdata)
    {
        foreach ($rdata as $item) {

            if (preg_match('/^(!?)([1|2]):([^\/]*)\/([0-9]{1,3})$/', $item, $m)) {

                $i = array(

                    'address_family' => $m[2],
                    'prefix' => $m[4],
                    'n' => ($m[1] == '!') ? 1 : 0,
                    'afd_part' => strtolower($m[3])
                );

                $address = $this->_trimZeros(
                    $i['address_family'], $i['afd_part']
                );

                $i['afd_length'] = count(explode('.', $address));

                $this->apl_items[] = $i;
            }
        }

        return true;
    }

    private function _trimZeros($family, $address)
    {
        switch ($family) {
            case 1:
                $a = array_reverse(explode('.', $address));
                break;
            case 2:
                $a = array_reverse(explode(':', $address));
                break;
            default:
                return '';
        }

        foreach ($a as $value) {

            if ($value === '0') {

                array_shift($a);
            }
        }

        return match ($family) {
            1 => implode('.', array_reverse($a)),
            2 => implode(':', array_reverse($a)),
            default => '',
        };
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {

            $offset = 0;

            while ($offset < $this->rdlength) {


                $x = unpack(
                    'naddress_family/Cprefix/Cextra', substr($this->rdata, $offset)
                );

                $item = array(

                    'address_family' => $x['address_family'],
                    'prefix' => $x['prefix'],
                    'n' => ($x['extra'] >> 7) & 0x1,
                    'afd_length' => $x['extra'] & 0xf
                );

                switch ($item['address_family']) {

                    case 1:
                        $r = unpack(
                            'C*', substr($this->rdata, $offset + 4, $item['afd_length'])
                        );
                        if (count($r) < 4) {

                            for ($c = count($r) + 1; $c < 4 + 1; $c++) {

                                $r[$c] = 0;
                            }
                        }

                        $item['afd_part'] = implode('.', $r);

                        break;
                    case 2:
                        $r = unpack(
                            'C*', substr($this->rdata, $offset + 4, $item['afd_length'])
                        );
                        if (count($r) < 8) {

                            for ($c = count($r) + 1; $c < 8 + 1; $c++) {

                                $r[$c] = 0;
                            }
                        }

                        $item['afd_part'] = sprintf(
                            '%x:%x:%x:%x:%x:%x:%x:%x',
                            $r[1], $r[2], $r[3], $r[4], $r[5], $r[6], $r[7], $r[8]
                        );

                        break;
                    default:
                        return false;
                }

                $this->apl_items[] = $item;

                $offset += 4 + $item['afd_length'];
            }

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (count($this->apl_items) > 0) {

            $data = '';

            foreach ($this->apl_items as $item) {


                $data .= pack(
                    'nCC',
                    $item['address_family'],
                    $item['prefix'],
                    ($item['n'] << 7) | $item['afd_length']
                );

                switch ($item['address_family']) {
                    case 1:
                        $address = explode(
                            '.',
                            $this->_trimZeros($item['address_family'], $item['afd_part'])
                        );

                        foreach ($address as $b) {
                            $data .= chr($b);
                        }
                        break;
                    case 2:
                        $address = explode(
                            ':',
                            $this->_trimZeros($item['address_family'], $item['afd_part'])
                        );

                        foreach ($address as $b) {
                            $data .= pack('H', $b);
                        }
                        break;
                    default:
                        return null;
                }
            }

            $packet->offset += strlen($data);

            return $data;
        }

        return null;
    }
}


class Net_DNS2_RR_ATMA extends Net_DNS2_RR
{

    public $format;

    public $address;

    protected function rrToString()
    {
        return $this->address;
    }

    protected function rrFromString(array $rdata)
    {
        $value = array_shift($rdata);

        if (ctype_xdigit($value)) {

            $this->format = 0;
            $this->address = $value;

        } else if (is_numeric($value)) {

            $this->format = 1;
            $this->address = $value;

        } else {

            return false;
        }

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {


            $x = unpack('Cformat/N*address', $this->rdata);

            $this->format = $x['format'];

            if ($this->format == 0) {

                $a = unpack('@1/H*address', $this->rdata);

                $this->address = $a['address'];

            } else if ($this->format == 1) {

                $this->address = substr($this->rdata, 1, $this->rdlength - 1);

            } else {

                return false;
            }

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        $data = chr($this->format);

        if ($this->format == 0) {

            $data .= pack('H*', $this->address);

        } else if ($this->format == 1) {

            $data .= $this->address;

        } else {

            return null;
        }

        $packet->offset += strlen($data);

        return $data;
    }
}


class Net_DNS2_RR_CAA extends Net_DNS2_RR
{

    public $flags;

    public $tag;

    public $value;

    protected function rrToString()
    {
        return $this->flags . ' ' . $this->tag . ' "' .
            trim($this->cleanString($this->value), '"') . '"';
    }

    protected function rrFromString(array $rdata)
    {
        $this->flags = array_shift($rdata);
        $this->tag = array_shift($rdata);

        $this->value = trim($this->cleanString(implode(' ', $rdata)), '"');

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {


            $x = unpack('Cflags/Ctag_length', $this->rdata);

            $this->flags = $x['flags'];
            $offset = 2;

            $this->tag = substr($this->rdata, $offset, $x['tag_length']);
            $offset += $x['tag_length'];

            $this->value = substr($this->rdata, $offset);

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->value) > 0) {

            $data = chr($this->flags);
            $data .= chr(strlen($this->tag)) . $this->tag . $this->value;

            $packet->offset += strlen($data);

            return $data;
        }

        return null;
    }
}


class Net_DNS2_RR_CERT extends Net_DNS2_RR
{

    const CERT_FORMAT_RES = 0;
    const CERT_FORMAT_PKIX = 1;
    const CERT_FORMAT_SPKI = 2;
    const CERT_FORMAT_PGP = 3;
    const CERT_FORMAT_IPKIX = 4;
    const CERT_FORMAT_ISPKI = 5;
    const CERT_FORMAT_IPGP = 6;
    const CERT_FORMAT_ACPKIX = 7;
    const CERT_FORMAT_IACPKIX = 8;
    const CERT_FORMAT_URI = 253;
    const CERT_FORMAT_OID = 254;

    public $cert_format_name_to_id = array();
    public $cert_format_id_to_name = array(

        self::CERT_FORMAT_RES => 'Reserved',
        self::CERT_FORMAT_PKIX => 'PKIX',
        self::CERT_FORMAT_SPKI => 'SPKI',
        self::CERT_FORMAT_PGP => 'PGP',
        self::CERT_FORMAT_IPKIX => 'IPKIX',
        self::CERT_FORMAT_ISPKI => 'ISPKI',
        self::CERT_FORMAT_IPGP => 'IPGP',
        self::CERT_FORMAT_ACPKIX => 'ACPKIX',
        self::CERT_FORMAT_IACPKIX => 'IACPKIX',
        self::CERT_FORMAT_URI => 'URI',
        self::CERT_FORMAT_OID => 'OID'
    );

    public $format;

    public $keytag;

    public $algorithm;

    public $certificate;

    public function __construct(Net_DNS2_Packet &$packet = null, array $rr = null)
    {
        parent::__construct($packet, $rr);


        $this->cert_format_name_to_id = array_flip($this->cert_format_id_to_name);
    }

    protected function rrToString()
    {
        return $this->format . ' ' . $this->keytag . ' ' . $this->algorithm .
            ' ' . base64_encode($this->certificate);
    }

    protected function rrFromString(array $rdata)
    {


        $this->format = array_shift($rdata);
        if (!is_numeric($this->format)) {

            $mnemonic = strtoupper(trim($this->format));
            if (!isset($this->cert_format_name_to_id[$mnemonic])) {

                return false;
            }

            $this->format = $this->cert_format_name_to_id[$mnemonic];
        } else {

            if (!isset($this->cert_format_id_to_name[$this->format])) {

                return false;
            }
        }

        $this->keytag = array_shift($rdata);


        $this->algorithm = array_shift($rdata);
        if (!is_numeric($this->algorithm)) {

            $mnemonic = strtoupper(trim($this->algorithm));
            if (!isset(Net_DNS2_Lookups::$algorithm_name_to_id[$mnemonic])) {

                return false;
            }

            $this->algorithm = Net_DNS2_Lookups::$algorithm_name_to_id[$mnemonic];
        } else {

            if (!isset(Net_DNS2_Lookups::$algorithm_id_to_name[$this->algorithm])) {
                return false;
            }
        }


        $this->certificate = base64_decode(implode(' ', $rdata));

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {


            $x = unpack('nformat/nkeytag/Calgorithm', $this->rdata);

            $this->format = $x['format'];
            $this->keytag = $x['keytag'];
            $this->algorithm = $x['algorithm'];


            $this->certificate = substr($this->rdata, 5, $this->rdlength - 5);

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->certificate) > 0) {

            $data = pack('nnC', $this->format, $this->keytag, $this->algorithm) .
                $this->certificate;

            $packet->offset += strlen($data);

            return $data;
        }

        return null;
    }
}


class Net_DNS2_RR_CNAME extends Net_DNS2_RR
{

    public $cname;

    protected function rrToString()
    {
        return $this->cleanString($this->cname) . '.';
    }

    protected function rrFromString(array $rdata)
    {
        $this->cname = $this->cleanString(array_shift($rdata));
        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {

            $offset = $packet->offset;
            $this->cname = Net_DNS2_Packet::expand($packet, $offset);

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->cname) > 0) {

            return $packet->compress($this->cname, $packet->offset);
        }

        return null;
    }
}


class Net_DNS2_RR_CSYNC extends Net_DNS2_RR
{

    public $serial;

    public $flags;

    public $type_bit_maps = array();

    protected function rrToString()
    {
        $out = $this->serial . ' ' . $this->flags;


        foreach ($this->type_bit_maps as $rr) {

            $out .= ' ' . strtoupper($rr);
        }

        return $out;
    }

    protected function rrFromString(array $rdata)
    {
        $this->serial = array_shift($rdata);
        $this->flags = array_shift($rdata);

        $this->type_bit_maps = $rdata;

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {


            $x = unpack('@' . $packet->offset . '/Nserial/nflags', $packet->rdata);

            $this->serial = Net_DNS2::expandUint32($x['serial']);
            $this->flags = $x['flags'];


            $this->type_bit_maps = Net_DNS2_BitMap::bitMapToArray(
                substr($this->rdata, 6)
            );

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {


        $data = pack('Nn', $this->serial, $this->flags);


        $data .= Net_DNS2_BitMap::arrayToBitMap($this->type_bit_maps);


        $packet->offset += strlen($data);

        return $data;
    }
}


class Net_DNS2_RR_DHCID extends Net_DNS2_RR
{

    public $id_type;

    public $digest_type;

    public $digest;

    protected function rrToString()
    {
        $out = pack('nC', $this->id_type, $this->digest_type);
        $out .= base64_decode($this->digest);

        return base64_encode($out);
    }

    protected function rrFromString(array $rdata)
    {
        $data = base64_decode(array_shift($rdata));
        if (strlen($data) > 0) {


            $x = unpack('nid_type/Cdigest_type', $data);

            $this->id_type = $x['id_type'];
            $this->digest_type = $x['digest_type'];


            $this->digest = base64_encode(substr($data, 3, strlen($data) - 3));

            return true;
        }

        return false;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {


            $x = unpack('nid_type/Cdigest_type', $this->rdata);

            $this->id_type = $x['id_type'];
            $this->digest_type = $x['digest_type'];


            $this->digest = base64_encode(
                substr($this->rdata, 3, $this->rdlength - 3)
            );

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->digest) > 0) {

            $data = pack('nC', $this->id_type, $this->digest_type) .
                base64_decode($this->digest);

            $packet->offset += strlen($data);

            return $data;
        }

        return null;
    }
}


class Net_DNS2_RR_DNAME extends Net_DNS2_RR
{

    public $dname;

    protected function rrToString()
    {
        return $this->cleanString($this->dname) . '.';
    }

    protected function rrFromString(array $rdata)
    {
        $this->dname = $this->cleanString(array_shift($rdata));
        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {

            $offset = $packet->offset;
            $this->dname = Net_DNS2_Packet::expand($packet, $offset);

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->dname) > 0) {

            return $packet->compress($this->dname, $packet->offset);
        }

        return null;
    }
}

class Net_DNS2_RR_DNSKEY extends Net_DNS2_RR
{

    public $flags;

    public $protocol;

    public $algorithm;

    public $key;

    protected function rrToString()
    {
        return $this->flags . ' ' . $this->protocol . ' ' .
            $this->algorithm . ' ' . $this->key;
    }

    protected function rrFromString(array $rdata)
    {
        $this->flags = array_shift($rdata);
        $this->protocol = array_shift($rdata);
        $this->algorithm = array_shift($rdata);
        $this->key = implode(' ', $rdata);

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {


            $x = unpack('nflags/Cprotocol/Calgorithm', $this->rdata);


            $this->flags = $x['flags'];
            $this->protocol = $x['protocol'];
            $this->algorithm = $x['algorithm'];

            $this->key = base64_encode(substr($this->rdata, 4));

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->key) > 0) {

            $data = pack('nCC', $this->flags, $this->protocol, $this->algorithm);
            $data .= base64_decode($this->key);

            $packet->offset += strlen($data);

            return $data;
        }

        return null;
    }
}

class Net_DNS2_RR_CDNSKEY extends Net_DNS2_RR_DNSKEY
{
}

class Net_DNS2_RR_DS extends Net_DNS2_RR
{

    public $keytag;

    public $algorithm;

    public $digesttype;

    public $digest;

    protected function rrToString()
    {
        return $this->keytag . ' ' . $this->algorithm . ' ' .
            $this->digesttype . ' ' . $this->digest;
    }

    protected function rrFromString(array $rdata)
    {
        $this->keytag = array_shift($rdata);
        $this->algorithm = array_shift($rdata);
        $this->digesttype = array_shift($rdata);
        $this->digest = implode('', $rdata);

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {


            $x = unpack('nkeytag/Calgorithm/Cdigesttype', $this->rdata);

            $this->keytag = $x['keytag'];
            $this->algorithm = $x['algorithm'];
            $this->digesttype = $x['digesttype'];


            $digest_size = 0;
            if ($this->digesttype == 1) {

                $digest_size = 20;

            } else if ($this->digesttype == 2) {

                $digest_size = 32;
            }


            $x = unpack('H*', substr($this->rdata, 4, $digest_size));
            $this->digest = $x[1];

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->digest) > 0) {

            $data = pack(
                'nCCH*',
                $this->keytag, $this->algorithm, $this->digesttype, $this->digest
            );

            $packet->offset += strlen($data);

            return $data;
        }

        return null;
    }
}

class Net_DNS2_RR_DLV extends Net_DNS2_RR_DS
{
}

class Net_DNS2_RR_CDS extends Net_DNS2_RR_DS
{
}

class Net_DNS2_RR_EID extends Net_DNS2_RR
{

    protected function rrToString()
    {
        return '';
    }

    protected function rrFromString(array $rdata)
    {
        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        return true;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        return $this->rdata;
    }
}


class Net_DNS2_RR_EUI48 extends Net_DNS2_RR
{

    public $address;

    protected function rrToString()
    {
        return $this->address;
    }

    protected function rrFromString(array $rdata)
    {
        $value = array_shift($rdata);


        $a = explode('-', $value);
        if (count($a) != 6) {

            return false;
        }


        foreach ($a as $i) {
            if (!ctype_xdigit($i)) {
                return false;
            }
        }


        $this->address = strtolower($value);

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {

            $x = unpack('C6', $this->rdata);
            if (count($x) == 6) {

                $this->address = vsprintf('%02x-%02x-%02x-%02x-%02x-%02x', $x);
                return true;
            }
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        $data = '';

        $a = explode('-', $this->address);
        foreach ($a as $b) {

            $data .= chr(hexdec($b));
        }

        $packet->offset += 6;
        return $data;
    }
}


class Net_DNS2_RR_EUI64 extends Net_DNS2_RR
{

    public $address;

    protected function rrToString()
    {
        return $this->address;
    }

    protected function rrFromString(array $rdata)
    {
        $value = array_shift($rdata);


        $a = explode('-', $value);
        if (count($a) != 8) {

            return false;
        }


        foreach ($a as $i) {
            if (!ctype_xdigit($i)) {
                return false;
            }
        }


        $this->address = strtolower($value);

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {

            $x = unpack('C8', $this->rdata);
            if (count($x) == 8) {

                $this->address = vsprintf(
                    '%02x-%02x-%02x-%02x-%02x-%02x-%02x-%02x', $x
                );
                return true;
            }
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        $data = '';

        $a = explode('-', $this->address);
        foreach ($a as $b) {

            $data .= chr(hexdec($b));
        }

        $packet->offset += 8;
        return $data;
    }
}


class Net_DNS2_RR_HINFO extends Net_DNS2_RR
{

    public $cpu;

    public $os;

    protected function rrToString()
    {
        return $this->formatString($this->cpu) . ' ' .
            $this->formatString($this->os);
    }

    protected function rrFromString(array $rdata)
    {
        $data = $this->buildString($rdata);
        if (count($data) == 2) {

            $this->cpu = $data[0];
            $this->os = $data[1];

            return true;
        }

        return false;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {

            $offset = $packet->offset;

            $this->cpu = trim(Net_DNS2_Packet::label($packet, $offset), '"');
            $this->os = trim(Net_DNS2_Packet::label($packet, $offset), '"');

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->cpu) > 0) {

            $data = chr(strlen($this->cpu)) . $this->cpu;
            $data .= chr(strlen($this->os)) . $this->os;

            $packet->offset += strlen($data);

            return $data;
        }

        return null;
    }
}


class Net_DNS2_RR_HIP extends Net_DNS2_RR
{

    public $hit_length;

    public $pk_algorithm;

    public $pk_length;

    public $hit;

    public $public_key;

    public $rendezvous_servers = array();

    protected function rrToString()
    {
        $out = $this->pk_algorithm . ' ' .
            $this->hit . ' ' . $this->public_key . ' ';

        foreach ($this->rendezvous_servers as $server) {

            $out .= $server . '. ';
        }

        return trim($out);
    }

    protected function rrFromString(array $rdata)
    {
        $this->pk_algorithm = array_shift($rdata);
        $this->hit = strtoupper(array_shift($rdata));
        $this->public_key = array_shift($rdata);


        if (count($rdata) > 0) {

            $this->rendezvous_servers = preg_replace('/\.$/', '', $rdata);
        }


        $this->hit_length = strlen(pack('H*', $this->hit));
        $this->pk_length = strlen(base64_decode($this->public_key));

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {


            $x = unpack('Chit_length/Cpk_algorithm/npk_length', $this->rdata);

            $this->hit_length = $x['hit_length'];
            $this->pk_algorithm = $x['pk_algorithm'];
            $this->pk_length = $x['pk_length'];

            $offset = 4;


            $hit = unpack('H*', substr($this->rdata, $offset, $this->hit_length));

            $this->hit = strtoupper($hit[1]);
            $offset += $this->hit_length;


            $this->public_key = base64_encode(
                substr($this->rdata, $offset, $this->pk_length)
            );
            $offset += $this->pk_length;


            $offset = $packet->offset + $offset;

            while (($offset - $packet->offset) < $this->rdlength) {

                $this->rendezvous_servers[] = Net_DNS2_Packet::expand(
                    $packet, $offset
                );
            }

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if ((strlen($this->hit) > 0) && (strlen($this->public_key) > 0)) {


            $data = pack(
                'CCnH*',
                $this->hit_length,
                $this->pk_algorithm,
                $this->pk_length,
                $this->hit
            );


            $data .= base64_decode($this->public_key);


            $packet->offset += strlen($data);


            foreach ($this->rendezvous_servers as $server) {

                $data .= $packet->compress($server, $packet->offset);
            }

            return $data;
        }

        return null;
    }
}


class Net_DNS2_RR_IPSECKEY extends Net_DNS2_RR
{
    const GATEWAY_TYPE_NONE = 0;
    const GATEWAY_TYPE_IPV4 = 1;
    const GATEWAY_TYPE_IPV6 = 2;
    const GATEWAY_TYPE_DOMAIN = 3;

    const ALGORITHM_NONE = 0;
    const ALGORITHM_DSA = 1;
    const ALGORITHM_RSA = 2;

    public $precedence;

    public $gateway_type;

    public $algorithm;

    public $gateway;

    public $key;

    protected function rrToString()
    {
        $out = $this->precedence . ' ' . $this->gateway_type . ' ' .
            $this->algorithm . ' ';

        switch ($this->gateway_type) {
            case self::GATEWAY_TYPE_NONE:
                $out .= '. ';
                break;

            case self::GATEWAY_TYPE_IPV4:
            case self::GATEWAY_TYPE_IPV6:
                $out .= $this->gateway . ' ';
                break;

            case self::GATEWAY_TYPE_DOMAIN:
                $out .= $this->gateway . '. ';
                break;
        }

        $out .= $this->key;
        return $out;
    }

    protected function rrFromString(array $rdata)
    {


        $precedence = array_shift($rdata);
        $gateway_type = array_shift($rdata);
        $algorithm = array_shift($rdata);
        $gateway = strtolower(trim(array_shift($rdata)));
        $key = array_shift($rdata);


        switch ($gateway_type) {
            case self::GATEWAY_TYPE_NONE:
                $gateway = '';
                break;

            case self::GATEWAY_TYPE_IPV4:
                if (!Net_DNS2::isIPv4($gateway)) {
                    return false;
                }
                break;

            case self::GATEWAY_TYPE_IPV6:
                if (!Net_DNS2::isIPv6($gateway)) {
                    return false;
                }
                break;

            case self::GATEWAY_TYPE_DOMAIN:
                break;

            default:
                return false;
        }


        switch ($algorithm) {
            case self::ALGORITHM_NONE:
                $key = '';
                break;

            case self::ALGORITHM_DSA:
            case self::ALGORITHM_RSA:
                break;

            default:
                return false;
        }


        $this->precedence = $precedence;
        $this->gateway_type = $gateway_type;
        $this->algorithm = $algorithm;
        $this->gateway = $gateway;
        $this->key = $key;

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {


            $x = unpack('Cprecedence/Cgateway_type/Calgorithm', $this->rdata);

            $this->precedence = $x['precedence'];
            $this->gateway_type = $x['gateway_type'];
            $this->algorithm = $x['algorithm'];

            $offset = 3;


            switch ($this->gateway_type) {
                case self::GATEWAY_TYPE_NONE:
                    $this->gateway = '';
                    break;

                case self::GATEWAY_TYPE_IPV4:
                    $this->gateway = inet_ntop(substr($this->rdata, $offset, 4));
                    $offset += 4;
                    break;

                case self::GATEWAY_TYPE_IPV6:
                    $ip = unpack('n8', substr($this->rdata, $offset, 16));
                    if (count($ip) == 8) {

                        $this->gateway = vsprintf('%x:%x:%x:%x:%x:%x:%x:%x', $ip);
                        $offset += 16;
                    } else {

                        return false;
                    }
                    break;

                case self::GATEWAY_TYPE_DOMAIN:

                    $doffset = $offset + $packet->offset;
                    $this->gateway = Net_DNS2_Packet::expand($packet, $doffset);
                    $offset = ($doffset - $packet->offset);
                    break;

                default:
                    return false;
            }


            switch ($this->algorithm) {
                case self::ALGORITHM_NONE:
                    $this->key = '';
                    break;

                case self::ALGORITHM_DSA:
                case self::ALGORITHM_RSA:
                    $this->key = base64_encode(substr($this->rdata, $offset));
                    break;

                default:
                    return false;
            }

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {


        $data = pack(
            'CCC', $this->precedence, $this->gateway_type, $this->algorithm
        );


        switch ($this->gateway_type) {
            case self::GATEWAY_TYPE_NONE:
                break;

            case self::GATEWAY_TYPE_IPV4:
            case self::GATEWAY_TYPE_IPV6:
                $data .= inet_pton($this->gateway);
                break;

            case self::GATEWAY_TYPE_DOMAIN:
                $data .= chr(strlen($this->gateway)) . $this->gateway;
                break;

            default:
                return null;
        }


        switch ($this->algorithm) {
            case self::ALGORITHM_NONE:
                break;

            case self::ALGORITHM_DSA:
            case self::ALGORITHM_RSA:
                $data .= base64_decode($this->key);
                break;

            default:
                return null;
        }

        $packet->offset += strlen($data);

        return $data;
    }
}


class Net_DNS2_RR_ISDN extends Net_DNS2_RR
{

    public $isdnaddress;

    public $sa;

    protected function rrToString()
    {
        return $this->formatString($this->isdnaddress) . ' ' .
            $this->formatString($this->sa);
    }

    protected function rrFromString(array $rdata)
    {
        $data = $this->buildString($rdata);
        if (count($data) >= 1) {

            $this->isdnaddress = $data[0];
            if (isset($data[1])) {

                $this->sa = $data[1];
            }

            return true;
        }

        return false;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {

            $this->isdnaddress = Net_DNS2_Packet::label($packet, $packet->offset);


            if ((strlen($this->isdnaddress) + 1) < $this->rdlength) {

                $this->sa = Net_DNS2_Packet::label($packet, $packet->offset);
            } else {

                $this->sa = '';
            }

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->isdnaddress) > 0) {

            $data = chr(strlen($this->isdnaddress)) . $this->isdnaddress;
            if (!empty($this->sa)) {

                $data .= chr(strlen($this->sa));
                $data .= $this->sa;
            }

            $packet->offset += strlen($data);

            return $data;
        }

        return null;
    }
}


class Net_DNS2_RR_KEY extends Net_DNS2_RR_DNSKEY
{
}


class Net_DNS2_RR_KX extends Net_DNS2_RR
{

    public $preference;

    public $exchange;

    protected function rrToString()
    {
        return $this->preference . ' ' . $this->cleanString($this->exchange) . '.';
    }

    protected function rrFromString(array $rdata)
    {
        $this->preference = array_shift($rdata);
        $this->exchange = $this->cleanString(array_shift($rdata));

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {


            $x = unpack('npreference', $this->rdata);
            $this->preference = $x['preference'];


            $offset = $packet->offset + 2;
            $this->exchange = Net_DNS2_Packet::label($packet, $offset);

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->exchange) > 0) {

            $data = pack('nC', $this->preference, strlen($this->exchange)) .
                $this->exchange;

            $packet->offset += strlen($data);

            return $data;
        }

        return null;
    }
}


class Net_DNS2_RR_L32 extends Net_DNS2_RR
{

    public $preference;

    public $locator32;

    protected function rrToString()
    {
        return $this->preference . ' ' . $this->locator32;
    }

    protected function rrFromString(array $rdata)
    {
        $this->preference = array_shift($rdata);
        $this->locator32 = array_shift($rdata);

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {


            $x = unpack('npreference/C4locator', $this->rdata);

            $this->preference = $x['preference'];


            $this->locator32 = $x['locator1'] . '.' . $x['locator2'] . '.' .
                $x['locator3'] . '.' . $x['locator4'];

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->locator32) > 0) {


            $n = explode('.', $this->locator32);


            return pack('nC4', $this->preference, $n[0], $n[1], $n[2], $n[3]);
        }

        return null;
    }
}


class Net_DNS2_RR_L64 extends Net_DNS2_RR
{

    public $preference;

    public $locator64;

    protected function rrToString()
    {
        return $this->preference . ' ' . $this->locator64;
    }

    protected function rrFromString(array $rdata)
    {
        $this->preference = array_shift($rdata);
        $this->locator64 = array_shift($rdata);

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {


            $x = unpack('npreference/n4locator', $this->rdata);

            $this->preference = $x['preference'];


            $this->locator64 = dechex($x['locator1']) . ':' .
                dechex($x['locator2']) . ':' .
                dechex($x['locator3']) . ':' .
                dechex($x['locator4']);

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->locator64) > 0) {


            $n = explode(':', $this->locator64);


            return pack(
                'n5', $this->preference, hexdec($n[0]), hexdec($n[1]),
                hexdec($n[2]), hexdec($n[3])
            );
        }

        return null;
    }
}


class Net_DNS2_RR_LOC extends Net_DNS2_RR
{

    const CONV_SEC = 1000;
    const CONV_MIN = 60000;
    const CONV_DEG = 3600000;
    const REFERENCE_ALT = 10000000;
    const REFERENCE_LATLON = 2147483648;
    public $version;
    public $size;
    public $horiz_pre;
    public $vert_pre;
    public $latitude;
    public $longitude;
    public $altitude;
    private $_powerOfTen = array(1, 10, 100, 1000, 10000, 100000,
        1000000, 10000000, 100000000, 1000000000);

    protected function rrToString()
    {
        if ($this->version == 0) {

            return $this->_d2Dms($this->latitude, 'LAT') . ' ' .
                $this->_d2Dms($this->longitude, 'LNG') . ' ' .
                sprintf('%.2fm', $this->altitude) . ' ' .
                sprintf('%.2fm', $this->size) . ' ' .
                sprintf('%.2fm', $this->horiz_pre) . ' ' .
                sprintf('%.2fm', $this->vert_pre);
        }

        return '';
    }

    private function _d2Dms($data, $latlng)
    {
        if ($latlng == 'LAT') {
            $hem = ($data > 0) ? 'N' : 'S';
        } else {
            $hem = ($data > 0) ? 'E' : 'W';
        }

        $data = abs($data);

        $deg = (int)$data;
        $min = (int)(($data - $deg) * 60);
        $sec = (int)(((($data - $deg) * 60) - $min) * 60);
        $msec = round((((((($data - $deg) * 60) - $min) * 60) - $sec) * 1000));

        return sprintf('%d %02d %02d.%03d %s', $deg, $min, $sec, round($msec), $hem);
    }

    protected function rrFromString(array $rdata)
    {


        $res = preg_match(
            '/^(\d+) \s+((\d+) \s+)?(([\d.]+) \s+)?([NS]) \s+(\d+) ' .
            '\s+((\d+) \s+)?(([\d.]+) \s+)?([EW]) \s+(-?[\d.]+) m?(\s+ ' .
            '([\d.]+) m?)?(\s+ ([\d.]+) m?)?(\s+ ([\d.]+) m?)?/ix',
            implode(' ', $rdata), $x
        );

        if ($res) {


            $latdeg = $x[1];
            $latmin = (isset($x[3])) ? $x[3] : 0;
            $latsec = (isset($x[5])) ? $x[5] : 0;
            $lathem = strtoupper($x[6]);

            $this->latitude = $this->_dms2d($latdeg, $latmin, $latsec, $lathem);


            $londeg = $x[7];
            $lonmin = (isset($x[9])) ? $x[9] : 0;
            $lonsec = (isset($x[11])) ? $x[11] : 0;
            $lonhem = strtoupper($x[12]);

            $this->longitude = $this->_dms2d($londeg, $lonmin, $lonsec, $lonhem);

            $this->size = (isset($x[15])) ? $x[15] : 1;
            $this->horiz_pre = ((isset($x[17])) ? $x[17] : 10000);
            $this->vert_pre = ((isset($x[19])) ? $x[19] : 10);
            $this->altitude = $x[13];

            return true;
        }

        return false;
    }

    private function _dms2d($deg, $min, $sec, $hem)
    {
        $deg = $deg - 0;
        $min = $min - 0;

        $sign = ($hem == 'W' || $hem == 'S') ? -1 : 1;
        return ((($sec / 60 + $min) / 60) + $deg) * $sign;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {


            $x = unpack(
                'Cver/Csize/Choriz_pre/Cvert_pre/Nlatitude/Nlongitude/Naltitude',
                $this->rdata
            );


            $this->version = $x['ver'];
            if ($this->version == 0) {

                $this->size = $this->_precsizeNtoA($x['size']);
                $this->horiz_pre = $this->_precsizeNtoA($x['horiz_pre']);
                $this->vert_pre = $this->_precsizeNtoA($x['vert_pre']);


                if ($x['latitude'] < 0) {

                    $this->latitude = ($x['latitude'] +
                            self::REFERENCE_LATLON) / self::CONV_DEG;
                } else {

                    $this->latitude = ($x['latitude'] -
                            self::REFERENCE_LATLON) / self::CONV_DEG;
                }

                if ($x['longitude'] < 0) {

                    $this->longitude = ($x['longitude'] +
                            self::REFERENCE_LATLON) / self::CONV_DEG;
                } else {

                    $this->longitude = ($x['longitude'] -
                            self::REFERENCE_LATLON) / self::CONV_DEG;
                }


                $this->altitude = ($x['altitude'] - self::REFERENCE_ALT) / 100;

                return true;

            } else {

                return false;
            }
        }

        return false;
    }

    private function _precsizeNtoA($prec)
    {
        $mantissa = (($prec >> 4) & 0x0f) % 10;
        $exponent = (($prec >> 0) & 0x0f) % 10;

        return $mantissa * $this->_powerOfTen[$exponent];
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if ($this->version == 0) {

            if ($this->latitude < 0) {

                $lat = ($this->latitude * self::CONV_DEG) - self::REFERENCE_LATLON;
            } else {

                $lat = ($this->latitude * self::CONV_DEG) + self::REFERENCE_LATLON;
            }

            if ($this->longitude < 0) {

                $lng = ($this->longitude * self::CONV_DEG) - self::REFERENCE_LATLON;
            } else {

                $lng = ($this->longitude * self::CONV_DEG) + self::REFERENCE_LATLON;
            }

            $packet->offset += 16;

            return pack(
                'CCCCNNN',
                $this->version,
                $this->_precsizeAtoN($this->size),
                $this->_precsizeAtoN($this->horiz_pre),
                $this->_precsizeAtoN($this->vert_pre),
                $lat, $lng,
                ($this->altitude * 100) + self::REFERENCE_ALT
            );
        }

        return null;
    }

    private function _precsizeAtoN($prec)
    {
        $exponent = 0;
        while ($prec >= 10) {

            $prec /= 10;
            ++$exponent;
        }

        return ($prec << 4) | ($exponent & 0x0f);
    }
}


class Net_DNS2_RR_LP extends Net_DNS2_RR
{

    public $preference;

    public $fqdn;

    protected function rrToString()
    {
        return $this->preference . ' ' . $this->fqdn . '.';
    }

    protected function rrFromString(array $rdata)
    {
        $this->preference = array_shift($rdata);
        $this->fqdn = trim(array_shift($rdata), '.');

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {


            $x = unpack('npreference', $this->rdata);
            $this->preference = $x['preference'];
            $offset = $packet->offset + 2;


            $this->fqdn = Net_DNS2_Packet::expand($packet, $offset);

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->fqdn) > 0) {

            $data = pack('n', $this->preference);
            $packet->offset += 2;

            $data .= $packet->compress($this->fqdn, $packet->offset);
            return $data;
        }

        return null;
    }
}


class Net_DNS2_RR_MX extends Net_DNS2_RR
{

    public $preference;

    public $exchange;

    protected function rrToString()
    {
        return $this->preference . ' ' . $this->cleanString($this->exchange) . '.';
    }

    protected function rrFromString(array $rdata)
    {
        $this->preference = array_shift($rdata);
        $this->exchange = $this->cleanString(array_shift($rdata));

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {


            $x = unpack('npreference', $this->rdata);
            $this->preference = $x['preference'];


            $offset = $packet->offset + 2;
            $this->exchange = Net_DNS2_Packet::expand($packet, $offset);

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->exchange) > 0) {

            $data = pack('n', $this->preference);
            $packet->offset += 2;

            $data .= $packet->compress($this->exchange, $packet->offset);
            return $data;
        }

        return null;
    }
}


class Net_DNS2_RR_NAPTR extends Net_DNS2_RR
{

    public $order;

    public $preference;

    public $flags;

    public $services;

    public $regexp;

    public $replacement;

    protected function rrToString()
    {
        return $this->order . ' ' . $this->preference . ' ' .
            $this->formatString($this->flags) . ' ' .
            $this->formatString($this->services) . ' ' .
            $this->formatString($this->regexp) . ' ' .
            $this->cleanString($this->replacement) . '.';
    }

    protected function rrFromString(array $rdata)
    {
        $this->order = array_shift($rdata);
        $this->preference = array_shift($rdata);

        $data = $this->buildString($rdata);
        if (count($data) == 4) {

            $this->flags = $data[0];
            $this->services = $data[1];
            $this->regexp = $data[2];
            $this->replacement = $this->cleanString($data[3]);

            return true;
        }

        return false;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {


            $x = unpack('norder/npreference', $this->rdata);

            $this->order = $x['order'];
            $this->preference = $x['preference'];

            $offset = $packet->offset + 4;

            $this->flags = Net_DNS2_Packet::label($packet, $offset);
            $this->services = Net_DNS2_Packet::label($packet, $offset);
            $this->regexp = Net_DNS2_Packet::label($packet, $offset);

            $this->replacement = Net_DNS2_Packet::expand($packet, $offset);

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if ((isset($this->order)) && (strlen($this->services) > 0)) {

            $data = pack('nn', $this->order, $this->preference);

            $data .= chr(strlen($this->flags)) . $this->flags;
            $data .= chr(strlen($this->services)) . $this->services;
            $data .= chr(strlen($this->regexp)) . $this->regexp;

            $packet->offset += strlen($data);

            $data .= $packet->compress($this->replacement, $packet->offset);

            return $data;
        }

        return null;
    }
}


class Net_DNS2_RR_NID extends Net_DNS2_RR
{

    public $preference;

    public $nodeid;

    protected function rrToString()
    {
        return $this->preference . ' ' . $this->nodeid;
    }

    protected function rrFromString(array $rdata)
    {
        $this->preference = array_shift($rdata);
        $this->nodeid = array_shift($rdata);

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {


            $x = unpack('npreference/n4nodeid', $this->rdata);

            $this->preference = $x['preference'];


            $this->nodeid = dechex($x['nodeid1']) . ':' .
                dechex($x['nodeid2']) . ':' .
                dechex($x['nodeid3']) . ':' .
                dechex($x['nodeid4']);

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->nodeid) > 0) {


            $n = explode(':', $this->nodeid);


            return pack(
                'n5', $this->preference, hexdec($n[0]), hexdec($n[1]),
                hexdec($n[2]), hexdec($n[3])
            );
        }

        return null;
    }
}


class Net_DNS2_RR_NIMLOCK extends Net_DNS2_RR
{

    protected function rrToString()
    {
        return '';
    }

    protected function rrFromString(array $rdata)
    {
        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        return true;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        return $this->rdata;
    }
}


class Net_DNS2_RR_NS extends Net_DNS2_RR
{

    public $nsdname;

    protected function rrToString()
    {
        return $this->cleanString($this->nsdname) . '.';
    }

    protected function rrFromString(array $rdata)
    {
        $this->nsdname = $this->cleanString(array_shift($rdata));
        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {

            $offset = $packet->offset;
            $this->nsdname = Net_DNS2_Packet::expand($packet, $offset);

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->nsdname) > 0) {

            return $packet->compress($this->nsdname, $packet->offset);
        }

        return null;
    }
}


class Net_DNS2_RR_NSAP extends Net_DNS2_RR
{
    public $afi;
    public $idi;
    public $dfi;
    public $aa;
    public $rsvd;
    public $rd;
    public $area;
    public $id;
    public $sel;

    protected function rrToString()
    {
        return $this->cleanString($this->afi) . '.' .
            $this->cleanString($this->idi) . '.' .
            $this->cleanString($this->dfi) . '.' .
            $this->cleanString($this->aa) . '.' .
            $this->cleanString($this->rsvd) . '.' .
            $this->cleanString($this->rd) . '.' .
            $this->cleanString($this->area) . '.' .
            $this->cleanString($this->id) . '.' .
            $this->sel;
    }

    protected function rrFromString(array $rdata)
    {
        $data = strtolower(trim(array_shift($rdata)));


        $data = str_replace(array('.', '0x'), '', $data);


        $x = unpack('A2afi/A4idi/A2dfi/A6aa/A4rsvd/A4rd/A4area/A12id/A2sel', $data);


        if ($x['afi'] == 47) {

            $this->afi = '0x' . $x['afi'];
            $this->idi = $x['idi'];
            $this->dfi = $x['dfi'];
            $this->aa = $x['aa'];
            $this->rsvd = $x['rsvd'];
            $this->rd = $x['rd'];
            $this->area = $x['area'];
            $this->id = $x['id'];
            $this->sel = $x['sel'];

            return true;
        }

        return false;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength == 20) {


            $this->afi = dechex(ord($this->rdata[0]));


            if ($this->afi == 47) {


                $x = unpack(
                    'Cafi/nidi/Cdfi/C3aa/nrsvd/nrd/narea/Nidh/nidl/Csel',
                    $this->rdata
                );

                $this->afi = sprintf('0x%02x', $x['afi']);
                $this->idi = sprintf('%04x', $x['idi']);
                $this->dfi = sprintf('%02x', $x['dfi']);
                $this->aa = sprintf(
                    '%06x', $x['aa1'] << 16 | $x['aa2'] << 8 | $x['aa3']
                );
                $this->rsvd = sprintf('%04x', $x['rsvd']);
                $this->rd = sprintf('%04x', $x['rd']);
                $this->area = sprintf('%04x', $x['area']);
                $this->id = sprintf('%08x', $x['idh']) .
                    sprintf('%04x', $x['idl']);
                $this->sel = sprintf('%02x', $x['sel']);

                return true;
            }
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if ($this->afi == 0x47) {


            $aa = unpack('A2x/A2y/A2z', $this->aa);


            $id = unpack('A8a/A4b', $this->id);


            $data = pack(
                'CnCCCCnnnNnC',
                hexdec($this->afi),
                hexdec($this->idi),
                hexdec($this->dfi),
                hexdec($aa['x']),
                hexdec($aa['y']),
                hexdec($aa['z']),
                hexdec($this->rsvd),
                hexdec($this->rd),
                hexdec($this->area),
                hexdec($id['a']),
                hexdec($id['b']),
                hexdec($this->sel)
            );

            if (strlen($data) == 20) {

                $packet->offset += 20;
                return $data;
            }
        }

        return null;
    }
}


class Net_DNS2_RR_NSEC extends Net_DNS2_RR
{

    public $next_domain_name;

    public $type_bit_maps = array();

    protected function rrToString()
    {
        $data = $this->cleanString($this->next_domain_name) . '.';

        foreach ($this->type_bit_maps as $rr) {

            $data .= ' ' . $rr;
        }

        return $data;
    }

    protected function rrFromString(array $rdata)
    {
        $this->next_domain_name = $this->cleanString(array_shift($rdata));
        $this->type_bit_maps = $rdata;

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {


            $offset = $packet->offset;
            $this->next_domain_name = Net_DNS2_Packet::expand($packet, $offset);


            $this->type_bit_maps = Net_DNS2_BitMap::bitMapToArray(
                substr($this->rdata, $offset - $packet->offset)
            );

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->next_domain_name) > 0) {

            $data = $packet->compress($this->next_domain_name, $packet->offset);
            $bitmap = Net_DNS2_BitMap::arrayToBitMap($this->type_bit_maps);

            $packet->offset += strlen($bitmap);

            return $data . $bitmap;
        }

        return null;
    }
}


class Net_DNS2_RR_NSEC3 extends Net_DNS2_RR
{

    public $algorithm;

    public $flags;

    public $iterations;

    public $salt_length;

    public $salt;

    public $hash_length;

    public $hashed_owner_name;

    public $type_bit_maps = array();

    protected function rrToString()
    {
        $out = $this->algorithm . ' ' . $this->flags . ' ' . $this->iterations . ' ';


        if ($this->salt_length > 0) {

            $out .= $this->salt;
        } else {

            $out .= '-';
        }


        $out .= ' ' . $this->hashed_owner_name;


        foreach ($this->type_bit_maps as $rr) {

            $out .= ' ' . strtoupper($rr);
        }

        return $out;
    }

    protected function rrFromString(array $rdata)
    {
        $this->algorithm = array_shift($rdata);
        $this->flags = array_shift($rdata);
        $this->iterations = array_shift($rdata);


        $salt = array_shift($rdata);
        if ($salt == '-') {

            $this->salt_length = 0;
            $this->salt = '';
        } else {

            $this->salt_length = strlen(pack('H*', $salt));
            $this->salt = strtoupper($salt);
        }

        $this->hashed_owner_name = array_shift($rdata);
        $this->hash_length = strlen(base64_decode($this->hashed_owner_name));

        $this->type_bit_maps = $rdata;

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {


            $x = unpack('Calgorithm/Cflags/niterations/Csalt_length', $this->rdata);

            $this->algorithm = $x['algorithm'];
            $this->flags = $x['flags'];
            $this->iterations = $x['iterations'];
            $this->salt_length = $x['salt_length'];

            $offset = 5;

            if ($this->salt_length > 0) {

                $x = unpack('H*', substr($this->rdata, $offset, $this->salt_length));
                $this->salt = strtoupper($x[1]);
                $offset += $this->salt_length;
            }


            $x = unpack('@' . $offset . '/Chash_length', $this->rdata);
            $offset++;


            $this->hash_length = $x['hash_length'];
            if ($this->hash_length > 0) {

                $this->hashed_owner_name = base64_encode(
                    substr($this->rdata, $offset, $this->hash_length)
                );
                $offset += $this->hash_length;
            }


            $this->type_bit_maps = Net_DNS2_BitMap::bitMapToArray(
                substr($this->rdata, $offset)
            );

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {


        $salt = pack('H*', $this->salt);
        $this->salt_length = strlen($salt);


        $data = pack(
            'CCnC',
            $this->algorithm, $this->flags, $this->iterations, $this->salt_length
        );
        $data .= $salt;


        $data .= chr($this->hash_length);
        if ($this->hash_length > 0) {

            $data .= base64_decode($this->hashed_owner_name);
        }


        $data .= Net_DNS2_BitMap::arrayToBitMap($this->type_bit_maps);

        $packet->offset += strlen($data);

        return $data;
    }
}


class Net_DNS2_RR_NSEC3PARAM extends Net_DNS2_RR
{

    public $algorithm;

    public $flags;

    public $iterations;

    public $salt_length;

    public $salt;

    protected function rrToString()
    {
        $out = $this->algorithm . ' ' . $this->flags . ' ' . $this->iterations . ' ';


        if ($this->salt_length > 0) {

            $out .= $this->salt;
        } else {

            $out .= '-';
        }

        return $out;
    }

    protected function rrFromString(array $rdata)
    {
        $this->algorithm = array_shift($rdata);
        $this->flags = array_shift($rdata);
        $this->iterations = array_shift($rdata);

        $salt = array_shift($rdata);
        if ($salt == '-') {

            $this->salt_length = 0;
            $this->salt = '';
        } else {

            $this->salt_length = strlen(pack('H*', $salt));
            $this->salt = strtoupper($salt);
        }

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {

            $x = unpack('Calgorithm/Cflags/niterations/Csalt_length', $this->rdata);

            $this->algorithm = $x['algorithm'];
            $this->flags = $x['flags'];
            $this->iterations = $x['iterations'];
            $this->salt_length = $x['salt_length'];

            if ($this->salt_length > 0) {

                $x = unpack('H*', substr($this->rdata, 5, $this->salt_length));
                $this->salt = strtoupper($x[1]);
            }

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        $salt = pack('H*', $this->salt);
        $this->salt_length = strlen($salt);

        $data = pack(
                'CCnC',
                $this->algorithm, $this->flags, $this->iterations, $this->salt_length
            ) . $salt;

        $packet->offset += strlen($data);

        return $data;
    }
}


class Net_DNS2_RR_OPENPGPKEY extends Net_DNS2_RR
{

    public $key;

    protected function rrToString()
    {
        return $this->key;
    }

    protected function rrFromString(array $rdata)
    {
        $this->key = array_shift($rdata);

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {

            $this->key = base64_encode($this->rdata);

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->key) > 0) {

            $data = base64_decode($this->key);

            $packet->offset += strlen($data);

            return $data;
        }

        return null;
    }
}


class Net_DNS2_RR_OPT extends Net_DNS2_RR
{

    public $option_code;

    public $option_length;

    public $option_data;

    public $extended_rcode;

    public $version;

    public $do;

    public $z;

    public function __construct(Net_DNS2_Packet &$packet = null, array $rr = null)
    {


        $this->type = 'OPT';
        $this->rdlength = 0;

        $this->option_length = 0;
        $this->extended_rcode = 0;
        $this->version = 0;
        $this->do = 0;
        $this->z = 0;


        if ((!is_null($packet)) && (!is_null($rr))) {

            parent::__construct($packet, $rr);
        }
    }

    protected function rrToString()
    {
        return $this->option_code . ' ' . $this->option_data;
    }

    protected function rrFromString(array $rdata)
    {
        $this->option_code = array_shift($rdata);
        $this->option_data = array_shift($rdata);
        $this->option_length = strlen($this->option_data);

        $x = unpack('Cextended/Cversion/Cdo/Cz', pack('N', $this->ttl));

        $this->extended_rcode = $x['extended'];
        $this->version = $x['version'];
        $this->do = ($x['do'] >> 7);
        $this->z = $x['z'];

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {


        $x = unpack('Cextended/Cversion/Cdo/Cz', pack('N', $this->ttl));

        $this->extended_rcode = $x['extended'];
        $this->version = $x['version'];
        $this->do = ($x['do'] >> 7);
        $this->z = $x['z'];


        if ($this->rdlength > 0) {


            $x = unpack('noption_code/noption_length', $this->rdata);

            $this->option_code = $x['option_code'];
            $this->option_length = $x['option_length'];


            $this->option_data = substr($this->rdata, 4);
        }

        return true;
    }

    protected function preBuild()
    {
        $ttl = unpack(
            'N',
            pack('CCCC', $this->extended_rcode, $this->version, ($this->do << 7), 0)
        );

        $this->ttl = $ttl[1];
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if ($this->option_code) {

            $data = pack('nn', $this->option_code, $this->option_length) .
                $this->option_data;

            $packet->offset += strlen($data);

            return $data;
        }

        return null;
    }
}


class Net_DNS2_RR_PTR extends Net_DNS2_RR
{

    public $ptrdname;

    protected function rrToString()
    {
        return rtrim($this->ptrdname, '.') . '.';
    }

    protected function rrFromString(array $rdata)
    {
        $this->ptrdname = rtrim(implode(' ', $rdata), '.');
        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {

            $offset = $packet->offset;
            $this->ptrdname = Net_DNS2_Packet::expand($packet, $offset);

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->ptrdname) > 0) {

            return $packet->compress($this->ptrdname, $packet->offset);
        }

        return null;
    }
}


class Net_DNS2_RR_PX extends Net_DNS2_RR
{

    public $preference;

    public $map822;

    public $mapx400;

    protected function rrToString()
    {
        return $this->preference . ' ' . $this->cleanString($this->map822) . '. ' .
            $this->cleanString($this->mapx400) . '.';
    }

    protected function rrFromString(array $rdata)
    {
        $this->preference = $rdata[0];
        $this->map822 = $this->cleanString($rdata[1]);
        $this->mapx400 = $this->cleanString($rdata[2]);

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {


            $x = unpack('npreference', $this->rdata);
            $this->preference = $x['preference'];

            $offset = $packet->offset + 2;

            $this->map822 = Net_DNS2_Packet::expand($packet, $offset);
            $this->mapx400 = Net_DNS2_Packet::expand($packet, $offset);

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->map822) > 0) {

            $data = pack('n', $this->preference);
            $packet->offset += 2;

            $data .= $packet->compress($this->map822, $packet->offset);
            $data .= $packet->compress($this->mapx400, $packet->offset);

            return $data;
        }

        return null;
    }
}


class Net_DNS2_RR_RP extends Net_DNS2_RR
{

    public $mboxdname;

    public $txtdname;

    protected function rrToString()
    {
        return $this->cleanString($this->mboxdname) . '. ' .
            $this->cleanString($this->txtdname) . '.';
    }

    protected function rrFromString(array $rdata)
    {
        $this->mboxdname = $this->cleanString($rdata[0]);
        $this->txtdname = $this->cleanString($rdata[1]);

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {

            $offset = $packet->offset;

            $this->mboxdname = Net_DNS2_Packet::expand($packet, $offset);
            $this->txtdname = Net_DNS2_Packet::expand($packet, $offset);

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->mboxdname) > 0) {

            return $packet->compress($this->mboxdname, $packet->offset) .
                $packet->compress($this->txtdname, $packet->offset);
        }

        return null;
    }
}


class Net_DNS2_RR_RRSIG extends Net_DNS2_RR
{

    public $typecovered;

    public $algorithm;

    public $labels;

    public $origttl;

    public $sigexp;

    public $sigincep;

    public $keytag;

    public $signname;

    public $signature;

    protected function rrToString()
    {
        return $this->typecovered . ' ' . $this->algorithm . ' ' .
            $this->labels . ' ' . $this->origttl . ' ' .
            $this->sigexp . ' ' . $this->sigincep . ' ' .
            $this->keytag . ' ' . $this->cleanString($this->signname) . '. ' .
            $this->signature;
    }

    protected function rrFromString(array $rdata)
    {
        $this->typecovered = strtoupper(array_shift($rdata));
        $this->algorithm = array_shift($rdata);
        $this->labels = array_shift($rdata);
        $this->origttl = array_shift($rdata);
        $this->sigexp = array_shift($rdata);
        $this->sigincep = array_shift($rdata);
        $this->keytag = array_shift($rdata);
        $this->signname = $this->cleanString(array_shift($rdata));

        foreach ($rdata as $line) {

            $this->signature .= $line;
        }

        $this->signature = trim($this->signature);

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {


            $x = unpack(
                'ntc/Calgorithm/Clabels/Norigttl/Nsigexp/Nsigincep/nkeytag',
                $this->rdata
            );

            $this->typecovered = Net_DNS2_Lookups::$rr_types_by_id[$x['tc']];
            $this->algorithm = $x['algorithm'];
            $this->labels = $x['labels'];
            $this->origttl = Net_DNS2::expandUint32($x['origttl']);


            $this->sigexp = gmdate('YmdHis', $x['sigexp']);
            $this->sigincep = gmdate('YmdHis', $x['sigincep']);


            $this->keytag = $x['keytag'];


            $offset = $packet->offset + 18;
            $sigoffset = $offset;

            $this->signname = strtolower(
                Net_DNS2_Packet::expand($packet, $sigoffset)
            );
            $this->signature = base64_encode(
                substr($this->rdata, 18 + ($sigoffset - $offset))
            );

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->signature) > 0) {


            preg_match(
                '/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/', $this->sigexp, $e
            );
            preg_match(
                '/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/', $this->sigincep, $i
            );


            $data = pack(
                'nCCNNNn',
                Net_DNS2_Lookups::$rr_types_by_name[$this->typecovered],
                $this->algorithm,
                $this->labels,
                $this->origttl,
                gmmktime($e[4], $e[5], $e[6], $e[2], $e[3], $e[1]),
                gmmktime($i[4], $i[5], $i[6], $i[2], $i[3], $i[1]),
                $this->keytag
            );


            $names = explode('.', strtolower($this->signname));
            foreach ($names as $name) {

                $data .= chr(strlen($name));
                $data .= $name;
            }
            $data .= "\0";


            $data .= base64_decode($this->signature);

            $packet->offset += strlen($data);

            return $data;
        }

        return null;
    }
}


class Net_DNS2_RR_RT extends Net_DNS2_RR
{

    public $preference;

    public $intermediatehost;

    protected function rrToString()
    {
        return $this->preference . ' ' .
            $this->cleanString($this->intermediatehost) . '.';
    }

    protected function rrFromString(array $rdata)
    {
        $this->preference = $rdata[0];
        $this->intermediatehost = $this->cleanString($rdata[1]);

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {


            $x = unpack('npreference', $this->rdata);

            $this->preference = $x['preference'];
            $offset = $packet->offset + 2;

            $this->intermediatehost = Net_DNS2_Packet::expand($packet, $offset);

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->intermediatehost) > 0) {

            $data = pack('n', $this->preference);
            $packet->offset += 2;

            $data .= $packet->compress($this->intermediatehost, $packet->offset);

            return $data;
        }

        return null;
    }
}


class Net_DNS2_RR_SIG extends Net_DNS2_RR
{

    public $private_key = null;

    public $typecovered;

    public $algorithm;

    public $labels;

    public $origttl;

    public $sigexp;

    public $sigincep;

    public $keytag;

    public $signname;

    public $signature;

    protected function rrToString()
    {
        return $this->typecovered . ' ' . $this->algorithm . ' ' .
            $this->labels . ' ' . $this->origttl . ' ' .
            $this->sigexp . ' ' . $this->sigincep . ' ' .
            $this->keytag . ' ' . $this->cleanString($this->signname) . '. ' .
            $this->signature;
    }

    protected function rrFromString(array $rdata)
    {
        $this->typecovered = strtoupper(array_shift($rdata));
        $this->algorithm = array_shift($rdata);
        $this->labels = array_shift($rdata);
        $this->origttl = array_shift($rdata);
        $this->sigexp = array_shift($rdata);
        $this->sigincep = array_shift($rdata);
        $this->keytag = array_shift($rdata);
        $this->signname = $this->cleanString(array_shift($rdata));

        foreach ($rdata as $line) {

            $this->signature .= $line;
        }

        $this->signature = trim($this->signature);

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {


            $x = unpack(
                'ntc/Calgorithm/Clabels/Norigttl/Nsigexp/Nsigincep/nkeytag',
                $this->rdata
            );

            $this->typecovered = Net_DNS2_Lookups::$rr_types_by_id[$x['tc']];
            $this->algorithm = $x['algorithm'];
            $this->labels = $x['labels'];
            $this->origttl = Net_DNS2::expandUint32($x['origttl']);


            $this->sigexp = gmdate('YmdHis', $x['sigexp']);
            $this->sigincep = gmdate('YmdHis', $x['sigincep']);


            $this->keytag = $x['keytag'];


            $offset = $packet->offset + 18;
            $sigoffset = $offset;

            $this->signname = strtolower(
                Net_DNS2_Packet::expand($packet, $sigoffset)
            );
            $this->signature = base64_encode(
                substr($this->rdata, 18 + ($sigoffset - $offset))
            );

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {


        preg_match(
            '/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/', $this->sigexp, $e
        );
        preg_match(
            '/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/', $this->sigincep, $i
        );


        $data = pack(
            'nCCNNNn',
            Net_DNS2_Lookups::$rr_types_by_name[$this->typecovered],
            $this->algorithm,
            $this->labels,
            $this->origttl,
            gmmktime($e[4], $e[5], $e[6], $e[2], $e[3], $e[1]),
            gmmktime($i[4], $i[5], $i[6], $i[2], $i[3], $i[1]),
            $this->keytag
        );


        $names = explode('.', strtolower($this->signname));
        foreach ($names as $name) {

            $data .= chr(strlen($name));
            $data .= $name;
        }

        $data .= chr('0');


        if ((strlen($this->signature) == 0)
            && ($this->private_key instanceof Net_DNS2_PrivateKey)
            && (extension_loaded('openssl') === true)
        ) {


            $new_packet = new Net_DNS2_Packet_Request('example.com', 'SOA', 'IN');


            $new_packet->copy($packet);


            array_pop($new_packet->additional);
            $new_packet->header->arcount = count($new_packet->additional);


            $sigdata = $data . $new_packet->get();


            switch ($this->algorithm) {


                case Net_DNS2_Lookups::DNSSEC_ALGORITHM_RSAMD5:

                    $algorithm = OPENSSL_ALGO_MD5;
                    break;


                case Net_DNS2_Lookups::DNSSEC_ALGORITHM_RSASHA1:

                    $algorithm = OPENSSL_ALGO_SHA1;
                    break;


                case Net_DNS2_Lookups::DNSSEC_ALGORITHM_RSASHA256:

                    if (version_compare(PHP_VERSION, '5.4.8', '<')) {

                        throw new Net_DNS2_Exception(
                            'SHA256 support is only available in PHP >= 5.4.8',
                            Net_DNS2_Lookups::E_OPENSSL_INV_ALGO
                        );
                    }

                    $algorithm = OPENSSL_ALGO_SHA256;
                    break;


                case Net_DNS2_Lookups::DNSSEC_ALGORITHM_RSASHA512:

                    if (version_compare(PHP_VERSION, '5.4.8', '<')) {

                        throw new Net_DNS2_Exception(
                            'SHA512 support is only available in PHP >= 5.4.8',
                            Net_DNS2_Lookups::E_OPENSSL_INV_ALGO
                        );
                    }

                    $algorithm = OPENSSL_ALGO_SHA512;
                    break;


                case Net_DNS2_Lookups::DNSSEC_ALGORITHM_DSA:
                case Net_DNS2_Lookups::DSNSEC_ALGORITHM_RSASHA1NSEC3SHA1:
                case Net_DNS2_Lookups::DNSSEC_ALGORITHM_DSANSEC3SHA1:
                default:
                    throw new Net_DNS2_Exception(
                        'invalid or unsupported algorithm',
                        Net_DNS2_Lookups::E_OPENSSL_INV_ALGO
                    );
            }


            if (!openssl_sign($sigdata, $this->signature, $this->private_key->instance, $algorithm)) {

                throw new Net_DNS2_Exception(
                    openssl_error_string(),
                    Net_DNS2_Lookups::E_OPENSSL_ERROR
                );
            }


            switch ($this->algorithm) {


                case Net_DNS2_Lookups::DNSSEC_ALGORITHM_RSAMD5:
                case Net_DNS2_Lookups::DNSSEC_ALGORITHM_RSASHA1:
                case Net_DNS2_Lookups::DNSSEC_ALGORITHM_RSASHA256:
                case Net_DNS2_Lookups::DNSSEC_ALGORITHM_RSASHA512:

                    $this->signature = base64_encode($this->signature);
                    break;
            }
        }


        $data .= base64_decode($this->signature);

        $packet->offset += strlen($data);

        return $data;
    }
}


class Net_DNS2_RR_SOA extends Net_DNS2_RR
{

    public $mname;

    public $rname;

    public $serial;

    public $refresh;

    public $retry;

    public $expire;

    public $minimum;

    protected function rrToString()
    {
        return $this->cleanString($this->mname) . '. ' .
            $this->cleanString($this->rname) . '. ' .
            $this->serial . ' ' . $this->refresh . ' ' . $this->retry . ' ' .
            $this->expire . ' ' . $this->minimum;
    }

    protected function rrFromString(array $rdata)
    {
        $this->mname = $this->cleanString($rdata[0]);
        $this->rname = $this->cleanString($rdata[1]);

        $this->serial = $rdata[2];
        $this->refresh = $rdata[3];
        $this->retry = $rdata[4];
        $this->expire = $rdata[5];
        $this->minimum = $rdata[6];

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {


            $offset = $packet->offset;

            $this->mname = Net_DNS2_Packet::expand($packet, $offset);
            $this->rname = Net_DNS2_Packet::expand($packet, $offset);


            $x = unpack(
                '@' . $offset . '/Nserial/Nrefresh/Nretry/Nexpire/Nminimum/',
                $packet->rdata
            );

            $this->serial = Net_DNS2::expandUint32($x['serial']);
            $this->refresh = Net_DNS2::expandUint32($x['refresh']);
            $this->retry = Net_DNS2::expandUint32($x['retry']);
            $this->expire = Net_DNS2::expandUint32($x['expire']);
            $this->minimum = Net_DNS2::expandUint32($x['minimum']);

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->mname) > 0) {

            $data = $packet->compress($this->mname, $packet->offset);
            $data .= $packet->compress($this->rname, $packet->offset);

            $data .= pack(
                'N5', $this->serial, $this->refresh, $this->retry,
                $this->expire, $this->minimum
            );

            $packet->offset += 20;

            return $data;
        }

        return null;
    }
}


class Net_DNS2_RR_SRV extends Net_DNS2_RR
{

    public $priority;

    public $weight;

    public $port;

    public $target;

    protected function rrToString()
    {
        return $this->priority . ' ' . $this->weight . ' ' .
            $this->port . ' ' . $this->cleanString($this->target) . '.';
    }

    protected function rrFromString(array $rdata)
    {
        $this->priority = $rdata[0];
        $this->weight = $rdata[1];
        $this->port = $rdata[2];

        $this->target = $this->cleanString($rdata[3]);

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {


            $x = unpack('npriority/nweight/nport', $this->rdata);

            $this->priority = $x['priority'];
            $this->weight = $x['weight'];
            $this->port = $x['port'];

            $offset = $packet->offset + 6;
            $this->target = Net_DNS2_Packet::expand($packet, $offset);

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->target) > 0) {

            $data = pack('nnn', $this->priority, $this->weight, $this->port);
            $packet->offset += 6;

            $data .= $packet->compress($this->target, $packet->offset);

            return $data;
        }

        return null;
    }
}


class Net_DNS2_RR_SSHFP extends Net_DNS2_RR
{

    const SSHFP_ALGORITHM_RES = 0;
    const SSHFP_ALGORITHM_RSA = 1;
    const SSHFP_ALGORITHM_DSS = 2;
    const SSHFP_FPTYPE_RES = 0;
    const SSHFP_FPTYPE_SHA1 = 1;
    public $algorithm;
    public $fp_type;
    public $fingerprint;

    protected function rrToString()
    {
        return $this->algorithm . ' ' . $this->fp_type . ' ' . $this->fingerprint;
    }

    protected function rrFromString(array $rdata)
    {


        $algorithm = array_shift($rdata);
        $fp_type = array_shift($rdata);
        $fingerprint = strtolower(implode('', $rdata));


        if (($algorithm != self::SSHFP_ALGORITHM_RSA)
            && ($algorithm != self::SSHFP_ALGORITHM_DSS)
        ) {
            return false;
        }


        if ($fp_type != self::SSHFP_FPTYPE_SHA1) {
            return false;
        }

        $this->algorithm = $algorithm;
        $this->fp_type = $fp_type;
        $this->fingerprint = $fingerprint;

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {


            $x = unpack('Calgorithm/Cfp_type', $this->rdata);

            $this->algorithm = $x['algorithm'];
            $this->fp_type = $x['fp_type'];


            if (($this->algorithm != self::SSHFP_ALGORITHM_RSA)
                && ($this->algorithm != self::SSHFP_ALGORITHM_DSS)
            ) {
                return false;
            }


            if ($this->fp_type != self::SSHFP_FPTYPE_SHA1) {
                return false;
            }


            $fp = unpack('H*a', substr($this->rdata, 2));
            $this->fingerprint = strtolower($fp['a']);

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->fingerprint) > 0) {

            $data = pack(
                'CCH*', $this->algorithm, $this->fp_type, $this->fingerprint
            );

            $packet->offset += strlen($data);

            return $data;
        }

        return null;
    }
}


class Net_DNS2_RR_TA extends Net_DNS2_RR_DS
{
}


class Net_DNS2_RR_TALINK extends Net_DNS2_RR
{

    public $previous;

    public $next;

    protected function rrToString()
    {
        return $this->cleanString($this->previous) . '. ' .
            $this->cleanString($this->next) . '.';
    }

    protected function rrFromString(array $rdata)
    {
        $this->previous = $this->cleanString($rdata[0]);
        $this->next = $this->cleanString($rdata[1]);

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {

            $offset = $packet->offset;

            $this->previous = Net_DNS2_Packet::label($packet, $offset);
            $this->next = Net_DNS2_Packet::label($packet, $offset);

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if ((strlen($this->previous) > 0) || (strlen($this->next) > 0)) {

            $data = chr(strlen($this->previous)) . $this->previous .
                chr(strlen($this->next)) . $this->next;

            $packet->offset += strlen($data);

            return $data;
        }

        return null;
    }
}


class Net_DNS2_RR_TKEY extends Net_DNS2_RR
{
    const TSIG_MODE_RES = 0;
    const TSIG_MODE_SERV_ASSIGN = 1;
    const TSIG_MODE_DH = 2;
    const TSIG_MODE_GSS_API = 3;
    const TSIG_MODE_RESV_ASSIGN = 4;
    const TSIG_MODE_KEY_DELE = 5;
    public $algorithm;
    public $inception;
    public $expiration;
    public $mode;
    public $error;
    public $key_size;
    public $key_data;
    public $other_size;
    public $other_data;
    public $tsgi_mode_id_to_name = array(

        self::TSIG_MODE_RES => 'Reserved',
        self::TSIG_MODE_SERV_ASSIGN => 'Server Assignment',
        self::TSIG_MODE_DH => 'Diffie-Hellman',
        self::TSIG_MODE_GSS_API => 'GSS-API',
        self::TSIG_MODE_RESV_ASSIGN => 'Resolver Assignment',
        self::TSIG_MODE_KEY_DELE => 'Key Deletion'
    );

    protected function rrToString()
    {
        $out = $this->cleanString($this->algorithm) . '. ' . $this->mode;
        if ($this->key_size > 0) {

            $out .= ' ' . trim($this->key_data, '.') . '.';
        } else {

            $out .= ' .';
        }

        return $out;
    }

    protected function rrFromString(array $rdata)
    {


        $this->algorithm = $this->cleanString(array_shift($rdata));
        $this->mode = array_shift($rdata);
        $this->key_data = trim(array_shift($rdata), '.');


        $this->inception = time();
        $this->expiration = time() + 86400;
        $this->error = 0;
        $this->key_size = strlen($this->key_data);
        $this->other_size = 0;
        $this->other_data = '';

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {


            $offset = $packet->offset;
            $this->algorithm = Net_DNS2_Packet::expand($packet, $offset);


            $x = unpack(
                '@' . $offset . '/Ninception/Nexpiration/nmode/nerror/nkey_size',
                $packet->rdata
            );

            $this->inception = Net_DNS2::expandUint32($x['inception']);
            $this->expiration = Net_DNS2::expandUint32($x['expiration']);
            $this->mode = $x['mode'];
            $this->error = $x['error'];
            $this->key_size = $x['key_size'];

            $offset += 14;


            if ($this->key_size > 0) {

                $this->key_data = substr($packet->rdata, $offset, $this->key_size);
                $offset += $this->key_size;
            }


            $x = unpack('@' . $offset . '/nother_size', $packet->rdata);

            $this->other_size = $x['other_size'];
            $offset += 2;


            if ($this->other_size > 0) {

                $this->other_data = substr(
                    $packet->rdata, $offset, $this->other_size
                );
            }

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->algorithm) > 0) {


            $this->key_size = strlen($this->key_data);
            $this->other_size = strlen($this->other_data);


            $data = Net_DNS2_Packet::pack($this->algorithm);


            $data .= pack(
                'NNnnn', $this->inception, $this->expiration,
                $this->mode, 0, $this->key_size
            );


            if ($this->key_size > 0) {

                $data .= $this->key_data;
            }


            $data .= pack('n', $this->other_size);
            if ($this->other_size > 0) {

                $data .= $this->other_data;
            }

            $packet->offset += strlen($data);

            return $data;
        }

        return null;
    }
}


class Net_DNS2_RR_TLSA extends Net_DNS2_RR
{

    public $cert_usage;

    public $selector;

    public $matching_type;

    public $certificate;

    protected function rrToString()
    {
        return $this->cert_usage . ' ' . $this->selector . ' ' .
            $this->matching_type . ' ' . base64_encode($this->certificate);
    }

    protected function rrFromString(array $rdata)
    {
        $this->cert_usage = array_shift($rdata);
        $this->selector = array_shift($rdata);
        $this->matching_type = array_shift($rdata);
        $this->certificate = base64_decode(implode('', $rdata));

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {


            $x = unpack('Cusage/Cselector/Ctype', $this->rdata);

            $this->cert_usage = $x['usage'];
            $this->selector = $x['selector'];
            $this->matching_type = $x['type'];


            $this->certificate = substr($this->rdata, 3, $this->rdlength - 3);

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->certificate) > 0) {

            $data = pack(
                    'CCC', $this->cert_usage, $this->selector, $this->matching_type
                ) . $this->certificate;

            $packet->offset += strlen($data);

            return $data;
        }

        return null;
    }
}


class Net_DNS2_RR_TSIG extends Net_DNS2_RR
{

    const HMAC_MD5 = 'hmac-md5.sig-alg.reg.int';
    const GSS_TSIG = 'gss-tsig';
    const HMAC_SHA1 = 'hmac-sha1';
    const HMAC_SHA224 = 'hmac-sha224';
    const HMAC_SHA256 = 'hmac-sha256';
    const HMAC_SHA384 = 'hmac-sha384';
    const HMAC_SHA512 = 'hmac-sha512';

    public static $hash_algorithms = array(

        self::HMAC_MD5 => 'md5',
        self::HMAC_SHA1 => 'sha1',
        self::HMAC_SHA224 => 'sha224',
        self::HMAC_SHA256 => 'sha256',
        self::HMAC_SHA384 => 'sha384',
        self::HMAC_SHA512 => 'sha512'
    );

    public $algorithm;

    public $time_signed;

    public $fudge;

    public $mac_size;

    public $mac;

    public $original_id;

    public $error;

    public $other_length;

    public $other_data;

    public $key;

    protected function rrToString()
    {
        $out = $this->cleanString($this->algorithm) . '. ' .
            $this->time_signed . ' ' .
            $this->fudge . ' ' . $this->mac_size . ' ' .
            base64_encode($this->mac) . ' ' . $this->original_id . ' ' .
            $this->error . ' ' . $this->other_length;

        if ($this->other_length > 0) {

            $out .= ' ' . $this->other_data;
        }

        return $out;
    }

    protected function rrFromString(array $rdata)
    {


        $this->key = preg_replace('/\s+/', '', array_shift($rdata));


        $this->algorithm = self::HMAC_MD5;
        $this->time_signed = time();
        $this->fudge = 300;
        $this->mac_size = 0;
        $this->mac = '';
        $this->original_id = 0;
        $this->error = 0;
        $this->other_length = 0;
        $this->other_data = '';


        $this->class = 'ANY';
        $this->ttl = 0;

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {


            $newoffset = $packet->offset;
            $this->algorithm = Net_DNS2_Packet::expand($packet, $newoffset);
            $offset = $newoffset - $packet->offset;


            $x = unpack(
                '@' . $offset . '/ntime_high/Ntime_low/nfudge/nmac_size',
                $this->rdata
            );

            $this->time_signed = Net_DNS2::expandUint32($x['time_low']);
            $this->fudge = $x['fudge'];
            $this->mac_size = $x['mac_size'];

            $offset += 10;


            if ($this->mac_size > 0) {

                $this->mac = substr($this->rdata, $offset, $this->mac_size);
                $offset += $this->mac_size;
            }


            $x = unpack(
                '@' . $offset . '/noriginal_id/nerror/nother_length',
                $this->rdata
            );

            $this->original_id = $x['original_id'];
            $this->error = $x['error'];
            $this->other_length = $x['other_length'];


            if ($this->error == Net_DNS2_Lookups::RCODE_BADTIME) {

                if ($this->other_length != 6) {

                    return false;
                }


                $x = unpack(
                    'nhigh/nlow',
                    substr($this->rdata, $offset + 6, $this->other_length)
                );
                $this->other_data = $x['low'];
            }

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->key) > 0) {


            $new_packet = new Net_DNS2_Packet_Request('example.com', 'SOA', 'IN');


            $new_packet->copy($packet);


            array_pop($new_packet->additional);
            $new_packet->header->arcount = count($new_packet->additional);


            $sig_data = $new_packet->get();


            $sig_data .= Net_DNS2_Packet::pack($this->name);


            $sig_data .= pack(
                'nN', Net_DNS2_Lookups::$classes_by_name[$this->class], $this->ttl
            );


            $sig_data .= Net_DNS2_Packet::pack(strtolower($this->algorithm));


            $sig_data .= pack(
                'nNnnn', 0, $this->time_signed, $this->fudge,
                $this->error, $this->other_length
            );
            if ($this->other_length > 0) {

                $sig_data .= pack('nN', 0, $this->other_data);
            }


            $this->mac = $this->_signHMAC(
                $sig_data, base64_decode($this->key), $this->algorithm
            );
            $this->mac_size = strlen($this->mac);


            $data = Net_DNS2_Packet::pack(strtolower($this->algorithm));


            $data .= pack(
                'nNnn', 0, $this->time_signed, $this->fudge, $this->mac_size
            );
            $data .= $this->mac;


            if ($this->error == Net_DNS2_Lookups::RCODE_BADTIME) {

                $this->other_length = strlen($this->other_data);
                if ($this->other_length != 6) {

                    return null;
                }
            } else {

                $this->other_length = 0;
                $this->other_data = '';
            }


            $data .= pack(
                'nnn', $packet->header->id, $this->error, $this->other_length
            );
            if ($this->other_length > 0) {

                $data .= pack('nN', 0, $this->other_data);
            }

            $packet->offset += strlen($data);

            return $data;
        }

        return null;
    }

    private function _signHMAC($data, $key = null, $algorithm = self::HMAC_MD5)
    {


        if (extension_loaded('hash')) {

            if (!isset(self::$hash_algorithms[$algorithm])) {

                throw new Net_DNS2_Exception(
                    'invalid or unsupported algorithm',
                    Net_DNS2_Lookups::E_PARSE_ERROR
                );
            }

            return hash_hmac(self::$hash_algorithms[$algorithm], $data, $key, true);
        }


        if ($algorithm != self::HMAC_MD5) {

            throw new Net_DNS2_Exception(
                'only HMAC-MD5 supported. please install the php-extension ' .
                '"hash" in order to use the sha-family',
                Net_DNS2_Lookups::E_PARSE_ERROR
            );
        }


        if (is_null($key)) {

            return pack('H*', md5($data));
        }

        $key = str_pad($key, 64, chr(0x00));
        if (strlen($key) > 64) {

            $key = pack('H*', md5($key));
        }

        $k_ipad = $key ^ str_repeat(chr(0x36), 64);
        $k_opad = $key ^ str_repeat(chr(0x5c), 64);

        return $this->_signHMAC(
            $k_opad . pack('H*', md5($k_ipad . $data)), null, $algorithm
        );
    }
}


class Net_DNS2_RR_TXT extends Net_DNS2_RR
{

    public $text = array();

    protected function rrToString()
    {
        if (count($this->text) == 0) {
            return '""';
        }

        $data = '';

        foreach ($this->text as $t) {

            $data .= $this->formatString($t) . ' ';
        }

        return trim($data);
    }

    protected function rrFromString(array $rdata)
    {
        $data = $this->buildString($rdata);
        if (count($data) > 0) {

            $this->text = $data;
        }

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {

            $length = $packet->offset + $this->rdlength;
            $offset = $packet->offset;

            while ($length > $offset) {

                $this->text[] = Net_DNS2_Packet::label($packet, $offset);
            }

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        $data = null;

        foreach ($this->text as $t) {

            $data .= chr(strlen($t)) . $t;
        }

        $packet->offset += strlen($data);

        return $data;
    }
}

class Net_DNS2_RR_SPF extends Net_DNS2_RR_TXT
{
}


class Net_DNS2_RR_URI extends Net_DNS2_RR
{

    public $priority;

    public $weight;

    public $target;

    protected function rrToString()
    {


        return $this->priority . ' ' . $this->weight . ' "' .
            $this->cleanString($this->target) . '"';
    }

    protected function rrFromString(array $rdata)
    {
        $this->priority = $rdata[0];
        $this->weight = $rdata[1];


        $this->target = trim($this->cleanString($rdata[2]), '"');

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {


            $x = unpack('npriority/nweight', $this->rdata);

            $this->priority = $x['priority'];
            $this->weight = $x['weight'];

            $offset = $packet->offset + 4;
            $this->target = Net_DNS2_Packet::expand($packet, $offset);

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->target) > 0) {

            $data = pack('nn', $this->priority, $this->weight);
            $packet->offset += 4;

            $data .= $packet->compress(trim($this->target, '"'), $packet->offset);

            return $data;
        }

        return null;
    }
}


class Net_DNS2_RR_WKS extends Net_DNS2_RR
{

    public $address;

    public $protocol;

    public $bitmap = array();

    protected function rrToString()
    {
        $data = $this->address . ' ' . $this->protocol;

        foreach ($this->bitmap as $port) {
            $data .= ' ' . $port;
        }

        return $data;
    }

    protected function rrFromString(array $rdata)
    {
        $this->address = strtolower(trim(array_shift($rdata), '.'));
        $this->protocol = array_shift($rdata);
        $this->bitmap = $rdata;

        return true;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {


            $x = unpack('Naddress/Cprotocol', $this->rdata);

            $this->address = long2ip($x['address']);
            $this->protocol = $x['protocol'];


            $port = 0;
            foreach (unpack('@5/C*', $this->rdata) as $set) {

                $s = sprintf('%08b', $set);

                for ($i = 0; $i < 8; $i++, $port++) {
                    if ($s[$i] == '1') {
                        $this->bitmap[] = $port;
                    }
                }
            }

            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->address) > 0) {

            $data = pack('NC', ip2long($this->address), $this->protocol);

            $ports = array();

            $n = 0;
            foreach ($this->bitmap as $port) {
                $ports[$port] = 1;

                if ($port > $n) {
                    $n = $port;
                }
            }
            for ($i = 0; $i < ceil($n / 8) * 8; $i++) {
                if (!isset($ports[$i])) {
                    $ports[$i] = 0;
                }
            }

            ksort($ports);

            $string = '';
            $n = 0;

            foreach ($ports as $s) {

                $string .= $s;
                $n++;

                if ($n == 8) {

                    $data .= chr(bindec($string));
                    $string = '';
                    $n = 0;
                }
            }

            $packet->offset += strlen($data);

            return $data;
        }

        return null;
    }
}


class Net_DNS2_RR_X25 extends Net_DNS2_RR
{

    public $psdnaddress;

    protected function rrToString()
    {
        return $this->formatString($this->psdnaddress);
    }

    protected function rrFromString(array $rdata)
    {
        $data = $this->buildString($rdata);
        if (count($data) == 1) {

            $this->psdnaddress = $data[0];
            return true;
        }

        return false;
    }

    protected function rrSet(Net_DNS2_Packet &$packet)
    {
        if ($this->rdlength > 0) {

            $this->psdnaddress = Net_DNS2_Packet::label($packet, $packet->offset);
            return true;
        }

        return false;
    }

    protected function rrGet(Net_DNS2_Packet &$packet)
    {
        if (strlen($this->psdnaddress) > 0) {

            $data = chr(strlen($this->psdnaddress)) . $this->psdnaddress;

            $packet->offset += strlen($data);

            return $data;
        }

        return null;
    }
}


class Net_DNS2_Socket_Sockets extends Net_DNS2_Socket
{

    public function open()
    {


        if (Net_DNS2::isIPv4($this->host)) {

            $this->sock = @socket_create(
                AF_INET, $this->type,
                ($this->type == Net_DNS2_Socket::SOCK_STREAM) ? SOL_TCP : SOL_UDP
            );

        } else if (Net_DNS2::isIPv6($this->host)) {

            $this->sock = @socket_create(
                AF_INET6, $this->type,
                ($this->type == Net_DNS2_Socket::SOCK_STREAM) ? SOL_TCP : SOL_UDP
            );

        } else {

            $this->last_error = 'invalid address type: ' . $this->host;
            return false;
        }

        if ($this->sock === false) {

            $this->last_error = socket_strerror(socket_last_error());
            return false;
        }

        @socket_set_option($this->sock, SOL_SOCKET, SO_REUSEADDR, 1);


        if (strlen($this->local_host) > 0) {

            $result = @socket_bind(
                $this->sock, $this->local_host,
                ($this->local_port > 0) ? $this->local_port : null
            );
            if ($result === false) {

                $this->last_error = socket_strerror(socket_last_error());
                return false;
            }
        }


        if (@socket_set_nonblock($this->sock) === false) {

            $this->last_error = socket_strerror(socket_last_error());
            return false;
        }


        @socket_connect($this->sock, $this->host, $this->port);

        $read = null;
        $write = array($this->sock);
        $except = null;


        $result = @socket_select($read, $write, $except, $this->timeout);
        if ($result === false) {

            $this->last_error = socket_strerror(socket_last_error());
            return false;

        } else if ($result == 0) {

            $this->last_error = 'timeout on write select for connect()';
            return false;
        }

        return true;
    }

    public function close()
    {
        if (is_resource($this->sock) === true) {

            @socket_close($this->sock);
        }
        return true;
    }

    public function write($data)
    {
        $length = strlen($data);
        if ($length == 0) {

            $this->last_error = 'empty data on write()';
            return false;
        }

        $read = null;
        $write = array($this->sock);
        $except = null;


        $result = @socket_select($read, $write, $except, $this->timeout);
        if ($result === false) {

            $this->last_error = socket_strerror(socket_last_error());
            return false;

        } else if ($result == 0) {

            $this->last_error = 'timeout on write select()';
            return false;
        }


        if ($this->type == Net_DNS2_Socket::SOCK_STREAM) {

            $s = chr($length >> 8) . chr($length);

            if (@socket_write($this->sock, $s) === false) {

                $this->last_error = socket_strerror(socket_last_error());
                return false;
            }
        }


        $size = @socket_write($this->sock, $data);
        if (($size === false) || ($size != $length)) {

            $this->last_error = socket_strerror(socket_last_error());
            return false;
        }

        return true;
    }

    public function read(&$size, $max_size)
    {
        $read = array($this->sock);
        $write = null;
        $except = null;


        if (@socket_set_nonblock($this->sock) === false) {

            $this->last_error = socket_strerror(socket_last_error());
            return false;
        }


        $result = @socket_select($read, $write, $except, $this->timeout);
        if ($result === false) {

            $this->last_error = socket_strerror(socket_last_error());
            return false;

        } else if ($result == 0) {

            $this->last_error = 'timeout on read select()';
            return false;
        }

        $data = '';
        $length = $max_size;


        if ($this->type == Net_DNS2_Socket::SOCK_STREAM) {

            if (($size = @socket_recv($this->sock, $data, 2, 0)) === false) {

                $this->last_error = socket_strerror(socket_last_error());
                return false;
            }

            $length = ord($data[0]) << 8 | ord($data[1]);
            if ($length < Net_DNS2_Lookups::DNS_HEADER_SIZE) {

                return false;
            }
        }


        if (@socket_set_block($this->sock) === false) {

            $this->last_error = socket_strerror(socket_last_error());
            return false;
        }


        $data = '';
        $size = 0;

        while (1) {

            $chunk_size = @socket_recv($this->sock, $chunk, $length, MSG_WAITALL);
            if ($chunk_size === false) {

                $size = $chunk_size;
                $this->last_error = socket_strerror(socket_last_error());

                return false;
            }

            $data .= $chunk;
            $size += $chunk_size;

            $length -= $chunk_size;
            if (($length <= 0) || ($this->type == Net_DNS2_Socket::SOCK_DGRAM)) {
                break;
            }
        }

        return $data;
    }
}


class Net_DNS2_Socket_Streams extends Net_DNS2_Socket
{
    private $_context;

    public function open()
    {


        $opts = array('socket' => array());


        if (strlen($this->local_host) > 0) {

            $opts['socket']['bindto'] = $this->local_host;
            if ($this->local_port > 0) {

                $opts['socket']['bindto'] .= ':' . $this->local_port;
            }
        }


        $this->_context = @stream_context_create($opts);


        $errno = 0;
        $errstr = "";

        switch ($this->type) {
            case Net_DNS2_Socket::SOCK_STREAM:

                if (Net_DNS2::isIPv4($this->host)) {

                    $this->sock = @stream_socket_client(
                        'tcp://' . $this->host . ':' . $this->port,
                        $errno, $errstr, $this->timeout,
                        STREAM_CLIENT_CONNECT, $this->_context
                    );
                } else if (Net_DNS2::isIPv6($this->host)) {

                    $this->sock = @stream_socket_client(
                        'tcp://[' . $this->host . ']:' . $this->port,
                        $errno, $errstr, $this->timeout,
                        STREAM_CLIENT_CONNECT, $this->_context
                    );
                } else {

                    $this->last_error = 'invalid address type: ' . $this->host;
                    return false;
                }

                break;

            case Net_DNS2_Socket::SOCK_DGRAM:

                if (Net_DNS2::isIPv4($this->host)) {

                    $this->sock = @stream_socket_client(
                        'udp://' . $this->host . ':' . $this->port,
                        $errno, $errstr, $this->timeout,
                        STREAM_CLIENT_CONNECT, $this->_context
                    );
                } else if (Net_DNS2::isIPv6($this->host)) {

                    $this->sock = @stream_socket_client(
                        'udp://[' . $this->host . ']:' . $this->port,
                        $errno, $errstr, $this->timeout,
                        STREAM_CLIENT_CONNECT, $this->_context
                    );
                } else {

                    $this->last_error = 'invalid address type: ' . $this->host;
                    return false;
                }

                break;

            default:
                $this->last_error = 'Invalid socket type: ' . $this->type;
                return false;
        }

        if ($this->sock === false) {

            $this->last_error = $errstr;
            return false;
        }


        @stream_set_blocking($this->sock, 0);
        @stream_set_timeout($this->sock, $this->timeout);

        return true;
    }

    public function close()
    {
        if (is_resource($this->sock) === true) {

            @fclose($this->sock);
        }
        return true;
    }

    public function write($data)
    {
        $length = strlen($data);
        if ($length == 0) {

            $this->last_error = 'empty data on write()';
            return false;
        }

        $read = null;
        $write = array($this->sock);
        $except = null;


        $result = stream_select($read, $write, $except, $this->timeout);
        if ($result === false) {

            $this->last_error = 'failed on write select()';
            return false;

        } else if ($result == 0) {

            $this->last_error = 'timeout on write select()';
            return false;
        }


        if ($this->type == Net_DNS2_Socket::SOCK_STREAM) {

            $s = chr($length >> 8) . chr($length);

            if (@fwrite($this->sock, $s) === false) {

                $this->last_error = 'failed to fwrite() 16bit length';
                return false;
            }
        }


        $size = @fwrite($this->sock, $data);
        if (($size === false) || ($size != $length)) {

            $this->last_error = 'failed to fwrite() packet';
            return false;
        }

        return true;
    }

    public function read(&$size, $max_size)
    {
        $read = array($this->sock);
        $write = null;
        $except = null;


        @stream_set_blocking($this->sock, 0);


        $result = stream_select($read, $write, $except, $this->timeout);
        if ($result === false) {

            $this->last_error = 'error on read select()';
            return false;

        } else if ($result == 0) {

            $this->last_error = 'timeout on read select()';
            return false;
        }

        $length = $max_size;

        if ($this->type == Net_DNS2_Socket::SOCK_STREAM) {

            if (($data = fread($this->sock, 2)) === false) {

                $this->last_error = 'failed on fread() for data length';
                return false;
            }

            $length = ord($data[0]) << 8 | ord($data[1]);
            if ($length < Net_DNS2_Lookups::DNS_HEADER_SIZE) {

                return false;
            }
        }


        @stream_set_blocking($this->sock, 1);


        $data = '';


        if ($this->type == Net_DNS2_Socket::SOCK_STREAM) {

            $chunk_size = $length;

            while (1) {

                $chunk = fread($this->sock, $chunk_size);
                if ($chunk === false) {

                    $this->last_error = 'failed on fread() for data';
                    return false;
                }

                $data .= $chunk;
                $chunk_size -= strlen($chunk);

                if (strlen($data) >= $length) {
                    break;
                }
            }

        } else {


            $data = fread($this->sock, $length);
            if ($length === false) {

                $this->last_error = 'failed on fread() for data';
                return false;
            }
        }

        $size = strlen($data);

        return $data;
    }
}

?>
