xref: /openbmc/bmcweb/features/redfish/lib/ethernet.hpp (revision 927a505acb2a22daec332b1c32d9459ae21b9a2a)
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           // Find interface
666           if (resp.find("/xyz/openbmc_project/network/" + ethiface_id) ==
667               resp.end()) {
668             // Interface has not been found
669             callback(false, eth_data, ipv4_data);
670             return;
671           }
672 
673           extractEthernetInterfaceData(ethiface_id, resp, eth_data);
674           extractIPv4Data(ethiface_id, resp, ipv4_data);
675 
676           // Fix global GW
677           for (IPv4AddressData &ipv4 : ipv4_data) {
678             if ((ipv4.global) &&
679                 ((ipv4.gateway == nullptr) || (*ipv4.gateway == "0.0.0.0"))) {
680               ipv4.gateway = eth_data.default_gateway;
681             }
682           }
683 
684           // Finally make a callback with usefull data
685           callback(true, eth_data, ipv4_data);
686         },
687         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
688         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
689   };
690 
691   /**
692    * Function that retrieves all Ethernet Interfaces available through Network
693    * Manager
694    * @param callback a function that shall be called to convert Dbus output into
695    * JSON.
696    */
697   template <typename CallbackFunc>
698   void getEthernetIfaceList(CallbackFunc &&callback) {
699     crow::connections::system_bus->async_method_call(
700         [ this, callback{std::move(callback)} ](
701             const boost::system::error_code error_code,
702             GetManagedObjectsType &resp) {
703           // Callback requires vector<string> to retrieve all available ethernet
704           // interfaces
705           std::vector<std::string> iface_list;
706           iface_list.reserve(resp.size());
707           if (error_code) {
708             // Something wrong on DBus, the error_code is not important at this
709             // moment, just return success=false, and empty output. Since size
710             // of vector may vary depending on information from Network Manager,
711             // and empty output could not be treated same way as error.
712             callback(false, iface_list);
713             return;
714           }
715 
716           // Iterate over all retrieved ObjectPaths.
717           for (auto &objpath : resp) {
718             // And all interfaces available for certain ObjectPath.
719             for (auto &interface : objpath.second) {
720               // If interface is xyz.openbmc_project.Network.EthernetInterface,
721               // this is what we're looking for.
722               if (interface.first ==
723                   "xyz.openbmc_project.Network.EthernetInterface") {
724                 // Cut out everyting until last "/", ...
725                 const std::string &iface_id =
726                     static_cast<const std::string &>(objpath.first);
727                 std::size_t last_pos = iface_id.rfind("/");
728                 if (last_pos != std::string::npos) {
729                   // and put it into output vector.
730                   iface_list.emplace_back(iface_id.substr(last_pos + 1));
731                 }
732               }
733             }
734           }
735           // Finally make a callback with useful data
736           callback(true, iface_list);
737         },
738         "xyz.openbmc_project.Network", "/xyz/openbmc_project/network",
739         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
740   };
741 };
742 
743 /**
744  * EthernetCollection derived class for delivering Ethernet Collection Schema
745  */
746 class EthernetCollection : public Node {
747  public:
748   // TODO(Pawel) Remove line from below, where we assume that there is only one
749   // manager called openbmc This shall be generic, but requires to update
750   // GetSubroutes method
751   EthernetCollection(CrowApp &app)
752       : Node(app, "/redfish/v1/Managers/openbmc/EthernetInterfaces/") {
753     Node::json["@odata.type"] =
754         "#EthernetInterfaceCollection.EthernetInterfaceCollection";
755     Node::json["@odata.context"] =
756         "/redfish/v1/"
757         "$metadata#EthernetInterfaceCollection.EthernetInterfaceCollection";
758     Node::json["@odata.id"] = "/redfish/v1/Managers/openbmc/EthernetInterfaces";
759     Node::json["Name"] = "Ethernet Network Interface Collection";
760     Node::json["Description"] =
761         "Collection of EthernetInterfaces for this Manager";
762 
763     entityPrivileges = {
764         {boost::beast::http::verb::get, {{"Login"}}},
765         {boost::beast::http::verb::head, {{"Login"}}},
766         {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
767         {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
768         {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
769         {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
770   }
771 
772  private:
773   /**
774    * Functions triggers appropriate requests on DBus
775    */
776   void doGet(crow::response &res, const crow::request &req,
777              const std::vector<std::string> &params) override {
778     // TODO(Pawel) this shall be parametrized call to get EthernetInterfaces for
779     // any Manager, not only hardcoded 'openbmc'.
780     std::string manager_id = "openbmc";
781 
782     // Get eth interface list, and call the below callback for JSON preparation
783     ethernet_provider.getEthernetIfaceList(
784         [&, manager_id{std::move(manager_id)} ](
785             const bool &success, const std::vector<std::string> &iface_list) {
786           if (success) {
787             nlohmann::json iface_array = nlohmann::json::array();
788             for (const std::string &iface_item : iface_list) {
789               iface_array.push_back(
790                   {{"@odata.id", "/redfish/v1/Managers/" + manager_id +
791                                      "/EthernetInterfaces/" + iface_item}});
792             }
793             Node::json["Members"] = iface_array;
794             Node::json["Members@odata.count"] = iface_array.size();
795             Node::json["@odata.id"] =
796                 "/redfish/v1/Managers/" + manager_id + "/EthernetInterfaces";
797             res.json_value = Node::json;
798           } else {
799             // No success, best what we can do is return INTERNALL ERROR
800             res.result(boost::beast::http::status::internal_server_error);
801           }
802           res.end();
803         });
804   }
805 
806   // Ethernet Provider object
807   // TODO(Pawel) consider move it to singleton
808   OnDemandEthernetProvider ethernet_provider;
809 };
810 
811 /**
812  * EthernetInterface derived class for delivering Ethernet Schema
813  */
814 class EthernetInterface : public Node {
815  public:
816   /*
817    * Default Constructor
818    */
819   // TODO(Pawel) Remove line from below, where we assume that there is only one
820   // manager called openbmc This shall be generic, but requires to update
821   // GetSubroutes method
822   EthernetInterface(CrowApp &app)
823       : Node(app, "/redfish/v1/Managers/openbmc/EthernetInterfaces/<str>/",
824              std::string()) {
825     Node::json["@odata.type"] = "#EthernetInterface.v1_2_0.EthernetInterface";
826     Node::json["@odata.context"] =
827         "/redfish/v1/$metadata#EthernetInterface.EthernetInterface";
828     Node::json["Name"] = "Manager Ethernet Interface";
829     Node::json["Description"] = "Management Network Interface";
830 
831     entityPrivileges = {
832         {boost::beast::http::verb::get, {{"Login"}}},
833         {boost::beast::http::verb::head, {{"Login"}}},
834         {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
835         {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
836         {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
837         {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
838   }
839 
840   // TODO(kkowalsk) Find a suitable class/namespace for this
841   static void handleVlanPatch(const std::string &ifaceId,
842                               const nlohmann::json &input,
843                               const EthernetInterfaceData &eth_data,
844                               const std::string &pathPrefix,
845                               const std::shared_ptr<AsyncResp> &asyncResp) {
846     if (!input.is_object()) {
847       messages::addMessageToJson(
848           asyncResp->res.json_value,
849           messages::propertyValueTypeError(input.dump(), "VLAN"), pathPrefix);
850       return;
851     }
852 
853     const std::string pathStart = (pathPrefix == "/") ? "" : pathPrefix;
854     nlohmann::json &paramsJson =
855         (pathPrefix == "/")
856             ? asyncResp->res.json_value
857             : asyncResp->res.json_value[nlohmann::json_pointer<nlohmann::json>(
858                   pathPrefix)];
859     bool inputVlanEnabled;
860     uint64_t inputVlanId;
861 
862     json_util::Result inputVlanEnabledState = json_util::getBool(
863         "VLANEnable", input, inputVlanEnabled,
864         static_cast<int>(json_util::MessageSetting::TYPE_ERROR),
865         asyncResp->res.json_value, std::string(pathStart + "/VLANEnable"));
866     json_util::Result inputVlanIdState = json_util::getUnsigned(
867         "VLANId", input, inputVlanId,
868         static_cast<int>(json_util::MessageSetting::TYPE_ERROR),
869         asyncResp->res.json_value, std::string(pathStart + "/VLANId"));
870     bool inputInvalid = false;
871 
872     // Do not proceed if fields in VLAN object were of wrong type
873     if (inputVlanEnabledState == json_util::Result::WRONG_TYPE ||
874         inputVlanIdState == json_util::Result::WRONG_TYPE) {
875       return;
876     }
877 
878     // Verify input
879     if (eth_data.vlan_id == nullptr) {
880       // This interface is not a VLAN. Cannot do anything with it
881       // TODO(kkowalsk) Change this message
882       messages::addMessageToJson(asyncResp->res.json_value,
883                                  messages::propertyMissing("VLANEnable"),
884                                  pathPrefix);
885 
886       inputInvalid = true;
887     } else {
888       // Load actual data into field values if they were not provided
889       if (inputVlanEnabledState == json_util::Result::NOT_EXIST) {
890         inputVlanEnabled = true;
891       }
892 
893       if (inputVlanIdState == json_util::Result::NOT_EXIST) {
894         inputVlanId = *eth_data.vlan_id;
895       }
896     }
897 
898     // Do not proceed if input has not been valid
899     if (inputInvalid) {
900       return;
901     }
902 
903     // VLAN is configured on the interface
904     if (inputVlanEnabled == true && inputVlanId != *eth_data.vlan_id) {
905       // Change VLAN Id
906       paramsJson["VLANId"] = inputVlanId;
907       OnDemandEthernetProvider::changeVlanId(
908           ifaceId, static_cast<uint32_t>(inputVlanId),
909           [&, asyncResp, pathPrefx{std::move(pathPrefix)} ](
910               const boost::system::error_code ec) {
911             if (ec) {
912               messages::addMessageToJson(asyncResp->res.json_value,
913                                          messages::internalError(), pathPrefix);
914             } else {
915               paramsJson["VLANEnable"] = true;
916             }
917           });
918     } else if (inputVlanEnabled == false) {
919       // Disable VLAN
920       OnDemandEthernetProvider::disableVlan(
921           ifaceId, [&, asyncResp, pathPrefx{std::move(pathPrefix)} ](
922                        const boost::system::error_code ec) {
923             if (ec) {
924               messages::addMessageToJson(asyncResp->res.json_value,
925                                          messages::internalError(), pathPrefix);
926             } else {
927               paramsJson["VLANEnable"] = false;
928             }
929           });
930     }
931   }
932 
933  private:
934   void handleHostnamePatch(const nlohmann::json &input,
935                            const EthernetInterfaceData &eth_data,
936                            const std::shared_ptr<AsyncResp> &asyncResp) {
937     if (input.is_string()) {
938       std::string newHostname = input.get<std::string>();
939 
940       if (eth_data.hostname == nullptr || newHostname != *eth_data.hostname) {
941         // Change hostname
942         ethernet_provider.setHostName(
943             newHostname,
944             [asyncResp, newHostname](const boost::system::error_code ec) {
945               if (ec) {
946                 messages::addMessageToJson(asyncResp->res.json_value,
947                                            messages::internalError(),
948                                            "/HostName");
949               } else {
950                 asyncResp->res.json_value["HostName"] = newHostname;
951               }
952             });
953       }
954     } else {
955       messages::addMessageToJson(
956           asyncResp->res.json_value,
957           messages::propertyValueTypeError(input.dump(), "HostName"),
958           "/HostName");
959     }
960   }
961 
962   void handleIPv4Patch(const std::string &ifaceId, const nlohmann::json &input,
963                        const std::vector<IPv4AddressData> &ipv4_data,
964                        const std::shared_ptr<AsyncResp> &asyncResp) {
965     if (!input.is_array()) {
966       messages::addMessageToJson(
967           asyncResp->res.json_value,
968           messages::propertyValueTypeError(input.dump(), "IPv4Addresses"),
969           "/IPv4Addresses");
970       return;
971     }
972 
973     // According to Redfish PATCH definition, size must be at least equal
974     if (input.size() < ipv4_data.size()) {
975       // TODO(kkowalsk) This should be a message indicating that not enough
976       // data has been provided
977       messages::addMessageToJson(asyncResp->res.json_value,
978                                  messages::internalError(), "/IPv4Addresses");
979       return;
980     }
981 
982     json_util::Result addressFieldState;
983     json_util::Result subnetMaskFieldState;
984     json_util::Result addressOriginFieldState;
985     json_util::Result gatewayFieldState;
986     const std::string *addressFieldValue;
987     const std::string *subnetMaskFieldValue;
988     const std::string *addressOriginFieldValue = nullptr;
989     const std::string *gatewayFieldValue;
990     uint8_t subnetMaskAsPrefixLength;
991     std::string addressOriginInDBusFormat;
992 
993     bool errorDetected = false;
994     for (unsigned int entryIdx = 0; entryIdx < input.size(); entryIdx++) {
995       // Check that entry is not of some unexpected type
996       if (!input[entryIdx].is_object() && !input[entryIdx].is_null()) {
997         // Invalid object type
998         messages::addMessageToJson(
999             asyncResp->res.json_value,
1000             messages::propertyValueTypeError(input[entryIdx].dump(),
1001                                              "IPv4Address"),
1002             "/IPv4Addresses/" + std::to_string(entryIdx));
1003 
1004         continue;
1005       }
1006 
1007       // Try to load fields
1008       addressFieldState = json_util::getString(
1009           "Address", input[entryIdx], addressFieldValue,
1010           static_cast<uint8_t>(json_util::MessageSetting::TYPE_ERROR),
1011           asyncResp->res.json_value,
1012           "/IPv4Addresses/" + std::to_string(entryIdx) + "/Address");
1013       subnetMaskFieldState = json_util::getString(
1014           "SubnetMask", input[entryIdx], subnetMaskFieldValue,
1015           static_cast<uint8_t>(json_util::MessageSetting::TYPE_ERROR),
1016           asyncResp->res.json_value,
1017           "/IPv4Addresses/" + std::to_string(entryIdx) + "/SubnetMask");
1018       addressOriginFieldState = json_util::getString(
1019           "AddressOrigin", input[entryIdx], addressOriginFieldValue,
1020           static_cast<uint8_t>(json_util::MessageSetting::TYPE_ERROR),
1021           asyncResp->res.json_value,
1022           "/IPv4Addresses/" + std::to_string(entryIdx) + "/AddressOrigin");
1023       gatewayFieldState = json_util::getString(
1024           "Gateway", input[entryIdx], gatewayFieldValue,
1025           static_cast<uint8_t>(json_util::MessageSetting::TYPE_ERROR),
1026           asyncResp->res.json_value,
1027           "/IPv4Addresses/" + std::to_string(entryIdx) + "/Gateway");
1028 
1029       if (addressFieldState == json_util::Result::WRONG_TYPE ||
1030           subnetMaskFieldState == json_util::Result::WRONG_TYPE ||
1031           addressOriginFieldState == json_util::Result::WRONG_TYPE ||
1032           gatewayFieldState == json_util::Result::WRONG_TYPE) {
1033         return;
1034       }
1035 
1036       if (addressFieldState == json_util::Result::SUCCESS &&
1037           !ethernet_provider.ipv4VerifyIpAndGetBitcount(*addressFieldValue)) {
1038         errorDetected = true;
1039         messages::addMessageToJson(
1040             asyncResp->res.json_value,
1041             messages::propertyValueFormatError(*addressFieldValue, "Address"),
1042             "/IPv4Addresses/" + std::to_string(entryIdx) + "/Address");
1043       }
1044 
1045       if (subnetMaskFieldState == json_util::Result::SUCCESS &&
1046           !ethernet_provider.ipv4VerifyIpAndGetBitcount(
1047               *subnetMaskFieldValue, &subnetMaskAsPrefixLength)) {
1048         errorDetected = true;
1049         messages::addMessageToJson(
1050             asyncResp->res.json_value,
1051             messages::propertyValueFormatError(*subnetMaskFieldValue,
1052                                                "SubnetMask"),
1053             "/IPv4Addresses/" + std::to_string(entryIdx) + "/SubnetMask");
1054       }
1055 
1056       // Get Address origin in proper format
1057       addressOriginInDBusFormat =
1058           ethernet_provider.translateAddressOriginBetweenDBusAndRedfish(
1059               addressOriginFieldValue, true, false);
1060 
1061       if (addressOriginFieldState == json_util::Result::SUCCESS &&
1062           addressOriginInDBusFormat.empty()) {
1063         errorDetected = true;
1064         messages::addMessageToJson(
1065             asyncResp->res.json_value,
1066             messages::propertyValueNotInList(*addressOriginFieldValue,
1067                                              "AddressOrigin"),
1068             "/IPv4Addresses/" + std::to_string(entryIdx) + "/AddressOrigin");
1069       }
1070 
1071       if (gatewayFieldState == json_util::Result::SUCCESS &&
1072           !ethernet_provider.ipv4VerifyIpAndGetBitcount(*gatewayFieldValue)) {
1073         errorDetected = true;
1074         messages::addMessageToJson(
1075             asyncResp->res.json_value,
1076             messages::propertyValueFormatError(*gatewayFieldValue, "Gateway"),
1077             "/IPv4Addresses/" + std::to_string(entryIdx) + "/Gateway");
1078       }
1079 
1080       // If any error occured do not proceed with current entry, but do not
1081       // end loop
1082       if (errorDetected) {
1083         errorDetected = false;
1084         continue;
1085       }
1086 
1087       if (entryIdx >= ipv4_data.size()) {
1088         asyncResp->res.json_value["IPv4Addresses"][entryIdx] = input[entryIdx];
1089 
1090         // Verify that all field were provided
1091         if (addressFieldState == json_util::Result::NOT_EXIST) {
1092           errorDetected = true;
1093           messages::addMessageToJson(
1094               asyncResp->res.json_value, messages::propertyMissing("Address"),
1095               "/IPv4Addresses/" + std::to_string(entryIdx) + "/Address");
1096         }
1097 
1098         if (subnetMaskFieldState == json_util::Result::NOT_EXIST) {
1099           errorDetected = true;
1100           messages::addMessageToJson(
1101               asyncResp->res.json_value,
1102               messages::propertyMissing("SubnetMask"),
1103               "/IPv4Addresses/" + std::to_string(entryIdx) + "/SubnetMask");
1104         }
1105 
1106         if (addressOriginFieldState == json_util::Result::NOT_EXIST) {
1107           errorDetected = true;
1108           messages::addMessageToJson(
1109               asyncResp->res.json_value,
1110               messages::propertyMissing("AddressOrigin"),
1111               "/IPv4Addresses/" + std::to_string(entryIdx) + "/AddressOrigin");
1112         }
1113 
1114         if (gatewayFieldState == json_util::Result::NOT_EXIST) {
1115           errorDetected = true;
1116           messages::addMessageToJson(
1117               asyncResp->res.json_value, messages::propertyMissing("Gateway"),
1118               "/IPv4Addresses/" + std::to_string(entryIdx) + "/Gateway");
1119         }
1120 
1121         // If any error occured do not proceed with current entry, but do not
1122         // end loop
1123         if (errorDetected) {
1124           errorDetected = false;
1125           continue;
1126         }
1127 
1128         // Create IPv4 with provided data
1129         ethernet_provider.createIPv4(
1130             ifaceId, entryIdx, subnetMaskAsPrefixLength, *gatewayFieldValue,
1131             *addressFieldValue, asyncResp);
1132       } else {
1133         // Existing object that should be modified/deleted/remain unchanged
1134         if (input[entryIdx].is_null()) {
1135           // Object should be deleted
1136           ethernet_provider.deleteIPv4(ifaceId, ipv4_data[entryIdx].id,
1137                                        entryIdx, asyncResp);
1138         } else if (input[entryIdx].is_object()) {
1139           if (input[entryIdx].size() == 0) {
1140             // Object shall remain unchanged
1141             continue;
1142           }
1143 
1144           // Apply changes
1145           if (addressFieldState == json_util::Result::SUCCESS &&
1146               ipv4_data[entryIdx].address != nullptr &&
1147               *ipv4_data[entryIdx].address != *addressFieldValue) {
1148             ethernet_provider.changeIPv4AddressProperty(
1149                 ifaceId, entryIdx, ipv4_data[entryIdx].id, "Address",
1150                 *addressFieldValue, asyncResp);
1151           }
1152 
1153           if (subnetMaskFieldState == json_util::Result::SUCCESS &&
1154               ipv4_data[entryIdx].netmask != *subnetMaskFieldValue) {
1155             ethernet_provider.changeIPv4SubnetMaskProperty(
1156                 ifaceId, entryIdx, ipv4_data[entryIdx].id,
1157                 *subnetMaskFieldValue, subnetMaskAsPrefixLength, asyncResp);
1158           }
1159 
1160           if (addressOriginFieldState == json_util::Result::SUCCESS &&
1161               ipv4_data[entryIdx].origin != *addressFieldValue) {
1162             ethernet_provider.changeIPv4Origin(
1163                 ifaceId, entryIdx, ipv4_data[entryIdx].id,
1164                 *addressOriginFieldValue, addressOriginInDBusFormat, asyncResp);
1165           }
1166 
1167           if (gatewayFieldState == json_util::Result::SUCCESS &&
1168               ipv4_data[entryIdx].gateway != nullptr &&
1169               *ipv4_data[entryIdx].gateway != *gatewayFieldValue) {
1170             ethernet_provider.changeIPv4AddressProperty(
1171                 ifaceId, entryIdx, ipv4_data[entryIdx].id, "Gateway",
1172                 *gatewayFieldValue, asyncResp);
1173           }
1174         }
1175       }
1176     }
1177   }
1178 
1179   nlohmann::json parseInterfaceData(
1180       const std::string &iface_id, const EthernetInterfaceData &eth_data,
1181       const std::vector<IPv4AddressData> &ipv4_data) {
1182     // Copy JSON object to avoid race condition
1183     nlohmann::json json_response(Node::json);
1184 
1185     // Fill out obvious data...
1186     json_response["Id"] = iface_id;
1187     json_response["@odata.id"] =
1188         "/redfish/v1/Managers/openbmc/EthernetInterfaces/" + iface_id;
1189 
1190     // ... then the one from DBus, regarding eth iface...
1191     if (eth_data.speed != nullptr) json_response["SpeedMbps"] = *eth_data.speed;
1192 
1193     if (eth_data.mac_address != nullptr)
1194       json_response["MACAddress"] = *eth_data.mac_address;
1195 
1196     if (eth_data.hostname != nullptr)
1197       json_response["HostName"] = *eth_data.hostname;
1198 
1199     if (eth_data.vlan_id != nullptr) {
1200       nlohmann::json &vlanObj = json_response["VLAN"];
1201       vlanObj["VLANEnable"] = true;
1202       vlanObj["VLANId"] = *eth_data.vlan_id;
1203     } else {
1204       nlohmann::json &vlanObj = json_response["VLANs"];
1205       vlanObj["@odata.id"] =
1206           "/redfish/v1/Managers/openbmc/EthernetInterfaces/" + iface_id +
1207           "/VLANs";
1208     }
1209 
1210     // ... at last, check if there are IPv4 data and prepare appropriate
1211     // collection
1212     if (ipv4_data.size() > 0) {
1213       nlohmann::json ipv4_array = nlohmann::json::array();
1214       for (auto &ipv4_config : ipv4_data) {
1215         nlohmann::json json_ipv4;
1216         if (ipv4_config.address != nullptr) {
1217           json_ipv4["Address"] = *ipv4_config.address;
1218           if (ipv4_config.gateway != nullptr)
1219             json_ipv4["Gateway"] = *ipv4_config.gateway;
1220 
1221           json_ipv4["AddressOrigin"] = ipv4_config.origin;
1222           json_ipv4["SubnetMask"] = ipv4_config.netmask;
1223 
1224           ipv4_array.push_back(std::move(json_ipv4));
1225         }
1226       }
1227       json_response["IPv4Addresses"] = std::move(ipv4_array);
1228     }
1229 
1230     return json_response;
1231   }
1232 
1233   /**
1234    * Functions triggers appropriate requests on DBus
1235    */
1236   void doGet(crow::response &res, const crow::request &req,
1237              const std::vector<std::string> &params) override {
1238     // TODO(Pawel) this shall be parametrized call (two params) to get
1239     // EthernetInterfaces for any Manager, not only hardcoded 'openbmc'.
1240     // Check if there is required param, truly entering this shall be
1241     // impossible.
1242     if (params.size() != 1) {
1243       res.result(boost::beast::http::status::internal_server_error);
1244       res.end();
1245       return;
1246     }
1247 
1248     const std::string &iface_id = params[0];
1249 
1250     // Get single eth interface data, and call the below callback for JSON
1251     // preparation
1252     ethernet_provider.getEthernetIfaceData(
1253         iface_id, [&, iface_id](const bool &success,
1254                                 const EthernetInterfaceData &eth_data,
1255                                 const std::vector<IPv4AddressData> &ipv4_data) {
1256           if (success) {
1257             res.json_value = parseInterfaceData(iface_id, eth_data, ipv4_data);
1258           } else {
1259             // ... otherwise return error
1260             // TODO(Pawel)consider distinguish between non existing object, and
1261             // other errors
1262             messages::addMessageToErrorJson(
1263                 res.json_value,
1264                 messages::resourceNotFound("EthernetInterface", iface_id));
1265             res.result(boost::beast::http::status::not_found);
1266           }
1267           res.end();
1268         });
1269   }
1270 
1271   void doPatch(crow::response &res, const crow::request &req,
1272                const std::vector<std::string> &params) override {
1273     // TODO(Pawel) this shall be parametrized call (two params) to get
1274     // EthernetInterfaces for any Manager, not only hardcoded 'openbmc'.
1275     // Check if there is required param, truly entering this shall be
1276     // impossible.
1277     if (params.size() != 1) {
1278       res.result(boost::beast::http::status::internal_server_error);
1279       res.end();
1280       return;
1281     }
1282 
1283     const std::string &iface_id = params[0];
1284 
1285     nlohmann::json patchReq;
1286 
1287     if (!json_util::processJsonFromRequest(res, req, patchReq)) {
1288       return;
1289     }
1290 
1291     // Get single eth interface data, and call the below callback for JSON
1292     // preparation
1293     ethernet_provider.getEthernetIfaceData(
1294         iface_id,
1295         [&, iface_id, patchReq = std::move(patchReq) ](
1296             const bool &success, const EthernetInterfaceData &eth_data,
1297             const std::vector<IPv4AddressData> &ipv4_data) {
1298           if (!success) {
1299             // ... otherwise return error
1300             // TODO(Pawel)consider distinguish between non existing object, and
1301             // other errors
1302             messages::addMessageToErrorJson(
1303                 res.json_value,
1304                 messages::resourceNotFound("VLAN Network Interface", iface_id));
1305             res.result(boost::beast::http::status::not_found);
1306             res.end();
1307 
1308             return;
1309           }
1310 
1311           res.json_value = parseInterfaceData(iface_id, eth_data, ipv4_data);
1312 
1313           std::shared_ptr<AsyncResp> asyncResp =
1314               std::make_shared<AsyncResp>(res);
1315 
1316           for (auto propertyIt = patchReq.begin(); propertyIt != patchReq.end();
1317                ++propertyIt) {
1318             if (propertyIt.key() == "VLAN") {
1319               handleVlanPatch(iface_id, propertyIt.value(), eth_data, "/VLAN",
1320                               asyncResp);
1321             } else if (propertyIt.key() == "HostName") {
1322               handleHostnamePatch(propertyIt.value(), eth_data, asyncResp);
1323             } else if (propertyIt.key() == "IPv4Addresses") {
1324               handleIPv4Patch(iface_id, propertyIt.value(), ipv4_data,
1325                               asyncResp);
1326             } else if (propertyIt.key() == "IPv6Addresses") {
1327               // TODO(kkowalsk) IPv6 Not supported on D-Bus yet
1328               messages::addMessageToJsonRoot(
1329                   res.json_value,
1330                   messages::propertyNotWritable(propertyIt.key()));
1331             } else {
1332               auto fieldInJsonIt = res.json_value.find(propertyIt.key());
1333 
1334               if (fieldInJsonIt == res.json_value.end()) {
1335                 // Field not in scope of defined fields
1336                 messages::addMessageToJsonRoot(
1337                     res.json_value,
1338                     messages::propertyUnknown(propertyIt.key()));
1339               } else if (*fieldInJsonIt != *propertyIt) {
1340                 // User attempted to modify non-writable field
1341                 messages::addMessageToJsonRoot(
1342                     res.json_value,
1343                     messages::propertyNotWritable(propertyIt.key()));
1344               }
1345             }
1346           }
1347         });
1348   }
1349 
1350   // Ethernet Provider object
1351   // TODO(Pawel) consider move it to singleton
1352   OnDemandEthernetProvider ethernet_provider;
1353 };
1354 
1355 class VlanNetworkInterfaceCollection;
1356 
1357 /**
1358  * VlanNetworkInterface derived class for delivering VLANNetworkInterface Schema
1359  */
1360 class VlanNetworkInterface : public Node {
1361  public:
1362   /*
1363    * Default Constructor
1364    */
1365   template <typename CrowApp>
1366   // TODO(Pawel) Remove line from below, where we assume that there is only one
1367   // manager called openbmc This shall be generic, but requires to update
1368   // GetSubroutes method
1369   VlanNetworkInterface(CrowApp &app)
1370       : Node(
1371             app,
1372             "/redfish/v1/Managers/openbmc/EthernetInterfaces/<str>/VLANs/<str>",
1373             std::string(), std::string()) {
1374     Node::json["@odata.type"] =
1375         "#VLanNetworkInterface.v1_1_0.VLanNetworkInterface";
1376     Node::json["@odata.context"] =
1377         "/redfish/v1/$metadata#VLanNetworkInterface.VLanNetworkInterface";
1378     Node::json["Name"] = "VLAN Network Interface";
1379 
1380     entityPrivileges = {
1381         {boost::beast::http::verb::get, {{"Login"}}},
1382         {boost::beast::http::verb::head, {{"Login"}}},
1383         {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1384         {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1385         {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1386         {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1387   }
1388 
1389  private:
1390   nlohmann::json parseInterfaceData(
1391       const std::string &parent_iface_id, const std::string &iface_id,
1392       const EthernetInterfaceData &eth_data,
1393       const std::vector<IPv4AddressData> &ipv4_data) {
1394     // Copy JSON object to avoid race condition
1395     nlohmann::json json_response(Node::json);
1396 
1397     if (eth_data.vlan_id == nullptr) {
1398       // Interface not a VLAN - abort
1399       messages::addMessageToErrorJson(json_response, messages::internalError());
1400       return json_response;
1401     }
1402 
1403     // Fill out obvious data...
1404     json_response["Id"] = iface_id;
1405     json_response["@odata.id"] =
1406         "/redfish/v1/Managers/openbmc/EthernetInterfaces/" + parent_iface_id +
1407         "/VLANs/" + iface_id;
1408 
1409     json_response["VLANEnable"] = true;
1410     json_response["VLANId"] = *eth_data.vlan_id;
1411 
1412     return json_response;
1413   }
1414 
1415   bool verifyNames(crow::response &res, const std::string &parent,
1416                    const std::string &iface) {
1417     if (!boost::starts_with(iface, parent + "_")) {
1418       messages::addMessageToErrorJson(
1419           res.json_value,
1420           messages::resourceNotFound("VLAN Network Interface", iface));
1421       res.result(boost::beast::http::status::not_found);
1422       res.end();
1423 
1424       return false;
1425     } else {
1426       return true;
1427     }
1428   }
1429 
1430   /**
1431    * Functions triggers appropriate requests on DBus
1432    */
1433   void doGet(crow::response &res, const crow::request &req,
1434              const std::vector<std::string> &params) override {
1435     // TODO(Pawel) this shall be parametrized call (two params) to get
1436     // EthernetInterfaces for any Manager, not only hardcoded 'openbmc'.
1437     // Check if there is required param, truly entering this shall be
1438     // impossible.
1439     if (params.size() != 2) {
1440       res.result(boost::beast::http::status::internal_server_error);
1441       res.end();
1442       return;
1443     }
1444 
1445     const std::string &parent_iface_id = params[0];
1446     const std::string &iface_id = params[1];
1447 
1448     // Get single eth interface data, and call the below callback for JSON
1449     // preparation
1450     ethernet_provider.getEthernetIfaceData(
1451         iface_id,
1452         [&, parent_iface_id, iface_id](
1453             const bool &success, const EthernetInterfaceData &eth_data,
1454             const std::vector<IPv4AddressData> &ipv4_data) {
1455           if (success) {
1456             res.json_value = parseInterfaceData(parent_iface_id, iface_id,
1457                                                 eth_data, ipv4_data);
1458           } else {
1459             // ... otherwise return error
1460             // TODO(Pawel)consider distinguish between non existing object, and
1461             // other errors
1462             messages::addMessageToErrorJson(
1463                 res.json_value,
1464                 messages::resourceNotFound("VLAN Network Interface", iface_id));
1465             res.result(boost::beast::http::status::not_found);
1466           }
1467           res.end();
1468         });
1469   }
1470 
1471   void doPatch(crow::response &res, const crow::request &req,
1472                const std::vector<std::string> &params) override {
1473     if (params.size() != 2) {
1474       res.result(boost::beast::http::status::internal_server_error);
1475       res.end();
1476       return;
1477     }
1478 
1479     const std::string &parent_iface_id = params[0];
1480     const std::string &iface_id = params[1];
1481 
1482     if (!verifyNames(res, parent_iface_id, iface_id)) {
1483       return;
1484     }
1485 
1486     nlohmann::json patchReq;
1487 
1488     if (!json_util::processJsonFromRequest(res, req, patchReq)) {
1489       return;
1490     }
1491 
1492     // Get single eth interface data, and call the below callback for JSON
1493     // preparation
1494     ethernet_provider.getEthernetIfaceData(
1495         iface_id,
1496         [&, parent_iface_id, iface_id, patchReq = std::move(patchReq) ](
1497             const bool &success, const EthernetInterfaceData &eth_data,
1498             const std::vector<IPv4AddressData> &ipv4_data) {
1499           if (!success) {
1500             // ... otherwise return error
1501             // TODO(Pawel)consider distinguish between non existing object,
1502             // and
1503             // other errors
1504             messages::addMessageToErrorJson(
1505                 res.json_value,
1506                 messages::resourceNotFound("VLAN Network Interface", iface_id));
1507             res.result(boost::beast::http::status::not_found);
1508             res.end();
1509 
1510             return;
1511           }
1512 
1513           res.json_value = parseInterfaceData(parent_iface_id, iface_id,
1514                                               eth_data, ipv4_data);
1515 
1516           std::shared_ptr<AsyncResp> asyncResp =
1517               std::make_shared<AsyncResp>(res);
1518 
1519           for (auto propertyIt = patchReq.begin(); propertyIt != patchReq.end();
1520                ++propertyIt) {
1521             if (propertyIt.key() != "VLANEnable" &&
1522                 propertyIt.key() != "VLANId") {
1523               auto fieldInJsonIt = res.json_value.find(propertyIt.key());
1524 
1525               if (fieldInJsonIt == res.json_value.end()) {
1526                 // Field not in scope of defined fields
1527                 messages::addMessageToJsonRoot(
1528                     res.json_value,
1529                     messages::propertyUnknown(propertyIt.key()));
1530               } else if (*fieldInJsonIt != *propertyIt) {
1531                 // User attempted to modify non-writable field
1532                 messages::addMessageToJsonRoot(
1533                     res.json_value,
1534                     messages::propertyNotWritable(propertyIt.key()));
1535               }
1536             }
1537           }
1538 
1539           EthernetInterface::handleVlanPatch(iface_id, patchReq, eth_data, "/",
1540                                              asyncResp);
1541         });
1542   }
1543 
1544   void doDelete(crow::response &res, const crow::request &req,
1545                 const std::vector<std::string> &params) override {
1546     if (params.size() != 2) {
1547       res.result(boost::beast::http::status::internal_server_error);
1548       res.end();
1549       return;
1550     }
1551 
1552     const std::string &parent_iface_id = params[0];
1553     const std::string &iface_id = params[1];
1554 
1555     if (!verifyNames(res, parent_iface_id, iface_id)) {
1556       return;
1557     }
1558 
1559     // Get single eth interface data, and call the below callback for JSON
1560     // preparation
1561     ethernet_provider.getEthernetIfaceData(
1562         iface_id,
1563         [&, parent_iface_id, iface_id](
1564             const bool &success, const EthernetInterfaceData &eth_data,
1565             const std::vector<IPv4AddressData> &ipv4_data) {
1566           if (success && eth_data.vlan_id != nullptr) {
1567             res.json_value = parseInterfaceData(parent_iface_id, iface_id,
1568                                                 eth_data, ipv4_data);
1569 
1570             // Disable VLAN
1571             OnDemandEthernetProvider::disableVlan(
1572                 iface_id, [&](const boost::system::error_code ec) {
1573                   if (ec) {
1574                     res.json_value = nlohmann::json::object();
1575                     messages::addMessageToErrorJson(res.json_value,
1576                                                     messages::internalError());
1577                     res.result(
1578                         boost::beast::http::status::internal_server_error);
1579                   }
1580                   res.end();
1581                 });
1582           } else {
1583             // ... otherwise return error
1584             // TODO(Pawel)consider distinguish between non existing object,
1585             // and
1586             // other errors
1587             messages::addMessageToErrorJson(
1588                 res.json_value,
1589                 messages::resourceNotFound("VLAN Network Interface", iface_id));
1590             res.result(boost::beast::http::status::not_found);
1591             res.end();
1592           }
1593         });
1594   }
1595 
1596   /**
1597    * This allows VlanNetworkInterfaceCollection to reuse this class' doGet
1598    * method, to maintain consistency of returned data, as Collection's doPost
1599    * should return data for created member which should match member's doGet
1600    * result in 100%.
1601    */
1602   friend VlanNetworkInterfaceCollection;
1603 
1604   // Ethernet Provider object
1605   // TODO(Pawel) consider move it to singleton
1606   OnDemandEthernetProvider ethernet_provider;
1607 };
1608 
1609 /**
1610  * VlanNetworkInterfaceCollection derived class for delivering
1611  * VLANNetworkInterface Collection Schema
1612  */
1613 class VlanNetworkInterfaceCollection : public Node {
1614  public:
1615   template <typename CrowApp>
1616   // TODO(Pawel) Remove line from below, where we assume that there is only one
1617   // manager called openbmc This shall be generic, but requires to update
1618   // GetSubroutes method
1619   VlanNetworkInterfaceCollection(CrowApp &app)
1620       : Node(app,
1621              "/redfish/v1/Managers/openbmc/EthernetInterfaces/<str>/VLANs/",
1622              std::string()),
1623         memberVlan(app) {
1624     Node::json["@odata.type"] =
1625         "#VLanNetworkInterfaceCollection.VLanNetworkInterfaceCollection";
1626     Node::json["@odata.context"] =
1627         "/redfish/v1/$metadata"
1628         "#VLanNetworkInterfaceCollection.VLanNetworkInterfaceCollection";
1629     Node::json["Name"] = "VLAN Network Interface Collection";
1630 
1631     entityPrivileges = {
1632         {boost::beast::http::verb::get, {{"Login"}}},
1633         {boost::beast::http::verb::head, {{"Login"}}},
1634         {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
1635         {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
1636         {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
1637         {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
1638   }
1639 
1640  private:
1641   /**
1642    * Functions triggers appropriate requests on DBus
1643    */
1644   void doGet(crow::response &res, const crow::request &req,
1645              const std::vector<std::string> &params) override {
1646     if (params.size() != 1) {
1647       // This means there is a problem with the router
1648       res.result(boost::beast::http::status::internal_server_error);
1649       res.end();
1650 
1651       return;
1652     }
1653 
1654     // TODO(Pawel) this shall be parametrized call to get EthernetInterfaces for
1655     // any Manager, not only hardcoded 'openbmc'.
1656     std::string manager_id = "openbmc";
1657     std::string rootInterfaceName = params[0];
1658 
1659     // Get eth interface list, and call the below callback for JSON preparation
1660     ethernet_provider.getEthernetIfaceList([
1661           &, manager_id{std::move(manager_id)},
1662           rootInterfaceName{std::move(rootInterfaceName)}
1663     ](const bool &success, const std::vector<std::string> &iface_list) {
1664       if (success) {
1665         bool rootInterfaceFound = false;
1666         nlohmann::json iface_array = nlohmann::json::array();
1667 
1668         for (const std::string &iface_item : iface_list) {
1669           if (iface_item == rootInterfaceName) {
1670             rootInterfaceFound = true;
1671           } else if (boost::starts_with(iface_item, rootInterfaceName + "_")) {
1672             iface_array.push_back(
1673                 {{"@odata.id", "/redfish/v1/Managers/" + manager_id +
1674                                    "/EthernetInterfaces/" + rootInterfaceName +
1675                                    "/VLANs/" + iface_item}});
1676           }
1677         }
1678 
1679         if (rootInterfaceFound) {
1680           Node::json["Members"] = iface_array;
1681           Node::json["Members@odata.count"] = iface_array.size();
1682           Node::json["@odata.id"] = "/redfish/v1/Managers/" + manager_id +
1683                                     "/EthernetInterfaces/" + rootInterfaceName +
1684                                     "/VLANs";
1685           res.json_value = Node::json;
1686         } else {
1687           messages::addMessageToErrorJson(
1688               res.json_value, messages::resourceNotFound("EthernetInterface",
1689                                                          rootInterfaceName));
1690           res.result(boost::beast::http::status::not_found);
1691           res.end();
1692         }
1693       } else {
1694         // No success, best what we can do is return INTERNALL ERROR
1695         res.result(boost::beast::http::status::internal_server_error);
1696       }
1697       res.end();
1698     });
1699   }
1700 
1701   void doPost(crow::response &res, const crow::request &req,
1702               const std::vector<std::string> &params) override {
1703     if (params.size() != 1) {
1704       // This means there is a problem with the router
1705       res.result(boost::beast::http::status::internal_server_error);
1706       res.end();
1707       return;
1708     }
1709 
1710     nlohmann::json postReq;
1711 
1712     if (!json_util::processJsonFromRequest(res, req, postReq)) {
1713       return;
1714     }
1715 
1716     // TODO(Pawel) this shall be parametrized call to get EthernetInterfaces for
1717     // any Manager, not only hardcoded 'openbmc'.
1718     std::string manager_id = "openbmc";
1719     std::string rootInterfaceName = params[0];
1720     uint64_t vlanId;
1721     bool errorDetected;
1722 
1723     if (json_util::getUnsigned(
1724             "VLANId", postReq, vlanId,
1725             static_cast<uint8_t>(json_util::MessageSetting::MISSING) |
1726                 static_cast<uint8_t>(json_util::MessageSetting::TYPE_ERROR),
1727             res.json_value, "/VLANId") != json_util::Result::SUCCESS) {
1728       res.end();
1729       return;
1730     }
1731 
1732     // Get eth interface list, and call the below callback for JSON preparation
1733     ethernet_provider.getEthernetIfaceList([
1734           &, manager_id{std::move(manager_id)},
1735           rootInterfaceName{std::move(rootInterfaceName)}
1736     ](const bool &success, const std::vector<std::string> &iface_list) {
1737       if (success) {
1738         bool rootInterfaceFound = false;
1739 
1740         for (const std::string &iface_item : iface_list) {
1741           if (iface_item == rootInterfaceName) {
1742             rootInterfaceFound = true;
1743             break;
1744           }
1745         }
1746 
1747         if (rootInterfaceFound) {
1748           ethernet_provider.createVlan(
1749               rootInterfaceName, vlanId,
1750               [&, vlanId, rootInterfaceName,
1751                req{std::move(req)} ](const boost::system::error_code ec) {
1752                 if (ec) {
1753                   messages::addMessageToErrorJson(res.json_value,
1754                                                   messages::internalError());
1755                   res.end();
1756                 } else {
1757                   memberVlan.doGet(
1758                       res, req,
1759                       {rootInterfaceName,
1760                        rootInterfaceName + "_" + std::to_string(vlanId)});
1761                 }
1762               });
1763         } else {
1764           messages::addMessageToErrorJson(
1765               res.json_value, messages::resourceNotFound("EthernetInterface",
1766                                                          rootInterfaceName));
1767           res.result(boost::beast::http::status::not_found);
1768           res.end();
1769         }
1770       } else {
1771         // No success, best what we can do is return INTERNALL ERROR
1772         res.result(boost::beast::http::status::internal_server_error);
1773         res.end();
1774       }
1775     });
1776   }
1777 
1778   // Ethernet Provider object
1779   // TODO(Pawel) consider move it to singleton
1780   OnDemandEthernetProvider ethernet_provider;
1781   VlanNetworkInterface memberVlan;
1782 };
1783 
1784 }  // namespace redfish
1785