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);