Add PSA Initial Attestation service

Attestation service can create a token on request, which contains a fix set of
device specific data.
Implementation:
-‘psa_initial_attest_get_token_size’- get exact size of initial attestation token in bytes.
-‘psa_initial_attest_get_token’- get the initial attestation token.
-‘psa_attestation_inject_key’ - Generate or import the attestation key pair and export the public part.

-Including CBOR lib and TFM attestation implemantation.
-Temporary claim’s data – no bootloader over V7 Single & Dual
pull/9668/head
Moran Peker 2019-02-11 23:04:59 +02:00 committed by Moran Peker
parent 6bdbe754cd
commit 9a4ea3d319
64 changed files with 19625 additions and 0 deletions

View File

@ -0,0 +1,26 @@
/*
* Copyright (c) 2018-2019, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#include "psa_attest_inject_key.h"
#include "psa_inject_attestation_key_impl.h"
psa_status_t
psa_attestation_inject_key(const uint8_t *key_data,
size_t key_data_length,
psa_key_type_t type,
uint8_t *public_key_data,
size_t public_key_data_size,
size_t *public_key_data_length)
{
psa_status_t status = PSA_SUCCESS;
status = psa_attestation_inject_key_impl(key_data,
key_data_length,
type,
public_key_data,
public_key_data_size,
public_key_data_length);
return (status);
}

View File

@ -0,0 +1,63 @@
/*
* Copyright (c) 2018-2019, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#include "psa_initial_attestation_api.h"
#include "psa/client.h"
#include "attestation.h"
#include <string.h>
int32_t g_caller_id = 0;
enum psa_attest_err_t
psa_initial_attest_get_token(const uint8_t *challenge_obj,
uint32_t challenge_size,
uint8_t *token,
uint32_t *token_size) {
enum psa_attest_err_t err;
err = attest_init();
if (err != PSA_ATTEST_ERR_SUCCESS)
{
return err;
}
psa_invec in_vec[1] = { { challenge_obj, challenge_size } };
psa_outvec out_vec[1] = { { token, *token_size } };
err = initial_attest_get_token(in_vec, 1, out_vec, 1);
if (err != PSA_ATTEST_ERR_SUCCESS)
{
return err;
}
*token_size = out_vec[0].len;
return err;
}
enum psa_attest_err_t
psa_initial_attest_get_token_size(uint32_t challenge_size,
uint32_t *token_size) {
enum psa_attest_err_t err;
err = attest_init();
if (err != PSA_ATTEST_ERR_SUCCESS)
{
return err;
}
psa_invec in_vec[1] = { { &challenge_size, sizeof(challenge_size) } };
psa_outvec out_vec[1] = { { token_size, sizeof(*token_size) } };
err = initial_attest_get_token_size(in_vec, 1, out_vec, 1);
if (err != PSA_ATTEST_ERR_SUCCESS)
{
return err;
}
return err;
}

View File

@ -0,0 +1,76 @@
/*
* Copyright (c) 2018-2019, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#include <stdint.h>
#include <string.h>
#include "attestation.h"
#include "attestation_bootloader_data.h"
#include "tfm_boot_status.h"
/*!
* \var shared_data_init_done
*
* \brief Indicates whether shared data area was already initialized.
*
*/
static uint32_t shared_data_init_done;
/*!
* \def SHARED_DATA_INITIALZED
*
* \brief Indicates that shared data was already initialized.
*/
#define SHARED_DATA_INITIALZED (1u)
enum psa_attest_err_t
attest_get_boot_data(uint8_t major_type, void *ptr, uint32_t len) {
if (shared_data_init_done == SHARED_DATA_INITIALZED)
{
return PSA_ATTEST_ERR_SUCCESS;
}
struct shared_data_tlv_header *tlv_header;
struct shared_data_tlv_header *ptr_tlv_header;
struct shared_data_tlv_entry *tlv_entry;
uintptr_t tlv_end, offset;
/* Get the boundaries of TLV section */
tlv_header = (struct shared_data_tlv_header *)BOOT_TFM_SHARED_DATA_BASE;
if (tlv_header->tlv_magic != SHARED_DATA_TLV_INFO_MAGIC)
{
return PSA_ATTEST_ERR_INIT_FAILED;
}
tlv_end = (uintptr_t)BOOT_TFM_SHARED_DATA_BASE + (uintptr_t)tlv_header->tlv_tot_len;
offset = (uintptr_t)BOOT_TFM_SHARED_DATA_BASE + (uintptr_t)SHARED_DATA_HEADER_SIZE;
/* Add header to output buffer as well */
if (len < SHARED_DATA_HEADER_SIZE)
{
return PSA_ATTEST_ERR_INIT_FAILED;
} else
{
ptr_tlv_header = (struct shared_data_tlv_header *)ptr;
ptr_tlv_header->tlv_magic = SHARED_DATA_TLV_INFO_MAGIC;
ptr_tlv_header->tlv_tot_len = SHARED_DATA_HEADER_SIZE;
}
ptr += SHARED_DATA_HEADER_SIZE;
/* Iterates over the TLV section and copy TLVs with requested major
* type to the provided buffer.
*/
for (; offset < tlv_end; offset += tlv_entry->tlv_len)
{
tlv_entry = (struct shared_data_tlv_entry *)offset;
if (GET_MAJOR(tlv_entry->tlv_type) == major_type) {
memcpy(ptr, (const void *)tlv_entry, tlv_entry->tlv_len);
ptr += tlv_entry->tlv_len;
ptr_tlv_header->tlv_tot_len += tlv_entry->tlv_len;
}
}
shared_data_init_done = SHARED_DATA_INITIALZED;
return PSA_ATTEST_ERR_SUCCESS;
}

View File

@ -0,0 +1,186 @@
/*
* Copyright (c) 2019, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#include "t_cose/src/t_cose_crypto.h"
#include "tfm_plat_defs.h"
#include "crypto.h"
#include "tfm_plat_crypto_keys.h"
#include <string.h>
static psa_hash_operation_t hash_handle;
enum t_cose_err_t
t_cose_crypto_pub_key_sign(int32_t cose_alg_id,
int32_t key_select,
struct useful_buf_c hash_to_sign,
struct useful_buf signature_buffer,
struct useful_buf_c *signature) {
enum t_cose_err_t cose_ret = T_COSE_SUCCESS;
psa_status_t crypto_ret;
(void)key_select;
const psa_key_id_t key_id = 17;
psa_key_handle_t handle = 0;
crypto_ret = psa_crypto_init();
if (crypto_ret != PSA_SUCCESS)
{
return T_COSE_ERR_HASH_GENERAL_FAIL;
}
crypto_ret = psa_open_key(PSA_KEY_LIFETIME_PERSISTENT, key_id, &handle);
if (crypto_ret != PSA_SUCCESS)
{
return T_COSE_ERR_NO_KID;
}
crypto_ret = psa_asymmetric_sign(handle,
PSA_ALG_ECDSA(PSA_ALG_SHA_256),
hash_to_sign.ptr,
hash_to_sign.len,
signature_buffer.ptr,
signature_buffer.len,
&(signature->len));
if (crypto_ret != PSA_SUCCESS)
{
psa_close_key(handle);
cose_ret = T_COSE_ERR_UNKNOWN_SIGNING_ALG;
} else
{
signature->ptr = signature_buffer.ptr;
}
psa_close_key(handle);
return cose_ret;
}
enum t_cose_err_t
t_cose_crypto_get_ec_pub_key(int32_t key_select,
int32_t *cose_curve_id,
struct useful_buf buf_to_hold_x_coord,
struct useful_buf buf_to_hold_y_coord,
struct useful_buf_c *x_coord,
struct useful_buf_c *y_coord) {
enum tfm_plat_err_t err;
enum ecc_curve_t cose_curve;
struct ecc_key_t attest_key = {0};
uint8_t key_buf[ECC_P_256_KEY_SIZE];
(void)key_select;
/* Get the initial attestation key */
err = tfm_plat_get_initial_attest_key(key_buf, sizeof(key_buf),
&attest_key, &cose_curve);
/* Check the availability of the private key */
if (err != TFM_PLAT_ERR_SUCCESS ||
attest_key.pubx_key == NULL ||
attest_key.puby_key == NULL)
{
return T_COSE_ERR_KEY_BUFFER_SIZE;
}
*cose_curve_id = (int32_t)cose_curve;
/* Check buffer size to avoid overflow */
if (buf_to_hold_x_coord.len < attest_key.pubx_key_size)
{
return T_COSE_ERR_KEY_BUFFER_SIZE;
}
/* Copy the X coordinate of the public key to the buffer */
memcpy(buf_to_hold_x_coord.ptr,
(const void *)attest_key.pubx_key,
attest_key.pubx_key_size);
/* Update size */
buf_to_hold_x_coord.len = attest_key.pubx_key_size;
/* Check buffer size to avoid overflow */
if (buf_to_hold_y_coord.len < attest_key.puby_key_size)
{
return T_COSE_ERR_KEY_BUFFER_SIZE;
}
/* Copy the Y coordinate of the public key to the buffer */
memcpy(buf_to_hold_y_coord.ptr,
(const void *)attest_key.puby_key,
attest_key.puby_key_size);
/* Update size */
buf_to_hold_y_coord.len = attest_key.puby_key_size;
x_coord->ptr = buf_to_hold_x_coord.ptr;
x_coord->len = buf_to_hold_x_coord.len;
y_coord->ptr = buf_to_hold_y_coord.ptr;
y_coord->len = buf_to_hold_y_coord.len;
return T_COSE_SUCCESS;
}
enum t_cose_err_t
t_cose_crypto_hash_start(struct t_cose_crypto_hash *hash_ctx,
int32_t cose_hash_alg_id) {
enum t_cose_err_t cose_ret = T_COSE_SUCCESS;
psa_status_t crypto_ret;
crypto_ret = psa_hash_setup(&hash_handle, PSA_ALG_SHA_256);
if (crypto_ret == PSA_ERROR_NOT_SUPPORTED)
{
cose_ret = T_COSE_ERR_UNSUPPORTED_HASH;
} else if (crypto_ret != PSA_SUCCESS)
{
cose_ret = T_COSE_ERR_HASH_GENERAL_FAIL;
}
return cose_ret;
}
void t_cose_crypto_hash_update(struct t_cose_crypto_hash *hash_ctx,
struct useful_buf_c data_to_hash)
{
if (data_to_hash.ptr != NULL)
{
psa_hash_update(&hash_handle, data_to_hash.ptr, data_to_hash.len);
} else
{
/* Intentionally do nothing, just computing the size of the token */
}
}
enum t_cose_err_t
t_cose_crypto_hash_finish(struct t_cose_crypto_hash *hash_ctx,
struct useful_buf buffer_to_hold_result,
struct useful_buf_c *hash_result) {
enum t_cose_err_t cose_ret = T_COSE_SUCCESS;
psa_status_t crypto_ret;
crypto_ret = psa_hash_finish(&hash_handle,
buffer_to_hold_result.ptr,
buffer_to_hold_result.len,
&(hash_result->len));
if (crypto_ret == PSA_ERROR_BUFFER_TOO_SMALL)
{
cose_ret = T_COSE_ERR_HASH_BUFFER_SIZE;
} else if (crypto_ret != PSA_SUCCESS)
{
cose_ret = T_COSE_ERR_HASH_GENERAL_FAIL;
}
if (cose_ret == T_COSE_SUCCESS)
{
hash_result->ptr = buffer_to_hold_result.ptr;
}
return crypto_ret;
}

View File

@ -0,0 +1,186 @@
/*
* Copyright (c) 2017-2019 ARM Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "tfm_plat_crypto_keys.h"
#include "crypto.h"
#include <stdlib.h>
#include <math.h>
#define ONE_BYTE (1u)
/**
* \brief Copy the key to the destination buffer
*
* \param[out] p_dst Pointer to buffer where to store the key
* \param[in] p_src Pointer to the key
* \param[in] size Length of the key
*/
static inline void copy_key(uint8_t *p_dst, const uint8_t *p_src, size_t size)
{
uint32_t i;
for (i = size; i > 0; i--) {
*p_dst = *p_src;
p_src++;
p_dst++;
}
}
static psa_status_t get_curve(psa_key_type_t type, enum ecc_curve_t *curve_type)
{
psa_ecc_curve_t curve = PSA_KEY_TYPE_GET_CURVE(type);
switch (curve) {
case PSA_ECC_CURVE_SECP256R1:
*curve_type = P_256;
break;
case PSA_ECC_CURVE_SECP384R1:
*curve_type = P_384;
break;
case PSA_ECC_CURVE_SECP521R1:
*curve_type = P_521;
break;
case PSA_ECC_CURVE_CURVE25519:
*curve_type = X25519;
break;
case PSA_ECC_CURVE_CURVE448:
*curve_type = X448;
break;
default:
return (PSA_ERROR_NOT_SUPPORTED);
}
return PSA_SUCCESS;
}
enum tfm_plat_err_t
tfm_plat_get_initial_attest_key(uint8_t *key_buf,
uint32_t size,
struct ecc_key_t *ecc_key,
enum ecc_curve_t *curve_type)
{
uint8_t *key_dst = NULL;
uint8_t *key_src;
uint32_t key_size;
psa_status_t crypto_ret;
uint8_t *public_key = NULL;
psa_key_type_t type;
psa_key_type_t public_type;
size_t bits;
size_t public_key_size = 0;
size_t public_key_length = 0;
uint32_t initial_attestation_public_x_key_size = 0;
uint32_t initial_attestation_public_y_key_size = 0;
const psa_key_id_t key_id = 17;
psa_key_handle_t handle = 0;
crypto_ret = psa_crypto_init();
if (crypto_ret != PSA_SUCCESS)
{
return TFM_PLAT_ERR_SYSTEM_ERR;
}
crypto_ret = psa_open_key(PSA_KEY_LIFETIME_PERSISTENT, key_id, &handle);
if (crypto_ret != PSA_SUCCESS)
{
return TFM_PLAT_ERR_SYSTEM_ERR;
}
crypto_ret = psa_get_key_information(handle, &type, &bits);
if (crypto_ret != PSA_SUCCESS)
{
return TFM_PLAT_ERR_SYSTEM_ERR;
}
if (!PSA_KEY_TYPE_IS_ECC(type))
{
return TFM_PLAT_ERR_SYSTEM_ERR;
}
public_type = PSA_KEY_TYPE_PUBLIC_KEY_OF_KEYPAIR(type);
public_key_size = PSA_KEY_EXPORT_MAX_SIZE(public_type, bits);
public_key = (uint8_t *) malloc(public_key_size);
if (public_key == NULL)
{
return TFM_PLAT_ERR_SYSTEM_ERR;
}
crypto_ret = psa_export_public_key(handle,
public_key, public_key_size,
&public_key_length);
if (crypto_ret != PSA_SUCCESS)
{
free(public_key);
return TFM_PLAT_ERR_SYSTEM_ERR;
}
/* Set the EC curve type which the key belongs to */
crypto_ret = get_curve(type, curve_type);
if (crypto_ret != PSA_SUCCESS)
{
free(public_key);
return TFM_PLAT_ERR_SYSTEM_ERR;
}
key_src = public_key;
key_dst = key_buf;
/* The representation of an ECC public key is:
** -The byte 0x04
** - `x_P` as a `ceiling(m/8)`-byte string, big-endian
** - `y_P` as a `ceiling(m/8)`-byte string, big-endian
** - where m is the bit size associated with the curve
** - 1 byte + 2 * point size
*/
initial_attestation_public_x_key_size = ceil((public_key_length - 1) / 2);
initial_attestation_public_y_key_size = ceil((public_key_length - 1) / 2);
/* Copy the x-coordinate of public key to the buffer */
if (initial_attestation_public_x_key_size != 0)
{
key_src = key_src + ONE_BYTE;
key_size = initial_attestation_public_x_key_size;
copy_key(key_dst, key_src, key_size);
ecc_key->pubx_key = key_dst;
ecc_key->pubx_key_size = key_size;
key_dst = key_dst + key_size;
} else
{
ecc_key->pubx_key = NULL;
ecc_key->pubx_key_size = 0;
}
/* Copy the y-coordinate of public key to the buffer */
if (initial_attestation_public_y_key_size != 0)
{
key_src += initial_attestation_public_x_key_size;
key_size = initial_attestation_public_y_key_size;
copy_key(key_dst, key_src, key_size);
ecc_key->puby_key = key_dst;
ecc_key->puby_key_size = key_size;
} else
{
ecc_key->puby_key = NULL;
ecc_key->puby_key_size = 0;
}
free(public_key);
return TFM_PLAT_ERR_SUCCESS;
}

View File

@ -0,0 +1,176 @@
/*
* Copyright (c) 2018-2019, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#include <stdint.h>
#include <string.h>
#include <stddef.h>
#include "tfm_plat_boot_seed.h"
#include "attestation_bootloader_data.h"
#include "tfm_attest_hal.h"
#include "psa_initial_attestation_api.h"
#include "attestation.h"
#include "crypto.h"
extern int32_t g_caller_id;
#define ATTEST_PUB_KEY_SHA_256_SIZE (32u)
/* Hash of attestation public key */
static enum tfm_plat_err_t attest_public_key_sha256(uint32_t *size, uint8_t *buf)
{
const psa_key_id_t key_id = 17;
psa_key_handle_t handle = 0;
uint8_t *public_key = NULL;
psa_key_type_t type;
psa_key_type_t public_type;
size_t bits;
size_t public_key_size = 0;
size_t public_key_length = 0;
psa_status_t crypto_ret;
psa_hash_operation_t hash_handle;
crypto_ret = psa_crypto_init();
if (crypto_ret != PSA_SUCCESS) {
return TFM_PLAT_ERR_SYSTEM_ERR;
}
crypto_ret = psa_open_key(PSA_KEY_LIFETIME_PERSISTENT, key_id, &handle);
if (crypto_ret != PSA_SUCCESS) {
return TFM_PLAT_ERR_SYSTEM_ERR;
}
crypto_ret = psa_get_key_information(handle, &type, &bits);
if (crypto_ret != PSA_SUCCESS) {
return TFM_PLAT_ERR_SYSTEM_ERR;
}
if (!PSA_KEY_TYPE_IS_ECC(type)) {
return TFM_PLAT_ERR_SYSTEM_ERR;
}
public_type = PSA_KEY_TYPE_PUBLIC_KEY_OF_KEYPAIR(type);
public_key_size = PSA_KEY_EXPORT_MAX_SIZE(public_type, bits);
public_key = (uint8_t *) malloc(public_key_size);
if (public_key == NULL) {
return TFM_PLAT_ERR_SYSTEM_ERR;
}
crypto_ret = psa_export_public_key(handle,
public_key, public_key_size,
&public_key_length);
if (crypto_ret != PSA_SUCCESS) {
free(public_key);
return TFM_PLAT_ERR_SYSTEM_ERR;
}
crypto_ret = psa_hash_setup(&hash_handle, PSA_ALG_SHA_256);
if (crypto_ret != PSA_SUCCESS) {
free(public_key);
return TFM_PLAT_ERR_SYSTEM_ERR;
}
psa_hash_update(&hash_handle, public_key, public_key_length);
crypto_ret = psa_hash_finish(&hash_handle,
buf,
*size,
(size_t *) size);
if (crypto_ret != PSA_SUCCESS) {
free(public_key);
return TFM_PLAT_ERR_SYSTEM_ERR;
}
free(public_key);
return TFM_PLAT_ERR_SUCCESS;
}
/**
* \brief Copy the device specific ID to the destination buffer
*
* \param[out] p_dst Pointer to buffer where to store ID
* \param[in] p_src Pointer to the ID
* \param[in] size Length of the ID
*/
static inline void copy_id(uint8_t *p_dst, uint8_t *p_src, size_t size)
{
uint32_t i;
for (i = size; i > 0; i--) {
*p_dst = *p_src;
p_src++;
p_dst++;
}
}
enum psa_attest_err_t attest_get_caller_client_id(int32_t *caller_id)
{
*caller_id = g_caller_id;
return PSA_ATTEST_ERR_SUCCESS;
}
enum tfm_plat_err_t tfm_plat_get_boot_seed(uint32_t size, uint8_t *buf)
{
return PSA_ATTEST_ERR_CLAIM_UNAVAILABLE;
}
/**
* Instance ID is mapped to EAT Universal Entity ID (UEID)
* This implementation creates the instance ID as follows:
* - byte 0: 0x01 indicates the type of UEID to be GUID
* - byte 1-32: Hash of attestation public key. Public key is hashed in raw
* format without any encoding.
*/
enum tfm_plat_err_t tfm_plat_get_instance_id(uint32_t *size, uint8_t *buf)
{
enum tfm_plat_err_t status;
uint8_t *p_dst;
uint8_t p_src[ATTEST_PUB_KEY_SHA_256_SIZE];
uint32_t p_src_size = ATTEST_PUB_KEY_SHA_256_SIZE;
buf[0] = 0x01; /* First byte is type byte: 0x01 indicates GUID */
p_dst = &buf[1];
status = attest_public_key_sha256(&p_src_size, p_src);
copy_id(p_dst, p_src, p_src_size);
/* Instance ID size: 1 type byte + size of public key hash */
*size = p_src_size + 1;
return status;
}
enum tfm_plat_err_t tfm_plat_get_hw_version(uint32_t *size, uint8_t *buf)
{
return PSA_ATTEST_ERR_CLAIM_UNAVAILABLE;
}
enum tfm_plat_err_t tfm_plat_get_implementation_id(uint32_t *size, uint8_t *buf)
{
memcpy(buf, impl_id_data, *size);
return PSA_ATTEST_ERR_SUCCESS;
}
enum tfm_security_lifecycle_t tfm_attest_hal_get_security_lifecycle(void)
{
return PSA_ATTEST_ERR_CLAIM_UNAVAILABLE;
}
const char *
tfm_attest_hal_get_verification_service(uint32_t *size)
{
return NULL;
}
const char *
tfm_attest_hal_get_profile_definition(uint32_t *size)
{
return NULL;
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (c) 2018-2019, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#include "attestation_bootloader_data.h"
/* Temporary Boodloader data - conatians temp mandatory claims */
__attribute__((aligned(4)))
const uint8_t temp_ram_page_data[] = {
0x16, 0x20, 0x6D, 0x00, //shared_data_tlv_header
0x88, 0x11, 0x24, 0x00, //TLV_MINOR_IAS_NSPE_MEASURE_VALUE
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7,
0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
0x82, 0x11, 0x06, 0x00, //TLV_MINOR_IAS_NSPE_EPOCH
0x00, 0x00,
0x00, 0x10, 0x24, 0x00, //TLV_MINOR_IAS_BOOT_SEED
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7,
0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
0x01, 0x10, 0x16, 0x00, //TLV_MINOR_IAS_HW_VERSION
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
0xB0, 0xB1,
0x02, 0x10, 0x06, 0x00, //TLV_MINOR_IAS_SLC
0x00, 0x20
};
uint8_t impl_id_data[TEMP_IMPL_ID_DATA_SIZE] = {TEMP_IMPL_ID_DATA};

View File

@ -0,0 +1,42 @@
/*
* Copyright (c) 2018-2019, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#ifndef __ATTESTATION_BOOTLOADER_DATA_H__
#define __ATTESTATION_BOOTLOADER_DATA_H__
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Temp Shared data area between bootloader and runtime firmware */
extern const uint8_t temp_ram_page_data[];
#define S_RAM_ALIAS_BASE (temp_ram_page_data)
#define BOOT_TFM_SHARED_DATA_BASE S_RAM_ALIAS_BASE
/* Temporary Implementation ID data: mandatory claim represents the original
** implementation signer of the attestation key and identifies the contract
** between the report and verification */
#define TEMP_IMPL_ID_DATA_SIZE (32u)
#define TEMP_IMPL_ID_DATA 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, \
0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, \
0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, \
0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF
extern uint8_t impl_id_data[];
#ifdef __cplusplus
}
#endif
#endif /* __ATTESTATION_BOOTLOADER_DATA_H__ */

View File

@ -0,0 +1,23 @@
/*
* Copyright (c) 2018-2019, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#include <stdint.h>
#include "attestation.h"
#include "attestation_key.h"
/* Implementation of mandatory functions that used by TFM attestation code */
enum psa_attest_err_t
attest_get_and_register_initial_attestation_key(void) {
return PSA_ATTEST_ERR_SUCCESS;
}
enum psa_attest_err_t
attest_check_memory_access(void *addr,
uint32_t size,
enum attest_memory_access_t access) {
return PSA_ATTEST_ERR_SUCCESS;
}

View File

@ -0,0 +1,99 @@
/*
* Copyright (c) 2018-2019, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#include "psa_inject_attestation_key_impl.h"
#define ECDSA_P256_KEY_SIZE_IN_BYTES 32
psa_status_t
psa_attestation_inject_key_impl(const uint8_t *key_data,
size_t key_data_length,
psa_key_type_t type,
uint8_t *public_key_data,
size_t public_key_data_size,
size_t *public_key_data_length)
{
psa_status_t status = PSA_SUCCESS;
size_t key_data_bits = 0;
psa_key_handle_t handle = 1;
psa_key_id_t key_id = 17;
psa_key_lifetime_t lifetime = PSA_KEY_LIFETIME_PERSISTENT;
psa_key_policy_t policy = PSA_KEY_POLICY_INIT;
psa_key_usage_t usage = PSA_KEY_USAGE_EXPORT | PSA_KEY_USAGE_SIGN;
psa_key_type_t public_type;
size_t bits;
size_t exported_size = 0;
psa_key_type_t type_key;
#if defined(MBEDTLS_ECP_C)
status = psa_crypto_init();
if (status != PSA_SUCCESS) {
return (status);
}
status = psa_create_key(lifetime, key_id, &handle);
if (status != PSA_SUCCESS) {
return (status);
}
psa_key_policy_init();
psa_key_policy_set_usage(&policy, usage, PSA_ALG_ECDSA(PSA_ALG_SHA_256));
status = psa_set_key_policy(handle, &policy);
if (status != PSA_SUCCESS) {
return (status);
}
if (! PSA_KEY_TYPE_IS_ECC_KEYPAIR(type)) {
return (PSA_ERROR_INVALID_ARGUMENT);
}
if (key_data != NULL) {
if (key_data_length != ECDSA_P256_KEY_SIZE_IN_BYTES) {
status = PSA_ERROR_INVALID_ARGUMENT;
goto exit;
}
status = psa_import_key(handle, type, key_data, key_data_length);
if (status != PSA_SUCCESS) {
goto exit;
}
} else {
/* generating key pair */
key_data_bits = ECDSA_P256_KEY_SIZE_IN_BYTES * 8;
status = psa_generate_key(handle, type, key_data_bits, NULL, 0);
if (status != PSA_SUCCESS) {
goto exit;
}
}
status = psa_get_key_information(handle, &type_key, &bits);
if (status != PSA_SUCCESS) {
goto exit;
}
public_type = PSA_KEY_TYPE_PUBLIC_KEY_OF_KEYPAIR(type_key);
exported_size = PSA_KEY_EXPORT_MAX_SIZE(public_type, bits);
status = psa_export_public_key(handle,
public_key_data,
public_key_data_size,
public_key_data_length);
if (status != PSA_SUCCESS) {
goto exit;
}
if (*public_key_data_length > exported_size) {
status = PSA_ERROR_INVALID_ARGUMENT;
goto exit;
}
exit:
psa_close_key(handle);
return (status);
#endif /* MBEDTLS_ECP_C */
return (PSA_ERROR_NOT_SUPPORTED);
}

View File

@ -0,0 +1,39 @@
/*
* Copyright (c) 2018-2019, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
/***************************************************************************/
/* DRAFT UNDER REVIEW */
/* These APIs are still evolving and are meant as a prototype for review.*/
/* The APIs will change depending on feedback and will be firmed up */
/* to a stable set of APIs once all the feedback has been considered. */
/***************************************************************************/
#ifndef __PSA_INITIAL_ATTESTATION_IMPL_H__
#define __PSA_INITIAL_ATTESTATION_IMPL_H__
#include "crypto.h"
#include <stdint.h>
#include <string.h>
#ifdef __cplusplus
extern "C"
{
#endif
psa_status_t
psa_attestation_inject_key_impl(const uint8_t *key_data,
size_t key_data_length,
psa_key_type_t type,
uint8_t *public_key_data,
size_t public_key_data_size,
size_t *public_key_data_length);
#ifdef __cplusplus
}
#endif
#endif /* __PSA_INITIAL_ATTESTATION_IMPL_H__ */

View File

@ -0,0 +1,39 @@
/*
* Copyright (c) 2019, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#ifndef __ATTEST_EAT_DEFINES_H__
#define __ATTEST_EAT_DEFINES_H__
#ifdef __cplusplus
extern "C" {
#endif
#define EAT_CBOR_ARM_RANGE_BASE (-75000)
#define EAT_CBOR_ARM_LABEL_PROFILE_DEFINITION (EAT_CBOR_ARM_RANGE_BASE - 0)
#define EAT_CBOR_ARM_LABEL_CLIENT_ID (EAT_CBOR_ARM_RANGE_BASE - 1)
#define EAT_CBOR_ARM_LABEL_SECURITY_LIFECYCLE (EAT_CBOR_ARM_RANGE_BASE - 2)
#define EAT_CBOR_ARM_LABEL_IMPLEMENTATION_ID (EAT_CBOR_ARM_RANGE_BASE - 3)
#define EAT_CBOR_ARM_LABEL_BOOT_SEED (EAT_CBOR_ARM_RANGE_BASE - 4)
#define EAT_CBOR_ARM_LABEL_HW_VERSION (EAT_CBOR_ARM_RANGE_BASE - 5)
#define EAT_CBOR_ARM_LABEL_SW_COMPONENTS (EAT_CBOR_ARM_RANGE_BASE - 6)
#define EAT_CBOR_ARM_LABEL_NO_SW_COMPONENTS (EAT_CBOR_ARM_RANGE_BASE - 7)
#define EAT_CBOR_ARM_LABEL_NONCE (EAT_CBOR_ARM_RANGE_BASE - 8)
#define EAT_CBOR_ARM_LABEL_UEID (EAT_CBOR_ARM_RANGE_BASE - 9)
#define EAT_CBOR_ARM_LABEL_ORIGINATION (EAT_CBOR_ARM_RANGE_BASE - 10)
#define EAT_CBOR_SW_COMPONENT_TYPE (1u)
#define EAT_CBOR_SW_COMPONENT_MEASUREMENT (2u)
#define EAT_CBOR_SW_COMPONENT_EPOCH (3u)
#define EAT_CBOR_SW_COMPONENT_VERSION (4u)
#define EAT_CBOR_SW_COMPONENT_SIGNER_ID (5u)
#define EAT_CBOR_SW_COMPONENT_MEASUREMENT_DESC (6u)
#ifdef __cplusplus
}
#endif
#endif /* __ATTEST_EAT_DEFINES_H__ */

View File

