test: Add integration test "framework".
This adds a test driver for integration tests in the main.c file.
Additinoally we provide a mechanism for the test to get TCTI
configuration data from the environment. Tests should include the
'test.h' file and implement the function whos prototype is defined
there.
diff --git a/Makefile.am b/Makefile.am
index 0168721..7fc55b2 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -36,10 +36,12 @@
sbin_PROGRAMS = $(resourcemgr)
noinst_PROGRAMS = $(tpmclient) $(tpmtest)
lib_LTLIBRARIES = $(libsapi) $(libtcti_device) $(libtcti_socket)
+noinst_LTLIBRARIES = test/integration/libtest_utils.la
+check_PROGRAMS = $(TESTS_UNIT) $(TESTS_INTEGRATION)
# unit tests
if UNIT
-check_PROGRAMS = \
+TESTS_UNIT = \
test/unit/CheckOverflow \
test/unit/CommonPreparePrologue \
test/unit/CopyCommandHeader \
@@ -56,6 +58,11 @@
TESTS = $(check_PROGRAMS)
CLEANFILES = $(nodist_pkgconfig_DATA)
+AM_TESTS_ENVIRONMENT = \
+ export TPM20TEST_TCTI_NAME="socket"; \
+ export TPM20TEST_SOCKET_ADDRESS="127.0.0.1"; \
+ export TPM20TEST_SOCKET_PORT="2321";
+
# headers and where to install them
libsapidir = $(includedir)/sapi
libsapi_HEADERS = $(srcdir)/include/sapi/*.h
@@ -196,6 +203,11 @@
test_tpmtest_tpmtest_LDADD = $(libsapi) $(libtcti_socket) $(libtcti_device)
test_tpmtest_tpmtest_SOURCES = $(TPMTEST_CXX) $(COMMON_C) $(SAMPLE_C)
+test_integration_libtest_utils_la_SOURCES = test/integration/test-options.c \
+ test/integration/context-util.c
+
+TESTS_LDADD = $(noinst_LTLIBRARIES) $(lib_LTLIBRARIES)
+
%.pc : %.pc.in
if [ ! -d $(dir $@) ]; then mkdir -p $(dir $@); fi
sed -e "s,[@]VERSION[@],$(PACKAGE_VERSION),g; \
diff --git a/test/integration/context-util.c b/test/integration/context-util.c
new file mode 100644
index 0000000..bc4cd17
--- /dev/null
+++ b/test/integration/context-util.c
@@ -0,0 +1,178 @@
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "tcti/tcti_device.h"
+#include "tcti/tcti_socket.h"
+
+#include "context-util.h"
+
+/*
+ * Initialize a TSS2_TCTI_CONTEXT for the device TCTI.
+ */
+TSS2_TCTI_CONTEXT*
+tcti_device_init (char const *device_path)
+{
+ TCTI_DEVICE_CONF conf = {
+ .device_path =device_path,
+ .logCallback = NULL,
+ .logData = NULL,
+ };
+ size_t size;
+ TSS2_RC rc;
+ TSS2_TCTI_CONTEXT *tcti_ctx;
+
+ rc = InitDeviceTcti (NULL, &size, 0);
+ if (rc != TSS2_RC_SUCCESS) {
+ fprintf (stderr,
+ "Failed to get allocation size for device tcti context: "
+ "0x%x\n", rc);
+ return NULL;
+ }
+ tcti_ctx = (TSS2_TCTI_CONTEXT*)calloc (1, size);
+ if (tcti_ctx == NULL) {
+ fprintf (stderr,
+ "Allocation for device TCTI context failed: %s\n",
+ strerror (errno));
+ return NULL;
+ }
+ rc = InitDeviceTcti (tcti_ctx, &size, &conf);
+ if (rc != TSS2_RC_SUCCESS) {
+ fprintf (stderr,
+ "Failed to initialize device TCTI context: 0x%x\n",
+ rc);
+ free (tcti_ctx);
+ return NULL;
+ }
+ return tcti_ctx;
+}
+/*
+ * Initialize a socket TCTI instance using the provided options structure.
+ * The hostname and port are the only configuration options used. Callbacks
+ * for logging are set to NULL.
+ * The caller is returned a TCTI context structure that is allocated by this
+ * function. This structure must be freed by the caller.
+ */
+TSS2_TCTI_CONTEXT*
+tcti_socket_init (char const *address,
+ uint16_t port)
+{
+ TCTI_SOCKET_CONF conf = {
+ .hostname = address,
+ .port = port,
+ .logCallback = NULL,
+ .logBufferCallback = NULL,
+ .logData = NULL,
+ };
+ size_t size;
+ TSS2_RC rc;
+ TSS2_TCTI_CONTEXT *tcti_ctx;
+
+ rc = InitSocketTcti (NULL, &size, &conf, 0);
+ if (rc != TSS2_RC_SUCCESS) {
+ fprintf (stderr, "Faled to get allocation size for tcti context: "
+ "0x%x\n", rc);
+ return NULL;
+ }
+ tcti_ctx = (TSS2_TCTI_CONTEXT*)calloc (1, size);
+ if (tcti_ctx == NULL) {
+ fprintf (stderr, "Allocation for tcti context failed: %s\n",
+ strerror (errno));
+ return NULL;
+ }
+ rc = InitSocketTcti (tcti_ctx, &size, &conf, 0);
+ if (rc != TSS2_RC_SUCCESS) {
+ fprintf (stderr, "Failed to initialize tcti context: 0x%x\n", rc);
+ return NULL;
+ }
+ return tcti_ctx;
+}
+/*
+ * Initialize a SAPI context using the TCTI context provided by the caller.
+ * This function allocates memory for the SAPI context and returns it to the
+ * caller. This memory must be freed by the caller.
+ */
+static TSS2_SYS_CONTEXT*
+sapi_init_from_tcti_ctx (TSS2_TCTI_CONTEXT *tcti_ctx)
+{
+ TSS2_SYS_CONTEXT *sapi_ctx;
+ TSS2_RC rc;
+ size_t size;
+ TSS2_ABI_VERSION abi_version = {
+ .tssCreator = TSSWG_INTEROP,
+ .tssFamily = TSS_SAPI_FIRST_FAMILY,
+ .tssLevel = TSS_SAPI_FIRST_LEVEL,
+ .tssVersion = TSS_SAPI_FIRST_VERSION,
+ };
+
+ size = Tss2_Sys_GetContextSize (0);
+ sapi_ctx = (TSS2_SYS_CONTEXT*)calloc (1, size);
+ if (sapi_ctx == NULL) {
+ fprintf (stderr,
+ "Failed to allocate 0x%zx bytes for the SAPI context\n",
+ size);
+ return NULL;
+ }
+ rc = Tss2_Sys_Initialize (sapi_ctx, size, tcti_ctx, &abi_version);
+ if (rc != TSS2_RC_SUCCESS) {
+ fprintf (stderr, "Failed to initialize SAPI context: 0x%x\n", rc);
+ return NULL;
+ }
+ return sapi_ctx;
+}
+/*
+ * Initialize a SAPI context to use a socket TCTI. Get configuration data from
+ * the provided structure.
+ */
+TSS2_SYS_CONTEXT*
+sapi_init_from_opts (test_opts_t *options)
+{
+ TSS2_TCTI_CONTEXT *tcti_ctx;
+ TSS2_SYS_CONTEXT *sapi_ctx;
+
+ tcti_ctx = tcti_init_from_opts (options);
+ if (tcti_ctx == NULL)
+ return NULL;
+ sapi_ctx = sapi_init_from_tcti_ctx (tcti_ctx);
+ if (sapi_ctx == NULL)
+ return NULL;
+ return sapi_ctx;
+}
+/*
+ * Initialize a TSS2_TCTI_CONTEXT using whatever TCTI data is in the options
+ * structure. This is a mechanism that allows the calling application to be
+ * mostly ignorant of which TCTI they're creating / initializing.
+ */
+TSS2_TCTI_CONTEXT*
+tcti_init_from_opts (test_opts_t *options)
+{
+ switch (options->tcti_type) {
+ case DEVICE_TCTI:
+ return tcti_device_init (options->device_file);
+ case SOCKET_TCTI:
+ return tcti_socket_init (options->socket_address,
+ options->socket_port);
+ default:
+ return NULL;
+ }
+}
+/*
+ * Teardown and free the resoruces associted with a SAPI context structure.
+ * This includes tearing down the TCTI as well.
+ */
+void
+sapi_teardown_full (TSS2_SYS_CONTEXT *sapi_context)
+{
+ TSS2_TCTI_CONTEXT *tcti_context = NULL;
+ TSS2_RC rc;
+
+ rc = Tss2_Sys_GetTctiContext (sapi_context, &tcti_context);
+ if (rc != TSS2_RC_SUCCESS)
+ return;
+ Tss2_Sys_Finalize (sapi_context);
+ free (sapi_context);
+ if (tcti_context) {
+ tss2_tcti_finalize (tcti_context);
+ free (tcti_context);
+ }
+}
diff --git a/test/integration/context-util.h b/test/integration/context-util.h
new file mode 100644
index 0000000..7f52ee4
--- /dev/null
+++ b/test/integration/context-util.h
@@ -0,0 +1,18 @@
+#ifndef CONTEXT_UTIL_H
+#define CONTEXT_UTIL_H
+
+#include "sapi/tpm20.h"
+#include "test-options.h"
+
+/**
+ * functions to setup TCTIs and SAPI contexts using data from the common
+ * options
+ */
+TSS2_TCTI_CONTEXT* tcti_device_init (char const *device_name);
+TSS2_TCTI_CONTEXT* tcti_socket_init (char const *address,
+ uint16_t port);
+TSS2_TCTI_CONTEXT* tcti_init_from_opts (test_opts_t *options);
+TSS2_SYS_CONTEXT* sapi_init_from_opts (test_opts_t *options);
+void sapi_teardown_full (TSS2_SYS_CONTEXT *sapi_context);
+
+#endif /* CONTEXT_UTIL_H */
diff --git a/test/integration/log.h b/test/integration/log.h
new file mode 100644
index 0000000..c264865
--- /dev/null
+++ b/test/integration/log.h
@@ -0,0 +1,26 @@
+#ifndef LOG_H
+#define LOG_H
+
+#include <stdio.h>
+
+#define print_log(fmt, ...) \
+ do { \
+ fprintf(stderr, \
+ "%s:%d:%s(): " fmt "\n", \
+ __FILE__, \
+ __LINE__, \
+ __func__, \
+ ##__VA_ARGS__); \
+ } while (0)
+#define print_fail(fmt, ...) \
+ do { \
+ fprintf(stdout, \
+ "%s:%d:%s(): " fmt "\n", \
+ __FILE__, \
+ __LINE__, \
+ __func__, \
+ ##__VA_ARGS__); \
+ exit(1); \
+ } while (0)
+
+#endif
diff --git a/test/integration/main.c b/test/integration/main.c
new file mode 100644
index 0000000..bb4c7f4
--- /dev/null
+++ b/test/integration/main.c
@@ -0,0 +1,40 @@
+#include <stdbool.h>
+
+#include "log.h"
+#include "test.h"
+#include "test-options.h"
+#include "context-util.h"
+
+/**
+ * This program is a template for integration tests (ones that use the TCTI
+ * and the SAPI contexts / API directly). It does nothing more than parsing
+ * command line options that allow the caller (likely a script) to specify
+ * which TCTI to use for the test.
+ */
+int
+main (int argc,
+ char *argv[])
+{
+ TSS2_RC rc;
+ TSS2_SYS_CONTEXT *sapi_context;
+ int ret;
+ test_opts_t opts = {
+ .tcti_type = TCTI_DEFAULT,
+ .device_file = DEVICE_PATH_DEFAULT,
+ .socket_address = HOSTNAME_DEFAULT,
+ .socket_port = PORT_DEFAULT,
+ };
+
+ get_test_opts_from_env (&opts);
+ if (sanity_check_test_opts (&opts) != 0)
+ exit (1);
+ sapi_context = sapi_init_from_opts (&opts);
+ if (sapi_context == NULL)
+ exit (1);
+ rc = Tss2_Sys_Startup(sapi_context, TPM_SU_CLEAR);
+ if (rc != TSS2_RC_SUCCESS && rc != TPM_RC_INITIALIZE)
+ print_fail("TPM Startup FAILED! Response Code : 0x%x", rc);
+ ret = test_invoke (sapi_context);
+ sapi_teardown_full (sapi_context);
+ return ret;
+}
diff --git a/test/integration/test-options.c b/test/integration/test-options.c
new file mode 100644
index 0000000..2834298
--- /dev/null
+++ b/test/integration/test-options.c
@@ -0,0 +1,120 @@
+#include <stdio.h>
+
+#include "test-options.h"
+
+/*
+ * A structure to map a string name to an element in the TCTI_TYPE
+ * enumeration.
+ */
+typedef struct {
+ char *name;
+ TCTI_TYPE type;
+} tcti_map_entry_t;
+/*
+ * A table of tcti_map_entry_t structures. This is how we map a string
+ * provided on the command line to the enumeration.
+ */
+tcti_map_entry_t tcti_map_table[] = {
+ {
+ .name = "device",
+ .type = DEVICE_TCTI,
+ },
+ {
+ .name = "socket",
+ .type = SOCKET_TCTI,
+ },
+ {
+ .name = "unknown",
+ .type = UNKNOWN_TCTI,
+ },
+};
+/*
+ * Convert from a string to an element in the TCTI_TYPE enumeration.
+ * An unkonwn name / string will map to UNKNOWN_TCTI.
+ */
+TCTI_TYPE
+tcti_type_from_name (char const *tcti_str)
+{
+ int i;
+ for (i = 0; i < N_TCTI; ++i)
+ if (strcmp (tcti_str, tcti_map_table[i].name) == 0)
+ return tcti_map_table[i].type;
+ return UNKNOWN_TCTI;
+}
+/*
+ * Convert from an element in the TCTI_TYPE enumeration to a string
+ * representation.
+ */
+char* const
+tcti_name_from_type (TCTI_TYPE tcti_type)
+{
+ int i;
+ for (i = 0; i < N_TCTI; ++i)
+ if (tcti_type == tcti_map_table[i].type)
+ return tcti_map_table[i].name;
+ return NULL;
+}
+/*
+ * return 0 if sanity test passes
+ * return 1 if sanity test fails
+ */
+int
+sanity_check_test_opts (test_opts_t *opts)
+{
+ switch (opts->tcti_type) {
+ case DEVICE_TCTI:
+ if (opts->device_file == NULL) {
+ fprintf (stderr, "device-path is NULL, check env\n");
+ return 1;
+ }
+ break;
+ case SOCKET_TCTI:
+ if (opts->socket_address == NULL || opts->socket_port == 0) {
+ fprintf (stderr,
+ "socket_address or socket_port is NULL, check env\n");
+ return 1;
+ }
+ break;
+ default:
+ fprintf (stderr, "unknown TCTI type, check env\n");
+ return 1;
+ }
+ return 0;
+}
+/*
+ * Parse command line options from argv extracting test options. These are
+ * returned to the caller in the provided options structure.
+ */
+int
+get_test_opts_from_env (test_opts_t *test_opts)
+{
+ char *env_str, *end_ptr;
+
+ if (test_opts == NULL)
+ return 1;
+ env_str = getenv (ENV_TCTI_NAME);
+ if (env_str != NULL)
+ test_opts->tcti_type = tcti_type_from_name (env_str);
+ env_str = getenv (ENV_DEVICE_FILE);
+ if (env_str != NULL)
+ test_opts->device_file = env_str;
+ env_str = getenv (ENV_SOCKET_ADDRESS);
+ if (env_str != NULL)
+ test_opts->socket_address = env_str;
+ env_str = getenv (ENV_SOCKET_PORT);
+ if (env_str != NULL)
+ test_opts->socket_port = strtol (env_str, &end_ptr, 10);
+ return 0;
+}
+/*
+ * Dump the contents of the test_opts_t structure to stdout.
+ */
+void
+dump_test_opts (test_opts_t *opts)
+{
+ printf ("test_opts_t:\n");
+ printf (" tcti_type: %s\n", tcti_name_from_type (opts->tcti_type));
+ printf (" device_file: %s\n", opts->device_file);
+ printf (" socket_address: %s\n", opts->socket_address);
+ printf (" socket_port: %d\n", opts->socket_port);
+}
diff --git a/test/integration/test-options.h b/test/integration/test-options.h
new file mode 100644
index 0000000..53e79a6
--- /dev/null
+++ b/test/integration/test-options.h
@@ -0,0 +1,45 @@
+#ifndef TEST_OPTIONS_H
+#define TEST_OPTIONS_H
+
+#include "sapi/tpm20.h"
+
+/* Default TCTI */
+#define TCTI_DEFAULT SOCKET_TCTI
+#define TCTI_DEFAULT_STR "socket"
+
+/* Defaults for Device TCTI */
+#define DEVICE_PATH_DEFAULT "/dev/tpm0"
+
+/* Deafults for Socket TCTI connections */
+#define HOSTNAME_DEFAULT "127.0.0.1"
+#define PORT_DEFAULT 2321
+
+/* environment variables holding TCTI config */
+#define ENV_TCTI_NAME "TPM20TEST_TCTI_NAME"
+#define ENV_DEVICE_FILE "TPM2OTEST_DEVICE_FILE"
+#define ENV_SOCKET_ADDRESS "TPM20TEST_SOCKET_ADDRESS"
+#define ENV_SOCKET_PORT "TPM20TEST_SOCKET_PORT"
+
+
+typedef enum {
+ UNKNOWN_TCTI,
+ DEVICE_TCTI,
+ SOCKET_TCTI,
+ N_TCTI,
+} TCTI_TYPE;
+
+typedef struct {
+ TCTI_TYPE tcti_type;
+ char *device_file;
+ char *socket_address;
+ uint16_t socket_port;
+} test_opts_t;
+
+/* functions to get test options from the user and to print helpful stuff */
+char* const tcti_name_from_type (TCTI_TYPE tcti_type);
+TCTI_TYPE tcti_type_from_name (char const *tcti_str);
+int get_test_opts_from_env (test_opts_t *opts);
+int sanity_check_test_opts (test_opts_t *opts);
+void dump_test_opts (test_opts_t *opts);
+
+#endif /* TEST_OPTIONS_H */
diff --git a/test/integration/test.h b/test/integration/test.h
new file mode 100644
index 0000000..90621b0
--- /dev/null
+++ b/test/integration/test.h
@@ -0,0 +1,12 @@
+#include "sapi/tpm20.h"
+
+/*
+ * This is the prototype for all integration tests in the TPM2.0-TSS
+ * project. Integration tests are intended to exercise the combined
+ * components in the software stack. This typically means executing some
+ * SAPI function using the socket TCTI to communicate with a software
+ * TPM2 simulator.
+ * Return values:
+ * A successful test will return 0, any other value indicates failure.
+ */
+int test_invoke (TSS2_SYS_CONTEXT *sapi_context);