xref: /openbmc/bmcweb/redfish-core/include/redfish_oem_routing.hpp (revision fdf51f5c824273aafaa9262932735ca443db23eb)
1 #pragma once
2 
3 #include "async_resp.hpp"
4 #include "http_response.hpp"
5 #include "logging.hpp"
6 #include "redfishoemrule.hpp"
7 #include "sub_request.hpp"
8 #include "sub_route_trie.hpp"
9 #include "utility.hpp"
10 #include "utils/query_param.hpp"
11 #include "verb.hpp"
12 
13 #include <array>
14 #include <cstddef>
15 #include <cstdint>
16 #include <functional>
17 #include <memory>
18 #include <optional>
19 #include <stdexcept>
20 #include <string>
21 #include <string_view>
22 #include <utility>
23 #include <vector>
24 
25 namespace redfish
26 {
27 
28 // Helper struct to allow parsing string literals at compile time until
29 // std::string is supported in constexpr context.
30 // NOLINTBEGIN
31 template <size_t N>
32 struct StringLiteral
33 {
StringLiteralredfish::StringLiteral34     constexpr StringLiteral(const char (&str)[N])
35     {
36         std::copy_n(str, N, value);
37     }
38 
operator std::string_viewredfish::StringLiteral39     constexpr operator std::string_view() const
40     {
41         return std::string_view(std::data(value), N - 1);
42     }
43 
44     char value[N];
45 };
46 // Explicit deduction guide to prevent Clang warnings
47 template <size_t N>
48 StringLiteral(const char (&)[N]) -> StringLiteral<N>;
49 // NOLINTEND
50 
51 class OemRouter
52 {
53     using SubRouteTrie = crow::SubRouteTrie<crow::SubRouteNode>;
54 
55   public:
56     OemRouter() = default;
57 
58     template <StringLiteral URI>
newRule(HttpVerb method)59     constexpr auto& newRule(HttpVerb method)
60     {
61         auto& perMethod = perMethods[static_cast<size_t>(method)];
62         constexpr std::string_view rule = URI;
63         constexpr uint64_t numArgs = crow::utility::getParameterTag(rule);
64 
65         if constexpr (numArgs == 0)
66         {
67             using RuleT = OemRule<>;
68             std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule);
69             RuleT* ptr = ruleObject.get();
70             perMethod.internalAdd(rule, std::move(ruleObject));
71             return *ptr;
72         }
73         else if constexpr (numArgs == 1)
74         {
75             using RuleT = OemRule<std::string>;
76             std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule);
77             RuleT* ptr = ruleObject.get();
78             perMethod.internalAdd(rule, std::move(ruleObject));
79             return *ptr;
80         }
81         else if constexpr (numArgs == 2)
82         {
83             using RuleT = OemRule<std::string, std::string>;
84             std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule);
85             RuleT* ptr = ruleObject.get();
86             perMethod.internalAdd(rule, std::move(ruleObject));
87             return *ptr;
88         }
89         else if constexpr (numArgs == 3)
90         {
91             using RuleT = OemRule<std::string, std::string, std::string>;
92             std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule);
93             RuleT* ptr = ruleObject.get();
94             perMethod.internalAdd(rule, std::move(ruleObject));
95             return *ptr;
96         }
97         else if constexpr (numArgs == 4)
98         {
99             using RuleT =
100                 OemRule<std::string, std::string, std::string, std::string>;
101             std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule);
102             RuleT* ptr = ruleObject.get();
103             perMethod.internalAdd(rule, std::move(ruleObject));
104             return *ptr;
105         }
106         else
107         {
108             using RuleT = OemRule<std::string, std::string, std::string,
109                                   std::string, std::string>;
110             std::unique_ptr<RuleT> ruleObject = std::make_unique<RuleT>(rule);
111             RuleT* ptr = ruleObject.get();
112             perMethod.internalAdd(rule, std::move(ruleObject));
113             return *ptr;
114         }
115     }
116 
117     struct PerMethod
118     {
119         std::vector<std::unique_ptr<OemBaseRule>> rules;
120         SubRouteTrie trie;
121         // rule index 0 has special meaning; preallocate it to avoid
122         // duplication.
PerMethodredfish::OemRouter::PerMethod123         PerMethod() : rules(1) {}
124 
internalAddredfish::OemRouter::PerMethod125         void internalAdd(std::string_view rule,
126                          std::unique_ptr<OemBaseRule>&& ruleObject)
127         {
128             rules.emplace_back(std::move(ruleObject));
129             trie.add(rule, static_cast<unsigned>(rules.size() - 1U));
130             // request to /resource/#/frag matches /resource#/frag
131             size_t hashPos = rule.find("/#/");
132             if (hashPos != std::string_view::npos)
133             {
134                 std::string url(rule.substr(0, hashPos));
135                 url += '#';
136                 url += rule.substr(hashPos + 2); // Skip "/#" (2 characters)
137                 std::string_view fragRule = url;
138                 trie.add(fragRule, static_cast<unsigned>(rules.size() - 1U));
139             }
140         }
141     };
142 
143     struct FindRoute
144     {
145         std::vector<OemBaseRule*> fragmentRules;
146         std::vector<std::string> params;
147     };
148 
149     struct FindRouteResponse
150     {
151         FindRoute route;
152     };
153 
findRouteByPerMethod(std::string_view url,const PerMethod & perMethod)154     static FindRoute findRouteByPerMethod(std::string_view url,
155                                           const PerMethod& perMethod)
156     {
157         FindRoute route;
158 
159         SubRouteTrie::FindResult found = perMethod.trie.find(url);
160         route.params = std::move(found.params);
161         for (auto fragmentRuleIndex : found.fragmentRuleIndexes)
162         {
163             if (fragmentRuleIndex >= perMethod.rules.size())
164             {
165                 throw std::runtime_error("Trie internal structure corrupted!");
166             }
167             route.fragmentRules.emplace_back(
168                 (perMethod.rules[fragmentRuleIndex]).get());
169         }
170 
171         return route;
172     }
173 
findRoute(const SubRequest & req) const174     FindRouteResponse findRoute(const SubRequest& req) const
175     {
176         FindRouteResponse findRoute;
177         std::optional<HttpVerb> verb = httpVerbFromBoost(req.method());
178         if (!verb)
179         {
180             return findRoute;
181         }
182         size_t reqMethodIndex = static_cast<size_t>(*verb);
183         if (reqMethodIndex >= perMethods.size())
184         {
185             return findRoute;
186         }
187 
188         FindRoute route =
189             findRouteByPerMethod(req.url(), perMethods[reqMethodIndex]);
190         if (!route.fragmentRules.empty())
191         {
192             findRoute.route = route;
193         }
194         else
195         {
196             BMCWEB_LOG_DEBUG(
197                 "No fragments for url {}, method {}", req.url(),
198                 httpVerbToString(static_cast<HttpVerb>(reqMethodIndex)));
199         }
200 
201         return findRoute;
202     }
203 
validate()204     void validate()
205     {
206         for (PerMethod& perMethod : perMethods)
207         {
208             perMethod.trie.validate();
209         }
210     }
211 
debugPrint()212     void debugPrint()
213     {
214         for (size_t i = 0; i < perMethods.size(); i++)
215         {
216             BMCWEB_LOG_CRITICAL("{}",
217                                 httpVerbToString(static_cast<HttpVerb>(i)));
218             perMethods[i].trie.debugPrint();
219         }
220     }
221 
handle(const std::shared_ptr<SubRequest> & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp) const222     void handle(const std::shared_ptr<SubRequest>& req,
223                 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) const
224     {
225         BMCWEB_LOG_DEBUG("Checking OEM routes");
226         FindRouteResponse foundRoute = findRoute(*req);
227         std::vector<OemBaseRule*> fragments =
228             std::move(foundRoute.route.fragmentRules);
229         std::vector<std::string> params = std::move(foundRoute.route.params);
230         if (!fragments.empty())
231         {
232             std::function<void(crow::Response&)> handler =
233                 asyncResp->res.releaseCompleteRequestHandler();
234             auto multiResp = std::make_shared<bmcweb::AsyncResp>();
235             multiResp->res.setCompleteRequestHandler(std::move(handler));
236 
237             // Copy so that they exists when completion handler is called.
238             auto uriFragments =
239                 std::make_shared<std::vector<OemBaseRule*>>(fragments);
240             auto uriParams = std::make_shared<std::vector<std::string>>(params);
241 
242             asyncResp->res.setCompleteRequestHandler(std::bind_front(
243                 query_param::MultiAsyncResp::startMultiFragmentHandle, req,
244                 multiResp, uriFragments, uriParams));
245         }
246         else
247         {
248             BMCWEB_LOG_DEBUG("No OEM routes found");
249         }
250     }
251 
252   private:
253     std::array<PerMethod, static_cast<size_t>(HttpVerb::Max)> perMethods;
254 };
255 } // namespace redfish
256