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