esp_http.c 8.26 KB
Newer Older
Jacob Priddy's avatar
Jacob Priddy committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
#include <esp_http_client.h>
#include <esp_log.h>
#include <esp_tls.h>
#include <esp_partition.h>
#include <esp_https_ota.h>
#include <esp_ota_ops.h>
#include "app/event.h"
#include "config.h"
#include "connectors/http.h"

#define HTTP_TOKEN_BEARER_KEY "Authorization"
#define HTTP_TOKEN_BEARER_STRING  "Bearer "
#define HTTP_HEADER_VERSION_KEY "Door-Controller-Version"
#define HTTP_TOKEN_GET_PARAM_PREFIX "?api_token="

static const char* TAG = "DOOR HTTP";
static const char* UTAG = "DOOR HTTP UPDATE";

19 20 21
// TODO: this module needs to be completely separate from any
// 	static storage... so figure out how to do callbacks without
//		this
Jacob Priddy's avatar
Jacob Priddy committed
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
static door_http_callback_t* current_request_callbacks = NULL;

/**
 * This function assumes that only one request is placed at a time.
 * This is fine currently as I am using blocking calls to get data,
 * but if this is ever changed the way this function works will also
 * need to be changed.
 *
 * @brief Event handler for the ESP http event handler
 * @return
 */
static esp_err_t http_event_handler(esp_http_client_event_t* evt)
{
	switch (evt->event_id)
	{
	case HTTP_EVENT_ERROR:
		ESP_LOGD(TAG, "HTTP_EVENT_ERROR");
		break;
	case HTTP_EVENT_ON_CONNECTED:
		ESP_LOGD(TAG, "HTTP_EVENT_ON_CONNECTED");
		break;
	case HTTP_EVENT_HEADER_SENT:
		ESP_LOGD(TAG, "HTTP_EVENT_HEADER_SENT");
		break;
	case HTTP_EVENT_ON_HEADER:
		ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value);
		break;
	case HTTP_EVENT_ON_DATA:
		ESP_LOGD(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
		ESP_LOGD(TAG, "HTTP_EVENT_ON_DATA, data: %.*s", evt->data_len, (char*)evt->data);
52 53
		if (current_request_callbacks && current_request_callbacks->on_data)
			if (!current_request_callbacks->on_data(evt->data, evt->data_len))
Jacob Priddy's avatar
Jacob Priddy committed
54 55 56 57
				return ESP_FAIL;
		break;
	case HTTP_EVENT_ON_FINISH:
		ESP_LOGD(TAG, "HTTP_EVENT_ON_FINISH");
58 59
		if (current_request_callbacks && current_request_callbacks->on_finish)
			current_request_callbacks->on_finish(esp_http_client_get_status_code(evt->client));
Jacob Priddy's avatar
Jacob Priddy committed
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
		break;
	case HTTP_EVENT_DISCONNECTED:
		ESP_LOGD(TAG, "HTTP_EVENT_DISCONNECTED");
		int mbedtls_err = 0;
		esp_err_t err = esp_tls_get_and_clear_last_error(evt->data, &mbedtls_err, NULL);
		if (err != 0)
		{
			ESP_LOGI(TAG, "Last esp error code: 0x%x", err);
			ESP_LOGI(TAG, "Last mbedtls failure: 0x%x", mbedtls_err);
		}
		break;
	default:
		break;
	}

	return ESP_OK;
}

78
extern const char door_root_cert_start[] asm("_binary_doorcode_root_cert_cert_start");
Jacob Priddy's avatar
Jacob Priddy committed
79 80 81 82 83 84 85 86

static esp_http_client_config_t http_base_config;

static void door_http_fill_config(esp_http_client_config_t* config)
{
	config->host = API_HOST;
	config->port = API_PORT;
	config->timeout_ms = API_TIMEOUT_MS;
87
	config->cert_pem = door_root_cert_start;
Jacob Priddy's avatar
Jacob Priddy committed
88 89 90 91 92 93 94 95 96 97 98 99
#ifdef API_USE_SSL
	config->transport_type = HTTP_TRANSPORT_OVER_SSL;
#ifdef API_NO_NAME_VERIFY
	config->skip_cert_common_name_check = true;
#else
	config->skip_cert_common_name_check = false;
#endif
#else
	config->transport_type = HTTP_TRANSPORT_OVER_TCP;
#endif
}

