GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Split KLAP IoT updates into smaller requests and validate KLAP responses by ZeliardM · Pull Request #1829 · python-kasa/python-kasa

Conversation

@ZeliardM

@ZeliardM ZeliardM commented Oct 30, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Collaborator

Over KLAP, some IoT devices answer a request whose response would be larger than they can build with a plain <html><body><center>200 OK</center></body></html> page instead of a KLAP message. The page then fails to decrypt with "The length of the provided data is not a multiple of the block length".

  • KS200M(US) fw 1.1.4 (KS200M: combined update gets a plain "200 OK" html page instead of a KLAP response #1828):
    • 5 of 6 switches fail every update; the combined update's response would be about 3.7 KB.
    • With the update split into three requests, 10 of 10 updates work.
  • KP115(US) hw 1.0 fw 1.1.1, KLAP v2, my own plug:
    • The full update gets the page, while each module answers on its own.
    • The request size doesn't matter: get_sysinfo padded to the failing request sizes still works.
    • The response size does: a combined request whose response is about 3.9 KB works, and adding time to it gets the page.
    • Right after a relay change, a request with a response of about 2.3 KB got the page too. A new session and a retry got it again.

Changes:

  • IotDevice.max_device_response_size is 2048 when the connection uses KLAP, so _modular_update splits the update by the existing estimates, as it already does for bulbs.
    • Bulbs keep 4096 and XOR devices keep 16 KiB.
    • Updates over KLAP take more requests, from the fixtures:
      • plugs, switches and dimmers: 1 to 3, or 1 to 5 with energy monitoring;
      • KP400: 3 to 7;
      • HS300: 7 to 21, because each outlet's energy and usage queries get their own request.
  • Before decrypting, KlapTransport checks that a response is a 32 byte signature followed by whole AES blocks, and that the signature matches the request's sequence number.
    • Both checks raise KasaException, so IotProtocol resets the session and doesn't retry, as it does for a decrypt failure today.
    • The message says what was wrong. For a body that isn't a KLAP message it shows the start of the body.

The Kasa app sends each method in its own request, so it never sends a combined update.

This PR earlier queried each module separately after a rejected update. That fallback, _InvalidResponseError and the HttpClient change are gone.

#1790 (HS100(UK) hw 4.1 fw 1.1.0) is no longer listed. Its failing request is 368 bytes, which is exactly the 0.11.0.1 update with the HomeKit query right after system (320 bytes without it), the same as #1788, which #1808 fixed in 0.12.0.

Tests:

  • Transport:
    • the page, an empty body, a bare signature and a partial block raise KasaException with the new message;
    • a reply signed with the wrong key or for another sequence number raises the signature error;
    • a correctly signed reply that doesn't decrypt still raises the decrypt error;
    • through IotProtocol the page is not retried and the next query does a new handshake (KLAP and KLAP v2).
  • Device:
    • every IoT fixture uses 2048 over KLAP (bulbs 4096) and keeps its old budget otherwise, children included;
    • a KS200M update over KLAP is split into three requests.
  • Read-only on real devices, with this branch:
    • HS300(US) hw 2.0 fw 1.1.2 x2 (KLAP v2): an update takes 21 requests in about 4 s, against 7 in about 1.5 s with the old 16 KiB budget, with the same state. Every response signature matched.
    • EP25 hw 2.6 fw 1.0.3 x2 (SMART over KLAP v2): updates work, and every response signature matched.
    • KP115(US) hw 1.0 fw 1.1.1 (KLAP v2): an update takes 5 requests in about 0.4 s, against 1 in about 0.1 s with the old 16 KiB budget, with the same state. Every response signature matched.

Fixes #1828

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@ZeliardM @claude
Split KS200M updates into smaller requests
6574dff
Some KS200M switches (fw 1.1.4) answer the combined update query with
an unencrypted "200 OK" html page instead of a KLAP response, which
fails to decrypt. Each module answers when it is queried on its own.

Cap max_device_response_size at 2048 for the KS200M, so the update is
split into three requests, the same mechanism IotBulb uses. Other wall
switches keep the default.

Fixes python-kasa#1828

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@ZeliardM ZeliardM mentioned this pull request Oct 30, 2026
KS200M (fw 1.1.4): poll flapping and "Error trying to decrypt ... not a multiple of the block length" on connect, even on a fresh connection ZeliardM/homebridge-kasa-python#405
Open
@codecov

codecov Bot commented Oct 30, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.50%. Comparing base (693bc4f) to head (ba87779).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1829      +/-   ##
==========================================
+ Coverage   93.49%   93.50%   +0.01%     
==========================================
  Files         161      161              
  Lines       10159    10169      +10     
  Branches     1050     1053       +3     
==========================================
+ Hits         9498     9509      +11     
  Misses        469      469              
+ Partials      192      191       -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ZeliardM @claude
Query IoT modules separately when a device rejects the update
c83c65e
Some KLAP IoT devices answer a request they reject with a plain
"<html><body><center>200 OK</center></body></html>" page instead of a
KLAP message: KS200M fw 1.1.4 (python-kasa#1828), HS100(UK) hw 4.1 fw 1.1.0 (python-kasa#1790)
and KL125 fw 1.1.1. With an http status line the page failed to decrypt,
without one aiohttp failed with "Bad status line".

KlapTransport now raises _InvalidResponseError for a response that can't
be a KLAP message, and HttpClient raises it when aiohttp can't parse the
response. When an IoT update fails with it, the device queries each
module in its own request from then on and retries the update once, like
SmartProtocol disables multi requests. Strip outlets share the setting
of their parent, whose connection they use.

This replaces the KS200M-only response size cap.

Fixes python-kasa#1828

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@ZeliardM ZeliardM changed the title Split KS200M updates into smaller requests Query IoT modules separately when a device rejects the update Oct 31, 2026
@ZeliardM @claude
Test that unsupported IoT modules are left out of the update
7688616
The skip moved into _query_modules and had no test.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
rytilahti
rytilahti requested changes Oct 31, 2026
View reviewed changes

@rytilahti rytilahti left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a quick comment about having a new exception type to handle this, but instead of doing a fallback to individual requests, we should IMO figure out if we can handle this otherwise. The reason being, that if this is fixable by making some queries optional and/or using the existing facilities to split the query sizes to avoid the issue, it would avoid so many extra queries. Wdyt?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread kasa/exceptions.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread kasa/transports/klaptransport.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

ZeliardM and others added 2 commits November 3, 2026 11:19
@ZeliardM @claude
Split KLAP IoT updates into smaller requests
3115249
Over KLAP, some IoT devices answer a request whose response would be
larger than about 4 KiB with a plain "200 OK" html page instead of a
KLAP message, and have less room right after a state change.

Use a 2048 byte max_device_response_size for IoT devices on KLAP, so
the update is split by the existing response size estimates, the same
way bulbs use 4096. Bulbs keep 4096 and XOR devices keep 16 KiB.

This replaces the KS200M-only cap, the per-module fallback and the
internal _InvalidResponseError from the earlier commits on this branch.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@ZeliardM @claude
Validate KLAP responses before decrypting
9c2fbad
A KLAP response is a 32 byte signature followed by whole AES blocks,
signed for the sequence number of the request. Some devices answer with
a plain "200 OK" html page instead, which failed later with "The length
of the provided data is not a multiple of the block length".

Check the length, block alignment and signature first, and raise a
KasaException that says what was wrong and, for a body that is not a
KLAP message, shows its start. As before, IotProtocol does not retry
and resets the session, so the next query does a new handshake.

The logging test's fake device signed its response for the next
sequence number; it now signs for the request's, as devices do.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@ZeliardM ZeliardM changed the title Query IoT modules separately when a device rejects the update Split KLAP IoT updates into smaller requests and validate KLAP responses Nov 3, 2026
@ZeliardM

ZeliardM commented Nov 3, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks, agreed. The fallback is gone. The limit turned out to be on the size of the response, so the existing split covers it:

  • On my KP115 (KLAP v2), padding a get_sysinfo request to the size of the failing update still works.
  • A combined request whose response is about 3.9 KB works, and adding time gets the page.
  • Right after a relay change it failed at about 2.3 KB, and a new session with a retry didn't help.

So updates over KLAP now use a 2048 response budget, and bulbs keep their 4096. That costs a few more requests per update (numbers in the description). The Kasa app sends each method in its own request, so it never sends a combined update.

rytilahti reacted with rocket emoji
All reactions
  • 🚀 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ZeliardM
ZeliardM requested a review from rytilahti November 3, 2026 17:14
@rytilahti rytilahti added the bug Something isn't working label Nov 4, 2026
rytilahti
rytilahti approved these changes Nov 4, 2026
View reviewed changes

@rytilahti rytilahti left a comment •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking if we should split some of the tests out from iotdevice tests as they would fit better to the transport tests, but it is fine as-is. Great test additions, thanks!

This likely fixes also #1837?

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@ZeliardM

ZeliardM commented Nov 4, 2026

Copy link
Copy Markdown
Collaborator Author

Not sure if it will resolve that or if 0.12.0 when we removed HomeKit will. I'm asking them to check before we make any other changes. I'm looking into splitting the tests up and will let you know.

rytilahti reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

ZeliardM and others added 2 commits November 4, 2026 15:07
@ZeliardM
Merge branch 'master' into fix/ks200m-smaller-queries
10604c1
@ZeliardM @claude
Move the KLAP budget tests to the KLAP transport tests
ba87779
The 2048 byte budget and the KS200M split only apply over KLAP, so
their tests now sit with the other KLAP tests, as suggested in review.
tests/iot/test_iotdevice.py and tests/iot/test_wallswitch.py are back
to their master versions.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@ZeliardM

ZeliardM commented Nov 4, 2026

Copy link
Copy Markdown
Collaborator Author

I moved the KLAP-only tests into tests/transports/test_klaptransport.py:

  • test_max_device_response_size_klap and test_max_device_response_size_before_update from tests/iot/test_iotdevice.py;
  • test_ks200m_update_split_klap from tests/iot/test_wallswitch.py.

Both IoT test files are back to master, so all of this PR's tests are in the KLAP test file now. I also merged master. Could you take another look?

rytilahti reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ZeliardM
ZeliardM merged commit 9478ed2 into python-kasa:master Nov 5, 2026
26 checks passed

Uh oh!

There was an error while loading. Please reload this page.

@ZeliardM
ZeliardM deleted the fix/ks200m-smaller-queries branch November 5, 2026 02:16
ZeliardM added a commit to ZeliardM/python-kasa that referenced this pull request Nov 5, 2026
@ZeliardM @claude
Merge branch 'master' into feature/tpap
b699b02
Brings in python-kasa#1829 (smaller KLAP IoT update requests and KLAP response
validation). No conflicts.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@bwilliam79

bwilliam79 commented Nov 5, 2026

Copy link
Copy Markdown

Thanks ZeliardM, and thanks again for the fix.

The fork branch had already been merged and deleted, so I ran the test on the final PR head ba87779 (9c2fbad or later). Local size cap was off; plugin was stock homebridge-kasa-python 3.3.0-beta.8.

Early readout (~3.4 h since this morning on six KS200Ms), against the Nov 1-5 baseline on 7688617 (95.5 h: decrypt/offline/200-OK all 0, 2 poll-failed timeouts, 664 toggles):

  • decrypt: 0
  • bad status line / 200 OK page: 0
  • not a KLAP message: 0
  • invalid signature: 0
  • offline: 0
  • timeout / poll-failed: 0
  • toggles: 22

Homebridge and the plugin's Python process stayed up the whole window. All 22 toggles landed after the grace was removed. Looking good so far; happy to keep it running and post a longer readout later if useful.

ZeliardM reacted with hooray emoji
All reactions
  • 🎉 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@rytilahti rytilahti rytilahti approved these changes

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

bug Something isn't working

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

KS200M: combined update gets a plain "200 OK" html page instead of a KLAP response

Uh oh!

There was an error while loading. Please reload this page.

3 participants

@ZeliardM @bwilliam79 @rytilahti
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.