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