]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Add SVCB records, parsing and writing
authorPieter Lexis <pieter.lexis@powerdns.com>
Thu, 30 Jul 2020 15:56:00 +0000 (17:56 +0200)
committerPieter Lexis <pieter.lexis@powerdns.com>
Fri, 25 Sep 2020 10:26:12 +0000 (12:26 +0200)
23 files changed:
modules/remotebackend/Makefile.am
pdns/Makefile.am
pdns/dnsdistdist/Makefile.am
pdns/dnsdistdist/svc-records.cc [new symlink]
pdns/dnsdistdist/svc-records.hh [new symlink]
pdns/dnsparser.cc
pdns/dnsparser.hh
pdns/dnsrecords.cc
pdns/dnsrecords.hh
pdns/dnswriter.cc
pdns/dnswriter.hh
pdns/qtype.hh
pdns/rcpgenerator.cc
pdns/rcpgenerator.hh
pdns/recursordist/Makefile.am
pdns/recursordist/svc-records.cc [new symlink]
pdns/recursordist/svc-records.hh [new symlink]
pdns/svc-records.cc
pdns/svc-records.hh
pdns/test-dnsrecords_cc.cc
pdns/test-dnswriter_cc.cc
pdns/test-rcpgenerator_cc.cc
pdns/test-svc_records_cc.cc

index fc120a423f168863def3ca1f0d0309350be91a07..e3589db94222c0da67851cd5c10cea1e5f508478 100644 (file)
@@ -123,6 +123,7 @@ libtestremotebackend_la_SOURCES = \
        ../../pdns/nameserver.cc \
        ../../pdns/rcpgenerator.cc \
        ../../pdns/unix_utility.cc \
+       ../../pdns/svc-records.cc ../../pdns/svc-records.hh \
        ../../pdns/json.hh ../../pdns/json.cc \
        ../../pdns/shuffle.hh ../../pdns/shuffle.cc \
        httpconnector.cc \
index d0fae49483cb185f3c03e56f805373b84373d349..0377047bb6839b90b003e95f6eaae1a161f1e068 100644 (file)
@@ -199,6 +199,7 @@ pdns_server_SOURCES = \
        qtype.cc qtype.hh \
        query-local-address.hh query-local-address.cc \
        rcpgenerator.cc \
+       svc-records.cc svc-records.hh \
        receiver.cc \
        resolver.cc resolver.hh \
        axfr-retriever.cc axfr-retriever.hh \
@@ -315,6 +316,7 @@ pdnsutil_SOURCES = \
        pdnsutil.cc \
        qtype.cc \
        rcpgenerator.cc rcpgenerator.hh \
+       svc-records.cc svc-records.hh \
        serialtweaker.cc \
        shuffle.cc shuffle.hh \
        signingpipe.cc \
@@ -390,6 +392,7 @@ zone2sql_SOURCES = \
        nsecrecords.cc \
        qtype.cc \
        rcpgenerator.cc \
+       svc-records.cc svc-records.hh \
        sillyrecords.cc \
        statbag.cc \
        unix_utility.cc \
@@ -417,6 +420,7 @@ zone2json_SOURCES = \
        nsecrecords.cc \
        qtype.cc \
        rcpgenerator.cc \
+       svc-records.cc svc-records.hh \
        sillyrecords.cc \
        statbag.cc \
        unix_utility.cc \
@@ -453,6 +457,7 @@ zone2ldap_SOURCES = \
        nsecrecords.cc \
        qtype.cc \
        rcpgenerator.cc \
+       svc-records.cc svc-records.hh \
        sillyrecords.cc \
        statbag.cc \
        unix_utility.cc \
@@ -478,6 +483,7 @@ sdig_SOURCES = \
        proxy-protocol.cc proxy-protocol.hh \
        qtype.cc \
        rcpgenerator.cc rcpgenerator.hh \
+       svc-records.cc svc-records.hh \
        sdig.cc \
        sillyrecords.cc \
        sstuff.hh \
@@ -510,6 +516,7 @@ calidns_SOURCES = \
        nsecrecords.cc \
        qtype.cc \
        rcpgenerator.cc rcpgenerator.hh \
+       svc-records.cc svc-records.hh \
        sillyrecords.cc \
        sstuff.hh \
        statbag.cc \
@@ -556,6 +563,7 @@ stubquery_SOURCES = \
        nsecrecords.cc \
        qtype.cc \
        rcpgenerator.cc \
+       svc-records.cc svc-records.hh \
        sillyrecords.cc \
        statbag.cc \
        stubresolver.cc stubresolver.hh \
@@ -581,6 +589,7 @@ saxfr_SOURCES = \
        nsecrecords.cc \
        qtype.cc \
        rcpgenerator.cc rcpgenerator.hh \
+       svc-records.cc svc-records.hh \
        saxfr.cc \
        sillyrecords.cc \
        sstuff.hh \
@@ -620,6 +629,7 @@ ixfrdist_SOURCES = \
        query-local-address.hh query-local-address.cc \
        qtype.cc \
        rcpgenerator.cc rcpgenerator.hh \
+       svc-records.cc svc-records.hh \
        resolver.cc \
        axfr-retriever.cc \
        pollmplexer.cc \
@@ -670,6 +680,7 @@ ixplore_SOURCES = \
        query-local-address.hh query-local-address.cc \
        qtype.cc \
        rcpgenerator.cc rcpgenerator.hh \
+       svc-records.cc svc-records.hh \
        resolver.cc \
        axfr-retriever.cc \
        ixfr.cc ixfr.hh \
@@ -704,6 +715,7 @@ dnstcpbench_SOURCES = \
        nsecrecords.cc \
        qtype.cc \
        rcpgenerator.cc rcpgenerator.hh \
+       svc-records.cc svc-records.hh \
        sillyrecords.cc \
        sstuff.hh \
        statbag.cc \
@@ -735,6 +747,7 @@ nsec3dig_SOURCES = \
        nsecrecords.cc \
        qtype.cc \
        rcpgenerator.cc rcpgenerator.hh \
+       svc-records.cc svc-records.hh \
        sillyrecords.cc \
        sstuff.hh \
        statbag.cc \
@@ -768,6 +781,7 @@ toysdig_SOURCES = \
        qtype.cc \
        root-dnssec.hh \
        rcpgenerator.cc rcpgenerator.hh \
+       svc-records.cc svc-records.hh \
        rec-lua-conf.hh \
        recursor_cache.hh \
        sholder.hh \
@@ -810,6 +824,7 @@ tsig_tests_SOURCES = \
        query-local-address.cc \
        qtype.cc \
        rcpgenerator.cc rcpgenerator.hh \
+       svc-records.cc svc-records.hh \
        resolver.cc \
        sillyrecords.cc \
        sstuff.hh \
@@ -839,6 +854,7 @@ speedtest_SOURCES = \
        nsecrecords.cc \
        qtype.cc \
        rcpgenerator.cc rcpgenerator.hh \
+       svc-records.cc svc-records.hh \
        sillyrecords.cc \
        speedtest.cc \
        statbag.cc \
@@ -881,6 +897,7 @@ dnsbulktest_SOURCES = \
        nsecrecords.cc \
        qtype.cc \
        rcpgenerator.cc \
+       svc-records.cc svc-records.hh \
        sillyrecords.cc \
        statbag.cc \
        unix_utility.cc \
@@ -911,6 +928,7 @@ comfun_SOURCES = \
        nsecrecords.cc \
        qtype.cc \
        rcpgenerator.cc \
+       svc-records.cc svc-records.hh \
        sillyrecords.cc \
        statbag.cc \
        unix_utility.cc \
@@ -942,6 +960,7 @@ dnsscan_SOURCES = \
        nsecrecords.cc \
        qtype.cc \
        rcpgenerator.cc rcpgenerator.hh \
+       svc-records.cc svc-records.hh \
        sillyrecords.cc \
        statbag.cc \
        unix_utility.cc \
@@ -972,6 +991,7 @@ dnsreplay_SOURCES = \
        nsecrecords.cc \
        qtype.cc \
        rcpgenerator.cc rcpgenerator.hh \
+       svc-records.cc svc-records.hh \
        sillyrecords.cc \
        statbag.cc \
        unix_utility.cc \
@@ -1002,6 +1022,7 @@ nproxy_SOURCES = \
        pollmplexer.cc \
        qtype.cc \
        rcpgenerator.cc rcpgenerator.hh \
+       svc-records.cc svc-records.hh \
        sillyrecords.cc \
        statbag.cc \
        unix_utility.cc
@@ -1032,6 +1053,7 @@ pdns_notify_SOURCES = \
        pollmplexer.cc \
        qtype.cc \
        rcpgenerator.cc rcpgenerator.hh \
+       svc-records.cc svc-records.hh \
        sillyrecords.cc \
        statbag.cc \
        unix_utility.cc \
@@ -1069,6 +1091,7 @@ dnsscope_SOURCES = \
        nsecrecords.cc \
        qtype.cc \
        rcpgenerator.cc rcpgenerator.hh \
+       svc-records.cc svc-records.hh \
        sillyrecords.cc \
        statbag.cc \
        statnode.cc statnode.hh \
@@ -1099,6 +1122,7 @@ dnsgram_SOURCES = \
        nsecrecords.cc \
        qtype.cc \
        rcpgenerator.cc rcpgenerator.hh \
+       svc-records.cc svc-records.hh \
        sillyrecords.cc \
        statbag.cc \
        unix_utility.cc \
@@ -1126,6 +1150,7 @@ dnsdemog_SOURCES = \
        nsecrecords.cc \
        qtype.cc \
        rcpgenerator.cc rcpgenerator.hh \
+       svc-records.cc svc-records.hh \
        sillyrecords.cc \
        statbag.cc \
        unix_utility.cc \
@@ -1156,6 +1181,7 @@ dnspcap2calidns_SOURCES = \
        nsecrecords.cc \
        qtype.cc \
        rcpgenerator.cc rcpgenerator.hh \
+       svc-records.cc svc-records.hh \
        sillyrecords.cc \
        statbag.cc \
        unix_utility.cc \
@@ -1198,6 +1224,7 @@ dnspcap2protobuf_SOURCES = \
        protobuf.cc protobuf.hh \
        qtype.cc \
        rcpgenerator.cc rcpgenerator.hh \
+       svc-records.cc svc-records.hh \
        sillyrecords.cc \
        statbag.cc \
        unix_utility.cc \
@@ -1263,7 +1290,7 @@ testrunner_SOURCES = \
        proxy-protocol.cc proxy-protocol.hh \
        qtype.cc \
        rcpgenerator.cc \
-       svc-records.cc \
+       svc-records.cc svc-records.hh \
        responsestats.cc \
        responsestats-auth.cc \
        shuffle.cc shuffle.hh \
@@ -1428,6 +1455,7 @@ fuzz_target_moadnsparser_SOURCES = \
        nsecrecords.cc  \
        qtype.cc qtype.hh \
        rcpgenerator.cc rcpgenerator.hh \
+       svc-records.cc svc-records.hh \
        sillyrecords.cc \
        statbag.cc statbag.hh \
        unix_utility.cc \
@@ -1443,6 +1471,7 @@ fuzz_target_packetcache_SOURCES = \
        dnsname.cc dnsname.hh \
        ednsoptions.cc ednsoptions.hh \
        misc.cc misc.hh \
+       svc-records.cc svc-records.hh \
        packetcache.hh \
        qtype.cc qtype.hh \
        statbag.cc statbag.hh
@@ -1475,7 +1504,8 @@ fuzz_target_dnsdistcache_SOURCES = \
        iputils.cc iputils.hh \
        misc.cc misc.hh \
        packetcache.hh \
