7. Integrating a Third-Party TPM

This section describes how to integrate an existing Transport Protocol Module (TPM) implementation with the Connext TSS framework. It assumes you already have a working transport layer and need to wire it into the Connext TSS architecture.

7.1. Integration Steps

To integrate your third-party TPM with the Connext TSS framework, follow the subsections below in order.

7.1.1. Wrap your TPM in the FACE interface

Your TPM must expose the FACE::TSS::TPM::TPMTS interface. In C, embed the FACE_TSS_TPM_TPMTS struct and populate its function pointer table. In C++, inherit from the abstract class.

For example, see the following C struct layout:

typedef struct {
    FACE_TSS_TPM_TPMTS tpm;                                 // required
    FACE_Configuration_Injectable configuration_injectable;  // required
    FACE_Logging_Injectable logging_injectable;              // optional
    void *data;                                              // your private state
} MY_TPM;

And see the following example C++ class:

class MyTPM : public FACE::TSS::TPM::TPMTS,
              public FACE::Configuration_Injectable::Injectable {
    // override all TPMTS pure virtuals
    // override Set_Reference for Configuration
};

Next, wire the function pointer table in an initialization function (C only):

void MY_TPM_init(MY_TPM *this_obj) {
    this_obj->tpm.ops.Initialize = MY_TPM_Initialize_impl;
    this_obj->tpm.ops.Open_Channel = MY_TPM_Open_Channel_impl;
    this_obj->tpm.ops.Close_Channel = MY_TPM_Close_Channel_impl;
    this_obj->tpm.ops.Write_To_Transport = MY_TPM_Write_To_Transport_impl;
    this_obj->tpm.ops.Read_From_Transport = MY_TPM_Read_From_Transport_impl;
    this_obj->tpm.ops.Register_TPM_Callback = MY_TPM_Register_TPM_Callback_impl;
    this_obj->tpm.ops.Unregister_TPM_Callback = MY_TPM_Unregister_TPM_Callback_impl;
    this_obj->tpm.ops.Is_Data_Available = MY_TPM_Is_Data_Available_impl;
    this_obj->tpm.ops.Get_TPM_Status = MY_TPM_Get_TPM_Status_impl;
    this_obj->tpm.ops.Request_TPM_State_Change = MY_TPM_Request_TPM_State_Change_impl;
    this_obj->configuration_injectable.ops.Configuration_Injectable_Set_Reference =
        MY_TPM_Configuration_Set_Reference_impl;
}

See TSS TPM Interfaces for complete parameter documentation.

7.1.2. Register your TPM with the application

TPMs are statically instantiated; there is no dynamic loading or plugin discovery. In your application startup code, instantiate your TPM with the following code:

// 1. Instantiate
MY_TPM my_tpm;
MY_TPM_init(&my_tpm);

// 2. Inject configuration service
FACE_Configuration_Injectable_Set_Reference(
    &my_tpm.configuration_injectable,
    &config_name, config_service, config_guid, &retcode);

// 3. Initialize your TPM
FACE_TSS_TPM_TPMTS_Initialize(&my_tpm.tpm, &tpm_config_name, &retcode);

// 4. Register with TSS Base (GUID must match TSS.xml)
FACE_TSS_TPM_TPMTS_Injectable_Set_Reference(
    &Base.tpm_injectable,
    &tpm_interface_name,
    &my_tpm,
    5,          // GUID matching your XML <tpm> element
    &retcode);

7.1.3. Declare your TPM in TSS.xml

Declare your TPM in the file TSS.xml with the following snippet:

<TSS>
  <configs>
    <config name='my_config'>
      <connections>
        <connection name='MyConnection'>
          <tpms><tpm guid='5'/></tpms>
        </connection>
      </connections>
      <tpms>
        <tpm name='MY_TPM' guid='5'/>
      </tpms>
    </config>
  </configs>
</TSS>

The guid attribute links your registered TPM instance to the connections that use it. This value must match the GUID passed in Register your TPM with the application.

