TCG TSS2 async APIs and event driven programming

API design is hard. Over the past year I’ve been contributing to an effort to standardize a set of APIs for interacting with TPM2 devices through the Trusted Computing Group’s (TCG) TPM2 Software Stack (TSS) working group (WG). The available APIs and implementations predate my involvement but I’m hoping to contribute some use cases to motivate the need for various aspects of the API and architecture. According to Joshua Bloch use-cases are core to API design so I figure having some thoughts and examples documented and available would be helpful.

Use-case driven design: asynchronous function calls

After watching Mr. Bloch’s talk a few times in the last year, the API design principle that’s stuck with me most is that the design should be motivated / driven by use cases. A use-case adopted by the TSS WG that I’ve found particularly interesting is support for event driven programming environments / frameworks. On several occasions this use case has come into question and generally the argument against the feature comes from the trade-offs involved: detractors argue that the utility of and demand for the use case does not outweigh the complexity that it introduces to the API.

But even though event driven programming is integral to many languages and frameworks, we don’t have any examples of applications using the TPM in such an environment. There aren’t many applications using the TPM to begin with and since the TSS for TPM 1.2 devices never supported async I/O (AFAIK) there was never an opportunity to do so. Since the TSS2 APIs include support for asynchronous operations I thought it was worth while to write some example code to demonstrate use of this part of the API in a popular programming framework.

You know what they say:

Integrating SAPI with GLib and GIO

The TSS2 APIs are limited to C. While some bindings to higher level languages have been emerging I’m expecting that C will be the primary language for interacting with the TPM for some time. There is no shortage of options for event frameworks in C, but for the purposes of this post I’m going to focus exclusively on GLib and the GIO libraries.

Before we dive any deeper though I want to include a quick note on event driven programming in general: The term “event driven” doesn’t necessarily mean asynchronous or even non-blocking. Further, not all asynchronous I/O operations are truly asynchronous. Many depend on polling file descriptors with some help from the kernel through the select or poll system calls. I don’t intend to get into the merits or purity of the various approaches though. In this post we’re just using the tools available in a way that works with GLib and GIO. If you’re interested in a “deep-dive” into event driven programming in C there are much better resources out there: start here

GLib and GIO primitives

At the heart of the GLib event model are 3 objects: the GMainLoop, the associated GMainContext and the GSource. These objects are very well documented so I’m going to assume general familiarity. For our purposes we only need to know a few things:

  1. GMainLoop is a thread that processes events. If used properly the GMainLoop will handle the asynchronous I/O operations so we don’t have to polling or threading code ourselves.
  2. GMainContext is a container used by the GMainLoop. It holds a collection of GSources that make up the events in the GMainLoop.
  3. GSource is an object that holds a number of function pointers that are invoked in response to some event.

Let’s do a quick example to illustrate before we mix in the TSS2 stuff. The following code is an example of a GMainLoop with a single timeout event source. The event source will invoke a callback after a specified time. The timeout event will occur on the interval so long as the callback returns G_SOURCE_CONTINUE.

#include 
#include 

#define TIMEOUT_COUNT_MAX 10
#define TIMEOUT_INTERVAL  100

typedef struct {
    GMainLoop *loop;
    size_t timeout_count;
} data_t;

gboolean
timeout_callback (gpointer user_data)
{
    data_t *data = (data_t*)user_data;

    g_print ("timeout_count: %zun", data->timeout_count);
    if (data->timeout_count timeout_count; 
        return G_SOURCE_CONTINUE;
    } else {
        g_main_loop_quit (data->loop);
        return G_SOURCE_REMOVE;
    }
}
int
main (void)
{
    GMainContext *context;
    GSource *source;
    data_t data = { 0 };

    source = g_timeout_source_new (TIMEOUT_INTERVAL);
    g_source_set_callback (source, timeout_callback, &data, NULL);
    context = g_main_context_new ();
    g_source_attach (source, context);
    g_source_unref (source);

    data.loop = g_main_loop_new (context, FALSE);
    g_main_context_unref (context);

    g_main_loop_run (data.loop);
    g_main_loop_unref (data.loop);
    return 0;
}

In ~40 LOC we have an event loop triggering a timeout on a fixed interval. After a fixed number of timeout events the program shuts itself down cleanly. Not bad for C. It’s worth noting as well that GLib has some convenience functions (in this case g_timeout_add) that would have saved us a few lines here but I’ve opted to do those bits manually to make future examples more clearly relatable to this one.

