Home | History | Annotate | Download | only in test
      1 /* Copyright (c) 2015, Google Inc.
      2  *
      3  * Permission to use, copy, modify, and/or distribute this software for any
      4  * purpose with or without fee is hereby granted, provided that the above
      5  * copyright notice and this permission notice appear in all copies.
      6  *
      7  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
      8  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
      9  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
     10  * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     11  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
     12  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
     13  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
     14 
     15 #include "file_test.h"
     16 
     17 #include <algorithm>
     18 #include <utility>
     19 
     20 #include <assert.h>
     21 #include <ctype.h>
     22 #include <errno.h>
     23 #include <stdarg.h>
     24 #include <stdio.h>
     25 #include <stdlib.h>
     26 #include <string.h>
     27 
     28 #include <openssl/err.h>
     29 
     30 #include "../internal.h"
     31 
     32 
     33 FileTest::FileTest(std::unique_ptr<FileTest::LineReader> reader,
     34                    std::function<void(const std::string &)> comment_callback)
     35     : reader_(std::move(reader)), comment_callback_(comment_callback) {}
     36 
     37 FileTest::~FileTest() {}
     38 
     39 // FindDelimiter returns a pointer to the first '=' or ':' in |str| or nullptr
     40 // if there is none.
     41 static const char *FindDelimiter(const char *str) {
     42   while (*str) {
     43     if (*str == ':' || *str == '=') {
     44       return str;
     45     }
     46     str++;
     47   }
     48   return nullptr;
     49 }
     50 
     51 // StripSpace returns a string containing up to |len| characters from |str| with
     52 // leading and trailing whitespace removed.
     53 static std::string StripSpace(const char *str, size_t len) {
     54   // Remove leading space.
     55   while (len > 0 && isspace(*str)) {
     56     str++;
     57     len--;
     58   }
     59   while (len > 0 && isspace(str[len - 1])) {
     60     len--;
     61   }
     62   return std::string(str, len);
     63 }
     64 
     65 static std::pair<std::string, std::string> ParseKeyValue(const char *str, const size_t len) {
     66   const char *delimiter = FindDelimiter(str);
     67   std::string key, value;
     68   if (delimiter == nullptr) {
     69     key = StripSpace(str, len);
     70   } else {
     71     key = StripSpace(str, delimiter - str);
     72     value = StripSpace(delimiter + 1, str + len - delimiter - 1);
     73   }
     74   return {key, value};
     75 }
     76 
     77 FileTest::ReadResult FileTest::ReadNext() {
     78   // If the previous test had unused attributes or instructions, it is an error.
     79   if (!unused_attributes_.empty()) {
     80     for (const std::string &key : unused_attributes_) {
     81       PrintLine("Unused attribute: %s", key.c_str());
     82     }
     83     return kReadError;
     84   }
     85   if (!unused_instructions_.empty()) {
     86     for (const std::string &key : unused_instructions_) {
     87       PrintLine("Unused instruction: %s", key.c_str());
     88     }
     89     return kReadError;
     90   }
     91 
     92   ClearTest();
     93 
     94   static const size_t kBufLen = 8192 * 4;
     95   std::unique_ptr<char[]> buf(new char[kBufLen]);
     96 
     97   bool in_instruction_block = false;
     98   is_at_new_instruction_block_ = false;
     99 
    100   while (true) {
    101     // Read the next line.
    102     switch (reader_->ReadLine(buf.get(), kBufLen)) {
    103       case kReadError:
    104         fprintf(stderr, "Error reading from input at line %u.\n", line_ + 1);
    105         return kReadError;
    106       case kReadEOF:
    107         // EOF is a valid terminator for a test.
    108         return start_line_ > 0 ? kReadSuccess : kReadEOF;
    109       case kReadSuccess:
    110         break;
    111     }
    112 
    113     line_++;
    114     size_t len = strlen(buf.get());
    115     if (buf[0] == '\n' || buf[0] == '\r' || buf[0] == '\0') {
    116       // Empty lines delimit tests.
    117       if (start_line_ > 0) {
    118         return kReadSuccess;
    119       }
    120       if (in_instruction_block) {
    121         in_instruction_block = false;
    122         // Delimit instruction block from test with a blank line.
    123         current_test_ += "\r\n";
    124       }
    125     } else if (buf[0] == '#') {
    126       if (comment_callback_) {
    127         comment_callback_(buf.get());
    128       }
    129       // Otherwise ignore comments.
    130     } else if (strcmp("[B.4.2 Key Pair Generation by Testing Candidates]\r\n",
    131                       buf.get()) == 0) {
    132       // The above instruction-like line is ignored because the FIPS lab's
    133       // request files are hopelessly inconsistent.
    134     } else if (buf[0] == '[') {  // Inside an instruction block.
    135       is_at_new_instruction_block_ = true;
    136       if (start_line_ != 0) {
    137         // Instructions should be separate blocks.
    138         fprintf(stderr, "Line %u is an instruction in a test case.\n", line_);
    139         return kReadError;
    140       }
    141       if (!in_instruction_block) {
    142         ClearInstructions();
    143         in_instruction_block = true;
    144       }
    145 
    146       // Parse the line as an instruction ("[key = value]" or "[key]").
    147       std::string kv = StripSpace(buf.get(), len);
    148       if (kv[kv.size() - 1] != ']') {
    149         fprintf(stderr, "Line %u, invalid instruction: %s\n", line_,
    150                 kv.c_str());
    151         return kReadError;
    152       }
    153       current_test_ += kv + "\r\n";
    154       kv = std::string(kv.begin() + 1, kv.end() - 1);
    155 
    156       for (;;) {
    157         size_t idx = kv.find(",");
    158         if (idx == std::string::npos) {
    159           idx = kv.size();
    160         }
    161         std::string key, value;
    162         std::tie(key, value) = ParseKeyValue(kv.c_str(), idx);
    163         instructions_[key] = value;
    164         if (idx == kv.size())
    165           break;
    166         kv = kv.substr(idx + 1);
    167       }
    168     } else {
    169       // Parsing a test case.
    170       if (in_instruction_block) {
    171         // Some NIST CAVP test files (TDES) have a test case immediately
    172         // following an instruction block, without a separate blank line, some
    173         // of the time.
    174         in_instruction_block = false;
    175       }
    176 
    177       current_test_ += std::string(buf.get(), len);
    178       std::string key, value;
    179       std::tie(key, value) = ParseKeyValue(buf.get(), len);
    180 
    181       // Duplicate keys are rewritten to have /2, /3,  suffixes.
    182       std::string mapped_key = key;
    183       for (unsigned i = 2; attributes_.count(mapped_key) != 0; i++) {
    184         char suffix[32];
    185         snprintf(suffix, sizeof(suffix), "/%u", i);
    186         suffix[sizeof(suffix)-1] = 0;
    187         mapped_key = key + suffix;
    188       }
    189 
    190       unused_attributes_.insert(mapped_key);
    191       attributes_[mapped_key] = value;
    192       if (start_line_ == 0) {
    193         // This is the start of a test.
    194         type_ = mapped_key;
    195         parameter_ = value;
    196         start_line_ = line_;
    197         for (const auto &kv : instructions_) {
    198           unused_instructions_.insert(kv.first);
    199         }
    200       }
    201     }
    202   }
    203 }
    204 
    205 void FileTest::PrintLine(const char *format, ...) {
    206   va_list args;
    207   va_start(args, format);
    208 
    209   fprintf(stderr, "Line %u: ", start_line_);
    210   vfprintf(stderr, format, args);
    211   fprintf(stderr, "\n");
    212 
    213   va_end(args);
    214 }
    215 
    216 const std::string &FileTest::GetType() {
    217   OnKeyUsed(type_);
    218   return type_;
    219 }
    220 
    221 const std::string &FileTest::GetParameter() {
    222   OnKeyUsed(type_);
    223   return parameter_;
    224 }
    225 
    226 bool FileTest::HasAttribute(const std::string &key) {
    227   OnKeyUsed(key);
    228   return attributes_.count(key) > 0;
    229 }
    230 
    231 bool FileTest::GetAttribute(std::string *out_value, const std::string &key) {
    232   OnKeyUsed(key);
    233   auto iter = attributes_.find(key);
    234   if (iter == attributes_.end()) {
    235     PrintLine("Missing attribute '%s'.", key.c_str());
    236     return false;
    237   }
    238   *out_value = iter->second;
    239   return true;
    240 }
    241 
    242 const std::string &FileTest::GetAttributeOrDie(const std::string &key) {
    243   if (!HasAttribute(key)) {
    244     abort();
    245   }
    246   return attributes_[key];
    247 }
    248 
    249 bool FileTest::HasInstruction(const std::string &key) {
    250   OnInstructionUsed(key);
    251   return instructions_.count(key) > 0;
    252 }
    253 
    254 bool FileTest::GetInstruction(std::string *out_value, const std::string &key) {
    255   OnInstructionUsed(key);
    256   auto iter = instructions_.find(key);
    257   if (iter == instructions_.end()) {
    258     PrintLine("Missing instruction '%s'.", key.c_str());
    259     return false;
    260   }
    261   *out_value = iter->second;
    262   return true;
    263 }
    264 
    265 const std::string &FileTest::CurrentTestToString() const {
    266   return current_test_;
    267 }
    268 
    269 static bool FromHexDigit(uint8_t *out, char c) {
    270   if ('0' <= c && c <= '9') {
    271     *out = c - '0';
    272     return true;
    273   }
    274   if ('a' <= c && c <= 'f') {
    275     *out = c - 'a' + 10;
    276     return true;
    277   }
    278   if ('A' <= c && c <= 'F') {
    279     *out = c - 'A' + 10;
    280     return true;
    281   }
    282   return false;
    283 }
    284 
    285 bool FileTest::GetBytes(std::vector<uint8_t> *out, const std::string &key) {
    286   std::string value;
    287   if (!GetAttribute(&value, key)) {
    288     return false;
    289   }
    290 
    291   if (value.size() >= 2 && value[0] == '"' && value[value.size() - 1] == '"') {
    292     out->assign(value.begin() + 1, value.end() - 1);
    293     return true;
    294   }
    295 
    296   if (value.size() % 2 != 0) {
    297     PrintLine("Error decoding value: %s", value.c_str());
    298     return false;
    299   }
    300   out->clear();
    301   out->reserve(value.size() / 2);
    302   for (size_t i = 0; i < value.size(); i += 2) {
    303     uint8_t hi, lo;
    304     if (!FromHexDigit(&hi, value[i]) || !FromHexDigit(&lo, value[i + 1])) {
    305       PrintLine("Error decoding value: %s", value.c_str());
    306       return false;
    307     }
    308     out->push_back((hi << 4) | lo);
    309   }
    310   return true;
    311 }
    312 
    313 static std::string EncodeHex(const uint8_t *in, size_t in_len) {
    314   static const char kHexDigits[] = "0123456789abcdef";
    315   std::string ret;
    316   ret.reserve(in_len * 2);
    317   for (size_t i = 0; i < in_len; i++) {
    318     ret += kHexDigits[in[i] >> 4];
    319     ret += kHexDigits[in[i] & 0xf];
    320   }
    321   return ret;
    322 }
    323 
    324 bool FileTest::ExpectBytesEqual(const uint8_t *expected, size_t expected_len,
    325                                 const uint8_t *actual, size_t actual_len) {
    326   if (expected_len == actual_len &&
    327       OPENSSL_memcmp(expected, actual, expected_len) == 0) {
    328     return true;
    329   }
    330 
    331   std::string expected_hex = EncodeHex(expected, expected_len);
    332   std::string actual_hex = EncodeHex(actual, actual_len);
    333   PrintLine("Expected: %s", expected_hex.c_str());
    334   PrintLine("Actual:   %s", actual_hex.c_str());
    335   return false;
    336 }
    337 
    338 void FileTest::ClearTest() {
    339   start_line_ = 0;
    340   type_.clear();
    341   parameter_.clear();
    342   attributes_.clear();
    343   unused_attributes_.clear();
    344   current_test_ = "";
    345 }
    346 
    347 void FileTest::ClearInstructions() {
    348   instructions_.clear();
    349   unused_attributes_.clear();
    350 }
    351 
    352 void FileTest::OnKeyUsed(const std::string &key) {
    353   unused_attributes_.erase(key);
    354 }
    355 
    356 void FileTest::OnInstructionUsed(const std::string &key) {
    357   unused_instructions_.erase(key);
    358 }
    359 
    360 bool FileTest::IsAtNewInstructionBlock() const {
    361   return is_at_new_instruction_block_;
    362 }
    363 
    364 void FileTest::InjectInstruction(const std::string &key,
    365                                  const std::string &value) {
    366   instructions_[key] = value;
    367 }
    368 
    369 class FileLineReader : public FileTest::LineReader {
    370  public:
    371   explicit FileLineReader(const char *path) : file_(fopen(path, "r")) {}
    372   ~FileLineReader() override {
    373     if (file_ != nullptr) {
    374       fclose(file_);
    375     }
    376   }
    377 
    378   // is_open returns true if the file was successfully opened.
    379   bool is_open() const { return file_ != nullptr; }
    380 
    381   FileTest::ReadResult ReadLine(char *out, size_t len) override {
    382     assert(len > 0);
    383     if (file_ == nullptr) {
    384       return FileTest::kReadError;
    385     }
    386 
    387     if (fgets(out, len, file_) == nullptr) {
    388       return feof(file_) ? FileTest::kReadEOF : FileTest::kReadError;
    389     }
    390 
    391     if (strlen(out) == len - 1 && out[len - 2] != '\n' && !feof(file_)) {
    392       fprintf(stderr, "Line too long.\n");
    393       return FileTest::kReadError;
    394     }
    395 
    396     return FileTest::kReadSuccess;
    397   }
    398 
    399  private:
    400   FILE *file_;
    401 
    402   FileLineReader(const FileLineReader &) = delete;
    403   FileLineReader &operator=(const FileLineReader &) = delete;
    404 };
    405 
    406 int FileTestMain(FileTestFunc run_test, void *arg, const char *path) {
    407   FileTest::Options opts;
    408   opts.callback = run_test;
    409   opts.arg = arg;
    410   opts.path = path;
    411 
    412   return FileTestMain(opts);
    413 }
    414 
    415 int FileTestMain(const FileTest::Options &opts) {
    416   std::unique_ptr<FileLineReader> reader(
    417       new FileLineReader(opts.path));
    418   if (!reader->is_open()) {
    419     fprintf(stderr, "Could not open file %s: %s.\n", opts.path,
    420             strerror(errno));
    421     return 1;
    422   }
    423 
    424   FileTest t(std::move(reader), opts.comment_callback);
    425 
    426   bool failed = false;
    427   while (true) {
    428     FileTest::ReadResult ret = t.ReadNext();
    429     if (ret == FileTest::kReadError) {
    430       return 1;
    431     } else if (ret == FileTest::kReadEOF) {
    432       break;
    433     }
    434 
    435     bool result = opts.callback(&t, opts.arg);
    436     if (t.HasAttribute("Error")) {
    437       if (result) {
    438         t.PrintLine("Operation unexpectedly succeeded.");
    439         failed = true;
    440         continue;
    441       }
    442       uint32_t err = ERR_peek_error();
    443       if (ERR_reason_error_string(err) != t.GetAttributeOrDie("Error")) {
    444         t.PrintLine("Unexpected error; wanted '%s', got '%s'.",
    445                     t.GetAttributeOrDie("Error").c_str(),
    446                     ERR_reason_error_string(err));
    447         failed = true;
    448         ERR_clear_error();
    449         continue;
    450       }
    451       ERR_clear_error();
    452     } else if (!result) {
    453       // In case the test itself doesn't print output, print something so the
    454       // line number is reported.
    455       t.PrintLine("Test failed");
    456       ERR_print_errors_fp(stderr);
    457       failed = true;
    458       continue;
    459     }
    460   }
    461 
    462   if (!opts.silent && !failed) {
    463     printf("PASS\n");
    464   }
    465 
    466   return failed ? 1 : 0;
    467 }
    468 
    469 void FileTest::SkipCurrent() {
    470   ClearTest();
    471 }
    472