@ -0,0 +1,225 @@
/*
* attest_token.c
*
* Copyright (c) 2018-2019, Laurence Lundblade. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
* See BSD-3-Clause license in README.md
*/
#include "attest_token.h"
#include "qcbor.h"
#include "t_cose_sign1.h"
/*
T H E C O M M E N T S
in this file are truthful, but not expansive,
complete of formatted yet...
*/
/*
Outline of what there is to do
start signing operation
- Create encoder context
- Write Headers
- - Protected Header
- - - Algorithm ID
- - Unprotected Headers
- - - Key ID
- Open Payload bstr
- - Write payload data lots of it
- - Get bstr back and hash
- Compute signature
- - Create a separate encoder context for Sig_structure
- - - Encode context
- - - Encode protected headers
- - - Encode two empty bstr
- - - Add one more empty bstr
- - - Close it off
- - Hash all but last two bytes
- - Get payload bstr ptr and length
- - Hash payload
- - Run ECDSA
- Write signature
- Close it out
*/
static enum attest_token_err_t t_cose_err_to_attest_err(enum t_cose_err_t err)
{
switch(err) {
case T_COSE_SUCCESS:
return ATTEST_TOKEN_ERR_SUCCESS;
case T_COSE_ERR_UNSUPPORTED_HASH:
return ATTEST_TOKEN_ERR_HASH_UNAVAILABLE;
/* TODO: fill in more of these */
default:
/* A lot of the errors are not mapped because they
are primarily internal errors that should never
happen. They end up here */
return ATTEST_TOKEN_ERR_GENERAL;
}
}
#if T_COSE_KEY_SELECT_TEST != ATTEST_TOKEN_KEY_SELECT_TEST
#error KEY_SELECT_TEST not identical for T_COSE and ATTEST_TOKEN
#endif
/*
Public function. See attest_token.h
*/
enum attest_token_err_t attest_token_start(struct attest_token_ctx *me,
uint32_t opt_flags,
int32_t key_select,
int32_t alg_select,
struct useful_buf out_buf)
{
/* approximate stack usage on 32-bit machine: 4 bytes */
enum t_cose_err_t cose_return_value;
enum attest_token_err_t return_value;
/* Remember some of the configuration values */
me->opt_flags = opt_flags;
me->key_select = key_select;
/* Spin up the CBOR encoder */
QCBOREncode_Init(&(me->cbor_enc_ctx), out_buf);
/* Initialize COSE signer. This will cause the cose headers to
be encoded and written into out_buf using me->cbor_enc_ctx
*/
cose_return_value = t_cose_sign1_init(&(me->signer_ctx),
opt_flags &
TOKEN_OPT_SHORT_CIRCUIT_SIGN,
alg_select,
key_select,
&(me->cbor_enc_ctx));
if(cose_return_value) {
return_value = t_cose_err_to_attest_err(cose_return_value);
goto Done;
}
/* Open the payload-wrapping bstr */
QCBOREncode_BstrWrap(&(me->cbor_enc_ctx));
QCBOREncode_OpenMap(&(me->cbor_enc_ctx));
return_value = ATTEST_TOKEN_ERR_SUCCESS;
Done:
return return_value;
}
/*
Public function. See attest_token.h
*/
QCBOREncodeContext *attest_token_borrow_cbor_cntxt(struct attest_token_ctx *me)
{
return &(me->cbor_enc_ctx);
}
/*
Public function. See attest_token.h
*/
void attest_token_add_integer(struct attest_token_ctx *me,
int32_t label,
int64_t Value)
{
QCBOREncode_AddInt64ToMapN(&(me->cbor_enc_ctx), label, Value);
}
/*
Public function. See attest_token.h
*/
void attest_token_add_bstr(struct attest_token_ctx *me,
int32_t label,
struct useful_buf_c bstr)
{
QCBOREncode_AddBytesToMapN(&(me->cbor_enc_ctx),
label,
bstr);
}
/*
Public function. See attest_token.h
*/
void attest_token_add_tstr(struct attest_token_ctx *me,
int32_t label,
struct useful_buf_c bstr)
{
QCBOREncode_AddTextToMapN(&(me->cbor_enc_ctx), label, bstr);
}
/*
See attest_token.h
*/
void attest_token_add_encoded(struct attest_token_ctx *me,
int32_t label,
struct useful_buf_c encoded)
{
QCBOREncode_AddEncodedToMapN(&(me->cbor_enc_ctx), label, encoded);
}
/*
Public function. See attest_token.h
*/
enum attest_token_err_t
attest_token_finish(struct attest_token_ctx *me,
struct useful_buf_c *completed_token)
{
/* approximate stack usage on 32-bit machine: 4 + 4 + 8 + 8 = 24 */
enum attest_token_err_t return_value = ATTEST_TOKEN_ERR_SUCCESS;
/* The payload with all the claims that is signed */
struct useful_buf_c token_payload_ub;
/* The completed and signed encoded cose_sign1 */
struct useful_buf_c completed_token_ub;
QCBORError qcbor_result;
enum t_cose_err_t cose_return_value;
QCBOREncode_CloseMap(&(me->cbor_enc_ctx));
/* Close off the payload-wrapping bstr. This gives us back the pointer
and length of the payload that needs to be hashed as part of the signature
*/
QCBOREncode_CloseBstrWrap(&(me->cbor_enc_ctx), &token_payload_ub);
/* Finish off the cose signature. This does all the interesting work of
hashing and signing */
cose_return_value =
t_cose_sign1_finish(&(me->signer_ctx), token_payload_ub);
if(cose_return_value) {
/* Main errors are invoking the hash or signature */
return_value = t_cose_err_to_attest_err(cose_return_value);
goto Done;
}
/* Close off the CBOR encoding and return the completed token */
qcbor_result = QCBOREncode_Finish(&(me->cbor_enc_ctx),
&completed_token_ub);
if(qcbor_result == QCBOR_ERR_BUFFER_TOO_SMALL) {
return_value = ATTEST_TOKEN_ERR_TOO_SMALL;
} else if (qcbor_result != QCBOR_SUCCESS) {
/* likely from array not closed, too many closes, ... */
return_value = ATTEST_TOKEN_ERR_CBOR_FORMATTING;
} else {
*completed_token = completed_token_ub;
}
Done:
return return_value;
}

View File

@ -0,0 +1,212 @@
/*
* attest_token.h
*
* Copyright (c) 2018-2019, Laurence Lundblade. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
* See BSD-3-Clause license in README.md
*/
#ifndef __ATTEST_TOKEN_h__
#define __ATTEST_TOKEN_h__
#include <stdint.h>
#include "qcbor.h"
#include "t_cose_sign1.h"
/**
* \file Attestation Token Creation
*
* The context and functions here are the way to create
* an attestation token. The steps are roughly:
*
* 1) Creation and initalize an attest_token_ctx indicating
* the options, key and such.
* 2) Use various add methods to fill in the payload
* with claims
* 3) Call finish to create the signature and finish
* formatting the COSE signed output.
*/
enum attest_token_err_t {
ATTEST_TOKEN_ERR_SUCCESS = 0,
/** The buffer passed in to receive the token is too small */
ATTEST_TOKEN_ERR_TOO_SMALL,
/** Something went wrong formatting the CBOR, most likely the payload
has maps or arrays that are not closed */
ATTEST_TOKEN_ERR_CBOR_FORMATTING,
/** A general, unspecific error when creating the token */
ATTEST_TOKEN_ERR_GENERAL,
/** A hash function that is needed to make the token is not available */
ATTEST_TOKEN_ERR_HASH_UNAVAILABLE
};
/** The default key to sign tokens with for this device
*/
#define TOKEN_OPT_DEFAULT_KEY 0x00
/** Sign with the globally known debug key. This key is
* always available. It is always the same on every device.
* It is very useful for test and debug. Tokens signed
* by it have no security value because they can be forged
* because this debug key is considered to be widely known.
*/
#define TOKEN_OPT_DEBUG_KEY 0x07
/** Request that the claims internally generated not be added to the token.
* This is a test mode that results in a static token that never changes.
*/
#define TOKEN_OPT_OMIT_CLAIMS 0x40000000
/** A special test mode where a proper signature is not produced. In its place
* there is a concatenation of hashes of the payload to be the same size as the
* signature. This works and can be used to verify all of the SW stack except
* the public signature part. The token has no security value in this mode
* because anyone can replicate it. */
#define TOKEN_OPT_SHORT_CIRCUIT_SIGN 0x80000000
/**
* The context for creating an attestation token.
* The caller of attest_token must create one of these
* and pass it to the functions here. It is small
* enough that it can go on the stack. It is most
* of the memory needed to create a token except
* the output buffer and any memory requirements
* for the cryptographic operations.
*
* The structure is opaque for the caller.
*
* This is roughly 148 + 8 + 32 = 188 bytes
*/
struct attest_token_ctx {
/* Private data structure */
QCBOREncodeContext cbor_enc_ctx;
uint32_t opt_flags;
int32_t key_select;
struct t_cose_sign1_ctx signer_ctx;
};
/**
* \brief Initalize a token creation context.
*
* \param[in] me The token creation context to be initialized
* \param[in] opt_flags Flags to select different custom options
* \param[in] key_select Selects which attestation key to sign with
* \param[in] alg_select Ciphersuite-like designator signing and hash
* algorithn to use
* \param[out] out_buffer The output buffer to write the encoded token into
*
* \return (TODO: error codes)
*
* The size of the buffer in pOut (pOut->len) determines the size of the
* token that can be created. It must be able to hold the final encoded
* and signed token. The data encoding overhead is just that of
* CBOR. The signing overhead depends on the signing key size. It is
* about 150 bytes for ATTEST_TOKEN_CIPHERSUITE_256.
*
*/
enum attest_token_err_t
attest_token_start(struct attest_token_ctx *me,
uint32_t opt_flags,
int32_t key_select,
int32_t alg_select,
struct useful_buf out_buffer);
/**
* \brief Get a copy of the CBOR encoding context
*
* \param[in] me Token Creation Context.
*
* \return The CBOR encoding context
*
* Allows the caller to encode CBOR right into
* the output buffer using any of the QCBOREncode_AddXXXX()
* methods. Anything added here will be part of the
* payload that gets hashed. This can be used
* to make complex CBOR structures. All open
* arrays and maps must be close before calling
* any other attest_token methods.
* QCBOREncode_Finish() should not be closed on
* this context.
*/
QCBOREncodeContext *attest_token_borrow_cbor_cntxt(struct attest_token_ctx *me);
/**
* \brief Add a 64-bit signed integer claim
*
* \param[in] me Token Creation Context.
* \param[in] label Integer label for claim.
* \param[in] value The integer claim data.
*/
void attest_token_add_integer(struct attest_token_ctx *me,
int32_t label,
int64_t value);
/**
* \brief Add a binary string claim
*
* \param[in] me Token Creation Context.
* \param[in] label Integer label for claim.
* \param[in] value The binary claim data.
*/
void attest_token_add_bstr(struct attest_token_ctx *me,
int32_t label,
struct useful_buf_c value);
/**
* \brief Add a text string claim
*
* \param[in] me Token Creation Context.
* \param[in] label Integer label for claim.
* \param[in] value The text claim data.
*/
void attest_token_add_tstr(struct attest_token_ctx *me,
int32_t label,
struct useful_buf_c value);
/**
* \brief Add some already-encoded CBOR to payload
*
* \param[in] me Token Creation Context.
* \param[in] label Integer label for claim.
* \param[in] encoded The already-encoded CBOR.
*
* Encoded CBOR must be a full map or full array
* or a non-aggregate type. It cannot be a partial
* map or array. It can be nested maps and
* arrays, but they must all be complete.
*/
void attest_token_add_encoded(struct attest_token_ctx *me,
int32_t label,
struct useful_buf_c encoded);
/**
* \brief Finish the token, complete the signing and get the result
*
* \param[in] me Token Creation Context.
* \param[out] completed_token Pointer and length to completed token.
*
* \return One of the ATTEST_TOKEN_ERR_XXXX codes (TODO: fill all this in)
*
* This completes the token after the payload has been added. When this
* is called the signing algorithm is run and the final formatting of
* the token is completed.
*/
enum attest_token_err_t
attest_token_finish(struct attest_token_ctx *me,
struct useful_buf_c *completed_token);
#endif /* __ATTEST_TOKEN_h__ */

View File

@ -0,0 +1,61 @@
/*
* Copyright (c) 2019, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#ifndef __ATTESTATION_KEY_H__
#define __ATTESTATION_KEY_H__
#include "psa_initial_attestation_api.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* \def ATTEST_PRIVATE_KEY_SLOT
*
* \brief Key slot number to store the initial attestation private key.
*
* Private key is used by initial attestation service to sign the
* initial attestation token (IAT).
*/
#define ATTEST_PRIVATE_KEY_SLOT (1u)
/**
* \def ATTEST_PUBLIC_KEY_SLOT
*
* \brief Key slot number to store the initial attestation public key.
*
* Public key is used by initial attestation test suit to verify the signature
* of the initial attestation token (IAT).
*/
#define ATTEST_PUBLIC_KEY_SLOT (2u)
/**
* \brief Get the initial attestation key from platform layer and register it
* to crypto service for further usage (signing or verification).
*
* Private key MUST be present on the device, public key is optional.
*
* \retval PSA_ATTEST_ERR_SUCCESS Key(s) was registered.
* \retval PSA_ATTEST_ERR_GENERAL Key(s) could not be registered.
*/
enum psa_attest_err_t attest_register_initial_attestation_key(void);
/**
* \brief Unregister the initial attestation key(s) from crypto service to do
* not occupy key slot(s).
*
* \retval PSA_ATTEST_ERR_SUCCESS Key(s) was unregistered.
* \retval PSA_ATTEST_ERR_GENERAL Key(s) could not be unregistered.
*/
enum psa_attest_err_t attest_unregister_initial_attestation_key(void);
#ifdef __cplusplus
}
#endif
#endif /* __ATTESTATION_KEY_H__ */

View File

@ -0,0 +1,32 @@
/*
* eat_attest_defines.h
*
* Copyright (c) 2018-2019, Laurence Lundblade. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
* See BSD-3-Clause license in README.md
*/
#ifndef __EAT_ATTEST_DEFINES_H__
#define __EAT_ATTEST_DEFINES_H__
/* These are definitions of labels for claims for EAT. EAT is
is still in development so, these are not official values
of any sort yet
*/
#define EAT_CBOR_LABEL_NONCE 44
#define EAT_CBOR_LABEL_SW_COMPONENTS 55
#define EAT_CBOR_LABEL_SW_NAME 66
#define EAT_CBOR_LABEL_VERSION 88
#define EAT_CBOR_LABEL_MEASUREMENT 99
#define EAT_CBOR_LABEL_MEASUREMENT_VALUE 77
#define EAT_CBOR_LABEL_MEASUREMENT_TYPE 99
#endif /* __EAT_ATTEST_DEFINES_H__ */

View File

@ -0,0 +1,42 @@
#-------------------------------------------------------------------------------
# Copyright (c) 2019, Arm Limited. All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
#
#-------------------------------------------------------------------------------
cmake_minimum_required(VERSION 3.7)
#Tell cmake where our modules can be found
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/../../../cmake)
#Include common stuff to control cmake.
include("Common/BuildSys")
#Start an embedded project.
embedded_project_start(CONFIG "${CMAKE_CURRENT_LIST_DIR}/../../../ConfigDefault.cmake")
project(tfm_qcbor LANGUAGES C)
embedded_project_fixup()
###Some project global settings
set (QCBOR_DIR "${CMAKE_CURRENT_LIST_DIR}")
#Append all our source files to global lists.
list(APPEND ALL_SRC_C
"${QCBOR_DIR}/src/ieee754.c"
"${QCBOR_DIR}/src/qcbor_decode.c"
"${QCBOR_DIR}/src/qcbor_encode.c"
"${QCBOR_DIR}/src/UsefulBuf.c"
)
#Setting include directories
embedded_include_directories(PATH ${QCBOR_DIR}/inc ABSOLUTE)
#Specify what we build (for the QCBOR, build as a static library)
add_library(${PROJECT_NAME} STATIC ${ALL_SRC_C})
#Set common compiler and linker flags
config_setting_shared_compiler_flags(tfm_qcbor)
config_setting_shared_linker_flags(tfm_qcbor)
embedded_project_end(${PROJECT_NAME})

View File

