Implement Acquire Lock Function in Lock Service

- This commit  implements the rest API by which any external
client(Ex: Hardware Management Console) can request for a
single/multiple locks as per the design specification mentioned
in `docs/designs/management-console/hmc-lock-management.md`

Tested By:

1. curl -k -H "X-Auth-Token:$bmc_tokens" -XPOST -H "Content-type: application/json" -d '{
  "Request" :[
             {
                 "LockType":"Read",
                 "SegmentFlags":
                                 [
                                   {"LockFlag":"LockSame","SegmentLength":3},
                                   {"LockFlag":"DontLock","SegmentLength":4}
                                 ],
                 "ResourceID": 256
              }
         ]
 }' https://<ip>/ibm/v1/HMC/LockService/Actions/LockService.AcquireLock

2.curl -k -H "X-Auth-Token:$bmc_tokens" -XPOST -H "Content-type: application/json" -d '{
 "Request" :[
             {
                 "LockType":"Read",
                 "SegmentFlags":
                                 [
                                   {"LockFlag":"LockAll","SegmentLength":2},
                                   {"LockFlag":"DontLock","SegmentLength":1}
                                 ],
                 "ResourceID": 234
              },
              {
                 "LockType" : "Read",
                 "SegmentFlags":
                                 [
                                   {"LockFlag":"DontLock","SegmentLength":2},
                                   {"LockFlag":"DontLock","SegmentLength":1}
                                 ],
                 "ResourceID": 234
             }
         ]}' https://<ip>/ibm/v1/HMC/LockService/Actions/LockService.AcquireLock

