# This file is part of PacketGen
# See https://github.com/sdaubert/packetgen for more informations
# Copyright (C) 2016 Sylvain Daubert <sylvain.daubert@laposte.net>
# This program is published under MIT license.

# frozen_string_literal: true

module PacketGen
  module Header
    class DNS
      # DNS Ressource Record
      # @author Sylvain Daubert
      class RR < Question
        # @!attribute ttl
        #  32-bit time to live
        #  @return [Integer]
        define_field :ttl, Types::Int32
        # @!attribute rdlength
        #  16-bit {#rdata} length
        #  @return [Integer]
        define_field :rdlength, Types::Int16
        # @!attribute rdata
        #  @return [Types::String]
        define_field :rdata, Types::String,
                     builder: ->(rr, t) { t.new(length_from: rr[:rdlength]) }

        # @param [DNS] dns
        # @param [Hash] options
        # @option options [String] :name domain as a dotted string
        # @option options [Integer,String] :type see {TYPES}. Default to +'A'+
        # @option options [Integer,String] :rrclass see {CLASSES}. Default to +'IN'+
        # @option options [Integer] :ttl
        # @option options [Integer] :rdlength if not provided, automatically set
        #   from +:rdata+ length
        # @option options [String] :rdata
        def initialize(dns, options={})
          super
          return unless options[:rdata] && options[:rdlength].nil?
          self.rdata = options[:rdata]
        end

        # Set rdata and rdlength from +data+
        # @param [String] data
        # @return [void]
        def rdata=(data)
          self[:rdlength].read data.size
          self[:rdata].read data
        end

        # Get human readable rdata
        # @return [String]
        def human_rdata
          str = self[:rdata].inspect

          # Need to mask: mDNS uses leftmost bit as a flag (CACHE FLUSH)
          if self.rrclass & 0x7fff == CLASSES['IN']
            case type
            when TYPES['A'], TYPES['AAAA']
              str = IPAddr.new_ntoh(self[:rdata]).to_s
            end
          end

          name = Name.new
          name.dns = self[:name].dns
          case type
          when TYPES['NS'], TYPES['PTR'], TYPES['CNAME']
            str = name.read(self[:rdata]).to_human
          when TYPES['SOA']
            mname = name.read(self[:rdata]).dup
            rname = name.read(self[:rdata][mname.sz..-1])
            serial = Types::Int32.new.read(self[:rdata][mname.sz + rname.sz, 4])
            refresh = Types::Int32.new.read(self[:rdata][mname.sz + rname.sz + 4, 4])
            retryi = Types::Int32.new.read(self[:rdata][mname.sz + rname.sz + 8, 4])
            expire = Types::Int32.new.read(self[:rdata][mname.sz + rname.sz + 12, 4])
            minimum = Types::Int32.new.read(self[:rdata][mname.sz + rname.sz + 16, 4])
            str = "#{mname.to_human} #{rname.to_human} #{serial.to_i} #{refresh.to_i} " \
                  "#{retryi.to_i} #{expire.to_i} #{minimum.to_i}"
          when TYPES['MX']
            pref = Types::Int16.new.read(self[:rdata][0, 2])
            exchange = name.read(self[:rdata][2..-1]).to_human
            str = '%u %s' % [pref.to_i, exchange]
          when TYPES['SRV']
            priority = Types::Int16.new.read(self[:rdata][0, 2])
            weight = Types::Int16.new.read(self[:rdata][2, 2])
            port = Types::Int16.new.read(self[:rdata][4, 2])
            target = name.read(self[:rdata][6, self[:rdata].size]).to_human
            str = "#{priority.to_i} #{weight.to_i} #{port.to_i} #{target}"
          end

          str
        end

        # Get human readable class
        # @return [String]
        def human_rrclass
          if self[:name].dns.is_a? MDNS
            str = self.class::CLASSES.key(self.rrclass & 0x7fff) || '0x%04x' % (self.rrclass & 0x7fff)
            str += ' CACHE-FLUSH' if self.rrclass & 0x8000 > 0
            str
          else
            self.class::CLASSES.key(self.rrclass) || '0x%04x' % self.rrclass
          end
        end

        # @return [String]
        def to_human
          "#{human_type} #{human_rrclass} #{name} TTL #{ttl} #{human_rdata}"
        end
      end
    end
  end
end