xref: /openbmc/bmcweb/http/routing/trie.hpp (revision c1a75ebc267a78853fb26a3da8c6b3388e6ee07c)
15b607eaeSEd Tanous // SPDX-License-Identifier: Apache-2.0
25b607eaeSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors
35b607eaeSEd Tanous 
45b607eaeSEd Tanous #pragma once
55b607eaeSEd Tanous 
65b607eaeSEd Tanous #include "logging.hpp"
75b607eaeSEd Tanous 
85b607eaeSEd Tanous #include <boost/container/flat_map.hpp>
95b607eaeSEd Tanous #include <boost/container/small_vector.hpp>
105b607eaeSEd Tanous 
115b607eaeSEd Tanous #include <cstddef>
125b607eaeSEd Tanous #include <format>
135b607eaeSEd Tanous #include <functional>
145b607eaeSEd Tanous #include <stdexcept>
155b607eaeSEd Tanous #include <string>
165b607eaeSEd Tanous #include <string_view>
175b607eaeSEd Tanous #include <utility>
185b607eaeSEd Tanous #include <vector>
195b607eaeSEd Tanous 
205b607eaeSEd Tanous namespace crow
215b607eaeSEd Tanous {
225b607eaeSEd Tanous 
235b607eaeSEd Tanous struct Node
245b607eaeSEd Tanous {
255b607eaeSEd Tanous     unsigned ruleIndex = 0U;
265b607eaeSEd Tanous 
275b607eaeSEd Tanous     size_t stringParamChild = 0U;
285b607eaeSEd Tanous     size_t pathParamChild = 0U;
295b607eaeSEd Tanous 
305b607eaeSEd Tanous     using ChildMap = boost::container::flat_map<
315b607eaeSEd Tanous         std::string, unsigned, std::less<>,
3281f915bcSRohit PAI         boost::container::small_vector<std::pair<std::string, unsigned>, 1>>;
335b607eaeSEd Tanous     ChildMap children;
345b607eaeSEd Tanous 
isSimpleNodecrow::Node355b607eaeSEd Tanous     bool isSimpleNode() const
365b607eaeSEd Tanous     {
3781f915bcSRohit PAI         return ruleIndex == 0 && stringParamChild == 0 && pathParamChild == 0;
385b607eaeSEd Tanous     }
395b607eaeSEd Tanous };
4081f915bcSRohit PAI template <typename ContainedType>
4181f915bcSRohit PAI class Trie
4281f915bcSRohit PAI {
4381f915bcSRohit PAI   public:
Trie()445b607eaeSEd Tanous     Trie() : nodes(1) {}
455b607eaeSEd Tanous 
465b607eaeSEd Tanous   private:
optimizeNode(ContainedType & node)4781f915bcSRohit PAI     void optimizeNode(ContainedType& node)
485b607eaeSEd Tanous     {
495b607eaeSEd Tanous         if (node.stringParamChild != 0U)
505b607eaeSEd Tanous         {
515b607eaeSEd Tanous             optimizeNode(nodes[node.stringParamChild]);
525b607eaeSEd Tanous         }
535b607eaeSEd Tanous         if (node.pathParamChild != 0U)
545b607eaeSEd Tanous         {
555b607eaeSEd Tanous             optimizeNode(nodes[node.pathParamChild]);
565b607eaeSEd Tanous         }
575b607eaeSEd Tanous 
585b607eaeSEd Tanous         if (node.children.empty())
595b607eaeSEd Tanous         {
605b607eaeSEd Tanous             return;
615b607eaeSEd Tanous         }
625b607eaeSEd Tanous         while (true)
635b607eaeSEd Tanous         {
645b607eaeSEd Tanous             bool didMerge = false;
6581f915bcSRohit PAI             typename ContainedType::ChildMap merged;
6681f915bcSRohit PAI             for (const typename ContainedType::ChildMap::value_type& kv :
6781f915bcSRohit PAI                  node.children)
685b607eaeSEd Tanous             {
6981f915bcSRohit PAI                 ContainedType& child = nodes[kv.second];
705b607eaeSEd Tanous                 if (child.isSimpleNode())
715b607eaeSEd Tanous                 {
7281f915bcSRohit PAI                     for (const typename ContainedType::ChildMap::value_type&
7381f915bcSRohit PAI                              childKv : child.children)
745b607eaeSEd Tanous                     {
755b607eaeSEd Tanous                         merged[kv.first + childKv.first] = childKv.second;
765b607eaeSEd Tanous                         didMerge = true;
775b607eaeSEd Tanous                     }
785b607eaeSEd Tanous                 }
795b607eaeSEd Tanous                 else
805b607eaeSEd Tanous                 {
815b607eaeSEd Tanous                     merged[kv.first] = kv.second;
825b607eaeSEd Tanous                 }
835b607eaeSEd Tanous             }
845b607eaeSEd Tanous             node.children = std::move(merged);
855b607eaeSEd Tanous             if (!didMerge)
865b607eaeSEd Tanous             {
875b607eaeSEd Tanous                 break;
885b607eaeSEd Tanous             }
895b607eaeSEd Tanous         }
905b607eaeSEd Tanous 
9181f915bcSRohit PAI         for (const typename ContainedType::ChildMap::value_type& kv :
9281f915bcSRohit PAI              node.children)
935b607eaeSEd Tanous         {
945b607eaeSEd Tanous             optimizeNode(nodes[kv.second]);
955b607eaeSEd Tanous         }
965b607eaeSEd Tanous     }
975b607eaeSEd Tanous 
optimize()985b607eaeSEd Tanous     void optimize()
995b607eaeSEd Tanous     {
1005b607eaeSEd Tanous         optimizeNode(head());
1015b607eaeSEd Tanous     }
1025b607eaeSEd Tanous 
1035b607eaeSEd Tanous   public:
validate()1045b607eaeSEd Tanous     void validate()
1055b607eaeSEd Tanous     {
1065b607eaeSEd Tanous         optimize();
1075b607eaeSEd Tanous     }
1085b607eaeSEd Tanous 
findRouteIndexesHelper(std::string_view reqUrl,std::vector<unsigned> & routeIndexes,const ContainedType & node) const1095b607eaeSEd Tanous     void findRouteIndexesHelper(std::string_view reqUrl,
1105b607eaeSEd Tanous                                 std::vector<unsigned>& routeIndexes,
11181f915bcSRohit PAI                                 const ContainedType& node) const
1125b607eaeSEd Tanous     {
11381f915bcSRohit PAI         for (const typename ContainedType::ChildMap::value_type& kv :
11481f915bcSRohit PAI              node.children)
1155b607eaeSEd Tanous         {
1165b607eaeSEd Tanous             const std::string& fragment = kv.first;
11781f915bcSRohit PAI             const ContainedType& child = nodes[kv.second];
1185b607eaeSEd Tanous             if (reqUrl.empty())
1195b607eaeSEd Tanous             {
1205b607eaeSEd Tanous                 if (child.ruleIndex != 0 && fragment != "/")
1215b607eaeSEd Tanous                 {
1225b607eaeSEd Tanous                     routeIndexes.push_back(child.ruleIndex);
1235b607eaeSEd Tanous                 }
1245b607eaeSEd Tanous                 findRouteIndexesHelper(reqUrl, routeIndexes, child);
1255b607eaeSEd Tanous             }
1265b607eaeSEd Tanous             else
1275b607eaeSEd Tanous             {
1285b607eaeSEd Tanous                 if (reqUrl.starts_with(fragment))
1295b607eaeSEd Tanous                 {
1305b607eaeSEd Tanous                     findRouteIndexesHelper(reqUrl.substr(fragment.size()),
1315b607eaeSEd Tanous                                            routeIndexes, child);
1325b607eaeSEd Tanous                 }
1335b607eaeSEd Tanous             }
1345b607eaeSEd Tanous         }
1355b607eaeSEd Tanous     }
1365b607eaeSEd Tanous 
findRouteIndexes(const std::string & reqUrl,std::vector<unsigned> & routeIndexes) const1375b607eaeSEd Tanous     void findRouteIndexes(const std::string& reqUrl,
1385b607eaeSEd Tanous                           std::vector<unsigned>& routeIndexes) const
1395b607eaeSEd Tanous     {
1405b607eaeSEd Tanous         findRouteIndexesHelper(reqUrl, routeIndexes, head());
1415b607eaeSEd Tanous     }
1425b607eaeSEd Tanous 
1435b607eaeSEd Tanous     struct FindResult
1445b607eaeSEd Tanous     {
1455b607eaeSEd Tanous         unsigned ruleIndex = 0;
1465b607eaeSEd Tanous         std::vector<std::string> params;
1475b607eaeSEd Tanous     };
1485b607eaeSEd Tanous 
1495b607eaeSEd Tanous   private:
findHelper(const std::string_view reqUrl,const ContainedType & node,std::vector<std::string> & params) const15081f915bcSRohit PAI     FindResult findHelper(const std::string_view reqUrl,
15181f915bcSRohit PAI                           const ContainedType& node,
1525b607eaeSEd Tanous                           std::vector<std::string>& params) const
1535b607eaeSEd Tanous     {
1545b607eaeSEd Tanous         if (reqUrl.empty())
1555b607eaeSEd Tanous         {
1565b607eaeSEd Tanous             return {node.ruleIndex, params};
1575b607eaeSEd Tanous         }
1585b607eaeSEd Tanous 
1595b607eaeSEd Tanous         if (node.stringParamChild != 0U)
1605b607eaeSEd Tanous         {
1615b607eaeSEd Tanous             size_t epos = 0;
1625b607eaeSEd Tanous             for (; epos < reqUrl.size(); epos++)
1635b607eaeSEd Tanous             {
1645b607eaeSEd Tanous                 if (reqUrl[epos] == '/')
1655b607eaeSEd Tanous                 {
1665b607eaeSEd Tanous                     break;
1675b607eaeSEd Tanous                 }
1685b607eaeSEd Tanous             }
1695b607eaeSEd Tanous 
1705b607eaeSEd Tanous             if (epos != 0)
1715b607eaeSEd Tanous             {
1725b607eaeSEd Tanous                 params.emplace_back(reqUrl.substr(0, epos));
1735b607eaeSEd Tanous                 FindResult ret = findHelper(
1745b607eaeSEd Tanous                     reqUrl.substr(epos), nodes[node.stringParamChild], params);
1755b607eaeSEd Tanous                 if (ret.ruleIndex != 0U)
1765b607eaeSEd Tanous                 {
1775b607eaeSEd Tanous                     return {ret.ruleIndex, std::move(ret.params)};
1785b607eaeSEd Tanous                 }
1795b607eaeSEd Tanous                 params.pop_back();
1805b607eaeSEd Tanous             }
1815b607eaeSEd Tanous         }
1825b607eaeSEd Tanous 
1835b607eaeSEd Tanous         if (node.pathParamChild != 0U)
1845b607eaeSEd Tanous         {
1855b607eaeSEd Tanous             params.emplace_back(reqUrl);
1865b607eaeSEd Tanous             FindResult ret = findHelper("", nodes[node.pathParamChild], params);
1875b607eaeSEd Tanous             if (ret.ruleIndex != 0U)
1885b607eaeSEd Tanous             {
1895b607eaeSEd Tanous                 return {ret.ruleIndex, std::move(ret.params)};
1905b607eaeSEd Tanous             }
1915b607eaeSEd Tanous             params.pop_back();
1925b607eaeSEd Tanous         }
1935b607eaeSEd Tanous 
19481f915bcSRohit PAI         for (const typename ContainedType::ChildMap::value_type& kv :
19581f915bcSRohit PAI              node.children)
1965b607eaeSEd Tanous         {
1975b607eaeSEd Tanous             const std::string& fragment = kv.first;
19881f915bcSRohit PAI             const ContainedType& child = nodes[kv.second];
1995b607eaeSEd Tanous 
2005b607eaeSEd Tanous             if (reqUrl.starts_with(fragment))
2015b607eaeSEd Tanous             {
2025b607eaeSEd Tanous                 FindResult ret =
2035b607eaeSEd Tanous                     findHelper(reqUrl.substr(fragment.size()), child, params);
2045b607eaeSEd Tanous                 if (ret.ruleIndex != 0U)
2055b607eaeSEd Tanous                 {
2065b607eaeSEd Tanous                     return {ret.ruleIndex, std::move(ret.params)};
2075b607eaeSEd Tanous                 }
2085b607eaeSEd Tanous             }
2095b607eaeSEd Tanous         }
2105b607eaeSEd Tanous 
2115b607eaeSEd Tanous         return {0U, std::vector<std::string>()};
2125b607eaeSEd Tanous     }
2135b607eaeSEd Tanous 
2145b607eaeSEd Tanous   public:
find(const std::string_view reqUrl) const2155b607eaeSEd Tanous     FindResult find(const std::string_view reqUrl) const
2165b607eaeSEd Tanous     {
2175b607eaeSEd Tanous         std::vector<std::string> start;
2185b607eaeSEd Tanous         return findHelper(reqUrl, head(), start);
2195b607eaeSEd Tanous     }
2205b607eaeSEd Tanous 
add(std::string_view urlIn,unsigned ruleIndex)2215b607eaeSEd Tanous     void add(std::string_view urlIn, unsigned ruleIndex)
2225b607eaeSEd Tanous     {
2235b607eaeSEd Tanous         size_t idx = 0;
2245b607eaeSEd Tanous 
2255b607eaeSEd Tanous         std::string_view url = urlIn;
2265b607eaeSEd Tanous 
2275b607eaeSEd Tanous         while (!url.empty())
2285b607eaeSEd Tanous         {
2295b607eaeSEd Tanous             char c = url[0];
2305b607eaeSEd Tanous             if (c == '<')
2315b607eaeSEd Tanous             {
2325b607eaeSEd Tanous                 bool found = false;
2335b607eaeSEd Tanous                 for (const std::string_view str1 :
2345b607eaeSEd Tanous                      {"<str>", "<string>", "<path>"})
2355b607eaeSEd Tanous                 {
2365b607eaeSEd Tanous                     if (!url.starts_with(str1))
2375b607eaeSEd Tanous                     {
2385b607eaeSEd Tanous                         continue;
2395b607eaeSEd Tanous                     }
2405b607eaeSEd Tanous                     found = true;
24181f915bcSRohit PAI                     ContainedType& node = nodes[idx];
2425b607eaeSEd Tanous                     size_t* param = &node.stringParamChild;
2435b607eaeSEd Tanous                     if (str1 == "<path>")
2445b607eaeSEd Tanous                     {
2455b607eaeSEd Tanous                         param = &node.pathParamChild;
2465b607eaeSEd Tanous                     }
2475b607eaeSEd Tanous                     if (*param == 0U)
2485b607eaeSEd Tanous                     {
2495b607eaeSEd Tanous                         *param = newNode();
2505b607eaeSEd Tanous                     }
2515b607eaeSEd Tanous                     idx = *param;
2525b607eaeSEd Tanous 
2535b607eaeSEd Tanous                     url.remove_prefix(str1.size());
2545b607eaeSEd Tanous                     break;
2555b607eaeSEd Tanous                 }
2565b607eaeSEd Tanous                 if (found)
2575b607eaeSEd Tanous                 {
2585b607eaeSEd Tanous                     continue;
2595b607eaeSEd Tanous                 }
2605b607eaeSEd Tanous 
2615b607eaeSEd Tanous                 BMCWEB_LOG_CRITICAL("Can't find tag for {}", urlIn);
2625b607eaeSEd Tanous                 return;
2635b607eaeSEd Tanous             }
2645b607eaeSEd Tanous             std::string piece(&c, 1);
2655b607eaeSEd Tanous             if (!nodes[idx].children.contains(piece))
2665b607eaeSEd Tanous             {
2675b607eaeSEd Tanous                 unsigned newNodeIdx = newNode();
2685b607eaeSEd Tanous                 nodes[idx].children.emplace(piece, newNodeIdx);
2695b607eaeSEd Tanous             }
2705b607eaeSEd Tanous             idx = nodes[idx].children[piece];
2715b607eaeSEd Tanous             url.remove_prefix(1);
2725b607eaeSEd Tanous         }
27381f915bcSRohit PAI         ContainedType& node = nodes[idx];
2745b607eaeSEd Tanous         if (node.ruleIndex != 0U)
2755b607eaeSEd Tanous         {
2765b607eaeSEd Tanous             BMCWEB_LOG_CRITICAL("handler already exists for \"{}\"", urlIn);
2775b607eaeSEd Tanous             throw std::runtime_error(
2785b607eaeSEd Tanous                 std::format("handler already exists for \"{}\"", urlIn));
2795b607eaeSEd Tanous         }
2805b607eaeSEd Tanous         node.ruleIndex = ruleIndex;
2815b607eaeSEd Tanous     }
2825b607eaeSEd Tanous 
2835b607eaeSEd Tanous   private:
debugNodePrint(ContainedType & n,size_t level)28481f915bcSRohit PAI     void debugNodePrint(ContainedType& n, size_t level)
2855b607eaeSEd Tanous     {
2865b607eaeSEd Tanous         std::string spaces(level, ' ');
2875b607eaeSEd Tanous         if (n.stringParamChild != 0U)
2885b607eaeSEd Tanous         {
2895b607eaeSEd Tanous             BMCWEB_LOG_DEBUG("{}<str>", spaces);
2905b607eaeSEd Tanous             debugNodePrint(nodes[n.stringParamChild], level + 5);
2915b607eaeSEd Tanous         }
2925b607eaeSEd Tanous         if (n.pathParamChild != 0U)
2935b607eaeSEd Tanous         {
2945b607eaeSEd Tanous             BMCWEB_LOG_DEBUG("{} <path>", spaces);
2955b607eaeSEd Tanous             debugNodePrint(nodes[n.pathParamChild], level + 6);
2965b607eaeSEd Tanous         }
29781f915bcSRohit PAI         for (const typename ContainedType::ChildMap::value_type& kv :
29881f915bcSRohit PAI              n.children)
2995b607eaeSEd Tanous         {
3005b607eaeSEd Tanous             BMCWEB_LOG_DEBUG("{}{}", spaces, kv.first);
3015b607eaeSEd Tanous             debugNodePrint(nodes[kv.second], level + kv.first.size());
3025b607eaeSEd Tanous         }
3035b607eaeSEd Tanous     }
3045b607eaeSEd Tanous 
3055b607eaeSEd Tanous   public:
debugPrint()3065b607eaeSEd Tanous     void debugPrint()
3075b607eaeSEd Tanous     {
3085b607eaeSEd Tanous         debugNodePrint(head(), 0U);
3095b607eaeSEd Tanous     }
3105b607eaeSEd Tanous 
311*c1a75ebcSrohitpai   protected:
head() const31281f915bcSRohit PAI     const ContainedType& head() const
3135b607eaeSEd Tanous     {
3145b607eaeSEd Tanous         return nodes.front();
3155b607eaeSEd Tanous     }
3165b607eaeSEd Tanous 
head()31781f915bcSRohit PAI     ContainedType& head()
3185b607eaeSEd Tanous     {
3195b607eaeSEd Tanous         return nodes.front();
3205b607eaeSEd Tanous     }
3215b607eaeSEd Tanous 
newNode()3225b607eaeSEd Tanous     unsigned newNode()
3235b607eaeSEd Tanous     {
3245b607eaeSEd Tanous         nodes.resize(nodes.size() + 1);
3255b607eaeSEd Tanous         return static_cast<unsigned>(nodes.size() - 1);
3265b607eaeSEd Tanous     }
3275b607eaeSEd Tanous 
32881f915bcSRohit PAI     std::vector<ContainedType> nodes{};
3295b607eaeSEd Tanous };
3305b607eaeSEd Tanous } // namespace crow
331