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