Validate changes to default settings.

Resolves openbmc/openbmc#1771

Change-Id: I763e811e88710425131ec504ff933e3c41c458e6
Signed-off-by: Dhruvaraj Subhashchandran <dhruvaraj@in.ibm.com>
diff --git a/Makefile.am b/Makefile.am
index 55ea2d6..50c380e 100755
--- a/Makefile.am
+++ b/Makefile.am
@@ -26,9 +26,11 @@
 
 phosphor_settings_manager_CXXFLAGS = \
 	$(PHOSPHOR_DBUS_INTERFACES_CFLAGS) \
-	$(SDBUSPLUS_CFLAGS)
+	$(SDBUSPLUS_CFLAGS) \
+        $(PHOSPHOR_LOGGING_CFLAGS)
 
 phosphor_settings_manager_LDADD = \
 	$(PHOSPHOR_DBUS_INTERFACES_LIBS) \
 	$(SDBUSPLUS_LIBS) \
+        $(PHOSPHOR_LOGGING_LIBS) \
 	-lstdc++fs
diff --git a/configure.ac b/configure.ac
index 083d623..895ce17 100644
--- a/configure.ac
+++ b/configure.ac
@@ -16,6 +16,8 @@
     AC_MSG_ERROR(["Requires phosphor-dbus-interfaces package."]))
 PKG_CHECK_MODULES([SDBUSPLUS], [sdbusplus],,
     AC_MSG_ERROR(["Requires sdbusplus package."]))
+PKG_CHECK_MODULES([PHOSPHOR_LOGGING], [phosphor-logging],,\
+     AC_MSG_ERROR(["Requires phosphor-logging package."]))
 
 LT_INIT
 
diff --git a/settings_example.yaml b/settings_example.yaml
index d6e7f88..82b63be 100644
--- a/settings_example.yaml
+++ b/settings_example.yaml
@@ -1,9 +1,31 @@
 /xyz/openbmc_project/control/host0/boot_mode:
     Interface: xyz.openbmc_project.Control.Boot.Mode
-    Defaults:
-        BootMode: Mode::Modes::Safe
+    Properties:
+        BootMode:
+            Default: Mode::Modes::Safe
 
 /xyz/openbmc_project/control/host1/boot_mode:
     Interface: xyz.openbmc_project.Control.Boot.Mode
-    Defaults:
-        BootMode: Mode::Modes::Regular
+    Properties:
+        BootMode:
+            Default: Mode::Modes::Regular
+
+/xyz/openbmc_project/network/host0/intf:
+    Interface: xyz.openbmc_project.Network.MACAddress
+    Properties:
+        MACAddress:
+            Default: '"00:00:00:00:00:00"'
+            Validation:
+                Type: "regex"
+                Validator: '^([0-9A-F]{2}[:-]){5}([0-9A-F]{2})$'
+/xyz/openbmc_project/control/host0/power_cap:
+    Interface: xyz.openbmc_project.Control.Power.Cap
+    Properties:
+        PowerCap:
+            Default: 0
+            Validation:
+                Type: "range"
+                Validator: "0..1000"
+                Unit: "Watts"
+        PowerCapEnable:
+             Default: 'false'
diff --git a/settings_manager.mako.hpp b/settings_manager.mako.hpp
index cc2ab5b..9fca3da 100644
--- a/settings_manager.mako.hpp
+++ b/settings_manager.mako.hpp
@@ -2,12 +2,14 @@
 ## into the rendered file; feel free to edit this file.
 // WARNING: Generated header. Do not edit!
 <%
+import re
 from collections import defaultdict
 objects = list(settingsDict.viewkeys())
 sdbusplus_namespaces = []
 sdbusplus_includes = []
 interfaces = []
 props = defaultdict(list)
+validators = defaultdict(tuple)
 
 def get_setting_sdbusplus_type(setting_intf):
     setting = "sdbusplus::" + setting_intf.replace('.', '::')
@@ -33,7 +35,12 @@
 #include <fstream>
 #include <utility>
 #include <experimental/filesystem>
+#include <regex>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/log.hpp>
 #include "config.h"
+#include <xyz/openbmc_project/Common/error.hpp>
 
 % for i in set(sdbusplus_includes):
 #include "${i}"
@@ -64,8 +71,14 @@
     intf = settingsDict[object]['Interface']
     interfaces.append(intf)
     if intf not in props:
