require 'ipaddr' ## # Created by DNSSD::Service#query_record class DNSSD::Reply::QueryRecord < DNSSD::Reply ## # A domain for registration or browsing attr_reader :domain ## # The service name attr_reader :name ## # DNS Record data attr_reader :record ## # DNS Record class (only IN is supported) attr_reader :record_class ## # DNS Record type attr_reader :record_type ## # Time-to-live for this record. See #expired? attr_reader :ttl ## # The service type attr_reader :type ## # Creates a new QueryRecord, called internally by # DNSSD::Service#query_record def initialize(service, flags, interface, fullname, record_type, record_class, record, ttl) super service, flags, interface set_fullname fullname @record_type = record_type @record_class = record_class @record = record @created = Time.now @ttl = ttl end ## # Converts a RFC 1035 character-string into a ruby String def character_string_to_string(character_string) length = character_string.slice 0 length = length.ord unless Numeric === length string = character_string.slice 1, length if string.length != length then raise TypeError, "invalid character string, expected #{length} got #{string.length} in #{@record.inspect}" end string end ## # Converts a RFC 1035 domain-name into a ruby String def domain_name_to_string(domain_name) return '.' if domain_name == "\0" domain_name = domain_name.dup string = [] until domain_name.empty? do string << character_string_to_string(domain_name) domain_name.slice! 0, string.last.length + 1 end string << nil unless string.last.empty? string.join('.') end ## # Has this QueryRecord passed its TTL? def expired? Time.now > @created + ttl end def inspect # :nodoc: "#<%s:0x%x %s %s %s %p interface: %s flags: %p>" % [ self.class, object_id, fullname, record_class_name, record_type_name, record, interface_name, @flags ] end ## # Name of this record's record_class def record_class_name return "unknown #{@record_class}" unless @record_class == DNSSD::Record::IN 'IN' # Only IN is supported end ## # Decodes output for #record, returning the raw record if it can't be # decoded. Handles: # # A AAAA CNAME MX NS PTR SOA SRV TXT def record_data return @record unless @record_class == DNSSD::Record::IN case @record_type when DNSSD::Record::A, DNSSD::Record::AAAA then IPAddr.new_ntoh @record when DNSSD::Record::CNAME, DNSSD::Record::NS, DNSSD::Record::PTR then domain_name_to_string @record when DNSSD::Record::MX then mx = @record.unpack 'nZ*' mx[-1] = domain_name_to_string mx.last mx when DNSSD::Record::SOA then soa = @record.unpack 'Z*Z*NNNNN' soa[0] = domain_name_to_string soa[0] soa[1] = domain_name_to_string soa[1] soa when DNSSD::Record::SRV then srv = @record.unpack 'nnnZ*' srv[-1] = domain_name_to_string srv.last srv when DNSSD::Record::TXT then record = @record.dup txt = [] until record.empty? do txt << character_string_to_string(record) record.slice! 0, txt.last.length + 1 end txt else @record end end ## # Name of this record's record_type def record_type_name return "unknown #{@record_type} for record class (#{@record_class})" unless @record_class == DNSSD::Record::IN DNSSD::Record::VALUE_TO_NAME[@record_type] end ## # Outputs this record in a BIND-like DNS format def to_s "%s %d %s %s %p" % [ fullname, ttl, record_class_name, record_type_name, record_data ] end end