#--
#Copyright 2007 Nominet UK
#
#Licensed under the Apache License, Version 2.0 (the "License");
#you may not use this file except in compliance with the License. 
#You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0 
#
#Unless required by applicable law or agreed to in writing, software 
#distributed under the License is distributed on an "AS IS" BASIS, 
#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
#See the License for the specific language governing permissions and 
#limitations under the License.
#++
require 'base64'
begin
require 'Digest/sha2'
rescue LoadError
  require 'digest/sha2'
end
module Dnsruby
  class RR
    #RFC4034, section 4
    #The DS Resource Record refers to a DNSKEY RR and is used in the DNS
    #DNSKEY authentication process.  A DS RR refers to a DNSKEY RR by
    #storing the key tag, algorithm number, and a digest of the DNSKEY RR.
    #Note that while the digest should be sufficient to identify the
    #public key, storing the key tag and key algorithm helps make the
    #identification process more efficient.  By authenticating the DS
    #record, a resolver can authenticate the DNSKEY RR to which the DS
    #record points.  The key authentication process is described in
    #[RFC4035].

    class DS < RR
      class DigestTypes < CodeMapper
        update()
        add_pair("SHA-1", 1)
        add_pair("SHA-2", 2 )
      end
  
      ClassValue = nil #:nodoc: all
      TypeValue = Types::DS #:nodoc: all
      
      #The RDATA for a DS RR consists of a 2 octet Key Tag field, a 1 octet
      #Algorithm field, a 1 octet Digest Type field, and a Digest field.
      #
      #                     1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3
      # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      #+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      #|           Key Tag             |  Algorithm    |  Digest Type  |
      #+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      #/                                                               /
      #/                            Digest                             /
      #/                                                               /
      #+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   

      #The Key Tag field lists the key tag of the DNSKEY RR referred to by
      #the DS record, in network byte order.
      attr_accessor :key_tag
      #The algorithm used for this key
      #See Dnsruby::Algorithms for permitted values
      attr_reader :algorithm
      #The DS RR refers to a DNSKEY RR by including a digest of that DNSKEY
      #RR.  The Digest Type field identifies the algorithm used to construct
      #the digest.
      attr_reader :digest_type
      #The DS record refers to a DNSKEY RR by including a digest of that
      #DNSKEY RR.
      attr_accessor :digest
      attr_accessor :digestbin
      
      def digest_type=(d)
        dig = DS.get_digest_type(d)
        @digest_type = dig
      end
      
      def DS.get_digest_type(d)
        if (d.instance_of?String)
          if (d.length == 1)
            d = d.to_i
          end
        end
        begin
          digest = DigestTypes.new(d)
          return digest
        rescue ArgumentError => e
          raise DecodeError.new(e)
        end        
      end
    
      def algorithm=(a)
        if (a.instance_of?String)
          if (a.length < 3)
            a = a.to_i
          end
        end
        begin
          alg = Algorithms.new(a)
          @algorithm = alg
        rescue ArgumentError => e
          raise DecodeError.new(e)
        end        
      end
      
      # Return the digest of the specified DNSKEY RR
      def digest_key(*args) # key, digest_type)
        digest_type = @digest_type
        key = args[0]
        if (args.length == 2)
            digest_type = args[1]
        end
        
        
        data = MessageEncoder.new {|msg|
           msg.put_name(key.name, true)
           key.encode_rdata(msg, true)
        }.to_s

        
        if (digest_type.code == 1)
            digestbin = OpenSSL::Digest::SHA1.digest(data)
            return digestbin
        elsif (digest_type.code == 2) 
            digestbin = Digest::SHA256.digest(data)
            return digestbin
        end

      end
      
      # Check if the key's digest is the same as that stored in the DS record      
      def check_key(key)
        if ((key.key_tag == @key_tag) && (key.algorithm == @algorithm))
          
          digestbin = digest_key(key)
          if (@digestbin == digestbin)
            if (!key.zone_key?)
            else
              return true
            end
          else
          end
        end
        return false
      end
      

      def DS.from_key(key, digest_type)
## The key must not be a NULL key.
#    if ((key.flags & 0xc000 ) == 0xc000 )
#	puts "\nCreating a DS record for a NULL key is illegal"
#        return
#    end
#    
#    # Bit 0 must not be set.
#    if (key.flags & 0x8000)
#	puts "\nCreating a DS record for a key with flag bit 0 set " +
#	    "to 0 is illegal"
#          return
#    end
#    
    # Bit 6 must be set to 0 bit 7 must be set to 1
    if (( key.flags & 0x300) != 0x100)
	puts "\nCreating a DS record for a key with flags 6 and 7 not set "+
	    "0  and 1 respectively is illegal"
         return
    end
#    
#
#    if (key.protocol  != 3 )
#	puts "\nCreating a DS record for a non DNSSEC (protocol=3) " +
#	    "key is illegal"
#          return
#    end
#    
        digest_type = get_digest_type(digest_type)
        # Create a new DS record from the specified key
        ds = RR.create(:name => key.name, :type => "DS", :ttl => key.ttl, 
                      :key_tag => key.key_tag,
                     :digest_type => digest_type, :algorithm => key.algorithm)
                   
        ds.digestbin = ds.digest_key(key, digest_type)
        ds.digest = ds.digestbin.unpack("H*")[0]
        return ds
      end
  
      def from_data(data) #:nodoc: all
        key_tag, algorithm, digest_type, digest = data
        self.key_tag=(key_tag)
        self.algorithm=(algorithm)
        self.digest_type=(digest_type)
        self.digestbin=(digest)
        self.digest=@digestbin.unpack("H*")[0]
      end
      
      def from_string(input)
        if (input.length > 0)
          data = input.split(" ")
          self.key_tag=(data[0].to_i)
          self.algorithm=(data[1])
          self.digest_type=(data[2])

          buf = ""
          index = 3
          end_index = data.length - 1
          if (data[index]=="(")
            end_index = data.length - 2
            index = 4
          end
          (index..end_index).each {|i|
            if (comment_index = data[i].index(";"))
              buf += data[i].slice(0, comment_index)
              # @TODO@ We lose the comments here - we should really keep them for when we write back to string format?
              break
            else
              buf += data[i]
            end
          } 
#          self.digest=Base64.decode64(buf)
          buf.gsub!(/\n/, "")
          buf.gsub!(/ /, "")
#          self.digest=buf.unpack("m*")[0]
          self.digest=buf
          self.digestbin = [buf].pack("H*")
        end
      end
      
      def rdata_to_string #:nodoc: all
        if (@key_tag != nil)
#          return "#{@key_tag.to_i} #{@algorithm.string} #{@digest_type} ( #{Base64.encode64(@digest)} )"
#          return "#{@key_tag.to_i} #{@algorithm.string} #{@digest_type.code} ( #{[@digest].pack("m*").gsub("\n", "")} )"
          return "#{@key_tag.to_i} #{@algorithm.string} #{@digest_type.code} ( #{@digest.upcase} )"
        else
          return ""
        end 
      end
      
      def encode_rdata(msg, canonical=false) #:nodoc: all
        msg.put_pack("ncc", @key_tag, @algorithm.code, @digest_type.code)
        msg.put_bytes(@digestbin)
      end
      
      def self.decode_rdata(msg) #:nodoc: all
        key_tag, algorithm, digest_type = msg.get_unpack("ncc")
        digest = msg.get_bytes
        return self.new(
          [key_tag, algorithm, digest_type, digest])
      end
    end 
  end
end