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