Format code

This commit is contained in:
Tomas Rezucha 2022-02-17 14:56:45 +01:00
parent 5362db237b
commit e651ec16c5
8 changed files with 1555 additions and 1537 deletions

View File

@ -9,6 +9,7 @@
#if 0 //High level layout for state machine
// *INDENT-OFF*
@startuml
[*] --> READ_MAGIC
READ_MAGIC --> READ_MAGIC : READ LEN < 4
@ -34,7 +35,7 @@ PROCESS_BINARY --> ESP_OK : READ LEN = BIN_SIZE
ESP_OK --> [*]
ESP_FAIL --> [*]
@enduml
// *INDENT-OFF*
#endif
#ifdef __cplusplus

View File

@ -1,448 +1,447 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "esp_encrypted_img.h"
#include <errno.h>
#include <esp_log.h>
#include <esp_err.h>
#include "mbedtls/pk.h"
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/gcm.h"
#include "sys/param.h"
static const char *TAG = "esp_encrypted_img";
typedef enum {
ESP_PRE_ENC_IMG_READ_MAGIC,
ESP_PRE_ENC_IMG_READ_GCM,
ESP_PRE_ENC_IMG_READ_IV,
ESP_PRE_ENC_IMG_READ_BINSIZE,
ESP_PRE_ENC_IMG_READ_AUTH,
ESP_PRE_ENC_IMG_READ_EXTRA_HEADER,
ESP_PRE_ENC_DATA_DECODE_STATE,
} esp_encrypted_img_state;
struct esp_encrypted_img_handle {
const char *rsa_pem;
size_t rsa_len;
uint32_t binary_file_len;
uint32_t binary_file_read;
char *gcm_key;
char *iv;
char auth_tag[16];
esp_encrypted_img_state state;
mbedtls_gcm_context gcm_ctx;
size_t cache_buf_len;
char *cache_buf;
};
#define GCM_KEY_SIZE 32
#define MAGIC_SIZE 4
#define ENC_GCM_KEY_SIZE 384
#define IV_SIZE 16
#define BIN_SIZE_DATA 4
#define AUTH_SIZE 16
#define RESERVED_HEADER 88
typedef struct {
char magic[MAGIC_SIZE];
char enc_gcm[ENC_GCM_KEY_SIZE];
char iv[IV_SIZE];
char bin_size[BIN_SIZE_DATA];
char auth[AUTH_SIZE];
char extra_header[RESERVED_HEADER];
} pre_enc_bin_header;
#define HEADER_DATA_SIZE sizeof(pre_enc_bin_header)
// Magic Byte is created using command: echo -n "esp_encrypted_img" | sha256sum
static uint32_t esp_enc_img_magic = 0x0788b6cf;
typedef struct esp_encrypted_img_handle esp_encrypted_img_t;
static int decipher_gcm_key(char *enc_gcm, esp_encrypted_img_t *handle)
{
int ret = 1;
handle->gcm_key = calloc(1, GCM_KEY_SIZE);
if (!handle->gcm_key) {
return ESP_ERR_NO_MEM;
}
size_t olen = 0;
mbedtls_pk_context pk;
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
const char *pers = "mbedtls_pk_encrypt";
mbedtls_ctr_drbg_init( &ctr_drbg );
mbedtls_entropy_init( &entropy );
mbedtls_pk_init( &pk );
if ((ret = mbedtls_ctr_drbg_seed( &ctr_drbg, mbedtls_entropy_func,
&entropy, (const unsigned char *) pers,
strlen(pers))) != 0) {
ESP_LOGE(TAG, "failed\n ! mbedtls_ctr_drbg_seed returned -0x%04x\n", (unsigned int) - ret);
free(handle->gcm_key);
goto exit;
}
ESP_LOGI(TAG, "Reading RSA private key");
if ( (ret = mbedtls_pk_parse_key(&pk, (const unsigned char *) handle->rsa_pem, handle->rsa_len, NULL, 0)) != 0) {
ESP_LOGE(TAG, "failed\n ! mbedtls_pk_parse_keyfile returned -0x%04x\n", (unsigned int) - ret );
free(handle->gcm_key);
goto exit;
}
if (( ret = mbedtls_pk_decrypt( &pk, (const unsigned char *)enc_gcm, ENC_GCM_KEY_SIZE, (unsigned char *)handle->gcm_key, &olen, GCM_KEY_SIZE,
mbedtls_ctr_drbg_random, &ctr_drbg ) ) != 0 ) {
ESP_LOGE(TAG, "failed\n ! mbedtls_pk_decrypt returned -0x%04x\n", (unsigned int) - ret );
free(handle->gcm_key);
goto exit;
}
handle->cache_buf = realloc(handle->cache_buf, 16);
if (!handle->cache_buf) {
return ESP_ERR_NO_MEM;
}
handle->state = ESP_PRE_ENC_IMG_READ_IV;
handle->binary_file_read = 0;
handle->cache_buf_len = 0;
handle->iv = calloc(1, IV_SIZE);
if (!handle->iv) {
return ESP_ERR_NO_MEM;
}
exit:
mbedtls_pk_free( &pk );
mbedtls_entropy_free( &entropy );
mbedtls_ctr_drbg_free( &ctr_drbg );
free((void *)handle->rsa_pem);
return (ret);
}
esp_decrypt_handle_t esp_encrypted_img_decrypt_start(const esp_decrypt_cfg_t *cfg)
{
if (cfg == NULL || cfg->rsa_pub_key == NULL) {
ESP_LOGE(TAG, "esp_encrypted_img_decrypt_start : Invalid argument");
return NULL;
}
ESP_LOGI(TAG, "Starting Decryption Process");
esp_encrypted_img_t *handle = calloc(1, sizeof(esp_encrypted_img_t));
if (!handle) {
ESP_LOGE(TAG, "Couldn't allocate memory to handle");
goto failure;
}
handle->rsa_pem = calloc(1, cfg->rsa_pub_key_len);
if (!handle->rsa_pem) {
ESP_LOGE(TAG, "Couldn't allocate memory to handle->rsa_pem");
goto failure;
}
handle->cache_buf = calloc(1, ENC_GCM_KEY_SIZE);
if (!handle->cache_buf) {
ESP_LOGE(TAG, "Couldn't allocate memory to handle->cache_buf");
goto failure;
}
memcpy((void *)handle->rsa_pem, cfg->rsa_pub_key, cfg->rsa_pub_key_len);
handle->rsa_len = cfg->rsa_pub_key_len;
handle->state = ESP_PRE_ENC_IMG_READ_MAGIC;
esp_decrypt_handle_t *ctx = (esp_decrypt_handle_t *)handle;
return ctx;
failure:
if (!handle) {
return NULL;
}
if (handle->rsa_pem) {
free((void *)handle->rsa_pem);
}
if (handle) {
free(handle);
}
return NULL;
}
static esp_err_t process_bin(esp_encrypted_img_t *handle, pre_enc_decrypt_arg_t *args, int curr_index)
{
size_t data_len = args->data_in_len;
handle->binary_file_read += data_len - curr_index;
int dec_len = 0;
if (handle->binary_file_read != handle->binary_file_len) {
size_t copy_len = 0;
if ((handle->cache_buf_len + (data_len - curr_index)) - (handle->cache_buf_len + (data_len - curr_index)) % 16 > 0) {
args->data_out = realloc(args->data_out, (handle->cache_buf_len + (data_len - curr_index)) - (handle->cache_buf_len + (data_len - curr_index)) % 16);
if (!args->data_out) {
return ESP_ERR_NO_MEM;
}
}
if (handle->cache_buf_len != 0) {
copy_len = MIN(16 - handle->cache_buf_len, data_len - curr_index);
memcpy(handle->cache_buf + handle->cache_buf_len, args->data_in + curr_index, copy_len);
handle->cache_buf_len += copy_len;
if (handle->cache_buf_len != 16) {
args->data_out_len = 0;
return ESP_ERR_NOT_FINISHED;
}
if (mbedtls_gcm_update(&handle->gcm_ctx, 16, (const unsigned char *)handle->cache_buf, (unsigned char *) args->data_out) != 0) {
return ESP_FAIL;
}
dec_len = 16;
}
handle->cache_buf_len = (data_len - curr_index - copy_len) % 16;
if (handle->cache_buf_len != 0) {
data_len -= handle->cache_buf_len;
memcpy(handle->cache_buf, args->data_in + (data_len), handle->cache_buf_len);
}
if (data_len - copy_len - curr_index > 0) {
if (mbedtls_gcm_update(&handle->gcm_ctx, data_len - copy_len - curr_index, (const unsigned char *)args->data_in + curr_index + copy_len, (unsigned char *)args->data_out + dec_len) != 0) {
return ESP_FAIL;
}
}
args->data_out_len = dec_len + data_len - curr_index - copy_len;
return ESP_ERR_NOT_FINISHED;
}
args->data_out = realloc(args->data_out, handle->cache_buf_len + data_len - curr_index);
if (!args->data_out) {
return ESP_ERR_NO_MEM;
}
size_t copy_len = 0;
copy_len = MIN(16 - handle->cache_buf_len, data_len - curr_index);
memcpy(handle->cache_buf + handle->cache_buf_len, args->data_in + curr_index, copy_len);
handle->cache_buf_len += copy_len;
if (mbedtls_gcm_update(&handle->gcm_ctx, handle->cache_buf_len, (const unsigned char *)handle->cache_buf, (unsigned char *)args->data_out) != 0) {
return ESP_FAIL;
}
if (data_len - curr_index - copy_len > 0) {
if (mbedtls_gcm_update(&handle->gcm_ctx, data_len - curr_index - copy_len, (const unsigned char *)(args->data_in + curr_index + copy_len), (unsigned char *)(args->data_out + 16)) != 0) {
return ESP_FAIL;
}
}
args->data_out_len = handle->cache_buf_len + data_len - copy_len - curr_index;
handle->cache_buf_len = 0;
return ESP_OK;
}
static void read_and_cache_data(esp_encrypted_img_t *handle, pre_enc_decrypt_arg_t *args, int *curr_index, int data_size)
{
const int data_left = data_size - handle->binary_file_read;
const int data_recv = args->data_in_len - *curr_index;
if (handle->state == ESP_PRE_ENC_IMG_READ_IV && handle->iv) {
memcpy(handle->iv + handle->cache_buf_len, args->data_in + *curr_index, MIN(data_recv, data_left));
} else if (handle->state == ESP_PRE_ENC_IMG_READ_AUTH) {
memcpy(handle->auth_tag + handle->cache_buf_len, args->data_in + *curr_index, MIN(data_recv, data_left));
} else {
memcpy(handle->cache_buf + handle->cache_buf_len, args->data_in + *curr_index, MIN(data_recv, data_left));
}
handle->cache_buf_len += MIN(data_recv, data_left);
int temp = *curr_index;
*curr_index += MIN(data_recv, data_left);
handle->binary_file_read += MIN(args->data_in_len - temp, data_left);
}
esp_err_t esp_encrypted_img_decrypt_data(esp_decrypt_handle_t *ctx, pre_enc_decrypt_arg_t *args)
{
if (ctx == NULL || args == NULL || args->data_in == NULL) {
return ESP_ERR_INVALID_ARG;
}
esp_encrypted_img_t *handle = (esp_encrypted_img_t *)ctx;
if (handle == NULL) {
ESP_LOGE(TAG, "esp_encrypted_img_decrypt_data: Invalid argument");
return ESP_ERR_INVALID_ARG;
}
esp_err_t err;
int curr_index = 0;
switch (handle->state) {
case ESP_PRE_ENC_IMG_READ_MAGIC:
if (handle->cache_buf_len == 0 && (args->data_in_len - curr_index) >= MAGIC_SIZE) {
uint32_t recv_magic = *(uint32_t *)args->data_in;
if (recv_magic != esp_enc_img_magic) {
ESP_LOGE(TAG, "Magic Verification failed");
free((void *)handle->rsa_pem);
return ESP_FAIL;
}
curr_index += MAGIC_SIZE;
} else {
read_and_cache_data(handle, args, &curr_index, MAGIC_SIZE);
if (handle->binary_file_read == MAGIC_SIZE) {
uint32_t recv_magic = *(uint32_t *)handle->cache_buf;
if (recv_magic != esp_enc_img_magic) {
ESP_LOGE(TAG, "Magic Verification failed");
free((void *)handle->rsa_pem);
return ESP_FAIL;
}
handle->binary_file_read = 0;
handle->cache_buf_len = 0;
} else {
return ESP_ERR_NOT_FINISHED;
}
}
ESP_LOGI(TAG, "Magic Verified");
handle->state = ESP_PRE_ENC_IMG_READ_GCM;
/* falls through */
case ESP_PRE_ENC_IMG_READ_GCM:
if (handle->cache_buf_len == 0 && args->data_in_len - curr_index >= ENC_GCM_KEY_SIZE) {
if (decipher_gcm_key(args->data_in + curr_index, handle) != 0) {
ESP_LOGE(TAG, "Unable to decipher GCM key");
return ESP_FAIL;
}
curr_index += ENC_GCM_KEY_SIZE;
} else {
read_and_cache_data(handle, args, &curr_index, ENC_GCM_KEY_SIZE);
if (handle->cache_buf_len == ENC_GCM_KEY_SIZE) {
if (decipher_gcm_key(handle->cache_buf, handle) != 0) {
ESP_LOGE(TAG, "Unable to decipher GCM key");
return ESP_FAIL;
}
} else {
return ESP_ERR_NOT_FINISHED;
}
}
/* falls through */
case ESP_PRE_ENC_IMG_READ_IV:
if (handle->cache_buf_len == 0 && args->data_in_len - curr_index >= IV_SIZE) {
memcpy(handle->iv, args->data_in + curr_index, IV_SIZE);
handle->binary_file_read = IV_SIZE;
curr_index += IV_SIZE;
} else {
read_and_cache_data(handle, args, &curr_index, IV_SIZE);
}
if (handle->binary_file_read == IV_SIZE) {
handle->state = ESP_PRE_ENC_IMG_READ_BINSIZE;
handle->binary_file_read = 0;
handle->cache_buf_len = 0;
mbedtls_gcm_init(&handle->gcm_ctx);
if ((err = mbedtls_gcm_setkey(&handle->gcm_ctx, MBEDTLS_CIPHER_ID_AES, (const unsigned char *)handle->gcm_key, GCM_KEY_SIZE * 8)) != 0) {
ESP_LOGE(TAG, "Error: mbedtls_gcm_set_key: -0x%04x\n", (unsigned int) - err);
return ESP_FAIL;
}
free(handle->gcm_key);
if (mbedtls_gcm_starts(&handle->gcm_ctx, MBEDTLS_GCM_DECRYPT, (const unsigned char *)handle->iv, IV_SIZE, NULL, 0) != 0) {
ESP_LOGE(TAG, "Error: mbedtls_gcm_starts: -0x%04x\n", (unsigned int) - err);
return ESP_FAIL;
}
free(handle->iv);
handle->iv = NULL;
} else {
return ESP_ERR_NOT_FINISHED;
}
/* falls through */
case ESP_PRE_ENC_IMG_READ_BINSIZE:
if (handle->cache_buf_len == 0 && (args->data_in_len - curr_index) >= BIN_SIZE_DATA) {
handle->binary_file_len = *(uint32_t *)(args->data_in + curr_index);
curr_index += BIN_SIZE_DATA;
} else {
read_and_cache_data(handle, args, &curr_index, BIN_SIZE_DATA);
if (handle->binary_file_read == BIN_SIZE_DATA) {
handle->binary_file_len = *(uint32_t *)handle->cache_buf;
} else {
return ESP_ERR_NOT_FINISHED;
}
}
handle->state = ESP_PRE_ENC_IMG_READ_AUTH;
handle->binary_file_read = 0;
handle->cache_buf_len = 0;
/* falls through */
case ESP_PRE_ENC_IMG_READ_AUTH:
if (handle->cache_buf_len == 0 && args->data_in_len - curr_index >= AUTH_SIZE) {
memcpy(handle->auth_tag, args->data_in + curr_index, AUTH_SIZE);
handle->binary_file_read = AUTH_SIZE;
curr_index += AUTH_SIZE;
} else {
read_and_cache_data(handle, args, &curr_index, AUTH_SIZE);
}
if (handle->binary_file_read == AUTH_SIZE) {
handle->state = ESP_PRE_ENC_IMG_READ_EXTRA_HEADER;
handle->binary_file_read = 0;
handle->cache_buf_len = 0;
} else {
return ESP_ERR_NOT_FINISHED;
}
/* falls through */
case ESP_PRE_ENC_IMG_READ_EXTRA_HEADER:
{
int temp = curr_index;
curr_index += MIN(args->data_in_len - curr_index, RESERVED_HEADER - handle->binary_file_read);
handle->binary_file_read += MIN(args->data_in_len - temp, RESERVED_HEADER - handle->binary_file_read);
if (handle->binary_file_read == RESERVED_HEADER) {
handle->state = ESP_PRE_ENC_DATA_DECODE_STATE;
handle->binary_file_read = 0;
handle->cache_buf_len = 0;
} else {
return ESP_ERR_NOT_FINISHED;
}
}
/* falls through */
case ESP_PRE_ENC_DATA_DECODE_STATE:
err = process_bin(handle, args, curr_index);
return err;
}
return ESP_OK;
}
esp_err_t esp_encrypted_img_decrypt_end(esp_decrypt_handle_t *ctx)
{
if (ctx == NULL) {
return ESP_ERR_INVALID_ARG;
}
esp_encrypted_img_t *handle = (esp_encrypted_img_t *)ctx;
esp_err_t err = ESP_OK;
if (handle == NULL) {
ESP_LOGE(TAG, "esp_encrypted_img_decrypt_data: Invalid argument");
return ESP_ERR_INVALID_ARG;
}
if (handle->state == ESP_PRE_ENC_DATA_DECODE_STATE) {
if (handle->cache_buf_len != 0 || handle->binary_file_read != handle->binary_file_len) {
ESP_LOGE(TAG, "Invalid operation");
err = ESP_FAIL;
goto exit;
}
char *got_auth = calloc(1, AUTH_SIZE);
if (!got_auth) {
ESP_LOGE(TAG, "Unable to allocate memory");
err = ESP_FAIL;
goto exit;
}
err = mbedtls_gcm_finish(&handle->gcm_ctx, (unsigned char *)got_auth, AUTH_SIZE);
if (err != 0) {
ESP_LOGE(TAG, "Error: %d", err);
free(got_auth);
err = ESP_FAIL;
goto exit;
}
if (memcmp(got_auth, handle->auth_tag, AUTH_SIZE) != 0) {
ESP_LOGE(TAG, "Invalid Auth");
free(got_auth);
err = ESP_FAIL;
goto exit;
}
free(got_auth);
}
err = ESP_OK;
exit:
mbedtls_gcm_free(&handle->gcm_ctx);
free(handle->cache_buf);
free(handle);
return err;
}
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "esp_encrypted_img.h"
#include <errno.h>
#include <esp_log.h>
#include <esp_err.h>
#include "mbedtls/pk.h"
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/gcm.h"
#include "sys/param.h"
static const char *TAG = "esp_encrypted_img";
typedef enum {
ESP_PRE_ENC_IMG_READ_MAGIC,
ESP_PRE_ENC_IMG_READ_GCM,
ESP_PRE_ENC_IMG_READ_IV,
ESP_PRE_ENC_IMG_READ_BINSIZE,
ESP_PRE_ENC_IMG_READ_AUTH,
ESP_PRE_ENC_IMG_READ_EXTRA_HEADER,
ESP_PRE_ENC_DATA_DECODE_STATE,
} esp_encrypted_img_state;
struct esp_encrypted_img_handle {
const char *rsa_pem;
size_t rsa_len;
uint32_t binary_file_len;
uint32_t binary_file_read;
char *gcm_key;
char *iv;
char auth_tag[16];
esp_encrypted_img_state state;
mbedtls_gcm_context gcm_ctx;
size_t cache_buf_len;
char *cache_buf;
};
#define GCM_KEY_SIZE 32
#define MAGIC_SIZE 4
#define ENC_GCM_KEY_SIZE 384
#define IV_SIZE 16
#define BIN_SIZE_DATA 4
#define AUTH_SIZE 16
#define RESERVED_HEADER 88
typedef struct {
char magic[MAGIC_SIZE];
char enc_gcm[ENC_GCM_KEY_SIZE];
char iv[IV_SIZE];
char bin_size[BIN_SIZE_DATA];
char auth[AUTH_SIZE];
char extra_header[RESERVED_HEADER];
} pre_enc_bin_header;
#define HEADER_DATA_SIZE sizeof(pre_enc_bin_header)
// Magic Byte is created using command: echo -n "esp_encrypted_img" | sha256sum
static uint32_t esp_enc_img_magic = 0x0788b6cf;
typedef struct esp_encrypted_img_handle esp_encrypted_img_t;
static int decipher_gcm_key(char *enc_gcm, esp_encrypted_img_t *handle)
{
int ret = 1;
handle->gcm_key = calloc(1, GCM_KEY_SIZE);
if (!handle->gcm_key) {
return ESP_ERR_NO_MEM;
}
size_t olen = 0;
mbedtls_pk_context pk;
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
const char *pers = "mbedtls_pk_encrypt";
mbedtls_ctr_drbg_init( &ctr_drbg );
mbedtls_entropy_init( &entropy );
mbedtls_pk_init( &pk );
if ((ret = mbedtls_ctr_drbg_seed( &ctr_drbg, mbedtls_entropy_func,
&entropy, (const unsigned char *) pers,
strlen(pers))) != 0) {
ESP_LOGE(TAG, "failed\n ! mbedtls_ctr_drbg_seed returned -0x%04x\n", (unsigned int) - ret);
free(handle->gcm_key);
goto exit;
}
ESP_LOGI(TAG, "Reading RSA private key");
if ( (ret = mbedtls_pk_parse_key(&pk, (const unsigned char *) handle->rsa_pem, handle->rsa_len, NULL, 0)) != 0) {
ESP_LOGE(TAG, "failed\n ! mbedtls_pk_parse_keyfile returned -0x%04x\n", (unsigned int) - ret );
free(handle->gcm_key);
goto exit;
}
if (( ret = mbedtls_pk_decrypt( &pk, (const unsigned char *)enc_gcm, ENC_GCM_KEY_SIZE, (unsigned char *)handle->gcm_key, &olen, GCM_KEY_SIZE,
mbedtls_ctr_drbg_random, &ctr_drbg ) ) != 0 ) {
ESP_LOGE(TAG, "failed\n ! mbedtls_pk_decrypt returned -0x%04x\n", (unsigned int) - ret );
free(handle->gcm_key);
goto exit;
}
handle->cache_buf = realloc(handle->cache_buf, 16);
if (!handle->cache_buf) {
return ESP_ERR_NO_MEM;
}
handle->state = ESP_PRE_ENC_IMG_READ_IV;
handle->binary_file_read = 0;
handle->cache_buf_len = 0;
handle->iv = calloc(1, IV_SIZE);
if (!handle->iv) {
return ESP_ERR_NO_MEM;
}
exit:
mbedtls_pk_free( &pk );
mbedtls_entropy_free( &entropy );
mbedtls_ctr_drbg_free( &ctr_drbg );
free((void *)handle->rsa_pem);
return (ret);
}
esp_decrypt_handle_t esp_encrypted_img_decrypt_start(const esp_decrypt_cfg_t *cfg)
{
if (cfg == NULL || cfg->rsa_pub_key == NULL) {
ESP_LOGE(TAG, "esp_encrypted_img_decrypt_start : Invalid argument");
return NULL;
}
ESP_LOGI(TAG, "Starting Decryption Process");
esp_encrypted_img_t *handle = calloc(1, sizeof(esp_encrypted_img_t));
if (!handle) {
ESP_LOGE(TAG, "Couldn't allocate memory to handle");
goto failure;
}
handle->rsa_pem = calloc(1, cfg->rsa_pub_key_len);
if (!handle->rsa_pem) {
ESP_LOGE(TAG, "Couldn't allocate memory to handle->rsa_pem");
goto failure;
}
handle->cache_buf = calloc(1, ENC_GCM_KEY_SIZE);
if (!handle->cache_buf) {
ESP_LOGE(TAG, "Couldn't allocate memory to handle->cache_buf");
goto failure;
}
memcpy((void *)handle->rsa_pem, cfg->rsa_pub_key, cfg->rsa_pub_key_len);
handle->rsa_len = cfg->rsa_pub_key_len;
handle->state = ESP_PRE_ENC_IMG_READ_MAGIC;
esp_decrypt_handle_t *ctx = (esp_decrypt_handle_t *)handle;
return ctx;
failure:
if (!handle) {
return NULL;
}
if (handle->rsa_pem) {
free((void *)handle->rsa_pem);
}
if (handle) {
free(handle);
}
return NULL;
}
static esp_err_t process_bin(esp_encrypted_img_t *handle, pre_enc_decrypt_arg_t *args, int curr_index)
{
size_t data_len = args->data_in_len;
handle->binary_file_read += data_len - curr_index;
int dec_len = 0;
if (handle->binary_file_read != handle->binary_file_len) {
size_t copy_len = 0;
if ((handle->cache_buf_len + (data_len - curr_index)) - (handle->cache_buf_len + (data_len - curr_index)) % 16 > 0) {
args->data_out = realloc(args->data_out, (handle->cache_buf_len + (data_len - curr_index)) - (handle->cache_buf_len + (data_len - curr_index)) % 16);
if (!args->data_out) {
return ESP_ERR_NO_MEM;
}
}
if (handle->cache_buf_len != 0) {
copy_len = MIN(16 - handle->cache_buf_len, data_len - curr_index);
memcpy(handle->cache_buf + handle->cache_buf_len, args->data_in + curr_index, copy_len);
handle->cache_buf_len += copy_len;
if (handle->cache_buf_len != 16) {
args->data_out_len = 0;
return ESP_ERR_NOT_FINISHED;
}
if (mbedtls_gcm_update(&handle->gcm_ctx, 16, (const unsigned char *)handle->cache_buf, (unsigned char *) args->data_out) != 0) {
return ESP_FAIL;
}
dec_len = 16;
}
handle->cache_buf_len = (data_len - curr_index - copy_len) % 16;
if (handle->cache_buf_len != 0) {
data_len -= handle->cache_buf_len;
memcpy(handle->cache_buf, args->data_in + (data_len), handle->cache_buf_len);
}
if (data_len - copy_len - curr_index > 0) {
if (mbedtls_gcm_update(&handle->gcm_ctx, data_len - copy_len - curr_index, (const unsigned char *)args->data_in + curr_index + copy_len, (unsigned char *)args->data_out + dec_len) != 0) {
return ESP_FAIL;
}
}
args->data_out_len = dec_len + data_len - curr_index - copy_len;
return ESP_ERR_NOT_FINISHED;
}
args->data_out = realloc(args->data_out, handle->cache_buf_len + data_len - curr_index);
if (!args->data_out) {
return ESP_ERR_NO_MEM;
}
size_t copy_len = 0;
copy_len = MIN(16 - handle->cache_buf_len, data_len - curr_index);
memcpy(handle->cache_buf + handle->cache_buf_len, args->data_in + curr_index, copy_len);
handle->cache_buf_len += copy_len;
if (mbedtls_gcm_update(&handle->gcm_ctx, handle->cache_buf_len, (const unsigned char *)handle->cache_buf, (unsigned char *)args->data_out) != 0) {
return ESP_FAIL;
}
if (data_len - curr_index - copy_len > 0) {
if (mbedtls_gcm_update(&handle->gcm_ctx, data_len - curr_index - copy_len, (const unsigned char *)(args->data_in + curr_index + copy_len), (unsigned char *)(args->data_out + 16)) != 0) {
return ESP_FAIL;
}
}
args->data_out_len = handle->cache_buf_len + data_len - copy_len - curr_index;
handle->cache_buf_len = 0;
return ESP_OK;
}
static void read_and_cache_data(esp_encrypted_img_t *handle, pre_enc_decrypt_arg_t *args, int *curr_index, int data_size)
{
const int data_left = data_size - handle->binary_file_read;
const int data_recv = args->data_in_len - *curr_index;
if (handle->state == ESP_PRE_ENC_IMG_READ_IV && handle->iv) {
memcpy(handle->iv + handle->cache_buf_len, args->data_in + *curr_index, MIN(data_recv, data_left));
} else if (handle->state == ESP_PRE_ENC_IMG_READ_AUTH) {
memcpy(handle->auth_tag + handle->cache_buf_len, args->data_in + *curr_index, MIN(data_recv, data_left));
} else {
memcpy(handle->cache_buf + handle->cache_buf_len, args->data_in + *curr_index, MIN(data_recv, data_left));
}
handle->cache_buf_len += MIN(data_recv, data_left);
int temp = *curr_index;
*curr_index += MIN(data_recv, data_left);
handle->binary_file_read += MIN(args->data_in_len - temp, data_left);
}
esp_err_t esp_encrypted_img_decrypt_data(esp_decrypt_handle_t *ctx, pre_enc_decrypt_arg_t *args)
{
if (ctx == NULL || args == NULL || args->data_in == NULL) {
return ESP_ERR_INVALID_ARG;
}
esp_encrypted_img_t *handle = (esp_encrypted_img_t *)ctx;
if (handle == NULL) {
ESP_LOGE(TAG, "esp_encrypted_img_decrypt_data: Invalid argument");
return ESP_ERR_INVALID_ARG;
}
esp_err_t err;
int curr_index = 0;
switch (handle->state) {
case ESP_PRE_ENC_IMG_READ_MAGIC:
if (handle->cache_buf_len == 0 && (args->data_in_len - curr_index) >= MAGIC_SIZE) {
uint32_t recv_magic = *(uint32_t *)args->data_in;
if (recv_magic != esp_enc_img_magic) {
ESP_LOGE(TAG, "Magic Verification failed");
free((void *)handle->rsa_pem);
return ESP_FAIL;
}
curr_index += MAGIC_SIZE;
} else {
read_and_cache_data(handle, args, &curr_index, MAGIC_SIZE);
if (handle->binary_file_read == MAGIC_SIZE) {
uint32_t recv_magic = *(uint32_t *)handle->cache_buf;
if (recv_magic != esp_enc_img_magic) {
ESP_LOGE(TAG, "Magic Verification failed");
free((void *)handle->rsa_pem);
return ESP_FAIL;
}
handle->binary_file_read = 0;
handle->cache_buf_len = 0;
} else {
return ESP_ERR_NOT_FINISHED;
}
}
ESP_LOGI(TAG, "Magic Verified");
handle->state = ESP_PRE_ENC_IMG_READ_GCM;
/* falls through */
case ESP_PRE_ENC_IMG_READ_GCM:
if (handle->cache_buf_len == 0 && args->data_in_len - curr_index >= ENC_GCM_KEY_SIZE) {
if (decipher_gcm_key(args->data_in + curr_index, handle) != 0) {
ESP_LOGE(TAG, "Unable to decipher GCM key");
return ESP_FAIL;
}
curr_index += ENC_GCM_KEY_SIZE;
} else {
read_and_cache_data(handle, args, &curr_index, ENC_GCM_KEY_SIZE);
if (handle->cache_buf_len == ENC_GCM_KEY_SIZE) {
if (decipher_gcm_key(handle->cache_buf, handle) != 0) {
ESP_LOGE(TAG, "Unable to decipher GCM key");
return ESP_FAIL;
}
} else {
return ESP_ERR_NOT_FINISHED;
}
}
/* falls through */
case ESP_PRE_ENC_IMG_READ_IV:
if (handle->cache_buf_len == 0 && args->data_in_len - curr_index >= IV_SIZE) {
memcpy(handle->iv, args->data_in + curr_index, IV_SIZE);
handle->binary_file_read = IV_SIZE;
curr_index += IV_SIZE;
} else {
read_and_cache_data(handle, args, &curr_index, IV_SIZE);
}
if (handle->binary_file_read == IV_SIZE) {
handle->state = ESP_PRE_ENC_IMG_READ_BINSIZE;
handle->binary_file_read = 0;
handle->cache_buf_len = 0;
mbedtls_gcm_init(&handle->gcm_ctx);
if ((err = mbedtls_gcm_setkey(&handle->gcm_ctx, MBEDTLS_CIPHER_ID_AES, (const unsigned char *)handle->gcm_key, GCM_KEY_SIZE * 8)) != 0) {
ESP_LOGE(TAG, "Error: mbedtls_gcm_set_key: -0x%04x\n", (unsigned int) - err);
return ESP_FAIL;
}
free(handle->gcm_key);
if (mbedtls_gcm_starts(&handle->gcm_ctx, MBEDTLS_GCM_DECRYPT, (const unsigned char *)handle->iv, IV_SIZE, NULL, 0) != 0) {
ESP_LOGE(TAG, "Error: mbedtls_gcm_starts: -0x%04x\n", (unsigned int) - err);
return ESP_FAIL;
}
free(handle->iv);
handle->iv = NULL;
} else {
return ESP_ERR_NOT_FINISHED;
}
/* falls through */
case ESP_PRE_ENC_IMG_READ_BINSIZE:
if (handle->cache_buf_len == 0 && (args->data_in_len - curr_index) >= BIN_SIZE_DATA) {
handle->binary_file_len = *(uint32_t *)(args->data_in + curr_index);
curr_index += BIN_SIZE_DATA;
} else {
read_and_cache_data(handle, args, &curr_index, BIN_SIZE_DATA);
if (handle->binary_file_read == BIN_SIZE_DATA) {
handle->binary_file_len = *(uint32_t *)handle->cache_buf;
} else {
return ESP_ERR_NOT_FINISHED;
}
}
handle->state = ESP_PRE_ENC_IMG_READ_AUTH;
handle->binary_file_read = 0;
handle->cache_buf_len = 0;
/* falls through */
case ESP_PRE_ENC_IMG_READ_AUTH:
if (handle->cache_buf_len == 0 && args->data_in_len - curr_index >= AUTH_SIZE) {
memcpy(handle->auth_tag, args->data_in + curr_index, AUTH_SIZE);
handle->binary_file_read = AUTH_SIZE;
curr_index += AUTH_SIZE;
} else {
read_and_cache_data(handle, args, &curr_index, AUTH_SIZE);
}
if (handle->binary_file_read == AUTH_SIZE) {
handle->state = ESP_PRE_ENC_IMG_READ_EXTRA_HEADER;
handle->binary_file_read = 0;
handle->cache_buf_len = 0;
} else {
return ESP_ERR_NOT_FINISHED;
}
/* falls through */
case ESP_PRE_ENC_IMG_READ_EXTRA_HEADER: {
int temp = curr_index;
curr_index += MIN(args->data_in_len - curr_index, RESERVED_HEADER - handle->binary_file_read);
handle->binary_file_read += MIN(args->data_in_len - temp, RESERVED_HEADER - handle->binary_file_read);
if (handle->binary_file_read == RESERVED_HEADER) {
handle->state = ESP_PRE_ENC_DATA_DECODE_STATE;
handle->binary_file_read = 0;
handle->cache_buf_len = 0;
} else {
return ESP_ERR_NOT_FINISHED;
}
}
/* falls through */
case ESP_PRE_ENC_DATA_DECODE_STATE:
err = process_bin(handle, args, curr_index);
return err;
}
return ESP_OK;
}
esp_err_t esp_encrypted_img_decrypt_end(esp_decrypt_handle_t *ctx)
{
if (ctx == NULL) {
return ESP_ERR_INVALID_ARG;
}
esp_encrypted_img_t *handle = (esp_encrypted_img_t *)ctx;
esp_err_t err = ESP_OK;
if (handle == NULL) {
ESP_LOGE(TAG, "esp_encrypted_img_decrypt_data: Invalid argument");
return ESP_ERR_INVALID_ARG;
}
if (handle->state == ESP_PRE_ENC_DATA_DECODE_STATE) {
if (handle->cache_buf_len != 0 || handle->binary_file_read != handle->binary_file_len) {
ESP_LOGE(TAG, "Invalid operation");
err = ESP_FAIL;
goto exit;
}
char *got_auth = calloc(1, AUTH_SIZE);
if (!got_auth) {
ESP_LOGE(TAG, "Unable to allocate memory");
err = ESP_FAIL;
goto exit;
}
err = mbedtls_gcm_finish(&handle->gcm_ctx, (unsigned char *)got_auth, AUTH_SIZE);
if (err != 0) {
ESP_LOGE(TAG, "Error: %d", err);
free(got_auth);
err = ESP_FAIL;
goto exit;
}
if (memcmp(got_auth, handle->auth_tag, AUTH_SIZE) != 0) {
ESP_LOGE(TAG, "Invalid Auth");
free(got_auth);
err = ESP_FAIL;
goto exit;
}
free(got_auth);
}
err = ESP_OK;
exit:
mbedtls_gcm_free(&handle->gcm_ctx);
free(handle->cache_buf);
free(handle);
return err;
}

