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