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