pw_router: Extract packet priority and forward metadata to egresses This adds a priority extractor to the router's PacketParser, and defines a metadata struct which is forwarded to egresses with optional data extracted from a packet. Change-Id: I69f856b900e73de22d1ecb86e912aa2f7a96c436 Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/48680 Reviewed-by: Wyatt Hepler <hepler@google.com>
diff --git a/pw_router/BUILD.gn b/pw_router/BUILD.gn index bcf934a..f6fc056 100644 --- a/pw_router/BUILD.gn +++ b/pw_router/BUILD.gn
@@ -53,7 +53,10 @@ pw_source_set("egress_function") { public_configs = [ ":public_include_path" ] public = [ "public/pw_router/egress_function.h" ] - public_deps = [ ":egress" ] + public_deps = [ + ":egress", + dir_pw_function, + ] } pw_doc_group("docs") {
diff --git a/pw_router/docs.rst b/pw_router/docs.rst index 76fec30..8788826 100644 --- a/pw_router/docs.rst +++ b/pw_router/docs.rst
@@ -22,6 +22,11 @@ link. Egress implementations provide a single ``SendPacket`` function, which takes the raw packet data and transmits it. +Egresses are provided with optional metadata extracted from the packet, if it +exists, to aid with transmitting decisions. For example, if packets in a project +include a priority, egresses may use it to provide quality-of-service by +dropping certain packets under heavy load. + Some common egress implementations are provided upstream in Pigweed. StaticRouter
diff --git a/pw_router/public/pw_router/egress.h b/pw_router/public/pw_router/egress.h index 09097e0..4c4972b 100644 --- a/pw_router/public/pw_router/egress.h +++ b/pw_router/public/pw_router/egress.h
@@ -13,6 +13,7 @@ // the License. #pragma once +#include <optional> #include <span> #include "pw_bytes/span.h" @@ -20,6 +21,12 @@ namespace pw::router { +// Data extracted from a packet which is forwarded to the egress. +struct PacketMetadata { + // Project-defined priority of the packet. + std::optional<uint32_t> priority; +}; + // Data egress for a router to send packets over some transport system. class Egress { public: @@ -29,7 +36,8 @@ // an error status on failure. // // TODO(frolv): Document possible return values. - virtual Status SendPacket(ConstByteSpan packet) = 0; + virtual Status SendPacket(ConstByteSpan packet, + const PacketMetadata& metadata) = 0; }; } // namespace pw::router
diff --git a/pw_router/public/pw_router/egress_function.h b/pw_router/public/pw_router/egress_function.h index e766766..2bbc69f 100644 --- a/pw_router/public/pw_router/egress_function.h +++ b/pw_router/public/pw_router/egress_function.h
@@ -15,6 +15,7 @@ #include <span> +#include "pw_function/function.h" #include "pw_router/egress.h" namespace pw::router { @@ -22,12 +23,17 @@ // Router egress that dispatches to a free function. class EgressFunction final : public Egress { public: - constexpr EgressFunction(Status (*func)(ConstByteSpan)) : func_(*func) {} + EgressFunction( + Function<Status(ConstByteSpan, const PacketMetadata&)> function) + : func_(std::move(function)) {} - Status SendPacket(ConstByteSpan packet) final { return func_(packet); } + Status SendPacket(ConstByteSpan packet, + const PacketMetadata& metadata) final { + return func_(packet, metadata); + } private: - Status (&func_)(ConstByteSpan); + Function<Status(ConstByteSpan, const PacketMetadata&)> func_; }; } // namespace pw::router
diff --git a/pw_router/public/pw_router/packet_parser.h b/pw_router/public/pw_router/packet_parser.h index c4ecc8a..4679454 100644 --- a/pw_router/public/pw_router/packet_parser.h +++ b/pw_router/public/pw_router/packet_parser.h
@@ -41,6 +41,13 @@ // Guaranteed to only be called if Parse() succeeded and while the data passed // to Parse() is valid. virtual std::optional<uint32_t> GetDestinationAddress() const = 0; + + // Extracts the project-specific priority of the last parsed packet, if one + // exists. + // + // Guaranteed to only be called if Parse() succeeded and while the data passed + // to Parse() is valid. + virtual std::optional<uint32_t> GetPriority() const { return std::nullopt; } }; } // namespace pw::router
diff --git a/pw_router/static_router.cc b/pw_router/static_router.cc index a32e74a..6f1ad54 100644 --- a/pw_router/static_router.cc +++ b/pw_router/static_router.cc
@@ -21,6 +21,7 @@ Status StaticRouter::RoutePacket(ConstByteSpan packet) { uint32_t address; + PacketMetadata metadata = {}; { // Only packet parsing is synchronized within the router; egresses must be @@ -39,6 +40,9 @@ } address = result.value(); + + // Populate the metadata with fields extracted from the packet. + metadata.priority = parser_.GetPriority(); } auto route = std::find_if(routes_.begin(), routes_.end(), [&](auto r) { @@ -49,7 +53,8 @@ return Status::NotFound(); } - if (Status status = route->egress.SendPacket(packet); !status.ok()) { + if (Status status = route->egress.SendPacket(packet, metadata); + !status.ok()) { egress_errors_.Increment(); return Status::Unavailable(); }
diff --git a/pw_router/static_router_test.cc b/pw_router/static_router_test.cc index 172cc01..50f3437 100644 --- a/pw_router/static_router_test.cc +++ b/pw_router/static_router_test.cc
@@ -25,12 +25,16 @@ static constexpr uint32_t kMagic = 0x8badf00d; constexpr BasicPacket(uint32_t addr, uint64_t data) - : magic(kMagic), address(addr), payload(data) {} + : magic(kMagic), address(addr), priority(0), payload(data) {} + + constexpr BasicPacket(uint32_t addr, uint32_t prio, uint64_t data) + : magic(kMagic), address(addr), priority(prio), payload(data) {} ConstByteSpan data() const { return std::as_bytes(std::span(this, 1)); } uint32_t magic; uint32_t address; + uint32_t priority; uint64_t payload; }; @@ -48,12 +52,19 @@ return packet_->address; } + std::optional<uint32_t> GetPriority() const final { + PW_DCHECK_NOTNULL(packet_); + return packet_->priority; + }; + private: const BasicPacket* packet_; }; -EgressFunction GoodEgress(+[](ConstByteSpan) { return OkStatus(); }); -EgressFunction BadEgress(+[](ConstByteSpan) { +EgressFunction GoodEgress(+[](ConstByteSpan, const PacketMetadata&) { + return OkStatus(); +}); +EgressFunction BadEgress(+[](ConstByteSpan, const PacketMetadata&) { return Status::ResourceExhausted(); }); @@ -67,6 +78,23 @@ Status::Unavailable()); } +TEST(StaticRouter, RoutePacket_ForwardsPacketMetadata) { + PacketMetadata metadata = {}; + EgressFunction metadata_egress( + [&metadata](ConstByteSpan, const PacketMetadata& md) { + metadata = md; + return OkStatus(); + }); + + BasicPacketParser parser; + StaticRouter::Route routes[] = {{1, metadata_egress}}; + StaticRouter router(parser, std::span(routes)); + + EXPECT_EQ(router.RoutePacket(BasicPacket(1, 71, 0xdddd).data()), OkStatus()); + ASSERT_TRUE(metadata.priority.has_value()); + EXPECT_EQ(metadata.priority.value(), 71u); +} + TEST(StaticRouter, RoutePacket_ReturnsParserError) { BasicPacketParser parser; constexpr StaticRouter::Route routes[] = {{1, GoodEgress}, {2, BadEgress}};