Skip to content

feat: HNAP session reuse + CM1200 timeout fix#123

Closed
kwschulz wants to merge 24 commits intomainfrom
feature/v3.13.2
Closed

feat: HNAP session reuse + CM1200 timeout fix#123
kwschulz wants to merge 24 commits intomainfrom
feature/v3.13.2

Conversation

@kwschulz
Copy link
Copy Markdown
Collaborator

Summary

  • HNAP session reuse — reuse existing sessions (uid cookie + private key) instead of re-authenticating on every poll. Prevents anti-brute-force reboots on S33/S33v2 modems (~1440 logins/day → 1). Includes stale session retry: if a reused session produces zero channels, clears cache and retries with fresh login once.
  • CM1200 timeout increase — bump request timeout from 10s to 20s for HTTPS modems that respond slowly.
  • Timeout plumbing fix — all _fetch_data paths now use self._timeout instead of hardcoded DEFAULT_TIMEOUT, so modem-specific timeout config actually takes effect.
  • S33v2 model aliases — adds S33v2/CommScope S33v2/ARRIS S33v2 to S33 detection aliases. Fixes S33 attribution to credit HAR contributors.
  • Expanded HAR test coverage — hooks up MB8600, C7000v2, TC4400, and S33v2 HAR captures to the test infrastructure (36 new HAR replay tests). Adds E2E session reuse tests against MockModemServer (4 tests) and unit tests for session reuse logic (12 tests).

Related to #117, #121.

