local coroutine = require "coroutine"
local ipOps = require "ipOps"
local nmap = require "nmap"
local stdnse = require "stdnse"
local string = require "string"
local stringaux = require "stringaux"
local table = require "table"
local base32 = require "base32"
local unittest = require "unittest"
_ENV = stdnse.module("dns", stdnse.seeall)
get_servers = nmap.get_dns_servers
types = {
  A = 1,
  NS = 2,
  SOA = 6,
  CNAME = 5,
  PTR = 12,
  HINFO = 13,
  MX = 15,
  TXT = 16,
  AAAA = 28,
  SRV = 33,
  OPT = 41,
  SSHFP = 44,
  NSEC = 47,
  NSEC3 = 50,
  AXFR = 252,
  ANY = 255
}
CLASS = {
  IN = 1,
  CH = 3,
  ANY = 255
}
local function sendPacketsUDP(data, host, port, timeout, cnt, multiple)
  local socket = nmap.new_socket("udp")
  local responses = {}
  socket:set_timeout(timeout)
  if ( not(multiple) ) then
    socket:connect( host, port, "udp" )
  end
  for i = 1, cnt do
    local status, err
    if ( multiple ) then
      status, err = socket:sendto(host, port, data)
    else
      status, err = socket:send(data)
    end
    if (not(status)) then return false, err end
    local response
    if ( multiple ) then
      while(true) do
        status, response = socket:receive()
        if( not(status) ) then break end
        local status, _, _, ip, _ = socket:get_info()
        table.insert(responses, { data = response, peer = ip } )
      end
    else
      status, response = socket:receive()
      if ( status ) then
        local status, _, _, ip, _ = socket:get_info()
        table.insert(responses, { data = response, peer = ip } )
      end
    end
    if (#responses>0) then
      socket:close()
      return true, responses
    end
  end
  socket:close()
  return false
end
local function sendPacketsTCP(data, host, port, timeout)
  local socket = nmap.new_socket()
  local response
  local responses = {}
  socket:set_timeout(timeout)
  socket:connect(host, port)
  local send_data = '\000' .. string.char(#data) .. data
  socket:send(send_data)
  local response = ''
  local got_response = false
  while true do
    local status, recv_data = socket:receive_bytes(1)
    if not status then break end
    got_response = true
    response = response .. recv_data
  end
  local status, _, _, ip, _ = socket:get_info()
  socket:close()
  if not got_response then
    return false
  end
  table.insert(responses, { data = string.sub(response,3), peer = ip } )
  return true, responses
end
local function sendPackets(data, host, port, timeout, cnt, multiple, proto)
  if proto == nil or proto == 'udp' then
    return sendPacketsUDP(data, host, port, timeout, cnt, multiple)
  else
    return sendPacketsTCP(data, host, port, timeout)
  end
end
local function gotAnswer(rPkt)
  if #rPkt.answers > 0 then
    if rPkt.questions[1] == nil then
      return true
    end
    if rPkt.questions[1].dtype == types.A then
      for _, v in ipairs(rPkt.answers) do
        if v.dtype == types.A then
          return true
        end
      end
      return false
    else -- there was no A request, CNAMEs are not of interest
      return true
    end
  elseif rPkt.flags.RC3 and rPkt.flags.RC4 then
    return true
  else
    return false
  end
end
local function getAuthDns(rPkt)
  if #rPkt.auth == 0 then
    if #rPkt.answers == 0 then
      return false
    else
      if rPkt.answers[1].dtype == types.CNAME then
        return {cname = rPkt.answers[1].domain}
      end
    end
  end
  if rPkt.auth[1].dtype == types.NS then
    if #rPkt.add > 0 then
      local hosts = {}
      for _, v in ipairs(rPkt.add) do
        if v.dtype == types.A then
          table.insert(hosts, v.ip)
        end
      end
      if #hosts > 0 then return hosts end
    end
    local status, next = query(rPkt.auth[1].domain, {dtype = "A" })
    return next
  end
  return false
end
local function processResponse( response, dname, dtype, options )
  local rPkt = decode(response)
  if gotAnswer(rPkt) then
    if (options.retPkt) then
      return true, rPkt
    else
      return findNiceAnswer(dtype, rPkt, options.retAll)
    end
  elseif ( not(options.noauth) ) then -- if not, ask the next server in authority
    local next_server = getAuthDns(rPkt)
    if type(next_server) == 'table' and next_server.cname then
      options.tries = options.tries - 1
      return query(next_server.cname, options)
    end
    if next_server and next_server ~= options.host and options.tries > 1 then
      options.host = next_server
      options.tries = options.tries - 1
      return query(dname, options)
    end
  elseif ( options.retPkt ) then
    return true, rPkt
  end
  stdnse.debug1("dns.query() failed to resolve the requested query%s%s", dname and ": " or ".", dname or "")
  return false, "No Answers"
end
function query(dname, options)
  if not options then options = {} end
  local dtype, host, port, proto = options.dtype, options.host, options.port, options.proto
  if proto == nil then proto = 'udp' end
  if port == nil then port = '53' end
  local class = options.class or CLASS.IN
  if not options.tries then options.tries = 10 end -- don't get into an infinite loop
  if not options.sendCount then options.sendCount = 2 end
  if type( options.timeout ) ~= "number" then options.timeout = get_default_timeout() end
  if type(dtype) == "string" then
    dtype = types[dtype]
  end
  if not dtype then dtype = types.A end
  local srv
  local srvI = 1
  if not port then port = 53 end
  if not host then
    srv = get_servers()
    if srv and srv[1] then
      host = srv[1]
    else
      return false, "No Servers"
    end
  elseif type(host) == "table" then
    srv = host
    host = srv[1]
  end
  local pkt = newPacket()
  addQuestion(pkt, dname, dtype, class)
  if options.norecurse then pkt.flags.RD = false end
  local dnssec = {}
  if ( options.dnssec ) then
    dnssec = { DO = true }
  end
  if ( options.nsid ) then
    addNSID(pkt, dnssec)
  elseif ( options.subnet ) then
    addClientSubnet(pkt, dnssec, options.subnet )
  elseif ( dnssec.DO ) then
    addOPT(pkt, {DO = true})
  end
  if ( options.flags ) then pkt.flags.raw = options.flags end
  if ( options.id ) then pkt.id = options.id end
  local data = encode(pkt)
  local status, response = sendPackets(data, host, port, options.timeout, options.sendCount, options.multiple, proto)
  while((not status) and srv and srvI < #srv) do
    srvI = srvI + 1
    host = srv[srvI]
    status, response = sendPackets(data, host, port, options.timeout, options.sendCount)
  end
  if status then
    if ( options.multiple ) then
      local multiresponse = {}
      for _, r in ipairs( response ) do
        local status, presponse = processResponse( r.data, dname, dtype, options )
        if( status ) then
          table.insert( multiresponse, { ['output']=presponse, ['peer']=r.peer } )
        end
      end
      return true, multiresponse
    else
      return processResponse( response[1].data, dname, dtype, options)
    end
  else
    stdnse.debug1("dns.query() got zero responses attempting to resolve query%s%s", dname and ": " or ".", dname or "")
    return false, "No Answers"
  end
end
function reverse(ip)
  ip = ipOps.expand_ip(ip)
  if type(ip) ~= "string" then return nil end
  local delim = "%."
  local arpa = ".in-addr.arpa"
  if ip:match(":") then
    delim = ":"
    arpa = ".ip6.arpa"
  end
  local ipParts = stringaux.strsplit(delim, ip)
  if #ipParts == 8 then
    local mask = "0000"
    for i, part in ipairs(ipParts) do
      ipParts[i] = mask:sub(1, #mask - #part) .. part
    end
    local temp = {}
    for i, hdt in ipairs(ipParts) do
      for part in hdt:gmatch("%x") do
        temp[#temp+1] = part
      end
    end
    ipParts = temp
  end
  local ipReverse = {}
  for i = #ipParts, 1, -1 do
    table.insert(ipReverse, ipParts[i])
  end
  return table.concat(ipReverse, ".") .. arpa
end
local answerFetcher = {}
answerFetcher[types.TXT] = function(dec, retAll)
  local answers = {}
  if not retAll and dec.answers[1].data then
    return true, string.sub(dec.answers[1].data, 2)
  elseif not retAll then
    stdnse.debug1("dns.answerFetcher found no records of the required type: TXT")
    return false, "No Answers"
  else
    for _, v in ipairs(dec.answers) do
      if v.TXT and v.TXT.text then
        for _, v in ipairs( v.TXT.text ) do
          table.insert(answers, v)
        end
      end
    end
  end
  if #answers == 0 then
    stdnse.debug1("dns.answerFetcher found no records of the required type: TXT")
    return false, "No Answers"
  end
  return true, answers
end
answerFetcher[types.A] = function(dec, retAll)
  local answers = {}
  for _, ans in ipairs(dec.answers) do
    if ans.dtype == types.A then
      if not retAll then
        return true, ans.ip
      end
      table.insert(answers, ans.ip)
    end
  end
  if not retAll or #answers == 0 then
    stdnse.debug1("dns.answerFetcher found no records of the required type: A")
    return false, "No Answers"
  end
  return true, answers
end
answerFetcher[types.CNAME] = function(dec, retAll)
  local answers = {}
  if not retAll and dec.answers[1].domain then
    return true, dec.answers[1].domain
  elseif not retAll then
    stdnse.debug1("dns.answerFetcher found no records of the required type: NS, PTR or CNAME")
    return false, "No Answers"
  else
    for _, v in ipairs(dec.answers) do
      if v.domain then table.insert(answers, v.domain) end
    end
  end
  if #answers == 0 then
    stdnse.debug1("dns.answerFetcher found no records of the required type: NS, PTR or CNAME")
    return false, "No Answers"
  end
  return true, answers
end
answerFetcher[types.MX] = function(dec, retAll)
  local mx, ip, answers = {}, {}, {}
  for _, ans in ipairs(dec.answers) do
    if ans.MX then mx[#mx+1] = ans.MX end
    if not retAll then break end
  end
  if #mx == 0 then
    stdnse.debug1("dns.answerFetcher found no records of the required type: MX")
    return false, "No Answers"
  end
  for _, add in ipairs(dec.add) do
    if ip[add.dname] then table.insert(ip[add.dname], add.ip)
    else ip[add.dname] = {add.ip} end
  end
  for _, mxrec in ipairs(mx) do
    if ip[mxrec.server] then
      table.insert( answers, ("%s:%s:%s"):format(mxrec.pref or "-", mxrec.server or "-", table.concat(ip[mxrec.server], ":")) )
      if not retAll then return true, answers[1] end
    else
      table.insert( answers, ("%s:%s"):format(mxrec.pref or "-", mxrec.server or "-") )
      if not retAll then return true, answers[1] end
    end
  end
  return true, answers
end
answerFetcher[types.SRV] = function(dec, retAll)
  local srv, ip, answers = {}, {}, {}
  for _, ans in ipairs(dec.answers) do
    if ans.dtype == types.SRV then
      if not retAll then
        return true, ("%s:%s:%s:%s"):format( ans.SRV.prio, ans.SRV.weight, ans.SRV.port, ans.SRV.target )
      end
      table.insert( answers, ("%s:%s:%s:%s"):format( ans.SRV.prio, ans.SRV.weight, ans.SRV.port, ans.SRV.target ) )
    end
  end
  if #answers == 0 then
    stdnse.debug1("dns.answerFetcher found no records of the required type: SRV")
    return false, "No Answers"
  end
  return true, answers
end
answerFetcher[types.NSEC] = function(dec, retAll)
  local nsec, answers = {}, {}
  for _, auth in ipairs(dec.auth) do
    if auth.NSEC then nsec[#nsec+1] = auth.NSEC end
    if not retAll then break end
  end
  if #nsec == 0 then
    stdnse.debug1("dns.answerFetcher found no records of the required type: NSEC")
    return false, "No Answers"
  end
  for _, nsecrec in ipairs(nsec) do
    table.insert( answers, ("%s:%s:%s"):format(nsecrec.name or "-", nsecrec.dname or "-", table.concat(nsecrec.types, ":") or "-"))
  end
  if not retAll then return true, answers[1] end
  return true, answers
end
answerFetcher[types.NS] = answerFetcher[types.CNAME]
answerFetcher[types.PTR] = answerFetcher[types.CNAME]
answerFetcher[types.AAAA] = function(dec, retAll)
  local answers = {}
  for _, ans in ipairs(dec.answers) do
    if ans.dtype == types.AAAA then
      if not retAll then
        return true, ans.ipv6
      end
      table.insert(answers, ans.ipv6)
    end
  end
  if not retAll or #answers == 0 then
    stdnse.debug1("dns.answerFetcher found no records of the required type: AAAA")
    return false, "No Answers"
  end
  return true, answers
end
function findNiceAnswer(dtype, dec, retAll)
  if (#dec.answers > 0) then
    if answerFetcher[dtype] then
      return answerFetcher[dtype](dec, retAll)
    else
      stdnse.debug1("dns.findNiceAnswer() does not have an answerFetcher for dtype %s", tostring(dtype))
      return false, "Unable to handle response"
    end
  elseif (dec.flags.RC3 and dec.flags.RC4) then
    return false, "No Such Name"
  else
    stdnse.debug1("dns.findNiceAnswer() found zero answers in a response, but got an unexpected flags.replycode")
    return false, "No Answers"
  end
end
local additionalFetcher = {}
additionalFetcher[types.TXT] = function(dec, retAll)
  local answers = {}
  if not retAll and dec.add[1].data then
    return true, string.sub(dec.add[1].data, 2)
  elseif not retAll then
    stdnse.debug1("dns.additionalFetcher found no records of the required type: TXT")
    return false, "No Answers"
  else
    for _, v in ipairs(dec.add) do
      if v.TXT and v.TXT.text then
        for _, v in ipairs( v.TXT.text ) do
          table.insert(answers, v)
        end
      end
    end
  end
  if #answers == 0 then
    stdnse.debug1("dns.answerFetcher found no records of the required type: TXT")
    return false, "No Answers"
  end
  return true, answers
end
additionalFetcher[types.A] = function(dec, retAll)
  local answers = {}
  for _, ans in ipairs(dec.add) do
    if ans.dtype == types.A then
      if not retAll then
        return true, ans.ip
      end
      table.insert(answers, ans.ip)
    end
  end
  if not retAll or #answers == 0 then
    stdnse.debug1("dns.answerFetcher found no records of the required type: A")
    return false, "No Answers"
  end
  return true, answers
end
additionalFetcher[types.SRV] = function(dec, retAll)
  local srv, ip, answers = {}, {}, {}
  for _, ans in ipairs(dec.add) do
    if ans.dtype == types.SRV then
      if not retAll then
        return true, ("%s:%s:%s:%s"):format( ans.SRV.prio, ans.SRV.weight, ans.SRV.port, ans.SRV.target )
      end
      table.insert( answers, ("%s:%s:%s:%s"):format( ans.SRV.prio, ans.SRV.weight, ans.SRV.port, ans.SRV.target ) )
    end
  end
  if #answers == 0 then
    stdnse.debug1("dns.answerFetcher found no records of the required type: SRV")
    return false, "No Answers"
  end
  return true, answers
end
additionalFetcher[types.AAAA] = function(dec, retAll)
  local answers = {}
  for _, ans in ipairs(dec.add) do
    if ans.dtype == types.AAAA then
      if not retAll then
        return true, ans.ipv6
      end
      table.insert(answers, ans.ipv6)
    end
  end
  if not retAll or #answers == 0 then
    stdnse.debug1("dns.answerFetcher found no records of the required type: AAAA")
    return false, "No Answers"
  end
  return true, answers
end
function findNiceAdditional(dtype, dec, retAll)
  if (#dec.add > 0) then
    if additionalFetcher[dtype] then
      return additionalFetcher[dtype](dec, retAll)
    else
      stdnse.debug1("dns.findNiceAdditional() does not have an additionalFetcher for dtype %s",
      (type(dtype) == 'string' and dtype) or type(dtype) or "nil")
      return false, "Unable to handle response"
    end
  elseif (dec.flags.RC3 and dec.flags.RC4) then
    return false, "No Such Name"
  else
    stdnse.debug1("dns.findNiceAdditional() found zero answers in a response, but got an unexpected flags.replycode")
    return false, "No Answers"
  end
end
local function encodeFQDN(fqdn)
  if ( not(fqdn) or #fqdn == 0 ) then return "\0" end
  local encQ = {}
  for part in string.gmatch(fqdn, "[^%.]+") do
    encQ[#encQ+1] = string.pack("s1", part)
  end
  encQ[#encQ+1] = "\0"
  return table.concat(encQ)
end
local function encodeQuestions(questions)
  if type(questions) ~= "table" then return nil end
  local encQ = {}
  for _, v in ipairs(questions) do
    encQ[#encQ+1] = encodeFQDN(v.dname)
    encQ[#encQ+1] = string.pack(">I2I2", v.dtype, v.class)
  end
  return table.concat(encQ)
end
local function encodeZones(zones)
  return encodeQuestions(zones)
end
local function encodeUpdates(updates)
  if type(updates) ~= "table" then return nil end
  local encQ = {}
  for _, v in ipairs(updates) do
    encQ[#encQ+1] = encodeFQDN(v.dname)
    encQ[#encQ+1] = string.pack(">I2I2I4s2", v.dtype, v.class, v.ttl, v.data)
  end
  return table.concat(encQ)
end
local function encodeAdditional(additional)
  if type(additional) ~= "table" then return nil end
  local encA = {}
  for _, v in ipairs(additional) do
    encA[#encA+1] = string.pack(">xI2I2I4s2",  v.type, v.class, v.ttl, v.rdata)
  end
  return table.concat(encA)
end
local function encodeFlags(flags)
  if type(flags) == "number" then return flags end
  if type(flags) ~= "table" then return nil end
  local fb = 0
  if flags.QR  then fb = fb|0x8000 end
  if flags.OC1 then fb = fb|0x4000 end
  if flags.OC2 then fb = fb|0x2000 end
  if flags.OC3 then fb = fb|0x1000 end
  if flags.OC4 then fb = fb|0x0800 end
  if flags.AA  then fb = fb|0x0400 end
  if flags.TC  then fb = fb|0x0200 end
  if flags.RD  then fb = fb|0x0100 end
  if flags.RA  then fb = fb|0x0080 end
  if flags.RC1 then fb = fb|0x0008 end
  if flags.RC2 then fb = fb|0x0004 end
  if flags.RC3 then fb = fb|0x0002 end
  if flags.RC4 then fb = fb|0x0001 end
  return fb
end
function encode(pkt)
  if type(pkt) ~= "table" then return nil end
  local encFlags = encodeFlags(pkt.flags)
  local additional = encodeAdditional(pkt.additional)
  local aorplen = #pkt.answers
  local data, qorzlen, aorulen
  if ( #pkt.questions > 0 ) then
    data = encodeQuestions( pkt.questions )
    qorzlen = #pkt.questions
    aorulen = 0
  else
    data = encodeZones( pkt.zones ) .. encodeUpdates( pkt.updates )
    qorzlen = #pkt.zones
    aorulen = #pkt.updates
  end
  local encStr
  if ( pkt.flags.raw ) then
    encStr = string.pack(">I2I2I2I2I2I2", pkt.id, pkt.flags.raw, qorzlen, aorplen, aorulen, #pkt.additional) .. data .. additional
  else
    encStr = string.pack(">I2I2I2I2I2I2", pkt.id, encFlags, qorzlen, aorplen, aorulen, #pkt.additional) .. data .. additional
  end
  return encStr
end
function decStr(data, pos)
  local function dec(data, pos, limit)
    local partlen
    local parts = {}
    local part
    limit = limit or 10
    if limit < 0 then
      return pos, nil
    end
    partlen, pos = string.unpack(">B", data, pos)
    while (partlen ~= 0) do
      if (partlen < 64) then
        if (#data - pos + 1) < partlen then
          return pos
        end
        part, pos = string.unpack("c" .. partlen, data, pos)
        table.insert(parts, part)
        partlen, pos = string.unpack(">B", data, pos)
      else
        partlen, pos = string.unpack(">I2", data, pos - 1)
        local _, part = dec(data, partlen - 0xC000 + 1, limit - 1)
        if part == nil then
          return pos
        end
        table.insert(parts, part)
        partlen = 0
      end
    end
    return pos, table.concat(parts, ".")
  end
  return dec(data, pos)
end
local function decodeQuestions(data, count, pos)
  local q = {}
  for i = 1, count do
    local currQ = {}
    pos, currQ.dname = decStr(data, pos)
    currQ.dtype, currQ.class, pos = string.unpack(">I2I2", data, pos)
    table.insert(q, currQ)
  end
  return pos, q
end
local decoder = {}
decoder[types.A] = function(entry)
  entry.ip = ipOps.str_to_ip(entry.data:sub(1,4))
end
decoder[types.AAAA] = function(entry)
  entry.ipv6 = ipOps.str_to_ip(entry.data:sub(1,16))
end
decoder[types.SSHFP] = function(entry)
  local pos
  entry.SSHFP = {}
  entry.SSHFP.algorithm, entry.SSHFP.fptype, pos = string.unpack(">BB", entry.data)
  entry.SSHFP.fingerprint = stdnse.tohex(entry.data:sub(pos))
end
decoder[types.SOA] = function(entry, data, pos)
  local np = pos - #entry.data
  entry.SOA = {}
  np, entry.SOA.mname = decStr(data, np)
  np, entry.SOA.rname = decStr(data, np)
  entry.SOA.serial,
  entry.SOA.refresh,
  entry.SOA.retry,
  entry.SOA.expire,
  entry.SOA.minimum,
  np = string.unpack(">I4I4I4I4I4", data, np)
end
local function bit_iter(bits)
  return coroutine.wrap(function()
    for i = 1, #bits do
      local n = string.byte(bits, i)
      local j = 0
      local mask = 0x80
      while mask > 0 do
        if (n & mask) ~= 0 then
          coroutine.yield((i - 1) * 8 + j)
        end
        j = j + 1
        mask = (mask >> 1)
      end
    end
  end)
end
decoder[types.NSEC] = function (entry, data, pos)
  local np = pos - #entry.data
  entry.NSEC = {}
  entry.NSEC.dname = entry.dname
  np, entry.NSEC.next_dname = decStr(data, np)
  while np < pos do
    local block_num, type_bitmap
    block_num, type_bitmap, np = string.unpack(">Bs1", data, np)
    entry.NSEC.types = {}
    for i in bit_iter(type_bitmap) do
      entry.NSEC.types[(block_num - 1) * 256 + i] = true
    end
  end
end
decoder[types.NSEC3] = function (entry, data, pos)
  local np = pos - #entry.data
  local _
  local flags
  entry.NSEC3 = {}
  entry.NSEC3.dname = entry.dname
  entry.NSEC3.salt, entry.NSEC3.hash = {}, {}
  entry.NSEC3.hash.alg, entry.NSEC3.flags, entry.NSEC3.iterations, np = string.unpack(">BBI2", data, np)
  entry.NSEC3.salt.bin, np = string.unpack(">s1",  data, np)
  entry.NSEC3.salt.hex = stdnse.tohex(entry.NSEC3.salt.bin)
  entry.NSEC3.hash.bin, np = string.unpack(">s1" , data, np)
  entry.NSEC3.hash.hex = stdnse.tohex(entry.NSEC3.hash.bin)
  entry.NSEC3.hash.base32 = base32.enc(entry.NSEC3.hash.bin, true)
  entry.NSEC3.WinBlockNo, entry.NSEC3.bin, np = string.unpack(">Bs1", data, np)
  entry.NSEC3.types = {}
  for i in bit_iter(entry.NSEC3.bin) do
    entry.NSEC3.types[(entry.NSEC3.WinBlockNo - 1) * 256 + i] = true
  end
end
local function decDomain(entry, data, pos)
  local np = pos - #entry.data
  local _
  _, entry.domain = decStr(data, np)
end
decoder[types.CNAME] = decDomain
decoder[types.NS] = decDomain
decoder[types.PTR] = decDomain
decoder[types.TXT] =
function (entry, data, pos)
  local len = entry.data:len()
  local np = pos - #entry.data
  local txt_len
  local txt
  if len > 0 then
    entry.TXT = {}
    entry.TXT.text = {}
  end
  while np < len do
    txt, np = string.unpack("s1", data, np)
    table.insert( entry.TXT.text, txt )
  end
end
decoder[types.OPT] =
function(entry, data, pos)
  local np = pos - #entry.data - 6
  local opt = { bufsize = entry.class }
  opt.rcode, opt.version, opt.zflags, opt.data, np = string.unpack(">BBI2s2", data, np)
  entry.OPT = opt
end
decoder[types.MX] =
function(entry, data, pos)
  local np = pos - #entry.data + 2
  local _
  entry.MX = {}
  entry.MX.pref = string.unpack(">I2", entry.data)
  _, entry.MX.server = decStr(data, np)
end
decoder[types.SRV] =
function(entry, data, pos)
  local np = pos - #entry.data
  local _
  entry.SRV = {}
  entry.SRV.prio, entry.SRV.weight, entry.SRV.port, np = string.unpack(">I2I2I2", data, np)
  np, entry.SRV.target = decStr(data, np)
end
local function decodeRR(data, count, pos)
  local ans = {}
  for i = 1, count do
    local currRR = {}
    pos, currRR.dname = decStr(data, pos)
    currRR.dtype, currRR.class, currRR.ttl, pos = string.unpack(">I2I2I4", data, pos)
    currRR.data, pos = string.unpack(">s2", data, pos)
    if decoder[currRR.dtype] then
      decoder[currRR.dtype](currRR, data, pos)
    end
    table.insert(ans, currRR)
  end
  return pos, ans
end
local function decodeFlags(flags)
  local tflags = {}
  if (flags & 0x8000) ~= 0 then tflags.QR  = true end
  if (flags & 0x4000) ~= 0 then tflags.OC1 = true end
  if (flags & 0x2000) ~= 0 then tflags.OC2 = true end
  if (flags & 0x1000) ~= 0 then tflags.OC3 = true end
  if (flags & 0x0800) ~= 0 then tflags.OC4 = true end
  if (flags & 0x0400) ~= 0 then tflags.AA  = true end
  if (flags & 0x0200) ~= 0 then tflags.TC  = true end
  if (flags & 0x0100) ~= 0 then tflags.RD  = true end
  if (flags & 0x0080) ~= 0 then tflags.RA  = true end
  if (flags & 0x0008) ~= 0 then tflags.RC1 = true end
  if (flags & 0x0004) ~= 0 then tflags.RC2 = true end
  if (flags & 0x0002) ~= 0 then tflags.RC3 = true end
  if (flags & 0x0001) ~= 0 then tflags.RC4 = true end
  return tflags
end
function decode(data)
  local pos
  local pkt = {}
  local encFlags
  local cnt = {}
  pkt.id, encFlags, cnt.q, cnt.a, cnt.auth, cnt.add, pos = string.unpack(">I2I2I2I2I2I2", data)
  pkt.flags = decodeFlags(encFlags)
  local flags = encodeFlags(pkt.flags)
  if (flags & 0xF000) == 0xA000 then
    return pkt
  else
    pos, pkt.questions = decodeQuestions(data, cnt.q, pos)
    pos, pkt.answers = decodeRR(data, cnt.a, pos)
    pos, pkt.auth = decodeRR(data, cnt.auth, pos)
    pos, pkt.add = decodeRR(data, cnt.add, pos)
  end
  return pkt
end
function newPacket()
  local pkt = {}
  pkt.id = 1
  pkt.flags = {}
  pkt.flags.RD = true
  pkt.questions = {}
  pkt.zones = {}
  pkt.updates = {}
  pkt.answers = {}
  pkt.auth = {}
  pkt.additional = {}
  return pkt
end
function addQuestion(pkt, dname, dtype, class)
  if type(pkt) ~= "table" then return nil end
  if type(pkt.questions) ~= "table" then return nil end
  local class = class or CLASS.IN
  local q = {}
  q.dname = dname
  q.dtype = dtype
  q.class = class
  table.insert(pkt.questions, q)
  return pkt
end
get_default_timeout = function()
  local timeout = {[0] = 10000, 7000, 5000, 4000, 4000, 4000}
  return timeout[nmap.timing_level()] or 4000
end
function addZone(pkt, dname)
  if ( type(pkt) ~= "table" ) or (type(pkt.updates) ~= "table") then return nil end
  table.insert(pkt.zones, { dname=dname, dtype=types.SOA, class=CLASS.IN })
  return pkt
end
local function encodeOPT_Z(flags)
  if type(flags) == "number" then return flags end
  assert(type(flags) == "table")
  local bits = 0
  if flags.DO then bits = bits|0x8000 end
  return bits
end
function addClientSubnet(pkt,Z,subnet)
  local family = subnet.family or 1
  if type(family) == "string" then
    family = ({inet=1,inet6=2})[family]
  end
  assert(family == 1 or family == 2, "Unsupported subnet family")
  local code = 8 -- https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-11
  local mask = subnet.mask
  local scope_mask = 0 -- In requests, it MUST be set to 0
  local addr = ipOps.get_first_ip(subnet.address, mask)
  addr = ipOps.ip_to_str(addr):sub(1, (mask + 7) // 8)
  local data = string.pack(">I2BB", family, mask, scope_mask) .. addr
  local opt = string.pack(">I2s2", code, data)
  addOPT(pkt,Z,opt)
end
function addNSID (pkt,Z)
  local opt = string.pack(">I2I2", 3, 0) -- nsid data
  addOPT(pkt,Z,opt)
end
function addOPT(pkt, Z, opt)
  local rdata = opt or ""
  if type(pkt) ~= "table" then return nil end
  if type(pkt.additional) ~= "table" then return nil end
  local Z_int = encodeOPT_Z(Z)
  local opt = {
    type = types.OPT,
    class = 4096,  -- Actually the sender UDP payload size.
    ttl = 0 * (0x01000000) + 0 * (0x00010000) + Z_int,
    rdata = rdata,
  }
  table.insert(pkt.additional, opt)
  return pkt
end
function addUpdate(pkt, dname, dtype, ttl, data, class)
  if ( type(pkt) ~= "table" ) or (type(pkt.updates) ~= "table") then return nil end
  table.insert(pkt.updates, { dname=dname, dtype=dtype, class=class, ttl=ttl, data=(data or "") } )
  return pkt
end
function update(dname, options)
  local options = options or {}
  local pkt = newPacket()
  local flags = pkt.flags
  local host, port = options.host, options.port
  local timeout = ( type(options.timeout) == "number" ) and options.timeout or get_default_timeout()
  local sendcount = options.sendCount or 2
  local dtype = ( type(options.dtype) == "string" ) and types[options.dtype] or types.A
  local updata = options.data
  local ttl = options.ttl or 86400
  local zone = options.zone or dname:match("^.-%.(.+)$")
  local class = CLASS.IN
  assert(host, "dns.update needs a valid host in options")
  assert(port, "dns.update needs a valid port in options")
  if ( options.zone ) then dname = dname .. "." .. options.zone end
  if ( not(zone) and not( dname:match("^.-%..+") ) ) then
    return false, "hostname needs to be supplied as FQDN"
  end
  flags.RD = false
  flags.OC1, flags.OC2, flags.OC3, flags.OC4 = false, true, false, true
  if ttl == 0 and (not updata or (type(updata) == "string" and #updata == 0)) then
    class = CLASS.ANY
    updata = ""
    if ( types.MX == dtype and not(options.zone) ) then zone=dname end
    if ( types.SRV == dtype and not(options.zone) ) then
      zone=dname:match("^_.-%._.-%.(.+)$")
    end
  else
    if ( dtype == types.A or dtype == types.AAAA ) then
      updata = updata and ipOps.ip_to_str(updata) or ""
    elseif( dtype == types.CNAME ) then
      updata = encodeFQDN(updata)
    elseif( dtype == types.MX ) then
      assert( not( type(updata) ~= "table" ), "dns.update expected options.data to be a table")
      if ( not(options.zone) ) then zone = dname end
      local data = string.pack(">I2", updata.pref)
      data = data .. encodeFQDN(updata.mx)
      updata = data
    elseif ( dtype == types.SRV ) then
      assert( not( type(updata) ~= "table" ), "dns.update expected options.data to be a table")
      local data = string.pack(">I2I2I2", updata.prio, updata.weight, updata.port )
      data = data .. encodeFQDN(updata.target)
      updata = data
      zone = options.zone or dname:match("^_.-%._.-%.(.+)$")
    else
      return false, "Unsupported record type"
    end
  end
  pkt = addZone(pkt, zone)
  pkt = addUpdate(pkt, dname, dtype, ttl, updata, class)
  local data = encode(pkt)
  local status, response = sendPackets(data, host, port, timeout, sendcount, false)
  if ( status ) then
    local decoded = decode(response[1].data)
    local flags = encodeFlags(decoded.flags)
    if (flags & 0xF) == 0 then
      return true
    end
  end
  return false
end
if not unittest.testing() then
  return _ENV
end
test_suite = unittest.TestSuite:new()
test_suite:add_test(unittest.equal(encodeFQDN("test.me.com"), "\x04test\x02me\x03com\0"), "encodeFQDN")
return _ENV;
