xref: /openbmc/bmcweb/features/redfish/lib/ethernet.hpp (revision ab6554f1d8197753cfb430a72798dde6f4461552)
1 /*
2 // Copyright (c) 2018 Intel Corporation
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 */
16 #pragma once
17 
18 #include <boost/container/flat_map.hpp>
19 #include <boost/container/flat_set.hpp>
20 #include <dbus_singleton.hpp>
21 #include <error_messages.hpp>
22 #include <node.hpp>
23 #include <optional>
24 #include <regex>
25 #include <utils/json_utils.hpp>
26 #include <variant>
27 
28 namespace redfish
29 {
30 
31 /**
32  * DBus types primitives for several generic DBus interfaces
33  * TODO(Pawel) consider move this to separate file into boost::dbus
34  */
35 using PropertiesMapType = boost::container::flat_map<
36     std::string, std::variant<std::string, bool, uint8_t, int16_t, uint16_t,
37                               int32_t, uint32_t, int64_t, uint64_t, double>>;
38 
39 using GetManagedObjects = std::vector<std::pair<
40     sdbusplus::message::object_path,
41     std::vector<std::pair<
42         std::string,
43         boost::container::flat_map<
44             std::string, sdbusplus::message::variant<
45                              std::string, bool, uint8_t, int16_t, uint16_t,
46                              int32_t, uint32_t, int64_t, uint64_t, double,
47                              std::vector<std::string>>>>>>>;
48 
49 enum class LinkType
50 {
51     Local,
52     Global
53 };
54 
55 /**
56  * Structure for keeping IPv4 data required by Redfish
57  */
58 struct IPv4AddressData
59 {
60     std::string id;
61     std::string address;
62     std::string domain;
63     std::string gateway;
64     std::string netmask;
65     std::string origin;
66     LinkType linktype;
67 
68     bool operator<(const IPv4AddressData &obj) const
69     {
70         return id < obj.id;
71     }
72 };
73 
74 /**
75  * Structure for keeping IPv6 data required by Redfish
76  */
77 struct IPv6AddressData
78 {
79     std::string id;
80     std::string address;
81     std::string origin;
82     uint8_t prefixLength;
83 
84     bool operator<(const IPv6AddressData &obj) const
85     {
86         return id < obj.id;
87     }
88 };
89 /**
90  * Structure for keeping basic single Ethernet Interface information
91  * available from DBus
92  */
93 struct EthernetInterfaceData
94 {
95     uint32_t speed;
96     bool auto_neg;
97     bool DNSEnabled;
98     bool NTPEnabled;
99     bool HostNameEnabled;
100     bool SendHostNameEnabled;
101     bool linkUp;
102     bool nicEnabled;
103     std::string DHCPEnabled;
104     std::string operatingMode;
105     std::string hostname;
106     std::string default_gateway;
107     std::string ipv6_default_gateway;
108     std::string mac_address;
109     std::vector<std::uint32_t> vlan_id;
110     std::vector<std::string> nameservers;
111     std::vector<std::string> domainnames;
112 };
113 
114 struct DHCPParameters
115 {
116     std::optional<bool> dhcpv4Enabled;
117     std::optional<bool> useDNSServers;
118     std::optional<bool> useNTPServers;
119     std::optional<bool> useUseDomainName;
120     std::optional<std::string> dhcpv6OperatingMode;
121 };
122 
123 // Helper function that changes bits netmask notation (i.e. /24)
124 // into full dot notation
125 inline std::string getNetmask(unsigned int bits)
126 {
127     uint32_t value = 0xffffffff << (32 - bits);
128     std::string netmask = std::to_string((value >> 24) & 0xff) + "." +
129                           std::to_string((value >> 16) & 0xff) + "." +
130                           std::to_string((value >> 8) & 0xff) + "." +
131                           std::to_string(value & 0xff);
132     return netmask;
133 }
134 
135 inline bool translateDHCPEnabledToBool(const std::string &inputDHCP,
136                                        bool isIPv4)
137 {
138     if (isIPv4)
139     {
140         return (
141             (inputDHCP ==
142              "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.v4") ||
143             (inputDHCP ==
144              "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.both"));
145     }
146     return ((inputDHCP ==
147              "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.v6") ||
148             (inputDHCP ==
149              "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.both"));
150 }
151 
152 inline std::string GetDHCPEnabledEnumeration(bool isIPv4, bool isIPv6)
153 {
154     if (isIPv4 && isIPv6)
155     {
156         return "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.both";
157     }
158     else if (isIPv4)
159     {
160         return "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.v4";
161     }
162     else if (isIPv6)
163     {
164         return "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.v6";
165     }
166     return "xyz.openbmc_project.Network.EthernetInterface.DHCPConf.none";
167 }
168 
169 inline std::string
170     translateAddressOriginDbusToRedfish(const std::string &inputOrigin,
171                                         bool isIPv4)
172 {
173     if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.Static")
174     {
175         return "Static";
176     }
177     if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.LinkLocal")
178     {
179         if (isIPv4)
180         {
181             return "IPv4LinkLocal";
182         }
183         else
184         {
185             return "LinkLocal";
186         }
187     }
188     if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.DHCP")
189     {
190         if (isIPv4)
191         {
192             return "DHCP";
193         }
194         else
195         {
196             return "DHCPv6";
197         }
198     }
199     if (inputOrigin == "xyz.openbmc_project.Network.IP.AddressOrigin.SLAAC")
200     {
201         return "SLAAC";
202     }
203     return "";
204 }
205 
206 inline bool extractEthernetInterfaceData(const std::string &ethiface_id,
207                                          const GetManagedObjects &dbus_data,
208                                          EthernetInterfaceData &ethData)
209 {
210     bool idFound = false;
211     for (const auto &objpath : dbus_data)
212     {
213         for (const auto &ifacePair : objpath.second)
214         {
215             if (objpath.first == "/xyz/openbmc_project/network/" + ethiface_id)
216             {
217                 idFound = true;
218                 if (ifacePair.first == "xyz.openbmc_project.Network.MACAddress")
219                 {
220                     for (const auto &propertyPair : ifacePair.second)
221                     {
222                         if (propertyPair.first == "MACAddress")
223                         {
224                             const std::string *mac =
225                                 std::get_if<std::string>(&propertyPair.second);
226                             if (mac != nullptr)
227                             {
228                                 ethData.mac_address = *mac;
229                             }
230                         }
231                     }
232                 }
233                 else if (ifacePair.first == "xyz.openbmc_project.Network.VLAN")
234                 {
235                     for (const auto &propertyPair : ifacePair.second)
236                     {
237                         if (propertyPair.first == "Id")
238                         {
239                             const uint32_t *id =
240                                 std::get_if<uint32_t>(&propertyPair.second);
241                             if (id != nullptr)
242                             {
243                                 ethData.vlan_id.push_back(*id);
244                             }
245                         }
246                     }
247                 }
248                 else if (ifacePair.first ==
249                          "xyz.openbmc_project.Network.EthernetInterface")
250                 {
251                     for (const auto &propertyPair : ifacePair.second)
252                     {
253                         if (propertyPair.first == "AutoNeg")
254                         {
255                             const bool *auto_neg =
256                                 std::get_if<bool>(&propertyPair.second);
257                             if (auto_neg != nullptr)
258                             {
259                                 ethData.auto_neg = *auto_neg;
260                             }
261                         }
262                         else if (propertyPair.first == "Speed")
263                         {
264                             const uint32_t *speed =
265                                 std::get_if<uint32_t>(&propertyPair.second);
266                             if (speed != nullptr)
267                             {
268                                 ethData.speed = *speed;
269                             }
270                         }
271                         else if (propertyPair.first == "LinkUp")
272                         {
273                             const bool *linkUp =
274                                 std::get_if<bool>(&propertyPair.second);
275                             if (linkUp != nullptr)
276                             {
277                                 ethData.linkUp = *linkUp;
278                             }
279                         }
280                         else if (propertyPair.first == "NICEnabled")
281                         {
282                             const bool *nicEnabled =
283                                 std::get_if<bool>(&propertyPair.second);
284                             if (nicEnabled != nullptr)
285                             {
286                                 ethData.nicEnabled = *nicEnabled;
287                             }
288                         }
289                         else if (propertyPair.first == "Nameservers")
290                         {
291                             const std::vector<std::string> *nameservers =
292                                 sdbusplus::message::variant_ns::get_if<
293                                     std::vector<std::string>>(
294                                     &propertyPair.second);
295                             if (nameservers != nullptr)
296                             {
297                                 ethData.nameservers = std::move(*nameservers);
298                             }
299                         }
300                         else if (propertyPair.first == "DHCPEnabled")
301                         {
302                             const std::string *DHCPEnabled =
303                                 std::get_if<std::string>(&propertyPair.second);
304                             if (DHCPEnabled != nullptr)
305                             {
306                                 ethData.DHCPEnabled = *DHCPEnabled;
307                             }
308                         }
309                         else if (propertyPair.first == "DomainName")
310                         {
311                             const std::vector<std::string> *domainNames =
312                                 sdbusplus::message::variant_ns::get_if<
313                                     std::vector<std::string>>(
314                                     &propertyPair.second);
315                             if (domainNames != nullptr)
316                             {
317                                 ethData.domainnames = std::move(*domainNames);
318                             }
319                         }
320                     }
321                 }
322             }
323 
324             if (objpath.first == "/xyz/openbmc_project/network/config/dhcp")
325             {
326                 if (ifacePair.first ==
327                     "xyz.openbmc_project.Network.DHCPConfiguration")
328                 {
329                     for (const auto &propertyPair : ifacePair.second)
330                     {
331                         if (propertyPair.first == "DNSEnabled")
332                         {
333                             const bool *DNSEnabled =
334                                 std::get_if<bool>(&propertyPair.second);
335                             if (DNSEnabled != nullptr)
336                             {
337                                 ethData.DNSEnabled = *DNSEnabled;
338                             }
339                         }
340                         else if (propertyPair.first == "NTPEnabled")
341                         {
342                             const bool *NTPEnabled =
343                                 std::get_if<bool>(&propertyPair.second);
344                             if (NTPEnabled != nullptr)
345                             {
346                                 ethData.NTPEnabled = *NTPEnabled;
347                             }
348                         }
349                         else if (propertyPair.first == "HostNameEnabled")
350                         {
351                             const bool *HostNameEnabled =
352                                 std::get_if<bool>(&propertyPair.second);
353                             if (HostNameEnabled != nullptr)
354                             {
355                                 ethData.HostNameEnabled = *HostNameEnabled;
356                             }
357                         }
358                         else if (propertyPair.first == "SendHostNameEnabled")
359                         {
360                             const bool *SendHostNameEnabled =
361                                 std::get_if<bool>(&propertyPair.second);
362                             if (SendHostNameEnabled != nullptr)
363                             {
364                                 ethData.SendHostNameEnabled =
365                                     *SendHostNameEnabled;
366                             }
367                         }
368                     }
369                 }
370             }
371             // System configuration shows up in the global namespace, so no need
372             // to check eth number
373             if (ifacePair.first ==
374                 "xyz.openbmc_project.Network.SystemConfiguration")
375             {
376                 for (const auto &propertyPair : ifacePair.second)
377                 {
378                     if (propertyPair.first == "HostName")
379                     {
380                         const std::string *hostname =
381                             sdbusplus::message::variant_ns::get_if<std::string>(
382                                 &propertyPair.second);
383                         if (hostname != nullptr)
384                         {
385                             ethData.hostname = *hostname;
386                         }
387                     }
388                     else if (propertyPair.first == "DefaultGateway")
389                     {
390                         const std::string *defaultGateway =
391                             sdbusplus::message::variant_ns::get_if<std::string>(
392                                 &propertyPair.second);
393                         if (defaultGateway != nullptr)
394                         {
395                             ethData.default_gateway = *defaultGateway;
396                         }
397                     }
398                     else if (propertyPair.first == "DefaultGateway6")
399                     {
400                         const std::string *defaultGateway6 =
401                             sdbusplus::message::variant_ns::get_if<std::string>(
402                                 &propertyPair.second);
403                         if (defaultGateway6 != nullptr)
404                         {
405                             ethData.ipv6_default_gateway = *defaultGateway6;
406                         }
407                     }
408                 }
409             }
410         }
411     }
412     return idFound;
413 }
414 
415 // Helper function that extracts data for single ethernet ipv6 address
416 inline void
417     extractIPV6Data(const std::string &ethiface_id,
418                     const GetManagedObjects &dbus_data,
419                     boost::container::flat_set<IPv6AddressData> &ipv6_config)
420 {
421     const std::string ipv6PathStart =
422         "/xyz/openbmc_project/network/" + ethiface_id + "/ipv6/";
423 
424     // Since there might be several IPv6 configurations aligned with
425     // single ethernet interface, loop over all of them
426     for (const auto &objpath : dbus_data)
427     {
428         // Check if proper pattern for object path appears
429         if (boost::starts_with(objpath.first.str, ipv6PathStart))
430         {
431             for (auto &interface : objpath.second)
432             {
433                 if (interface.first == "xyz.openbmc_project.Network.IP")
434                 {
435                     // Instance IPv6AddressData structure, and set as
436                     // appropriate
437                     std::pair<
438                         boost::container::flat_set<IPv6AddressData>::iterator,
439                         bool>
440                         it = ipv6_config.insert(IPv6AddressData{});
441                     IPv6AddressData &ipv6_address = *it.first;
442                     ipv6_address.id =
443                         objpath.first.str.substr(ipv6PathStart.size());
444                     for (auto &property : interface.second)
445                     {
446                         if (property.first == "Address")
447                         {
448                             const std::string *address =
449                                 std::get_if<std::string>(&property.second);
450                             if (address != nullptr)
451                             {
452                                 ipv6_address.address = *address;
453                             }
454                         }
455                         else if (property.first == "Origin")
456                         {
457                             const std::string *origin =
458                                 std::get_if<std::string>(&property.second);
459                             if (origin != nullptr)
460                             {
461                                 ipv6_address.origin =
462                                     translateAddressOriginDbusToRedfish(*origin,
463                                                                         false);
464                             }
465                         }
466                         else if (property.first == "PrefixLength")
467                         {
468                             const uint8_t *prefix =
469                                 std::get_if<uint8_t>(&property.second);
470                             if (prefix != nullptr)
471                             {
472                                 ipv6_address.prefixLength = *prefix;
473                             }
474                         }
475                         else
476                         {
477                             BMCWEB_LOG_ERROR
478                                 << "Got extra property: " << property.first
479                                 << " on the " << objpath.first.str << " object";
480                         }
481                     }
482                 }
483             }
484         }
485     }
486 }
487 
488 // Helper function that extracts data for single ethernet ipv4 address
489 inline void
490     extractIPData(const std::string &ethiface_id,
491                   const GetManagedObjects &dbus_data,
492                   boost::container::flat_set<IPv4AddressData> &ipv4_config)
493 {
494     const std::string ipv4PathStart =
495         "/xyz/openbmc_project/network/" + ethiface_id + "/ipv4/";
496 
497     // Since there might be several IPv4 configurations aligned with
498     // single ethernet interface, loop over all of them
499     for (const auto &objpath : dbus_data)
500     {
501         // Check if proper pattern for object path appears
502         if (boost::starts_with(objpath.first.str, ipv4PathStart))
503         {
504             for (auto &interface : objpath.second)
505             {
506                 if (interface.first == "xyz.openbmc_project.Network.IP")
507                 {
508                     // Instance IPv4AddressData structure, and set as
509                     // appropriate
510                     std::pair<
511                         boost::container::flat_set<IPv4AddressData>::iterator,
512                         bool>
513                         it = ipv4_config.insert(IPv4AddressData{});
514                     IPv4AddressData &ipv4_address = *it.first;
515                     ipv4_address.id =
516                         objpath.first.str.substr(ipv4PathStart.size());
517                     for (auto &property : interface.second)
518                     {
519                         if (property.first == "Address")
520                         {
521                             const std::string *address =
522                                 std::get_if<std::string>(&property.second);
523                             if (address != nullptr)
524                             {
525                                 ipv4_address.address = *address;
526                             }
527                         }
528                         else if (property.first == "Gateway")
529                         {
530                             const std::string *gateway =
531                                 std::get_if<std::string>(&property.second);
532                             if (gateway != nullptr)
533                             {
534                                 ipv4_address.gateway = *gateway;
535                             }
536                         }
537                         else if (property.first == "Origin")
538                         {
539                             const std::string *origin =
540                                 std::get_if<std::string>(&property.second);
541                             if (origin != nullptr)
542                             {
543                                 ipv4_address.origin =
544                                     translateAddressOriginDbusToRedfish(*origin,
545                                                                         true);
546                             }
547                         }
548                         else if (property.first == "PrefixLength")
549                         {
550                             const uint8_t *mask =
551                                 std::get_if<uint8_t>(&property.second);
552                             if (mask != nullptr)
553                             {
554                                 // convert it to the string
555                                 ipv4_address.netmask = getNetmask(*mask);
556                             }
557                         }
558                         else
559                         {
560                             BMCWEB_LOG_ERROR
561                                 << "Got extra property: " << property.first
562                                 << " on the " << objpath.first.str << " object";
563                         }
564                     }
565                     // Check if given address is local, or global
566                     ipv4_address.linktype =
567                         boost::starts_with(ipv4_address.address, "169.254.")
568                             ? LinkType::Local
569                             : LinkType::Global;
570                 }
571             }
572         }
573     }
574 }
575 
576 /**
577  * @brief Sets given Id on the given VLAN interface through D-Bus
578  *
579  * @param[in] ifaceId       Id of VLAN interface that should be modified
580  * @param[in] inputVlanId   New ID of the VLAN
581  * @param[in] callback      Function that will be called after the operation
582  *
583  * @return None.
584  */
585 template <typename CallbackFunc>
586 void changeVlanId(const std::string &ifaceId, const uint32_t &inputVlanId,
587                   CallbackFunc &&callback)
588 {
589     crow::connections::systemBus->async_method_call(
590         callback, "xyz.openbmc_project.Network",
591         std::string("/xyz/openbmc_project/network/") + ifaceId,
592         "org.freedesktop.DBus.Properties", "Set",
593         "xyz.openbmc_project.Network.VLAN", "Id",
594         std::variant<uint32_t>(inputVlanId));
595 }
596 
597 /**
598  * @brief Helper function that verifies IP address to check if it is in
599  *        proper format. If bits pointer is provided, also calculates active
600  *        bit count for Subnet Mask.
601  *
602  * @param[in]  ip     IP that will be verified
603  * @param[out] bits   Calculated mask in bits notation
604  *
605  * @return true in case of success, false otherwise
606  */
607 inline bool ipv4VerifyIpAndGetBitcount(const std::string &ip,
608                                        uint8_t *bits = nullptr)
609 {
610     std::vector<std::string> bytesInMask;
611 
612     boost::split(bytesInMask, ip, boost::is_any_of("."));
613 
614     static const constexpr int ipV4AddressSectionsCount = 4;
615     if (bytesInMask.size() != ipV4AddressSectionsCount)
616     {
617         return false;
618     }
619 
620     if (bits != nullptr)
621     {
622         *bits = 0;
623     }
624 
625     char *endPtr;
626     long previousValue = 255;
627     bool firstZeroInByteHit;
628     for (const std::string &byte : bytesInMask)
629     {
630         if (byte.empty())
631         {
632             return false;
633         }
634 
635         // Use strtol instead of stroi to avoid exceptions
636         long value = std::strtol(byte.c_str(), &endPtr, 10);
637 
638         // endPtr should point to the end of the string, otherwise given string
639         // is not 100% number
640         if (*endPtr != '\0')
641         {
642             return false;
643         }
644 
645         // Value should be contained in byte
646         if (value < 0 || value > 255)
647         {
648             return false;
649         }
650 
651         if (bits != nullptr)
652         {
653             // Mask has to be continuous between bytes
654             if (previousValue != 255 && value != 0)
655             {
656                 return false;
657             }
658 
659             // Mask has to be continuous inside bytes
660             firstZeroInByteHit = false;
661 
662             // Count bits
663             for (int bitIdx = 7; bitIdx >= 0; bitIdx--)
664             {
665                 if (value & (1 << bitIdx))
666                 {
667                     if (firstZeroInByteHit)
668                     {
669                         // Continuity not preserved
670                         return false;
671                     }
672                     else
673                     {
674                         (*bits)++;
675                     }
676                 }
677                 else
678                 {
679                     firstZeroInByteHit = true;
680                 }
681             }
682         }
683 
684         previousValue = value;
685     }
686 
687     return true;
688 }
689 
690 /**
691  * @brief Deletes given IPv4 interface
692  *
693  * @param[in] ifaceId     Id of interface whose IP should be deleted
694  * @param[in] ipHash      DBus Hash id of IP that should be deleted
695  * @param[io] asyncResp   Response object that will be returned to client
696  *
697  * @return None
698  */
699 inline void deleteIPv4(const std::string &ifaceId, const std::string &ipHash,
700                        const std::shared_ptr<AsyncResp> asyncResp)
701 {
702     crow::connections::systemBus->async_method_call(
703         [asyncResp](const boost::system::error_code ec) {
704             if (ec)
705             {
706                 messages::internalError(asyncResp->res);
707             }
708         },
709         "xyz.openbmc_project.Network",
710         "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + ipHash,
711         "xyz.openbmc_project.Object.Delete", "Delete");
712 }
713 
714 /**
715  * @brief Creates a static IPv4 entry
716  *
717  * @param[in] ifaceId      Id of interface upon which to create the IPv4 entry
718  * @param[in] prefixLength IPv4 prefix syntax for the subnet mask
719  * @param[in] gateway      IPv4 address of this interfaces gateway
720  * @param[in] address      IPv4 address to assign to this interface
721  * @param[io] asyncResp    Response object that will be returned to client
722  *
723  * @return None
724  */
725 inline void createIPv4(const std::string &ifaceId, unsigned int ipIdx,
726                        uint8_t prefixLength, const std::string &gateway,
727                        const std::string &address,
728                        std::shared_ptr<AsyncResp> asyncResp)
729 {
730     crow::connections::systemBus->async_method_call(
731         [asyncResp](const boost::system::error_code ec) {
732             if (ec)
733             {
734                 messages::internalError(asyncResp->res);
735             }
736         },
737         "xyz.openbmc_project.Network",
738         "/xyz/openbmc_project/network/" + ifaceId,
739         "xyz.openbmc_project.Network.IP.Create", "IP",
740         "xyz.openbmc_project.Network.IP.Protocol.IPv4", address, prefixLength,
741         gateway);
742 }
743 
744 /**
745  * @brief Deletes the IPv4 entry for this interface and creates a replacement
746  * static IPv4 entry
747  *
748  * @param[in] ifaceId      Id of interface upon which to create the IPv4 entry
749  * @param[in] id           The unique hash entry identifying the DBus entry
750  * @param[in] prefixLength IPv4 prefix syntax for the subnet mask
751  * @param[in] gateway      IPv4 address of this interfaces gateway
752  * @param[in] address      IPv4 address to assign to this interface
753  * @param[io] asyncResp    Response object that will be returned to client
754  *
755  * @return None
756  */
757 inline void deleteAndCreateIPv4(const std::string &ifaceId,
758                                 const std::string &id, uint8_t prefixLength,
759                                 const std::string &gateway,
760                                 const std::string &address,
761                                 std::shared_ptr<AsyncResp> asyncResp)
762 {
763     crow::connections::systemBus->async_method_call(
764         [asyncResp, ifaceId, address, prefixLength,
765          gateway](const boost::system::error_code ec) {
766             if (ec)
767             {
768                 messages::internalError(asyncResp->res);
769             }
770             crow::connections::systemBus->async_method_call(
771                 [asyncResp](const boost::system::error_code ec) {
772                     if (ec)
773                     {
774                         messages::internalError(asyncResp->res);
775                     }
776                 },
777                 "xyz.openbmc_project.Network",
778                 "/xyz/openbmc_project/network/" + ifaceId,
779                 "xyz.openbmc_project.Network.IP.Create", "IP",
780                 "xyz.openbmc_project.Network.IP.Protocol.IPv4", address,
781                 prefixLength, gateway);
782         },
783         "xyz.openbmc_project.Network",
784         +"/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + id,
785         "xyz.openbmc_project.Object.Delete", "Delete");
786 }
787 
788 /**
789  * @brief Deletes given IPv6
790  *
791  * @param[in] ifaceId     Id of interface whose IP should be deleted
792  * @param[in] ipHash      DBus Hash id of IP that should be deleted
793  * @param[io] asyncResp   Response object that will be returned to client
794  *
795  * @return None
796  */
797 inline void deleteIPv6(const std::string &ifaceId, const std::string &ipHash,
798                        const std::shared_ptr<AsyncResp> asyncResp)
799 {
800     crow::connections::systemBus->async_method_call(
801         [asyncResp](const boost::system::error_code ec) {
802             if (ec)
803             {
804                 messages::internalError(asyncResp->res);
805             }
806         },
807         "xyz.openbmc_project.Network",
808         "/xyz/openbmc_project/network/" + ifaceId + "/ipv6/" + ipHash,
809         "xyz.openbmc_project.Object.Delete", "Delete");
810 }
811 
812 /**
813  * @brief Deletes the IPv6 entry for this interface and creates a replacement
814  * static IPv6 entry
815  *
816  * @param[in] ifaceId      Id of interface upon which to create the IPv6 entry
817  * @param[in] id           The unique hash entry identifying the DBus entry
818  * @param[in] prefixLength IPv6 prefix syntax for the subnet mask
819  * @param[in] address      IPv6 address to assign to this interface
820  * @param[io] asyncResp    Response object that will be returned to client
821  *
822  * @return None
823  */
824 inline void deleteAndCreateIPv6(const std::string &ifaceId,
825                                 const std::string &id, uint8_t prefixLength,
826                                 const std::string &address,
827                                 std::shared_ptr<AsyncResp> asyncResp)
828 {
829     crow::connections::systemBus->async_method_call(
830         [asyncResp, ifaceId, address,
831          prefixLength](const boost::system::error_code ec) {
832             if (ec)
833             {
834                 messages::internalError(asyncResp->res);
835             }
836             crow::connections::systemBus->async_method_call(
837                 [asyncResp](const boost::system::error_code ec) {
838                     if (ec)
839                     {
840                         messages::internalError(asyncResp->res);
841                     }
842                 },
843                 "xyz.openbmc_project.Network",
844                 "/xyz/openbmc_project/network/" + ifaceId,
845                 "xyz.openbmc_project.Network.IP.Create", "IP",
846                 "xyz.openbmc_project.Network.IP.Protocol.IPv6", address,
847                 prefixLength, "");
848         },
849         "xyz.openbmc_project.Network",
850         +"/xyz/openbmc_project/network/" + ifaceId + "/ipv6/" + id,
851         "xyz.openbmc_project.Object.Delete", "Delete");
852 }
853 
854 /**
855  * @brief Creates IPv6 with given data
856  *
857  * @param[in] ifaceId      Id of interface whose IP should be added
858  * @param[in] prefixLength Prefix length that needs to be added
859  * @param[in] address      IP address that needs to be added
860  * @param[io] asyncResp    Response object that will be returned to client
861  *
862  * @return None
863  */
864 inline void createIPv6(const std::string &ifaceId, uint8_t prefixLength,
865                        const std::string &address,
866                        std::shared_ptr<AsyncResp> asyncResp)
867 {
868     auto createIpHandler = [asyncResp](const boost::system::error_code ec) {
869         if (ec)
870         {
871             messages::internalError(asyncResp->res);
872         }
873     };
874     // Passing null for gateway, as per redfish spec IPv6StaticAddresses object
875     // does not have assosiated gateway property
876     crow::connections::systemBus->async_method_call(
877         std::move(createIpHandler), "xyz.openbmc_project.Network",
878         "/xyz/openbmc_project/network/" + ifaceId,
879         "xyz.openbmc_project.Network.IP.Create", "IP",
880         "xyz.openbmc_project.Network.IP.Protocol.IPv6", address, prefixLength,
881         "");
882 }
883 
884 /**
885  * Function that retrieves all properties for given Ethernet Interface
886  * Object
887  * from EntityManager Network Manager
888  * @param ethiface_id a eth interface id to query on DBus
889  * @param callback a function that shall be called to convert Dbus output
890  * into JSON
891  */
892 template <typename CallbackFunc>
893 void getEthernetIfaceData(const std::string &ethiface_id,
894                           CallbackFunc &&callback)
895 {
896     crow::connections::systemBus->async_method_call(
897         [ethiface_id{std::string{ethiface_id}}, callback{std::move(callback)}](
898             const boost::system::error_code error_code,
899             const GetManagedObjects &resp) {
900             EthernetInterfaceData ethData{};
901             boost::container::flat_set<IPv4AddressData> ipv4Data;
902             boost::container::flat_set<IPv6AddressData> ipv6Data;
903 
904             if (error_code)
905             {
906                 callback(false, ethData, ipv4Data, ipv6Data);
907                 return;
908             }
909 
910             bool found =
911                 extractEthernetInterfaceData(ethiface_id, resp, ethData);
912             if (!found)
913             {
914                 callback(false, ethData, ipv4Data, ipv6Data);
915                 return;
916             }
917 
918             extractIPData(ethiface_id, resp, ipv4Data);
919             // Fix global GW
920             for (IPv4AddressData &ipv4 : ipv4Data)
921             {
922                 if (((ipv4.linktype == LinkType::Global) &&
923                      (ipv4.gateway == "0.0.0.0")) ||
924                     (ipv4.origin == "DHCP"))
925                 {
926                     ipv4.gateway = ethData.default_gateway;
927                 }
928             }
929 
930             extractIPV6Data(ethiface_id, resp, ipv6Data);
931             // Finally make a callback with usefull data
932             callback(true, ethData, ipv4Data, ipv6Data);
933         },
934         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
935         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
936 }
937 
938 /**
939  * Function that retrieves all Ethernet Interfaces available through Network
940  * Manager
941  * @param callback a function that shall be called to convert Dbus output
942  * into JSON.
943  */
944 template <typename CallbackFunc>
945 void getEthernetIfaceList(CallbackFunc &&callback)
946 {
947     crow::connections::systemBus->async_method_call(
948         [callback{std::move(callback)}](
949             const boost::system::error_code error_code,
950             GetManagedObjects &resp) {
951             // Callback requires vector<string> to retrieve all available
952             // ethernet interfaces
953             boost::container::flat_set<std::string> iface_list;
954             iface_list.reserve(resp.size());
955             if (error_code)
956             {
957                 callback(false, iface_list);
958                 return;
959             }
960 
961             // Iterate over all retrieved ObjectPaths.
962             for (const auto &objpath : resp)
963             {
964                 // And all interfaces available for certain ObjectPath.
965                 for (const auto &interface : objpath.second)
966                 {
967                     // If interface is
968                     // xyz.openbmc_project.Network.EthernetInterface, this is
969                     // what we're looking for.
970                     if (interface.first ==
971                         "xyz.openbmc_project.Network.EthernetInterface")
972                     {
973                         // Cut out everyting until last "/", ...
974                         const std::string &iface_id = objpath.first.str;
975                         std::size_t last_pos = iface_id.rfind("/");
976                         if (last_pos != std::string::npos)
977                         {
978                             // and put it into output vector.
979                             iface_list.emplace(iface_id.substr(last_pos + 1));
980                         }
981                     }
982                 }
983             }
984             // Finally make a callback with useful data
985             callback(true, iface_list);
986         },
987         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
988         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
989 }
990 
991 /**
992  * EthernetCollection derived class for delivering Ethernet Collection Schema
993  */
994 class EthernetCollection : public Node
995 {
996   public:
997     template <typename CrowApp>
998     EthernetCollection(CrowApp &app) :
999         Node(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/")
1000     {
1001         entityPrivileges = {
1002             {boost::beast::http::verb::get, {{"Login"}}},
1003             {boost::beast::http::verb::head, {{"Login"}}},
1004             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1005             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1006             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1007             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1008     }
1009 
1010   private:
1011     /**
1012      * Functions triggers appropriate requests on DBus
1013      */
1014     void doGet(crow::Response &res, const crow::Request &req,
1015                const std::vector<std::string> &params) override
1016     {
1017         res.jsonValue["@odata.type"] =
1018             "#EthernetInterfaceCollection.EthernetInterfaceCollection";
1019         res.jsonValue["@odata.id"] =
1020             "/redfish/v1/Managers/bmc/EthernetInterfaces";
1021         res.jsonValue["Name"] = "Ethernet Network Interface Collection";
1022         res.jsonValue["Description"] =
1023             "Collection of EthernetInterfaces for this Manager";
1024         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1025         // Get eth interface list, and call the below callback for JSON
1026         // preparation
1027         getEthernetIfaceList(
1028             [asyncResp](
1029                 const bool &success,
1030                 const boost::container::flat_set<std::string> &iface_list) {
1031                 if (!success)
1032                 {
1033                     messages::internalError(asyncResp->res);
1034                     return;
1035                 }
1036 
1037                 nlohmann::json &iface_array =
1038                     asyncResp->res.jsonValue["Members"];
1039                 iface_array = nlohmann::json::array();
1040                 std::string tag = "_";
1041                 for (const std::string &iface_item : iface_list)
1042                 {
1043                     std::size_t found = iface_item.find(tag);
1044                     if (found == std::string::npos)
1045                     {
1046                         iface_array.push_back(
1047                             {{"@odata.id",
1048                               "/redfish/v1/Managers/bmc/EthernetInterfaces/" +
1049                                   iface_item}});
1050                     }
1051                 }
1052 
1053                 asyncResp->res.jsonValue["Members@odata.count"] =
1054                     iface_array.size();
1055                 asyncResp->res.jsonValue["@odata.id"] =
1056                     "/redfish/v1/Managers/bmc/EthernetInterfaces";
1057             });
1058     }
1059 };
1060 
1061 /**
1062  * EthernetInterface derived class for delivering Ethernet Schema
1063  */
1064 class EthernetInterface : public Node
1065 {
1066   public:
1067     /*
1068      * Default Constructor
1069      */
1070     template <typename CrowApp>
1071     EthernetInterface(CrowApp &app) :
1072         Node(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/",
1073              std::string())
1074     {
1075         entityPrivileges = {
1076             {boost::beast::http::verb::get, {{"Login"}}},
1077             {boost::beast::http::verb::head, {{"Login"}}},
1078             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1079             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1080             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1081             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1082     }
1083 
1084   private:
1085     void handleHostnamePatch(const std::string &hostname,
1086                              const std::shared_ptr<AsyncResp> asyncResp)
1087     {
1088         // SHOULD handle host names of up to 255 characters(RFC 1123)
1089         if (hostname.length() > 255)
1090         {
1091             messages::propertyValueFormatError(asyncResp->res, hostname,
1092                                                "HostName");
1093             return;
1094         }
1095         crow::connections::systemBus->async_method_call(
1096             [asyncResp](const boost::system::error_code ec) {
1097                 if (ec)
1098                 {
1099                     messages::internalError(asyncResp->res);
1100                 }
1101             },
1102             "xyz.openbmc_project.Network",
1103             "/xyz/openbmc_project/network/config",
1104             "org.freedesktop.DBus.Properties", "Set",
1105             "xyz.openbmc_project.Network.SystemConfiguration", "HostName",
1106             std::variant<std::string>(hostname));
1107     }
1108 
1109     void handleDomainnamePatch(const std::string &ifaceId,
1110                                const std::string &domainname,
1111                                const std::shared_ptr<AsyncResp> asyncResp)
1112     {
1113         std::vector<std::string> vectorDomainname = {domainname};
1114         crow::connections::systemBus->async_method_call(
1115             [asyncResp](const boost::system::error_code ec) {
1116                 if (ec)
1117                 {
1118                     messages::internalError(asyncResp->res);
1119                 }
1120             },
1121             "xyz.openbmc_project.Network",
1122             "/xyz/openbmc_project/network/" + ifaceId,
1123             "org.freedesktop.DBus.Properties", "Set",
1124             "xyz.openbmc_project.Network.EthernetInterface", "DomainName",
1125             std::variant<std::vector<std::string>>(vectorDomainname));
1126     }
1127 
1128     void handleFqdnPatch(const std::string &ifaceId, const std::string &fqdn,
1129                          const std::shared_ptr<AsyncResp> asyncResp)
1130     {
1131         // Total length of FQDN must not exceed 255 characters(RFC 1035)
1132         if (fqdn.length() > 255)
1133         {
1134             messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN");
1135             return;
1136         }
1137 
1138         size_t pos = fqdn.find('.');
1139         if (pos == std::string::npos)
1140         {
1141             messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN");
1142             return;
1143         }
1144 
1145         std::string hostname;
1146         std::string domainname;
1147         domainname = (fqdn).substr(pos + 1);
1148         hostname = (fqdn).substr(0, pos);
1149 
1150         if (!isHostnameValid(hostname) || !isDomainnameValid(domainname))
1151         {
1152             messages::propertyValueFormatError(asyncResp->res, fqdn, "FQDN");
1153             return;
1154         }
1155 
1156         handleHostnamePatch(hostname, asyncResp);
1157         handleDomainnamePatch(ifaceId, domainname, asyncResp);
1158     }
1159 
1160     bool isHostnameValid(const std::string &hostname)
1161     {
1162         // A valid host name can never have the dotted-decimal form (RFC 1123)
1163         if (std::all_of(hostname.begin(), hostname.end(), ::isdigit))
1164         {
1165             return false;
1166         }
1167         // Each label(hostname/subdomains) within a valid FQDN
1168         // MUST handle host names of up to 63 characters (RFC 1123)
1169         // labels cannot start or end with hyphens (RFC 952)
1170         // labels can start with numbers (RFC 1123)
1171         const std::regex pattern(
1172             "^[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9]$");
1173 
1174         return std::regex_match(hostname, pattern);
1175     }
1176 
1177     bool isDomainnameValid(const std::string &domainname)
1178     {
1179         // Can have multiple subdomains
1180         // Top Level Domain's min length is 2 character
1181         const std::regex pattern("^([A-Za-z0-9][a-zA-Z0-9\\-]{1,61}|[a-zA-Z0-9]"
1182                                  "{1,30}\\.)*[a-zA-Z]{2,}$");
1183 
1184         return std::regex_match(domainname, pattern);
1185     }
1186 
1187     void handleMACAddressPatch(const std::string &ifaceId,
1188                                const std::string &macAddress,
1189                                const std::shared_ptr<AsyncResp> &asyncResp)
1190     {
1191         crow::connections::systemBus->async_method_call(
1192             [asyncResp, macAddress](const boost::system::error_code ec) {
1193                 if (ec)
1194                 {
1195                     messages::internalError(asyncResp->res);
1196                     return;
1197                 }
1198             },
1199             "xyz.openbmc_project.Network",
1200             "/xyz/openbmc_project/network/" + ifaceId,
1201             "org.freedesktop.DBus.Properties", "Set",
1202             "xyz.openbmc_project.Network.MACAddress", "MACAddress",
1203             std::variant<std::string>(macAddress));
1204     }
1205 
1206     void setDHCPEnabled(const std::string &ifaceId,
1207                         const std::string &propertyName, const bool v4Value,
1208                         const bool v6Value,
1209                         const std::shared_ptr<AsyncResp> asyncResp)
1210     {
1211         const std::string dhcp = GetDHCPEnabledEnumeration(v4Value, v6Value);
1212         crow::connections::systemBus->async_method_call(
1213             [asyncResp](const boost::system::error_code ec) {
1214                 if (ec)
1215                 {
1216                     BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec;
1217                     messages::internalError(asyncResp->res);
1218                     return;
1219                 }
1220             },
1221             "xyz.openbmc_project.Network",
1222             "/xyz/openbmc_project/network/" + ifaceId,
1223             "org.freedesktop.DBus.Properties", "Set",
1224             "xyz.openbmc_project.Network.EthernetInterface", propertyName,
1225             std::variant<std::string>{dhcp});
1226     }
1227 
1228     void setEthernetInterfaceBoolProperty(
1229         const std::string &ifaceId, const std::string &propertyName,
1230         const bool &value, const std::shared_ptr<AsyncResp> asyncResp)
1231     {
1232         crow::connections::systemBus->async_method_call(
1233             [asyncResp](const boost::system::error_code ec) {
1234                 if (ec)
1235                 {
1236                     BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec;
1237                     messages::internalError(asyncResp->res);
1238                     return;
1239                 }
1240             },
1241             "xyz.openbmc_project.Network",
1242             "/xyz/openbmc_project/network/" + ifaceId,
1243             "org.freedesktop.DBus.Properties", "Set",
1244             "xyz.openbmc_project.Network.EthernetInterface", propertyName,
1245             std::variant<bool>{value});
1246     }
1247 
1248     void setDHCPv4Config(const std::string &propertyName, const bool &value,
1249                          const std::shared_ptr<AsyncResp> asyncResp)
1250     {
1251         BMCWEB_LOG_DEBUG << propertyName << " = " << value;
1252         crow::connections::systemBus->async_method_call(
1253             [asyncResp](const boost::system::error_code ec) {
1254                 if (ec)
1255                 {
1256                     BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec;
1257                     messages::internalError(asyncResp->res);
1258                     return;
1259                 }
1260             },
1261             "xyz.openbmc_project.Network",
1262             "/xyz/openbmc_project/network/config/dhcp",
1263             "org.freedesktop.DBus.Properties", "Set",
1264             "xyz.openbmc_project.Network.DHCPConfiguration", propertyName,
1265             std::variant<bool>{value});
1266     }
1267 
1268     void handleDHCPPatch(const std::string &ifaceId,
1269                          const EthernetInterfaceData &ethData,
1270                          DHCPParameters v4dhcpParms, DHCPParameters v6dhcpParms,
1271                          const std::shared_ptr<AsyncResp> asyncResp)
1272     {
1273         bool ipv4Active = translateDHCPEnabledToBool(ethData.DHCPEnabled, true);
1274         bool ipv6Active =
1275             translateDHCPEnabledToBool(ethData.DHCPEnabled, false);
1276 
1277         bool nextv4DHCPState =
1278             v4dhcpParms.dhcpv4Enabled ? *v4dhcpParms.dhcpv4Enabled : ipv4Active;
1279 
1280         bool nextv6DHCPState{};
1281         if (v6dhcpParms.dhcpv6OperatingMode)
1282         {
1283             if ((*v6dhcpParms.dhcpv6OperatingMode != "Stateful") &&
1284                 (*v6dhcpParms.dhcpv6OperatingMode != "Stateless") &&
1285                 (*v6dhcpParms.dhcpv6OperatingMode != "Disabled"))
1286             {
1287                 messages::propertyValueFormatError(
1288                     asyncResp->res, *v6dhcpParms.dhcpv6OperatingMode,
1289                     "OperatingMode");
1290                 return;
1291             }
1292             nextv6DHCPState = (*v6dhcpParms.dhcpv6OperatingMode == "Stateful");
1293         }
1294         else
1295         {
1296             nextv6DHCPState = ipv6Active;
1297         }
1298 
1299         bool nextDNS{};
1300         if (v4dhcpParms.useDNSServers && v6dhcpParms.useDNSServers)
1301         {
1302             if (*v4dhcpParms.useDNSServers != *v6dhcpParms.useDNSServers)
1303             {
1304                 messages::generalError(asyncResp->res);
1305                 return;
1306             }
1307             nextDNS = *v4dhcpParms.useDNSServers;
1308         }
1309         else if (v4dhcpParms.useDNSServers)
1310         {
1311             nextDNS = *v4dhcpParms.useDNSServers;
1312         }
1313         else if (v6dhcpParms.useDNSServers)
1314         {
1315             nextDNS = *v6dhcpParms.useDNSServers;
1316         }
1317         else
1318         {
1319             nextDNS = ethData.DNSEnabled;
1320         }
1321 
1322         bool nextNTP{};
1323         if (v4dhcpParms.useNTPServers && v6dhcpParms.useNTPServers)
1324         {
1325             if (*v4dhcpParms.useNTPServers != *v6dhcpParms.useNTPServers)
1326             {
1327                 messages::generalError(asyncResp->res);
1328                 return;
1329             }
1330             nextNTP = *v4dhcpParms.useNTPServers;
1331         }
1332         else if (v4dhcpParms.useNTPServers)
1333         {
1334             nextNTP = *v4dhcpParms.useNTPServers;
1335         }
1336         else if (v6dhcpParms.useNTPServers)
1337         {
1338             nextNTP = *v6dhcpParms.useNTPServers;
1339         }
1340         else
1341         {
1342             nextNTP = ethData.NTPEnabled;
1343         }
1344 
1345         bool nextUseDomain{};
1346         if (v4dhcpParms.useUseDomainName && v6dhcpParms.useUseDomainName)
1347         {
1348             if (*v4dhcpParms.useUseDomainName != *v6dhcpParms.useUseDomainName)
1349             {
1350                 messages::generalError(asyncResp->res);
1351                 return;
1352             }
1353             nextUseDomain = *v4dhcpParms.useUseDomainName;
1354         }
1355         else if (v4dhcpParms.useUseDomainName)
1356         {
1357             nextUseDomain = *v4dhcpParms.useUseDomainName;
1358         }
1359         else if (v6dhcpParms.useUseDomainName)
1360         {
1361             nextUseDomain = *v6dhcpParms.useUseDomainName;
1362         }
1363         else
1364         {
1365             nextUseDomain = ethData.HostNameEnabled;
1366         }
1367 
1368         BMCWEB_LOG_DEBUG << "set DHCPEnabled...";
1369         setDHCPEnabled(ifaceId, "DHCPEnabled", nextv4DHCPState, nextv6DHCPState,
1370                        asyncResp);
1371         BMCWEB_LOG_DEBUG << "set DNSEnabled...";
1372         setDHCPv4Config("DNSEnabled", nextDNS, asyncResp);
1373         BMCWEB_LOG_DEBUG << "set NTPEnabled...";
1374         setDHCPv4Config("NTPEnabled", nextNTP, asyncResp);
1375         BMCWEB_LOG_DEBUG << "set HostNameEnabled...";
1376         setDHCPv4Config("HostNameEnabled", nextUseDomain, asyncResp);
1377     }
1378 
1379     boost::container::flat_set<IPv4AddressData>::const_iterator
1380         GetNextStaticIPEntry(
1381             boost::container::flat_set<IPv4AddressData>::const_iterator head,
1382             boost::container::flat_set<IPv4AddressData>::const_iterator end)
1383     {
1384         for (; head != end; head++)
1385         {
1386             if (head->origin == "Static")
1387             {
1388                 return head;
1389             }
1390         }
1391         return end;
1392     }
1393 
1394     boost::container::flat_set<IPv6AddressData>::const_iterator
1395         GetNextStaticIPEntry(
1396             boost::container::flat_set<IPv6AddressData>::const_iterator head,
1397             boost::container::flat_set<IPv6AddressData>::const_iterator end)
1398     {
1399         for (; head != end; head++)
1400         {
1401             if (head->origin == "Static")
1402             {
1403                 return head;
1404             }
1405         }
1406         return end;
1407     }
1408 
1409     void handleIPv4StaticPatch(
1410         const std::string &ifaceId, nlohmann::json &input,
1411         const boost::container::flat_set<IPv4AddressData> &ipv4Data,
1412         const std::shared_ptr<AsyncResp> asyncResp)
1413     {
1414         if ((!input.is_array()) || input.empty())
1415         {
1416             messages::propertyValueTypeError(asyncResp->res, input.dump(),
1417                                              "IPv4StaticAddresses");
1418             return;
1419         }
1420 
1421         unsigned entryIdx = 1;
1422         // Find the first static IP address currently active on the NIC and
1423         // match it to the first JSON element in the IPv4StaticAddresses array.
1424         // Match each subsequent JSON element to the next static IP programmed
1425         // into the NIC.
1426         boost::container::flat_set<IPv4AddressData>::const_iterator NICIPentry =
1427             GetNextStaticIPEntry(ipv4Data.cbegin(), ipv4Data.cend());
1428 
1429         for (nlohmann::json &thisJson : input)
1430         {
1431             std::string pathString =
1432                 "IPv4StaticAddresses/" + std::to_string(entryIdx);
1433 
1434             if (!thisJson.is_null() && !thisJson.empty())
1435             {
1436                 std::optional<std::string> address;
1437                 std::optional<std::string> subnetMask;
1438                 std::optional<std::string> gateway;
1439 
1440                 if (!json_util::readJson(thisJson, asyncResp->res, "Address",
1441                                          address, "SubnetMask", subnetMask,
1442                                          "Gateway", gateway))
1443                 {
1444                     messages::propertyValueFormatError(
1445                         asyncResp->res, thisJson.dump(), pathString);
1446                     return;
1447                 }
1448 
1449                 // Find the address/subnet/gateway values. Any values that are
1450                 // not explicitly provided are assumed to be unmodified from the
1451                 // current state of the interface. Merge existing state into the
1452                 // current request.
1453                 const std::string *addr = nullptr;
1454                 const std::string *gw = nullptr;
1455                 uint8_t prefixLength = 0;
1456                 bool errorInEntry = false;
1457                 if (address)
1458                 {
1459                     if (ipv4VerifyIpAndGetBitcount(*address))
1460                     {
1461                         addr = &(*address);
1462                     }
1463                     else
1464                     {
1465                         messages::propertyValueFormatError(
1466                             asyncResp->res, *address, pathString + "/Address");
1467                         errorInEntry = true;
1468                     }
1469                 }
1470                 else if (NICIPentry != ipv4Data.cend())
1471                 {
1472                     addr = &(NICIPentry->address);
1473                 }
1474                 else
1475                 {
1476                     messages::propertyMissing(asyncResp->res,
1477                                               pathString + "/Address");
1478                     errorInEntry = true;
1479                 }
1480 
1481                 if (subnetMask)
1482                 {
1483                     if (!ipv4VerifyIpAndGetBitcount(*subnetMask, &prefixLength))
1484                     {
1485                         messages::propertyValueFormatError(
1486                             asyncResp->res, *subnetMask,
1487                             pathString + "/SubnetMask");
1488                         errorInEntry = true;
1489                     }
1490                 }
1491                 else if (NICIPentry != ipv4Data.cend())
1492                 {
1493                     if (!ipv4VerifyIpAndGetBitcount(NICIPentry->netmask,
1494                                                     &prefixLength))
1495                     {
1496                         messages::propertyValueFormatError(
1497                             asyncResp->res, NICIPentry->netmask,
1498                             pathString + "/SubnetMask");
1499                         errorInEntry = true;
1500                     }
1501                 }
1502                 else
1503                 {
1504                     messages::propertyMissing(asyncResp->res,
1505                                               pathString + "/SubnetMask");
1506                     errorInEntry = true;
1507                 }
1508 
1509                 if (gateway)
1510                 {
1511                     if (ipv4VerifyIpAndGetBitcount(*gateway))
1512                     {
1513                         gw = &(*gateway);
1514                     }
1515                     else
1516                     {
1517                         messages::propertyValueFormatError(
1518                             asyncResp->res, *gateway, pathString + "/Gateway");
1519                         errorInEntry = true;
1520                     }
1521                 }
1522                 else if (NICIPentry != ipv4Data.cend())
1523                 {
1524                     gw = &NICIPentry->gateway;
1525                 }
1526                 else
1527                 {
1528                     messages::propertyMissing(asyncResp->res,
1529                                               pathString + "/Gateway");
1530                     errorInEntry = true;
1531                 }
1532 
1533                 if (errorInEntry)
1534                 {
1535                     return;
1536                 }
1537 
1538                 if (NICIPentry != ipv4Data.cend())
1539                 {
1540                     if (gw != nullptr || addr != nullptr)
1541                     {
1542                         // Shouldn't be possible based on errorInEntry, but
1543                         // it flags -wmaybe-uninitialized in the compiler,
1544                         // so defend against that
1545                         return;
1546                     }
1547                     deleteAndCreateIPv4(ifaceId, NICIPentry->id, prefixLength,
1548                                         *gw, *addr, asyncResp);
1549                     NICIPentry =
1550                         GetNextStaticIPEntry(++NICIPentry, ipv4Data.cend());
1551                 }
1552                 else
1553                 {
1554                     createIPv4(ifaceId, entryIdx, prefixLength, *gateway,
1555                                *address, asyncResp);
1556                 }
1557                 entryIdx++;
1558             }
1559             else
1560             {
1561                 if (NICIPentry == ipv4Data.cend())
1562                 {
1563                     // Requesting a DELETE/DO NOT MODIFY action for an item
1564                     // that isn't present on the eth(n) interface. Input JSON is
1565                     // in error, so bail out.
1566                     if (thisJson.is_null())
1567                     {
1568                         messages::resourceCannotBeDeleted(asyncResp->res);
1569                         return;
1570                     }
1571                     else
1572                     {
1573                         messages::propertyValueFormatError(
1574                             asyncResp->res, thisJson.dump(), pathString);
1575                         return;
1576                     }
1577                 }
1578 
1579                 if (thisJson.is_null())
1580                 {
1581                     deleteIPv4(ifaceId, NICIPentry->id, asyncResp);
1582                 }
1583                 if (NICIPentry != ipv4Data.cend())
1584                 {
1585                     NICIPentry =
1586                         GetNextStaticIPEntry(++NICIPentry, ipv4Data.cend());
1587                 }
1588                 entryIdx++;
1589             }
1590         }
1591     }
1592 
1593     void handleStaticNameServersPatch(
1594         const std::string &ifaceId,
1595         const std::vector<std::string> &updatedStaticNameServers,
1596         const std::shared_ptr<AsyncResp> &asyncResp)
1597     {
1598         crow::connections::systemBus->async_method_call(
1599             [asyncResp](const boost::system::error_code ec) {
1600                 if (ec)
1601                 {
1602                     messages::internalError(asyncResp->res);
1603                     return;
1604                 }
1605             },
1606             "xyz.openbmc_project.Network",
1607             "/xyz/openbmc_project/network/" + ifaceId,
1608             "org.freedesktop.DBus.Properties", "Set",
1609             "xyz.openbmc_project.Network.EthernetInterface", "Nameservers",
1610             std::variant<std::vector<std::string>>{updatedStaticNameServers});
1611     }
1612 
1613     void handleIPv6StaticAddressesPatch(
1614         const std::string &ifaceId, nlohmann::json &input,
1615         const boost::container::flat_set<IPv6AddressData> &ipv6Data,
1616         const std::shared_ptr<AsyncResp> asyncResp)
1617     {
1618         if (!input.is_array() || input.empty())
1619         {
1620             messages::propertyValueTypeError(asyncResp->res, input.dump(),
1621                                              "IPv6StaticAddresses");
1622             return;
1623         }
1624         size_t entryIdx = 1;
1625         boost::container::flat_set<IPv6AddressData>::const_iterator NICIPentry =
1626             GetNextStaticIPEntry(ipv6Data.cbegin(), ipv6Data.cend());
1627         for (nlohmann::json &thisJson : input)
1628         {
1629             std::string pathString =
1630                 "IPv6StaticAddresses/" + std::to_string(entryIdx);
1631 
1632             if (!thisJson.is_null() && !thisJson.empty())
1633             {
1634                 std::optional<std::string> address;
1635                 std::optional<uint8_t> prefixLength;
1636 
1637                 if (!json_util::readJson(thisJson, asyncResp->res, "Address",
1638                                          address, "PrefixLength", prefixLength))
1639                 {
1640                     messages::propertyValueFormatError(
1641                         asyncResp->res, thisJson.dump(), pathString);
1642                     return;
1643                 }
1644 
1645                 const std::string *addr;
1646                 uint8_t prefix;
1647 
1648                 // Find the address and prefixLength values. Any values that are
1649                 // not explicitly provided are assumed to be unmodified from the
1650                 // current state of the interface. Merge existing state into the
1651                 // current request.
1652                 if (address)
1653                 {
1654                     addr = &(*address);
1655                 }
1656                 else if (NICIPentry != ipv6Data.end())
1657                 {
1658                     addr = &(NICIPentry->address);
1659                 }
1660                 else
1661                 {
1662                     messages::propertyMissing(asyncResp->res,
1663                                               pathString + "/Address");
1664                     return;
1665                 }
1666 
1667                 if (prefixLength)
1668                 {
1669                     prefix = *prefixLength;
1670                 }
1671                 else if (NICIPentry != ipv6Data.end())
1672                 {
1673                     prefix = NICIPentry->prefixLength;
1674                 }
1675                 else
1676                 {
1677                     messages::propertyMissing(asyncResp->res,
1678                                               pathString + "/PrefixLength");
1679                     return;
1680                 }
1681 
1682                 if (NICIPentry != ipv6Data.end())
1683                 {
1684                     deleteAndCreateIPv6(ifaceId, NICIPentry->id, prefix, *addr,
1685                                         asyncResp);
1686                     NICIPentry =
1687                         GetNextStaticIPEntry(++NICIPentry, ipv6Data.cend());
1688                 }
1689                 else
1690                 {
1691                     createIPv6(ifaceId, *prefixLength, *addr, asyncResp);
1692                 }
1693                 entryIdx++;
1694             }
1695             else
1696             {
1697                 if (NICIPentry == ipv6Data.end())
1698                 {
1699                     // Requesting a DELETE/DO NOT MODIFY action for an item
1700                     // that isn't present on the eth(n) interface. Input JSON is
1701                     // in error, so bail out.
1702                     if (thisJson.is_null())
1703                     {
1704                         messages::resourceCannotBeDeleted(asyncResp->res);
1705                         return;
1706                     }
1707                     else
1708                     {
1709                         messages::propertyValueFormatError(
1710                             asyncResp->res, thisJson.dump(), pathString);
1711                         return;
1712                     }
1713                 }
1714 
1715                 if (thisJson.is_null())
1716                 {
1717                     deleteIPv6(ifaceId, NICIPentry->id, asyncResp);
1718                 }
1719                 if (NICIPentry != ipv6Data.cend())
1720                 {
1721                     NICIPentry =
1722                         GetNextStaticIPEntry(++NICIPentry, ipv6Data.cend());
1723                 }
1724                 entryIdx++;
1725             }
1726         }
1727     }
1728 
1729     void parseInterfaceData(
1730         std::shared_ptr<AsyncResp> asyncResp, const std::string &iface_id,
1731         const EthernetInterfaceData &ethData,
1732         const boost::container::flat_set<IPv4AddressData> &ipv4Data,
1733         const boost::container::flat_set<IPv6AddressData> &ipv6Data)
1734     {
1735         constexpr const std::array<const char *, 1> inventoryForEthernet = {
1736             "xyz.openbmc_project.Inventory.Item.Ethernet"};
1737 
1738         nlohmann::json &json_response = asyncResp->res.jsonValue;
1739         json_response["Id"] = iface_id;
1740         json_response["@odata.id"] =
1741             "/redfish/v1/Managers/bmc/EthernetInterfaces/" + iface_id;
1742         json_response["InterfaceEnabled"] = ethData.nicEnabled;
1743 
1744         auto health = std::make_shared<HealthPopulate>(asyncResp);
1745 
1746         crow::connections::systemBus->async_method_call(
1747             [health](const boost::system::error_code ec,
1748                      std::vector<std::string> &resp) {
1749                 if (ec)
1750                 {
1751                     return;
1752                 }
1753 
1754                 health->inventory = std::move(resp);
1755             },
1756             "xyz.openbmc_project.ObjectMapper",
1757             "/xyz/openbmc_project/object_mapper",
1758             "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "/",
1759             int32_t(0), inventoryForEthernet);
1760 
1761         health->populate();
1762 
1763         if (ethData.nicEnabled)
1764         {
1765             json_response["LinkStatus"] = "LinkUp";
1766             json_response["Status"]["State"] = "Enabled";
1767         }
1768         else
1769         {
1770             json_response["LinkStatus"] = "NoLink";
1771             json_response["Status"]["State"] = "Disabled";
1772         }
1773 
1774         json_response["LinkStatus"] = ethData.linkUp ? "LinkUp" : "LinkDown";
1775         json_response["SpeedMbps"] = ethData.speed;
1776         json_response["MACAddress"] = ethData.mac_address;
1777         json_response["DHCPv4"]["DHCPEnabled"] =
1778             translateDHCPEnabledToBool(ethData.DHCPEnabled, true);
1779         json_response["DHCPv4"]["UseNTPServers"] = ethData.NTPEnabled;
1780         json_response["DHCPv4"]["UseDNSServers"] = ethData.DNSEnabled;
1781         json_response["DHCPv4"]["UseDomainName"] = ethData.HostNameEnabled;
1782 
1783         json_response["DHCPv6"]["OperatingMode"] =
1784             translateDHCPEnabledToBool(ethData.DHCPEnabled, false) ? "Stateful"
1785                                                                    : "Disabled";
1786         json_response["DHCPv6"]["UseNTPServers"] = ethData.NTPEnabled;
1787         json_response["DHCPv6"]["UseDNSServers"] = ethData.DNSEnabled;
1788         json_response["DHCPv6"]["UseDomainName"] = ethData.HostNameEnabled;
1789 
1790         if (!ethData.hostname.empty())
1791         {
1792             json_response["HostName"] = ethData.hostname;
1793 
1794             // When domain name is empty then it means, that it is a network
1795             // without domain names, and the host name itself must be treated as
1796             // FQDN
1797             std::string FQDN = std::move(ethData.hostname);
1798             if (!ethData.domainnames.empty())
1799             {
1800                 FQDN += "." + ethData.domainnames[0];
1801             }
1802             json_response["FQDN"] = FQDN;
1803         }
1804 
1805         json_response["VLANs"] = {
1806             {"@odata.id", "/redfish/v1/Managers/bmc/EthernetInterfaces/" +
1807                               iface_id + "/VLANs"}};
1808 
1809         if (translateDHCPEnabledToBool(ethData.DHCPEnabled, true) &&
1810             ethData.DNSEnabled)
1811         {
1812             json_response["StaticNameServers"] = nlohmann::json::array();
1813         }
1814         else
1815         {
1816             json_response["StaticNameServers"] = ethData.nameservers;
1817         }
1818 
1819         nlohmann::json &ipv4_array = json_response["IPv4Addresses"];
1820         nlohmann::json &ipv4_static_array =
1821             json_response["IPv4StaticAddresses"];
1822         ipv4_array = nlohmann::json::array();
1823         ipv4_static_array = nlohmann::json::array();
1824         for (auto &ipv4_config : ipv4Data)
1825         {
1826 
1827             std::string gatewayStr = ipv4_config.gateway;
1828             if (gatewayStr.empty())
1829             {
1830                 gatewayStr = "0.0.0.0";
1831             }
1832 
1833             ipv4_array.push_back({{"AddressOrigin", ipv4_config.origin},
1834                                   {"SubnetMask", ipv4_config.netmask},
1835                                   {"Address", ipv4_config.address},
1836                                   {"Gateway", gatewayStr}});
1837             if (ipv4_config.origin == "Static")
1838             {
1839                 ipv4_static_array.push_back(
1840                     {{"AddressOrigin", ipv4_config.origin},
1841                      {"SubnetMask", ipv4_config.netmask},
1842                      {"Address", ipv4_config.address},
1843                      {"Gateway", gatewayStr}});
1844             }
1845         }
1846 
1847         json_response["IPv6DefaultGateway"] = ethData.ipv6_default_gateway;
1848 
1849         nlohmann::json &ipv6_array = json_response["IPv6Addresses"];
1850         nlohmann::json &ipv6_static_array =
1851             json_response["IPv6StaticAddresses"];
1852         ipv6_array = nlohmann::json::array();
1853         ipv6_static_array = nlohmann::json::array();
1854         for (auto &ipv6_config : ipv6Data)
1855         {
1856             ipv6_array.push_back({{"Address", ipv6_config.address},
1857                                   {"PrefixLength", ipv6_config.prefixLength},
1858                                   {"AddressOrigin", ipv6_config.origin}});
1859             if (ipv6_config.origin == "Static")
1860             {
1861                 ipv6_static_array.push_back(
1862                     {{"Address", ipv6_config.address},
1863                      {"PrefixLength", ipv6_config.prefixLength},
1864                      {"AddressOrigin", ipv6_config.origin}});
1865             }
1866         }
1867     }
1868 
1869     /**
1870      * Functions triggers appropriate requests on DBus
1871      */
1872     void doGet(crow::Response &res, const crow::Request &req,
1873                const std::vector<std::string> &params) override
1874     {
1875         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1876         if (params.size() != 1)
1877         {
1878             messages::internalError(asyncResp->res);
1879             return;
1880         }
1881 
1882         getEthernetIfaceData(
1883             params[0],
1884             [this, asyncResp, iface_id{std::string(params[0])}](
1885                 const bool &success, const EthernetInterfaceData &ethData,
1886                 const boost::container::flat_set<IPv4AddressData> &ipv4Data,
1887                 const boost::container::flat_set<IPv6AddressData> &ipv6Data) {
1888                 if (!success)
1889                 {
1890                     // TODO(Pawel)consider distinguish between non existing
1891                     // object, and other errors
1892                     messages::resourceNotFound(asyncResp->res,
1893                                                "EthernetInterface", iface_id);
1894                     return;
1895                 }
1896 
1897                 asyncResp->res.jsonValue["@odata.type"] =
1898                     "#EthernetInterface.v1_4_1.EthernetInterface";
1899                 asyncResp->res.jsonValue["Name"] = "Manager Ethernet Interface";
1900                 asyncResp->res.jsonValue["Description"] =
1901                     "Management Network Interface";
1902 
1903                 parseInterfaceData(asyncResp, iface_id, ethData, ipv4Data,
1904                                    ipv6Data);
1905             });
1906     }
1907 
1908     void doPatch(crow::Response &res, const crow::Request &req,
1909                  const std::vector<std::string> &params) override
1910     {
1911         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
1912         if (params.size() != 1)
1913         {
1914             messages::internalError(asyncResp->res);
1915             return;
1916         }
1917 
1918         const std::string &iface_id = params[0];
1919 
1920         std::optional<std::string> hostname;
1921         std::optional<std::string> fqdn;
1922         std::optional<std::string> macAddress;
1923         std::optional<std::string> ipv6DefaultGateway;
1924         std::optional<nlohmann::json> ipv4StaticAddresses;
1925         std::optional<nlohmann::json> ipv6StaticAddresses;
1926         std::optional<std::vector<std::string>> staticNameServers;
1927         std::optional<nlohmann::json> dhcpv4;
1928         std::optional<nlohmann::json> dhcpv6;
1929         std::optional<bool> interfaceEnabled;
1930         DHCPParameters v4dhcpParms;
1931         DHCPParameters v6dhcpParms;
1932 
1933         if (!json_util::readJson(
1934                 req, res, "HostName", hostname, "FQDN", fqdn,
1935                 "IPv4StaticAddresses", ipv4StaticAddresses, "MACAddress",
1936                 macAddress, "StaticNameServers", staticNameServers,
1937                 "IPv6DefaultGateway", ipv6DefaultGateway, "IPv6StaticAddresses",
1938                 ipv6StaticAddresses, "DHCPv4", dhcpv4, "DHCPv6", dhcpv6,
1939                 "InterfaceEnabled", interfaceEnabled))
1940         {
1941             return;
1942         }
1943         if (dhcpv4)
1944         {
1945             if (!json_util::readJson(*dhcpv4, res, "DHCPEnabled",
1946                                      v4dhcpParms.dhcpv4Enabled, "UseDNSServers",
1947                                      v4dhcpParms.useDNSServers, "UseNTPServers",
1948                                      v4dhcpParms.useNTPServers, "UseDomainName",
1949                                      v4dhcpParms.useUseDomainName))
1950             {
1951                 return;
1952             }
1953         }
1954 
1955         if (dhcpv6)
1956         {
1957             if (!json_util::readJson(*dhcpv6, res, "OperatingMode",
1958                                      v6dhcpParms.dhcpv6OperatingMode,
1959                                      "UseDNSServers", v6dhcpParms.useDNSServers,
1960                                      "UseNTPServers", v6dhcpParms.useNTPServers,
1961                                      "UseDomainName",
1962                                      v6dhcpParms.useUseDomainName))
1963             {
1964                 return;
1965             }
1966         }
1967 
1968         // Get single eth interface data, and call the below callback for
1969         // JSON preparation
1970         getEthernetIfaceData(
1971             iface_id,
1972             [this, asyncResp, iface_id, hostname = std::move(hostname),
1973              fqdn = std::move(fqdn), macAddress = std::move(macAddress),
1974              ipv4StaticAddresses = std::move(ipv4StaticAddresses),
1975              ipv6DefaultGateway = std::move(ipv6DefaultGateway),
1976              ipv6StaticAddresses = std::move(ipv6StaticAddresses),
1977              staticNameServers = std::move(staticNameServers),
1978              dhcpv4 = std::move(dhcpv4), dhcpv6 = std::move(dhcpv6),
1979              v4dhcpParms = std::move(v4dhcpParms),
1980              v6dhcpParms = std::move(v6dhcpParms),
1981              interfaceEnabled = std::move(interfaceEnabled)](
1982                 const bool &success, const EthernetInterfaceData &ethData,
1983                 const boost::container::flat_set<IPv4AddressData> &ipv4Data,
1984                 const boost::container::flat_set<IPv6AddressData> &ipv6Data) {
1985                 if (!success)
1986                 {
1987                     // ... otherwise return error
1988                     // TODO(Pawel)consider distinguish between non existing
1989                     // object, and other errors
1990                     messages::resourceNotFound(asyncResp->res,
1991                                                "Ethernet Interface", iface_id);
1992                     return;
1993                 }
1994 
1995                 if (dhcpv4 || dhcpv6)
1996                 {
1997                     handleDHCPPatch(iface_id, ethData, std::move(v4dhcpParms),
1998                                     std::move(v6dhcpParms), asyncResp);
1999                 }
2000 
2001                 if (hostname)
2002                 {
2003                     handleHostnamePatch(*hostname, asyncResp);
2004                 }
2005 
2006                 if (fqdn)
2007                 {
2008                     handleFqdnPatch(iface_id, *fqdn, asyncResp);
2009                 }
2010 
2011                 if (macAddress)
2012                 {
2013                     handleMACAddressPatch(iface_id, *macAddress, asyncResp);
2014                 }
2015 
2016                 if (ipv4StaticAddresses)
2017                 {
2018                     // TODO(ed) for some reason the capture of ipv4Addresses
2019                     // above is returning a const value, not a non-const
2020                     // value. This doesn't really work for us, as we need to
2021                     // be able to efficiently move out the intermedia
2022                     // nlohmann::json objects. This makes a copy of the
2023                     // structure, and operates on that, but could be done
2024                     // more efficiently
2025                     nlohmann::json ipv4Static = std::move(*ipv4StaticAddresses);
2026                     handleIPv4StaticPatch(iface_id, ipv4Static, ipv4Data,
2027                                           asyncResp);
2028                 }
2029 
2030                 if (staticNameServers)
2031                 {
2032                     handleStaticNameServersPatch(iface_id, *staticNameServers,
2033                                                  asyncResp);
2034                 }
2035 
2036                 if (ipv6DefaultGateway)
2037                 {
2038                     messages::propertyNotWritable(asyncResp->res,
2039                                                   "IPv6DefaultGateway");
2040                 }
2041 
2042                 if (ipv6StaticAddresses)
2043                 {
2044                     nlohmann::json ipv6Static = std::move(*ipv6StaticAddresses);
2045                     handleIPv6StaticAddressesPatch(iface_id, ipv6Static,
2046                                                    ipv6Data, asyncResp);
2047                 }
2048 
2049                 if (interfaceEnabled)
2050                 {
2051                     setEthernetInterfaceBoolProperty(
2052                         iface_id, "NICEnabled", *interfaceEnabled, asyncResp);
2053                 }
2054             });
2055     }
2056 };
2057 
2058 /**
2059  * VlanNetworkInterface derived class for delivering VLANNetworkInterface
2060  * Schema
2061  */
2062 class VlanNetworkInterface : public Node
2063 {
2064   public:
2065     /*
2066      * Default Constructor
2067      */
2068     template <typename CrowApp>
2069     VlanNetworkInterface(CrowApp &app) :
2070         Node(app,
2071              "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/<str>",
2072              std::string(), std::string())
2073     {
2074         entityPrivileges = {
2075             {boost::beast::http::verb::get, {{"Login"}}},
2076             {boost::beast::http::verb::head, {{"Login"}}},
2077             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
2078             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
2079             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
2080             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
2081     }
2082 
2083   private:
2084     void parseInterfaceData(
2085         nlohmann::json &json_response, const std::string &parent_iface_id,
2086         const std::string &iface_id, const EthernetInterfaceData &ethData,
2087         const boost::container::flat_set<IPv4AddressData> &ipv4Data,
2088         const boost::container::flat_set<IPv6AddressData> &ipv6Data)
2089     {
2090         // Fill out obvious data...
2091         json_response["Id"] = iface_id;
2092         json_response["@odata.id"] =
2093             "/redfish/v1/Managers/bmc/EthernetInterfaces/" + parent_iface_id +
2094             "/VLANs/" + iface_id;
2095 
2096         json_response["VLANEnable"] = true;
2097         if (!ethData.vlan_id.empty())
2098         {
2099             json_response["VLANId"] = ethData.vlan_id.back();
2100         }
2101     }
2102 
2103     bool verifyNames(const std::string &parent, const std::string &iface)
2104     {
2105         if (!boost::starts_with(iface, parent + "_"))
2106         {
2107             return false;
2108         }
2109         else
2110         {
2111             return true;
2112         }
2113     }
2114 
2115     /**
2116      * Functions triggers appropriate requests on DBus
2117      */
2118     void doGet(crow::Response &res, const crow::Request &req,
2119                const std::vector<std::string> &params) override
2120     {
2121         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
2122         // TODO(Pawel) this shall be parameterized call (two params) to get
2123         // EthernetInterfaces for any Manager, not only hardcoded 'openbmc'.
2124         // Check if there is required param, truly entering this shall be
2125         // impossible.
2126         if (params.size() != 2)
2127         {
2128             messages::internalError(res);
2129             res.end();
2130             return;
2131         }
2132 
2133         const std::string &parent_iface_id = params[0];
2134         const std::string &iface_id = params[1];
2135         res.jsonValue["@odata.type"] =
2136             "#VLanNetworkInterface.v1_1_0.VLanNetworkInterface";
2137         res.jsonValue["Name"] = "VLAN Network Interface";
2138 
2139         if (!verifyNames(parent_iface_id, iface_id))
2140         {
2141             return;
2142         }
2143 
2144         // Get single eth interface data, and call the below callback for
2145         // JSON preparation
2146         getEthernetIfaceData(
2147             params[1],
2148             [this, asyncResp, parent_iface_id{std::string(params[0])},
2149              iface_id{std::string(params[1])}](
2150                 const bool &success, const EthernetInterfaceData &ethData,
2151                 const boost::container::flat_set<IPv4AddressData> &ipv4Data,
2152                 const boost::container::flat_set<IPv6AddressData> &ipv6Data) {
2153                 if (success && ethData.vlan_id.size() != 0)
2154                 {
2155                     parseInterfaceData(asyncResp->res.jsonValue,
2156                                        parent_iface_id, iface_id, ethData,
2157                                        ipv4Data, ipv6Data);
2158                 }
2159                 else
2160                 {
2161                     // ... otherwise return error
2162                     // TODO(Pawel)consider distinguish between non existing
2163                     // object, and other errors
2164                     messages::resourceNotFound(
2165                         asyncResp->res, "VLAN Network Interface", iface_id);
2166                 }
2167             });
2168     }
2169 
2170     void doPatch(crow::Response &res, const crow::Request &req,
2171                  const std::vector<std::string> &params) override
2172     {
2173         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
2174         if (params.size() != 2)
2175         {
2176             messages::internalError(asyncResp->res);
2177             return;
2178         }
2179 
2180         const std::string &parentIfaceId = params[0];
2181         const std::string &ifaceId = params[1];
2182 
2183         if (!verifyNames(parentIfaceId, ifaceId))
2184         {
2185             messages::resourceNotFound(asyncResp->res, "VLAN Network Interface",
2186                                        ifaceId);
2187             return;
2188         }
2189 
2190         bool vlanEnable = false;
2191         uint64_t vlanId = 0;
2192 
2193         if (!json_util::readJson(req, res, "VLANEnable", vlanEnable, "VLANId",
2194                                  vlanId))
2195         {
2196             return;
2197         }
2198 
2199         // Get single eth interface data, and call the below callback for
2200         // JSON preparation
2201         getEthernetIfaceData(
2202             params[1],
2203             [asyncResp, parentIfaceId{std::string(params[0])},
2204              ifaceId{std::string(params[1])}, &vlanEnable, &vlanId](
2205                 const bool &success, const EthernetInterfaceData &ethData,
2206                 const boost::container::flat_set<IPv4AddressData> &ipv4Data,
2207                 const boost::container::flat_set<IPv6AddressData> &ipv6Data) {
2208                 if (success && !ethData.vlan_id.empty())
2209                 {
2210                     auto callback =
2211                         [asyncResp](const boost::system::error_code ec) {
2212                             if (ec)
2213                             {
2214                                 messages::internalError(asyncResp->res);
2215                             }
2216                         };
2217 
2218                     if (vlanEnable == true)
2219                     {
2220                         crow::connections::systemBus->async_method_call(
2221                             std::move(callback), "xyz.openbmc_project.Network",
2222                             "/xyz/openbmc_project/network/" + ifaceId,
2223                             "org.freedesktop.DBus.Properties", "Set",
2224                             "xyz.openbmc_project.Network.VLAN", "Id",
2225                             std::variant<uint32_t>(vlanId));
2226                     }
2227                     else
2228                     {
2229                         BMCWEB_LOG_DEBUG << "vlanEnable is false. Deleting the "
2230                                             "vlan interface";
2231                         crow::connections::systemBus->async_method_call(
2232                             std::move(callback), "xyz.openbmc_project.Network",
2233                             std::string("/xyz/openbmc_project/network/") +
2234                                 ifaceId,
2235                             "xyz.openbmc_project.Object.Delete", "Delete");
2236                     }
2237                 }
2238                 else
2239                 {
2240                     // TODO(Pawel)consider distinguish between non existing
2241                     // object, and other errors
2242                     messages::resourceNotFound(
2243                         asyncResp->res, "VLAN Network Interface", ifaceId);
2244                     return;
2245                 }
2246             });
2247     }
2248 
2249     void doDelete(crow::Response &res, const crow::Request &req,
2250                   const std::vector<std::string> &params) override
2251     {
2252         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
2253         if (params.size() != 2)
2254         {
2255             messages::internalError(asyncResp->res);
2256             return;
2257         }
2258 
2259         const std::string &parentIfaceId = params[0];
2260         const std::string &ifaceId = params[1];
2261 
2262         if (!verifyNames(parentIfaceId, ifaceId))
2263         {
2264             messages::resourceNotFound(asyncResp->res, "VLAN Network Interface",
2265                                        ifaceId);
2266             return;
2267         }
2268 
2269         // Get single eth interface data, and call the below callback for
2270         // JSON preparation
2271         getEthernetIfaceData(
2272             params[1],
2273             [asyncResp, parentIfaceId{std::string(params[0])},
2274              ifaceId{std::string(params[1])}](
2275                 const bool &success, const EthernetInterfaceData &ethData,
2276                 const boost::container::flat_set<IPv4AddressData> &ipv4Data,
2277                 const boost::container::flat_set<IPv6AddressData> &ipv6Data) {
2278                 if (success && !ethData.vlan_id.empty())
2279                 {
2280                     auto callback =
2281                         [asyncResp](const boost::system::error_code ec) {
2282                             if (ec)
2283                             {
2284                                 messages::internalError(asyncResp->res);
2285                             }
2286                         };
2287                     crow::connections::systemBus->async_method_call(
2288                         std::move(callback), "xyz.openbmc_project.Network",
2289                         std::string("/xyz/openbmc_project/network/") + ifaceId,
2290                         "xyz.openbmc_project.Object.Delete", "Delete");
2291                 }
2292                 else
2293                 {
2294                     // ... otherwise return error
2295                     // TODO(Pawel)consider distinguish between non existing
2296                     // object, and other errors
2297                     messages::resourceNotFound(
2298                         asyncResp->res, "VLAN Network Interface", ifaceId);
2299                 }
2300             });
2301     }
2302 };
2303 
2304 /**
2305  * VlanNetworkInterfaceCollection derived class for delivering
2306  * VLANNetworkInterface Collection Schema
2307  */
2308 class VlanNetworkInterfaceCollection : public Node
2309 {
2310   public:
2311     template <typename CrowApp>
2312     VlanNetworkInterfaceCollection(CrowApp &app) :
2313         Node(app, "/redfish/v1/Managers/bmc/EthernetInterfaces/<str>/VLANs/",
2314              std::string())
2315     {
2316         entityPrivileges = {
2317             {boost::beast::http::verb::get, {{"Login"}}},
2318             {boost::beast::http::verb::head, {{"Login"}}},
2319             {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
2320             {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
2321             {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
2322             {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
2323     }
2324 
2325   private:
2326     /**
2327      * Functions triggers appropriate requests on DBus
2328      */
2329     void doGet(crow::Response &res, const crow::Request &req,
2330                const std::vector<std::string> &params) override
2331     {
2332         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
2333         if (params.size() != 1)
2334         {
2335             // This means there is a problem with the router
2336             messages::internalError(asyncResp->res);
2337             return;
2338         }
2339 
2340         const std::string &rootInterfaceName = params[0];
2341 
2342         // Get eth interface list, and call the below callback for JSON
2343         // preparation
2344         getEthernetIfaceList(
2345             [asyncResp, rootInterfaceName{std::string(rootInterfaceName)}](
2346                 const bool &success,
2347                 const boost::container::flat_set<std::string> &iface_list) {
2348                 if (!success)
2349                 {
2350                     messages::internalError(asyncResp->res);
2351                     return;
2352                 }
2353 
2354                 if (iface_list.find(rootInterfaceName) == iface_list.end())
2355                 {
2356                     messages::resourceNotFound(asyncResp->res,
2357                                                "VLanNetworkInterfaceCollection",
2358                                                rootInterfaceName);
2359                     return;
2360                 }
2361 
2362                 asyncResp->res.jsonValue["@odata.type"] =
2363                     "#VLanNetworkInterfaceCollection."
2364                     "VLanNetworkInterfaceCollection";
2365                 asyncResp->res.jsonValue["Name"] =
2366                     "VLAN Network Interface Collection";
2367 
2368                 nlohmann::json iface_array = nlohmann::json::array();
2369 
2370                 for (const std::string &iface_item : iface_list)
2371                 {
2372                     if (boost::starts_with(iface_item, rootInterfaceName + "_"))
2373                     {
2374                         iface_array.push_back(
2375                             {{"@odata.id",
2376                               "/redfish/v1/Managers/bmc/EthernetInterfaces/" +
2377                                   rootInterfaceName + "/VLANs/" + iface_item}});
2378                     }
2379                 }
2380 
2381                 asyncResp->res.jsonValue["Members@odata.count"] =
2382                     iface_array.size();
2383                 asyncResp->res.jsonValue["Members"] = std::move(iface_array);
2384                 asyncResp->res.jsonValue["@odata.id"] =
2385                     "/redfish/v1/Managers/bmc/EthernetInterfaces/" +
2386                     rootInterfaceName + "/VLANs";
2387             });
2388     }
2389 
2390     void doPost(crow::Response &res, const crow::Request &req,
2391                 const std::vector<std::string> &params) override
2392     {
2393         std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res);
2394         if (params.size() != 1)
2395         {
2396             messages::internalError(asyncResp->res);
2397             return;
2398         }
2399         bool vlanEnable = false;
2400         uint32_t vlanId = 0;
2401         if (!json_util::readJson(req, res, "VLANId", vlanId, "VLANEnable",
2402                                  vlanEnable))
2403         {
2404             return;
2405         }
2406         // Need both vlanId and vlanEnable to service this request
2407         if (!vlanId)
2408         {
2409             messages::propertyMissing(asyncResp->res, "VLANId");
2410         }
2411         if (!vlanEnable)
2412         {
2413             messages::propertyMissing(asyncResp->res, "VLANEnable");
2414         }
2415         if (static_cast<bool>(vlanId) ^ vlanEnable)
2416         {
2417             return;
2418         }
2419 
2420         const std::string &rootInterfaceName = params[0];
2421         auto callback = [asyncResp](const boost::system::error_code ec) {
2422             if (ec)
2423             {
2424                 // TODO(ed) make more consistent error messages based on
2425                 // phosphor-network responses
2426                 messages::internalError(asyncResp->res);
2427                 return;
2428             }
2429             messages::created(asyncResp->res);
2430         };
2431         crow::connections::systemBus->async_method_call(
2432             std::move(callback), "xyz.openbmc_project.Network",
2433             "/xyz/openbmc_project/network",
2434             "xyz.openbmc_project.Network.VLAN.Create", "VLAN",
2435             rootInterfaceName, vlanId);
2436     }
2437 };
2438 } // namespace redfish
2439