1
0
mirror of https://github.com/taigrr/arduinolibs synced 2025-01-18 04:33:12 -08:00
arduinolibs/host/Crypto/noise/test-vector.cpp
2018-06-18 05:24:27 +10:00

793 lines
29 KiB
C++

/*
* Copyright (C) 2016,2018 Southern Storm Software, Pty Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include <NoiseProtocol.h>
#include "json-reader.h"
#include <setjmp.h>
#include <stdlib.h>
#include <unistd.h>
#define MAX_MESSAGES 32
#define MAX_MESSAGE_SIZE 4096
#define MAX_PSKS 8
/**
* \brief Information about a single test vector.
*/
typedef struct
{
long line_number; /**< Line number for the "name" */
char *name; /**< Full name of the test case */
char *protocol_name; /**< Full name of the protocol */
uint8_t *init_static; /**< Initiator's static private key */
size_t init_static_len; /**< Length of init_static in bytes */
uint8_t *init_public_static; /**< Initiator's public key known to responder */
size_t init_public_static_len; /**< Length of init_public_static in bytes */
uint8_t *resp_static; /**< Responder's static private key */
size_t resp_static_len; /**< Length of resp_static in bytes */
uint8_t *resp_public_static; /**< Responder's public key known to initiator */
size_t resp_public_static_len; /**< Length of resp_public_static in bytes */
uint8_t *init_ephemeral; /**< Initiator's ephemeral key */
size_t init_ephemeral_len; /**< Length of init_ephemeral in bytes */
uint8_t *resp_ephemeral; /**< Responder's ephemeral key */
size_t resp_ephemeral_len; /**< Length of resp_ephemeral in bytes */
uint8_t *init_hybrid; /**< Initiator's hybrid ephemeral key */
size_t init_hybrid_len; /**< Length of init_hybrid in bytes */
uint8_t *resp_hybrid; /**< Responder's hybrid ephemeral key */
size_t resp_hybrid_len; /**< Length of resp_hybrid in bytes */
uint8_t *init_prologue; /**< Initiator's prologue data */
size_t init_prologue_len; /**< Length of init_prologue in bytes */
uint8_t *resp_prologue; /**< Responder's prologue data */
size_t resp_prologue_len; /**< Length of resp_prologue in bytes */
uint8_t init_psks[MAX_PSKS][32];/**< Initiator pre-shared keys */
size_t num_init_psks; /**< Number of initiator PSK's */
uint8_t resp_psks[MAX_PSKS][32];/**< Responder pre-shared keys */
size_t num_resp_psks; /**< Number of responder PSK's */
uint8_t *handshake_hash; /**< Hash at the end of the handshake */
size_t handshake_hash_len; /**< Length of handshake_hash in bytes */
int fail; /**< Failure expected on last message */
int fallback; /**< Handshake involves IK to XXfallback */
char *fallback_pattern; /**< Name of the pattern to fall back to */
int is_one_way; /**< True if the base pattern is one-way */
struct {
uint8_t *payload; /**< Payload for this message */
size_t payload_len; /**< Length of payload in bytes */
uint8_t *ciphertext; /**< Ciphertext for this message */
size_t ciphertext_len; /**< Length of ciphertext in bytes */
} messages[MAX_MESSAGES]; /**< All test messages */
size_t num_messages; /**< Number of test messages */
} TestVector;
/**
* \brief Frees the memory for a test vector.
*
* \param vec The test vector.
*/
static void test_vector_free(TestVector *vec)
{
size_t index;
#define free_field(name) do { if (vec->name) free(vec->name); } while (0)
free_field(name);
free_field(protocol_name);
free_field(init_static);
free_field(init_public_static);
free_field(resp_static);
free_field(resp_public_static);
free_field(init_ephemeral);
free_field(resp_ephemeral);
free_field(init_hybrid);
free_field(resp_hybrid);
free_field(init_prologue);
free_field(resp_prologue);
free_field(handshake_hash);
free_field(fallback_pattern);
for (index = 0; index < vec->num_messages; ++index) {
if (vec->messages[index].payload)
free(vec->messages[index].payload);
if (vec->messages[index].ciphertext)
free(vec->messages[index].ciphertext);
}
memset(vec, 0, sizeof(TestVector));
}
static jmp_buf test_jump_back;
/**
* \brief Immediate fail of the test.
*
* \param message The failure message to print.
*/
#define _fail(message) \
do { \
printf("%s, failed at " __FILE__ ":%d\n", (message), __LINE__); \
longjmp(test_jump_back, 1); \
} while (0)
#define fail(message) _fail((message))
/**
* \brief Skips the current test.
*/
#define skip() longjmp(test_jump_back, 2)
/**
* \brief Verifies that a condition is true, failing the test if not.
*
* \param condition The boolean condition to test.
*/
#define _verify(condition) \
do { \
if (!(condition)) { \
printf(#condition " failed at " __FILE__ ":%d\n", __LINE__); \
longjmp(test_jump_back, 1); \
} \
} while (0)
#define verify(condition) _verify((condition))
/**
* \brief Compares two integer values for equality, failing the test if not.
*
* \param actual The actual value that was computed by the code under test.
* \param expected The value that is expected.
*/
#define compare(actual, expected) \
do { \
long long _actual = (long long)(actual); \
long long _expected = (long long)(expected); \
if (_actual != _expected) { \
printf(#actual " != " #expected " at " __FILE__ ":%d\n", __LINE__); \
printf(" actual : %lld (0x%llx)\n", _actual, _actual); \
printf(" expected: %lld (0x%llx)\n", _expected, _expected); \
longjmp(test_jump_back, 1); \
} \
} while (0)
static void dump_block(uint8_t *block, size_t len)
{
size_t index;
if (len > 16)
printf("\n ");
for (index = 0; index < len; ++index) {
printf(" %02x", block[index]);
if ((index % 16) == 15 && len > 16)
printf("\n ");
}
printf("\n");
}
#define compare_blocks(name, actual, actual_len, expected, expected_len) \
do { \
if ((actual_len) != (expected_len) || \
memcmp((actual), (expected), (actual_len)) != 0) { \
printf("%s wrong at " __FILE__ ":%d\n", (name), __LINE__); \
printf(" actual :"); \
dump_block((actual), (actual_len)); \
printf(" expected:"); \
dump_block((expected), (expected_len)); \
longjmp(test_jump_back, 1); \
} \
} while (0)
/**
* \brief Creates a handshake object for a specific protocol.
*
* \pararm protocol The name of the protocol.
*/
static NoiseHandshakeState *create_handshake(const char *protocol)
{
if (!strcmp(protocol, "Noise_IK_25519_AESGCM_SHA256"))
return new NoiseHandshakeState_IK_25519_AESGCM_SHA256();
if (!strcmp(protocol, "Noise_IK_25519_ChaChaPoly_BLAKE2s"))
return new NoiseHandshakeState_IK_25519_ChaChaPoly_BLAKE2s();
if (!strcmp(protocol, "Noise_IK_25519_ChaChaPoly_SHA256"))
return new NoiseHandshakeState_IK_25519_ChaChaPoly_SHA256();
if (!strcmp(protocol, "Noise_NNpsk0_25519_AESGCM_SHA256"))
return new NoiseHandshakeState_NNpsk0_25519_AESGCM_SHA256();
if (!strcmp(protocol, "Noise_NNpsk0_25519_ChaChaPoly_BLAKE2s"))
return new NoiseHandshakeState_NNpsk0_25519_ChaChaPoly_BLAKE2s();
if (!strcmp(protocol, "Noise_NNpsk0_25519_ChaChaPoly_SHA256"))
return new NoiseHandshakeState_NNpsk0_25519_ChaChaPoly_SHA256();
if (!strcmp(protocol, "Noise_XX_25519_AESGCM_SHA256"))
return new NoiseHandshakeState_XX_25519_AESGCM_SHA256();
if (!strcmp(protocol, "Noise_XX_25519_ChaChaPoly_BLAKE2s"))
return new NoiseHandshakeState_XX_25519_ChaChaPoly_BLAKE2s();
if (!strcmp(protocol, "Noise_XX_25519_ChaChaPoly_SHA256"))
return new NoiseHandshakeState_XX_25519_ChaChaPoly_SHA256();
fail(protocol);
return 0;
}
/**
* \brief Test a connection between an initiator and a responder.
*
* \param vec The test vector.
*/
static void test_connection(const TestVector *vec)
{
NoiseHandshakeState *initiator;
NoiseHandshakeState *responder;
NoiseHandshakeState *send;
NoiseHandshakeState *recv;
NoiseCipherState *c1init;
NoiseCipherState *c2init;
NoiseCipherState *c1resp;
NoiseCipherState *c2resp;
NoiseCipherState *csend;
NoiseCipherState *crecv;
uint8_t message[MAX_MESSAGE_SIZE];
uint8_t payload[MAX_MESSAGE_SIZE];
int result;
size_t index;
size_t mac_len;
Noise::Party role;
/* Create the two ends of the connection */
initiator = create_handshake(vec->protocol_name);
responder = create_handshake(vec->protocol_name);
/* Should be able to start the handshake now on both sides */
initiator->start
(Noise::Initiator, vec->init_prologue, vec->init_prologue_len);
responder->start
(Noise::Responder, vec->resp_prologue, vec->resp_prologue_len);
compare(initiator->state(), Noise::Write);
compare(responder->state(), Noise::Read);
/* Set all keys that we need to use. We do this after start()
because otherwise it will clear the test ephemeral keys we're
about to set on the objects */
if (vec->init_static) {
verify(initiator->setParameter
(Noise::LocalStaticPrivateKey,
vec->init_static, vec->init_static_len));
}
if (vec->init_public_static) {
verify(responder->setParameter
(Noise::RemoteStaticPublicKey,
vec->init_public_static, vec->init_public_static_len));
}
if (vec->resp_static) {
verify(responder->setParameter
(Noise::LocalStaticPrivateKey,
vec->resp_static, vec->resp_static_len));
}
if (vec->resp_public_static) {
verify(initiator->setParameter
(Noise::RemoteStaticPublicKey,
vec->resp_public_static, vec->resp_public_static_len));
}
if (vec->init_ephemeral) {
verify(initiator->setParameter
(Noise::LocalEphemPrivateKey,
vec->init_ephemeral, vec->init_ephemeral_len));
}
if (vec->resp_ephemeral) {
verify(responder->setParameter
(Noise::LocalEphemPrivateKey,
vec->resp_ephemeral, vec->resp_ephemeral_len));
}
if (vec->num_init_psks) {
/* For now we only support one PSK per handshake */
verify(initiator->setParameter
(Noise::PreSharedKey, vec->init_psks[0], 32));
}
if (vec->num_resp_psks) {
verify(responder->setParameter
(Noise::PreSharedKey, vec->resp_psks[0], 32));
}
/* Work through the messages one by one until both sides "split" */
role = Noise::Initiator;
for (index = 0; index < vec->num_messages; ++index) {
if (initiator->state() == Noise::Split &&
responder->state() == Noise::Split) {
break;
}
if (role == Noise::Initiator) {
/* Send on the initiator, receive on the responder */
send = initiator;
recv = responder;
role = Noise::Responder;
} else {
/* Send on the responder, receive on the initiator */
send = responder;
recv = initiator;
role = Noise::Initiator;
}
compare(send->state(), Noise::Write);
compare(recv->state(), Noise::Read);
memset(message, 0xAA, sizeof(message));
result = send->write(message, sizeof(message),
vec->messages[index].payload,
vec->messages[index].payload_len);
verify(result >= 0);
compare_blocks("ciphertext", message, (size_t)result,
vec->messages[index].ciphertext,
vec->messages[index].ciphertext_len);
memset(payload, 0xBB, sizeof(payload));
result = recv->read(payload, sizeof(payload), message, result);
verify(result >= 0);
compare_blocks("plaintext", payload, (size_t)result,
vec->messages[index].payload,
vec->messages[index].payload_len);
}
/* Handshake finished. Check the handshake hash values */
if (vec->handshake_hash_len) {
memset(payload, 0xAA, sizeof(payload));
verify(initiator->getHandshakeHash(payload, vec->handshake_hash_len));
compare_blocks("handshake_hash", payload, vec->handshake_hash_len,
vec->handshake_hash, vec->handshake_hash_len);
memset(payload, 0xAA, sizeof(payload));
verify(responder->getHandshakeHash(payload, vec->handshake_hash_len));
compare_blocks("handshake_hash", payload, vec->handshake_hash_len,
vec->handshake_hash, vec->handshake_hash_len);
}
/* Now handle the data transport */
verify(initiator->split(&c1init, &c2init));
verify(responder->split(&c2resp, &c1resp));
mac_len = 16;
for (; index < vec->num_messages; ++index) {
if (role == Noise::Initiator) {
/* Send on the initiator, receive on the responder */
csend = c1init;
crecv = c1resp;
role = Noise::Responder;
} else {
/* Send on the responder, receive on the initiator */
csend = c2resp;
crecv = c2init;
role = Noise::Initiator;
}
verify(sizeof(message) >= (vec->messages[index].payload_len + mac_len));
verify(sizeof(payload) >= (vec->messages[index].payload_len + mac_len));
result = csend->encryptPacket
(message, sizeof(message),
vec->messages[index].payload, vec->messages[index].payload_len);
verify(result >= 0);
compare_blocks("ciphertext", message, (size_t)result,
vec->messages[index].ciphertext,
vec->messages[index].ciphertext_len);
result = crecv->decryptPacket
(payload, sizeof(payload), message, result);
verify(result >= 0);
compare_blocks("plaintext", payload, (size_t)result,
vec->messages[index].payload,
vec->messages[index].payload_len);
}
/* Clean up */
delete initiator;
delete responder;
delete c1init;
delete c2init;
delete c1resp;
delete c2resp;
}
/**
* \brief Runs a fully parsed test vector.
*
* \param reader The input stream, for error reporting.
* \param vec The test vector.
*
* \return Non-zero if the test succeeded, zero if it failed.
*/
static int test_vector_run(JSONReader *reader, const TestVector *vec)
{
int value;
printf("%s ... ", vec->name);
fflush(stdout);
if ((value = setjmp(test_jump_back)) == 0) {
test_connection(vec);
printf("ok\n");
return 1;
} else if (value == 2) {
printf("skipped\n");
return 1;
} else {
printf("-> test data at %s:%ld\n", reader->filename, vec->line_number);
return 0;
}
}
/**
* \brief Look for a specific token next in the input stream.
*
* \param reader The input stream.
* \param token The token code.
* \param name The token name for error reporting.
*/
static void expect_token(JSONReader *reader, JSONToken token, const char *name)
{
if (reader->errors)
return;
if (reader->token == token)
json_next_token(reader);
else
json_error(reader, "Expecting '%s'", name);
}
/**
* \brief Look for a specific field name next in the input stream,
* followed by a colon.
*
* \param reader The input stream.
* \param name The field name.
*/
static void expect_name(JSONReader *reader, const char *name)
{
if (reader->errors)
return;
if (json_is_name(reader, name)) {
json_next_token(reader);
expect_token(reader, JSON_TOKEN_COLON, ":");
} else {
json_error(reader, "Expecting \"%s\"", name);
}
}
/**
* \brief Look for a field with a string value.
*
* \param reader The input stream.
* \param value The location where to place the string value.
*/
static void expect_string_field(JSONReader *reader, char **value)
{
json_next_token(reader);
expect_token(reader, JSON_TOKEN_COLON, ":");
if (!reader->errors && reader->token == JSON_TOKEN_STRING) {
*value = reader->str_value;
reader->str_value = 0;
json_next_token(reader);
if (!reader->errors && reader->token == JSON_TOKEN_COMMA)
json_next_token(reader);
}
}
/**
* \brief Converts an ASCII character into a hexadecimal digit.
*
* \param ch The ASCII character.
*
* \return The digit between 0 and 15, or -1 if \a ch is not hexadecimal.
*/
static int from_hex_digit(int ch)
{
if (ch >= '0' && ch <= '9')
return ch - '0';
else if (ch >= 'A' && ch <= 'F')
return ch - 'A' + 10;
else if (ch >= 'a' && ch <= 'f')
return ch - 'a' + 10;
else
return -1;
}
/**
* \brief Look for a field with a binary value.
*
* \param reader The input stream.
* \param value The location where to place the binary value.
*
* \return The size of the binary value in bytes.
*/
static size_t expect_binary_field(JSONReader *reader, uint8_t **value)
{
size_t size = 0;
size_t posn;
const char *hex;
int digit1, digit2;
json_next_token(reader);
expect_token(reader, JSON_TOKEN_COLON, ":");
if (!reader->errors && reader->token == JSON_TOKEN_STRING) {
size = strlen(reader->str_value) / 2;
*value = (uint8_t *)calloc(1, size + 1);
if (!(*value)) {
json_error(reader, "Out of memory");
return 0;
}
hex = reader->str_value;
for (posn = 0; posn < size; ++posn) {
digit1 = from_hex_digit(hex[posn * 2]);
digit2 = from_hex_digit(hex[posn * 2 + 1]);
if (digit1 < 0 || digit2 < 0) {
json_error(reader, "Invalid hexadecimal data");
return 0;
}
(*value)[posn] = digit1 * 16 + digit2;
}
json_next_token(reader);
if (!reader->errors && reader->token == JSON_TOKEN_COMMA)
json_next_token(reader);
}
return size;
}
/**
* \brief Look for a field with a boolean value.
*
* \param reader The input stream.
* \return The boolean value.
*/
static int expect_boolean_field(JSONReader *reader)
{
int result = 0;
json_next_token(reader);
expect_token(reader, JSON_TOKEN_COLON, ":");
if (!reader->errors && (reader->token == JSON_TOKEN_TRUE ||
reader->token == JSON_TOKEN_FALSE)) {
result = (reader->token == JSON_TOKEN_TRUE);
json_next_token(reader);
if (!reader->errors && reader->token == JSON_TOKEN_COMMA)
json_next_token(reader);
}
return result;
}
/**
* \brief Parse a list of PSK's from a JSON input stream.
*
* \param reader The input stream.
* \param Array to receive the PSK's.
* \return The number of PSK's that were parsed.
*/
static size_t parse_psk_list(JSONReader *reader, uint8_t psks[MAX_PSKS][32])
{
size_t count = 0;
json_next_token(reader);
expect_token(reader, JSON_TOKEN_COLON, ":");
expect_token(reader, JSON_TOKEN_LSQUARE, "[");
while (!reader->errors && reader->token == JSON_TOKEN_STRING) {
const char *hex = reader->str_value;
size_t size = strlen(hex) / 2;
size_t posn;
if (size != 32) {
json_error(reader, "PSK is not 32 bytes in size");
return 0;
}
if (count >= MAX_PSKS) {
json_error(reader, "Too many PSK's");
return 0;
}
for (posn = 0; posn < size; ++posn) {
int digit1 = from_hex_digit(hex[posn * 2]);
int digit2 = from_hex_digit(hex[posn * 2 + 1]);
if (digit1 < 0 || digit2 < 0) {
json_error(reader, "Invalid hexadecimal data");
return 0;
}
psks[count][posn] = digit1 * 16 + digit2;
}
++count;
json_next_token(reader);
if (!reader->errors && reader->token == JSON_TOKEN_COMMA)
json_next_token(reader);
}
expect_token(reader, JSON_TOKEN_RSQUARE, "]");
if (!reader->errors && reader->token == JSON_TOKEN_COMMA)
json_next_token(reader);
return reader->errors ? 0 : count;
}
/**
* \brief Processes a single test vector from an input stream.
*
* \param reader The reader representing the input stream.
*
* \return Non-zero if the test succeeded, zero if it failed.
*/
static int process_test_vector(JSONReader *reader)
{
TestVector vec;
int retval = 1;
memset(&vec, 0, sizeof(TestVector));
while (!reader->errors && reader->token == JSON_TOKEN_STRING) {
if (json_is_name(reader, "name")) {
vec.line_number = reader->line_number;
expect_string_field(reader, &(vec.name));
} else if (json_is_name(reader, "protocol_name")) {
vec.line_number = reader->line_number;
expect_string_field(reader, &(vec.protocol_name));
} else if (json_is_name(reader, "init_static")) {
vec.init_static_len =
expect_binary_field(reader, &(vec.init_static));
} else if (json_is_name(reader, "init_remote_static")) {
/* Refers to the initiator have pre-knowledge of the responder's
public key, which is "resp_public_static" in TestVector */
vec.resp_public_static_len =
expect_binary_field(reader, &(vec.resp_public_static));
} else if (json_is_name(reader, "resp_static")) {
vec.resp_static_len =
expect_binary_field(reader, &(vec.resp_static));
} else if (json_is_name(reader, "resp_remote_static")) {
/* Refers to the responder have pre-knowledge of the initiator's
public key, which is "init_public_static" in TestVector */
vec.init_public_static_len =
expect_binary_field(reader, &(vec.init_public_static));
} else if (json_is_name(reader, "init_ephemeral")) {
vec.init_ephemeral_len =
expect_binary_field(reader, &(vec.init_ephemeral));
} else if (json_is_name(reader, "resp_ephemeral")) {
vec.resp_ephemeral_len =
expect_binary_field(reader, &(vec.resp_ephemeral));
} else if (json_is_name(reader, "init_hybrid_ephemeral")) {
vec.init_hybrid_len =
expect_binary_field(reader, &(vec.init_hybrid));
} else if (json_is_name(reader, "resp_hybrid_ephemeral")) {
vec.resp_hybrid_len =
expect_binary_field(reader, &(vec.resp_hybrid));
} else if (json_is_name(reader, "init_prologue")) {
vec.init_prologue_len =
expect_binary_field(reader, &(vec.init_prologue));
} else if (json_is_name(reader, "resp_prologue")) {
vec.resp_prologue_len =
expect_binary_field(reader, &(vec.resp_prologue));
} else if (json_is_name(reader, "init_psks")) {
vec.num_init_psks = parse_psk_list(reader, vec.init_psks);
} else if (json_is_name(reader, "resp_psks")) {
vec.num_resp_psks = parse_psk_list(reader, vec.resp_psks);
} else if (json_is_name(reader, "handshake_hash")) {
vec.handshake_hash_len =
expect_binary_field(reader, &(vec.handshake_hash));
} else if (json_is_name(reader, "fail")) {
vec.fail = expect_boolean_field(reader);
} else if (json_is_name(reader, "fallback")) {
vec.fallback = expect_boolean_field(reader);
} else if (json_is_name(reader, "fallback_pattern")) {
expect_string_field(reader, &(vec.fallback_pattern));
} else if (json_is_name(reader, "messages")) {
json_next_token(reader);
expect_token(reader, JSON_TOKEN_COLON, ":");
expect_token(reader, JSON_TOKEN_LSQUARE, "[");
while (!reader->errors && reader->token == JSON_TOKEN_LBRACE) {
if (vec.num_messages >= MAX_MESSAGES) {
json_error(reader, "Too many messages for test vector");
break;
}
expect_token(reader, JSON_TOKEN_LBRACE, "{");
while (!reader->errors && reader->token == JSON_TOKEN_STRING) {
if (json_is_name(reader, "payload")) {
vec.messages[vec.num_messages].payload_len =
expect_binary_field
(reader, &(vec.messages[vec.num_messages].payload));
} else if (json_is_name(reader, "ciphertext")) {
vec.messages[vec.num_messages].ciphertext_len =
expect_binary_field
(reader, &(vec.messages[vec.num_messages].ciphertext));
} else {
json_error(reader, "Unknown message field '%s'",
reader->str_value);
}
}
if (!vec.messages[vec.num_messages].payload)
json_error(reader, "Missing payload for message");
if (!vec.messages[vec.num_messages].ciphertext)
json_error(reader, "Missing ciphertext for message");
++(vec.num_messages);
expect_token(reader, JSON_TOKEN_RBRACE, "}");
if (!reader->errors && reader->token == JSON_TOKEN_COMMA)
json_next_token(reader);
}
expect_token(reader, JSON_TOKEN_RSQUARE, "]");
if (!reader->errors && reader->token == JSON_TOKEN_COMMA)
json_next_token(reader);
} else {
json_error(reader, "Unknown field '%s'", reader->str_value);
}
}
if (!vec.protocol_name) {
json_error(reader, "Missing 'protocol_name' field");
} else if (!vec.name) {
vec.name = strdup(vec.protocol_name);
}
if (!reader->errors) {
retval = test_vector_run(reader, &vec);
}
test_vector_free(&vec);
return retval;
}
/**
* \brief Processes the test vectors from an input stream.
*
* \param reader The reader representing the input stream.
*/
static void process_test_vectors(JSONReader *reader)
{
int ok = 1;
printf("--------------------------------------------------------------\n");
printf("Processing vectors from %s\n", reader->filename);
json_next_token(reader);
expect_token(reader, JSON_TOKEN_LBRACE, "{");
expect_name(reader, "vectors");
expect_token(reader, JSON_TOKEN_LSQUARE, "[");
while (!reader->errors && reader->token != JSON_TOKEN_RSQUARE) {
expect_token(reader, JSON_TOKEN_LBRACE, "{");
if (!process_test_vector(reader))
ok = 0;
expect_token(reader, JSON_TOKEN_RBRACE, "}");
if (!reader->errors && reader->token == JSON_TOKEN_COMMA)
expect_token(reader, JSON_TOKEN_COMMA, ",");
}
expect_token(reader, JSON_TOKEN_RSQUARE, "]");
expect_token(reader, JSON_TOKEN_RBRACE, "}");
expect_token(reader, JSON_TOKEN_END, "EOF");
printf("--------------------------------------------------------------\n");
if (!ok) {
/* Some of the test vectors failed, so report a global failure */
++(reader->errors);
}
}
static int process_file(const char *filename)
{
int retval = 0;
FILE *file = fopen(filename, "r");
if (file) {
JSONReader reader;
json_init(&reader, filename, file);
process_test_vectors(&reader);
if (reader.errors > 0)
retval = 1;
json_free(&reader);
fclose(file);
} else {
perror(filename);
retval = 1;
}
return retval;
}
int main(int argc, char *argv[])
{
int retval = 0;
char *srcdir = getenv("srcdir");
if (argc <= 1 && !srcdir) {
fprintf(stderr, "Usage: %s vectors1.txt vectors2.txt ...\n", argv[0]);
return 1;
} else if (argc > 1) {
while (argc > 1) {
retval |= process_file(argv[1]);
--argc;
++argv;
}
} else {
if (chdir(srcdir) < 0) {
perror(srcdir);
return 1;
}
retval |= process_file("cacophony.txt");
retval |= process_file("noise-c-basic.txt");
retval |= process_file("noise-c-fallback.txt");
retval |= process_file("noise-c-hybrid.txt");
}
return retval;
}