fru-device: move Adding and updating a property into a helper

Adds updating and adding properties to a helper function
that's divorced from dbus logic. This allows other code
to modify a fru buffer.

Tested:
Called updateproperty on nvl32-obmc. Result was correct

Change-Id: I432f89003cf5608900c018f72edef877534bfe40
Signed-off-by: Marc Olberding <molberding@nvidia.com>
diff --git a/src/fru_device/fru_utils.cpp b/src/fru_device/fru_utils.cpp
index 385ea72..c9669de 100644
--- a/src/fru_device/fru_utils.cpp
+++ b/src/fru_device/fru_utils.cpp
@@ -1667,3 +1667,86 @@
     // Match against editable fields
     return std::ranges::contains(editableFields, subField);
 }
+
+bool updateAddProperty(const std::string& propertyValue,
+                       const std::string& propertyName,
+                       std::vector<uint8_t>& fruData)
+{
+    // Validate field length: must be 2–63 characters
+    const size_t len = propertyValue.length();
+    if (len == 1 || len > 63)
+    {
+        lg2::error(
+            "FRU field data must be 0 or between 2 and 63 characters. Invalid Length: {LEN}",
+            "LEN", len);
+        return false;
+    }
+
+    if (fruData.empty())
+    {
+        lg2::error("Empty FRU data\n");
+        return false;
+    }
+
+    // Extract area name (prefix before underscore)
+    std::string areaName = propertyName.substr(0, propertyName.find('_'));
+    auto areaIterator =
+        std::find(fruAreaNames.begin(), fruAreaNames.end(), areaName);
+    if (areaIterator == fruAreaNames.end())
+    {
+        lg2::error("Failed to get FRU area for property: {AREA}", "AREA",
+                   areaName);
+        return false;
+    }
+
+    fruAreas fruAreaToUpdate = static_cast<fruAreas>(
+        std::distance(fruAreaNames.begin(), areaIterator));
+
+    std::vector<std::vector<uint8_t>> areasData;
+    if (!disassembleFruData(fruData, areasData))
+    {
+        lg2::error("Failed to disassemble Fru Data");
+        return false;
+    }
+
+    std::vector<uint8_t>& areaData =
+        areasData[static_cast<size_t>(fruAreaToUpdate)];
+    if (areaData.empty())
+    {
+        // If ENABLE_FRU_AREA_RESIZE is not defined then return with failure
+#ifndef ENABLE_FRU_AREA_RESIZE
+        lg2::error(
+            "FRU area {AREA} not present and ENABLE_FRU_AREA_RESIZE is not set. "
+            "Returning failure.",
+            "AREA", areaName);
+        return false;
+#endif
+        if (!createDummyArea(fruAreaToUpdate, areaData))
+        {
+            lg2::error("Failed to create dummy area for {AREA}", "AREA",
+                       areaName);
+            return false;
+        }
+    }
+
+    if (!setField(fruAreaToUpdate, areaData, propertyName, propertyValue))
+    {
+        lg2::error("Failed to set field value for property: {PROPERTY}",
+                   "PROPERTY", propertyName);
+        return false;
+    }
+
+    if (!assembleFruData(fruData, areasData))
+    {
+        lg2::error("Failed to reassemble FRU data");
+        return false;
+    }
+
+    if (fruData.empty())
+    {
+        lg2::error("FRU data is empty after assembly");
+        return false;
+    }
+
+    return true;
+}