100
door_http_client_handle_t door_http_create_client(const char* token)
Jacob Priddy's avatar
Jacob Priddy committed
101 102 103 104
{
	char* token_string;
	const esp_partition_t *running;
	esp_app_desc_t running_app_info;
105
	door_http_client_handle_t client;
Jacob Priddy's avatar
Jacob Priddy committed
106 107 108 109 110 111 112 113 114

	door_http_fill_config(&http_base_config);
	http_base_config.path = API_BASE_PATH;
	http_base_config.event_handler = http_event_handler;

	client = esp_http_client_init(&http_base_config);
	if (!client)
	{
		ESP_LOGE(TAG, "Failed to initialize the esp http client");
115
		return NULL;
Jacob Priddy's avatar
Jacob Priddy committed
116 117 118 119
	}

	// sizeof includes a null index so I don't need to + 1
	token_string = malloc(strlen(token) + sizeof(HTTP_TOKEN_BEARER_STRING));
120 121 122 123
	if (!token_string) {
		ESP_LOGE(TAG, "Could not allocate memory for token_string");
		return NULL;
	}
Jacob Priddy's avatar
Jacob Priddy committed
124

125
	// Set bearer token header
Jacob Priddy's avatar
Jacob Priddy committed
126 127 128
	*token_string = 0x00;
	strcat(token_string, HTTP_TOKEN_BEARER_STRING);
	strcat(token_string, token);
129 130
	esp_http_client_set_header(client, HTTP_TOKEN_BEARER_KEY, token_string);
	free(token_string);
Jacob Priddy's avatar
Jacob Priddy committed
131

132
	// set version header
Jacob Priddy's avatar
Jacob Priddy committed
133 134 135 136 137
	running = esp_ota_get_running_partition();
	if (esp_ota_get_partition_description(running, &running_app_info) == ESP_OK) {
		esp_http_client_set_header(client, HTTP_HEADER_VERSION_KEY, running_app_info.version);
	}

138
	return client;
Jacob Priddy's avatar
Jacob Priddy committed
139 140
}

141
void door_http_destroy_client_handle(door_http_client_handle_t client)
Jacob Priddy's avatar
Jacob Priddy committed
142 143 144 145 146 147 148 149
{
	if (client)
	{
		esp_http_client_close(client);
		esp_http_client_cleanup(client);
	}
}

150
int door_http_get(door_http_client_handle_t client, const char* path, door_http_callback_t* callbacks)
Jacob Priddy's avatar
Jacob Priddy committed
151
{
152
	esp_http_client_handle_t handle = (esp_http_client_handle_t)client;
Jacob Priddy's avatar
Jacob Priddy committed
153
	ESP_LOGI(TAG, "DOOR_HTTP_SET: sending GET request to: %s", path);
154
	if (!door_rtos_event_get(WIFI_CONNECTED_EVENT) || !handle)
Jacob Priddy's avatar
Jacob Priddy committed
155 156 157 158 159
	{
		ESP_LOGI(TAG, "Not connected to wifi or not initialized");
		return DOOR_HTTP_ERR_FAIL;
	}

160 161 162 163
	current_request_callbacks = callbacks;
	esp_http_client_set_url(handle, path);
	esp_http_client_set_method(handle, HTTP_METHOD_GET);
	if (esp_http_client_perform(handle) != ESP_OK)
Jacob Priddy's avatar
Jacob Priddy committed
164
	{
165
		return DOOR_HTTP_ERR_FAIL;
Jacob Priddy's avatar
Jacob Priddy committed
166
	}
167 168 169
	int status_code = esp_http_client_get_status_code(handle);
	ESP_LOGI(TAG, "Status code from get call: %d", status_code);
	return status_code;
Jacob Priddy's avatar
Jacob Priddy committed
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
}

static esp_err_t door_http_ota_update_validate_image_header(esp_app_desc_t *new_app_info)
{
	if (new_app_info == NULL) {
		return ESP_ERR_INVALID_ARG;
	}

	const esp_partition_t *running = esp_ota_get_running_partition();
	esp_app_desc_t running_app_info;
	if (esp_ota_get_partition_description(running, &running_app_info) == ESP_OK) {
		ESP_LOGI(UTAG, "Running firmware version: %s", running_app_info.version);
	}

#ifndef UPDATE_SKIP_VERSION_CHECK
	if (memcmp(new_app_info->version, running_app_info.version, sizeof(new_app_info->version)) == 0) {
		ESP_LOGW(UTAG, "Current running version is the same as a new. We will not continue the update.");
		return ESP_FAIL;
	}
#endif

	return ESP_OK;
}

bool door_http_ota_update(const char* api_token)
{
	char* token_get;
	esp_err_t ota_finish_err;
	esp_http_client_config_t config = {0};
	ESP_LOGI(UTAG, "Beginning OTA update");
200
	if (!door_rtos_event_get(WIFI_CONNECTED_EVENT))
Jacob Priddy's avatar
Jacob Priddy committed
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
	{
		ESP_LOGI(UTAG, "Not connected to wifi");
		return false;
	}
	door_http_fill_config(&config);
	// Have to set the API_TOKEN as a get parameter because I don't know how to set headers on the ota https client
	// sizeof includes a null index so I don't need to + 1
	token_get = malloc(sizeof(HTTP_TOKEN_GET_PARAM_PREFIX) + strlen(api_token) + sizeof(API_UPDATE_URL) - 1);
	if (!token_get)
	{
		ESP_LOGE(UTAG, "ESP HTTPS OTA memory allocation failed");
		return false;
	}

	*token_get = 0x00;
	strcat(token_get, API_UPDATE_URL);
	strcat(token_get, HTTP_TOKEN_GET_PARAM_PREFIX);
	strcat(token_get, api_token);

	config.path = token_get;
	esp_https_ota_config_t ota_config = {
		.http_config = &config,
	};

	esp_https_ota_handle_t https_ota_handle = NULL;
	esp_err_t err = esp_https_ota_begin(&ota_config, &https_ota_handle);
	free(token_get);
	if (err != ESP_OK) {
		ESP_LOGE(UTAG, "ESP HTTPS OTA Begin failed");
		return false;
	}

	esp_app_desc_t app_desc;
	err = esp_https_ota_get_img_desc(https_ota_handle, &app_desc);
	if (err != ESP_OK) {
		ESP_LOGE(UTAG, "esp_https_ota_read_img_desc failed");
		goto ota_end;
	}
	err = door_http_ota_update_validate_image_header(&app_desc);
	if (err != ESP_OK) {
		ESP_LOGE(UTAG, "image header verification failed");
		goto ota_end;
	}

	while (1) {
		err = esp_https_ota_perform(https_ota_handle);
		if (err != ESP_ERR_HTTPS_OTA_IN_PROGRESS) {
			break;
		}
		// esp_https_ota_perform returns after every read operation which gives user the ability to
		// monitor the status of OTA upgrade by calling esp_https_ota_get_image_len_read, which gives length of image
		// data read so far.
		ESP_LOGD(UTAG, "Image bytes read: %d", esp_https_ota_get_image_len_read(https_ota_handle));
	}

	if (esp_https_ota_is_complete_data_received(https_ota_handle) != true) {
		// the OTA image was not completely received and user can customise the response to this situation.
		ESP_LOGE(UTAG, "Complete data was not received.");
	}

ota_end:
	ota_finish_err = esp_https_ota_finish(https_ota_handle);
	if ((err == ESP_OK) && (ota_finish_err == ESP_OK)) {
		ESP_LOGI(UTAG, "ESP_HTTPS_OTA upgrade successful.");
		return true;
	} else {
		if (ota_finish_err == ESP_ERR_OTA_VALIDATE_FAILED) {
			ESP_LOGE(UTAG, "Image validation failed, image is corrupted");
		}
		ESP_LOGE(UTAG, "ESP_HTTPS_OTA upgrade failed %d", ota_finish_err);
	}
	return false;
}