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

#include <unistd.h>

#include <gtest/gtest.h>
#include <binder/IServiceManager.h>
#include <binder/ProcessState.h>
#include <utils/threads.h>
#include <utils/KeyedVector.h>
#include <utils/String8.h>
#include <utils/SystemClock.h>
#include <VehiclePropertyAccessControlForTesting.h>

namespace android {

class VehiclePropertyAccessControlTest : public testing::Test {
public:
    VehiclePropertyAccessControlTest() {}
    ~VehiclePropertyAccessControlTest() {}

public:
    static std::string xmlData;
    static std::string xmlData2;
    static const int32_t prop1;
    static const int32_t prop2;
    static const int32_t prop3;
    static const int32_t uid1;
    static const int32_t uid2;
    static const int32_t uid3;

protected:
    void SetUp() {}

protected:
    xmlDoc* doc;
    VehiclePropertyAccessControlForTesting mVehiclePropertyAccessControl;
};

std::string VehiclePropertyAccessControlTest::xmlData =
                            "<ALLOW>"
                            "<PROPERTY name=\"PROP1\" value=\"0xA\">"
                            "<UID name=\"UID1\" access=\"r\" value=\"1000\"/>"
                            "</PROPERTY>"
                            "<PROPERTY name=\"PROP2\" value=\"0xB\">"
                            "<UID name=\"UID2\" access=\"w\" value=\"2000\"/>"
                            "</PROPERTY>"
                            "<PROPERTY name=\"PROP3\" value=\"0xC\">"
                            "<UID name=\"UID3\" access=\"rw\" value=\"3000\"/>"
                            "</PROPERTY>"
                            "</ALLOW>";

const int32_t VehiclePropertyAccessControlTest::prop1 = 0xa;
const int32_t VehiclePropertyAccessControlTest::prop2 = 0xb;
const int32_t VehiclePropertyAccessControlTest::prop3 = 0xc;
const int32_t VehiclePropertyAccessControlTest::uid1 = 1000;
const int32_t VehiclePropertyAccessControlTest::uid2 = 2000;
const int32_t VehiclePropertyAccessControlTest::uid3 = 3000;

TEST_F(VehiclePropertyAccessControlTest, isHexNotation) {
    static const std::string shouldPass[] =
        {"0x01234567",
         "0x01abcdef",
         "0x01ABCDEF",
         "0x0"};

    static const std::string shouldFail[] =
        {"0",
         "0x",
         "01234567",
         "ABCDEF01",
         "0xabi"};

    for(auto& h : shouldPass) {
        ASSERT_TRUE(mVehiclePropertyAccessControl.isHexNotation(h));
    }

    for(auto& h : shouldFail) {
        ASSERT_FALSE(mVehiclePropertyAccessControl.isHexNotation(h));
    }
}

TEST_F(VehiclePropertyAccessControlTest, accessToInt) {
    static const char* property = "property";
    static const char* uid = "uid";
    struct ShouldPassType {std::string str; int32_t value;};
    static const struct ShouldPassType shouldPass[] = {
            {"r", VEHICLE_PROP_ACCESS_READ},
            {"w", VEHICLE_PROP_ACCESS_WRITE},
            {"rw", VEHICLE_PROP_ACCESS_READ_WRITE},
            {"wr", VEHICLE_PROP_ACCESS_READ_WRITE}
    };
    static const char* shouldFail[] = {"rr", "ww", "rww", "rwr", "", "k"};
    int32_t value;

    for(auto& h : shouldPass) {
        ASSERT_TRUE(mVehiclePropertyAccessControl.accessToInt(&value,
            (const xmlChar*)property, (const xmlChar*)uid,
            (const xmlChar*)h.str.c_str()));
        ASSERT_EQ(h.value, value);
    }

    for(auto& h : shouldFail) {
        ASSERT_FALSE(mVehiclePropertyAccessControl.accessToInt(&value,
            (const xmlChar*)property, (const xmlChar*)uid, (const xmlChar*)h));
    }
}

TEST_F(VehiclePropertyAccessControlTest, updateOrCreate) {
    std::map<int32_t, int32_t> *accessMap;

    // Empty the map
    mVehiclePropertyAccessControl.emptyAccessControlMap();

    // Make sure the property does not exist
    ASSERT_FALSE(mVehiclePropertyAccessControl.getAccessToProperty(prop1,
                                                                   &accessMap));

    // Create the property and give uid read access
    ASSERT_FALSE(mVehiclePropertyAccessControl.updateOrCreate(uid1, prop1,
                                                 VEHICLE_PROP_ACCESS_READ));

    // Make sure the property was created
    ASSERT_TRUE(mVehiclePropertyAccessControl.getAccessToProperty(prop1,
                                                                   &accessMap));

    // Make sure uid has read access to the property
    ASSERT_EQ((*accessMap)[uid1], VEHICLE_PROP_ACCESS_READ);

    // Give uid2 read/write access to the property
    ASSERT_FALSE(mVehiclePropertyAccessControl.updateOrCreate(uid2, prop1,
                                                VEHICLE_PROP_ACCESS_READ_WRITE));

    // Get the accessMap
    ASSERT_TRUE(mVehiclePropertyAccessControl.getAccessToProperty(prop1,
                                                                   &accessMap));
    // Make sure uid2 has read/write access to the property
    ASSERT_EQ((*accessMap)[uid2], VEHICLE_PROP_ACCESS_READ_WRITE);

    // Make sure uid still has read access to the property
    ASSERT_EQ((*accessMap)[uid1], VEHICLE_PROP_ACCESS_READ);

    // Change uid access to write for property
    ASSERT_TRUE(mVehiclePropertyAccessControl.updateOrCreate(uid1, prop1,
                                                     VEHICLE_PROP_ACCESS_WRITE));

    // Get the accessMap
    ASSERT_TRUE(mVehiclePropertyAccessControl.getAccessToProperty(prop1,
                                                                   &accessMap));

    // Make sure uid has write access to property
    ASSERT_EQ((*accessMap)[uid1], VEHICLE_PROP_ACCESS_WRITE);

    // Make sure uid2 has read write access to property
    ASSERT_EQ((*accessMap)[uid2], VEHICLE_PROP_ACCESS_READ_WRITE);
}

TEST_F(VehiclePropertyAccessControlTest, populate) {
    xmlNode* root_element;
    std::map<int32_t, int32_t> *accessMap;

    // Empty the map
    mVehiclePropertyAccessControl.emptyAccessControlMap();

    doc = xmlReadMemory(xmlData.c_str(), xmlData.length(), NULL, NULL, 0);
    ASSERT_TRUE(doc != NULL);
    root_element = xmlDocGetRootElement(doc);
    ASSERT_TRUE(root_element != NULL);

    bool result = mVehiclePropertyAccessControl.populate(root_element->children);

    ASSERT_TRUE(result);

    // Get the accessMap
    ASSERT_TRUE(mVehiclePropertyAccessControl.getAccessToProperty(prop1,
                                                                  &accessMap));

    // Make sure uid still has read access to the property
    ASSERT_EQ((*accessMap)[uid1], VEHICLE_PROP_ACCESS_READ);

    // Get the accessMap
    ASSERT_TRUE(mVehiclePropertyAccessControl.getAccessToProperty(prop2,
                                                                  &accessMap));

    // Make sure uid still has write access to the property
    ASSERT_EQ((*accessMap)[uid2], VEHICLE_PROP_ACCESS_WRITE);

    ASSERT_TRUE(mVehiclePropertyAccessControl.testAccess(prop1, uid1, 0));
    ASSERT_FALSE(mVehiclePropertyAccessControl.testAccess(prop1, uid1, 1));
    ASSERT_TRUE(mVehiclePropertyAccessControl.testAccess(prop2, uid2, 1));
    ASSERT_FALSE(mVehiclePropertyAccessControl.testAccess(prop2, uid2, 0));
    ASSERT_TRUE(mVehiclePropertyAccessControl.testAccess(prop3, uid3, 1));
    ASSERT_TRUE(mVehiclePropertyAccessControl.testAccess(prop3, uid3, 0));

    static const std::string dump_msg =
            "UID 1000: property 0x0000000a, access read\n"
            "UID 2000: property 0x0000000b, access write\n"
            "UID 3000: property 0x0000000c, access read/write\n";

    String8 msg;
    mVehiclePropertyAccessControl.dump(msg);

    ASSERT_EQ(dump_msg.compare(msg.string()), 0);

}

TEST_F(VehiclePropertyAccessControlTest, init) {
    xmlFreeDoc(doc);
    xmlCleanupParser();
    // Empty the map
    mVehiclePropertyAccessControl.emptyAccessControlMap();
    ASSERT_TRUE(mVehiclePropertyAccessControl.init());
}
}; // namespace android