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