Skip to content

Firmware Integration

This integration guide assumes, that both, the TLCP library AND the NFC Reader Library are integrated as provided with this SDK. It’s further assumed, that an NXP PN512 chip is used as NFC controller chip. If a different chip is used, the according components of the NFC reader library need to be adjusted by the implementer accordingly.

Conventions

Byte arrays and strings are always passed with corresponding explicit length information. Strings are never 0-terminated.

tkExt.h header file

The TLCP library includes a header file named tkExt.h. This file declares functions that must be implemented by the consuming application. These functions include:

  • Get/set date and time (of the locking device's clock).
  • Get seed for random number generator.
  • Trigger mechanical lock after authorization.
  • Read measurement values like battery voltage and ambient temperature if applicable.
  • Read from/write to non-volatile memory.
/**
 Generic conventions:

 * Byte arrays and strings are always passed with explicit length information. Strings are never
   0-terminated.
 * Bytes are assumed to have 8 bits.
 * Offsets and sizes are always expressed in number of bytes.
 */

/** Return value types for the Tapkey external library functions */
typedef enum _TK_EXT_RET {
    TK_EXT_RET_Ok = 0,                      /**< The operation completed successfully */
    TK_EXT_RET_AsyncResult = -2,            /**< The operation was initiated as requested but the result shall be queried separately. */
    TK_EXT_RET_ERR_Generic = -1,            /**< The operation completed with an unspecific error */
    TK_EXT_RET_ERR_NotFound = 1,            /**< The requested item couldn't be found or there is no such thing */
    TK_EXT_RET_ERR_Timeout = 2,             /**< Timeout */
    TK_EXT_RET_ERR_NotFullyAssembled = 3,   /**< The operation didn't complete, because the locking device is not fully assembled. */
    TK_EXT_RET_ERR_EOF = 4,                 /**< The operation didn't complete, because the file reached it's end or there isn't enough space available. */
} TK_EXT_RET;

#define TKEXT_CHECK(x) do { TK_EXT_RET __tmp_res = (x); if (__tmp_res != TK_EXT_RET_Ok) return __tmp_res; } while (0)

/**
 * @brief Returns the manufacturer ID as defined by Tapkey
 *
 * @param[in] pExternalContext      Pointer to the external context
 *
 * @return uint16_t
 * @retval Manufacturer ID
 */
uint16_t tkExt_GetManufacturerId(void* pExternalContext);

/**
 * @brief Returns the firmware version as integer.
 *
 * This value might be used to detect and prevent a version downgrade during firmware upgrade.
 * Greater values represent newer versions. The returned value must be > 0.
 *
 * @param[in] pExternalContext      Pointer to the external context
 *
 * @return uint32_t
 * @retval Firmware version
 */
uint32_t tkExt_GetFirmwareVersionInt(void* pExternalContext);

/**
 * @brief Returns the oldest firmware version, this firmware can be downgraded to.
 *
 * When the firmware is to be downgraded, Tapkey checks the value returned by this function, to
 * check, whether it should allow the downgrade. It will not allow a downgrade, if the version to
 * be installed is lower than the value returned by this function.
 *
 * A downgrade will typically only be possible, if the target version uses the same storage layout
 * and the same TLCP protocol version as the current version.
 *
 * If no downgrade should be supported at all, this function should return the value 
 * `tkExt_GetFirmwareVersionInt() + 1`.
 *
 * Caution: Maintain the value returned by this function with care! Allowing firmware downgrades to
 * versions that are too old can cause the device to die permanently or to introduce severe
 * security issues.
 */
uint32_t tkExt_GetFirmwareDowngradeableToVersionInt(void* pExternalContext);

/**
 * @brief Retrieves an ASCII string that specifies the type of firmware this device can run
 *
 * During FW-upgrades this value is compared against the type specified with the new FW.
 * Only matching FW packages will be installed. The returned string must not exceed a
 * length of 8 characters and only contain readable characters, alphanumeric and the following: -_.
 * The returned string must not be 0-terminated.
 *
 * @param[in]  pExternalContext     Pointer to the external context
 * @param[out] pbuffer              A pointer to a buffer where the read data shall be stored at
 * @param[in]  bufferSize           How many bytes to read
 * @param[out] pActualSize          How many bytes actually read
 *
 * @return int
 * @retval 0        Read successful
 * @retval 1        No such string
 * @retval -1       String couldn't been retrieved
 */
int tkExt_GetFirmwareType(void* pExternalContext, uint8_t* pBuffer, uint8_t bufferSize, uint8_t* pActualSize);

/**
 * @brief Retrieves an ASCII string that uniquely identifies the build of this firmware.
 *
 * Might be used to identify the correct FW version for debugging purposes, etc.
 * The returned value must only contain readable characters, alphanumeric and the following: -_
 * The returned string must not be 0-terminated
 *
 * @param[in]  pExternalContext     Pointer to the external context
 * @param[out] pbuffer              A pointer to a buffer where the read data shall be stored at
 * @param[in]  bufferSize           How many bytes to read
 * @param[out] pActualSize          How many bytes actually read
 *
 * @return int
 * @retval 0        Read successful
 * @retval 1        No such string
 * @retval -1       String couldn't been retrieved
 */
int tkExt_GetFirmwareBuildString(void* pExternalContext, uint8_t* pBuffer, uint8_t bufferSize, uint8_t* pActualSize);

/**
 * @brief Retrieves an ASCII string that uniquely specifies the device model.
 *
 * Should include information about the device type (cylinder, wall reader, etc.), the hardware revision, etc.
 * This value might be used together with the manufacturer ID to display the correct device image to the user,
 * to retrieve the correct user manual or to launch the correct troubleshooting guide for the customer support.
 * The returned value must only contain readable characters, alphanumeric and the following: -_
 * The returned string must not be 0-terminated
 *
 * @param[in]  pExternalContext     Pointer to the external context
 * @param[out] pbuffer              A pointer to a buffer where the read data shall be stored at
 * @param[in]  bufferSize           How many bytes to read
 * @param[out] pActualSize          How many bytes actually read
 *
 * @return int
 * @retval 0        Read successful
 * @retval 1        No such string
 * @retval -1       String couldn't been retrieved
 */
int tkExt_GetModelString(void* pExternalContext, uint8_t* pBuffer, uint8_t bufferSize, uint8_t* pActualSize);

/**
 * @brief Retrieves an ASCII formatted version of the device serial number.
 *
 * Together with the manufacturer ID and the model string this value should uniquely identify the individual device.
 * The returned value must only contain readable characters, alphanumeric and the following: -_
 * The returned string must not be 0-terminated
 *
 * @param[in]  pExternalContext     Pointer to the external context
 * @param[out] pbuffer              A pointer to a buffer where the read data shall be stored at
 * @param[in]  bufferSize           How many bytes to read
 * @param[out] pActualSize          How many bytes actually read
 *
 * @return int
 * @retval 0        Read successful
 * @retval 1        No such string
 * @retval -1       String couldn't been retrieved
 */
int tkExt_GetDeviceSerialNoString(void* pExternalContext, uint8_t* pBuffer, uint8_t bufferSize, uint8_t* pActualSize);

/**
 * @brief Fills the provided buffer with a seed value
 *
 * The value can be used to create secure random numbers
 * The actual size of the returned seed is up to the implementor but must be appropriate
 * For calculating secure random numbers and is limited by the size provided in bufferSize.
 *
 * @param[in]  pExternalContext     Pointer to the external context
 * @param[out] pSeed                Pointer to a buffer where the Seed value shall be stored
 * @param[in]  seedSize             How many bytes to read
 *
 *@return void
 */
void tkExt_GetRandomSeed(void* pExternalContext, uint8_t* pSeed, uint32_t seedSize);

/**
 * @brief This function returns the time in milliseconds since 2000-01-01 00:00:00 UTC.
 *
 * The value must not be >= 2^48
 *
 * @param[in]  pExternalContext     Pointer to the external context
 *
 * @return uint64_t
 * @retval Time in milliseconds
 */
uint64_t tkExt_GetTimeMillisSince2000Utc(void* pExternalContext);

/**
 * @brief Sets the current real time in milliseconds since 2000-01-01 00:00:00 UTC.
 *
 * @param[in]  pExternalContext         Pointer to the external context
 * @param[in]  timeMillisSince2000Utc   Current time in milliseconds
 */
void tkExt_SetTimeMillisSince2000Utc(void* pExternalContext, uint64_t timeMillisSince2000Utc);

/**
 * @brief Returns the current time set on the hardware clock.
 *
 * This value might be different from the UTC time in that it doesn't change if the real time
 * clock is adjusted. It is used to calculate short living timeouts like the session timeout
 *  which shouldn't get extended if the real time is changed.
 *
 * @param[in]  pExternalContext         Pointer to the external context
 *
 * @return uint64_t
 * @retval Time in milliseconds
 */
uint64_t tkExt_GetDeviceTimeMillis(void* pExternalContext);


/** Tapkey lock action types */
typedef enum {
    TK_EXT_LOCK_ACTION_TYPE_Unknown = 0,            /**< Action unknown */
    TK_EXT_LOCK_ACTION_TYPE_PermanentUnlock = 1,    /**< Permanently unlock */
    TK_EXT_LOCK_ACTION_TYPE_PermanentLock = 2,      /**< Permanently lock */
    TK_EXT_LOCK_ACTION_TYPE_TemporaryUnlock = 3,    /**< Temporary unlock */
} TK_EXT_LOCK_ACTION_TYPE;

/**
 * @brief Trigger an actual lock action.
 *
 * This is a high level command. Triggering a lock action often involves activating an electric motor
 * for activating a bolt and releasing the bolt after a couple of seconds.
 * This command activates the whole process. The implementor must take care of completing the process
 * (i.e. releasing the bolt after the specified duration). The function MUST return immediately.
 * If the lockActionType is "TemporaryUnlock", the parameter "durationSec" specifies the unlock duration.
 * The implementation of this function should not issue any signals to the user as this is triggered
 * by a separate call to tkExt_OnSignal.
 *
 * @param[in] pExternalContext          Pointer to the external context
 * @param[in] lockActionType            Type of locking action, permanent or temporary
 * @param[in] durationSec               In case of temporary unlock actions, this value
 *                                      specifies the duration of the lock action.
 * @param[in] pCommandData              Optional, application-specific data related to
 *                                      this command. Not used in most regular locking devices.
 * @param[in] commandDataSize           Size of the passed command data.
 *
 * @return TK_EXT_RET
 * @retval TK_EXT_RET_OK                    The operation was completed as requested
 * @retval TK_EXT_RET_AsyncResult           The operation was initiated as requested, but the operation
 *                                          result should be queried asynchronously. This value may only
 *                                          be returned if the application provided an according callback
 *                                          function which will then be called by Tapkey to query
 *                                          the response, possible asynchronously.
 * @retval TK_EXT_RET_ERR_NotFullyAssembled The operation couldn't be initiated due to a missing
 *                                          communication link between the MCU and the mechanics components.
 * @retval TK_EXT_RET_ERR_Generic           Any other error
 */
TK_EXT_RET tkExt_TriggerLock(void* pExternalContext, TK_EXT_LOCK_ACTION_TYPE lockActionType, uint8_t durationSec, uint8_t* pCommandData, uint16_t commandDataSize);

/**
 * @brief Called, when the Tapkey Library is idle for a short time.
 *
 * This is the case when
 * a) When reading a card: the card has been read and processed and Tapkey is waiting for the card
 *    to be removed.
 * b) When acting as reader and communicating with a smartphone in CE mode: When waiting for the
 *    next frame.
 * c) When running in card emulation mode: When waiting for the next frame.
 * As a communication session might still be in progress, timing is essential. Therefore the
 * implementation of this function may only block for a short time (no longer than 1 ms).
 * Examples for what this function might be used for:
 * feeding the watchdog timer
 * yielding control to other asynchronous tasks like controling signaling, motor control, etc. In a
 * non-preemptive multitasking environment the implementation of this  function may yield control
 * to other tasks.
 *
 * @param[in] pExternalContext          Pointer to the external context
 *
 * @return void
 */
void tkExt_OnIdle(void* pExternalContext);

/**
 * @brief This function delays execution (blocks) for the specified amount of time.
 *
 * The function shall be implemented with an accuracy better than +- 5ms.
 *
 * @param[in] pExternalContext          Pointer to the external context
 * @param[in] delayMs                   Delay in milliseconds
 *
 * @return void
 */
void tkExt_Delay(void* pExternalContext, uint16_t delayMs);

#define TK_EXT_STACKSIZE_UNKNOWN 0xffffffff

/** 
 * Returns the available size on the current thread's stack in bytes. This is the additional number
 * of bytes that can be allocated by the calling function without exceeding the bounds of the stack
 * and without overwriting any other memory in use.
 * This function is used to determine, whether certain stack-demanding functiionality
 * can be executed.
 * If the size cannot be calculated, TK_EXT_STACKSIZE_UNKNOWN is returned.
 */
uint32_t tkExt_GetStackSizeAvailable(void* pExternalContext);

/** Tapkey signal types */
typedef enum {
    TK_EXT_SIGNAL_TYPE_Unknown,                     /**< Unknown signal */
    TK_EXT_SIGNAL_TYPE_AccessGranted,               /**< Access granted signal */
    TK_EXT_SIGNAL_TYPE_AccessDenied,                /**< Access denied signal */
    TK_EXT_SIGNAL_TYPE_GenericPositive,             /**< Generic positive signal */
    TK_EXT_SIGNAL_TYPE_GenericNegative,             /**< Generic negative signal */
    TK_EXT_SIGNAL_TYPE_AdminModeActive,             /**< Admin mode active signal */
    TK_EXT_SIGNAL_TYPE_FirmwareUpgradeInProgress,   /**< Firmware package validation signal */
} TK_EXT_SIGNAL_TYPE;

/**
 * @brief This function is called when a signal is to be issued to the user.
 *
 * This might be a positive or negative signal.If the signal indicates a certain
 * event (e.g. success or error), the signal is emitted once and durationSec is set to 0.
 * If the signal indicates a state (e.g. admin mode active), durationSec specifies the time after
 * which the state automatically gets deactivated. If tkExt_OnSignal gets called for a state
 * while the state is still active from a previous call, the new values override the values from
 * the old call. I.e. this is equivalent to calling tkExt_OnSignalCancelled for this state before
 * calling this function. A state might get canceled before the specified duration expires by
 * calling tkExt_OnSignalCancelled.
 *
 * Note on TK_EXT_SIGNAL_TYPE_AccessGranted: If the according lock action passed to tkExt_TriggerLock
 * was set to TK_EXT_LOCK_ACTION_TYPE_TemporaryUnlock, this signal type is treated as state where
 * durationSec specifies the duration of the temporary unlock. In this case the duration passed
 * equals the value passed to tkExt_TriggerLock. If permanent lock or permanent unlock was specified,
 * durationSec is set to 0. This function is only called to issue according signals to the user,
 * not to trigger the related action (i.e. for triggering an lock action, tkExt_TriggerLock is called,
 * for issuing the related signal, this function is called).
 *
 * @param[in] pExternalContext          Pointer to the external context
 * @param[in] signalType                The type of signal to issue
 * @param[in] durationSec               The duration in seconds for how long to issue the signal
 *
 * @return void
 */
void tkExt_OnSignal(void* pExternalContext, TK_EXT_SIGNAL_TYPE signalType, uint16_t durationSec);

/**
 * @brief Called, when an active signal should get canceled.
 *
 * A state signal that was triggered via tkExt_OnSignal might either expire after the duration
 * specified in the call to tkExt_OnSignal or it might get canceled by a call to this function.
 * If this function is called for a state that is not currently active, the call must be ignored.
 *
 * @param[in] pExternalContext          Pointer to the external context
 * @param[in] signalType                The type of signal to cancel
 *
 * @return void
 */
void tkExt_OnSignalCanceled(void* pExternalContext, TK_EXT_SIGNAL_TYPE signalType);

/** Tapkey event types */
typedef enum {
    TK_EXT_EVENT_TYPE_Unknown,              /**< Unknown event */
    TK_EXT_EVENT_TYPE_AdminModeActivated,   /**< Activated admin mode */
    TK_EXT_EVENT_TYPE_AdminModeDeactivated, /**< Deactivated admin mode */
    TK_EXT_EVENT_TYPE_Binding,              /**< Binding event */
    TK_EXT_EVENT_TYPE_Unbinding,            /**< Unbinding event */

} TK_EXT_EVENT_TYPE;

/**
 * @brief Called before certain events are executed.
 *
 * Allows the implementing application to execute code prior to these events
 * (e.g. activate logging before the lock is bound). Also allows the implementing application
 * to cancel the event. This function is only called for a limited set of event types.
 *
 * @param[in] pExternalContext          Pointer to the external context
 * @param[in] eventType                 Type of the event to be executed
 *
 * @return TK_EXT_RET
 * @retval TK_EXT_RET_Ok                Execution of the event should be executed as planned
 * @retval Other                        Cancel execution
 */
TK_EXT_RET tkExt_OnPreEvent(void* pExternalContext, TK_EXT_EVENT_TYPE eventType);

/**
 * @brief Called after certain events were executed.
 *
 * Allows the implementing application to execute code after the event happened
 * (e.g. deactivate logging after the lock was unbound).
 *
 * @param[in] pExternalContext          Pointer to the external context
 * @param[in] eventType                 Type of the event to wait for
 *
 * @return void
 */
void tkExt_OnPostEvent(void* pExternalContext, TK_EXT_EVENT_TYPE eventType);

/** Tapkey measurement type */
typedef enum {
    TK_EXT_MEASUREMENT_TYPE_BatteryVoltage,         /**< Battery voltage in units of 0.01V
                                                     *   (3.1V is represented as 310)
                                                     */
    TK_EXT_MEASUREMENT_TYPE_Temperature,            /**< Temperature in 0.1 degrees C
                                                     *   (21.5 degrees C is represented as 215)
                                                     */
    TK_EXT_MEASUREMENT_TYPE_BatteryWarningLevel,    /**<The battery warning level. */
    TK_EXT_MEASUREMENT_TYPE_WakeupTotalCnt,         /**< The total number of wakeups regardless
                                                     *   whether some peer was detected afterwards.
                                                     */
    TK_EXT_MEASUREMENT_TYPE_WakeupWithPeerCnt,      /**< The number of wakeups after which some peer
                                                     *   (card or phone) was detected, regardless of
                                                     *   whether or not a successful Tapkey session
                                                     *   could be completed.
                                                     */
    TK_EXT_MEASUREMENT_TYPES_FirstCustom = 0x80,    /**< End symbol */
} TK_EXT_MEASUREMENT_TYPE;

/**
 * @brief Returns a list of measurement types supported by this device.
 *
 * @param[in]  pExternalContext         Pointer to the external context
 * @param[out] ppMeasurementTypes       List of supported measurement types
 * @param[out] pNofMeasurementTypes     Number of supported measurement types
 *
 * @return void
 */
void tkExt_GetSupportedMeasurementTypes(void* pExternalContext, TK_EXT_MEASUREMENT_TYPE** ppMeasurementTypes, uint8_t* pNofMeasurementTypes);

/**
 * @brief   Retrieves the specified measurement.
 *
 * If mayGenerateLoad flag is set, the function may  generate load to retrieve the measurement.
 * I.e. on a battery powered device, battery measurement wouldn't be triggered if the
 * mayGenerateLoad flag wouldn't be set. Instead a cached value would be returned from
 * the last measurement, if available.
 *
 * @param[in]  pExternalContext             Pointer to the external context
 * @param[in]  measurementType              The measurement to query according to
 *                                          the values provided in tkExt_GetSupportedMeasurementTypes.
 * @param[in]  mayGenerateLoad              Specifies whether this function may generate load to query
 *                                          this value, e.g. by executing a battery measurement.
 *                                          If this parameter is set to 0 and no cached value is available,
 *                                          no value is returned (indicated by returning a return value <> 0).
 * @param[in]  includeConfidential          If set to 0, this value might get presented to unauthorized users,
 *                                          so if the requested value is confidential, it shouldn't get returned.
 * @param[out] pMeasurement                 The actual measurement
 * @param[out] pMeasurementTimeMsSince2000  The date and time when the returned measurement was executed.
 *                                          If this value isn't available, it should be set to 0.
 *
 * @return int
 * @retval 0                                The measurement was retrieved successfully
 * @retval Other                            The measurement could not be retrieved
 */
int tkExt_GetMeasurement(void* pExternalContext, TK_EXT_MEASUREMENT_TYPE measurementType, uint8_t mayGenerateLoad, uint8_t includeConfidential, uint16_t* pMeasurement, uint64_t* pMeasurementTimeMsSince2000);

/** Size of simple CRC data that is assigned to blocks written to secure storage */
#define TK_EXT_STORAGE_CRC_SIZE 4
/** Size of cryptographic MAC data that is assigned to blocks written to unsecure storage */
#define TK_EXT_STORAGE_MAC_SIZE 8

/**
 * Storage blocks are required to have minimum sizes for payload data as specified in the following.
 * The values don't contain space for assigned CRCs/MACs. The full capacity isn't necessarily
 * consumed by the Tapkey library but these sizes might be consumed in future versions of the
 * library.
 */
#define TK_EXT_STORAGE_PROVISIONING_MINSIZE 1280
#define TK_EXT_STORAGE_BINDING_MINSIZE 64
#define TK_EXT_STORAGE_RCL_MINSIZE 512
#define TK_EXT_STORAGE_LOGHDR_MINSIZE 64
#define TK_EXT_STORAGE_LOGCURMAC_MINSIZE 32

#define TK_EXT_STORAGE_LOGPAGE_OVERALL_SIZE 264
#define TK_EXT_STORAGE_LOGPAGE_PAYLOAD_SIZE (TK_EXT_STORAGE_LOGPAGE_OVERALL_SIZE - TK_EXT_STORAGE_MAC_SIZE)

/** Tapkey storage block types */
typedef enum {
    TK_EXT_STORAGE_BLOCK_STATE = 0,             /**< Some overall storage state.
                                                */
    TK_EXT_STORAGE_BLOCK_PROVISIONING = 1,      /**< Lock base data like own identity and cryptographic
                                                 *   keys for storage access.
                                                 *   Only modified at manufacturing time.
                                                 *   Must be located in secure storage.
                                                 */
    TK_EXT_STORAGE_BLOCK_BINDING = 2,           /**< Binding data, changed when lock is registered with
                                                 *   new lock owner which normally. happens very infrequently.
                                                 */
    TK_EXT_STORAGE_BLOCK_RCL = 3,               /**< Revocation list, changed when the lock's revocation
                                                 *   list is modified. Usually happens when permissions
                                                 *   are explicitly revoked by the lock owner.
                                                 */
    TK_EXT_STORAGE_BLOCK_LOGHDR = 4,            /**< Log control data; changes whenever a new log page
                                                 *   is created which happens apx. every five lock operations.
                                                 */
    TK_EXT_STORAGE_BLOCK_LOGCURRENTMAC = 5,     /**< The checksum of the current log page. Changed with every
                                                 *   log entry. Should be persisted to storage that allows for
                                                 *   many write/erase cycles.
                                                 */
} TK_EXT_STORAGE_BLOCK;

/**
*   ----------------------
*   Storage functions
*   ----------------------
*   The hosting application must provide functionality for data persistence. It is designed to
*   specifically support persistence to flash and EEPROM storage by avoiding excessive erase cycles.
*
*   Data persistence functions are split into multiple parts:
*
*   * Secure Data Storage: Used to store and retrieve root key data, that is used for securing
*     all the other persisted data. It's essential, that the data is persisted to storage that can
*     not be read by an attacker using standard development equipment like JTAG, logic analyzer,
*     etc. This kind of data is persisted only when the storage is first initialized and won't
*     change over time.
*
*   * File Data Storage: Used to persist structured data like certificates, keys, revocation lists,
*     etc. The data is encrypted and may be stored in external, less secure storage. However, an
*     attacker being able to read and modify this storage, will be able to perform replay attacks,
*     e.g. on revocation lists.
*
*   * Log Data Store: Used to incrementally store log data. Not encrypted but cryptographically
*     protected.
*
*   * Firmware Storage: used to store and read firmware data in preparation of a firwmare upgrade.
*
*   * Legacy Data Storage: Used to read legacy data for migration from storage persisted with
*     SDK versions prior to 1.8.0.
*/

/**
 * The maximum size of the data block stored via tkExt_Storage_SecureData_Write() in bytes.
 */
#define TK_EXT_STORAGE_SECURE_DATA_MAX_SIZE 100


typedef struct _tkext_storage_securedata_api {

    /**
     * @brief Writes data to secure storage.
     *
     * Data stored using this function must be stored to secure storage, that is, storage, that can
     * not easily be extracted using debug probes, and the like.
     *
     * The function will be used to store key data that is used to encrypt data stored via other
     * storage functions. Loosing confidentiality of the stored data will compromise the security
     * of the whole device. If the device belongs to device groups, compromising the devices security
     * will compromise other devices in the same group too.
     *
     * Secure data is persisted only rarely, e.g. at provisioning time or when migrating from
     * legacy storage.
     *
     * @param[in]  pExternalContext     Pointer to the external context
     * @param[in]  pData                Data to be stored
     * @param[in]  dataSize             Size of the data to be stored
     *
     * @retval #TK_EXT_RET_Ok on success.
     * @retval #TK_EXT_RET_ERR_Generic on any error.
     */
    TK_EXT_RET (*write)(void* pExternalContext, uint8_t const* pData, uint32_t dataSize);

    /**
     * @brief Reads data from secure storage.
     *
     * @param[in]  pExternalContext     Pointer to the external context
     * @param[out] pData                Pointer to the buffer receiving the read data.
     * @param[in]  dataSize             Size of the buffer.
     * @param[out] pBytesRead           Pointer to the value, receiving the actual number of bytes read. May be NULL.
     *
     * @retval #TK_EXT_RET_Ok on success.
     * @retval #TK_EXT_RET_ERR_Generic on any error.
     */
    TK_EXT_RET (*read)(void* pExternalContext, uint8_t* pData, uint32_t dataSize, uint32_t* pBytesRead);

} tkext_storage_securedata_api_t;

/**
 * Gets a pointer to the secure data API.
 *
 * Data stored and read using this API must be stored to secure storage, that is, storage, that can
 * not easily be extracted using debug probes, and the like.
 *
 * The functions will be used to store and read key data that is used to encrypt data stored via
 * other storage functions. Loosing confidentiality of the stored data will compromise the security
 * of the whole device. If the device belongs to device groups, compromising the devices security
 * will compromise other devices in the same group too.
 *
 * Secure data is persisted only rarely, e.g. at provisioning time or when migrating from
 * legacy storage but read frequently.
 *
 * If the implementation of tkExt_Storage_File_GetFileApi() refers to a secure storage (such as
 * an MCUs internal flash memory), the implementation provided via
 * tkExtImpl_Storage_SecureData_GetApiForSecureFileSystem() may be used.
 *
 * @param[out] ppApi            Pointer to the api structure.
 * @param[out] ppApiContext     Pointer to a context value, being passed through to the api functions.
 */
void tkExt_Storage_SecureData_GetApi(void* pExternalContext, tkext_storage_securedata_api_t const** ppApi, void** ppApiContext);


/**
 * The maximum, exclusive value passed to the file storage functions as fileId. Values
 * greater than or equal to this value may be used by custom code for storing custom files.
 */
#define TK_EXT_STORAGE_FILE_MAX_FILE_ID 0x80000000

typedef struct _tkext_storage_file_api {

    /**
     * Opens a file for writing.
     * @param[in]  pApiContext  API context as returned by tkExt_Storage_File_GetFileApi().
     * @param[out] pFileHandle  Pointer to a value receiving the file handle of the opened file.
     * @param[in]  fileId       ID of the file to be opened.
     * @param[in]  maxFileSize  Maximum size of the file being written in bytes.
     *
     * @retval #TK_EXT_RET_Ok on success
     * @retval #TK_EXT_RET_ERR_EOF in case of insufficient space.
     * @retval #TK_EXT_RET_ERR_Generic on any other error.
     */
    TK_EXT_RET (*openWrite)(void* pApiContext, void** pFileHandle, uint32_t fileId, uint32_t maxFileSize);

    /**
     * Opens a file for reading.
     * @param[in]  pApiContext  API context as returned by tkExt_Storage_File_GetFileApi().
     * @param[out] pFileHandle  Pointer to a value receiving the file handle of the opened file.
     * @param[in]  fileId       ID of the file to be opened.
     *
     * @retval #TK_EXT_RET_Ok on success
     * @retval #TKFS_RET_ERR_NotFound if the file isn't found
     * @retval #TK_EXT_RET_ERR_Generic on any other error.
     */
    TK_EXT_RET (*openRead)(void* pApiContext, void** pFileHandle, uint32_t fileId);

    /**
     * Writes data to a file at the current position.
     *
     * @param[in]  pApiContext  API context as returned by tkExt_Storage_File_GetFileApi().
     * @param[in]  fileHandle   Handle of the file to be read.
     * @param[in]  pData        Pointer to the data to be written.
     * @param[in]  dataSize     Size of the data to be written. Aligned to 8-byte boundaries.
     *
     * @retval #TK_EXT_RET_Ok on success
     * @retval #TKFS_RET_ERR_EOF if the maximum size as specified in the openWrite() function, is
     *         exceeded.
     * @retval #TK_EXT_RET_ERR_Generic on any other error.
     */
    TK_EXT_RET (*write)(void* pApiContext, void* fileHandle, uint8_t const* pData, uint32_t dataSize);

    /**
     * Reads data from a file at the current position.
     *
     * @param[in]  pApiContext  API context as returned by tkExt_Storage_File_GetFileApi().
     * @param[in]  fileHandle   Handle of the file to be read.
     * @param[out] pData        Pointer to the buffer receiving the read data.
     * @param[in]  dataSize     Size of the buffer.
     * @param[out] pBytesRead   Pointer to the value receiving the actual number of bytes read. May be NULL.
     *
     * @retval #TK_EXT_RET_Ok on success
     * @retval #TKFS_RET_ERR_EOF if no more data is available.
     * @retval #TK_EXT_RET_ERR_Generic on any other error.
     */
    TK_EXT_RET (*read)(void* pApiContext, void* fileHandle, uint8_t* pData, uint32_t dataSize, uint32_t* pBytesRead);

    /**
     * Move the current read position of the open file by the specified offset. Can only be used
     * with files open for reading.
     *
     * @param[in]  pApiContext  API context as returned by tkExt_Storage_File_GetFileApi().
     * @param[in]  fileHandle   Handle of the file to be read.
     * @param[in]  offset       The offset to move the current position.
     *
     * @retval #TK_EXT_RET_Ok on success
     * @retval #TKFS_RET_ERR_EOF if the beginning of the end of the file are exceeded.
     * @retval #TK_EXT_RET_ERR_Generic on any other error.
     */
    TK_EXT_RET (*seek)(void* pApiContext, void* fileHandle, int32_t offset);

    /**
     * Closes the specified file. If the file was open for writing, this function finalizes the
     * write operation. I.e. it replaces the old version of the file (if present) with the new one.
     * If closing succeeds, the new file is considered as permanently persisted. Before the file is
     * closed successfully, the previous content of the file is still considered valid.
     *
     * @param[in]  pApiContext  API context as returned by tkExt_Storage_File_GetFileApi().
     * @param[in]  fileHandle   Handle of the file to be closed.
     *
     * @retval #TK_EXT_RET_Ok on success
     * @retval #TK_EXT_RET_ERR_Generic on any error.
     */
    TK_EXT_RET (*close)(void* pApiContext, void* fileHandle);

    /**
     * Deletes the specified file.
     *
     * @param[in]  pApiContext  API context as returned by tkExt_Storage_File_GetFileApi().
     * @param[in]  fileId       ID of the file to be deleted.
     *
     * @retval #TK_EXT_RET_Ok on success
     * @retval #TKFS_RET_ERR_NotFound if the file isn't found
     * @retval #TK_EXT_RET_ERR_Generic on any other error.
     */
    TK_EXT_RET (*del)(void* pApiContext, uint32_t fileId);

    /**
     * Returns statistics about the file system.
     *
     * @param[in]  pApiContext            API context as returned by tkExt_Storage_File_GetFileApi().
     * @param[out] pMaxSingleFileSpace    The maximum size, a single new file can have. Pointer may be NULL.
     * @param[out] pOverallFreeSpace      The overall free space currently available in the file system.
     *
     * @retval #TK_EXT_RET_Ok on success
     * @retval #TK_EXT_RET_ERR_Generic on any error.
     */
    TK_EXT_RET (*getFreeSpace)(void* pExternalContext, uint32_t* pMaxSingleFileSpace, uint32_t* pOverallFreeSpace);

} tkext_storage_file_api_t;

/**
 * Gets a pointer to the file storage API.
 *
 * The file data storage is used to persist structured data like certificates, keys, revocation
 * lists, etc. The data is encrypted and may be stored to external, less secure storage. However,
 * an attacker being able to read and modify this storage, will be able to perform replay attacks,
 * e.g. on revocation lists.
 *
 * The API must fulfill following requirements:
 * * Files have sizes between 0 and 4000 bytes.
 * * The total size of all files together will not exceed 7000 bytes.
 * * No more than one file will be open at any given point in time.
 * * Data must be written in an atomic manner. This means, that in case of a write sequence (from
 *   opening the file until closing it) must either complete as a whole or fail completely. In case
 *   of a failure (most likely due to brown out or because the storage is full), the previous file
 *   must stay unchanged.
 *
 * If the Tapkey file system (TKFS) should be used for persistence, the implementation may forward
 * to tkExtImpl_Storage_File_tkfs_GetFileApi().
 *
 * @param[out] ppApi            Pointer to the api structure.
 * @param[out] ppApiContext     Pointer to a context value, being passed through to the api functions.
 */
void tkExt_Storage_File_GetFileApi(void* pExternalContext, tkext_storage_file_api_t const** ppApi, void** ppApiContext);

/**
 * @brief This function checks if the specified block is located in the secured storage.
 *
 * @param[in]  pExternalContext     Pointer to the external context
 * @param[in]  block                Block to be checked
 *
 * @return int
 * @retval 1        Block is located in the secured storage
 * @retval Other    Block is not located in the secured storage *
 */
int tkExt_Legacy_Storage_IsInSecureStorage(void* pExternalContext, TK_EXT_STORAGE_BLOCK block);

/**
 * @brief Reads specified amount of data from the specified block.
 *
 * The CRC was previously provided by Tapkey to the tkExt_Storage_WriteBlock function. Tapkey will
 * compute the expected CRC from the returned data compare the returned CRC to the expected value.
 *
 * @param[in]  pExternalContext     Pointer to the external context
 * @param[in]  block                A block to read the data from
 * @param[out] pData                Pointer to the buffer where the read data shall be stored
 * @param[in]  dataSize             Size of the data to be read
 * @param[out] pCrc                 Pointer to the buffer where the read CRC shall be stored
 * @param[in]  crcSize              Size of the CRC to be read
 *
 * @return int
 * @retval 0        Read successful
 * @retval Other    Error reading data
 */
int tkExt_Legacy_Storage_ReadBlock(void* pExternalContext, TK_EXT_STORAGE_BLOCK block, uint8_t* pData, uint32_t dataSize, uint8_t* pCrc, uint32_t crcSize);

/**
 * @brief Function that gets the number of overall log pages available.
 *
 * This returned number is  expected not to change over time. It does not relate to the number of
 * pages actually containing log data but to the overall number available.
 *
 * @param[in]  pExternalContext     Pointer to the external context
 *
 * @return int
 * @retval Number of pages
 */
uint16_t tkExt_Storage_GetNofLogPages(void* pExternalContext);

/**
 * @brief Function that erases the content of the specified log page.
 *
 * After erasing, the page is expected to be filled with 0xFF.
 *
 * @param[in]  pExternalContext     Pointer to the external context
 * @param[in]  pageNo               Number of page to be erased
 *
 * @return int
 * @retval 0        Success
 * @retval Other    Error
 */
int tkExt_Storage_EraseLogPage(void* pExternalContext, uint16_t pageNo);

/**
 * @brief Writes data to the specified log page.
 *
 * The data is written without erasing, by applying bitwise AND to the specified
 * data regions. Most often only parts of a page are written (i.e. a single log entry).
 *
 * @param[in]  pExternalContext     Pointer to the external context
 * @param[in]  pageNo               Number of the page to write
 * @param[in]  trgOffset            Offset
 * @param[in]  pData                Pointer to the data to be written
 * @param[in]  dataSize             Size of the data to be written
 * @param[in]  pCrc                 Pointer to the data's checksum to be written. If 0,
 *                                  no checksum should be written
 * @param[in]  crcSize              Size of the checksum to be written
 *
 * @return int
 * @retval 0        Write successful
 * @retval Other    Error writing data
 */
int tkExt_Storage_WriteLogPage(void* pExternalContext, uint16_t pageNo, uint32_t trgOffset, uint8_t const* pData, uint32_t dataSize, uint8_t const* pCrc, uint32_t crcSize);

/**
 * @brief Reads data from the specified log page into the provided buffer.
 *
 * @param[in]  pExternalContext     Pointer to the external context
 * @param[in]  pageNo               Number of the page to read the data from
 * @param[in]  srcOffset            Offset
 * @param[out] pData                Pointer to the buffer where the read data shall be stored
 * @param[in]  dataSize             Size of the data to be read
 * @param[out] pCrc                 Pointer to the buffer where the read CRC shall be stored
 * @param[in]  crcSize              Size of the CRC to be read
 *
 * @return int
 * @retval 0        Read successful
 * @retval Other    Error reading data
 */
int tkExt_Storage_ReadLogPage(void* pExternalContext, uint16_t pageNo, uint32_t srcOffset, uint8_t* pData, uint32_t dataSize, uint8_t* pCrc, uint32_t crcSize);

/**
 * @brief Writes a chunk of firmware data to the buffer for new firmware.
 *
 * @param[in]  pExternalContext     Pointer to the external context
 * @param[in]  trgOffset            Offset in the buffer
 * @param[in]  dataSize             Size of the data to be written
 * @param[in]  pData                Pointer to the data to be written
 *
 * @return int
 * @retval 0        Write successful
 * @retval Other    Error writing data
 */
int tkExt_Storage_WriteFirmwareBlock(void* pExternalContext, uint32_t trgOffset, uint32_t dataSize, uint8_t const* pData);

/**
 * @brief Reads a chunk of firmware data from the buffer for new firmware to the provided memory buffer.
 *
 * @param[in]  pExternalContext     Pointer to the external context
 * @param[in]  srcOffset            Offset of the chunk inside the buffer
 * @param[in]  dataSize             Size of the data to be read
 * @param[out] pData                Pointer to the buffer where the read data shall be stored
 *
 * @return int
 * @retval 0        Read successful
 * @retval Other    Error reading data
 */
int tkExt_Storage_ReadFirmwareBlock(void* pExternalContext, uint32_t srcOffset, uint32_t dataSize, uint8_t* pData);


/*
    ----------------------
    Firmware Upgrade
    ----------------------
*/

/**
 * @brief This function informs the implementor about a new firmware being uploaded.
 *
 * This function may be used to validate the metadata or perform any preparing steps.
 * This function will be called at the very beginning of an update sequence. If the upload
 * is interrupted and continued, this function will be called again. The implementor
 * therefore mustn't erase data previously written via tkExt_Storage_WriteFirmwareBlock,
 * except a new firmware is to be uploaded, which could be detected via the passed metadata.
 * The function must not block for more than 5 ms.
 *
 * @param[in]  pExternalContext     Pointer to the external context
 * @param[in]  size                 Size of the firmware package to activate.
 * @param[in]  pMetadata            The metadata provided by the manufacturer with the firmware.
 * @param[in]  metadataSize         Size of the metadata.
 *
 * @return int
 * @retval 0        Success
 * @retval Other    Error
 */
int tkExt_PrepareFirmwareUpload(void* pExternalContext, uint32_t size, uint8_t* pMetadata, uint8_t metadataSize);

/**
 * @brief Completes the upload of a new firmware that was previously written via tkExt_Storage_WriteFirmwareBlock.
 *
 * Prepares the device to install the new firmware on the next restart. The function doesn't trigger
 * the restart itself. The data passed via pMetadata is the data provided by the manufacturer with
 * the firmware package. If the uploaded firmware is stored in memory external to the microcontroller
 * it is installed on, the metadata *should* contain a cryptographic checksum that is kept in internal
 * memory to ensure, it cannot be manipulated by an attacker. The update procedure must ensure, that
 * the firmware package doesn't get manipulated between writing via tkExt_Storage_WriteFirmwareBlock
 * and final activation by the boot loader or at least, that a manipulation gets detected and a
 * manipulated firmware doesn't get activated. A strong cryptographic checksum (SHA-256, etc.) that can
 * be used for that purpose may be included in the metadata. The metadata is provided by the firmware
 * manufacturer and is specific for the actual implementation of the update procedure. On installation,
 * an implementor would check the integrity of the firmware package before copying it to the internal
 * program space and again after copying it to the internal program space, before finally activating it.
 * This function must not block for more than 5 ms.
 *
 * @param[in]  pExternalContext     Pointer to the external context
 * @param[in]  size                 Size of the firmware package to activate.
 * @param[in]  pMetadata            The metadata provided by the manufacturer with the firmware.
 * @param[in]  metadataSize         Size of the metadata.
 *
 * @return int
 * @retval 0        Success
 * @retval Other    Error
 */
int tkExt_ActivateNewFirmwareOnNextRestart(void* pExternalContext, uint32_t size, uint8_t* pMetadata, uint8_t metadataSize);

/**
 * @brief Reboots the device.
 *
 * If a firmware activation was triggered via tkExt_ActivateNewFirmwareOnNextRestart, the new
 * firmware is installed during reboot.
 *
 * @param[in]  pExternalContext     Pointer to the external context
 *
 * @return int
 * @retval Other than 0     Error
 */
int tkExt_Reboot(void* pExternalContext);

Operating System Abstraction Layer (OSAL)

The OSAL is defined and required by the NXP NFC Reader Library. It’s responsible for providing certain operating system functions to the library. Following functions need to be implemented:

/**
* \brief Timer wait function
*
* \return Status code
* \retval #PH_ERR_SUCCESS Operation successful.
* \retval Other Depending on implementation and underlaying component.
*/
phStatus_t phOsal_Timer_Wait( void      *pDataParams,       /**< [In] Pointer to this layers parameter structure. */
                              uint8_t    bTimerDelayUnit,   /**< [In] Delay value unit could be in microseconds or milliseconds */
                              uint16_t   wDelay             /**< [In] Time Delay */
                             );

/**
* \brief Waits for the next interrupt relevant to the caller. On potentially long waits,
* suggestSleep is set which might be respected by the implementor by bringing the MCU
* into a sleep state. If suggestSleep is not set, this function should return quickly
* after a relevant interrupt occured to avoid timing issues.
* Interrupt-less implementations may implement this function equivalently to phOsal_Yield.
* If a WDT is running, this function should not be used to feed it, because it might be called
* even if the SW is in an unrecoverable state.
*
* \return Status code
* \retval #PH_ERR_SUCCESS Operation successful.
* \retval Other Depending on implementation and underlaying component.
*/
phStatus_t phOsal_WaitForIrq(
                        void * pDataParams,  /**< [In] Pointer to this layers parameter structure. */
                        int suggestSleep     /**< [In] Flag to suggest implementing layer whether or not to sleep during wait. */
                        );

Hint

Example implementations of the OSAL for use with the Tapkey reference hardware are provided with this SDK.

Bus Abstraction Layer (BAL)

The BAL is defined and required by the NXP NFC Reader Library. It’s responsible for connecting the library to the NFC controller chip. I.e. it provides functions to send and receive data to/from the chip (e.g. via SPI or I²C) as implemented by the target hardware. The following prototype represents the central exchange function for exchanging data via SPI:

/**
* \brief Perform Data Exchange on the bus.
* \return Status code
* \retval #PH_ERR_SUCCESS Operation successful.
* \retval #PH_ERR_INVALID_PARAMETER \b wOption is invalid.
* \retval #PH_ERR_IO_TIMEOUT No response received within given time frame.
* \retval #PH_ERR_BUFFER_OVERFLOW Response is too big for either given receive buffer or internal buffer.
* \retval #PH_ERR_INTERFACE_ERROR Communication error.
*/
phStatus_t phbalReg_Exchange(
                             void * pDataParams,    /**< [In] Pointer to this layer's parameter structure. */
                             uint16_t wOption,      /**< [In] Option parameter. */
                             uint8_t * pTxBuffer,   /**< [In] Data to transmit. */
                             uint16_t wTxLength,    /**< [In] Number of bytes to transmit. */
                             uint16_t wRxBufSize,   /**< [In] Size of receive buffer / Number of bytes to receive (depending on implementation). */
                             uint8_t * pRxBuffer,   /**< [Out] Received data. */
                             uint16_t * pRxLength   /**< [Out] Number of received data bytes. */
                             );

Additonal hooks, that must be implemented but don’t require special functionality for Tapkey and may simply return PH_ERR_SUCCESS. These functions may be used as hooks for app-defined functionality.

/**
* \brief Open communication port.
* \return Status code
* \retval #PH_ERR_SUCCESS Operation successful.
* \retval #PH_ERR_USE_CONDITION Communication port is already open.
* \retval #PH_ERR_INTERFACE_ERROR Error while opening port.
*/
phStatus_t phbalReg_OpenPort(
                             void * pDataParams /**< [In] Pointer to this layer's parameter structure. */
                             );

/**
* \brief Close communication port.
* \return Status code
* \retval #PH_ERR_SUCCESS Operation successful.
* \retval #PH_ERR_USE_CONDITION Communication port is not open.
* \retval #PH_ERR_INTERFACE_ERROR Error while closing port.
*/
phStatus_t phbalReg_ClosePort(
                              void * pDataParams    /**< [In] Pointer to this layer's parameter structure. */
                              );

/**
* \brief Set configuration parameter.
* \return Status code
* \retval #PH_ERR_SUCCESS Operation successful.
* \retval #PH_ERR_UNSUPPORTED_PARAMETER Configuration is not supported or invalid.
* \retval #PH_ERR_INVALID_PARAMETER Parameter value is invalid.
* \retval #PH_ERR_INTERFACE_ERROR Communication error.
*/
phStatus_t phbalReg_SetConfig(
                              void * pDataParams,   /**< [In] Pointer to this layer's parameter structure. */
                              uint16_t wConfig,     /**< [In] Configuration Identifier. */
                              uint16_t wValue       /**< [In] Configuration Value. */
                              );

Hint

An example implementation of the BAL for use with an SPI bus on STM32L1 series MCUs is provided with this SDK.

Provisioning

At manufacturing time, new locks are provided with initialization data like the lock identity and master keys. This data is included in a so called provisioning file. For each lock an individual provisioning file is provided which is to be provided to the Tapkey library exactly once at manufacturing time by calling tkIfInit_ResetAndPerformProvisioning. Calling this function resets all data and must not be executed more than once.

Initialization

At startup time the consuming application initializes the storage component by calling tkIfInit_StorageInit(). It then reads the persisted storage data by calling tkIfInit_StorageRestore.

The provided cache memory block must have at least the size as specified in TK_CACHE_MIN_SIZE, which is approximately 2 kB.

Main loop

The application implements a main loop, calling into the NFC Reader Library and Tapkey library. It should implement some kind of field detection and call into the NFC routines as soon as some potential NFC device is detected. It should put the device into battery saving mode while no peer is present. An example implementation is provided with this SDK.

Hint

An example implementation is provided with this SDK. The example as designed does not implement any power saving features.

#ifndef _TAPKEY_INTEGRATION_H_
#define _TAPKEY_INTEGRATION_H_

#include <tkDef.h>
#include <tkInterface.h>
#include <tk_ce_iso7816.h>
#include <tk_ce_tapkeyT4NdefApp.h>
#include <tkCardDep.h>
#include <tkCardAppIso7816.h>
#include <tkCardApp.h>

/** Function pointer definition for a disconnecting callback function */
typedef void (*tk_OnDisconnectingCallback)(int* pWaitForCardRemoval);

/**  Tapkey card emulation types */
typedef enum {
    TK_CE_STATE_UNINITIALIZED = 0,          /**< Uninitalized state */
    TK_CE_STATE_NORMAL = 1,                 /**< Normal state */
    TK_CE_STATE_WAIT_FOR_CARD_REMOVAL = 2,  /**< Wait for card removal state */
} TK_CE_STATE;

/** Tapkey main context structure */
typedef struct {
    tk_context tkContext;   /**XXX*/
    tk_ce_tapkeyT4NdefApp_state tapkeyCeT4AppState;
    tk_ce_iso7816_app tkCeApp;
    tk_ce_iso7816_app* tkCeApps[1];
    tk_ce_iso7816_state ceAppState;
    TK_CE_STATE ceState;
    TK_TRANSPORT_CHANNELS supportedTransportChannels;
    uint8_t* pRam;
    uint16_t ramSize;
    tk_OnDisconnectingCallback onDisconnectingCallback;
} tapkey_mainContext;

/** Tapkey activation level types */
typedef enum {
    TK_ACTIVATION_LEVEL_NONE = 0,           /**< No activation level */
    TK_ACTIVATION_LEVEL_PEER_DETECTED = 1,  /**< Peer detected */
    TK_ACTIVATION_LEVEL_APPLICATION = 2,    /**< Application */
} TK_ACTIVATION_LEVEL;

/** Tapkey invocation reason types */
typedef enum {
    TK_INVOCATION_REASON_Unknown,               /**< Unknown invocation reason. */
    TK_INVOCATION_REASON_Polling,               /**< Invocation happens without previous detection
                                                 *   of any devices. This will trigger a dual
                                                 *   CE/RW polling loop.
                                                 */
    TK_INVOCATION_REASON_DetectedUnclassified,  /**< Something unclassified was detected, e.g. by
                                                 *   an inductive sensor. This will trigger a dual
                                                 *   CE/RW polling loop.
                                                 */
    TK_INVOCATION_REASON_DetectedTarget,        /**< An NFC target was detected, e.g. a card. This
                                                 *   will cause the polling loop to start in RW
                                                 *   mode and eventually falling falling back to
                                                 *   CE mode if no target can be activated.
                                                 */
    TK_INVOCATION_REASON_DetectedInitiator,     /**< An NFC initiator was detected, e.g. a smartphone
                                                 *   acting as reader. This will cause the polling
                                                 *   loop to only run in CE mode but not to switch to
                                                 *   RW mode.
                                                 */
    TK_INVOCATION_REASON_ActAsReader,           /**< The polling loop should be set up to only connect
                                                 *   to targets. This will cause the polling loop to
                                                 *   only run in RW mode. This is similar to
                                                 *   TK_INVOCATION_REASON_DetectedTarget but doesn't
                                                 *   fall back to CE mode if no target is detected.
                                                 */
} TK_INVOCATION_REASON;

/**
 * @brief Initializes the provided tapkey_mainContext structure.
 *
 * @param[in]  pMainContext             The structure to be initialized.
 * @param[in]  pStorage                 Pointer to the pre-initialized storage component
 * @param[in]  pExternalContext         Pointer to an application-defined structure,
 *                                      passed to tkExt functions.
 * @param[in]  isFirstAfterDeviceStart  Indicates, whether this is the first call to this
 *                                      function after restart; usually 1
 *
 * @return TK_RET
 */
TK_RET tapkey_integration_init( tapkey_mainContext* pMainContext,
                                tk_storage* pStorage,
                                void* pExternalContext,
                                int isFirstAfterDeviceStart
                                );

/**
 * @brief Runs a single iteration of a Tapkey communication sequence.
 *
 * Sequence involves:
 *      detect a possible peer (active or passive) by trying communication in NFC
 *      initiator and target mode
 *      delegate an established connection to the TLCP library
 *      process multiple request/response pairs while communicating with a single peer
 *
 * @param[in]  pMainContext             Pointer to the pre-initialized tapkey_mainContext
 *                                      structure.
 * @param[in]  pBal                     Pointer to the BAL (bus abstraction layer) struct
 *                                      as used by the NXP reader lib.
 * @param[in]  pOsal                    Pointer to the OSAL (OS abstraction layer) struct
 *                                      as used by the NXP reader lib.
 * @param[in]  invocationReason         Indicates the reason for invoking Tapkey. Might be
 *                                      used to configure the NFC polling loop.
 *
 * @return TK_ACTIVATION_LEVEL
 * @retval Level of activation reached
 */
TK_ACTIVATION_LEVEL tapkey_integration_runSingleIteration( tapkey_mainContext* pMainContext,
                                                           void* pBal,
                                                           void* pOsal,
                                                           TK_INVOCATION_REASON invocationReason
);

/**
 * @brief Registers a callback function that is called when Tapkey is about to disconnect
 *  the current NFC connection (in either RW or CE mode).
 *
 * It allows the called function to override, whether or not Tapkey should wait for the peer
 * (card or smartphone) to be removed before returning from the tapkey_integration_runSingleIteration.
 * If the *pWaitForCardRemoval parameter passed to the callback function is changed from <> 0 to 0,
 * the caller of tapkey_integration_runSingleIteration MUST make sure, that
 * tapkey_integration_runSingleIteration is not called again immediately, because this would cause
 * behavior unexpected to the end user. I.e. the caller must perform a presence check on her own or
 * wait for a reasonable amount of time (e.g. for an unlock duration).
 *
 * @param[in]  pMainContext             Pointer to the pre-initialized tapkey_mainContext structure.
 * @param[in]  onDisconnectingCallback  Callback to be invoked on disconnection.
 *
 * @return void
 */
void tapkey_integration_setOnDisconnectingCallback(
                    tapkey_mainContext* pMainContext,
                    tk_OnDisconnectingCallback onDisconnectingCallback);

#endif /* _TAPKEY_INTEGRATION_H_ */

A call into tapkey_integration_runSingleIteration may block as long as a peer (smartphone or ident media) is present, which usually takes one to a few seconds (but can take up to minutes in scenarios like FW-upgrades). Processing of signals, issued by this function via calls to tkExt functions (i.e. triggering a lock action or issuing signals), must not be delayed until the function returns.

Hint

Pseudo-code for a dual main loop supporting switching from a legacy mode to Tapkey on first binding is included in the examples section.

Measurement callbacks

On systems that provide measurements for critical environmental values, Tapkey stores these measurements and sends them to connected mobile devices and the Tapkey backend. The values can be used to issue battery alarms to the user or send battery alarms by e-mail to the lock owner, etc. Tapkey doesn’t trigger the measurements itself, instead the calling application should inform the Tapkey library if certain measurements took place by calling following function:

typedef enum {
    TK_MEASUREMENT_REASON_Unknown,

    // Measurement was triggered as a side effect of some user-triggered activity, like locking.
    TK_MEASUREMENT_REASON_SideEffectOfUserActivity,

    // Measurement was triggered due to a periodic schedule (e.g. once per day).
    TK_MEASUREMENT_REASON_PeriodicSchedule,

    TK_MEASUREMENT_REASON_Other,

} TK_MEASUREMENT_REASON;

// Informs Tapkey that a measurement took place and the corresponding values can be retrieved via
// tkExt_GetMeasurement. If multiple measurement take place at once (that is, logically related and
// within a short period of time) this function should only get called once after all related
// measurements were completed. The function might block for a short period of time, e.g. for
// retrieving measured data and writing the received data to the logs.
//   param measurementTypes: The measurement types that have been measured. This is interpreted as
//         bit field, where each bit represents a measurement type according to (1 << measurementType)
//         and where the measurementTypes correspond to the values provided by
//         tkExt_GetSupportedMeasurementTypes.
//   param measurementReason: Specifies the reason, why the measurement took place. Tapkey might
//         decide whether to store the value to the logs, based on this value. E.g. if access logs
//         are turned off, Tapkey won't store measurements based caused as side-effect of a locking
//         activity.
void tkIfOp_OnMeasurement(tk_context* pContext, uint32_t measurementTypes, TK_MEASUREMENT_REASON measurementReason);

Custom commands

/*
    ----------------------
    Custom Commands
    ----------------------

    Custom commands can be used to offer functionality to a lock owner, that is specific to the
    current device type and is not covered by the Tapkey core. These functions can be invoked by
    the user while the lock is in owner mode.

    The lock provides the definitions of these commands to the communicating mobile device on
    demand. The mobile device might then display a menu structure reflecting the custom command
    structure, allowing the user (the lock owner) to trigger these commands.

    Candidates for such functions include:
    * temporarily switch the lock to some native, none-Tapkey communication scheme.
    * permanently switch the lock to a different mode.

    Implementation procedure:
    * Provide an implementation corresponding to the tkIf_GetCustomCommandsDescriptor_t prototype
      which will be called to query custom commands available to the end user.
    * Provide an implementation corresponding to the tkIf_ExecuteCustomCommand_t prototype, which
      will be called to execute a certain custom command.
    * Register the implemented functions using tkIfInit_RegisterCustomCommandCallbacks.


    Security Considerations:
    At this time, custom commands can only be executed while a lock is in owner mode. Tapkey
    ensures, that the owner mode is enabled, when a custom command is to be executed. In future
    versions this behavior might change in favor of a more flexible model where the custom app
    may specify the lock modes in which a custom command may be executed. This configuration would
    be specified by the application in the tk_customcommanddescriptor structures. To avoid missing
    new configuration fields in future versions, initialization of these structures should always
    be performed using the macros/functions provided by the SDK, i.e.
    TK_CUSTOMCOMMANDDSR_STATICINIT.

    Consider that the only thing validated by Tapkey when executing a custom command is whether or
    not the lock is in owner mode. The customCommandId passed to the implementation most likely
    isn't validated against the structures provided by the tkIf_GetCustomCommandsDescriptor_t
    function.

    While in owner mode, communication between lock and mobile/card is not protected by strong
    cryptography. The data passed to the tkIf_ExecuteCustomCommand_t function therefore must be
    considered as not confidential and as potentially malicious. Implementations of custom commands
    therefore MUST be considered as potential target for attackers, thus requiring strong
    protection against buffer overflows, etc.

    Any security-related checks, except for the lock being in owner mode,  are up to the
    implementor. Custom commands MUST NOT compromise any of Tapkey's security mechanisms.

    E.g., Tapkey doesn't allow triggering an unlock action while in owner mode. This implies, that
    a custom command may never trigger an unlock action without any further strong authentication
    functionality, preventing the functionality from being used outside the factory.

    E.g., if using this functionality for triggering firmware upgrades outside the factory,
    security of the update process must be ensured by the custom implementation, including checks
    for upgrade image integrity, authenticity, compatibility, downgrade prevention, etc. by means
    of strong cryptography.


    Implementation of custom commands is optional. Don't implement custom commands, unless
    absolutely required!

    Example implementation:

        #define CUSTOM_COMMAND_TEST_LED_ID 42

        static int tkIfCallbackImpl_GetCustomCommandsDescriptor(void* pExternalContext, uint8_t* pData, uint16_t maxDataSize, uint16_t* pActualDataSize) {

            static const tk_customcommanddescriptor customCommandTest = TK_CUSTOMCOMMANDDSR_STATICINIT(
                    CUSTOM_COMMAND_TEST_LED_ID,
                    "testLed",
                    "Test LEDs"
                    );

            static const tk_customcommanddescriptor const* customCommands[] = {
                    &customCommandTest,
            };

            if (tkCodecCustomCommandsDescriptorEncode(&customCommands[0], sizeof(customCommands)/sizeof(customCommands[0]), pData, maxDataSize, pActualDataSize) != TK_RET_OK)
                return -1;

            return 0;
        }

        static int tkIfCallbackImpl_ExecuteCustomCommand(void* pExternalContext, uint16_t customCommandId, uint8_t* pCommandData, uint16_t commandDataSize) {

            switch (customCommandId) {
            case CUSTOM_COMMAND_TEST_LED_ID: {

                testLeds();
                return 0;
            }

            default:
                return 1;
            }
        }

        void tkIfCallbackImpl_RegisterCallbacks(tk_storage* pStorage) {

            tkIfInit_RegisterCustomCommandCallbacks(pStorage, tkIfCallbackImpl_GetCustomCommandsDescriptor, tkIfCallbackImpl_ExecuteCustomCommand);
        }

*/


typedef struct
{
    // the ID that identifies the custom command on this device.
    uint16_t customCommandId;

    // UTF-8 string that allows the mobile client to look up text and other resources related
    // to this command. The string is not 0-terminated.
    uint8_t* pCommandSymbolUtf8;
    uint16_t commandSymbolSize;

    // The default text printed on the button offered to the user, encoded as UTF-8 string.
    // The string is not 0-terminated.
    uint8_t* pDefaultCommandTitleUtf8;
    uint16_t defaultCommandTitleSize;
} tk_customcommanddescriptor;

// Macro to initialize tk_customcommanddescriptor. The structure should always be initialized using
// this macro to enforce compiler errors in case the structure changes in future versions.
#define TK_CUSTOMCOMMANDDSR_STATICINIT(customCommandId, commandSymbolUtf8, defaultCommandTitleUtf8) { \
    customCommandId,                    \
    &commandSymbolUtf8[0],              \
    sizeof(commandSymbolUtf8)-1,        \
    &defaultCommandTitleUtf8[0],        \
    sizeof(defaultCommandTitleUtf8)-1   \
    }