7.2. Integration Constraints

This section describes the contracts your TPM must satisfy in order to work correctly with the Connext TSS Type Abstraction (TA) layer. Violating any of these contracts will cause silent data corruption, crashes, or deadlocks.

7.2.1. Data format contract

All data passes between layers as a FACE_TSS_MESSAGE_TYPE, as shown below:

typedef struct FACE_TSS_MESSAGE_TYPE {
  FACE_TSS_MESSAGE_GUID_TYPE message_guid;
  FACE_TSS_DATA_BUFFER_TYPE buffer;
} FACE_TSS_MESSAGE_TYPE;

typedef struct FACE_TSS_DATA_BUFFER_TYPE {
  FACE_SYSTEM_ADDRESS_TYPE buffer_address;   // void*
  FACE_TSS_BYTE_SIZE_TYPE buffer_capacity;   // size in bytes
} FACE_TSS_DATA_BUFFER_TYPE;

The TA layer passes this structure through unchanged; it never transforms or copies the data. Your TPM is the only layer that performs serialization or deserialization. Therefore, your TPM must satisfy the following contracts:

  • Sending (Write_To_Transport): buffer_address points to the typed object in its in-memory C/C++ struct layout. Serialize it into your wire format.

  • Receiving (callback or Read_From_Transport): Deserialize your wire data into the exact in-memory struct layout the TS layer expects.

    Warning

    If your byte layout does not match (field order, padding, endianness, etc.), the application will receive corrupt data silently; there is no validation at the TA layer.

  • Buffer ownership on receive: The TA layer passes your buffer_address directly to the TS layer without copying. Your buffer must remain valid after the callback returns; use heap memory or a pre-allocated pool.

  • GUID matching: The message_guid you set on receive must match the GUID configured for the channel’s type. The TA layer uses it to route messages.

Write_To_Transport and Read_From_Transport also pass a TSS header, as shown below:

typedef struct FACE_TSS_HEADER_TYPE {
  FACE_TSS_UID_TYPE instance_uid;
  FACE_TSS_UID_TYPE source_uid;
  FACE_SYSTEM_TIME_TYPE timestamp;
} FACE_TSS_HEADER_TYPE;

On sending, the TA layer provides these values. Transmit them if your transport carries per-message metadata. Upon receiving, populate from your transport’s metadata or set to zero if unavailable.

7.2.2. Threading contract

The Connext TSS framework uses a synchronous, non-queued threading model. This section describes the threading model in detail.

7.2.2.1. Thread safety

Connext TSS protects the shared Type Abstraction or Base state (e.g., connection and callback metadata) with internal framework synchronization.

Send_Message() and Receive_Message() may run concurrently. No framework lock is held across Transport Protocol Module I/O, so transport work that blocks does not block other tasks.

The callback registration/unregistration state is synchronized at the framework level.

Sending data is synchronous through your entire Write_To_Transport() call. Receiving callbacks are also synchronous; your transport thread invokes the callback, and processing completes before control returns.

See Thread Safety Model in the API Reference for more information.

7.2.2.2. Application responsibilities

Your Transport Protocol Module and application must satisfy the following concurrency rules:

  1. The Transport Protocol Module I/O must be thread-safe. Write_To_Transport() and Read_From_Transport() may be active concurrently on different threads.

  2. Do not hold locks across callback invocation. If callback code calls back into Send_Message() (re-entering Write_To_Transport()), a held lock can cause a deadlock. Use separate send/recieve locks, or release locks before invoking callbacks.

  3. After Unregister_TPM_Callback() returns, no further callbacks may fire. Ensure your receive path checks registration state and waits for in-flight callback completion as needed.

  4. Keep initialization and teardown ordered: inject references first, then initialize, then unregister/close before teardown. Even with internal synchronization, this avoids lifecycle races in application code.

7.2.3. Callback contract