Signed-off-by: manojkiraneda <manojkiran.eda@gmail.com>
Change-Id: Ia173878702afe7c00160b7935d6a03099b7df622
diff --git a/include/ibm/locks.hpp b/include/ibm/locks.hpp
new file mode 100644
index 0000000..60c55bf
--- /dev/null
+++ b/include/ibm/locks.hpp
@@ -0,0 +1,422 @@
+#pragma once
+
+#include <app.h>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/container/flat_map.hpp>
+#include <filesystem>
+#include <nlohmann/json.hpp>
+
+namespace crow
+{
+namespace ibm_mc_lock
+{
+
+namespace fs = std::filesystem;
+using SType = std::string;
+
+/*----------------------------------------
+|Segment flags : LockFlag | SegmentLength|
+------------------------------------------*/
+
+using SegmentFlags = std::vector<std::pair<SType, uint32_t>>;
+
+// Lockrequest = session-id | hmc-id | locktype | resourceid | segmentinfo
+using LockRequest = std::tuple<SType, SType, SType, uint64_t, SegmentFlags>;
+
+using LockRequests = std::vector<LockRequest>;
+using Rc =
+    std::pair<bool, std::variant<uint32_t, std::pair<uint32_t, LockRequest>>>;
+using RcRelaseLock = std::pair<bool, LockRequest>;
+using RcGetLocklist = std::pair<
+    bool,
+    std::variant<std::string, std::vector<std::pair<uint32_t, LockRequests>>>>;
+
+using RcAcquireLock = std::pair<bool, std::variant<Rc, std::pair<bool, int>>>;
+
+class Lock
+{
+    uint32_t transactionId;
+    boost::container::flat_map<uint32_t, LockRequests> lockTable;
+
+    /*
+     * This function implements the logic for validating an incomming
+     * lock request/requests.
+     *
+     * Returns : True (if Valid)
+     * Returns : False (if not a Valid lock request)
+     */
+
+    bool isValidLockRequest(const LockRequest);
+
+    /*
+     * This function implements the logic of checking if the incomming
+     * multi-lock request is not having conflicting requirements.
+     *
+     * Returns : True (if conflicting)
+     * Returns : False (if not conflicting)
+     */
+
+    bool isConflictRequest(const LockRequests);
+    /*
+     * Implements the core algorithm to find the conflicting
+     * lock requests.
+     *
+     * This functions takes two lock requests and check if both
+     * are conflicting to each other.
+     *
+     * Returns : True (if conflicting)
+     * Returns : False (if not conflicting)
+     */
+    bool isConflictRecord(const LockRequest, const LockRequest);
+
+    /*
+     * This function implements the logic of checking the conflicting
+     * locks from a incomming single/multi lock requests with the already
+     * existing lock request in the lock table.
+     *
+     */
+
+    Rc isConflictWithTable(const LockRequests);
+
+    /*
+     * This function implements the algorithm for checking the respective
+     * bytes of the resource id based on the lock management algorithm.
+     */
+
+    bool checkByte(uint64_t, uint64_t, uint32_t);
+
+    /*
+     * This functions implements a counter that generates a unique 32 bit
+     * number for every successful transaction. This number will be used by
+     * the Management Console for debug.
+     */
+    uint32_t generateTransactionId();
+
+  public:
+    /*
+     * This function implements the logic for acquiring a lock on a
+     * resource if the incomming request is legitimate without any
+     * conflicting requirements & without any conflicting requirement
+     * with the exsiting locks in the lock table.
+     *
+     */
+
+    RcAcquireLock acquireLock(const LockRequests);
+
+  public:
+    Lock()
+    {
+        transactionId = 0;
+    }
+
+} lockObject;
+
+RcAcquireLock Lock::acquireLock(const LockRequests lockRequestStructure)
+{
+
+    // validate the lock request
+
+    for (auto &lockRecord : lockRequestStructure)
+    {
+        bool status = isValidLockRequest(lockRecord);
+        if (!status)
+        {
+            BMCWEB_LOG_DEBUG << "Not a Valid record";
+            BMCWEB_LOG_DEBUG << "Bad json in request";
+            return std::make_pair(true, std::make_pair(status, 0));
+        }
+    }
+    // check for conflict record
+
+    const LockRequests &multiRequest = lockRequestStructure;
+    bool status = isConflictRequest(multiRequest);
+
+    if (status)
+    {
+        BMCWEB_LOG_DEBUG << "There is a conflict within itself";
+        return std::make_pair(true, std::make_pair(status, 1));
+    }
+    else
+    {
+        BMCWEB_LOG_DEBUG << "The request is not conflicting within itself";
+
+        // Need to check for conflict with the locktable entries.
+
+        auto conflict = isConflictWithTable(multiRequest);
+
+        BMCWEB_LOG_DEBUG << "Done with checking conflict with the locktable";
+        return std::make_pair(false, conflict);
+    }
+
+    return std::make_pair(true, std::make_pair(true, 1));
+}
+
+bool Lock::isValidLockRequest(const LockRequest refLockRecord)
+{
+
+    // validate the locktype
+
+    if (!((boost::equals(std::get<2>(refLockRecord), "Read") ||
+           (boost::equals(std::get<2>(refLockRecord), "Write")))))
+    {
+        BMCWEB_LOG_DEBUG << "Validation of LockType Failed";
+        BMCWEB_LOG_DEBUG << "Locktype : " << std::get<2>(refLockRecord);
+        return false;
+    }
+
+    BMCWEB_LOG_DEBUG << static_cast<int>(std::get<4>(refLockRecord).size());
+
+    // validate the number of segments
+    // Allowed No of segments are between 2 and 6
+    if ((static_cast<int>(std::get<4>(refLockRecord).size()) > 6) ||
+        (static_cast<int>(std::get<4>(refLockRecord).size()) < 2))
+    {
+        BMCWEB_LOG_DEBUG << "Validation of Number of Segements Failed";
+        BMCWEB_LOG_DEBUG << "Number of Segments provied : "
+                         << sizeof(std::get<4>(refLockRecord));
+        return false;
+    }
+
+    int lockFlag = 0;
+    // validate the lockflags & segment length
+
+    for (const auto &p : std::get<4>(refLockRecord))
+    {
+
+        // validate the lock flags
+        // Allowed lockflags are locksame,lockall & dontlock
+
+        if (!((boost::equals(p.first, "LockSame") ||
+               (boost::equals(p.first, "LockAll")) ||
+               (boost::equals(p.first, "DontLock")))))
+        {
+            BMCWEB_LOG_DEBUG << "Validation of lock flags failed";
+            BMCWEB_LOG_DEBUG << p.first;
+            return false;
+        }
+
+        // validate the segment length
+        // Allowed values of segment length are between 1 and 4
+
+        if (p.second < 1 || p.second > 4)
+        {
+            BMCWEB_LOG_DEBUG << "Validation of Segment Length Failed";
+            BMCWEB_LOG_DEBUG << p.second;
+            return false;
+        }
+
+        if ((boost::equals(p.first, "LockSame") ||
+             (boost::equals(p.first, "LockAll"))))
+        {
+            ++lockFlag;
+            if (lockFlag >= 2)
+            {
+                return false;
+            }
+        }
+    }
+
+    // validate the segment length
+    return true;
+}
+
+Rc Lock::isConflictWithTable(const LockRequests refLockRequestStructure)
+{
+
+    uint32_t transactionId;
+
+    if (lockTable.empty())
+    {
+        transactionId = generateTransactionId();
+        BMCWEB_LOG_DEBUG << transactionId;
+        // Lock table is empty, so we are safe to add the lockrecords
+        // as there will be no conflict
+        BMCWEB_LOG_DEBUG << "Lock table is empty, so adding the lockrecords";
+        lockTable.emplace(std::pair<uint32_t, LockRequests>(
+            transactionId, refLockRequestStructure));
+
+        return std::make_pair(false, transactionId);
+    }
+
+    else
+    {
+        BMCWEB_LOG_DEBUG
+            << "Lock table is not empty, check for conflict with lock table";
+        // Lock table is not empty, compare the lockrequest entries with
+        // the entries in the lock table
+
+        for (const auto &lockRecord1 : refLockRequestStructure)
+        {
+            for (const auto &map : lockTable)
+            {
+                for (const auto &lockRecord2 : map.second)
+                {
+                    bool status = isConflictRecord(lockRecord1, lockRecord2);
+                    if (status)
+                    {
+                        return std::make_pair(
+                            true, std::make_pair(map.first, lockRecord2));
+                    }
+                }
+            }
+        }
+
+        // Reached here, so no conflict with the locktable, so we are safe to
+        // add the request records into the lock table
+
+        // Lock table is empty, so we are safe to add the lockrecords
+        // as there will be no conflict
+        BMCWEB_LOG_DEBUG << " Adding elements into lock table";
+        transactionId = generateTransactionId();
+        lockTable.emplace(
+            std::make_pair(transactionId, refLockRequestStructure));
+    }
+    return std::make_pair(false, transactionId);
+}
+
+bool Lock::isConflictRequest(const LockRequests refLockRequestStructure)
+{
+    // check for all the locks coming in as a part of single request
+    // return conflict if any two lock requests are conflicting
+
+    if (refLockRequestStructure.size() == 1)
+    {
+        BMCWEB_LOG_DEBUG << "Only single lock request, so there is no conflict";
+        // This means , we have only one lock request in the current
+        // request , so no conflict within the request
+        return false;
+    }
+
+    else
+    {
+        BMCWEB_LOG_DEBUG
+            << "There are multiple lock requests coming in a single request";
+
+        // There are multiple requests a part of one request
+
+        for (uint32_t i = 0; i < refLockRequestStructure.size(); i++)
+        {
+            for (uint32_t j = i + 1; j < refLockRequestStructure.size(); j++)
+            {
+                const LockRequest &p = refLockRequestStructure[i];
+                const LockRequest &q = refLockRequestStructure[j];
+                bool status = isConflictRecord(p, q);
+
+                if (status)
+                {
+                    return true;
+                }
+            }
+        }
+    }
+    return false;
+}
+
+// This function converts the provided uint64_t resource id's from the two
+// lock requests subjected for comparision, and this function also compares
+// the content by bytes mentioned by a uint32_t number.
+
+// If all the elements in the lock requests which are subjected for comparison
+// are same, then the last comparision would be to check for the respective
+// bytes in the resourceid based on the segment length.
+
+bool Lock::checkByte(uint64_t resourceId1, uint64_t resourceId2,
+                     uint32_t position)
+{
+    uint8_t *p = reinterpret_cast<uint8_t *>(&resourceId1);
+    uint8_t *q = reinterpret_cast<uint8_t *>(&resourceId2);
+
+    BMCWEB_LOG_DEBUG << "Comparing bytes " << std::to_string(p[position]) << ","
+                     << std::to_string(q[position]);
+    if (p[position] != q[position])
+    {
+        return false;
+    }
+
+    else
+    {
+        return true;
+    }
+    return true;
+}
+
+bool Lock::isConflictRecord(const LockRequest refLockRecord1,
+                            const LockRequest refLockRecord2)
+{
+    // No conflict if both are read locks
+
+    if (boost::equals(std::get<2>(refLockRecord1), "Read") &&
+        boost::equals(std::get<2>(refLockRecord2), "Read"))
+    {
+        BMCWEB_LOG_DEBUG << "Both are read locks, no conflict";
+        return false;
+    }
+
+    else
+    {
+        uint32_t i = 0;
+        for (const auto &p : std::get<4>(refLockRecord1))
+        {
+
+            // return conflict when any of them is try to lock all resources
+            // under the current resource level.
+            if (boost::equals(p.first, "LockAll") ||
+                boost::equals(std::get<4>(refLockRecord2)[i].first, "LockAll"))
+            {
+                BMCWEB_LOG_DEBUG
+                    << "Either of the Comparing locks are trying to lock all "
+                       "resources under the current resource level";
+                return true;
+            }
+
+            // determine if there is a lock-all-with-same-segment-size.
+            // If the current segment sizes are the same,then we should fail.
+
+            if ((boost::equals(p.first, "LockSame") ||
+                 boost::equals(std::get<4>(refLockRecord2)[i].first,
+                               "LockSame")) &&
+                (p.second == std::get<4>(refLockRecord2)[i].second))
+            {
+                return true;
+            }
+
+            // if segment lengths are not the same, it means two different locks
+            // So no conflict
+            if (p.second != std::get<4>(refLockRecord2)[i].second)
+            {
+                BMCWEB_LOG_DEBUG << "Segment lengths are not same";
+                BMCWEB_LOG_DEBUG << "Segment 1 length : " << p.second;
+                BMCWEB_LOG_DEBUG << "Segment 2 length : "
+                                 << std::get<4>(refLockRecord2)[i].second;
+                return false;
+            }
+
+            // compare segment data
+
+            for (uint32_t i = 0; i < p.second; i++)
+            {
+                // if the segment data is different , then the locks is on a
+                // different resource So no conflict between the lock records
+                if (!(checkByte(std::get<3>(refLockRecord1),
+                                std::get<3>(refLockRecord2), i)))
+                {
+                    return false;
+                }
+            }
+
+            ++i;
+        }
+    }
+
+    return false;
+}
+
+uint32_t Lock::generateTransactionId()
+{
+    ++transactionId;
+    return transactionId;
+}
+
+} // namespace ibm_mc_lock
+} // namespace crow