#[non_exhaustive]pub struct Matcher {
pub side: Option<ProxySide>,
pub track_alias: Option<RangeSet>,
pub group_id: Option<RangeSet>,
pub subgroup_id: Option<RangeSet>,
pub object_id: Option<RangeSet>,
pub priority: Option<RangeInclusive<u8>>,
pub stream_kind: Option<MatchKind>,
pub every_nth: Option<(u64, u64)>,
}Expand description
Which units a ClassRule claims.
All present fields must match (AND). An absent field matches
everything. A field the wire did not carry — None on
ObjectMeta — does not match.
#[non_exhaustive] with a Default, exactly as
EgressConfig is. The pairing is
load-bearing: #[non_exhaustive] alone would make this type
unconstructible from an integration-test crate or from a caller’s
code, because struct-expression and functional-update syntax
are both illegal outside the defining crate. Note what that leaves:
..Matcher::default() is also illegal there (E0639), so an
outside caller writes let mut m = Matcher::default(); and then assigns
per field — which is what tests/actions_shaping.rs does. Inside this
crate both forms compile, which is why the unit tests below use the
shorter one. The Default is all-None — a matcher that claims every
unit.
In the written form every key defaults to absent, so a matcher naming one
field is one line, and an unknown key is refused rather than skipped — a
misspelled group_id would otherwise widen the rule to claim every unit
on the stream instead of the ten groups it named.
Fields (Non-exhaustive)§
This struct is marked as non-exhaustive
Struct { .. } syntax; cannot be matched against without a wildcard ..; and struct update syntax will not work.side: Option<ProxySide>The direction the unit arrived on.
Only ClientToProxy and RelayToProxy are ever seen at a hook
site; the two egress labels are used for teardown reporting. Naming
an egress side here is rejected by
ShapeProfile::try_new rather than
left to match nothing.
Written as the variant’s own name in kebab-case —
"client-to-proxy" — by the side_serde mapping below rather than
by a derive, because ProxySide lives in a module that carries no
serde dependency of its own.
track_alias: Option<RangeSet>Track alias from the stream header. None on every fetch stream,
so a rule keyed here never claims fetch units.
group_id: Option<RangeSet>Group ID. Always present on a framed object.
subgroup_id: Option<RangeSet>Subgroup ID. None on eight drafts in first-object mode and on
17-19 in reserved mode 3, so a rule keyed here claims nothing there.
object_id: Option<RangeSet>Absolute object ID. Always present on a framed object.
priority: Option<RangeInclusive<u8>>MoQT publisher_priority. None on drafts 15-19 whenever the
header set the default-priority bit.
stream_kind: Option<MatchKind>Which kind of stream the unit came from.
every_nth: Option<(u64, u64)>(n, offset) over a counter of hook-visible units on this
stream, scoped per stream and not per class, never
ObjectMeta::index_in_stream — which counts oversized objects
that never reach the hook and would silently shift the pattern.
Matches when unit_index % n == offset % n. n == 0 names no
units, so ShapeProfile::try_new
rejects it as ShapeError::InertMatcher
rather than accepting a class that can never claim anything.
Implementations§
Source§impl Matcher
impl Matcher
Sourcepub fn matches(
&self,
side: ProxySide,
meta: &ObjectMeta,
unit_index: u64,
) -> bool
pub fn matches( &self, side: ProxySide, meta: &ObjectMeta, unit_index: u64, ) -> bool
Whether this matcher claims one unit.
side is the forwarding task’s own direction label — ObjectMeta
has no side field. unit_index is the per-stream count of
hook-visible units described on Matcher::every_nth, supplied by
the caller for the same reason charge takes now: this function
owns no state and reads no counter, so it can be tested exhaustively
from a table.
Sourcepub fn matches_datagram(
&self,
side: ProxySide,
meta: &AnyDatagramMeta,
unit_index: u64,
) -> bool
pub fn matches_datagram( &self, side: ProxySide, meta: &AnyDatagramMeta, unit_index: u64, ) -> bool
Whether this matcher claims one datagram.
Self::matches’s sibling, and the two answer through one
conjunction — see the private Keys it is written over. What differs
is what fills that in: a
datagram states its own track alias, Group ID, Object ID and (from
draft-15, conditionally) priority, and states no subgroup ID on
any draft, so a rule keyed there claims no datagram. That last one is
a fact about the carrier rather than about one header, so it is
refused before the run by
Capabilities::admit_class
rather than discovered from one.
unit_index counts hook-visible datagrams per forwarding
direction, which is the only scope a datagram has: it belongs to no
stream, so Matcher::every_nth’s per-stream reading has nothing to
key against here and the session’s two directions count separately.
Sourcefn claims(
&self,
side: ProxySide,
kind: MatchKind,
keys: Keys,
unit_index: u64,
) -> bool
fn claims( &self, side: ProxySide, kind: MatchKind, keys: Keys, unit_index: u64, ) -> bool
The conjunction both carriers answer through.
Written once rather than twice because a six-key AND copied is a sixth key forgotten: a matcher key added to this struct and to only one of the two callers would widen every rule on the other carrier, silently, in the direction of claiming more than it named.
Sourcepub(crate) fn unmatchable_fields(
&self,
meta: &ObjectMeta,
) -> [Option<MatcherField>; 3]
pub(crate) fn unmatchable_fields( &self, meta: &ObjectMeta, ) -> [Option<MatcherField>; 3]
The keys this matcher names that meta cannot carry, so a
non-match against them is a rule that can never fire rather than a
rule that did not fire.
The distinction is the whole point of this function: None never
matches, and a silent fall to the default class is precisely the
failure mode this project exists to prevent. The caller reports each
(class, field) once per session.
Returns a fixed-size array rather than a Vec — it is called on the
data path, once per rule that failed to match, and must not
allocate. [None; 3] is the answer for a matcher whose keys are all
carried, which is the common case.
Every row here is a key this unit did not carry. There is no row
for a key no unit of this draft could ever carry, because there is no
longer such a key: the one candidate was a Fetch-aimed class on
drafts 18 and 19, and a fetch stream there is addressed now — see
MatchKind::Fetch.
Only checked after Self::matches has answered false: a rule
that matched cannot have been defeated by an absent key.
Sourcepub(crate) fn unmatchable_fields_datagram(
&self,
draft: DraftVersion,
meta: &AnyDatagramMeta,
) -> [Option<MatcherField>; 3]
pub(crate) fn unmatchable_fields_datagram( &self, draft: DraftVersion, meta: &AnyDatagramMeta, ) -> [Option<MatcherField>; 3]
Self::unmatchable_fields’s datagram sibling: the keys this matcher
names that this datagram could not carry.
One of the three is answered here and two are deliberately not.
MatcherField::Priorityis reported on the same terms as on a framed object — drafts 15 and later let a datagram’s type byte set a default-priority bit and leave the field off, and a rule keyed on priority cannot claim one that did.MatcherField::TrackAliasnever: every datagram of every draft states one.MatcherField::SubgroupIdnever, and this is the interesting one. No datagram carries a subgroup ID on any draft, so a datagram-aimed rule keyed there is refused before the session starts —Capabilities::admit_class, which is where a key a carrier never has belongs, because rejecting beats reporting wherever the answer exists without traffic. A rule that names no stream kind and keys onsubgroup_idis a live subgroup rule, and reporting it here because a datagram went past would be a diagnostic about a rule that works.
Sourcepub fn inert_key(&self) -> Option<&'static str>
pub fn inert_key(&self) -> Option<&'static str>
The first key this matcher names that can never claim a unit —
not because the wire withheld it, but because the key itself names
an empty set of values.
The crate-internal unmatchable_fields’s sibling, and the difference is
where each is answerable: an unmatchable field depends on the draft and
the unit, so it can only be reported during a run, while an inert key
is a property of the configuration alone and is therefore
ShapeProfile::try_new’s to reject
before a session ever starts. Rejecting is strictly better than
reporting: a class that can never fire is the configuration that looks
applied and does nothing that constructor exists to prevent, and here
it is knowable without a single byte of traffic.
Public because a caller that builds matchers from its own configuration
needs the same pre-flight refusal try_new gets: a matcher handed to a
ProxyHook rather than to a shaping class
reaches no constructor that could check it.
The three shapes, each of which try_new used to accept:
- a
RangeSetbuilt from an inverted range —RangeSet::newdropsstart > end, so the set is empty andcontainsis alwaysfalse; - an empty
Matcher::priorityrange (200..=100); Matcher::every_nthwithn == 0, whichSelf::matchesanswersfalsefor unconditionally.
Returns the key’s field name as it is spelled on this struct, so the error message names something the author can search their own configuration for.