-       qtype.cc qtype.hh
+       qtype.cc qtype.hh \
+       svc-records.cc svc-records.hh
 
 fuzz_target_dnsdistcache_DEPENDENCIES = $(fuzz_targets_deps)
 fuzz_target_dnsdistcache_LDFLAGS = $(fuzz_targets_ldflags)
@@ -1495,6 +1525,7 @@ fuzz_target_zoneparsertng_SOURCES = \
        nsecrecords.cc  \
        qtype.cc qtype.hh \
        rcpgenerator.cc rcpgenerator.hh \
+       svc-records.cc svc-records.hh \
        sillyrecords.cc \
        statbag.cc statbag.hh \
        unix_utility.cc \
index f70a58ba625ef121e0d8ea1731b665f105a9bf06..8471bea6d3a5bfd4f88b2ce60603ead0499937a2 100644 (file)
@@ -191,6 +191,7 @@ dnsdist_SOURCES = \
        snmp-agent.cc snmp-agent.hh \
        sodcrypto.cc sodcrypto.hh \
        sstuff.hh \
+       svc-records.cc svc-records.hh \
        statnode.cc statnode.hh \
        tcpiohandler.cc tcpiohandler.hh \
        threadname.hh threadname.cc \
@@ -260,6 +261,7 @@ testrunner_SOURCES = \
        sodcrypto.cc \
        sstuff.hh \
        statnode.cc statnode.hh \
+       svc-records.cc svc-records.hh \
        threadname.hh threadname.cc \
        testrunner.cc \
        uuid-utils.hh uuid-utils.cc \
diff --git a/pdns/dnsdistdist/svc-records.cc b/pdns/dnsdistdist/svc-records.cc
new file mode 120000 (symlink)
index 0000000..1408db2
--- /dev/null
@@ -0,0 +1 @@
+../svc-records.cc
\ No newline at end of file
diff --git a/pdns/dnsdistdist/svc-records.hh b/pdns/dnsdistdist/svc-records.hh
new file mode 120000 (symlink)
index 0000000..0498b76
--- /dev/null
@@ -0,0 +1 @@
+../svc-records.hh
\ No newline at end of file
index a84fc2141ac0793289e0a93b75d076b080537152..245f3c8e384e7a3b6e7a4238dcef429dcba66a04 100644 (file)
@@ -553,6 +553,107 @@ void PacketReader::xfrBlob(string& blob, int length)
   }
 }
 
+void PacketReader::xfrSvcParamKeyVals(set<SvcParam> &kvs) {
+  while (d_pos < (d_startrecordpos + d_recordlen)) {
+    if (d_pos + 2 > (d_startrecordpos + d_recordlen)) {
+      throw std::out_of_range("incomplete key");
+    }
+    uint16_t keyInt;
+    xfr16BitInt(keyInt);
+    auto key = static_cast<SvcParam::SvcParamKey>(keyInt);
+    uint16_t len;
+    xfr16BitInt(len);
+    
+    if (d_pos + len > (d_startrecordpos + d_recordlen)) {
+      throw std::out_of_range("record is shorter than SVCB lengthfield implies");
+    }
+
+    switch (key)
+    {
+    case SvcParam::mandatory: {
+      if (len % 2 != 0) {
+        throw std::out_of_range("mandatory SvcParam has invalid length");
+      }
+      if (len == 0) {
+        throw std::out_of_range("empty 'mandatory' values");
+      }
+      std::set<SvcParam::SvcParamKey> paramKeys;
+      size_t stop = d_pos + len;
+      while (d_pos < stop) {
+        uint16_t keyval;
+        xfr16BitInt(keyval);
+        paramKeys.insert(static_cast<SvcParam::SvcParamKey>(keyval));
+      }
+      kvs.insert(SvcParam(key, paramKeys));
+      break;
+    }
+    case SvcParam::alpn: {
+      size_t stop = d_pos + len;
+      std::vector<string> alpns;
+      while (d_pos < stop) {
+        string alpn;
+        uint8_t alpnLen = 0;
+        xfr8BitInt(alpnLen);
+        if (alpnLen == 0) {
+          throw std::out_of_range("alpn length of 0");
+        }
+        xfrBlob(alpn, alpnLen);
+        alpns.push_back(alpn);
+      }
+      kvs.insert(SvcParam(key, alpns));
+      break;
+    }
+    case SvcParam::no_default_alpn: {
+      if (len != 0) {
+        throw std::out_of_range("invalid length for no-default-alpn");
+      }
+      kvs.insert(SvcParam(key));
+      break;
+    }
+    case SvcParam::port: {
+      if (len != 2) {
+        throw std::out_of_range("invalid length for port");
+      }
+      uint16_t port;
+      xfr16BitInt(port);
+      kvs.insert(SvcParam(key, port));
+      break;
+    }
+    case SvcParam::ipv4hint: /* fall-through */
+    case SvcParam::ipv6hint: {
+      size_t addrLen = (key == SvcParam::ipv4hint ? 4 : 16);
+      if (len % addrLen != 0) {
+        throw std::out_of_range("invalid length for " + SvcParam::keyToString(key));
+      }
+      vector<ComboAddress> addresses;
+      auto stop = d_pos + len;
+      while (d_pos < stop)
+      {
+        ComboAddress addr;
+        xfrCAWithoutPort(key, addr);
+        addresses.push_back(addr);
+      }
+      kvs.insert(SvcParam(key, addresses));
+      break;
+    }
+    case SvcParam::echconfig: {
+      std::string blob;
+      blob.reserve(len);
+      xfrBlobNoSpaces(blob, len);
+      kvs.insert(SvcParam(key, blob));
+      break;
+    }
+    default: {
+      std::string blob;
+      blob.reserve(len);
+      xfrBlob(blob, len);
+      kvs.insert(SvcParam(key, blob));
+      break;
+    }
+    }
+  }
+}
+
 
 void PacketReader::xfrHexBlob(string& blob, bool keepReading)
 {
index 34ab2bc13fa65529066337b02c895b98cd3aeaca..d15be9e77688a8ec746bd06c9ee631cbe616dd25 100644 (file)
@@ -36,6 +36,7 @@
 #include "dnsname.hh"
 #include "pdnsexception.hh"
 #include "iputils.hh"
+#include "svc-records.hh"
 
 /** DNS records have three representations:
     1) in the packet
@@ -152,6 +153,7 @@ public:
   void xfrBlobNoSpaces(string& blob, int len);
   void xfrBlob(string& blob, int length);
   void xfrHexBlob(string& blob, bool keepReading=false);
+  void xfrSvcParamKeyVals(set<SvcParam> &kvs);
 
   void getDnsrecordheader(struct dnsrecordheader &ah);
   void copyRecord(vector<unsigned char>& dest, uint16_t len);
index 04613000f23b113a603ef970f8efd9c599192eaf..40908377ed0817b92efb679741e8ce330acdaf59 100644 (file)
@@ -326,6 +326,22 @@ boilerplate_conv(OPENPGPKEY, 61,
                  conv.xfrBlob(d_keyring);
                  )
 
+boilerplate_conv(SVCB, 64,
+                 conv.xfr16BitInt(d_priority);
+                 conv.xfrName(d_target, false, true);
+                 if (d_priority != 0) {
+                   conv.xfrSvcParamKeyVals(d_params);
+                 }
+                 )
+
+boilerplate_conv(HTTPS, 65,
+                 conv.xfr16BitInt(d_priority);
+                 conv.xfrName(d_target, false, true);
+                 if (d_priority != 0) {
+                   conv.xfrSvcParamKeyVals(d_params);
+                 }
+                 )
+
 boilerplate_conv(SMIMEA, 53,
                  conv.xfr8BitInt(d_certusage);
                  conv.xfr8BitInt(d_selector);
@@ -859,6 +875,8 @@ void reportOtherTypes()
    TLSARecordContent::report();
    SMIMEARecordContent::report();
    OPENPGPKEYRecordContent::report();
+   SVCBRecordContent::report();
+   HTTPSRecordContent::report();
    DLVRecordContent::report();
    DNSRecordContent::regist(QClass::ANY, QType::TSIG, &TSIGRecordContent::make, &TSIGRecordContent::make, "TSIG");
    DNSRecordContent::regist(QClass::ANY, QType::TKEY, &TKEYRecordContent::make, &TKEYRecordContent::make, "TKEY");
index b625ebfdee2255067f786f192477f2889a7a4636..4daa894c87e56d1f585fea84098131a9c18e9f18 100644 (file)
@@ -31,6 +31,7 @@
 #include <bitset>
 #include "namespaces.hh"
 #include "iputils.hh"
+#include "svc-records.hh"
 
 #define includeboilerplate(RNAME)   RNAME##RecordContent(const DNSRecord& dr, PacketReader& pr); \
   RNAME##RecordContent(const string& zoneData);                                                  \
@@ -496,6 +497,27 @@ private:
   string d_keyring;
 };
 
+class SVCBRecordContent : public DNSRecordContent
+{
+public:
+  includeboilerplate(SVCB)
+
+private:
+  uint16_t d_priority;
+  DNSName d_target;
+  set<SvcParam> d_params;
+};
+
+class HTTPSRecordContent : public DNSRecordContent
+{
+public:
+  includeboilerplate(HTTPS)
+
+private:
+  uint16_t d_priority;
+  DNSName d_target;
+  set<SvcParam> d_params;
+};
 
 class RRSIGRecordContent : public DNSRecordContent
 {
index 1cbd2b219c4f40598b5526dd950a46ad191bfcc1..640922c950d871be5781afd04d60f412526de45b 100644 (file)
@@ -388,6 +388,63 @@ void DNSPacketWriter::xfrHexBlob(const string& blob, bool keepReading)
   xfrBlob(blob);
 }
 
+void DNSPacketWriter::xfrSvcParamKeyVals(const std::set<SvcParam> &kvs)
+{
+  for (auto const &param : kvs) {
+    // Key first!
+    xfr16BitInt(param.getKey());
+
+    switch (param.getKey())
+    {
+    case SvcParam::mandatory:
+      xfr16BitInt(2 * param.getMandatory().size());
+      for (auto const &m: param.getMandatory()) {
+        xfr16BitInt(m);
+      }
+      break;
+    case SvcParam::alpn:
+    {
+      uint16_t totalSize = param.getALPN().size(); // All 1 octet size headers for each value
+      for (auto const &a : param.getALPN()) {
+        totalSize += a.length();
+      }
+      xfr16BitInt(totalSize);
+      for (auto const &a : param.getALPN()) {
+        xfrUnquotedText(a, true); // will add the 1-byte length field
+      }
+      break;
+    }
+    case SvcParam::no_default_alpn:
+      xfr16BitInt(0); // no size :)
+      break;
+    case SvcParam::port:
+      xfr16BitInt(2); // size
+      xfr16BitInt(param.getPort());
+      break;
+    case SvcParam::ipv4hint:
+      xfr16BitInt(param.getIPHints().size() * 4); // size
+      for (auto a: param.getIPHints()) {
+        xfrCAWithoutPort(param.getKey(), a);
+      }
+      break;
+    case SvcParam::ipv6hint:
+      xfr16BitInt(param.getIPHints().size() * 16); // size
+      for (auto a: param.getIPHints()) {
+        xfrCAWithoutPort(param.getKey(), a);
+      }
+      break;
+    case SvcParam::echconfig:
+      xfr16BitInt(param.getEchConfig().size()); // size
+      xfrBlobNoSpaces(param.getEchConfig());
+      break;
+    default:
+      xfr16BitInt(param.getValue().size());
+      xfrUnquotedText(param.getValue(), false);
+      break;
+    }
+  }
+}
+
 // call __before commit__
 void DNSPacketWriter::getRecordPayload(string& records)
 {
index 2eed85c2967d449c42b0e22ff3be9b2c174a50ae..25cfd6f3e5e5c8517488670f4a3fa7a9a6655bc6 100644 (file)
@@ -27,6 +27,7 @@
 #include "dnsname.hh"
 #include "namespaces.hh"
 #include "iputils.hh"
+#include "svc-records.hh"
 #include <arpa/inet.h>
 
 
@@ -129,6 +130,7 @@ public:
   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 xfrSvcParamKeyVals(const set<SvcParam>& kvs);
   void xfrBlobNoSpaces(const string& blob, int len=-1);
   void xfrHexBlob(const string& blob, bool keepReading=false);
 
index 5c9b80cf46a98915a016b46bfda3eea365b0f1d8..8cbf2441dc543acd901e70a95dcfe1c235fa134a 100644 (file)
@@ -111,6 +111,8 @@ public:
     CDS=59,
     CDNSKEY=60,
     OPENPGPKEY=61,
+    SVCB=64,
+    HTTPS=65,
     SPF=99,
     EUI48=108,
     EUI64=109,
@@ -211,6 +213,8 @@ private:
       qtype_insert("CDS", 59);
       qtype_insert("CDNSKEY", 60);
       qtype_insert("OPENPGPKEY", 61);
+      qtype_insert("SVCB", 64);
+      qtype_insert("HTTPS", 65);
       qtype_insert("SPF", 99);
       qtype_insert("EUI48", 108);
       qtype_insert("EUI64", 109);
index 72e5c08060c1f0cab75325fd9c942e90905411f5..eca64b68a4a2811fade537620df47d0a1012c061 100644 (file)
@@ -135,7 +135,7 @@ void RecordTextReader::xfrIP(uint32_t &val)
       if(octet > 255)
         throw RecordTextException("unable to parse IP address");
     }
-    else if(dns_isspace(d_string.at(d_pos))) 
+    else if(dns_isspace(d_string.at(d_pos)) || d_string.at(d_pos) == ','
       break;
     else {
       throw RecordTextException(string("unable to parse IP address, strange character: ")+d_string.at(d_pos));
@@ -296,6 +296,117 @@ void RecordTextReader::xfrBlob(string& val, int)
   B64Decode(tmp, val);
 }
 
+void RecordTextReader::xfrSvcParamKeyVals(set<SvcParam>& val)
+{
+  while (d_pos != d_end) {
+    skipSpaces();
+    if (d_pos == d_end)
+      return;
+
+    // Find the SvcParamKey
+    size_t pos = d_pos;
+    while (d_pos != d_end) {
+      if (d_string.at(d_pos) == '=' || d_string.at(d_pos) == ' ') {
+        break;
+      }
+      d_pos++;
+    }
+
+    // We've reached a space or equals-sign or the end of the string (d_pos is at this char)
+    string k = d_string.substr(pos, d_pos - pos);
+    SvcParam::SvcParamKey key;
+    try {
+      key = SvcParam::keyFromString(k);
+    } catch (const std::invalid_argument &e) {
+      throw RecordTextException(e.what());
+    }
+
+    if (key != SvcParam::no_default_alpn) {
+      if (d_pos == d_end || d_string.at(d_pos) != '=') {
+        throw RecordTextException("expected '=' after " + k);
+      }
+      d_pos++; // Now on the first character after '='
+      if (d_pos == d_end || d_string.at(d_pos) == ' ') {
+        throw RecordTextException("expected value after " + k + "=");
+      }
+    }
+
+    switch (key) {
+    case SvcParam::no_default_alpn:
+      if (d_pos != d_end && d_string.at(d_pos) == '=') {
+        throw RecordTextException(k + " key can not have values");
+      }
+      val.insert(SvcParam(key));
+      break;
+    case SvcParam::ipv4hint: /* fall-through */
+    case SvcParam::ipv6hint: {
+      vector<ComboAddress> hints;
+      do {
+        ComboAddress address;
+        xfrCAWithoutPort(key, address); // The SVBC authors chose 4 and 6 to represent v4hint and v6hint :)
+        hints.push_back(address);
+        if (d_pos < d_end && d_string.at(d_pos) == ',') {
+          d_pos++; // Go to the next address
+        }
+      } while (d_pos != d_end && d_string.at(d_pos) != ' ');
+      try {
+        val.insert(SvcParam(key, hints));
+      }
+      catch (const std::invalid_argument& e) {
+        throw RecordTextException(e.what());
+      }
+      break;
+    }
+    case SvcParam::alpn: {
+      string value;
+      xfrUnquotedText(value, false);
+      vector<string> parts;
+      stringtok(parts, value, ",");
+      val.insert(SvcParam(key, parts));
+      break;
+    }
+    case SvcParam::mandatory: {
+      string value;
+      xfrUnquotedText(value, false);
+      vector<string> parts;
+      stringtok(parts, value, ",");
+      set<string> values(parts.begin(), parts.end());
+      val.insert(SvcParam(key, values));
+      break;
+    }
+    case SvcParam::port: {
+      uint16_t port;
+      xfr16BitInt(port);
+      val.insert(SvcParam(key, port));
+      break;
+    }
+    case SvcParam::echconfig: {
+      bool haveQuote = d_string.at(d_pos) == '"';
+      if (haveQuote) {
+        d_pos++;
+      }
+      string value;
+      xfrBlobNoSpaces(value);
+      if (haveQuote) {
+        d_pos++;
+      }
+      val.insert(SvcParam(key, value));
+      break;
+    }
+    default: {
+      string value;
+      if (d_string.at(d_pos) == '"') {
+        xfrText(value);
+      }
+      else {
+        xfrUnquotedText(value);
+      }
+      val.insert(SvcParam(key, value));
+      break;
+    }
+    }
+  }
+}
 
 static inline uint8_t hextodec(uint8_t val)
 {
@@ -607,6 +718,61 @@ void RecordTextWriter::xfrHexBlob(const string& val, bool)
   }
 }
 