@ -0,0 +1,175 @@
# QCBOR
QCBOR encodes and decodes [RFC 7049](https://tools.ietf.org/html/rfc7049) CBOR.
## Characteristics
**Implemented in C with minimal dependency** Only dependencies are
C99, <stdint.h>, <stddef.h>, <stdbool.h> and <string.h> making it
highly portable. There are no #ifdefs to be configured at all.
**Focused on C / native data representation** Simpler code because
there is no support for encoding/decoding to/from JSON, pretty
printing, diagnostic notation... Only encoding from native C
representations and decoding to native C representations is supported.
**Small simple memory model** Malloc is not needed. The encode
context is 136 bytes, decode context is 104 bytes and the
description of decoded data item is 56 bytes. Stack use is light and
there is no recursion. The caller supplies the memory to hold the
encoded CBOR and encode/decode contexts so caller has full control
of memory usage making it good for embedded implementations that
have to run in small fixed memory.
**Supports nearly all of RFC 7049** Only minor, corner-case parts of
RFC 7049 are not directly supported (canonicalization, decimal
fractions, big floats). Decoding indefinite length strings is supported,
but requires a string allocator (see documentation). Encoding indefinite
length strings is not supported, but is also not necessary or
preferred.
**Extensible and general** Provides a way to handle data types that
are not directly supported.
**Secure coding style** Uses a construct called UsefulBuf as a
discipline for very safe coding the handling of binary data.
**Small code size** When optimized for size using the compiler -Os
option, x86 code is about 4KB (~1.1KB encode, ~2.5KB decode,
~0.4KB common). Other decoders may be smaller, but they may
also do less for you, so overall size of the implementation may
be larger. For example, QCBOR internally tracks error status
so you don't have to check a return code on every operation.
**Clear documented public interface** The public interface is
separated from the implementation. It can be put to use without
reading the source.
**Comprehensive test suite** Easy to verify on a new platform
or OS with the test suite. The test suite dependencies are also
minimal, only additionally requiring <math.h> for floating point
tests.
## Code Status
QCBOR was originally developed by Qualcomm. It was [open sourced
through CAF](https://source.codeaurora.org/quic/QCBOR/QCBOR/) with a
permissive Linux license, September 2018 (thanks Qualcomm!).
This code in [Laurence's
GitHub](https://github.com/laurencelundblade/QCBOR) has diverged from
the CAF source with some small simplifications and tidying up.
From Nov 3, 2018, the interface and code are fairly stable. Large
changes are not planned or expected, particularly in the
interface. The test coverage is pretty good.
## Building
There is a simple makefile for the UNIX style command line binary that
compiles everything to run the tests.
These seven files, the contents of the src and inc directories, make
up the entire implementation.
* inc
* UsefulBuf.h
* qcbor.h
* src
* UsefulBuf.c
* qcbor_encode.c
* qcbor_decode.c
* ieee754.h
* ieee754.c
For most use cases you should just be able to add them to your
project. Hopefully the easy portability of this implementation makes
this work straight away, whatever your development environment is.
The files ieee754.c and ieee754.h are support for half-precision
floating point. The encoding side of the floating point functionality
is about 500 bytes. If it is never called because no floating point
numbers are ever encoded, all 500 bytes will be dead stripped and not
impact code size. The decoding side is about 150 bytes of object
code. It is never dead stripped because it directly referenced by the
core decoder, however it doesn't add very much to the size.
The test directory includes some tests that are nearly as portable as
the main implementation. If your development environment doesn't
support UNIX style command line and make, you should be able to make a
simple project and add the test files to it. Then just call
RunTests() to invoke them all.
## Changes from CAF Version
* Float support is restored
* Minimal length float encoding is added
* indefinite length arrays/maps are supported
* indefinite length strings are supported
* Tag decoding is changed; unlimited number of tags supported, any tag
value supported, tag utility function for easier tag checking
* Addition functions in UsefulBuf
* QCBOREncode_Init takes a UsefulBuf instead of a pointer and size
* QCBOREncode_Finish takes a UsefulBufC and EncodedCBOR is remove
* bstr wrapping of arrays/maps is replaced with OpenBstrwrap
* AddRaw renamed to AddEncoded and can now only add whole arrays or maps,
not partial maps and arrays (simplification; was a dangerous feature)
* Finish cannot be called repeatedly on a partial decode (some tests used
this, but it is not really a good thing to use in the first place)
* UsefulOutBuf_OutUBuf changed to work differently
* UsefulOutBuf_Init works differently
* The "_3" functions are replaced with a small number of simpler functions
* There is a new AddTag functon instead of the "_3" functions, making
the interface simpler and saving some code
* QCBOREncode_AddRawSimple_2 is removed (the macros that referenced
still exist and work the same)
## Credits
* Ganesh Kanike for porting to QSEE
* Mark Bapst for sponsorship and release as open source by Qualcomm
* Sachin Sharma for release through CAF
* Tamas Ban for porting to TF-M and 32-bit ARM
## Copyright and License
QCBOR is available under what is essentially the 3-Clause BSD License.
Files created inside Qualcomm and open-sourced through CAF (The Code
Aurora Forum) have a slightly modified 3-Clause BSD License. The
modification additionally disclaims NON-INFRINGEMENT.
Files created after release to CAF use the standard 3-Clause BSD
License with no modification. These files have the SPDX license
identifier, "SPDX-License-Identifier: BSD-3-Clause" in them.
### BSD-3-Clause license
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
### Copyright for this README
Copyright (c) 2018-2019, Laurence Lundblade. All rights reserved.

View File

@ -0,0 +1,329 @@
/*==============================================================================
Copyright (c) 2016-2018, The Linux Foundation.
Copyright (c) 2018-2019, Laurence Lundblade.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of The Linux Foundation nor the names of its
contributors, nor the name "Laurence Lundblade" may be used to
endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
==============================================================================*/
/*===================================================================================
FILE: UsefulBuf.c
DESCRIPTION: General purpose input and output buffers
EDIT HISTORY FOR FILE:
This section contains comments describing changes made to the module.
Notice that changes are listed in reverse chronological order.
when who what, where, why
-------- ---- ---------------------------------------------------
09/07/17 llundbla Fix critical bug in UsefulBuf_Find() -- a read off
the end of memory when the bytes to find is longer
than the bytes to search.
06/27/17 llundbla Fix UsefulBuf_Compare() bug. Only affected comparison
for < or > for unequal length buffers. Added
UsefulBuf_Set() function.
05/30/17 llundbla Functions for NULL UsefulBufs and const / unconst
11/13/16 llundbla Initial Version.
=====================================================================================*/
#include "UsefulBuf.h"
#define USEFUL_OUT_BUF_MAGIC (0x0B0F) // used to catch use of uninitialized or corrupted UOBs
/*
Public function -- see UsefulBuf.h
*/
UsefulBufC UsefulBuf_CopyOffset(UsefulBuf Dest, size_t uOffset, const UsefulBufC Src)
{
// Do this with subtraction so it doesn't give erroneous result if uOffset + Src.len overflows
if(uOffset > Dest.len || Src.len > Dest.len - uOffset) { // uOffset + Src.len > Dest.len
return NULLUsefulBufC;
}
memcpy((uint8_t *)Dest.ptr + uOffset, Src.ptr, Src.len);
return (UsefulBufC){Dest.ptr, Src.len + uOffset};
}
/*
Public function -- see UsefulBuf.h
*/
int UsefulBuf_Compare(const UsefulBufC UB1, const UsefulBufC UB2)
{
// use the comparisons rather than subtracting lengths to
// return an int instead of a size_t
if(UB1.len < UB2.len) {
return -1;
} else if (UB1.len > UB2.len) {
return 1;
} // else UB1.len == UB2.len
return memcmp(UB1.ptr, UB2.ptr, UB1.len);
}
/*
Public function -- see UsefulBuf.h
*/
size_t UsefulBuf_FindBytes(UsefulBufC BytesToSearch, UsefulBufC BytesToFind)
{
if(BytesToSearch.len < BytesToFind.len) {
return SIZE_MAX;
}
for(size_t uPos = 0; uPos <= BytesToSearch.len - BytesToFind.len; uPos++) {
if(!UsefulBuf_Compare((UsefulBufC){((uint8_t *)BytesToSearch.ptr) + uPos, BytesToFind.len}, BytesToFind)) {
return uPos;
}
}
return SIZE_MAX;
}
/*
Public function -- see UsefulBuf.h
Code Reviewers: THIS FUNCTION DOES POINTER MATH
*/
void UsefulOutBuf_Init(UsefulOutBuf *me, UsefulBuf Storage)
{
me->magic = USEFUL_OUT_BUF_MAGIC;
UsefulOutBuf_Reset(me);
me->UB = Storage;
#if 0
// This check is off by default.
// The following check fails on ThreadX
// Sanity check on the pointer and size to be sure we are not
// passed a buffer that goes off the end of the address space.
// Given this test, we know that all unsigned lengths less than
// me->size are valid and won't wrap in any pointer additions
// based off of pStorage in the rest of this code.
const uintptr_t ptrM = UINTPTR_MAX - Storage.len;
if(Storage.ptr && (uintptr_t)Storage.ptr > ptrM) // Check #0
me->err = 1;
#endif
}
/*
Public function -- see UsefulBuf.h
The core of UsefulOutBuf -- put some bytes in the buffer without writing off the end of it.
Code Reviewers: THIS FUNCTION DOES POINTER MATH
This function inserts the source buffer, NewData, into the destination buffer, me->UB.ptr.
Destination is represented as:
me->UB.ptr -- start of the buffer
me->UB.len -- size of the buffer UB.ptr
me->data_len -- length of value data in UB
Source is data:
NewData.ptr -- start of source buffer
NewData.len -- length of source buffer
Insertion point:
uInsertionPos.
Steps:
0. Corruption checks on UsefulOutBuf
1. Figure out if the new data will fit or not
2. Is insertion position in the range of valid data?
3. If insertion point is not at the end, slide data to the right of the insertion point to the right
4. Put the new data in at the insertion position.
*/
void UsefulOutBuf_InsertUsefulBuf(UsefulOutBuf *me, UsefulBufC NewData, size_t uInsertionPos)
{
if(me->err) {
// Already in error state.
return;
}
/* 0. Sanity check the UsefulOutBuf structure */
// A "counter measure". If magic number is not the right number it
// probably means me was not initialized or it was corrupted. Attackers
// can defeat this, but it is a hurdle and does good with very
// little code.
if(me->magic != USEFUL_OUT_BUF_MAGIC) {
me->err = 1;
return; // Magic number is wrong due to uninitalization or corrption
}
// Make sure valid data is less than buffer size. This would only occur
// if there was corruption of me, but it is also part of the checks to
// be sure there is no pointer arithmatic under/overflow.
if(me->data_len > me->UB.len) { // Check #1
me->err = 1;
return; // Offset of valid data is off the end of the UsefulOutBuf due to uninitialization or corruption
}
/* 1. Will it fit? */
// WillItFit() is the same as: NewData.len <= (me->size - me->data_len)
// Check #1 makes sure subtraction in RoomLeft will not wrap around
if(! UsefulOutBuf_WillItFit(me, NewData.len)) { // Check #2
// The new data will not fit into the the buffer.
me->err = 1;
return;
}
/* 2. Check the Insertion Position */
// This, with Check #1, also confirms that uInsertionPos <= me->data_len
if(uInsertionPos > me->data_len) { // Check #3
// Off the end of the valid data in the buffer.
me->err = 1;
return;
}
/* 3. Slide existing data to the right */
uint8_t *pSourceOfMove = ((uint8_t *)me->UB.ptr) + uInsertionPos; // PtrMath #1
size_t uNumBytesToMove = me->data_len - uInsertionPos; // PtrMath #2
uint8_t *pDestinationOfMove = pSourceOfMove + NewData.len; // PtrMath #3
if(uNumBytesToMove && me->UB.ptr) {
// To know memmove won't go off end of destination, see PtrMath #4
memmove(pDestinationOfMove, pSourceOfMove, uNumBytesToMove);
}
/* 4. Put the new data in */
uint8_t *pInsertionPoint = ((uint8_t *)me->UB.ptr) + uInsertionPos; // PtrMath #5
if(me->UB.ptr) {
// To know memmove won't go off end of destination, see PtrMath #6
memmove(pInsertionPoint, NewData.ptr, NewData.len);
}
me->data_len += NewData.len ;
}
/*
Rationale that describes why the above pointer math is safe
PtrMath #1 will never wrap around over because
Check #0 in UsefulOutBuf_Init makes sure me->UB.ptr + me->UB.len doesn't wrap
Check #1 makes sure me->data_len is less than me->UB.len
Check #3 makes sure uInsertionPos is less than me->data_len
PtrMath #2 will never wrap around under because
Check #3 makes sure uInsertionPos is less than me->data_len
PtrMath #3 will never wrap around over because todo
PtrMath #1 is checked resulting in pSourceOfMove being between me->UB.ptr and a maximum valid ptr
Check #2 that NewData.len will fit
PtrMath #4 will never wrap under because
Calculation for extent or memmove is uRoomInDestination = me->UB.len - (uInsertionPos + NewData.len)
Check #3 makes sure uInsertionPos is less than me->data_len
Check #3 allows Check #2 to be refactored as NewData.Len > (me->size - uInsertionPos)
This algebraically rearranges to me->size > uInsertionPos + NewData.len
PtrMath #5 is exactly the same as PtrMath #1
PtrMath #6 will never wrap under because
Calculation for extent of memove is uRoomInDestination = me->UB.len - uInsertionPos;
Check #1 makes sure me->data_len is less than me->size
Check #3 makes sure uInsertionPos is less than me->data_len
*/
/*
Public function -- see UsefulBuf.h
*/
UsefulBufC UsefulOutBuf_OutUBuf(UsefulOutBuf *me)
{
if(me->err) {
return NULLUsefulBufC;
}
if(me->magic != USEFUL_OUT_BUF_MAGIC) {
me->err = 1;
return NULLUsefulBufC;
}
return (UsefulBufC){me->UB.ptr,me->data_len};
}
/*
Public function -- see UsefulBuf.h
Copy out the data accumulated in to the output buffer.
*/
UsefulBufC UsefulOutBuf_CopyOut(UsefulOutBuf *me, UsefulBuf pDest)
{
const UsefulBufC Tmp = UsefulOutBuf_OutUBuf(me);
if(UsefulBuf_IsNULLC(Tmp)) {
return NULLUsefulBufC;
}
return UsefulBuf_Copy(pDest, Tmp);
}
/*
Public function -- see UsefulBuf.h
The core of UsefulInputBuf -- consume some bytes without going off the end of the buffer.
Code Reviewers: THIS FUNCTION DOES POINTER MATH
*/
const void * UsefulInputBuf_GetBytes(UsefulInputBuf *me, size_t uAmount)
{
// Already in error state. Do nothing.
if(me->err) {
return NULL;
}
if(!UsefulInputBuf_BytesAvailable(me, uAmount)) {
// The number of bytes asked for at current position are more than available
me->err = 1;
return NULL;
}
// This is going to succeed
const void * const result = ((uint8_t *)me->UB.ptr) + me->cursor;
me->cursor += uAmount; // this will not overflow because of check using UsefulInputBuf_BytesAvailable()
return result;
}

View File

@ -0,0 +1,497 @@
/*==============================================================================
ieee754.c -- floating point conversion between half, double and single precision
Copyright (c) 2018-2019, Laurence Lundblade. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause
See BSD-3-Clause license in README.md
Created on 7/23/18
==============================================================================*/
#include "ieee754.h"
#include <string.h> // For memcpy()
/*
This code is written for clarity and verifiability, not for size, on the assumption
that the optimizer will do a good job. The LLVM optimizer, -Os, does seem to do the
job and the resulting object code is smaller from combining code for the many different
cases (normal, subnormal, infinity, zero...) for the conversions.
Dead stripping is also really helpful to get code size down when floating point
encoding is not needed.
This code works solely using shifts and masks and thus has no dependency on
any math libraries. It can even work if the CPU doesn't have any floating
point support, though that isn't the most useful thing to do.
The memcpy() dependency is only for CopyFloatToUint32() and friends which only
is needed to avoid type punning when converting the actual float bits to
an unsigned value so the bit shifts and masks can work.
*/
/*
The references used to write this code:
- IEEE 754-2008, particularly section 3.6 and 6.2.1
- https://en.wikipedia.org/wiki/IEEE_754 and subordinate pages
- https://stackoverflow.com/questions/19800415/why-does-ieee-754-reserve-so-many-nan-values
*/
// ----- Half Precsion -----------
#define HALF_NUM_SIGNIFICAND_BITS (10)
#define HALF_NUM_EXPONENT_BITS (5)
#define HALF_NUM_SIGN_BITS (1)
#define HALF_SIGNIFICAND_SHIFT (0)
#define HALF_EXPONENT_SHIFT (HALF_NUM_SIGNIFICAND_BITS)
#define HALF_SIGN_SHIFT (HALF_NUM_SIGNIFICAND_BITS + HALF_NUM_EXPONENT_BITS)
#define HALF_SIGNIFICAND_MASK (0x3ff) // The lower 10 bits // 0x03ff
#define HALF_EXPONENT_MASK (0x1f << HALF_EXPONENT_SHIFT) // 0x7c00 5 bits of exponent
#define HALF_SIGN_MASK (0x01 << HALF_SIGN_SHIFT) // // 0x80001 bit of sign
#define HALF_QUIET_NAN_BIT (0x01 << (HALF_NUM_SIGNIFICAND_BITS-1)) // 0x0200
/* Biased Biased Unbiased Use
0x00 0 -15 0 and subnormal
0x01 1 -14 Smallest normal exponent
0x1e 30 15 Largest normal exponent
0x1F 31 16 NaN and Infinity */
#define HALF_EXPONENT_BIAS (15)
#define HALF_EXPONENT_MAX (HALF_EXPONENT_BIAS) // 15 Unbiased
#define HALF_EXPONENT_MIN (-HALF_EXPONENT_BIAS+1) // -14 Unbiased
#define HALF_EXPONENT_ZERO (-HALF_EXPONENT_BIAS) // -15 Unbiased
#define HALF_EXPONENT_INF_OR_NAN (HALF_EXPONENT_BIAS+1) // 16 Unbiased
// ------ Single Precision --------
#define SINGLE_NUM_SIGNIFICAND_BITS (23)
#define SINGLE_NUM_EXPONENT_BITS (8)
#define SINGLE_NUM_SIGN_BITS (1)
#define SINGLE_SIGNIFICAND_SHIFT (0)
#define SINGLE_EXPONENT_SHIFT (SINGLE_NUM_SIGNIFICAND_BITS)
#define SINGLE_SIGN_SHIFT (SINGLE_NUM_SIGNIFICAND_BITS + SINGLE_NUM_EXPONENT_BITS)
#define SINGLE_SIGNIFICAND_MASK (0x7fffffUL) // The lower 23 bits
#define SINGLE_EXPONENT_MASK (0xffUL << SINGLE_EXPONENT_SHIFT) // 8 bits of exponent
#define SINGLE_SIGN_MASK (0x01UL << SINGLE_SIGN_SHIFT) // 1 bit of sign
#define SINGLE_QUIET_NAN_BIT (0x01UL << (SINGLE_NUM_SIGNIFICAND_BITS-1))
/* Biased Biased Unbiased Use
0x0000 0 -127 0 and subnormal
0x0001 1 -126 Smallest normal exponent
0x7f 127 0 1
0xfe 254 127 Largest normal exponent
0xff 255 128 NaN and Infinity */
#define SINGLE_EXPONENT_BIAS (127)
#define SINGLE_EXPONENT_MAX (SINGLE_EXPONENT_BIAS) // 127 unbiased
#define SINGLE_EXPONENT_MIN (-SINGLE_EXPONENT_BIAS+1) // -126 unbiased
#define SINGLE_EXPONENT_ZERO (-SINGLE_EXPONENT_BIAS) // -127 unbiased
#define SINGLE_EXPONENT_INF_OR_NAN (SINGLE_EXPONENT_BIAS+1) // 128 unbiased
// --------- Double Precision ----------
#define DOUBLE_NUM_SIGNIFICAND_BITS (52)
#define DOUBLE_NUM_EXPONENT_BITS (11)
#define DOUBLE_NUM_SIGN_BITS (1)
#define DOUBLE_SIGNIFICAND_SHIFT (0)
#define DOUBLE_EXPONENT_SHIFT (DOUBLE_NUM_SIGNIFICAND_BITS)
#define DOUBLE_SIGN_SHIFT (DOUBLE_NUM_SIGNIFICAND_BITS + DOUBLE_NUM_EXPONENT_BITS)
#define DOUBLE_SIGNIFICAND_MASK (0xfffffffffffffULL) // The lower 52 bits
#define DOUBLE_EXPONENT_MASK (0x7ffULL << DOUBLE_EXPONENT_SHIFT) // 11 bits of exponent
#define DOUBLE_SIGN_MASK (0x01ULL << DOUBLE_SIGN_SHIFT) // 1 bit of sign
#define DOUBLE_QUIET_NAN_BIT (0x01ULL << (DOUBLE_NUM_SIGNIFICAND_BITS-1))
/* Biased Biased Unbiased Use
0x00000000 0 -1023 0 and subnormal
0x00000001 1 -1022 Smallest normal exponent
0x000007fe 2046 1023 Largest normal exponent
0x000007ff 2047 1024 NaN and Infinity */
#define DOUBLE_EXPONENT_BIAS (1023)
#define DOUBLE_EXPONENT_MAX (DOUBLE_EXPONENT_BIAS) // unbiased
#define DOUBLE_EXPONENT_MIN (-DOUBLE_EXPONENT_BIAS+1) // unbiased
#define DOUBLE_EXPONENT_ZERO (-DOUBLE_EXPONENT_BIAS) // unbiased
#define DOUBLE_EXPONENT_INF_OR_NAN (DOUBLE_EXPONENT_BIAS+1) // unbiased
/*
Convenient functions to avoid type punning, compiler warnings and such
The optimizer reduces them to a simple assignment.
This is a crusty corner of C. It shouldn't be this hard.
These are also in UsefulBuf.h under a different name. They are copied
here to avoid a dependency on UsefulBuf.h. There is no
object code size impact because these always optimze down to a
simple assignment.
*/
static inline uint32_t CopyFloatToUint32(float f)
{
uint32_t u32;
memcpy(&u32, &f, sizeof(uint32_t));
return u32;
}
static inline uint64_t CopyDoubleToUint64(double d)
{
uint64_t u64;
memcpy(&u64, &d, sizeof(uint64_t));
return u64;
}
static inline float CopyUint32ToFloat(uint32_t u32)
{
float f;
memcpy(&f, &u32, sizeof(uint32_t));
return f;
}
static inline double CopyUint64ToDouble(uint64_t u64)
{
double d;
memcpy(&d, &u64, sizeof(uint64_t));
return d;
}
// Public function; see ieee754.h
uint16_t IEEE754_FloatToHalf(float f)
{
// Pull the three parts out of the single-precision float
const uint32_t uSingle = CopyFloatToUint32(f);
const int32_t nSingleUnbiasedExponent = ((uSingle & SINGLE_EXPONENT_MASK) >> SINGLE_EXPONENT_SHIFT) - SINGLE_EXPONENT_BIAS;
const uint32_t uSingleSign = (uSingle & SINGLE_SIGN_MASK) >> SINGLE_SIGN_SHIFT;
const uint32_t uSingleSignificand = uSingle & SINGLE_SIGNIFICAND_MASK;
// Now convert the three parts to half-precision.
uint16_t uHalfSign, uHalfSignificand, uHalfBiasedExponent;
if(nSingleUnbiasedExponent == SINGLE_EXPONENT_INF_OR_NAN) {
// +/- Infinity and NaNs -- single biased exponent is 0xff
uHalfBiasedExponent = HALF_EXPONENT_INF_OR_NAN + HALF_EXPONENT_BIAS;
if(!uSingleSignificand) {
// Infinity
uHalfSignificand = 0;
} else {
// Copy the LBSs of the NaN payload that will fit from the single to the half
uHalfSignificand = uSingleSignificand & (HALF_SIGNIFICAND_MASK & ~HALF_QUIET_NAN_BIT);
if(uSingleSignificand & SINGLE_QUIET_NAN_BIT) {
// It's a qNaN; copy the qNaN bit
uHalfSignificand |= HALF_QUIET_NAN_BIT;
} else {
// It's a sNaN; make sure the significand is not zero so it stays a NaN
// This is needed because not all significand bits are copied from single
if(!uHalfSignificand) {
// Set the LSB. This is what wikipedia shows for sNAN.
uHalfSignificand |= 0x01;
}
}
}
} else if(nSingleUnbiasedExponent == SINGLE_EXPONENT_ZERO) {
// 0 or a subnormal number -- singled biased exponent is 0
uHalfBiasedExponent = 0;
uHalfSignificand = 0; // Any subnormal single will be too small to express as a half precision
} else if(nSingleUnbiasedExponent > HALF_EXPONENT_MAX) {
// Exponent is too large to express in half-precision; round up to infinity
uHalfBiasedExponent = HALF_EXPONENT_INF_OR_NAN + HALF_EXPONENT_BIAS;
uHalfSignificand = 0;
} else if(nSingleUnbiasedExponent < HALF_EXPONENT_MIN) {
// Exponent is too small to express in half-precision normal; make it a half-precision subnormal
uHalfBiasedExponent = (uint16_t)(HALF_EXPONENT_ZERO + HALF_EXPONENT_BIAS);
// Difference between single normal exponent and the base exponent of a half subnormal
const uint32_t nExpDiff = -(nSingleUnbiasedExponent - HALF_EXPONENT_MIN);
// Also have to shift the significand by the difference in number of bits between a single and a half significand
const int32_t nSignificandBitsDiff = SINGLE_NUM_SIGNIFICAND_BITS - HALF_NUM_SIGNIFICAND_BITS;
// Add in the 1 that is implied in the significand of a normal number; it needs to be present in a subnormal
const uint32_t uSingleSignificandSubnormal = uSingleSignificand + (0x01L << SINGLE_NUM_SIGNIFICAND_BITS);
uHalfSignificand = uSingleSignificandSubnormal >> (nExpDiff + nSignificandBitsDiff);
} else {
// The normal case
uHalfBiasedExponent = nSingleUnbiasedExponent + HALF_EXPONENT_BIAS;
uHalfSignificand = uSingleSignificand >> (SINGLE_NUM_SIGNIFICAND_BITS - HALF_NUM_SIGNIFICAND_BITS);
}
uHalfSign = uSingleSign;
// Put the 3 values in the right place for a half precision
const uint16_t uHalfPrecision = uHalfSignificand |
(uHalfBiasedExponent << HALF_EXPONENT_SHIFT) |
(uHalfSign << HALF_SIGN_SHIFT);
return uHalfPrecision;
}
// Public function; see ieee754.h
uint16_t IEEE754_DoubleToHalf(double d)
{
// Pull the three parts out of the double-precision float
const uint64_t uDouble = CopyDoubleToUint64(d);
const int64_t nDoubleUnbiasedExponent = ((uDouble & DOUBLE_EXPONENT_MASK) >> DOUBLE_EXPONENT_SHIFT) - DOUBLE_EXPONENT_BIAS;
const uint64_t uDoubleSign = (uDouble & DOUBLE_SIGN_MASK) >> DOUBLE_SIGN_SHIFT;
const uint64_t uDoubleSignificand = uDouble & DOUBLE_SIGNIFICAND_MASK;
// Now convert the three parts to half-precision.
uint16_t uHalfSign, uHalfSignificand, uHalfBiasedExponent;
if(nDoubleUnbiasedExponent == DOUBLE_EXPONENT_INF_OR_NAN) {
// +/- Infinity and NaNs -- single biased exponent is 0xff
uHalfBiasedExponent = HALF_EXPONENT_INF_OR_NAN + HALF_EXPONENT_BIAS;
if(!uDoubleSignificand) {
// Infinity
uHalfSignificand = 0;
} else {
// Copy the LBSs of the NaN payload that will fit from the double to the half
uHalfSignificand = uDoubleSignificand & (HALF_SIGNIFICAND_MASK & ~HALF_QUIET_NAN_BIT);
if(uDoubleSignificand & DOUBLE_QUIET_NAN_BIT) {
// It's a qNaN; copy the qNaN bit
uHalfSignificand |= HALF_QUIET_NAN_BIT;
} else {
// It's an sNaN; make sure the significand is not zero so it stays a NaN
// This is needed because not all significand bits are copied from single
if(!uHalfSignificand) {
// Set the LSB. This is what wikipedia shows for sNAN.
uHalfSignificand |= 0x01;
}
}
}
} else if(nDoubleUnbiasedExponent == DOUBLE_EXPONENT_ZERO) {
// 0 or a subnormal number -- double biased exponent is 0
uHalfBiasedExponent = 0;
uHalfSignificand = 0; // Any subnormal single will be too small to express as a half precision; TODO, is this really true?
} else if(nDoubleUnbiasedExponent > HALF_EXPONENT_MAX) {
// Exponent is too large to express in half-precision; round up to infinity; TODO, is this really true?
uHalfBiasedExponent = HALF_EXPONENT_INF_OR_NAN + HALF_EXPONENT_BIAS;
uHalfSignificand = 0;
} else if(nDoubleUnbiasedExponent < HALF_EXPONENT_MIN) {
// Exponent is too small to express in half-precision; round down to zero
uHalfBiasedExponent = (uint16_t)(HALF_EXPONENT_ZERO + HALF_EXPONENT_BIAS);
// Difference between double normal exponent and the base exponent of a half subnormal
const uint64_t nExpDiff = -(nDoubleUnbiasedExponent - HALF_EXPONENT_MIN);
// Also have to shift the significand by the difference in number of bits between a double and a half significand
const int64_t nSignificandBitsDiff = DOUBLE_NUM_SIGNIFICAND_BITS - HALF_NUM_SIGNIFICAND_BITS;
// Add in the 1 that is implied in the significand of a normal number; it needs to be present in a subnormal
const uint64_t uDoubleSignificandSubnormal = uDoubleSignificand + (0x01ULL << DOUBLE_NUM_SIGNIFICAND_BITS);
uHalfSignificand = uDoubleSignificandSubnormal >> (nExpDiff + nSignificandBitsDiff);
} else {
// The normal case
uHalfBiasedExponent = nDoubleUnbiasedExponent + HALF_EXPONENT_BIAS;
uHalfSignificand = uDoubleSignificand >> (DOUBLE_NUM_SIGNIFICAND_BITS - HALF_NUM_SIGNIFICAND_BITS);
}
uHalfSign = uDoubleSign;
// Put the 3 values in the right place for a half precision
const uint16_t uHalfPrecision = uHalfSignificand |
(uHalfBiasedExponent << HALF_EXPONENT_SHIFT) |
(uHalfSign << HALF_SIGN_SHIFT);
return uHalfPrecision;
}
// Public function; see ieee754.h
float IEEE754_HalfToFloat(uint16_t uHalfPrecision)
{
// Pull out the three parts of the half-precision float
const uint16_t uHalfSignificand = uHalfPrecision & HALF_SIGNIFICAND_MASK;
const int16_t nHalfUnBiasedExponent = ((uHalfPrecision & HALF_EXPONENT_MASK) >> HALF_EXPONENT_SHIFT) - HALF_EXPONENT_BIAS;
const uint16_t uHalfSign = (uHalfPrecision & HALF_SIGN_MASK) >> HALF_SIGN_SHIFT;
// Make the three parts of the single-precision number
uint32_t uSingleSignificand, uSingleSign, uSingleBiasedExponent;
if(nHalfUnBiasedExponent == HALF_EXPONENT_ZERO) {
// 0 or subnormal
if(uHalfSignificand) {
// Subnormal case
uSingleBiasedExponent = -HALF_EXPONENT_BIAS + SINGLE_EXPONENT_BIAS +1;
// A half-precision subnormal can always be converted to a normal single-precision float because the ranges line up
uSingleSignificand = uHalfSignificand;
// Shift bits from right of the decimal to left, reducing the exponent by 1 each time
do {
uSingleSignificand <<= 1;
uSingleBiasedExponent--;
} while ((uSingleSignificand & 0x400) == 0);
uSingleSignificand &= HALF_SIGNIFICAND_MASK;
uSingleSignificand <<= (SINGLE_NUM_SIGNIFICAND_BITS - HALF_NUM_SIGNIFICAND_BITS);
} else {
// Just zero
uSingleBiasedExponent = SINGLE_EXPONENT_ZERO + SINGLE_EXPONENT_BIAS;
uSingleSignificand = 0;
}
} else if(nHalfUnBiasedExponent == HALF_EXPONENT_INF_OR_NAN) {
// NaN or Inifinity
uSingleBiasedExponent = SINGLE_EXPONENT_INF_OR_NAN + SINGLE_EXPONENT_BIAS;
if(uHalfSignificand) {
// NaN
// First preserve the NaN payload from half to single
uSingleSignificand = uHalfSignificand & ~HALF_QUIET_NAN_BIT;
if(uHalfSignificand & HALF_QUIET_NAN_BIT) {
// Next, set qNaN if needed since half qNaN bit is not copied above
uSingleSignificand |= SINGLE_QUIET_NAN_BIT;
}
} else {
// Infinity
uSingleSignificand = 0;
}
} else {
// Normal number
uSingleBiasedExponent = nHalfUnBiasedExponent + SINGLE_EXPONENT_BIAS;
uSingleSignificand = uHalfSignificand << (SINGLE_NUM_SIGNIFICAND_BITS - HALF_NUM_SIGNIFICAND_BITS);
}
uSingleSign = uHalfSign;
// Shift the three parts of the single precision into place
const uint32_t uSinglePrecision = uSingleSignificand |
(uSingleBiasedExponent << SINGLE_EXPONENT_SHIFT) |
(uSingleSign << SINGLE_SIGN_SHIFT);
return CopyUint32ToFloat(uSinglePrecision);
}
// Public function; see ieee754.h
double IEEE754_HalfToDouble(uint16_t uHalfPrecision)
{
// Pull out the three parts of the half-precision float
const uint16_t uHalfSignificand = uHalfPrecision & HALF_SIGNIFICAND_MASK;
const int16_t nHalfUnBiasedExponent = ((uHalfPrecision & HALF_EXPONENT_MASK) >> HALF_EXPONENT_SHIFT) - HALF_EXPONENT_BIAS;
const uint16_t uHalfSign = (uHalfPrecision & HALF_SIGN_MASK) >> HALF_SIGN_SHIFT;
// Make the three parts of hte single-precision number
uint64_t uDoubleSignificand, uDoubleSign, uDoubleBiasedExponent;
if(nHalfUnBiasedExponent == HALF_EXPONENT_ZERO) {
// 0 or subnormal
uDoubleBiasedExponent = DOUBLE_EXPONENT_ZERO + DOUBLE_EXPONENT_BIAS;
if(uHalfSignificand) {
// Subnormal case
uDoubleBiasedExponent = -HALF_EXPONENT_BIAS + DOUBLE_EXPONENT_BIAS +1;
// A half-precision subnormal can always be converted to a normal double-precision float because the ranges line up
uDoubleSignificand = uHalfSignificand;
// Shift bits from right of the decimal to left, reducing the exponent by 1 each time
do {
uDoubleSignificand <<= 1;
uDoubleBiasedExponent--;
} while ((uDoubleSignificand & 0x400) == 0);
uDoubleSignificand &= HALF_SIGNIFICAND_MASK;
uDoubleSignificand <<= (DOUBLE_NUM_SIGNIFICAND_BITS - HALF_NUM_SIGNIFICAND_BITS);
} else {
// Just zero
uDoubleSignificand = 0;
}
} else if(nHalfUnBiasedExponent == HALF_EXPONENT_INF_OR_NAN) {
// NaN or Inifinity
uDoubleBiasedExponent = DOUBLE_EXPONENT_INF_OR_NAN + DOUBLE_EXPONENT_BIAS;
if(uHalfSignificand) {
// NaN
// First preserve the NaN payload from half to single
uDoubleSignificand = uHalfSignificand & ~HALF_QUIET_NAN_BIT;
if(uHalfSignificand & HALF_QUIET_NAN_BIT) {
// Next, set qNaN if needed since half qNaN bit is not copied above
uDoubleSignificand |= DOUBLE_QUIET_NAN_BIT;
}
} else {
// Infinity
uDoubleSignificand = 0;
}
} else {
// Normal number
uDoubleBiasedExponent = nHalfUnBiasedExponent + DOUBLE_EXPONENT_BIAS;
uDoubleSignificand = (uint64_t)uHalfSignificand << (DOUBLE_NUM_SIGNIFICAND_BITS - HALF_NUM_SIGNIFICAND_BITS);
}
uDoubleSign = uHalfSign;
// Shift the 3 parts into place as a double-precision
const uint64_t uDouble = uDoubleSignificand |
(uDoubleBiasedExponent << DOUBLE_EXPONENT_SHIFT) |
(uDoubleSign << DOUBLE_SIGN_SHIFT);
return CopyUint64ToDouble(uDouble);
}
// Public function; see ieee754.h
IEEE754_union IEEE754_FloatToSmallest(float f)
{
IEEE754_union result;
// Pull the neeed two parts out of the single-precision float
const uint32_t uSingle = CopyFloatToUint32(f);
const int32_t nSingleExponent = ((uSingle & SINGLE_EXPONENT_MASK) >> SINGLE_EXPONENT_SHIFT) - SINGLE_EXPONENT_BIAS;
const uint32_t uSingleSignificand = uSingle & SINGLE_SIGNIFICAND_MASK;
// Bit mask that is the significand bits that would be lost when converting
// from single-precision to half-precision
const uint64_t uDroppedSingleBits = SINGLE_SIGNIFICAND_MASK >> HALF_NUM_SIGNIFICAND_BITS;
// Optimizer will re organize so there is only one call to IEEE754_FloatToHalf()
if(uSingle == 0) {
// Value is 0.0000, not a a subnormal
result.uSize = IEEE754_UNION_IS_HALF;
result.uValue = IEEE754_FloatToHalf(f);
} else if(nSingleExponent == SINGLE_EXPONENT_INF_OR_NAN) {
// NaN, +/- infinity
result.uSize = IEEE754_UNION_IS_HALF;
result.uValue = IEEE754_FloatToHalf(f);
} else if((nSingleExponent >= HALF_EXPONENT_MIN) && nSingleExponent <= HALF_EXPONENT_MAX && (!(uSingleSignificand & uDroppedSingleBits))) {
// Normal number in exponent range and precision won't be lost
result.uSize = IEEE754_UNION_IS_HALF;
result.uValue = IEEE754_FloatToHalf(f);
} else {
// Subnormal, exponent out of range, or precision will be lost
result.uSize = IEEE754_UNION_IS_SINGLE;
result.uValue = uSingle;
}
return result;
}
// Public function; see ieee754.h
IEEE754_union IEEE754_DoubleToSmallestInternal(double d, int bAllowHalfPrecision)
{
IEEE754_union result;
// Pull the needed two parts out of the double-precision float
const uint64_t uDouble = CopyDoubleToUint64(d);
const int64_t nDoubleExponent = ((uDouble & DOUBLE_EXPONENT_MASK) >> DOUBLE_EXPONENT_SHIFT) - DOUBLE_EXPONENT_BIAS;
const uint64_t uDoubleSignificand = uDouble & DOUBLE_SIGNIFICAND_MASK;
// Masks to check whether dropped significand bits are zero or not
const uint64_t uDroppedDoubleBits = DOUBLE_SIGNIFICAND_MASK >> HALF_NUM_SIGNIFICAND_BITS;
const uint64_t uDroppedSingleBits = DOUBLE_SIGNIFICAND_MASK >> SINGLE_NUM_SIGNIFICAND_BITS;
// The various cases
if(d == 0.0) { // Take care of positive and negative zero
// Value is 0.0000, not a a subnormal
result.uSize = IEEE754_UNION_IS_HALF;
result.uValue = IEEE754_DoubleToHalf(d);
} else if(nDoubleExponent == DOUBLE_EXPONENT_INF_OR_NAN) {
// NaN, +/- infinity
result.uSize = IEEE754_UNION_IS_HALF;
result.uValue = IEEE754_DoubleToHalf(d);
} else if(bAllowHalfPrecision && (nDoubleExponent >= HALF_EXPONENT_MIN) && nDoubleExponent <= HALF_EXPONENT_MAX && (!(uDoubleSignificand & uDroppedDoubleBits))) {
// Can convert to half without precision loss
result.uSize = IEEE754_UNION_IS_HALF;
result.uValue = IEEE754_DoubleToHalf(d);
} else if((nDoubleExponent >= SINGLE_EXPONENT_MIN) && nDoubleExponent <= SINGLE_EXPONENT_MAX && (!(uDoubleSignificand & uDroppedSingleBits))) {
// Can convert to single without precision loss
result.uSize = IEEE754_UNION_IS_SINGLE;
result.uValue = CopyFloatToUint32((float)d);
} else {
// Can't convert without precision loss
result.uSize = IEEE754_UNION_IS_DOUBLE;
result.uValue = uDouble;
}
return result;
}

View File

@ -0,0 +1,168 @@
/*==============================================================================
ieee754.c -- floating point conversion between half, double and single precision
Copyright (c) 2018-2019, Laurence Lundblade. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause
See BSD-3-Clause license in README.md
Created on 7/23/18
==============================================================================*/
#ifndef ieee754_h
#define ieee754_h
#include <stdint.h>
/*
General comments
This is a complete in that it handles all conversion cases
including +/- infinity, +/- zero, subnormal numbers, qNaN, sNaN
and NaN payloads.
This confirms to IEEE 754-2008, but note that this doesn't
specify conversions, just the encodings.
NaN payloads are preserved with alignment on the LSB. The
qNaN bit is handled differently and explicity copied. It
is always the MSB of the significand. The NaN payload MSBs
(except the qNaN bit) are truncated when going from
double or single to half.
TODO: what does the C cast do with NaN payloads from
double to single?
*/
/*
Most simply just explicilty encode the type you want, single or double.
This works easily everywhere since standard C supports both
these types and so does qcbor. This encoder also supports
half precision and there's a few ways to use it to encode
floating point numbers in less space.
Without losing precision, you can encode a single or double
such that the special values of 0, NaN and Infinity encode
as half-precision. This CBOR decodoer and most others
should handle this properly.
If you don't mind losing precision, then you can use half-precision.
One way to do this is to set up your environment to use
___fp_16. Some compilers and CPUs support it even though it is not
standard C. What is nice about this is that your program
will use less memory and floating point operations like
multiplying, adding and such will be faster.
Another way to make use of half-precision is to represent
the values in your program as single or double, but encode
them in CBOR as half-precision. This cuts the size
of the encoded messages by 2 or 4, but doesn't reduce
memory needs or speed because you are still using
single or double in your code.
encode:
- float as float
- double as double
- half as half
- float as half_precision, for environments that don't support a half-precision type
- double as half_precision, for environments that don't support a half-precision type
- float with NaN, Infinity and 0 as half
- double with NaN, Infinity and 0 as half
*/
/*
Convert single precision float to half-precision float.
Precision and NaN payload bits will be lost. Too large
values will round up to infinity and too small to zero.
*/
uint16_t IEEE754_FloatToHalf(float f);
/*
Convert half precision float to single precision float.
This is a loss-less conversion.
*/
float IEEE754_HalfToFloat(uint16_t uHalfPrecision);
/*
Convert double precision float to half-precision float.
Precision and NaN payload bits will be lost. Too large
values will round up to infinity and too small to zero.
*/
uint16_t IEEE754_DoubleToHalf(double d);
/*
Convert half precision float to double precision float.
This is a loss-less conversion.
*/
double IEEE754_HalfToDouble(uint16_t uHalfPrecision);
// Both tags the value and gives the size
#define IEEE754_UNION_IS_HALF 2
#define IEEE754_UNION_IS_SINGLE 4
#define IEEE754_UNION_IS_DOUBLE 8
typedef struct {
uint8_t uSize; // One of IEEE754_IS_xxxx
uint64_t uValue;
} IEEE754_union;
/*
Converts double-precision to single-precision or half-precision if possible without
loss of precisions. If not, leaves it as a double. Only converts to single-precision
unless bAllowHalfPrecision is set.
*/
IEEE754_union IEEE754_DoubleToSmallestInternal(double d, int bAllowHalfPrecision);
/*
Converts double-precision to single-precision if possible without
loss of precision. If not, leaves it as a double.
*/
static inline IEEE754_union IEEE754_DoubleToSmall(double d)
{
return IEEE754_DoubleToSmallestInternal(d, 0);
}
/*
Converts double-precision to single-precision or half-precision if possible without
loss of precisions. If not, leaves it as a double.
*/
static inline IEEE754_union IEEE754_DoubleToSmallest(double d)
{
return IEEE754_DoubleToSmallestInternal(d, 1);
}
/*
Converts single-precision to half-precision if possible without
loss of precision. If not leaves as single-precision.
*/
IEEE754_union IEEE754_FloatToSmallest(float f);
#endif /* ieee754_h */

View File

@ -0,0 +1,650 @@
/*==============================================================================
Copyright (c) 2016-2018, The Linux Foundation.
Copyright (c) 2018-2019, Laurence Lundblade.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of The Linux Foundation nor the names of its
contributors, nor the name "Laurence Lundblade" may be used to
endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
==============================================================================*/
/*===================================================================================
FILE: qcbor_encode.c
DESCRIPTION: This file contains the implementation of QCBOR.
EDIT HISTORY FOR FILE:
This section contains comments describing changes made to the module.
Notice that changes are listed in reverse chronological order.
when who what, where, why
-------- ---- ---------------------------------------------------
12/30/18 llundblade Small efficient clever encode of type & argument.
11/29/18 llundblade Rework to simpler handling of tags and labels.
11/9/18 llundblade Error codes are now enums.
11/1/18 llundblade Floating support.
10/31/18 llundblade Switch to one license that is almost BSD-3.
09/28/18 llundblade Added bstr wrapping feature for COSE implementation.
02/05/18 llundbla Works on CPUs which require integer alignment.
Requires new version of UsefulBuf.
07/05/17 llundbla Add bstr wrapping of maps/arrays for COSE
03/01/17 llundbla More data types
11/13/16 llundbla Integrate most TZ changes back into github version.
09/30/16 gkanike Porting to TZ.
03/15/16 llundbla Initial Version.
=====================================================================================*/
#include "qcbor.h"
#include "ieee754.h"
/*...... This is a ruler that is 80 characters long...........................*/
/*
CBOR's two nesting types, arrays and maps, are tracked here. There is a
limit of QCBOR_MAX_ARRAY_NESTING to the number of arrays and maps
that can be nested in one encoding so the encoding context stays
small enough to fit on the stack.
When an array / map is opened, pCurrentNesting points to the element
in pArrays that records the type, start position and accumluates a
count of the number of items added. When closed the start position is
used to go back and fill in the type and number of items in the array
/ map.
Encoded output be just items like ints and strings that are
not part of any array / map. That is, the first thing encoded
does not have to be an array or a map.
*/
inline static void Nesting_Init(QCBORTrackNesting *pNesting)
{
// assumes pNesting has been zeroed
pNesting->pCurrentNesting = &pNesting->pArrays[0];
// Implied CBOR array at the top nesting level. This is never returned,
// but makes the item count work correctly.
pNesting->pCurrentNesting->uMajorType = CBOR_MAJOR_TYPE_ARRAY;
}
inline static QCBORError Nesting_Increase(QCBORTrackNesting *pNesting,
uint8_t uMajorType,
uint32_t uPos)
{
QCBORError nReturn = QCBOR_SUCCESS;
if(pNesting->pCurrentNesting == &pNesting->pArrays[QCBOR_MAX_ARRAY_NESTING]) {
// trying to open one too many
nReturn = QCBOR_ERR_ARRAY_NESTING_TOO_DEEP;
} else {
pNesting->pCurrentNesting++;
pNesting->pCurrentNesting->uCount = 0;
pNesting->pCurrentNesting->uStart = uPos;
pNesting->pCurrentNesting->uMajorType = uMajorType;
}
return nReturn;
}
inline static void Nesting_Decrease(QCBORTrackNesting *pNesting)
{
pNesting->pCurrentNesting--;
}
inline static QCBORError Nesting_Increment(QCBORTrackNesting *pNesting)
{
if(1 >= QCBOR_MAX_ITEMS_IN_ARRAY - pNesting->pCurrentNesting->uCount) {
return QCBOR_ERR_ARRAY_TOO_LONG;
}
pNesting->pCurrentNesting->uCount += 1;
return QCBOR_SUCCESS;
}
inline static uint16_t Nesting_GetCount(QCBORTrackNesting *pNesting)
{
// The nesting count recorded is always the actual number of individiual
// data items in the array or map. For arrays CBOR uses the actual item
// count. For maps, CBOR uses the number of pairs. This function returns
// the number needed for the CBOR encoding, so it divides the number of
// items by two for maps to get the number of pairs. This implementation
// takes advantage of the map major type being one larger the array major
// type, hence uDivisor is either 1 or 2.
const uint16_t uDivisor = pNesting->pCurrentNesting->uMajorType - CBOR_MAJOR_TYPE_ARRAY+1;
return pNesting->pCurrentNesting->uCount / uDivisor;
}
inline static uint32_t Nesting_GetStartPos(QCBORTrackNesting *pNesting)
{
return pNesting->pCurrentNesting->uStart;
}
inline static uint8_t Nesting_GetMajorType(QCBORTrackNesting *pNesting)
{
return pNesting->pCurrentNesting->uMajorType;
}
inline static int Nesting_IsInNest(QCBORTrackNesting *pNesting)
{
return pNesting->pCurrentNesting == &pNesting->pArrays[0] ? 0 : 1;
}
/*
Error tracking plan -- Errors are tracked internally and not returned
until Finish is called. The CBOR errors are in me->uError.
UsefulOutBuf also tracks whether the buffer is full or not in its
context. Once either of these errors is set they are never
cleared. Only QCBOREncode_Init() resets them. Or said another way, they must
never be cleared or we'll tell the caller all is good when it is not.
Only one error code is reported by QCBOREncode_Finish() even if there are
multiple errors. The last one set wins. The caller might have to fix
one error to reveal the next one they have to fix. This is OK.
The buffer full error tracked by UsefulBuf is only pulled out of
UsefulBuf in Finish() so it is the one that usually wins. UsefulBuf
will never go off the end of the buffer even if it is called again
and again when full.
It is really tempting to not check for overflow on the count in the
number of items in an array. It would save a lot of code, it is
extremely unlikely that any one will every put 65,000 items in an
array, and the only bad thing that would happen is the CBOR would be
bogus.
Since this does not parse any input, you could in theory remove all
error checks in this code if you knew the caller called it
correctly. Maybe someday CDDL or some such language will be able to
generate the code to call this and the calling code would always be
correct. This could also automatically size some of the data
structures like array/map nesting resulting in some stack memory
savings.
Errors returned here fall into two categories:
Sizes
QCBOR_ERR_BUFFER_TOO_LARGE -- Encoded output exceeded UINT32_MAX
QCBOR_ERR_BUFFER_TOO_SMALL -- output buffer too small
QCBOR_ERR_ARRAY_NESTING_TOO_DEEP -- Array/map nesting > QCBOR_MAX_ARRAY_NESTING1
QCBOR_ERR_ARRAY_TOO_LONG -- Too many things added to an array/map
Nesting constructed incorrectly
QCBOR_ERR_TOO_MANY_CLOSES -- more close calls than opens
QCBOR_ERR_CLOSE_MISMATCH -- Type of close does not match open
QCBOR_ERR_ARRAY_OR_MAP_STILL_OPEN -- Finish called without enough closes
*/
/*
Public function for initialization. See header qcbor.h
*/
void QCBOREncode_Init(QCBOREncodeContext *me, UsefulBuf Storage)
{
memset(me, 0, sizeof(QCBOREncodeContext));
UsefulOutBuf_Init(&(me->OutBuf), Storage);
Nesting_Init(&(me->nesting));
}
/*
All CBOR data items have a type and an "argument". The argument is
either the value of the item for integer types, the length of the
content for string, byte, array and map types, a tag for major type
6, and has several uses for major type 7.
This function encodes the type and the argument. There are several
encodings for the argument depending on how large it is and how it is
used.
Every encoding of the type and argument has at least one byte, the
"initial byte".
The top three bits of the initial byte are the major type for the
CBOR data item. The eight major types defined by the standard are
defined as CBOR_MAJOR_TYPE_xxxx in qcbor.h.
The remaining five bits, known as "additional information", and
possibly more bytes encode the argument. If the argument is less than
24, then it is encoded entirely in the five bits. This is neat
because it allows you to encode an entire CBOR data item in 1 byte
for many values and types (integers 0-23, true, false, and tags).
If the argument is larger than 24, then it is encoded in 1,2,4 or 8
additional bytes, with the number of these bytes indicated by the
values of the 5 bits 24, 25, 25 and 27.
It is possible to encode a particular argument in many ways with this
representation. This implementation always uses the smallest
possible representation. This conforms with CBOR preferred encoding.
This function inserts them into the output buffer at the specified
position. AppendEncodedTypeAndNumber() appends to the end.
This function takes care of converting to network byte order.
This function is also used to insert floats and doubles. Before this
function is called the float or double must be copied into a
uint64_t. That is how they are passed in. They are then converted to
network byte order correctly. The uMinLen param makes sure that even
if all the digits of a half, float or double are 0 it is still
correctly encoded in 2, 4 or 8 bytes.
*/
static void InsertEncodedTypeAndNumber(QCBOREncodeContext *me,
uint8_t uMajorType,
int nMinLen,
uint64_t uNumber,
size_t uPos)
{
/*
This code does endian conversion without hton or knowing the
endianness of the machine using masks and shifts. This avoids the
dependency on hton and the mess of figuring out how to find the
machine's endianness.
This is a good efficient implementation on little-endian machines.
A faster and small implementation is possible on big-endian
machines because CBOR/network byte order is big endian. However
big endian machines are uncommon.
On x86, it is about 200 bytes instead of 500 bytes for the more
formal unoptimized code.
This also does the CBOR preferred shortest encoding for integers
and is called to do endian conversion for floats.
It works backwards from the LSB to the MSB as needed.
Code Reviewers: THIS FUNCTION DOES POINTER MATH
*/
// Holds up to 9 bytes of type and argument
// plus one extra so pointer always points to
// valid bytes.
uint8_t bytes[sizeof(uint64_t)+2];
// Point to the last bytes and work backwards
uint8_t *pByte = &bytes[sizeof(bytes)-1];
// This is the 5 bits in the initial byte that is not the major type
uint8_t uAdditionalInfo;
if(uNumber < CBOR_TWENTY_FOUR && nMinLen == 0) {
// Simple case where argument is < 24
uAdditionalInfo = uNumber;
} else {
/*
Encode argument in 1,2,4 or 8 bytes. Outer loop
runs once for 1 byte and 4 times for 8 bytes.
Inner loop runs 1, 2 or 4 times depending on
outer loop counter. This works backwards taking
8 bits off the argument being encoded at a time
until all bits from uNumber have been encoded
and the minimum encoding size is reached.
Minimum encoding size is for floating point
numbers with zero bytes.
*/
static const uint8_t aIterate[] = {1,1,2,4};
uint8_t i;
for(i = 0; uNumber || nMinLen > 0; i++) {
const uint8_t uIterations = aIterate[i];
for(int j = 0; j < uIterations; j++) {
*--pByte = uNumber & 0xff;
uNumber = uNumber >> 8;
}
nMinLen -= uIterations;
}
// Additional info is the encoding of the
// number of additional bytes to encode
// argument.
uAdditionalInfo = LEN_IS_ONE_BYTE-1 + i;
}
*--pByte = (uMajorType << 5) + uAdditionalInfo;
UsefulOutBuf_InsertData(&(me->OutBuf), pByte, &bytes[sizeof(bytes)-1] - pByte, uPos);
}
/*
Append the type and number info to the end of the buffer.
See InsertEncodedTypeAndNumber() function above for details
*/
inline static void AppendEncodedTypeAndNumber(QCBOREncodeContext *me,
uint8_t uMajorType,
uint64_t uNumber)
{
// An append is an insert at the end.
InsertEncodedTypeAndNumber(me,
uMajorType,
0,
uNumber,
UsefulOutBuf_GetEndPosition(&(me->OutBuf)));
}
/*
Public functions for closing arrays and maps. See header qcbor.h
*/
void QCBOREncode_AddUInt64(QCBOREncodeContext *me, uint64_t uValue)
{
if(me->uError == QCBOR_SUCCESS) {
AppendEncodedTypeAndNumber(me, CBOR_MAJOR_TYPE_POSITIVE_INT, uValue);
me->uError = Nesting_Increment(&(me->nesting));
}
}
/*
Public functions for closing arrays and maps. See header qcbor.h
*/
void QCBOREncode_AddInt64(QCBOREncodeContext *me, int64_t nNum)
{
if(me->uError == QCBOR_SUCCESS) {
uint8_t uMajorType;
uint64_t uValue;
if(nNum < 0) {
// In CBOR -1 encodes as 0x00 with major type negative int.
uValue = (uint64_t)(-nNum - 1);
uMajorType = CBOR_MAJOR_TYPE_NEGATIVE_INT;
} else {
uValue = (uint64_t)nNum;
uMajorType = CBOR_MAJOR_TYPE_POSITIVE_INT;
}
AppendEncodedTypeAndNumber(me, uMajorType, uValue);
me->uError = Nesting_Increment(&(me->nesting));
}
}
/*
Semi-private function. It is exposed to user of the interface,
but they will usually call one of the inline wrappers rather than this.
See header qcbor.h
Does the work of adding some bytes to the CBOR output. Works for a
byte and text strings, which are the same in in CBOR though they have
different major types. This is also used to insert raw
pre-encoded CBOR.
*/
void QCBOREncode_AddBuffer(QCBOREncodeContext *me, uint8_t uMajorType, UsefulBufC Bytes)
{
if(me->uError == QCBOR_SUCCESS) {
// If it is not Raw CBOR, add the type and the length
if(uMajorType != CBOR_MAJOR_NONE_TYPE_RAW) {
AppendEncodedTypeAndNumber(me, uMajorType, Bytes.len);
}
// Actually add the bytes
UsefulOutBuf_AppendUsefulBuf(&(me->OutBuf), Bytes);
// Update the array counting if there is any nesting at all
me->uError = Nesting_Increment(&(me->nesting));
}
}
/*
Public functions for closing arrays and maps. See header qcbor.h
*/
void QCBOREncode_AddTag(QCBOREncodeContext *me, uint64_t uTag)
{
AppendEncodedTypeAndNumber(me, CBOR_MAJOR_TYPE_OPTIONAL, uTag);
}
/*
Semi-private function. It is exposed to user of the interface,
but they will usually call one of the inline wrappers rather than this.
See header qcbor.h
*/
void QCBOREncode_AddType7(QCBOREncodeContext *me, size_t uSize, uint64_t uNum)
{
if(me->uError == QCBOR_SUCCESS) {
// This function call takes care of endian swapping for the float / double
InsertEncodedTypeAndNumber(me,
// The major type for floats and doubles
CBOR_MAJOR_TYPE_SIMPLE,
// size makes sure floats with zeros encode correctly
(int)uSize,
// Bytes of the floating point number as a uint
uNum,
// end position because this is append
UsefulOutBuf_GetEndPosition(&(me->OutBuf)));
me->uError = Nesting_Increment(&(me->nesting));
}
}
/*
Public functions for closing arrays and maps. See header qcbor.h
*/
void QCBOREncode_AddDouble(QCBOREncodeContext *me, double dNum)
{
const IEEE754_union uNum = IEEE754_DoubleToSmallest(dNum);
QCBOREncode_AddType7(me, uNum.uSize, uNum.uValue);
}
/*
Semi-public function. It is exposed to user of the interface,
but they will usually call one of the inline wrappers rather than this.
See header qcbor.h
*/
void QCBOREncode_OpenMapOrArray(QCBOREncodeContext *me, uint8_t uMajorType)
{
// Add one item to the nesting level we are in for the new map or array
me->uError = Nesting_Increment(&(me->nesting));
if(me->uError == QCBOR_SUCCESS) {
// The offset where the length of an array or map will get written
// is stored in a uint32_t, not a size_t to keep stack usage smaller. This
// checks to be sure there is no wrap around when recording the offset.
// Note that on 64-bit machines CBOR larger than 4GB can be encoded as long as no
// array / map offsets occur past the 4GB mark, but the public interface
// says that the maximum is 4GB to keep the discussion simpler.
size_t uEndPosition = UsefulOutBuf_GetEndPosition(&(me->OutBuf));
// QCBOR_MAX_ARRAY_OFFSET is slightly less than UINT32_MAX so this
// code can run on a 32-bit machine and tests can pass on a 32-bit
// machine. If it was exactly UINT32_MAX, then this code would
// not compile or run on a 32-bit machine and an #ifdef or some
// machine size detection would be needed reducing portability.
if(uEndPosition >= QCBOR_MAX_ARRAY_OFFSET) {
me->uError = QCBOR_ERR_BUFFER_TOO_LARGE;
} else {
// Increase nesting level because this is a map or array.
// Cast from size_t to uin32_t is safe because of check above
me->uError = Nesting_Increase(&(me->nesting), uMajorType, (uint32_t)uEndPosition);
}
}
}
/*
Public functions for closing arrays and maps. See header qcbor.h
*/
void QCBOREncode_CloseMapOrArray(QCBOREncodeContext *me,
uint8_t uMajorType,
UsefulBufC *pWrappedCBOR)
{
if(me->uError == QCBOR_SUCCESS) {
if(!Nesting_IsInNest(&(me->nesting))) {
me->uError = QCBOR_ERR_TOO_MANY_CLOSES;
} else if(Nesting_GetMajorType(&(me->nesting)) != uMajorType) {
me->uError = QCBOR_ERR_CLOSE_MISMATCH;
} else {
// When the array, map or bstr wrap was started, nothing was done
// except note the position of the start of it. This code goes back
// and inserts the actual CBOR array, map or bstr and its length.
// That means all the data that is in the array, map or wrapped
// needs to be slid to the right. This is done by UsefulOutBuf's
// insert function that is called from inside
// InsertEncodedTypeAndNumber()
const size_t uInsertPosition = Nesting_GetStartPos(&(me->nesting));
const size_t uEndPosition = UsefulOutBuf_GetEndPosition(&(me->OutBuf));
// This can't go negative because the UsefulOutBuf always only grows
// and never shrinks. UsefulOutBut itself also has defenses such that
// it won't write were it should not even if given hostile input lengths
const size_t uLenOfEncodedMapOrArray = uEndPosition - uInsertPosition;
// Length is number of bytes for a bstr and number of items a for map & array
const size_t uLength = uMajorType == CBOR_MAJOR_TYPE_BYTE_STRING ?
uLenOfEncodedMapOrArray : Nesting_GetCount(&(me->nesting));
// Actually insert
InsertEncodedTypeAndNumber(me,
uMajorType, // major type bstr, array or map
0, // no minimum length for encoding
uLength, // either len of bstr or num map / array items
uInsertPosition); // position in out buffer
// Return pointer and length to the enclosed encoded CBOR. The intended
// use is for it to be hashed (e.g., SHA-256) in a COSE implementation.
// This must be used right away, as the pointer and length go invalid
// on any subsequent calls to this function because of the
// InsertEncodedTypeAndNumber() call that slides data to the right.
if(pWrappedCBOR) {
const UsefulBufC PartialResult = UsefulOutBuf_OutUBuf(&(me->OutBuf));
const size_t uBstrLen = UsefulOutBuf_GetEndPosition(&(me->OutBuf)) - uEndPosition;
*pWrappedCBOR = UsefulBuf_Tail(PartialResult, uInsertPosition+uBstrLen);
}
Nesting_Decrease(&(me->nesting));
}
}
}
/*
Public functions to finish and get the encoded result. See header qcbor.h
*/
QCBORError QCBOREncode_Finish(QCBOREncodeContext *me, UsefulBufC *pEncodedCBOR)
{
QCBORError uReturn = me->uError;
if(uReturn != QCBOR_SUCCESS) {
goto Done;
}
if (Nesting_IsInNest(&(me->nesting))) {
uReturn = QCBOR_ERR_ARRAY_OR_MAP_STILL_OPEN;
goto Done;
}
if(UsefulOutBuf_GetError(&(me->OutBuf))) {
// Stuff didn't fit in the buffer.
// This check catches this condition for all the appends and inserts
// so checks aren't needed when the appends and inserts are performed.
// And of course UsefulBuf will never overrun the input buffer given
// to it. No complex analysis of the error handling in this file is
// needed to know that is true. Just read the UsefulBuf code.
uReturn = QCBOR_ERR_BUFFER_TOO_SMALL;
goto Done;
}
*pEncodedCBOR = UsefulOutBuf_OutUBuf(&(me->OutBuf));
Done:
return uReturn;
}
/*
Public functions to finish and get the encoded result. See header qcbor.h
*/
QCBORError QCBOREncode_FinishGetSize(QCBOREncodeContext *me, size_t *puEncodedLen)
{
UsefulBufC Enc;
QCBORError nReturn = QCBOREncode_Finish(me, &Enc);
if(nReturn == QCBOR_SUCCESS) {
*puEncodedLen = Enc.len;
}
return nReturn;
}
/*
Notes on the code
CBOR Major Type Public Function
0 QCBOREncode_AddUInt64
0, 1 QCBOREncode_AddUInt64, QCBOREncode_AddInt64
2, 3 QCBOREncode_AddBuffer, Also QCBOREncode_OpenMapOrArray
4, 5 QCBOREncode_OpenMapOrArray
6 QCBOREncode_AddTag
7 QCBOREncode_AddDouble, QCBOREncode_AddType7
Object code sizes on X86 with LLVM compiler and -Os (Dec 30, 2018)
_QCBOREncode_Init 69
_QCBOREncode_AddUInt64 76
_QCBOREncode_AddInt64 87
_QCBOREncode_AddBuffer 113
_QCBOREncode_AddTag 27
_QCBOREncode_AddType7 87
_QCBOREncode_AddDouble 36
_QCBOREncode_OpenMapOrArray 103
_QCBOREncode_CloseMapOrArray 181
_InsertEncodedTypeAndNumber 190
_QCBOREncode_Finish 72
_QCBOREncode_FinishGetSize 70
Total is about 1.1KB
_QCBOREncode_CloseMapOrArray is larger because it has a lot
of nesting tracking to do and much of Nesting_ inlines
into it. It probably can't be reduced much.
If the error returned by Nesting_Increment() can be ignored
because the limit is so high and the consequence of exceeding
is proved to be inconsequential, then a lot of if(me->uError)
instance can be removed, saving some code.
*/

View File

@ -0,0 +1,700 @@
/*==============================================================================
Copyright (c) 2016-2018, The Linux Foundation.
Copyright (c) 2018-2019, Laurence Lundblade.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of The Linux Foundation nor the names of its
contributors, nor the name "Laurence Lundblade" may be used to
endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
==============================================================================*/
#include "UsefulBuf.h"
/* Basic exercise...
Call all the main public functions.
Binary compare the result to the expected.
There is nothing adversarial in this test
*/
const char * UOBTest_NonAdversarial()
{
const char *szReturn = NULL;
UsefulBuf_MAKE_STACK_UB(outbuf,50);
UsefulOutBuf UOB;
UsefulOutBuf_Init(&UOB, outbuf);
if(!UsefulOutBuf_AtStart(&UOB)) {
szReturn = "Not at start";
goto Done;
}
// Put 7 bytes at beginning of buf
UsefulOutBuf_AppendData(&UOB, "bluster", 7);
if(UsefulOutBuf_AtStart(&UOB)) {
szReturn = "At start";
goto Done;
}
// add a space to end
UsefulOutBuf_AppendByte(&UOB, ' ');
// Add 5 bytes to the end
UsefulBufC UBC = {"hunny", 5};
UsefulOutBuf_AppendUsefulBuf(&UOB, UBC);
// Insert 9 bytes at the beginning, slide the previous stuff right
UsefulOutBuf_InsertData(&UOB, "heffalump", 9, 0);
UsefulOutBuf_InsertByte(&UOB, ' ', 9);
// Put 9 bytes in at position 10 -- just after "heffalump "
UsefulBufC UBC2 = {"unbounce ", 9};
UsefulOutBuf_InsertUsefulBuf(&UOB, UBC2, 10);
// Make it a null terminated string (because all the appends and inserts above not strcpy !)
UsefulOutBuf_AppendByte(&UOB, '\0');
UsefulBufC U = UsefulOutBuf_OutUBuf(&UOB);
const char *expected = "heffalump unbounce bluster hunny";
if(UsefulBuf_IsNULLC(U) || U.len-1 != strlen(expected) || strcmp(expected, U.ptr) || UsefulOutBuf_GetError(&UOB)) {
szReturn = "OutUBuf";
}
UsefulBuf_MAKE_STACK_UB(buf, 50);
UsefulBufC Out = UsefulOutBuf_CopyOut(&UOB, buf);
if(UsefulBuf_IsNULLC(Out) || Out.len-1 != strlen(expected) || strcmp(expected, Out.ptr)) {
szReturn = "CopyOut";
}
Done:
return szReturn;
}
/*
Append test utility.
pUOB is the buffer to append too
num is the amount to append
expected is the expected return code, 0 or 1
returns 0 if test passed
*/
static int AppendTest(UsefulOutBuf *pUOB, size_t num, int expected)
{
//reset
UsefulOutBuf_Reset(pUOB);
// check status first
if(UsefulOutBuf_GetError(pUOB))
return 1;
// append the bytes
UsefulOutBuf_AppendData(pUOB, (const uint8_t *)"bluster", num);
// check error status after
if(UsefulOutBuf_GetError(pUOB) != expected)
return 1;
return 0;
}
/*
Same as append, but takes a position param too
*/
static int InsertTest(UsefulOutBuf *pUOB, size_t num, size_t pos, int expected)
{
// reset
UsefulOutBuf_Reset(pUOB);
// check
if(UsefulOutBuf_GetError(pUOB))
return 1;
UsefulOutBuf_InsertData(pUOB, (const uint8_t *)"bluster", num, pos);
if(UsefulOutBuf_GetError(pUOB) != expected)
return 1;
return 0;
}
/*
Boundary conditions to test
- around 0
- around the buffer size
- around MAX size_t
Test these for the buffer size and the cursor, the insert amount, the append amount and the insert position
*/
const char *UOBTest_BoundaryConditionsTest()
{
UsefulBuf_MAKE_STACK_UB(outbuf,2);
UsefulOutBuf UOB;
UsefulOutBuf_Init(&UOB, outbuf);
// append 0 byte to a 2 byte buffer --> success
if(AppendTest(&UOB, 0, 0))
return "Append 0 bytes failed";
// append 1 byte to a 2 byte buffer --> success
if(AppendTest(&UOB, 1, 0))
return "Append of 1 byte failed";
// append 2 byte to a 2 byte buffer --> success
if(AppendTest(&UOB, 2, 0))
return "Append to fill buffer failed";
// append 3 bytes to a 2 byte buffer --> failure
if(AppendTest(&UOB, 3, 1))
return "Overflow of buffer not caught";
// append max size_t to a 2 byte buffer --> failure
if(AppendTest(&UOB, SIZE_MAX, 1))
return "Append of SIZE_MAX error not caught";
if(InsertTest(&UOB, 1, 0, 0))
return "Insert 1 byte at start failed";
if(InsertTest(&UOB, 2, 0, 0))
return "Insert 2 bytes at start failed";
if(InsertTest(&UOB, 3, 0, 1))
return "Insert overflow not caught";
if(InsertTest(&UOB, 1, 1, 1))
return "Bad insertion point not caught";
UsefulBuf_MAKE_STACK_UB(outBuf2,10);
UsefulOutBuf_Init(&UOB, outBuf2);
UsefulOutBuf_Reset(&UOB);
// put data in the buffer
UsefulOutBuf_AppendString(&UOB, "abc123");
UsefulOutBuf_InsertString(&UOB, "xyz*&^", 0);
if(!UsefulOutBuf_GetError(&UOB)) {
return "insert with data should have failed";
}
UsefulOutBuf_Init(&UOB, (UsefulBuf){NULL, SIZE_MAX - 5});
UsefulOutBuf_AppendData(&UOB, "123456789", SIZE_MAX -6);
if(UsefulOutBuf_GetError(&UOB)) {
return "insert in huge should have succeeded";
}
UsefulOutBuf_Init(&UOB, (UsefulBuf){NULL, SIZE_MAX - 5});
UsefulOutBuf_AppendData(&UOB, "123456789", SIZE_MAX -5);
if(UsefulOutBuf_GetError(&UOB)) {
return "insert in huge should have succeeded";
}
UsefulOutBuf_Init(&UOB, (UsefulBuf){NULL, SIZE_MAX - 5});
UsefulOutBuf_AppendData(&UOB, "123456789", SIZE_MAX - 4);
if(!UsefulOutBuf_GetError(&UOB)) {
return "lengths near max size";
}
return NULL;
}
// Test function to get size and magic number check
const char *TestBasicSanity()
{
UsefulBuf_MAKE_STACK_UB(outbuf,10);
UsefulOutBuf UOB;
// First -- make sure that the room left function returns the right amount
UsefulOutBuf_Init(&UOB, outbuf);
if(UsefulOutBuf_RoomLeft(&UOB) != 10)
return "room left failed";
if(!UsefulOutBuf_WillItFit(&UOB, 9)) {
return "it did not fit";
}
if(UsefulOutBuf_WillItFit(&UOB, 11)) {
return "it should have not fit";
}
// Next -- make sure that the magic number checking is working right
UOB.magic = 8888; // make magic bogus
UsefulOutBuf_AppendData(&UOB, (const uint8_t *)"bluster", 7);
if(!UsefulOutBuf_GetError(&UOB))
return "magic corruption check failed";
// Next make sure that the valid data length check is working right
UsefulOutBuf_Init(&UOB, outbuf);
UOB.data_len = UOB.UB.len+1; // make size bogus
UsefulOutBuf_AppendData(&UOB, (const uint8_t *)"bluster", 7);
if(!UsefulOutBuf_GetError(&UOB))
return "valid data check failed";
return NULL;
}
const char *UBMacroConversionsTest()
{
char *szFoo = "foo";
UsefulBufC Foo = UsefulBuf_FromSZ(szFoo);
if(Foo.len != 3 || strncmp(Foo.ptr, szFoo, 3))
return "SZToUsefulBufC failed";
UsefulBufC Too = UsefulBuf_FROM_SZ_LITERAL("Toooo");
if(Too.len != 5 || strncmp(Too.ptr, "Toooo", 5))
return "UsefulBuf_FROM_SZ_LITERAL failed";
uint8_t pB[] = {0x42, 0x6f, 0x6f};
UsefulBufC Boo = UsefulBuf_FROM_BYTE_ARRAY_LITERAL(pB);
if(Boo.len != 3 || strncmp(Boo.ptr, "Boo", 3))
return "UsefulBuf_FROM_BYTE_ARRAY_LITERAL failed";
char *sz = "not const"; // some data for the test
UsefulBuf B = (UsefulBuf){sz, sizeof(sz)};
UsefulBufC BC = UsefulBuf_Const(B);
if(BC.len != sizeof(sz) || BC.ptr != sz)
return "UsefulBufConst failed";
return NULL;
}
const char *UBUtilTests()
{
UsefulBuf UB = NULLUsefulBuf;
if(!UsefulBuf_IsNULL(UB)){
return "IsNull failed";
}
if(!UsefulBuf_IsEmpty(UB)){
return "IsEmpty failed";
}
if(!UsefulBuf_IsNULLOrEmpty(UB)) {
return "IsNULLOrEmpty failed";
}
const UsefulBufC UBC = UsefulBuf_Const(UB);
if(!UsefulBuf_IsNULLC(UBC)){
return "IsNull const failed";
}
if(!UsefulBuf_IsEmptyC(UBC)){
return "IsEmptyC failed";
}
if(!UsefulBuf_IsNULLOrEmptyC(UBC)){
return "IsNULLOrEmptyC failed";
}
const UsefulBuf UB2 = UsefulBuf_Unconst(UBC);
if(!UsefulBuf_IsEmpty(UB2)) {
return "Back to UB is Empty failed";
}
UB.ptr = "x"; // just some valid pointer
if(UsefulBuf_IsNULL(UB)){
return "IsNull failed";
}
if(!UsefulBuf_IsEmptyC(UBC)){
return "IsEmpty failed";
}
// test the Unconst.
if(UsefulBuf_Unconst(UBC).ptr != NULL) {
return "Unconst failed";
}
// Set 100 bytes of '+'; validated a few tests later
UsefulBuf_MAKE_STACK_UB(Temp, 100);
const UsefulBufC TempC = UsefulBuf_Set(Temp, '+');
// Try to copy into a buf that is too small and see failure
UsefulBuf_MAKE_STACK_UB(Temp2, 99);
if(!UsefulBuf_IsNULLC(UsefulBuf_Copy(Temp2, TempC))) {
return "Copy should have failed";
}
if(UsefulBuf_IsNULLC(UsefulBuf_CopyPtr(Temp2, "xx", 2))) {
return "CopyPtr failed";
}
UsefulBufC xxyy = UsefulBuf_CopyOffset(Temp2, 2, UsefulBuf_FROM_SZ_LITERAL("yy"));
if(UsefulBuf_IsNULLC(xxyy)) {
return "CopyOffset Failed";
}
if(UsefulBuf_Compare(UsefulBuf_Head(xxyy, 3), UsefulBuf_FROM_SZ_LITERAL("xxy"))) {
return "head failed";
}
if(UsefulBuf_Compare(UsefulBuf_Tail(xxyy, 1), UsefulBuf_FROM_SZ_LITERAL("xyy"))) {
return "tail failed";
}
if(!UsefulBuf_IsNULLC(UsefulBuf_Head(xxyy, 5))) {
return "head should have failed";
}
if(!UsefulBuf_IsNULLC(UsefulBuf_Tail(xxyy, 5))) {
return "tail should have failed";
}
if(!UsefulBuf_IsNULLC(UsefulBuf_Tail(NULLUsefulBufC, 0))) {
return "tail of NULLUsefulBufC is not NULLUsefulBufC";
}
const UsefulBufC TailResult = UsefulBuf_Tail((UsefulBufC){NULL, 100}, 99);
if(TailResult.ptr != NULL || TailResult.len != 1) {
return "tail of NULL and length incorrect";
}
if(!UsefulBuf_IsNULLC(UsefulBuf_CopyOffset(Temp2, 100, UsefulBuf_FROM_SZ_LITERAL("yy")))) {
return "Copy Offset should have failed";
}
// Try to copy into a NULL/empty buf and see failure
const UsefulBuf UBNull = NULLUsefulBuf;
if(!UsefulBuf_IsNULLC(UsefulBuf_Copy(UBNull, TempC))) {
return "Copy to NULL should have failed";
}
// Try to set a NULL/empty buf; nothing should happen
UsefulBuf_Set(UBNull, '+'); // This will crash on failure
// Copy successfully to a buffer
UsefulBuf_MAKE_STACK_UB(Temp3, 101);
if(UsefulBuf_IsNULLC(UsefulBuf_Copy(Temp3, TempC))) {
return "Copy should not have failed";
}
static const uint8_t pExpected[] = {
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
};
UsefulBufC Expected = UsefulBuf_FROM_BYTE_ARRAY_LITERAL(pExpected);
// This validates comparison for equality and the UsefulBuf_Set
if(UsefulBuf_Compare(Expected, TempC)) {
return "Set / Copy / Compare failed";
}
// Compare two empties and expect success
if(UsefulBuf_Compare(NULLUsefulBufC, NULLUsefulBufC)){
return "Compare Empties failed";
}
// Compare with empty and expect the first to be larger
if(UsefulBuf_Compare(Expected, NULLUsefulBufC) <= 0){
return "Compare with empty failed";
}
static const uint8_t pExpectedBigger[] = {
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', ',',
};
const UsefulBufC ExpectedBigger = UsefulBuf_FROM_BYTE_ARRAY_LITERAL(pExpectedBigger);
// Expect -1 when the first arg is smaller
if(UsefulBuf_Compare(Expected, ExpectedBigger) >= 0){
return "Compare with bigger";
}
static const uint8_t pExpectedSmaller[] = {
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '*',
};
const UsefulBufC ExpectedSmaller = UsefulBuf_FROM_BYTE_ARRAY_LITERAL(pExpectedSmaller);
// Expect +1 when the first arg is larger
if(UsefulBuf_Compare(Expected, ExpectedSmaller) <= 0){
return "Compare with smaller";
}
static const uint8_t pExpectedLonger[] = {
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+', '+'
};
const UsefulBufC ExpectedLonger = UsefulBuf_FROM_BYTE_ARRAY_LITERAL(pExpectedLonger);
// Expect -1 when the first arg is smaller
if(UsefulBuf_Compare(Expected, ExpectedLonger) >= 0){
return "Compare with longer";
}
static const uint8_t pExpectedShorter[] = {
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+', '+',
'+', '+', '+', '+', '+', '+', '+', '+', '+',
};
const UsefulBufC ExpectedShorter = UsefulBuf_FROM_BYTE_ARRAY_LITERAL(pExpectedShorter);
// Expect +1 with the first arg is larger
if(UsefulBuf_Compare(Expected, ExpectedShorter) <= 0){
return "Compare with shorter";
}
if(UsefulBuf_IsNULLC(UsefulBuf_Copy(Temp, NULLUsefulBufC))) {
return "Copy null/empty failed";
}
// Look for +++++... in +++++... and find it at the beginning
if(0 != UsefulBuf_FindBytes(ExpectedLonger, ExpectedShorter)){
return "Failed to find";
}
// look for ++* in ....++* and find it at the end
static const uint8_t pToFind[] = {'+', '+', '*'};
const UsefulBufC ToBeFound = UsefulBuf_FROM_BYTE_ARRAY_LITERAL(pToFind);
if(97 != UsefulBuf_FindBytes(ExpectedSmaller, ToBeFound)){
return "Failed to find 2";
}
// look for ++* in ....++, and find it near the end
if(SIZE_MAX != UsefulBuf_FindBytes(ExpectedBigger, ToBeFound)){
return "Failed to not find";
}
// Look for the whole buffer in itself and succeed.
if(0 != UsefulBuf_FindBytes(ExpectedLonger, ExpectedLonger)){
return "Failed to find 3";
}
return NULL;
}
const char * UIBTest_IntegerFormat()
{
UsefulOutBuf_MakeOnStack(UOB,100);
const uint32_t u32 = 0x0A0B0C0D; // from https://en.wikipedia.org/wiki/Endianness
const uint64_t u64 = 1984738472938472;
const uint16_t u16 = 40000;
const uint8_t u8 = 9;
const float f = (float)314.15;
const double d = 2.1e10;
UsefulOutBuf_AppendUint32(&UOB, u32); // Also tests UsefulOutBuf_InsertUint64 and UsefulOutBuf_GetEndPosition
UsefulOutBuf_AppendUint64(&UOB, u64); // Also tests UsefulOutBuf_InsertUint32
UsefulOutBuf_AppendUint16(&UOB, u16); // Also tests UsefulOutBuf_InsertUint16
UsefulOutBuf_AppendByte(&UOB, u8);
UsefulOutBuf_AppendFloat(&UOB, f); // Also tests UsefulOutBuf_InsertFloat
UsefulOutBuf_AppendDouble(&UOB, d); // Also tests UsefulOutBuf_InsertDouble
const UsefulBufC O = UsefulOutBuf_OutUBuf(&UOB);
if(UsefulBuf_IsNULLC(O))
return "Couldn't output integers";
// from https://en.wikipedia.org/wiki/Endianness
const uint8_t pExpectedNetworkOrder[4] = {0x0A, 0x0B, 0x0C, 0x0D};
if(memcmp(O.ptr, pExpectedNetworkOrder, 4)) {
return "not in network order";
}
UsefulInputBuf UIB;
UsefulInputBuf_Init(&UIB, O);
if(UsefulInputBuf_Tell(&UIB) != 0) {
return "UsefulInputBuf_Tell failed";
}
if(UsefulInputBuf_GetUint32(&UIB) != u32) {
return "u32 out then in failed";
}
if(UsefulInputBuf_GetUint64(&UIB) != u64) {
return "u64 out then in failed";
}
if(UsefulInputBuf_GetUint16(&UIB) != u16) {
return "u16 out then in failed";
}
if(UsefulInputBuf_GetByte(&UIB) != u8) {
return "u8 out then in failed";
}
if(UsefulInputBuf_GetFloat(&UIB) != f) {
return "float out then in failed";
}
if(UsefulInputBuf_GetDouble(&UIB) != d) {
return "double out then in failed";
}
// Reset and go again for a few more tests
UsefulInputBuf_Init(&UIB, O);
const UsefulBufC Four = UsefulInputBuf_GetUsefulBuf(&UIB, 4);
if(UsefulBuf_IsNULLC(Four)) {
return "Four is NULL";
}
if(UsefulBuf_Compare(Four, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(pExpectedNetworkOrder))) {
return "Four compare failed";
}
if(UsefulInputBuf_BytesUnconsumed(&UIB) != 23){
return "Wrong number of unconsumed bytes";
}
if(!UsefulInputBuf_BytesAvailable(&UIB, 23)){
return "Wrong number of bytes available I";
}
if(UsefulInputBuf_BytesAvailable(&UIB, 24)){
return "Wrong number of bytes available II";
}
UsefulInputBuf_Seek(&UIB, 0);
if(UsefulInputBuf_GetError(&UIB)) {
return "unexpected error after seek";
}
const uint8_t *pGetBytes = (const uint8_t *)UsefulInputBuf_GetBytes(&UIB, 4);
if(pGetBytes == NULL) {
return "GetBytes returns NULL";
}
if(memcmp(pGetBytes, pExpectedNetworkOrder, 4)) {
return "Got wrong bytes";
}
UsefulInputBuf_Seek(&UIB, 28);
if(!UsefulInputBuf_GetError(&UIB)) {
return "expected error after seek";
}
return NULL;
}
const char *UBUTest_CopyUtil()
{
if(UsefulBufUtil_CopyFloatToUint32(65536.0F) != 0x47800000) {
return "CopyFloatToUint32 failed";
}
if(UsefulBufUtil_CopyDoubleToUint64(4e-40F) != 0X37C16C2800000000ULL) {
return "CopyDoubleToUint64 failed";
}
if(UsefulBufUtil_CopyUint64ToDouble(0X37C16C2800000000ULL) != 4e-40F) {
return "CopyUint64ToDouble failed";
}
if(UsefulBufUtil_CopyUint32ToFloat(0x47800000) != 65536.0F) {
return "CopyUint32ToFloat failed";
}
return NULL;
}

View File

@ -0,0 +1,50 @@
/*==============================================================================
Copyright (c) 2016-2018, The Linux Foundation.
Copyright (c) 2018, Laurence Lundblade.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of The Linux Foundation nor the names of its
contributors, nor the name "Laurence Lundblade" may be used to
endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
==============================================================================*/
#ifndef UsefulBuf_UsefulBuf_Tests_h
#define UsefulBuf_UsefulBuf_Tests_h
const char * UOBTest_NonAdversarial(void);
const char * TestBasicSanity(void);
const char * UOBTest_BoundaryConditionsTest(void);
const char * UBMacroConversionsTest(void);
const char * UBUtilTests(void);
const char * UIBTest_IntegerFormat(void);
const char * UBUTest_CopyUtil(void);
#endif

View File

@ -0,0 +1,474 @@
/*==============================================================================
float_tests.c -- tests for float and conversion to/from half-precision
Copyright (c) 2018-2019, Laurence Lundblade. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause
See BSD-3-Clause license in README.md
Created on 9/19/18
==============================================================================*/
#include "float_tests.h"
#include "qcbor.h"
#include "half_to_double_from_rfc7049.h"
#include <math.h> // For INFINITY and NAN and isnan()
static const uint8_t spExpectedHalf[] = {
0xB1,
0x64,
0x7A, 0x65, 0x72, 0x6F,
0xF9, 0x00, 0x00, // 0.000
0x6A,
0x69, 0x6E, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x74, 0x79,
0xF9, 0x7C, 0x00, // Infinity
0x73,
0x6E, 0x65, 0x67, 0x61, 0x74, 0x69, 0x76, 0x65, 0x20, 0x69, 0x6E, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x74, 0x79,
0xF9, 0xFC, 0x00, // -Inifinity
0x63,
0x4E, 0x61, 0x4E,
0xF9, 0x7E, 0x00, // NaN
0x63,
0x6F, 0x6E, 0x65,
0xF9, 0x3C, 0x00, // 1.0
0x69,
0x6F, 0x6E, 0x65, 0x20, 0x74, 0x68, 0x69, 0x72, 0x64,
0xF9, 0x35, 0x55, // 0.333251953125
0x76,
0x6C, 0x61, 0x72, 0x67, 0x65, 0x73, 0x74, 0x20, 0x68, 0x61, 0x6C, 0x66, 0x2D, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6F, 0x6E,
0xF9, 0x7B, 0xFF, // 65504.0
0x78, 0x18, 0x74, 0x6F, 0x6F, 0x2D, 0x6C, 0x61, 0x72, 0x67, 0x65, 0x20, 0x68, 0x61, 0x6C, 0x66, 0x2D, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6F, 0x6E,
0xF9, 0x7C, 0x00, // Infinity
0x72,
0x73, 0x6D, 0x61, 0x6C, 0x6C, 0x65, 0x73, 0x74, 0x20, 0x73, 0x75, 0x62, 0x6E, 0x6F, 0x72, 0x6D, 0x61, 0x6C,
0xF9, 0x00, 0x01, // 0.000000059604
0x6F,
0x73, 0x6D, 0x61, 0x6C, 0x6C, 0x65, 0x73, 0x74, 0x20, 0x6E, 0x6F, 0x72, 0x6D, 0x61, 0x6C,
0xF9, 0x03, 0xFF, // 0.0000609755516
0x71,
0x62, 0x69, 0x67, 0x67, 0x65, 0x73, 0x74, 0x20, 0x73, 0x75, 0x62, 0x6E, 0x6F, 0x72, 0x6D, 0x61, 0x6C,
0xF9, 0x04, 0x00, // 0.000061988
0x70,
0x73, 0x75, 0x62, 0x6E, 0x6F, 0x72, 0x6D, 0x61, 0x6C, 0x20, 0x73, 0x69, 0x6E, 0x67, 0x6C, 0x65,
0xF9, 0x00, 0x00,
0x03,
0xF9, 0xC0, 0x00, // -2
0x04,
0xF9, 0x7E, 0x00, // qNaN
0x05,
0xF9, 0x7C, 0x01, // sNaN
0x06,
0xF9, 0x7E, 0x0F, // qNaN with payload 0x0f
0x07,
0xF9, 0x7C, 0x0F, // sNaN with payload 0x0f
};
int HalfPrecisionDecodeBasicTests()
{
UsefulBufC HalfPrecision = UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spExpectedHalf);
QCBORDecodeContext DC;
QCBORDecode_Init(&DC, HalfPrecision, 0);
QCBORItem Item;
QCBORDecode_GetNext(&DC, &Item);
if(Item.uDataType != QCBOR_TYPE_MAP) {
return -1;
}
QCBORDecode_GetNext(&DC, &Item);
if(Item.uDataType != QCBOR_TYPE_DOUBLE || Item.val.dfnum != 0.0F) {
return -2;
}
QCBORDecode_GetNext(&DC, &Item);
if(Item.uDataType != QCBOR_TYPE_DOUBLE || Item.val.dfnum != INFINITY) {
return -3;
}
QCBORDecode_GetNext(&DC, &Item);
if(Item.uDataType != QCBOR_TYPE_DOUBLE || Item.val.dfnum != -INFINITY) {
return -4;
}
QCBORDecode_GetNext(&DC, &Item); // TODO, is this really converting right? It is carrying payload, but this confuses things.
if(Item.uDataType != QCBOR_TYPE_DOUBLE || !isnan(Item.val.dfnum)) {
return -5;
}
QCBORDecode_GetNext(&DC, &Item);
if(Item.uDataType != QCBOR_TYPE_DOUBLE || Item.val.dfnum != 1.0F) {
return -6;
}
QCBORDecode_GetNext(&DC, &Item);
if(Item.uDataType != QCBOR_TYPE_DOUBLE || Item.val.dfnum != 0.333251953125F) {
return -7;
}
QCBORDecode_GetNext(&DC, &Item);
if(Item.uDataType != QCBOR_TYPE_DOUBLE || Item.val.dfnum != 65504.0F) {
return -8;
}
QCBORDecode_GetNext(&DC, &Item);
if(Item.uDataType != QCBOR_TYPE_DOUBLE || Item.val.dfnum != INFINITY) {
return -9;
}
QCBORDecode_GetNext(&DC, &Item); // TODO: check this
if(Item.uDataType != QCBOR_TYPE_DOUBLE || Item.val.dfnum != 0.0000000596046448F) {
return -10;
}
QCBORDecode_GetNext(&DC, &Item); // TODO: check this
if(Item.uDataType != QCBOR_TYPE_DOUBLE || Item.val.dfnum != 0.0000609755516F) {
return -11;
}
QCBORDecode_GetNext(&DC, &Item); // TODO check this
if(Item.uDataType != QCBOR_TYPE_DOUBLE || Item.val.dfnum != 0.0000610351563F) {
return -12;
}
QCBORDecode_GetNext(&DC, &Item);
if(Item.uDataType != QCBOR_TYPE_DOUBLE || Item.val.dfnum != 0) {
return -13;
}
QCBORDecode_GetNext(&DC, &Item);
if(Item.uDataType != QCBOR_TYPE_DOUBLE || Item.val.dfnum != -2.0F) {
return -14;
}
// TODO: double check these four tests
QCBORDecode_GetNext(&DC, &Item); // qNaN
if(Item.uDataType != QCBOR_TYPE_DOUBLE || UsefulBufUtil_CopyDoubleToUint64(Item.val.dfnum) != 0x7ff8000000000000ULL) {
return -15;
}
QCBORDecode_GetNext(&DC, &Item); // sNaN
if(Item.uDataType != QCBOR_TYPE_DOUBLE || UsefulBufUtil_CopyDoubleToUint64(Item.val.dfnum) != 0x7ff0000000000001ULL) {
return -16;
}
QCBORDecode_GetNext(&DC, &Item); // qNaN with payload 0x0f
if(Item.uDataType != QCBOR_TYPE_DOUBLE || UsefulBufUtil_CopyDoubleToUint64(Item.val.dfnum) != 0x7ff800000000000fULL) {
return -17;
}
QCBORDecode_GetNext(&DC, &Item); // sNaN with payload 0x0f
if(Item.uDataType != QCBOR_TYPE_DOUBLE || UsefulBufUtil_CopyDoubleToUint64(Item.val.dfnum) != 0x7ff000000000000fULL) {
return -18;
}
if(QCBORDecode_Finish(&DC)) {
return -19;
}
return 0;
}
int HalfPrecisionAgainstRFCCodeTest()
{
for(uint32_t uHalfP = 0; uHalfP < 0xffff; uHalfP += 60) {
unsigned char x[2];
x[1] = uHalfP & 0xff;
x[0] = uHalfP >> 8;
double d = decode_half(x);
// Contruct the CBOR for the half-precision float by hand
UsefulBuf_MAKE_STACK_UB(__xx, 3);
UsefulOutBuf UOB;
UsefulOutBuf_Init(&UOB, __xx);
const uint8_t uHalfPrecInitialByte = HALF_PREC_FLOAT + (CBOR_MAJOR_TYPE_SIMPLE << 5); // 0xf9
UsefulOutBuf_AppendByte(&UOB, uHalfPrecInitialByte); // The initial byte for a half-precision float
UsefulOutBuf_AppendUint16(&UOB, (uint16_t)uHalfP);
// Now parse the hand-constructed CBOR. This will invoke the conversion to a float
QCBORDecodeContext DC;
QCBORDecode_Init(&DC, UsefulOutBuf_OutUBuf(&UOB), 0);
QCBORItem Item;
QCBORDecode_GetNext(&DC, &Item);
if(Item.uDataType != QCBOR_TYPE_DOUBLE) {
return -1;
}
//printf("%04x QCBOR:%15.15f RFC: %15.15f (%8x)\n", uHalfP,Item.val.fnum, d , UsefulBufUtil_CopyFloatToUint32(d));
if(isnan(d)) {
// The RFC code uses the native instructions which may or may not
// handle sNaN, qNaN and NaN payloads correctly. This test just
// makes sure it is a NaN and doesn't worry about the type of NaN
if(!isnan(Item.val.dfnum)) {
return -3;
}
} else {
if(Item.val.dfnum != d) {
return -2;
}
}
}
return 0;
}
/*
{"zero": 0.0,
"negative zero": -0.0,
"infinitity": Infinity,
"negative infinitity": -Infinity,
"NaN": NaN,
"one": 1.0,
"one third": 0.333251953125,
"largest half-precision": 65504.0,
"largest half-precision point one": 65504.1,
"too-large half-precision": 65536.0,
"smallest subnormal": 5.96046448e-8,
"smallest normal": 0.00006103515261202119,
"biggest subnormal": 0.00006103515625,
"subnormal single": 4.00000646641519e-40,
3: -2.0,
"large single exp": 2.5521177519070385e+38,
"too-large single exp": 5.104235503814077e+38,
"biggest single with prec": 16777216.0,
"first single with prec loss": 16777217.0,
1: "fin"}
*/
static const uint8_t spExpectedSmallest[] = {
0xB4, 0x64, 0x7A, 0x65, 0x72, 0x6F, 0xF9, 0x00, 0x00, 0x6D,
0x6E, 0x65, 0x67, 0x61, 0x74, 0x69, 0x76, 0x65, 0x20, 0x7A,
0x65, 0x72, 0x6F, 0xF9, 0x80, 0x00, 0x6A, 0x69, 0x6E, 0x66,
0x69, 0x6E, 0x69, 0x74, 0x69, 0x74, 0x79, 0xF9, 0x7C, 0x00,
0x73, 0x6E, 0x65, 0x67, 0x61, 0x74, 0x69, 0x76, 0x65, 0x20,
0x69, 0x6E, 0x66, 0x69, 0x6E, 0x69, 0x74, 0x69, 0x74, 0x79,
0xF9, 0xFC, 0x00, 0x63, 0x4E, 0x61, 0x4E, 0xF9, 0x7E, 0x00,
0x63, 0x6F, 0x6E, 0x65, 0xF9, 0x3C, 0x00, 0x69, 0x6F, 0x6E,
0x65, 0x20, 0x74, 0x68, 0x69, 0x72, 0x64, 0xF9, 0x35, 0x55,
0x76, 0x6C, 0x61, 0x72, 0x67, 0x65, 0x73, 0x74, 0x20, 0x68,
0x61, 0x6C, 0x66, 0x2D, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73,
0x69, 0x6F, 0x6E, 0xF9, 0x7B, 0xFF, 0x78, 0x20, 0x6C, 0x61,
0x72, 0x67, 0x65, 0x73, 0x74, 0x20, 0x68, 0x61, 0x6C, 0x66,
0x2D, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6F, 0x6E,
0x20, 0x70, 0x6F, 0x69, 0x6E, 0x74, 0x20, 0x6F, 0x6E, 0x65,
0xFB, 0x40, 0xEF, 0xFC, 0x03, 0x33, 0x33, 0x33, 0x33, 0x78,
0x18, 0x74, 0x6F, 0x6F, 0x2D, 0x6C, 0x61, 0x72, 0x67, 0x65,
0x20, 0x68, 0x61, 0x6C, 0x66, 0x2D, 0x70, 0x72, 0x65, 0x63,
0x69, 0x73, 0x69, 0x6F, 0x6E, 0xFA, 0x47, 0x80, 0x00, 0x00,
0x72, 0x73, 0x6D, 0x61, 0x6C, 0x6C, 0x65, 0x73, 0x74, 0x20,
0x73, 0x75, 0x62, 0x6E, 0x6F, 0x72, 0x6D, 0x61, 0x6C, 0xFB,
0x3E, 0x70, 0x00, 0x00, 0x00, 0x1C, 0x5F, 0x68, 0x6F, 0x73,
0x6D, 0x61, 0x6C, 0x6C, 0x65, 0x73, 0x74, 0x20, 0x6E, 0x6F,
0x72, 0x6D, 0x61, 0x6C, 0xFA, 0x38, 0x7F, 0xFF, 0xFF, 0x71,
0x62, 0x69, 0x67, 0x67, 0x65, 0x73, 0x74, 0x20, 0x73, 0x75,
0x62, 0x6E, 0x6F, 0x72, 0x6D, 0x61, 0x6C, 0xF9, 0x04, 0x00,
0x70, 0x73, 0x75, 0x62, 0x6E, 0x6F, 0x72, 0x6D, 0x61, 0x6C,
0x20, 0x73, 0x69, 0x6E, 0x67, 0x6C, 0x65, 0xFB, 0x37, 0xC1,
0x6C, 0x28, 0x00, 0x00, 0x00, 0x00, 0x03, 0xF9, 0xC0, 0x00,
0x70, 0x6C, 0x61, 0x72, 0x67, 0x65, 0x20, 0x73, 0x69, 0x6E,
0x67, 0x6C, 0x65, 0x20, 0x65, 0x78, 0x70, 0xFA, 0x7F, 0x40,
0x00, 0x00, 0x74, 0x74, 0x6F, 0x6F, 0x2D, 0x6C, 0x61, 0x72,
0x67, 0x65, 0x20, 0x73, 0x69, 0x6E, 0x67, 0x6C, 0x65, 0x20,
0x65, 0x78, 0x70, 0xFB, 0x47, 0xF8, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x78, 0x18, 0x62, 0x69, 0x67, 0x67, 0x65, 0x73,
0x74, 0x20, 0x73, 0x69, 0x6E, 0x67, 0x6C, 0x65, 0x20, 0x77,
0x69, 0x74, 0x68, 0x20, 0x70, 0x72, 0x65, 0x63, 0xFA, 0x4B,
0x80, 0x00, 0x00, 0x78, 0x1B, 0x66, 0x69, 0x72, 0x73, 0x74,
0x20, 0x73, 0x69, 0x6E, 0x67, 0x6C, 0x65, 0x20, 0x77, 0x69,
0x74, 0x68, 0x20, 0x70, 0x72, 0x65, 0x63, 0x20, 0x6C, 0x6F,
0x73, 0x73, 0xFB, 0x41, 0x70, 0x00, 0x00, 0x10, 0x00, 0x00,
0x00, 0x01, 0x63, 0x66, 0x69, 0x6E
};
int DoubleAsSmallestTest()
{
UsefulBuf_MAKE_STACK_UB(EncodedHalfsMem, 420);
#define QCBOREncode_AddDoubleAsSmallestToMap QCBOREncode_AddDoubleToMap
#define QCBOREncode_AddDoubleAsSmallestToMapN QCBOREncode_AddDoubleToMapN
QCBOREncodeContext EC;
QCBOREncode_Init(&EC, EncodedHalfsMem);
// These are mostly from https://en.wikipedia.org/wiki/Half-precision_floating-point_format
QCBOREncode_OpenMap(&EC);
// 64 # text(4)
// 7A65726F # "zero"
// F9 0000 # primitive(0)
QCBOREncode_AddDoubleAsSmallestToMap(&EC, "zero", 0.00);
// 64 # text(4)
// 7A65726F # "negative zero"
// F9 8000 # primitive(0)
QCBOREncode_AddDoubleAsSmallestToMap(&EC, "negative zero", -0.00);
// 6A # text(10)
// 696E66696E6974697479 # "infinitity"
// F9 7C00 # primitive(31744)
QCBOREncode_AddDoubleAsSmallestToMap(&EC, "infinitity", INFINITY);
// 73 # text(19)
// 6E6567617469766520696E66696E6974697479 # "negative infinitity"
// F9 FC00 # primitive(64512)
QCBOREncode_AddDoubleAsSmallestToMap(&EC, "negative infinitity", -INFINITY);
// 63 # text(3)
// 4E614E # "NaN"
// F9 7E00 # primitive(32256)
QCBOREncode_AddDoubleAsSmallestToMap(&EC, "NaN", NAN);
// TODO: test a few NaN variants
// 63 # text(3)
// 6F6E65 # "one"
// F9 3C00 # primitive(15360)
QCBOREncode_AddDoubleAsSmallestToMap(&EC, "one", 1.0);
// 69 # text(9)
// 6F6E65207468697264 # "one third"
// F9 3555 # primitive(13653)
QCBOREncode_AddDoubleAsSmallestToMap(&EC, "one third", 0.333251953125);
// 76 # text(22)
// 6C6172676573742068616C662D707265636973696F6E # "largest half-precision"
// F9 7BFF # primitive(31743)
QCBOREncode_AddDoubleAsSmallestToMap(&EC, "largest half-precision",65504.0);
// 76 # text(22)
// 6C6172676573742068616C662D707265636973696F6E # "largest half-precision"
// F9 7BFF # primitive(31743)
QCBOREncode_AddDoubleAsSmallestToMap(&EC, "largest half-precision point one",65504.1);
// Float 65536.0F is 0x47800000 in hex. It has an exponent of 16, which is larger than 15, the largest half-precision exponent
// 78 18 # text(24)
// 746F6F2D6C617267652068616C662D707265636973696F6E # "too-large half-precision"
// FA 47800000 # primitive(31743)
QCBOREncode_AddDoubleAsSmallestToMap(&EC, "too-large half-precision", 65536.0);
// The smallest possible half-precision subnormal, but digitis are lost converting
// to half, so this turns into a double
// 72 # text(18)
// 736D616C6C657374207375626E6F726D616C # "smallest subnormal"
// FB 3E700000001C5F68 # primitive(4499096027744984936)
QCBOREncode_AddDoubleAsSmallestToMap(&EC, "smallest subnormal", 0.0000000596046448);
// The smallest possible half-precision snormal, but digitis are lost converting
// to half, so this turns into a single TODO: confirm this is right
// 6F # text(15)
// 736D616C6C657374206E6F726D616C # "smallest normal"
// FA 387FFFFF # primitive(947912703)
// in hex single is 0x387fffff, exponent -15, significand 7fffff
QCBOREncode_AddDoubleAsSmallestToMap(&EC, "smallest normal", 0.0000610351526F);
// 71 # text(17)
// 62696767657374207375626E6F726D616C # "biggest subnormal"
// F9 0400 # primitive(1024)
// in hex single is 0x38800000, exponent -14, significand 0
QCBOREncode_AddDoubleAsSmallestToMap(&EC, "biggest subnormal", 0.0000610351563F);
// 70 # text(16)
// 7375626E6F726D616C2073696E676C65 # "subnormal single"
// FB 37C16C2800000000 # primitive(4017611261645684736)
QCBOREncode_AddDoubleAsSmallestToMap(&EC, "subnormal single", 4e-40F);
// 03 # unsigned(3)
// F9 C000 # primitive(49152)
QCBOREncode_AddDoubleAsSmallestToMapN(&EC, 3, -2.0);
// 70 # text(16)
// 6C617267652073696E676C6520657870 # "large single exp"
// FA 7F400000 # primitive(2134900736)
// (0x01LL << (DOUBLE_NUM_SIGNIFICAND_BITS-1)) | ((127LL + DOUBLE_EXPONENT_BIAS) << DOUBLE_EXPONENT_SHIFT);
QCBOREncode_AddDoubleAsSmallestToMap(&EC, "large single exp", 2.5521177519070385E+38); // Exponent fits single
// 74 # text(20)
// 746F6F2D6C617267652073696E676C6520657870 # "too-large single exp"
// FB 47F8000000000000 # primitive(5185894970917126144)
// (0x01LL << (DOUBLE_NUM_SIGNIFICAND_BITS-1)) | ((128LL + DOUBLE_EXPONENT_BIAS) << DOUBLE_EXPONENT_SHIFT);
QCBOREncode_AddDoubleAsSmallestToMap(&EC, "too-large single exp", 5.104235503814077E+38); // Exponent too large for single
// 66 # text(6)
// 646664666465 # "dfdfde"
// FA 4B800000 # primitive(1266679808)
QCBOREncode_AddDoubleAsSmallestToMap(&EC, "biggest single with prec",16777216); // Single with no precision loss
// 78 18 # text(24)
// 626967676573742073696E676C6520776974682070726563 # "biggest single with prec"
// FA 4B800000 # primitive(1266679808)
QCBOREncode_AddDoubleAsSmallestToMap(&EC, "first single with prec loss",16777217); // Double becuase of precision loss
// Just a convenient marker when cutting and pasting encoded CBOR
QCBOREncode_AddSZStringToMapN(&EC, 1, "fin");
QCBOREncode_CloseMap(&EC);
UsefulBufC EncodedHalfs;
int nReturn = QCBOREncode_Finish(&EC, &EncodedHalfs);
if(nReturn) {
return -1;
}
if(UsefulBuf_Compare(EncodedHalfs, UsefulBuf_FROM_BYTE_ARRAY_LITERAL(spExpectedSmallest))) {
return -3;
}
return 0;
}
#ifdef NAN_EXPERIMENT
/*
Code for checking what the double to float cast does with
NaNs. Not run as part of tests. Keep it around to
be able to check various platforms and CPUs.
*/
#define DOUBLE_NUM_SIGNIFICAND_BITS (52)
#define DOUBLE_NUM_EXPONENT_BITS (11)
#define DOUBLE_NUM_SIGN_BITS (1)
#define DOUBLE_SIGNIFICAND_SHIFT (0)
#define DOUBLE_EXPONENT_SHIFT (DOUBLE_NUM_SIGNIFICAND_BITS)
#define DOUBLE_SIGN_SHIFT (DOUBLE_NUM_SIGNIFICAND_BITS + DOUBLE_NUM_EXPONENT_BITS)
#define DOUBLE_SIGNIFICAND_MASK (0xfffffffffffffULL) // The lower 52 bits
#define DOUBLE_EXPONENT_MASK (0x7ffULL << DOUBLE_EXPONENT_SHIFT) // 11 bits of exponent
#define DOUBLE_SIGN_MASK (0x01ULL << DOUBLE_SIGN_SHIFT) // 1 bit of sign
#define DOUBLE_QUIET_NAN_BIT (0x01ULL << (DOUBLE_NUM_SIGNIFICAND_BITS-1))
static int NaNExperiments() {
double dqNaN = UsefulBufUtil_CopyUint64ToDouble(DOUBLE_EXPONENT_MASK | DOUBLE_QUIET_NAN_BIT);
double dsNaN = UsefulBufUtil_CopyUint64ToDouble(DOUBLE_EXPONENT_MASK | 0x01);
double dqNaNPayload = UsefulBufUtil_CopyUint64ToDouble(DOUBLE_EXPONENT_MASK | DOUBLE_QUIET_NAN_BIT | 0xf00f);
float f1 = (float)dqNaN;
float f2 = (float)dsNaN;
float f3 = (float)dqNaNPayload;
uint32_t uqNaN = UsefulBufUtil_CopyFloatToUint32((float)dqNaN);
uint32_t usNaN = UsefulBufUtil_CopyFloatToUint32((float)dsNaN);
uint32_t uqNaNPayload = UsefulBufUtil_CopyFloatToUint32((float)dqNaNPayload);
// Result of this on x86 is that every NaN is a qNaN. The intel
// CVTSD2SS instruction ignores the NaN payload and even converts
// a sNaN to a qNaN.
return 0;
}
#endif

View File

@ -0,0 +1,23 @@
/*==============================================================================
float_tests.h -- tests for float and conversion to/from half-precision
Copyright (c) 2018-2019, Laurence Lundblade. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause
See BSD-3-Clause license in README.md
Created on 9/19/18
==============================================================================*/
#ifndef float_tests_h
#define float_tests_h
int HalfPrecisionDecodeBasicTests(void);
int DoubleAsSmallestTest(void);
int HalfPrecisionAgainstRFCCodeTest(void);
#endif /* float_tests_h */

View File

@ -0,0 +1,45 @@
/*
Copyright (c) 2013 IETF Trust and the persons identified as the
document authors. All rights reserved.
This document is subject to BCP 78 and the IETF Trust's Legal
Provisions Relating to IETF Documents
(http://trustee.ietf.org/license-info) in effect on the date of
publication of this document. Please review these documents
carefully, as they describe your rights and restrictions with respect
to this document. Code Components extracted from this document must
include Simplified BSD License text as described in Section 4.e of
the Trust Legal Provisions and are provided without warranty as
described in the Simplified BSD License.
*/
/*
This code is from RFC 7049. It is not used in the main implementation
because:
a) it adds a dependency on <math.h> and ldexp().
b) the license may be an issue
QCBOR does support half-precision, but rather than using
floating point math like this, it does it with bit shifting
and masking.
This code is here to test that code.
*/
#include "half_to_double_from_rfc7049.h"
#include <math.h>
double decode_half(unsigned char *halfp) {
int half = (halfp[0] << 8) + halfp[1];
int exp = (half >> 10) & 0x1f;
int mant = half & 0x3ff;
double val;
if (exp == 0) val = ldexp(mant, -24);
else if (exp != 31) val = ldexp(mant + 1024, exp - 25);
else val = mant == 0 ? INFINITY : NAN;
return half & 0x8000 ? -val : val;
}

View File

@ -0,0 +1,18 @@
/*==============================================================================
half_to_double_from_rfc7049.h -- interface to IETF float conversion code.
Copyright (c) 2018-2019, Laurence Lundblade. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause
See BSD-3-Clause license in README.md
Created on 9/23/18
==============================================================================*/
#ifndef half_to_double_from_rfc7049_h
#define half_to_double_from_rfc7049_h
double decode_half(unsigned char *halfp);
#endif /* half_to_double_from_rfc7049_h */

View File

@ -0,0 +1,229 @@
/*==============================================================================
Copyright (c) 2016-2018, The Linux Foundation.
Copyright (c) 2018-2019, Laurence Lundblade.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of The Linux Foundation nor the names of its
contributors, nor the name "Laurence Lundblade" may be used to
endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
==============================================================================*/
#ifndef __QCBOR__qcbort_decode_tests__
#define __QCBOR__qcbort_decode_tests__
#include "qcbor.h"
/*
Notes:
- All the functions in qcbor.h are called once in the aggregation of all the tests below.
- All the types that are supported are given as input and parsed by these tests
- There is some hostile input such as invalid lengths and CBOR too complex
and types this parser doesn't handle
*/
/*
Parse a well-known set of integers including those around the boundaries and
make sure the expected values come out
*/
int IntegerValuesParseTest(void);
/*
Decode a simple CBOR encoded array and make sure it returns all the correct values.
This is a decode test.
*/
int SimpleArrayTest(void);
/*
Make sure a maximally deep array can be parsed and that the
reported nesting level is correct. This uses test vector
of CBOR encoded data with a depth of 10. This a parse test.
*/
int ParseDeepArrayTest(void);
/*
See that the correct error is reported when parsing
an array of depth 11, one too large.
*/
int ParseTooDeepArrayTest(void);
/*
Try to parse some legit CBOR types that this parsers
doesn't support.
*/
int UnsupportedCBORDecodeTest(void);
/*
This takes the encoded CBOR integers used in the above test and parses
it over and over with one more byte less each time. It should fail
every time on incorrect CBOR input. This is a hostile input decode test.
*/
int ShortBufferParseTest(void);
/*
Same as ShortBufferParseTest, but with a different encoded CBOR input.
It is another hostile input test
*/
int ShortBufferParseTest2(void);
/*
Parses the somewhat complicated CBOR MAP and makes sure all the correct
values parse out. About 15 values are tested. This is a decode test.
*/
int ParseMapTest(void);
int FloatValuesTest1(void);
int SimpleValuesTest1(void);
/*
*/
int ParseMapAsArrayTest(void);
int ParseSimpleTest(void);
/*
Tests a number of failure cases on bad CBOR to get the right error code
*/
int FailureTests(void);
/*
Parses all possible inputs that are two bytes long. Main point
is that the test doesn't crash as it doesn't evaluate the
input for correctness in any way.
(Parsing all possible 3 byte strings takes too long on all but
very fast machines).
*/
int ComprehensiveInputTest(void);
/*
Parses all possible inputs that are four bytes long. Main point
is that the test doesn't crash as it doesn't evaluate the
input for correctness in any way. This runs very slow, so it
is only practical as a once-in-a-while regression test on
fast machines.
*/
int BigComprehensiveInputTest(void);
/*
Thest the date types -- epoch and strings
*/
int DateParseTest(void);
/*
Test optional tags like the CBOR magic number.
*/
int OptTagParseTest(void);
/*
Parse some big numbers, positive and negative
*/
int BignumParseTest(void);
int StringDecoderModeFailTest(void);
/*
Parse some nested maps
*/
int NestedMapTest(void);
/*
Parse maps with indefinite lengths
*/
int NestedMapTestIndefLen(void);
/*
Parse some maps and arrays with indefinite lengths.
Includes some error cases.
*/
int IndefiniteLengthArrayMapTest(void);
/*
Parse indefinite length strings. Uses
MemPool. Includes error cases.
*/
int IndefiniteLengthStringTest(void);
/*
Test deep nesting of indefinite length
maps and arrays including too deep.
*/
int IndefiniteLengthNestTest(void);
/*
Test parsing strings were all strings, not
just indefinite length strings, are
allocated. Includes error test cases.
*/
int AllocAllStringsTest(void);
/*
Direct test of MemPool string allocator
*/
int MemPoolTest(void);
#endif /* defined(__QCBOR__qcbort_decode_tests__) */

View File

@ -0,0 +1,145 @@
/*==============================================================================
Copyright (c) 2016-2018, The Linux Foundation.
Copyright (c) 2018-2019, Laurence Lundblade.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of The Linux Foundation nor the names of its
contributors, nor the name "Laurence Lundblade" may be used to
endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
==============================================================================*/
#ifndef __QCBOR__qcbor_encode_tests__
#define __QCBOR__qcbor_encode_tests__
#include "qcbor.h"
/*
Notes:
- All the functions in qcbor.h are called once in the aggregation of all the tests below.
- All the types that are supported are given as input and parsed by these tests
- There is some hostile input such as invalid lengths and CBOR too complex
and types this parser doesn't handle
*/
/*
Most basic test.
*/
int BasicEncodeTest(void);
/*
Encode lots of integer values, particularly around the boundary and make sure they
Match the expected binary output. Primarily an encoding test.
*/
int IntegerValuesTest1(void);
/*
Create nested arrays to the max depth allowed and make sure it succeeds.
This is an encoding test.
*/
int ArrayNestingTest1(void);
/*
Create nested arrays to one more than the meax depth and make sure it fails.
This is an encoding test.
*/
int ArrayNestingTest2(void);
/*
Encoding test.
Create arrays to max depth and close one extra time and look for correct error code
*/
int ArrayNestingTest3(void);
/*
This tests the QCBOREncode_AddRaw() function by adding two chunks or RAWCBOR to an
array and comparing with expected values. This is an encoding test.
*/
int EncodeRawTest(void);
/*
This creates a somewhat complicated CBOR MAP and verifies it against expected
data. This is an encoding test.
*/
int MapEncodeTest(void);
/*
Encodes a goodly number of floats and doubles and checks encoding is right
*/
int FloatValuesTest1(void);
/*
Encodes true, false and the like
*/
int SimpleValuesTest1(void);
/*
Encodes most data formats that are supported */
int EncodeDateTest(void);
/*
Encodes particular data structure that a particular app will need...
*/
int RTICResultsTest(void);
/*
Calls all public encode methods in qcbor.h once.
*/
int AllAddMethodsTest(void);
/*
The binary string wrapping of maps and arrays used by COSE
*/
int BstrWrapTest(void);
int BstrWrapErrorTest(void);
int BstrWrapNestTest(void);
int CoseSign1TBSTest(void);
int EncodeErrorTests(void);
#endif /* defined(__QCBOR__qcbor_encode_tests__) */

View File

@ -0,0 +1,285 @@
/*==============================================================================
run_tests.c -- test aggregator and results reporting
Copyright (c) 2018-2019, Laurence Lundblade. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause
See BSD-3-Clause license in README.md
Created on 9/30/18
==============================================================================*/
#include "run_tests.h"
#include "UsefulBuf.h"
#include <stdbool.h>
#include "float_tests.h"
#include "qcbor_decode_tests.h"
#include "qcbor_encode_tests.h"
#include "UsefulBuf_Tests.h"
// Used to test RunTests
int fail_test()
{
return -44;
}
/*
Convert a number up to 999999999 to a string. This is so sprintf doesn't
have to be linked in so as to minimized dependencies even in test code.
*/
const char *NumToString(int32_t nNum, UsefulBuf StringMem)
{
const int32_t nMax = 1000000000;
UsefulOutBuf OutBuf;
UsefulOutBuf_Init(&OutBuf, StringMem);
if(nNum < 0) {
UsefulOutBuf_AppendByte(&OutBuf, '-');
nNum = -nNum;
}
if(nNum > nMax-1) {
return "XXX";
}
bool bDidSomeOutput = false;
for(int n = nMax; n > 0; n/=10) {
int x = nNum/n;
if(x || bDidSomeOutput){
bDidSomeOutput = true;
UsefulOutBuf_AppendByte(&OutBuf, '0' + x);
nNum -= x * n;
}
}
if(!bDidSomeOutput){
UsefulOutBuf_AppendByte(&OutBuf, '0');
}
UsefulOutBuf_AppendByte(&OutBuf, '\0');
return UsefulOutBuf_GetError(&OutBuf) ? "" : StringMem.ptr;
}
typedef int (test_fun_t)(void);
typedef const char * (test_fun2_t)(void);
#define TEST_ENTRY(test_name) {#test_name, test_name, true}
#define TEST_ENTRY_DISABLED(test_name) {#test_name, test_name, false}
typedef struct {
const char *szTestName;
test_fun_t *test_fun;
bool bEnabled;
} test_entry;
typedef struct {
const char *szTestName;
test_fun2_t *test_fun;
bool bEnabled;
} test_entry2;
test_entry2 s_tests2[] = {
TEST_ENTRY(UBUTest_CopyUtil),
TEST_ENTRY(UOBTest_NonAdversarial),
TEST_ENTRY(TestBasicSanity),
TEST_ENTRY(UOBTest_BoundaryConditionsTest),
TEST_ENTRY(UBMacroConversionsTest),
TEST_ENTRY(UBUtilTests),
TEST_ENTRY(UIBTest_IntegerFormat)
};
test_entry s_tests[] = {
TEST_ENTRY(ParseMapAsArrayTest),
TEST_ENTRY(AllocAllStringsTest),
TEST_ENTRY(IndefiniteLengthNestTest),
TEST_ENTRY(NestedMapTestIndefLen),
TEST_ENTRY(ParseSimpleTest),
TEST_ENTRY(EncodeRawTest),
TEST_ENTRY(RTICResultsTest),
TEST_ENTRY(MapEncodeTest),
TEST_ENTRY(ArrayNestingTest1),
TEST_ENTRY(ArrayNestingTest2),
TEST_ENTRY(ArrayNestingTest3),
TEST_ENTRY(EncodeDateTest),
TEST_ENTRY(SimpleValuesTest1),
TEST_ENTRY(IntegerValuesTest1),
TEST_ENTRY(AllAddMethodsTest),
TEST_ENTRY(ParseTooDeepArrayTest),
TEST_ENTRY(ComprehensiveInputTest),
TEST_ENTRY(ParseMapTest),
TEST_ENTRY(IndefiniteLengthArrayMapTest),
TEST_ENTRY(BasicEncodeTest),
TEST_ENTRY(NestedMapTest),
TEST_ENTRY(BignumParseTest),
TEST_ENTRY(OptTagParseTest),
TEST_ENTRY(DateParseTest),
TEST_ENTRY(ShortBufferParseTest2),
TEST_ENTRY(ShortBufferParseTest),
TEST_ENTRY(ParseDeepArrayTest),
TEST_ENTRY(SimpleArrayTest),
TEST_ENTRY(IntegerValuesParseTest),
TEST_ENTRY(MemPoolTest),
TEST_ENTRY(IndefiniteLengthStringTest),
TEST_ENTRY(HalfPrecisionDecodeBasicTests),
TEST_ENTRY(DoubleAsSmallestTest),
TEST_ENTRY(HalfPrecisionAgainstRFCCodeTest),
TEST_ENTRY(BstrWrapTest),
TEST_ENTRY(BstrWrapErrorTest),
TEST_ENTRY(BstrWrapNestTest),
TEST_ENTRY(CoseSign1TBSTest),
TEST_ENTRY(StringDecoderModeFailTest),
TEST_ENTRY_DISABLED(BigComprehensiveInputTest),
TEST_ENTRY(EncodeErrorTests),
//TEST_ENTRY(fail_test),
};
int RunTests(const char *szTestNames[], OutputStringCB pfOutput, void *poutCtx, int *pNumTestsRun)
{
int nTestsFailed = 0;
int nTestsRun = 0;
UsefulBuf_MAKE_STACK_UB(StringStorage, 5);
test_entry2 *t2;
const test_entry2 *s_tests2_end = s_tests2 + sizeof(s_tests2)/sizeof(test_entry2);
for(t2 = s_tests2; t2 < s_tests2_end; t2++) {
if(szTestNames[0]) {
// Some tests have been named
const char **szRequestedNames;
for(szRequestedNames = szTestNames; *szRequestedNames; szRequestedNames++) {
if(!strcmp(t2->szTestName, *szRequestedNames)) {
break; // Name matched
}
}
if(*szRequestedNames == NULL) {
// Didn't match this test
continue;
}
} else {
// no tests named, but don't run "disabled" tests
if(!t2->bEnabled) {
// Don't run disabled tests when all tests are being run
// as indicated by no specific test names being given
continue;
}
}
const char * szTestResult = (t2->test_fun)();
nTestsRun++;
if(pfOutput) {
(*pfOutput)(t2->szTestName, poutCtx, 0);
}
if(szTestResult) {
if(pfOutput) {
(*pfOutput)(" FAILED (returned ", poutCtx, 0);
(*pfOutput)(szTestResult, poutCtx, 0);
(*pfOutput)(")", poutCtx, 1);
}
nTestsFailed++;
} else {
if(pfOutput) {
(*pfOutput)( " PASSED", poutCtx, 1);
}
}
}
test_entry *t;
const test_entry *s_tests_end = s_tests + sizeof(s_tests)/sizeof(test_entry);
for(t = s_tests; t < s_tests_end; t++) {
if(szTestNames[0]) {
// Some tests have been named
const char **szRequestedNames;
for(szRequestedNames = szTestNames; *szRequestedNames; szRequestedNames++) {
if(!strcmp(t->szTestName, *szRequestedNames)) {
break; // Name matched
}
}
if(*szRequestedNames == NULL) {
// Didn't match this test
continue;
}
} else {
// no tests named, but don't run "disabled" tests
if(!t->bEnabled) {
// Don't run disabled tests when all tests are being run
// as indicated by no specific test names being given
continue;
}
}
int nTestResult = (t->test_fun)();
nTestsRun++;
if(pfOutput) {
(*pfOutput)(t->szTestName, poutCtx, 0);
}
if(nTestResult) {
if(pfOutput) {
(*pfOutput)(" FAILED (returned ", poutCtx, 0);
(*pfOutput)(NumToString(nTestResult, StringStorage), poutCtx, 0);
(*pfOutput)(")", poutCtx, 1);
}
nTestsFailed++;
} else {
if(pfOutput) {
(*pfOutput)( " PASSED", poutCtx, 1);
}
}
}
if(pNumTestsRun) {
*pNumTestsRun = nTestsRun;
}
if(pfOutput) {
(*pfOutput)( "SUMMARY: ", poutCtx, 0);
(*pfOutput)( NumToString(nTestsRun, StringStorage), poutCtx, 0);
(*pfOutput)( " tests run; ", poutCtx, 0);
(*pfOutput)( NumToString(nTestsFailed, StringStorage), poutCtx, 0);
(*pfOutput)( " tests failed", poutCtx, 1);
}
return nTestsFailed;
}
static void PrintSize(const char *szWhat, uint32_t uSize, OutputStringCB pfOutput, void *pOutCtx)
{
UsefulBuf_MAKE_STACK_UB(buffer, 20);
(*pfOutput)(szWhat, pOutCtx, 0);
(*pfOutput)(" ", pOutCtx, 0);
(*pfOutput)(NumToString(uSize, buffer), pOutCtx, 0);
(*pfOutput)("", pOutCtx, 1);
}
void PrintSizes(OutputStringCB pfOutput, void *pOutCtx)
{
// Type and size of return from sizeof() varies. These will never be large so cast is safe
PrintSize("sizeof(QCBORTrackNesting)", (uint32_t)sizeof(QCBORTrackNesting), pfOutput, pOutCtx);
PrintSize("sizeof(QCBOREncodeContext)", (uint32_t)sizeof(QCBOREncodeContext), pfOutput, pOutCtx);
PrintSize("sizeof(QCBORDecodeNesting)", (uint32_t)sizeof(QCBORDecodeNesting), pfOutput, pOutCtx);
PrintSize("sizeof(QCBORDecodeContext)", (uint32_t)sizeof(QCBORDecodeContext), pfOutput, pOutCtx);
PrintSize("sizeof(QCBORItem)", (uint32_t)sizeof(QCBORItem), pfOutput, pOutCtx);
PrintSize("sizeof(QCBORStringAllocator)",(uint32_t)sizeof(QCBORStringAllocator), pfOutput, pOutCtx);
PrintSize("sizeof(QCBORTagListIn)", (uint32_t)sizeof(QCBORTagListIn), pfOutput, pOutCtx);
PrintSize("sizeof(QCBORTagListOut)", (uint32_t)sizeof(QCBORTagListOut), pfOutput, pOutCtx);
(*pfOutput)("", pOutCtx, 1);
}

View File

@ -0,0 +1,66 @@
/*==============================================================================
run_tests.h -- test aggregator and results reporting
Copyright (c) 2018-2019, Laurence Lundblade. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause
See BSD-3-Clause license in README.md
Created 9/30/18
==============================================================================*/
/**
@file run_tests.h
*/
/**
@brief Type for function to output a text string
@param[in] szString The string to output
@param[in] pOutCtx A context pointer; NULL if not needed
@param[in] bNewline If non-zero, output a newline after the string
This is a prototype of a function to be passed to RunTests() to
output text strings.
This can be implemented with stdio (if available) using a straight
call to fputs() where the FILE * is passed as the pOutCtx as shown in
the example code below. This code is for Linux where the newline is
a \\n. Windows usually prefers \\r\\n.
@code
static void fputs_wrapper(const char *szString, void *pOutCtx, int bNewLine)
{
fputs(szString, (FILE *)pOutCtx);
if(bNewLine) {
fputs("\n", pOutCtx);
}
}
@endcode
*/
typedef void (*OutputStringCB)(const char *szString, void *pOutCtx, int bNewline);
/**
@brief Runs the QCBOR tests.
@param[in] szTestNames An argv-style list of test names to run. If
empty, all are run.
@param[in] pfOutput Function that is called to output text strings.
@param[in] pOutCtx Context pointer passed to output function.
@param[out] pNumTestsRun Returns the number of tests run. May be NULL.
@return The number of tests that failed. Zero means overall success.
*/
int RunTests(const char *szTestNames[], OutputStringCB pfOutput, void *pOutCtx, int *pNumTestsRun);
/**
@brief Print sizes of encoder / decoder contexts.
@param[in] pfOutput Function that is called to output text strings.
@param[in] pOutCtx Context pointer passed to output function.
*/
void PrintSizes(OutputStringCB pfOutput, void *pOutCtx);

View File

@ -0,0 +1,63 @@
/*
* t_cose_common.h
*
* Copyright 2019, Laurence Lundblade
*
* SPDX-License-Identifier: BSD-3-Clause
*
* See BSD-3-Clause license in README.mdE.
*/
#ifndef __T_COSE_COMMON_H__
#define __T_COSE_COMMON_H__
/*
T H E C O M M E N T S
in this file are truthful, but not expansive,
complete of formatted yet...
*/
/* Private value. Intentionally not documented for Doxygen.
* This is the space allocated for the encoded protected headers. It
* needs to be big enough for make_protected_header() to succeed. It
* currently sized for one header with an algorithm ID up to 32 bits
* long -- one byte for the wrapping map, one byte for the label, 5
* bytes for the ID. If this is made accidentially too small, QCBOR will
* only return an error, and not overrun any buffers.
*/
#define T_COSE_SIGN1_MAX_PROT_HEADER (1+1+5)
/* TODO: document this */
enum t_cose_err_t {
T_COSE_SUCCESS = 0,
T_COSE_ERR_UNKNOWN_SIGNING_ALG,
T_COSE_ERR_PROTECTED_HEADERS,
T_COSE_ERR_UNSUPPORTED_HASH,
T_COSE_ERR_HASH_GENERAL_FAIL,
T_COSE_ERR_HASH_BUFFER_SIZE,
T_COSE_ERR_SIG_BUFFER_SIZE,
T_COSE_ERR_KEY_BUFFER_SIZE,
T_COSE_ERR_FORMAT,
T_COSE_ERR_CBOR_NOT_WELL_FORMED,
T_COSE_ERR_NO_ALG_ID,
T_COSE_ERR_NO_KID,
T_COSE_ERR_SIG_VERIFY
};
/*
CBOR Label for proprietary header indicating short-circuit
signing was used. Just a random number in the proprietary
label space */
#define T_COSE_SHORT_CIRCUIT_LABEL -8675309
#endif /* __T_COSE_COMMON_H__ */

View File

@ -0,0 +1,102 @@
/*
* t_cose_sign1.h
*
* Copyright (c) 2018-2019, Laurence Lundblade. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
* See BSD-3-Clause license in README.md
*/
#ifndef __T_COSE_SIGN1_H__
#define __T_COSE_SIGN1_H__
#include <stdint.h>
#include "qcbor.h"
#include "t_cose_common.h"
/**
* This is the context for creating a COSE Sign1
* structure. The caller should allocate it and
* pass it to the functions here.
* This is about 32 bytes.
*/
struct t_cose_sign1_ctx {
/* Private data structure */
uint8_t buffer_for_protected_headers[T_COSE_SIGN1_MAX_PROT_HEADER];
struct useful_buf_c protected_headers;
int32_t cose_algorithm_id;
int32_t key_select;
bool short_circuit_sign;
QCBOREncodeContext *cbor_encode_ctx;
};
/**
* \brief Initialize to start creating a COSE_Sign1.
*
* \param[in] me The cose signing context.
* \param[in] short_circuit_sign Select special test mode.
* \param[in] cose_algorithm_id The signing alg ID from COSE IANA registry.
* \param[in] key_select Which signing key to use.
* \param[in] cbor_encode_ctx The CBOR encoder context to output to.
*
* \return Error code (TODO: error codes)
*
* This COSE_Sign1 implementations is optimized for creating EAT tokens. It
* should work for CWT and others use cases too. The main point of the
* optimization is that only one output buffer is needed. There is no need
* for one buffer to hold the payload and another to hold the end result
* COSE Sign 1. The payload can be encoded right into its final place in
* the end result COSE Sign 1.
*
* To use this, create a QCBOREncodeContext and initialize it with an
* output buffer big enough to hold the payload and the COSE Sign 1
* overhead. This overhead is about 30 bytes plus the size of the
* signatue and the size of the key ID.
*
* After the QCBOREncodeContext is initialized, call t_cose_sign1_init()
* on it.
*
* Next call QCBOREncode_BstrWrap() to indicate the start of the paylod.
*
* Next call various QCBOREncode_Addxxxx methods to create the payload.
*
* Next call QCBOREncode_CloseBstrWrap() to indicate the end of the
* payload. This will also return a pointer and length of the payload
* that gets hashed.
*
* Next call t_cose_sign1_finish() with the pointer and length of the
* payload. This will do all the cryptography and complete the COSE
* Sign1.
*
* Finally, call QCBOREncode_Finish() to get the pointer and length of
* the complete token.
*/
enum t_cose_err_t t_cose_sign1_init(struct t_cose_sign1_ctx *me,
bool short_circuit_sign,
int32_t cose_algorithm_id,
int32_t key_select,
QCBOREncodeContext *cbor_encode_ctx);
/**
* \brief Finish creation of the COSE_Sign1.
*
* \param[in] me The cose signing context.
* \param[in] payload The pointer and length of the payload.
*
* \return One of the T_COSE_ERR_XXX errors (TODO: detail the errors)
*
* This is when the signature algorithm is run. The payload is
* used only to compute the hash for signing. The completed COSE_Sign1
* is retrieved from the cbor_encode_ctx by calling QCBOREncode_Finish()
*/
enum t_cose_err_t t_cose_sign1_finish(struct t_cose_sign1_ctx *me,
struct useful_buf_c payload);
#endif /* __T_COSE_SIGN1_H__ */

View File

@ -0,0 +1,116 @@
/*
* t_cose_crypto.h
*
* Copyright 2019, Laurence Lundblade
*
* SPDX-License-Identifier: BSD-3-Clause
*
* See BSD-3-Clause license in README.mdE.
*/
#ifndef __T_COSE_CRYPTO_H__
#define __T_COSE_CRYPTO_H__
#include "t_cose_common.h"
#include "useful_buf.h"
#include <stdint.h>
/*
T H E C O M M E N T S
in this file are truthful, but not expansive,
complete of formatted yet...
*/
/*
A small wrapper around the pub key functions to a) map cose alg IDs to
TF-M alg IDs, b) map crypto errors to t_cose errors, c) have inputs
and outputs be struct useful_buf_c and struct useful_buf,
d) handle key selection.
*/
enum t_cose_err_t
t_cose_crypto_pub_key_sign(int32_t cose_alg_id,
int32_t key_select,
struct useful_buf_c hash_to_sign,
struct useful_buf signature_buffer,
struct useful_buf_c *signature);
enum t_cose_err_t
t_cose_crypto_pub_key_verify(int32_t cose_alg_id,
int32_t key_select,
struct useful_buf_c hash_to_verify,
struct useful_buf_c signature);
/*
Size of X and Y coord in 2 param style EC public key. Format
is as defined in COSE RFC and http://www.secg.org/sec1-v2.pdf.
No function to get private key because there is no need for it.
The private signing key only needs to exist behind
t_cose_crypto_pub_key_sign()
*/
/* This size well-known by public standards. Implementation should
have a compile time cross check to be sure it matches
*/
#define T_COSE_CRYPTO_P256_COORD_SIZE 32
enum t_cose_err_t
t_cose_crypto_get_ec_pub_key(int32_t key_select,
int32_t *cose_curve_id,
struct useful_buf buf_to_hold_x_coord,
struct useful_buf buf_to_hold_y_coord,
struct useful_buf_c *x_coord,
struct useful_buf_c *y_coord);
/*
A small wrapper around the hash function to a) map cose alg IDs to
TF-M alg IDs, b) map crypto errors to t_cose errors, c) have inputs
and outputs be struct useful_buf_c and struct useful_buf.
*/
struct t_cose_crypto_hash {
/* Can't put actual size here without creating dependecy on actual
hash implementation, so pick a fairly large and accommodating
size. There are checks that it is big enough in the implementation
so no risk of buffer overflow */
uint8_t bytes[128];
};
/*
These sizes are well-known and fixed. Do not want to include
platform-dependent headers here and make this file platform
dependent. There are compile-time checks in the implementation
to make sure it matches the platform's size. It would be
very weird if it didn't.
*/
#define T_COSE_CRYPTO_SHA256_SIZE 32
enum t_cose_err_t t_cose_crypto_hash_start(struct t_cose_crypto_hash *hash_ctx,
int32_t cose_hash_alg_id);
void t_cose_crypto_hash_update(struct t_cose_crypto_hash *hash_ctx,
struct useful_buf_c data_to_hash);
enum t_cose_err_t
t_cose_crypto_hash_finish(struct t_cose_crypto_hash *hash_ctx,
struct useful_buf buffer_to_hold_result,
struct useful_buf_c *hash_result);
#endif /* __T_COSE_CRYPTO_H__ */

View File

@ -0,0 +1,159 @@
/*
* t_cose_defines.h
*
* Copyright (c) 2018-2019, Laurence Lundblade. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
* See BSD-3-Clause license in README.md
*/
#ifndef __T_COSE_DEFINES_H__
#define __T_COSE_DEFINES_H__
/*
These are defined in the COSE standard, RFC 8152, or in the IANA
registry for COSE at https://www.iana.org/assignments/cose/cose.xhtml
*/
/* COSE Header parameters:
https://www.iana.org/assignments/cose/cose.xhtml#header-parameters
*/
/**
* \def COSE_HEADER_PARAM_ALG
*
* \brief Label of COSE header that indicates an algorithm.
*/
#define COSE_HEADER_PARAM_ALG 1
/**
* \def COSE_HEADER_PARAM_KID
*
* \brief Label of COSE header that contains a key ID.
*/
#define COSE_HEADER_PARAM_KID 4
/* COSE Algorithms:
* https://www.iana.org/assignments/cose/cose.xhtml#algorithms
*/
/**
* \def COSE_ALG_ES256
*
* \brief Value for COSE_HEADER_PARAM_ALG to indicate ECDSA
* w/SHA-256
*/
#define COSE_ALG_ES256 -7
/**
* \def COSE_SIG_CONTEXT_STRING_SIGNATURE1
*
* \brief This is a strong constant used by COSE to
* label COSE_Sign1 structures. SeeRFC 8152, section 4.4.
*/
#define COSE_SIG_CONTEXT_STRING_SIGNATURE1 "Signature1"
/**
* \def COSE_ALG_SHA256_PROPRIETARY
*
* \brief COSE-style algorithm ID for SHA256. The offical COSE
* algorithm registry doesn't yet define an ID for a pure hash
* function. One is needed for internal use, so this is defined.
*/
#define COSE_ALG_SHA256_PROPRIETARY -72000
/* From CBOR IANA registry,
* https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml
*/
/**
* \def COSE_KEY_TYPE_EC2
*
* \brief In a COSE_Key, this is a value of the
* data item labeled COSE_KEY_KTY that indicates
* the COSE_Key is an EC key specified with
* two coordinates, X and Y.
*/
#define COSE_KEY_TYPE_EC2 2
/**
* \def COSE_ELLIPTIC_CURVE_P_256
*
* \brief In a COSE_Key, this is a value of the
* data item labeled COSE_KEY_CRV to indicate the
* NIST P-256 curve.
*/
#define COSE_ELLIPTIC_CURVE_P_256 1
/**
* \def COSE_KEY_KTY
*
* \brief In a COSE_Key, label that indicates
* the data item containing the key type
*/
#define COSE_KEY_KTY 1
/**
* \def COSE_KEY_CRV
*
* \brief In a COSE_Key that holds an EC key,
* this is label that indicates
* the data item containing the specific curve.
*/
#define COSE_KEY_CRV -1
/**
* \def COSE_KEY_X_COORDINATE
*
* \brief In a COSE_Key that holds an EC key,
* this is label that indicates
* the data item containing the X coordinate.
*/
#define COSE_KEY_X_COORDINATE -2
/**
* \def COSE_KEY_Y_COORDINATE
*
* \brief In a COSE_Key that holds an EC key,
* this is label that indicates
* the data item containing the Y coordinate.
*/
#define COSE_KEY_Y_COORDINATE -3
/**
* \def T_COSE_MAX_SIG_SIZE
*
* \brief Compile time constant for largest signature that can be handled.
* Set at 64 bytes for ECDSA 256
*/
#define T_COSE_MAX_SIG_SIZE 64
/**
* \brief Get the size in bytes of a particular signature type
*
* \param[in] cose_signature_algorithm The COSE algorithm ID.
*
* \return The size in bytes of the signature
*/
static inline size_t t_cose_signature_size(int32_t cose_signature_algorithm)
{
switch(cose_signature_algorithm) {
case COSE_ALG_ES256:
return 64; /* Size of an ECDSA 256 signature */
default:
return T_COSE_MAX_SIG_SIZE;
}
}
#endif /* __T_COSE_DEFINES_H__ */

View File

@ -0,0 +1,413 @@
/*
t_cose_sign1.c
* Copyright (c) 2018-2019, Laurence Lundblade. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
* See BSD-3-Clause license in README.md
*/
#include "t_cose_sign1.h"
#include "qcbor.h"
#include "t_cose_defines.h"
#include "t_cose_crypto.h"
#include "t_cose_util.h"
/*
T H E C O M M E N T S
in this file are truthful, but not expansive,
complete of formatted yet...
*/
/* This creates the short-circut signature that is a
concatenation of hashes up to the expected size of the
signature. This is a test mode only has it has no
security value. This is retained in commercial production
code as a useful test or demo that can run even if key
material is not set up or acecssible.
*/
static inline enum t_cose_err_t
short_circuit_sign(int32_t cose_alg_id,
struct useful_buf_c hash_to_sign,
struct useful_buf signature_buffer,
struct useful_buf_c *signature)
{
/* approximate stack use on 32-bit machine:
local use: 16
*/
enum t_cose_err_t return_value;
size_t array_indx;
size_t amount_to_copy;
size_t sig_size;
sig_size = t_cose_signature_size(cose_alg_id);
if(sig_size > signature_buffer.len) {
/* Buffer too small for this signature type */
return_value = T_COSE_ERR_SIG_BUFFER_SIZE;
goto Done;
}
/* Loop concatening copies of the hash to fill out to signature size */
for(array_indx = 0; array_indx < sig_size; array_indx += hash_to_sign.len) {
amount_to_copy = sig_size - array_indx;
if(amount_to_copy > hash_to_sign.len) {
amount_to_copy = hash_to_sign.len;
}
memcpy((uint8_t *)signature_buffer.ptr + array_indx,
hash_to_sign.ptr,
amount_to_copy);
}
signature->ptr = signature_buffer.ptr;
signature->len = sig_size;
return_value = T_COSE_SUCCESS;
Done:
return return_value;
}
#define MAX_ENCODED_COSE_KEY_SIZE \
1 + /* 1 byte to encode map */ \
2 + /* 2 bytes to encode key type */ \
2 + /* 2 bytes to encode curve */ \
2 * /* the X and Y coordinates at 32 bytes each */ \
(T_COSE_CRYPTO_P256_COORD_SIZE + 1 + 2)
static enum t_cose_err_t
t_cose_encode_cose_key(int32_t key_select,
struct useful_buf buffer_for_cose_key,
struct useful_buf_c *key_id)
{
/* approximate stack use on 32-bit machine:
local use: 328
with calls: 370
*/
enum t_cose_err_t return_value;
QCBORError qcbor_result;
QCBOREncodeContext cbor_encode_ctx;
USEFUL_BUF_MAKE_STACK_UB( buffer_for_x_coord,T_COSE_CRYPTO_P256_COORD_SIZE);
USEFUL_BUF_MAKE_STACK_UB( buffer_for_y_coord,T_COSE_CRYPTO_P256_COORD_SIZE);
struct useful_buf_c x_coord;
struct useful_buf_c y_coord;
int32_t cose_curve_id;
struct useful_buf_c encoded_key_id;
/* Get the public key x and y */
return_value = t_cose_crypto_get_ec_pub_key(key_select,
&cose_curve_id,
buffer_for_x_coord,
buffer_for_y_coord,
&x_coord,
&y_coord);
if(return_value != T_COSE_SUCCESS) {
goto Done;
}
/* Encode it into a COSE_Key structure */
QCBOREncode_Init(&cbor_encode_ctx, buffer_for_cose_key);
QCBOREncode_OpenMap(&cbor_encode_ctx);
QCBOREncode_AddInt64ToMapN(&cbor_encode_ctx,
COSE_KEY_KTY,
COSE_KEY_TYPE_EC2);
QCBOREncode_AddInt64ToMapN(&cbor_encode_ctx,
COSE_KEY_CRV,
cose_curve_id);
QCBOREncode_AddBytesToMapN(&cbor_encode_ctx,
COSE_KEY_X_COORDINATE,
x_coord);
QCBOREncode_AddBytesToMapN(&cbor_encode_ctx,
COSE_KEY_Y_COORDINATE,
y_coord);
QCBOREncode_CloseMap(&cbor_encode_ctx);
qcbor_result = QCBOREncode_Finish(&cbor_encode_ctx, &encoded_key_id);
if(qcbor_result != QCBOR_SUCCESS) {
/* Mainly means that the COSE_Key was too big for buffer_for_cose_key */
return_value = T_COSE_ERR_PROTECTED_HEADERS;
goto Done;
}
/* Finish up and return */
*key_id = encoded_key_id;
return_value = T_COSE_SUCCESS;
Done:
return return_value;
}
/* Having this as a separate function helps keep
stack usage down and is convenient */
static enum t_cose_err_t quick_sha256(struct useful_buf_c bytes_to_hash,
struct useful_buf buffer_for_hash,
struct useful_buf_c *hash)
{
/* approximate stack use on 32-bit machine:
local use: 132
*/
enum t_cose_err_t return_value = 0;
struct t_cose_crypto_hash hash_ctx;
return_value = t_cose_crypto_hash_start(&hash_ctx,
COSE_ALG_SHA256_PROPRIETARY);
if(return_value) {
goto Done;
}
t_cose_crypto_hash_update(&hash_ctx, bytes_to_hash);
return_value = t_cose_crypto_hash_finish(&hash_ctx,
buffer_for_hash,
hash);
Done:
return return_value;
}
/*
Get the kid (Key ID) which is the SHA-256 hash of the pub key as a COSE_Key.
Making kids like this probably should be an IETF standard someday.
*/
static inline enum t_cose_err_t get_keyid(int32_t key_select,
struct useful_buf buffer_for_key_id,
struct useful_buf_c *key_id)
{
/* approximate stack use on 32-bit machine:
local use: 100
with calls inlined: 560
with calls not inlined: 428
*/
enum t_cose_err_t return_value;
USEFUL_BUF_MAKE_STACK_UB( buffer_for_cose_key, MAX_ENCODED_COSE_KEY_SIZE);
struct useful_buf_c cose_key;
/* Doing the COSE encoding and the hashing in separate functions
called from here reduces the stack usage in this function by
a lot
*/
/* Get the key and encode it as a COSE_Key */
return_value = t_cose_encode_cose_key(key_select,
buffer_for_cose_key,
&cose_key);
if(return_value != T_COSE_SUCCESS) {
goto Done;
}
/* SHA256 hash of it is all we care about in the end */
return_value = quick_sha256(cose_key, buffer_for_key_id, key_id);
Done:
return return_value;
}
/*
Makes the protected headers for COSE.
returns USEFUL_VEC_NULL_INVEC if buffer_for_header was too small See
also definition of T_COSE_SIGN1_MAX_PROT_HEADER
*/
static inline struct useful_buf_c
make_protected_header(int32_t algorithm_id,
struct useful_buf buffer_for_header)
{
/* approximate stack use on 32-bit machine:
local use: 170
with calls: 210
*/
struct useful_buf_c protected_headers;
QCBORError qcbor_result;
QCBOREncodeContext cbor_encode_ctx;
struct useful_buf_c return_value;
QCBOREncode_Init(&cbor_encode_ctx, buffer_for_header);
QCBOREncode_OpenMap(&cbor_encode_ctx);
QCBOREncode_AddInt64ToMapN(&cbor_encode_ctx,
COSE_HEADER_PARAM_ALG,
algorithm_id);
QCBOREncode_CloseMap(&cbor_encode_ctx);
qcbor_result = QCBOREncode_Finish(&cbor_encode_ctx, &protected_headers);
if(qcbor_result == QCBOR_SUCCESS) {
return_value = protected_headers;
} else {
return_value = NULL_USEFUL_BUF_C;
}
return return_value;
}
static inline void add_unprotected_headers(QCBOREncodeContext *cbor_encode_ctx,
struct useful_buf_c kid,
bool short_circuit_sign)
{
QCBOREncode_OpenMap(cbor_encode_ctx);
QCBOREncode_AddBytesToMapN(cbor_encode_ctx, COSE_HEADER_PARAM_KID, kid);
if(short_circuit_sign) {
/* Indicate short-circuit signing was used */
QCBOREncode_AddBoolToMapN(cbor_encode_ctx,
T_COSE_SHORT_CIRCUIT_LABEL,
true);
}
QCBOREncode_CloseMap(cbor_encode_ctx);
}
/*
See t_cose_sign1.h
*/
enum t_cose_err_t t_cose_sign1_init(struct t_cose_sign1_ctx *me,
bool short_circuit_sign,
int32_t cose_alg_id,
int32_t key_select,
QCBOREncodeContext *cbor_encode_ctx)
{
/* approximate stack use on 32-bit machine:
local use: 66
with calls inlined: 900
with calls not inlined: 500
*/
int32_t hash_alg;
enum t_cose_err_t return_value;
USEFUL_BUF_MAKE_STACK_UB( buffer_for_kid, T_COSE_CRYPTO_SHA256_SIZE);
struct useful_buf_c kid;
struct useful_buf buffer_for_protected_header;
/* Check the cose_alg_id now by getting the hash alg as an early
error check even though it is not used until later. */
hash_alg = hash_alg_id_from_sig_alg_id(cose_alg_id);
if(hash_alg == INT32_MAX) {
return T_COSE_ERR_UNKNOWN_SIGNING_ALG;
}
/* Remember all the parameters in the context */
me->cose_algorithm_id = cose_alg_id;
me->key_select = key_select;
me->short_circuit_sign = short_circuit_sign;
me->cbor_encode_ctx = cbor_encode_ctx;
/* Get the key id because it goes into the headers that are about
to be made. */
return_value = get_keyid(key_select, buffer_for_kid, &kid);
if(return_value) {
goto Done;
}
/* Get started with the tagged array that holds the four parts of
a cose single signed message */
QCBOREncode_AddTag(cbor_encode_ctx, CBOR_TAG_COSE_SIGN1);
QCBOREncode_OpenArray(cbor_encode_ctx);
/* The protected headers, which are added as a wrapped bstr */
buffer_for_protected_header =
USEFUL_BUF_FROM_BYTE_ARRAY(me->buffer_for_protected_headers);
me->protected_headers = make_protected_header(cose_alg_id,
buffer_for_protected_header);
if(useful_buf_c_is_null(me->protected_headers)) {
/* The sizing of storage for protected headers is
off (should never happen in tested, released code) */
return_value = T_COSE_SUCCESS;
goto Done;
}
QCBOREncode_AddBytes(cbor_encode_ctx, me->protected_headers);
/* The Unprotected headers */
add_unprotected_headers(cbor_encode_ctx, kid, short_circuit_sign);
/* Any failures in CBOR encoding will be caught in finish
when the CBOR encoding is closed off. No need to track
here as the CBOR encoder tracks it internally. */
return_value = T_COSE_SUCCESS;
Done:
return return_value;
}
/*
See t_cose_sign1.h
*/
enum t_cose_err_t t_cose_sign1_finish(struct t_cose_sign1_ctx *me,
struct useful_buf_c signed_payload)
{
/* approximate stack use on 32-bit machine:
local use: 116
with calls inline: 500
with calls not inlined; 450
*/
enum t_cose_err_t return_value;
/* pointer and length of the completed tbs hash */
struct useful_buf_c tbs_hash;
/* Pointer and length of the completed signature */
struct useful_buf_c signature;
/* Buffer for the actual signature */
USEFUL_BUF_MAKE_STACK_UB( buffer_for_signature,
T_COSE_MAX_SIG_SIZE);
/* Buffer for the tbs hash. Only big enough for SHA256 */
USEFUL_BUF_MAKE_STACK_UB( buffer_for_tbs_hash,
T_COSE_CRYPTO_SHA256_SIZE);
/* Create the hash of the to-be-signed bytes. Inputs to the hash
are the protected headers, the payload that getting signed, the
cose signature alg from which the hash alg is determined. The
cose_algorithm_id was checked in t_cose_sign1_init() so it
doesn't need to be checked here. */
return_value = create_tbs_hash(me->cose_algorithm_id,
buffer_for_tbs_hash,
&tbs_hash,
me->protected_headers,
signed_payload);
if(return_value) {
goto Done;
}
/* Compute the signature using public key crypto. The key selector
and algorithm ID are passed in to know how and what to sign
with. The hash of the TBS bytes are what is signed. A buffer in
which to place the signature is passed in and the signature is
returned.
Short-circuit signing is invoked if requested. It does
no public key operation and requires no key. It is
just a test mode. */
if(me->short_circuit_sign) {
return_value = short_circuit_sign(me->cose_algorithm_id,
tbs_hash,
buffer_for_signature,
&signature);
} else {
return_value = t_cose_crypto_pub_key_sign(me->cose_algorithm_id,
me->key_select,
tbs_hash,
buffer_for_signature,
&signature);
}
if(return_value) {
goto Done;
}
/* Add signature to CBOR and close out the array */
QCBOREncode_AddBytes(me->cbor_encode_ctx, signature);
QCBOREncode_CloseArray(me->cbor_encode_ctx);
/* CBOR encoding errors are tracked in the CBOR encoding
context and handled in the layer above this */
Done:
return return_value;
}

View File

@ -0,0 +1,162 @@
//
// t_cose_util.c
/*
* t_cose_common.h
*
* Copyright 2019, Laurence Lundblade
*
* SPDX-License-Identifier: BSD-3-Clause
*
* See BSD-3-Clause license in README.mdE.
*/
#include "t_cose_util.h"
#include "qcbor.h"
#include "t_cose_defines.h"
#include "t_cose_common.h"
#include "t_cose_crypto.h"
/*
T H E C O M M E N T S
in this file are truthful, but not expansive,
complete of formatted yet...
*/
/*
Returns COSE ID of hash algorithm for a particular signature algorithm
for COSE-defined algorithm IDs.
returns INT32_MAX when sig alg is not known
*/
int32_t hash_alg_id_from_sig_alg_id(int32_t cose_sig_alg_id)
{
/* if other hashes, particularly those that output bigger hashes
are added here, various other parts of this code have
to be changed to have larger buffers.
*/
switch(cose_sig_alg_id) {
case COSE_ALG_ES256:
return COSE_ALG_SHA256_PROPRIETARY;
default:
return INT32_MAX;
}
}
/*
Format of TBS bytes
Sig_structure = [
context : "Signature" / "Signature1" / "CounterSignature",
body_protected : empty_or_serialized_map,
? sign_protected : empty_or_serialized_map,
external_aad : bstr,
payload : bstr
]
*/
/*
This is the size of the first part of the CBOR encoded
TBS bytes. It is around 20 bytes. See create_tbs_hash()
*/
#define T_COSE_SIZE_OF_TBS \
1 + /* For opening the array */ \
sizeof(COSE_SIG_CONTEXT_STRING_SIGNATURE1) + /* "Signature1" */ \
2 + /* Overhead for encoding string */ \
T_COSE_SIGN1_MAX_PROT_HEADER + /* entire protected headers */ \
3 * ( /* 3 NULL bstrs for fields not used */ \
1 /* size of a NULL bstr */ \
)
/*
Creates the hash of the to-be-signed (TBS) bytes for COSE
Possible errors:
- Something gone wrong with hashing
- Protected headers too big
*/
enum t_cose_err_t create_tbs_hash(int32_t cose_alg_id,
struct useful_buf buffer_for_hash,
struct useful_buf_c *hash,
struct useful_buf_c protected_headers,
struct useful_buf_c payload)
{
/* approximate stack use on 32-bit machine:
local use: 320
with calls: 360
*/
enum t_cose_err_t return_value;
QCBOREncodeContext cbor_encode_ctx;
UsefulBuf_MAKE_STACK_UB( buffer_for_TBS_first_part, T_COSE_SIZE_OF_TBS);
struct useful_buf_c tbs_first_part;
QCBORError qcbor_result;
struct t_cose_crypto_hash hash_ctx;
int32_t hash_alg_id;
/* This builds the CBOR-format to-be-signed bytes */
QCBOREncode_Init(&cbor_encode_ctx, buffer_for_TBS_first_part);
QCBOREncode_OpenArray(&cbor_encode_ctx);
/* context */
QCBOREncode_AddSZString(&cbor_encode_ctx,
COSE_SIG_CONTEXT_STRING_SIGNATURE1);
/* body_protected */
QCBOREncode_AddBytes(&cbor_encode_ctx,
protected_headers);
/* sign_protected */
QCBOREncode_AddBytes(&cbor_encode_ctx, NULL_USEFUL_BUF_C);
/* external_aad */
QCBOREncode_AddBytes(&cbor_encode_ctx, NULL_USEFUL_BUF_C);
/* fake payload */
QCBOREncode_AddBytes(&cbor_encode_ctx, NULL_USEFUL_BUF_C);
QCBOREncode_CloseArray(&cbor_encode_ctx);
/* get the result and convert it to struct useful_buf_c representation */
qcbor_result = QCBOREncode_Finish(&cbor_encode_ctx, &tbs_first_part);
if(qcbor_result) {
/* Mainly means that the protected_headers were too big
(which should never happen) */
return_value = T_COSE_ERR_PROTECTED_HEADERS;
goto Done;
}
/* Start the hashing */
hash_alg_id = hash_alg_id_from_sig_alg_id(cose_alg_id);
return_value = t_cose_crypto_hash_start(&hash_ctx, hash_alg_id);
if(return_value) {
goto Done;
}
/* Hash the first part of the TBS. Take all but the last two
bytes. The last two bytes are the fake payload from above. It is
replaced by the real payload which is hashed next. The fake
payload is needed so the array count is right. This is one of the
main things that make it possible to implement with one buffer
for the whole cose sign1.
*/
t_cose_crypto_hash_update(&hash_ctx,
useful_buf_head(tbs_first_part,
tbs_first_part.len - 2));
/* Hash the payload */
t_cose_crypto_hash_update(&hash_ctx, payload);
/* Finish the hash and set up to return it */
return_value = t_cose_crypto_hash_finish(&hash_ctx,
buffer_for_hash,
hash);
Done:
return return_value;
}

View File

@ -0,0 +1,29 @@
//
// t_cose_util.h
/*
* t_cose_common.h
*
* Copyright 2019, Laurence Lundblade
*
* SPDX-License-Identifier: BSD-3-Clause
*
* See BSD-3-Clause license in README.mdE.
*/
#ifndef t_cose_util_h
#define t_cose_util_h
#include <stdint.h>
#include "useful_buf.h"
#include "t_cose_common.h"
int32_t hash_alg_id_from_sig_alg_id(int32_t cose_sig_alg_id);
enum t_cose_err_t create_tbs_hash(int32_t cose_alg_id,
struct useful_buf buffer_for_hash,
struct useful_buf_c *hash,
struct useful_buf_c protected_headers,
struct useful_buf_c payload);
#endif /* t_cose_util_h */

View File

@ -0,0 +1,75 @@
/*
* Copyright (c) 2019, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#ifndef __TFM_ATTEST_HAL_H__
#define __TFM_ATTEST_HAL_H__
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* \brief Security lifecycle of the device
*/
enum tfm_security_lifecycle_t {
TFM_SLC_UNKNOWN = 0x0000u,
TFM_SLC_ASSEMBLY_AND_TEST = 0x1000u,
TFM_SLC_PSA_ROT_PROVISIONING = 0x2000u,
TFM_SLC_SECURED = 0x3000u,
TFM_SLC_NON_PSA_ROT_DEBUG = 0x4000u,
TFM_SLC_RECOVERABLE_PSA_ROT_DEBUG = 0x5000u,
TFM_SLC_DECOMMISSIONED = 0x6000u,
};
/**
* \brief Retrieve the security lifecycle of the device
*
* Security lifecycle is a mandatory claim in the initial attestation token.
*
* \return According to \ref tfm_security_lifecycle_t
*/
enum tfm_security_lifecycle_t tfm_attest_hal_get_security_lifecycle(void);
/**
* \brief Retrieve the verification service indicator for initial attestation.
*
* It is used by relying party to locate a validation service for the token.
* It can be a text string that can be used to locate the service or can be a
* URL specifying the address of the service.
*
* \param[out] size Length of the string, without the termination zero byte.
*
* \return NULL pointer if not available otherwise the address of the
* verification service string in the device memory.
*/
const char *
tfm_attest_hal_get_verification_service(uint32_t *size);
/**
* \brief Retrieve the name of the profile definition document for initial
* attestation.
*
* This document describes the 'profile' of the initial attestation token,
* being a full description of the claims, their usage, verification and
* token signing.
*
* \param[out] size Length of the document name, without the termination zero
* byte.
*
* \return NULL pointer if not available otherwise the address of the document
* name string in the device memory.
*/
const char *
tfm_attest_hal_get_profile_definition(uint32_t *size);
#ifdef __cplusplus
}
#endif
#endif /* __TFM_ATTEST_HAL_H__ */

View File

@ -0,0 +1,207 @@
/*
* Copyright (c) 2018-2019, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#ifndef __TFM_BOOT_STATUS_H__
#define __TFM_BOOT_STATUS_H__
#include <stdint.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/* Major numbers (4 bit) to identify
* the consumer of shared data in runtime SW
*/
#define TLV_MAJOR_CORE 0x0
#define TLV_MAJOR_IAS 0x1
/**
* The shared data between boot loader and runtime SW is TLV encoded. The
* shared data is stored in a well known location in secure memory and this is
* a contract between boot loader and runtime SW.
*
* The structure of shared data must be the following:
* - At the beginning there must be a header: struct shared_data_tlv_header
* This contains a magic number and a size field which covers the entire
* size of the shared data area including this header.
* - After the header there come the entries which are composed from an entry
* header structure: struct shared_data_tlv_entry and the data. In the entry
* header is a type field (tly_type) which identify the consumer of the
* entry in the runtime SW and specify the subtype of that data item. There
* is a size field (tlv_len) which covers the size of the entry header and
* the data. After this structure comes the actual data.
* - Arbitrary number and size of data entry can be in the shared memory area.
*
* This table gives of overview about the tlv_type field in the entry header.
* The tlv_type always composed from a major and minor number. Major number
* identifies the addressee in runtime SW, who should process the data entry.
* Minor number used to encode more info about the data entry. The actual
* definition of minor number could change per major number. In case of boot
* status data, which is going to be processed by initial attestation service
* the minor number is split further to two part: sw_module and claim. The
* sw_module identifies the SW component in the system which the data item
* belongs to and the claim part identifies the exact type of the data.
*
* |---------------------------------------|
* | tlv_type (16) |
* |---------------------------------------|
* | tlv_major(4)| tlv_minor(12) |
* |---------------------------------------|
* | MAJOR_IAS | sw_module(6) | claim(6) |
* |---------------------------------------|
* | MAJOR_CORE | TBD |
* |---------------------------------------|
*/
/* Initial attestation: SW components / SW modules
* This list is intended to be adjusted per device. It contains more SW
* components than currently available in TF-M project. It serves as an example,
* what kind of SW components might be available.
*/
#define SW_GENERAL 0x00
#define SW_BL2 0x01
#define SW_PROT 0x02
#define SW_AROT 0x03
#define SW_SPE 0x04
#define SW_NSPE 0x05
#define SW_S_NS 0x06
#define SW_MAX 0x07
/* Initial attestation: Claim per SW components / SW modules */
/* Bits: 0-2 */
#define SW_VERSION 0x00
#define SW_SIGNER_ID 0x01
#define SW_EPOCH 0x02
#define SW_TYPE 0x03
/* Bits: 3-5 */
#define SW_MEASURE_VALUE 0x08
#define SW_MEASURE_TYPE 0x09
/* Initial attestation: General claim does not belong any particular SW
* component. But they might be part of the boot status.
*/
#define BOOT_SEED 0x00
#define HW_VERSION 0x01
#define SECURITY_LIFECYCLE 0x02
/* Minor numbers (12 bit) to identify attestation service related data */
#define TLV_MINOR_IAS_BOOT_SEED ((SW_GENERAL << 6) | BOOT_SEED)
#define TLV_MINOR_IAS_HW_VERSION ((SW_GENERAL << 6) | HW_VERSION)
#define TLV_MINOR_IAS_SLC ((SW_GENERAL << 6) | SECURITY_LIFECYCLE)
/* Bootloader - It can be more stage */
#define TLV_MINOR_IAS_BL2_MEASURE_VALUE ((SW_BL2 << 6) | SW_MEASURE_VALUE)
#define TLV_MINOR_IAS_BL2_MEASURE_TYPE ((SW_BL2 << 6) | SW_MEASURE_TYPE)
#define TLV_MINOR_IAS_BL2_VERSION ((SW_BL2 << 6) | SW_VERSION)
#define TLV_MINOR_IAS_BL2_SIGNER_ID ((SW_BL2 << 6) | SW_SIGNER_ID)
#define TLV_MINOR_IAS_BL2_EPOCH ((SW_BL2 << 6) | SW_EPOCH)
#define TLV_MINOR_IAS_BL2_TYPE ((SW_BL2 << 6) | SW_TYPE)
/* PROT: PSA Root of Trust */
#define TLV_MINOR_IAS_PROT_MEASURE_VALUE ((SW_PROT << 6) | SW_MEASURE_VALUE)
#define TLV_MINOR_IAS_PROT_MEASURE_TYPE ((SW_PROT << 6) | SW_MEASURE_TYPE)
#define TLV_MINOR_IAS_PROT_VERSION ((SW_PROT << 6) | SW_VERSION)
#define TLV_MINOR_IAS_PROT_SIGNER_ID ((SW_PROT << 6) | SW_SIGNER_ID)
#define TLV_MINOR_IAS_PROT_EPOCH ((SW_PROT << 6) | SW_EPOCH)
#define TLV_MINOR_IAS_PROT_TYPE ((SW_PROT << 6) | SW_TYPE)
/* AROT: Application Root of Trust */
#define TLV_MINOR_IAS_AROT_MEASURE_VALUE ((SW_AROT << 6) | SW_MEASURE_VALUE)
#define TLV_MINOR_IAS_AROT_MEASURE_TYPE ((SW_AROT << 6) | SW_MEASURE_TYPE)
#define TLV_MINOR_IAS_AROT_VERSION ((SW_AROT << 6) | SW_VERSION)
#define TLV_MINOR_IAS_AROT_SIGNER_ID ((SW_AROT << 6) | SW_SIGNER_ID)
#define TLV_MINOR_IAS_AROT_EPOCH ((SW_AROT << 6) | SW_EPOCH)
#define TLV_MINOR_IAS_AROT_TYPE ((SW_AROT << 6) | SW_TYPE)
/* Non-secure processing environment - single non-secure image */
#define TLV_MINOR_IAS_NSPE_MEASURE_VALUE ((SW_NSPE << 6) | SW_MEASURE_VALUE)
#define TLV_MINOR_IAS_NSPE_MEASURE_TYPE ((SW_NSPE << 6) | SW_MEASURE_TYPE)
#define TLV_MINOR_IAS_NSPE_VERSION ((SW_NSPE << 6) | SW_VERSION)
#define TLV_MINOR_IAS_NSPE_SIGNER_ID ((SW_NSPE << 6) | SW_SIGNER_ID)
#define TLV_MINOR_IAS_NSPE_EPOCH ((SW_NSPE << 6) | SW_EPOCH)
#define TLV_MINOR_IAS_NSPE_TYPE ((SW_NSPE << 6) | SW_TYPE)
/* Secure processing environment (ARoT + PRoT) - single secure image */
#define TLV_MINOR_IAS_SPE_MEASURE_VALUE ((SW_SPE << 6) | SW_MEASURE_VALUE)
#define TLV_MINOR_IAS_SPE_MEASURE_TYPE ((SW_SPE << 6) | SW_MEASURE_TYPE)
#define TLV_MINOR_IAS_SPE_VERSION ((SW_SPE << 6) | SW_VERSION)
#define TLV_MINOR_IAS_SPE_SIGNER_ID ((SW_SPE << 6) | SW_SIGNER_ID)
#define TLV_MINOR_IAS_SPE_EPOCH ((SW_SPE << 6) | SW_EPOCH)
#define TLV_MINOR_IAS_SPE_TYPE ((SW_SPE << 6) | SW_TYPE)
/* SPE + NSPE - combined secure and non-secure image */
#define TLV_MINOR_IAS_S_NS_MEASURE_VALUE ((SW_S_NS << 6) | SW_MEASURE_VALUE)
#define TLV_MINOR_IAS_S_NS_MEASURE_TYPE ((SW_S_NS << 6) | SW_MEASURE_TYPE)
#define TLV_MINOR_IAS_S_NS_VERSION ((SW_S_NS << 6) | SW_VERSION)
#define TLV_MINOR_IAS_S_NS_SIGNER_ID ((SW_S_NS << 6) | SW_SIGNER_ID)
#define TLV_MINOR_IAS_S_NS_EPOCH ((SW_S_NS << 6) | SW_EPOCH)
#define TLV_MINOR_IAS_S_NS_TYPE ((SW_S_NS << 6) | SW_TYPE)
/* General macros to handle TLV type */
#define MAJOR_MASK 0xF /* 4 bit */
#define MAJOR_POS 12 /* 12 bit */
#define MINOR_MASK 0xFFF /* 12 bit */
#define SET_TLV_TYPE(major, minor) \
((((major) & MAJOR_MASK) << MAJOR_POS) | ((minor) & MINOR_MASK))
#define GET_MAJOR(tlv_type) ((tlv_type) >> MAJOR_POS)
#define GET_MINOR(tlv_type) ((tlv_type) & MINOR_MASK)
/* Initial attestation specific macros */
#define MODULE_POS 6 /* 6 bit */
#define CLAIM_MASK 0x3F /* 6 bit */
#define MEASUREMENT_CLAIM_POS 3 /* 3 bit */
#define GET_IAS_MODULE(tlv_type) (GET_MINOR(tlv_type) >> MODULE_POS)
#define GET_IAS_CLAIM(tlv_type) (GET_MINOR(tlv_type) & CLAIM_MASK)
#define SET_IAS_MINOR(sw_module, claim) (((sw_module) << 6) | (claim))
#define GET_IAS_MEASUREMENT_CLAIM(ias_claim) ((ias_claim) >> \
MEASUREMENT_CLAIM_POS)
/* Magic value which marks the beginning of shared data area in memory */
#define SHARED_DATA_TLV_INFO_MAGIC 0x2016
/**
* Shared data TLV header. All fields in little endian.
*
* -----------------------------------
* | tlv_magic(16) | tlv_tot_len(16) |
* -----------------------------------
*/
struct shared_data_tlv_header {
uint16_t tlv_magic;
uint16_t tlv_tot_len; /* size of whole TLV area (including this header) */
};
#define SHARED_DATA_HEADER_SIZE sizeof(struct shared_data_tlv_header)
/**
* Shared data TLV entry header format. All fields in little endian.
*
* -------------------------------
* | tlv_type(16) | tlv_len(16) |
* -------------------------------
* | Raw data |
* -------------------------------
*/
struct shared_data_tlv_entry {
uint16_t tlv_type;
uint16_t tlv_len; /* size of single TLV entry (including this header). */
};
#define SHARED_DATA_ENTRY_HEADER_SIZE sizeof(struct shared_data_tlv_entry)
#define SHARED_DATA_ENTRY_SIZE(size) (size + SHARED_DATA_ENTRY_HEADER_SIZE)
#ifdef __cplusplus
}
#endif
#endif /* __TFM_BOOT_STATUS_H__ */

View File

@ -0,0 +1,56 @@
/*
* Copyright (c) 2018, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#ifndef __TFM_PLAT_BOOT_SEED_H__
#define __TFM_PLAT_BOOT_SEED_H__
/**
* \file tfm_plat_boot_seed.h
*
* Boot seed is used by a validating entity to ensure multiple reports were
* generated in the same boot session. Boot seed is a random number, generated
* only once during a boot cycle and its value is constant in the same cycle.
* Size recommendation is 256-bit to meet the statistically improbable property.
* Boot seed can be generated by secure boot loader an included to the measured
* boot state or can be generated by PRoT SW.
*/
/**
* \note The interfaces defined in this file must be implemented for each
* SoC.
*/
#include <stdint.h>
#include "tfm_plat_defs.h"
#ifdef __cplusplus
extern "C" {
#endif
/*!
* \def BOOT_SEED_SIZE
*
* \brief Size of boot seed in bytes.
*/
#define BOOT_SEED_SIZE (32u)
/**
* \brief Gets the boot seed, which is a constant random number during a boot
* cycle.
*
* \param[in] size The required size of boot seed in bytes
* \param[out] buf Pointer to the buffer to store boot seed
*
* \return TFM_PLAT_ERR_SUCCESS if the value is generated correctly. Otherwise,
* it returns TFM_PLAT_ERR_SYSTEM_ERR.
*/
enum tfm_plat_err_t tfm_plat_get_boot_seed(uint32_t size, uint8_t *buf);
#ifdef __cplusplus
}
#endif
#endif /* __TFM_PLAT_BOOT_SEED_H__ */

