| #include "tlbmc/redfish/routing.h" | 
 |  | 
 | #include <array> | 
 | #include <cstddef> | 
 | #include <cstdlib> | 
 | #include <functional> | 
 | #include <memory> | 
 | #include <optional> | 
 | #include <stdexcept> | 
 | #include <string> | 
 | #include <string_view> | 
 | #include <tuple> | 
 | #include <utility> | 
 | #include <vector> | 
 |  | 
 | #include "absl/log/log.h" | 
 | #include "absl/strings/str_cat.h" | 
 | #include "absl/time/clock.h" | 
 | #include "tlbmc/redfish/common.h" | 
 | #include "tlbmc/redfish/query.h" | 
 | #include "tlbmc/redfish/request.h" | 
 | #include "tlbmc/redfish/response.h" | 
 | #include "tlbmc/redfish/verb.h" | 
 | #include "tlbmc/trace/tracer.h" | 
 |  | 
 | namespace milotic_tlbmc { | 
 | namespace crow { | 
 |  | 
 | using ::milotic_tlbmc::HttpVerb; | 
 | using ::milotic_tlbmc::HttpVerbFromBoost; | 
 | using ::milotic_tlbmc::kMaxVerbIndex; | 
 | using ::milotic_tlbmc::kMethodNotAllowedIndex; | 
 | using ::milotic_tlbmc::RedfishRequest; | 
 | using ::milotic_tlbmc::RedfishResponse; | 
 |  | 
 | void Trie::OptimizeNode(Node* node) { | 
 |   for (size_t x : node->param_children) { | 
 |     if (x == 0U) { | 
 |       continue; | 
 |     } | 
 |     Node* child = &nodes_[x]; | 
 |     OptimizeNode(child); | 
 |   } | 
 |   if (node->children.empty()) { | 
 |     return; | 
 |   } | 
 |   bool merge_with_child = true; | 
 |   for (const Node::ChildMap::value_type& kv : node->children) { | 
 |     Node* child = &nodes_[kv.second]; | 
 |     if (!child->IsSimpleNode()) { | 
 |       merge_with_child = false; | 
 |       break; | 
 |     } | 
 |   } | 
 |   if (merge_with_child) { | 
 |     Node::ChildMap merged; | 
 |     for (const Node::ChildMap::value_type& kv : node->children) { | 
 |       Node* child = &nodes_[kv.second]; | 
 |       for (const Node::ChildMap::value_type& child_kv : child->children) { | 
 |         merged[kv.first + child_kv.first] = child_kv.second; | 
 |       } | 
 |     } | 
 |     node->children = std::move(merged); | 
 |     OptimizeNode(node); | 
 |   } else { | 
 |     for (const Node::ChildMap::value_type& kv : node->children) { | 
 |       Node* child = &nodes_[kv.second]; | 
 |       OptimizeNode(child); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | std::pair<unsigned int, RoutingParams> Trie::Find(std::string_view url, | 
 |                                                   const Node* node, size_t pos, | 
 |                                                   RoutingParams* params) const { | 
 |   RoutingParams empty; | 
 |   if (params == nullptr) { | 
 |     params = ∅ | 
 |   } | 
 |  | 
 |   unsigned found{}; | 
 |   RoutingParams match_params; | 
 |  | 
 |   if (node == nullptr) { | 
 |     node = Head(); | 
 |   } | 
 |   if (pos == url.size()) { | 
 |     return {node->rule_index, *params}; | 
 |   } | 
 |  | 
 |   auto update_found = [&found, | 
 |                        &match_params](std::pair<unsigned, RoutingParams>& ret) { | 
 |     // Current Logic is to choose the rule with the lowest index in the allRules | 
 |     // vector | 
 |     bool replaceFound = found == 0U || found > ret.first; | 
 |  | 
 |     if (ret.first != 0U && replaceFound) { | 
 |       found = ret.first; | 
 |       match_params = std::move(ret.second); | 
 |     } | 
 |   }; | 
 |  | 
 |   for (const Node::ChildMap::value_type& kv : node->children) { | 
 |     const std::string& fragment = kv.first; | 
 |     const Node* child = &nodes_[kv.second]; | 
 |  | 
 |     if (url.compare(pos, fragment.size(), fragment) == 0) { | 
 |       std::pair<unsigned, RoutingParams> ret = | 
 |           Find(url, child, pos + fragment.size(), params); | 
 |       update_found(ret); | 
 |     } | 
 |   } | 
 |  | 
 |   if (node->param_children[static_cast<size_t>(ParamType::kString)] != 0U) { | 
 |     size_t epos = pos; | 
 |     for (; epos < url.size(); epos++) { | 
 |       if (url[epos] == '/') { | 
 |         break; | 
 |       } | 
 |     } | 
 |  | 
 |     if (epos != pos) { | 
 |       params->string_params.emplace_back(url.substr(pos, epos - pos)); | 
 |       std::pair<unsigned, RoutingParams> ret = | 
 |           Find(url, | 
 |                &nodes_[node->param_children[static_cast<size_t>( | 
 |                    ParamType::kString)]], | 
 |                epos, params); | 
 |       update_found(ret); | 
 |       params->string_params.pop_back(); | 
 |     } | 
 |   } | 
 |  | 
 |   return {found, match_params}; | 
 | } | 
 |  | 
 | void Trie::Add(const std::string& url, unsigned int index) { | 
 |   size_t idx = 0; | 
 |  | 
 |   for (unsigned i = 0; i < url.size(); i++) { | 
 |     char c = url[i]; | 
 |     if (c == '<') { | 
 |       constexpr std::array<std::pair<ParamType, std::string_view>, 2> | 
 |           param_traits = { | 
 |               std::pair<ParamType, std::string_view>{ParamType::kString, | 
 |                                                      "<str>"}, | 
 |               std::pair<ParamType, std::string_view>{ParamType::kString, | 
 |                                                      "<string>"}, | 
 |           }; | 
 |  | 
 |       for (const std::pair<ParamType, std::string_view>& x : param_traits) { | 
 |         if (url.compare(i, x.second.size(), x.second) == 0) { | 
 |           size_t index_inner = static_cast<size_t>(x.first); | 
 |           if (nodes_[idx].param_children[index_inner] == 0U) { | 
 |             unsigned new_node_index = NewNode(); | 
 |             nodes_[idx].param_children[index_inner] = new_node_index; | 
 |           } | 
 |           idx = nodes_[idx].param_children[index_inner]; | 
 |           i += static_cast<unsigned>(x.second.size()); | 
 |           break; | 
 |         } | 
 |       } | 
 |  | 
 |       i--; | 
 |     } else { | 
 |       std::string piece(&c, 1); | 
 |       if (nodes_[idx].children.count(piece) == 0U) { | 
 |         unsigned new_node_index = NewNode(); | 
 |         nodes_[idx].children.emplace(piece, new_node_index); | 
 |       } | 
 |       idx = nodes_[idx].children[piece]; | 
 |     } | 
 |   } | 
 |   if (nodes_[idx].rule_index != 0U) { | 
 |     throw std::runtime_error("handler already exists for " + url); | 
 |   } | 
 |   nodes_[idx].rule_index = index; | 
 | } | 
 |  | 
 | void Router::InternalAddRuleObject(const std::string& rule, | 
 |                                    BaseRule* rule_object) { | 
 |   if (rule_object == nullptr) { | 
 |     return; | 
 |   } | 
 |   for (size_t method = 0, method_bit = 1; method <= kMethodNotAllowedIndex; | 
 |        method++, method_bit <<= 1) { | 
 |     if ((rule_object->methods_bit_field_ & method_bit) > 0U) { | 
 |       per_methods_[method].rules.push_back(rule_object); | 
 |       per_methods_[method].trie.Add( | 
 |           rule, static_cast<unsigned>(per_methods_[method].rules.size() - 1U)); | 
 |       // directory case: | 
 |       //   request to `/about' url matches `/about/' rule | 
 |       if (rule.size() > 2 && rule.back() == '/') { | 
 |         per_methods_[method].trie.Add( | 
 |             rule.substr(0, rule.size() - 1), | 
 |             static_cast<unsigned>(per_methods_[method].rules.size() - 1)); | 
 |       } | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | void Router::Validate() { | 
 |   for (std::unique_ptr<BaseRule>& rule : all_rules_) { | 
 |     if (rule) { | 
 |       rule->Validate(); | 
 |       InternalAddRuleObject(rule->rule_, rule.get()); | 
 |     } | 
 |   } | 
 |   for (PerMethod& per_method : per_methods_) { | 
 |     per_method.trie.Validate(); | 
 |   } | 
 | } | 
 |  | 
 | Router::FindRoute Router::FindRouteByIndex(std::string_view url, | 
 |                                            size_t index) const { | 
 |   FindRoute route; | 
 |   if (index >= per_methods_.size()) { | 
 |     return route; | 
 |   } | 
 |   const PerMethod& per_method = per_methods_[index]; | 
 |   std::pair<unsigned, RoutingParams> found = per_method.trie.Find(url); | 
 |   if (found.first >= per_method.rules.size()) { | 
 |     throw std::runtime_error("Trie internal structure corrupted!"); | 
 |   } | 
 |   // Found a 404 route, switch that in | 
 |   if (found.first != 0U) { | 
 |     route.rule = per_method.rules[found.first]; | 
 |     route.params = std::move(found.second); | 
 |   } | 
 |   return route; | 
 | } | 
 |  | 
 | Router::FindRouteResponse Router::FindRouteByRequest( | 
 |     const RedfishRequest& req) const { | 
 |   FindRouteResponse find_route; | 
 |  | 
 |   std::optional<HttpVerb> verb = HttpVerbFromBoost(req.Method()); | 
 |   if (!verb) { | 
 |     return find_route; | 
 |   } | 
 |   size_t req_method_index = static_cast<size_t>(*verb); | 
 |   // Check to see if this url exists at any verb | 
 |   for (size_t per_method_index = 0; per_method_index <= kMaxVerbIndex; | 
 |        per_method_index++) { | 
 |     // Make sure it's safe to deference the array at that index | 
 |     static_assert(kMaxVerbIndex < std::tuple_size_v<decltype(per_methods_)>); | 
 |     FindRoute route = FindRouteByIndex(req.Target(), per_method_index); | 
 |     if (route.rule == nullptr) { | 
 |       continue; | 
 |     } | 
 |     if (per_method_index == req_method_index) { | 
 |       find_route.route = route; | 
 |     } | 
 |   } | 
 |   return find_route; | 
 | } | 
 |  | 
 | void Router::Handle(const RedfishRequest& req, RedfishResponse& resp) const { | 
 |   DLOG(INFO) << "Router Handle: " << req.Target(); | 
 |   std::optional<HttpVerb> verb = HttpVerbFromBoost(req.Method()); | 
 |   if (!verb || static_cast<size_t>(*verb) >= per_methods_.size()) { | 
 |     resp.SetToNotFound(absl::StrCat("No route found for url: ", req.Target(), | 
 |                                     " and method: ", req.Method())); | 
 |     resp.End(); | 
 |     return; | 
 |   } | 
 |  | 
 |   FindRouteResponse found_route = FindRouteByRequest(req); | 
 |  | 
 |   if (found_route.route.rule == nullptr) { | 
 |     resp.SetToNotFound(absl::StrCat("No route found for url: ", req.Target(), | 
 |                                     " and method: ", req.Method())); | 
 |     resp.End(); | 
 |     return; | 
 |   } | 
 |  | 
 |   BaseRule& rule = *found_route.route.rule; | 
 |   RoutingParams params = std::move(found_route.route.params); | 
 |   milotic_tlbmc::Tracer::GetInstance().AddRepeatedDatapoint( | 
 |       "Tlbmc-Redfish-Routing-End", absl::Now()); | 
 |   if (!RedfishPreprocess(req, resp)) { | 
 |     return; | 
 |   } | 
 |   rule.Handle(req, resp, params); | 
 |  | 
 |   // Handle Query Parameters | 
 |   RedfishPostprocess(smart_router_, std::ref(*this), req, resp); | 
 |   resp.End(); | 
 | } | 
 |  | 
 | std::vector<std::string_view> Router::GetRegisteredRoutes() const { | 
 |   std::vector<std::string_view> routes; | 
 |   for (const std::unique_ptr<BaseRule>& rule : all_rules_) { | 
 |     if (rule) { | 
 |       routes.push_back(rule->rule_); | 
 |     } | 
 |   } | 
 |   return routes; | 
 | } | 
 |  | 
 | }  // namespace crow | 
 | }  // namespace milotic_tlbmc |