The following rules apply regarding callbacks:

  • Only one active callback is allowed per channel. Return NO_ACTION on duplicate registration.

  • No callbacks may fire after Unregister_TPM_Callback() returns. Your receiving thread must check the registration state before each invocation.

    This is an obligation on your Transport Protocol Module, and it is what Connext TSS relies on. It is not the same statement as the Connext TSS-level one at FACE::TSS::BASE::Unregister_Callback(), which says a final callback invocation may still be completing when Unregister_Callback() returns. Connext TSS covers the case where the underlying detach is not synchronous; your Transport Protocol Module must still stop dispatching once Unregister_TPM_Callback() has returned.

  • Callbacks block the entire receive path; do not perform unbounded input/output (I/O) inside.

7.2.4. Error code contract

Your TPM must return these FACE codes for the following errors:

Error

Code

Timeout

TIMED_OUT

Invalid channel ID

INVALID_PARAM

Not initialized / unavailable

NOT_AVAILABLE

Buffer null or too small

INVALID_PARAM

Duplicate callback

NO_ACTION

Invalid configuration

INVALID_PARAM

7.2.5. SafetyBase profile constraints

The following constraints apply if your TPM targets the SafetyBase profile:

  • No free() or delete functions are allowed at runtime. You must allocate everything during the Initialize() and Open_Channel() functions.

  • No entity deletion. Once channels are open, do not destroy transport resources.

  • No heap reallocation. Only bounded sequences and strings are allowed.

  • Pre-allocate buffer pools during initialization for use during I/O.

7.3. Troubleshooting

This section describes problems you may encounter during or after integrating a third-party TPM, along with likely causes and fixes.

7.3.1. Connection returns NOT_AVAILABLE

Possible causes:

  • Initialize() was called before injecting required services.

  • The TPM was never registered with TSS Base. Verify that FACE_TSS_TPM_TPMTS_Injectable_Set_Reference() is called during startup.

Things to check:

  1. Ensure all Set_Reference calls (such as Configuration, Serialization, or Logging) are complete before calling Initialize().

  2. Print or log the return code from each Set_Reference call. If any returns non-zero, the injectable was not accepted.

  3. Confirm that the configuration service is fully populated (file loaded, entries parsed) before injection.

  4. Add a log statement at the top of your Initialize() implementation to confirm it is being called at all.

  5. Verify that FACE_TSS_TPM_TPMTS_Injectable_Set_Reference() is called during startup.

7.3.2. Send or receive silently produces corrupt data

Possible causes:

  • The serialization byte layout does not match the in-memory struct layout the TS layer expects.

  • On receive, message_guid is not set to the correct type GUID. The TA layer routes by this value; an incorrect GUID delivers data to the wrong handler or drops it.

