Add check for hidl package root hash.

When compiling a module, say foo.bar.baz@1.0::IBaz, assume the package
root is foo.bar:some/dir/interfaces, then:
- look at the file some/dir/interfaces/current.txt
- read hash from file corresponding from foo.bar.baz@1.0::IBaz
- file supports same-line comments with '#'
- format of file is each line is blank or looks like:
    "<sha-256 hash> <fqName>"
- if the file is misformed or the hal does not match a sha in that file,
    then it is considered build breakage.

Test: build with and without changing frozen files
Bug: 34178341
Merged-In: Ieddbc796ea974ac7c2e8b95ca69009c31e0cfb60
Change-Id: Ieddbc796ea974ac7c2e8b95ca69009c31e0cfb60
diff --git a/Coordinator.cpp b/Coordinator.cpp
index 72c8557..dee56aa 100644
--- a/Coordinator.cpp
+++ b/Coordinator.cpp
@@ -26,6 +26,7 @@
 #include <hidl-util/StringHelper.h>
 
 #include "AST.h"
+#include "Hash.h"
 #include "Interface.h"
 
 extern android::status_t parseFile(android::AST *ast);
@@ -378,7 +379,14 @@
     }
 
     // enforce all rules.
-    status_t err = enforceMinorVersionUprevs(package);
+    status_t err;
+
+    err = enforceMinorVersionUprevs(package);
+    if (err != OK) {
+        return err;
+    }
+
+    err = enforceHashes(package);
     if (err != OK) {
         return err;
     }
@@ -491,6 +499,51 @@
     return OK;
 }
 
+status_t Coordinator::enforceHashes(const FQName &currentPackage) {
+    status_t err = OK;
+    std::vector<FQName> packageInterfaces;
+    err = appendPackageInterfacesToVector(currentPackage, &packageInterfaces);
+    if (err != OK) {
+        return err;
+    }
+
+    for (const FQName &currentFQName : packageInterfaces) {
+        AST *ast = parse(currentFQName);
+
+        if (ast == nullptr) {
+            err = UNKNOWN_ERROR;
+            continue;
+        }
+
+        std::string packageRootPath = getPackageRootPath(currentFQName);
+        std::string error;
+        std::vector<std::string> frozen = Hash::lookupHash(packageRootPath, currentFQName.string(), &error);
+
+        if (error.size() > 0) {
+            LOG(ERROR) << error;
+            err = UNKNOWN_ERROR;
+            continue;
+        }
+
+        // hash not define, interface not frozen
+        if (frozen.size() == 0) {
+            continue;
+        }
+
+        std::string currentHash = Hash::getHash(ast->getFilename()).hexString();
+
+        if(std::find(frozen.begin(), frozen.end(), currentHash) == frozen.end()) {
+            LOG(ERROR) << currentFQName.string() << " has hash " << currentHash
+                       << " which does not match hash on record. This interface has "
+                       << "been frozen. Do not change it!";
+            err = UNKNOWN_ERROR;
+            continue;
+        }
+    }
+
+    return err;
+}
+
 // static
 bool Coordinator::MakeParentHierarchy(const std::string &path) {
     static const mode_t kMode = 0755;
diff --git a/Coordinator.h b/Coordinator.h
index ddf5dfa..9f75d46 100644
--- a/Coordinator.h
+++ b/Coordinator.h
@@ -92,6 +92,7 @@
     // Enforce a set of restrictions on a set of packages. These include:
     //    - minor version upgrades
     // "packages" contains names like "android.hardware.nfc@1.1".
+    //    - hashing restrictions
     status_t enforceRestrictionsOnPackage(const FQName &fqName);
 
     static bool MakeParentHierarchy(const std::string &path);
@@ -117,6 +118,7 @@
 
     // Rules of enforceRestrictionsOnPackage are listed below.
     status_t enforceMinorVersionUprevs(const FQName &fqName);
+    status_t enforceHashes(const FQName &fqName);
 
     DISALLOW_COPY_AND_ASSIGN(Coordinator);
 };
diff --git a/Hash.cpp b/Hash.cpp
index 348a79f..0a8df47 100644
--- a/Hash.cpp
+++ b/Hash.cpp
@@ -19,8 +19,10 @@
 #include <fstream>
 #include <iomanip>
 #include <map>
+#include <regex>
 #include <sstream>
 
+#include <android-base/logging.h>
 #include <openssl/sha.h>
 
 namespace android {
@@ -72,4 +74,97 @@
     return mPath;
 }
 
+#define TOKEN "([^\\s*]+)"
+#define HASH TOKEN
+#define FQNAME TOKEN
+#define SPACES " +"
+#define MAYBE_SPACES " *"
+#define OPTIONAL_COMMENT "(?:#.*)?"
+static const std::regex kHashLine(
+    "(?:"
+        MAYBE_SPACES HASH SPACES FQNAME MAYBE_SPACES
+    ")?"
+    OPTIONAL_COMMENT);
+
+struct HashFile {
+    static const HashFile *parse(const std::string &path, std::string *err) {
+        static std::map<std::string, HashFile*> hashfiles;
+        auto it = hashfiles.find(path);
+
+        if (it == hashfiles.end()) {
+            it = hashfiles.insert(it, {path, readHashFile(path, err)});
+        }
+
+        return it->second;
+    }
+
+    std::vector<std::string> lookup(const std::string &fqName) const {
+        auto it = hashes.find(fqName);
+
+        if (it == hashes.end()) {
+            return {};
+        }
+
+        return it->second;
+    }
+
+private:
+    static HashFile *readHashFile(const std::string &path, std::string *err) {
+        std::ifstream stream(path);
+        if (!stream) {
+            return nullptr;
+        }
+
+        HashFile *file = new HashFile();
+        file->path = path;
+
+        std::string line;
+        while(std::getline(stream, line)) {
+            std::smatch match;
+            bool valid = std::regex_match(line, match, kHashLine);
+
+            if (!valid) {
+                *err = "Error reading line from " + path + ": " + line;
+                delete file;
+                return nullptr;
+            }
+
+            CHECK_EQ(match.size(), 3u);
+
+            std::string hash = match.str(1);
+            std::string fqName = match.str(2);
+
+            if (hash.size() == 0 && fqName.size() == 0) {
+                continue;
+            }
+
+            if (hash.size() == 0 || fqName.size() == 0) {
+                *err = "Hash or fqName empty on " + path + ": " + line;
+                delete file;
+                return nullptr;
+            }
+
+            file->hashes[fqName].push_back(hash);
+        }
+        return file;
+    }
+
+    std::string path;
+    std::map<std::string,std::vector<std::string>> hashes;
+};
+
+//static
+std::vector<std::string> Hash::lookupHash(const std::string &path,
+                                          const std::string &interfaceName,
+                                          std::string *err) {
+    *err = "";
+    const HashFile *file = HashFile::parse(path + "/current.txt", err);
+
+    if (file == nullptr || err->size() > 0) {
+        return {};
+    }
+
+    return file->lookup(interfaceName);
+}
+
 }  // android
\ No newline at end of file
diff --git a/Hash.h b/Hash.h
index 99dea31..420ff1c 100644
--- a/Hash.h
+++ b/Hash.h
@@ -26,6 +26,11 @@
 struct Hash {
     static const Hash &getHash(const std::string &path);
 
+    // returns matching hashes of interfaceName in $path/current.txt
+    static std::vector<std::string> lookupHash(const std::string &path,
+                                               const std::string &interfaceName,
+                                               std::string *err);
+
     std::string hexString() const;
 
     const std::vector<uint8_t> &raw() const;