File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / strongswan / scripts / aes-test.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Wed Jun 3 09:46:49 2020 UTC (4 years, 3 months ago) by misho
Branches: strongswan, MAIN
CVS tags: v5_9_2p0, v5_8_4p7, HEAD
Strongswan

    1: /*
    2:  * Copyright (C) 2013 Tobias Brunner
    3:  * HSR Hochschule fuer Technik Rapperswil
    4:  *
    5:  * This program is free software; you can redistribute it and/or modify it
    6:  * under the terms of the GNU General Public License as published by the
    7:  * Free Software Foundation; either version 2 of the License, or (at your
    8:  * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
    9:  *
   10:  * This program is distributed in the hope that it will be useful, but
   11:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
   12:  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   13:  * for more details.
   14:  */
   15: 
   16: #include <stdio.h>
   17: #include <stdlib.h>
   18: #include <string.h>
   19: #include <unistd.h>
   20: #include <getopt.h>
   21: #include <errno.h>
   22: 
   23: #include <library.h>
   24: 
   25: /** plugins to load */
   26: #undef PLUGINS
   27: #define PLUGINS "openssl"
   28: 
   29: /**
   30:  * Context
   31:  */
   32: static struct {
   33: 	/** input file */
   34: 	FILE *in;
   35: 	/** output file */
   36: 	FILE *out;
   37: 	/** whether to use GCM or CBC */
   38: 	bool use_gcm;
   39: 	/** whether to run the Monte Carlo Test */
   40: 	bool use_mct;
   41: 	/** whether to test encryption or decryption */
   42: 	bool decrypt;
   43: 	/** IV length in bits in case of GCM */
   44: 	int ivlen;
   45: 	/** ICV length in bits in case of GCM */
   46: 	int icvlen;
   47: } ctx;
   48: 
   49: /**
   50:  * Types of parameters of a test vector
   51:  */
   52: typedef enum {
   53: 	PARAM_UNKNOWN,
   54: 	PARAM_COUNT,
   55: 	PARAM_KEY,
   56: 	PARAM_IV,
   57: 	PARAM_PLAINTEXT,
   58: 	PARAM_CIPHERTEXT,
   59: 	PARAM_AAD,
   60: 	PARAM_ICV,
   61: } param_t;
   62: 
   63: static param_t parse_parameter(char *param)
   64: {
   65: 	if (strcaseeq(param, "COUNT"))
   66: 	{
   67: 		return PARAM_COUNT;
   68: 	}
   69: 	if (strcaseeq(param, "KEY"))
   70: 	{
   71: 		return PARAM_KEY;
   72: 	}
   73: 	if (strcaseeq(param, "IV"))
   74: 	{
   75: 		return PARAM_IV;
   76: 	}
   77: 	if (strcaseeq(param, "PLAINTEXT") ||
   78: 		strcaseeq(param, "PT"))
   79: 	{
   80: 		return PARAM_PLAINTEXT;
   81: 	}
   82: 	if (strcaseeq(param, "CIPHERTEXT") ||
   83: 		strcaseeq(param, "CT"))
   84: 	{
   85: 		return PARAM_CIPHERTEXT;
   86: 	}
   87: 	if (strcaseeq(param, "AAD"))
   88: 	{
   89: 		return PARAM_AAD;
   90: 	}
   91: 	if (strcaseeq(param, "TAG"))
   92: 	{
   93: 		return PARAM_ICV;
   94: 	}
   95: 	return PARAM_UNKNOWN;
   96: }
   97: 
   98: /**
   99:  * Test vector
  100:  */
  101: typedef struct {
  102: 	/** encryption/decryption key */
  103: 	chunk_t key;
  104: 	/** initialization vector */
  105: 	chunk_t iv;
  106: 	/** plain text */
  107: 	chunk_t plain;
  108: 	/** cipher text */
  109: 	chunk_t cipher;
  110: 	/** associated data */
  111: 	chunk_t aad;
  112: 	/** ICV/tag */
  113: 	chunk_t icv;
  114: 	/** whether the IV was provided */
  115: 	bool external_iv;
  116: 	/** whether the decryption/verification in GCM mode was successful */
  117: 	bool success;
  118: } test_vector_t;
  119: 
  120: static void test_vector_free(test_vector_t *test)
  121: {
  122: 	chunk_free(&test->key);
  123: 	chunk_free(&test->iv);
  124: 	chunk_free(&test->plain);
  125: 	chunk_free(&test->cipher);
  126: 	chunk_free(&test->aad);
  127: 	chunk_free(&test->icv);
  128: }
  129: 
  130: static void print_result(test_vector_t *test)
  131: {
  132: 	if (ctx.use_gcm)
  133: 	{
  134: 		if (ctx.decrypt)
  135: 		{
  136: 			if (test->success)
  137: 			{
  138: 				fprintf(ctx.out, "PT = %+B\n", &test->plain);
  139: 			}
  140: 			else
  141: 			{
  142: 				fprintf(ctx.out, "FAIL\n");
  143: 			}
  144: 			return;
  145: 		}
  146: 		if (!test->external_iv)
  147: 		{
  148: 			fprintf(ctx.out, "IV = %+B\n", &test->iv);
  149: 		}
  150: 		fprintf(ctx.out, "CT = %+B\n", &test->cipher);
  151: 		fprintf(ctx.out, "Tag = %+B\n", &test->icv);
  152: 	}
  153: 	else
  154: 	{
  155: 		fprintf(ctx.out, "%s = %+B\n", ctx.decrypt ? "PLAINTEXT" : "CIPHERTEXT",
  156: 				ctx.decrypt ? &test->plain : &test->cipher);
  157: 	}
  158: }
  159: 
  160: static bool get_next_test_vector(test_vector_t *test)
  161: {
  162: 	param_t param = PARAM_UNKNOWN;
  163: 	char line[512];
  164: 
  165: 	memset(test, 0, sizeof(test_vector_t));
  166: 
  167: 	while (fgets(line, sizeof(line), ctx.in))
  168: 	{
  169: 		enumerator_t *enumerator;
  170: 		chunk_t value = chunk_empty;
  171: 		char *token;
  172: 		int i;
  173: 
  174: 		switch (line[0])
  175: 		{
  176: 			case '\n':
  177: 			case '\r':
  178: 			case '#':
  179: 			case '\0':
  180: 				/* copy comments, empty lines etc. directly to the output */
  181: 				if (param != PARAM_UNKNOWN)
  182: 				{	/* seems we got a complete test vector */
  183: 					return TRUE;
  184: 				}
  185: 				fputs(line, ctx.out);
  186: 				continue;
  187: 			case '[':
  188: 				/* control directives */
  189: 				fputs(line, ctx.out);
  190: 				if (strpfx(line, "[ENCRYPT]"))
  191: 				{
  192: 					ctx.decrypt = FALSE;
  193: 				}
  194: 				else if (strpfx(line, "[DECRYPT]"))
  195: 				{
  196: 					ctx.decrypt = TRUE;
  197: 				}
  198: 				else if (strcasepfx(line, "[IVlen = "))
  199: 				{
  200: 					ctx.ivlen = atoi(line + strlen("[IVlen = "));
  201: 				}
  202: 				else if (strcasepfx(line, "[Taglen = "))
  203: 				{
  204: 					ctx.icvlen = atoi(line + strlen("[Taglen = "));
  205: 				}
  206: 				continue;
  207: 			default:
  208: 				/* we assume the rest of the lines are PARAM = VALUE pairs*/
  209: 				fputs(line, ctx.out);
  210: 				break;
  211: 		}
  212: 
  213: 		i = 0;
  214: 		enumerator = enumerator_create_token(line, "=", " \n\r");
  215: 		while (enumerator->enumerate(enumerator, &token))
  216: 		{
  217: 			switch (i++)
  218: 			{
  219: 				case 0: /* PARAM */
  220: 					param = parse_parameter(token);
  221: 					continue;
  222: 				case 1: /* VALUE */
  223: 					if (param != PARAM_UNKNOWN && param != PARAM_COUNT)
  224: 					{
  225: 						value = chunk_from_hex(chunk_from_str(token), NULL);
  226: 					}
  227: 					else
  228: 					{
  229: 						value = chunk_empty;
  230: 					}
  231: 					continue;
  232: 				default:
  233: 					break;
  234: 			}
  235: 			break;
  236: 		}
  237: 		enumerator->destroy(enumerator);
  238: 		if (i < 2)
  239: 		{
  240: 			value = chunk_empty;
  241: 		}
  242: 		switch (param)
  243: 		{
  244: 			case PARAM_KEY:
  245: 				test->key = value;
  246: 				break;
  247: 			case PARAM_IV:
  248: 				test->iv = value;
  249: 				test->external_iv = TRUE;
  250: 				break;
  251: 			case PARAM_PLAINTEXT:
  252: 				test->plain = value;
  253: 				break;
  254: 			case PARAM_CIPHERTEXT:
  255: 				test->cipher = value;
  256: 				break;
  257: 			case PARAM_AAD:
  258: 				test->aad = value;
  259: 				break;
  260: 			case PARAM_ICV:
  261: 				test->icv = value;
  262: 				break;
  263: 			default:
  264: 				chunk_free(&value);
  265: 				break;
  266: 		}
  267: 	}
  268: 	if (param != PARAM_UNKNOWN)
  269: 	{	/* could be that the file ended with a complete test vector */
  270: 		return TRUE;
  271: 	}
  272: 	return FALSE;
  273: }
  274: 
  275: static bool verify_test_vector(test_vector_t *test)
  276: {
  277: 	if (ctx.use_gcm)
  278: 	{
  279: 		if (ctx.decrypt)
  280: 		{
  281: 			return test->key.ptr && test->iv.ptr && test->cipher.ptr &&
  282: 				   test->icv.ptr;
  283: 		}
  284: 		return test->key.ptr && test->plain.ptr;
  285: 	}
  286: 	if (ctx.decrypt)
  287: 	{
  288: 		return test->key.ptr && test->iv.ptr && test->cipher.ptr;
  289: 	}
  290: 	return test->key.ptr && test->iv.ptr && test->plain.ptr;
  291: }
  292: 
  293: static bool do_test_gcm(test_vector_t *test)
  294: {
  295: 	encryption_algorithm_t alg;
  296: 	chunk_t key, iv;
  297: 	aead_t *aead;
  298: 	size_t saltlen, ivlen;
  299: 
  300: 	switch (ctx.icvlen / 8)
  301: 	{
  302: 		case 8:
  303: 			alg = ENCR_AES_GCM_ICV8;
  304: 			break;
  305: 		case 12:
  306: 			alg = ENCR_AES_GCM_ICV12;
  307: 			break;
  308: 		case 16:
  309: 			alg = ENCR_AES_GCM_ICV16;
  310: 			break;
  311: 		default:
  312: 			DBG1(DBG_APP, "unsupported ICV length: %d", ctx.icvlen);
  313: 			return FALSE;
  314: 	}
  315: 
  316: 	aead = lib->crypto->create_aead(lib->crypto, alg, test->key.len, 4);
  317: 	if (!aead)
  318: 	{
  319: 		DBG1(DBG_APP, "algorithm %N or key length (%d bits) not supported",
  320: 			 encryption_algorithm_names, alg, test->key.len * 8);
  321: 		return FALSE;
  322: 	}
  323: 	/* our API is quite RFC 4106 specific, that is, part of the IV is provided
  324: 	 * at the end of the key. */
  325: 	saltlen = aead->get_key_size(aead) - test->key.len;
  326: 	ivlen = aead->get_iv_size(aead);
  327: 	if (ctx.ivlen / 8 != saltlen + ivlen)
  328: 	{
  329: 		DBG1(DBG_APP, "unsupported IV length: %d", ctx.ivlen);
  330: 		aead->destroy(aead);
  331: 		return FALSE;
  332: 	}
  333: 	if (!test->external_iv)
  334: 	{
  335: 		rng_t *rng;
  336: 
  337: 		/* the IV consists of saltlen random bytes (usually additional keymat)
  338: 		 * followed by a counter, zero here */
  339: 		test->iv = chunk_alloc(saltlen + ivlen);
  340: 		memset(test->iv.ptr, 0, test->iv.len);
  341: 		rng = lib->crypto->create_rng(lib->crypto, RNG_STRONG);
  342: 		if (!rng || !rng->get_bytes(rng, saltlen, test->iv.ptr))
  343: 		{
  344: 			DBG1(DBG_APP, "failed to generate IV");
  345: 			DESTROY_IF(rng);
  346: 			aead->destroy(aead);
  347: 			return FALSE;
  348: 		}
  349: 		rng->destroy(rng);
  350: 	}
  351: 	key = chunk_alloca(test->key.len + saltlen);
  352: 	memcpy(key.ptr, test->key.ptr, test->key.len);
  353: 	memcpy(key.ptr + test->key.len, test->iv.ptr, saltlen);
  354: 	iv = chunk_alloca(ivlen);
  355: 	memcpy(iv.ptr, test->iv.ptr + saltlen, iv.len);
  356: 	if (!aead->set_key(aead, key))
  357: 	{
  358: 		DBG1(DBG_APP, "failed to set key");
  359: 		aead->destroy(aead);
  360: 		return FALSE;
  361: 	}
  362: 	if (ctx.decrypt)
  363: 	{
  364: 		/* the ICV is expected to follow the cipher text */
  365: 		chunk_t cipher = chunk_cata("cc", test->cipher, test->icv);
  366: 		/* store if the verification of the ICV verification is successful */
  367: 		test->success = aead->decrypt(aead, cipher, test->aad, iv,
  368: 									  &test->plain);
  369: 	}
  370: 	else
  371: 	{
  372: 		if (!aead->encrypt(aead, test->plain, test->aad, iv, &test->cipher))
  373: 		{
  374: 			DBG1(DBG_APP, "encryption failed");
  375: 			aead->destroy(aead);
  376: 			return FALSE;
  377: 		}
  378: 		/* copy ICV from the end of the cipher text */
  379: 		test->icv = chunk_alloc(ctx.icvlen / 8);
  380: 		test->cipher.len -= test->icv.len;
  381: 		memcpy(test->icv.ptr, test->cipher.ptr + test->cipher.len,
  382: 			   test->icv.len);
  383: 	}
  384: 	aead->destroy(aead);
  385: 	return TRUE;
  386: }
  387: 
  388: static bool do_crypt(crypter_t *crypter, test_vector_t *test)
  389: {
  390: 	if (ctx.decrypt)
  391: 	{
  392: 		if (!crypter->decrypt(crypter, test->cipher, test->iv, &test->plain))
  393: 		{
  394: 			DBG1(DBG_APP, "decryption failed");
  395: 			return FALSE;
  396: 		}
  397: 	}
  398: 	else
  399: 	{
  400: 		if (!crypter->encrypt(crypter, test->plain, test->iv, &test->cipher))
  401: 		{
  402: 			DBG1(DBG_APP, "encryption failed");
  403: 			return FALSE;
  404: 		}
  405: 	}
  406: 	return TRUE;
  407: }
  408: 
  409: static bool do_test_cbc(test_vector_t *test)
  410: {
  411: 	crypter_t *crypter;
  412: 
  413: 	crypter = lib->crypto->create_crypter(lib->crypto, ENCR_AES_CBC,
  414: 										  test->key.len);
  415: 	if (!crypter)
  416: 	{
  417: 		DBG1(DBG_APP, "algorithm %N or key length (%d bits) not supported",
  418: 			 encryption_algorithm_names, ENCR_AES_CBC, test->key.len * 8);
  419: 		return FALSE;
  420: 	}
  421: 	if (!crypter->set_key(crypter, test->key))
  422: 	{
  423: 		DBG1(DBG_APP, "failed to set key");
  424: 		crypter->destroy(crypter);
  425: 		return FALSE;
  426: 	}
  427: 	if (!do_crypt(crypter, test))
  428: 	{
  429: 		crypter->destroy(crypter);
  430: 		return FALSE;
  431: 	}
  432: 	crypter->destroy(crypter);
  433: 	return TRUE;
  434: }
  435: 
  436: static bool do_test_mct(test_vector_t *test)
  437: {
  438: 	crypter_t *crypter;
  439: 	chunk_t prev, *input, *output;
  440: 	int i, j;
  441: 
  442: 	crypter = lib->crypto->create_crypter(lib->crypto, ENCR_AES_CBC,
  443: 										  test->key.len);
  444: 	if (!crypter)
  445: 	{
  446: 		DBG1(DBG_APP, "algorithm %N or key length (%d bits) not supported",
  447: 			 encryption_algorithm_names, ENCR_AES_CBC, test->key.len * 8);
  448: 		return FALSE;
  449: 	}
  450: 	input = ctx.decrypt ? &test->cipher : &test->plain;
  451: 	output = ctx.decrypt ? &test->plain : &test->cipher;
  452: 	if (crypter->get_block_size(crypter) != input->len)
  453: 	{
  454: 		DBG1(DBG_APP, "MCT only works for input with a length of one block");
  455: 		crypter->destroy(crypter);
  456: 		return FALSE;
  457: 	}
  458: 	prev = chunk_alloca(input->len);
  459: 	/* assume initial IV as previous output */
  460: 	*output = chunk_clone(test->iv);
  461: 	for (i = 0; i < 100; i++)
  462: 	{
  463: 		if (i > 0)
  464: 		{	/* we copied the original lines already */
  465: 			fprintf(ctx.out, "COUNT = %d\n", i);
  466: 			fprintf(ctx.out, "KEY = %+B\n", &test->key);
  467: 			fprintf(ctx.out, "IV = %+B\n", &test->iv);
  468: 			fprintf(ctx.out, "%s = %+B\n",
  469: 					ctx.decrypt ? "CIPHERTEXT" : "PLAINTEXT", input);
  470: 		}
  471: 		if (!crypter->set_key(crypter, test->key))
  472: 		{
  473: 			DBG1(DBG_APP, "failed to set key");
  474: 			return FALSE;
  475: 		}
  476: 		for (j = 0; j < 1000; j++)
  477: 		{
  478: 			/* store previous output as it is used as input after next */
  479: 			memcpy(prev.ptr, output->ptr, prev.len);
  480: 			chunk_free(output);
  481: 			if (!do_crypt(crypter, test))
  482: 			{
  483: 				crypter->destroy(crypter);
  484: 				return FALSE;
  485: 			}
  486: 			/* prepare the next IV (our API does not allow incremental calls) */
  487: 			if (ctx.decrypt)
  488: 			{
  489: 				memcpy(test->iv.ptr, input->ptr, test->iv.len);
  490: 			}
  491: 			else
  492: 			{
  493: 				memcpy(test->iv.ptr, output->ptr, test->iv.len);
  494: 			}
  495: 			/* the previous output is the next input */
  496: 			memcpy(input->ptr, prev.ptr, input->len);
  497: 		}
  498: 		fprintf(ctx.out, "%s = %+B\n\n",
  499: 				ctx.decrypt ? "PLAINTEXT" : "CIPHERTEXT", output);
  500: 		/* derive key for next round */
  501: 		switch (test->key.len)
  502: 		{
  503: 			case 16:
  504: 				memxor(test->key.ptr, output->ptr, output->len);
  505: 				break;
  506: 			case 24:
  507: 				memxor(test->key.ptr, prev.ptr + 8, 8);
  508: 				memxor(test->key.ptr + 8, output->ptr, output->len);
  509: 				break;
  510: 			case 32:
  511: 				memxor(test->key.ptr, prev.ptr, prev.len);
  512: 				memxor(test->key.ptr + prev.len, output->ptr, output->len);
  513: 				break;
  514: 		}
  515: 		/* the current output is used as IV for the next round */
  516: 		memcpy(test->iv.ptr, output->ptr, test->iv.len);
  517: 	}
  518: 	crypter->destroy(crypter);
  519: 	/* we return FALSE as we print the output ourselves */
  520: 	return FALSE;
  521: }
  522: 
  523: static bool do_test(test_vector_t *test)
  524: {
  525: 	if (ctx.use_gcm)
  526: 	{
  527: 		return do_test_gcm(test);
  528: 	}
  529: 	if (ctx.use_mct)
  530: 	{
  531: 		return do_test_mct(test);
  532: 	}
  533: 	return do_test_cbc(test);
  534: }
  535: 
  536: static void usage(FILE *out, char *name)
  537: {
  538: 	fprintf(out, "Test AES implementation according to the AES Algorithm Validation Suite (AESAVS)\n");
  539: 	fprintf(out, "and the GCM Validation System (GCMVS)\n\n");
  540: 	fprintf(out, "%s [OPTIONS]\n\n", name);
  541: 	fprintf(out, "Options:\n");
  542: 	fprintf(out, "  -h, --help          print this help.\n");
  543: 	fprintf(out, "  -d, --debug=LEVEL   set debug level (default 1).\n");
  544: 	fprintf(out, "  -m, --mode=MODE     mode to test, either CBC or GCM (default CBC).\n");
  545: 	fprintf(out, "  -t, --mct           run Monte Carlo Test (MCT), only for CBC.\n");
  546: 	fprintf(out, "  -x, --decrypt       test decryption (not needed for CBC as files contain control directives).\n");
  547: 	fprintf(out, "  -i, --in=FILE       request file (default STDIN).\n");
  548: 	fprintf(out, "  -o, --out=FILE      response file (default STDOUT).\n");
  549: 	fprintf(out, "\n");
  550: }
  551: 
  552: int main(int argc, char *argv[])
  553: {
  554: 	test_vector_t test;
  555: 
  556: 	ctx.in = stdin;
  557: 	ctx.out = stdout;
  558: 
  559: 	library_init(NULL, "aes-test");
  560: 	atexit(library_deinit);
  561: 
  562: 	while (true)
  563: 	{
  564: 		struct option long_opts[] = {
  565: 			{"help",		no_argument,		NULL,	'h' },
  566: 			{"debug",		required_argument,	NULL,	'd' },
  567: 			{"mode",		required_argument,	NULL,	'm' },
  568: 			{"mct",			no_argument,		NULL,	't' },
  569: 			{"decrypt",		no_argument,		NULL,	'x' },
  570: 			{"in",			required_argument,	NULL,	'i' },
  571: 			{"out",			required_argument,	NULL,	'o' },
  572: 			{0,0,0,0 },
  573: 		};
  574: 		switch (getopt_long(argc, argv, "hd:m:txi:o:", long_opts, NULL))
  575: 		{
  576: 			case EOF:
  577: 				break;
  578: 			case 'h':
  579: 				usage(stdout, argv[0]);
  580: 				return 0;
  581: 			case 'd':
  582: 				dbg_default_set_level(atoi(optarg));
  583: 				continue;
  584: 			case 'm':
  585: 				if (strcaseeq(optarg, "GCM"))
  586: 				{
  587: 					ctx.use_gcm = TRUE;
  588: 				}
  589: 				else if (!strcaseeq(optarg, "CBC"))
  590: 				{
  591: 					usage(stderr, argv[0]);
  592: 					return 1;
  593: 				}
  594: 				continue;
  595: 			case 't':
  596: 				ctx.use_mct = TRUE;
  597: 				continue;
  598: 			case 'x':
  599: 				ctx.decrypt = TRUE;
  600: 				continue;
  601: 			case 'i':
  602: 				ctx.in = fopen(optarg, "r");
  603: 				if (!ctx.in)
  604: 				{
  605: 					fprintf(stderr, "failed to open '%s': %s\n", optarg,
  606: 							strerror(errno));
  607: 					usage(stderr, argv[0]);
  608: 					return 1;
  609: 				}
  610: 				continue;
  611: 			case 'o':
  612: 				ctx.out = fopen(optarg, "w");
  613: 				if (!ctx.out)
  614: 				{
  615: 					fprintf(stderr, "failed to open '%s': %s\n", optarg,
  616: 							strerror(errno));
  617: 					usage(stderr, argv[0]);
  618: 					return 1;
  619: 				}
  620: 				continue;
  621: 			default:
  622: 				usage(stderr, argv[0]);
  623: 				return 1;
  624: 		}
  625: 		break;
  626: 	}
  627: 	/* TODO: maybe make plugins configurable */
  628: 	lib->plugins->load(lib->plugins, PLUGINS);
  629: 	lib->plugins->status(lib->plugins, LEVEL_CTRL);
  630: 
  631: 	while (get_next_test_vector(&test))
  632: 	{
  633: 		if (verify_test_vector(&test))
  634: 		{
  635: 			if (do_test(&test))
  636: 			{
  637: 				print_result(&test);
  638: 			}
  639: 		}
  640: 		else
  641: 		{
  642: 			DBG1(DBG_APP, "test vector with missing data encountered");
  643: 		}
  644: 		fprintf(ctx.out, "\n");
  645: 		test_vector_free(&test);
  646: 	}
  647: 
  648: 	if (ctx.in != stdin)
  649: 	{
  650: 		fclose(ctx.in);
  651: 	}
  652: 	if (ctx.out != stdout)
  653: 	{
  654: 		fclose(ctx.out);
  655: 	}
  656: 	return 0;
  657: }

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>