// Encodes the custom command structures as required for the functions passed to
// tkIfInit_RegisterCustomCommandCallbacks.
//
// param ppCommandDescriptors: Pointer to array of pointers to command descriptors to be encoded.
// param nofCommandDescriptors: Number of commands to be encoded.
// param pData: Buffer to hold the encoded data.
// param dataCapacity: Capacity of the passed buffer in number of bytes.
// param pActualDataSize: Out parameter: Actual size of the encoded buffer.
// returns: TK_RET_OK if successful, a different value otherwise.
TK_RET tkIfCustomCommand_EncodeCustomCommandsDescriptor(tk_customcommanddescriptor const* const* ppCommandDescriptors, uint16_t nofCommandDescriptors, uint8_t* pData, uint16_t dataCapacity, uint16_t* pActualDataSize);

// Prototype of a function, called to query a description of available custom commands from the
// implementing application.
// The tkIfCustomCommand_EncodeCustomCommandsDescriptor function should be used to encode the data
// structure to be returned. Examples of how this function may be implemented can be found in the
// Tapkey SDK, tkIf_Callbacks.*.
// param pExternalContext: The callback context passed to Tapkey by the application.
// param pData: Pointer to the memory block to receive the data structure to return.
// param maxDataSize: Size of the memory block passed in pData in bytes.
// param pActualDataSize: Pointer to the size of field, receiving the actual size of the returned
//       structure in bytes.
// returns: 0 if successful, 1 if there are no custom commands, -1 on any error.
typedef int(*tkIf_GetCustomCommandsDescriptor_t)(void* pExternalContext, uint8_t* pData, uint16_t maxDataSize, uint16_t* pActualDataSize);

// Prototype of a function, called to execute a custom commands by the implementing application.
// param pExternalContext: The callback context passed to Tapkey by the application.
// param customCommandId: The ID of the custom command to execute. May not be verified by Tapkey.
// param pCommandData: Pointer to a memory block, holding additional data passed in tby the client.
//       Might be NULL if commandDataSize == 0.
// param commandDataSize: Size of the memory block passed in pCommandData in bytes.
// returns: 0 if successful, 1 if there is no such custom commands, -1 on any error.
typedef int(*tkIf_ExecuteCustomCommand_t)(void* pExternalContext, uint16_t customCommandId, uint8_t* pCommandData, uint16_t commandDataSize);

// Allows the registration of callback functions for implementation of custom owner mode functions.
// For an example of how to implement the passed callback functions refer to the tkIf_Callback.c
// file provided with the Tapkey SDK.
// Registration of these functions is optional.
//
// param getCustomCommandDescriptorFunc: Called to retrieve an encoded description of the custom
//       commands available to the end user. The tkIfCustomCommand_EncodeCustomCommandsDescriptor
//       function should be used to encode the data structure to be returned.
// param executeCustomCommand: Callback that is called by Tapkey to execute a certain custom
//       command. The data passed (customCommandId, pData) is the data passed by the client app
//       (mobile app or card). Tapkey ensures, that the lock is in owner mode, before this function
//       is called. However, the actual customCommandId may or may not be validated against the
//       descriptors returned by the function passed in getCustomCommandDescriptorFunc.
void tkIfInit_RegisterCustomCommandCallbacks(tk_storage* pStorage, tkIf_GetCustomCommandsDescriptor_t getCustomCommandDescriptorFunc, tkIf_ExecuteCustomCommand_t executeCustomCommand);

