blob: 30d97cbbc0d16449224dc2d8fe11f4d8ccd26821 [file] [log] [blame]
#ifndef THIRD_PARTY_MILOTIC_EXTERNAL_CC_TLBMC_REDFISH_ROUTING_H_
#define THIRD_PARTY_MILOTIC_EXTERNAL_CC_TLBMC_REDFISH_ROUTING_H_
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <limits>
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#include <vector>
#include "absl/log/log.h"
#include "boost/container/flat_map.hpp" // NOLINT
#include "tlbmc/redfish/black_magic.h"
#include "tlbmc/redfish/common.h"
#include "tlbmc/redfish/request.h"
#include "tlbmc/redfish/response.h"
#include "tlbmc/redfish/verb.h"
#include "router_interface.h"
namespace milotic_tlbmc {
namespace crow {
class BaseRule {
public:
explicit BaseRule(std::string_view thisRule) : rule_(thisRule) {}
virtual ~BaseRule() = default;
BaseRule(const BaseRule&) = delete;
BaseRule(BaseRule&&) = delete;
BaseRule& operator=(const BaseRule&) = delete;
BaseRule& operator=(const BaseRule&&) = delete;
virtual void Validate() = 0;
virtual void Handle(const milotic_tlbmc::RedfishRequest& request,
milotic_tlbmc::RedfishResponse& response,
const RoutingParams& params) = 0;
size_t methods_bit_field_{
1 << static_cast<size_t>(milotic_tlbmc::HttpVerb::kGet)};
static_assert(std::numeric_limits<decltype(methods_bit_field_)>::digits >
milotic_tlbmc::kMethodNotAllowedIndex,
"Not enough bits to store bitfield");
std::string rule_;
friend class Router;
template <typename T>
friend struct RuleParameterTraits;
};
template <typename T>
struct RuleParameterTraits {
using self_t = T;
self_t& methods(boost::beast::http::verb method) {
self_t* self = static_cast<self_t*>(this);
std::optional<milotic_tlbmc::HttpVerb> verb =
milotic_tlbmc::HttpVerbFromBoost(method);
if (verb) {
self->methods_bit_field_ = 1U << static_cast<size_t>(*verb);
}
return *self;
}
};
namespace detail {
namespace routing_handler_call_helper {
template <typename T, int Pos>
struct CallPair {
using type = T;
static const int pos = Pos;
};
template <typename H1>
struct CallParams {
H1& handler;
const RoutingParams& params;
const milotic_tlbmc::RedfishRequest& req;
milotic_tlbmc::RedfishResponse& resp;
};
template <typename F, int NInt, int NUint, int NDouble, int NString,
typename S1, typename S2>
struct Call {};
template <typename F, int NInt, int NUint, int NDouble, int NString,
typename... Args1, typename... Args2>
struct Call<F, NInt, NUint, NDouble, NString,
black_magic::S<std::string, Args1...>, black_magic::S<Args2...>> {
void operator()(F cparams) {
using pushed = typename black_magic::S<Args2...>::template push_back<
CallPair<std::string, NString>>;
Call<F, NInt, NUint, NDouble, NString + 1, black_magic::S<Args1...>,
pushed>()(cparams);
}
};
template <typename F, int NInt, int NUint, int NDouble, int NString,
typename... Args1>
struct Call<F, NInt, NUint, NDouble, NString, black_magic::S<>,
black_magic::S<Args1...>> {
void operator()(F cparams) {
cparams.handler(
cparams.req, cparams.resp,
cparams.params.template Get<typename Args1::type>(Args1::pos)...);
}
};
} // namespace routing_handler_call_helper
} // namespace detail
template <typename... Args>
class TaggedRule : public BaseRule,
public RuleParameterTraits<TaggedRule<Args...>> {
public:
using self_t = TaggedRule<Args...>;
explicit TaggedRule(std::string_view ruleIn) : BaseRule(ruleIn) {}
void Validate() override {
if (!handler_) {
throw std::runtime_error("no handler for url " + rule_);
}
}
template <typename Func>
typename std::enable_if<
!black_magic::CallHelper<Func, black_magic::S<Args...>>::value &&
!black_magic::CallHelper<
Func,
black_magic::S<milotic_tlbmc::RedfishRequest, Args...>>::value,
void>::type
operator()(Func&& f) {
static_assert(
black_magic::CallHelper<Func, black_magic::S<Args...>>::value ||
black_magic::CallHelper<
Func, black_magic::S<milotic_tlbmc::RedfishRequest,
Args...>>::value ||
black_magic::CallHelper<
Func, black_magic::S<milotic_tlbmc::RedfishRequest,
milotic_tlbmc::RedfishResponse&,
Args...>>::value,
"Handler type is mismatched with URL parameters");
static_assert(
std::is_same<void,
decltype(f(std::declval<milotic_tlbmc::RedfishRequest>(),
std::declval<milotic_tlbmc::RedfishResponse&>(),
std::declval<Args>()...))>::value,
"Handler function with response argument should have void "
"return "
"type");
handler_ = std::forward<Func>(f);
}
template <typename Func>
void operator()(std::string_view name, Func&& f) {
(*this).template operator()<Func>(std::forward(f));
}
template <typename Func>
void ReplaceHandler(Func&& f) {
this->operator()(f);
}
template <typename Func>
void AppendHandler(Func&& f) {
this->operator()(
[f = std::forward<Func>(f), previous_func = this->handler_](
const milotic_tlbmc::RedfishRequest & req,
milotic_tlbmc::RedfishResponse & resp, Args... args) {
previous_func(req, resp, args...);
f(req, resp, args...);
});
}
void Handle(const milotic_tlbmc::RedfishRequest& req,
milotic_tlbmc::RedfishResponse& resp,
const RoutingParams& params) override {
detail::routing_handler_call_helper::Call<
detail::routing_handler_call_helper::CallParams<decltype(handler_)>, 0,
0, 0, 0, black_magic::S<Args...>, black_magic::S<>>()(
detail::routing_handler_call_helper::CallParams<decltype(handler_)>{
handler_, params, req, resp});
}
private:
std::function<void(const milotic_tlbmc::RedfishRequest&,
milotic_tlbmc::RedfishResponse& resp, Args...)>
handler_;
};
// Trie is thread-compatible.
// A trie is used to store the rules and efficiently find the rule for a given
// URL and a method.
class Trie {
public:
struct Node {
unsigned rule_index{};
std::array<size_t, static_cast<size_t>(ParamType::kMax)> param_children{};
using ChildMap = boost::container::flat_map<
std::string, unsigned, std::less<>,
std::vector<std::pair<std::string, unsigned>>>;
ChildMap children;
bool IsSimpleNode() const {
return rule_index == 0 &&
std::all_of(std::begin(param_children), std::end(param_children),
[](size_t x) { return x == 0U; });
}
};
Trie() : nodes_(1) {}
// Builds the trie and optimizes it.
void Validate() { Optimize(); }
std::pair<unsigned, RoutingParams> Find(
std::string_view url, const Node* node = nullptr, size_t pos = 0,
RoutingParams* params = nullptr) const;
void Add(const std::string& url, unsigned index);
private:
void OptimizeNode(Node* node);
// Optimizes the trie by merging nodes that do not have rules.
void Optimize() { OptimizeNode(&nodes_.front()); }
const Node* Head() const { return &nodes_.front(); }
unsigned NewNode() {
nodes_.resize(nodes_.size() + 1);
return static_cast<unsigned>(nodes_.size() - 1);
}
std::vector<Node> nodes_;
};
// Router is thread-compatible.
// Router provides a way to register rules and find the rule for a given URL and
// a method.
// After creation, `Handle` is safe to call from multiple threads
// simultaneously.
class Router {
public:
struct FindRoute {
BaseRule* rule = nullptr;
RoutingParams params;
};
struct FindRouteResponse {
FindRoute route;
};
Router() = default;
// Creates a new rule with the given tag and rule string.
// The rule string must end with '/'.
// Returns a reference to the created rule.
template <uint64_t N>
typename black_magic::Arguments<N>::type::template rebind<TaggedRule>&
NewRuleTagged(std::string_view rule) {
using RuleT =
typename black_magic::Arguments<N>::type::template rebind<TaggedRule>;
if (!rule.empty() && rule.back() != '/') {
throw std::runtime_error("rule must end with '/'");
}
std::unique_ptr<RuleT> rule_object = std::make_unique<RuleT>(rule);
RuleT* ptr = rule_object.get();
all_rules_.emplace_back(std::move(rule_object));
return *ptr;
}
template <uint64_t Tag>
void RemoveRuleTagged(std::string_view url, boost::beast::http::verb method) {
auto it = FindRule<Tag>(url, method);
// Incorrect plugin configuration should kill the program.
if (it == all_rules_.end()) {
LOG(FATAL) << "Cannot Remove Rule: Rule not found: " << url << " "
<< method;
}
all_rules_.erase(it);
}
template <uint64_t Tag, typename Func>
void ReplaceHandler(std::string_view url, boost::beast::http::verb method,
Func&& func) {
auto iter = FindRule<Tag>(url, method);
if (iter == all_rules_.end()) {
LOG(FATAL) << "Cannot Replace Handler: Rule not found: " << url << " "
<< method;
}
using RuleT =
black_magic::Arguments<Tag>::type::template rebind<TaggedRule>;
RuleT* rule = dynamic_cast<RuleT*>(iter->get());
rule->ReplaceHandler(func);
}
template <uint64_t Tag, typename Func>
void AppendHandler(std::string_view url, boost::beast::http::verb method,
Func&& func) {
auto iter = FindRule<Tag>(url, method);
if (iter == all_rules_.end()) {
LOG(FATAL) << "Cannot append Handler: Rule not found: " << url << " "
<< method;
}
using RuleT =
typename black_magic::Arguments<Tag>::type::template rebind<TaggedRule>;
RuleT* rule = dynamic_cast<RuleT*>(iter->get());
rule->AppendHandler(func);
}
// Optimizes the trie so it can be used for fast lookup.
void Validate();
std::vector<std::string_view> GetRegisteredRoutes() const;
// Handles the request by finding the rule for the request and calling the
// handler inside the rule.
void Handle(const milotic_tlbmc::RedfishRequest& req,
milotic_tlbmc::RedfishResponse& asyncResp) const;
void SetSmartRouter(::crow::RouterInterface* smart_router) {
smart_router_ = smart_router;
}
protected:
template <uint64_t Tag>
std::vector<std::unique_ptr<BaseRule>>::iterator FindRule(
std::string_view url, boost::beast::http::verb method) {
std::optional<HttpVerb> verb = HttpVerbFromBoost(method);
if (!verb) {
return all_rules_.end();
}
for (auto iter = all_rules_.begin(); iter != all_rules_.end(); ++iter) {
if ((*iter)->rule_ == url && ((*iter)->methods_bit_field_ &
(1 << static_cast<size_t>(*verb))) > 0) {
return iter;
}
}
return all_rules_.end();
}
struct PerMethod {
std::vector<BaseRule*> rules;
Trie trie;
// rule index 0 has special meaning; preallocate it to avoid
// duplication.
PerMethod() : rules(1) {}
};
FindRoute FindRouteByIndex(std::string_view url, size_t index) const;
FindRouteResponse FindRouteByRequest(
const milotic_tlbmc::RedfishRequest& req) const;
void InternalAddRuleObject(const std::string& rule, BaseRule* rule_object);
std::array<PerMethod, milotic_tlbmc::kMethodNotAllowedIndex + 1> per_methods_;
std::vector<std::unique_ptr<BaseRule>> all_rules_;
::crow::RouterInterface* smart_router_ = nullptr;
};
} // namespace crow
} // namespace milotic_tlbmc
#endif // THIRD_PARTY_MILOTIC_EXTERNAL_CC_TLBMC_REDFISH_ROUTING_H_