/*
 * 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.
 */

#define LOG_TAG "VehiclePropertyAccessControl"
#include <string>
#include <stdint.h>
#include <sys/types.h>
#include <IVehicleNetwork.h>
#include "VehiclePropertyAccessControl.h"
#include <hardware/vehicle.h>
#include <private/android_filesystem_config.h>
#include <vehicle-internal.h>

//#define DBG_EVENT
//#define DBG_VERBOSE
#ifdef DBG_EVENT
#define EVENT_LOG(x...) ALOGD(x)
#else
#define EVENT_LOG(x...)
#endif
#ifdef DBG_VERBOSE
#define LOG_VERBOSE(x...) ALOGD(x)
#else
#define LOG_VERBOSE(x...)
#endif


namespace android {

VehiclePropertyAccessControl::VehiclePropertyAccessControl() {
}

VehiclePropertyAccessControl::~VehiclePropertyAccessControl() {
    int index;
    int size;

    for (auto& i: mVehicleAccessControlMap) {
        delete(&i);
    }

    mVehicleAccessControlMap.clear();
}

// Returns true if the given string, s, is a hex number that starts with 0x.
// Otherwise false is returned.
bool VehiclePropertyAccessControl::isHexNotation(std::string const& s) {
    return s.compare(0, 2, "0x") == 0
            && s.size() > 2
            && s.find_first_not_of("0123456789abcdefABCDEF", 2)
            == std::string::npos;
}

// Converts the string representation, access, to an integer form and store it
// in value. true is returned if the parameter, access, is "r", "w", "rw" or
// "wr". Otherwise false is returned. The parameters property and uid are
// only used for logging in the event that the string, access, was not
// recognized.
bool VehiclePropertyAccessControl::accessToInt(int32_t* const value,
                                               const xmlChar* property,
                                               const xmlChar* uid,
                                               const xmlChar* access) {
    if (!value || !property || !uid || !access) {
        ALOGE("Internal Error\n");
        return false;
    }

    if (xmlStrcmp(access, (const xmlChar *)"r") == 0) {
        *value = VEHICLE_PROP_ACCESS_READ;
    }
    else if (xmlStrcmp(access, (const xmlChar *)"w") == 0) {
        *value = VEHICLE_PROP_ACCESS_WRITE;
    }
    else if ((xmlStrcmp(access, (const xmlChar *)"rw") == 0)
            || (xmlStrcmp(access, (const xmlChar *)"wr") == 0)) {
        *value = VEHICLE_PROP_ACCESS_READ_WRITE;
    }
    else {
        ALOGE("Unknown access tag %s for UID %s in PROPERTY %s\n",access, uid,
              property);
        return false;
    }

    return true;
}

// Adds the property/uid pair to the mVehicleAccessControlMap map if the pair
// doesn't already exist. If the pair does exist, the access is updated.
bool VehiclePropertyAccessControl::updateOrCreate(int32_t uid, int32_t property,
                                                  int32_t access) {
    // check if property exists
    if (mVehicleAccessControlMap.count(property) == 0) {
        std::map<int32_t, int32_t>* uid_access =
                new std::map<int32_t, int32_t>();
        mVehicleAccessControlMap[property] = uid_access;
    }

    // Get the propertyAccessMap
    std::map<int32_t, int32_t>* uidAccessMap =
            mVehicleAccessControlMap[property];

    // Now check if uid exists
    if (uidAccessMap->count(uid) == 0) {
        (*uidAccessMap)[uid] = access;
        // uid was not found
        return false;
    }

    // The Property, Uid pair exist. So update the access
    (*uidAccessMap)[uid] = access;

    return true;
}

// Start parsing the xml file and populating the mVehicleAccessControlMap
// map. The parameter, a_node, must point to the first <PROPERTY> tag.
// true is returned if the parsing completed else false.
bool VehiclePropertyAccessControl::populate(xmlNode * a_node) {
    xmlNode* cur_node = NULL;
    xmlNode* child = NULL;
    xmlChar* property = NULL;
    xmlChar* property_value_str = NULL;
    xmlChar* uid = NULL;
    xmlChar* uid_value_str = NULL;
    xmlChar* access = NULL;
    int32_t property_value;
    int32_t uid_value;
    int32_t access_value;

    if (!a_node) {
        ALOGE("Internal Error");
        return false;
    }

    // Loop over all the PROPERTY tags
    for (cur_node = a_node; cur_node; cur_node = cur_node->next) {
        if ((xmlStrcmp(cur_node->name, (const xmlChar *)"PROPERTY") == 0) &&
                (cur_node->type == XML_ELEMENT_NODE)) {
            // Free the old property tag
            xmlFree(property);
            // get new property tag name attribute
            property = xmlGetProp(cur_node, (const xmlChar *)"name");
            if (!property) {
                ALOGE("PROPERTY given without name attribute");
                continue;
            }

            // get new property tag value attribute
            property_value_str = xmlGetProp(cur_node, (const xmlChar*)"value");
            if (!property_value_str) {
                ALOGE("PROPERTY given without value attribute");
                continue;
            }

            std::string tmp_str((const char*)property_value_str);
            if (isHexNotation(tmp_str)) {
                property_value = std::stoul(tmp_str, nullptr, 16);
            } else {
                property_value = std::stoul(tmp_str, nullptr, 10);
            }

            // Loop over all UID tags
            for (child = cur_node->children; child; child = child->next) {
                if ((xmlStrcmp(child->name, (const xmlChar*)"UID")==0) &&
                        (child->type == XML_ELEMENT_NODE)) {
                    if (property != NULL) {
                        // Free the old uid tag
                        xmlFree(uid);
                        // Free the old access tag
                        xmlFree(access);
                        // get new uid tag
                        uid = xmlGetProp(child, (const xmlChar*)"name");
                        // get new uid tag
                        uid_value_str = xmlGetProp(child,
                                                   (const xmlChar*)"value");
                        // get new access tag
                        access = xmlGetProp(child, (const xmlChar *)"access");

                        if (uid == NULL) {
                            ALOGE(
                                "UID tag for property %s given without name attribute\n",
                                property);
                        } else if (uid_value_str == NULL) {
                            ALOGE(
                                "UID tag for property %s given without value attribute\n",
                                property);
                        } else if (access == NULL) {
                            ALOGE(
                                "UID tag for property %s given without access attribute\n",
                                property);
                        } else {
                            std::string tmp_str((const char *)uid_value_str);
                            if (isHexNotation(tmp_str)) {
                                uid_value = std::stoul(tmp_str, nullptr, 16);
                            } else {
                                uid_value = std::stoul(tmp_str, nullptr, 10);
                            }

                            bool re1 = accessToInt(&access_value, property, uid,
                                                   access);
                            if (re1) {
                                if (!updateOrCreate(uid_value, property_value,
                                                    access_value)) {
                                    LOG_VERBOSE(
                                        "Property %08x was added: uid=%d access=%d\n",
                                        property_value, uid_value, access_value);
                                } else {
                                    LOG_VERBOSE("Property %08x was updated: uid=%d access=%d\n",
                                          property_value, uid_value, access_value);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    xmlFree(property);
    xmlFree(uid);
    xmlFree(access);

    return true;
}

// This method initializes the class by parsing the mandatory
// /system/etc/vns/vns_policy.xml file and then the optional
// /system/etc/vns/vendor_vns_policy.xml if found.
// false is returned if vns_policy.xml was not found or is
// invalid else true is returned.
bool VehiclePropertyAccessControl::init() {
    static const char* default_policy = "/system/etc/vns/vns_policy.xml";
    static const char* vendor_policy = "/system/etc/vns/vendor_vns_policy.xml";

    if (!process(default_policy)) {
        return false;
    }

    if (process(vendor_policy)) {
        ALOGE("Vendor VNS Policy was applied\n");
    }

    return true;
}

// Processes the vns_policy.xml or vendor_vns_policy.xml files
// and returns true on success else false is returned.
bool VehiclePropertyAccessControl::process(const char* policy) {
    xmlDoc* doc = NULL;
    xmlNode* root_element = NULL;

    doc = xmlReadFile(policy, NULL, 0);
    if (doc == NULL) {
        ALOGE("Could not find %s\n", policy);
        return false;
    }

    root_element = xmlDocGetRootElement(doc);
    if (!root_element) {
        ALOGE("Not a valid config file %s\n", policy);
        xmlFreeDoc(doc);
        return false;
    }

    if (xmlStrcmp(root_element->name, (const xmlChar *)"ALLOW") != 0) {
        ALOGE("Not a valid config file %s\n", policy);
        xmlFreeDoc(doc);
        return false;
    }

    bool ret = populate(root_element->children);

    xmlFreeDoc(doc);

    return ret;
}

void VehiclePropertyAccessControl::dump(String8& msg) {
    std::string perm;
    int32_t property;
    int32_t uid;
    int32_t access;
    std::map<int32_t, int32_t> *uid_access_map;

    for (auto& i: mVehicleAccessControlMap) {
        property = i.first;
        uid_access_map = mVehicleAccessControlMap[property];
        for (auto& j: *uid_access_map) {
            uid = j.first;
            access = (*uid_access_map)[uid];
            switch(access) {
                case VEHICLE_PROP_ACCESS_READ: perm = "read"; break;
                case VEHICLE_PROP_ACCESS_WRITE: perm = "write"; break;
                case VEHICLE_PROP_ACCESS_READ_WRITE: perm = "read/write"; break;
                default: perm="unknown";
            }
            msg.appendFormat("UID %d: property 0x%08x, access %s\n", uid,
                             property, perm.c_str());
        }
    }
}

// Test if the given uid has (read or write) access to the given property. If it
// does, true is returned and false is returned if it doesn't have access or the
// property or uid is unknown.
bool VehiclePropertyAccessControl::testAccess(int32_t property, int32_t uid,
                                              bool isWrite) {
    // Check if the property exists
    if (mVehicleAccessControlMap.count(property) == 0) {
        // property was not found
        return false;
    }

    // Get the uidAccessMap
    std::map<int32_t, int32_t>* uidAccessMap =
            mVehicleAccessControlMap[property];

    // Now check if uid exists
    if (uidAccessMap->count(uid) == 0) {
        // uid was not found
        return false;
    }

    // Get Access to this Property
    int32_t access = (*uidAccessMap)[uid];

    // Test if the UID has access to the property
    if (isWrite) {
        if ((access == VEHICLE_PROP_ACCESS_WRITE)
                || (access == VEHICLE_PROP_ACCESS_READ_WRITE)) {
            return true;
        } else {
            return false;
        }
    } else {
        if ((access == VEHICLE_PROP_ACCESS_READ)
                || (access == VEHICLE_PROP_ACCESS_READ_WRITE)) {
            return true;
        } else {
            return false;
        }
    }
}

};