+void RecordTextWriter::xfrSvcParamKeyVals(const set<SvcParam>& val) {
+  for (auto const &param : val) {
+    if (!d_string.empty())
+      d_string.append(1, ' ');
+
+    d_string.append(SvcParam::keyToString(param.getKey()));
+    if (param.getKey() != SvcParam::no_default_alpn) {
+      d_string.append(1, '=');
+    }
+
+    switch (param.getKey())
+    {
+    case SvcParam::no_default_alpn:
+      break;
+    case SvcParam::ipv4hint: /* fall-through */
+    case SvcParam::ipv6hint:
+      // TODO use xfrCA and put commas in between?
+      d_string.append(ComboAddress::caContainerToString(param.getIPHints(), false));
+      break;
+    case SvcParam::alpn:
+      // This is safe, as this value needs no quotes
+      d_string.append(boost::join(param.getALPN(), ","));
+      break;
+    case SvcParam::mandatory:
+    {
+      bool doComma = false;
+      for (auto const &k: param.getMandatory()) {
+        if (doComma)
+          d_string.append(1, ',');
+        d_string.append(SvcParam::keyToString(k));
+        doComma = true;
+      }
+      break;
+    }
+    case SvcParam::port: {
+      auto str = d_string;
+      d_string.clear();
+      xfr16BitInt(param.getPort());
+      d_string = str + d_string;
+      break;
+    }
+    case SvcParam::echconfig: {
+      auto str = d_string;
+      d_string.clear();
+      xfrBlobNoSpaces(param.getEchConfig());
+      d_string = str + '"' + d_string + '"';
+      break;
+    }
+    default:
+      d_string.append(param.getValue());
+      break;
+    }
+  }
+}
+
 void RecordTextWriter::xfrText(const string& val, bool multi, bool lenField)
 {
   if(!d_string.empty())
index d17c750b47e8ab9495d032d5e8afca2947b64762..afc1dcb25a21008a46e39f0de1c1e6964e27cf30 100644 (file)
@@ -27,6 +27,7 @@
 #include "namespaces.hh"
 #include "dnsname.hh"
 #include "iputils.hh"
+#include "svc-records.hh"
 
 class RecordTextException : public runtime_error
 {
@@ -61,6 +62,8 @@ public:
   void xfrBlobNoSpaces(string& val, int len=-1);
   void xfrBlob(string& val, int len=-1);
 
+  void xfrSvcParamKeyVals(set<SvcParam>& val);
+
   const string getRemaining() const {
     return d_string.substr(d_pos);
   }
@@ -96,6 +99,7 @@ public:
   void xfrBlobNoSpaces(const string& val, int len=-1);
   void xfrBlob(const string& val, int len=-1);
   void xfrHexBlob(const string& val, bool keepReading=false);
+  void xfrSvcParamKeyVals(const set<SvcParam>& val);
   bool eof() { return true; };
 
   const string getRemaining() const {
index adac88c82b446e1fa4f5b3a2457c255506156c70..c4ae2bc5ddbdd703759187ec7d60f37d60511c10 100644 (file)
@@ -175,6 +175,7 @@ pdns_recursor_SOURCES = \
        sortlist.cc sortlist.hh \
        sstuff.hh \
        stable-bloom.hh \
+       svc-records.cc svc-records.hh \
        syncres.cc syncres.hh \
        threadname.hh threadname.cc \
        tsigverifier.cc tsigverifier.hh \
@@ -269,6 +270,7 @@ testrunner_SOURCES = \
        sholder.hh \
        sstuff.hh \
        stable-bloom.hh \
+       svc-records.cc svc-records.hh \
        syncres.cc syncres.hh \
        test-arguments_cc.cc \
        test-base32_cc.cc \
diff --git a/pdns/recursordist/svc-records.cc b/pdns/recursordist/svc-records.cc
new file mode 120000 (symlink)
index 0000000..1408db2
--- /dev/null
@@ -0,0 +1 @@
+../svc-records.cc
\ No newline at end of file
diff --git a/pdns/recursordist/svc-records.hh b/pdns/recursordist/svc-records.hh
new file mode 120000 (symlink)
index 0000000..0498b76
--- /dev/null
@@ -0,0 +1 @@
+../svc-records.hh
\ No newline at end of file
index 1431a718d4374e78672a7708719c6d933765534b..994a11c36db1b21b2afb25e9fe396f5f6be91eef 100644 (file)
@@ -76,24 +76,40 @@ SvcParam::SvcParam(const SvcParamKey &key, const std::string &value) {
     d_echconfig = value;
     return;
   }
-  d_value = value;
+  d_value = std::move(value);
 }
 
-SvcParam::SvcParam(const SvcParamKey &key, const std::set<std::string> &value) {
+SvcParam::SvcParam(const SvcParamKey &key, const std::vector<std::string> & value) {
   d_key = key;
-  if (d_key != SvcParamKey::alpn && d_key != SvcParamKey::mandatory) {
+  if (d_key != SvcParamKey::alpn) {
     throw std::invalid_argument("can not create SvcParam for " + keyToString(key) + " with a string-set value");
   }
   if (d_key == SvcParamKey::alpn) {
-    d_alpn = value;
+    d_alpn = std::move(value);
+  }
+}
+
+SvcParam::SvcParam(const SvcParamKey &key, const std::set<std::string> &value) {
+  d_key = key;
+  if (d_key != SvcParamKey::mandatory) {
+    throw std::invalid_argument("can not create SvcParam for " + keyToString(key) + " with a string-set value");
   }
   if (d_key == SvcParamKey::mandatory) {
-    // TODO validate entries
-    d_mandatory = value;
+    for (auto const &v: value) {
+      d_mandatory.insert(keyFromString(std::move(v)));
+    }
   }
 }
 
-SvcParam::SvcParam(const SvcParamKey &key, const std::set<ComboAddress> &value) {
+SvcParam::SvcParam(const SvcParamKey &key, const std::set<SvcParam::SvcParamKey> &value) {
+  d_key = key;
+  if (d_key != SvcParamKey::mandatory) {
+    throw std::invalid_argument("can not create SvcParam for " + keyToString(key) + " with a string-set value");
+  }
+  d_mandatory = std::move(value);
+}
+
+SvcParam::SvcParam(const SvcParamKey &key, const std::vector<ComboAddress> &value) {
   d_key = key;
   if (d_key != SvcParamKey::ipv6hint && d_key != SvcParamKey::ipv4hint) {
     throw std::invalid_argument("can not create SvcParam for " + keyToString(key) + " with an IP address value");
@@ -106,7 +122,7 @@ SvcParam::SvcParam(const SvcParamKey &key, const std::set<ComboAddress> &value)
       throw std::invalid_argument("non-IPv4 address ('" + addr.toString() + "') passed for " + keyToString(key));
     }
   }
-  d_ipHints = value;
+  d_ipHints = std::move(value);
 }
 
 SvcParam::SvcParam(const SvcParamKey &key, const uint16_t value) {
@@ -117,7 +133,12 @@ SvcParam::SvcParam(const SvcParamKey &key, const uint16_t value) {
   d_port = value;
 }
 
-std::set<ComboAddress> SvcParam::getIPHints() const {
+//! This ensures an std::set<SvcParam> will be sorted by key (section 2.2 mandates this for wire format)
+bool SvcParam::operator<(const SvcParam& other) const {
+  return this->d_key < other.getKey();
+}
+
+std::vector<ComboAddress> SvcParam::getIPHints() const {
   if (d_key != SvcParamKey::ipv6hint && d_key != SvcParamKey::ipv4hint) {
     throw std::invalid_argument("getIPHints called for non-IP address key '" + keyToString(d_key) + "'");
   }
@@ -131,14 +152,14 @@ uint16_t SvcParam::getPort() const {
   return d_port;
 }
 
-std::set<std::string> SvcParam::getALPN() const {
+std::vector<std::string> SvcParam::getALPN() const {
   if (d_key != SvcParam::alpn) {
     throw std::invalid_argument("getALPN called for non-alpn key '" + keyToString(d_key) + "'");
   }
   return d_alpn;
 }
 
-std::set<std::string> SvcParam::getMandatory() const {
+std::set<SvcParam::SvcParamKey> SvcParam::getMandatory() const {
   if (d_key != SvcParam::mandatory) {
     throw std::invalid_argument("getMandatory called for non-mandatory key '" + keyToString(d_key) + "'");
   }
index 5de7f0fd018a4ced3adca852aa6712e0dac69118..5488ade44f74b785544dc87e740ce07d44560ff7 100644 (file)
@@ -50,11 +50,17 @@ class SvcParam {
   //! To create a "generic" SvcParam (for keyNNNNN and echconfig)
   SvcParam(const SvcParamKey &key, const std::string &value);
 
-  //! To create a multi-value SvcParam (like alpn and mandatory)
+  //! To create a multi-value SvcParam (like mandatory)
   SvcParam(const SvcParamKey &key, const std::set<std::string> &value);
 
+  //! To create a multi-value SvcParam (like alpn)
+  SvcParam(const SvcParamKey &key, const std::vector<std::string> &value);
+
+  //! To create a multi-value SvcParam with key values (like mandatory)
+  SvcParam(const SvcParamKey &key, const std::set<SvcParamKey> &value);
+
   //! To create and ipv{4,6}hists SvcParam
-  SvcParam(const SvcParamKey &key, const std::set<ComboAddress> &value);
+  SvcParam(const SvcParamKey &key, const std::vector<ComboAddress> &value);
 
   //! To create a port SvcParam
   SvcParam(const SvcParamKey &key, const uint16_t value);
@@ -65,18 +71,16 @@ class SvcParam {
   //! Returns the string value of the SvcParamKey
   static std::string keyToString(const SvcParamKey &k);
 
-  bool operator< (const SvcParam &other) const {
-    return this->d_key < other.d_key;
-  };
+  bool operator< (const SvcParam &other) const;
 
   SvcParamKey getKey() const {
     return d_key;
   }
 
   uint16_t getPort() const;
-  std::set<ComboAddress> getIPHints() const;
-  std::set<std::string> getALPN() const;
-  std::set<std::string> getMandatory() const;
+  std::vector<ComboAddress> getIPHints() const;
+  std::vector<std::string> getALPN() const;
+  std::set<SvcParamKey> getMandatory() const;
   std::string getEchConfig() const;
   std::string getValue() const;
 
@@ -84,9 +88,9 @@ class SvcParam {
     SvcParamKey d_key;
     std::string d_value; // For keyNNNNN vals
 
-    std::set<std::string> d_alpn; // For ALPN
-    std::set<std::string> d_mandatory; // For mandatory
-    std::set<ComboAddress> d_ipHints; // For ipv{6,4}hints
+    std::vector<std::string> d_alpn; // For ALPN
+    std::set<SvcParamKey> d_mandatory; // For mandatory
+    std::vector<ComboAddress> d_ipHints; // For ipv{6,4}hints
     std::string d_echconfig; // For echconfig
     uint16_t d_port; // For port
 
index a17738253aaa51ed35956655fbc8cc6c4994adb8..0616cd88f1f389309ed5a23232ef0ac3fa531f65 100644 (file)
@@ -201,6 +201,24 @@ BOOST_AUTO_TEST_CASE(test_record_types) {
 
      (CASE_S(QType::OPENPGPKEY, "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", "\x99\x02\x0d\x04\x55\x08\x5e\x1d\x01\x10\x00\xcd\x3e\x52\xfa\x36\x95\x84\x69\x1d\x8a\x27\x1e\xa9\xd7\xdb\x1c\x21\x5a\x6c\x04\x8a\x3b\x52\xac\xc2\x23\x37\x85\x6d\x12\x5a\x18\x38\x67\xfc\x79\x50\x81\xcc\x00\x6a\x57\x15\x36\x65\x94\x5f\x46\x50\xb2\xb7\x67\xf4\x21\x1f\x2d\x6b\x85\xbb\xd3\x8d\xda\x8d\x53\xb2\x56\x22\xc2\x7f\xa8\x48\x09\x47\xda\xb6\x85\x9b\x67\xb2\x25\x3a\x7f\x88\xce\xc8\x51\x40\xad\x7b\x98\xe6\x7f\x77\x7e\x2f\xda\xe3\x54\x8e\xc9\x8e\x63\x27\x6d\x5a\x3a\x49\x56\x36\x28\x30\xc3\xef\x67\x54\x2d\xc8\x3b\x22\xa6\x1c\x08\x03\x78\x09\x3f\xad\x3d\x93\xf9\xcb\x87\x18\x7f\xe0\xf1\x46\x5a\x71\x87\x1b\xea\xaa\x21\xb2\x02\x2e\xa5\x01\xc2\x76\xe3\xff\x1f\x27\x88\x6c\x1b\x49\x52\xd6\xf5\x35\xd8\xb5\x9e\x59\x31\xda\xe5\xa4\x4c\x33\xec\x7d\x92\x6b\xf7\x19\x87\x57\x1b\xe1\x71\x16\xa6\x70\xe7\x69\x19\x12\x70\x2c\x72\x90\xb6\xca\x66\xd7\xfb\x56\x05\x0a\xbf\x2c\x8c\x66\x06\x75\xef\xc3\xfb\x97\x0b\xbe\xeb\xb8\x3c\x63\x59\xe1\xf9\x12\x79\x2f\xb4\xc9\x7b\xde\xfe\xce\xae\xd6\x65\xfa\x46\x49\x05\xe9\x23\x86\x5a\xe2\x5f\x04\x93\x86\x75\x6c\xd8\xc1\xb7\xab\xf6\x4a\xa0\xaa\x91\xd3\x8e\x9a\xfc\x1c\xec\x9b\xcd\x5a\xe8\x06\x28\xed\x4e\x50\x7f\xb6\x89\xa6\x21\x47\x78\x05\x39\x3d\x26\xfe\x1f\xbf\x68\x00\xe1\x49\x02\xd7\x75\x0c\xf4\xa0\x45\xd4\xd9\xf3\x52\x72\x82\xf8\x9b\x38\xf4\xed\xe6\x8f\x41\xd9\x0e\x80\xab\x91\x63\x27\x67\x20\xec\x12\x68\x4c\xc9\x03\xd4\xe9\x84\x45\x1d\x69\x81\xe8\xe4\xca\x78\x3f\xd9\x9e\x4f\x9b\xd6\xec\xfe\x1c\x80\x60\xad\xb3\x6e\x88\x6f\xc0\xd1\xcd\x34\x56\x78\xfb\x95\x1e\x29\xf7\x8a\x0e\xbb\x2a\x48\xd3\x45\xee\xb0\xbb\x09\xc0\xfb\x23\x23\x81\x20\x14\x62\x9d\xa1\x1b\x4c\xd2\xca\x49\x86\x2f\xb7\x81\xff\x3c\x56\xad\x0d\x10\xd0\x5a\x15\x51\xcb\x04\x65\x95\x15\xa5\x6b\x7d\xfb\xab\x19\xc0\xd9\x61\x40\x1d\x9e\xf9\xbc\x8d\x23\xc2\x80\x13\xdf\x17\x1a\x59\xba\xd7\xb1\x36\x42\xe3\xc9\xa6\x97\x8c\x3b\x60\x6b\x9f\xe7\xe6\x04\x9d\xff\x74\x69\x58\x2b\xe9\xf6\xed\xd6\xe6\x37\x13\x34\x36\x45\xe1\x33\x91\x9c\xe5\xd4\x58\x77\x28\xb7\xff\x97\x9b\x21\xfc\xff\xe7\x27\xf3\x27\x6d\xd2\x9a\xce\x2c\x64\x3e\x16\xd7\x48\xf8\x05\xb2\x30\x25\xc7\x7a\x7e\x9a\x69\x9d\x63\x79\x22\xd9\xab\x00\x11\x01\x00\x01\xb4\x5a\x70\x64\x6e\x73\x20\x72\x65\x67\x72\x65\x73\x73\x69\x6f\x6e\x20\x74\x65\x73\x74\x69\x6e\x67\x20\x6b\x65\x79\x20\x28\x6f\x6e\x6c\x79\x20\x66\x6f\x72\x20\x74\x65\x73\x74\x69\x6e\x67\x20\x74\x68\x65\x20\x6f\x70\x65\x6e\x70\x67\x70\x6b\x65\x79\x20\x72\x72\x29\x20\x3c\x72\x65\x67\x72\x65\x73\x73\x69\x6f\x6e\x40\x70\x6f\x77\x65\x72\x64\x6e\x73\x2e\x6f\x72\x67\x3e\x89\x02\x37\x04\x13\x01\x08\x00\x21\x05\x02\x55\x08\x5e\x1d\x02\x1b\x03\x05\x0b\x09\x08\x07\x02\x06\x15\x08\x09\x0a\x0b\x02\x04\x16\x02\x03\x01\x02\x1e\x01\x02\x17\x80\x00\x0a\x09\x10\x3e\xbf\xca\xaa\x8c\x8a\xd9\x9e\xc0\xe9\x0f\xff\x60\xe0\x6e\xf2\x7c\x2d\xf9\xf8\x2e\x4a\x8f\xef\xc8\x29\x67\xd0\xfa\xa7\x4e\x20\x66\xe1\x1b\xc4\xda\x03\xc3\x52\x7b\x0a\xba\x85\xe8\x3f\x91\x79\xe9\x71\xc1\x5c\xa9\x7d\x90\x48\x26\xc9\xc8\x6d\xfe\x77\x3b\x73\xb3\x51\x1f\x35\x21\x69\xc3\x36\x45\x06\xae\xe8\x28\x0d\x64\x4f\x0a\x2d\xaa\x83\x4c\xa9\x44\xb9\xcf\xc0\x36\xda\xe7\x18\x66\x06\xf2\x03\x08\x77\x84\xe5\xcd\x4e\x6d\x68\xb1\x00\xf3\x2a\xc7\x20\x79\xf0\x9c\xcb\x83\x0e\x9c\x75\x6b\x41\x13\x91\xb6\x02\xae\x3e\xc2\xca\x7a\x9d\x85\x70\x80\x06\xa1\x63\x81\x12\xfb\x41\xbd\xfb\x52\x72\xcf\x13\xb6\x1d\x16\xde\xdf\x44\x6c\x19\x54\xb9\xdb\xdc\x65\x59\x2c\xa1\xee\xad\x9d\x13\x00\xdb\xcb\x75\x15\x43\x49\x3d\xdf\xd2\x50\x9c\x60\xca\x69\x3d\xb1\xc0\xbe\x2d\x8c\x21\x4e\xf5\x14\xf1\xa4\x9a\xcf\xbe\xb6\x3e\x20\x4e\x5b\x6a\xd1\x54\x6f\xac\xc3\x66\xd5\xee\x6f\xe3\xe8\x47\x67\xef\x90\x37\x8b\x26\xe3\xe0\xc9\x1a\x5a\x3b\x97\xce\x74\x82\x73\x17\x73\xb7\x3b\x1a\x44\x46\xb6\x0f\x20\x57\x48\x79\x93\x98\xd7\xe6\x27\x1d\x83\x1c\xaf\x10\xd4\xee\x05\x0f\x90\xa6\xb9\x18\x4d\x18\x7d\xb9\x8c\xe9\xa1\x34\x3e\x35\x84\x59\x96\x06\x89\xc7\x16\x83\x22\xfd\x4a\xfe\x46\xf7\xa0\xfb\x67\x00\x42\x89\x04\x2d\x36\x21\x96\xc1\xd0\x48\x92\xdd\x1e\x69\xc5\x85\xe1\x50\xda\x16\x57\xb7\xb3\xd8\x0e\x21\x5f\x52\xfe\xb5\xfb\xe5\x80\x20\x1b\x6d\x0f\xdc\x04\xbb\xea\x5e\x0a\x60\xb1\xff\x71\xe3\x28\x12\x8b\x9d\xaf\x40\x1c\xb7\xb7\x06\x72\xae\xd2\xec\xa2\x0c\x5a\x73\x10\x03\x7c\xf7\x32\x95\xe1\xed\x8e\xef\xfe\x95\x29\x2e\xf9\x9e\xbe\x5b\xa3\xa8\x8c\x0d\x24\x63\x61\xe5\x68\xcc\x90\x01\x32\x4b\x21\x77\x7e\xe8\xd1\x3f\xb5\x8c\xbb\xd1\x1b\x14\xf4\xea\xb4\x3b\x25\x53\x8f\x18\x76\xd5\x23\xcb\xb0\x4c\x5e\x4b\x04\x9f\x72\x25\x52\x2e\x7e\xc8\xb8\xa1\x88\x39\x5f\x36\x30\x41\xc5\x86\x9a\x35\x52\x1b\xfe\x1b\x9d\x89\xf1\x3d\x5f\x23\xbd\xf4\xb1\x2e\x13\x97\x90\xde\x90\xb6\x82\xcf\x5a\x60\xfc\x59\x88\xfc\x44\x1e\x02\xc6\x2a\x9b\xdf\x2e\x0c\x06\x85\x41\xcb\x3a\x80\x3b\x1e\xf1\x56\x06\x9a\x9d\x97\x62\xca\x02\x5b\xbd\x9a\xb3\xc1\x25\x2a\x63\x08\x6d\x0f\xf1\x0f\xba\xb1\x0c\xe4\x4b\x9a\x30\xad\x56\x8c\x8a\x1f\x5f\x7e\xb3\xa3\xe9\xeb\x56\xb9\x02\x0d\x04\x55\x08\x5e\x1d\x01\x10\x00\xc3\x7a\xa5\xc2\xc9\x52\x1c\x3d\x6d\xf3\xb9\x92\xa8\x4b\xbf\x8a\x6b\xb7\x40\x2a\x54\x15\x28\x93\xa5\x3c\xb4\x86\x84\x73\xdb\xc3\x69\xab\x01\x70\x51\xd7\x80\xfb\x28\x38\x6f\x7b\x0d\x70\x7a\x05\xdb\xec\x1e\xe2\xbf\xe6\xe3\x15\x1f\x93\xcd\x7b\x9f\x0c\x2e\x9d\x3c\x88\x2a\x08\xb7\x40\xb4\x09\xb3\x77\xf8\x3f\x50\x6d\xe8\x37\xec\x0c\x8a\xc8\x56\xa7\x07\xf0\x97\xab\x5b\x4f\x29\x50\x89\x73\xa0\x4c\x36\x43\xbf\x06\xcc\x27\x52\xc4\xd3\x9a\x57\x1d\xe0\x6a\x2f\xa5\xa2\x54\x38\xe6\x73\x6e\x51\x94\x40\x16\xe1\xcd\x1a\x21\x86\x63\x86\x7d\xdb\x12\x70\x67\x9e\x60\xc1\x3f\xc8\xd1\x6a\xce\xdd\x59\x02\xb5\x78\xbd\xad\x61\x38\xb3\x94\xa4\xd1\x6f\xf5\x33\xb2\xd1\x95\x62\xff\x20\xe2\xfa\xc6\x00\xf5\xf0\x92\x65\x4f\x5a\x95\x62\x3f\x71\xb0\x5c\xb7\x92\x3e\xdd\xb8\x75\x97\x6e\xae\x9a\x56\x6c\x61\x09\xbc\xba\xa9\xd2\x82\x24\x16\xa5\x19\xba\x50\x93\xba\xc5\x06\xe6\x50\xdf\x46\xcc\x26\x32\x28\x29\xa9\xcd\x94\x5d\xba\xaa\xa3\x8e\xd8\x85\xd7\x45\xb5\xcb\x38\xa8\x4d\xf4\xed\xab\x67\x2f\x53\xa3\xdd\x20\xd6\x18\xf3\x0d\x94\xb8\xe8\xd1\x77\xd8\x02\x86\xdb\xc6\xb9\xdb\x8e\x23\x0a\xa5\x45\x36\x12\x69\x75\xf5\x74\x3a\x05\x7b\x5b\xd6\xa9\x9c\xd1\x9b\xea\x48\x0e\xe9\x8e\xf5\xe5\xf9\x8c\xc9\xb3\x15\x72\xc3\x1b\x6a\x43\xda\x5f\x7b\xce\x84\xa3\x54\x57\xff\xa5\x8a\x01\x6a\xc8\x17\xe3\x77\xb7\x57\xf6\x84\x93\xd3\xd4\x81\x76\xd8\x16\x6d\x90\xc4\x53\x1c\x21\x84\x8e\xf4\x61\x03\x32\x81\x3e\x43\x1a\x0c\x5d\xfd\x54\x8c\xbc\x4c\x08\xae\x60\x0f\x98\x87\x2f\x71\x5f\xdb\x3c\x97\x2f\x39\x53\x64\x4f\x9f\x1c\xf3\x0b\x70\x09\x33\x72\x9d\xae\xea\x92\x7d\xc6\x90\x06\x7a\x4a\xaa\x7a\xa9\xc8\x49\x76\x76\xa3\xcc\x97\xb2\xab\xfc\x35\x5b\xe5\xd7\x36\x92\x89\x2a\x94\xfa\x46\xa4\xf6\x62\x20\xff\x6f\x96\x0b\xd5\xb5\xa8\xed\x8f\x79\x22\xd6\xda\xd8\xa2\xb3\xdf\x34\x42\x79\xb2\x0c\xe0\x60\xda\x82\xf4\xd4\x84\xe1\xfe\xef\x86\x4e\x87\x44\x1b\x07\xd1\x60\x9a\x0d\x00\x98\x14\x8d\xc5\x0c\xa5\x69\x74\x2e\xea\x06\xf2\x51\xba\xc2\xe9\x21\x03\x84\x18\x15\x6a\x5f\xd9\x03\xd5\x81\x34\x7a\xdd\x56\xe9\x16\x0c\x02\x9d\x47\x12\x10\xfa\x87\x07\x40\x51\x0a\xc0\xc7\xb1\xdd\x24\xfb\xf6\x12\xb8\xfa\x25\x00\x11\x01\x00\x01\x89\x02\x1f\x04\x18\x01\x08\x00\x09\x05\x02\x55\x08\x5e\x1d\x02\x1b\x0c\x00\x0a\x09\x10\x3e\xbf\xca\xaa\x8c\x8a\xd9\x9e\xb3\x3c\x0f\xfe\x3e\x42\x53\xcc\xff\x24\x43\x0e\x06\x5b\x4c\x76\x67\x15\xdf\x3a\x69\x11\x55\x8a\x52\x12\x81\xde\x85\xfe\xb2\xf5\x81\x4d\xd3\x4f\x93\xf2\x96\xfb\xd5\x11\x88\xb6\xfb\x97\xe6\xfe\xa8\x3c\xa3\xc2\x94\xe9\xc8\x56\x0d\x96\x54\x73\xaa\xe9\x0d\xc7\xb1\x71\x33\x97\xba\x08\x6c\xeb\x4f\x13\xdf\x16\x5c\x78\x95\x5e\xe7\x7f\x76\x37\x39\xe2\xe1\xf3\xf5\x68\xd2\x3d\xfc\xbf\x89\xd3\xfb\x48\xcb\x25\xa3\x6f\x41\x1d\x1f\xca\xf6\x74\x24\x88\x6b\x5a\xc8\x3e\x7b\xc1\xdc\x60\x6c\xb5\xce\x6c\x4d\xdf\x03\x48\x25\xe0\x0b\x16\x97\x51\x19\xcc\xd7\x16\x54\xbe\x12\x22\xbb\xe7\x04\x6a\x58\x21\x41\x81\x72\xc4\xc8\x88\xfd\xe9\xd3\xd6\xee\xe1\x07\xfa\x1e\x32\x51\x5d\x99\x41\xba\x2c\xe2\x80\x88\x1e\x3b\x7f\x65\xfc\x6e\xad\x6f\xa1\x80\xd5\xc9\xdb\xab\x8c\xba\x68\x2b\x50\x79\x71\xcb\xae\x7b\xda\x93\x58\xd1\xab\x39\xe5\x25\x65\x2a\x4b\x59\x90\x80\xe3\xc5\xd4\xcb\x8e\x76\xe1\xc0\xbd\x06\xdf\x30\x14\x17\x00\x47\xe0\x39\x3c\xee\x1f\xd3\xee\x2a\x81\xa6\xec\xc7\x8f\xc1\x3a\xd4\xe4\x6b\x0e\xa4\xd8\x12\xe0\xb6\xef\x21\x13\x83\x27\x16\x03\xfa\xf2\x30\x03\x90\x67\x8a\x28\xe0\x7c\xdf\x5a\x8f\xd2\x90\x54\xc5\x11\xad\xde\x0d\xa7\xb8\xb4\x11\x59\xa3\xf0\xff\x45\xb4\xd8\x18\xb0\x46\x83\xb7\xb9\x5a\x1a\x93\x41\xf7\xef\x58\xf6\x17\x9b\xea\x43\x44\xba\x51\x7b\x28\x8e\xf2\xf0\x22\xa2\x92\x0b\xc5\x18\x71\xc2\xc9\x7d\x81\x08\x26\xd0\x06\xa8\xba\xc1\xb1\x06\xab\x5e\xaa\x91\x32\x66\x7a\xc6\x2e\xf6\x28\x38\xbf\x8c\x43\xfd\x0f\xdc\x2c\x91\x73\x88\x92\xe3\x11\xc5\xac\x2f\xb8\x6a\xee\xad\xa8\xe9\xee\xcd\x2f\xa8\x5f\xe5\xa4\xc7\xd3\xf6\xdd\x78\xc5\xcc\xa9\x1c\xc3\x08\x01\x50\xe5\x9f\xb1\xd1\x05\x02\x81\x5a\x27\xc7\x38\xfe\x0a\xe4\xc3\xfc\xb7\x8e\xb3\xf4\x07\xbb\xff\x5e\x9a\xf9\xbd\x10\xe3\x18\x63\xf5\xfd\xbe\x27\x4b\x7f\x9f\x0f\x4f\xf0\x0c\xa8\xc8\x98\x6c\xd5\xad\xcb\xaa\x98\xea\x4d\x8b\x33\x0e\xf8\x61\xde\x3e\x84\xef\x93\x8e\xb9\x3c\x32\xba\x9b\x09\x4b\x3d\x87\xa8\xa1\xf4\x83\x82\xa3\xaf\x09\x4d\x64\x10\x6e\x05\x92\x93\x64\xc0\xc9\xdd\xd4\xe0\xea\x93\xc8\x19\xc7\x5a\xbd\x07\x84\x70\xd1\x4c\xec\x72\x0d\xb6\x54\xb5\x76\xe4\xfe\xbe\x10\xe2\x04\xdc\x02\xdf\xaa\x8e\x9b\x30\x3f\x29"))
 
+     // Alias mode
+     (CASE_S(QType::SVCB, "0 foo.powerdns.org.", "\0\0\3foo\x08powerdns\x03org\x00"))
+     (CASE_L(QType::SVCB, "0 foo.powerdns.org", "0 foo.powerdns.org.", "\0\0\3foo\x08powerdns\x03org\x00"))
+
+     // Service mode
+     (CASE_S(QType::SVCB, "1 .", "\x00\x01\x00"))
+     (CASE_S(QType::SVCB, "1 foo.powerdns.org. mandatory=alpn", "\0\x01\3foo\x08powerdns\x03org\x00\x00\x00\x00\x02\x00\x01"))
+     (CASE_S(QType::SVCB, "1 foo.powerdns.org. no-default-alpn", "\0\x01\3foo\x08powerdns\x03org\x00\x00\x02\x00\x00"))
+     (CASE_S(QType::SVCB, "1 foo.powerdns.org. alpn=h3,h2", "\0\x01\3foo\x08powerdns\x03org\x00\x00\x01\x00\x06\x02h3\x02h2"))
+     (CASE_S(QType::SVCB, "1 foo.powerdns.org. port=53", "\0\x01\3foo\x08powerdns\x03org\x00\x00\x03\x00\x02\x00\x35"))
+     (CASE_S(QType::SVCB, "1 foo.powerdns.org. ipv4hint=192.0.2.53,192.0.2.2", "\0\x01\3foo\x08powerdns\x03org\x00\x00\x04\x00\x08\xc0\x00\x02\x35\xc0\x00\x02\x02"))
+     (CASE_S(QType::SVCB, "1 foo.powerdns.org. echconfig=\"aGVsbG8=\"", "\0\x01\3foo\x08powerdns\x03org\x00\x00\x05\x00\x05hello"))
+     (CASE_L(QType::SVCB, "1 foo.powerdns.org. echconfig=aGVsbG8=", "1 foo.powerdns.org. echconfig=\"aGVsbG8=\"", "\0\x01\3foo\x08powerdns\x03org\x00\x00\x05\x00\x05hello"))
+     (CASE_S(QType::SVCB, "1 foo.powerdns.org. ipv6hint=2001:db8::1,2001:db8::53:1", "\0\x01\3foo\x08powerdns\x03org\x00\x00\x06\x00\x20\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x53\x00\x01"))
+
+     (CASE_S(QType::SVCB, "16 foo.powerdns.org. mandatory=alpn alpn=h2,h3 ipv4hint=192.0.2.1", "\0\x10\3foo\x08powerdns\x03org\x00\x00\x00\x00\x02\x00\x01\x00\x01\x00\x06\x02h2\x02h3\x00\x04\x00\x04\xc0\x00\x02\x01"))
+     (CASE_L(QType::SVCB, "16 foo.powerdns.org. alpn=h2,h3 mandatory=alpn ipv4hint=192.0.2.1", "16 foo.powerdns.org. mandatory=alpn alpn=h2,h3 ipv4hint=192.0.2.1", "\0\x10\3foo\x08powerdns\x03org\x00\x00\x00\x00\x02\x00\x01\x00\x01\x00\x06\x02h2\x02h3\x00\x04\x00\x04\xc0\x00\x02\x01"))
+
      (CASE_S(QType::SPF, "\"v=spf1 a:mail.rec.test ~all\"", "\x1bv=spf1 a:mail.rec.test ~all"))
      (CASE_S(QType::EUI48, "00-11-22-33-44-55", "\x00\x11\x22\x33\x44\x55"))
      (CASE_S(QType::EUI64, "00-11-22-33-44-55-66-77", "\x00\x11\x22\x33\x44\x55\x66\x77"))
index 9512299610b25196e4bf44482736fb0498b4f67c..4a73e66b993c2a6d86c236d28af0cadcb268ffe1 100644 (file)
@@ -78,4 +78,224 @@ BOOST_AUTO_TEST_CASE(test_compressionBoundary) {
   BOOST_CHECK_NO_THROW(MOADNSParser mdp(false, spacket));
 }
 
+BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_mandatory) {
+  DNSName name("powerdns.com.");
+  vector<uint8_t> packet;
+  DNSPacketWriter pwR(packet, name, QType::SVCB, QClass::IN, 0);
+  pwR.getHeader()->qr = 1;
+
+  set<string> keys({"alpn", "ipv6hint"});
+  set<SvcParam> params({SvcParam(SvcParam::mandatory, keys)});
+
+  pwR.startRecord(name, QType::SVCB);
+  pwR.commit();
+  auto start = pwR.getContent().size();
+
+  pwR.xfrSvcParamKeyVals(params);
+  pwR.commit();
+  auto cit = pwR.getContent().begin();
+  for (size_t i = 0; i<start; i++)
+    cit++;
+
+  vector<uint8_t> c(cit, pwR.getContent().end());
+  BOOST_CHECK(c == vector<uint8_t>({0,0,0,4,0,1,0,6}));
+}
+
+BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_alpn) {
+  DNSName name("powerdns.com.");
+  vector<uint8_t> packet;
+  DNSPacketWriter pwR(packet, name, QType::SVCB, QClass::IN, 0);
+  pwR.getHeader()->qr = 1;
+
+  vector<string> alpns({"h2", "h2c", "h3"});
+  set<SvcParam> params({SvcParam(SvcParam::alpn, alpns)});
+
+  pwR.startRecord(name, QType::SVCB);
+  pwR.commit();
+  auto start = pwR.getContent().size();
+
+  pwR.xfrSvcParamKeyVals(params);
+  pwR.commit();
+  auto cit = pwR.getContent().begin();
+  for (size_t i = 0; i<start; i++)
+    cit++;
+
+  vector<uint8_t> c(cit, pwR.getContent().end());
+  BOOST_CHECK(c == vector<uint8_t>({
+    0,1,0,10,
+    2,'h','2',
+    3,'h','2','c',
+    2,'h','3'}));
+}
+
+BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_no_default_alpn) {
+  DNSName name("powerdns.com.");
+  vector<uint8_t> packet;
+  DNSPacketWriter pwR(packet, name, QType::SVCB, QClass::IN, 0);
+  pwR.getHeader()->qr = 1;
+
+  set<SvcParam> params({SvcParam(SvcParam::no_default_alpn)});
+
+  pwR.startRecord(name, QType::SVCB);
+  pwR.commit();
+  auto start = pwR.getContent().size();
+
+  pwR.xfrSvcParamKeyVals(params);
+  pwR.commit();
+  auto cit = pwR.getContent().begin();
+  for (size_t i = 0; i<start; i++)
+    cit++;
+
+  vector<uint8_t> c(cit, pwR.getContent().end());
+  BOOST_CHECK(c == vector<uint8_t>({0,2,0,0}));
+}
+
+BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_port) {
+  DNSName name("powerdns.com.");
+  vector<uint8_t> packet;
+  DNSPacketWriter pwR(packet, name, QType::SVCB, QClass::IN, 0);
+  pwR.getHeader()->qr = 1;
+
+  set<SvcParam> params({SvcParam(SvcParam::port, 53)});
+
+  pwR.startRecord(name, QType::SVCB);
+  pwR.commit();
+  auto start = pwR.getContent().size();
+
+  pwR.xfrSvcParamKeyVals(params);
+  pwR.commit();
+  auto cit = pwR.getContent().begin();
+  for (size_t i = 0; i<start; i++)
+    cit++;
+
+  vector<uint8_t> c(cit, pwR.getContent().end());
+  BOOST_CHECK(c == vector<uint8_t>({0,3,0,2,0,53}));
+}
+
+BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_ipv4hint) {
+  DNSName name("powerdns.com.");
+  vector<uint8_t> packet;
+  DNSPacketWriter pwR(packet, name, QType::SVCB, QClass::IN, 0);
+  pwR.getHeader()->qr = 1;
+
+  vector<ComboAddress> addrs({ComboAddress("192.0.2.1"), ComboAddress("192.0.2.2")});
+  set<SvcParam> params({SvcParam(SvcParam::ipv4hint, addrs)});
+
+  pwR.startRecord(name, QType::SVCB);
+  pwR.commit();
+  auto start = pwR.getContent().size();
+
+  pwR.xfrSvcParamKeyVals(params);
+  pwR.commit();
+  auto cit = pwR.getContent().begin();
+  for (size_t i = 0; i<start; i++)
+    cit++;
+
+  vector<uint8_t> c(cit, pwR.getContent().end());
+  BOOST_CHECK(c == vector<uint8_t>({0,4,0,8,192,0,2,1,192,0,2,2}));
+}
+
+BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_echconfig) {
+  DNSName name("powerdns.com.");
+  vector<uint8_t> packet;
+  DNSPacketWriter pwR(packet, name, QType::SVCB, QClass::IN, 0);
+  pwR.getHeader()->qr = 1;
+
+  set<SvcParam> params({SvcParam(SvcParam::echconfig, "a very bogus echconfig value")});
+
+  pwR.startRecord(name, QType::SVCB);
+  pwR.commit();
+  auto start = pwR.getContent().size();
+
+  pwR.xfrSvcParamKeyVals(params);
+  pwR.commit();
+  auto cit = pwR.getContent().begin();
+  for (size_t i = 0; i<start; i++)
+    cit++;
+
+  vector<uint8_t> c(cit, pwR.getContent().end());
+  BOOST_CHECK(c == vector<uint8_t>({0,5,0,28,
+  'a',' ','v','e','r','y',' ','b','o','g','u','s',' ',
+  'e','c','h','c','o','n','f','i','g',' ','v','a','l','u','e'
+  }));
+}
+
+BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_ipv6hint) {
+  DNSName name("powerdns.com.");
+  vector<uint8_t> packet;
+  DNSPacketWriter pwR(packet, name, QType::SVCB, QClass::IN, 0);
+  pwR.getHeader()->qr = 1;
+
+  vector<ComboAddress> addrs({ComboAddress("2001:db8::1"), ComboAddress("2001:db8::2")});
+  set<SvcParam> params({SvcParam(SvcParam::ipv6hint, addrs)});
+
+  pwR.startRecord(name, QType::SVCB);
+  pwR.commit();
+  auto start = pwR.getContent().size();
+
+  pwR.xfrSvcParamKeyVals(params);
+  pwR.commit();
+  auto cit = pwR.getContent().begin();
+  for (size_t i = 0; i<start; i++)
+    cit++;
+
+  vector<uint8_t> c(cit, pwR.getContent().end());
+  BOOST_CHECK(c == vector<uint8_t>({0,6,0,32,
+  32,1,13,184,0,0,0,0,0,0,0,0,0,0,0,1,
+  32,1,13,184,0,0,0,0,0,0,0,0,0,0,0,2}));
+}
+
+BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_generic) {
+  DNSName name("powerdns.com.");
+  vector<uint8_t> packet;
+  DNSPacketWriter pwR(packet, name, QType::SVCB, QClass::IN, 0);
+  pwR.getHeader()->qr = 1;
+
+  set<SvcParam> params({SvcParam(SvcParam::keyFromString("key666"), "mycoolvalue")});
+
+  pwR.startRecord(name, QType::SVCB);
+  pwR.commit();
+  auto start = pwR.getContent().size();
+
+  pwR.xfrSvcParamKeyVals(params);
+  pwR.commit();
+  auto cit = pwR.getContent().begin();
+  for (size_t i = 0; i<start; i++)
+    cit++;
+
+  vector<uint8_t> c(cit, pwR.getContent().end());
+  BOOST_CHECK(c == vector<uint8_t>({2,154,0,11,
+  'm','y','c','o','o','l','v','a','l','u','e'
+  }));
+}
+
+BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_multiple) {
+  DNSName name("powerdns.com.");
+  vector<uint8_t> packet;
+  DNSPacketWriter pwR(packet, name, QType::SVCB, QClass::IN, 0);
+  pwR.getHeader()->qr = 1;
+
+  vector<ComboAddress> addrs({ComboAddress("2001:db8::1"), ComboAddress("2001:db8::2")});
+  vector<string> alpns({"h2", "h2c", "h3"});
+  set<SvcParam> params({SvcParam(SvcParam::alpn, alpns), SvcParam(SvcParam::ipv6hint, addrs), SvcParam(SvcParam::port, 53)});
+
+  pwR.startRecord(name, QType::SVCB);
+  pwR.commit();
+  auto start = pwR.getContent().size();
+
+  pwR.xfrSvcParamKeyVals(params);
+  pwR.commit();
+  auto cit = pwR.getContent().begin();
+  for (size_t i = 0; i<start; i++)
+    cit++;
+
+  vector<uint8_t> c(cit, pwR.getContent().end());
+  BOOST_CHECK(c == vector<uint8_t>({
+  0,1,0,10,2,'h','2',3,'h','2','c',2,'h','3',  // alpn
+  0,3,0,2,0,53,                                // port    
+  0,6,0,32,                                    // ipv6
+  32,1,13,184,0,0,0,0,0,0,0,0,0,0,0,1,
+  32,1,13,184,0,0,0,0,0,0,0,0,0,0,0,2}));
+}
+
 BOOST_AUTO_TEST_SUITE_END()
index a5303cd74580aca73f5cc5d4b1dd28ded1bb1c4a..9d587333b8e2e8ecaadb7c3e43c12d6484974a02 100644 (file)
@@ -30,5 +30,327 @@ BOOST_AUTO_TEST_CASE(test_xfrIP6) {
         
 }
 
-BOOST_AUTO_TEST_SUITE_END()
+BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_alpn) {
+        string source("alpn=h2");
+        RecordTextReader rtr(source);
+        set<SvcParam> v;
+        rtr.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(v.size(), 1);
+        auto alpn = v.begin()->getALPN();
+        BOOST_CHECK_EQUAL(alpn.size(), 1);
+        auto val = alpn.begin();
+        BOOST_CHECK_EQUAL(*val, "h2");
 
+        // Check the writer
+        string target;
+        RecordTextWriter rtw(target);
+        rtw.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(target, source);
+
+        v.clear();
+        source = "alpn=h2,h3";
+        RecordTextReader rtr2(source);
+        rtr2.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(v.size(), 1);
+        alpn = v.begin()->getALPN();
+        BOOST_CHECK_EQUAL(alpn.size(), 2);
+        val = alpn.begin();
+        BOOST_CHECK_EQUAL(*val, "h2");
+        val++;
+        BOOST_CHECK_EQUAL(*val, "h3");
+
+        // Check the writer
+        target.clear();
+        RecordTextWriter rtw2(target);
+        rtw2.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(target, source);
+}
+
+BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_mandatory) {
+        string source("mandatory=alpn");
+        RecordTextReader rtr(source);
+        set<SvcParam> v;
+        rtr.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(v.size(), 1);
+        auto m = v.begin()->getMandatory();
+        BOOST_CHECK_EQUAL(m.size(), 1);
+        auto val = m.begin();
+        BOOST_CHECK(*val == SvcParam::alpn);
+
+        // Check the writer
+        string target;
+        RecordTextWriter rtw(target);
+        rtw.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(target, source);
+
+        v.clear();
+        source = "mandatory=alpn,ipv4hint";
+        RecordTextReader rtr2("mandatory=alpn,ipv4hint");
+        rtr2.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(v.size(), 1);
+        m = v.begin()->getMandatory();
+        BOOST_CHECK_EQUAL(m.size(), 2);
+        val = m.begin();
+        BOOST_CHECK(*val == SvcParam::alpn);
+        val++;
+        BOOST_CHECK(*val ==  SvcParam::ipv4hint);
+
+        // Check the writer
+        target.clear();
+        RecordTextWriter rtw2(target);
+        rtw2.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(target, source);
+}
+
+BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_no_default_alpn) {
+        string source("no-default-alpn");
+        RecordTextReader rtr(source);
+        set<SvcParam> v;
+        rtr.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(v.size(), 1);
+        auto k = v.begin()->getKey();
+        BOOST_CHECK(k == SvcParam::no_default_alpn);
+
+        // Check the writer
+        string target;
+        RecordTextWriter rtw(target);
+        rtw.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(target, source);
+
+        RecordTextReader rtr2("no-default-alpn=");
+        v.clear();
+        BOOST_CHECK_THROW(rtr2.xfrSvcParamKeyVals(v), RecordTextException);
+}
+
+BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_ipv4hint) {
+        string source("ipv4hint=192.0.2.1");
+        RecordTextReader rtr(source);
+        set<SvcParam> v;
+        rtr.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(v.size(), 1);
+        auto k = v.begin()->getKey();
+        BOOST_CHECK(k == SvcParam::ipv4hint);
+        auto val = v.begin()->getIPHints();
+        BOOST_CHECK_EQUAL(val.size(), 1);
+        BOOST_CHECK_EQUAL(val.begin()->toString(), "192.0.2.1");
+
+        // Check the writer
+        string target;
+        RecordTextWriter rtw(target);
+        rtw.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(target, source);
+
+        v.clear();
+        source = "ipv4hint=192.0.2.1,192.0.2.2,192.0.2.3";
+        RecordTextReader rtr2(source);
+        rtr2.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(v.size(), 1);
+        k = v.begin()->getKey();
+        BOOST_CHECK(k == SvcParam::ipv4hint);
+
+        val = v.begin()->getIPHints();
+        BOOST_CHECK_EQUAL(val.size(), 3);
+        auto valit = val.begin();
+        BOOST_CHECK_EQUAL(valit->toString(), "192.0.2.1");
+        valit++;
+        BOOST_CHECK_EQUAL(valit->toString(), "192.0.2.2");
+        valit++;
+        BOOST_CHECK_EQUAL(valit->toString(), "192.0.2.3");
+
+        // Check the writer
+        target.clear();
+        RecordTextWriter rtw2(target);
+        rtw2.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(target, source);
+
+        v.clear();
+        RecordTextReader rtr3("ipv4hint=2001:db8::1");
+        BOOST_CHECK_THROW(rtr3.xfrSvcParamKeyVals(v), RecordTextException);
+
+        v.clear();
+        RecordTextReader rtr4("ipv4hint=192.0.2.1,2001:db8::1");
+        BOOST_CHECK_THROW(rtr4.xfrSvcParamKeyVals(v), RecordTextException);
+}
+
+BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_ipv6hint) {
+        string source("ipv6hint=2001:db8::1");
+        RecordTextReader rtr(source);
+        set<SvcParam> v;
+        rtr.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(v.size(), 1);
+        auto k = v.begin()->getKey();
+        BOOST_CHECK(k == SvcParam::ipv6hint);
+        auto val = v.begin()->getIPHints();
+        BOOST_CHECK_EQUAL(val.size(), 1);
+        BOOST_CHECK_EQUAL(val.begin()->toString(), "2001:db8::1");
+
+        // Check the writer
+        string target;
+        RecordTextWriter rtw(target);
+        rtw.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(target, source);
+
+        v.clear();
+        source = "ipv6hint=2001:db8::1,2001:db8::2,2001:db8::3";
+        RecordTextReader rtr2(source);
+        rtr2.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(v.size(), 1);
+        k = v.begin()->getKey();
+        BOOST_CHECK(k == SvcParam::ipv6hint);
+
+        val = v.begin()->getIPHints();
+        BOOST_CHECK_EQUAL(val.size(), 3);
+        auto valit = val.begin();
+        BOOST_CHECK_EQUAL(valit->toString(), "2001:db8::1");
+        valit++;
+        BOOST_CHECK_EQUAL(valit->toString(), "2001:db8::2");
+        valit++;
+        BOOST_CHECK_EQUAL(valit->toString(), "2001:db8::3");
+
+        // Check the writer
+        target.clear();
+        RecordTextWriter rtw2(target);
+        rtw2.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(target, source);
+
+        v.clear();
+        RecordTextReader rtr3("ipv6hint=192.0.2.1");
+        BOOST_CHECK_THROW(rtr3.xfrSvcParamKeyVals(v), RecordTextException);
+
+        v.clear();
+        RecordTextReader rtr4("ipv6hint=192.0.2.1,2001:db8::1");
+        BOOST_CHECK_THROW(rtr4.xfrSvcParamKeyVals(v), RecordTextException);
+}
+
+BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_port) {
+        string source("port=53");
+        RecordTextReader rtr(source);
+        set<SvcParam> v;
+        rtr.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(v.size(), 1);
+        auto k = v.begin()->getKey();
+        BOOST_CHECK(k == SvcParam::port);
+        auto val = v.begin()->getPort();
+        BOOST_CHECK_EQUAL(val, 53);
+
+        // Check the writer
+        string target;
+        RecordTextWriter rtw(target);
+        rtw.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(target, source);
+
+        v.clear();
+        RecordTextReader rtr2("port=100000");
+        BOOST_CHECK_THROW(rtr2.xfrSvcParamKeyVals(v), RecordTextException);
+
+        v.clear();
+        RecordTextReader rtr3("port=foo");
+        BOOST_CHECK_THROW(rtr3.xfrSvcParamKeyVals(v), RecordTextException);
+
+        v.clear();
+        RecordTextReader rtr4("port=");
+        BOOST_CHECK_THROW(rtr4.xfrSvcParamKeyVals(v), RecordTextException);
+
+        v.clear();
+        RecordTextReader rtr5("port");
+        BOOST_CHECK_THROW(rtr5.xfrSvcParamKeyVals(v), RecordTextException);
+}
+
+BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_generic) {
+        string source("key666=foobar");
+        RecordTextReader rtr(source);
+        set<SvcParam> v;
+        rtr.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(v.size(), 1);
+        auto k = v.begin()->getKey();
+        BOOST_CHECK(k == 666);
+        auto val = v.begin()->getValue();
+        BOOST_CHECK_EQUAL(val, "foobar");
+
+        // Check the writer
+        string target;
+        RecordTextWriter rtw(target);
+        rtw.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(target, source);
+
+        v.clear();
+        RecordTextReader rtr2("key666=");
+        BOOST_CHECK_THROW(rtr2.xfrSvcParamKeyVals(v), RecordTextException);
+
+        v.clear();
+        RecordTextReader rtr3("key666");
+        BOOST_CHECK_THROW(rtr3.xfrSvcParamKeyVals(v), RecordTextException);
+
+        v.clear();
+        source = "key666=\"blablabla\"";
+        RecordTextReader rtr4(source);
+        rtr4.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(v.size(), 1);
+        k = v.begin()->getKey();
+        BOOST_CHECK(k == SvcParam::keyFromString("key666"));
+        val = v.begin()->getValue();
+        BOOST_CHECK_EQUAL(val, "\"blablabla\"");
+
+        // Check the writer
+        target.clear();
+        RecordTextWriter rtw2(target);
+        rtw2.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(target, source);
+}
+
+BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_multiple) {
+        RecordTextReader rtr("key666=foobar echconfig=\"dG90YWxseSBib2d1cyBlY2hjb25maWcgdmFsdWU=\" ipv6hint=2001:db8::1 alpn=h2,h3 mandatory=alpn ipv4hint=192.0.2.1,192.0.2.2"); // out of order, resulting set should be in-order
+        set<SvcParam> v;
+        rtr.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(v.size(), 6);
+        auto vit = v.begin();
+
+        // Check ordering
+        for (size_t i = 0; i < v.size(); i++) {
+                if (i == 0) {
+                        BOOST_CHECK(vit->getKey() == SvcParam::mandatory);
+                }
+                if (i == 1) {
+                        BOOST_CHECK(vit->getKey() == SvcParam::alpn);
+                }
+                if (i == 2) {
+                        BOOST_CHECK(vit->getKey() == SvcParam::ipv4hint);
+                }
+                if (i == 3) {
+                        BOOST_CHECK(vit->getKey() == SvcParam::echconfig);
+                }
+                if (i == 4) {
+                        BOOST_CHECK(vit->getKey() == SvcParam::ipv6hint);
+                }
+                if (i == 5) {
+                        BOOST_CHECK(vit->getKey() == SvcParam::keyFromString("key666"));
+                }
+                vit++;
+        }
+
+        // Check the writer
+        string target;
+        RecordTextWriter rtw(target);
+        rtw.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(target, "mandatory=alpn alpn=h2,h3 ipv4hint=192.0.2.1,192.0.2.2 echconfig=\"dG90YWxseSBib2d1cyBlY2hjb25maWcgdmFsdWU=\" ipv6hint=2001:db8::1 key666=foobar");
+}
+
+BOOST_AUTO_TEST_CASE(test_xfrSvcParamKeyVals_echconfig) {
+        string source("echconfig=\"dG90YWxseSBib2d1cyBlY2hjb25maWcgdmFsdWU=\"");
+        RecordTextReader rtr(source);
+        set<SvcParam> v;
+        rtr.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(v.size(), 1);
+        auto k = v.begin()->getKey();
+        BOOST_CHECK(k == SvcParam::echconfig);
+        auto val = v.begin()->getEchConfig();
+        BOOST_CHECK_EQUAL(val, "totally bogus echconfig value"); // decoded!
+
+        // Check the writer
+        string target;
+        RecordTextWriter rtw(target);
+        rtw.xfrSvcParamKeyVals(v);
+        BOOST_CHECK_EQUAL(source, target);
+}
+
+BOOST_AUTO_TEST_SUITE_END()
\ No newline at end of file
index 1c08e4333adc4a49088bd72809428c436edff310..3e0989ad46bd31aa41aec6628d20765603d01021 100644 (file)
@@ -15,39 +15,39 @@ BOOST_AUTO_TEST_CASE(test_SvcParam_keyFromString) {
     SvcParam::SvcParamKey k;
 
     k = SvcParam::keyFromString("mandatory");
-    BOOST_CHECK_EQUAL(k, 0);
-    BOOST_CHECK_EQUAL(k, SvcParam::mandatory);
+    BOOST_CHECK(k == 0);
+    BOOST_CHECK(k == SvcParam::mandatory);
 
     k = SvcParam::keyFromString("alpn");
-    BOOST_CHECK_EQUAL(k, 1);
-    BOOST_CHECK_EQUAL(k, SvcParam::alpn);
+    BOOST_CHECK(k == 1);
+    BOOST_CHECK(k == SvcParam::alpn);
 
     k = SvcParam::keyFromString("no-default-alpn");
-    BOOST_CHECK_EQUAL(k, 2);
-    BOOST_CHECK_EQUAL(k, SvcParam::no_default_alpn);
+    BOOST_CHECK(k == 2);
+    BOOST_CHECK(k == SvcParam::no_default_alpn);
 
     k = SvcParam::keyFromString("port");
-    BOOST_CHECK_EQUAL(k, 3);
-    BOOST_CHECK_EQUAL(k, SvcParam::port);
+    BOOST_CHECK(k == 3);
+    BOOST_CHECK(k == SvcParam::port);
 
     k = SvcParam::keyFromString("ipv4hint");
-    BOOST_CHECK_EQUAL(k, 4);
-    BOOST_CHECK_EQUAL(k, SvcParam::ipv4hint);
+    BOOST_CHECK(k == 4);
+    BOOST_CHECK(k == SvcParam::ipv4hint);
 
     k = SvcParam::keyFromString("echconfig");
-    BOOST_CHECK_EQUAL(k, 5);
-    BOOST_CHECK_EQUAL(k, SvcParam::echconfig);
+    BOOST_CHECK(k == 5);
+    BOOST_CHECK(k == SvcParam::echconfig);
 
     k = SvcParam::keyFromString("ipv6hint");
-    BOOST_CHECK_EQUAL(k, 6);
-    BOOST_CHECK_EQUAL(k, SvcParam::ipv6hint);
+    BOOST_CHECK(k == 6);
+    BOOST_CHECK(k == SvcParam::ipv6hint);
 
     k = SvcParam::keyFromString("key0");
-    BOOST_CHECK_EQUAL(k, 0);
-    BOOST_CHECK_EQUAL(k, SvcParam::mandatory);
+    BOOST_CHECK(k == 0);
+    BOOST_CHECK(k == SvcParam::mandatory);
 
     k = SvcParam::keyFromString("key666");
-    BOOST_CHECK_EQUAL(k, 666);
+    BOOST_CHECK(k == 666);
 
     BOOST_CHECK_THROW(SvcParam::keyFromString("MANDATORY"), std::invalid_argument);
 }
