[Redfish-Ethernet Inf] Enable Read/Write for FQDN

Enabling read/write requirement of FQDN in Ethernet Interface Schema
to make it OCP compliant.

Tested:
1. Tested using PATCH:
- https://bmc-ip/redfish/v1/Managers/bmc/EthernetInterfaces/eth-id
  {"FQDN": "hostname.domainname"}

  GET Response- "FQDN": "hostname.domainname" //Success

- When given invalid hostname/domainname
  Error Message "propertyValueFormatError"

2. Ran the Redfish validator and no new issues found.

Signed-off-by: Joshi-Mansi <mansi.joshi@linux.intel.com>
Change-Id: Ief2e94f8b499be59196b0e7073ceffe8d49268ca
diff --git a/redfish-core/lib/ethernet.hpp b/redfish-core/lib/ethernet.hpp
index 0a2a185..56e1a15 100644
--- a/redfish-core/lib/ethernet.hpp
+++ b/redfish-core/lib/ethernet.hpp
@@ -21,6 +21,7 @@
 #include <error_messages.hpp>
 #include <node.hpp>
 #include <optional>
+#include <regex>
 #include <utils/json_utils.hpp>
 #include <variant>
 
@@ -1084,7 +1085,13 @@
     void handleHostnamePatch(const std::string &hostname,
                              const std::shared_ptr<AsyncResp> asyncResp)
     {
-        asyncResp->res.jsonValue["HostName"] = hostname;
+        // SHOULD handle host names of up to 255 characters(RFC 1123)
+        if (hostname.length() > 255)
+        {
+            messages::propertyValueFormatError(asyncResp->res, hostname,
+                                               "HostName");
+            return;
+        }
         crow::connections::systemBus->async_method_call(
             [asyncResp](const boost::system::error_code ec) {
                 if (ec)
@@ -1099,6 +1106,84 @@
             std::variant<std::string>(hostname));
     }
 
+    void handleDomainnamePatch(const std::string &ifaceId,
+                               const std::string &domainname,
+                               const std::shared_ptr<AsyncResp> asyncResp)
+    {
+        std::vector<std::string> vectorDomainname = {domainname};
+        crow::connections::systemBus->async_method_call(
+            [asyncResp](const boost::system::error_code ec) {
+                if (ec)
+                {
+                    messages::internalError(asyncResp->res);
+                }
+            },
+            "xyz.openbmc_project.Network",
+            "/xyz/openbmc_project/network/" + ifaceId,
+            "org.freedesktop.DBus.Properties", "Set",
+            "xyz.openbmc_project.Network.EthernetInterface", "DomainName",
+            std::variant<std::vector<std::string>>(vectorDomainname));
+    }
+
+    void handleFqdnPatch(const std::string &ifaceId, const std::string &fqdn,
+                         const std::shared_ptr<AsyncResp> asyncResp)
+    {
+        // Total length of FQDN must not exceed 255 characters(RFC 1035)
+        if (fqdn.length() > 255)
+        {
+            messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN");
+            return;
+        }
+
+        size_t pos = fqdn.find('.');
+        if (pos == std::string::npos)
+        {
+            messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN");
+            return;
+        }
+
+        std::string hostname;
+        std::string domainname;
+        domainname = (fqdn).substr(pos + 1);
+        hostname = (fqdn).substr(0, pos);
+
+        if (!isHostnameValid(hostname) || !isDomainnameValid(domainname))
+        {
+            messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN");
+            return;
+        }
+
+        handleHostnamePatch(hostname, asyncResp);
+        handleDomainnamePatch(ifaceId, domainname, asyncResp);
+    }
+
+    bool isHostnameValid(const std::string &hostname)
+    {
+        // A valid host name can never have the dotted-decimal form (RFC 1123)
+        if (std::all_of(hostname.begin(), hostname.end(), ::isdigit))
+        {
+            return false;
+        }
+        // Each label(hostname/subdomains) within a valid FQDN
+        // MUST handle host names of up to 63 characters (RFC 1123)
+        // labels cannot start or end with hyphens (RFC 952)
+        // labels can start with numbers (RFC 1123)
+        const std::regex pattern(
+            "^[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9]$");
+
+        return std::regex_match(hostname, pattern);
+    }
+
+    bool isDomainnameValid(const std::string &domainname)
+    {
+        // Can have multiple subdomains
+        // Top Level Domain's min length is 2 character
+        const std::regex pattern("^([A-Za-z0-9][a-zA-Z0-9\\-]{1,61}|[a-zA-Z0-9]"
+                                 "{1,30}\\.)*[a-zA-Z]{2,}$");
+
+        return std::regex_match(domainname, pattern);
+    }
+
     void handleMACAddressPatch(const std::string &ifaceId,
                                const std::string &macAddress,
                                const std::shared_ptr<AsyncResp> &asyncResp)
@@ -1705,11 +1790,16 @@
         if (!ethData.hostname.empty())
         {
             json_response["HostName"] = ethData.hostname;
+
+            // When domain name is empty then it means, that it is a network
+            // without domain names, and the host name itself must be treated as
+            // FQDN
+            std::string FQDN = std::move(ethData.hostname);
             if (!ethData.domainnames.empty())
             {
-                json_response["FQDN"] =
-                    ethData.hostname + "." + ethData.domainnames[0];
+                FQDN += "." + ethData.domainnames[0];
             }
+            json_response["FQDN"] = FQDN;
         }
 
         json_response["VLANs"] = {
@@ -1828,6 +1918,7 @@
         const std::string &iface_id = params[0];
 
         std::optional<std::string> hostname;
+        std::optional<std::string> fqdn;
         std::optional<std::string> macAddress;
         std::optional<std::string> ipv6DefaultGateway;
         std::optional<nlohmann::json> ipv4StaticAddresses;
@@ -1840,12 +1931,12 @@
         DHCPParameters v6dhcpParms;
 
         if (!json_util::readJson(
-                req, res, "HostName", hostname, "IPv4StaticAddresses",
-                ipv4StaticAddresses, "MACAddress", macAddress,
-                "StaticNameServers", staticNameServers, "IPv6DefaultGateway",
-                ipv6DefaultGateway, "IPv6StaticAddresses", ipv6StaticAddresses,
-                "DHCPv4", dhcpv4, "DHCPv6", dhcpv6, "InterfaceEnabled",
-                interfaceEnabled))
+                req, res, "HostName", hostname, "FQDN", fqdn,
+                "IPv4StaticAddresses", ipv4StaticAddresses, "MACAddress",
+                macAddress, "StaticNameServers", staticNameServers,
+                "IPv6DefaultGateway", ipv6DefaultGateway, "IPv6StaticAddresses",
+                ipv6StaticAddresses, "DHCPv4", dhcpv4, "DHCPv6", dhcpv6,
+                "InterfaceEnabled", interfaceEnabled))
         {
             return;
         }
@@ -1879,7 +1970,7 @@
         getEthernetIfaceData(
             iface_id,
             [this, asyncResp, iface_id, hostname = std::move(hostname),
-             macAddress = std::move(macAddress),
+             fqdn = std::move(fqdn), macAddress = std::move(macAddress),
              ipv4StaticAddresses = std::move(ipv4StaticAddresses),
              ipv6DefaultGateway = std::move(ipv6DefaultGateway),
              ipv6StaticAddresses = std::move(ipv6StaticAddresses),
@@ -1912,6 +2003,11 @@
                     handleHostnamePatch(*hostname, asyncResp);
                 }
 
+                if (fqdn)
+                {
+                    handleFqdnPatch(iface_id, *fqdn, asyncResp);
+                }
+
                 if (macAddress)
                 {
                     handleMACAddressPatch(iface_id, *macAddress, asyncResp);