/*
 * 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.
 */
#pragma once
#include <string>
#include <vector>
#include <map>
#include "dns.hh"
#include "dnsname.hh"
#include "namespaces.hh"
#include "iputils.hh"
#include "svc-records.hh"
#include <arpa/inet.h>


/** this class can be used to write DNS packets. It knows about DNS in the sense that it makes
    the packet header and record headers.

    The model is:

    packetheader (recordheader recordcontent)*

    The packetheader needs to be updated with the amount of packets of each kind (answer, auth, additional)

    Each recordheader contains the length of a dns record.

    Calling convention:

    vector<uint8_t> content;
    DNSPacketWriter dpw(content, const string& qname, uint16_t qtype, uint16_t qclass=QClass:IN);  // sets the question
    dpw.startrecord("this.is.an.ip.address.", ns_t_a);    // does nothing, except store qname and qtype
    dpw.xfr32BitInt(0x01020304);                         // adds 4 bytes (0x01020304) to the record buffer
    dpw.startrecord("this.is.an.ip.address.", ns_t_a);    // aha! writes out dnsrecord header containing qname and qtype and length 4, plus the recordbuffer, which gets emptied
                                                         // new qname and qtype are stored
    dpw.xfr32BitInt(0x04030201);                         // adds 4 bytes (0x04030201) to the record buffer
    dpw.commit();                                        // writes out dnsrecord header containing qname and qtype and length 4, plus the recordbuffer

    // content now contains the ready packet, with 1 question and 2 answers

*/

template <typename Container> class GenericDNSPacketWriter : public boost::noncopyable
{

public:
  //! Start a DNS Packet in the vector passed, with question qname, qtype and qclass
  GenericDNSPacketWriter(Container& content, const DNSName& qname, uint16_t  qtype, uint16_t qclass=QClass::IN, uint8_t opcode=0);

  /** Start a new DNS record within this packet for name, qtype, ttl, class and in the requested place. Note that packets can only be written in natural order -
      ANSWER, AUTHORITY, ADDITIONAL */
  void startRecord(const DNSName& name, uint16_t qtype, uint32_t ttl=3600, uint16_t qclass=QClass::IN, DNSResourceRecord::Place place=DNSResourceRecord::ANSWER, bool compress=true);

  /** Shorthand way to add an Opt-record, for example for EDNS0 purposes */
  using optvect_t = vector<pair<uint16_t,std::string> >;
  void addOpt(const uint16_t udpsize, const uint16_t extRCode, const uint16_t ednsFlags, const optvect_t& options=optvect_t(), const uint8_t version=0);

  /** needs to be called after the last record is added, but can be called again and again later on. Is called internally by startRecord too.
      The content of the vector<> passed to the constructor is inconsistent until commit is called.
   */
  void commit();

  uint32_t size() const; // needs to be 32 bit because otherwise we don't see the wrap coming when it happened!

  /** Should the packet have grown too big for the writer's liking, rollback removes the record currently being written */
  void rollback();

  /** Discard all content except the question section */
  void truncate();

  void xfr48BitInt(uint64_t val);
  void xfrNodeOrLocatorID(const NodeOrLocatorID& val);
  void xfr32BitInt(uint32_t val);
  void xfr16BitInt(uint16_t val);
  void xfrType(uint16_t val)
  {
    xfr16BitInt(val);
  }
  void xfrIP(const uint32_t& val)
  {
    xfr32BitInt(htonl(val));
  }
  void xfrIP6(const std::string& val)
  {
    xfrBlob(val,16);
  }

  void xfrCAWithoutPort(uint8_t version, const ComboAddress &val)
  {
    if (version == 4) xfrIP(val.sin4.sin_addr.s_addr);
    else if (version == 6) {
      string blob;
      blob.assign((const char*)val.sin6.sin6_addr.s6_addr, 16);
      xfrBlob(blob, 16);
    }
    else throw runtime_error("invalid IP protocol");
  }

  void xfrCAPort(const ComboAddress &val)
  {
    uint16_t port;
    port = val.sin4.sin_port;
    xfr16BitInt(port);
  }

  void xfrTime(const uint32_t& val)
  {
    xfr32BitInt(val);
  }

  void xfr8BitInt(uint8_t val);

  void xfrName(const DNSName& name, bool compress=false);
  void xfrText(const string& text, bool multi=false, bool lenField=true);
  void xfrUnquotedText(const string& text, bool lenField);
  void xfrBlob(const string& blob, int len=-1);
  void xfrBlob(const vector<uint8_t>& blob);
  void xfrSvcParamKeyVals(const set<SvcParam>& kvs);
  void xfrBlobNoSpaces(const string& blob, int len=-1);
  void xfrHexBlob(const string& blob, bool keepReading=false);

  dnsheader* getHeader();
  void getRecordPayload(string& records); // call __before commit__
  void getWireFormatContent(string& record); // call __before commit__

  void setCanonic(bool val)
  {
    d_canonic=val;
  }

  void setLowercase(bool val)
  {
    d_lowerCase=val;
  }
  Container& getContent()
  {
    return d_content;
  }
  bool eof() { return true; } // we don't know how long the record should be

  const string getRemaining() const {
    return "";
  }

  size_t getSizeWithOpts(const optvect_t& options) const;

private:
  uint16_t lookupName(const DNSName& name, uint16_t* matchlen);

  std::vector<uint16_t> d_namepositions;
  DNSName d_qname;
  Container& d_content;
  size_t d_sor{0};
  uint16_t d_rollbackmarker{0}; // start of last complete packet, for rollback
  uint16_t d_truncatemarker{0}; // end of header, for truncate
  DNSResourceRecord::Place d_recordplace{DNSResourceRecord::QUESTION};
  bool d_canonic{false};
  bool d_lowerCase{false};
  bool d_compress{false};
};

using DNSPacketWriter = GenericDNSPacketWriter<std::vector<uint8_t>>;

std::vector<string> segmentDNSText(const string& text); // from dnslabeltext.rl
