Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 8 additions & 20 deletions src/core/uritemplate/include/sourcemeta/core/uritemplate_router.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <functional> // std::function
#include <memory> // std::unique_ptr
#include <span> // std::span
#include <string> // std::string
#include <string_view> // std::string_view
#include <utility> // std::pair
#include <variant> // std::variant
Expand Down Expand Up @@ -99,35 +100,19 @@ class SOURCEMETA_CORE_URITEMPLATE_EXPORT URITemplateRouter {
[[nodiscard]] auto arguments() const noexcept
-> const std::vector<std::pair<Identifier, std::vector<Argument>>> &;

/// Access the base path prefix
[[nodiscard]] auto base_path() const noexcept -> std::string_view;

private:
Node root_;
std::string_view base_path_;
std::string base_path_;
std::vector<std::pair<Identifier, std::vector<Argument>>> arguments_;
};

/// @ingroup uritemplate
/// A read-only view of a serialized URI Template router
class SOURCEMETA_CORE_URITEMPLATE_EXPORT URITemplateRouterView {
public:
/// A serialized node in the binary format
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable : 4324)
#endif
struct alignas(8) Node {
std::uint32_t string_offset;
std::uint32_t string_length;
std::uint32_t first_literal_child;
std::uint32_t literal_child_count;
std::uint32_t variable_child;
URITemplateRouter::NodeType type;
std::uint8_t padding;
URITemplateRouter::Identifier identifier;
};
#if defined(_MSC_VER)
#pragma warning(pop)
#endif

/// Save a router to a binary file
static auto save(const URITemplateRouter &router,
const std::filesystem::path &path) -> void;
Expand All @@ -153,6 +138,9 @@ class SOURCEMETA_CORE_URITEMPLATE_EXPORT URITemplateRouterView {
const URITemplateRouter::ArgumentCallback &callback) const
-> void;

/// Access the base path prefix
[[nodiscard]] auto base_path() const noexcept -> std::string_view;

private:
std::vector<std::uint8_t> data_;
};
Expand Down
10 changes: 10 additions & 0 deletions src/core/uritemplate/uritemplate_router.cc
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@ inline auto extract_segment(const char *start, const char *end)
URITemplateRouter::URITemplateRouter(const std::string_view base_path)
: base_path_{base_path} {
assert(this->base_path_.empty() || this->base_path_.front() == '/');
const auto last = this->base_path_.find_last_not_of('/');
if (last == std::string::npos) {
this->base_path_.clear();
} else {
this->base_path_.erase(last + 1);
}
}

auto URITemplateRouter::base_path() const noexcept -> std::string_view {
return this->base_path_;
}

