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
33FileTest::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
37FileTest::~FileTest() {}
38
39// FindDelimiter returns a pointer to the first '=' or ':' in |str| or nullptr
40// if there is none.
41static 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.
53static 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
65static 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
77FileTest::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
205void 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
216const std::string &FileTest::GetType() {
217  OnKeyUsed(type_);
218  return type_;
219}
220
221const std::string &FileTest::GetParameter() {
222  OnKeyUsed(type_);
223  return parameter_;
224}
225
226bool FileTest::HasAttribute(const std::string &key) {
227  OnKeyUsed(key);
228  return attributes_.count(key) > 0;
229}
230
231bool 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
242const std::string &FileTest::GetAttributeOrDie(const std::string &key) {
243  if (!HasAttribute(key)) {
244    abort();
245  }
246  return attributes_[key];
247}
248
249bool FileTest::HasInstruction(const std::string &key) {
250  OnInstructionUsed(key);
251  return instructions_.count(key) > 0;
252}
253
254bool 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
265const std::string &FileTest::CurrentTestToString() const {
266  return current_test_;
267}
268
269static 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
285bool 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
313static 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
324bool 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
338void FileTest::ClearTest() {
339  start_line_ = 0;
340  type_.clear();
341  parameter_.clear();
342  attributes_.clear();
343  unused_attributes_.clear();
344  current_test_ = "";
345}
346
347void FileTest::ClearInstructions() {
348  instructions_.clear();
349  unused_attributes_.clear();
350}
351
352void FileTest::OnKeyUsed(const std::string &key) {
353  unused_attributes_.erase(key);
354}
355
356void FileTest::OnInstructionUsed(const std::string &key) {
357  unused_instructions_.erase(key);
358}
359
360bool FileTest::IsAtNewInstructionBlock() const {
361  return is_at_new_instruction_block_;
362}
363
364void FileTest::InjectInstruction(const std::string &key,
365                                 const std::string &value) {
366  instructions_[key] = value;
367}
368
369class 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
406int 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
415int 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
469void FileTest::SkipCurrent() {
470  ClearTest();
471}
472