Async unlock response

The tkExt_TriggerLock function is required to return immediately without waiting for some mechanical processes. In some cases, implementers might want to wait for the mechanical sequence to complete in order to evaluate the success of the operation and return it to the mobile client. For such cases Tapkey offers functionality to wait for the asynchronous unlock result and return the result to the client.

The following example illustrates the flow when waiting for async trigger lock results.

sequenceDiagram Mobile->>Tapkey Lock SDK: Trigger Lock activate Tapkey Lock SDK Tapkey Lock SDK->>+Implementing FW:tkExt_TriggerLock Implementing FW-->>-Tapkey Lock SDK:TK_EXT_RET_AsyncResult Implementing FW->>Mechanics:Trigger Lock activate Mechanics loop callback Tapkey Lock SDK->>+Implementing FW:tkIf_TriggerLock_WaitForAsyncResult_t Implementing FW-->>-Tapkey Lock SDK:TK_EXT_RET_AsyncResult end Mechanics-->>Implementing FW:mechanical sequence completed deactivate Mechanics Tapkey Lock SDK-->>Mobile:potentially other interaction Tapkey Lock SDK->>+Implementing FW:tkIf_TriggerLock_WaitForAsyncResult_t Implementing FW-->>-Tapkey Lock SDK:TK_EXT_RET_Ok Tapkey Lock SDK->>+Implementing FW:tkIf_TriggerLock_GetAsyncResultData_t Implementing FW-->>-Tapkey Lock SDK:TK_EXT_RET_Ok Tapkey Lock SDK-->>Mobile:TK_EXT_RET_Ok deactivate Tapkey Lock SDK