auto URITemplateRouter::add(const std::string_view uri_template,
Expand Down
81 changes: 68 additions & 13 deletions src/core/uritemplate/uritemplate_router_view.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace sourcemeta::core {
namespace {

constexpr std::uint32_t ROUTER_MAGIC = 0x52544552; // "RTER"
constexpr std::uint32_t ROUTER_VERSION = 2;
constexpr std::uint32_t ROUTER_VERSION = 3;
constexpr std::uint32_t NO_CHILD = std::numeric_limits<std::uint32_t>::max();

// Type tags for argument value serialization
Expand All @@ -28,6 +28,8 @@ struct RouterHeader {
std::uint32_t node_count;
std::uint32_t string_table_offset;
std::uint32_t arguments_offset;
std::uint32_t base_path_offset;
std::uint32_t base_path_length;
};

struct ArgumentEntryHeader {
Expand All @@ -36,9 +38,20 @@ struct ArgumentEntryHeader {
std::uint32_t blob_length;
};

struct alignas(8) SerializedNode {
std::uint32_t string_offset;
std::uint32_t string_length;
std::uint32_t first_literal_child;
std::uint32_t literal_child_count;
std::uint32_t variable_child;
URITemplateRouter::NodeType type;
std::uint8_t padding;
URITemplateRouter::Identifier identifier;
};

// Binary search for a literal child matching the given segment
inline auto binary_search_literal_children(
const URITemplateRouterView::Node *nodes, const char *string_table,
const SerializedNode *nodes, const char *string_table,
const std::size_t string_table_size, const std::uint32_t first_child,
const std::uint32_t child_count, const char *segment,
const std::uint32_t segment_length) noexcept -> std::uint32_t {
Expand Down Expand Up @@ -82,15 +95,15 @@ inline auto binary_search_literal_children(

auto URITemplateRouterView::save(const URITemplateRouter &router,
const std::filesystem::path &path) -> void {
std::vector<Node> nodes;
std::vector<SerializedNode> nodes;
std::string string_table;
std::queue<const URITemplateRouter::Node *> queue;
std::unordered_map<const URITemplateRouter::Node *, std::uint32_t>
node_indices;

const auto &root = router.root();

Node root_serialized{};
SerializedNode root_serialized{};
root_serialized.string_offset = 0;
root_serialized.string_length = 0;
root_serialized.type = URITemplateRouter::NodeType::Root;
Expand Down Expand Up @@ -125,7 +138,7 @@ auto URITemplateRouterView::save(const URITemplateRouter &router,
const auto *node = queue.front();
queue.pop();

Node serialized{};
SerializedNode serialized{};
serialized.string_offset = static_cast<std::uint32_t>(string_table.size());
serialized.type = node->type;
serialized.string_length = static_cast<std::uint32_t>(node->value.size());
Expand Down Expand Up @@ -224,23 +237,32 @@ auto URITemplateRouterView::save(const URITemplateRouter &router,
argument_entries.push_back(entry);
}

// Append the base path to the string table
const auto base_path_string_offset =
static_cast<std::uint32_t>(string_table.size());
const auto base_path_value = router.base_path();
string_table.append(base_path_value.data(), base_path_value.size());

RouterHeader header{};
header.magic = ROUTER_MAGIC;
header.version = ROUTER_VERSION;
header.node_count = static_cast<std::uint32_t>(nodes.size());
header.string_table_offset = static_cast<std::uint32_t>(
sizeof(RouterHeader) + nodes.size() * sizeof(Node));
sizeof(RouterHeader) + nodes.size() * sizeof(SerializedNode));
header.arguments_offset = static_cast<std::uint32_t>(
header.string_table_offset + string_table.size());
header.base_path_offset = base_path_string_offset;
header.base_path_length = static_cast<std::uint32_t>(base_path_value.size());

std::ofstream file(path, std::ios::binary);
if (!file) {
throw URITemplateRouterSaveError{path, "Failed to open file for writing"};
}

file.write(reinterpret_cast<const char *>(&header), sizeof(header));
file.write(reinterpret_cast<const char *>(nodes.data()),
static_cast<std::streamsize>(nodes.size() * sizeof(Node)));
file.write(
reinterpret_cast<const char *>(nodes.data()),
static_cast<std::streamsize>(nodes.size() * sizeof(SerializedNode)));
file.write(string_table.data(),
static_cast<std::streamsize>(string_table.size()));

Expand Down Expand Up @@ -306,15 +328,15 @@ auto URITemplateRouterView::match(const std::string_view path,
}

if (header->node_count == 0 ||
header->node_count >
(this->data_.size() - sizeof(RouterHeader)) / sizeof(Node)) {
header->node_count > (this->data_.size() - sizeof(RouterHeader)) /
sizeof(SerializedNode)) {
return 0;
}

const auto *nodes =
reinterpret_cast<const Node *>(this->data_.data() + sizeof(RouterHeader));
const auto *nodes = reinterpret_cast<const SerializedNode *>(
this->data_.data() + sizeof(RouterHeader));
const auto nodes_size =
static_cast<std::size_t>(header->node_count) * sizeof(Node);
static_cast<std::size_t>(header->node_count) * sizeof(SerializedNode);
const auto expected_string_table_offset = sizeof(RouterHeader) + nodes_size;
if (header->string_table_offset < expected_string_table_offset ||
header->string_table_offset > this->data_.size()) {
Expand Down Expand Up @@ -599,4 +621,37 @@ auto URITemplateRouterView::arguments(
}
}

auto URITemplateRouterView::base_path() const noexcept -> std::string_view {
if (this->data_.size() < sizeof(RouterHeader)) {
return {};
}

const auto *header =
reinterpret_cast<const RouterHeader *>(this->data_.data());
if (header->magic != ROUTER_MAGIC || header->version != ROUTER_VERSION) {
return {};
}

if (header->base_path_length == 0) {
return {};
}

if (header->string_table_offset > this->data_.size() ||
header->arguments_offset < header->string_table_offset ||
header->arguments_offset > this->data_.size()) {
return {};
}

const auto *string_table = reinterpret_cast<const char *>(
this->data_.data() + header->string_table_offset);
const auto string_table_size =
header->arguments_offset - header->string_table_offset;
if (header->base_path_offset > string_table_size ||
header->base_path_length > string_table_size - header->base_path_offset) {
return {};
}

return {string_table + header->base_path_offset, header->base_path_length};
}

} // namespace sourcemeta::core
Loading
Loading