Things to check:

  1. Check field order, padding, alignment, and endianness.

  2. Hex-dump the buffer contents immediately after serialization (on the sending side) and immediately after deserialization (on the receiving side). Compare byte-for-byte against the expected struct layout using offsetof() for each field.

  3. Verify sizeof(YourType) matches on both sides. Compiler padding differences (e.g., #pragma pack) between your TPM and the generated type code are a common source of mismatch.

  4. Log message_guid on both send/ and receive. Compare against the GUID value in TSS.xml for the connection’s type.

  5. If using a network transport, capture packets and verify wire-format bytes match your serialized output.

7.3.3. Callback never fires on receive

Possible causes:

  • message_guid on the received message does not match the channel’s configured type GUID. The TA layer silently discards unmatched messages.

  • Register_TPM_Callback() was not called, or was called on the wrong channel.

  • The TPM’s receive thread is not running.

Things to check:

  1. Add a log statement inside your receive thread loop to confirm it is executing and receiving data from the transport.

  2. Log the message_guid you set on each received message and compare it against the GUID in your TSS.xml <connection> element.

  3. Verify Register_TPM_Callback() returned NO_ERROR. If it returned NO_ACTION, a callback was already registered.

  4. Confirm the channel ID passed to Register_TPM_Callback() matches the one returned by Open_Channel().

  5. Check that your receive thread was started after Open_Channel() completes, not during Initialize().

7.3.4. Deadlock during send or receive

Possible causes:

  • A single lock is held across both the send path and the callback invocation path. If the application’s callback handler calls Send_Message() (re-entering Write_To_Transport), the lock will deadlock.

Things to check:

  1. Use separate locks for send and receive, or release locks before invoking callbacks.

  2. Attach a debugger and inspect all thread backtraces when the hang occurs. Look for two threads waiting on the same mutex.

  3. Identify whether your callback handler (or anything it calls) invokes Send_Message() or any other TSS API that re-enters the TPM.

  4. Temporarily replace your mutex with a try-lock and log failures. This reveals contention without hanging.

  5. Verify that you do not hold a lock when invoking the registered callback function pointer.

7.3.5. Crash or use-after-free during shutdown

Possible causes:

  • Callback fires after Unregister_TPM_Callback() returns. Your receive thread must check registration state before each callback invocation.

  • Buffers are freed before the TS layer finishes processing. The TA layer passes buffer_address directly to the TS layer without copying.

Things to check:

  1. Run under a memory sanitizer (ASan) or Valgrind to identify the exact access-after-free location.

  2. Verify that Unregister_TPM_Callback() waits (e.g., via a condition variable or join) until any in-progress callback has returned before it completes.

  3. Confirm that your receive-side buffers are not freed inside the callback itself. They must remain valid until the callback returns to your TPM code.

  4. Add a flag that your receive thread checks before each callback invocation; set it in Unregister_TPM_Callback() and verify the thread observes it promptly (use a memory barrier or atomic).

7.3.6. Crash or segfault on first I/O call

Possible causes:

  • One or more function pointers in the ops table are NULL.

  • Open_Channel() was not called before I/O. Channels must be opened before Write_To_Transport or Register_TPM_Callback can be used.

Things to check:

  1. Verify that your initialization function populates every entry in the function pointer table.

  2. Zero-initialize your TPM struct (memset) before calling your init function, then verify no ops entry is NULL afterward. A missing assignment becomes obvious when the struct starts zeroed.

  3. Step through the crash in a debugger — if the program counter is at address 0x0, a function pointer was not set.

  4. Log the return code from Open_Channel(). If it failed, subsequent I/O calls operate on an invalid channel.

7.3.7. GUID mismatch: connection not associated with TPM

Possible causes:

  • The GUID passed to FACE_TSS_TPM_TPMTS_Injectable_Set_Reference() does not match the <tpm guid='...'/> in TSS.xml. These must be identical.

Things to check:

  1. Search your TSS.xml for all guid attributes and confirm each one has a corresponding Set_Reference call with the same numeric value.

  2. Log the GUID value at registration time. Compare it against your XML (remember the XML value is a string — ensure you are parsing it as the correct integer type).

  3. If using multiple TPMs, verify each TPM instance uses a unique GUID and maps to the correct <connection> element.

7.3.8. SafetyBase build fails or aborts at runtime

Possible causes:

  • Runtime heap allocation (malloc/free) detected. All buffers and resources must be pre-allocated during Initialize() or Open_Channel().

  • Sequence or string exceeds its bounded maximum.

Things to check:

  1. Audit your TPM source for any malloc, calloc, realloc, free, new, or delete calls outside of Initialize() and Open_Channel().

  2. Verify that all sequence and string types have explicit bounds in your IDL and that your buffers are sized to those bounds.

  3. Run with a heap-intercepting tool to confirm no allocations occur after initialization completes.

7.3.9. Application receives TIMED_OUT unexpectedly

Possible causes:

  • Your Read_From_Transport or Write_To_Transport does not honor the timeout parameter. If the caller passes a finite timeout, your implementation must return TIMED_OUT when that duration elapses without completing the operation — not block indefinitely.

  • A zero timeout means it is non-blocking. If no data is immediately available, it will return TIMED_OUT rather than waiting.

Things to check:

  1. Log the timeout value received by your TPM function. Confirm it matches what the application passed to Receive_Message or Send_Message.

  2. Verify your timeout arithmetic uses the correct units. The FACE API uses nanoseconds (FACE_SYSTEM_TIME_TYPE); converting incorrectly causes premature or delayed timeouts.

  3. Test with FACE_INF_TIME_VALUE (infinite) and zero separately to confirm both blocking and non-blocking paths work correctly.

7.3.10. Application receives INVALID_PARAM on seemingly valid calls

Possible causes:

  • Your TPM is returning INVALID_PARAM for a channel ID it does not recognize.

  • A NULL buffer address or zero buffer capacity was passed in. Your TPM should validate these at entry and return INVALID_PARAM.

  • Configuration name passed to Initialize() does not match any entry in your TPM’s expected configuration.

Things to check:

  1. Log all parameters at entry to the function returning INVALID_PARAM. Identify which validation check is triggering.

  2. Print the channel ID from Open_Channel() and the channel ID passed to the failing call. Confirm they are the same value and type.

  3. If the error comes from Initialize(), print the configuration name string and compare character-by-character against your XML (watch for trailing whitespace or null characters).

  4. Confirm the caller is not passing an uninitialized FACE_TSS_MESSAGE_TYPE.

7.3.11. Register_Callback returns NO_ACTION

Possble cause:

  • A callback is already registered on that channel. Only one active callback per channel is permitted.

Things to check:

  1. Log inside Register_TPM_Callback() whether the channel’s callback slot is already occupied.

  2. Trace your startup sequence to determine if Register_Callback is being called twice for the same channel (e.g., from both the application and an initialization helper).

  3. Call Unregister_TPM_Callback() on the existing registration before registering a new one.

7.3.12. Write or Read returns NOT_AVAILABLE

Possible causes:

  • The TPM has not been initialized.

  • The channel is not open. Open_Channel() must return NO_ERROR before I/O is attempted on that channel ID.

  • The TPM is in a shutdown or error state. Check that Request_TPM_State_Change() has not transitioned the TPM to an unavailable state.

Things to check:

  1. Log the TPM’s internal state (uninitialized, initialized, channel-open, shutting-down) at the point where NOT_AVAILABLE is returned.

  2. Ensure Initialize() completed successfully (returned NO_ERROR) before calling any I/O function.

  3. Verify the call order: Initialize()Open_Channel() → I/O. If any earlier step failed silently, subsequent calls will see an invalid state.

  4. Check whether another thread called Request_TPM_State_Change() or Close_Channel() concurrently.

  5. Check that Request_TPM_State_Change() has not transitioned the TPM to an unavailable state.

7.3.13. TPM returns NO_ERROR but application still reports an error

Possible causes:

  • Your TPM returns NO_ERROR from Write_To_Transport even when the underlying transport failed to send.

  • On receive, returning NO_ERROR with an incorrect buffer_capacity (e.g., zero) causes the TS layer to treat the message as empty.

Things to check:

  1. Propagate transport-layer failures as the appropriate FACE return code so the application can detect and handle them.

  2. Add error checking after every transport-layer call inside your TPM (socket send, shared-memory write, etc.) and map failures to FACE return codes.

  3. Log buffer_capacity on the receive path immediately before returning. Confirm it equals the deserialized payload size, not the raw wire size or the buffer’s total capacity.

  4. Check whether the TS layer above is reporting a different error code. The mismatch between your NO_ERROR and the application’s error indicates the TS or TA layer detected an inconsistency (e.g., size mismatch, null buffer).

7.4. Validation

To validate your third-party TPM implementation, do the following:

  1. Start from a known-working Connext TSS example configuration and replace one element at a time.

  2. Verify Initialize -> Open_Channel -> Register_Callback -> I/O -> Unregister_Callback -> Close_Channel in that order.

  3. Run both nominal and fault-injection tests (e.g., invalid GUID, unavailable transport endpoint, malformed configuration, and/or callback timeout).

  4. Test your TPM with the target FACE profile (GeneralPurpose or SafetyBase) and the target transport or middleware runtime.