@@ -65,8 +65,8 @@ BOOST_AUTO_TEST_CASE(test_SvcParam_keyToString) {
 }
 
 BOOST_AUTO_TEST_CASE(test_SvcParam_ctor_no_value) {
-    BOOST_CHECK_NO_THROW(SvcParam(SvcParam::no_default_alpn));
-    BOOST_CHECK_THROW(SvcParam(SvcParam::alpn), std::invalid_argument);
+    BOOST_CHECK_NO_THROW(SvcParam(SvcParam::keyFromString("no-default-alpn")));
+    BOOST_CHECK_THROW(SvcParam(SvcParam::keyFromString("alpn")), std::invalid_argument);
     BOOST_CHECK_THROW(SvcParam(SvcParam::keyFromString("key666")), std::invalid_argument);
 }
 
@@ -109,27 +109,42 @@ BOOST_AUTO_TEST_CASE(test_SvcParam_ctor_value) {
 BOOST_AUTO_TEST_CASE(test_SvcParam_ctor_set_string_value) {
     set<string> val({"foo", "bar", "baz"});
 
+    BOOST_CHECK_THROW(SvcParam(SvcParam::alpn, val), std::invalid_argument);
     BOOST_CHECK_THROW(SvcParam(SvcParam::no_default_alpn, val), std::invalid_argument);
     BOOST_CHECK_THROW(SvcParam(SvcParam::port, val), std::invalid_argument);
     BOOST_CHECK_THROW(SvcParam(SvcParam::ipv4hint, val), std::invalid_argument);
     BOOST_CHECK_THROW(SvcParam(SvcParam::echconfig, val), std::invalid_argument);
     BOOST_CHECK_THROW(SvcParam(SvcParam::ipv6hint, val), std::invalid_argument);
 
+    set<string> mandatoryVal = {"alpn", "key666"};
+    set<SvcParam::SvcParamKey> mandatoryExpected = {SvcParam::alpn, (SvcParam::SvcParamKey)666};
     SvcParam param;
-    BOOST_CHECK_NO_THROW(param = SvcParam(SvcParam::keyFromString("mandatory"), val)); // TODO this will fail once we start checking the contents
+    BOOST_CHECK_NO_THROW(param = SvcParam(SvcParam::keyFromString("mandatory"), mandatoryVal));
 
     auto retval = param.getMandatory();
-    BOOST_CHECK_EQUAL_COLLECTIONS(retval.begin(), retval.end(), val.begin(), val.end());
+    BOOST_CHECK(retval == mandatoryExpected);
     BOOST_CHECK_THROW(param.getALPN(), std::invalid_argument);
     BOOST_CHECK_THROW(param.getEchConfig(), std::invalid_argument);
     BOOST_CHECK_THROW(param.getIPHints(), std::invalid_argument);
     BOOST_CHECK_THROW(param.getPort(), std::invalid_argument);
     BOOST_CHECK_THROW(param.getValue(), std::invalid_argument);
+}
 
-    retval.clear();
+BOOST_AUTO_TEST_CASE(test_SvcParam_ctor_vector_string_value) {
+    auto val = vector<string>({"h3, h2"});
+
+    BOOST_CHECK_THROW(SvcParam(SvcParam::mandatory, val), std::invalid_argument);
+    BOOST_CHECK_THROW(SvcParam(SvcParam::no_default_alpn, val), std::invalid_argument);
+    BOOST_CHECK_THROW(SvcParam(SvcParam::port, val), std::invalid_argument);
+    BOOST_CHECK_THROW(SvcParam(SvcParam::ipv4hint, val), std::invalid_argument);
+    BOOST_CHECK_THROW(SvcParam(SvcParam::echconfig, val), std::invalid_argument);
+    BOOST_CHECK_THROW(SvcParam(SvcParam::ipv6hint, val), std::invalid_argument);
+
+    SvcParam param;
     BOOST_CHECK_NO_THROW(param = SvcParam(SvcParam::keyFromString("alpn"), val));
-    retval = param.getALPN();
-    BOOST_CHECK_EQUAL_COLLECTIONS(retval.begin(), retval.end(), val.begin(), val.end());
+    auto alpns = param.getALPN();
+
+    BOOST_CHECK_EQUAL_COLLECTIONS(alpns.begin(), alpns.end(), val.begin(), val.end());
     BOOST_CHECK_THROW(param.getMandatory(), std::invalid_argument);
     BOOST_CHECK_THROW(param.getEchConfig(), std::invalid_argument);
     BOOST_CHECK_THROW(param.getIPHints(), std::invalid_argument);
@@ -143,9 +158,9 @@ BOOST_AUTO_TEST_CASE(test_SvcParam_ctor_set_comboaddress_value) {
     ComboAddress ca3("2001:db8::1");
     ComboAddress ca4("2001:db8::2");
 
-    set<ComboAddress> mixedVal({ca1, ca3});
-    set<ComboAddress> v4Val({ca1, ca2});
-    set<ComboAddress> v6Val({ca3, ca4});
+    vector<ComboAddress> mixedVal({ca1, ca3});
+    vector<ComboAddress> v4Val({ca1, ca2});
+    vector<ComboAddress> v6Val({ca3, ca4});
 
     BOOST_CHECK_THROW(SvcParam(SvcParam::mandatory, v4Val), std::invalid_argument);
     BOOST_CHECK_THROW(SvcParam(SvcParam::alpn, v4Val), std::invalid_argument);
@@ -200,4 +215,4 @@ BOOST_AUTO_TEST_CASE(test_SvcParam_ctor_uint16_value) {
     BOOST_CHECK_THROW(param.getValue(), std::invalid_argument);
 }
 
-BOOST_AUTO_TEST_SUITE_END()
\ No newline at end of file
+BOOST_AUTO_TEST_SUITE_END()