Dense Wi-Fi Scan Results: Bound Memory Without Losing the Networks the User Needs

A fixed-memory device cannot promise to retain every access point in an arbitrarily dense environment. It can promise bounded storage, explicit truncation, stable pages and selection of the exact record the user chose. Separate the stack’s scan cache from the product’s presentation cache, then define admission, retention and stale-selection behavior for each.

This guide uses wpa_supplicant 2.11 to expose concrete cache boundaries and proposes a separate application model for ordinary infrastructure BSS records. It excludes multi-link grouping, mesh records and hidden-name reconciliation. SSID encoding is retained as bytes; this article does not redesign that encoding or infer identity from display text.

Do not mistake a stack setting for a hard RAM budget

Wpa_supplicant 2.11 documents bss_max_count with a default of 200, bss_expiration_age with a default of 180 seconds, and expiration after two missed scans by default. These are cache-policy settings, not a complete allocation bound. The exact implementation allocates a BSS object together with variable-length probe and beacon information elements. wpa_supplicant 2.11, wpa_supplicant.conf and bss.c

More importantly, the add path can increase bss_max_count when it cannot remove an eligible old entry because entries are in use. Its eviction path first seeks an old entry not matching a configured network and not considered a WPS candidate, then an old entry not currently in use. Therefore, do not certify an absolute record or byte ceiling from the configuration name alone. Inspect the shipped source and measure its allocation behavior separately.

An application cache with a strict bound does not retroactively bound the supplicant, driver or firmware cache. Include those allocations in the product memory qualification. If the full stack cannot satisfy the budget, changing only the UI page size is not a sufficient remedy.

Define a bounded record and admission rule

Field Purpose Bound or rule
BSSID and raw SSID Ordinary BSS identity in this model Fixed address and explicit bounded byte length
Observation time and scan provenance Freshness and coverage interpretation Monotonic time; retain the source generation
Parsed security/capability summary Safe display and later compatibility check Versioned bounded representation
Retained raw information elements Optional diagnostics or parser input Explicit per-record byte cap
Record revision and pin state Selection revalidation and retention Never reuse a revision ambiguously within a live UI session

The 2.11 supplicant’s ordinary lookup uses BSSID plus SSID bytes; the proposed application model follows that restricted identity choice. Two BSSIDs with the same displayed name remain separate observations. A later display grouping may combine eligible rows for presentation, but must retain the underlying choices rather than overwriting them by name. wpa_supplicant 2.11, wpa_bss_update_scan_res()

Choose an entry cap K and an information-element cap L, plus fixed metadata storage. Account separately for live records, immutable page snapshots, parser scratch space and transport serialization. A bounded count with unbounded raw information elements or unlimited concurrent snapshots is still an unbounded design.

A bounded AP cache admits valid records, evicts only eligible unpinned entries, reports overflow, and exposes an immutable snapshot whose selected row is revalidated against live state.
Stable pagination and safe selection require different checks: a page can remain consistent while its selected live record has become stale.

Preserve important rows without making pins unlimited

Use an explicit, bounded pin budget for the active association and any in-progress user selection. Pins protect records from eviction; they do not make old observations fresh. Display stale pinned entries as stale and revalidate them before connecting.

For an incoming new record, evict an eligible unpinned record according to the declared policy, such as oldest observation first. If every slot is pinned, refuse admission and increment an overflow indicator. Do not silently increase K. If a required parsed field cannot be retained or an information element exceeds the supported limit, reject or mark the candidate unsupported; never truncate security data and then treat the partial parse as authoritative.

Age records with the clock and coverage evidence actually available. A network absent from a scan that did not cover its channel is not equivalent to an observed disappearance. The application can use a simple monotonic age policy, but it must label that as its own retention choice rather than claiming to reproduce every supplicant scan-expiration rule.

Make pagination a snapshot contract

Sorting a live list by RSSI between page requests can duplicate or skip rows. Instead, create an immutable, deterministically ordered snapshot with a snapshot identifier. A cursor contains that identifier and an offset, not merely “page two of whatever exists now.” Bound the number and size of active snapshots, and explicitly invalidate old cursors when their snapshot is replaced.

Selection needs another gate. Send a snapshot identifier, record key and record revision with the user’s choice. Resolve that token against current live state before starting a connection. If the row was evicted or materially revised, return a stale-selection result and refresh the choice. Never reinterpret an old row index as a newly arrived AP occupying the same slot.

A snapshot can preserve presentation while the radio environment changes. That is intentional. Its timestamp and truncation indicator tell the user how much confidence to place in the list; the live revalidation prevents presentation stability from becoming an identity bug.

Test churn before testing the crowded room

  1. Duplicate update. Repeated observations of one key update its revision and freshness without consuming another slot.
  2. Same name, different BSSID. Retain distinct records and verify that display grouping does not lose the selected identity.
  3. Overflow with pins. Fill the cache, pin the protected records, and admit a newcomer. Verify the exact eligible victim, then pin every slot and require explicit refusal.
  4. Snapshot under churn. Read the first page, change the live cache, and read the next page from the original snapshot. The page sequence must remain stable.
  5. Stale selection. Select an evicted or revised snapshot row. Require a stale result, not a connection using another row’s data.
  6. Oversize and aging. Reject oversized input without refreshing the old record, retain stale pins visibly, and expire unpinned records according to the declared clock policy.

A hypothetical three-slot fixture retains one selected AP while new observations replace older unpinned rows. An old page still displays its original row, but selecting an evicted row fails revalidation. That is a controlled outcome, not lost user intent: the UI explains that its observation changed instead of connecting to an unintended candidate.

Executed model and remaining qualification

A local Python fixture passed the ten logical cases covering identity, updates, eviction, pins, overflow, oversize input, aging, snapshot replacement and selection revalidation. Its immutable records model bounded owned cache storage; Python allocator usage is not a firmware RAM measurement. It contains no information-element security parser and did not execute a supplicant scan.

Finish with target heap/high-water measurements, actual stack-cache behavior and dense authorized RF tests. Release only with the entry, byte and snapshot bounds, overflow indication and stale-selection behavior documented. A short list can be dependable when its omissions and identity rules are explicit.

Similar Posts