View File

@ -1,119 +1,119 @@
/*
* SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <expat.h>
#include <string.h>
#include "unity.h"
typedef struct {
int depth;
char output[512];
int output_off;
} user_data_t;
static void insert_space(user_data_t *user_data)
{
const char align_str[] = " ";
TEST_ASSERT(sizeof(user_data->output) >= user_data->output_off);
user_data->output[user_data->output_off++] = '\n';
for (int i = 0; i < user_data->depth; i++) {
for (int j = 0; j < strlen(align_str); ++j) {
TEST_ASSERT(sizeof(user_data->output) >= user_data->output_off);
user_data->output[user_data->output_off++] = align_str[j];
}
}
}
static void XMLCALL start_element(void *userData, const XML_Char *name, const XML_Char **atts)
{
user_data_t *user_data = (user_data_t *) userData;
insert_space(user_data);
const int ret = snprintf(user_data->output + user_data->output_off,
sizeof(user_data->output) - user_data->output_off,
"<%s>", name);
TEST_ASSERT_EQUAL(strlen(name) + 2, ret); // 2 are the tag characters: "<>"
user_data->output_off += ret;
++user_data->depth;
}
static void XMLCALL end_element(void *userData, const XML_Char *name)
{
user_data_t *user_data = (user_data_t *) userData;
--user_data->depth;
insert_space(user_data);
int ret = snprintf(user_data->output + user_data->output_off, sizeof(user_data->output) - user_data->output_off,
"</%s>", name);
TEST_ASSERT_EQUAL(strlen(name) + 3, ret); // 3 are the tag characters: "</>"
user_data->output_off += ret;
}
static void data_handler(void *userData, const XML_Char *s, int len)
{
user_data_t *user_data = (user_data_t *) userData;
insert_space(user_data);
// s is not zero-terminated
char tmp_str[len+1];
strlcpy(tmp_str, s, len+1);
int ret = snprintf(user_data->output + user_data->output_off, sizeof(user_data->output) - user_data->output_off,
"%s", tmp_str);
TEST_ASSERT_EQUAL(strlen(tmp_str), ret);
user_data->output_off += ret;
}
TEST_CASE("Expat parses XML", "[expat]")
{
const char test_in[] = "<html><title>Page title</title><body><h>header</h><ol><li>A</li>"\
"<li>B</li><li>C</li></ol></body></html>";
const char test_expected[] = "\n"\
"<html>\n"\
" <title>\n"\
" Page title\n"\
" </title>\n"\
" <body>\n"\
" <h>\n"\
" header\n"\
" </h>\n"\
" <ol>\n"\
" <li>\n"\
" A\n"\
" </li>\n"\
" <li>\n"\
" B\n"\
" </li>\n"\
" <li>\n"\
" C\n"\
" </li>\n"\
" </ol>\n"\
" </body>\n"\
"</html>";
user_data_t user_data = {
.depth = 0,
.output = { '\0' },
.output_off = 0
};
XML_Parser parser = XML_ParserCreate(NULL);
XML_SetUserData(parser, &user_data);
XML_SetElementHandler(parser, start_element, end_element);
XML_SetCharacterDataHandler(parser, data_handler);
TEST_ASSERT_NOT_EQUAL(XML_STATUS_ERROR, XML_Parse(parser, test_in, strlen(test_in), 1));
XML_ParserFree(parser);
TEST_ASSERT_EQUAL(0, user_data.depth); // all closing tags have been found
TEST_ASSERT_EQUAL(strlen(test_expected), strlen(user_data.output));
TEST_ASSERT_EQUAL_STRING(test_expected, user_data.output);
}
/*
* SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <expat.h>
#include <string.h>
#include "unity.h"
typedef struct {
int depth;
char output[512];
int output_off;
} user_data_t;
static void insert_space(user_data_t *user_data)
{
const char align_str[] = " ";
TEST_ASSERT(sizeof(user_data->output) >= user_data->output_off);
user_data->output[user_data->output_off++] = '\n';
for (int i = 0; i < user_data->depth; i++) {
for (int j = 0; j < strlen(align_str); ++j) {
TEST_ASSERT(sizeof(user_data->output) >= user_data->output_off);
user_data->output[user_data->output_off++] = align_str[j];
}
}
}
static void XMLCALL start_element(void *userData, const XML_Char *name, const XML_Char **atts)
{
user_data_t *user_data = (user_data_t *) userData;
insert_space(user_data);
const int ret = snprintf(user_data->output + user_data->output_off,
sizeof(user_data->output) - user_data->output_off,
"<%s>", name);
TEST_ASSERT_EQUAL(strlen(name) + 2, ret); // 2 are the tag characters: "<>"
user_data->output_off += ret;
++user_data->depth;
}
static void XMLCALL end_element(void *userData, const XML_Char *name)
{
user_data_t *user_data = (user_data_t *) userData;
--user_data->depth;
insert_space(user_data);
int ret = snprintf(user_data->output + user_data->output_off, sizeof(user_data->output) - user_data->output_off,
"</%s>", name);
TEST_ASSERT_EQUAL(strlen(name) + 3, ret); // 3 are the tag characters: "</>"
user_data->output_off += ret;
}
static void data_handler(void *userData, const XML_Char *s, int len)
{
user_data_t *user_data = (user_data_t *) userData;
insert_space(user_data);
// s is not zero-terminated
char tmp_str[len + 1];
strlcpy(tmp_str, s, len + 1);
int ret = snprintf(user_data->output + user_data->output_off, sizeof(user_data->output) - user_data->output_off,
"%s", tmp_str);
TEST_ASSERT_EQUAL(strlen(tmp_str), ret);
user_data->output_off += ret;
}
TEST_CASE("Expat parses XML", "[expat]")
{
const char test_in[] = "<html><title>Page title</title><body><h>header</h><ol><li>A</li>"\
"<li>B</li><li>C</li></ol></body></html>";
const char test_expected[] = "\n"\
"<html>\n"\
" <title>\n"\
" Page title\n"\
" </title>\n"\
" <body>\n"\
" <h>\n"\
" header\n"\
" </h>\n"\
" <ol>\n"\
" <li>\n"\
" A\n"\
" </li>\n"\
" <li>\n"\
" B\n"\
" </li>\n"\
" <li>\n"\
" C\n"\
" </li>\n"\
" </ol>\n"\
" </body>\n"\
"</html>";
user_data_t user_data = {
.depth = 0,
.output = { '\0' },
.output_off = 0
};
XML_Parser parser = XML_ParserCreate(NULL);
XML_SetUserData(parser, &user_data);
XML_SetElementHandler(parser, start_element, end_element);
XML_SetCharacterDataHandler(parser, data_handler);
TEST_ASSERT_NOT_EQUAL(XML_STATUS_ERROR, XML_Parse(parser, test_in, strlen(test_in), 1));
XML_ParserFree(parser);
TEST_ASSERT_EQUAL(0, user_data.depth); // all closing tags have been found
TEST_ASSERT_EQUAL(strlen(test_expected), strlen(user_data.output));
TEST_ASSERT_EQUAL_STRING(test_expected, user_data.output);
}

View File

@ -1,106 +1,106 @@
/*
* Copyright (c) 2010 Serge A. Zaitsev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* @file jsmn.h
* @brief Definition of the JSMN (Jasmine) JSON parser.
*
* For more information on JSMN:
* @see http://zserge.com/jsmn.html
*/
#ifndef __JSMN_H_
#define __JSMN_H_
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* JSON type identifier. Basic types are:
* o Object
* o Array
* o String
* o Other primitive: number, boolean (true/false) or null
*/
typedef enum {
JSMN_UNDEFINED = 0,
JSMN_OBJECT = 1,
JSMN_ARRAY = 2,
JSMN_STRING = 3,
JSMN_PRIMITIVE = 4
} jsmntype_t;
enum jsmnerr {
/* Not enough tokens were provided */
JSMN_ERROR_NOMEM = -1,
/* Invalid character inside JSON string */
JSMN_ERROR_INVAL = -2,
/* The string is not a full JSON packet, more bytes expected */
JSMN_ERROR_PART = -3
};
/**
* JSON token description.
* @param type type (object, array, string etc.)
* @param start start position in JSON data string
* @param end end position in JSON data string
*/
typedef struct {
jsmntype_t type;
int start;
int end;
int size;
#ifdef JSMN_PARENT_LINKS
int parent;
#endif
} jsmntok_t;
/**
* JSON parser. Contains an array of token blocks available. Also stores
* the string being parsed now and current position in that string
*/
typedef struct {
unsigned int pos; /* offset in the JSON string */
unsigned int toknext; /* next token to allocate */
int toksuper; /* superior token node, e.g parent object or array */
} jsmn_parser;
/**
* Create JSON parser over an array of tokens
*/
void jsmn_init(jsmn_parser *parser);
/**
* Run JSON parser. It parses a JSON data string into and array of tokens, each describing
* a single JSON object.
*/
int jsmn_parse(jsmn_parser *parser, const char *js, size_t len,
jsmntok_t *tokens, unsigned int num_tokens);
#ifdef __cplusplus
}
#endif
#endif /* __JSMN_H_ */
/*
* Copyright (c) 2010 Serge A. Zaitsev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* @file jsmn.h
* @brief Definition of the JSMN (Jasmine) JSON parser.
*
* For more information on JSMN:
* @see http://zserge.com/jsmn.html
*/
#ifndef __JSMN_H_
#define __JSMN_H_
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* JSON type identifier. Basic types are:
* o Object
* o Array
* o String
* o Other primitive: number, boolean (true/false) or null
*/
typedef enum {
JSMN_UNDEFINED = 0,
JSMN_OBJECT = 1,
JSMN_ARRAY = 2,
JSMN_STRING = 3,
JSMN_PRIMITIVE = 4
} jsmntype_t;
enum jsmnerr {
/* Not enough tokens were provided */
JSMN_ERROR_NOMEM = -1,
/* Invalid character inside JSON string */
JSMN_ERROR_INVAL = -2,
/* The string is not a full JSON packet, more bytes expected */
JSMN_ERROR_PART = -3
};
/**
* JSON token description.
* @param type type (object, array, string etc.)
* @param start start position in JSON data string
* @param end end position in JSON data string
*/
typedef struct {
jsmntype_t type;
int start;
int end;
int size;
#ifdef JSMN_PARENT_LINKS
int parent;
#endif
} jsmntok_t;
/**
* JSON parser. Contains an array of token blocks available. Also stores
* the string being parsed now and current position in that string
*/
typedef struct {
unsigned int pos; /* offset in the JSON string */
unsigned int toknext; /* next token to allocate */
int toksuper; /* superior token node, e.g parent object or array */
} jsmn_parser;
/**
* Create JSON parser over an array of tokens
*/
void jsmn_init(jsmn_parser *parser);
/**
* Run JSON parser. It parses a JSON data string into and array of tokens, each describing
* a single JSON object.
*/
int jsmn_parse(jsmn_parser *parser, const char *js, size_t len,
jsmntok_t *tokens, unsigned int num_tokens);
#ifdef __cplusplus
}
#endif
#endif /* __JSMN_H_ */

View File

@ -1,340 +1,356 @@
/*
* Copyright (c) 2010 Serge A. Zaitsev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* @file jsmn.c
* @brief Implementation of the JSMN (Jasmine) JSON parser.
*
* For more information on JSMN:
* @see http://zserge.com/jsmn.html
*/
#include "jsmn.h"
/**
* Allocates a fresh unused token from the token pull.
*/
static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser,
jsmntok_t *tokens, size_t num_tokens) {
jsmntok_t *tok;
if (parser->toknext >= num_tokens) {
return NULL;
}
tok = &tokens[parser->toknext++];
tok->start = tok->end = -1;
tok->size = 0;
#ifdef JSMN_PARENT_LINKS
tok->parent = -1;
#endif
return tok;
}
/**
* Fills token type and boundaries.
*/
static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type,
int start, int end) {
token->type = type;
token->start = start;
token->end = end;
token->size = 0;
}
/**
* Fills next available token with JSON primitive.
*/
static int jsmn_parse_primitive(jsmn_parser *parser, const char *js,
size_t len, jsmntok_t *tokens, size_t num_tokens) {
jsmntok_t *token;
int start;
start = parser->pos;
for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
switch (js[parser->pos]) {
#ifndef JSMN_STRICT
/* In strict mode primitive must be followed by "," or "}" or "]" */
case ':':
#endif
case '\t' : case '\r' : case '\n' : case ' ' :
case ',' : case ']' : case '}' :
goto found;
}
if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
parser->pos = start;
return JSMN_ERROR_INVAL;
}
}
#ifdef JSMN_STRICT
/* In strict mode primitive must be followed by a comma/object/array */
parser->pos = start;
return JSMN_ERROR_PART;
#endif
found:
if (tokens == NULL) {
parser->pos--;
return 0;
}
token = jsmn_alloc_token(parser, tokens, num_tokens);
if (token == NULL) {
parser->pos = start;
return JSMN_ERROR_NOMEM;
}
jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
#ifdef JSMN_PARENT_LINKS
token->parent = parser->toksuper;
#endif
parser->pos--;
return 0;
}
/**
* Fills next token with JSON string.
*/
static int jsmn_parse_string(jsmn_parser *parser, const char *js,
size_t len, jsmntok_t *tokens, size_t num_tokens) {
jsmntok_t *token;
int start = parser->pos;
parser->pos++;
/* Skip starting quote */
for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
char c = js[parser->pos];
/* Quote: end of string */
if (c == '\"') {
if (tokens == NULL) {
return 0;
}
token = jsmn_alloc_token(parser, tokens, num_tokens);
if (token == NULL) {
parser->pos = start;
return JSMN_ERROR_NOMEM;
}
jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos);
#ifdef JSMN_PARENT_LINKS
token->parent = parser->toksuper;
#endif
return 0;
}
/* Backslash: Quoted symbol expected */
if (c == '\\' && parser->pos + 1 < len) {
int i;
parser->pos++;
switch (js[parser->pos]) {
/* Allowed escaped symbols */
case '\"': case '/' : case '\\' : case 'b' :
case 'f' : case 'r' : case 'n' : case 't' :
break;
/* Allows escaped symbol \uXXXX */
case 'u':
parser->pos++;
for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) {
/* If it isn't a hex character we have an error */
if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */
(js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */
(js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */
parser->pos = start;
return JSMN_ERROR_INVAL;
}
parser->pos++;
}
parser->pos--;
break;
/* Unexpected symbol */
default:
parser->pos = start;
return JSMN_ERROR_INVAL;
}
}
}
parser->pos = start;
return JSMN_ERROR_PART;
}
/**
* Parse JSON string and fill tokens.
*/
int jsmn_parse(jsmn_parser *parser, const char *js, size_t len,
jsmntok_t *tokens, unsigned int num_tokens) {
int r;
int i;
jsmntok_t *token;
int count = parser->toknext;
for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
char c;
jsmntype_t type;
c = js[parser->pos];
switch (c) {
case '{': case '[':
count++;
if (tokens == NULL) {
break;
}
token = jsmn_alloc_token(parser, tokens, num_tokens);
if (token == NULL)
return JSMN_ERROR_NOMEM;
if (parser->toksuper != -1) {
tokens[parser->toksuper].size++;
#ifdef JSMN_PARENT_LINKS
token->parent = parser->toksuper;
#endif
}
token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
token->start = parser->pos;
parser->toksuper = parser->toknext - 1;
break;
case '}': case ']':
if (tokens == NULL)
break;
type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
#ifdef JSMN_PARENT_LINKS
if (parser->toknext < 1) {
return JSMN_ERROR_INVAL;
}
token = &tokens[parser->toknext - 1];
for (;;) {
if (token->start != -1 && token->end == -1) {
if (token->type != type) {
return JSMN_ERROR_INVAL;
}
token->end = parser->pos + 1;
parser->toksuper = token->parent;
break;
}
if (token->parent == -1) {
break;
}
token = &tokens[token->parent];
}
#else
for (i = parser->toknext - 1; i >= 0; i--) {
token = &tokens[i];
if (token->start != -1 && token->end == -1) {
if (token->type != type) {
return JSMN_ERROR_INVAL;
}
parser->toksuper = -1;
token->end = parser->pos + 1;
break;
}
}
/* Error if unmatched closing bracket */
if (i == -1) return JSMN_ERROR_INVAL;
for (; i >= 0; i--) {
token = &tokens[i];
if (token->start != -1 && token->end == -1) {
parser->toksuper = i;
break;
}
}
#endif
break;
case '\"':
r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
if (r < 0) return r;
count++;
if (parser->toksuper != -1 && tokens != NULL)
tokens[parser->toksuper].size++;
break;
case '\t' : case '\r' : case '\n' : case ' ':
break;
case ':':
parser->toksuper = parser->toknext - 1;
break;
case ',':
if (tokens != NULL && parser->toksuper != -1 &&
tokens[parser->toksuper].type != JSMN_ARRAY &&
tokens[parser->toksuper].type != JSMN_OBJECT) {
#ifdef JSMN_PARENT_LINKS
parser->toksuper = tokens[parser->toksuper].parent;
#else
for (i = parser->toknext - 1; i >= 0; i--) {
if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) {
if (tokens[i].start != -1 && tokens[i].end == -1) {
parser->toksuper = i;
break;
}
}
}
#endif
}
break;
#ifdef JSMN_STRICT
/* In strict mode primitives are: numbers and booleans */
case '-': case '0': case '1' : case '2': case '3' : case '4':
case '5': case '6': case '7' : case '8': case '9':
case 't': case 'f': case 'n' :
/* And they must not be keys of the object */
if (tokens != NULL && parser->toksuper != -1) {
jsmntok_t *t = &tokens[parser->toksuper];
if (t->type == JSMN_OBJECT ||
(t->type == JSMN_STRING && t->size != 0)) {
return JSMN_ERROR_INVAL;
}
}
#else
/* In non-strict mode every unquoted value is a primitive */
default:
#endif
r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
if (r < 0) return r;
count++;
if (parser->toksuper != -1 && tokens != NULL)
tokens[parser->toksuper].size++;
break;
#ifdef JSMN_STRICT
/* Unexpected char in strict mode */
default:
return JSMN_ERROR_INVAL;
#endif
}
}
if (tokens != NULL) {
for (i = parser->toknext - 1; i >= 0; i--) {
/* Unmatched opened object or array */
if (tokens[i].start != -1 && tokens[i].end == -1) {
return JSMN_ERROR_PART;
}
}
}
return count;
}
/**
* Creates a new parser based over a given buffer with an array of tokens
* available.
*/
void jsmn_init(jsmn_parser *parser) {
parser->pos = 0;
parser->toknext = 0;
parser->toksuper = -1;
}
/*
* Copyright (c) 2010 Serge A. Zaitsev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* @file jsmn.c
* @brief Implementation of the JSMN (Jasmine) JSON parser.
*
* For more information on JSMN:
* @see http://zserge.com/jsmn.html
*/
#include "jsmn.h"
/**
* Allocates a fresh unused token from the token pull.
*/
static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser,
jsmntok_t *tokens, size_t num_tokens)
{
jsmntok_t *tok;
if (parser->toknext >= num_tokens) {
return NULL;
}
tok = &tokens[parser->toknext++];
tok->start = tok->end = -1;
tok->size = 0;
#ifdef JSMN_PARENT_LINKS
tok->parent = -1;
#endif
return tok;
}
/**
* Fills token type and boundaries.
*/
static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type,
int start, int end)
{
token->type = type;
token->start = start;
token->end = end;
token->size = 0;
}
/**
* Fills next available token with JSON primitive.
*/
static int jsmn_parse_primitive(jsmn_parser *parser, const char *js,
size_t len, jsmntok_t *tokens, size_t num_tokens)
{
jsmntok_t *token;
int start;
start = parser->pos;
for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
switch (js[parser->pos]) {
#ifndef JSMN_STRICT
/* In strict mode primitive must be followed by "," or "}" or "]" */
case ':':
#endif
case '\t' : case '\r' : case '\n' : case ' ' :
case ',' : case ']' : case '}' :
goto found;
}
if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
parser->pos = start;
return JSMN_ERROR_INVAL;
}
}
#ifdef JSMN_STRICT
/* In strict mode primitive must be followed by a comma/object/array */
parser->pos = start;
return JSMN_ERROR_PART;
#endif
found:
if (tokens == NULL) {
parser->pos--;
return 0;
}
token = jsmn_alloc_token(parser, tokens, num_tokens);
if (token == NULL) {
parser->pos = start;
return JSMN_ERROR_NOMEM;
}
jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
#ifdef JSMN_PARENT_LINKS
token->parent = parser->toksuper;
#endif
parser->pos--;
return 0;
}
/**
* Fills next token with JSON string.
*/
static int jsmn_parse_string(jsmn_parser *parser, const char *js,
size_t len, jsmntok_t *tokens, size_t num_tokens)
{
jsmntok_t *token;
int start = parser->pos;
parser->pos++;
/* Skip starting quote */
for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
char c = js[parser->pos];
/* Quote: end of string */
if (c == '\"') {
if (tokens == NULL) {
return 0;
}
token = jsmn_alloc_token(parser, tokens, num_tokens);
if (token == NULL) {
parser->pos = start;
return JSMN_ERROR_NOMEM;
}
jsmn_fill_token(token, JSMN_STRING, start + 1, parser->pos);
#ifdef JSMN_PARENT_LINKS
token->parent = parser->toksuper;
#endif
return 0;
}
/* Backslash: Quoted symbol expected */
if (c == '\\' && parser->pos + 1 < len) {
int i;
parser->pos++;
switch (js[parser->pos]) {
/* Allowed escaped symbols */
case '\"': case '/' : case '\\' : case 'b' :
case 'f' : case 'r' : case 'n' : case 't' :
break;
/* Allows escaped symbol \uXXXX */
case 'u':
parser->pos++;
for (i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) {
/* If it isn't a hex character we have an error */
if (!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */
(js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */
(js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */
parser->pos = start;
return JSMN_ERROR_INVAL;
}
parser->pos++;
}
parser->pos--;
break;
/* Unexpected symbol */
default:
parser->pos = start;
return JSMN_ERROR_INVAL;
}
}
}
parser->pos = start;
return JSMN_ERROR_PART;
}
/**
* Parse JSON string and fill tokens.
*/
int jsmn_parse(jsmn_parser *parser, const char *js, size_t len,
jsmntok_t *tokens, unsigned int num_tokens)
{
int r;
int i;
jsmntok_t *token;
int count = parser->toknext;
for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
char c;
jsmntype_t type;
c = js[parser->pos];
switch (c) {
case '{': case '[':
count++;
if (tokens == NULL) {
break;
}
token = jsmn_alloc_token(parser, tokens, num_tokens);
if (token == NULL) {
return JSMN_ERROR_NOMEM;
}
if (parser->toksuper != -1) {
tokens[parser->toksuper].size++;
#ifdef JSMN_PARENT_LINKS
token->parent = parser->toksuper;
#endif
}
token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
token->start = parser->pos;
parser->toksuper = parser->toknext - 1;
break;
case '}': case ']':
if (tokens == NULL) {
break;
}
type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
#ifdef JSMN_PARENT_LINKS
if (parser->toknext < 1) {
return JSMN_ERROR_INVAL;
}
token = &tokens[parser->toknext - 1];
for (;;) {
if (token->start != -1 && token->end == -1) {
if (token->type != type) {
return JSMN_ERROR_INVAL;
}
token->end = parser->pos + 1;
parser->toksuper = token->parent;
break;
}
if (token->parent == -1) {
break;
}
token = &tokens[token->parent];
}
#else
for (i = parser->toknext - 1; i >= 0; i--) {
token = &tokens[i];
if (token->start != -1 && token->end == -1) {
if (token->type != type) {
return JSMN_ERROR_INVAL;
}
parser->toksuper = -1;
token->end = parser->pos + 1;
break;
}
}
/* Error if unmatched closing bracket */
if (i == -1) {
return JSMN_ERROR_INVAL;
}
for (; i >= 0; i--) {
token = &tokens[i];
if (token->start != -1 && token->end == -1) {
parser->toksuper = i;
break;
}
}
#endif
break;
case '\"':
r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
if (r < 0) {
return r;
}
count++;
if (parser->toksuper != -1 && tokens != NULL) {
tokens[parser->toksuper].size++;
}
break;
case '\t' : case '\r' : case '\n' : case ' ':
break;
case ':':
parser->toksuper = parser->toknext - 1;
break;
case ',':
if (tokens != NULL && parser->toksuper != -1 &&
tokens[parser->toksuper].type != JSMN_ARRAY &&
tokens[parser->toksuper].type != JSMN_OBJECT) {
#ifdef JSMN_PARENT_LINKS
parser->toksuper = tokens[parser->toksuper].parent;
#else
for (i = parser->toknext - 1; i >= 0; i--) {
if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) {
if (tokens[i].start != -1 && tokens[i].end == -1) {
parser->toksuper = i;
break;
}
}
}
#endif
}
break;
#ifdef JSMN_STRICT
/* In strict mode primitives are: numbers and booleans */
case '-': case '0': case '1' : case '2': case '3' : case '4':
case '5': case '6': case '7' : case '8': case '9':
case 't': case 'f': case 'n' :
/* And they must not be keys of the object */
if (tokens != NULL && parser->toksuper != -1) {
jsmntok_t *t = &tokens[parser->toksuper];
if (t->type == JSMN_OBJECT ||
(t->type == JSMN_STRING && t->size != 0)) {
return JSMN_ERROR_INVAL;
}
}
#else
/* In non-strict mode every unquoted value is a primitive */
default:
#endif
r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
if (r < 0) {
return r;
}
count++;
if (parser->toksuper != -1 && tokens != NULL) {
tokens[parser->toksuper].size++;
}
break;
#ifdef JSMN_STRICT
/* Unexpected char in strict mode */
default:
return JSMN_ERROR_INVAL;
#endif
}
}
if (tokens != NULL) {
for (i = parser->toknext - 1; i >= 0; i--) {
/* Unmatched opened object or array */
if (tokens[i].start != -1 && tokens[i].end == -1) {
return JSMN_ERROR_PART;
}
}
}
return count;
}
/**
* Creates a new parser based over a given buffer with an array of tokens
* available.
*/
void jsmn_init(jsmn_parser *parser)
{
parser->pos = 0;
parser->toknext = 0;
parser->toksuper = -1;
}

View File

@ -1,124 +1,126 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "unity.h"
#include "sodium/crypto_hash_sha256.h"
#include "sodium/crypto_hash_sha512.h"
/* Note: a lot of these libsodium test programs assert() things, but they're not complete unit tests - most expect
output to be compared to the matching .exp file.
We don't do this automatically yet, maybe once we have more options for
internal filesystem storage.
*/
extern int aead_chacha20poly1305_xmain(void);
TEST_CASE("aead_chacha20poly1305 test vectors", "[libsodium]")
{
printf("Running aead_chacha20poly1305\n");
TEST_ASSERT_EQUAL(0, aead_chacha20poly1305_xmain());
}
extern int chacha20_xmain(void);
TEST_CASE("chacha20 test vectors", "[libsodium]")
{
printf("Running chacha20\n");
TEST_ASSERT_EQUAL(0, chacha20_xmain());
}
extern int box_xmain(void);
extern int box2_xmain(void);
TEST_CASE("box tests", "[libsodium]")
{
printf("Running box\n");
TEST_ASSERT_EQUAL(0, box_xmain());
printf("Running box2\n");
TEST_ASSERT_EQUAL(0, box2_xmain());
}
extern int ed25519_convert_xmain(void);
TEST_CASE("ed25519_convert tests", "[libsodium][timeout=60]")
{
printf("Running ed25519_convert\n");
TEST_ASSERT_EQUAL(0, ed25519_convert_xmain() );
}
extern int sign_xmain(void);
TEST_CASE("sign tests", "[libsodium]")
{
printf("Running sign\n");
TEST_ASSERT_EQUAL(0, sign_xmain() );
}
extern int hash_xmain(void);
TEST_CASE("hash tests", "[libsodium]")
{
printf("Running hash\n");
TEST_ASSERT_EQUAL(0, hash_xmain() );
}
TEST_CASE("sha256 sanity check", "[libsodium]")
{
const uint8_t expected[] = { 0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41,
0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23, 0xb0, 0x03,
0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff,
0x61, 0xf2, 0x00, 0x15, 0xad, };
uint8_t calculated[32];
crypto_hash_sha256_state state;
const uint8_t *in = (const uint8_t *)"abc";
const size_t inlen = 3;
// One-liner version
crypto_hash_sha256(calculated, in, inlen);
TEST_ASSERT_EQUAL(sizeof(calculated), sizeof(expected));
TEST_ASSERT_EQUAL(sizeof(calculated), crypto_hash_sha256_bytes());
TEST_ASSERT_EQUAL_MEMORY(expected, calculated, crypto_hash_sha256_bytes());
// Multi-line version
crypto_hash_sha256_init(&state);
crypto_hash_sha256_update(&state, in, inlen - 1); // split into two updates
crypto_hash_sha256_update(&state, in + (inlen -1), 1);
crypto_hash_sha256_final(&state, calculated);
TEST_ASSERT_EQUAL_MEMORY(expected, calculated, crypto_hash_sha256_bytes());
}
TEST_CASE("sha512 sanity check", "[libsodium]")
{
const uint8_t expected[] = { 0xdd, 0xaf, 0x35, 0xa1, 0x93, 0x61, 0x7a, 0xba, 0xcc,
0x41, 0x73, 0x49, 0xae, 0x20, 0x41, 0x31, 0x12, 0xe6,
0xfa, 0x4e, 0x89, 0xa9, 0x7e, 0xa2, 0x0a, 0x9e, 0xee,
0xe6, 0x4b, 0x55, 0xd3, 0x9a, 0x21, 0x92, 0x99, 0x2a,
0x27, 0x4f, 0xc1, 0xa8, 0x36, 0xba, 0x3c, 0x23, 0xa3,
0xfe, 0xeb, 0xbd, 0x45, 0x4d, 0x44, 0x23, 0x64, 0x3c,
0xe8, 0x0e, 0x2a, 0x9a, 0xc9, 0x4f, 0xa5, 0x4c, 0xa4,
0x9f };
uint8_t calculated[64];
crypto_hash_sha512_state state;
const uint8_t *in = (const uint8_t *)"abc";
const size_t inlen = 3;
// One-liner version
crypto_hash_sha512(calculated, in, inlen);
TEST_ASSERT_EQUAL(sizeof(calculated), sizeof(expected));
TEST_ASSERT_EQUAL(sizeof(calculated), crypto_hash_sha512_bytes());
TEST_ASSERT_EQUAL_MEMORY(expected, calculated, crypto_hash_sha512_bytes());
// Multi-line version
crypto_hash_sha512_init(&state);
crypto_hash_sha512_update(&state, in, inlen - 1); // split into two updates
crypto_hash_sha512_update(&state, in + (inlen -1), 1);
crypto_hash_sha512_final(&state, calculated);
TEST_ASSERT_EQUAL_MEMORY(expected, calculated, crypto_hash_sha512_bytes());
}
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "unity.h"
#include "sodium/crypto_hash_sha256.h"
#include "sodium/crypto_hash_sha512.h"
/* Note: a lot of these libsodium test programs assert() things, but they're not complete unit tests - most expect
output to be compared to the matching .exp file.
We don't do this automatically yet, maybe once we have more options for
internal filesystem storage.
*/
extern int aead_chacha20poly1305_xmain(void);
TEST_CASE("aead_chacha20poly1305 test vectors", "[libsodium]")
{
printf("Running aead_chacha20poly1305\n");
TEST_ASSERT_EQUAL(0, aead_chacha20poly1305_xmain());
}
extern int chacha20_xmain(void);
TEST_CASE("chacha20 test vectors", "[libsodium]")
{
printf("Running chacha20\n");
TEST_ASSERT_EQUAL(0, chacha20_xmain());
}
extern int box_xmain(void);
extern int box2_xmain(void);
TEST_CASE("box tests", "[libsodium]")
{
printf("Running box\n");
TEST_ASSERT_EQUAL(0, box_xmain());
printf("Running box2\n");
TEST_ASSERT_EQUAL(0, box2_xmain());
}
extern int ed25519_convert_xmain(void);
TEST_CASE("ed25519_convert tests", "[libsodium][timeout=60]")
{
printf("Running ed25519_convert\n");
TEST_ASSERT_EQUAL(0, ed25519_convert_xmain() );
}
extern int sign_xmain(void);
TEST_CASE("sign tests", "[libsodium]")
{
printf("Running sign\n");
TEST_ASSERT_EQUAL(0, sign_xmain() );
}
extern int hash_xmain(void);
TEST_CASE("hash tests", "[libsodium]")
{
printf("Running hash\n");
TEST_ASSERT_EQUAL(0, hash_xmain() );
}
TEST_CASE("sha256 sanity check", "[libsodium]")
{
const uint8_t expected[] = { 0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41,
0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23, 0xb0, 0x03,
0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff,
0x61, 0xf2, 0x00, 0x15, 0xad,
};
uint8_t calculated[32];
crypto_hash_sha256_state state;
const uint8_t *in = (const uint8_t *)"abc";
const size_t inlen = 3;
// One-liner version
crypto_hash_sha256(calculated, in, inlen);
TEST_ASSERT_EQUAL(sizeof(calculated), sizeof(expected));
TEST_ASSERT_EQUAL(sizeof(calculated), crypto_hash_sha256_bytes());
TEST_ASSERT_EQUAL_MEMORY(expected, calculated, crypto_hash_sha256_bytes());
// Multi-line version
crypto_hash_sha256_init(&state);
crypto_hash_sha256_update(&state, in, inlen - 1); // split into two updates
crypto_hash_sha256_update(&state, in + (inlen - 1), 1);
crypto_hash_sha256_final(&state, calculated);
TEST_ASSERT_EQUAL_MEMORY(expected, calculated, crypto_hash_sha256_bytes());
}
TEST_CASE("sha512 sanity check", "[libsodium]")
{
const uint8_t expected[] = { 0xdd, 0xaf, 0x35, 0xa1, 0x93, 0x61, 0x7a, 0xba, 0xcc,
0x41, 0x73, 0x49, 0xae, 0x20, 0x41, 0x31, 0x12, 0xe6,
0xfa, 0x4e, 0x89, 0xa9, 0x7e, 0xa2, 0x0a, 0x9e, 0xee,
0xe6, 0x4b, 0x55, 0xd3, 0x9a, 0x21, 0x92, 0x99, 0x2a,
0x27, 0x4f, 0xc1, 0xa8, 0x36, 0xba, 0x3c, 0x23, 0xa3,
0xfe, 0xeb, 0xbd, 0x45, 0x4d, 0x44, 0x23, 0x64, 0x3c,
0xe8, 0x0e, 0x2a, 0x9a, 0xc9, 0x4f, 0xa5, 0x4c, 0xa4,
0x9f
};
uint8_t calculated[64];
crypto_hash_sha512_state state;
const uint8_t *in = (const uint8_t *)"abc";
const size_t inlen = 3;
// One-liner version
crypto_hash_sha512(calculated, in, inlen);
TEST_ASSERT_EQUAL(sizeof(calculated), sizeof(expected));
TEST_ASSERT_EQUAL(sizeof(calculated), crypto_hash_sha512_bytes());
TEST_ASSERT_EQUAL_MEMORY(expected, calculated, crypto_hash_sha512_bytes());
// Multi-line version
crypto_hash_sha512_init(&state);
crypto_hash_sha512_update(&state, in, inlen - 1); // split into two updates
crypto_hash_sha512_update(&state, in + (inlen - 1), 1);
crypto_hash_sha512_final(&state, calculated);
TEST_ASSERT_EQUAL_MEMORY(expected, calculated, crypto_hash_sha512_bytes());
}

View File

@ -1,29 +1,29 @@
#ifndef __HAVE_CONFIG_H_
#define __HAVE_CONFIG_H_
#define _U_
#define SIZEOF_INT_P 2
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#if (!defined(nghttp_unlikely))
#define nghttp_unlikely(Expression) !!(Expression)
#endif
#define nghttp_ASSERT(Expression) do{if (!(Expression)) printf("%d\n", __LINE__);}while(0)
#define CU_ASSERT(a) nghttp_ASSERT(a)
#define CU_ASSERT_FATAL(a) nghttp_ASSERT(a)
#define NGHTTP_PLATFORM_HTONS(_n) ((uint16_t)((((_n) & 0xff) << 8) | (((_n) >> 8) & 0xff)))
#define NGHTTP_PLATFORM_HTONL(_n) ((uint32_t)( (((_n) & 0xff) << 24) | (((_n) & 0xff00) << 8) | (((_n) >> 8) & 0xff00) | (((_n) >> 24) & 0xff) ))
#define htons(x) NGHTTP_PLATFORM_HTONS(x)
#define ntohs(x) NGHTTP_PLATFORM_HTONS(x)
#define htonl(x) NGHTTP_PLATFORM_HTONL(x)
#define ntohl(x) NGHTTP_PLATFORM_HTONL(x)
#endif // __HAVE_CONFIG_H_
#ifndef __HAVE_CONFIG_H_
#define __HAVE_CONFIG_H_
#define _U_
#define SIZEOF_INT_P 2
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#if (!defined(nghttp_unlikely))
#define nghttp_unlikely(Expression) !!(Expression)
#endif
#define nghttp_ASSERT(Expression) do{if (!(Expression)) printf("%d\n", __LINE__);}while(0)
#define CU_ASSERT(a) nghttp_ASSERT(a)
#define CU_ASSERT_FATAL(a) nghttp_ASSERT(a)
#define NGHTTP_PLATFORM_HTONS(_n) ((uint16_t)((((_n) & 0xff) << 8) | (((_n) >> 8) & 0xff)))
#define NGHTTP_PLATFORM_HTONL(_n) ((uint32_t)( (((_n) & 0xff) << 24) | (((_n) & 0xff00) << 8) | (((_n) >> 8) & 0xff00) | (((_n) >> 24) & 0xff) ))
#define htons(x) NGHTTP_PLATFORM_HTONS(x)
#define ntohs(x) NGHTTP_PLATFORM_HTONS(x)
#define htonl(x) NGHTTP_PLATFORM_HTONL(x)
#define ntohl(x) NGHTTP_PLATFORM_HTONL(x)
#endif // __HAVE_CONFIG_H_

View File

@ -1,370 +1,370 @@
/*
* SPDX-FileCopyrightText: 2017-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <netdb.h>
#include <esp_log.h>
#include <http_parser.h>
#include "sh2lib.h"
static const char *TAG = "sh2lib";
#define DBG_FRAME_SEND 1
/*
* The implementation of nghttp2_send_callback type. Here we write
* |data| with size |length| to the network and return the number of
* bytes actually written. See the documentation of
* nghttp2_send_callback for the details.
*/
static ssize_t callback_send_inner(struct sh2lib_handle *hd, const uint8_t *data,
size_t length)
{
int rv = esp_tls_conn_write(hd->http2_tls, data, length);
if (rv <= 0) {
if (rv == ESP_TLS_ERR_SSL_WANT_READ || rv == ESP_TLS_ERR_SSL_WANT_WRITE) {
rv = NGHTTP2_ERR_WOULDBLOCK;
} else {
rv = NGHTTP2_ERR_CALLBACK_FAILURE;
}
}
return rv;
}
static ssize_t callback_send(nghttp2_session *session, const uint8_t *data,
size_t length, int flags, void *user_data)
{
int rv = 0;
struct sh2lib_handle *hd = user_data;
int copy_offset = 0;
int pending_data = length;
/* Send data in 1000 byte chunks */
while (copy_offset != length) {
int chunk_len = pending_data > 1000 ? 1000 : pending_data;
int subrv = callback_send_inner(hd, data + copy_offset, chunk_len);
if (subrv <= 0) {
if (copy_offset == 0) {
/* If no data is transferred, send the error code */
rv = subrv;
}
break;
}
copy_offset += subrv;
pending_data -= subrv;
rv += subrv;
}
return rv;
}
/*
* The implementation of nghttp2_recv_callback type. Here we read data
* from the network and write them in |buf|. The capacity of |buf| is
* |length| bytes. Returns the number of bytes stored in |buf|. See
* the documentation of nghttp2_recv_callback for the details.
*/
static ssize_t callback_recv(nghttp2_session *session, uint8_t *buf,
size_t length, int flags, void *user_data)
{
struct sh2lib_handle *hd = user_data;
int rv;
rv = esp_tls_conn_read(hd->http2_tls, (char *)buf, (int)length);
if (rv < 0) {
if (rv == ESP_TLS_ERR_SSL_WANT_READ || rv == ESP_TLS_ERR_SSL_WANT_WRITE) {
rv = NGHTTP2_ERR_WOULDBLOCK;
} else {
rv = NGHTTP2_ERR_CALLBACK_FAILURE;
}
} else if (rv == 0) {
rv = NGHTTP2_ERR_EOF;
}
return rv;
}
const char *sh2lib_frame_type_str(int type)
{
switch (type) {
case NGHTTP2_HEADERS:
return "HEADERS";
break;
case NGHTTP2_RST_STREAM:
return "RST_STREAM";
break;
case NGHTTP2_GOAWAY:
return "GOAWAY";
break;
case NGHTTP2_DATA:
return "DATA";
break;
case NGHTTP2_SETTINGS:
return "SETTINGS";
break;
case NGHTTP2_PUSH_PROMISE:
return "PUSH_PROMISE";
break;
case NGHTTP2_PING:
return "PING";
break;
default:
return "other";
break;
}
}
static int callback_on_frame_send(nghttp2_session *session,
const nghttp2_frame *frame, void *user_data)
{
ESP_LOGD(TAG, "[frame-send] frame type %s", sh2lib_frame_type_str(frame->hd.type));
switch (frame->hd.type) {
case NGHTTP2_HEADERS:
if (nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)) {
ESP_LOGD(TAG, "[frame-send] C ----------------------------> S (HEADERS)");
#if DBG_FRAME_SEND
ESP_LOGD(TAG, "[frame-send] headers nv-len = %d", frame->headers.nvlen);
const nghttp2_nv *nva = frame->headers.nva;
size_t i;
for (i = 0; i < frame->headers.nvlen; ++i) {
ESP_LOGD(TAG, "[frame-send] %s : %s", nva[i].name, nva[i].value);
}
#endif
}
break;
}
return 0;
}
static int callback_on_frame_recv(nghttp2_session *session,
const nghttp2_frame *frame, void *user_data)
{
ESP_LOGD(TAG, "[frame-recv][sid: %d] frame type %s", frame->hd.stream_id, sh2lib_frame_type_str(frame->hd.type));
if (frame->hd.type != NGHTTP2_DATA) {
return 0;
}
/* Subsequent processing only for data frame */
sh2lib_frame_data_recv_cb_t data_recv_cb = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
if (data_recv_cb) {
struct sh2lib_handle *h2 = user_data;
(*data_recv_cb)(h2, NULL, 0, DATA_RECV_FRAME_COMPLETE);
}
return 0;
}
static int callback_on_stream_close(nghttp2_session *session, int32_t stream_id,
uint32_t error_code, void *user_data)
{
ESP_LOGD(TAG, "[stream-close][sid %d]", stream_id);
sh2lib_frame_data_recv_cb_t data_recv_cb = nghttp2_session_get_stream_user_data(session, stream_id);
if (data_recv_cb) {
struct sh2lib_handle *h2 = user_data;
(*data_recv_cb)(h2, NULL, 0, DATA_RECV_RST_STREAM);
}
return 0;
}
static int callback_on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
int32_t stream_id, const uint8_t *data,
size_t len, void *user_data)
{
sh2lib_frame_data_recv_cb_t data_recv_cb;
ESP_LOGD(TAG, "[data-chunk][sid:%d]", stream_id);
data_recv_cb = nghttp2_session_get_stream_user_data(session, stream_id);
if (data_recv_cb) {
ESP_LOGD(TAG, "[data-chunk] C <---------------------------- S (DATA chunk)"
"%lu bytes",
(unsigned long int)len);
struct sh2lib_handle *h2 = user_data;
(*data_recv_cb)(h2, (char *)data, len, 0);
/* TODO: What to do with the return value: look for pause/abort */
}
return 0;
}
static int callback_on_header(nghttp2_session *session, const nghttp2_frame *frame,
const uint8_t *name, size_t namelen, const uint8_t *value,
size_t valuelen, uint8_t flags, void *user_data)
{
ESP_LOGD(TAG, "[hdr-recv][sid:%d] %s : %s", frame->hd.stream_id, name, value);
return 0;
}
static int do_http2_connect(struct sh2lib_handle *hd)
{
int ret;
nghttp2_session_callbacks *callbacks;
nghttp2_session_callbacks_new(&callbacks);
nghttp2_session_callbacks_set_send_callback(callbacks, callback_send);
nghttp2_session_callbacks_set_recv_callback(callbacks, callback_recv);
nghttp2_session_callbacks_set_on_frame_send_callback(callbacks, callback_on_frame_send);
nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, callback_on_frame_recv);
nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, callback_on_stream_close);
nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, callback_on_data_chunk_recv);
nghttp2_session_callbacks_set_on_header_callback(callbacks, callback_on_header);
ret = nghttp2_session_client_new(&hd->http2_sess, callbacks, hd);
if (ret != 0) {
ESP_LOGE(TAG, "[sh2-connect] New http2 session failed");
nghttp2_session_callbacks_del(callbacks);
return -1;
}
nghttp2_session_callbacks_del(callbacks);
/* Create the SETTINGS frame */
ret = nghttp2_submit_settings(hd->http2_sess, NGHTTP2_FLAG_NONE, NULL, 0);
if (ret != 0) {
ESP_LOGE(TAG, "[sh2-connect] Submit settings failed");
return -1;
}
return 0;
}
int sh2lib_connect(struct sh2lib_config_t *cfg, struct sh2lib_handle *hd)
{
memset(hd, 0, sizeof(*hd));
if (cfg == NULL) {
ESP_LOGE(TAG, "[sh2-connect] pointer to sh2lib configurations cannot be NULL");
goto error;
}
const char *proto[] = {"h2", NULL};
esp_tls_cfg_t tls_cfg = {
.alpn_protos = proto,
.cacert_buf = cfg->cacert_buf,
.cacert_bytes = cfg->cacert_bytes,
.non_block = true,
.timeout_ms = 10 * 1000,
};
if ((hd->http2_tls = esp_tls_conn_http_new(cfg->uri, &tls_cfg)) == NULL) {
ESP_LOGE(TAG, "[sh2-connect] esp-tls connection failed");
goto error;
}
struct http_parser_url u;
http_parser_url_init(&u);
http_parser_parse_url(cfg->uri, strlen(cfg->uri), 0, &u);
hd->hostname = strndup(&cfg->uri[u.field_data[UF_HOST].off], u.field_data[UF_HOST].len);
/* HTTP/2 Connection */
if (do_http2_connect(hd) != 0) {
ESP_LOGE(TAG, "[sh2-connect] HTTP2 Connection failed with %s", cfg->uri);
goto error;
}
return 0;
error:
sh2lib_free(hd);
return -1;
}
void sh2lib_free(struct sh2lib_handle *hd)
{
if (hd->http2_sess) {
nghttp2_session_del(hd->http2_sess);
hd->http2_sess = NULL;
}
if (hd->http2_tls) {
esp_tls_conn_destroy(hd->http2_tls);
hd->http2_tls = NULL;
}
if (hd->hostname) {
free(hd->hostname);
hd->hostname = NULL;
}
}
int sh2lib_execute(struct sh2lib_handle *hd)
{
int ret;
ret = nghttp2_session_send(hd->http2_sess);
if (ret != 0) {
ESP_LOGE(TAG, "[sh2-execute] HTTP2 session send failed %d", ret);
return -1;
}
ret = nghttp2_session_recv(hd->http2_sess);
if (ret != 0) {
ESP_LOGE(TAG, "[sh2-execute] HTTP2 session recv failed %d", ret);
return -1;
}
return 0;
}
int sh2lib_do_get_with_nv(struct sh2lib_handle *hd, const nghttp2_nv *nva, size_t nvlen, sh2lib_frame_data_recv_cb_t recv_cb)
{
int ret = nghttp2_submit_request(hd->http2_sess, NULL, nva, nvlen, NULL, recv_cb);
if (ret < 0) {
ESP_LOGE(TAG, "[sh2-do-get] HEADERS call failed");
return -1;
}
return ret;
}
int sh2lib_do_get(struct sh2lib_handle *hd, const char *path, sh2lib_frame_data_recv_cb_t recv_cb)
{
const nghttp2_nv nva[] = { SH2LIB_MAKE_NV(":method", "GET"),
SH2LIB_MAKE_NV(":scheme", "https"),
SH2LIB_MAKE_NV(":authority", hd->hostname),
SH2LIB_MAKE_NV(":path", path),
};
return sh2lib_do_get_with_nv(hd, nva, sizeof(nva) / sizeof(nva[0]), recv_cb);
}
ssize_t sh2lib_data_provider_cb(nghttp2_session *session, int32_t stream_id, uint8_t *buf,
size_t length, uint32_t *data_flags,
nghttp2_data_source *source, void *user_data)
{
struct sh2lib_handle *h2 = user_data;
sh2lib_putpost_data_cb_t data_cb = source->ptr;
return (*data_cb)(h2, (char *)buf, length, data_flags);
}
int sh2lib_do_putpost_with_nv(struct sh2lib_handle *hd, const nghttp2_nv *nva, size_t nvlen,
sh2lib_putpost_data_cb_t send_cb,
sh2lib_frame_data_recv_cb_t recv_cb)
{
nghttp2_data_provider sh2lib_data_provider;
sh2lib_data_provider.read_callback = sh2lib_data_provider_cb;
sh2lib_data_provider.source.ptr = send_cb;
int ret = nghttp2_submit_request(hd->http2_sess, NULL, nva, nvlen, &sh2lib_data_provider, recv_cb);
if (ret < 0) {
ESP_LOGE(TAG, "[sh2-do-putpost] HEADERS call failed");
return -1;
}
return ret;
}
int sh2lib_do_post(struct sh2lib_handle *hd, const char *path,
sh2lib_putpost_data_cb_t send_cb,
sh2lib_frame_data_recv_cb_t recv_cb)
{
const nghttp2_nv nva[] = { SH2LIB_MAKE_NV(":method", "POST"),
SH2LIB_MAKE_NV(":scheme", "https"),
SH2LIB_MAKE_NV(":authority", hd->hostname),
SH2LIB_MAKE_NV(":path", path),
};
return sh2lib_do_putpost_with_nv(hd, nva, sizeof(nva) / sizeof(nva[0]), send_cb, recv_cb);
}
int sh2lib_do_put(struct sh2lib_handle *hd, const char *path,
sh2lib_putpost_data_cb_t send_cb,
sh2lib_frame_data_recv_cb_t recv_cb)
{
const nghttp2_nv nva[] = { SH2LIB_MAKE_NV(":method", "PUT"),
SH2LIB_MAKE_NV(":scheme", "https"),
SH2LIB_MAKE_NV(":authority", hd->hostname),
SH2LIB_MAKE_NV(":path", path),
};
return sh2lib_do_putpost_with_nv(hd, nva, sizeof(nva) / sizeof(nva[0]), send_cb, recv_cb);
}
/*
* SPDX-FileCopyrightText: 2017-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <stdint.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <netdb.h>
#include <esp_log.h>
#include <http_parser.h>
#include "sh2lib.h"
static const char *TAG = "sh2lib";
#define DBG_FRAME_SEND 1
/*
* The implementation of nghttp2_send_callback type. Here we write
* |data| with size |length| to the network and return the number of
* bytes actually written. See the documentation of
* nghttp2_send_callback for the details.
*/
static ssize_t callback_send_inner(struct sh2lib_handle *hd, const uint8_t *data,
size_t length)
{
int rv = esp_tls_conn_write(hd->http2_tls, data, length);
if (rv <= 0) {
if (rv == ESP_TLS_ERR_SSL_WANT_READ || rv == ESP_TLS_ERR_SSL_WANT_WRITE) {
rv = NGHTTP2_ERR_WOULDBLOCK;
} else {
rv = NGHTTP2_ERR_CALLBACK_FAILURE;
}
}
return rv;
}
static ssize_t callback_send(nghttp2_session *session, const uint8_t *data,
size_t length, int flags, void *user_data)
{
int rv = 0;
struct sh2lib_handle *hd = user_data;
int copy_offset = 0;
int pending_data = length;
/* Send data in 1000 byte chunks */
while (copy_offset != length) {
int chunk_len = pending_data > 1000 ? 1000 : pending_data;
int subrv = callback_send_inner(hd, data + copy_offset, chunk_len);
if (subrv <= 0) {
if (copy_offset == 0) {
/* If no data is transferred, send the error code */
rv = subrv;
}
break;
}
copy_offset += subrv;
pending_data -= subrv;
rv += subrv;
}
return rv;
}
/*
* The implementation of nghttp2_recv_callback type. Here we read data
* from the network and write them in |buf|. The capacity of |buf| is
* |length| bytes. Returns the number of bytes stored in |buf|. See
* the documentation of nghttp2_recv_callback for the details.
*/
static ssize_t callback_recv(nghttp2_session *session, uint8_t *buf,
size_t length, int flags, void *user_data)
{
struct sh2lib_handle *hd = user_data;
int rv;
rv = esp_tls_conn_read(hd->http2_tls, (char *)buf, (int)length);
if (rv < 0) {
if (rv == ESP_TLS_ERR_SSL_WANT_READ || rv == ESP_TLS_ERR_SSL_WANT_WRITE) {
rv = NGHTTP2_ERR_WOULDBLOCK;
} else {
rv = NGHTTP2_ERR_CALLBACK_FAILURE;
}
} else if (rv == 0) {
rv = NGHTTP2_ERR_EOF;
}
return rv;
}
const char *sh2lib_frame_type_str(int type)
{
switch (type) {
case NGHTTP2_HEADERS:
return "HEADERS";
break;
case NGHTTP2_RST_STREAM:
return "RST_STREAM";
break;
case NGHTTP2_GOAWAY:
return "GOAWAY";
break;
case NGHTTP2_DATA:
return "DATA";
break;
case NGHTTP2_SETTINGS:
return "SETTINGS";
break;
case NGHTTP2_PUSH_PROMISE:
return "PUSH_PROMISE";
break;
case NGHTTP2_PING:
return "PING";
break;
default:
return "other";
break;
}
}
static int callback_on_frame_send(nghttp2_session *session,
const nghttp2_frame *frame, void *user_data)
{
ESP_LOGD(TAG, "[frame-send] frame type %s", sh2lib_frame_type_str(frame->hd.type));
switch (frame->hd.type) {
case NGHTTP2_HEADERS:
if (nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)) {
ESP_LOGD(TAG, "[frame-send] C ----------------------------> S (HEADERS)");
#if DBG_FRAME_SEND
ESP_LOGD(TAG, "[frame-send] headers nv-len = %d", frame->headers.nvlen);
const nghttp2_nv *nva = frame->headers.nva;
size_t i;
for (i = 0; i < frame->headers.nvlen; ++i) {
ESP_LOGD(TAG, "[frame-send] %s : %s", nva[i].name, nva[i].value);
}
#endif
}
break;
}
return 0;
}
static int callback_on_frame_recv(nghttp2_session *session,
const nghttp2_frame *frame, void *user_data)
{
ESP_LOGD(TAG, "[frame-recv][sid: %d] frame type %s", frame->hd.stream_id, sh2lib_frame_type_str(frame->hd.type));
if (frame->hd.type != NGHTTP2_DATA) {
return 0;
}
/* Subsequent processing only for data frame */
sh2lib_frame_data_recv_cb_t data_recv_cb = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
if (data_recv_cb) {
struct sh2lib_handle *h2 = user_data;
(*data_recv_cb)(h2, NULL, 0, DATA_RECV_FRAME_COMPLETE);
}
return 0;
}
static int callback_on_stream_close(nghttp2_session *session, int32_t stream_id,
uint32_t error_code, void *user_data)
{
ESP_LOGD(TAG, "[stream-close][sid %d]", stream_id);
sh2lib_frame_data_recv_cb_t data_recv_cb = nghttp2_session_get_stream_user_data(session, stream_id);
if (data_recv_cb) {
struct sh2lib_handle *h2 = user_data;
(*data_recv_cb)(h2, NULL, 0, DATA_RECV_RST_STREAM);
}
return 0;
}
static int callback_on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
int32_t stream_id, const uint8_t *data,
size_t len, void *user_data)
{
sh2lib_frame_data_recv_cb_t data_recv_cb;
ESP_LOGD(TAG, "[data-chunk][sid:%d]", stream_id);
data_recv_cb = nghttp2_session_get_stream_user_data(session, stream_id);
if (data_recv_cb) {
ESP_LOGD(TAG, "[data-chunk] C <---------------------------- S (DATA chunk)"
"%lu bytes",
(unsigned long int)len);
struct sh2lib_handle *h2 = user_data;
(*data_recv_cb)(h2, (char *)data, len, 0);
/* TODO: What to do with the return value: look for pause/abort */
}
return 0;
}
static int callback_on_header(nghttp2_session *session, const nghttp2_frame *frame,
const uint8_t *name, size_t namelen, const uint8_t *value,
size_t valuelen, uint8_t flags, void *user_data)
{
ESP_LOGD(TAG, "[hdr-recv][sid:%d] %s : %s", frame->hd.stream_id, name, value);
return 0;
}
static int do_http2_connect(struct sh2lib_handle *hd)
{
int ret;
nghttp2_session_callbacks *callbacks;
nghttp2_session_callbacks_new(&callbacks);
nghttp2_session_callbacks_set_send_callback(callbacks, callback_send);
nghttp2_session_callbacks_set_recv_callback(callbacks, callback_recv);
nghttp2_session_callbacks_set_on_frame_send_callback(callbacks, callback_on_frame_send);
nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, callback_on_frame_recv);
nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, callback_on_stream_close);
nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, callback_on_data_chunk_recv);
nghttp2_session_callbacks_set_on_header_callback(callbacks, callback_on_header);
ret = nghttp2_session_client_new(&hd->http2_sess, callbacks, hd);
if (ret != 0) {
ESP_LOGE(TAG, "[sh2-connect] New http2 session failed");
nghttp2_session_callbacks_del(callbacks);
return -1;
}
nghttp2_session_callbacks_del(callbacks);
/* Create the SETTINGS frame */
ret = nghttp2_submit_settings(hd->http2_sess, NGHTTP2_FLAG_NONE, NULL, 0);
if (ret != 0) {
ESP_LOGE(TAG, "[sh2-connect] Submit settings failed");
return -1;
}
return 0;
}
int sh2lib_connect(struct sh2lib_config_t *cfg, struct sh2lib_handle *hd)
{
memset(hd, 0, sizeof(*hd));
if (cfg == NULL) {
ESP_LOGE(TAG, "[sh2-connect] pointer to sh2lib configurations cannot be NULL");
goto error;
}
const char *proto[] = {"h2", NULL};
esp_tls_cfg_t tls_cfg = {
.alpn_protos = proto,
.cacert_buf = cfg->cacert_buf,
.cacert_bytes = cfg->cacert_bytes,
.non_block = true,
.timeout_ms = 10 * 1000,
};
if ((hd->http2_tls = esp_tls_conn_http_new(cfg->uri, &tls_cfg)) == NULL) {
ESP_LOGE(TAG, "[sh2-connect] esp-tls connection failed");
goto error;
}
struct http_parser_url u;
http_parser_url_init(&u);
http_parser_parse_url(cfg->uri, strlen(cfg->uri), 0, &u);
hd->hostname = strndup(&cfg->uri[u.field_data[UF_HOST].off], u.field_data[UF_HOST].len);
/* HTTP/2 Connection */
if (do_http2_connect(hd) != 0) {
ESP_LOGE(TAG, "[sh2-connect] HTTP2 Connection failed with %s", cfg->uri);
goto error;
}
return 0;
error:
sh2lib_free(hd);
return -1;
}
void sh2lib_free(struct sh2lib_handle *hd)
{
if (hd->http2_sess) {
nghttp2_session_del(hd->http2_sess);
hd->http2_sess = NULL;
}
if (hd->http2_tls) {
esp_tls_conn_destroy(hd->http2_tls);
hd->http2_tls = NULL;
}
if (hd->hostname) {
free(hd->hostname);
hd->hostname = NULL;
}
}
int sh2lib_execute(struct sh2lib_handle *hd)
{
int ret;
ret = nghttp2_session_send(hd->http2_sess);
if (ret != 0) {
ESP_LOGE(TAG, "[sh2-execute] HTTP2 session send failed %d", ret);
return -1;
}
ret = nghttp2_session_recv(hd->http2_sess);
if (ret != 0) {
ESP_LOGE(TAG, "[sh2-execute] HTTP2 session recv failed %d", ret);
return -1;
}
return 0;
}
int sh2lib_do_get_with_nv(struct sh2lib_handle *hd, const nghttp2_nv *nva, size_t nvlen, sh2lib_frame_data_recv_cb_t recv_cb)
{
int ret = nghttp2_submit_request(hd->http2_sess, NULL, nva, nvlen, NULL, recv_cb);
if (ret < 0) {
ESP_LOGE(TAG, "[sh2-do-get] HEADERS call failed");
return -1;
}
return ret;
}
int sh2lib_do_get(struct sh2lib_handle *hd, const char *path, sh2lib_frame_data_recv_cb_t recv_cb)
{
const nghttp2_nv nva[] = { SH2LIB_MAKE_NV(":method", "GET"),
SH2LIB_MAKE_NV(":scheme", "https"),
SH2LIB_MAKE_NV(":authority", hd->hostname),
SH2LIB_MAKE_NV(":path", path),
};
return sh2lib_do_get_with_nv(hd, nva, sizeof(nva) / sizeof(nva[0]), recv_cb);
}
ssize_t sh2lib_data_provider_cb(nghttp2_session *session, int32_t stream_id, uint8_t *buf,
size_t length, uint32_t *data_flags,
nghttp2_data_source *source, void *user_data)
{
struct sh2lib_handle *h2 = user_data;
sh2lib_putpost_data_cb_t data_cb = source->ptr;
return (*data_cb)(h2, (char *)buf, length, data_flags);
}
int sh2lib_do_putpost_with_nv(struct sh2lib_handle *hd, const nghttp2_nv *nva, size_t nvlen,
sh2lib_putpost_data_cb_t send_cb,
sh2lib_frame_data_recv_cb_t recv_cb)
{
nghttp2_data_provider sh2lib_data_provider;
sh2lib_data_provider.read_callback = sh2lib_data_provider_cb;
sh2lib_data_provider.source.ptr = send_cb;
int ret = nghttp2_submit_request(hd->http2_sess, NULL, nva, nvlen, &sh2lib_data_provider, recv_cb);
if (ret < 0) {
ESP_LOGE(TAG, "[sh2-do-putpost] HEADERS call failed");
return -1;
}
return ret;
}
int sh2lib_do_post(struct sh2lib_handle *hd, const char *path,
sh2lib_putpost_data_cb_t send_cb,
sh2lib_frame_data_recv_cb_t recv_cb)
{
const nghttp2_nv nva[] = { SH2LIB_MAKE_NV(":method", "POST"),
SH2LIB_MAKE_NV(":scheme", "https"),
SH2LIB_MAKE_NV(":authority", hd->hostname),
SH2LIB_MAKE_NV(":path", path),
};
return sh2lib_do_putpost_with_nv(hd, nva, sizeof(nva) / sizeof(nva[0]), send_cb, recv_cb);
}
int sh2lib_do_put(struct sh2lib_handle *hd, const char *path,
sh2lib_putpost_data_cb_t send_cb,
sh2lib_frame_data_recv_cb_t recv_cb)
{
const nghttp2_nv nva[] = { SH2LIB_MAKE_NV(":method", "PUT"),
SH2LIB_MAKE_NV(":scheme", "https"),
SH2LIB_MAKE_NV(":authority", hd->hostname),
SH2LIB_MAKE_NV(":path", path),
};
return sh2lib_do_putpost_with_nv(hd, nva, sizeof(nva) / sizeof(nva[0]), send_cb, recv_cb);
}