xref: /openbmc/bmcweb/features/redfish/lib/ethernet.hpp (revision 8ceb2eca408311131e661227122b2a72a7f65938)
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 <dbus_singleton.hpp>
19 #include <error_messages.hpp>
20 #include <node.hpp>
21 #include <utils/json_utils.hpp>
22 #include <boost/container/flat_map.hpp>
23 
24 namespace redfish {
25 
26 /**
27  * DBus types primitives for several generic DBus interfaces
28  * TODO(Pawel) consider move this to separate file into boost::dbus
29  */
30 using PropertiesMapType = boost::container::flat_map<
31     std::string,
32     sdbusplus::message::variant<std::string, bool, uint8_t, int16_t, uint16_t,
33                                 int32_t, uint32_t, int64_t, uint64_t, double>>;
34 
35 using GetManagedObjectsType = boost::container::flat_map<
36     sdbusplus::message::object_path,
37     boost::container::flat_map<
38         std::string,
39         boost::container::flat_map<
40             std::string, sdbusplus::message::variant<
41                              std::string, bool, uint8_t, int16_t, uint16_t,
42                              int32_t, uint32_t, int64_t, uint64_t, double>>>>;
43 
44 /**
45  * Structure for keeping IPv4 data required by Redfish
46  * TODO(Pawel) consider change everything to ptr, or to non-ptr values.
47  */
48 struct IPv4AddressData {
49   std::string id;
50   const std::string *address;
51   const std::string *domain;
52   const std::string *gateway;
53   std::string netmask;
54   std::string origin;
55   bool global;
56   /**
57    * @brief Operator< to enable sorting
58    *
59    * @param[in] obj   Object to compare with
60    *
61    * @return This object id < supplied object id
62    */
63   bool operator<(const IPv4AddressData &obj) const { return (id < obj.id); }
64 };
65 
66 /**
67  * Structure for keeping basic single Ethernet Interface information
68  * available from DBus
69  */
70 struct EthernetInterfaceData {
71   const unsigned int *speed;
72   const bool *autoNeg;
73   const std::string *hostname;
74   const std::string *defaultGateway;
75   const std::string *macAddress;
76   const unsigned int *vlanId;
77 };
78 
79 /**
80  * OnDemandEthernetProvider
81  * Ethernet provider class that retrieves data directly from dbus, before
82  * setting it into JSON output. This does not cache any data.
83  *
84  * TODO(Pawel)
85  * This perhaps shall be different file, which has to be chosen on compile time
86  * depending on OEM needs
87  */
88 class OnDemandEthernetProvider {
89  private:
90   // Consts that may have influence on EthernetProvider performance/memory usage
91   const size_t maxIpV4AddressesPerInterface = 10;
92 
93   // Helper function that allows to extract GetAllPropertiesType from
94   // GetManagedObjectsType, based on object path, and interface name
95   const PropertiesMapType *extractInterfaceProperties(
96       const sdbusplus::message::object_path &objpath,
97       const std::string &interface, const GetManagedObjectsType &dbus_data) {
98     const auto &dbusObj = dbus_data.find(objpath);
99     if (dbusObj != dbus_data.end()) {
100       const auto &iface = dbusObj->second.find(interface);
101       if (iface != dbusObj->second.end()) {
102         return &iface->second;
103       }
104     }
105     return nullptr;
106   }
107 
108   // Helper Wrapper that does inline object_path conversion from string
109   // into sdbusplus::message::object_path type
110   inline const PropertiesMapType *extractInterfaceProperties(
111       const std::string &objpath, const std::string &interface,
112       const GetManagedObjectsType &dbus_data) {
113     const auto &dbusObj = sdbusplus::message::object_path{objpath};
114     return extractInterfaceProperties(dbusObj, interface, dbus_data);
115   }
116 
117   // Helper function that allows to get pointer to the property from
118   // GetAllPropertiesType native, or extracted by GetAllPropertiesType
119   template <typename T>
120   inline T const *const extractProperty(const PropertiesMapType &properties,
121                                         const std::string &name) {
122     const auto &property = properties.find(name);
123     if (property != properties.end()) {
124       return mapbox::getPtr<const T>(property->second);
125     }
126     return nullptr;
127   }
128   // TODO(Pawel) Consider to move the above functions to dbus
129   // generic_interfaces.hpp
130 
131   // Helper function that extracts data from several dbus objects and several
132   // interfaces required by single ethernet interface instance
133   void extractEthernetInterfaceData(const std::string &ethifaceId,
134                                     const GetManagedObjectsType &dbus_data,
135                                     EthernetInterfaceData &eth_data) {
136     // Extract data that contains MAC Address
137     const PropertiesMapType *macProperties = extractInterfaceProperties(
138         "/xyz/openbmc_project/network/" + ethifaceId,
139         "xyz.openbmc_project.Network.MACAddress", dbus_data);
140 
141     if (macProperties != nullptr) {
142       eth_data.macAddress =
143           extractProperty<std::string>(*macProperties, "MACAddress");
144     }
145 
146     const PropertiesMapType *vlanProperties = extractInterfaceProperties(
147         "/xyz/openbmc_project/network/" + ethifaceId,
148         "xyz.openbmc_project.Network.VLAN", dbus_data);
149 
150     if (vlanProperties != nullptr) {
151       eth_data.vlanId = extractProperty<unsigned int>(*vlanProperties, "Id");
152     }
153 
154     // Extract data that contains link information (auto negotiation and speed)
155     const PropertiesMapType *ethProperties = extractInterfaceProperties(
156         "/xyz/openbmc_project/network/" + ethifaceId,
157         "xyz.openbmc_project.Network.EthernetInterface", dbus_data);
158 
159     if (ethProperties != nullptr) {
160       eth_data.autoNeg = extractProperty<bool>(*ethProperties, "AutoNeg");
161       eth_data.speed = extractProperty<unsigned int>(*ethProperties, "Speed");
162     }
163 
164     // Extract data that contains network config (HostName and DefaultGW)
165     const PropertiesMapType *configProperties = extractInterfaceProperties(
166         "/xyz/openbmc_project/network/config",
167         "xyz.openbmc_project.Network.SystemConfiguration", dbus_data);
168 
169     if (configProperties != nullptr) {
170       eth_data.hostname =
171           extractProperty<std::string>(*configProperties, "HostName");
172       eth_data.defaultGateway =
173           extractProperty<std::string>(*configProperties, "DefaultGateway");
174     }
175   }
176 
177   // Helper function that changes bits netmask notation (i.e. /24)
178   // into full dot notation
179   inline std::string getNetmask(unsigned int bits) {
180     uint32_t value = 0xffffffff << (32 - bits);
181     std::string netmask = std::to_string((value >> 24) & 0xff) + "." +
182                           std::to_string((value >> 16) & 0xff) + "." +
183                           std::to_string((value >> 8) & 0xff) + "." +
184                           std::to_string(value & 0xff);
185     return netmask;
186   }
187 
188   // Helper function that extracts data for single ethernet ipv4 address
189   void extractIPv4Data(const std::string &ethifaceId,
190                        const GetManagedObjectsType &dbus_data,
191                        std::vector<IPv4AddressData> &ipv4_config) {
192     const std::string pathStart =
193         "/xyz/openbmc_project/network/" + ethifaceId + "/ipv4/";
194 
195     // Since there might be several IPv4 configurations aligned with
196     // single ethernet interface, loop over all of them
197     for (auto &objpath : dbus_data) {
198       // Check if proper patter for object path appears
199       if (boost::starts_with(static_cast<const std::string &>(objpath.first),
200                              pathStart)) {
201         // and get approrpiate interface
202         const auto &interface =
203             objpath.second.find("xyz.openbmc_project.Network.IP");
204         if (interface != objpath.second.end()) {
205           // Make a properties 'shortcut', to make everything more readable
206           const PropertiesMapType &properties = interface->second;
207           // Instance IPv4AddressData structure, and set as appropriate
208           IPv4AddressData ipv4Address;
209 
210           ipv4Address.id = static_cast<const std::string &>(objpath.first)
211                                .substr(pathStart.size());
212 
213           // IPv4 address
214           ipv4Address.address =
215               extractProperty<std::string>(properties, "Address");
216           // IPv4 gateway
217           ipv4Address.gateway =
218               extractProperty<std::string>(properties, "Gateway");
219 
220           // Origin is kind of DBus object so fetch pointer...
221           const std::string *origin =
222               extractProperty<std::string>(properties, "Origin");
223           if (origin != nullptr) {
224             ipv4Address.origin =
225                 translateAddressOriginBetweenDBusAndRedfish(origin, true, true);
226           }
227 
228           // Netmask is presented as PrefixLength
229           const auto *mask =
230               extractProperty<uint8_t>(properties, "PrefixLength");
231           if (mask != nullptr) {
232             // convert it to the string
233             ipv4Address.netmask = getNetmask(*mask);
234           }
235 
236           // Attach IPv4 only if address is present
237           if (ipv4Address.address != nullptr) {
238             // Check if given address is local, or global
239             if (boost::starts_with(*ipv4Address.address, "169.254")) {
240               ipv4Address.global = false;
241             } else {
242               ipv4Address.global = true;
243             }
244             ipv4_config.emplace_back(std::move(ipv4Address));
245           }
246         }
247       }
248     }
249 
250     /**
251      * We have to sort this vector and ensure that order of IPv4 addresses
252      * is consistent between GETs to allow modification and deletion in PATCHes
253      */
254     std::sort(ipv4_config.begin(), ipv4_config.end());
255   }
256 
257   static const constexpr int ipV4AddressSectionsCount = 4;
258 
259  public:
260   /**
261    * @brief Creates VLAN for given interface with given Id through D-Bus
262    *
263    * @param[in] ifaceId       Id of interface for which VLAN will be created
264    * @param[in] inputVlanId   ID of the new VLAN
265    * @param[in] callback      Function that will be called after the operation
266    *
267    * @return None.
268    */
269   template <typename CallbackFunc>
270   void createVlan(const std::string &ifaceId, const uint64_t &inputVlanId,
271                   CallbackFunc &&callback) {
272     crow::connections::systemBus->async_method_call(
273         callback, "xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
274         "xyz.openbmc_project.Network.VLAN.Create", "VLAN", ifaceId,
275         static_cast<uint32_t>(inputVlanId));
276   };
277 
278   /**
279    * @brief Sets given Id on the given VLAN interface through D-Bus
280    *
281    * @param[in] ifaceId       Id of VLAN interface that should be modified
282    * @param[in] inputVlanId   New ID of the VLAN
283    * @param[in] callback      Function that will be called after the operation
284    *
285    * @return None.
286    */
287   template <typename CallbackFunc>
288   static void changeVlanId(const std::string &ifaceId,
289                            const uint32_t &inputVlanId,
290                            CallbackFunc &&callback) {
291     crow::connections::systemBus->async_method_call(
292         callback, "xyz.openbmc_project.Network",
293         std::string("/xyz/openbmc_project/network/") + ifaceId,
294         "org.freedesktop.DBus.Properties", "Set",
295         "xyz.openbmc_project.Network.VLAN", "Id",
296         sdbusplus::message::variant<uint32_t>(inputVlanId));
297   };
298 
299   /**
300    * @brief Helper function that verifies IP address to check if it is in
301    *        proper format. If bits pointer is provided, also calculates active
302    *        bit count for Subnet Mask.
303    *
304    * @param[in]  ip     IP that will be verified
305    * @param[out] bits   Calculated mask in bits notation
306    *
307    * @return true in case of success, false otherwise
308    */
309   bool ipv4VerifyIpAndGetBitcount(const std::string &ip,
310                                   uint8_t *bits = nullptr) {
311     std::vector<std::string> bytesInMask;
312 
313     boost::split(bytesInMask, ip, boost::is_any_of("."));
314 
315     if (bytesInMask.size() != ipV4AddressSectionsCount) {
316       return false;
317     }
318 
319     if (bits != nullptr) {
320       *bits = 0;
321     }
322 
323     char *endPtr;
324     long previousValue = 255;
325     bool firstZeroInByteHit;
326     for (const std::string &byte : bytesInMask) {
327       if (byte.empty()) {
328         return false;
329       }
330 
331       // Use strtol instead of stroi to avoid exceptions
332       long value = std::strtol(byte.c_str(), &endPtr, 10);
333 
334       // endPtr should point to the end of the string, otherwise given string
335       // is not 100% number
336       if (*endPtr != '\0') {
337         return false;
338       }
339 
340       // Value should be contained in byte
341       if (value < 0 || value > 255) {
342         return false;
343       }
344 
345       if (bits != nullptr) {
346         // Mask has to be continuous between bytes
347         if (previousValue != 255 && value != 0) {
348           return false;
349         }
350 
351         // Mask has to be continuous inside bytes
352         firstZeroInByteHit = false;
353 
354         // Count bits
355         for (int bitIdx = 7; bitIdx >= 0; bitIdx--) {
356           if (value & (1 << bitIdx)) {
357             if (firstZeroInByteHit) {
358               // Continuity not preserved
359               return false;
360             } else {
361               (*bits)++;
362             }
363           } else {
364             firstZeroInByteHit = true;
365           }
366         }
367       }
368 
369       previousValue = value;
370     }
371 
372     return true;
373   }
374 
375   /**
376    * @brief Changes IPv4 address type property (Address, Gateway)
377    *
378    * @param[in] ifaceId     Id of interface whose IP should be modified
379    * @param[in] ipIdx       index of IP in input array that should be modified
380    * @param[in] ipHash      DBus Hash id of modified IP
381    * @param[in] name        Name of field in JSON representation
382    * @param[in] newValue    New value that should be written
383    * @param[io] asyncResp   Response object that will be returned to client
384    *
385    * @return true if give IP is valid and has been sent do D-Bus, false
386    * otherwise
387    */
388   void changeIPv4AddressProperty(const std::string &ifaceId, int ipIdx,
389                                  const std::string &ipHash,
390                                  const std::string &name,
391                                  const std::string &newValue,
392                                  const std::shared_ptr<AsyncResp> &asyncResp) {
393     auto callback = [
394       asyncResp, ipIdx{std::move(ipIdx)}, name{std::move(name)},
395       newValue{std::move(newValue)}
396     ](const boost::system::error_code ec) {
397       if (ec) {
398         messages::addMessageToJson(
399             asyncResp->res.jsonValue, messages::internalError(),
400             "/IPv4Addresses/" + std::to_string(ipIdx) + "/" + name);
401       } else {
402         asyncResp->res.jsonValue["IPv4Addresses"][ipIdx][name] = newValue;
403       }
404     };
405 
406     crow::connections::systemBus->async_method_call(
407         std::move(callback), "xyz.openbmc_project.Network",
408         "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + ipHash,
409         "org.freedesktop.DBus.Properties", "Set",
410         "xyz.openbmc_project.Network.IP", name,
411         sdbusplus::message::variant<std::string>(newValue));
412   };
413 
414   /**
415    * @brief Changes IPv4 address origin property
416    *
417    * @param[in] ifaceId       Id of interface whose IP should be modified
418    * @param[in] ipIdx         index of IP in input array that should be modified
419    * @param[in] ipHash        DBus Hash id of modified IP
420    * @param[in] newValue      New value in Redfish format
421    * @param[in] newValueDbus  New value in D-Bus format
422    * @param[io] asyncResp     Response object that will be returned to client
423    *
424    * @return true if give IP is valid and has been sent do D-Bus, false
425    * otherwise
426    */
427   void changeIPv4Origin(const std::string &ifaceId, int ipIdx,
428                         const std::string &ipHash, const std::string &newValue,
429                         const std::string &newValueDbus,
430                         const std::shared_ptr<AsyncResp> &asyncResp) {
431     auto callback =
432         [ asyncResp, ipIdx{std::move(ipIdx)},
433           newValue{std::move(newValue)} ](const boost::system::error_code ec) {
434       if (ec) {
435         messages::addMessageToJson(
436             asyncResp->res.jsonValue, messages::internalError(),
437             "/IPv4Addresses/" + std::to_string(ipIdx) + "/AddressOrigin");
438       } else {
439         asyncResp->res.jsonValue["IPv4Addresses"][ipIdx]["AddressOrigin"] =
440             newValue;
441       }
442     };
443 
444     crow::connections::systemBus->async_method_call(
445         std::move(callback), "xyz.openbmc_project.Network",
446         "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + ipHash,
447         "org.freedesktop.DBus.Properties", "Set",
448         "xyz.openbmc_project.Network.IP", "Origin",
449         sdbusplus::message::variant<std::string>(newValueDbus));
450   };
451 
452   /**
453    * @brief Modifies SubnetMask for given IP
454    *
455    * @param[in] ifaceId      Id of interface whose IP should be modified
456    * @param[in] ipIdx        index of IP in input array that should be modified
457    * @param[in] ipHash       DBus Hash id of modified IP
458    * @param[in] newValueStr  Mask in dot notation as string
459    * @param[in] newValue     Mask as PrefixLength in bitcount
460    * @param[io] asyncResp   Response object that will be returned to client
461    *
462    * @return None
463    */
464   void changeIPv4SubnetMaskProperty(
465       const std::string &ifaceId, int ipIdx, const std::string &ipHash,
466       const std::string &newValueStr, uint8_t &newValue,
467       const std::shared_ptr<AsyncResp> &asyncResp) {
468     auto callback = [
469       asyncResp, ipIdx{std::move(ipIdx)}, newValueStr{std::move(newValueStr)}
470     ](const boost::system::error_code ec) {
471       if (ec) {
472         messages::addMessageToJson(
473             asyncResp->res.jsonValue, messages::internalError(),
474             "/IPv4Addresses/" + std::to_string(ipIdx) + "/SubnetMask");
475       } else {
476         asyncResp->res.jsonValue["IPv4Addresses"][ipIdx]["SubnetMask"] =
477             newValueStr;
478       }
479     };
480 
481     crow::connections::systemBus->async_method_call(
482         std::move(callback), "xyz.openbmc_project.Network",
483         "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + ipHash,
484         "org.freedesktop.DBus.Properties", "Set",
485         "xyz.openbmc_project.Network.IP", "PrefixLength",
486         sdbusplus::message::variant<uint8_t>(newValue));
487   };
488 
489   /**
490    * @brief Disables VLAN with given ifaceId
491    *
492    * @param[in] ifaceId   Id of VLAN interface that should be disabled
493    * @param[in] callback  Function that will be called after the operation
494    *
495    * @return None.
496    */
497   template <typename CallbackFunc>
498   static void disableVlan(const std::string &ifaceId, CallbackFunc &&callback) {
499     crow::connections::systemBus->async_method_call(
500         callback, "xyz.openbmc_project.Network",
501         std::string("/xyz/openbmc_project/network/") + ifaceId,
502         "xyz.openbmc_project.Object.Delete", "Delete");
503   };
504 
505   /**
506    * @brief Sets given HostName of the machine through D-Bus
507    *
508    * @param[in] newHostname   New name that HostName will be changed to
509    * @param[in] callback      Function that will be called after the operation
510    *
511    * @return None.
512    */
513   template <typename CallbackFunc>
514   void setHostName(const std::string &newHostname, CallbackFunc &&callback) {
515     crow::connections::systemBus->async_method_call(
516         callback, "xyz.openbmc_project.Network",
517         "/xyz/openbmc_project/network/config",
518         "org.freedesktop.DBus.Properties", "Set",
519         "xyz.openbmc_project.Network.SystemConfiguration", "HostName",
520         sdbusplus::message::variant<std::string>(newHostname));
521   };
522 
523   /**
524    * @brief Deletes given IPv4
525    *
526    * @param[in] ifaceId     Id of interface whose IP should be deleted
527    * @param[in] ipIdx       index of IP in input array that should be deleted
528    * @param[in] ipHash      DBus Hash id of IP that should be deleted
529    * @param[io] asyncResp   Response object that will be returned to client
530    *
531    * @return None
532    */
533   void deleteIPv4(const std::string &ifaceId, const std::string &ipHash,
534                   unsigned int ipIdx,
535                   const std::shared_ptr<AsyncResp> &asyncResp) {
536     crow::connections::systemBus->async_method_call(
537         [ ipIdx{std::move(ipIdx)}, asyncResp{std::move(asyncResp)} ](
538             const boost::system::error_code ec) {
539           if (ec) {
540             messages::addMessageToJson(
541                 asyncResp->res.jsonValue, messages::internalError(),
542                 "/IPv4Addresses/" + std::to_string(ipIdx) + "/");
543           } else {
544             asyncResp->res.jsonValue["IPv4Addresses"][ipIdx] = nullptr;
545           }
546         },
547         "xyz.openbmc_project.Network",
548         "/xyz/openbmc_project/network/" + ifaceId + "/ipv4/" + ipHash,
549         "xyz.openbmc_project.Object.Delete", "Delete");
550   }
551 
552   /**
553    * @brief Creates IPv4 with given data
554    *
555    * @param[in] ifaceId     Id of interface whose IP should be deleted
556    * @param[in] ipIdx       index of IP in input array that should be deleted
557    * @param[in] ipHash      DBus Hash id of IP that should be deleted
558    * @param[io] asyncResp   Response object that will be returned to client
559    *
560    * @return None
561    */
562   void createIPv4(const std::string &ifaceId, unsigned int ipIdx,
563                   uint8_t subnetMask, const std::string &gateway,
564                   const std::string &address,
565                   const std::shared_ptr<AsyncResp> &asyncResp) {
566     auto createIpHandler = [
567       ipIdx{std::move(ipIdx)}, asyncResp{std::move(asyncResp)}
568     ](const boost::system::error_code ec) {
569       if (ec) {
570         messages::addMessageToJson(
571             asyncResp->res.jsonValue, messages::internalError(),
572             "/IPv4Addresses/" + std::to_string(ipIdx) + "/");
573       }
574     };
575 
576     crow::connections::systemBus->async_method_call(
577         std::move(createIpHandler), "xyz.openbmc_project.Network",
578         "/xyz/openbmc_project/network/" + ifaceId,
579         "xyz.openbmc_project.Network.IP.Create", "IP",
580         "xyz.openbmc_project.Network.IP.Protocol.IPv4", address, subnetMask,
581         gateway);
582   }
583 
584   /**
585    * @brief Translates Address Origin value from D-Bus to Redfish format and
586    *        vice-versa
587    *
588    * @param[in] inputOrigin Input value that should be translated
589    * @param[in] isIPv4      True for IPv4 origins, False for IPv6
590    * @param[in] isFromDBus  True for DBus->Redfish conversion, false for reverse
591    *
592    * @return Empty string in case of failure, translated value otherwise
593    */
594   std::string translateAddressOriginBetweenDBusAndRedfish(
595       const std::string *inputOrigin, bool isIPv4, bool isFromDBus) {
596     // Invalid pointer
597     if (inputOrigin == nullptr) {
598       return "";
599     }
600 
601     static const constexpr unsigned int firstIPv4OnlyIdx = 1;
602     static const constexpr unsigned int firstIPv6OnlyIdx = 3;
603 
604     std::array<std::pair<const char *, const char *>, 6> translationTable{
605         {{"xyz.openbmc_project.Network.IP.AddressOrigin.Static", "Static"},
606          {"xyz.openbmc_project.Network.IP.AddressOrigin.DHCP", "DHCP"},
607          {"xyz.openbmc_project.Network.IP.AddressOrigin.LinkLocal",
608           "IPv4LinkLocal"},
609          {"xyz.openbmc_project.Network.IP.AddressOrigin.DHCP", "DHCPv6"},
610          {"xyz.openbmc_project.Network.IP.AddressOrigin.LinkLocal",
611           "LinkLocal"},
612          {"xyz.openbmc_project.Network.IP.AddressOrigin.SLAAC", "SLAAC"}}};
613 
614     for (unsigned int i = 0; i < translationTable.size(); i++) {
615       // Skip unrelated
616       if (isIPv4 && i >= firstIPv6OnlyIdx) break;
617       if (!isIPv4 && i >= firstIPv4OnlyIdx && i < firstIPv6OnlyIdx) continue;
618 
619       // When translating D-Bus to Redfish compare input to first element
620       if (isFromDBus && translationTable[i].first == *inputOrigin)
621         return translationTable[i].second;
622 
623       // When translating Redfish to D-Bus compare input to second element
624       if (!isFromDBus && translationTable[i].second == *inputOrigin)
625         return translationTable[i].first;
626     }
627 
628     // If we are still here, that means that value has not been found
629     return "";
630   }
631 
632   /**
633    * Function that retrieves all properties for given Ethernet Interface
634    * Object
635    * from EntityManager Network Manager
636    * @param ethifaceId a eth interface id to query on DBus
637    * @param callback a function that shall be called to convert Dbus output
638    * into JSON
639    */
640   template <typename CallbackFunc>
641   void getEthernetIfaceData(const std::string &ethifaceId,
642                             CallbackFunc &&callback) {
643     crow::connections::systemBus->async_method_call(
644         [
645           this, ethifaceId{std::move(ethifaceId)}, callback{std::move(callback)}
646         ](const boost::system::error_code error_code,
647           const GetManagedObjectsType &resp) {
648 
649           EthernetInterfaceData ethData{};
650           std::vector<IPv4AddressData> ipv4Data;
651           ipv4Data.reserve(maxIpV4AddressesPerInterface);
652 
653           if (error_code) {
654             // Something wrong on DBus, the error_code is not important at
655             // this moment, just return success=false, and empty output. Since
656             // size of vector may vary depending on information from Network
657             // Manager, and empty output could not be treated same way as
658             // error.
659             callback(false, ethData, ipv4Data);
660             return;
661           }
662 
663           // Find interface
664           if (resp.find("/xyz/openbmc_project/network/" + ethifaceId) ==
665               resp.end()) {
666             // Interface has not been found
667             callback(false, ethData, ipv4Data);
668             return;
669           }
670 
671           extractEthernetInterfaceData(ethifaceId, resp, ethData);
672           extractIPv4Data(ethifaceId, resp, ipv4Data);
673 
674           // Fix global GW
675           for (IPv4AddressData &ipv4 : ipv4Data) {
676             if ((ipv4.global) &&
677                 ((ipv4.gateway == nullptr) || (*ipv4.gateway == "0.0.0.0"))) {
678               ipv4.gateway = ethData.defaultGateway;
679             }
680           }
681 
682           // Finally make a callback with useful data
683           callback(true, ethData, ipv4Data);
684         },
685         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
686         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
687   };
688 
689   /**
690    * Function that retrieves all Ethernet Interfaces available through Network
691    * Manager
692    * @param callback a function that shall be called to convert Dbus output into
693    * JSON.
694    */
695   template <typename CallbackFunc>
696   void getEthernetIfaceList(CallbackFunc &&callback) {
697     crow::connections::systemBus->async_method_call(
698         [ this, callback{std::move(callback)} ](
699             const boost::system::error_code error_code,
700             GetManagedObjectsType &resp) {
701           // Callback requires vector<string> to retrieve all available ethernet
702           // interfaces
703           std::vector<std::string> ifaceList;
704           ifaceList.reserve(resp.size());
705           if (error_code) {
706             // Something wrong on DBus, the error_code is not important at this
707             // moment, just return success=false, and empty output. Since size
708             // of vector may vary depending on information from Network Manager,
709             // and empty output could not be treated same way as error.
710             callback(false, ifaceList);
711             return;
712           }
713 
714           // Iterate over all retrieved ObjectPaths.
715           for (auto &objpath : resp) {
716             // And all interfaces available for certain ObjectPath.
717             for (auto &interface : objpath.second) {
718               // If interface is xyz.openbmc_project.Network.EthernetInterface,
719               // this is what we're looking for.
720               if (interface.first ==
721                   "xyz.openbmc_project.Network.EthernetInterface") {
722                 // Cut out everything until last "/", ...
723                 const std::string &ifaceId =
724                     static_cast<const std::string &>(objpath.first);
725                 std::size_t lastPos = ifaceId.rfind("/");
726                 if (lastPos != std::string::npos) {
727                   // and put it into output vector.
728                   ifaceList.emplace_back(ifaceId.substr(lastPos + 1));
729                 }
730               }
731             }
732           }
733           // Finally make a callback with useful data
734           callback(true, ifaceList);
735         },
736         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
737         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
738   };
739 };
740 
741 /**
742  * EthernetCollection derived class for delivering Ethernet Collection Schema
743  */
744 class EthernetCollection : public Node {
745  public:
746   // TODO(Pawel) Remove line from below, where we assume that there is only one
747   // manager called openbmc This shall be generic, but requires to update
748   // GetSubroutes method
749   EthernetCollection(CrowApp &app)
750       : Node(app, "/redfish/v1/Managers/openbmc/EthernetInterfaces/") {
751     Node::json["@odata.type"] =
752         "#EthernetInterfaceCollection.EthernetInterfaceCollection";
753     Node::json["@odata.context"] =
754         "/redfish/v1/"
755         "$metadata#EthernetInterfaceCollection.EthernetInterfaceCollection";
756     Node::json["@odata.id"] = "/redfish/v1/Managers/openbmc/EthernetInterfaces";
757     Node::json["Name"] = "Ethernet Network Interface Collection";
758     Node::json["Description"] =
759         "Collection of EthernetInterfaces for this Manager";
760 
761     entityPrivileges = {
762         {boost::beast::http::verb::get, {{"Login"}}},
763         {boost::beast::http::verb::head, {{"Login"}}},
764         {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
765         {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
766         {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
767         {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
768   }
769 
770  private:
771   /**
772    * Functions triggers appropriate requests on DBus
773    */
774   void doGet(crow::Response &res, const crow::Request &req,
775              const std::vector<std::string> &params) override {
776     // TODO(Pawel) this shall be parametrized call to get EthernetInterfaces for
777     // any Manager, not only hardcoded 'openbmc'.
778     std::string managerId = "openbmc";
779 
780     // get eth interface list, and call the below callback for JSON preparation
781     ethernetProvider.getEthernetIfaceList([&, managerId{std::move(managerId)} ](
782         const bool &success, const std::vector<std::string> &iface_list) {
783       if (success) {
784         nlohmann::json ifaceArray = nlohmann::json::array();
785         for (const std::string &ifaceItem : iface_list) {
786           ifaceArray.push_back(
787               {{"@odata.id", "/redfish/v1/Managers/" + managerId +
788                                  "/EthernetInterfaces/" + ifaceItem}});
789         }
790         Node::json["Members"] = ifaceArray;
791         Node::json["Members@odata.count"] = ifaceArray.size();
792         Node::json["@odata.id"] =
793             "/redfish/v1/Managers/" + managerId + "/EthernetInterfaces";
794         res.jsonValue = Node::json;
795       } else {
796         // No success, best what we can do is return INTERNALL ERROR
797         res.result(boost::beast::http::status::internal_server_error);
798       }
799       res.end();
800     });
801   }
802 
803   // Ethernet Provider object
804   // TODO(Pawel) consider move it to singleton
805   OnDemandEthernetProvider ethernetProvider;
806 };
807 
808 /**
809  * EthernetInterface derived class for delivering Ethernet Schema
810  */
811 class EthernetInterface : public Node {
812  public:
813   /*
814    * Default Constructor
815    */
816   // TODO(Pawel) Remove line from below, where we assume that there is only one
817   // manager called openbmc This shall be generic, but requires to update
818   // GetSubroutes method
819   EthernetInterface(CrowApp &app)
820       : Node(app, "/redfish/v1/Managers/openbmc/EthernetInterfaces/<str>/",
821              std::string()) {
822     Node::json["@odata.type"] = "#EthernetInterface.v1_2_0.EthernetInterface";
823     Node::json["@odata.context"] =
824         "/redfish/v1/$metadata#EthernetInterface.EthernetInterface";
825     Node::json["Name"] = "Manager Ethernet Interface";
826     Node::json["Description"] = "Management Network Interface";
827 
828     entityPrivileges = {
829         {boost::beast::http::verb::get, {{"Login"}}},
830         {boost::beast::http::verb::head, {{"Login"}}},
831         {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
832         {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
833         {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
834         {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
835   }
836 
837   // TODO(kkowalsk) Find a suitable class/namespace for this
838   static void handleVlanPatch(const std::string &ifaceId,
839                               const nlohmann::json &input,
840                               const EthernetInterfaceData &eth_data,
841                               const std::string &pathPrefix,
842                               const std::shared_ptr<AsyncResp> &asyncResp) {
843     if (!input.is_object()) {
844       messages::addMessageToJson(
845           asyncResp->res.jsonValue,
846           messages::propertyValueTypeError(input.dump(), "VLAN"), pathPrefix);
847       return;
848     }
849 
850     const std::string pathStart = (pathPrefix == "/") ? "" : pathPrefix;
851     nlohmann::json &paramsJson =
852         (pathPrefix == "/")
853             ? asyncResp->res.jsonValue
854             : asyncResp->res.jsonValue[nlohmann::json_pointer<nlohmann::json>(
855                   pathPrefix)];
856     bool inputVlanEnabled;
857     uint64_t inputVlanId;
858 
859     json_util::Result inputVlanEnabledState = json_util::getBool(
860         "VLANEnable", input, inputVlanEnabled,
861         static_cast<int>(json_util::MessageSetting::TYPE_ERROR),
862         asyncResp->res.jsonValue, std::string(pathStart + "/VLANEnable"));
863     json_util::Result inputVlanIdState = json_util::getUnsigned(
864         "VLANId", input, inputVlanId,
865         static_cast<int>(json_util::MessageSetting::TYPE_ERROR),
866         asyncResp->res.jsonValue, std::string(pathStart + "/VLANId"));
867     bool inputInvalid = false;
868 
869     // Do not proceed if fields in VLAN object were of wrong type
870     if (inputVlanEnabledState == json_util::Result::WRONG_TYPE ||
871         inputVlanIdState == json_util::Result::WRONG_TYPE) {
872       return;
873     }
874 
875     // Verify input
876     if (eth_data.vlanId == nullptr) {
877       // This interface is not a VLAN. Cannot do anything with it
878       // TODO(kkowalsk) Change this message
879       messages::addMessageToJson(asyncResp->res.jsonValue,
880                                  messages::propertyMissing("VLANEnable"),
881                                  pathPrefix);
882 
883       inputInvalid = true;
884     } else {
885       // Load actual data into field values if they were not provided
886       if (inputVlanEnabledState == json_util::Result::NOT_EXIST) {
887         inputVlanEnabled = true;
888       }
889 
890       if (inputVlanIdState == json_util::Result::NOT_EXIST) {
891         inputVlanId = *eth_data.vlanId;
892       }
893     }
894 
895     // Do not proceed if input has not been valid
896     if (inputInvalid) {
897       return;
898     }
899 
900     // VLAN is configured on the interface
901     if (inputVlanEnabled == true && inputVlanId != *eth_data.vlanId) {
902       // Change VLAN Id
903       paramsJson["VLANId"] = inputVlanId;
904       OnDemandEthernetProvider::changeVlanId(
905           ifaceId, static_cast<uint32_t>(inputVlanId),
906           [&, asyncResp, pathPrefx{std::move(pathPrefix)} ](
907               const boost::system::error_code ec) {
908             if (ec) {
909               messages::addMessageToJson(asyncResp->res.jsonValue,
910                                          messages::internalError(), pathPrefix);
911             } else {
912               paramsJson["VLANEnable"] = true;
913             }
914           });
915     } else if (inputVlanEnabled == false) {
916       // Disable VLAN
917       OnDemandEthernetProvider::disableVlan(
918           ifaceId, [&, asyncResp, pathPrefx{std::move(pathPrefix)} ](
919                        const boost::system::error_code ec) {
920             if (ec) {
921               messages::addMessageToJson(asyncResp->res.jsonValue,
922                                          messages::internalError(), pathPrefix);
923             } else {
924               paramsJson["VLANEnable"] = false;
925             }
926           });
927     }
928   }
929 
930  private:
931   void handleHostnamePatch(const nlohmann::json &input,
932                            const EthernetInterfaceData &eth_data,
933                            const std::shared_ptr<AsyncResp> &asyncResp) {
934     if (input.is_string()) {
935       std::string newHostname = input.get<std::string>();
936 
937       if (eth_data.hostname == nullptr || newHostname != *eth_data.hostname) {
938         // Change hostname
939         ethernetProvider.setHostName(
940             newHostname,
941             [asyncResp, newHostname](const boost::system::error_code ec) {
942               if (ec) {
943                 messages::addMessageToJson(asyncResp->res.jsonValue,
944                                            messages::internalError(),
945                                            "/HostName");
946               } else {
947                 asyncResp->res.jsonValue["HostName"] = newHostname;
948               }
949             });
950       }
951     } else {
952       messages::addMessageToJson(
953           asyncResp->res.jsonValue,
954           messages::propertyValueTypeError(input.dump(), "HostName"),
955           "/HostName");
956     }
957   }
958 
959   void handleIPv4Patch(const std::string &ifaceId, const nlohmann::json &input,
960                        const std::vector<IPv4AddressData> &ipv4_data,
961                        const std::shared_ptr<AsyncResp> &asyncResp) {
962     if (!input.is_array()) {
963       messages::addMessageToJson(
964           asyncResp->res.jsonValue,
965           messages::propertyValueTypeError(input.dump(), "IPv4Addresses"),
966           "/IPv4Addresses");
967       return;
968     }
969 
970     // According to Redfish PATCH definition, size must be at least equal
971     if (input.size() < ipv4_data.size()) {
972       // TODO(kkowalsk) This should be a message indicating that not enough
973       // data has been provided
974       messages::addMessageToJson(asyncResp->res.jsonValue,
975                                  messages::internalError(), "/IPv4Addresses");
976       return;
977     }
978 
979     json_util::Result addressFieldState;
980     json_util::Result subnetMaskFieldState;
981     json_util::Result addressOriginFieldState;
982     json_util::Result gatewayFieldState;
983     const std::string *addressFieldValue;
984     const std::string *subnetMaskFieldValue;
985     const std::string *addressOriginFieldValue = nullptr;
986     const std::string *gatewayFieldValue;
987     uint8_t subnetMaskAsPrefixLength;
988     std::string addressOriginInDBusFormat;
989 
990     bool errorDetected = false;
991     for (unsigned int entryIdx = 0; entryIdx < input.size(); entryIdx++) {
992       // Check that entry is not of some unexpected type
993       if (!input[entryIdx].is_object() && !input[entryIdx].is_null()) {
994         // Invalid object type
995         messages::addMessageToJson(
996             asyncResp->res.jsonValue,
997             messages::propertyValueTypeError(input[entryIdx].dump(),
998                                              "IPv4Address"),
999             "/IPv4Addresses/" + std::to_string(entryIdx));
1000 
1001         continue;
1002       }
1003 
1004       // Try to load fields
1005       addressFieldState = json_util::getString(
1006           "Address", input[entryIdx], addressFieldValue,
1007           static_cast<uint8_t>(json_util::MessageSetting::TYPE_ERROR),
1008           asyncResp->res.jsonValue,
1009           "/IPv4Addresses/" + std::to_string(entryIdx) + "/Address");
1010       subnetMaskFieldState = json_util::getString(
1011           "SubnetMask", input[entryIdx], subnetMaskFieldValue,
1012           static_cast<uint8_t>(json_util::MessageSetting::TYPE_ERROR),
1013           asyncResp->res.jsonValue,
1014           "/IPv4Addresses/" + std::to_string(entryIdx) + "/SubnetMask");
1015       addressOriginFieldState = json_util::getString(
1016           "AddressOrigin", input[entryIdx], addressOriginFieldValue,
1017           static_cast<uint8_t>(json_util::MessageSetting::TYPE_ERROR),
1018           asyncResp->res.jsonValue,
1019           "/IPv4Addresses/" + std::to_string(entryIdx) + "/AddressOrigin");
1020       gatewayFieldState = json_util::getString(
1021           "Gateway", input[entryIdx], gatewayFieldValue,
1022           static_cast<uint8_t>(json_util::MessageSetting::TYPE_ERROR),
1023           asyncResp->res.jsonValue,
1024           "/IPv4Addresses/" + std::to_string(entryIdx) + "/Gateway");
1025 
1026       if (addressFieldState == json_util::Result::WRONG_TYPE ||
1027           subnetMaskFieldState == json_util::Result::WRONG_TYPE ||
1028           addressOriginFieldState == json_util::Result::WRONG_TYPE ||
1029           gatewayFieldState == json_util::Result::WRONG_TYPE) {
1030         return;
1031       }
1032 
1033       if (addressFieldState == json_util::Result::SUCCESS &&
1034           !ethernetProvider.ipv4VerifyIpAndGetBitcount(*addressFieldValue)) {
1035         errorDetected = true;
1036         messages::addMessageToJson(
1037             asyncResp->res.jsonValue,
1038             messages::propertyValueFormatError(*addressFieldValue, "Address"),
1039             "/IPv4Addresses/" + std::to_string(entryIdx) + "/Address");
1040       }
1041 
1042       if (subnetMaskFieldState == json_util::Result::SUCCESS &&
1043           !ethernetProvider.ipv4VerifyIpAndGetBitcount(
1044               *subnetMaskFieldValue, &subnetMaskAsPrefixLength)) {
1045         errorDetected = true;
1046         messages::addMessageToJson(
1047             asyncResp->res.jsonValue,
1048             messages::propertyValueFormatError(*subnetMaskFieldValue,
1049                                                "SubnetMask"),
1050             "/IPv4Addresses/" + std::to_string(entryIdx) + "/SubnetMask");
1051       }
1052 
1053       // get Address origin in proper format
1054       addressOriginInDBusFormat =
1055           ethernetProvider.translateAddressOriginBetweenDBusAndRedfish(
1056               addressOriginFieldValue, true, false);
1057 
1058       if (addressOriginFieldState == json_util::Result::SUCCESS &&
1059           addressOriginInDBusFormat.empty()) {
1060         errorDetected = true;
1061         messages::addMessageToJson(
1062             asyncResp->res.jsonValue,
1063             messages::propertyValueNotInList(*addressOriginFieldValue,
1064                                              "AddressOrigin"),
1065             "/IPv4Addresses/" + std::to_string(entryIdx) + "/AddressOrigin");
1066       }
1067 
1068       if (gatewayFieldState == json_util::Result::SUCCESS &&
1069           !ethernetProvider.ipv4VerifyIpAndGetBitcount(*gatewayFieldValue)) {
1070         errorDetected = true;
1071         messages::addMessageToJson(
1072             asyncResp->res.jsonValue,
1073             messages::propertyValueFormatError(*gatewayFieldValue, "Gateway"),
1074             "/IPv4Addresses/" + std::to_string(entryIdx) + "/Gateway");
1075       }
1076 
1077       // If any error occured do not proceed with current entry, but do not
1078       // end loop
1079       if (errorDetected) {
1080         errorDetected = false;
1081         continue;
1082       }
1083 
1084       if (entryIdx >= ipv4_data.size()) {
1085         asyncResp->res.jsonValue["IPv4Addresses"][entryIdx] = input[entryIdx];
1086 
1087         // Verify that all field were provided
1088         if (addressFieldState == json_util::Result::NOT_EXIST) {
1089           errorDetected = true;
1090           messages::addMessageToJson(
1091               asyncResp->res.jsonValue, messages::propertyMissing("Address"),
1092               "/IPv4Addresses/" + std::to_string(entryIdx) + "/Address");
1093         }
1094 
1095         if (subnetMaskFieldState == json_util::Result::NOT_EXIST) {
1096           errorDetected = true;
1097           messages::addMessageToJson(
1098               asyncResp->res.jsonValue, messages::propertyMissing("SubnetMask"),
1099               "/IPv4Addresses/" + std::to_string(entryIdx) + "/SubnetMask");
1100         }
1101 
1102         if (addressOriginFieldState == json_util::Result::NOT_EXIST) {
1103           errorDetected = true;
1104           messages::addMessageToJson(
1105               asyncResp->res.jsonValue,
1106               messages::propertyMissing("AddressOrigin"),
1107               "/IPv4Addresses/" + std::to_string(entryIdx) + "/AddressOrigin");
1108         }
1109 
1110         if (gatewayFieldState == json_util::Result::NOT_EXIST) {
1111           errorDetected = true;
1112           messages::addMessageToJson(
1113               asyncResp->res.jsonValue, messages::propertyMissing("Gateway"),
1114               "/IPv4Addresses/" + std::to_string(entryIdx) + "/Gateway");
1115         }
1116 
1117         // If any error occured do not proceed with current entry, but do not
1118         // end loop
1119         if (errorDetected) {
1120           errorDetected = false;
1121           continue;
1122         }
1123 
1124         // Create IPv4 with provided data
1125         ethernetProvider.createIPv4(ifaceId, entryIdx, subnetMaskAsPrefixLength,
1126                                     *gatewayFieldValue, *addressFieldValue,
1127                                     asyncResp);
1128       } else {
1129         // Existing object that should be modified/deleted/remain unchanged
1130         if (input[entryIdx].is_null()) {
1131           // Object should be deleted
1132           ethernetProvider.deleteIPv4(ifaceId, ipv4_data[entryIdx].id, entryIdx,
1133                                       asyncResp);
1134         } else if (input[entryIdx].is_object()) {
1135           if (input[entryIdx].size() == 0) {
1136             // Object shall remain unchanged
1137             continue;
1138           }
1139 
1140           // Apply changes
1141           if (addressFieldState == json_util::Result::SUCCESS &&
1142               ipv4_data[entryIdx].address != nullptr &&
1143               *ipv4_data[entryIdx].address != *addressFieldValue) {
1144             ethernetProvider.changeIPv4AddressProperty(
1145                 ifaceId, entryIdx, ipv4_data[entryIdx].id, "Address",
1146                 *addressFieldValue, asyncResp);
1147           }
1148 
1149           if (subnetMaskFieldState == json_util::Result::SUCCESS &&
1150               ipv4_data[entryIdx].netmask != *subnetMaskFieldValue) {
1151             ethernetProvider.changeIPv4SubnetMaskProperty(
1152                 ifaceId, entryIdx, ipv4_data[entryIdx].id,
1153                 *subnetMaskFieldValue, subnetMaskAsPrefixLength, asyncResp);
1154           }
1155 
1156           if (addressOriginFieldState == json_util::Result::SUCCESS &&
1157               ipv4_data[entryIdx].origin != *addressFieldValue) {
1158             ethernetProvider.changeIPv4Origin(
1159                 ifaceId, entryIdx, ipv4_data[entryIdx].id,
1160                 *addressOriginFieldValue, addressOriginInDBusFormat, asyncResp);
1161           }
1162 
1163           if (gatewayFieldState == json_util::Result::SUCCESS &&
1164               ipv4_data[entryIdx].gateway != nullptr &&
1165               *ipv4_data[entryIdx].gateway != *gatewayFieldValue) {
1166             ethernetProvider.changeIPv4AddressProperty(
1167                 ifaceId, entryIdx, ipv4_data[entryIdx].id, "Gateway",
1168                 *gatewayFieldValue, asyncResp);
1169           }
1170         }
1171       }
1172     }
1173   }
1174 
1175   nlohmann::json parseInterfaceData(
1176       const std::string &ifaceId, const EthernetInterfaceData &eth_data,
1177       const std::vector<IPv4AddressData> &ipv4_data) {
1178     // Copy JSON object to avoid race condition
1179     nlohmann::json jsonResponse(Node::json);
1180 
1181     // Fill out obvious data...
1182     jsonResponse["Id"] = ifaceId;
1183     jsonResponse["@odata.id"] =
1184         "/redfish/v1/Managers/openbmc/EthernetInterfaces/" + ifaceId;
1185 
1186     // ... then the one from DBus, regarding eth iface...
1187     if (eth_data.speed != nullptr) jsonResponse["SpeedMbps"] = *eth_data.speed;
1188 
1189     if (eth_data.macAddress != nullptr)
1190       jsonResponse["MACAddress"] = *eth_data.macAddress;
1191 
1192     if (eth_data.hostname != nullptr)
1193       jsonResponse["HostName"] = *eth_data.hostname;
1194 
1195     if (eth_data.vlanId != nullptr) {
1196       nlohmann::json &vlanObj = jsonResponse["VLAN"];
1197       vlanObj["VLANEnable"] = true;
1198       vlanObj["VLANId"] = *eth_data.vlanId;
1199     } else {
1200       nlohmann::json &vlanObj = jsonResponse["VLANs"];
1201       vlanObj["@odata.id"] =
1202           "/redfish/v1/Managers/openbmc/EthernetInterfaces/" + ifaceId +
1203           "/VLANs";
1204     }
1205 
1206     // ... at last, check if there are IPv4 data and prepare appropriate
1207     // collection
1208     if (ipv4_data.size() > 0) {
1209       nlohmann::json ipv4Array = nlohmann::json::array();
1210       for (auto &ipv4Config : ipv4_data) {
1211         nlohmann::json jsonIpv4;
1212         if (ipv4Config.address != nullptr) {
1213           jsonIpv4["Address"] = *ipv4Config.address;
1214           if (ipv4Config.gateway != nullptr)
1215             jsonIpv4["Gateway"] = *ipv4Config.gateway;
1216 
1217           jsonIpv4["AddressOrigin"] = ipv4Config.origin;
1218           jsonIpv4["SubnetMask"] = ipv4Config.netmask;
1219 
1220           ipv4Array.push_back(std::move(jsonIpv4));
1221         }
1222       }
1223       jsonResponse["IPv4Addresses"] = std::move(ipv4Array);
1224     }
1225 
1226     return jsonResponse;
1227   }
1228 
1229   /**
1230    * Functions triggers appropriate requests on DBus
1231    */
1232   void doGet(crow::Response &res, const crow::Request &req,
1233              const std::vector<std::string> &params) override {
1234     // TODO(Pawel) this shall be parametrized call (two params) to get
1235     // EthernetInterfaces for any Manager, not only hardcoded 'openbmc'.
1236     // Check if there is required param, truly entering this shall be
1237     // impossible.
1238     if (params.size() != 1) {
1239       res.result(boost::beast::http::status::internal_server_error);
1240       res.end();
1241       return;
1242     }
1243 
1244     const std::string &ifaceId = params[0];
1245 
1246     // get single eth interface data, and call the below callback for JSON
1247     // preparation
1248     ethernetProvider.getEthernetIfaceData(
1249         ifaceId,
1250         [&, ifaceId](const bool &success, const EthernetInterfaceData &eth_data,
1251                      const std::vector<IPv4AddressData> &ipv4_data) {
1252           if (success) {
1253             res.jsonValue = parseInterfaceData(ifaceId, eth_data, ipv4_data);
1254           } else {
1255             // ... otherwise return error
1256             // TODO(Pawel)consider distinguish between non existing object, and
1257             // other errors
1258             messages::addMessageToErrorJson(
1259                 res.jsonValue,
1260                 messages::resourceNotFound("EthernetInterface", ifaceId));
1261             res.result(boost::beast::http::status::not_found);
1262           }
1263           res.end();
1264         });
1265   }
1266 
1267   void doPatch(crow::Response &res, const crow::Request &req,
1268                const std::vector<std::string> &params) override {
1269     // TODO(Pawel) this shall be parametrized call (two params) to get
1270     // EthernetInterfaces for any Manager, not only hardcoded 'openbmc'.
1271     // Check if there is required param, truly entering this shall be
1272     // impossible.
1273     if (params.size() != 1) {
1274       res.result(boost::beast::http::status::internal_server_error);
1275       res.end();
1276       return;
1277     }
1278 
1279     const std::string &ifaceId = params[0];
1280 
1281     nlohmann::json patchReq;
1282 
1283     if (!json_util::processJsonFromRequest(res, req, patchReq)) {
1284       return;
1285     }
1286 
1287     // get single eth interface data, and call the below callback for JSON
1288     // preparation
1289     ethernetProvider.getEthernetIfaceData(
1290         ifaceId, [&, ifaceId, patchReq = std::move(patchReq) ](
1291                      const bool &success, const EthernetInterfaceData &eth_data,
1292                      const std::vector<IPv4AddressData> &ipv4_data) {
1293           if (!success) {
1294             // ... otherwise return error
1295             // TODO(Pawel)consider distinguish between non existing object, and
1296             // other errors
1297             messages::addMessageToErrorJson(
1298                 res.jsonValue,
1299                 messages::resourceNotFound("VLAN Network Interface", ifaceId));
1300             res.result(boost::beast::http::status::not_found);
1301             res.end();
1302 
1303             return;
1304           }
1305 
1306           res.jsonValue = parseInterfaceData(ifaceId, eth_data, ipv4_data);
1307 
1308           std::shared_ptr<AsyncResp> asyncResp =
1309               std::make_shared<AsyncResp>(res);
1310 
1311           for (auto propertyIt = patchReq.begin(); propertyIt != patchReq.end();
1312                ++propertyIt) {
1313             if (propertyIt.key() == "VLAN") {
1314               handleVlanPatch(ifaceId, propertyIt.value(), eth_data, "/VLAN",
1315                               asyncResp);
1316             } else if (propertyIt.key() == "HostName") {
1317               handleHostnamePatch(propertyIt.value(), eth_data, asyncResp);
1318             } else if (propertyIt.key() == "IPv4Addresses") {
1319               handleIPv4Patch(ifaceId, propertyIt.value(), ipv4_data,
1320                               asyncResp);
1321             } else if (propertyIt.key() == "IPv6Addresses") {
1322               // TODO(kkowalsk) IPv6 Not supported on D-Bus yet
1323               messages::addMessageToJsonRoot(
1324                   res.jsonValue,
1325                   messages::propertyNotWritable(propertyIt.key()));
1326             } else {
1327               auto fieldInJsonIt = res.jsonValue.find(propertyIt.key());
1328 
1329               if (fieldInJsonIt == res.jsonValue.end()) {
1330                 // Field not in scope of defined fields
1331                 messages::addMessageToJsonRoot(
1332                     res.jsonValue, messages::propertyUnknown(propertyIt.key()));
1333               } else if (*fieldInJsonIt != *propertyIt) {
1334                 // User attempted to modify non-writable field
1335                 messages::addMessageToJsonRoot(
1336                     res.jsonValue,
1337                     messages::propertyNotWritable(propertyIt.key()));
1338               }
1339             }
1340           }
1341         });
1342   }
1343 
1344   // Ethernet Provider object
1345   // TODO(Pawel) consider move it to singleton
1346   OnDemandEthernetProvider ethernetProvider;
1347 };
1348 
1349 class VlanNetworkInterfaceCollection;
1350 
1351 /**
1352  * VlanNetworkInterface derived class for delivering VLANNetworkInterface Schema
1353  */
1354 class VlanNetworkInterface : public Node {
1355  public:
1356   /*
1357    * Default Constructor
1358    */
1359   template <typename CrowApp>
1360   // TODO(Pawel) Remove line from below, where we assume that there is only one
1361   // manager called openbmc This shall be generic, but requires to update
1362   // GetSubroutes method
1363   VlanNetworkInterface(CrowApp &app)
1364       : Node(
1365             app,
1366             "/redfish/v1/Managers/openbmc/EthernetInterfaces/<str>/VLANs/<str>",
1367             std::string(), std::string()) {
1368     Node::json["@odata.type"] =
1369         "#VLanNetworkInterface.v1_1_0.VLanNetworkInterface";
1370     Node::json["@odata.context"] =
1371         "/redfish/v1/$metadata#VLanNetworkInterface.VLanNetworkInterface";
1372     Node::json["Name"] = "VLAN Network Interface";
1373 
1374     entityPrivileges = {
1375         {boost::beast::http::verb::get, {{"Login"}}},
1376         {boost::beast::http::verb::head, {{"Login"}}},
1377         {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1378         {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1379         {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1380         {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1381   }
1382 
1383  private:
1384   nlohmann::json parseInterfaceData(
1385       const std::string &parent_ifaceId, const std::string &ifaceId,
1386       const EthernetInterfaceData &eth_data,
1387       const std::vector<IPv4AddressData> &ipv4_data) {
1388     // Copy JSON object to avoid race condition
1389     nlohmann::json jsonResponse(Node::json);
1390 
1391     // Fill out obvious data...
1392     jsonResponse["Id"] = ifaceId;
1393     jsonResponse["@odata.id"] =
1394         "/redfish/v1/Managers/openbmc/EthernetInterfaces/" + parent_ifaceId +
1395         "/VLANs/" + ifaceId;
1396 
1397     jsonResponse["VLANEnable"] = true;
1398     jsonResponse["VLANId"] = *eth_data.vlanId;
1399 
1400     return jsonResponse;
1401   }
1402 
1403   bool verifyNames(crow::Response &res, const std::string &parent,
1404                    const std::string &iface) {
1405     if (!boost::starts_with(iface, parent + "_")) {
1406       messages::addMessageToErrorJson(
1407           res.jsonValue,
1408           messages::resourceNotFound("VLAN Network Interface", iface));
1409       res.result(boost::beast::http::status::not_found);
1410       res.end();
1411 
1412       return false;
1413     } else {
1414       return true;
1415     }
1416   }
1417 
1418   /**
1419    * Functions triggers appropriate requests on DBus
1420    */
1421   void doGet(crow::Response &res, const crow::Request &req,
1422              const std::vector<std::string> &params) override {
1423     // TODO(Pawel) this shall be parametrized call (two params) to get
1424     // EthernetInterfaces for any Manager, not only hardcoded 'openbmc'.
1425     // Check if there is required param, truly entering this shall be
1426     // impossible.
1427     if (params.size() != 2) {
1428       res.result(boost::beast::http::status::internal_server_error);
1429       res.end();
1430       return;
1431     }
1432 
1433     const std::string &parentIfaceId = params[0];
1434     const std::string &ifaceId = params[1];
1435 
1436     if (!verifyNames(res, parentIfaceId, ifaceId)) {
1437       return;
1438     }
1439 
1440     // Get single eth interface data, and call the below callback for JSON
1441     // preparation
1442     ethernetProvider.getEthernetIfaceData(
1443         ifaceId, [&, parentIfaceId, ifaceId](
1444                      const bool &success, const EthernetInterfaceData &eth_data,
1445                      const std::vector<IPv4AddressData> &ipv4_data) {
1446           if (success && eth_data.vlanId != nullptr) {
1447             res.jsonValue =
1448                 parseInterfaceData(parentIfaceId, ifaceId, eth_data, ipv4_data);
1449           } else {
1450             // ... otherwise return error
1451             // TODO(Pawel)consider distinguish between non existing object, and
1452             // other errors
1453             messages::addMessageToErrorJson(
1454                 res.jsonValue,
1455                 messages::resourceNotFound("VLAN Network Interface", ifaceId));
1456             res.result(boost::beast::http::status::not_found);
1457           }
1458           res.end();
1459         });
1460   }
1461 
1462   void doPatch(crow::Response &res, const crow::Request &req,
1463                const std::vector<std::string> &params) override {
1464     if (params.size() != 2) {
1465       res.result(boost::beast::http::status::internal_server_error);
1466       res.end();
1467       return;
1468     }
1469 
1470     const std::string &parent_ifaceId = params[0];
1471     const std::string &ifaceId = params[1];
1472 
1473     if (!verifyNames(res, parent_ifaceId, ifaceId)) {
1474       return;
1475     }
1476 
1477     nlohmann::json patchReq;
1478 
1479     if (!json_util::processJsonFromRequest(res, req, patchReq)) {
1480       return;
1481     }
1482 
1483     // Get single eth interface data, and call the below callback for JSON
1484     // preparation
1485     ethernetProvider.getEthernetIfaceData(
1486         ifaceId, [&, parent_ifaceId, ifaceId, patchReq = std::move(patchReq) ](
1487                      const bool &success, const EthernetInterfaceData &eth_data,
1488                      const std::vector<IPv4AddressData> &ipv4_data) {
1489           if (!success) {
1490             // ... otherwise return error
1491             // TODO(Pawel)consider distinguish between non existing object,
1492             // and
1493             // other errors
1494             messages::addMessageToErrorJson(
1495                 res.jsonValue,
1496                 messages::resourceNotFound("VLAN Network Interface", ifaceId));
1497             res.result(boost::beast::http::status::not_found);
1498             res.end();
1499 
1500             return;
1501           }
1502 
1503           res.jsonValue =
1504               parseInterfaceData(parent_ifaceId, ifaceId, eth_data, ipv4_data);
1505 
1506           std::shared_ptr<AsyncResp> asyncResp =
1507               std::make_shared<AsyncResp>(res);
1508 
1509           for (auto propertyIt = patchReq.begin(); propertyIt != patchReq.end();
1510                ++propertyIt) {
1511             if (propertyIt.key() != "VLANEnable" &&
1512                 propertyIt.key() != "VLANId") {
1513               auto fieldInJsonIt = res.jsonValue.find(propertyIt.key());
1514 
1515               if (fieldInJsonIt == res.jsonValue.end()) {
1516                 // Field not in scope of defined fields
1517                 messages::addMessageToJsonRoot(
1518                     res.jsonValue, messages::propertyUnknown(propertyIt.key()));
1519               } else if (*fieldInJsonIt != *propertyIt) {
1520                 // User attempted to modify non-writable field
1521                 messages::addMessageToJsonRoot(
1522                     res.jsonValue,
1523                     messages::propertyNotWritable(propertyIt.key()));
1524               }
1525             }
1526           }
1527 
1528           EthernetInterface::handleVlanPatch(ifaceId, patchReq, eth_data, "/",
1529                                              asyncResp);
1530         });
1531   }
1532 
1533   void doDelete(crow::Response &res, const crow::Request &req,
1534                 const std::vector<std::string> &params) override {
1535     if (params.size() != 2) {
1536       res.result(boost::beast::http::status::internal_server_error);
1537       res.end();
1538       return;
1539     }
1540 
1541     const std::string &parent_ifaceId = params[0];
1542     const std::string &ifaceId = params[1];
1543 
1544     if (!verifyNames(res, parent_ifaceId, ifaceId)) {
1545       return;
1546     }
1547 
1548     // Get single eth interface data, and call the below callback for JSON
1549     // preparation
1550     ethernetProvider.getEthernetIfaceData(
1551         ifaceId, [&, parent_ifaceId, ifaceId](
1552                      const bool &success, const EthernetInterfaceData &eth_data,
1553                      const std::vector<IPv4AddressData> &ipv4_data) {
1554           if (success && eth_data.vlanId != nullptr) {
1555             res.jsonValue = parseInterfaceData(parent_ifaceId, ifaceId,
1556                                                eth_data, ipv4_data);
1557 
1558             // Disable VLAN
1559             OnDemandEthernetProvider::disableVlan(
1560                 ifaceId, [&](const boost::system::error_code ec) {
1561                   if (ec) {
1562                     res.jsonValue = nlohmann::json::object();
1563                     messages::addMessageToErrorJson(res.jsonValue,
1564                                                     messages::internalError());
1565                     res.result(
1566                         boost::beast::http::status::internal_server_error);
1567                   }
1568                   res.end();
1569                 });
1570           } else {
1571             // ... otherwise return error
1572             // TODO(Pawel)consider distinguish between non existing object,
1573             // and
1574             // other errors
1575             messages::addMessageToErrorJson(
1576                 res.jsonValue,
1577                 messages::resourceNotFound("VLAN Network Interface", ifaceId));
1578             res.result(boost::beast::http::status::not_found);
1579             res.end();
1580           }
1581         });
1582   }
1583 
1584   /**
1585    * This allows VlanNetworkInterfaceCollection to reuse this class' doGet
1586    * method, to maintain consistency of returned data, as Collection's doPost
1587    * should return data for created member which should match member's doGet
1588    * result in 100%.
1589    */
1590   friend VlanNetworkInterfaceCollection;
1591 
1592   // Ethernet Provider object
1593   // TODO(Pawel) consider move it to singleton
1594   OnDemandEthernetProvider ethernetProvider;
1595 };
1596 
1597 /**
1598  * VlanNetworkInterfaceCollection derived class for delivering
1599  * VLANNetworkInterface Collection Schema
1600  */
1601 class VlanNetworkInterfaceCollection : public Node {
1602  public:
1603   template <typename CrowApp>
1604   // TODO(Pawel) Remove line from below, where we assume that there is only one
1605   // manager called openbmc This shall be generic, but requires to update
1606   // GetSubroutes method
1607   VlanNetworkInterfaceCollection(CrowApp &app)
1608       : Node(app,
1609              "/redfish/v1/Managers/openbmc/EthernetInterfaces/<str>/VLANs/",
1610              std::string()),
1611         memberVlan(app) {
1612     Node::json["@odata.type"] =
1613         "#VLanNetworkInterfaceCollection.VLanNetworkInterfaceCollection";
1614     Node::json["@odata.context"] =
1615         "/redfish/v1/$metadata"
1616         "#VLanNetworkInterfaceCollection.VLanNetworkInterfaceCollection";
1617     Node::json["Name"] = "VLAN Network Interface Collection";
1618 
1619     entityPrivileges = {
1620         {boost::beast::http::verb::get, {{"Login"}}},
1621         {boost::beast::http::verb::head, {{"Login"}}},
1622         {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1623         {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1624         {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1625         {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1626   }
1627 
1628  private:
1629   /**
1630    * Functions triggers appropriate requests on DBus
1631    */
1632   void doGet(crow::Response &res, const crow::Request &req,
1633              const std::vector<std::string> &params) override {
1634     if (params.size() != 1) {
1635       // This means there is a problem with the router
1636       res.result(boost::beast::http::status::internal_server_error);
1637       res.end();
1638 
1639       return;
1640     }
1641 
1642     // TODO(Pawel) this shall be parametrized call to get EthernetInterfaces for
1643     // any Manager, not only hardcoded 'openbmc'.
1644     std::string managerId = "openbmc";
1645     std::string rootInterfaceName = params[0];
1646 
1647     // get eth interface list, and call the below callback for JSON preparation
1648     ethernetProvider.getEthernetIfaceList([
1649           &, managerId{std::move(managerId)},
1650           rootInterfaceName{std::move(rootInterfaceName)}
1651     ](const bool &success, const std::vector<std::string> &iface_list) {
1652       if (success) {
1653         bool rootInterfaceFound = false;
1654         nlohmann::json ifaceArray = nlohmann::json::array();
1655 
1656         for (const std::string &ifaceItem : iface_list) {
1657           if (ifaceItem == rootInterfaceName) {
1658             rootInterfaceFound = true;
1659           } else if (boost::starts_with(ifaceItem, rootInterfaceName + "_")) {
1660             ifaceArray.push_back(
1661                 {{"@odata.id", "/redfish/v1/Managers/" + managerId +
1662                                    "/EthernetInterfaces/" + rootInterfaceName +
1663                                    "/VLANs/" + ifaceItem}});
1664           }
1665         }
1666 
1667         if (rootInterfaceFound) {
1668           Node::json["Members"] = ifaceArray;
1669           Node::json["Members@odata.count"] = ifaceArray.size();
1670           Node::json["@odata.id"] = "/redfish/v1/Managers/" + managerId +
1671                                     "/EthernetInterfaces/" + rootInterfaceName +
1672                                     "/VLANs";
1673           res.jsonValue = Node::json;
1674         } else {
1675           messages::addMessageToErrorJson(
1676               res.jsonValue, messages::resourceNotFound("EthernetInterface",
1677                                                         rootInterfaceName));
1678           res.result(boost::beast::http::status::not_found);
1679           res.end();
1680         }
1681       } else {
1682         // No success, best what we can do is return INTERNALL ERROR
1683         res.result(boost::beast::http::status::internal_server_error);
1684       }
1685       res.end();
1686     });
1687   }
1688 
1689   void doPost(crow::Response &res, const crow::Request &req,
1690               const std::vector<std::string> &params) override {
1691     if (params.size() != 1) {
1692       // This means there is a problem with the router
1693       res.result(boost::beast::http::status::internal_server_error);
1694       res.end();
1695       return;
1696     }
1697 
1698     nlohmann::json postReq;
1699 
1700     if (!json_util::processJsonFromRequest(res, req, postReq)) {
1701       return;
1702     }
1703 
1704     // TODO(Pawel) this shall be parametrized call to get EthernetInterfaces for
1705     // any Manager, not only hardcoded 'openbmc'.
1706     std::string managerId = "openbmc";
1707     std::string rootInterfaceName = params[0];
1708     uint64_t vlanId;
1709     bool errorDetected;
1710 
1711     if (json_util::getUnsigned(
1712             "VLANId", postReq, vlanId,
1713             static_cast<uint8_t>(json_util::MessageSetting::MISSING) |
1714                 static_cast<uint8_t>(json_util::MessageSetting::TYPE_ERROR),
1715             res.jsonValue, "/VLANId") != json_util::Result::SUCCESS) {
1716       res.end();
1717       return;
1718     }
1719 
1720     // get eth interface list, and call the below callback for JSON preparation
1721     ethernetProvider.getEthernetIfaceList([
1722           &, managerId{std::move(managerId)},
1723           rootInterfaceName{std::move(rootInterfaceName)}
1724     ](const bool &success, const std::vector<std::string> &iface_list) {
1725       if (success) {
1726         bool rootInterfaceFound = false;
1727 
1728         for (const std::string &ifaceItem : iface_list) {
1729           if (ifaceItem == rootInterfaceName) {
1730             rootInterfaceFound = true;
1731             break;
1732           }
1733         }
1734 
1735         if (rootInterfaceFound) {
1736           ethernetProvider.createVlan(
1737               rootInterfaceName, vlanId,
1738               [&, vlanId, rootInterfaceName,
1739                req{std::move(req)} ](const boost::system::error_code ec) {
1740                 if (ec) {
1741                   messages::addMessageToErrorJson(res.jsonValue,
1742                                                   messages::internalError());
1743                   res.end();
1744                 } else {
1745                   memberVlan.doGet(
1746                       res, req,
1747                       {rootInterfaceName,
1748                        rootInterfaceName + "_" + std::to_string(vlanId)});
1749                 }
1750               });
1751         } else {
1752           messages::addMessageToErrorJson(
1753               res.jsonValue, messages::resourceNotFound("EthernetInterface",
1754                                                         rootInterfaceName));
1755           res.result(boost::beast::http::status::not_found);
1756           res.end();
1757         }
1758       } else {
1759         // No success, best what we can do is return INTERNALL ERROR
1760         res.result(boost::beast::http::status::internal_server_error);
1761         res.end();
1762       }
1763     });
1764   }
1765 
1766   // Ethernet Provider object
1767   // TODO(Pawel) consider move it to singleton
1768   OnDemandEthernetProvider ethernetProvider;
1769   VlanNetworkInterface memberVlan;
1770 };
1771 
1772 }  // namespace redfish
1773