Skip to content

Commit 704eb46

Browse files
authored
Verify backup file after backup (#107)
* Add `CcuBackupVerifier` for HomeMatic CCU backup validation and integrate with `FirmwareBackupClient`. Add tests for backup verification logic. * Enhance `CcuBackupVerifier` to validate additional HomeMatic CCU backup entries (`usr_local.tar.gz`, `firmware_version`, and `key_index`) and improve error specificity. Update tests to cover new validation logic comprehensively. * Add tests for `BackupCcuCommand` including `SkipVerify` support and update solution with corresponding test project.
1 parent bc9896d commit 704eb46

18 files changed

Lines changed: 1722 additions & 58 deletions

.serena/project.yml

Lines changed: 40 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,27 @@ project_name: "HomeMatic"
33

44

55
# list of languages for which language servers are started; choose from:
6-
# al bash clojure cpp csharp
7-
# csharp_omnisharp dart elixir elm erlang
8-
# fortran fsharp go groovy haskell
9-
# haxe java julia kotlin lua
10-
# markdown
11-
# matlab nix pascal perl php
12-
# php_phpactor powershell python python_jedi r
13-
# rego ruby ruby_solargraph rust scala
14-
# swift terraform toml typescript typescript_vts
15-
# vue yaml zig
6+
# al angular ansible bash clojure
7+
# cpp cpp_ccls crystal csharp csharp_omnisharp
8+
# dart elixir elm erlang fortran
9+
# fsharp go groovy haskell haxe
10+
# hlsl html java json julia
11+
# kotlin lean4 lua luau markdown
12+
# matlab msl nix ocaml pascal
13+
# perl php php_phpactor powershell python
14+
# python_jedi python_ty r rego ruby
15+
# ruby_solargraph rust scala scss solidity
16+
# svelte swift systemverilog terraform toml
17+
# typescript typescript_vts vue yaml zig
1618
# (This list may be outdated. For the current list, see values of Language enum here:
1719
# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py
1820
# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
1921
# Note:
2022
# - For C, use cpp
2123
# - For JavaScript, use typescript
24+
# - For Angular projects, use angular (subsumes typescript+html; requires `npm install` in the project root)
25+
# - For Svelte projects, use svelte (subsumes typescript/javascript for .svelte projects; requires npm)
26+
# - For SCSS / Sass / plain CSS, use scss (some-sass-language-server handles all three)
2227
# - For Free Pascal/Lazarus, use pascal
2328
# Special requirements:
2429
# Some languages require additional setup/installations.
@@ -66,54 +71,17 @@ read_only: false
6671

6772
# list of tool names to exclude.
6873
# This extends the existing exclusions (e.g. from the global configuration)
69-
#
70-
# Below is the complete list of tools for convenience.
71-
# To make sure you have the latest list of tools, and to view their descriptions,
72-
# execute `uv run scripts/print_tool_overview.py`.
73-
#
74-
# * `activate_project`: Activates a project based on the project name or path.
75-
# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
76-
# * `create_text_file`: Creates/overwrites a file in the project directory.
77-
# * `delete_memory`: Delete a memory file. Should only happen if a user asks for it explicitly,
78-
# for example by saying that the information retrieved from a memory file is no longer correct
79-
# or no longer relevant for the project.
80-
# * `edit_memory`: Replaces content matching a regular expression in a memory.
81-
# * `execute_shell_command`: Executes a shell command.
82-
# * `find_file`: Finds files in the given relative paths
83-
# * `find_referencing_symbols`: Finds symbols that reference the given symbol using the language server backend
84-
# * `find_symbol`: Performs a global (or local) search using the language server backend.
85-
# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
86-
# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
87-
# * `initial_instructions`: Provides instructions Serena usage (i.e. the 'Serena Instructions Manual')
88-
# for clients that do not read the initial instructions when the MCP server is connected.
89-
# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
90-
# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
91-
# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
92-
# * `list_memories`: List available memories. Any memory can be read using the `read_memory` tool.
93-
# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
94-
# * `read_file`: Reads a file within the project directory.
95-
# * `read_memory`: Read the content of a memory file. This tool should only be used if the information
96-
# is relevant to the current task. You can infer whether the information
97-
# is relevant from the memory file name.
98-
# You should not read the same memory file multiple times in the same conversation.
99-
# * `rename_memory`: Renames or moves a memory. Moving between project and global scope is supported
100-
# (e.g., renaming "global/foo" to "bar" moves it from global to project scope).
101-
# * `rename_symbol`: Renames a symbol throughout the codebase using language server refactoring capabilities.
102-
# For JB, we use a separate tool.
103-
# * `replace_content`: Replaces content in a file (optionally using regular expressions).
104-
# * `replace_symbol_body`: Replaces the full definition of a symbol using the language server backend.
105-
# * `safe_delete_symbol`:
106-
# * `search_for_pattern`: Performs a search for a pattern in the project.
107-
# * `write_memory`: Write some information (utf-8-encoded) about this project that can be useful for future tasks to a memory in md format.
108-
# The memory name should be meaningful.
74+
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
10975
excluded_tools: []
11076

11177
# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default).
11278
# This extends the existing inclusions (e.g. from the global configuration).
79+
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
11380
included_optional_tools: []
11481

11582
# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
11683
# This cannot be combined with non-empty excluded_tools or included_optional_tools.
84+
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
11785
fixed_tools: []
11886

11987
# list of mode names to that are always to be included in the set of active modes
@@ -124,11 +92,14 @@ fixed_tools: []
12492
# Set this to a list of mode names to always include the respective modes for this project.
12593
base_modes:
12694

127-
# list of mode names that are to be activated by default.
128-
# The full set of modes to be activated is base_modes + default_modes.
129-
# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply.
95+
# list of mode names that are to be activated by default, overriding the setting in the global configuration.
96+
# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes.
97+
# If the setting is undefined/empty, the default_modes from the global configuration (serena_config.yml) apply.
13098
# Otherwise, this overrides the setting from the global configuration (serena_config.yml).
99+
# Therefore, you can set this to [] if you do not want the default modes defined in the global config to apply
100+
# for this project.
131101
# This setting can, in turn, be overridden by CLI parameters (--mode).
102+
# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes
132103
default_modes:
133104

134105
# initial prompt for the project. It will always be given to the LLM upon activating the project
@@ -152,3 +123,19 @@ read_only_memory_patterns: []
152123
# Extends the list from the global configuration, merging the two lists.
153124
# Example: ["_archive/.*", "_episodes/.*"]
154125
ignored_memory_patterns: []
126+
127+
# list of mode names to be activated additionally for this project, e.g. ["query-projects"]
128+
# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes.
129+
# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes
130+
added_modes:
131+
132+
# list of additional workspace folder paths for cross-package reference support (e.g. in monorepos).
133+
# Paths can be absolute or relative to the project root.
134+
# Each folder is registered as an LSP workspace folder, enabling language servers to discover
135+
# symbols and references across package boundaries.
136+
# Currently supported for: TypeScript.
137+
# Example:
138+
# additional_workspace_folders:
139+
# - ../sibling-package
140+
# - ../shared-lib
141+
additional_workspace_folders: []

HomeMatic.sln

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CreativeCoders.HomeMatic.To
8484
EndProject
8585
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CreativeCoders.HomeMatic.XmlRpc.Tests", "tests\CreativeCoders.HomeMatic.XmlRpc.Tests\CreativeCoders.HomeMatic.XmlRpc.Tests.csproj", "{5614CD87-E146-4D66-A199-5F110B2A0445}"
8686
EndProject
87+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CreativeCoders.HomeMatic.Tools.Cli.Commands.Tests", "tests\CreativeCoders.HomeMatic.Tools.Cli.Commands.Tests\CreativeCoders.HomeMatic.Tools.Cli.Commands.Tests.csproj", "{E5300021-4351-4729-8026-3DBD1094E736}"
88+
EndProject
8789
Global
8890
GlobalSection(SolutionConfigurationPlatforms) = preSolution
8991
Debug|Any CPU = Debug|Any CPU
@@ -248,6 +250,18 @@ Global
248250
{5614CD87-E146-4D66-A199-5F110B2A0445}.Release|x64.Build.0 = Release|Any CPU
249251
{5614CD87-E146-4D66-A199-5F110B2A0445}.Release|x86.ActiveCfg = Release|Any CPU
250252
{5614CD87-E146-4D66-A199-5F110B2A0445}.Release|x86.Build.0 = Release|Any CPU
253+
{E5300021-4351-4729-8026-3DBD1094E736}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
254+
{E5300021-4351-4729-8026-3DBD1094E736}.Debug|Any CPU.Build.0 = Debug|Any CPU
255+
{E5300021-4351-4729-8026-3DBD1094E736}.Debug|x64.ActiveCfg = Debug|Any CPU
256+
{E5300021-4351-4729-8026-3DBD1094E736}.Debug|x64.Build.0 = Debug|Any CPU
257+
{E5300021-4351-4729-8026-3DBD1094E736}.Debug|x86.ActiveCfg = Debug|Any CPU
258+
{E5300021-4351-4729-8026-3DBD1094E736}.Debug|x86.Build.0 = Debug|Any CPU
259+
{E5300021-4351-4729-8026-3DBD1094E736}.Release|Any CPU.ActiveCfg = Release|Any CPU
260+
{E5300021-4351-4729-8026-3DBD1094E736}.Release|Any CPU.Build.0 = Release|Any CPU
261+
{E5300021-4351-4729-8026-3DBD1094E736}.Release|x64.ActiveCfg = Release|Any CPU
262+
{E5300021-4351-4729-8026-3DBD1094E736}.Release|x64.Build.0 = Release|Any CPU
263+
{E5300021-4351-4729-8026-3DBD1094E736}.Release|x86.ActiveCfg = Release|Any CPU
264+
{E5300021-4351-4729-8026-3DBD1094E736}.Release|x86.Build.0 = Release|Any CPU
251265
EndGlobalSection
252266
GlobalSection(SolutionProperties) = preSolution
253267
HideSolutionNode = FALSE
@@ -270,6 +284,7 @@ Global
270284
{822ECD72-5DB0-4637-B794-CE27B02827AC} = {B79F3B3E-C9CE-4629-ADE3-B1659AF9C673}
271285
{E3F3D28A-919C-4283-AEBD-A915A3EFE047} = {5BD797BA-4D66-4F55-A55E-5F1063678D8B}
272286
{5614CD87-E146-4D66-A199-5F110B2A0445} = {5BD797BA-4D66-4F55-A55E-5F1063678D8B}
287+
{E5300021-4351-4729-8026-3DBD1094E736} = {5BD797BA-4D66-4F55-A55E-5F1063678D8B}
273288
EndGlobalSection
274289
GlobalSection(ExtensibilityGlobals) = postSolution
275290
SolutionGuid = {3E5E58EB-0096-4ED2-B1DE-D7FC5951CAB7}

source/CreativeCoders.HomeMatic/CreativeCoders.HomeMatic.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
<PackageReference Include="Microsoft.Extensions.Http"/>
1010
</ItemGroup>
1111

12+
<ItemGroup>
13+
<InternalsVisibleTo Include="CreativeCoders.HomeMatic.Tests"/>
14+
<InternalsVisibleTo Include="DynamicProxyGenAssembly2"/>
15+
</ItemGroup>
16+
1217
<ItemGroup>
1318
<ProjectReference Include="..\CreativeCoders.HomeMatic.Core\CreativeCoders.HomeMatic.Core.csproj"/>
1419
<ProjectReference Include="..\CreativeCoders.HomeMatic.JsonRpc\CreativeCoders.HomeMatic.JsonRpc.csproj"/>

source/CreativeCoders.HomeMatic/FirmwareBackup/FirmwareBackupClient.cs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,20 @@ public sealed class FirmwareBackupClient : IFirmwareBackupClient
1212
{
1313
private readonly ICcuSessionClient _sessionClient;
1414
private readonly IFirmwareBackupDownloader _downloader;
15+
private readonly ICcuBackupVerifier _verifier;
1516
private readonly FirmwareBackupOptions _options;
1617
private readonly IFileSystem _fileSystem;
1718

1819
internal FirmwareBackupClient(
1920
ICcuSessionClient sessionClient,
2021
IFirmwareBackupDownloader downloader,
22+
ICcuBackupVerifier verifier,
2123
FirmwareBackupOptions options,
2224
IFileSystem fileSystem)
2325
{
2426
_sessionClient = Ensure.NotNull(sessionClient);
2527
_downloader = Ensure.NotNull(downloader);
28+
_verifier = Ensure.NotNull(verifier);
2629
_options = Ensure.NotNull(options);
2730
_fileSystem = Ensure.NotNull(fileSystem);
2831
}
@@ -38,11 +41,36 @@ public async Task<FirmwareBackupResult> CreateBackupAsync(CancellationToken canc
3841
{
3942
var download = await _downloader.DownloadAsync(sessionId, cancellationToken).ConfigureAwait(false);
4043

44+
var capacity = download.ContentLength is > 0 and <= int.MaxValue
45+
? (int)download.ContentLength.Value
46+
: 0;
47+
var content = new MemoryStream(capacity);
48+
49+
try
50+
{
51+
await using (var httpResources = download.HttpResources.ConfigureAwait(false))
52+
{
53+
await download.Content.CopyToAsync(content, cancellationToken).ConfigureAwait(false);
54+
}
55+
56+
if (_options.VerifyBackup)
57+
{
58+
content.Position = 0;
59+
await _verifier.VerifyAsync(content, cancellationToken).ConfigureAwait(false);
60+
}
61+
62+
content.Position = 0;
63+
}
64+
catch
65+
{
66+
await content.DisposeAsync().ConfigureAwait(false);
67+
throw;
68+
}
69+
4170
return new FirmwareBackupResult(
42-
download.Content,
71+
content,
4372
download.FileName,
4473
download.ContentLength,
45-
download.HttpResources,
4674
new LogoutDisposable(_sessionClient, sessionId));
4775
}
4876
catch

source/CreativeCoders.HomeMatic/FirmwareBackup/FirmwareBackupClientFactory.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ public IFirmwareBackupClient Create(FirmwareBackupOptions options)
5757
options.BackupCgiPath,
5858
options.BackupAction);
5959

60-
return new FirmwareBackupClient(sessionClient, downloader, options, _fileSystem);
60+
var verifier = new CcuBackupVerifier();
61+
62+
return new FirmwareBackupClient(sessionClient, downloader, verifier, options, _fileSystem);
6163
}
6264
}

source/CreativeCoders.HomeMatic/FirmwareBackup/FirmwareBackupOptions.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ public FirmwareBackupOptions(Uri baseUrl, NetworkCredential credential)
5454
/// </summary>
5555
public bool AcceptAnyServerCertificate { get; set; } = true;
5656

57+
/// <summary>
58+
/// Gets or sets a value indicating whether the downloaded backup is verified after the download.
59+
/// Default: <see langword="true"/>.
60+
/// </summary>
61+
public bool VerifyBackup { get; set; } = true;
62+
5763
/// <summary>
5864
/// Gets or sets the request timeout used for both the JSON-RPC and the CGI download call.
5965
/// Default: 5 minutes (creating a backup on the CCU can take a while).

0 commit comments

Comments
 (0)