Implement VintfObject::CheckCompatibility.

Test: libvintf_test

Bug: 36814503 libvintf Java API
Bug: 36126928 create tests for HalManifest::checkIncompatibility

Change-Id: I1ea9b6030a068623742d23f38d287b01946f2fc8
diff --git a/VintfObject.cpp b/VintfObject.cpp
index 8b908be..00e02dd 100644
--- a/VintfObject.cpp
+++ b/VintfObject.cpp
@@ -16,6 +16,9 @@
 
 #include "VintfObject.h"
 
+#include "CompatibilityMatrix.h"
+#include "parse_xml.h"
+
 #include <functional>
 #include <memory>
 #include <mutex>
@@ -36,9 +39,10 @@
 template <typename T, typename F>
 static const T *Get(
         LockedUniquePtr<T> *ptr,
+        bool skipCache,
         const F &fetchAllInformation) {
     std::unique_lock<std::mutex> _lock(ptr->mutex);
-    if (ptr->object == nullptr) {
+    if (skipCache || ptr->object == nullptr) {
         ptr->object = std::make_unique<T>();
         if (fetchAllInformation(ptr->object.get()) != OK) {
             ptr->object = nullptr; // frees the old object
@@ -48,31 +52,234 @@
 }
 
 // static
-const HalManifest *VintfObject::GetDeviceHalManifest() {
-    return Get(&gDeviceManifest,
+const HalManifest *VintfObject::GetDeviceHalManifest(bool skipCache) {
+    return Get(&gDeviceManifest, skipCache,
             std::bind(&HalManifest::fetchAllInformation, std::placeholders::_1,
                 "/vendor/manifest.xml"));
 }
 
 // static
-const HalManifest *VintfObject::GetFrameworkHalManifest() {
-    return Get(&gFrameworkManifest,
+const HalManifest *VintfObject::GetFrameworkHalManifest(bool skipCache) {
+    return Get(&gFrameworkManifest, skipCache,
             std::bind(&HalManifest::fetchAllInformation, std::placeholders::_1,
                 "/system/manifest.xml"));
 }
 
 // static
-const RuntimeInfo *VintfObject::GetRuntimeInfo() {
-    return Get(&gDeviceRuntimeInfo,
+const RuntimeInfo *VintfObject::GetRuntimeInfo(bool skipCache) {
+    return Get(&gDeviceRuntimeInfo, skipCache,
             std::bind(&RuntimeInfo::fetchAllInformation, std::placeholders::_1));
 }
 
+namespace details {
+
+static status_t mountSystem() {
+    // TODO(b/36814503): mount system partition here for recovery mode.
+    return OK;
+}
+
+static status_t mountVendor() {
+    // TODO(b/36814503): mount vendor partition here for recovery mode.
+    return OK;
+}
+
+enum class ParseStatus {
+    OK,
+    PARSE_ERROR,
+    DUPLICATED_FWK_ENTRY,
+    DUPLICATED_DEV_ENTRY,
+};
+
+std::string toString(ParseStatus status) {
+    switch(status) {
+        case ParseStatus::OK:                   return "OK";
+        case ParseStatus::PARSE_ERROR:          return "parse error";
+        case ParseStatus::DUPLICATED_FWK_ENTRY: return "duplicated framework";
+        case ParseStatus::DUPLICATED_DEV_ENTRY: return "duplicated device";
+    }
+    return "";
+}
+
+template<typename T>
+ParseStatus tryParse(const std::string &xml, const XmlConverter<T> &parse,
+        std::unique_ptr<T> *fwk, std::unique_ptr<T> *dev) {
+    std::unique_ptr<T> ret = std::make_unique<T>();
+    if (!parse(ret.get(), xml)) {
+        return ParseStatus::PARSE_ERROR;
+    }
+    if (ret->type() == SchemaType::FRAMEWORK) {
+        if (fwk->get() != nullptr) {
+            return ParseStatus::DUPLICATED_FWK_ENTRY;
+        }
+        *fwk = std::move(ret);
+    } else if (ret->type() == SchemaType::DEVICE) {
+        if (dev->get() != nullptr) {
+            return ParseStatus::DUPLICATED_DEV_ENTRY;
+        }
+        *dev = std::move(ret);
+    }
+    return ParseStatus::OK;
+}
+
+template<typename T, typename GetFunction>
+static status_t getMissing(const T *pkg, bool mount,
+        std::function<status_t(void)> mountFunction,
+        const T **updated,
+        GetFunction getFunction) {
+    if (pkg != nullptr) {
+        *updated = pkg;
+    } else {
+        if (mount) {
+            (void)mountFunction(); // ignore mount errors
+        }
+        *updated = getFunction();
+    }
+    return OK;
+}
+
+#define ADD_MESSAGE(__error__)  \
+    if (error != nullptr) {     \
+        *error += (__error__);  \
+    }                           \
+
+struct PackageInfo {
+    struct Pair {
+        std::unique_ptr<HalManifest>         manifest;
+        std::unique_ptr<CompatibilityMatrix> matrix;
+    };
+    Pair dev;
+    Pair fwk;
+};
+
+struct UpdatedInfo {
+    struct Pair {
+        const HalManifest         *manifest;
+        const CompatibilityMatrix *matrix;
+    };
+    Pair dev;
+    Pair fwk;
+    const RuntimeInfo *runtimeInfo;
+};
+
+// Parse all information from package;
+// Get missing information from the device;
+// Do compatibility check.
+int32_t checkCompatibility(const std::vector<std::string> &xmls, bool mount,
+        std::function<status_t(void)> mountSystem,
+        std::function<status_t(void)> mountVendor,
+        std::function<const HalManifest *(bool)> GetFrameworkHalManifest,
+        std::function<const HalManifest *(bool)> GetDeviceHalManifest,
+        std::function<const RuntimeInfo *(bool)> GetRuntimeInfo,
+        std::string *error) {
+
+    status_t status;
+    ParseStatus parseStatus;
+    PackageInfo pkg; // All information from package.
+    UpdatedInfo updated; // All files and runtime info after the update.
+
+    if (xmls.empty()) {
+        ADD_MESSAGE("nothing to update");
+        return BAD_VALUE;
+    }
+
+    // parse all information from package
+    for (const auto &xml : xmls) {
+        parseStatus = tryParse(xml, gHalManifestConverter, &pkg.fwk.manifest, &pkg.dev.manifest);
+        if (parseStatus == ParseStatus::OK) {
+            continue; // work on next one
+        }
+        if (parseStatus != ParseStatus::PARSE_ERROR) {
+            ADD_MESSAGE(toString(parseStatus) + " manifest");
+            return ALREADY_EXISTS;
+        }
+        parseStatus = tryParse(xml, gCompatibilityMatrixConverter, &pkg.fwk.matrix, &pkg.dev.matrix);
+        if (parseStatus == ParseStatus::OK) {
+            continue; // work on next one
+        }
+        if (parseStatus != ParseStatus::PARSE_ERROR) {
+            ADD_MESSAGE(toString(parseStatus) + " matrix");
+            return ALREADY_EXISTS;
+        }
+        ADD_MESSAGE(toString(parseStatus)); // parse error
+        return BAD_VALUE;
+    }
+
+    // get missing info from device
+    if ((status = getMissing(pkg.fwk.manifest.get(), mount, mountSystem, &updated.fwk.manifest,
+            std::bind(GetFrameworkHalManifest, true /* skipCache */))) != OK) {
+        return status;
+    }
+    if ((status = getMissing(pkg.dev.manifest.get(), mount, mountVendor, &updated.dev.manifest,
+            std::bind(GetDeviceHalManifest, true /* skipCache */))) != OK) {
+        return status;
+    }
+    updated.runtimeInfo = GetRuntimeInfo(true /* skipCache */);
+    // TODO(b/37321309) get matrices from the device as well.
+    updated.fwk.matrix = pkg.fwk.matrix.get();
+    updated.dev.matrix = pkg.dev.matrix.get();
+
+    // null checks for files and runtime info after the update
+    // TODO(b/37321309) if a compat mat is missing, it is not matched and considered compatible.
+    if (updated.fwk.manifest == nullptr) {
+        ADD_MESSAGE("No framework manifest file from device or from update package");
+        return NO_INIT;
+    }
+    if (updated.dev.manifest == nullptr) {
+        ADD_MESSAGE("No device manifest file from device or from update package");
+        return NO_INIT;
+    }
+    if (updated.fwk.matrix == nullptr) {
+        ADD_MESSAGE("No framework matrix, skipping;");
+        // TODO(b/37321309) consider missing matricies as errors.
+    }
+    if (updated.dev.matrix == nullptr) {
+        ADD_MESSAGE("No device matrix, skipping;");
+        // TODO(b/37321309) consider missing matricies as errors.
+    }
+    if (updated.runtimeInfo == nullptr) {
+        ADD_MESSAGE("No runtime info from device");
+        return NO_INIT;
+    }
+
+    // compatiblity check.
+    // TODO(b/37321309) outer if checks can be removed if we consider missing matrices as errors.
+    if (updated.dev.manifest && updated.fwk.matrix) {
+        if (!updated.dev.manifest->checkCompatibility(*updated.fwk.matrix, error)) {
+            error->insert(0, "Device manifest and framework compatibility matrix "
+                             "are incompatible: ");
+            return INCOMPATIBLE;
+        }
+    }
+    if (updated.fwk.manifest && updated.dev.matrix) {
+        if (!updated.fwk.manifest->checkCompatibility(*updated.dev.matrix, error)) {
+            error->insert(0, "Framework manifest and device compatibility matrix "
+                             "are incompatible: ");
+            return INCOMPATIBLE;
+        }
+    }
+    if (updated.runtimeInfo && updated.fwk.matrix) {
+        if (!updated.runtimeInfo->checkCompatibility(*updated.fwk.matrix, error)) {
+            error->insert(0, "Runtime info and framework compatibility matrix "
+                             "are incompatible: ");
+            return INCOMPATIBLE;
+        }
+    }
+
+    return COMPATIBLE;
+}
+
+} // namespace details
+
 // static
 int32_t VintfObject::CheckCompatibility(
-        const std::vector<std::string> &,
-        bool) {
-    // TODO(b/36814503): actually do the verification.
-    return -1;
+        const std::vector<std::string> &xmls,
+        bool mount, std::string *error) {
+    return details::checkCompatibility(xmls, mount,
+            &details::mountSystem, &details::mountVendor,
+            &VintfObject::GetFrameworkHalManifest,
+            &VintfObject::GetDeviceHalManifest,
+            &VintfObject::GetRuntimeInfo,
+            error);
 }