Event-driven TSS2 function calls

Now that we have the basic tools required to use a GMainLoop for asynchronous events we’ll play around with adapting them for use with asynchronous calls in the SAPI library. We’ll make this next step a small one though. We won’t go straight to creating GObjects to plug into the GMainLoop. Instead let’s take a look at *how* the TSS2 libraries expose the primitives we need to make async calls and *what* those primitives are.

Asynchronous TSS2

The TCG TSS2 APIs all work together to provide the mechanisms necessary to make asynchronous calls. Each layer in the stack has a purpose and an asynchronous mechanism for carrying out that purpose. From the perspective of an application, the lowest layer is the TCTI library. This API is extremely small and provides transmit / receive functions as an interface to the underlying IPC mechanism. This API can be used by higher layers to send TPM2 command buffers and receive the associated response without having to worry about the underlying implementation.

If the underlying IPC mechanism used by the TCTI library is capable of asynchronous operation then the TCTI may expose non-blocking behavior to the client. The TCTI API provides only one function capable of non-blocking I/O: The `receive` function can be passed a timeout value that will cause the calling thread to block for some time waiting for a response. If no response is received before the timeout then the caller will be returned a response code instructing them to “try again”. If this timeout is provided a timeout value of TSS2_TCTI_TIMEOUT_NONE then the function will not block at all. This is about as simple as non-blocking I/O can get.

Additionally, TCTI libraries provide the getPollHandle function. This function returns the TSS2_TCTI_POLL_HANDLE type which we map to an asynchronous I/O mechanism for various platforms. On Linux it defined as struct pollfd. On Windows it would make sense to map it to the HANDLE type.

But the TSS2_TCTI_POLL_HANDLE isn’t intended for use by the typical developer. Instead it’s intended for use as a mechanism to integrate the TSS2 stack into event driven frameworks. So if the TCTI mechanism works as advertised we should be able to use this type to drive events in the GLib main loop.

Asynchronous SAPI function calls

The mechanisms from the TCTI layer are necessary for asynchronous interaction with TPM2 devices but alone they’re not sufficient. The layers above the TCTI in the TSS2 architecture must participate by providing asynchronous versions of each function. Using the SAPI as an example, each TPM2 function is mapped to a SAPI function call. But the SAPI provides not only a synchronous version of the function call (let’s use Tss2_Sys_GetCapability in this example), but also an asynchronous version.

For our the TPM2 GetCapability function the SAPI header provides:

  1. Tss2_Sys_GetCapability – A synchronous function that will send a command to the TPM2 and block until a response is ready to be passed back to the caller.
  2. Tss2_Sys_GetCapability_Prepare – A function to prepare / construct the TPM2 command buffer from the function parameters before they’re transmitted to the TPM.
  3. Tss2_Sys_GetCapability_Complete – A function to convert the response received by the _ExecuteFinish from the byte stream representation to C structures.

Additionally the utility functions are required:

  1. Tss2_Sys_ExecuteAsync – A generic function to send the previously prepared TPM2 command buffer to the TPM. It will only call the underlying TCTI transmit function.
  2. Tss2_Sys_ExecuteFinish – A generic function to receive the TPM2 response buffer (the response to the command previously sent). The second parameter to this function is a timeout value that can be set to non-blocking.

These functions provide us with all we need to create the TPM2 command buffer, transmit it, receive the response and transform the response buffer into C structures that we can use in our program.
The event mechanism is able to trigger an event for us when there’s data ready on the struct pollfd from the underlying TCTI module.

Combining GSource and the TSS2

We’ve effectively enumerated the tools at our disposal. Now we put them together into a very minimal example. Our goal here isn’t to get perfect integration with the GLib object model, just to use the available tools to hopefully demonstrate the concept.

The TCTI layer and the TSS2_TCTI_POLL_HANDLE type mapping to the struct pollfd was the big hint in this whole thing. Using the pollfd structure we can extract the underlying file descriptor and use this well known IPC interface to learn of events from the fd. But before we set off to implement a GSource to drive events for the SAPI let’s see if there’s an existing one that we might use. File descriptors (fds) are pretty common on *nix platforms so it makes sense that there may already be a tool we can use here.

Sure enough the GLib UNIX specific utilities and integration provides us with just this. All we must do is provide the g_unix_fd_source_new function with the fd from the underlying TCTI while requesting the event be triggered when the G_IO_IN (input data ready) condition becomes true for the fd.

NOTE: Before we get too far though it’s important to point out that the TCTI we use for this example is libtcti-tabrmd. This TCTI requires the tpm2-abrmd user space resource management daemon. The other TCTIs for communicating with the kernel device driver and the TPM2 emulator do not support asynchronous I/O. The kernel driver does not support asynchronous I/O. We use a few utility functions for creating the TCTI and SAPI contexts.

The header:

#include 
#include 
    
/*  
 * Allocate and initialize an instance of the TCTI module associated with the
 * tpm2-abrmd. A successful call to this function will return a TCTI context
 * allocated by the function. It must be freed by the caller.
 * A failed call to this function will return NULL.
 */ 
TSS2_TCTI_CONTEXT*
tcti_tabrmd_init ();
    
/*  
 * Allocate and initialize an instance of a SAPI context. This context will be
 * configured to use the provided TCTI context. A successful call to this
 * function will return a SAPI context allocated by the function. It must be
 * freed by the caller.
 * A failed call to this function will return NULL.
 */
TSS2_SYS_CONTEXT*
sapi_init_from_tcti_ctx (TSS2_TCTI_CONTEXT *tcti_ctx);

/*
 * Allocate and initialize an instance of a TCTI and SAPI context. The SAPI
 * context will be configured to use a newly allocated instance of the tabrmd
 * TCTI. A successful call to this function will return a TCTI and SAPI
 * context allocated by the function. Both must be freed by the caller.
 * A failed call to this function will return NULL.
 */
TSS2_SYS_CONTEXT*
sapi_init_tabrmd (void);

And the implementation:

#include 
#include 
#include 
#include 
    
#include 
#include 
#include 

#include "context-util.h"

TSS2_TCTI_CONTEXT*
tcti_tabrmd_init ()
{   
    TSS2_RC rc;
    TSS2_TCTI_CONTEXT *tcti_ctx;
    size_t size;
    
    rc = tss2_tcti_tabrmd_init (NULL, &size);
    if (rc != TSS2_RC_SUCCESS) {
        g_critical ("Failed to get allocation size for tabrmd TCTI context: "
                    "0x%" PRIx32, rc);
        return NULL;
    }
    tcti_ctx = calloc (1, size);
    if (tcti_ctx == NULL) {
        g_critical ("Allocation for TCTI context failed: %s",
                    strerror (errno));
        return NULL;
    }
    rc = tss2_tcti_tabrmd_init (tcti_ctx, &size);
    if (rc != TSS2_RC_SUCCESS) {
        g_critical ("Failed to initialize tabrmd TCTI context: 0x%" PRIx32,
                    rc);
        free (tcti_ctx);
        return NULL;
    }
    return tcti_ctx;
}
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) {
        g_critical ("Failed to allocate 0x%zx bytes for the SAPI contextn",
                    size);
        return NULL;
    }
    rc = Tss2_Sys_Initialize (sapi_ctx, size, tcti_ctx, &abi_version);
    if (rc != TSS2_RC_SUCCESS) {
        g_critical ("Failed to initialize SAPI context: 0x%xn", rc);
        free (sapi_ctx);
        return NULL;
    }
    return sapi_ctx;
}
TSS2_SYS_CONTEXT*
sapi_init_tabrmd (void) {
    TSS2_SYS_CONTEXT  *sapi_context;
    TSS2_TCTI_CONTEXT *tcti_context;

    tcti_context = tcti_tabrmd_init ();
    if (tcti_context == NULL) {
        return NULL;
    }
    sapi_context = sapi_init_from_tcti_ctx (tcti_context);
    if (sapi_context == NULL) {
        free (tcti_context);
        return NULL;
    }
    return sapi_context;
}

By combining all of this we can create a simple program that makes an asynchronous SAPI function call. At a high level our goal is to execute a TPM2 command without blocking. When the TPM2 device sends our application a response we want the GLib event loop to invoke our callback so that we can do something with the response. We accomplish this with roughly the following steps:

  1. Create the TCTI & SAPI contexts.
  2. Create a GSource for the fd associated with the TCTI using the g_unix_fd_source_new GSource constructor.
  3. Connect the GSource with the GMainContext and register a callback.
  4. Create the GMainLoop and associate it with the proper context.
  5. Make the asynchronous SAPI function call.
  6. Start the GMainLoop.

When data is ready in the TCTI our callback will be invoked and it will do the following:

  1. Finish the SAPI function call.
  2. Dump out some data returned from the SAPI function call.
  3. Quit the main loop (will terminate the program).

Here’s the code:

#include 
#include 
#include 
#include  
#include 
#include  
    
#include "context-util.h"

typedef struct { 
    TSS2_SYS_CONTEXT *sapi_context;
    GMainLoop *loop;
} data_t;
    
gboolean
fd_callback (gint fd,
             GIOCondition condition,
             gpointer user_data)
{
    data_t *data = (data_t*)user_data;
    TSS2_RC rc;
    TPMI_YES_NO more_data;
    TPMS_CAPABILITY_DATA capability_data = { 0 };

    rc = Tss2_Sys_ExecuteFinish (data->sapi_context, 0);
    rc = Tss2_Sys_GetCapability_Complete (data->sapi_context,
                                          &more_data,
                                          &capability_data);
    g_print ("Capability: 0x%" PRIx32 "n", capability_data.capability);
    g_print ("Capability data command count: %" PRIu32 "n",
             capability_data.data.command.count);
    g_main_loop_quit (data->loop);
    return G_SOURCE_REMOVE;
}
int
main (void)
{
    GMainContext *context;
    GSource *source;
    TSS2_TCTI_CONTEXT *tcti_context;
    TSS2_RC rc;
    TSS2_TCTI_POLL_HANDLE poll_handles[1];
    data_t data;
    size_t poll_handle_count;

    /* setup TCTI & SAPI contexts */
    tcti_context = tcti_tabrmd_init ();
    g_assert (tcti_context != NULL);
    data.sapi_context = sapi_init_from_tcti_ctx (tcti_context);
    g_assert (data.sapi_context != NULL);
    /* get fds to poll for I/O events */
    rc = tss2_tcti_get_poll_handles (tcti_context,
                                     poll_handles,
                                     &poll_handle_count);
    g_assert (rc == TSS2_RC_SUCCESS);
    g_assert (poll_handle_count == 1);
    if (!g_unix_set_fd_nonblocking (poll_handles[0].fd, TRUE, NULL)) {
        g_error ("failed to set fd %d to non-blocking", poll_handles[0].fd);
    }
    /* setup GLib source to monitor this fd */
    source = g_unix_fd_source_new (poll_handles[0].fd, G_IO_IN);
    context = g_main_context_new ();
    g_source_attach (source, context);
    /* setup callback */
    g_source_set_callback (source, (GSourceFunc)fd_callback, &data, NULL);
    data.loop = g_main_loop_new (context, FALSE);
    g_main_context_unref (context);
    /* make initial get capability call */
    rc = Tss2_Sys_GetCapability_Prepare (data.sapi_context,
                                         TPM_CAP_COMMANDS,
                                         TPM_CC_FIRST,
                                         MAX_CAP_CC);
    g_assert (rc == TSS2_RC_SUCCESS);
    rc = Tss2_Sys_ExecuteAsync (data.sapi_context);
    g_assert (rc == TSS2_RC_SUCCESS);
    /* run main loop */
    g_main_loop_run (data.loop);
    g_main_loop_unref (data.loop);
    /* cleanup TCTI / SAPI stuff */
    Tss2_Sys_Finalize (data.sapi_context);
    g_free (data.sapi_context);
    tss2_tcti_finalize (tcti_context);
    g_free (tcti_context);

    return 0;
}

Conclusion & Next Steps

Pretty straight forward all things considered. But this demonstration leaves a few things to be desired: Firstly no application should have to dig directly into the TCTI to pull out the underlying fd. Instead we should have a function to create a GSource from the SAPI context directly much like g_unix_fd_source_new`

Secondly, there is little in the program to convince us that the function call is truly asynchronous. To make our point we need additional tasks that the GMain loop can execute in the background while the TPM is handling our command. A GTK+ application with a chain of slow TPM2 functions that create a key hierarchy or perform some slow RSA key function (encrypt) while the UI remains responsive would be much more convincing. This may be the topic of a future post if I don’t get distracted.

Finally: this is a lot of machinery and it often comes under attack as adding too much complexity to the API. Instead I’d argue that complexity here is the minimum complexity necessary to enable easy integration of the TSS2 APIs into event driven programming frameworks. Most programmers will never have to interact with this machinery. Instead they should be used to create higher level APIs / Objects that integrate into these event-driven programming frameworks.