View File

@ -0,0 +1,113 @@
/*
* Copyright (c) 2017-2019, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#ifndef __TFM_PLAT_CRYPTO_KEYS_H__
#define __TFM_PLAT_CRYPTO_KEYS_H__
/**
* \note The interfaces defined in this file must be implemented for each
* SoC.
*/
#include <stdint.h>
#include "tfm_plat_defs.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* Elliptic curve key type identifiers according to RFC8152 (COSE encoding)
* https://www.iana.org/assignments/cose/cose.xhtml#elliptic-curves
*/
enum ecc_curve_t {
P_256 = 1, /* NIST P-256 also known as secp256r1 */
P_384 = 2, /* NIST P-384 also known as secp384r1 */
P_521 = 3, /* NIST P-521 also known as secp521r1 */
X25519 = 4, /* X25519 for use with ECDH only */
X448 = 5, /* X448 for use with ECDH only */
ED25519 = 6, /* Ed25519 for use with EdDSA only */
ED448 = 7, /* Ed448 for use with EdDSA only */
};
/**
* Structure definition to carry pointer and size information about an Elliptic
* curve key which is stored in a buffer(key_buf) in raw format (without
* encoding):
* - priv_key Base address of the private key in key_buf. It must be
* present on the device.
* - priv_key_size Size of the private key in bytes.
* - pubx_key Base address of x-coordinate of the public key in key_buf.
* It can be empty, because it can be recomputed based on
* private key.
* - pubx_key_size Length of x-coordinate of the public key in key_buf.
* It can be empty, because it can be recomputed based on
* private key.
* - puby_key Base address of y-coordinate of the public key in key_buf.
* It can be empty, because either it can be recomputed based
* on private key or some curve type works without it.
* - puby_key_size Length of y-coordinate of the public key in key_buf.
*/
struct ecc_key_t {
uint8_t *priv_key;
uint32_t priv_key_size;
uint8_t *pubx_key;
uint32_t pubx_key_size;
uint8_t *puby_key;
uint32_t puby_key_size;
};
#define ECC_P_256_KEY_SIZE (96u) /* 3 x 32 = 96 bytes priv + pub-x + pub-y */
/**
* \brief Gets hardware unique key for encryption
*
* \param[out] key Buf to store the key in
* \param[in] size Size of the buffer
*
* \return Returns error code specified in \ref tfm_plat_err_t
*/
enum tfm_plat_err_t tfm_plat_get_crypto_huk(uint8_t *key, uint32_t size);
/**
* \brief Get the initial attestation key
*
* The device MUST contain an initial attestation key, which is used to sign the
* token. Initial attestation service supports elliptic curve signing
* algorithms. Device maker can decide whether store only the private key on the
* device or store both (public and private) key. Public key can be recomputed
* based on private key. Keys must be provided in raw format, just binary data
* without any encoding (DER, COSE). Caller provides a buffer to copy all the
* available key components to there. Key components must be copied after
* each other to the buffer. The base address and the length of each key
* component must be indicating in the corresponding field of ecc_key
* (\ref struct ecc_key_t).
* Curve_type indicates to which curve belongs the key.
*
*
* Keys must be provided in
*
* \param[in/out] key_buf Buffer to store the initial attestation key.
* \param[in] size Size of the buffer.
* \param[out] ecc_key A structure to carry pointer and size information
* about the initial attestation key, which is
* stored in key_buf.
* \param[out] curve_type The type of the EC curve, which the key belongs
* to according to \ref ecc_curve_t
*
* \return Returns error code specified in \ref tfm_plat_err_t
*/
enum tfm_plat_err_t
tfm_plat_get_initial_attest_key(uint8_t *key_buf,
uint32_t size,
struct ecc_key_t *ecc_key,
enum ecc_curve_t *curve_type);
#ifdef __cplusplus
}
#endif
#endif /* __TFM_PLAT_CRYPTO_KEYS_H__ */

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) 2017-2018, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#ifndef __TFM_PLAT_DEFS_H__
#define __TFM_PLAT_DEFS_H__
/**
* \note The interfaces defined in this file must be implemented for each
* target.
*/
#include <stdint.h>
#include <limits.h>
enum tfm_plat_err_t {
TFM_PLAT_ERR_SUCCESS = 0,
TFM_PLAT_ERR_SYSTEM_ERR,
TFM_PLAT_ERR_MAX_VALUE,
/* Following entry is only to ensure the error code of int size */
TFM_PLAT_ERR_FORCE_INT_SIZE = INT_MAX
};
/*!
* \def TFM_LINK_SET_OBJECT_IN_PARTITION_SECTION(TFM_PARTITION_NAME)
*
* \brief This macro provides a mechanism to place a function code in a specific
* secure partition at linker time in TF-M Level 3.
*
* \param[in] TFM_PARTITION_NAME TF-M partition name assigned in the manifest
* file "tfm_partition_name" field.
*/
#define TFM_LINK_SET_OBJECT_IN_PARTITION_SECTION(TFM_PARTITION_NAME) \
__attribute__((section(TFM_PARTITION_NAME"_ATTR_FN")))
#endif /* __TFM_PLAT_DEFS_H__ */

View File

@ -0,0 +1,117 @@
/*
* Copyright (c) 2018-2019, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#ifndef __TFM_PLAT_DEVICE_ID_H__
#define __TFM_PLAT_DEVICE_ID_H__
/**
* \file tfm_plat_device_id.h
*
* The interfaces defined in this file are meant to provide the following
* attributes of the device:
* - Instance ID: Unique identifier of the device.
* - Implementation ID: Original implementation signer of the attestation key.
* - Hardware version: Identify the GDSII that went to fabrication.
*/
/**
* \note The interfaces defined in this file must be implemented for each
* SoC.
*/
#include <stdint.h>
#include "tfm_plat_defs.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* \def INSTANCE_ID_MAX_SIZE
*
* \brief Maximum size of instance ID in bytes
*/
#define INSTANCE_ID_MAX_SIZE (33u)
/**
* \def IMPLEMENTATION_ID_MAX_SIZE
*
* \brief Maximum size of implementation ID in bytes
*/
#define IMPLEMENTATION_ID_MAX_SIZE (32u)
/**
* \def HW_VERSION_MAX_SIZE
*
* \brief Maximum size of hardware version in bytes
*
* Recommended to use the European Article Number format: EAN-13+5
*/
#define HW_VERSION_MAX_SIZE (18u)
/**
* \brief Get the UEID of the device.
*
* This mandatory claim represents the unique identifier of the instance.
* In the PSA definition is a hash of the public attestation key of the
* instance. The claim will be represented by the EAT standard claim UEID
* of type GUID. The EAT definition of a GUID type is that it will be between
* 128 & 256 bits but this implementation will use the full 256 bits to
* accommodate a hash result.
*
* \param[in/out] size As an input value it indicates the size of the caller
* allocated buffer (in bytes) to store the UEID. At return
* its value is updated with the exact size of the UEID.
* \param[out] buf Pointer to the buffer to store the UEID
*
* \return Returns error code specified in \ref tfm_plat_err_t
*/
enum tfm_plat_err_t tfm_plat_get_instance_id(uint32_t *size, uint8_t *buf);
/**
* \brief Get the Implementation ID of the device.
*
* This mandatory claim represents the original implementation signer of the
* attestation key and identifies the contract between the report and
* verification. A verification service will use this claim to locate the
* details of the verification process. The claim will be represented by a
* custom EAT claim with a value consisting of a CBOR byte string. The size of
* this string will normally be 32 bytes to accommodate a 256 bit hash.
*
* \param[in/out] size As an input value it indicates the size of the caller
* allocated buffer (in bytes) to store the implementation
* ID. At return its value is updated with the exact size
* of the implementation ID.
* \param[out] buf Pointer to the buffer to store the implementation ID
*
* \return Returns error code specified in \ref tfm_plat_err_t
*/
enum tfm_plat_err_t tfm_plat_get_implementation_id(uint32_t *size,
uint8_t *buf);
/**
* \brief Get the hardware version of the device.
*
* This optional claim provides metadata linking the token to the GDSII that
* went to fabrication for this instance. It is represented as CBOR text string.
* It is recommended to use for identification the format of the European
* Article Number: EAN-13+5.
*
* \param[in/out] size As an input value it indicates the size of the caller
* allocated buffer (in bytes) to store the HW version. At
* return its value is updated with the exact size of the
* HW version.
* \param[out] buf Pointer to the buffer to store the HW version
*
* \return Returns error code specified in \ref tfm_plat_err_t
*/
enum tfm_plat_err_t tfm_plat_get_hw_version(uint32_t *size, uint8_t *buf);
#ifdef __cplusplus
}
#endif
#endif /* __TFM_PLAT_DEVICE_ID_H__ */

View File

@ -0,0 +1,151 @@
/*
* useful_buf.h
*
* Copyright 2019, Laurence Lundblade
*
* SPDX-License-Identifier: BSD-3-Clause
*
* See BSD-3-Clause license in README.mdE.
*/
#ifndef __USEFUL_BUF_H__
#define __USEFUL_BUF_H__
#include "UsefulBuf.h"
/*
T H E C O M M E N T S
in this file are truthful, but not expansive,
complete of formatted yet...
*/
/**
\file TF-M coding style version of UsefulBuf
*/
#define NULL_USEFUL_BUF_C NULLUsefulBufC
#define NULL_USEFUL_BUF NULLUsefulBuf
/* See UsefulBuf.h */
static inline int useful_buf_c_is_null(struct useful_buf_c in)
{
return UsefulBuf_IsNULLC(in);
}
static inline int useful_buf_is_null(struct useful_buf in)
{
return UsefulBuf_IsNULL(in);
}
static inline int useful_buf_c_is_empty(struct useful_buf_c in)
{
return UsefulBuf_IsEmptyC(in);
}
static inline int useful_buf_is_empty(struct useful_buf in)
{
return UsefulBuf_IsEmpty(in);
}
static inline int useful_buf_is_null_or_empty(struct useful_buf in)
{
return UsefulBuf_IsNULLOrEmpty(in);
}
static inline int useful_buf_c_is_null_or_empty(struct useful_buf_c in)
{
return UsefulBuf_IsNULLOrEmptyC(in);
}
static inline struct useful_buf useful_buf_unconst(struct useful_buf_c in)
{
return UsefulBuf_Unconst(in);
}
#define USEFUL_BUF_FROM_SZ_LITERAL UsefulBuf_FROM_SZ_LITERAL
#define USEFUL_BUF_FROM_BYTE_ARRAY_LITERAL UsefulBuf_FROM_BYTE_ARRAY_LITERAL
#define USEFUL_BUF_MAKE_STACK_UB UsefulBuf_MAKE_STACK_UB
#define USEFUL_BUF_FROM_BYTE_ARRAY UsefulBuf_FROM_BYTE_ARRAY
static inline struct useful_buf_c useful_buf_from_sz(const char *string)
{
return UsefulBuf_FromSZ(string);
}
static inline struct
useful_buf_c useful_buf_copy_offset(struct useful_buf dest,
size_t offset,
struct useful_buf_c src)
{
return UsefulBuf_CopyOffset(dest, offset, src);
}
static inline struct useful_buf_c useful_buf_copy(struct useful_buf dest,
struct useful_buf_c src)
{
return UsefulBuf_Copy(dest, src);
}
static inline struct useful_buf_c useful_buf_set(struct useful_buf dest,
uint8_t value)
{
return UsefulBuf_Set(dest, value);
}
static inline struct useful_buf_c useful_buf_copy_ptr(struct useful_buf dest,
const void *ptr,
size_t len)
{
return UsefulBuf_CopyPtr(dest, ptr, len);
}
static inline struct useful_buf_c useful_buf_head(struct useful_buf_c buf,
size_t amount)
{
return UsefulBuf_Head(buf, amount);
}
static inline struct useful_buf_c useful_buf_tail(struct useful_buf_c buf,
size_t amount)
{
return UsefulBuf_Tail(buf, amount);
}
static inline int useful_buf_compare(const struct useful_buf_c buf1,
const struct useful_buf_c buf2)
{
return UsefulBuf_Compare(buf1, buf2);
}
static inline size_t
useful_buf_find_bytes(const struct useful_buf_c bytes_to_search,
const struct useful_buf_c bytes_to_find)
{
return UsefulBuf_FindBytes(bytes_to_search, bytes_to_find);
}
#endif /* __USEFUL_BUF_H__ */

