# -*- coding: binary -*-
#---
# $Id: Question.rb,v 1.8 2006/07/28 19:00:03 bluemonk Exp $     
#+++

require 'net/dns/dns'
require 'net/dns/names/names'
require 'net/dns/rr/types'
require 'net/dns/rr/classes'

module Net # :nodoc:
  module DNS 

    #
    # =Name
    #
    # Net::DNS::Question - DNS packet question class
    #
    # =Synopsis
    # 
    #   require 'net/dns/question'
    #
    # =Description
    #
    # This class represent the Question portion of a DNS packet. The number
    # of question entries is stored in the +qdCount+ variable of an Header
    # object. 
    #
    # A new object can be created passing the name of the query and the type
    # of answer desired, plus an optional argument containing the class:
    #
    #   question = Net::DNS::Question.new("google.com.", Net::DNS::A)
    #      #=> "google.com.                   A       IN"
    #
    # Alternatevly, a new object is created when processing a binary
    # packet, as when an answer is received.
    # To obtain the binary data from a question object you can use
    # the method Question#data:
    # 
    #   question.data
    #      #=> "\006google\003com\000\000\001\000\001"
    #
    # A lot of methods were written to keep a compatibility layer with
    # the Perl version of the library, as long as methods name which are
    # more or less the same. 
    #
    # =Error classes
    #
    # Some error classes has been defined for the Net::DNS::Header class,
    # which are listed here to keep a light and browsable main documentation.
    # We have:
    #
    # * QuestionArgumentError: generic argument error
    # * QuestionNameError:     an error in the +name+ part of a Question entry
    #
    # =Copyright
    # 
    # Copyright (c) 2006 Marco Ceresa
    #
    # All rights reserved. This program is free software; you may redistribute 
    # it and/or modify it under the same terms as Ruby itself.
    #
    class Question
      
      include Net::DNS::Names
      
      # +name+ part of a Question entry
      attr_reader :qName 
      # +type+ part of a Question entry
      attr_reader :qType 
      # +class+ part of a Question entry
      attr_reader :qClass 
      
      # Creates a new Net::DNS::Question object:
      #
      #   question = Net::DNS::Question.new("example.com")
      #      #=> "example.com                   A       IN"
      #   question = Net::DNS::Question.new("example.com", Net::DNS::MX)
      #      #=> "example.com                   MX      IN"
      #   question = Net::DNS::Question.new("example.com", Net::DNS::TXT, Net::DNS::HS)
      #      #=> "example.com                   TXT     HS"
      
      # If not specified, +type+ and +cls+ arguments defaults 
      # to Net::DNS::A and Net::DNS::IN respectively.
      #
      def initialize(name,type=Net::DNS::A,cls=Net::DNS::IN)
        @qName = check_name name
        @qType = Net::DNS::RR::Types.new type
        @qClass = Net::DNS::RR::Classes.new cls
      end

      # Return a new Net::DNS::Question object created by
      # parsing binary data, such as an answer from the
      # nameserver.
      #
      #   question = Net::DNS::Question.parse(data)
      #   puts "Queried for #{question.qName} type #{question.qType.to_s}"
      #     #=> Queried for example.com type A
      #
      def self.parse(arg)
        if arg.kind_of? String
          o = allocate
          o.send(:new_from_binary,arg)
          o
        else
          raise QuestionArgumentError, "Wrong argument format, must be a String"
        end
      end

      # Known inspect method with nice formatting
      def inspect
        if @qName.size > 29 then 
          len = @qName.size + 1 
        else
          len = 29
        end
        [@qName,@qClass.to_s,@qType.to_s].pack("A#{len} A8 A8")
      end
      
      # Outputs binary data from a Question object
      #
      #   question.data
      #      #=> "\006google\003com\000\000\001\000\001"
      #
      def data
        [pack_name(@qName),@qType.to_i,@qClass.to_i].pack("a*nn")
      end
      
      # Return the binary data of the objects, plus an offset
      # and an Hash with references to compressed names. For use in 
      # Net::DNS::Packet compressed packet creation.
      #
      def comp_data
        arr = @qName.split(".")
        str = pack_name(@qName)
        string = ""
        names = {}
        offset = Net::DNS::HFIXEDSZ
        arr.size.times do |i|
          x = i+1
          elem = arr[-x]
          len = elem.size
          string = ((string.reverse)+([len,elem].pack("Ca*")).reverse).reverse
          names[string] = offset
          offset += len
        end
        offset += 2 * Net::DNS::INT16SZ
        str += "\000"
        [[str,@qType.to_i,@qClass.to_i].pack("a*nn"),offset,names]
      end
      
      private
      
      def build_qName(str)
        result = ""
        offset = 0
        loop do
          len = str.unpack("@#{offset} C")[0]
          break if len == 0
          offset += 1
          result += str[offset..offset+len-1]
          result += "."
          offset += len
        end
        result
      end
      
      def check_name(name)
        name.strip!
        if name =~ /[^\w\.\-_]/
          raise QuestionNameError, "Question name #{name.inspect} not valid"          
        else
          name
        end
      rescue
        raise QuestionNameError, "Question name #{name.inspect} not valid"                  
      end
      
      def new_from_binary(data)
        str,type,cls = data.unpack("a#{data.size-4}nn")
        @qName = build_qName(str)
        @qType = Net::DNS::RR::Types.new type
        @qClass = Net::DNS::RR::Classes.new cls
      rescue StandardError => e
        raise QuestionArgumentError, "Invalid data: #{data.inspect}\n{e.backtrace}"
      end

    end # class Question
    
  end # class DNS
end # module Net

class QuestionArgumentError < ArgumentError # :nodoc:
end
class QuestionNameError < StandardError # :nodoc:
end