-        for property, value in settingsDict[object]['Defaults'].items():
+        for property, property_metadata in settingsDict[object]['Properties'].items():
             props[intf].append(property)
+            for attribute, value in property_metadata.items():
+                if attribute == 'Validation':
+                    if value['Type'] == "range":
+                        validators[property] = (value['Type'], value['Validator'], value['Unit'])
+                    else:
+                        validators[property] = (value['Type'], value['Validator'])
 %>\
 % endfor
 % for intf in set(interfaces):
@@ -94,11 +107,29 @@
 
 % for arg in props[intf]:
 <% t = arg[:1].lower() + arg[1:] %>\
+<% fname = "validate"+arg %>\
         decltype(std::declval<Base>().${t}()) ${t}(decltype(std::declval<Base>().${t}()) value) override
         {
             auto result = Base::${t}();
             if (value != result)
             {
+            % if arg in validators.keys():
+                if (!${fname}(value))
+                {
+                    namespace error =
+                        sdbusplus::xyz::openbmc_project::Common::Error;
+                    namespace metadata =
+                        phosphor::logging::xyz::openbmc_project::Common;
+                    phosphor::logging::report<error::InvalidArgument>(
+                        metadata::InvalidArgument::ARGUMENT_NAME("${t}"),
+                    % if validators[arg][0] != "regex":
+                        metadata::InvalidArgument::ARGUMENT_VALUE(std::to_string(value).c_str()));
+                    % else:
+                        metadata::InvalidArgument::ARGUMENT_VALUE(value.c_str()));
+                    % endif
+                    return result;
+                }
+             % endif
                 fs::path p(SETTINGS_PERSIST_PATH);
                 p /= path;
                 fs::create_directories(p.parent_path());
@@ -114,6 +145,46 @@
 % endfor
     private:
         fs::path path;
+% for arg in props[intf]:
+% if arg in validators.keys():
+<% funcName = "validate"+arg %>\
+<% t = arg[:1].lower() + arg[1:] %>\
+
+        bool ${funcName}(decltype(std::declval<Base>().${t}()) value)
+        {
+            bool matched = false;
+        % if (arg in validators.keys()) and (validators[arg][0] == 'regex'):
+            std::regex regexToCheck("${validators[arg][1]}");
+            matched = std::regex_search(value, regexToCheck);
+            if (!matched)
+            {
+                std::string err = "Input parameter for ${arg} is invalid "
+                    "Input: " + value + " not in the format of this regex: "
+                    "${validators[arg][1]}";
+                using namespace phosphor::logging;
+                log<level::ERR>(err.c_str());
+            }
+        % elif (arg in validators.keys()) and (validators[arg][0] == 'range'):
+<% lowhigh = re.split('\.\.', validators[arg][1]) %>\
+            if ((value <= ${lowhigh[1]}) && (value >= ${lowhigh[0]}))
+            {
+                matched = true;
+            }
+            else
+            {
+                std::string err = "Input parameter for ${arg} is invalid "
+                    "Input: " + std::to_string(value) + "in uint: "
+                    "${validators[arg][2]} is not in range:${validators[arg][1]}";
+                using namespace phosphor::logging;
+                log<level::ERR>(err.c_str());
+            }
+        % elif (arg in validators.keys()):
+            <% assert("Unknown validation type: arg") %>\
+        % endif
+            return matched;
+        }
+% endif
+% endfor
 };
 
 template<class Archive>
@@ -184,8 +255,9 @@
 % endfor
 
 % for index, object in enumerate(objects):
-  % for property, value in settingsDict[object]['Defaults'].items():
+  % for property, value in settingsDict[object]['Properties'].items():
 <% p = property[:1].lower() + property[1:] %>\
+<% defaultValue = value['Default'] %>\
             path = fs::path(SETTINGS_PERSIST_PATH) / "${object}";
             if (fs::exists(path))
             {
@@ -196,7 +268,7 @@
             else
             {
                 std::get<${index}>(settings)->
-                    ${get_setting_sdbusplus_type(settingsDict[object]['Interface'])}::${p}(${value});
+                    ${get_setting_sdbusplus_type(settingsDict[object]['Interface'])}::${p}(${defaultValue});
             }
   % endfor
             std::get<${index}>(settings)->emit_object_added();