mirror of
https://github.com/Lurkars/esp-ena.git
synced 2024-11-25 11:16:12 +01:00
422 lines
17 KiB
C
422 lines
17 KiB
C
// Copyright 2020 Lukas Haubaum
|
|
//
|
|
// Licensed under the GNU Affero General Public License, Version 3;
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
|
|
// https://www.gnu.org/licenses/agpl-3.0.html
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
#include <string.h>
|
|
#include "time.h"
|
|
#include "esp_log.h"
|
|
#include "esp_event.h"
|
|
#include "esp_http_client.h"
|
|
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
|
|
#include "ena-crypto.h"
|
|
#include "ena-storage.h"
|
|
#include "ena-exposure.h"
|
|
#include "wifi-controller.h"
|
|
|
|
#include "ena-eke-proxy.h"
|
|
|
|
#define HOUR_IN_SECONDS (60 * 60)
|
|
#define DAY_IN_SECONDS (HOUR_IN_SECONDS * 24)
|
|
|
|
extern const uint8_t cert_pem_start[] asm("_binary_cert_pem_start");
|
|
extern const uint8_t cert_pem_end[] asm("_binary_cert_pem_end");
|
|
|
|
static size_t current_page = 0;
|
|
static time_t request_sleep = 0;
|
|
static uint32_t request_sleep_waiting = 30;
|
|
static time_t last_check = 0;
|
|
static bool wait_for_request = false;
|
|
static bool request_pause = false;
|
|
|
|
void ena_eke_proxy_pause(void)
|
|
{
|
|
while (wait_for_request || request_pause)
|
|
{
|
|
ESP_LOGD(ENA_EKE_PROXY_LOG, "waiting for other requests to finish...");
|
|
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
|
}
|
|
|
|
request_pause = true;
|
|
}
|
|
|
|
void ena_eke_proxy_resume(void)
|
|
{
|
|
request_pause = false;
|
|
}
|
|
|
|
esp_err_t ena_eke_proxy_fetch_event_handler(esp_http_client_event_t *evt)
|
|
{
|
|
static uint8_t *output_buffer;
|
|
static int output_len;
|
|
switch (evt->event_id)
|
|
{
|
|
case HTTP_EVENT_ON_DATA:
|
|
if (!esp_http_client_is_chunked_response(evt->client))
|
|
{
|
|
if (output_buffer == NULL)
|
|
{
|
|
output_buffer = malloc(esp_http_client_get_content_length(evt->client));
|
|
output_len = 0;
|
|
if (output_buffer == NULL)
|
|
{
|
|
ESP_LOGE(ENA_EKE_PROXY_LOG, "Failed to allocate memory for output buffer, memory: %d kB", (xPortGetFreeHeapSize() / 1024));
|
|
return ESP_FAIL;
|
|
}
|
|
}
|
|
memcpy(output_buffer + output_len, evt->data, evt->data_len);
|
|
output_len += evt->data_len;
|
|
}
|
|
|
|
break;
|
|
case HTTP_EVENT_ON_FINISH:
|
|
if (output_buffer != NULL && esp_http_client_get_status_code(evt->client) == 200)
|
|
{
|
|
|
|
if (output_len % 28 != 0)
|
|
{
|
|
ESP_LOGW(ENA_EKE_PROXY_LOG, "Response length does not match key size! %d", output_len);
|
|
}
|
|
|
|
size_t temporary_exposure_keys = (output_len / 28);
|
|
|
|
if (temporary_exposure_keys > 0)
|
|
{
|
|
uint32_t start_time = (uint32_t)time(NULL);
|
|
ena_temporary_exposure_key_t temporary_exposure_key;
|
|
int tek_index = 0;
|
|
|
|
memcpy(&(temporary_exposure_key.key_data), &output_buffer[tek_index * 28], ENA_KEY_LENGTH);
|
|
memcpy(&(temporary_exposure_key.rolling_start_interval_number), &output_buffer[tek_index * 28 + ENA_KEY_LENGTH], 4);
|
|
memcpy(&(temporary_exposure_key.rolling_period), &output_buffer[tek_index * 28 + ENA_KEY_LENGTH + 4], 4);
|
|
memcpy(&(temporary_exposure_key.days_since_onset_of_symptoms), &output_buffer[tek_index * 28 + ENA_KEY_LENGTH + 8], 4);
|
|
|
|
uint32_t timestamp_start = temporary_exposure_key.rolling_start_interval_number * ENA_TIME_WINDOW;
|
|
int min = ena_expore_check_find_min(timestamp_start);
|
|
|
|
tek_index = temporary_exposure_keys - 1;
|
|
memcpy(&(temporary_exposure_key.key_data), &output_buffer[tek_index * 28], ENA_KEY_LENGTH);
|
|
memcpy(&(temporary_exposure_key.rolling_start_interval_number), &output_buffer[tek_index * 28 + ENA_KEY_LENGTH], 4);
|
|
memcpy(&(temporary_exposure_key.rolling_period), &output_buffer[tek_index * 28 + ENA_KEY_LENGTH + 4], 4);
|
|
memcpy(&(temporary_exposure_key.days_since_onset_of_symptoms), &output_buffer[tek_index * 28 + ENA_KEY_LENGTH + 8], 4);
|
|
|
|
uint32_t timestamp_end = (temporary_exposure_key.rolling_start_interval_number + temporary_exposure_key.rolling_period) * ENA_TIME_WINDOW;
|
|
int max = ena_expore_check_find_max(timestamp_end);
|
|
|
|
if (min >= 0 && max >= 0 && min <= max)
|
|
{
|
|
ESP_LOGI(ENA_EKE_PROXY_LOG, "start check with beacons [%d,%d] for [%u,%u]", min, max, timestamp_start, timestamp_end);
|
|
ena_beacon_t beacon;
|
|
for (int y = min; y <= max; y++)
|
|
{
|
|
ena_storage_get_beacon(y, &beacon);
|
|
for (int i = 0; i < temporary_exposure_keys; i++)
|
|
{
|
|
memcpy(&(temporary_exposure_key.key_data), &output_buffer[i * 28], ENA_KEY_LENGTH);
|
|
memcpy(&(temporary_exposure_key.rolling_start_interval_number), &output_buffer[i * 28 + ENA_KEY_LENGTH], 4);
|
|
memcpy(&(temporary_exposure_key.rolling_period), &output_buffer[i * 28 + ENA_KEY_LENGTH + 4], 4);
|
|
memcpy(&(temporary_exposure_key.days_since_onset_of_symptoms), &output_buffer[i * 28 + ENA_KEY_LENGTH + 8], 4);
|
|
#ifdef DEBUG_ENA_EKE_PROXY
|
|
ESP_LOGD(ENA_EKE_PROXY_LOG, "key payload: ");
|
|
ESP_LOG_BUFFER_HEXDUMP(ENA_EKE_PROXY_LOG, &output_buffer[i * 28], 28, ESP_LOG_DEBUG);
|
|
ESP_LOGD(ENA_EKE_PROXY_LOG, "received key: ");
|
|
ESP_LOG_BUFFER_HEXDUMP(ENA_EKE_PROXY_LOG, &(temporary_exposure_key.key_data), ENA_KEY_LENGTH, ESP_LOG_DEBUG);
|
|
ESP_LOG_BUFFER_HEXDUMP(ENA_EKE_PROXY_LOG, &output_buffer[i * 28], ENA_KEY_LENGTH, ESP_LOG_DEBUG);
|
|
ESP_LOGD(ENA_EKE_PROXY_LOG, "rolling_start_interval_number %u", temporary_exposure_key.rolling_start_interval_number);
|
|
ESP_LOG_BUFFER_HEXDUMP(ENA_EKE_PROXY_LOG, &output_buffer[i * 28 + ENA_KEY_LENGTH], 4, ESP_LOG_DEBUG);
|
|
ESP_LOGD(ENA_EKE_PROXY_LOG, "rolling_period %u", temporary_exposure_key.rolling_period);
|
|
ESP_LOG_BUFFER_HEXDUMP(ENA_EKE_PROXY_LOG, &output_buffer[i * 28 + ENA_KEY_LENGTH + 4], 4, ESP_LOG_DEBUG);
|
|
ESP_LOGD(ENA_EKE_PROXY_LOG, "days_since_onset_of_symptoms %u", temporary_exposure_key.days_since_onset_of_symptoms);
|
|
ESP_LOG_BUFFER_HEXDUMP(ENA_EKE_PROXY_LOG, &output_buffer[i * 28 + ENA_KEY_LENGTH + 8], 4, ESP_LOG_DEBUG);
|
|
#endif
|
|
ena_exposure_check(beacon, temporary_exposure_key);
|
|
}
|
|
}
|
|
uint32_t end_time = (uint32_t)time(NULL);
|
|
ESP_LOGI(ENA_EKE_PROXY_LOG, "check took %u seconds", (end_time - start_time));
|
|
}
|
|
else
|
|
{
|
|
ESP_LOGD(ENA_EKE_PROXY_LOG, "no matching beacons for [%u,%u]", timestamp_start, timestamp_end);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ESP_LOGW(ENA_EKE_PROXY_LOG, "no keys in request, should not happen on 200 status!");
|
|
}
|
|
|
|
current_page = current_page + 1;
|
|
free(output_buffer);
|
|
}
|
|
else if (esp_http_client_get_status_code(evt->client) == 204)
|
|
{
|
|
// finished!
|
|
if (difftime(time(NULL), last_check) >= DAY_IN_SECONDS)
|
|
{
|
|
last_check = last_check + DAY_IN_SECONDS;
|
|
}
|
|
else
|
|
{
|
|
last_check = last_check + HOUR_IN_SECONDS;
|
|
}
|
|
ena_storage_write_last_exposure_date(last_check);
|
|
current_page = 0;
|
|
request_sleep = 0;
|
|
request_sleep_waiting = 30;
|
|
ena_exposure_summary(ena_exposure_default_config());
|
|
|
|
ena_exposure_summary_t *current_summary = ena_exposure_current_summary();
|
|
ESP_LOGD(ENA_EKE_PROXY_LOG, "current summary\nlast update: %u\ndays_since_last_exposure: %d\nnum_exposures: %d\nmax_risk_score: %d\nrisk_score_sum: %d",
|
|
current_summary->last_update,
|
|
current_summary->days_since_last_exposure,
|
|
current_summary->num_exposures,
|
|
current_summary->max_risk_score,
|
|
current_summary->risk_score_sum);
|
|
}
|
|
else
|
|
{
|
|
current_page = 0;
|
|
request_sleep = time(NULL) + request_sleep_waiting;
|
|
if (request_sleep_waiting < HOUR_IN_SECONDS)
|
|
{
|
|
request_sleep_waiting = request_sleep_waiting * 3;
|
|
}
|
|
}
|
|
|
|
output_buffer = NULL;
|
|
output_len = 0;
|
|
wait_for_request = false;
|
|
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t ena_eke_proxy_receive_keys(char *url)
|
|
{
|
|
static int retries = 0;
|
|
wait_for_request = true;
|
|
esp_http_client_config_t config = {
|
|
.url = url,
|
|
.timeout_ms = 30000,
|
|
.event_handler = ena_eke_proxy_fetch_event_handler,
|
|
};
|
|
|
|
if (memcmp(url, "https", 5) == 0)
|
|
{
|
|
config.cert_pem = (char *)cert_pem_start;
|
|
}
|
|
|
|
ESP_LOGD(ENA_EKE_PROXY_LOG, "start request: url = %s | memory: %d kB", url, (xPortGetFreeHeapSize() / 1024));
|
|
esp_http_client_handle_t client = esp_http_client_init(&config);
|
|
esp_err_t err = esp_http_client_perform(client);
|
|
|
|
if (err == ESP_OK)
|
|
{
|
|
int content_length = esp_http_client_get_content_length(client);
|
|
ESP_LOGD(ENA_EKE_PROXY_LOG, "finished request: url = %s, status = %d, content_length = %d | memory: %d kB", url,
|
|
esp_http_client_get_status_code(client),
|
|
content_length, (xPortGetFreeHeapSize() / 1024));
|
|
retries = 0;
|
|
}
|
|
|
|
free(url);
|
|
esp_http_client_close(client);
|
|
esp_http_client_cleanup(client);
|
|
|
|
if (err != ESP_OK && retries < 6)
|
|
{
|
|
retries = retries + 1;
|
|
ESP_LOGD(ENA_EKE_PROXY_LOG, "retry %d for url = %s", retries, url);
|
|
return ena_eke_proxy_receive_keys(url);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
esp_err_t ena_eke_proxy_receive_daily_keys(char *date_string, size_t page, size_t size)
|
|
{
|
|
char *url = malloc(strlen(ENA_EKE_PROXY_KEYFILES_DAILY_URL) + strlen(date_string) + 16);
|
|
sprintf(url, ENA_EKE_PROXY_KEYFILES_DAILY_URL, date_string, page, size);
|
|
return ena_eke_proxy_receive_keys(url);
|
|
}
|
|
|
|
esp_err_t ena_eke_proxy_receive_hourly_keys(char *date_string, uint8_t hour, size_t page, size_t size)
|
|
{
|
|
char *url = malloc(strlen(ENA_EKE_PROXY_KEYFILES_HOURLY_URL) + strlen(date_string) + 24);
|
|
sprintf(url, ENA_EKE_PROXY_KEYFILES_HOURLY_URL, date_string, hour, page, size);
|
|
return ena_eke_proxy_receive_keys(url);
|
|
}
|
|
|
|
void ena_eke_proxy_run(void)
|
|
{
|
|
static time_t current_time = 0;
|
|
static struct tm current_tm;
|
|
static struct tm last_check_tm;
|
|
static double check_diff = 0;
|
|
static time_t wifi_reconnect = 0;
|
|
static uint32_t wifi_reconnect_waiting = 15;
|
|
current_time = time(NULL);
|
|
last_check = (time_t)ena_storage_read_last_exposure_date();
|
|
check_diff = difftime(current_time, last_check);
|
|
|
|
if (check_diff > HOUR_IN_SECONDS && !wait_for_request && !request_pause && current_time > request_sleep)
|
|
{
|
|
if (wifi_controller_connection() == NULL && current_time > wifi_reconnect && wifi_reconnect_waiting < 86400)
|
|
{
|
|
wifi_controller_reconnect(NULL);
|
|
wifi_reconnect = current_time + wifi_reconnect_waiting;
|
|
wifi_reconnect_waiting = wifi_reconnect_waiting * 4;
|
|
}
|
|
else if (wifi_controller_connection() != NULL)
|
|
{
|
|
wifi_reconnect = 0;
|
|
wifi_reconnect_waiting = 15;
|
|
int current_day_offset = check_diff / DAY_IN_SECONDS;
|
|
|
|
if (current_day_offset > ENA_EKE_PROXY_MAX_PAST_DAYS)
|
|
{
|
|
current_day_offset = ENA_EKE_PROXY_MAX_PAST_DAYS;
|
|
last_check = (current_time - (DAY_IN_SECONDS * current_day_offset));
|
|
}
|
|
|
|
memcpy(¤t_tm, gmtime(¤t_time), sizeof current_tm);
|
|
memcpy(&last_check_tm, gmtime(&last_check), sizeof last_check_tm);
|
|
|
|
if (current_day_offset > 0 || current_tm.tm_mday > last_check_tm.tm_mday || current_tm.tm_mon > last_check_tm.tm_mon)
|
|
{
|
|
last_check_tm.tm_hour = 0;
|
|
if (current_day_offset <= 0)
|
|
{
|
|
current_day_offset = 1;
|
|
}
|
|
}
|
|
|
|
last_check_tm.tm_min = 0;
|
|
last_check_tm.tm_sec = 0;
|
|
last_check = mktime(&last_check_tm);
|
|
|
|
esp_err_t err;
|
|
|
|
char date_string[11];
|
|
strftime(date_string, 11, ENA_EKE_PROXY_KEYFILES_DAILY_FORMAT, &last_check_tm);
|
|
|
|
if (current_day_offset == 0 && ENA_EKE_PROXY_KEYFILES_HOURLY)
|
|
{
|
|
ESP_LOGD(ENA_EKE_PROXY_LOG, "eke-proxy request for /%s/hour/%d?page=%d&size=%d : %d kB, ", date_string, last_check_tm.tm_hour, current_page, ENA_EKE_PROXY_DEFAULT_LIMIT, (xPortGetFreeHeapSize() / 1024));
|
|
err = ena_eke_proxy_receive_hourly_keys(date_string, last_check_tm.tm_hour, current_page, ENA_EKE_PROXY_DEFAULT_LIMIT);
|
|
}
|
|
else
|
|
{
|
|
ESP_LOGD(ENA_EKE_PROXY_LOG, "eke-proxy request for /%s?page=%d&size=%d : %d kB, ", date_string, current_page, ENA_EKE_PROXY_DEFAULT_LIMIT, (xPortGetFreeHeapSize() / 1024));
|
|
err = ena_eke_proxy_receive_daily_keys(date_string, current_page, ENA_EKE_PROXY_DEFAULT_LIMIT);
|
|
}
|
|
|
|
if (err != ESP_OK)
|
|
{
|
|
ESP_LOGD(ENA_EKE_PROXY_LOG, "error eke-proxy /%s/%u %d, ", date_string, last_check_tm.tm_hour, (xPortGetFreeHeapSize() / 1024));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
esp_err_t ena_eke_proxy_fetch_upload_handler(esp_http_client_event_t *evt)
|
|
{
|
|
switch (evt->event_id)
|
|
{
|
|
case HTTP_EVENT_ON_DATA:
|
|
break;
|
|
case HTTP_EVENT_ON_FINISH:
|
|
ena_eke_proxy_resume();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t ena_eke_proxy_upload(char *token, uint32_t days_since_onset_of_symptoms)
|
|
{
|
|
|
|
ena_eke_proxy_pause();
|
|
|
|
ESP_LOGD(ENA_EKE_PROXY_LOG, "try to upload keys:");
|
|
esp_http_client_config_t config = {
|
|
.url = ENA_EKE_PROXY_KEYFILES_UPLOAD_URL,
|
|
.timeout_ms = 30000,
|
|
.event_handler = ena_eke_proxy_fetch_upload_handler,
|
|
.method = HTTP_METHOD_POST,
|
|
};
|
|
|
|
if (memcmp(ENA_EKE_PROXY_KEYFILES_UPLOAD_URL, "https", 5) == 0)
|
|
{
|
|
config.cert_pem = (char *)cert_pem_start;
|
|
}
|
|
|
|
uint32_t tek_count = ena_storage_tek_count();
|
|
uint32_t teks_to_send = 0;
|
|
char *output_buffer = malloc(tek_count * 28);
|
|
ena_tek_t tek;
|
|
for (int i = 0; i < tek_count; i++)
|
|
{
|
|
ena_storage_get_tek(i, &tek);
|
|
if (((((uint32_t)time(NULL)) - (tek.enin * ENA_TIME_WINDOW)) / DAY_IN_SECONDS) < ENA_STORAGE_TEK_MAX)
|
|
{
|
|
memcpy(&output_buffer[teks_to_send * 28], &(tek.key_data), ENA_KEY_LENGTH);
|
|
memcpy(&output_buffer[teks_to_send * 28 + ENA_KEY_LENGTH], &(tek.enin), 4);
|
|
uint32_t rolling_period = tek.rolling_period;
|
|
memcpy(&output_buffer[teks_to_send * 28 + ENA_KEY_LENGTH + 4], &rolling_period, 4);
|
|
memcpy(&output_buffer[teks_to_send * 28 + ENA_KEY_LENGTH + 8], &days_since_onset_of_symptoms, 4);
|
|
teks_to_send++;
|
|
}
|
|
}
|
|
|
|
ESP_LOG_BUFFER_HEXDUMP(ENA_EKE_PROXY_LOG, output_buffer, teks_to_send * 28, ESP_LOG_DEBUG);
|
|
|
|
esp_http_client_handle_t client = esp_http_client_init(&config);
|
|
esp_http_client_set_header(client, "Authorization", token);
|
|
esp_http_client_set_header(client, "Content-Type", "application/binary");
|
|
esp_http_client_set_header(client, "Accept", "application/octet-stream");
|
|
esp_http_client_set_post_field(client, output_buffer, teks_to_send * 28);
|
|
esp_err_t err = esp_http_client_perform(client);
|
|
|
|
if (err == ESP_OK)
|
|
{
|
|
int content_length = esp_http_client_get_content_length(client);
|
|
int status = esp_http_client_get_status_code(client);
|
|
if (status == 200)
|
|
{
|
|
ESP_LOGI(ENA_EKE_PROXY_LOG, "successfully uploaded keys : url = %s, status = %d, content_length = %d", ENA_EKE_PROXY_KEYFILES_UPLOAD_URL, status, content_length);
|
|
err = ESP_OK;
|
|
}
|
|
else
|
|
{
|
|
ESP_LOGW(ENA_EKE_PROXY_LOG, "failed to upload keys : url = %s, status = %d, content_length = %d", ENA_EKE_PROXY_KEYFILES_UPLOAD_URL, status, content_length);
|
|
err = ESP_FAIL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ESP_LOGW(ENA_EKE_PROXY_LOG, "Keyupload failed!");
|
|
ena_eke_proxy_resume();
|
|
}
|
|
|
|
free(output_buffer);
|
|
esp_http_client_close(client);
|
|
esp_http_client_cleanup(client);
|
|
|
|
return err;
|
|
} |