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