To allow the Tapkey libraries to wait for async results, the implementing firmware uses the tkIfInit_RegisterTriggerLockAsyncResultCallbacks function to provide according callbacks. The involved funcions are as follows (see docs for tkExt_TriggerLock above).

// Prototype for a function that can be used to asynchronously wait for the result of a trigger
// lock signal. This function is not intended for awaiting the unlock period as passed to the
// tkExt_TriggerLock in the durationSec parameter. Instead it's intended to await the initiation of
// the command for no more than apx. 2 seconds. If the trigger lock sequence involves running a
// motor, this function may be used to wait for the motor rotation to complete and evaluate whether
// the mechanics resides in the expected state afterwards.
// The implementation may wait for the async result to arrive for a maximum duration
// as specified in the timeoutMs parameter. If the response hasn't arrived at that time, the
// function must return with error code TK_EXT_RET_AsncResult. if the function may be called again
// to coninue waiting. In such a case, the Tapkey library may or may not decide to call the
// function again at a later point in time in order to keep waiting. If the function may not be
// called again to continue waiting, TK_EXT_RET_ERR_Timeout shall be returned.
// It's crucial for the implementer to observe the timeoutMs value. Exceeding the
// specified timeout might cause unexpected behavior like aborts of the connection to the user's
// smartphone or even resets of the MCU caused by the watchdog timer.
// If an async result arrives without being queried by the Tapkey library (most likely because it
// arrived late), the result data must be discarded at least when the next trigger lock signal is
// issued.
// param timeoutMs: The timeout value in milliseconds to be observed by the implementor.
// param ppAsyncResponseHandle: The pointer to a handle representing the async result. If this
//       value is set by the implementor, the Tapkey library will also call the function provided
//       for getting the async result. The value will be passed to the
//       tkIf_TriggerLock_GetAsyncResultData_t in the pAsyncResponseHandle parameter.
// returns
//       TK_EXT_RET_Ok on success
//       TK_EXT_RET_AsncResult if the timeout period exceeded before the result arrived but the
//              result might arrive at a later, and waiting may be continued by calling this func
//              again.
//       TK_EXT_RET_ERR_Timeout if waiting timed out and waiting shall not be continued by calling
//              this function again for the given async result.
//       TK_EXT_RET_ERR_Generic on any unexpected error
typedef TK_EXT_RET (*tkIf_TriggerLock_WaitForAsyncResult_t)(void* pExternalContext, uint16_t timeoutMs, void** ppAsyncResponseHandle);

