This commit adds a flow graph parsing API. This is a helper API intended to
help ethdev drivers implement rte_flow parsers, as common usages map to
graph traversal problem very well.

Features provided by the API:
- Flow graph, edge, and node definitions
- Graph traversal logic
- Declarative validation against common flow item types
- Per-node validation and state processing callbacks

Signed-off-by: Anatoly Burakov <[email protected]>
---
Depends-on: series-39013 ("I40E refactors")

 doc/guides/prog_guide/ethdev/flow_graph.rst | 748 ++++++++++++++++++++
 doc/guides/prog_guide/ethdev/index.rst      |   1 +
 doc/guides/rel_notes/release_26_11.rst      |   7 +
 lib/ethdev/meson.build                      |   1 +
 lib/ethdev/rte_flow_graph.h                 | 532 ++++++++++++++
 5 files changed, 1289 insertions(+)
 create mode 100644 doc/guides/prog_guide/ethdev/flow_graph.rst
 create mode 100644 lib/ethdev/rte_flow_graph.h

diff --git a/doc/guides/prog_guide/ethdev/flow_graph.rst 
b/doc/guides/prog_guide/ethdev/flow_graph.rst
new file mode 100644
index 0000000000..0b754a2a73
--- /dev/null
+++ b/doc/guides/prog_guide/ethdev/flow_graph.rst
@@ -0,0 +1,748 @@
+..  SPDX-License-Identifier: BSD-3-Clause
+    Copyright(c) 2026 Intel Corporation
+
+Flow Graph Parser
+=================
+
+Introduction
+------------
+
+The flow graph parser is a helper library for PMD drivers that implements 
``rte_flow`` pattern matching.
+It lets a driver declare the protocol sequences it supports as a directed 
graph of nodes and edges.
+It then validates and extracts fields from an ``rte_flow_item`` pattern in a 
single traversal.
+
+The library is defined in ``rte_flow_graph.h`` and is header-only.
+
+Scope and Limitations
+~~~~~~~~~~~~~~~~~~~~~
+
+Because the parser is graph-based, it is well suited for matching *protocol 
stacks*.
+These are sequences of protocol headers such as ``ETH / IPv4 / TCP``.
+Any pattern that can be expressed as "from protocol A, transitions to protocol 
B or C are allowed" fits naturally into the graph model.
+
+The library is **not** designed to cover every ``rte_flow`` item type.
+Items that do not represent a position in a protocol stack do not have a 
natural place in a protocol graph.
+This includes conntrack state, meter color, and other metadata items.
+Such items are best handled outside the graph, either before or after the 
graph parse call.
+
+Defining a Graph
+----------------
+
+A graph consists of three parts:
+
+1. An **enum** that assigns a numeric index to every node.
+2. A **node array** (``struct rte_flow_graph_node[]``) indexed by that enum.
+3. An **edge array** (``struct rte_flow_graph_edge[]``) also indexed by that 
enum, describing allowed transitions.
+
+These parts are bundled together in a ``struct rte_flow_graph``.
+
+The running example used throughout this guide models the following protocol 
graph::
+
+   START -> ETH -> [VLAN] -> (IPv4 | IPv6) -> [(TCP | UDP | SCTP)] -> END
+
+Brackets ``[...]`` denote optional items.
+Parentheses ``(...)`` denote a required choice between alternatives.
+The key ideas are:
+
+* ``ETH`` is required after ``START``.
+* After ``ETH``, an optional ``VLAN`` may appear, but the pattern must then 
see an IP layer.
+* After an IP layer, an optional transport layer may appear; it may be TCP, 
UDP, or SCTP, after which the pattern reaches ``END``.
+
+Node Enum
+~~~~~~~~~
+
+Every node needs a stable index.
+The first node **must** be at index ``RTE_FLOW_NODE_FIRST`` (which is 0).
+This is the *start node*.
+It is used only as a traversal anchor and must not carry callbacks.
+
+.. code-block:: c
+
+   enum example_node_id {
+       EXAMPLE_NODE_START = RTE_FLOW_NODE_FIRST,
+       EXAMPLE_NODE_ETH,
+       EXAMPLE_NODE_VLAN,
+       EXAMPLE_NODE_IPV4,
+       EXAMPLE_NODE_IPV6,
+       EXAMPLE_NODE_TCP,
+       EXAMPLE_NODE_UDP,
+       EXAMPLE_NODE_SCTP,
+       EXAMPLE_NODE_END,
+       /* keep last */
+       EXAMPLE_NODE_MAX,
+   };
+
+Node Definitions
+~~~~~~~~~~~~~~~~
+
+Each node maps to one ``rte_flow_item_type``.
+It can also carry a *validate* callback, a *process* callback, and a set of 
*constraints*.
+The the ``END`` node can also have callbacks to perform end-of-match 
processing.
+
+A minimal skeleton (callbacks and constraints are added in later sections):
+
+.. code-block:: c
+
+   const struct rte_flow_graph example_graph = {
+       .nodes = (struct rte_flow_graph_node[]){
+           [EXAMPLE_NODE_START] = {
+               .name = "START",
+               /* Start node: no type, no callbacks */
+           },
+           [EXAMPLE_NODE_ETH] = {
+               .name  = "ETH",
+               .type  = RTE_FLOW_ITEM_TYPE_ETH,
+           },
+           [EXAMPLE_NODE_VLAN] = {
+               .name  = "VLAN",
+               .type  = RTE_FLOW_ITEM_TYPE_VLAN,
+           },
+           [EXAMPLE_NODE_IPV4] = {
+               .name  = "IPV4",
+               .type  = RTE_FLOW_ITEM_TYPE_IPV4,
+           },
+           [EXAMPLE_NODE_IPV6] = {
+               .name  = "IPV6",
+               .type  = RTE_FLOW_ITEM_TYPE_IPV6,
+           },
+           [EXAMPLE_NODE_TCP] = {
+               .name  = "TCP",
+               .type  = RTE_FLOW_ITEM_TYPE_TCP,
+           },
+           [EXAMPLE_NODE_UDP] = {
+               .name  = "UDP",
+               .type  = RTE_FLOW_ITEM_TYPE_UDP,
+           },
+           [EXAMPLE_NODE_SCTP] = {
+               .name  = "SCTP",
+               .type  = RTE_FLOW_ITEM_TYPE_SCTP,
+           },
+           [EXAMPLE_NODE_END] = {
+               .name  = "END",
+               .type  = RTE_FLOW_ITEM_TYPE_END,
+           },
+       },
+   };
+
+Edge Definitions
+~~~~~~~~~~~~~~~~
+
+Edges express which nodes may follow the current one.
+Every edge list is terminated by the ``RTE_FLOW_NODE_EDGE_END`` sentinel.
+All non-``END`` nodes **must** have an edge list.
+The ``END`` node itself does not need one.
+
+.. code-block:: c
+
+   const struct rte_flow_graph example_graph = {
+       .nodes = (struct rte_flow_graph_node[]){
+           /* ... same nodes as above ... */
+       },
+       .edges = (struct rte_flow_graph_edge[]){
+           [EXAMPLE_NODE_START] = {
+               .next = (const size_t[]){
+                   EXAMPLE_NODE_ETH,
+                   RTE_FLOW_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_ETH] = {
+               .next = (const size_t[]){
+                   EXAMPLE_NODE_VLAN,
+                   EXAMPLE_NODE_IPV4,
+                   EXAMPLE_NODE_IPV6,
+                   RTE_FLOW_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_VLAN] = {
+               .next = (const size_t[]){
+                   EXAMPLE_NODE_IPV4,
+                   EXAMPLE_NODE_IPV6,
+                   RTE_FLOW_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_IPV4] = {
+               .next = (const size_t[]){
+                   EXAMPLE_NODE_TCP,
+                   EXAMPLE_NODE_UDP,
+                   EXAMPLE_NODE_SCTP,
+                   EXAMPLE_NODE_END,
+                   RTE_FLOW_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_IPV6] = {
+               .next = (const size_t[]){
+                   EXAMPLE_NODE_TCP,
+                   EXAMPLE_NODE_UDP,
+                   EXAMPLE_NODE_SCTP,
+                   EXAMPLE_NODE_END,
+                   RTE_FLOW_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_TCP] = {
+               .next = (const size_t[]){
+                   EXAMPLE_NODE_END,
+                   RTE_FLOW_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_UDP] = {
+               .next = (const size_t[]){
+                   EXAMPLE_NODE_END,
+                   RTE_FLOW_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_SCTP] = {
+               .next = (const size_t[]){
+                   EXAMPLE_NODE_END,
+                   RTE_FLOW_NODE_EDGE_END,
+               },
+           },
+       },
+   };
+
+Reading the edges back:
+
+* From ``START``, the parser can only reach ``ETH``, which makes ``ETH`` 
required.
+* From ``ETH``, the parser can reach ``VLAN``, ``IPV4``, or ``IPV6``, which 
makes ``VLAN`` optional.
+* From ``IPV4`` or ``IPV6``, the parser can reach ``TCP``, ``UDP``, ``SCTP``, 
or ``END``, which makes the transport layer optional.
+
+Assembling the Graph
+~~~~~~~~~~~~~~~~~~~~
+
+With nodes and edges defined inline, assembling the graph is just a matter of
+combining the two arrays into a single compound literal:
+
+.. code-block:: c
+
+   const struct rte_flow_graph example_graph = {
+       .nodes = (struct rte_flow_graph_node[]){
+           [EXAMPLE_NODE_START] = {
+               .name = "START"
+           },
+           [EXAMPLE_NODE_ETH]   = {
+               .name = "ETH",
+               .type = RTE_FLOW_ITEM_TYPE_ETH
+           },
+           /* ... remaining nodes ... */
+           [EXAMPLE_NODE_END]   = {
+               .name = "END",
+               .type = RTE_FLOW_ITEM_TYPE_END
+           },
+       },
+       .edges = (struct rte_flow_graph_edge[]){
+           [EXAMPLE_NODE_START] = {
+               .next = (const size_t[]){
+                   EXAMPLE_NODE_ETH,
+                   RTE_FLOW_NODE_EDGE_END
+               }
+           },
+           /* ... remaining edges ... */
+       },
+   };
+
+Callbacks
+---------
+
+The graph calls up to two callbacks on every visited node: *validate* and 
*process*.
+
+Both callbacks share the same return convention.
+On success they must return ``0``.
+On failure they must call ``rte_flow_error_set`` to record a descriptive error 
and return its result.
+
+Validate Callback
+~~~~~~~~~~~~~~~~~
+
+.. code-block:: c
+
+   typedef int (*rte_flow_node_validate_fn)(
+       const void *ctx,
+       const struct rte_flow_item *item,
+       struct rte_flow_error *error);
+
+This callback receives a **read-only** context pointer.
+The canonical intent is that it should check whether the item's spec, mask, 
and last values are acceptable for the driver.
+On failure it returns the result of ``rte_flow_error_set`` (see the return 
convention above).
+
+It is recommended to use this callback for **all checks that can reject a 
rule**.
+This includes unsupported mask bits, conflicting field combinations, hardware 
limitations, and other applicable criteria.
+
+Process Callback
+~~~~~~~~~~~~~~~~
+
+.. code-block:: c
+
+   typedef int (*rte_flow_node_process_fn)(
+       void *ctx,
+       const struct rte_flow_item *item,
+       struct rte_flow_error *error);
+
+This callback receives a **mutable** context pointer.
+The canonical expectation is that it should extract the fields needed for 
hardware programming.
+It should then store extracted data in the driver's context structure.
+On the rare failure path it returns the result of ``rte_flow_error_set`` (see 
the return convention above).
+
+It is recommended to use this callback for the **happy path**.
+For example, it can copy addresses, ports, and protocol IDs into the driver 
context so they can be programmed later.
+
+Defining a Context Structure
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The opaque ``ctx`` pointer passed to every callback is driver-defined.
+A typical context accumulates the parsed protocol fields:
+
+.. code-block:: c
+
+   struct example_parsed_flow {
+       /* L2 */
+       struct rte_ether_addr dst_mac;
+       bool has_vlan;
+       uint16_t vlan_tci;
+
+       /* L3 */
+       bool is_ipv6;
+       rte_be32_t ipv4_src;
+       rte_be32_t ipv4_dst;
+       uint8_t ipv6_src[16];
+       uint8_t ipv6_dst[16];
+
+       /* L4 */
+       enum rte_flow_item_type l4_proto;
+       rte_be16_t src_port;
+       rte_be16_t dst_port;
+   };
+
+These fields are meant to reflect the structure used by the driver to 
programming hardware with.
+
+Callback Example
+~~~~~~~~~~~~~~~~
+
+Below is a validate/process pair for the IPv4 node.
+The validate callback rejects unsupported mask bits.
+The process callback copies addresses into the context:
+
+.. code-block:: c
+
+   static int
+   example_validate_ipv4(const void *ctx __rte_unused,
+                         const struct rte_flow_item *item,
+                         struct rte_flow_error *error)
+   {
+       const struct rte_flow_item_ipv4 *mask = item->mask;
+
+       if (mask->hdr.version_ihl ||
+           mask->hdr.type_of_service ||
+           mask->hdr.total_length ||
+           mask->hdr.packet_id ||
+           mask->hdr.fragment_offset ||
+           mask->hdr.time_to_live ||
+           mask->hdr.next_proto_id ||
+           mask->hdr.hdr_checksum) {
+           return rte_flow_error_set(error, EINVAL,
+                   RTE_FLOW_ERROR_TYPE_ITEM, item,
+                   "Only src/dst addresses supported");
+       }
+       return 0;
+   }
+
+   static int
+   example_process_ipv4(void *ctx,
+                        const struct rte_flow_item *item,
+                        struct rte_flow_error *error __rte_unused)
+   {
+       struct example_parsed_flow *parsed = ctx;
+       const struct rte_flow_item_ipv4 *spec = item->spec;
+
+       parsed->is_ipv6 = false;
+       if (spec != NULL) {
+           parsed->ipv4_src = spec->hdr.src_addr;
+           parsed->ipv4_dst = spec->hdr.dst_addr;
+       }
+       return 0;
+   }
+
+Add the callbacks to the node definition:
+
+.. code-block:: c
+
+   [EXAMPLE_NODE_IPV4] = {
+       .name      = "IPV4",
+       .type      = RTE_FLOW_ITEM_TYPE_IPV4,
+       .validate  = example_validate_ipv4,
+       .process   = example_process_ipv4,
+   },
+
+Node Constraints
+----------------
+
+Many nodes share common requirements about which combination of ``spec``, 
``mask``, and ``last`` pointers an item must carry.
+Instead of checking these in every validate callback, they can be declared via 
the ``constraints`` field.
+The field uses ``rte_flow_graph_node_expect`` flags.
+
+Available constraint flags (may be ORed together):
+
+``RTE_FLOW_NODE_EXPECT_EMPTY``
+   The item must have ``spec == NULL``, ``mask == NULL``, and
+   ``last == NULL``.
+
+``RTE_FLOW_NODE_EXPECT_SPEC``
+   ``spec`` is required; ``mask`` and ``last`` must be NULL.
+
+``RTE_FLOW_NODE_EXPECT_MASK``
+   ``mask`` is required; ``spec`` and ``last`` must be NULL.
+
+``RTE_FLOW_NODE_EXPECT_SPEC_MASK``
+   Both ``spec`` and ``mask`` are required; ``last`` must be NULL.
+
+``RTE_FLOW_NODE_EXPECT_RANGE``
+   All three (``spec``, ``mask``, ``last``) are required.
+
+``RTE_FLOW_NODE_EXPECT_NOT_RANGE``
+   ``last`` must be NULL (``spec`` and ``mask`` are unconstrained).
+
+Multiple flags can be ORed together.
+The item is accepted if **any one** of the flagged constraints is satisfied.
+
+For example, an IPv4 node that accepts either a mask-only item or a spec+mask 
item:
+
+.. code-block:: c
+
+   [EXAMPLE_NODE_IPV4] = {
+       .name        = "IPV4",
+       .type        = RTE_FLOW_ITEM_TYPE_IPV4,
+       .validate    = example_validate_ipv4,
+       .process     = example_process_ipv4,
+       .constraints = RTE_FLOW_NODE_EXPECT_MASK |
+                      RTE_FLOW_NODE_EXPECT_SPEC_MASK,
+   },
+
+An Ethernet node that may appear empty (no spec/mask) or with spec+mask:
+
+.. code-block:: c
+
+   [EXAMPLE_NODE_ETH] = {
+       .name        = "ETH",
+       .type        = RTE_FLOW_ITEM_TYPE_ETH,
+       .validate    = example_validate_eth,
+       .process     = example_process_eth,
+       .constraints = RTE_FLOW_NODE_EXPECT_EMPTY |
+                      RTE_FLOW_NODE_EXPECT_SPEC_MASK,
+   },
+
+Constraints are checked **before** the validate callback is invoked.
+
+Ignoring Item Types
+~~~~~~~~~~~~~~~~~~~
+
+The ``ignore_nodes`` field on ``struct rte_flow_graph`` is an optional 
complement to node constraints.
+When the pattern may contain item types that are irrelevant to the driver, 
list them in ``ignore_nodes``.
+For example, metadata items like ``RTE_FLOW_ITEM_TYPE_MARK`` do not represent 
a protocol header.
+The parser skips ignored items silently without advancing the current graph 
position:
+
+.. code-block:: c
+
+   const struct rte_flow_graph example_graph = {
+       /* ... nodes and edges ... */
+       .ignore_nodes = (const enum rte_flow_item_type[]){
+           RTE_FLOW_ITEM_TYPE_MARK,
+           RTE_FLOW_ITEM_TYPE_END,
+       },
+   };
+
+``RTE_FLOW_ITEM_TYPE_VOID`` is always ignored regardless of this list.
+Omit ``ignore_nodes`` entirely when no additional item types need to be 
skipped.
+
+Calling the Parser
+------------------
+
+``rte_flow_graph_parse`` walks the pattern against the graph:
+
+.. code-block:: c
+
+   int
+   rte_flow_graph_parse(const struct rte_flow_graph *graph,
+                        const struct rte_flow_item *pattern,
+                        struct rte_flow_error *error,
+                        void *ctx);
+
+A typical call site looks like this:
+
+.. code-block:: c
+
+   struct example_parsed_flow parsed;
+   int ret;
+
+   memset(&parsed, 0, sizeof(parsed));
+
+   ret = rte_flow_graph_parse(&example_graph, pattern, error, &parsed);
+   if (ret != 0)
+       return ret;
+
+   /* 'parsed' now contains the extracted protocol fields */
+
+The function returns success or failure, with ``error`` populated.
+
+Error conditions:
+
+* **Graph is NULL** — for example, when the graph pointer itself is not 
provided.
+* **Pattern is NULL**.
+* **Unsupported transition** — when an item type has no matching edge from the 
current node.
+    This is the primary way the graph rejects unsupported protocol sequences.
+* **Constraint failure** — when the spec, mask, and last combination does not 
satisfy the node's declared constraints.
+* **Validate callback failure** — when driver-specific validation rejects the 
item.
+* **Process callback failure** — when driver-specific extraction path fails.
+
+.. warning::
+
+    Malformed graph tables (for example invalid node indices, missing 
sentinels,
+    or otherwise inconsistent driver-defined graph structures) are considered 
to be a driver implementation bug.
+    Graphs are trusted by default: driver-owned graph structures are expected 
to be valid and are not fully validated.
+
+The traversal processes items in order, skipping ignored types.
+After the last non-``END`` item, the parser looks for an ``END`` node 
reachable from the current position.
+It then visits that node and runs its callbacks, if any.
+This means drivers can attach a process callback to the ``END`` node for 
post-traversal finalization.
+
+Putting It All Together
+-----------------------
+
+The complete graph definition with callbacks and constraints:
+
+.. code-block:: c
+
+   const struct rte_flow_graph example_graph = {
+       .nodes = (struct rte_flow_graph_node[]){
+           [EXAMPLE_NODE_START] = {
+               .name = "START",
+           },
+           [EXAMPLE_NODE_ETH] = {
+               .name        = "ETH",
+               .type        = RTE_FLOW_ITEM_TYPE_ETH,
+               .validate    = example_validate_eth,
+               .process     = example_process_eth,
+               .constraints = RTE_FLOW_NODE_EXPECT_EMPTY
+                            | RTE_FLOW_NODE_EXPECT_SPEC_MASK,
+           },
+           [EXAMPLE_NODE_VLAN] = {
+               .name        = "VLAN",
+               .type        = RTE_FLOW_ITEM_TYPE_VLAN,
+               .process     = example_process_vlan,
+               .constraints = RTE_FLOW_NODE_EXPECT_SPEC_MASK,
+           },
+           [EXAMPLE_NODE_IPV4] = {
+               .name        = "IPV4",
+               .type        = RTE_FLOW_ITEM_TYPE_IPV4,
+               .validate    = example_validate_ipv4,
+               .process     = example_process_ipv4,
+               .constraints = RTE_FLOW_NODE_EXPECT_MASK
+                            | RTE_FLOW_NODE_EXPECT_SPEC_MASK,
+           },
+           [EXAMPLE_NODE_IPV6] = {
+               .name        = "IPV6",
+               .type        = RTE_FLOW_ITEM_TYPE_IPV6,
+               .validate    = example_validate_ipv6,
+               .process     = example_process_ipv6,
+               .constraints = RTE_FLOW_NODE_EXPECT_MASK
+                            | RTE_FLOW_NODE_EXPECT_SPEC_MASK,
+           },
+           [EXAMPLE_NODE_TCP] = {
+               .name        = "TCP",
+               .type        = RTE_FLOW_ITEM_TYPE_TCP,
+               .validate    = example_validate_tcp,
+               .process     = example_process_tcp,
+               .constraints = RTE_FLOW_NODE_EXPECT_MASK
+                            | RTE_FLOW_NODE_EXPECT_SPEC_MASK,
+           },
+           [EXAMPLE_NODE_UDP] = {
+               .name        = "UDP",
+               .type        = RTE_FLOW_ITEM_TYPE_UDP,
+               .validate    = example_validate_udp,
+               .process     = example_process_udp,
+               .constraints = RTE_FLOW_NODE_EXPECT_MASK
+                            | RTE_FLOW_NODE_EXPECT_SPEC_MASK,
+           },
+           [EXAMPLE_NODE_SCTP] = {
+               .name        = "SCTP",
+               .type        = RTE_FLOW_ITEM_TYPE_SCTP,
+               .process     = example_process_sctp,
+               .constraints = RTE_FLOW_NODE_EXPECT_EMPTY
+                            | RTE_FLOW_NODE_EXPECT_MASK
+                            | RTE_FLOW_NODE_EXPECT_SPEC_MASK,
+           },
+           [EXAMPLE_NODE_END] = {
+               .name = "END",
+               .type = RTE_FLOW_ITEM_TYPE_END,
+           },
+       },
+       .edges = (struct rte_flow_graph_edge[]){
+           [EXAMPLE_NODE_START] = {
+               .next = (const size_t[]){
+                   EXAMPLE_NODE_ETH,
+                   RTE_FLOW_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_ETH] = {
+               .next = (const size_t[]){
+                   EXAMPLE_NODE_VLAN,
+                   EXAMPLE_NODE_IPV4,
+                   EXAMPLE_NODE_IPV6,
+                   RTE_FLOW_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_VLAN] = {
+               .next = (const size_t[]){
+                   EXAMPLE_NODE_IPV4,
+                   EXAMPLE_NODE_IPV6,
+                   RTE_FLOW_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_IPV4] = {
+               .next = (const size_t[]){
+                   EXAMPLE_NODE_TCP,
+                   EXAMPLE_NODE_UDP,
+                   EXAMPLE_NODE_SCTP,
+                   EXAMPLE_NODE_END,
+                   RTE_FLOW_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_IPV6] = {
+               .next = (const size_t[]){
+                   EXAMPLE_NODE_TCP,
+                   EXAMPLE_NODE_UDP,
+                   EXAMPLE_NODE_SCTP,
+                   EXAMPLE_NODE_END,
+                   RTE_FLOW_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_TCP] = {
+               .next = (const size_t[]){
+                   EXAMPLE_NODE_END,
+                   RTE_FLOW_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_UDP] = {
+               .next = (const size_t[]){
+                   EXAMPLE_NODE_END,
+                   RTE_FLOW_NODE_EDGE_END,
+               },
+           },
+           [EXAMPLE_NODE_SCTP] = {
+               .next = (const size_t[]){
+                   EXAMPLE_NODE_END,
+                   RTE_FLOW_NODE_EDGE_END,
+               },
+           },
+       },
+   };
+
+
+Tunnel Graphs and Repeated Item Types
+-------------------------------------
+
+Tunneled patterns often repeat the same ``rte_flow_item_type`` in outer and 
inner headers.
+A simple representative example is a TCP-IPv4 over GTP-U pattern::
+
+   ETH -> IPV4 -> UDP -> GTPU -> IPV4 -> TCP
+
+The graph library supports this naturally.
+Multiple nodes may use the same ``type`` value, as long as they are distinct 
nodes in the graph and reached through different edges.
+
+For tunnel parsing, the recommended style is to model repeated protocol types 
as separate inner/outer nodes, for example ``OUTER_IPV4`` and ``INNER_IPV4``.
+This makes the graph intent explicit, keeps callback logic clear, and avoids 
unexpected graph paths due to loops.
+
+.. code-block:: c
+
+   enum tunnel_node_id {
+       TUNNEL_NODE_START = RTE_FLOW_NODE_FIRST,
+       TUNNEL_NODE_ETH,
+       TUNNEL_NODE_OUTER_IPV4,
+       TUNNEL_NODE_TCP,
+       TUNNEL_NODE_UDP,
+       TUNNEL_NODE_GTPU,
+       TUNNEL_NODE_INNER_IPV4,
+       TUNNEL_NODE_END,
+       TUNNEL_NODE_MAX,
+   };
+
+   const struct rte_flow_graph tunnel_graph = {
+       .nodes = (struct rte_flow_graph_node[]){
+           /* Minimal topology example: callbacks and constraints are omitted. 
*/
+           [TUNNEL_NODE_START] = { .name = "START" },
+           [TUNNEL_NODE_ETH] = {
+               .name = "ETH",
+               .type = RTE_FLOW_ITEM_TYPE_ETH,
+           },
+           [TUNNEL_NODE_OUTER_IPV4] = {
+               .name = "OUTER_IPV4",
+               .type = RTE_FLOW_ITEM_TYPE_IPV4,
+           },
+           [TUNNEL_NODE_UDP] = {
+               .name = "UDP",
+               .type = RTE_FLOW_ITEM_TYPE_UDP,
+           },
+           [TUNNEL_NODE_GTPU] = {
+               .name = "GTPU",
+               .type = RTE_FLOW_ITEM_TYPE_GTPU,
+           },
+           [TUNNEL_NODE_INNER_IPV4] = {
+               .name = "INNER_IPV4",
+               .type = RTE_FLOW_ITEM_TYPE_IPV4,
+           },
+           [TUNNEL_NODE_TCP] = {
+               .name = "TCP",
+               .type = RTE_FLOW_ITEM_TYPE_TCP,
+           },
+           [TUNNEL_NODE_END] = {
+               .name = "END",
+               .type = RTE_FLOW_ITEM_TYPE_END,
+           },
+       },
+       .edges = (struct rte_flow_graph_edge[]){
+           [TUNNEL_NODE_START] = {
+               .next = (const size_t[]){
+                   TUNNEL_NODE_ETH,
+                   RTE_FLOW_NODE_EDGE_END,
+               },
+           },
+           [TUNNEL_NODE_ETH] = {
+               .next = (const size_t[]){
+                   TUNNEL_NODE_OUTER_IPV4,
+                   RTE_FLOW_NODE_EDGE_END,
+               },
+           },
+           [TUNNEL_NODE_OUTER_IPV4] = {
+               .next = (const size_t[]){
+                   TUNNEL_NODE_UDP,
+                   RTE_FLOW_NODE_EDGE_END,
+               },
+           },
+           [TUNNEL_NODE_UDP] = {
+               .next = (const size_t[]){
+                   TUNNEL_NODE_GTPU,
+                   RTE_FLOW_NODE_EDGE_END,
+               },
+           },
+           [TUNNEL_NODE_GTPU] = {
+               .next = (const size_t[]){
+                   TUNNEL_NODE_INNER_IPV4,
+                   RTE_FLOW_NODE_EDGE_END,
+               },
+           },
+           [TUNNEL_NODE_INNER_IPV4] = {
+               .next = (const size_t[]){
+                   TUNNEL_NODE_TCP,
+                   RTE_FLOW_NODE_EDGE_END,
+               },
+           },
+           [TUNNEL_NODE_TCP] = {
+               .next = (const size_t[]){
+                   TUNNEL_NODE_END,
+                   RTE_FLOW_NODE_EDGE_END,
+               },
+           },
+       },
+   };
+
+In other words, traversal follows graph edges (node-to-node), while node 
matching is done against each candidate node's ``rte_flow_item_type``.
+That combination allows repeated protocol layers to be represented cleanly 
with separate nodes for different parsing contexts.
+
+Although arbitrary loops are possible in the graph, tunnel protocol graphs are 
usually easier to reason about when repeated item types are split into explicit 
inner/outer nodes.
+It is not recommended to create loops in the graph, as these loops will be 
unbounded.
diff --git a/doc/guides/prog_guide/ethdev/index.rst 
b/doc/guides/prog_guide/ethdev/index.rst
index 392ced0a2e..b41fd045bb 100644
--- a/doc/guides/prog_guide/ethdev/index.rst
+++ b/doc/guides/prog_guide/ethdev/index.rst
@@ -10,6 +10,7 @@ Ethernet Device Library
     ethdev
     switch_representation
     flow_offload
+    flow_graph
     traffic_metering_and_policing
     traffic_management
     qos_framework
diff --git a/doc/guides/rel_notes/release_26_11.rst 
b/doc/guides/rel_notes/release_26_11.rst
index c8cc86295d..a4235d7dac 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst
@@ -55,6 +55,13 @@ New Features
      Also, make sure to start the actual text at the margin.
      =======================================================
 
+* **Added internal ethdev flow graph parser helper API.**
+
+  Added ``rte_flow_graph`` helper definitions in ``rte_flow_graph.h``
+  for PMD drivers to build graph-based pattern parsers.
+  This internal driver API validates ``rte_flow_item`` protocol sequences
+  through node and edge traversal, with per-node callbacks and constraints.
+
 
 Removed Items
 -------------
diff --git a/lib/ethdev/meson.build b/lib/ethdev/meson.build
index 8ba6c708a2..686b64e3c2 100644
--- a/lib/ethdev/meson.build
+++ b/lib/ethdev/meson.build
@@ -40,6 +40,7 @@ driver_sdk_headers += files(
         'ethdev_pci.h',
         'ethdev_vdev.h',
         'rte_flow_driver.h',
+        'rte_flow_graph.h',
         'rte_mtr_driver.h',
         'rte_tm_driver.h',
 )
diff --git a/lib/ethdev/rte_flow_graph.h b/lib/ethdev/rte_flow_graph.h
new file mode 100644
index 0000000000..e9f898ad68
--- /dev/null
+++ b/lib/ethdev/rte_flow_graph.h
@@ -0,0 +1,532 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2025 Intel Corporation
+ */
+
+#ifndef _RTE_FLOW_GRAPH_H_
+#define _RTE_FLOW_GRAPH_H_
+
+/**
+ * @file
+ * RTE Flow Graph Parser (Internal Driver API)
+ *
+ * This file provides a graph-based flow pattern parser for PMD drivers.
+ * It defines structures and functions to validate and process rte_flow
+ * patterns using a directed graph representation.
+ *
+ * @warning
+ * This is an internal API for PMD drivers only. Applications must not use it.
+ */
+
+#include <rte_flow.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Logging for flow graph parse errors. This is an internal driver API;
+ * RTE_FLOW_GRAPH_LOG requires RTE_COMPONENT_NAME (set by meson for drivers)
+ * and the corresponding driver logtype variable to be registered.
+ */
+#ifdef RTE_COMPONENT_NAME
+extern int RTE_CONCAT(RTE_COMPONENT_NAME, _logtype_driver);
+#define RTE_FLOW_GRAPH_LOG(level, fmt, ...) \
+       rte_log(RTE_LOG_##level, RTE_CONCAT(RTE_COMPONENT_NAME, 
_logtype_driver), \
+               "ETHDEV FLOW GRAPH: %s(): " fmt "\n", __func__, ##__VA_ARGS__)
+#else
+/* Use ETHDEV log level when included outside driver context */
+#define RTE_FLOW_GRAPH_LOG(level, fmt, ...) \
+       rte_log(RTE_LOG_##level, \
+                       rte_eth_dev_logtype, \
+                       "ETHDEV FLOW GRAPH: %s(): " fmt "\n", __func__, 
##__VA_ARGS__)
+#endif
+
+#define RTE_FLOW_NODE_FIRST (0)
+/* Edge array termination sentinel (not a valid node index). */
+#define RTE_FLOW_NODE_EDGE_END ((size_t)~0U)
+
+static inline const char *
+rte_flow_graph_item_type_to_str(enum rte_flow_item_type type)
+{
+       const char *name = NULL;
+       int ret;
+
+       /*
+        * this is a hack of monumental proportions.
+        *
+        * currently, chkincs will build each driver SDK header file using three
+        * build flag combinations: with INTERNAL+EXPERIMENTAL allowed, with
+        * EXPERIMENTAL allowed, and with neither allowed. That last one will
+        * fail for this header, because `rte_flow_conv` is defined as an
+        * experimental API.
+        *
+        * Arguably, this is a bug in chkincs because this header is installed
+        * as driver_sdk only (see lib/ethdev Meson file), meaning that this
+        * file is driver-internal only and is never exported to the user.
+        * Drivers themselves are already always built with experimental API
+        * enabled (see drivers Meson file, specifically default_cflags), so
+        * in practice chkincs tests a configuration that never exists in real
+        * life for driver SDK.
+        *
+        * So, again, arguably, this should be fixed in chkincs, but I have no
+        * idea what would be the correct way to do that, so for now I'll just
+        * avoid calling `rte_flow_conv` whenever experimental API's aren't
+        * allowed, and hopefully we'll come up with a proper solution in one
+        * of the next versions of this patchset.
+        */
+#ifdef ALLOW_EXPERIMENTAL_API
+       ret = rte_flow_conv(RTE_FLOW_CONV_OP_ITEM_NAME_PTR,
+                       &name, sizeof(name), (const void *)(uintptr_t)type, 
NULL);
+#else
+       ret = -1;
+       RTE_SET_USED(type);
+#endif
+       if (ret < 0 || name == NULL)
+               return "UNKNOWN";
+
+       return name;
+}
+
+/**
+ * For a lot of nodes, there are multiple common patterns of validation 
behavior. This enum allows
+ * marking nodes as implementing one of these common behaviors without need 
for expressing that in
+ * validation code. Can be ORed together to express support for multiple node 
types. These checks
+ * are not combined (any one of them being satisfied is sufficient).
+ */
+enum rte_flow_graph_node_expect {
+       RTE_FLOW_NODE_EXPECT_NONE = 0,             /**< No special constraints. 
*/
+       RTE_FLOW_NODE_EXPECT_EMPTY = (1 << 0),     /**< spec, mask, last must 
be NULL. */
+       RTE_FLOW_NODE_EXPECT_SPEC = (1 << 1),      /**< spec is required, mask 
and last must be NULL. */
+       RTE_FLOW_NODE_EXPECT_MASK = (1 << 2),      /**< mask is required, spec 
and last must be NULL. */
+       RTE_FLOW_NODE_EXPECT_SPEC_MASK = (1 << 3), /**< spec and mask required, 
last must be NULL. */
+       RTE_FLOW_NODE_EXPECT_RANGE = (1 << 4),     /**< spec, mask, and last 
are required. */
+       RTE_FLOW_NODE_EXPECT_NOT_RANGE = (1 << 5), /**< last must be NULL. */
+};
+
+/**
+ * Node validation callback.
+ *
+ * Called when the graph traversal reaches this node. Validates the
+ * rte_flow_item (spec, mask, last) against driver-specific constraints.
+ *
+ * Drivers are suggested to perform all checks in this callback.
+ *
+ * @param ctx
+ *   Opaque driver context for accumulating parsed state.
+ * @param item
+ *   Pointer to the rte_flow_item being validated.
+ * @param error
+ *   Pointer to rte_flow_error structure for reporting failures.
+ * @return
+ *   0 on success, or the value returned by rte_flow_error_set() on failure.
+ *   On failure the callback must report the error with rte_flow_error_set().
+ */
+typedef int (*rte_flow_node_validate_fn)(
+       const void *ctx,
+       const struct rte_flow_item *item,
+       struct rte_flow_error *error);
+
+/**
+ * Node processing callback.
+ *
+ * Called after validation succeeds. Extracts fields from the rte_flow_item
+ * and stores them in driver-specific state for later hardware programming.
+ *
+ * Drivers are suggested to implement "happy path" in this callback.
+ *
+ * @param ctx
+ *   Opaque driver context for accumulating parsed state.
+ * @param item
+ *   Pointer to the rte_flow_item to process.
+ * @param error
+ *   Pointer to rte_flow_error structure for reporting failures.
+ * @return
+ *   0 on success, or the value returned by rte_flow_error_set() on failure.
+ *   On failure the callback must report the error with rte_flow_error_set().
+ */
+typedef int (*rte_flow_node_process_fn)(
+       void *ctx,
+       const struct rte_flow_item *item,
+       struct rte_flow_error *error);
+
+/**
+ * Graph node definition.
+ *
+ * Node validity rules:
+ * - all nodes must define a name,
+ * - all non-END nodes must define an edge list,
+ * - start node must not define validation/processing callbacks.
+ */
+struct rte_flow_graph_node {
+       const char *name;                    /**< Node name. */
+       const enum rte_flow_item_type type;  /**< Corresponding 
rte_flow_item_type. */
+       const enum rte_flow_graph_node_expect constraints; /**< Common 
validation constraints (ORed). */
+       rte_flow_node_validate_fn validate;  /**< Validation callback (NULL if 
unsupported). */
+       rte_flow_node_process_fn process;    /**< Processing callback (NULL if 
no extraction needed). */
+};
+
+/**
+ * Graph edge definition.
+ *
+ * Describes allowed transitions from one node to others. The 'next' array
+ * lists all valid successor node types and is terminated by 
RTE_FLOW_NODE_EDGE_END.
+ * Drivers define edges to express their supported protocol sequences. Edges
+ * must be unique, as split path following is not supported.
+ */
+struct rte_flow_graph_edge {
+       const size_t *next;  /**< Array of valid successor nodes, terminated by 
RTE_FLOW_NODE_EDGE_END. */
+};
+
+/**
+ * Flow graph to be implemented by drivers.
+ *
+ * Graph contents are expected to be well-formed. This library validates
+ * traversal semantics for pattern items, but does not attempt to harden
+ * against arbitrary malformed node/edge table definitions.
+ */
+struct rte_flow_graph {
+       struct rte_flow_graph_node *nodes;
+       struct rte_flow_graph_edge *edges;
+       const enum rte_flow_item_type *ignore_nodes; /**< Additional node types 
to ignore, terminated by RTE_FLOW_ITEM_TYPE_END. */
+};
+
+static inline bool
+__flow_graph_node_check_constraint(enum rte_flow_graph_node_expect c,
+               bool has_spec, bool has_mask, bool has_last)
+{
+       bool empty = !has_spec && !has_mask && !has_last;
+
+       if ((c & RTE_FLOW_NODE_EXPECT_EMPTY) && empty)
+               return true;
+       if ((c & RTE_FLOW_NODE_EXPECT_NOT_RANGE) && !has_last)
+               return true;
+       if ((c & RTE_FLOW_NODE_EXPECT_SPEC) && has_spec && !has_mask && 
!has_last)
+               return true;
+       if ((c & RTE_FLOW_NODE_EXPECT_MASK) && has_mask && !has_spec && 
!has_last)
+               return true;
+       if ((c & RTE_FLOW_NODE_EXPECT_SPEC_MASK) && has_spec && has_mask && 
!has_last)
+               return true;
+       if ((c & RTE_FLOW_NODE_EXPECT_RANGE) && has_mask && has_spec && 
has_last)
+               return true;
+
+       return false;
+}
+
+static inline bool
+__flow_graph_node_is_expected(const struct rte_flow_graph_node *node,
+               const struct rte_flow_item *item, struct rte_flow_error *error)
+{
+       enum rte_flow_graph_node_expect c = node->constraints;
+
+       if (c == RTE_FLOW_NODE_EXPECT_NONE)
+               return true;
+
+       bool has_spec = (item->spec != NULL);
+       bool has_mask = (item->mask != NULL);
+       bool has_last = (item->last != NULL);
+
+       if (__flow_graph_node_check_constraint(c, has_spec, has_mask, has_last))
+               return true;
+
+       /*
+        * In the interest of everyone debugging flow parsing code, we should 
provide the user with
+        * meaningful messages about exactly what failed, as no one likes 
non-descript "node
+        * constraints not met" errors with no clear indication of where this 
is even coming from.
+        * What follows is us building said meaningful error messages. It's a 
bit ugly, but it is
+        * for the greater good.
+        */
+       const char *msg;
+
+       /* for empty items, we know exactly what went wrong */
+       if (c == RTE_FLOW_NODE_EXPECT_EMPTY) {
+               if (has_spec)
+                       msg = "Unexpected spec in flow item";
+               else if (has_mask)
+                       msg = "Unexpected mask in flow item";
+               else /* has_last */
+                       msg = "Unexpected last in flow item";
+       } else {
+               /*
+                * for non-empty constraints, we need to figure out the one 
thing user is missing
+                * (or has extra) that would've satisfied the constraints.
+                * We do that by flipping each presence bit in turn and seeing 
whether that single
+                * change would have satisfied the node constraints.
+                */
+
+               /* check spec first */
+               if (!has_spec && __flow_graph_node_check_constraint(c, true, 
has_mask, has_last)) {
+                       msg = "Missing spec in flow item";
+               } else if (has_spec && __flow_graph_node_check_constraint(c, 
false, has_mask, has_last)) {
+                       msg = "Unexpected spec in flow item";
+               }
+               /* check mask next */
+               else if (!has_mask && __flow_graph_node_check_constraint(c, 
has_spec, true, has_last)) {
+                       msg = "Missing mask in flow item";
+               } else if (has_mask && __flow_graph_node_check_constraint(c, 
has_spec, false, has_last)) {
+                       msg = "Unexpected mask in flow item";
+               }
+               /* finally, check range */
+               else if (!has_last && __flow_graph_node_check_constraint(c, 
has_spec, has_mask, true)) {
+                       msg = "Missing last in flow item";
+               } else if (has_last && __flow_graph_node_check_constraint(c, 
has_spec, has_mask, false)) {
+                       msg = "Unexpected last in flow item";
+               /* multiple things are wrong with the constraint, so just 
output a generic error */
+               } else {
+                       msg = "Flow item does not meet node constraints";
+               }
+       }
+
+       rte_flow_error_set(error, EINVAL, RTE_FLOW_ERROR_TYPE_ITEM, item, msg);
+
+       return false;
+}
+
+/**
+ * Check if a flow item type should be ignored by the graph.
+ *
+ * Checks if the item type is in the graph's ignore list.
+ */
+static inline bool
+__flow_graph_node_is_ignored(const struct rte_flow_graph *graph,
+                         enum rte_flow_item_type fi_type)
+{
+       const enum rte_flow_item_type *ignored;
+
+       /* Always skip VOID items */
+       if (fi_type == RTE_FLOW_ITEM_TYPE_VOID)
+               return true;
+
+       if (graph->ignore_nodes == NULL)
+               return false;
+
+       for (ignored = graph->ignore_nodes; *ignored != RTE_FLOW_ITEM_TYPE_END; 
ignored++) {
+               if (*ignored == fi_type)
+                       return true;
+       }
+
+       return false;
+}
+
+/**
+ * Get the index of a node within a graph.
+ */
+static inline size_t
+__flow_graph_get_node_index(const struct rte_flow_graph *graph, const struct 
rte_flow_graph_node *node)
+{
+       return (size_t)(node - graph->nodes);
+}
+
+/**
+ * Check if a graph node is valid.
+ */
+static inline bool
+__flow_graph_node_is_valid(const struct rte_flow_graph *graph,
+                          const struct rte_flow_graph_node *node,
+                          struct rte_flow_error *error)
+{
+       size_t node_idx;
+
+       if (node == NULL) {
+               rte_flow_error_set(error, EINVAL,
+                               RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+                               "Flow graph node pointer is NULL");
+               return false;
+       }
+
+       if (node->name == NULL) {
+               rte_flow_error_set(error, EINVAL,
+                               RTE_FLOW_ERROR_TYPE_UNSPECIFIED, node,
+                               "Flow graph node name is not defined");
+               return false;
+       }
+
+       node_idx = __flow_graph_get_node_index(graph, node);
+
+       /* first node can't have callbacks because there's no item */
+       if (node_idx == RTE_FLOW_NODE_FIRST &&
+                       (node->validate != NULL || node->process != NULL)) {
+               rte_flow_error_set(error, EINVAL,
+                               RTE_FLOW_ERROR_TYPE_UNSPECIFIED, node,
+                               "Flow graph start node callbacks are not 
allowed");
+               return false;
+       }
+
+       /* all non-END nodes must have edges */
+       if (node->type != RTE_FLOW_ITEM_TYPE_END &&
+                       graph->edges[node_idx].next == NULL) {
+               rte_flow_error_set(error, EINVAL,
+                               RTE_FLOW_ERROR_TYPE_UNSPECIFIED, node,
+                               "Flow graph edge list is not defined for 
non-END node");
+               return false;
+       }
+
+       return true;
+}
+
+/**
+ * Find the next node in the graph matching the given item type.
+ */
+static inline const struct rte_flow_graph_node *
+__flow_graph_find_next_node(const struct rte_flow_graph *graph,
+                     const struct rte_flow_graph_node *cur_node,
+                     enum rte_flow_item_type next_type,
+                     struct rte_flow_error *error)
+{
+       const size_t *next_nodes;
+       size_t cur_idx, edge_idx;
+
+       if (!__flow_graph_node_is_valid(graph, cur_node, error))
+               return NULL;
+
+       cur_idx = __flow_graph_get_node_index(graph, cur_node);
+       next_nodes = graph->edges[cur_idx].next;
+
+       for (edge_idx = 0; next_nodes[edge_idx] != RTE_FLOW_NODE_EDGE_END; 
edge_idx++) {
+               const struct rte_flow_graph_node *tmp =
+                               &graph->nodes[next_nodes[edge_idx]];
+               /* if node is invalid, graph is broken */
+               if (!__flow_graph_node_is_valid(graph, tmp, error))
+                       return NULL;
+               if (tmp->type == next_type)
+                       return tmp;
+       }
+
+       return NULL;
+}
+
+/**
+ * Visit (validate and extract) a node's item.
+ */
+static inline int
+__flow_graph_visit_node(const struct rte_flow_graph_node *node, void *ctx,
+               const struct rte_flow_item *item, struct rte_flow_error *error)
+{
+       int ret;
+
+       /* if we expect a certain type of node, check for it */
+       if (item != NULL && !__flow_graph_node_is_expected(node, item, error))
+               return -EINVAL;
+
+       /* Does this node fit driver's criteria? */
+       if (node->validate != NULL) {
+               ret = node->validate(ctx, item, error);
+               if (ret != 0)
+                       return ret;
+       }
+
+       /* Extract data from this item */
+       if (node->process != NULL) {
+               ret = node->process(ctx, item, error);
+               if (ret != 0)
+                       return ret;
+       }
+
+       return 0;
+}
+
+/**
+ * Parse and validate a flow pattern using the flow graph.
+ *
+ * Traverses the pattern items and validates them against the driver's graph
+ * structure. For each item, checks that the transition from the current node
+ * is allowed, then invokes validation and processing callbacks.
+ *
+ * @param graph
+ *   Pointer to the driver's flow graph definition with nodes and edges.
+ * @param pattern
+ *   Array of rte_flow_item structures to parse, terminated by 
RTE_FLOW_ITEM_TYPE_END.
+ * @param error
+ *   Pointer to rte_flow_error structure for reporting failures.
+ * @param ctx
+ *   Opaque driver context for accumulating parsed state.
+ * @return
+ *   0 on success, negative errno on failure (error is set).
+ */
+static inline int
+rte_flow_graph_parse(const struct rte_flow_graph *graph, const struct 
rte_flow_item *pattern,
+               struct rte_flow_error *error, void *ctx)
+{
+       const struct rte_flow_graph_node *cur_node;
+       const struct rte_flow_item *item;
+       int ret;
+
+       if (graph == NULL || graph->nodes == NULL || graph->edges == NULL) {
+               RTE_FLOW_GRAPH_LOG(DEBUG, "flow graph is not defined");
+               return rte_flow_error_set(error, ENOTSUP,
+                               RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+                               "Flow graph is not defined");
+       }
+       if (pattern == NULL) {
+               RTE_FLOW_GRAPH_LOG(DEBUG, "flow pattern is NULL");
+               return rte_flow_error_set(error, EINVAL,
+                               RTE_FLOW_ERROR_TYPE_ITEM, NULL,
+                               "Flow pattern is NULL");
+       }
+
+       /* use start node as traversal anchor */
+       cur_node = &graph->nodes[RTE_FLOW_NODE_FIRST];
+
+       /* is the node valid? */
+       if (!__flow_graph_node_is_valid(graph, cur_node, error)) {
+               /* error may be NULL */
+               if (error != NULL)
+                       RTE_FLOW_GRAPH_LOG(DEBUG, "%s", error->message);
+               return -EINVAL;
+       }
+
+       /* Traverse pattern items */
+       for (item = pattern; item->type != RTE_FLOW_ITEM_TYPE_END; item++) {
+
+               /* Skip items in the graph's ignore list */
+               if (__flow_graph_node_is_ignored(graph, item->type)) {
+                       RTE_FLOW_GRAPH_LOG(DEBUG, "ignored item %s",
+                                       
rte_flow_graph_item_type_to_str(item->type));
+                       continue;
+               }
+
+               /* Find the next graph node for this item type */
+               cur_node = __flow_graph_find_next_node(graph, cur_node,
+                               item->type, error);
+               if (cur_node == NULL) {
+                       RTE_FLOW_GRAPH_LOG(DEBUG, "cannot traverse to item %s",
+                                       
rte_flow_graph_item_type_to_str(item->type));
+                       return rte_flow_error_set(error, ENOTSUP,
+                                       RTE_FLOW_ERROR_TYPE_ITEM,
+                                       item, "Pattern item not supported");
+               }
+               RTE_FLOW_GRAPH_LOG(DEBUG, "processing %s", cur_node->name);
+               /* Validate and process the current item at this node */
+               ret = __flow_graph_visit_node(cur_node, ctx, item, error);
+               if (ret != 0) {
+                       /* error may be NULL */
+                       if (error != NULL)
+                               RTE_FLOW_GRAPH_LOG(DEBUG, "%s", error->message);
+                       return ret;
+               }
+       }
+
+       /* Pattern items have ended but we still need to process the end */
+       cur_node = __flow_graph_find_next_node(graph, cur_node, item->type, 
error);
+       if (cur_node == NULL) {
+               RTE_FLOW_GRAPH_LOG(DEBUG, "cannot traverse to item %s",
+                               rte_flow_graph_item_type_to_str(item->type));
+               return rte_flow_error_set(error, ENOTSUP,
+                               RTE_FLOW_ERROR_TYPE_ITEM,
+                               item, "Pattern item not supported");
+       }
+       ret = __flow_graph_visit_node(cur_node, ctx, item, error);
+       if (ret != 0) {
+               /* error may be NULL */
+               if (error != NULL)
+                       RTE_FLOW_GRAPH_LOG(DEBUG, "%s", error->message);
+               return ret;
+       }
+
+       return 0;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _RTE_FLOW_GRAPH_H_ */
-- 
2.52.0

Reply via email to