Test plan

  • ruff check . passes
  • pytest full suite passes (2599 tests)
  • S33v2 HAR replay tests validate HMAC-MD5, HNAP auth flow, channel data format (15 tests)
  • E2E session reuse tests confirm login called once across 2 polls, stale retry works, capture mode bypasses reuse (4 tests)
  • Unit tests cover _has_valid_session(), pre-auth skip logic, stale retry conditions (12 tests)
  • MB8600, C7000v2, TC4400 HAR replay tests pass locally (21 tests, skip in CI — HAR files gitignored)
  • User validation: @mmiller7 tests S33v2 ([Bug]: Arris S33v2 crashing when monitor enabled #117), @DeFlanko re-tests CM1200 ([Bug]: CM1200 Broken in 3.13.0 #121)

🤖 Generated with Claude Code

kwschulz and others added 5 commits February 27, 2026 20:16
The global DEFAULT_TIMEOUT was reduced from 20s to 10s in v3.13.0, but
the CM1200 over HTTPS needs more headroom. Diagnostics show read timeouts
on /DocsisStatus.htm at 10s. Setting modem-specific timeout to 20s.

Related to #121

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Skip redundant login when valid session credentials (uid cookie +
HNAP private key) already exist. Previously, every 60s poll performed
a full HNAP challenge-response login (~1440/day), triggering anti-
brute-force protection on modems like the Arris S33 which reboots
the device.

Session reuse returns auth_performed=True to prevent _authenticate()
from calling _login() on the fallback path. A separate _session_reused
flag enables one stale-session retry (clear cache + fresh login) when
a reused session produces zero channels.

Also: S33 modem.yaml adds S33v2 model aliases and fixes attribution,
CM1200 modem.yaml increases timeout to 20s for HTTPS.

Related to #117

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
adapter.py maps yaml "hnap" → "hnap_session" and "basic" → "basic_http"
at setup time. _has_valid_session() was checking the yaml short forms,
so session reuse would never activate in production. Accept both forms.

Related to #117

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The modem.yaml timeout field was intended to override the 10s default
for all orchestrator requests, but _fetch_data(), _authenticate(), and
discovery probing all hardcoded DEFAULT_TIMEOUT. Only the HTMLLoader
respected the modem-specific value.

Replace all DEFAULT_TIMEOUT references with self._timeout so modems
like the CM1200 (timeout: 20) get the configured value everywhere.

Related to #121

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add integration tests for v3.13.2 session reuse feature:
- E2E MockModemServer tests for HNAP session reuse (4 tests)
- S33v2 HAR replay tests validating HMAC-MD5 and auth flow (15 tests)
- MB8600 HAR tests confirming HNAP auth detection (8 tests)
- C7000v2 HAR tests for structure and content validation (6 tests)
- TC4400 HAR tests for no-auth modem structure (7 tests)
- S33 hnap_full_status.json fixture for MockModemServer

Related to #117

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

⚠️ CHANGELOG.md Update Required

This pull request contains code changes but CHANGELOG.md has not been updated.

Required Action

Please add an entry to CHANGELOG.md under the [Unreleased] section following the Keep a Changelog format.

Format

## [Unreleased]

### Added
- New features go here

### Changed
- Changes to existing functionality go here

### Deprecated
- Soon-to-be removed features go here

### Removed
- Removed features go here

### Fixed
- Bug fixes go here

### Security
- Security fixes go here

Examples

### Added
- Support for ARRIS SB8200 modem (#123)
- New sensor for modem temperature

### Fixed
- Authentication timeout on Motorola MB7621 (#456)
- Channel parsing error on Technicolor TC4400

Exemptions

If this PR only contains:

  • Documentation updates
  • Test improvements
  • CI/CD configuration changes
  • Non-functional changes

Then a CHANGELOG update may not be required. Please add a comment explaining why.


For more information, see CONTRIBUTING.md.

Comment thread tests/integration/core/test_hnap_session_reuse.py Fixed
kwschulz and others added 3 commits February 28, 2026 17:14
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Previously mypy only ran on custom_components/ files during commit,
missing type errors in modems/ and tests/ that CI catches.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
kwschulz and others added 16 commits March 1, 2026 20:46
Remove outdated HACS prerequisites and manual install method.
Promote branch/pre-release testing via Developer Tools → Actions.
Separate Installation from Setup to reduce duplication.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
HAR captures confirm the CM1200 serves status pages without any
authentication. The previous basic auth config caused intermittent 401
failures because the modem rejects Authorization headers it doesn't
expect. This was masked by v3.12's auth discovery fallback but became
a hard failure in v3.13 which trusts modem.yaml as source of truth.

Existing users must remove and re-add the integration to pick up the
new auth type from modem.yaml.

Related to #121

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The E2E test checks that public pages return 200 from the mock
server. /index.htm was never observed in any HAR capture and has
no fixture file, so listing it as public caused a test failure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…hook

The two CM1200 modem.yaml fixes (auth type basic→none, remove /index.htm)
only updated the source in modems/ but were never synced to
custom_components/. This caused James's config flow crash when re-adding
the integration — HA was still reading basic auth config.

Add a pre-commit hook that runs `make sync` whenever modems/ files
change, so custom_components/ can never drift again.

Also bumps har-capture dependency to 0.4.0 (raw HTTP probe support).

Related to #121

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix CodeQL "redundant comparison" by comparing against stored variable
- Add isinstance narrowing for HnapAuthHandler to satisfy mypy
- Add assert-not-None guards for parser/adapter lookups

Related to PR #123.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add CONF_PROTOCOL as a new config entry field to store the user's
explicit protocol choice separately from the hostname. New entries
store decomposed fields; old entries fall back to parsing the host
string at runtime. When CONF_PROTOCOL is set, the orchestrator locks
to that protocol with no runtime fallback.

Also fix pre-existing mypy method-assign/attr-defined warnings in
test_data_orchestrator.py by using mocker.patch.object instead of
direct attribute assignment.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The S33/S34 modems are case-sensitive and only serve the capitalized
/Cmconnectionstatus.html. The lowercase variant returned 404 on every
poll cycle, wasting a request. Confirmed via HAR captures from both
S33 and S33v2.

Related to #117

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ences

- Move har_auth_extractor.py to scripts/har/ with new har_auth_format.py
  and har_page_analyzer.py decomposition scripts plus README
- Add tests for all three HAR scripts (tests/scripts/har/)
- Rewrite ARCHITECTURE.md (1714 → 301 lines, reflect implemented code)
- Rewrite TESTING.md with test architecture section, Python 3.12+ prereqs
- Remove stale docs/plans/ (8 files), MODEM_LANDSCAPE.md, TECH_DEBT.md
- Remove unused scripts/dev/ files (README_HOOKS.md, test_metrics.py)
- Remove poc_legacy_ssl.py
- Scope .gitignore har/ pattern to modems/**/har/ so scripts/har/ isn't ignored
- Replace hardcoded RAW_DATA paths with generic references in docs and modem.yaml

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
har-capture v0.4.4 probe revealed the CM1200 returns 401 with
WWW-Authenticate: Basic realm="Netgear" over HTTPS. Previous HAR
captures were HTTP-only (no auth needed), leading to auth: none
in modem.yaml which broke v3.13.0.

The 401 also sets an XSRF_TOKEN cookie, but the server does not
enforce it — Basic Auth alone is sufficient.

Related to #121

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The CM1200 HTTPS variant returns 401 with an XSRF_TOKEN cookie that
must accompany the Authorization header. Add a `challenge_cookie` flag
to BasicAuthConfig that retries after the initial 401 so the session
jar captures the required cookie before re-sending credentials.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
challenge_cookie was wired through the config flow but never reached
the runtime DataOrchestrator → AuthHandler path.  Every poll hit the
CM1200's 401 challenge, got no retry, and failed.

Read challenge_cookie from modem_adapter.get_static_auth_config() in
__init__.py, accept it in DataOrchestrator.__init__, and forward it to
AuthHandler.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
HNAP firmware can return LoginResult "LOCKUP" or "REBOOT" to signal
anti-brute-force escalation.  Previously these fell into the generic
failure path, causing the orchestrator to retry login on the next poll
and compound the problem.

The HNAP builder now raises LoginLockoutError for these responses.  The
orchestrator catches it, sets a 3-poll backoff, and suppresses login
attempts until the backoff expires.  This keeps the builder stateless
with respect to lockout (signal only) and the orchestrator in control of
all retry policy.

Also updates AI_CONTEXT.md HNAP guidance to reflect current architecture.

Related to #117

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add ISP and contributor to modem.yaml, update VERIFICATION_STATUS.md
and modem database README.

Related to #126

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…onfig

The url_token auth pipeline was silently dropping ajax_login and
auth_header_data values from modem.yaml. Both handler._create_typed_config
and adapter._convert_url_token_config now pass these fields through,
so the SB8200 HTTPS variant correctly uses X-Requested-With on login
and omits the Authorization header on data requests.

Related to #81

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@kwschulz
Copy link
Copy Markdown
Collaborator Author

kwschulz commented Apr 4, 2026

Closing — the session reuse and timeout fixes in this PR shipped in v3.14 alpha and both affected users (#117, #121) have confirmed success on the alpha track. No need for a v3.13.2 backport.

@kwschulz kwschulz closed this Apr 4, 2026
@kwschulz kwschulz deleted the feature/v3.13.2 branch April 5, 2026 00:31
kwschulz added a commit that referenced this pull request Apr 6, 2026
- Fix CodeQL "redundant comparison" by comparing against stored variable
- Add isinstance narrowing for HnapAuthHandler to satisfy mypy
- Add assert-not-None guards for parser/adapter lookups

Related to PR #123.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@mmiller7
Copy link
Copy Markdown

mmiller7 commented Apr 8, 2026

Just a follow up - been a week and still going strong no crashing! And I caught my ISP having a signal issue at 3AM this morning before my modem dropped offline!

Thanks for your effort maintaining this!

@DeFlanko
Copy link
Copy Markdown

DeFlanko commented Apr 8, 2026

Hey @mmiller7! i think we tried to work on something in the past.

@kwschulz This integration is really solid, no issues with this version for the CM1200.

@kwschulz
Copy link
Copy Markdown
Collaborator Author

kwschulz commented Apr 8, 2026

Thank you both! I'm trying to work through the backlog, one at a time. New v3.14 architecture is holding up pretty well. To close out issues, I just need a clean diagnostics file as proof/artifact the config is working as expected. Appreciate your support in making this a rock solid integration!

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.

4 participants