// Prototype of a function to retrieve async response data for a trigger lock result. This function
// will be called, if the tkIf_TriggerLock_WaitForAsyncResult_t returned without error and
// specified a value in the ppAsyncResponseHandle parameter.
// The data returned by this function via the pData buffer will be contained in the response sent
// to the smartphone application.
// param pAsyncResponseHandle: The handle provided by the tkIf_TriggerLock_WaitForAsyncResult_t
//       function.
// param pData: The buffer to hold the response data.
// returns
//       TK_EXT_RET_Ok on success
//       TK_EXT_RET_ERR_Generic on any unexpected error
//
typedef TK_EXT_RET (*tkIf_TriggerLock_GetAsyncResultData_t)(void* pExternalContext, void* pAsyncResponseHandle, uint8_t* pData, uint16_t maxDataSize, uint16_t* pActualDataSize);

// In system providing feedback on trigger lock signals asynchronously, this function can be used
// to provide callbacks, that the Tapkey libraries will use to (possibly) asynchronously query the
// operation outcome and maybe additional related data.
// param waitForAsyncResult: A function that is called to wait for the async response data to
//       arrive. See documentation for the function typedef for additional information.
// param getAsyncResultData: A function that is called to retrieve additional response data.
//       See documentation for the function typedef for additional information.
void tkIfInit_RegisterTriggerLockAsyncResultCallbacks(tk_storage* pStorage, tkIf_TriggerLock_WaitForAsyncResult_t waitForAsyncResult, tkIf_TriggerLock_GetAsyncResultData_t getAsyncResultData);