/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * 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.
 */

/************************************************************************************
 *
 *  Filename:      btif_uid.c
 *
 *  Description:   Contains data structures and functions for keeping track of
 *                 socket usage per app UID.
 *
 ***********************************************************************************/
#define LOG_TAG "bt_uid_set"
#include "bt_common.h"
#include "btif_uid.h"

#include <log/log.h>
#include <pthread.h>

typedef struct uid_set_node_t {
    struct uid_set_node_t* next;
    bt_uid_traffic_t data;
} uid_set_node_t;

typedef struct uid_set_t {
    pthread_mutex_t lock;
    uid_set_node_t* head;
} uid_set_t;

uid_set_t* uid_set_create(void) {
    uid_set_t* set = osi_calloc(sizeof(uid_set_t));
    pthread_mutex_init(&set->lock, NULL);
    return set;
}

void uid_set_destroy(uid_set_t* set) {
    pthread_mutex_lock(&set->lock);
    uid_set_node_t* node = set->head;
    while (node) {
        uid_set_node_t* temp = node;
        node = node->next;
        osi_free(temp);
    }
    set->head = NULL;
    pthread_mutex_unlock(&set->lock);
    pthread_mutex_destroy(&set->lock);
    osi_free(set);
}

// Lock in uid_set_t must be held.
static uid_set_node_t* uid_set_find_or_create_node(uid_set_t* set, int32_t app_uid) {
    uid_set_node_t* node = set->head;
    while (node && node->data.app_uid != app_uid) {
        node = node->next;
    }

    if (!node) {
        node = osi_calloc(sizeof(uid_set_node_t));
        node->data.app_uid = app_uid;
        node->next = set->head;
        set->head = node;
    }
    return node;
}

void uid_set_add_tx(uid_set_t* set, int32_t app_uid, uint64_t bytes) {
    if (app_uid == -1 || bytes == 0) {
        return;
    }

    pthread_mutex_lock(&set->lock);
    uid_set_node_t* node = uid_set_find_or_create_node(set, app_uid);
    node->data.tx_bytes += bytes;
    pthread_mutex_unlock(&set->lock);
}

void uid_set_add_rx(uid_set_t* set, int32_t app_uid, uint64_t bytes) {
    if (app_uid == -1 || bytes == 0) {
        return;
    }

    pthread_mutex_lock(&set->lock);
    uid_set_node_t* node = uid_set_find_or_create_node(set, app_uid);
    node->data.rx_bytes += bytes;
    pthread_mutex_unlock(&set->lock);
}

bt_uid_traffic_t* uid_set_read_and_clear(uid_set_t* set) {
    pthread_mutex_lock(&set->lock);

    // Find the length
    size_t len = 0;
    uid_set_node_t* node = set->head;
    while (node) {
        len++;
        node = node->next;
    }

    // Allocate an array of elements + 1, to signify the end with app_uid set to -1.
    bt_uid_traffic_t* result = osi_calloc(sizeof(bt_uid_traffic_t) * (len + 1));

    bt_uid_traffic_t* data = result;
    node = set->head;
    while (node) {
        // Copy the data.
        *data = node->data;
        data++;

        // Clear the counters.
        node->data.rx_bytes = 0;
        node->data.tx_bytes = 0;
        node = node->next;
    }

    // Mark the last entry
    data->app_uid = -1;

    pthread_mutex_unlock(&set->lock);

    return result;
}