/*
 * Copyright (C) 2021 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.
 */

#define TLOG_TAG "metrics-consumer"
#include "consumer.h"

#include <interface/metrics/consumer.h>
#include <lib/tipc/tipc.h>
#include <lib/tipc/tipc_srv.h>
#include <metrics_consts.h>
#include <openssl/sha.h>
#include <stddef.h>
#include <string.h>
#include <trusty_log.h>
#include <uapi/err.h>

/*
Current use cases:
1) Metrics daemon
2) Trusty kernel metrics reporter
3) Storage metrics
4) Android metrics test
*/
#define MAX_METRICS_TA_CONNECTIONS 4

static const struct uuid zero_uuid = UUID_INITIAL_VALUE(zero_uuid);

static bool is_zero_uuid(const struct uuid* peer) {
    return equal_uuid(peer, &zero_uuid);
}

static int on_connect(const struct tipc_port* port,
                      handle_t chan,
                      const struct uuid* peer,
                      void** ctx_p) {

    struct srv_state* state = get_srv_state(port);
    if(is_zero_uuid(peer))
    {
        TLOGD("Updating metrics daemon handle :%d\n", chan);
        if(state->ns_handle != INVALID_IPC_HANDLE) {
                close(state->ns_handle);
        }
        state->ns_handle = chan;
    }

    return NO_ERROR;
}

void hash_trusty_metrics(uint64_t metric, char *app_id, uint8_t *output) {

    const unsigned char CONST_SALT[] = {
    0xf2, 0xe7, 0x8c, 0x19, 0xa4, 0xd3, 0x5b, 0x68
    };

    /* Convert the metric to an array of uint8_t prepended with salt*/
    uint8_t metric_arr[8 + UUID_STR_SIZE + sizeof(CONST_SALT)];

    memcpy(metric_arr, app_id, UUID_STR_SIZE);
    memcpy(metric_arr+UUID_STR_SIZE, CONST_SALT, sizeof(CONST_SALT));

    for (size_t i = 0; i < 8; ++i) {
        metric_arr[i+ UUID_STR_SIZE + sizeof(CONST_SALT)] = (metric >> (8 * i)) & 0xFF;
    }

    SHA512(metric_arr, sizeof(metric_arr), output);
}

static int on_message(const struct tipc_port* port, handle_t chan, void* ctx) {
    int rc;
    struct metrics_req req;
    uint8_t msg[METRICS_MAX_MSG_SIZE];

    memset(msg, 0, sizeof(msg));
    int msg_size = tipc_recv1(chan, sizeof(req),  msg, sizeof(msg));
    if (msg_size < 0) {
        TLOGE("failed (%d) to receive metrics event\n", msg_size);
        return msg_size;
    }

    uint32_t cmd;
    cmd = ((struct metrics_req*)msg)->cmd;

    if(cmd == METRICS_CMD_REPORT_CRASH) {
        struct metrics_crash_msg *crash_msg = (struct metrics_crash_msg *)msg;
        if (crash_msg->crash_args.is_hash) {
            hash_trusty_metrics(crash_msg->crash_args.far, crash_msg->crash_args.app_id, crash_msg->crash_args.far_hash);
            hash_trusty_metrics(crash_msg->crash_args.elr, crash_msg->crash_args.app_id, crash_msg->crash_args.elr_hash);
            crash_msg->crash_args.far = 0;
            crash_msg->crash_args.elr = 0;
        }
    }

    // Check if NS metricsd connected, if so forward it there.
    struct srv_state* state = get_srv_state(port);
    if(is_ns_connected(state)) {
        rc = tipc_send1(state->ns_handle, msg, msg_size);
        if (rc < 0) {
            TLOGE("failed (%d) to send metrics event tp NS metricsd\n", rc);
            return rc;
        }
    }
    else {
        TLOGD("NS metrics daemon not connected\n");
    }

    struct metrics_resp resp = {
        .cmd = (cmd | METRICS_CMD_RESP_BIT)
    };

    rc = tipc_send1(chan, &resp, sizeof(resp));
    if (rc < 0) {
        TLOGE("failed (%d) to send metrics event response\n", rc);
        return rc;
    }

    if ((size_t)rc != sizeof(resp)) {
        TLOGE("unexpected number of bytes sent: %d\n", rc);
        return ERR_BAD_LEN;
    }

    return NO_ERROR;
}

int add_metrics_consumer_service(struct tipc_hset* hset, struct srv_state* state) {
    static struct tipc_port_acl port_acl = {
            .flags = IPC_PORT_ALLOW_TA_CONNECT | IPC_PORT_ALLOW_NS_CONNECT,
    };
    static struct tipc_port port = {
            .name = METRICS_CONSUMER_PORT,
            .msg_max_size = METRICS_MAX_MSG_SIZE,
            .msg_queue_len = 1,
            .acl = &port_acl,
    };
    static struct tipc_srv_ops ops = {
            .on_message = on_message,
            .on_connect = on_connect,
    };
    set_srv_state(&port, state);

    return tipc_add_service(hset, &port, 1, MAX_METRICS_TA_CONNECTIONS, &ops);
}