module Net # :nodoc:
  module DNS

    module Names

      # Base error class.
      class Error < StandardError
      end

      # Generic Names Error.
      class ExpandError < Error
      end


      INT16SZ = 2

      # Expand a compressed name in a DNS Packet object. Please
      # see RFC1025 for an explanation of how the compression
      # in DNS packets works, how may it be useful and how should
      # be handled.
      #
      # This method accept two parameters: a raw packet data and an
      # offset, which indicates the point in the packet in which the
      # parsing has arrived.
      #
      def dn_expand(packet,offset)
        name = ""
        packetlen = packet.size
        while true
          raise ExpandError, "Offset is greater than packet lenght!" if packetlen < (offset+1)
          len = packet.unpack("@#{offset} C")[0]

          if len == 0
            offset += 1
            break
          elsif (len & 0xC0) == 0xC0
            raise ExpandError, "Packet ended before offset expand" if packetlen < (offset+INT16SZ)
            ptr = packet.unpack("@#{offset} n")[0]
            ptr &= 0x3FFF
            name2 = dn_expand(packet,ptr)[0]
            raise ExpandError, "Packet is malformed!" if name2 == nil
            name += name2
            offset += INT16SZ
            break
          else
            offset += 1
            raise ExpandError, "No expansion found" if packetlen < (offset+len)
            elem = packet[offset..offset+len-1]
            name += "#{elem}."
            offset += len
          end
        end
        [name, offset] # name.chomp(".") if trailing dot has to be omitted
      end

      def pack_name(name)
        if name.size > 255 
          raise ArgumentError, "Name may not exceed 255 chars"
        end
        arr = name.split(".")
        str = ""
        arr.each do |elem|
          if elem.size > 63
            raise ArgumentError, "Label may not exceed 63 chars"
          end
          str += [elem.size,elem].pack("Ca*")
        end
        str += [0].pack("C")
        str
      end

      def names_array(name)
        arr = name.split(".")
        ar = []
        string = ""
        arr.size.times do |i|
          x = i+1
          elem = arr[-x]
          len = elem.size
          string = ((string.reverse)+([len,elem].pack("Ca*")).reverse).reverse
          ar.unshift(string)
        end
        return ar
      end

      def dn_comp(name,offset,compnames)
        names = {}
        ptr = 0
        str = ""
        arr = names_array(name)
        arr.each do |entry|
          if compnames.has_key?(entry)
            ptr = 0xC000 | compnames[entry]
            str += [ptr].pack("n")
            offset += INT16SZ
            break
          else
            len = entry.unpack("C")[0]
            elem = entry[1..len]
            str += [len,elem].pack("Ca*")
            names.update({"#{entry}" => offset})
            offset += len
          end
        end
        return str,offset,names
      end

      def valid?(name)
        if name =~ /^([a-z0-9]([-a-z0-9]*[a-z0-9])?\.)+((a[cdefgilmnoqrstuwxz]|aero|arpa)|(b[abdefghijmnorstvwyz]|biz)|(c[acdfghiklmnorsuvxyz]|cat|com|coop)|d[ejkmoz]|(e[ceghrstu]|edu)|f[ijkmor]|(g[abdefghilmnpqrstuwy]|gov)|h[kmnrtu]|(i[delmnoqrst]|info|int)|(j[emop]|jobs)|k[eghimnprwyz]|l[abcikrstuvy]|(m[acdghklmnopqrstuvwxyz]|mil|mobi|museum)|(n[acefgilopruz]|name|net)|(om|org)|(p[aefghklmnrstwy]|pro)|qa|r[eouw]|s[abcdeghijklmnortvyz]|(t[cdfghjklmnoprtvwz]|travel)|u[agkmsyz]|v[aceginu]|w[fs]|y[etu]|z[amw])$/i
          return name
        else
          raise ArgumentError, "Invalid FQDN: #{name}"
        end
      end

    end
  end
end
