Browse Source

[2430] Support $GENERATE format modifiers

Supports everything except nibble mode. Nibble mode will come soon.
Mukund Sivaraman 11 years ago
parent
commit
734c918dd9
2 changed files with 145 additions and 3 deletions
  1. 58 3
      src/lib/dns/master_loader.cc
  2. 87 0
      src/lib/dns/tests/master_loader_unittest.cc

+ 58 - 3
src/lib/dns/master_loader.cc

@@ -452,7 +452,7 @@ MasterLoader::MasterLoaderImpl::generateForIter(const std::string& str,
 
   for (std::string::const_iterator it = str.begin(); it != str.end();) {
       switch (*it) {
-      case '$':
+      case '$': {
           ++it;
           if ((it != str.end()) && (*it == '$')) {
               rstr.push_back('$');
@@ -460,9 +460,56 @@ MasterLoader::MasterLoaderImpl::generateForIter(const std::string& str,
               continue;
           }
 
-          // TODO: This doesn't handle format specifiers in {} yet.
-          rstr += boost::str(boost::format("%d") % i);
+          bool nibble_mode = false;
+          bool nibble_uppercase = false;
+          std::string fmt("%d");
+          int delta = 0;
+
+          if (*it == '{') {
+              const char* scan_str =
+                  str.c_str() + std::distance(str.begin(), it);
+              unsigned int width;
+              char mode[2] = {'d', 0}; // char plus null byte
+              const int n = sscanf(scan_str, "{%d,%u,%1[doxXnN]}",
+                                   &delta, &width, mode);
+              switch (n) {
+              case 1:
+                  break;
+
+              case 2:
+                  fmt = boost::str(boost::format("%%0%ud") % width);
+                  break;
+
+              case 3:
+                  if ((mode[0] == 'n') || (mode[0] == 'N')) {
+                      nibble_mode = true;
+                      nibble_uppercase = (mode[0] == 'N');
+                  }
+                  fmt = boost::str(boost::format("%%0%u%c") % width % mode[0]);
+                  break;
+
+              default:
+                  reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+                              "Invalid $GENERATE format modifiers");
+                  return ("");
+              }
+
+              /* Skip past closing brace. */
+              while ((it != str.end()) && (*it != '}')) {
+                  ++it;
+              }
+              if (it != str.end()) {
+                  ++it;
+              }
+          }
+
+          // TODO: Handle nibble mode
+          assert(!nibble_mode);
+          nibble_uppercase = nibble_uppercase;
+
+          rstr += boost::str(boost::format(fmt) % (i + delta));
           break;
+      }
 
       case '\\':
           rstr.push_back(*it);
@@ -539,6 +586,14 @@ MasterLoader::MasterLoaderImpl::doGenerate() {
     for (int i = start; i <= stop; i += step) {
         const std::string generated_name = generateForIter(lhs, i);
         const std::string generated_rdata = generateForIter(rhs, i);
+        if (generated_name.empty() || generated_rdata.empty()) {
+            // The error should have been sent to the callbacks already
+            // by generateForIter().
+            reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+                        "$GENERATE error");
+            return;
+        }
+
         const size_t name_length = generated_name.size();
         last_name_.reset(new Name(generated_name.c_str(), name_length,
                                   &active_origin_));

+ 87 - 0
src/lib/dns/tests/master_loader_unittest.cc

@@ -502,6 +502,93 @@ TEST_F(MasterLoaderTest, generateWithStep) {
     checkRR("host31.example.org", RRType::A(), "192.0.2.31");
 }
 
+TEST_F(MasterLoaderTest, generateWithModifiers) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$TTL 3600\n"
+        "$GENERATE 2-9/2 host${1} A 192.0.2.${-1}\n"
+        "$GENERATE 10-12 host${0,4} A 192.0.2.$\n"
+        "$GENERATE 14-15 host${0,4,d} A 192.0.2.$\n"
+        "$GENERATE 30-31 host${0,4,x} A 192.0.2.$\n"
+        // Names are case-insensitive, so we use TXT's RDATA to check
+        // case.
+        "$GENERATE 42-43 host$ TXT \"Value ${0,4,X}\"\n"
+        "$GENERATE 45-46 host${0,4,o} A 192.0.2.$\n"
+        // Junk type will not parse and 'd' is assumed.
+        "$GENERATE 100-101 host${0,4,j} A 192.0.2.$\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_TRUE(loader_->loadedSucessfully());
+    EXPECT_TRUE(errors_.empty());
+
+    checkRR("host3.example.org", RRType::A(), "192.0.2.1");
+    checkRR("host5.example.org", RRType::A(), "192.0.2.3");
+    checkRR("host7.example.org", RRType::A(), "192.0.2.5");
+    checkRR("host9.example.org", RRType::A(), "192.0.2.7");
+
+    checkRR("host0010.example.org", RRType::A(), "192.0.2.10");
+    checkRR("host0011.example.org", RRType::A(), "192.0.2.11");
+    checkRR("host0012.example.org", RRType::A(), "192.0.2.12");
+
+    checkRR("host0014.example.org", RRType::A(), "192.0.2.14");
+    checkRR("host0015.example.org", RRType::A(), "192.0.2.15");
+
+    checkRR("host001e.example.org", RRType::A(), "192.0.2.30");
+    checkRR("host001f.example.org", RRType::A(), "192.0.2.31");
+
+    checkRR("host42.example.org", RRType::TXT(), "Value 002A");
+    checkRR("host43.example.org", RRType::TXT(), "Value 002B");
+
+    checkRR("host0055.example.org", RRType::A(), "192.0.2.45");
+    checkRR("host0056.example.org", RRType::A(), "192.0.2.46");
+
+    checkRR("host0100.example.org", RRType::A(), "192.0.2.100");
+    checkRR("host0101.example.org", RRType::A(), "192.0.2.101");
+}
+
+TEST_F(MasterLoaderTest, generateWithNoModifiers) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$TTL 3600\n"
+        "$GENERATE 10-12 host${} A 192.0.2.$\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_FALSE(loader_->loadedSucessfully());
+    ASSERT_EQ(2, errors_.size()); // For the broken GENERATE
+    EXPECT_TRUE(warnings_.empty());
+
+    checkCallbackMessage(errors_.at(0),
+                         "Invalid $GENERATE format modifiers", 3);
+    checkCallbackMessage(errors_.at(1),
+                         "$GENERATE error", 3);
+}
+
+TEST_F(MasterLoaderTest, generateWithBadModifiers) {
+    const string input =
+        "$ORIGIN example.org.\n"
+        "$TTL 3600\n"
+        "$GENERATE 10-12 host${GARBAGE} A 192.0.2.$\n";
+    stringstream ss(input);
+    setLoader(ss, Name("example.org."), RRClass::IN(),
+              MasterLoader::MANY_ERRORS);
+
+    loader_->load();
+    EXPECT_FALSE(loader_->loadedSucessfully());
+    ASSERT_EQ(2, errors_.size()); // For the broken GENERATE
+    EXPECT_TRUE(warnings_.empty());
+
+    checkCallbackMessage(errors_.at(0),
+                         "Invalid $GENERATE format modifiers", 3);
+    checkCallbackMessage(errors_.at(1),
+                         "$GENERATE error", 3);
+}
+
 TEST_F(MasterLoaderTest, generateMissingRange) {
     const string input =
         "$ORIGIN example.org.\n"