Skip to content

Instantly share code, notes, and snippets.

@mpeven
Created July 7, 2016 03:31
Show Gist options
  • Select an option

  • Save mpeven/18d1cf76fcb7079b7adaa18ef5137ff0 to your computer and use it in GitHub Desktop.

Select an option

Save mpeven/18d1cf76fcb7079b7adaa18ef5137ff0 to your computer and use it in GitHub Desktop.

Revisions

  1. mpeven created this gist Jul 7, 2016.
    288 changes: 288 additions & 0 deletions main.c
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,288 @@
    /*
    * Device Application
    * ------------------
    * This is the brains of the device.
    * It serves 2 functions:
    * 1) Updating the backend with all the relevant data.
    * 2) Changing settings when certain things happen
    * (ULPM, turning access to data on/off)
    */

    /*
    * Installation Instructions
    * -------------------------
    * Copy all files over. Might need to copy the json-c library into this
    * directory and compile it. This is how to do that:
    mkdir json-c && curl -sL https://github.com/json-c/json-c/archive/json-c-0.12.1-20160607.tar.gz | tar xz -C json-c --strip-components 1 && cd json-c && sh autogen.sh && ./configure && make && make install && cd ../
    *
    * Then compile this directory with "make" and run with "./prog"
    */

    /* Standard includes */
    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>

    /* libcurl (http://curl.haxx.se/libcurl/c) */
    #include <curl/curl.h>

    /* json-c (https://github.com/json-c/json-c) */
    #include <json-c/json.h>

    /* Global variables */
    static const unsigned int LOOP_TIMER = 5;
    static const char *GET_USER_ENDPOINT = "http://backend-django-env.dpsrk7dr9t.us-west-2.elasticbeanstalk.com/api/users/278/";
    static const char *PUT_UPDATE_DATA_ENDPOINT = "http://backend-django-env.dpsrk7dr9t.us-west-2.elasticbeanstalk.com/api/users/ABC123/update_data_used/";
    static const char *VNSTAT_DELETE = "vnstat -i en1 --delete --force > /dev/null 2>&1";
    static const char *VNSTAT_UPDATE = "vnstat -i en1 -u > /dev/null 2>&1";
    static const char *VNSTAT_OUTPUT = "vnstat -i en1 --json";

    /* Structs */
    struct string {
    char *ptr;
    size_t len;
    };

    /* Function declarations */
    size_t writefunc();
    long long get_data_purchased();
    long long get_backend_data_used();
    long long get_session_data_used();
    void update_backend(long long);

    /* Main */
    int main(void)
    {
    /* Delete all pre-existing vnstat data and start fresh */
    system(VNSTAT_DELETE);

    /* Start saving vnstat data right away */
    // DONE --> Will automatically happen

    /* Create initial device object */
    long long data_purchased = get_data_purchased();
    long long backend_data_used = get_backend_data_used();
    long long previous_data_used = backend_data_used;
    long long data_used_diff = 0;
    long long session_data_used = get_session_data_used();
    long user_id = 278;
    char device_id[] = "ABC123";
    printf("--- initial values ---\n");
    printf("data_purchased: %lli\n", data_purchased);
    printf("previous_data_used: %lli\n", previous_data_used);
    printf("session_data_used: %lli\n", session_data_used);
    printf("user_id: %li\n", user_id);
    printf("device_id: %s\n", device_id);
    printf("----------------------\n");

    /* Update the backend every LOOP_TIMER seconds */
    // TODO - not finished
    while(1) {
    system(VNSTAT_UPDATE);
    data_purchased = get_data_purchased();
    session_data_used = get_session_data_used();
    backend_data_used = get_backend_data_used();
    data_used_diff = (previous_data_used + session_data_used) - backend_data_used;
    printf("session_data_used: %lli\n", session_data_used);
    printf("backend_data_used: %lli\n", backend_data_used);
    /*
    * Update the backend by sending it the difference between the total
    * data used on the device since it was registered and the data used
    * amount stored on the backend
    */
    update_backend(data_used_diff);
    sleep(LOOP_TIMER);
    };

    /* End gracefully on command line */
    return 0;
    }

    /*
    * Writes a stream into the response string struct, reallocates memory as
    * necessary
    */
    size_t writefunc(void *ptr, size_t size, size_t nmemb, struct string *response)
    {
    size_t new_len = response->len + size*nmemb;
    response->ptr = realloc(response->ptr, new_len+1);
    if (response->ptr == NULL) {
    fprintf(stderr, "realloc() failed\n");
    exit(EXIT_FAILURE);
    }
    memcpy(response->ptr+response->len, ptr, size*nmemb);
    response->ptr[new_len] = '\0';
    response->len = new_len;
    return size*nmemb;
    }

    /*
    * Asks the backend for the user object and returns amount of data purchased
    * If there is no internet, or the API call fails, it returns 0
    *
    * @returns data purchased in bytes or 0 if no connection
    */
    long long get_data_purchased()
    {
    CURL *curl;
    curl = curl_easy_init();

    if(curl) {
    struct string response;
    response.len = 0;
    response.ptr = malloc(0);
    if (response.ptr == NULL) {
    fprintf(stderr, "malloc() failed\n");
    exit(EXIT_FAILURE);
    }
    response.ptr[0] = '\0';

    curl_easy_setopt(curl, CURLOPT_URL, GET_USER_ENDPOINT);
    /* Set a callback function to receive incoming data chunks */
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc);
    /* The callback takes a user defined argument (the string) */
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    /* Perform request */
    curl_easy_perform(curl);

    /* Create a json object and then parse for "data_purchased" */
    struct json_object *user_object;
    struct json_object *data_purchased_object;
    user_object = json_tokener_parse(response.ptr);
    json_object_object_get_ex(user_object, "data_purchased", &data_purchased_object);

    /* Get the json_object as a string then convert into a long long */
    const char *data_purchased_string = json_object_to_json_string(data_purchased_object);
    long long data_purchased = atoll(data_purchased_string);

    /* always cleanup */
    free(response.ptr);
    curl_easy_cleanup(curl);

    return data_purchased;
    }
    return 0;
    }

    /*
    * Asks the backend for the user object and returns amount of data used
    * If there is no internet, or the API call fails, it returns 0
    *
    * @returns data used in bytes or 0 if no connection
    */
    long long get_backend_data_used()
    {
    CURL *curl;
    curl = curl_easy_init();

    if(curl) {
    struct string response;
    response.len = 0;
    response.ptr = malloc(0);
    if (response.ptr == NULL) {
    fprintf(stderr, "malloc() failed\n");
    exit(EXIT_FAILURE);
    }
    response.ptr[0] = '\0';

    curl_easy_setopt(curl, CURLOPT_URL, GET_USER_ENDPOINT);
    /* Set a callback function to receive incoming data chunks */
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writefunc);
    /* The callback takes a user defined argument (the string) */
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
    /* Perform request */
    curl_easy_perform(curl);

    /* Create a json object and then parse for "data_purchased" */
    struct json_object *user_object;
    struct json_object *data_used_object;
    user_object = json_tokener_parse(response.ptr);
    json_object_object_get_ex(user_object, "data_used", &data_used_object);

    /* Get the json_object as a string then convert into a long long */
    const char *data_used_string = json_object_to_json_string(data_used_object);
    long long data_used = atoll(data_used_string);

    /* always cleanup */
    free(response.ptr);
    curl_easy_cleanup(curl);

    return data_used;
    }
    return 0;
    }

    /*
    * Makes a system call to vnstat and returns the total traffic over the
    * specified interface
    *
    * @returns traffic (tx + rx) over a certain interface in bytes
    */
    long long get_session_data_used()
    {
    // First update vnstat database
    system(VNSTAT_UPDATE);

    FILE *in;
    extern FILE *popen();
    char vnstat_json_string[512];
    if(!(in = popen(VNSTAT_OUTPUT, "r"))) {
    exit(1);
    }
    fgets(vnstat_json_string, sizeof(vnstat_json_string), in);
    pclose(in);

    /* Turn vnstat output into json object */
    struct json_object *vnstat_object;
    struct json_object *interfaces_object;
    struct json_object *traffic_object;
    struct json_object *total_object;
    struct json_object *en1_object;
    vnstat_object = json_tokener_parse(vnstat_json_string);
    json_object_object_get_ex(vnstat_object, "interfaces", &interfaces_object);
    en1_object = json_object_array_get_idx(interfaces_object, 0);
    json_object_object_get_ex(en1_object, "traffic", &traffic_object);
    json_object_object_get_ex(traffic_object, "total", &total_object);

    struct json_object *data_tx_object;
    struct json_object *data_rx_object;
    json_object_object_get_ex(total_object, "tx", &data_tx_object);
    json_object_object_get_ex(total_object, "rx", &data_rx_object);
    const char *data_tx_string = json_object_to_json_string(data_tx_object);
    const char *data_rx_string = json_object_to_json_string(data_rx_object);
    long long data_tx_kib = atoll(data_tx_string);
    long long data_rx_kib = atoll(data_rx_string);

    long long total_traffic_bytes = (data_tx_kib + data_rx_kib) * 1024;

    return total_traffic_bytes;
    }

    void update_backend(long long data_diff)
    {
    CURL *curl;
    curl = curl_easy_init();

    if (curl) {
    /* Build objects for PUT request */
    json_object *json;
    json = json_object_new_object();
    json_object_object_add(json, "data_used", json_object_new_int(data_diff));

    struct curl_slist *headers = NULL;
    headers = curl_slist_append(headers, "Content-Type: application/json");

    /* Make request */
    curl_easy_setopt(curl, CURLOPT_URL, PUT_UPDATE_DATA_ENDPOINT);
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); // Set headers
    curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT"); // PUT request
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_object_to_json_string(json));
    FILE *devnull = fopen("/dev/null","w");
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, devnull); // Write to nothing
    curl_easy_perform(curl);

    curl_slist_free_all(headers);
    curl_easy_cleanup(curl);
    }
    }