Skip to content

feat: derive the DIAL discovery grace from the advertised max-age#54

Merged
sbogomolov merged 1 commit into
mainfrom
dial/max-age-grace
Jul 9, 2026
Merged

feat: derive the DIAL discovery grace from the advertised max-age#54
sbogomolov merged 1 commit into
mainfrom
dial/max-age-grace

Conversation

@sbogomolov

Copy link
Copy Markdown
Owner

Derive the DIAL Discovery listener's eviction grace from the advertisement's CACHE-CONTROL: max-age instead of a fixed 60s.

  • ParseCacheControlMaxAge (ssdp_message): first CACHE-CONTROL field, first max-age directive among its comma-separated directives, both case-insensitive. The value must be a bare integer fitting uint32_t (trailing OWS tolerated), like the MX parse. Whitespace around = is accepted: HTTP's cache-directive grammar has none, but UDA's own header templates and examples write max-age = 1800 and devices copy them. Longer tokens sharing the prefix (max-agenda=...) are skipped; a malformed max-age invalidates the field.
  • DialProxy: endpoints carry a per-endpoint grace. Every advertisement re-states it — absent/unparseable falls to DEFAULT_DISCOVERY_GRACE (30 min, the UDA-recommended minimum device validity), and the value is clamped to MAX_DISCOVERY_GRACE (2 h) since it is attacker-controlled and sets a proxy-slot eviction deadline. Rest listeners keep their fixed activity-refreshed 300s grace (minted from description responses, which carry no max-age); a Discovery endpoint promoted to Rest keeps its advertised validity.
  • The rest-vs-discovery grace ordering inverts (300s vs the 30-min default), so the role-grace eviction test swaps its phases.

Tests

13 new: 9 parser (incl. OWS-around-=, longer-token disambiguation, garbage/overflow → default), 3 proxy-level (advertised grace honored, oversized clamp, re-advertisement replaces the grace in both directions), 1 reflector integration (max-age flows through the LOCATION rewrite: the listener outlives the default validity).

Each behavior was verified to fail its test when the corresponding production line is removed (clamp, reuse-refresh, plumbing, advertised-sets-grace), then restored.

Verification

  • Native unit suite green (Debug, ASan/UBSan): 880.
  • Docker/Linux unit suite green (Debug, ASan/UBSan): 868.

A Discovery listener's eviction grace was a fixed 60s; a device's
advertisement actually states its validity in CACHE-CONTROL max-age,
so honor it. ParseCacheControlMaxAge (ssdp_message) reads the first
max-age directive of the first CACHE-CONTROL field -- bare integer
only, like the MX parse. Whitespace around '=' is tolerated: HTTP's
cache-directive grammar has none, but UDA's own header templates and
examples write "max-age = 1800" and devices copy them. The proxy
stores a per-endpoint grace: each advertisement re-states it
(absent/unparseable -> the 30-minute UDA-recommended default), clamped
to 2 hours since the value is attacker-controlled and pins a scarce
proxy slot. Rest listeners keep their fixed activity-refreshed grace
(they are minted from description responses, which carry no max-age);
a promoted endpoint keeps its advertised validity.

The rest-vs-discovery grace ordering inverts (300s vs the 30-minute
default), so the role-grace eviction test swaps phases.
@sbogomolov sbogomolov self-assigned this Jul 9, 2026
@sbogomolov
sbogomolov merged commit cfd9c67 into main Jul 9, 2026
17 checks passed
@sbogomolov
sbogomolov deleted the dial/max-age-grace branch July 9, 2026 22:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant