Skip to content

[Bug]: Harden backup pipeline — frame size, base64 strictness, integrity check #30

@tashda

Description

@tashda

Summary

Three latent issues in the backup save path can produce silent failures or corrupt backups under real-world conditions. Backups are a data-integrity feature — they need to be 100% bulletproof before users rely on them.

The current pipeline is pure pass-through (decode base64 → atomic write), which is correct in spirit, but three guardrails are missing.

Issues

1. WebSocket frame size limit (highest impact)

Z2MWebSocketClient does not set maximumMessageSize on its URLSessionWebSocketTask. The default is 1 MB.

A populated Z2M install (30+ devices, months of state, rotated config backups) easily produces a 3–10 MB backup zip. Once base64-encoded inside the JSON envelope (+33% inflation), the WS frame crosses 1 MB and the receive call fails — the user sees a generic error or a disconnect, with no way to retry into a working state.

Fix: raise maximumMessageSize on connect (e.g. 64 MB).

2. Strict base64 decoding

BackupView.saveBackup calls Data(base64Encoded: base64) with default options, which rejects any whitespace or newline characters. Some MQTT/WS serializers insert line breaks in long strings. If that ever happens upstream, the decode silently returns nil and the user sees "Invalid base64 zip" with no actionable info.

Fix: pass .ignoreUnknownCharacters.

3. No post-write integrity check

After writing the zip, we report success based solely on the file existing. A truncated, empty, or corrupt file would still show "Backup ready" — and the user only discovers it when restore fails months later.

Fix: after data.write, verify:

  • data.count > 0
  • First 4 bytes are 50 4B 03 04 (ZIP local file header magic)

If either fails, surface a failed status and delete the partial file.

Code references

  • Shellbee/Features/Settings/Backup/BackupView.swiftsaveBackup(base64:) and triggerBackup()
  • Shellbee/Core/Networking/Z2MWebSocketClient.swift — WS task configuration (no maximumMessageSize set today)
  • Shellbee/Core/Store/AppStore.swiftbackupResponseHandler dispatch (line ~204)

Test plan

  • Add a unit test that feeds saveBackup a known-good base64 zip → asserts file exists, size matches, magic bytes correct.
  • Add a unit test for the corruption path: truncated base64, base64 with embedded newlines (passes with .ignoreUnknownCharacters), and a non-zip payload — assert each path either succeeds or returns a failed status, never a false "ready".
  • Manual: drive seeder to emit a backup >1 MB (inflate fixture state) and confirm Shellbee receives it without disconnect after the maximumMessageSize change.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions