/*
 * This file is part of PowerDNS or dnsdist.
 * Copyright -- PowerDNS.COM B.V. and its contributors
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * In addition, for the avoidance of any doubt, permission is granted to
 * link this program with OpenSSL and to (re)distribute the binaries
 * produced as the result of such linking.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "utility.hh"
#include "dnssecinfra.hh"
#include "dnsseckeeper.hh"
#include "base32.hh"
#include <cerrno>
#include "communicator.hh"
#include <set>
#include <boost/utility.hpp>
#include "dnsbackend.hh"
#include "ueberbackend.hh"
#include "packethandler.hh"
#include "axfr-retriever.hh"
#include "logger.hh"
#include "dns.hh"
#include "arguments.hh"
#include "auth-caches.hh"

#include "base64.hh"
#include "inflighter.cc" // NOLINT(bugprone-suspicious-include)
#include "namespaces.hh"
#include "auth-main.hh"
#include "query-local-address.hh"

#include "ixfr.hh"

static std::string humanTime(time_t time)
{
  std::array<char, 80> buf{};
  struct tm tm0{};
  strftime(buf.data(), buf.size() - 1, "%F %H:%M:%S", localtime_r(&time, &tm0));
  return {buf.data(), strlen(buf.data())};
}

void CommunicatorClass::addSuckRequest(const ZoneName& domain, const ComboAddress& primary, SuckRequest::RequestPriority priority, bool force)
{
  auto data = d_data.lock();
  SuckRequest sr; // NOLINT(readability-identifier-length)
  sr.domain = domain;
  sr.primary = primary;
  sr.force = force;
  sr.priorityAndOrder.first = priority;
  sr.priorityAndOrder.second = data->d_sorthelper++;
  pair<UniQueue::iterator, bool> res;

  res = data->d_suckdomains.insert(sr);
  if (res.second) {
    d_suck_sem.post();
  }
  else {
    data->d_suckdomains.modify(res.first, [priorityAndOrder = sr.priorityAndOrder](SuckRequest& so) { // NOLINT(readability-identifier-length)
      if (priorityAndOrder.first < so.priorityAndOrder.first) {
        so.priorityAndOrder = priorityAndOrder;
      }
    });
  }
}

struct XFRContext
{
  DomainInfo domain; // domain being XFR'ed
  ComboAddress remote; // request originator

  bool isDnssecZone{false};
  bool isPresigned{false};
  bool isNSEC3{false};
  bool optOutFlag{false};
  NSEC3PARAMRecordContent ns3pr;

  bool isNarrow{false};
  unsigned int soa_serial{0};
  set<DNSName> nsset, qnames, secured;
  size_t numDeltas{0};

  // Logging-related fields
  mutable string logPrefix;
  std::shared_ptr<Logr::Logger> slog;
};

// NOLINTNEXTLINE(readability-function-cognitive-complexity)
static bool catalogDiff(const XFRContext& ctx, vector<CatalogInfo>& fromXFR, vector<CatalogInfo>& fromDB)
{
  bool doTransaction{true};
  bool inTransaction{false};
  CatalogInfo ciCreate;
  CatalogInfo ciRemove;
  std::unordered_map<ZoneName, bool> clearCache;
  vector<CatalogInfo> retrieve;

  try {
    sort(fromXFR.begin(), fromXFR.end());
    sort(fromDB.begin(), fromDB.end());

    auto xfr = fromXFR.cbegin();
    auto db = fromDB.cbegin(); // NOLINT(readability-identifier-length)

    while (xfr != fromXFR.end() || db != fromDB.end()) {
      bool create{false};
      bool remove{false};

      if (xfr != fromXFR.end() && (db == fromDB.end() || *xfr < *db)) { // create
        ciCreate = *xfr;
        create = true;
        ++xfr;
      }
      else if (db != fromDB.end() && (xfr == fromXFR.end() || *db < *xfr)) { // remove
        ciRemove = *db;
        remove = true;
        ++db;
      }
      else {
        CatalogInfo ciXFR = *xfr;
        CatalogInfo ciDB = *db;
        if (ciDB.d_unique.empty() || ciXFR.d_unique == ciDB.d_unique) { // update
          bool doOptions{false};
          bool doType{false};

          if (ciDB.d_unique.empty()) { // set unique
            SLOG(g_log << Logger::Warning << ctx.logPrefix << "set unique, zone '" << ciXFR.d_zone << "' is now a member" << endl,
                 ctx.slog->info(Logr::Warning, "Catalog-Zone: set unique, adding new member", "new member", Logging::Loggable(ciXFR.d_zone)));
            ciDB.d_unique = ciXFR.d_unique;
            doOptions = true;
          }

          if (ciXFR.d_coo != ciDB.d_coo) { // update coo
            SLOG(g_log << Logger::Warning << ctx.logPrefix << "update coo for zone '" << ciXFR.d_zone << "' to '" << ciXFR.d_coo << "'" << endl,
                 ctx.slog->info(Logr::Warning, "Catalog-Zone: update coo", "zone", Logging::Loggable(ciXFR.d_zone), "to", Logging::Loggable(ciXFR.d_coo)));
            ciDB.d_coo = ciXFR.d_coo;
            doOptions = true;
          }

          if (ciXFR.d_group != ciDB.d_group) { // update group
            SLOG(g_log << Logger::Warning << ctx.logPrefix << "update group for zone '" << ciXFR.d_zone << "' to '" << boost::join(ciXFR.d_group, ", ") << "'" << endl,
                 ctx.slog->info(Logr::Warning, "Catalog-Zone: update group", "zone", Logging::Loggable(ciXFR.d_zone), "group", Logging::Loggable(boost::join(ciXFR.d_group, ", ")))); // can't apply Logging::IterLoggable on set
            doType = !empty(g_memberCatalogGroup) && ((ciXFR.d_group.count(g_memberCatalogGroup) != 0) != (ciDB.d_group.count(g_memberCatalogGroup) != 0));
            ciDB.d_group = ciXFR.d_group;
            doOptions = true;
          }

          if (doOptions) { // update zone options
            if (doTransaction && (inTransaction = ctx.domain.backend->startTransaction(ctx.domain.zone))) {
              SLOG(g_log << Logger::Warning << ctx.logPrefix << "backend transaction started" << endl,
                   ctx.slog->info(Logr::Warning, "Catalog-Zone: backend transaction started"));
              doTransaction = false;
            }

            SLOG(g_log << Logger::Warning << ctx.logPrefix << "update options for zone '" << ciXFR.d_zone << "'" << endl,
                 ctx.slog->info(Logr::Warning, "Catalog-Zone: update options", "zone", Logging::Loggable(ciXFR.d_zone)));
            ctx.domain.backend->setOptions(ciXFR.d_zone, ciDB.toJson());
          }

          if (doType) { // update zone type
            if (doTransaction && (inTransaction = ctx.domain.backend->startTransaction(ctx.domain.zone))) {
              SLOG(g_log << Logger::Warning << ctx.logPrefix << "backend transaction started" << endl,
                   ctx.slog->info(Logr::Warning, "Catalog-Zone: backend transaction started"));
              doTransaction = false;
            }

            DomainInfo::DomainKind kind = ciXFR.d_group.count(g_memberCatalogGroup) != 0 ? DomainInfo::Consumer : DomainInfo::Secondary;
            SLOG(g_log << Logger::Warning << ctx.logPrefix << "update type to '" << DomainInfo::getKindString(kind) << "' for zone '" << ciXFR.d_zone << "'" << endl,
                 ctx.slog->info(Logr::Warning, "Catalog-Zone: update type", "zone", Logging::Loggable(ciXFR.d_zone), "type", Logging::Loggable(kind)));
            ctx.domain.backend->setKind(ciXFR.d_zone, kind);
          }

          if (ctx.domain.primaries != ciDB.d_primaries) { // update primaries
            if (doTransaction && (inTransaction = ctx.domain.backend->startTransaction(ctx.domain.zone))) {
              SLOG(g_log << Logger::Warning << ctx.logPrefix << "backend transaction started" << endl,
                   ctx.slog->info(Logr::Warning, "Catalog-Zone: backend transaction started"));
              doTransaction = false;
            }

            vector<string> primaries;
            for (const auto& primary : ctx.domain.primaries) {
              primaries.push_back(primary.toStringWithPortExcept(53));
            }
            SLOG(g_log << Logger::Warning << ctx.logPrefix << "update primaries for zone '" << ciXFR.d_zone << "' to '" << boost::join(primaries, ", ") << "'" << endl,
                 ctx.slog->info(Logr::Warning, "Catalog-Zone: update primaries", "zone", Logging::Loggable(ciXFR.d_zone), "primaries", Logging::Loggable(boost::join(primaries, ", ")))); // can't apply Logging::IterLoggable on vector
            ctx.domain.backend->setPrimaries(ciXFR.d_zone, ctx.domain.primaries);

            retrieve.emplace_back(ciXFR);
          }
        }
        else { // reset
          ciCreate = *xfr;
          ciRemove = *db;
          create = true;
          remove = true;
        }
        ++xfr;
        ++db;
      }

      DomainInfo d; // NOLINT(readability-identifier-length)
      if (create && remove) {
        SLOG(g_log << Logger::Warning << ctx.logPrefix << "zone '" << ciCreate.d_zone << "' state reset" << endl,
             ctx.slog->info(Logr::Warning, "Catalog-Zone: state reset", "zone", Logging::Loggable(ciCreate.d_zone)));
      }
      else if (create && ctx.domain.backend->getDomainInfo(ciCreate.d_zone, d)) { // detect clash
        CatalogInfo ci; // NOLINT(readability-identifier-length)
        ci.fromJson(d.options, CatalogInfo::CatalogType::Consumer);

        if (ctx.domain.zone != d.catalog && ctx.domain.zone.operator const DNSName&() == ci.d_coo) {
          if (ciCreate.d_unique == ci.d_unique) {
            SLOG(g_log << Logger::Warning << ctx.logPrefix << "zone '" << d.zone << "' owner change without state reset, old catalog '" << d.catalog << "', new catalog '" << ctx.domain.zone << "'" << endl,
                 ctx.slog->info(Logr::Warning, "Catalog-Zone: owner change without state reset", "zone", Logging::Loggable(d.zone), "old catalog", Logging::Loggable(d.catalog), "new catalog", Logging::Loggable(ctx.domain.zone)));

            if (doTransaction && (inTransaction = ctx.domain.backend->startTransaction(ctx.domain.zone))) {
              SLOG(g_log << Logger::Warning << ctx.logPrefix << "backend transaction started" << endl,
                   ctx.slog->info(Logr::Warning, "Catalog-Zone: backend transaction started"));
              doTransaction = false;
            }

            ctx.domain.backend->setPrimaries(ciCreate.d_zone, ctx.domain.primaries);
            ctx.domain.backend->setOptions(ciCreate.d_zone, ciCreate.toJson());
            ctx.domain.backend->setCatalog(ciCreate.d_zone, ctx.domain.zone);

            retrieve.emplace_back(ciCreate);
            continue;
          }
          SLOG(g_log << Logger::Warning << ctx.logPrefix << "zone '" << d.zone << "' owner change with state reset, old catalog '" << d.catalog << "', new catalog '" << ctx.domain.zone << "'" << endl,
               ctx.slog->info(Logr::Warning, "Catalog-Zone: owner change with state reset", "zone", Logging::Loggable(d.zone), "old catalog", Logging::Loggable(d.catalog), "new catalog", Logging::Loggable(ctx.domain.zone)));

          ciRemove.d_zone = d.zone;
          remove = true;
        }
        else {
          if (g_slogStructured) {
            if (d.catalog.empty()) {
              ctx.slog->info(Logr::Warning, "Catalog-Zone: zone already exists, not created", "zone", Logging::Loggable(d.zone));
            }
            else {
              ctx.slog->info(Logr::Warning, "Catalog-Zone: zone already exists in catalog, not created", "zone", Logging::Loggable(d.zone), "catalog", Logging::Loggable(d.catalog));
            }
          }
          else {
            g_log << Logger::Warning << ctx.logPrefix << "zone '" << d.zone << "' already exists";
            if (!d.catalog.empty()) {
              g_log << " in catalog '" << d.catalog;
            }
            g_log << "', create skipped" << endl;
          }
          continue;
        }
      }

      if (remove) { // delete zone
        if (doTransaction && (inTransaction = ctx.domain.backend->startTransaction(ctx.domain.zone))) {
          SLOG(g_log << Logger::Warning << ctx.logPrefix << "backend transaction started" << endl,
               ctx.slog->info(Logr::Warning, "Catalog-Zone: backend transaction started"));
          doTransaction = false;
        }

        SLOG(g_log << Logger::Warning << ctx.logPrefix << "delete zone '" << ciRemove.d_zone << "'" << endl,
             ctx.slog->info(Logr::Warning, "Catalog-Zone: delete zone", "zone", Logging::Loggable(ciRemove.d_zone)));
        ctx.domain.backend->deleteDomain(ciRemove.d_zone);

        if (!create) {
          clearCache[ciRemove.d_zone] = false;
        }
      }

      if (create) { // create zone
        if (doTransaction && (inTransaction = ctx.domain.backend->startTransaction(ctx.domain.zone))) {
          SLOG(g_log << Logger::Warning << ctx.logPrefix << "backend transaction started" << endl,
               ctx.slog->info(Logr::Warning, "Catalog-Zone: backend transaction started"));
          doTransaction = false;
        }

        SLOG(g_log << Logger::Warning << ctx.logPrefix << "create zone '" << ciCreate.d_zone << "'" << endl,
             ctx.slog->info(Logr::Warning, "Catalog-Zone: create zone", "zone", Logging::Loggable(ciCreate.d_zone)));
        d.kind = !empty(g_memberCatalogGroup) && ciCreate.d_group.count(g_memberCatalogGroup) != 0 ? DomainInfo::Consumer : DomainInfo::Secondary;
        ctx.domain.backend->createDomain(ciCreate.d_zone, d.kind, ciCreate.d_primaries, "");

        ctx.domain.backend->setPrimaries(ciCreate.d_zone, ctx.domain.primaries);
        ctx.domain.backend->setOptions(ciCreate.d_zone, ciCreate.toJson());
        ctx.domain.backend->setCatalog(ciCreate.d_zone, ctx.domain.zone);

        clearCache[ciCreate.d_zone] = true;
        retrieve.emplace_back(ciCreate);
      }
    }

    if (inTransaction && ctx.domain.backend->commitTransaction()) {
      SLOG(g_log << Logger::Warning << ctx.logPrefix << "backend transaction committed" << endl,
           ctx.slog->info(Logr::Warning, "Catalog-Zone: backend transaction started"));
    }

    // Update zonecache and clear all caches
    DomainInfo d; // NOLINT(readability-identifier-length)
    for (const auto& zone : clearCache) {
      if (g_zoneCache.isEnabled()) {
        if (zone.second) {
          if (ctx.domain.backend->getDomainInfo(zone.first, d)) {
            g_zoneCache.add(zone.first, d.id);
          }
          else {
            SLOG(g_log << Logger::Error << ctx.logPrefix << "new zone '" << zone.first << "' does not exist and was not inserted in the zone-cache" << endl,
                 ctx.slog->info(Logr::Error, "Catalog-Zone: new zone does not exist and was not inserted in the zone cache", "zone", Logging::Loggable(zone.first)));
          }
        }
        else {
          g_zoneCache.remove(zone.first);
        }
      }

      DNSSECKeeper::clearCaches(zone.first);
      purgeAuthCaches(zone.first.operator const DNSName&().toString() + "$");
    }

    // retrieve new and updated zones with new primaries
    auto primaries = ctx.domain.primaries;
    if (!primaries.empty()) {
      for (auto& ret : retrieve) {
        shuffle(primaries.begin(), primaries.end(), pdns::dns_random_engine());
        const auto& primary = primaries.front();
        Communicator.addSuckRequest(ret.d_zone, primary, SuckRequest::Notify);
      }
    }

    return true;
  }
  catch (DBException& re) {
    SLOG(g_log << Logger::Error << ctx.logPrefix << "DBException " << re.reason << endl,
         ctx.slog->error(Logr::Error, re.reason, "Catalog-Zone: DBException"));
  }
  catch (PDNSException& pe) {
    SLOG(g_log << Logger::Error << ctx.logPrefix << "PDNSException " << pe.reason << endl,
         ctx.slog->error(Logr::Error, pe.reason, "Catalog-Zone: PDNSException"));
  }
  catch (std::exception& re) {
    SLOG(g_log << Logger::Error << ctx.logPrefix << "std::exception " << re.what() << endl,
         ctx.slog->error(Logr::Error, re.what(), "Catalog-Zone: std::exception"));
  }

  if (ctx.domain.backend != nullptr && inTransaction) {
    SLOG(g_log << Logger::Info << ctx.logPrefix << "aborting possible open transaction" << endl,
         ctx.slog->info(Logr::Info, "Catalog-Zone: aborting possible open transaction"));
    ctx.domain.backend->abortTransaction();
  }

  return false;
}

static bool catalogProcess(const XFRContext& ctx, vector<DNSResourceRecord>& rrs)
{
  if (!g_slogStructured) {
    ctx.logPrefix.append("Catalog-Zone ");
  }

  vector<CatalogInfo> fromXFR;
  vector<CatalogInfo> fromDB;
  std::unordered_set<ZoneName> dupcheck;

  // From XFR
  bool hasSOA{false};
  bool zoneInvalid{false};
  int hasVersion{0};

  CatalogInfo ci; // NOLINT(readability-identifier-length)

  vector<DNSResourceRecord> ret;

  const auto compare = [](const DNSResourceRecord& a, const DNSResourceRecord& b) { return a.qname == b.qname ? a.qtype < b.qtype : a.qname.canonCompare(b.qname); }; // NOLINT(readability-identifier-length)
  sort(rrs.begin(), rrs.end(), compare);

  DNSName rel;
  DNSName unique;
  for (auto& rr : rrs) { // NOLINT(readability-identifier-length)
    if (ctx.domain.zone.operator const DNSName&() == rr.qname) {
      if (rr.qtype == QType::SOA) {
        hasSOA = true;
        continue;
      }
      if (rr.qtype == QType::NS) {
        continue;
      }
    }

    else if (rr.qname == g_versiondnsname + ctx.domain.zone.operator const DNSName&() && rr.qtype == QType::TXT) {
      if (hasVersion != 0) {
        SLOG(g_log << Logger::Warning << ctx.logPrefix << "zone '" << ctx.domain.zone << "', multiple version records found, aborting" << endl,
             ctx.slog->info(Logr::Warning, "Catalog-Zone: multiple version records found, aborting"));
        return false;
      }

      if (rr.content == "\"1\"") {
        hasVersion = 1;
      }
      else if (rr.content == "\"2\"") {
        hasVersion = 2;
      }
      else {
        SLOG(g_log << Logger::Warning << ctx.logPrefix << "zone '" << ctx.domain.zone << "', unsupported catalog zone schema version " << rr.content << ", aborting" << endl,
             ctx.slog->info(Logr::Warning, "Catalog-Zone: unsupported catalog zon schema version, aborting", "version", Logging::Loggable(rr.content)));
        return false;
      }
    }

    else if (rr.qname.isPartOf(g_zonesdnsname + ctx.domain.zone.operator const DNSName&())) {
      if (rel.empty() && hasVersion == 0) {
        SLOG(g_log << Logger::Warning << ctx.logPrefix << "zone '" << ctx.domain.zone << "', catalog zone schema version missing, aborting" << endl,
             ctx.slog->info(Logr::Warning, "Catalog-Zone: catalog zone schema version missing, aborting"));
        return false;
      }

      rel = rr.qname.makeRelative(g_zonesdnsname + ctx.domain.zone.operator const DNSName&());

      if (rel.countLabels() == 1 && rr.qtype == QType::PTR) {
        if (!unique.empty()) {
          if (rel != unique) {
            fromXFR.emplace_back(ci);
          }
          else {
            SLOG(g_log << Logger::Warning << ctx.logPrefix << "zone '" << ctx.domain.zone << "', duplicate unique '" << unique << "'" << endl,
                 ctx.slog->info(Logr::Warning, "Catalog-Zone: duplicate unique value", "unique", Logging::Loggable(unique)));
            zoneInvalid = true;
          }
        }

        unique = rel;

        ci = {};
        ci.setType(CatalogInfo::CatalogType::Consumer);
        ci.d_zone = ZoneName(rr.content);
        ci.d_unique = unique;

        if (!dupcheck.insert(ci.d_zone).second) {
          SLOG(g_log << Logger::Warning << ctx.logPrefix << "zone '" << ctx.domain.zone << "', duplicate member zone'" << ci.d_zone << "'" << endl,
               ctx.slog->info(Logr::Warning, "Catalog-Zone: duplicate member zone", "member zone", Logging::Loggable(ci.d_zone)));
          zoneInvalid = true;
        }
      }

      else if (hasVersion == 2) {
        if (rel == (g_coodnsname + unique) && rr.qtype == QType::PTR) {
          if (!ci.d_coo.empty()) {
            SLOG(g_log << Logger::Warning << ctx.logPrefix << "zone '" << ctx.domain.zone << "', duplicate COO for unique '" << unique << "'" << endl,
                 ctx.slog->info(Logr::Warning, "Catalog-Zone: duplicate COO for unique value", "unique", Logging::Loggable(unique)));
            zoneInvalid = true;
          }
          else {
            ci.d_coo = DNSName(rr.content);
          }
        }
        else if (rel == (g_groupdnsname + unique) && rr.qtype == QType::TXT) {
          std::string content = rr.content;
          if (content.length() >= 2 && content.at(0) == '\"' && content.at(content.length() - 1) == '\"') { // TXT pain
            content = content.substr(1, content.length() - 2);
          }
          ci.d_group.insert(std::move(content));
        }
      }
    }
    rr.disabled = true;
  }
  if (!ci.d_zone.empty()) {
    fromXFR.emplace_back(ci);
  }

  if (!hasSOA || hasVersion == 0 || zoneInvalid) {
    SLOG(g_log << Logger::Warning << ctx.logPrefix << "zone '" << ctx.domain.zone << "' is invalid, skip updates" << endl,
         ctx.slog->info(Logr::Warning, "Catalog-Zone: invalid zone, skipping updates"));
    return false;
  }

  // Get catalog info from db
  if (!ctx.domain.backend->getCatalogMembers(ctx.domain.zone, fromDB, CatalogInfo::CatalogType::Consumer)) {
    return false;
  }

  // Process
  return catalogDiff(ctx, fromXFR, fromDB);
}

void CommunicatorClass::ixfrSuck(const TSIGTriplet& tsig, const ComboAddress& laddr, XFRContext& ctx, vector<DNSRecord>* axfr)
{
  if (!g_slogStructured) {
    ctx.logPrefix = "IXFR-in zone '" + ctx.domain.zone.toLogString() + "', primary '" + ctx.remote.toString() + "', ";
  }

  UeberBackend B; // fresh UeberBackend // NOLINT(readability-identifier-length)

  try {
    DNSSECKeeper dk(ctx.slog, &B); // reuse our UeberBackend copy for DNSSECKeeper // NOLINT(readability-identifier-length)

    if (ctx.domain.kind != DomainInfo::Secondary) {
      SLOG(g_log << Logger::Error << ctx.logPrefix << "zone '" << ctx.domain.zone << "' not configured as secondary" << endl,
           ctx.slog->info(Logr::Error, "IXFR: zone not configured as secondary"));
      return;
    }

    uint16_t xfrTimeout = ::arg().asNum("axfr-fetch-timeout");
    soatimes drsoa_soatimes = {ctx.domain.serial, 0, 0, 0, 0};
    DNSRecord drsoa;
    drsoa.setContent(std::make_shared<SOARecordContent>(g_rootdnsname, g_rootdnsname, drsoa_soatimes));
    auto deltas = getIXFRDeltas(ctx.slog, ctx.remote, ctx.domain.zone.operator const DNSName&(), drsoa, xfrTimeout, false, tsig, laddr.sin4.sin_family != 0 ? &laddr : nullptr, ((size_t)::arg().asNum("xfr-max-received-mbytes")) * 1024 * 1024);
    ctx.numDeltas = deltas.size();
    //    cout<<"Got "<<deltas.size()<<" deltas from serial "<<ctx.domain.serial<<", applying.."<<endl;

    // Do not perform Lua records updates if not allowed to.
    if (!::arg().mustDo("enable-lua-record-updates")) {
      bool foundLua{false};
      for (const auto& d : deltas) { // NOLINT(readability-identifier-length)
        for (const auto& rec : d.first) {
          if (rec.d_type == QType::LUA) {
            foundLua = true;
            break;
          }
        }
        if (foundLua) {
          break;
        }
        for (const auto& rec : d.second) {
          if (rec.d_type == QType::LUA) {
            foundLua = true;
            break;
          }
        }
        if (foundLua) {
          break;
        }
      }
      if (foundLua) {
        SLOG(g_log << Logger::Warning << ctx.logPrefix << "refused as it contains Lua record updates" << endl,
             ctx.slog->info(Logr::Warning, "IXFR: refused as it contains Lua record updates"));
        return;
      }
    }

    for (const auto& d : deltas) { // NOLINT(readability-identifier-length)
      const auto& remove = d.first;
      const auto& add = d.second;
      //      cout<<"Delta sizes: "<<remove.size()<<", "<<add.size()<<endl;

      if (remove.empty()) { // we got passed an AXFR!
        *axfr = add;
        return;
      }

      // our hammer is 'replaceRRSet(ctx.domain.id, qname, qt, vector<DNSResourceRecord>& rrset)
      // which thinks in terms of RRSETs
      // however, IXFR does not, and removes and adds *records* (bummer)
      // this means that we must group updates by {qname,qtype}, retrieve the RRSET, apply
      // the add/remove updates, and replaceRRSet the whole thing.

      map<pair<ZoneName, uint16_t>, pair<vector<DNSRecord>, vector<DNSRecord>>> grouped;

      for (const auto& x : remove) { // NOLINT(readability-identifier-length)
        grouped[{ZoneName(x.d_name), x.d_type}].first.push_back(x);
      }
      for (const auto& x : add) { // NOLINT(readability-identifier-length)
        grouped[{ZoneName(x.d_name), x.d_type}].second.push_back(x);
      }

      ctx.domain.backend->startTransaction(ctx.domain.zone, UnknownDomainID);
      for (const auto& g : grouped) { // NOLINT(readability-identifier-length)
        vector<DNSRecord> rrset;
        {
          DNSZoneRecord zrr;
          ctx.domain.backend->lookup(QType(g.first.second), g.first.first.operator const DNSName&() + ctx.domain.zone.operator const DNSName&(), ctx.domain.id);
          while (ctx.domain.backend->get(zrr)) {
            zrr.dr.d_name.makeUsRelative(ctx.domain.zone);
            rrset.push_back(zrr.dr);
          }
        }
        // O(N^2)!
        rrset.erase(remove_if(rrset.begin(), rrset.end(),
                              [&g](const DNSRecord& dr) { // NOLINT(readability-identifier-length)
                                return count(g.second.first.cbegin(),
                                             g.second.first.cend(), dr);
                              }),
                    rrset.end());
        // the DNSRecord== operator compares on name, type, class and lowercase content representation

        for (const auto& x : g.second.second) { // NOLINT(readability-identifier-length)
          rrset.push_back(x);
        }

        vector<DNSResourceRecord> replacement;
        for (const auto& dr : rrset) { // NOLINT(readability-identifier-length)
          auto rr = DNSResourceRecord::fromWire(dr); // NOLINT(readability-identifier-length)
          rr.qname += ctx.domain.zone.operator const DNSName&();
          rr.domain_id = ctx.domain.id;
          if (dr.d_type == QType::SOA) {
            //            cout<<"New SOA: "<<x.d_content->getZoneRepresentation()<<endl;
            auto sr = getRR<SOARecordContent>(dr); // NOLINT(readability-identifier-length)
            ctx.soa_serial = sr->d_st.serial;
          }

          replacement.emplace_back(std::move(rr));
        }
        ctx.domain.backend->replaceRRSet(ctx.domain.id, g.first.first.operator const DNSName&() + ctx.domain.zone.operator const DNSName&(), QType(g.first.second), replacement);
      }
      ctx.domain.backend->commitTransaction();
    }
  }
  catch (std::exception& p) { // NOLINT(readability-identifier-length)
    SLOG(g_log << Logger::Error << ctx.logPrefix << "got exception (std::exception): " << p.what() << endl,
         ctx.slog->error(Logr::Error, p.what(), "IXFR: got exception (std::exception)"));
    throw;
  }
  catch (PDNSException& p) { // NOLINT(readability-identifier-length)
    SLOG(g_log << Logger::Error << ctx.logPrefix << "got exception (PDNSException): " << p.reason << endl,
         ctx.slog->error(Logr::Error, p.reason, "IXFR: got exception (PDNSException)"));
    throw;
  }
}

static bool processRecordForZS(const DNSName& domain, bool& firstNSEC3, DNSResourceRecord& rr, XFRContext& ctx) // NOLINT(readability-identifier-length)
{
  switch (rr.qtype.getCode()) {
  case QType::NSEC3PARAM:
    ctx.ns3pr = NSEC3PARAMRecordContent(rr.content);
    ctx.isDnssecZone = ctx.isNSEC3 = true;
    ctx.isNarrow = false;
    return false;
  case QType::NSEC3: {
    NSEC3RecordContent ns3rc(rr.content);
    if (firstNSEC3) {
      ctx.isDnssecZone = ctx.isPresigned = true;
      firstNSEC3 = false;
    }
    else if (ctx.optOutFlag != static_cast<bool>(ns3rc.d_flags & 1)) {
      throw PDNSException("Zones with a mixture of Opt-Out NSEC3 RRs and non-Opt-Out NSEC3 RRs are not supported.");
    }
    ctx.optOutFlag = static_cast<bool>(ns3rc.d_flags & 1);
    if (ns3rc.isSet(QType::NS) && !(rr.qname == domain)) {
      DNSName hashPart = rr.qname.makeRelative(domain);
      ctx.secured.insert(std::move(hashPart));
    }
    return false;
  }

  case QType::NSEC:
    ctx.isDnssecZone = ctx.isPresigned = true;
    return false;

  case QType::NS:
    if (rr.qname != domain) {
      ctx.nsset.insert(rr.qname);
    }
    break;
  }

  ctx.qnames.insert(rr.qname);

  rr.domain_id = ctx.domain.id;
  return true;
}

/* So this code does a number of things.
   1) It will AXFR a domain from a primary
      The code can retrieve the current serial number in the database itself.
      It may attempt an IXFR
   2) It will filter the zone through a lua *filter* script
   3) The code walks through the zone records do determine DNSSEC status (secured, nsec/nsec3, optout)
   4) It inserts the zone into the database
      With the right 'ordername' fields
   5) It updates the Empty Non Terminals
*/

static vector<DNSResourceRecord> doAxfr(const TSIGTriplet& tt, const ComboAddress& laddr, unique_ptr<AuthLua4>& pdl, XFRContext& ctx) // NOLINT(readability-identifier-length)
{
  uint16_t axfr_timeout = ::arg().asNum("axfr-fetch-timeout");
  vector<DNSResourceRecord> rrs;
  AXFRRetriever retriever(ctx.slog, ctx.remote, ctx.domain.zone, tt, (laddr.sin4.sin_family == 0) ? nullptr : &laddr, ((size_t)::arg().asNum("xfr-max-received-mbytes")) * 1024 * 1024, axfr_timeout);
  Resolver::res_t recs;
  bool first = true;
  bool firstNSEC3{true};
  bool soa_received{false};

  if (!g_slogStructured) {
    ctx.logPrefix = "AXFR-in zone '" + ctx.domain.zone.toLogString() + "', primary '" + ctx.remote.toString() + "', ";
  }

  while (retriever.getChunk(recs, nullptr, axfr_timeout) != 0) {
    if (first) {
      SLOG(g_log << Logger::Notice << ctx.logPrefix << "retrieval started" << endl,
           ctx.slog->info(Logr::Notice, "AXFR: retrieval started"));
      first = false;
    }

    for (auto& rec : recs) {
      rec.qname.makeUsLowerCase();
      if (rec.qtype.getCode() == QType::OPT || rec.qtype.getCode() == QType::TSIG) { // ignore EDNS0 & TSIG
        continue;
      }

      if (!rec.qname.isPartOf(ctx.domain.zone.operator const DNSName&())) {
        SLOG(g_log << Logger::Warning << ctx.logPrefix << "primary tried to sneak in out-of-zone data '" << rec.qname << "'|" << rec.qtype.toString() << ", ignoring" << endl,
             ctx.slog->info(Logr::Warning, "AXFR: primary tried to sneak in out-of-zone data, ignoring", "name", Logging::Loggable(rec.qname), "type", Logging::Loggable(rec.qtype)));
        continue;
      }

      vector<DNSResourceRecord> out;
      if (!pdl || !pdl->axfrfilter(ctx.remote, ctx.domain.zone.operator const DNSName&(), rec, out)) {
        out.push_back(rec); // if axfrfilter didn't do anything, we put our record in 'out' ourselves
      }

      for (auto& rr : out) { // NOLINT(readability-identifier-length)
        if (!rr.qname.isPartOf(ctx.domain.zone.operator const DNSName&())) {
          SLOG(g_log << Logger::Error << ctx.logPrefix << "axfrfilter() filter tried to sneak in out-of-zone data '" << rr.qname << "'|" << rr.qtype.toString() << ", ignoring" << endl,
               ctx.slog->info(Logr::Warning, "AXFR: axfrfilter() filter tried to sneak in out-of-zone data, ignoring", "name", Logging::Loggable(rr.qname), "type", Logging::Loggable(rr.qtype)));
          continue;
        }
        if (!processRecordForZS(ctx.domain.zone.operator const DNSName&(), firstNSEC3, rr, ctx)) {
          continue;
        }
        if (rr.qtype.getCode() == QType::SOA) {
          if (soa_received) {
            continue; // skip the last SOA
          }
          SOAData sd; // NOLINT(readability-identifier-length)
          fillSOAData(rr.content, sd);
          ctx.soa_serial = sd.serial;
          soa_received = true;
        }

        rrs.push_back(rr);
      }
    }
  }
  return rrs;
}

void CommunicatorClass::suck(const ZoneName& domain, const ComboAddress& remote, bool force) // NOLINT(readability-function-cognitive-complexity)
{
  {
    auto data = d_data.lock();
    if (data->d_inprogress.count(domain) != 0) {
      return;
    }
    data->d_inprogress.insert(domain);
  }
  RemoveSentinel rs(domain, this); // this removes us from d_inprogress when we go out of scope // NOLINT(readability-identifier-length)

  XFRContext ctx;
  ctx.remote = remote;
  ctx.domain.backend = nullptr;
  if (g_slogStructured) {
    ctx.slog = d_slog->withValues("XFR zone", Logging::Loggable(domain), "primary", Logging::Loggable(remote));
  }
  else {
    ctx.logPrefix = "XFR-in zone: '" + domain.toLogString() + "', primary: '" + remote.toString() + "', ";
  }

  SLOG(g_log << Logger::Notice << ctx.logPrefix << "initiating transfer" << endl,
       ctx.slog->info(Logr::Notice, "XFR: initiating transfer"));
  UeberBackend B; // fresh UeberBackend // NOLINT(readability-identifier-length)

  bool transaction = false;
  try {
    DNSSECKeeper dk(ctx.slog, &B); // reuse our UeberBackend copy for DNSSECKeeper // NOLINT(readability-identifier-length)

    if (!B.getDomainInfo(domain, ctx.domain) || ctx.domain.backend == nullptr) {
      SLOG(g_log << Logger::Error << ctx.logPrefix << "zone '" << domain << "' not found" << endl,
           ctx.slog->info(Logr::Error, "XFR: zone not found"));
      return;
    }
    if (!force && !ctx.domain.isSecondaryType()) {
      SLOG(g_log << Logger::Error << ctx.logPrefix << "zone '" << domain << "' not configured as secondary" << endl,
           ctx.slog->info(Logr::Error, "XFR: zone not configured as secondary"));
      return;
    }

    TSIGTriplet tt; // NOLINT(readability-identifier-length)
    if (dk.getTSIGForAccess(domain, remote, &tt.name)) {
      string tsigsecret64;
      if (B.getTSIGKey(tt.name, tt.algo, tsigsecret64)) {
        if (B64Decode(tsigsecret64, tt.secret) != 0) {
          SLOG(g_log << Logger::Error << ctx.logPrefix << "unable to Base-64 decode TSIG key '" << tt.name << "' or zone not found" << endl,
               ctx.slog->info(Logr::Error, "XFR: unable to Base-64 decode TSIG key, or zone not found", "key", Logging::Loggable(tt.name)));
          return;
        }
      }
      else {
        SLOG(g_log << Logger::Warning << ctx.logPrefix << "TSIG key '" << tt.name << "' for zone not found" << endl,
             ctx.slog->info(Logr::Warning, "XFR: TSIG key not found", "key", Logging::Loggable(tt.name)));
        return;
      }
    }

    unique_ptr<AuthLua4> pdl{nullptr};
    vector<string> scripts;
    string script = ::arg()["lua-axfr-script"];
    if (B.getDomainMetadata(domain, "LUA-AXFR-SCRIPT", scripts) && !scripts.empty()) {
      if (pdns_iequals(scripts[0], "NONE")) {
        script.clear();
      }
      else {
        script = scripts[0];
      }
    }
    if (!script.empty()) {
      try {
        pdl = make_unique<AuthLua4>(::arg()["lua-global-include-dir"]);
        pdl->loadFile(script);
        SLOG(g_log << Logger::Info << ctx.logPrefix << "loaded Lua script '" << script << "'" << endl,
             ctx.slog->info(Logr::Info, "XFR: loaded Lua script", "name", Logging::Loggable(script)));
      }
      catch (std::exception& e) {
        SLOG(g_log << Logger::Error << ctx.logPrefix << "failed to load Lua script '" << script << "': " << e.what() << endl,
             ctx.slog->error(Logr::Error, e.what(), "XFR: failed to load Lua script", "name", Logging::Loggable(script)));
        return;
      }
    }

    vector<string> localaddr;
    ComboAddress laddr;

    if (B.getDomainMetadata(domain, "AXFR-SOURCE", localaddr) && !localaddr.empty()) {
      try {
        laddr = ComboAddress(localaddr[0]);
        SLOG(g_log << Logger::Info << ctx.logPrefix << "xfr source set to " << localaddr[0] << endl,
             ctx.slog->info(Logr::Info, "XFR: source set according to AXFR-SOURCE metadata", "source", Logging::Loggable(localaddr[0])));
      }
      catch (std::exception& e) {
        SLOG(g_log << Logger::Error << ctx.logPrefix << "failed to set xfr source '" << localaddr[0] << "': " << e.what() << endl,
             ctx.slog->error(Logr::Error, e.what(), "XFR: failed to set source", "source", Logging::Loggable(localaddr[0])));
        return;
      }
    }
    else {
      if (!pdns::isQueryLocalAddressFamilyEnabled(remote.sin4.sin_family)) {
        bool isV6 = remote.sin4.sin_family == AF_INET6;
        SLOG(g_log << Logger::Warning << ctx.logPrefix << "unable to xfr, address family (IPv" << (isV6 ? "6" : "4") << " is not enabled for outgoing traffic (query-local-address)" << endl,
             ctx.slog->info(Logr::Warning, "XFR: unable to xfr, address family is not enabled for outgoing traffic (query-local-address)", "address family", Logging::Loggable(isV6 ? "IPv6" : "IPv4")));
        return;
      }
      laddr = pdns::getQueryLocalAddress(remote.sin4.sin_family, 0);
    }

    bool hadDnssecZone = false;
    bool hadPresigned = false;
    bool hadNSEC3 = false;
    NSEC3PARAMRecordContent hadNs3pr;
    bool hadNarrow = false;

    vector<DNSResourceRecord> rrs;
    if (dk.isSecuredZone(domain, false)) {
      hadDnssecZone = true;
      hadPresigned = dk.isPresigned(domain, false);
      if (dk.getNSEC3PARAM(domain, &ctx.ns3pr, &ctx.isNarrow, false)) {
        hadNSEC3 = true;
        hadNs3pr = ctx.ns3pr;
        hadNarrow = ctx.isNarrow;
      }
    }
    else if (ctx.domain.serial != 0) {
      vector<string> meta;
      B.getDomainMetadata(domain, "IXFR", meta);
      if (!meta.empty() && meta[0] == "1") {
        if (!g_slogStructured) {
          ctx.logPrefix.insert(0, 1, 'I'); // XFR -> IXFR
        }
        vector<DNSRecord> axfr;
        SLOG(g_log << Logger::Notice << ctx.logPrefix << "starting IXFR" << endl,
             ctx.slog->info(Logr::Notice, "IXFR: starting"));
        CommunicatorClass::ixfrSuck(tt, laddr, ctx, &axfr);
        if (!axfr.empty()) {
          SLOG(g_log << Logger::Notice << ctx.logPrefix << "IXFR turned into an AXFR" << endl,
               ctx.slog->info(Logr::Notice, "IXFR: turning into an AXFR"));
          if (!g_slogStructured) {
            ctx.logPrefix[0] = 'A'; // IXFR -> AXFR
          }
          bool firstNSEC3 = true;
          rrs.reserve(axfr.size());
          for (const auto& dr : axfr) { // NOLINT(readability-identifier-length)
            auto rr = DNSResourceRecord::fromWire(dr); // NOLINT(readability-identifier-length)
            rr.qname += domain.operator const DNSName&();
            rr.qname.makeUsLowerCase();
            rr.domain_id = ctx.domain.id;
            if (!processRecordForZS(domain.operator const DNSName&(), firstNSEC3, rr, ctx)) {
              continue;
            }
            if (dr.d_type == QType::SOA) {
              auto sd = getRR<SOARecordContent>(dr); // NOLINT(readability-identifier-length)
              ctx.soa_serial = sd->d_st.serial;
            }
            rrs.emplace_back(std::move(rr));
          }
        }
        else {
          SLOG(g_log << Logger::Warning << ctx.logPrefix << "got " << ctx.numDeltas << " delta" << addS(ctx.numDeltas) << ", zone committed with serial " << ctx.soa_serial << endl,
               ctx.slog->info(Logr::Warning, "IXFR: zone commited", "serial", Logging::Loggable(ctx.soa_serial), "deltas", Logging::Loggable(ctx.numDeltas)));
          purgeAuthCaches(domain.operator const DNSName&().toString() + "$");
          return;
        }
      }
    }

    if (rrs.empty()) {
      SLOG(g_log << Logger::Notice << ctx.logPrefix << "starting AXFR" << endl,
           ctx.slog->info(Logr::Notice, "AXFR: starting"));
      rrs = doAxfr(tt, laddr, pdl, ctx);
      SLOG(g_log << Logger::Notice << ctx.logPrefix << "retrieval finished" << endl,
           ctx.slog->info(Logr::Notice, "AXFR: retrieval finished"));
    }

    if (ctx.domain.kind == DomainInfo::Consumer) {
      if (!catalogProcess(ctx, rrs)) {
        SLOG(g_log << Logger::Warning << ctx.logPrefix << "Catalog-Zone update failed, only import records" << endl,
             ctx.slog->info(Logr::Warning, "AXFR: Catalog-Zone update failed, only records will be imported"));
      }
    }

    if (ctx.isNSEC3) {
      ctx.ns3pr.d_flags = ctx.optOutFlag ? 1 : 0;
    }

    if (!ctx.isPresigned) {
      DNSSECKeeper::keyset_t keys = dk.getKeys(domain, false);
      if (!keys.empty()) {
        ctx.isDnssecZone = true;
        ctx.isNSEC3 = hadNSEC3;
        ctx.ns3pr = hadNs3pr;
        ctx.optOutFlag = static_cast<bool>(hadNs3pr.d_flags & 1);
        ctx.isNarrow = hadNarrow;
      }
    }

    if (ctx.isDnssecZone) {
      if (!ctx.isNSEC3) {
        SLOG(g_log << Logger::Debug << ctx.logPrefix << "adding NSEC ordering information" << endl,
             ctx.slog->info(Logr::Debug, "AXFR: adding NSEC ordering information"));
      }
      else if (!ctx.isNarrow) {
        SLOG(g_log << Logger::Debug << ctx.logPrefix << "adding NSEC3 hashed ordering information" << endl,
             ctx.slog->info(Logr::Debug, "AXFR: adding NSEC3 hashed ordering information"));
      }
      else {
        SLOG(g_log << Logger::Debug << ctx.logPrefix << "zone is narrow, only setting 'auth' fields" << endl,
             ctx.slog->info(Logr::Debug, "AXFR: zone is narrow, only setting 'auth' fields"));
      }
    }

    // Do not perform Lua records updates if not allowed to.
    if (!::arg().mustDo("enable-lua-record-updates")) {
      for (DNSResourceRecord& drr : rrs) {
        if (drr.qtype.getCode() == QType::LUA) {
          SLOG(g_log << Logger::Warning << ctx.logPrefix << "refused as it contains Lua record updates" << endl,
               ctx.slog->info(Logr::Warning, "AXFR: refused as it contains Lua record updates"));
          return;
        }
      }
    }
    transaction = ctx.domain.backend->startTransaction(domain, ctx.domain.id);
    SLOG(g_log << Logger::Info << ctx.logPrefix << "storage transaction started" << endl,
         ctx.slog->info(Logr::Info, "AXFR: storage transaction started"));

    // update the presigned flag and NSEC3PARAM
    if (ctx.isDnssecZone) {
      // update presigned if there was a change
      if (ctx.isPresigned && !hadPresigned) {
        // zone is now presigned
        dk.setPresigned(domain);
      }
      else if (hadPresigned && !ctx.isPresigned) {
        // zone is no longer presigned
        dk.unsetPresigned(domain);
      }
      // update NSEC3PARAM
      if (ctx.isNSEC3) {
        // zone is NSEC3, only update if there was a change
        if (!hadNSEC3 || (hadNarrow != ctx.isNarrow) || (ctx.ns3pr.d_algorithm != hadNs3pr.d_algorithm) || (ctx.ns3pr.d_flags != hadNs3pr.d_flags) || (ctx.ns3pr.d_iterations != hadNs3pr.d_iterations) || (ctx.ns3pr.d_salt != hadNs3pr.d_salt)) {
          dk.setNSEC3PARAM(domain, ctx.ns3pr, ctx.isNarrow);
        }
      }
      else if (hadNSEC3) {
        // zone is no longer NSEC3
        dk.unsetNSEC3PARAM(domain);
      }
    }
    else if (hadDnssecZone) {
      // zone is no longer signed
      if (hadPresigned) {
        // remove presigned
        dk.unsetPresigned(domain);
      }
      if (hadNSEC3) {
        // unset NSEC3PARAM
        dk.unsetNSEC3PARAM(domain);
      }
    }

    bool doent = true;
    uint32_t maxent = ::arg().asNum("max-ent-entries");
    DNSName shorter;
    DNSName ordername;
    set<DNSName> rrterm;
    map<DNSName, bool> nonterm;

    for (DNSResourceRecord& rr : rrs) { // NOLINT(readability-identifier-length)
      if (!ctx.isPresigned) {
        if (rr.qtype.getCode() == QType::RRSIG) {
          continue;
        }
        if (ctx.isDnssecZone && rr.qtype.getCode() == QType::DNSKEY && !::arg().mustDo("direct-dnskey")) {
          continue;
        }
      }

      // Figure out auth and ents
      rr.auth = true;
      shorter = rr.qname;
      rrterm.clear();
      do {
        if (doent) {
          if (ctx.qnames.count(shorter) == 0) {
            rrterm.insert(shorter);
          }
        }
        if (ctx.nsset.count(shorter) != 0 && rr.qtype.getCode() != QType::DS) {
          rr.auth = false;
        }

        if (shorter == domain.operator const DNSName&()) { // stop at apex
          break;
        }
      } while (shorter.chopOff());

      // Insert ents
      if (doent && !rrterm.empty()) {
        bool auth{rr.auth};
        if (!rr.auth && rr.qtype.getCode() == QType::NS) {
          if (ctx.isNSEC3) {
            ordername = DNSName(toBase32Hex(hashQNameWithSalt(ctx.ns3pr, rr.qname)));
          }
          auth = (!ctx.isNSEC3 || !ctx.optOutFlag || ctx.secured.count(ordername) != 0);
        }

        for (const auto& nt : rrterm) { // NOLINT(readability-identifier-length)
          if (nonterm.count(nt) == 0) {
            nonterm.insert(pair<DNSName, bool>(nt, auth));
          }
          else if (auth) {
            nonterm[nt] = true;
          }
        }

        if (nonterm.size() > maxent) {
          SLOG(g_log << Logger::Warning << ctx.logPrefix << "zone has too many empty non terminals" << endl,
               ctx.slog->info(Logr::Warning, "AXFR: zone has too many empty non terminals"));
          nonterm.clear();
          doent = false;
        }
      }

      // RRSIG is always auth, even inside a delegation
      if (rr.qtype.getCode() == QType::RRSIG) {
        rr.auth = true;
      }

      // Add ordername and insert record
      if (ctx.isDnssecZone && rr.qtype.getCode() != QType::RRSIG) {
        if (ctx.isNSEC3) {
          // NSEC3
          ordername = DNSName(toBase32Hex(hashQNameWithSalt(ctx.ns3pr, rr.qname)));
          if (!ctx.isNarrow && (rr.auth || (rr.qtype.getCode() == QType::NS && (!ctx.optOutFlag || ctx.secured.count(ordername) != 0)))) {
            ctx.domain.backend->feedRecord(rr, ordername, true);
          }
          else {
            ctx.domain.backend->feedRecord(rr, DNSName());
          }
        }
        else {
          // NSEC
          if (rr.auth || rr.qtype.getCode() == QType::NS) {
            ordername = rr.qname.makeRelative(domain);
            ctx.domain.backend->feedRecord(rr, ordername);
          }
          else {
            ctx.domain.backend->feedRecord(rr, DNSName());
          }
        }
      }
      else {
        ctx.domain.backend->feedRecord(rr, DNSName());
      }
    }

    // Insert empty non-terminals
    if (doent && !nonterm.empty()) {
      if (ctx.isNSEC3) {
        ctx.domain.backend->feedEnts3(ctx.domain.id, domain.operator const DNSName&(), nonterm, ctx.ns3pr, ctx.isNarrow);
      }
      else {
        ctx.domain.backend->feedEnts(ctx.domain.id, nonterm);
      }
    }

    ctx.domain.backend->commitTransaction();
    transaction = false;
    ctx.domain.backend->setFresh(ctx.domain.id);
    purgeAuthCaches(domain.operator const DNSName&().toString() + "$");

    SLOG(g_log << Logger::Warning << ctx.logPrefix << "zone committed with serial " << ctx.soa_serial << endl,
         ctx.slog->info(Logr::Warning, "AXFR: zone committed", "serial", Logging::Loggable(ctx.soa_serial)));

    // Send secondary re-notifications
    bool doNotify{false};
    vector<string> meta;
    if (B.getDomainMetadata(domain, "SLAVE-RENOTIFY", meta) && !meta.empty()) {
      doNotify = (meta.front() == "1");
    }
    else {
      doNotify = (::arg().mustDo("secondary-do-renotify"));
    }
    if (doNotify) {
      notifyDomain(domain, &B);
    }
  }
  catch (DBException& re) {
    SLOG(g_log << Logger::Error << ctx.logPrefix << "unable to feed record: " << re.reason << endl,
         ctx.slog->error(Logr::Error, re.reason, "XFR: unable to feed record"));
    if (transaction) {
      SLOG(g_log << Logger::Info << ctx.logPrefix << "aborting possible open transaction" << endl,
           ctx.slog->info(Logr::Info, "XFR: aborting possible open transaction"));
      ctx.domain.backend->abortTransaction();
    }
  }
  catch (const MOADNSException& mde) {
    SLOG(g_log << Logger::Error << ctx.logPrefix << "unable to parse record (MOADNSException): " << mde.what() << endl,
         ctx.slog->error(Logr::Error, mde.what(), "XFR: unable to parse record (MOADNSException)"));
    if (transaction) {
      SLOG(g_log << Logger::Info << ctx.logPrefix << "aborting possible open transaction" << endl,
           ctx.slog->info(Logr::Info, "XFR: aborting possible open transaction"));
      ctx.domain.backend->abortTransaction();
    }
  }
  catch (std::exception& re) {
    SLOG(g_log << Logger::Error << ctx.logPrefix << "unable to xfr zone (std::exception): " << re.what() << endl,
         ctx.slog->error(Logr::Error, re.what(), "XFR: unable to xfr zone (std::exception)"));
    if (transaction) {
      SLOG(g_log << Logger::Info << ctx.logPrefix << "aborting possible open transaction" << endl,
           ctx.slog->info(Logr::Info, "XFR: aborting possible open transaction"));
      ctx.domain.backend->abortTransaction();
    }
  }
  catch (ResolverException& re) {
    {
      // The AXFR probably failed due to a problem on the primary server. If SOA-checks against this primary
      // still succeed, we would constantly try to AXFR the zone. To avoid this, we add the zone to the list of
      // failed secondary-checks. This will suspend secondary-checks (and subsequent AXFR) for this zone for some time.
      auto [newCount, nextCheck] = markAsFailed(domain);
      SLOG(g_log << Logger::Warning << ctx.logPrefix << "unable to xfr zone (ResolverException): " << re.reason << " (This was attempt number " << newCount << ". Excluding zone from secondary-checks until " << humanTime(nextCheck) << ")" << endl,
           ctx.slog->error(Logr::Warning, re.reason, "XFR: unable to xfr zone (ResolverException), excluding zone from secondary-checks", "attempt number", Logging::Loggable(newCount), "end of exclusion", Logging::Loggable(humanTime(nextCheck))));
    }
    if (transaction) {
      SLOG(g_log << Logger::Info << "aborting possible open transaction" << endl,
           ctx.slog->info(Logr::Info, "XFR: aborting possible open transaction"));
      ctx.domain.backend->abortTransaction();
    }
  }
  catch (PDNSException& ae) {
    SLOG(g_log << Logger::Error << ctx.logPrefix << "unable to xfr zone (PDNSException): " << ae.reason << endl,
         ctx.slog->error(Logr::Error, ae.reason, "XFR: unable to xfr zone (PDNSException)"));
    if (transaction) {
      SLOG(g_log << Logger::Info << ctx.logPrefix << "aborting possible open transaction" << endl,
           ctx.slog->info(Logr::Info, "XFR: aborting possible open transaction"));
      ctx.domain.backend->abortTransaction();
    }
  }
}
namespace
{
struct DomainNotificationInfo // NOLINT(bugprone-exception-escape)
{
  DomainInfo di;
  bool dnssecOk{false};
  ComboAddress localaddr;
  DNSName tsigkeyname, tsigalgname;
  string tsigsecret;
};
}

struct SecondarySenderReceiver
{
  SecondarySenderReceiver(Logr::log_t slog) :
    d_resolver(slog) {}

  using Identifier = std::tuple<DNSName, ComboAddress, uint16_t>;

  struct Answer
  {
    uint32_t theirSerial;
    uint32_t theirInception;
    uint32_t theirExpire;
  };

  map<uint32_t, Answer> d_freshness;

  void deliverTimeout(const Identifier& /* i */)
  {
  }

  Identifier send(DomainNotificationInfo& dni)
  {
    shuffle(dni.di.primaries.begin(), dni.di.primaries.end(), pdns::dns_random_engine());
    try {
      return {dni.di.zone.operator const DNSName&(),
              *dni.di.primaries.begin(),
              d_resolver.sendResolve(*dni.di.primaries.begin(),
                                     dni.localaddr,
                                     dni.di.zone.operator const DNSName&(),
                                     QType::SOA,
                                     nullptr,
                                     dni.dnssecOk, dni.tsigkeyname, dni.tsigalgname, dni.tsigsecret)};
    }
    catch (PDNSException& e) {
      throw runtime_error("While attempting to query freshness of '" + dni.di.zone.toLogString() + "': " + e.reason);
    }
  }

  bool receive(Identifier& id, Answer& a) // NOLINT(readability-identifier-length)
  {
    return d_resolver.tryGetSOASerial(&(std::get<0>(id)), &(std::get<1>(id)), &a.theirSerial, &a.theirInception, &a.theirExpire, &(std::get<2>(id)));
  }

  void deliverAnswer(const DomainNotificationInfo& dni, const Answer& a, unsigned int /* usec */) // NOLINT(readability-identifier-length)
  {
    d_freshness[dni.di.id] = a;
  }

  Resolver d_resolver;
};

void CommunicatorClass::addSecondaryCheckRequest(const DomainInfo& di, const ComboAddress& remote) // NOLINT(readability-identifier-length)
{
  auto data = d_data.lock();
  DomainInfo ours = di;
  ours.backend = nullptr;

  // When adding a check, if the remote addr from which notification was
  // received is a primary, clear all other primaries so we can be sure the
  // query goes to that one.
  for (const auto& primary : di.primaries) {
    if (ComboAddress::addressOnlyEqual()(remote, primary)) {
      ours.primaries.clear();
      ours.primaries.push_back(primary);
      break;
    }
  }
  data->d_tocheck.erase(di);
  data->d_tocheck.insert(ours);
  d_any_sem.post(); // kick the loop!
}

void CommunicatorClass::addTryAutoPrimaryRequest(const DNSPacket& p) // NOLINT(readability-identifier-length)
{
  const DNSPacket& ours = p;
  auto data = d_data.lock();
  if (data->d_potentialautoprimaries.insert(ours).second) {
    d_any_sem.post(); // kick the loop!
  }
}

// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void CommunicatorClass::secondaryRefresh(PacketHandler* P) // NOLINT(readability-identifier-length)
{
  // not unless we are secondary
  if (!::arg().mustDo("secondary")) {
    return;
  }

  UeberBackend* B = P->getBackend(); // NOLINT(readability-identifier-length)
  vector<DomainInfo> rdomains;
  vector<DomainNotificationInfo> sdomains;
  set<DNSPacket, Data::cmp> trysuperdomains;
  {
    auto data = d_data.lock();
    set<DomainInfo> requeue;
    rdomains.reserve(data->d_tocheck.size());
    for (const auto& di : data->d_tocheck) { // NOLINT(readability-identifier-length)
      if (data->d_inprogress.count(di.zone) != 0) {
        SLOG(g_log << Logger::Debug << "Got NOTIFY for " << di.zone << " while AXFR in progress, requeueing SOA check" << endl,
             d_slog->info(Logr::Debug, "Got NOTIFY while AXFR in progress, requeuing SOA check", "zone", Logging::Loggable(di.zone)));
        requeue.insert(di);
      }
      else {
        // We received a NOTIFY for a zone. This means at least one of the zone's primary server is working.
        // Therefore we delete the zone from the list of failed secondary-checks to allow immediate checking.
        const auto wasFailedDomain = data->d_failedSecondaryRefresh.find(di.zone);
        if (wasFailedDomain != data->d_failedSecondaryRefresh.end()) {
          SLOG(g_log << Logger::Debug << "Got NOTIFY for " << di.zone << ", removing zone from list of failed secondary-checks and going to check SOA serial" << endl,
               d_slog->info(Logr::Debug, "Got NOTIFY, removing zone from list of failed secondary-checks and about to check SOA serial", "zone", Logging::Loggable(di.zone)));
          data->d_failedSecondaryRefresh.erase(di.zone);
        }
        else {
          SLOG(g_log << Logger::Debug << "Got NOTIFY for " << di.zone << ", going to check SOA serial" << endl,
               d_slog->info(Logr::Debug, "Got NOTIFY, about to check SOA serial", "zone", Logging::Loggable(di.zone)));
        }
        rdomains.push_back(di);
      }
    }
    data->d_tocheck.swap(requeue);

    trysuperdomains = std::move(data->d_potentialautoprimaries);
    data->d_potentialautoprimaries.clear();
  }

  for (const DNSPacket& dp : trysuperdomains) { // NOLINT(readability-identifier-length)
    // get the TSIG key name
    TSIGRecordContent trc;
    DNSName tsigkeyname;
    dp.getTSIGDetails(&trc, &tsigkeyname);
    P->tryAutoPrimarySynchronous(dp, tsigkeyname);
  }
  if (rdomains.empty()) { // if we have priority domains, check them first
    B->getUnfreshSecondaryInfos(&rdomains);
  }
  sdomains.reserve(rdomains.size());
  DNSSECKeeper dk(d_slog, B); // NOW HEAR THIS! This DK uses our B backend, so no interleaved access! // NOLINT(readability-identifier-length)
  bool checkSignatures = ::arg().mustDo("secondary-check-signature-freshness") && dk.doesDNSSEC();
  {
    auto data = d_data.lock();
    domains_by_name_t& nameindex = boost::multi_index::get<IDTag>(data->d_suckdomains);
    time_t now = time(nullptr);

    for (DomainInfo& di : rdomains) { // NOLINT(readability-identifier-length)
      const auto failed = data->d_failedSecondaryRefresh.find(di.zone);
      if (failed != data->d_failedSecondaryRefresh.end() && now < failed->second.second) {
        // If the domain has failed before and the time before the next check has not expired, skip this domain
        SLOG(g_log << Logger::Debug << "Zone '" << di.zone << "' is on the list of failed SOA checks. Skipping SOA checks until " << humanTime(failed->second.second) << endl,
             d_slog->info(Logr::Debug, "Zone is on the list of failed SOA checks, skipping", "zone", Logging::Loggable(di.zone), "end of exclusion", Logging::Loggable(humanTime(failed->second.second))));
        continue;
      }
      std::vector<std::string> localaddr;
      SuckRequest sr; // NOLINT(readability-identifier-length)
      sr.domain = di.zone;
      if (di.primaries.empty()) { // secondary domains w/o primaries are ignored
        continue;
      }
      // remove unfresh domains already queued for AXFR, no sense polling them again
      sr.primary = *di.primaries.begin();
      if (nameindex.count(sr) != 0) { // this does NOT however protect us against AXFRs already in progress!
        continue;
      }
      if (data->d_inprogress.count(sr.domain) != 0) { // this does
        continue;
      }

      DomainNotificationInfo dni;
      dni.di = di;
      dni.dnssecOk = checkSignatures;

      if (dk.getTSIGForAccess(di.zone, sr.primary, &dni.tsigkeyname)) {
        string secret64;
        if (!B->getTSIGKey(dni.tsigkeyname, dni.tsigalgname, secret64)) {
          SLOG(g_log << Logger::Warning << "TSIG key '" << dni.tsigkeyname << "' for domain '" << di.zone << "' not found, can not AXFR." << endl,
               d_slog->info(Logr::Warning, "TSIG key not found, unable to AXFR", "zone", Logging::Loggable(di.zone), "key", Logging::Loggable(dni.tsigkeyname)));
          continue;
        }
        if (B64Decode(secret64, dni.tsigsecret) == -1) {
          SLOG(g_log << Logger::Error << "Unable to Base-64 decode TSIG key '" << dni.tsigkeyname << "' for domain '" << di.zone << "', can not AXFR." << endl,
               d_slog->info(Logr::Error, "Unable to Base-64 decode TSIG key, unable to AXFR", "zone", Logging::Loggable(di.zone), "key", Logging::Loggable(dni.tsigkeyname)));
          continue;
        }
      }

      localaddr.clear();
      // check for AXFR-SOURCE
      if (B->getDomainMetadata(di.zone, "AXFR-SOURCE", localaddr) && !localaddr.empty()) {
        try {
          dni.localaddr = ComboAddress(localaddr[0]);
          SLOG(g_log << Logger::Info << "Freshness check source (AXFR-SOURCE) for domain '" << di.zone << "' set to " << localaddr[0] << endl,
               d_slog->info(Logr::Info, "Freshness check source (AXFR-SOURCE) set", "zone", Logging::Loggable(di.zone), "source", Logging::Loggable(localaddr[0])));
        }
        catch (std::exception& e) {
          SLOG(g_log << Logger::Error << "Failed to load freshness check source '" << localaddr[0] << "' for '" << di.zone << "': " << e.what() << endl,
               d_slog->error(Logr::Error, e.what(), "Failed to load freshness check source", "zone", Logging::Loggable(di.zone)));
          return;
        }
      }
      else {
        dni.localaddr.sin4.sin_family = 0;
      }

      sdomains.push_back(std::move(dni));
    }
  }
  if (sdomains.empty()) {
    if (d_secondarieschanged) {
      auto data = d_data.lock();
      SLOG(g_log << Logger::Info << "No new unfresh secondary domains, " << data->d_suckdomains.size() << " queued for AXFR already, " << data->d_inprogress.size() << " in progress" << endl,
           d_slog->info(Logr::Info, "No new unfresh secondary domains", "number of queued domains", Logging::Loggable(data->d_suckdomains.size()), "in progress", Logging::Loggable(data->d_inprogress.size())));
    }
    d_secondarieschanged = !rdomains.empty();
    return;
  }
  {
    auto data = d_data.lock();
    SLOG(g_log << Logger::Info << sdomains.size() << " secondary domain" << (sdomains.size() > 1 ? "s" : "") << " need" << (sdomains.size() > 1 ? "" : "s") << " checking, " << data->d_suckdomains.size() << " queued for AXFR" << endl,
         d_slog->info(Logr::Info, "queuing domains in need of checking for AXFR", "domain count", Logging::Loggable(sdomains.size())));
  }

  SecondarySenderReceiver ssr(d_slog);

  Inflighter<vector<DomainNotificationInfo>, SecondarySenderReceiver> ifl(sdomains, ssr);

  ifl.d_maxInFlight = 200;

  for (;;) {
    try {
      ifl.run();
      break;
    }
    catch (std::exception& e) {
      SLOG(g_log << Logger::Error << "While checking domain freshness: " << e.what() << endl,
           d_slog->error(Logr::Error, e.what(), "Error while checking domain freshness"));
    }
    catch (PDNSException& re) {
      SLOG(g_log << Logger::Error << "While checking domain freshness: " << re.reason << endl,
           d_slog->error(Logr::Error, re.reason, "Error while checking domain freshness"));
    }
  }

  if (ifl.getTimeouts() != 0) {
    SLOG(g_log << Logger::Warning << "Received serial number updates for " << ssr.d_freshness.size() << " zone" << addS(ssr.d_freshness.size()) << ", had " << ifl.getTimeouts() << " timeout" << addS(ifl.getTimeouts()) << endl,
         d_slog->info(Logr::Warning, "Received serial number updates", "updated zones", Logging::Loggable(ssr.d_freshness.size()), "timeouts", Logging::Loggable(ifl.getTimeouts())));
  }
  else {
    SLOG(g_log << Logger::Info << "Received serial number updates for " << ssr.d_freshness.size() << " zone" << addS(ssr.d_freshness.size()) << endl,
         d_slog->info(Logr::Warning, "Received serial number updates", "updated zones", Logging::Loggable(ssr.d_freshness.size())));
  }

  time_t now = time(nullptr);
  for (auto& val : sdomains) {
    DomainInfo& di(val.di); // NOLINT(readability-identifier-length)
    // If our di comes from packethandler (caused by incoming NOTIFY), di.backend will not be filled out,
    // and di.serial will not either.
    // Conversely, if our di came from getUnfreshSecondaryInfos, di.backend and di.serial are valid.
    if (di.backend == nullptr) {
      // Do not overwrite received DI just to make sure it exists in backend:
      // di.primaries should contain the picked primary (as first entry)!
      DomainInfo tempdi;
      if (!B->getDomainInfo(di.zone, tempdi, false)) {
        SLOG(g_log << Logger::Info << "Ignore domain " << di.zone << " since it has been removed from our backend" << endl,
             d_slog->info(Logr::Info, "Ignoring domain since it has been removed from our backend", "zone", Logging::Loggable(di.zone)));
        continue;
      }
      // Backend for di still doesn't exist and this might cause us to
      // SEGFAULT on the setFresh command later on
      di.backend = tempdi.backend;
    }

    if (ssr.d_freshness.count(di.id) == 0) { // If we don't have an answer for the domain
      auto [newCount, nextCheck] = markAsFailed(di.zone);
      if (newCount == 1) {
        SLOG(g_log << Logger::Warning << "Unable to retrieve SOA for " << di.zone << ", this was the first time. NOTE: For every subsequent failed SOA check the domain will be suspended from freshness checks for 'num-errors x " << d_tickinterval << " seconds', with a maximum of " << (uint64_t)::arg().asNum("default-ttl") << " seconds. Skipping SOA checks until " << humanTime(nextCheck) << endl,
             d_slog->info(Logr::Warning, "Unable to retrieve SOA for the first time", "zone", Logging::Loggable(di.zone), "SOA checks skipped until", Logging::Loggable(humanTime(nextCheck))));
      }
      else if (newCount % 10 == 0) {
        SLOG(g_log << Logger::Notice << "Unable to retrieve SOA for " << di.zone << ", this was the " << std::to_string(newCount) << "th time. Skipping SOA checks until " << humanTime(nextCheck) << endl,
             d_slog->info(Logr::Warning, "Unable to retrieve SOA", "zone", Logging::Loggable(di.zone), "attempt number", Logging::Loggable(newCount), "SOA checks skipped until", Logging::Loggable(humanTime(nextCheck))));
      }
      // Make sure we recheck SOA for notifies
      if (di.receivedNotify) {
        di.backend->setStale(di.id);
      }
      continue;
    }

    {
      auto data = d_data.lock();
      const auto wasFailedDomain = data->d_failedSecondaryRefresh.find(di.zone);
      if (wasFailedDomain != data->d_failedSecondaryRefresh.end()) {
        data->d_failedSecondaryRefresh.erase(di.zone);
      }
    }

    bool hasSOA = false;
    SOAData sd; // NOLINT(readability-identifier-length)
    try {
      // Use UeberBackend cache for SOA. Cache gets cleared after AXFR/IXFR.
      B->lookup(QType(QType::SOA), di.zone.operator const DNSName&(), di.id, nullptr);
      DNSZoneRecord zr; // NOLINT(readability-identifier-length)
      hasSOA = B->get(zr);
      if (hasSOA) {
        fillSOAData(zr, sd);
        B->lookupEnd();
      }
    }
    catch (...) {
    }

    uint32_t theirserial = ssr.d_freshness[di.id].theirSerial;
    uint32_t ourserial = sd.serial;
    const ComboAddress remote = *di.primaries.begin();

    if (hasSOA && rfc1982LessThan(theirserial, ourserial) && !::arg().mustDo("axfr-lower-serial")) {
      SLOG(g_log << Logger::Warning << "Domain '" << di.zone << "' more recent than primary " << remote.toStringWithPortExcept(53) << ", our serial " << ourserial << " > their serial " << theirserial << endl,
           d_slog->info(Logr::Warning, "Domain more recent than primary", "zone", Logging::Loggable(di.zone), "our serial", Logging::Loggable(ourserial), "primary serial", Logging::Loggable(theirserial)));
      di.backend->setFresh(di.id);
    }
    else if (hasSOA && theirserial == ourserial) {
      uint32_t maxExpire = 0;
      uint32_t maxInception = 0;
      if (checkSignatures && dk.isPresigned(di.zone)) {
        B->lookup(QType(QType::RRSIG), di.zone.operator const DNSName&(), di.id); // can't use DK before we are done with this lookup!
        DNSZoneRecord zr; // NOLINT(readability-identifier-length)
        while (B->get(zr)) {
          auto rrsig = getRR<RRSIGRecordContent>(zr.dr);
          if (rrsig->d_type == QType::SOA) {
            maxInception = std::max(maxInception, rrsig->d_siginception);
            maxExpire = std::max(maxExpire, rrsig->d_sigexpire);
          }
        }
      }

      SuckRequest::RequestPriority prio = SuckRequest::SignaturesRefresh;
      if (di.receivedNotify) {
        prio = SuckRequest::Notify;
      }

      if (maxInception == 0 && ssr.d_freshness[di.id].theirInception == 0) {
        SLOG(g_log << Logger::Info << "Domain '" << di.zone << "' is fresh (no DNSSEC), serial is " << ourserial << " (checked primary " << remote.toStringWithPortExcept(53) << ")" << endl,
             d_slog->info(Logr::Info, "Domain is fresh (no DNSSEC)", "zone", Logging::Loggable(di.zone), "serial", Logging::Loggable(ourserial), "checked against", Logging::Loggable(remote.toStringWithPortExcept(53))));
        di.backend->setFresh(di.id);
      }
      else if (maxInception == ssr.d_freshness[di.id].theirInception && maxExpire == ssr.d_freshness[di.id].theirExpire) {
        SLOG(g_log << Logger::Info << "Domain '" << di.zone << "' is fresh and SOA RRSIGs match, serial is " << ourserial << " (checked primary " << remote.toStringWithPortExcept(53) << ")" << endl,
             d_slog->info(Logr::Info, "Domain is fresh and SOA RRSIGs match", "zone", Logging::Loggable(di.zone), "serial", Logging::Loggable(ourserial), "checked against", Logging::Loggable(remote.toStringWithPortExcept(53))));
        di.backend->setFresh(di.id);
      }
      else if (maxExpire >= now && ssr.d_freshness[di.id].theirInception == 0) {
        SLOG(g_log << Logger::Info << "Domain '" << di.zone << "' is fresh, primary " << remote.toStringWithPortExcept(53) << " is no longer signed but (some) signatures are still valid, serial is " << ourserial << endl,
             d_slog->info(Logr::Info, "Domain is fresh, primary is no longer signed but (some) signatures are still valid", "zone", Logging::Loggable(di.zone), "serial", Logging::Loggable(ourserial), "checked against", Logging::Loggable(remote.toStringWithPortExcept(53))));
        di.backend->setFresh(di.id);
      }
      else if (maxInception != 0 && ssr.d_freshness[di.id].theirInception == 0) {
        SLOG(g_log << Logger::Notice << "Domain '" << di.zone << "' is stale, primary " << remote.toStringWithPortExcept(53) << " is no longer signed and all signatures have expired, serial is " << ourserial << endl,
             d_slog->info(Logr::Notice, "Domain is stale, primary is no longer signed and all signatures have expired", "zone", Logging::Loggable(di.zone), "serial", Logging::Loggable(ourserial), "checked against", Logging::Loggable(remote.toStringWithPortExcept(53))));
        addSuckRequest(di.zone, remote, prio);
      }
      else if (dk.doesDNSSEC() && maxInception == 0 && ssr.d_freshness[di.id].theirInception != 0) {
        SLOG(g_log << Logger::Notice << "Domain '" << di.zone << "' is stale, primary " << remote.toStringWithPortExcept(53) << " has signed, serial is " << ourserial << endl,
             d_slog->info(Logr::Notice, "Domain is stale, primary has signed", "zone", Logging::Loggable(di.zone), "serial", Logging::Loggable(ourserial), "checked against", Logging::Loggable(remote.toStringWithPortExcept(53))));
        addSuckRequest(di.zone, remote, prio);
      }
      else {
        SLOG(g_log << Logger::Notice << "Domain '" << di.zone << "' is fresh, but RRSIGs differ on primary " << remote.toStringWithPortExcept(53) << ", so DNSSEC is stale, serial is " << ourserial << endl,
             d_slog->info(Logr::Notice, "Domain is fresh, but RRSIGs differ on primary, so DNSSEC is stale", "zone", Logging::Loggable(di.zone), "serial", Logging::Loggable(ourserial), "checked against", Logging::Loggable(remote.toStringWithPortExcept(53))));
        addSuckRequest(di.zone, remote, prio);
      }
    }
    else {
      SuckRequest::RequestPriority prio = SuckRequest::SerialRefresh;
      if (di.receivedNotify) {
        prio = SuckRequest::Notify;
      }

      if (hasSOA) {
        SLOG(g_log << Logger::Notice << "Domain '" << di.zone << "' is stale, primary " << remote.toStringWithPortExcept(53) << " serial " << theirserial << ", our serial " << ourserial << endl,
             d_slog->info(Logr::Notice, "Domain is stale", "zone", Logging::Loggable(di.zone), "serial", Logging::Loggable(ourserial), "primary serial", Logging::Loggable(theirserial), "checked against", Logging::Loggable(remote.toStringWithPortExcept(53))));
      }
      else {
        SLOG(g_log << Logger::Notice << "Domain '" << di.zone << "' is empty, primary " << remote.toStringWithPortExcept(53) << " serial " << theirserial << endl,
             d_slog->info(Logr::Notice, "Domain is empty", "zone", Logging::Loggable(di.zone), "primary serial", Logging::Loggable(theirserial), "checked against", Logging::Loggable(remote.toStringWithPortExcept(53))));
      }
      addSuckRequest(di.zone, remote, prio);
    }
  }
}

vector<pair<ZoneName, ComboAddress>> CommunicatorClass::getSuckRequests()
{
  vector<pair<ZoneName, ComboAddress>> ret;
  auto data = d_data.lock();
  ret.reserve(data->d_suckdomains.size());
  for (auto const& d : data->d_suckdomains) { // NOLINT(readability-identifier-length)
    ret.emplace_back(d.domain, d.primary);
  }
  return ret;
}

size_t CommunicatorClass::getSuckRequestsWaiting()
{
  return d_data.lock()->d_suckdomains.size();
}

std::pair<uint64_t, time_t> CommunicatorClass::markAsFailed(const ZoneName& domain)
{
  uint64_t newCount = 1;
  auto data = d_data.lock();
  time_t now = time(nullptr);
  const auto failedEntry = data->d_failedSecondaryRefresh.find(domain);
  if (failedEntry != data->d_failedSecondaryRefresh.end()) {
    newCount = data->d_failedSecondaryRefresh[domain].first + 1;
  }
  time_t nextCheck = now + std::min(newCount * d_tickinterval, (uint64_t)::arg().asNum("default-ttl")); // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
  data->d_failedSecondaryRefresh[domain] = {newCount, nextCheck};
  return std::make_pair(newCount, nextCheck);
}
