|
|
|
|
@ -0,0 +1,436 @@
|
|
|
|
|
#include <signal.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <time.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
|
|
#include <cjson/cJSON.h>
|
|
|
|
|
#include <curl/curl.h>
|
|
|
|
|
|
|
|
|
|
#include "log.h"
|
|
|
|
|
|
|
|
|
|
#include "main.h"
|
|
|
|
|
|
|
|
|
|
void handle_sigint(int signal)
|
|
|
|
|
{
|
|
|
|
|
struct timespec ts = {10, 0}; // 2 seconds, 0 nanoseconds
|
|
|
|
|
|
|
|
|
|
log_info("Received SIGINT gracefully shutting down...");
|
|
|
|
|
nanosleep(&ts, NULL);
|
|
|
|
|
log_info("Finished cleaning up");
|
|
|
|
|
exit(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t write_chunk(void *data, size_t size, size_t nmemb, void *clientp)
|
|
|
|
|
{
|
|
|
|
|
size_t real_size;
|
|
|
|
|
struct Response *response;
|
|
|
|
|
char *ptr;
|
|
|
|
|
|
|
|
|
|
real_size = size * nmemb;
|
|
|
|
|
response = (struct Response *) clientp;
|
|
|
|
|
|
|
|
|
|
ptr = realloc(response->string, response->size + real_size + 1);
|
|
|
|
|
if (ptr == NULL) {
|
|
|
|
|
return CURL_WRITEFUNC_ERROR;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
response->string = ptr;
|
|
|
|
|
memcpy(&(response->string[response->size]), data, real_size);
|
|
|
|
|
response->size += real_size;
|
|
|
|
|
response->string[response->size] = '\0'; // ensure that string is NULL-terminated
|
|
|
|
|
|
|
|
|
|
return real_size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Ensure that the response returned is freed
|
|
|
|
|
*/
|
|
|
|
|
struct Response* post_request(const struct Request *request)
|
|
|
|
|
{
|
|
|
|
|
CURL *curl;
|
|
|
|
|
|
|
|
|
|
curl = curl_easy_init();
|
|
|
|
|
if (curl == NULL) {
|
|
|
|
|
fprintf(stderr, "curl_easy_init failed");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct Response *response = (struct Response *)malloc(sizeof(struct Response));
|
|
|
|
|
response->string = malloc(1);
|
|
|
|
|
response->size = 0;
|
|
|
|
|
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_URL, request->endpoint);
|
|
|
|
|
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_COOKIEJAR, "cookies.txt");
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "cookies.txt");
|
|
|
|
|
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
|
|
|
|
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, "PostmanRuntime/7.28.4");
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Set userdata to response struct defined earlier
|
|
|
|
|
* This allows us to store the response of the rqeuest in the response structure
|
|
|
|
|
*/
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_chunk);
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) response);
|
|
|
|
|
|
|
|
|
|
struct curl_slist *headers;
|
|
|
|
|
headers = NULL;
|
|
|
|
|
headers = curl_slist_append(headers, "version: 4.7");
|
|
|
|
|
headers = curl_slist_append(headers, "product: llu.ios");
|
|
|
|
|
headers = curl_slist_append(headers, "Content-Type: application/json");
|
|
|
|
|
headers = curl_slist_append(headers, "Accept: application/json");
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
|
|
|
|
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request->body);
|
|
|
|
|
|
|
|
|
|
CURLcode result = curl_easy_perform(curl);
|
|
|
|
|
if (result != CURLE_OK) {
|
|
|
|
|
log_error("curl_easy_perform failed: %s\n", curl_easy_strerror(result));
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
curl_slist_free_all(headers);
|
|
|
|
|
curl_easy_cleanup(curl);
|
|
|
|
|
|
|
|
|
|
return response;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Ensure that the response returned is freed
|
|
|
|
|
*/
|
|
|
|
|
struct Response* get_request(const struct Request *request)
|
|
|
|
|
{
|
|
|
|
|
CURL *curl;
|
|
|
|
|
|
|
|
|
|
curl = curl_easy_init();
|
|
|
|
|
if (curl == NULL) {
|
|
|
|
|
fprintf(stderr, "curl_easy_init failed");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct Response *response = (struct Response *)malloc(sizeof(struct Response));
|
|
|
|
|
response->string = malloc(1);
|
|
|
|
|
response->size = 0;
|
|
|
|
|
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET");
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_URL, request->endpoint);
|
|
|
|
|
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_COOKIEJAR, "cookies.txt");
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "cookies.txt");
|
|
|
|
|
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
|
|
|
|
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, "PostmanRuntime/7.28.4");
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Set userdata to response struct defined earlier
|
|
|
|
|
* This allows us to store the response of the rqeuest in the response structure
|
|
|
|
|
*/
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_chunk);
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) response);
|
|
|
|
|
|
|
|
|
|
struct curl_slist *headers;
|
|
|
|
|
headers = NULL;
|
|
|
|
|
char authorization_header[BUFFER_SIZE];
|
|
|
|
|
sprintf(authorization_header, "Authorization: Bearer %s", request->token);
|
|
|
|
|
headers = curl_slist_append(headers, authorization_header);
|
|
|
|
|
headers = curl_slist_append(headers, "version: 4.7");
|
|
|
|
|
headers = curl_slist_append(headers, "product: llu.ios");
|
|
|
|
|
headers = curl_slist_append(headers, "Accept: application/json");
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
|
|
|
|
|
|
|
|
|
CURLcode result = curl_easy_perform(curl);
|
|
|
|
|
if (result != CURLE_OK) {
|
|
|
|
|
log_error("curl_easy_perform failed: %s\n", curl_easy_strerror(result));
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
curl_slist_free_all(headers);
|
|
|
|
|
curl_easy_cleanup(curl);
|
|
|
|
|
|
|
|
|
|
return response;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t get_auth_token(const char *email, const char *password, char *auth_token)
|
|
|
|
|
{
|
|
|
|
|
int body_size;
|
|
|
|
|
|
|
|
|
|
struct Request request;
|
|
|
|
|
request.endpoint = "https://api.libreview.io/llu/auth/login";
|
|
|
|
|
|
|
|
|
|
body_size = sprintf(
|
|
|
|
|
request.body,
|
|
|
|
|
"{\n \"email\": \"%s\",\n \"password\": \"%s\"\n}",
|
|
|
|
|
email,
|
|
|
|
|
password
|
|
|
|
|
);
|
|
|
|
|
if (body_size == 0) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Response *response = post_request(&request);
|
|
|
|
|
|
|
|
|
|
cJSON *json = cJSON_Parse(response->string);
|
|
|
|
|
if (json == NULL) {
|
|
|
|
|
fprintf(stderr, "error parsing JSON\n");
|
|
|
|
|
free(response->string);
|
|
|
|
|
free(response);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cJSON *data = cJSON_GetObjectItem(json, "data");
|
|
|
|
|
if (data == NULL) {
|
|
|
|
|
log_error("failed to parse JSON object data");
|
|
|
|
|
free(response->string);
|
|
|
|
|
free(response);
|
|
|
|
|
cJSON_Delete(json);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cJSON *auth_ticket = cJSON_GetObjectItem(data, "authTicket");
|
|
|
|
|
if (auth_ticket == NULL) {
|
|
|
|
|
printf("No authTicket found\n");
|
|
|
|
|
free(response->string);
|
|
|
|
|
free(response);
|
|
|
|
|
cJSON_Delete(json);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cJSON *token = cJSON_GetObjectItem(auth_ticket, "token");
|
|
|
|
|
if (token == NULL) {
|
|
|
|
|
printf("No token found\n");
|
|
|
|
|
free(response->string);
|
|
|
|
|
free(response);
|
|
|
|
|
cJSON_Delete(json);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t token_length = strlen(token->valuestring)+1;
|
|
|
|
|
strncpy(auth_token, token->valuestring, token_length);
|
|
|
|
|
|
|
|
|
|
cJSON_Delete(json);
|
|
|
|
|
free(response->string);
|
|
|
|
|
free(response);
|
|
|
|
|
|
|
|
|
|
return strlen(auth_token);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t get_patient_id(const char *token, char *patient_id)
|
|
|
|
|
{
|
|
|
|
|
CURL *curl;
|
|
|
|
|
|
|
|
|
|
curl = curl_easy_init();
|
|
|
|
|
if (curl == NULL) {
|
|
|
|
|
fprintf(stderr, "curl_easy_init failed");
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct Request request;
|
|
|
|
|
request.endpoint = "https://api.libreview.io/llu/connections";
|
|
|
|
|
request.token = token;
|
|
|
|
|
request.body = "";
|
|
|
|
|
|
|
|
|
|
Response *response = get_request(&request);
|
|
|
|
|
if (response->string == NULL) {
|
|
|
|
|
patient_id = NULL;
|
|
|
|
|
log_error("get_request returned null");
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cJSON *json = cJSON_Parse(response->string);
|
|
|
|
|
if (json == NULL) {
|
|
|
|
|
fprintf(stderr, "error parsing JSON\n");
|
|
|
|
|
free(response->string);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cJSON *data = cJSON_GetObjectItem(json, "data");
|
|
|
|
|
if (data == NULL) {
|
|
|
|
|
fprintf(stderr, "no data found\n");
|
|
|
|
|
free(response->string);
|
|
|
|
|
cJSON_Delete(json);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int number_of_connections = cJSON_GetArraySize(data);
|
|
|
|
|
if (number_of_connections < 1) {
|
|
|
|
|
fprintf(stderr, "no connections found\n");
|
|
|
|
|
free(response->string);
|
|
|
|
|
cJSON_Delete(json);
|
|
|
|
|
return 0;
|
|
|
|
|
} else if (number_of_connections > 1) {
|
|
|
|
|
fprintf(stderr, "more than one connection found, sorry program only supports accounts with one connection");
|
|
|
|
|
free(response->string);
|
|
|
|
|
cJSON_Delete(json);
|
|
|
|
|
exit(-1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cJSON *sub_item = cJSON_GetArrayItem(data, 0);
|
|
|
|
|
cJSON *patient_id_object = cJSON_GetObjectItem(sub_item, "patientId");
|
|
|
|
|
if (patient_id_object == NULL) {
|
|
|
|
|
fprintf(stderr, "failed to get patient ID");
|
|
|
|
|
free(response->string);
|
|
|
|
|
cJSON_Delete(json);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t patient_id_length = strlen(patient_id_object->valuestring);
|
|
|
|
|
strncpy(patient_id, patient_id_object->valuestring, patient_id_length);
|
|
|
|
|
|
|
|
|
|
free(response->string);
|
|
|
|
|
cJSON_Delete(json);
|
|
|
|
|
|
|
|
|
|
return patient_id_length;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double get_glucose_units(const char *token, const char *patient_id)
|
|
|
|
|
{
|
|
|
|
|
CURL *curl;
|
|
|
|
|
|
|
|
|
|
curl = curl_easy_init();
|
|
|
|
|
if (curl == NULL) {
|
|
|
|
|
fprintf(stderr, "curl_easy_init failed");
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct Response response;
|
|
|
|
|
response.string = malloc(1);
|
|
|
|
|
response.size = 0;
|
|
|
|
|
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "GET");
|
|
|
|
|
|
|
|
|
|
char url[BUFFER_SIZE];
|
|
|
|
|
/*
|
|
|
|
|
* Having the null byte in the string means it ends up in the URL
|
|
|
|
|
* this makes libcurl very sad, as the URL is no longer valid.
|
|
|
|
|
* We therefore copy everything except the NULL byte to non_null_terinated_patient_id
|
|
|
|
|
* Why is this -2? Shouldn't it just be -1 to get rid of the NULL byte?
|
|
|
|
|
*/
|
|
|
|
|
size_t length_of_patient_id_without_null_byte = strlen(patient_id)-2;
|
|
|
|
|
char non_null_terminated_patient_id[length_of_patient_id_without_null_byte];
|
|
|
|
|
memcpy(non_null_terminated_patient_id, patient_id, length_of_patient_id_without_null_byte);
|
|
|
|
|
sprintf(url, "https://api.libreview.io/llu/connections/%s/graph", non_null_terminated_patient_id);
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_URL, url);
|
|
|
|
|
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_COOKIEJAR, "cookies.txt");
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "cookies.txt");
|
|
|
|
|
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
|
|
|
|
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, "Postmanuntime/7.28.4");
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Set userdata to response struct defined earlier
|
|
|
|
|
* This allows us to store the response of the rqeuest in the response structure
|
|
|
|
|
*/
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_chunk);
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) &response);
|
|
|
|
|
|
|
|
|
|
struct curl_slist *headers;
|
|
|
|
|
headers = NULL;
|
|
|
|
|
char authorization_header[BUFFER_SIZE];
|
|
|
|
|
sprintf(authorization_header, "Authorization: Bearer %s", token);
|
|
|
|
|
headers = curl_slist_append(headers, authorization_header);
|
|
|
|
|
headers = curl_slist_append(headers, "version: 4.7");
|
|
|
|
|
headers = curl_slist_append(headers, "product: llu.ios");
|
|
|
|
|
headers = curl_slist_append(headers, "Accept: application/json");
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
|
|
|
|
|
|
|
|
|
CURLcode result = curl_easy_perform(curl);
|
|
|
|
|
if (result != CURLE_OK) {
|
|
|
|
|
fprintf(stderr, "curl_easy_perform failed: %s\n", curl_easy_strerror(result));
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
curl_slist_free_all(headers);
|
|
|
|
|
curl_easy_cleanup(curl);
|
|
|
|
|
|
|
|
|
|
cJSON *json = cJSON_Parse(response.string);
|
|
|
|
|
if (json == NULL) {
|
|
|
|
|
fprintf(stderr, "error parsing JSON\n");
|
|
|
|
|
free(response.string);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cJSON *data = cJSON_GetObjectItem(json, "data");
|
|
|
|
|
if (data == NULL) {
|
|
|
|
|
fprintf(stderr, "error parsing JSON for data object\n");
|
|
|
|
|
free(response.string);
|
|
|
|
|
cJSON_Delete(json);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cJSON *connection = cJSON_GetObjectItem(data, "connection");
|
|
|
|
|
if (connection == NULL) {
|
|
|
|
|
fprintf(stderr, "error parsing JSON for connection\n");
|
|
|
|
|
free(response.string);
|
|
|
|
|
cJSON_Delete(json);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cJSON *glucose_measurement = cJSON_GetObjectItem(connection, "glucoseMeasurement");
|
|
|
|
|
if (glucose_measurement == NULL) {
|
|
|
|
|
fprintf(stderr, "error parsing JSON for glucoseMeasurement\n");
|
|
|
|
|
free(response.string);
|
|
|
|
|
cJSON_Delete(json);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cJSON *value = cJSON_GetObjectItem(glucose_measurement, "Value");
|
|
|
|
|
if (value == NULL) {
|
|
|
|
|
fprintf(stderr, "error parsing JSON\n");
|
|
|
|
|
free(response.string);
|
|
|
|
|
cJSON_Delete(json);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cJSON_Delete(json);
|
|
|
|
|
free(response.string);
|
|
|
|
|
|
|
|
|
|
return value->valuedouble;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int main(void)
|
|
|
|
|
{
|
|
|
|
|
signal(SIGINT, handle_sigint);
|
|
|
|
|
|
|
|
|
|
curl_global_init(CURL_GLOBAL_DEFAULT);
|
|
|
|
|
|
|
|
|
|
char token[BUFFER_SIZE]; ;
|
|
|
|
|
size_t token_size;
|
|
|
|
|
|
|
|
|
|
token_size = get_auth_token(USERNAME, PASSWORD, token);
|
|
|
|
|
if (token_size < 1) {
|
|
|
|
|
log_fatal("get_auth_token failed to return a token");
|
|
|
|
|
exit(-1);
|
|
|
|
|
}
|
|
|
|
|
log_debug("token: %s\n", token);
|
|
|
|
|
|
|
|
|
|
char patient_id[BUFFER_SIZE];
|
|
|
|
|
size_t patient_id_length;
|
|
|
|
|
|
|
|
|
|
patient_id_length = get_patient_id(token, patient_id);
|
|
|
|
|
if (patient_id_length < 1) {
|
|
|
|
|
log_fatal("failed to get patient ID, exiting...");
|
|
|
|
|
exit(-1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
log_debug("patient ID: %s\n", patient_id);
|
|
|
|
|
|
|
|
|
|
struct timespec ts = {5, 0}; // 5 seconds, 0 nanoseconds
|
|
|
|
|
time_t t;
|
|
|
|
|
char time_str[BUFFER_SIZE];
|
|
|
|
|
while (true) {
|
|
|
|
|
time(&t);
|
|
|
|
|
strftime(time_str, BUFFER_SIZE, "%H:%M:%S %A %d %B %Y", localtime(&t));
|
|
|
|
|
|
|
|
|
|
printf("Glucose units at %s are %lf\n", time_str, get_glucose_units(token, patient_id));
|
|
|
|
|
|
|
|
|
|
nanosleep(&ts, NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
curl_global_cleanup();
|
|
|
|
|
}
|