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