diff --git a/Makefile-test.am b/Makefile-test.am index 5f2478aad..41622f37b 100644 --- a/Makefile-test.am +++ b/Makefile-test.am @@ -139,6 +139,9 @@ endif if ENABLE_TCTI_I2C_FTDI TESTS_UNIT += test/unit/tcti-i2c-ftdi endif +if ENABLE_TCTI_PY +TESTS_UNIT += test/unit/tcti-py +endif if ESYS TESTS_UNIT += \ test/unit/esys-context-null \ @@ -599,6 +602,15 @@ test_unit_tcti_i2c_ftdi_SOURCES = test/unit/tcti-i2c-ftdi.c \ src/tss2-tcti/tcti-i2c-ftdi.c endif +if ENABLE_TCTI_PY +test_unit_tcti_py_CFLAGS = $(CMOCKA_CFLAGS) $(TESTS_CFLAGS) $(PYTHON3_EMBED_CFLAGS) +test_unit_tcti_py_LDFLAGS = $(PYTHON3_EMBED_LIBS) -ldl -lpthread -ltss2-mu -ltss2-esys +test_unit_tcti_py_LDADD = $(CMOCKA_LIBS) $(test_unit_log_LDADD) +test_unit_tcti_py_SOURCES = test/unit/tcti-py.c \ + src/tss2-tcti/tcti-py.c \ + src/tss2-tcti/tcti-common.c +endif + test_unit_tctildr_CFLAGS = $(CMOCKA_CFLAGS) $(TESTS_CFLAGS) test_unit_tctildr_LDADD = $(CMOCKA_LIBS) $(libutil) test_unit_tctildr_LDFLAGS = -Wl,--wrap=calloc,--wrap=free \ diff --git a/Makefile.am b/Makefile.am index af0d83347..655e87af8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -541,6 +541,28 @@ src_tss2_tcti_libtss2_tcti_i2c_ftdi_la_SOURCES = \ src/tss2-tcti/tcti-i2c-ftdi.h endif # ENABLE_TCTI_I2C_FTDI +# tcti library for python implemented TCTIs +if ENABLE_TCTI_PY +libtss2_tcti_py = src/tss2-tcti/libtss2-tcti-py.la +tss2_HEADERS += $(srcdir)/include/tss2/tss2_tcti_py.h +lib_LTLIBRARIES += $(libtss2_tcti_py) +pkgconfig_DATA += lib/tss2-tcti-py.pc +EXTRA_DIST += lib/tss2-tcti-py.map \ + lib/tss2-tcti-py.def + +src_tss2_tcti_libtss2_tcti_py_la_CFLAGS = $(AM_CFLAGS) $(PYTHON3_EMBED_CFLAGS) -Wno-deprecated-declarations +src_tss2_tcti_libtss2_tcti_py_la_LDFLAGS = -ldl -lpthread $(PYTHON3_EMBED_LIBS) +if HAVE_LD_VERSION_SCRIPT +src_tss2_tcti_libtss2_tcti_py_la_LDFLAGS += -Wl,--version-script=$(srcdir)/lib/tss2-tcti-py.map +endif # HAVE_LD_VERSION_SCRIPT +src_tss2_tcti_libtss2_tcti_py_la_LIBADD = $(libutil) +src_tss2_tcti_libtss2_tcti_py_la_SOURCES = \ + src/tss2-tcti/tcti-common.c \ + src/tss2-tcti/tcti-py.c \ + src/tss2-tcti/tcti-py.h +endif # ENABLE_TCTI_PY + + ### TCG TSS SYS spec library ### libtss2_sys = src/tss2-sys/libtss2-sys.la tss2_HEADERS += $(srcdir)/include/tss2/tss2_sys.h @@ -936,6 +958,7 @@ man7_MANS = \ man/man7/tss2-tcti-spi-ftdi.7 \ man/man7/tss2-tcti-i2c-helper.7 \ man/man7/tss2-tcti-i2c-ftdi.7 \ + man/man7/tss2-tcti-py.7 \ man/man7/tss2-tctildr.7 if FAPI @@ -1033,6 +1056,7 @@ EXTRA_DIST += \ man/tss2-tcti-spi-ftdi.7.in \ man/tss2-tcti-i2c-helper.7.in \ man/tss2-tcti-i2c-ftdi.7.in \ + man/tss2-tcti-py.7.in \ man/tss2-tctildr.7.in CLEANFILES += \ diff --git a/configure.ac b/configure.ac index 4250802ed..101f37592 100644 --- a/configure.ac +++ b/configure.ac @@ -15,7 +15,8 @@ m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) #Backward compatible setti AC_CONFIG_HEADERS([config.h]) -AC_CONFIG_FILES([Makefile Doxyfile lib/tss2-sys.pc lib/tss2-esys.pc lib/tss2-mu.pc lib/tss2-tcti-device.pc lib/tss2-tcti-mssim.pc lib/tss2-tcti-swtpm.pc lib/tss2-tcti-pcap.pc lib/tss2-tcti-libtpms.pc lib/tss2-rc.pc lib/tss2-tctildr.pc lib/tss2-fapi.pc lib/tss2-tcti-cmd.pc lib/tss2-policy.pc lib/tss2-tcti-spi-helper.pc lib/tss2-tcti-spi-ltt2go.pc lib/tss2-tcti-spidev.pc lib/tss2-tcti-spi-ftdi.pc lib/tss2-tcti-i2c-helper.pc lib/tss2-tcti-i2c-ftdi.pc]) +AC_CONFIG_FILES([Makefile Doxyfile lib/tss2-sys.pc lib/tss2-esys.pc lib/tss2-mu.pc lib/tss2-tcti-device.pc lib/tss2-tcti-mssim.pc lib/tss2-tcti-swtpm.pc lib/tss2-tcti-pcap.pc lib/tss2-tcti-libtpms.pc lib/tss2-rc.pc lib/tss2-tctildr.pc lib/tss2-fapi.pc lib/tss2-tcti-cmd.pc lib/tss2-policy.pc lib/tss2-tcti-spi-helper.pc lib/tss2-tcti-spi-ltt2go.pc lib/tss2-tcti-spidev.pc lib/tss2-tcti-spi-ftdi.pc lib/tss2-tcti-i2c-helper.pc lib/tss2-tcti-i2c-ftdi.pc +lib/tss2-tcti-py.pc]) # propagate configure arguments to distcheck AC_SUBST([DISTCHECK_CONFIGURE_FLAGS],[$ac_configure_args]) @@ -390,6 +391,34 @@ AM_CONDITIONAL([ENABLE_TCTI_I2C_FTDI], [test "x$enable_tcti_i2c_ftdi" != xno]) AS_IF([test "x$enable_tcti_i2c_ftdi" = "xyes"], AC_DEFINE([TCTI_I2C_FTDI],[1], [TCTI FOR USB BASED ACCESS TO I2C BASED TPM OVER THE FTDI MPSSE USB TO I2C BRIDGE])) +AC_ARG_ENABLE([tcti-py], + [AS_HELP_STRING([--enable-tcti-py], + [build the tcti-py module])],, + [enable_tcti_py=no]) + +AC_DEFUN([handle_tcti_py], [ + + PKG_CHECK_MODULES([PYTHON3_EMBED], [python3-embed]) + + AM_PATH_PYTHON([3.0], + [AC_SUBST([PYTHON_INTERPRETER], [$PYTHON])], + [AC_MSG_ERROR([Integration tests enabled but python >= 3.0 executable not found.])] + ) + + AC_DEFINE([TCTI_PY],[1], [TCTI FOR PYTHON3 BASED TCTI IMPLEMENTATIONS]) + + PYLIB=[$(pkg-config --libs python3-embed | python3 -c 'import sys; print(f"lib{sys.stdin.read()[2:].strip()}.so")')] + AC_DEFINE_UNQUOTED([TCTI_PYLIB], ["$PYLIB"], + [Library to dlopen(3) for bug workaround]) +]) + +AM_CONDITIONAL([ENABLE_TCTI_PY], [test "x$enable_tcti_py" != xno]) +AS_IF([test "x$enable_tcti_py" = "xyes"], [handle_tcti_py]) + +# If building tcti-py with unit tests you need ESAPI +AS_IF([test "x$enable_tcti_py" = "xyes" -a "x$enable_unit" = "xyes" -a "x$enable_esys" != "xyes" ], + [AC_MSG_ERROR(["Must enable esys with tss2-py-tcti])]) + AC_ARG_ENABLE([tcti-fuzzing], [AS_HELP_STRING([--enable-tcti-fuzzing], [build the tcti-fuzzing module])],, @@ -754,4 +783,5 @@ AC_MSG_RESULT([ tcti_spidev $enable_tcti_spidev tcti_spi_ftdi $enable_tcti_spi_ftdi tcti_i2c_ftdi $enable_tcti_i2c_ftdi + tcti_py $enable_tcti_py ]) diff --git a/doc/tcti-py.md b/doc/tcti-py.md new file mode 100644 index 000000000..e74c01da7 --- /dev/null +++ b/doc/tcti-py.md @@ -0,0 +1,63 @@ +# Python TCTI (py-tcti) + +The Python TCTI can be used to write TCTI modules in Python3. This allows a user to make use of the +robust language features and modules that are available in Python3. + +## Invoking the Python TCTI + +Like implementing any TCTI, one can follow the friendly conventions or provide the full path of the shared +object to `Tss2_TCTILdr_Initialize` or one can instantiate the TCTI directly with `TSS2_TCTI_Py_Initialize`. + +One needs to specify a module name (the name of a python file) and optionally arguments to pass to an init +function in that module. The signature of this method is: `def tcti_init(args: str) -> Object` and the +args string passed in is the args option appended on the configuration string. + +One possible way to use it with the command line tools is via the `--tcti=py::`. For +example: +```bash +# Python3 file pytcti.py exists in $HOME +PYTHONPATH=$HOME tpm2_getcap --tcti=py:pytcti properties-fixed +``` + +## Example Python TCTI + +The below sample code TCTI just uses tpm2-pytss package to call TCTILdr with whatever +argument string is provided. It just showcases full path delivery of commands. To invoke +the example code below, assuming that the python file is named pytcti.py and you want +to connect to tpm2-abrmd resource manager do: +```bash +PYTHONPATH=$HOME tpm2_getcap --tcti=py:pytcti:tabrmd properties-fixed +``` + +```python3 +# SPDX-License-Identifier: BSD-2-Clause +from tpm2_pytss import TCTILdr + + +class MyPyTCTI(object): + def __init__(self, args: str): + c = args.split(":", maxsplit=1) + mod = c[0] + args = c[1] if len(c) > 1 else "None" + print(f"PYTHON: Initializing TCTI Ldr with mod: {mod} args: {args}") + self._tcti = TCTILdr(mod, args) + + @property + def magic(self): + # Optional Method + print("PYTHON magic") + return 42 + + def receive(self, timeout: int) -> bytes: + print("PYTHON receive") + return self._tcti.receive(timeout=timeout) + + def transmit(self, data: bytes): + print("PYTHON transmit") + self._tcti.transmit(data) + + +def tcti_init(args: str) -> MyPyTCTI: + print(f"PYTHON tcti_init called with: {args}") + return MyPyTCTI(args) +``` diff --git a/include/tss2/tss2_tcti_py.h b/include/tss2/tss2_tcti_py.h new file mode 100644 index 000000000..5c0b2f92c --- /dev/null +++ b/include/tss2/tss2_tcti_py.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +#ifndef TSS2_TCTI_CMD_H +#define TSS2_TCTI_CMD_H + +#include "tss2_tcti.h" + +#ifdef __cplusplus +extern "C" { +#endif + +TSS2_RC Tss2_Tcti_Py_Init ( + TSS2_TCTI_CONTEXT *tctiContext, + size_t *size, + const char *conf); + +#ifdef __cplusplus +} +#endif + +#endif /* TSS2_TCTI_CMD_H */ diff --git a/lib/tss2-tcti-py.def b/lib/tss2-tcti-py.def new file mode 100644 index 000000000..4e01b5d4e --- /dev/null +++ b/lib/tss2-tcti-py.def @@ -0,0 +1,4 @@ +LIBRARY tss2-tcti-py +EXPORTS + Tss2_Tcti_Info + Tss2_Tcti_Py_Init diff --git a/lib/tss2-tcti-py.map b/lib/tss2-tcti-py.map new file mode 100644 index 000000000..2ed89e768 --- /dev/null +++ b/lib/tss2-tcti-py.map @@ -0,0 +1,7 @@ +{ + global: + Tss2_Tcti_Info; + Tss2_Tcti_Py_Init; + local: + *; +}; diff --git a/lib/tss2-tcti-py.pc.in b/lib/tss2-tcti-py.pc.in new file mode 100644 index 000000000..cc173c681 --- /dev/null +++ b/lib/tss2-tcti-py.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: tss2-tcti-py +Description: TCTI library for implementing TCTIs in Python3. +URL: https://github.com/tpm2-software/tpm2-tss +Version: @VERSION@ +Cflags: -I${includedir} -I${includedir}/tss2 +Libs: -ltss2-py -L${libdir} diff --git a/man/tss2-tcti-py.7.in b/man/tss2-tcti-py.7.in new file mode 100644 index 000000000..423f1ff04 --- /dev/null +++ b/man/tss2-tcti-py.7.in @@ -0,0 +1,14 @@ +.\" Process this file with +.\" groff -man -Tascii foo.1 +.\" +.TH TCTI-PY 7 "JANUARY 2023" "TPM2 Software Stack" +.SH NAME +tcti-py \- Launch Python3 TPM2 TCTIs +.SH SYNOPSIS +Write a TCTI in Python3. +.SH DESCRIPTION +tcti-py is a library that allows creating TCTIs in Python3 by implementing a +a TCTI interface in Python. More complete documentation can be found by +visiting this URL: https://github.com/tpm2-software/tpm2-tss/tree/master/doc/tcti-py.md + +It is related to the TPM Command Transmission Interface Specification\*(rq specification. diff --git a/src/tss2-tcti/tcti-py.c b/src/tss2-tcti/tcti-py.c new file mode 100644 index 000000000..c532c991f --- /dev/null +++ b/src/tss2-tcti/tcti-py.c @@ -0,0 +1,628 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#define PY_SSIZE_T_CLEAN +#include + +#include "tss2_tcti_py.h" + +#include "tcti-py.h" +#include "tcti-common.h" +#define LOGMODULE tcti +#include "util/log.h" + +#include "util/tss2_endian.h" + +#define TCTI_PY_VERSION 2 + +#define SAFE_DECREF(x) \ + do { \ + Py_XDECREF(x); \ + x = NULL; \ + } while(0) + +/* + * I wanted to use constructor and destructor attributes for this, however, gcc was + * complaining that 'deconstructor' attribute directive ignored. So instead we + * just do a simple refcnt. + */ +static struct { + size_t refcnt; + pthread_mutex_t lock; + void *dlhandle; +} _global = { + .lock = PTHREAD_MUTEX_INITIALIZER +}; + +/* + * This function wraps the "up-cast" of the opaque TCTI context type to the + * type for the command TCTI context. If passed a NULL context the function + * returns a NULL ptr. The function doesn't check magic number anymore + * It should checked by the appropriate tcti_common_checks. + */ +static TSS2_TCTI_PY_CONTEXT* +tcti_py_context_cast (TSS2_TCTI_CONTEXT *tcti_ctx) +{ + if (tcti_ctx == NULL) + return NULL; + + return (TSS2_TCTI_PY_CONTEXT*) tcti_ctx; +} +/* + * This function down-casts the cmd TCTI context to the common context + * defined in the tcti-common module. + */ +static TSS2_TCTI_COMMON_CONTEXT* +tcti_py_down_cast (TSS2_TCTI_PY_CONTEXT *tcti_cmd) +{ + if (tcti_cmd == NULL) { + return NULL; + } + return &tcti_cmd->common; +} + +static TSS2_RC +tcti_py_get_poll_handles_not_implemented (TSS2_TCTI_CONTEXT *tctiContext, + TSS2_TCTI_POLL_HANDLE *handles, size_t *num_handles) +{ + UNUSED(tctiContext); + UNUSED(handles); + UNUSED(num_handles); + return TSS2_TCTI_RC_NOT_IMPLEMENTED; +} + +static TSS2_RC +tcti_py_transmit (TSS2_TCTI_CONTEXT *tcti_ctx, size_t size, + const uint8_t *buffer) +{ + TSS2_RC rc = TSS2_TCTI_RC_GENERAL_FAILURE; + + PyObject *py_buf = NULL; + TSS2_TCTI_PY_CONTEXT *tcti_py = tcti_py_context_cast (tcti_ctx); + TSS2_TCTI_COMMON_CONTEXT *tcti_common = tcti_py_down_cast (tcti_py); + + TSS2_RC r = tcti_common_transmit_checks (tcti_common, buffer, + TSS2_TCTI_MAGIC (tcti_common)); + if (r != TSS2_RC_SUCCESS) { + return r; + } + + py_buf = Py_BuildValue("(y#)", buffer, size); + if (!py_buf) { + rc = TSS2_TCTI_RC_MEMORY; + goto py_error; + } + + + PyObject_CallObject(tcti_py->methods.transmit, py_buf); + if(PyErr_Occurred()) { + goto py_error; + } + + tcti_common->state = TCTI_STATE_RECEIVE; + rc = TSS2_RC_SUCCESS; +out: + return rc; + +py_error: + PyErr_Print(); + SAFE_DECREF(py_buf); + goto out; +} + +static TSS2_RC +tcti_py_receive (TSS2_TCTI_CONTEXT *tctiContext, size_t *response_size, + unsigned char *response_buffer, int32_t timeout) +{ + PyObject *py_timeout = NULL, *py_response = NULL; + + TSS2_RC rc = TSS2_TCTI_RC_GENERAL_FAILURE; + TSS2_TCTI_PY_CONTEXT *tcti_py = tcti_py_context_cast (tctiContext); + TSS2_TCTI_COMMON_CONTEXT *tcti_common = tcti_py_down_cast (tcti_py); + + TSS2_RC r = tcti_common_receive_checks (tcti_common, response_size, + TSS2_TCTI_MAGIC (tcti_common)); + if (r != TSS2_RC_SUCCESS) { + return r; + } + + /* + * we we're called before and have a response that needs to be returned + * if we don't have a response, go fetch one from the Python TCTI + */ + if (!tcti_py->cur_response) { + + /* long is at least 32, but may be 64 so just don't pass int32_t + * or python may read too far if long is 64 + */ + long timeout_long = timeout; + py_timeout = Py_BuildValue("(l)", timeout_long); + if (!py_timeout) { + rc = TSS2_TCTI_RC_MEMORY; + goto py_error; + } + + + py_response = PyObject_CallObject(tcti_py->methods.receive, py_timeout); + if (!py_response) { + *response_size = 0; + goto py_error; + } + + if (!PyBytes_Check(py_response)) { + LOG_ERROR("Expected Python TCTI receive to return a bytes like object"); + rc = TSS2_TCTI_RC_BAD_VALUE; + goto error; + } + } else { + /* use our remembered response from before */ + py_response = tcti_py->cur_response; + /* current function takes ownership */ + tcti_py->cur_response = NULL; + } + + Py_ssize_t py_response_size = PyBytes_Size(py_response); + + /* + * State: No response buffer calling for size, + * Action: Remember this buffer until the next call, return OK + */ + if (!response_buffer) { + /* take ownership */ + tcti_py->cur_response = py_response; + py_response = NULL; + *response_size = py_response_size; + + } else if (py_response_size > (ssize_t)*response_size) { + /* + * State: Response buffer provided but too small + * Action: + * if buffer too small return TSS2_TCTI_RC_INSUFFICIENT_BUFFER and + * remember buffer for next call + */ + + /* take ownership */ + tcti_py->cur_response = py_response; + py_response = NULL; + + /* let caller know needed size */ + *response_size = py_response_size; + rc = TSS2_TCTI_RC_INSUFFICIENT_BUFFER; + goto error; + + } else { + + /* + * State: Buffer of sufficient size + * Action: Copy to user and forget remembered buffer + * + * Note: The current remembered buffer may or may not be set depending + * on if this function succeeded on the first try (not set) vs multiple + * calls. + */ + char *data = PyBytes_AsString(py_response); + if (!data) { + /* forget the internal buffer on error if set and NULL it */ + SAFE_DECREF(tcti_py->cur_response); + rc = TSS2_TCTI_RC_MEMORY; + goto py_error; + } + + /* copy to user */ + memcpy(response_buffer, data, py_response_size); + SAFE_DECREF(tcti_py->cur_response); + + + /* + * Executing code beyond this point transitions the state machine to + * TRANSMIT. Another call to this function will not be possible until + * another command is sent to the TPM. + */ + tcti_common->header.size = 0; + tcti_common->state = TCTI_STATE_TRANSMIT; + } + + rc = TSS2_RC_SUCCESS; +out: + return rc; + +py_error: + PyErr_Print(); +error: + SAFE_DECREF(py_response); + SAFE_DECREF(py_timeout); + goto out; +} + +static TSS2_RC +tcti_py_make_sticky (TSS2_TCTI_CONTEXT *tcti_ctx, TPM2_HANDLE *handle, uint8_t sticky) +{ + TSS2_RC rc = TSS2_TCTI_RC_GENERAL_FAILURE; + + PyObject *py_args = NULL, *py_result = NULL; + TSS2_TCTI_PY_CONTEXT *tcti_py = tcti_py_context_cast (tcti_ctx); + + if (!tcti_py->methods.make_sticky) { + return TSS2_TCTI_RC_NOT_IMPLEMENTED; + } + + py_args = Py_BuildValue("(kb)", *handle, sticky); + if (!py_args) { + rc = TSS2_TCTI_RC_MEMORY; + goto py_error; + } + + py_result = PyObject_CallObject(tcti_py->methods.make_sticky, py_args); + if(PyErr_Occurred()) { + goto py_error; + } + + if(!PyLong_Check(py_result)) { + LOG_ERROR("Expected make_sticky to return integer"); + goto error; + } + + long x = PyLong_AsUnsignedLong(py_result); + *handle = (TPM2_HANDLE)x; + + rc = TSS2_RC_SUCCESS; +out: + return rc; + +py_error: + PyErr_Print(); +error: + SAFE_DECREF(py_args); + SAFE_DECREF(py_result); + goto out; +} + +static TSS2_RC +tcti_py_set_locality (TSS2_TCTI_CONTEXT *tcti_ctx, uint8_t locality) +{ + TSS2_RC rc = TSS2_TCTI_RC_GENERAL_FAILURE; + + PyObject *py_locality = NULL; + TSS2_TCTI_PY_CONTEXT *tcti_py = tcti_py_context_cast (tcti_ctx); + + + if (!tcti_py->methods.set_locality) { + return TSS2_TCTI_RC_NOT_IMPLEMENTED; + } + + py_locality = Py_BuildValue("(i)", locality); + if (!py_locality) { + PyErr_Print(); + rc = TSS2_TCTI_RC_MEMORY; + goto py_error; + } + + PyObject_CallObject(tcti_py->methods.set_locality, py_locality); + if(PyErr_Occurred()) { + goto py_error; + } + + rc = TSS2_RC_SUCCESS; +out: + return rc; + +py_error: + PyErr_Print(); + SAFE_DECREF(py_locality); + goto out; +} + +static void +tcti_py_finalize (TSS2_TCTI_CONTEXT *tctiContext) +{ + TSS2_TCTI_PY_CONTEXT *tcti_py = tcti_py_context_cast (tctiContext); + + if (tcti_py == NULL) { + return; + } + + SAFE_DECREF(tcti_py->methods.set_locality); + SAFE_DECREF(tcti_py->methods.make_sticky); + SAFE_DECREF(tcti_py->methods.receive); + SAFE_DECREF(tcti_py->methods.transmit); + SAFE_DECREF(tcti_py->py_tcti); + SAFE_DECREF(tcti_py->cur_response); + + /* Decrement the reference count on PYTHON init */ + pthread_mutex_lock(&_global.lock); + assert(_global.refcnt != 0); + _global.refcnt--; + if (_global.refcnt == 0) { + Py_Finalize(); + if (_global.dlhandle) { + dlclose(_global.dlhandle); + _global.dlhandle = NULL; + } + } + pthread_mutex_unlock(&_global.lock); +} + +#define GET_TRANSMIT_FROM_PY(tcti_py) _get_method(tcti_py, &tcti_py->methods.transmit, "transmit", "transmit(self, data: bytes) -> None", false) +#define GET_RECIEVE_FROM_PY(tcti_py) _get_method(tcti_py, &tcti_py->methods.receive, "receive", "receive(self, timeout: int) -> bytes", false) +#define GET_MAKE_STICKY_FROM_PY(tcti_py) _get_method(tcti_py, &tcti_py->methods.make_sticky, "make_sticky", "make_sticky(self, handle: int, sticky: bool) -> int", true) +#define GET_SET_LOCALITY_FROM_PY(tcti_py) _get_method(tcti_py, &tcti_py->methods.set_locality, "set_locality", "set_locality(self, locality: int) -> None", true) +static TSS2_RC _get_method(TSS2_TCTI_PY_CONTEXT *tcti_py, PyObject **save, const char *method, const char *signature, bool none_ok) { + + PyObject *py_method = NULL; + + TSS2_RC rc = TSS2_TCTI_RC_NOT_IMPLEMENTED; + + py_method = PyObject_GetAttrString(tcti_py->py_tcti, method); + if (!py_method) { + if (none_ok) { + PyErr_Clear(); + LOG_DEBUG("Python TCTI does not implement method %s", method); + goto success; + } + LOG_ERROR("Expected Python TCTI to have method %s", signature); + goto py_error; + } + + if (!PyCallable_Check(py_method)) { + LOG_ERROR("Expected %s to be a callable method", signature); + goto py_error; + } + + /* Take Ownership of the PyObject method */ + *save = py_method; + py_method = NULL; + +success: + rc = TSS2_RC_SUCCESS; +out: + return rc; + +py_error: + PyErr_Print(); + SAFE_DECREF(py_method); + goto out; +} + +static TSS2_RC get_magic(TSS2_TCTI_PY_CONTEXT *tcti_py) { + + PyObject *py_magic = NULL; + TSS2_RC rc = TSS2_TCTI_RC_NOT_IMPLEMENTED; + TSS2_TCTI_COMMON_CONTEXT *tcti_common = tcti_py_down_cast (tcti_py); + + py_magic = PyObject_GetAttrString(tcti_py->py_tcti, "magic"); + if (!py_magic) { + LOG_ERROR("Expected module to implement attribute magic returning an 8 byte max integer"); + goto py_error; + } + + if (PyBytes_Check(py_magic)) { + /* they returned a byte buffer */ + Py_ssize_t len = 0; + char *buf = NULL; + + if(PyBytes_AsStringAndSize(py_magic, &buf, &len)) { + LOG_ERROR("Could not get magic bytes"); + goto py_error; + } + + char mag_buf[8] = { 0 }; + if ((size_t)len > sizeof(mag_buf) || len < 1) { + LOG_ERROR("Unexpected number of magic bytes"); + goto out; + } + + memcpy(mag_buf, buf, len); + TSS2_TCTI_MAGIC (tcti_common) = BE_TO_HOST_64(*((uint64_t *)mag_buf)); + } else if (PyLong_Check(py_magic)) { + /* they returned an integer */ + TSS2_TCTI_MAGIC (tcti_common) = PyLong_AsUnsignedLongLong(py_magic); + } else { + LOG_ERROR("Expected attribute magic to return 8 bytes or int"); + goto out; + } + + + rc = TSS2_RC_SUCCESS; + +py_error: + PyErr_Print(); +out: + SAFE_DECREF(py_magic); + + return rc; +} + +TSS2_RC tcti_py_init_py_module (TSS2_TCTI_PY_CONTEXT *tcti_py) { + + TSS2_RC rc = TSS2_TCTI_RC_NOT_IMPLEMENTED; + TSS2_TCTI_COMMON_CONTEXT *tcti_common = tcti_py_down_cast (tcti_py); + + /* optional methods */ + rc = GET_MAKE_STICKY_FROM_PY(tcti_py); + if (rc) { + goto error; + } + + rc = GET_SET_LOCALITY_FROM_PY(tcti_py); + if (rc) { + goto error; + } + + /* required methods */ + rc = get_magic(tcti_py); + if (rc) { + goto error; + } + + rc = GET_TRANSMIT_FROM_PY(tcti_py); + if (rc) { + goto error; + } + + rc = GET_RECIEVE_FROM_PY(tcti_py); + if (rc) { + goto error; + } + + TSS2_TCTI_VERSION (tcti_common) = TCTI_PY_VERSION; + TSS2_TCTI_TRANSMIT (tcti_common) = tcti_py_transmit; + TSS2_TCTI_RECEIVE (tcti_common) = tcti_py_receive; + TSS2_TCTI_FINALIZE (tcti_common) = tcti_py_finalize; + + TSS2_TCTI_MAKE_STICKY (tcti_common) = tcti_py_make_sticky; + TSS2_TCTI_SET_LOCALITY (tcti_common) = tcti_py_set_locality; + + /* No get_poll_handles for now, as it's OS specific we would need to replicate something like tpm2-pytss PollData */ + TSS2_TCTI_GET_POLL_HANDLES (tcti_common) = tcti_py_get_poll_handles_not_implemented; + + tcti_common->state = TCTI_STATE_TRANSMIT; + tcti_common->locality = 0; + + rc = TSS2_RC_SUCCESS; + +error: + return rc; + +} + +TSS2_RC +Tss2_Tcti_Py_Init (TSS2_TCTI_CONTEXT *tcti_context, size_t *size, + const char *conf) +{ + TSS2_RC rc = TSS2_TCTI_RC_GENERAL_FAILURE; + + TSS2_TCTI_PY_CONTEXT *tcti_py = tcti_py_context_cast(tcti_context); + + char *conf_copy = NULL; + PyObject *py_module = NULL, *py_arg = NULL; + + if (size == NULL) { + LOG_ERROR("size pointer must be valid"); + return TSS2_TCTI_RC_BAD_VALUE; + } + + if (!conf) { + LOG_ERROR("must have a module name to run in conf"); + return TSS2_TCTI_RC_BAD_VALUE; + } + + if (tcti_context == NULL) { + *size = sizeof (TSS2_TCTI_PY_CONTEXT); + return TSS2_RC_SUCCESS; + } + + memset(tcti_context, 0, *size); + + conf_copy = strdup(conf); + if (!conf_copy) { + return TSS2_TCTI_RC_MEMORY; + } + + char *arg = NULL; + char *module = conf_copy; + char *sep = strchr(conf_copy, ':'); + if (sep) { + *sep = '\0'; + arg = &sep[1]; + } + + LOG_DEBUG ("Initializing Python TCTI with module: \"%s\" arg \"%s\"", + module, arg ? arg : "(null)"); + + /* Increment the reference count on PYTHON init */ + pthread_mutex_lock(&_global.lock); + size_t new_cnt = 0; + bool overflow = __builtin_add_overflow(_global.refcnt, 1, &new_cnt); + if (!overflow) { + _global.refcnt = new_cnt; + if (new_cnt == 1) { + /* See: https://stackoverflow.com/questions/60719987/embedding-python-which-uses-numpy-in-c-doesnt-work-in-library-dynamically-loa */ + _global.dlhandle = dlopen (TCTI_PYLIB, RTLD_LAZY|RTLD_GLOBAL); + if (!_global.dlhandle) { + LOG_WARNING("Could not dlopen libpython3.so, some things may not work: %s", dlerror()); + } + Py_Initialize(); + } + } + pthread_mutex_unlock(&_global.lock); + if (overflow) { + LOG_ERROR("Max instance count limit reached"); + goto error; + } + + py_arg = Py_BuildValue("(z)", arg); + if (!py_arg) { + goto py_error; + } + + py_module = PyImport_ImportModule(module); + if (!py_module) { + goto py_error; + } + + /* Borrowed reference DO NOT DECREF */ + PyObject *py_dict = PyModule_GetDict(py_module); + if (!py_dict) { + goto py_error; + } + + /* + * modules implement a method called tcti_init(args) -> Object + * Where the returned object implements the required attributes. + * This is also a borrowd ref DO NOT DECREF + */ + PyObject *py_initfn = PyDict_GetItemString(py_dict, (char*)"tcti_init"); + if (!py_initfn) { + goto py_error; + } + + if (!PyCallable_Check(py_initfn)) { + goto py_error; + } + + /* This is the instance object that implements the TCTI */ + tcti_py->py_tcti = PyObject_CallObject(py_initfn, py_arg); + if (!tcti_py->py_tcti) { + goto py_error; + } + + rc = tcti_py_init_py_module (tcti_py); + if (rc != TSS2_RC_SUCCESS) { + goto py_error; + } + + rc = TSS2_RC_SUCCESS; + + /* Do NOT DECREMENT py_dict/py_initfn borrowed ref */ + SAFE_DECREF(py_module); + + return rc; + +py_error: + PyErr_Print(); +error: + SAFE_DECREF(py_module); + tcti_py_finalize(tcti_context); + return rc; +} + +/* public info structure */ +const TSS2_TCTI_INFO tss2_tcti_info = { + .version = TCTI_PY_VERSION, + .name = TCTI_PY_NAME, + .description = TCTI_PY_DESCRIPTION, + .config_help = TCTI_PY_HELP, + .init = Tss2_Tcti_Py_Init, +}; + +const TSS2_TCTI_INFO* +Tss2_Tcti_Info (void) +{ + return &tss2_tcti_info; +} diff --git a/src/tss2-tcti/tcti-py.h b/src/tss2-tcti/tcti-py.h new file mode 100644 index 000000000..6661a86f8 --- /dev/null +++ b/src/tss2-tcti/tcti-py.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ + +#ifndef TCTI_CMD_H +#define TCTI_CMD_H + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include + +#include "tcti-common.h" +#include "util-io/io.h" + +#define TCTI_PY_NAME "tcti-py" +#define TCTI_PY_DESCRIPTION "TCTI module for writing a TCTI in Python3." +#define TCTI_PY_HELP "String used as module:arg passed to tcti_init(arg: str) -> TCTI Object." + +typedef struct TSS2_TCTI_PY_CONTEXT TSS2_TCTI_PY_CONTEXT; +struct TSS2_TCTI_PY_CONTEXT { + TSS2_TCTI_COMMON_CONTEXT common; + PyObject *py_tcti; + PyObject *cur_response; + struct { + /* no finalize as __del__ will be called for the TCTI instance */ + PyObject *transmit; + PyObject *receive; + PyObject *make_sticky; + PyObject *set_locality; + } methods; +}; + +#endif /* TCTI_CMD_H */ diff --git a/test/helper/pytcti.py b/test/helper/pytcti.py new file mode 100644 index 000000000..a7353cd11 --- /dev/null +++ b/test/helper/pytcti.py @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: BSD-2-Clause + + +class MyPyTCTI(object): + def __init__(self, args: str): + print(f"PyTCTI ARGS: {args}") + if args == "init": + raise RuntimeError(f"Failing in {args}") + + self._fail_fn = args + + @property + def magic(self): + if self._fail_fn == "magic": + raise RuntimeError("Failing in magic") + # Optional attribute + return 42 + + def receive(self, timeout: int) -> bytes: + if self._fail_fn == "receive": + raise RuntimeError("Failing in receive") + + data = b"\x80\x01\x00\x00\x01\x83\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00.\x00\x00\x01\x002.0\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x01\x02\x00\x00\x00\xa4\x00\x00\x01\x03\x00\x00\x00K\x00\x00\x01\x04\x00\x00\x07\xe5\x00\x00\x01\x05IBM\x00\x00\x00\x01\x06SW \x00\x00\x01\x07 TPM\x00\x00\x01\x08\x00\x00\x00\x00\x00\x00\x01\t\x00\x00\x00\x00\x00\x00\x01\n\x00\x00\x00\x01\x00\x00\x01\x0b \x19\x10#\x00\x00\x01\x0c\x00\x1666\x00\x00\x01\r\x00\x00\x04\x00\x00\x00\x01\x0e\x00\x00\x00\x03\x00\x00\x01\x0f\x00\x00\x00\x07\x00\x00\x01\x10\x00\x00\x00\x03\x00\x00\x01\x11\x00\x00\x00@\x00\x00\x01\x12\x00\x00\x00\x18\x00\x00\x01\x13\x00\x00\x00\x03\x00\x00\x01\x14\xff\xff\xff\xff\x00\x00\x01\x16\x00\x00\x00\x00\x00\x00\x01\x17\x00\x00\x08\x00\x00\x00\x01\x18\x00\x00\x00\x06\x00\x00\x01\x19\x00\x00\x10\x00\x00\x00\x01\x1a\x00\x00\x00\r\x00\x00\x01\x1b\x00\x00\x00\x06\x00\x00\x01\x1c\x00\x00\x01\x00\x00\x00\x01\x1d\x00\x00\x00\xff\x00\x00\x01\x1e\x00\x00\x10\x00\x00\x00\x01\x1f\x00\x00\x10\x00\x00\x00\x01 \x00\x00\x00@\x00\x00\x01!\x00\x00\n\x84\x00\x00\x01\"\x00\x00\x01\x94\x00\x00\x01#2.0\x00\x00\x00\x01$\x00\x00\x00\x00\x00\x00\x01%\x00\x00\x00\xa4\x00\x00\x01&\x00\x00\x00K\x00\x00\x01'\x00\x00\x07\xe5\x00\x00\x01(\x00\x00\x00\x80\x00\x00\x01)\x00\x00\x00n\x00\x00\x01*\x00\x00\x00n\x00\x00\x01+\x00\x00\x00\x00\x00\x00\x01,\x00\x00\x04\x00\x00\x00\x01-\x00\x00\x00\x00\x00\x00\x01.\x00\x00\x04\x00" + return data + + def transmit(self, data: bytes): + if self._fail_fn == "transmit": + raise RuntimeError("Failing in transmit") + + def make_sticky(self, handle:int, sticky: bool): + if self._fail_fn == "make_sticky": + raise RuntimeError("Failing in make_sticky") + return handle + int(sticky) + + def set_locality(self, locality: int): + if self._fail_fn == "set_locality": + raise RuntimeError("Failing in set_locality") + if locality != 42: + raise ValueError("Expected locality to be 42") + +def tcti_init(args: str) -> MyPyTCTI: + return MyPyTCTI(args) diff --git a/test/unit/tcti-py.c b/test/unit/tcti-py.c new file mode 100644 index 000000000..0e391f16b --- /dev/null +++ b/test/unit/tcti-py.c @@ -0,0 +1,296 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "tss2/tss2_esys.h" +#include "tss2/tss2_mu.h" +#include "tss2/tss2_tcti_py.h" + +#include "tss2-tcti/tcti-py.h" + +/* test/helper/pytcti.py */ +#define PY_TCTI "pytcti" + +static char expected_response[] = { + 0x00, + 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x01, 0x00, 0x32, + 0x2e, 0x30, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0xa4, 0x00, 0x00, 0x01, 0x03, 0x00, + 0x00, 0x00, 0x4b, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x07, 0xe5, 0x00, + 0x00, 0x01, 0x05, 0x49, 0x42, 0x4d, 0x00, 0x00, 0x00, 0x01, 0x06, 0x53, + 0x57, 0x20, 0x20, 0x00, 0x00, 0x01, 0x07, 0x20, 0x54, 0x50, 0x4d, 0x00, + 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x09, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0a, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x01, 0x0b, 0x20, 0x19, 0x10, 0x23, 0x00, 0x00, 0x01, 0x0c, 0x00, + 0x16, 0x36, 0x36, 0x00, 0x00, 0x01, 0x0d, 0x00, 0x00, 0x04, 0x00, 0x00, + 0x00, 0x01, 0x0e, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x0f, 0x00, + 0x00, 0x00, 0x07, 0x00, 0x00, 0x01, 0x10, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x01, 0x11, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x01, 0x12, 0x00, + 0x00, 0x00, 0x18, 0x00, 0x00, 0x01, 0x13, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x01, 0x14, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x01, 0x16, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x17, 0x00, 0x00, 0x08, 0x00, 0x00, + 0x00, 0x01, 0x18, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x01, 0x19, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x1a, 0x00, 0x00, 0x00, 0x0d, 0x00, + 0x00, 0x01, 0x1b, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x01, 0x1c, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x1d, 0x00, 0x00, 0x00, 0xff, 0x00, + 0x00, 0x01, 0x1e, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x1f, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x01, 0x20, 0x00, 0x00, 0x00, 0x40, 0x00, + 0x00, 0x01, 0x21, 0x00, 0x00, 0x0a, 0x84, 0x00, 0x00, 0x01, 0x22, 0x00, + 0x00, 0x01, 0x94, 0x00, 0x00, 0x01, 0x23, 0x32, 0x2e, 0x30, 0x00, 0x00, + 0x00, 0x01, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x25, 0x00, + 0x00, 0x00, 0xa4, 0x00, 0x00, 0x01, 0x26, 0x00, 0x00, 0x00, 0x4b, 0x00, + 0x00, 0x01, 0x27, 0x00, 0x00, 0x07, 0xe5, 0x00, 0x00, 0x01, 0x28, 0x00, + 0x00, 0x00, 0x80, 0x00, 0x00, 0x01, 0x29, 0x00, 0x00, 0x00, 0x6e, 0x00, + 0x00, 0x01, 0x2a, 0x00, 0x00, 0x00, 0x6e, 0x00, 0x00, 0x01, 0x2b, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x2c, 0x00, 0x00, 0x04, 0x00, 0x00, + 0x00, 0x01, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x2e, 0x00, + 0x00, 0x04, 0x00 +}; + +static void +tcti_py_init_context_and_size_null_test (void **state) +{ + TSS2_RC rc; + + rc = Tss2_Tcti_Py_Init (NULL, NULL, NULL); + assert_int_equal (rc, TSS2_TCTI_RC_BAD_VALUE); +} + +static void +tcti_py_init_size_test (void **state) +{ + size_t tcti_size = 0; + TSS2_RC rc; + + rc = Tss2_Tcti_Py_Init (NULL, &tcti_size, "foomod"); + assert_int_equal (rc, TSS2_RC_SUCCESS); + assert_int_equal (tcti_size, sizeof (TSS2_TCTI_PY_CONTEXT)); +} + +static void +tcti_py_test_cmd (void **state) +{ + TSS2_RC rc; + + char _tcti[sizeof(TSS2_TCTI_PY_CONTEXT)]; + size_t tcti_size = sizeof(_tcti); + TSS2_TCTI_CONTEXT *tcti = (TSS2_TCTI_CONTEXT *)&_tcti; + rc = Tss2_Tcti_Py_Init (tcti, &tcti_size, PY_TCTI); + assert_int_equal (rc, TSS2_RC_SUCCESS); + + ESYS_CONTEXT *ectx = NULL; + rc = Esys_Initialize(&ectx, tcti, NULL); + assert_int_equal (rc, TSS2_RC_SUCCESS); + + TPMI_YES_NO yes_no = TPM2_NO; + TPMS_CAPABILITY_DATA *cap_data = NULL; + rc = Esys_GetCapability(ectx, + ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, + TPM2_CAP_TPM_PROPERTIES, TPM2_PT_FIXED, TPM2_MAX_TPM_PROPERTIES, + &yes_no, &cap_data); + assert_int_equal (rc, TSS2_RC_SUCCESS); + assert_non_null(cap_data); + + size_t offset = 0; + uint8_t buf[sizeof(*cap_data)]; + rc = Tss2_MU_TPMS_CAPABILITY_DATA_Marshal(cap_data, buf, sizeof(buf), &offset); + Esys_Free(cap_data); + assert_int_equal (rc, TSS2_RC_SUCCESS); + + assert_int_equal(offset, sizeof(expected_response)); + assert_memory_equal(buf, expected_response, offset); +} + +static void +tcti_py_test_make_sticky_good (void **state) +{ + TSS2_RC rc; + + char _tcti[sizeof(TSS2_TCTI_PY_CONTEXT)]; + size_t tcti_size = sizeof(_tcti); + TSS2_TCTI_CONTEXT *tcti = (TSS2_TCTI_CONTEXT *)&_tcti; + rc = Tss2_Tcti_Py_Init (tcti, &tcti_size, PY_TCTI); + assert_int_equal (rc, TSS2_RC_SUCCESS); + + TPM2_HANDLE handle = 0xbadcc0de; + rc = Tss2_Tcti_MakeSticky(tcti, &handle, 1); + assert_int_equal (rc, TSS2_RC_SUCCESS); + assert_int_equal(handle, 0xbadcc0de + 1); + + handle = 0xdeadbeef; + rc = Tss2_Tcti_MakeSticky(tcti, &handle, 0); + assert_int_equal (rc, TSS2_RC_SUCCESS); + assert_int_equal(handle, 0xdeadbeef); +} + +static void +tcti_py_test_make_sticky_bad (void **state) +{ + TSS2_RC rc; + + char _tcti[sizeof(TSS2_TCTI_PY_CONTEXT)]; + size_t tcti_size = sizeof(_tcti); + TSS2_TCTI_CONTEXT *tcti = (TSS2_TCTI_CONTEXT *)&_tcti; + rc = Tss2_Tcti_Py_Init (tcti, &tcti_size, PY_TCTI":make_sticky"); + assert_int_equal (rc, TSS2_RC_SUCCESS); + + TPM2_HANDLE handle = 0xbadcc0de; + rc = Tss2_Tcti_MakeSticky(tcti, &handle, 1); + assert_int_not_equal (rc, TSS2_RC_SUCCESS); + /* value should be left untouched on failure */ + assert_int_equal(handle, 0xbadcc0de); +} + +static void +tcti_py_test_set_locality_good (void **state) +{ + TSS2_RC rc; + + char _tcti[sizeof(TSS2_TCTI_PY_CONTEXT)]; + size_t tcti_size = sizeof(_tcti); + TSS2_TCTI_CONTEXT *tcti = (TSS2_TCTI_CONTEXT *)&_tcti; + rc = Tss2_Tcti_Py_Init (tcti, &tcti_size, PY_TCTI); + assert_int_equal (rc, TSS2_RC_SUCCESS); + + rc = Tss2_Tcti_SetLocality(tcti, 42); + assert_int_equal (rc, TSS2_RC_SUCCESS); +} + +static void +tcti_py_test_set_locality_bad (void **state) +{ + TSS2_RC rc; + + char _tcti[sizeof(TSS2_TCTI_PY_CONTEXT)]; + size_t tcti_size = sizeof(_tcti); + TSS2_TCTI_CONTEXT *tcti = (TSS2_TCTI_CONTEXT *)&_tcti; + rc = Tss2_Tcti_Py_Init (tcti, &tcti_size, PY_TCTI":set_locality"); + assert_int_equal (rc, TSS2_RC_SUCCESS); + + rc = Tss2_Tcti_SetLocality(tcti, 42); + assert_int_not_equal (rc, TSS2_RC_SUCCESS); +} + +static void +tcti_py_test_tcti_init_exception (void **state) +{ + TSS2_RC rc; + + char _tcti[sizeof(TSS2_TCTI_PY_CONTEXT)]; + size_t tcti_size = sizeof(_tcti); + TSS2_TCTI_CONTEXT *tcti = (TSS2_TCTI_CONTEXT *)&_tcti; + + rc = Tss2_Tcti_Py_Init (tcti, &tcti_size, PY_TCTI":init"); + assert_int_not_equal (rc, TSS2_RC_SUCCESS); +} + +static void +tcti_py_test_tcti_recv_exception (void **state) +{ + TSS2_RC rc; + + char _tcti[sizeof(TSS2_TCTI_PY_CONTEXT)]; + size_t tcti_size = sizeof(_tcti); + TSS2_TCTI_CONTEXT *tcti = (TSS2_TCTI_CONTEXT *)&_tcti; + + rc = Tss2_Tcti_Py_Init (tcti, &tcti_size, PY_TCTI":receive"); + assert_int_equal (rc, TSS2_RC_SUCCESS); + + ESYS_CONTEXT *ectx = NULL; + rc = Esys_Initialize(&ectx, tcti, NULL); + assert_int_equal (rc, TSS2_RC_SUCCESS); + + TPMI_YES_NO yes_no = TPM2_NO; + TPMS_CAPABILITY_DATA *cap_data = NULL; + rc = Esys_GetCapability(ectx, + ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, + TPM2_CAP_TPM_PROPERTIES, TPM2_PT_FIXED, TPM2_MAX_TPM_PROPERTIES, + &yes_no, &cap_data); + assert_int_not_equal (rc, TSS2_RC_SUCCESS); +} + +static void +tcti_py_test_tcti_transmit_exception (void **state) +{ + TSS2_RC rc; + + char _tcti[sizeof(TSS2_TCTI_PY_CONTEXT)]; + size_t tcti_size = sizeof(_tcti); + TSS2_TCTI_CONTEXT *tcti = (TSS2_TCTI_CONTEXT *)&_tcti; + + rc = Tss2_Tcti_Py_Init (tcti, &tcti_size, PY_TCTI":transmit"); + assert_int_equal (rc, TSS2_RC_SUCCESS); + + ESYS_CONTEXT *ectx = NULL; + rc = Esys_Initialize(&ectx, tcti, NULL); + assert_int_equal (rc, TSS2_RC_SUCCESS); + + TPMI_YES_NO yes_no = TPM2_NO; + TPMS_CAPABILITY_DATA *cap_data = NULL; + rc = Esys_GetCapability(ectx, + ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, + TPM2_CAP_TPM_PROPERTIES, TPM2_PT_FIXED, TPM2_MAX_TPM_PROPERTIES, + &yes_no, &cap_data); + assert_int_not_equal (rc, TSS2_RC_SUCCESS); +} + +static void +tcti_py_test_tcti_finalize_good (void **state) +{ + TSS2_RC rc; + + char _tcti[sizeof(TSS2_TCTI_PY_CONTEXT)]; + size_t tcti_size = sizeof(_tcti); + TSS2_TCTI_CONTEXT *tcti = (TSS2_TCTI_CONTEXT *)&_tcti; + + rc = Tss2_Tcti_Py_Init (tcti, &tcti_size, PY_TCTI); + assert_int_equal (rc, TSS2_RC_SUCCESS); + + Tss2_Tcti_Finalize(tcti); +} + +static void +tcti_py_test_tcti_finalize_bad (void **state) +{ + Tss2_Tcti_Finalize(NULL); +} + +static int group_setup(void **state) +{ + return setenv("PYTHONPATH", "test/helper", 1); +} + +int +main (int argc, + char *argv[]) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test (tcti_py_init_context_and_size_null_test), + cmocka_unit_test (tcti_py_init_size_test), + cmocka_unit_test (tcti_py_test_cmd), + cmocka_unit_test (tcti_py_test_tcti_init_exception), + cmocka_unit_test (tcti_py_test_tcti_recv_exception), + cmocka_unit_test (tcti_py_test_tcti_transmit_exception), + cmocka_unit_test (tcti_py_test_tcti_finalize_good), + cmocka_unit_test (tcti_py_test_tcti_finalize_bad), + cmocka_unit_test (tcti_py_test_make_sticky_good), + cmocka_unit_test (tcti_py_test_make_sticky_bad), + cmocka_unit_test (tcti_py_test_set_locality_good), + cmocka_unit_test (tcti_py_test_set_locality_bad), + }; + + return cmocka_run_group_tests (tests, group_setup, NULL); +}