View File

@ -0,0 +1,55 @@
/*
* Copyright (c) 2018-2019, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#include "psa_attest_inject_key.h"
#include "crypto.h"
#include "psa/client.h"
#include "psa_attest_srv_ifs.h"
#define MINOR_VER 1
psa_status_t
psa_attestation_inject_key(const uint8_t *key_data,
size_t key_data_length,
psa_key_type_t type,
uint8_t *public_key_data,
size_t public_key_data_size,
size_t *public_key_data_length)
{
psa_handle_t handle = PSA_NULL_HANDLE;
psa_status_t call_error = PSA_SUCCESS;
psa_invec in_vec[2];
psa_outvec out_vec[2];
in_vec[0] = (psa_invec) {
&type,
sizeof(psa_key_type_t)
};
in_vec[1] = (psa_invec) {
key_data, key_data_length
};
out_vec[0] = (psa_outvec) {
public_key_data, public_key_data_size
};
out_vec[1] = (psa_outvec) {
public_key_data_length, sizeof(*public_key_data_length)
};
handle = psa_connect(PSA_ATTEST_INJECT_KEY_ID, MINOR_VER);
if (handle <= 0) {
return (PSA_ERROR_COMMUNICATION_FAILURE);
}
call_error = psa_call(handle, in_vec, 2, out_vec, 2);
psa_close(handle);
if (call_error < 0) {
call_error = PSA_ERROR_COMMUNICATION_FAILURE;
}
return call_error;
}

View File

@ -0,0 +1,72 @@
/*
* Copyright (c) 2018-2019, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#include "psa_initial_attestation_api.h"
#include "crypto.h"
#include "psa/client.h"
#include "attestation.h"
#include <string.h>
#include "psa_attest_srv_ifs.h"
#define MINOR_VER 1
enum psa_attest_err_t
psa_initial_attest_get_token(const uint8_t *challenge_obj,
uint32_t challenge_size,
uint8_t *token,
uint32_t *token_size) {
psa_status_t err_call;
psa_handle_t handle = PSA_NULL_HANDLE;
psa_invec in_vec[1] = { { challenge_obj, challenge_size } };
psa_outvec out_vec[1] = { { token, *token_size } };
handle = psa_connect(PSA_ATTEST_GET_TOKEN_ID, MINOR_VER);
if (handle <= 0)
{
return (PSA_ATTEST_ERR_GENERAL);
}
err_call = psa_call(handle, in_vec, 1, out_vec, 1);
psa_close(handle);
if (err_call < 0)
{
err_call = PSA_ATTEST_ERR_GENERAL;
}
*token_size = out_vec[0].len;
return ((enum psa_attest_err_t) err_call);
}
enum psa_attest_err_t
psa_initial_attest_get_token_size(uint32_t challenge_size,
uint32_t *token_size) {
psa_status_t err_call;
psa_handle_t handle = PSA_NULL_HANDLE;
psa_invec in_vec[1] = { { &challenge_size, sizeof(uint32_t) } };
psa_outvec out_vec[1] = { { token_size, sizeof(uint32_t) } };
handle = psa_connect(PSA_ATTEST_GET_TOKEN_SIZE_ID, MINOR_VER);
if (handle <= 0)
{
return (PSA_ATTEST_ERR_GENERAL);
}
err_call = psa_call(handle, in_vec, 1, out_vec, 1);
psa_close(handle);
if (err_call < 0)
{
err_call = PSA_ATTEST_ERR_GENERAL;
}
return ((enum psa_attest_err_t) err_call);
}

View File

@ -0,0 +1,136 @@
/* Copyright (c) 2017-2019 ARM Limited
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/***********************************************************************************************************************
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* THIS FILE IS AN AUTO-GENERATED FILE - DO NOT MODIFY IT.
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* Template Version 1.0
* Generated by tools/spm/generate_partition_code.py Version 1.0
**********************************************************************************************************************/
#include "cmsis.h"
#include "mbed_toolchain.h" /* For using MBED_ALIGN macro */
#include "rtx_os.h"
#include "spm_panic.h"
#include "spm_internal.h"
#include "psa_attest_srv_partition.h"
#include "psa_attest_srv_ifs.h"
#include "psa_crypto_srv_ifs.h"
/* Threads stacks */
MBED_ALIGN(8) uint8_t attest_srv_thread_stack[8192] = {0};
/* Threads control blocks */
osRtxThread_t attest_srv_thread_cb = {0};
/* Thread attributes - for thread initialization */
osThreadAttr_t attest_srv_thread_attr = {
.name = "attest_srv",
.attr_bits = 0,
.cb_mem = &attest_srv_thread_cb,
.cb_size = sizeof(attest_srv_thread_cb),
.stack_mem = attest_srv_thread_stack,
.stack_size = 8192,
.priority = osPriorityNormal,
.tz_module = 0,
.reserved = 0
};
spm_rot_service_t attest_srv_rot_services[ATTEST_SRV_ROT_SRV_COUNT] = {
{
.sid = PSA_ATTEST_GET_TOKEN_ID,
.mask = PSA_ATTEST_GET_TOKEN,
.partition = NULL,
.min_version = 1,
.min_version_policy = PSA_MINOR_VERSION_POLICY_STRICT,
.allow_nspe = true,
.queue = {
.head = NULL,
.tail = NULL
}
},
{
.sid = PSA_ATTEST_GET_TOKEN_SIZE_ID,
.mask = PSA_ATTEST_GET_TOKEN_SIZE,
.partition = NULL,
.min_version = 1,
.min_version_policy = PSA_MINOR_VERSION_POLICY_STRICT,
.allow_nspe = true,
.queue = {
.head = NULL,
.tail = NULL
}
},
{
.sid = PSA_ATTEST_INJECT_KEY_ID,
.mask = PSA_ATTEST_INJECT_KEY,
.partition = NULL,
.min_version = 1,
.min_version_policy = PSA_MINOR_VERSION_POLICY_STRICT,
.allow_nspe = true,
.queue = {
.head = NULL,
.tail = NULL
}
},
};
/* External SIDs used by ATTEST_SRV */
const uint32_t attest_srv_external_sids[6] = {
PSA_CRYPTO_INIT_ID,
PSA_HASH_ID,
PSA_ASYMMETRIC_ID,
PSA_KEY_MNG_ID,
PSA_CRYPTO_FREE_ID,
PSA_GENERATOR_ID,
};
static osRtxMutex_t attest_srv_mutex = {0};
static const osMutexAttr_t attest_srv_mutex_attr = {
.name = "attest_srv_mutex",
.attr_bits = osMutexRecursive | osMutexPrioInherit | osMutexRobust,
.cb_mem = &attest_srv_mutex,
.cb_size = sizeof(attest_srv_mutex),
};
extern void attest_main(void *ptr);
void attest_srv_init(spm_partition_t *partition)
{
if (NULL == partition) {
SPM_PANIC("partition is NULL!\n");
}
partition->mutex = osMutexNew(&attest_srv_mutex_attr);
if (NULL == partition->mutex) {
SPM_PANIC("Failed to create mutex for secure partition attest_srv!\n");
}
for (uint32_t i = 0; i < ATTEST_SRV_ROT_SRV_COUNT; ++i) {
attest_srv_rot_services[i].partition = partition;
}
partition->rot_services = attest_srv_rot_services;
partition->thread_id = osThreadNew(attest_main, NULL, &attest_srv_thread_attr);
if (NULL == partition->thread_id) {
SPM_PANIC("Failed to create start main thread of partition attest_srv!\n");
}
}

View File

@ -0,0 +1,56 @@
/* Copyright (c) 2017-2019 ARM Limited
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/***********************************************************************************************************************
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* THIS FILE IS AN AUTO-GENERATED FILE - DO NOT MODIFY IT.
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* Template Version 1.0
* Generated by tools/spm/generate_partition_code.py Version 1.0
**********************************************************************************************************************/
#ifndef PSA_ATTEST_SRV_PARTITION_H
#define PSA_ATTEST_SRV_PARTITION_H
#define ATTEST_SRV_ID 37
#define ATTEST_SRV_ROT_SRV_COUNT (3UL)
#define ATTEST_SRV_EXT_ROT_SRV_COUNT (6UL)
/* ATTEST_SRV event flags */
#define ATTEST_SRV_RESERVED1_POS (1UL)
#define ATTEST_SRV_RESERVED1_MSK (1UL << ATTEST_SRV_RESERVED1_POS)
#define ATTEST_SRV_RESERVED2_POS (2UL)
#define ATTEST_SRV_RESERVED2_MSK (1UL << ATTEST_SRV_RESERVED2_POS)
#define PSA_ATTEST_GET_TOKEN_POS (4UL)
#define PSA_ATTEST_GET_TOKEN (1UL << PSA_ATTEST_GET_TOKEN_POS)
#define PSA_ATTEST_GET_TOKEN_SIZE_POS (5UL)
#define PSA_ATTEST_GET_TOKEN_SIZE (1UL << PSA_ATTEST_GET_TOKEN_SIZE_POS)
#define PSA_ATTEST_INJECT_KEY_POS (6UL)
#define PSA_ATTEST_INJECT_KEY (1UL << PSA_ATTEST_INJECT_KEY_POS)
#define ATTEST_SRV_WAIT_ANY_SID_MSK (\
PSA_ATTEST_GET_TOKEN | \
PSA_ATTEST_GET_TOKEN_SIZE | \
PSA_ATTEST_INJECT_KEY)
#endif // PSA_ATTEST_SRV_PARTITION_H

View File

@ -0,0 +1,230 @@
// ---------------------------------- Includes ---------------------------------
#include "psa/service.h"
#include "psa/client.h"
#define PSA_ATTEST_SECURE 1
#include "psa_attest_srv_partition.h"
#include "psa_initial_attestation_api.h"
#include "psa_attest_inject_key.h"
#include "psa_inject_attestation_key_impl.h"
#include "attestation.h"
#include <string.h>
#include "mbedtls/entropy.h"
#include "crypto.h"
#if defined(PLATFORM_C)
#include "mbedtls/platform.h"
#else
#define calloc calloc
#define free free
#endif
int32_t g_caller_id = 0;
static void set_caller_id(psa_msg_t msg)
{
g_caller_id = psa_identity(msg.handle);
}
// ------------------------- Partition's Main Thread ---------------------------
static void psa_attest_get_token(void)
{
psa_msg_t msg = { 0 };
enum psa_attest_err_t status = PSA_ATTEST_ERR_SUCCESS;
psa_get(PSA_ATTEST_GET_TOKEN, &msg);
switch (msg.type) {
case PSA_IPC_CONNECT:
case PSA_IPC_DISCONNECT: {
break;
}
case PSA_IPC_CALL: {
uint8_t *challenge_buff = NULL;
uint8_t *token_buff = NULL;
uint32_t bytes_read = 0;
challenge_buff = calloc(1, msg.in_size[0]);
if (challenge_buff == NULL) {
status = PSA_ATTEST_ERR_GENERAL;
break;
}
bytes_read = psa_read(msg.handle, 0,
challenge_buff, msg.in_size[0]);
if (bytes_read != msg.in_size[0]) {
free(challenge_buff);
SPM_PANIC("SPM read length mismatch");
}
token_buff = calloc(1, msg.out_size[0]);
if (token_buff == NULL) {
status = PSA_ATTEST_ERR_GENERAL;
break;
}
psa_invec in_vec[1] = { { challenge_buff, msg.in_size[0] } };
psa_outvec out_vec[1] = { { token_buff, msg.out_size[0] } };
status = attest_init();
if (status != PSA_ATTEST_ERR_SUCCESS) {
free(challenge_buff);
break;
}
set_caller_id(msg);
status = initial_attest_get_token(in_vec, 1, out_vec, 1);
if (status == PSA_ATTEST_ERR_SUCCESS) {
psa_write(msg.handle, 0, out_vec[0].base, out_vec[0].len);
}
free(challenge_buff);
break;
}
default: {
SPM_PANIC("Unexpected message type %d!", (int)(msg.type));
break;
}
}
psa_reply(msg.handle, status);
}
static void psa_attest_get_token_size(void)
{
psa_msg_t msg = { 0 };
enum psa_attest_err_t status = PSA_ATTEST_ERR_SUCCESS;
psa_get(PSA_ATTEST_GET_TOKEN_SIZE, &msg);
switch (msg.type) {
case PSA_IPC_CONNECT:
case PSA_IPC_DISCONNECT: {
break;
}
case PSA_IPC_CALL: {
uint32_t challenge_size;
uint32_t token_size;
uint32_t bytes_read = 0;
bytes_read = psa_read(msg.handle, 0,
&challenge_size, msg.in_size[0]);
if (bytes_read != msg.in_size[0]) {
SPM_PANIC("SPM read length mismatch");
}
psa_invec in_vec[1] = { { &challenge_size, msg.in_size[0] } };
psa_outvec out_vec[1] = { { &token_size, msg.out_size[0] } };
status = attest_init();
if (status != PSA_ATTEST_ERR_SUCCESS) {
break;
}
set_caller_id(msg);
status = initial_attest_get_token_size(in_vec, 1, out_vec, 1);
if (status == PSA_ATTEST_ERR_SUCCESS) {
psa_write(msg.handle, 0, out_vec[0].base, out_vec[0].len);
}
break;
}
default: {
SPM_PANIC("Unexpected message type %d!", (int)(msg.type));
break;
}
}
psa_reply(msg.handle, status);
}
static void psa_attest_inject_key(void)
{
psa_msg_t msg = { 0 };
psa_status_t status = PSA_SUCCESS;
psa_get(PSA_ATTEST_INJECT_KEY, &msg);
switch (msg.type) {
case PSA_IPC_CONNECT:
case PSA_IPC_DISCONNECT: {
break;
}
case PSA_IPC_CALL: {
uint8_t *public_key_data = NULL;
size_t public_key_data_length = 0;
uint8_t *key_data = NULL;
psa_key_type_t type;
uint32_t bytes_read = 0;
if (msg.in_size[0] != sizeof(psa_key_type_t)) {
status = PSA_ERROR_COMMUNICATION_FAILURE;
break;
}
bytes_read = psa_read(msg.handle, 0, &type, msg.in_size[0]);
if (bytes_read != msg.in_size[0]) {
SPM_PANIC("SPM read length mismatch");
}
public_key_data = calloc(1, msg.out_size[0]);
if (public_key_data == NULL) {
status = PSA_ERROR_INSUFFICIENT_MEMORY;
break;
}
if (msg.in_size[1] != 0) {
key_data = calloc(1, msg.in_size[1]);
if (key_data == NULL) {
status = PSA_ERROR_INSUFFICIENT_MEMORY;
free(public_key_data);
break;
}
bytes_read = psa_read(msg.handle, 1,
key_data, msg.in_size[1]);
if (bytes_read != msg.in_size[1]) {
SPM_PANIC("SPM read length mismatch");
}
}
status = psa_attestation_inject_key_impl(key_data,
msg.in_size[1],
type,
public_key_data,
msg.out_size[0],
&public_key_data_length);
if (status == PSA_SUCCESS) {
psa_write(msg.handle, 0, public_key_data, public_key_data_length);
}
psa_write(msg.handle, 1,
&public_key_data_length, sizeof(public_key_data_length));
free(public_key_data);
break;
}
default: {
SPM_PANIC("Unexpected message type %d!", (int)(msg.type));
break;
}
}
psa_reply(msg.handle, status);
}
void attest_main(void *ptr)
{
while (1) {
uint32_t signals = psa_wait_any(PSA_BLOCK);
if (signals & PSA_ATTEST_GET_TOKEN) {
psa_attest_get_token();
}
if (signals & PSA_ATTEST_GET_TOKEN_SIZE) {
psa_attest_get_token_size();
}
if (signals & PSA_ATTEST_INJECT_KEY) {
psa_attest_inject_key();
}
}
}

View File

@ -0,0 +1,108 @@
/*
* Copyright (c) 2018-2019, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#ifndef __ATTESTATION_H__
#define __ATTESTATION_H__
#include "psa_initial_attestation_api.h"
#include "tfm_client.h"
#ifdef __cplusplus
extern "C" {
#endif
/*!
* \brief Type of memory access
*/
enum attest_memory_access_t {
TFM_ATTEST_ACCESS_RO = 1,
TFM_ATTEST_ACCESS_RW = 2,
};
/*!
* \brief Copy the boot data (coming from boot loader) from shared memory area
* to service memory area
*
* \param[in] major_type Major type of TLV entries to copy
* \param[out] ptr Pointer to the buffer to store the boot data
* \parma[in] len Size of the buffer to store the boot data
*
* \return Returns error code as specified in \ref psa_attest_err_t
*/
enum psa_attest_err_t
attest_get_boot_data(uint8_t major_type, void *ptr, uint32_t len);
/*!
* \brief Get the ID of the caller thread.
*
* \param[out] caller_id Pointer where to store caller ID
*
* \return Returns error code as specified in \ref psa_attest_err_t
*/
enum psa_attest_err_t
attest_get_caller_client_id(int32_t *caller_id);
/*!
* \brief Verify memory access rights
*
* \param[in] addr Pointer to the base of the address range to check
* \param[in] size Size of the address range to check
* \param[in] access Type of memory access as specified in
* \ref attest_memory_access
*
* \return Returns error code as specified in \ref psa_attest_err_t
*/
enum psa_attest_err_t
attest_check_memory_access(void *addr,
uint32_t size,
enum attest_memory_access_t access);
/*!
* \brief Initialise the initial attestation service during the TF-M boot up
* process.
*
* \return Returns PSA_ATTEST_ERR_SUCCESS if init has been completed,
* otherwise error as specified in \ref psa_attest_err_t
*/
enum psa_attest_err_t attest_init(void);
/*!
* \brief Get initial attestation token
*
* \param[in] in_vec Pointer to in_vec array, which contains input data
* to attestation service
* \param[in] num_invec Number of elements in in_vec array
* \param[in/out] out_vec Pointer out_vec array, which contains output data
* to attestation service
* \param[in] num_outvec Number of elements in out_vec array
*
* \return Returns error code as specified in \ref psa_attest_err_t
*/
enum psa_attest_err_t
initial_attest_get_token(const psa_invec *in_vec, uint32_t num_invec,
psa_outvec *out_vec, uint32_t num_outvec);
/**
* \brief Get the size of the initial attestation token
*
* \param[in] in_vec Pointer to in_vec array, which contains input data
* to attestation service
* \param[in] num_invec Number of elements in in_vec array
* \param[out] out_vec Pointer to out_vec array, which contains pointer
* where to store the output data
* \param[in] num_outvec Number of elements in out_vec array
*
* \return Returns error code as specified in \ref psa_attest_err_t
*/
enum psa_attest_err_t
initial_attest_get_token_size(const psa_invec *in_vec, uint32_t num_invec,
psa_outvec *out_vec, uint32_t num_outvec);
#ifdef __cplusplus
}
#endif
#endif /* __ATTESTATION_H__ */

View File

@ -0,0 +1,46 @@
{
"name": "ATTEST_SRV",
"type": "APPLICATION-ROT",
"priority": "NORMAL",
"id": "0x00000025",
"entry_point": "attest_main",
"stack_size": "0x2000",
"heap_size": "0x2000",
"services": [
{
"name": "PSA_ATTEST_GET_TOKEN_ID",
"identifier": "0x00000F10",
"signal": "PSA_ATTEST_GET_TOKEN",
"non_secure_clients": true,
"minor_version": 1,
"minor_policy": "STRICT"
},
{
"name": "PSA_ATTEST_GET_TOKEN_SIZE_ID",
"identifier": "0x00000F11",
"signal": "PSA_ATTEST_GET_TOKEN_SIZE",
"non_secure_clients": true,
"minor_version": 1,
"minor_policy": "STRICT"
},
{
"name": "PSA_ATTEST_INJECT_KEY_ID",
"identifier": "0x00000F12",
"signal": "PSA_ATTEST_INJECT_KEY",
"non_secure_clients": true,
"minor_version": 1,
"minor_policy": "STRICT"
}
],
"extern_sids": [
"PSA_CRYPTO_INIT_ID",
"PSA_HASH_ID",
"PSA_ASYMMETRIC_ID",
"PSA_KEY_MNG_ID",
"PSA_CRYPTO_FREE_ID",
"PSA_GENERATOR_ID"
],
"source_files": [
"COMPONENT_SPE/psa_attestation_partition.c"
]
}

View File

@ -0,0 +1,77 @@
/*
* Copyright (c) 2018-2019, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
/***************************************************************************/
/* DRAFT UNDER REVIEW */
/* These APIs are still evolving and are meant as a prototype for review.*/
/* The APIs will change depending on feedback and will be firmed up */
/* to a stable set of APIs once all the feedback has been considered. */
/***************************************************************************/
#ifndef __PSA_INJECT_KEY_H__
#define __PSA_INJECT_KEY_H__
#include "crypto.h"
#include <stdint.h>
#include <string.h>
#ifdef __cplusplus
extern "C"
{
#endif
/**
* \brief Generate or import a given key pair and export the public part in a binary format.
* Initial attestation key: Private key for ECDSA-P256 to sign initial attestation token.
* Attestation private key is a persistent key that saved to
* persistent storage with persistent storage id = 17.
*
* \param[in] key_data Buffer containing the private key data if given.
* It must conain the format described in the documentation
* of psa_export_public_key() for
* the chosen type.
* In case of generate the private key - NULL will pass.
* \param key_data_length Size of the \p data buffer in bytes - must be 256 bits. in case key_data isn't NULL.
* In case of private key generation - 0 will pass.
* \param type Key type - must be a ECC key type
* (a \c PSA_KEY_TYPE_ECC_KEYPAIR(PSA_ECC_CURVE_XXX) value).
* \param[out] data Buffer where the key data is to be written.
* \param data_size Size of the \p data buffer in bytes -
* needs to be bigger then the max size of the public part.
* \param[out] data_length On success, the number of bytes
* that make up the key data.
*
* \retval #PSA_SUCCESS
* Success.
* \retval #PSA_ERROR_INVALID_HANDLE
* \retval #PSA_ERROR_OCCUPIED_SLOT
* There is already a key in the specified slot.
* \retval #PSA_ERROR_NOT_SUPPORTED
* \retval #PSA_ERROR_INVALID_ARGUMENT
* \retval #PSA_ERROR_INSUFFICIENT_MEMORY
* \retval #PSA_ERROR_INSUFFICIENT_ENTROPY
* \retval #PSA_ERROR_COMMUNICATION_FAILURE
* \retval #PSA_ERROR_HARDWARE_FAILURE
* \retval #PSA_ERROR_TAMPERING_DETECTED
* \retval #PSA_ERROR_BAD_STATE
* The library has not been previously initialized by psa_crypto_init().
* It is implementation-dependent whether a failure to initialize
* results in this error code.
*/
psa_status_t
psa_attestation_inject_key(const uint8_t *key_data,
size_t key_data_length,
psa_key_type_t type,
uint8_t *public_key_data,
size_t public_key_data_size,
size_t *public_key_data_length);
#ifdef __cplusplus
}
#endif
#endif /* __PSA_INJECT_KEY_H__ */

View File

@ -0,0 +1,33 @@
/* Copyright (c) 2017-2019 ARM Limited
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/***********************************************************************************************************************
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* THIS FILE IS AN AUTO-GENERATED FILE - DO NOT MODIFY IT.
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* Template Version 1.0
* Generated by tools/spm/generate_partition_code.py Version 1.0
**********************************************************************************************************************/
#ifndef PSA_ATTEST_SRV_PARTITION_ROT_SERVICES_H
#define PSA_ATTEST_SRV_PARTITION_ROT_SERVICES_H
#define PSA_ATTEST_GET_TOKEN_ID 0x00000F10
#define PSA_ATTEST_GET_TOKEN_SIZE_ID 0x00000F11
#define PSA_ATTEST_INJECT_KEY_ID 0x00000F12
#endif // PSA_ATTEST_SRV_PARTITION_ROT_SERVICES_H

View File

@ -0,0 +1,30 @@
/*
* Copyright (c) 2018-2019, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
/***************************************************************************/
/* DRAFT UNDER REVIEW */
/* These APIs are still evolving and are meant as a prototype for review.*/
/* The APIs will change depending on feedback and will be firmed up */
/* to a stable set of APIs once all the feedback has been considered. */
/***************************************************************************/
#ifndef __PSA_INITIAL_ATTESTATION_H__
#define __PSA_INITIAL_ATTESTATION_H__
#include "psa_initial_attestation_api.h"
#include "psa_attest_inject_key.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
#endif /* __PSA_INITIAL_ATTESTATION_H__ */

View File

@ -0,0 +1,219 @@
/*
* Copyright (c) 2018-2019, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
/***************************************************************************/
/* DRAFT UNDER REVIEW */
/* These APIs are still evolving and are meant as a prototype for review.*/
/* The APIs will change depending on feedback and will be firmed up */
/* to a stable set of APIs once all the feedback has been considered. */
/***************************************************************************/
#ifndef __PSA_INITIAL_ATTESTATION_API_H__
#define __PSA_INITIAL_ATTESTATION_API_H__
#include <limits.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* \brief PSA INITIAL ATTESTATION API version
*/
#define PSA_INITIAL_ATTEST_API_VERSION_MAJOR (0)
#define PSA_INITIAL_ATTEST_API_VERSION_MINOR (9)
/**
* \enum psa_attest_err_t
*
* \brief Initial attestation service error types
*
*/
enum psa_attest_err_t {
/* Action was performed successfully */
PSA_ATTEST_ERR_SUCCESS = 0,
/* Boot status data is unavailable or malformed */
PSA_ATTEST_ERR_INIT_FAILED,
/* Token buffer is too small to store the created token there */
PSA_ATTEST_ERR_TOKEN_BUFFER_OVERFLOW,
/* Some of the mandatory claims are unavailable*/
PSA_ATTEST_ERR_CLAIM_UNAVAILABLE,
/* Some parameter or combination of parameters are recognised as invalid:
* - challenge size is not allowed
* - challenge object is unavailable
* - token buffer is unavailable
*/
PSA_ATTEST_ERR_INVALID_INPUT,
/* Unexpected error happened during operation */
PSA_ATTEST_ERR_GENERAL,
/* Following entry is only to ensure the error code of integer size */
PSA_ATTEST_ERR_FORCE_INT_SIZE = INT_MAX
};
/**
* The allowed size of input challenge in bytes: 32, 48, 64
* Challenge can be a nonce from server
* or the hash of some combined data : nonce + attested data by caller.
*/
#define PSA_INITIAL_ATTEST_CHALLENGE_SIZE_32 (32u)
#define PSA_INITIAL_ATTEST_CHALLENGE_SIZE_48 (48u)
#define PSA_INITIAL_ATTEST_CHALLENGE_SIZE_64 (64u)
/**
* The list of fixed claims in the initial attestation token is still evolving,
* you can expect slight changes in the future.
*
* The initial attestation token is planned to be aligned with future version of
* Entity Attestation Token format:
* https://tools.ietf.org/html/draft-mandyam-eat-01
*
* Current list of claims:
* - Challenge: Input object from caller. Can be a single nonce from server
* or hash of nonce and attested data. It is intended to provide
* freshness to reports and the caller has responsibility to
* arrange this. Allowed length: 32, 48, 64 bytes. The claim is
* modeled to be eventually represented by the EAT standard
* claim nonce. Until such a time as that standard exists,
* the claim will be represented by a custom claim. Value
* is encoded as byte string.
*
* - Instance ID: It represents the unique identifier of the instance. In the
* PSA definition it is a hash of the public attestation key
* of the instance. The claim is modeled to be eventually
* represented by the EAT standard claim UEID of type GUID.
* Until such a time as that standard exists, the claim will be
* represented by a custom claim Value is encoded as byte
* string.
*
* - Verification service indicator: Optional, recommended claim. It is used by
* a Relying Party to locate a validation service for the token.
* The value is a text string that can be used to locate the
* service or a URL specifying the address of the service. The
* claim is modeled to be eventually represented by the EAT
* standard claim origination. Until such a time as that
* standard exists, the claim will be represented by a custom
* claim. Value is encoded as text string.
*
* - Profile definition: Optional, recommended claim. It contains the name of
* a document that describes the 'profile' of the token, being
* a full description of the claims, their usage, verification
* and token signing. The document name may include versioning.
* Custom claim with a value encoded as text string.
*
* - Implementation ID: It represents the original implementation signer of the
* attestation key and identifies the contract between the
* report and verification. A verification service will use this
* claim to locate the details of the verification process.
* Custom claim with a value encoded as byte string.
*
* - Security lifecycle: It represents the current lifecycle state of the
* instance. Custom claim with a value encoded as unsigned
* integer (enum). Possible values:
* - Unknown (0x1000u),
* - PSA_RoT_Provisioning (0x2000u),
* - Secured (0x3000u),
* - Non_PSA_RoT_Debug(0x4000u),
* - Recoverable_PSA_RoT_Debug (0x5000u),
* - Decommissioned (0x6000u)
*
* - Client ID: The partition ID of that secure partition or non-secure
* thread who called the initial attestation API. Custom claim
* with a value encoded as a *signed* integer. Negative number
* represents non-secure caller, positive numbers represents
* secure callers, zero is invalid.
*
* - HW version: Optional claim. Globally unique number in EAN-13 format
* identifying the GDSII that went to fabrication, HW and ROM.
* It can be used to reference the security level of the PSA-ROT
* via a certification website. Custom claim with a value is
* encoded as text string.
* - Boot seed: It represents a random value created at system boot time that
* will allow differentiation of reports from different system
* sessions. The size is 32 bytes. Custom claim with a value is
* encoded as byte string.
*
* - Software components: Recommended claim. It represents the software state
* of the system. The value of the claim is an array of CBOR map
* entries, with one entry per software component within the
* device. Each map contains multiple claims that describe
* evidence about the details of the software component.
*
* - Type: It represents the role of the software component. Value is
* encoded as short(!) text string.
*
* - Measurement: It represents a hash of the invariant software component
* in memory at start-up time. Value is encoded as byte
* string.
*
* - Security epoch: It represents the security control point of the
* software component. Value is encoded as unsigned integer.
*
* - Signer ID: Optional claim. It represents the hash of a signing
* authority public key. Value is encoded as byte string.
*
* - Version: Optional claim. It represents the issued software version.
* Value is encoded as text string.
*
* - Measurement description: Optional claim. It represents the way in which
* the measurement value of the software component is
* computed. Value is encoded as text string containing an
* abbreviated description (name) of the measurement method.
*
* - No software measurements: In the event that the implementation does not
* contain any software measurements then the software
* components claim above can be omitted but instead
* it is mandatory to include this claim to indicate this is a
* deliberate state. Custom claim a value is encoded as unsigned
* integer set to 1.
*/
/**
* \brief Get initial attestation token
*
* \param[in] challenge_obj Pointer to buffer where challenge input is
* stored. Nonce and / or hash of attested data.
* Must be always
* \ref PSA_INITIAL_ATTEST_CHALLENGE_SIZE bytes
* long.
* \param[in] challenge_size Size of challenge object in bytes.
* \param[out] token Pointer to the buffer where attestation token
* must be stored.
* \param[in/out] token_size Size of allocated buffer for token, which
* updated by initial attestation service with
* final token size.
*
* \return Returns error code as specified in \ref psa_attest_err_t
*/
enum psa_attest_err_t
psa_initial_attest_get_token(const uint8_t *challenge_obj,
uint32_t challenge_size,
uint8_t *token,
uint32_t *token_size);
/**
* \brief Get the exact size of initial attestation token in bytes.
*
* It just returns with the size of the IAT token. It can be used if the caller
* dynamically allocates memory for the token buffer.
*
* \param[in] challenge_size Size of challenge object in bytes.
* \param[out] token_size Size of the token in bytes, which is created by
* initial attestation service.
*
* \return Returns error code as specified in \ref psa_attest_err_t
*/
enum psa_attest_err_t
psa_initial_attest_get_token_size(uint32_t challenge_size,
uint32_t *token_size);
#ifdef __cplusplus
}
#endif
#endif /* __PSA_INITIAL_ATTESTATION_API_H__ */

View File

@ -0,0 +1,21 @@
/*
* Copyright (c) 2019, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#ifndef __TFM_CLIENT_H__
#define __TFM_CLIENT_H__
#include "psa_defs.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
#endif /* __TFM_CLIENT_H__ */