From 7113222ccb797b5f16f10c2f083c1439cc478a73 Mon Sep 17 00:00:00 2001 From: Daniel Jacob Chittoor Date: Tue, 9 Dec 2025 12:15:17 +0530 Subject: [PATCH 1/6] Implement hash verification for flash file --- app/src/controller/downloader.manager.js | 36 ++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/app/src/controller/downloader.manager.js b/app/src/controller/downloader.manager.js index 8940e77..a04e15c 100644 --- a/app/src/controller/downloader.manager.js +++ b/app/src/controller/downloader.manager.js @@ -41,6 +41,22 @@ export class Downloader { const blob = await this.download(file.path, (value, total) => { onDownloadProgress(value, total, file.name); }); + + // Simple zip integrity check: fetch ".sha256sum" and compare. + try { + const expected = await this.fetchChecksum(`${file.path}.sha256sum`); + const actual = await this.computeSha256(blob); + if (expected && actual !== expected) { + throw new Error( + `Checksum mismatch for ${file.name}: expected ${expected} got ${actual}`, + ); + } + } catch (checksumError) { + throw new Error( + `Checksum verification failed for ${file.name}: ${checksumError.message || checksumError}`, + ); + } + if (file.unzip) { const zipReader = new ZipReader(new BlobReader(blob)); const filesEntries = await zipReader.getEntries(); @@ -103,6 +119,26 @@ export class Downloader { return filename; } + async fetchChecksum(url) { + const res = await ky.get(url); + if (!res.ok) { + throw new Error(`Cannot fetch checksum (${res.status})`); + } + const body = (await res.text()).trim(); + const match = body.match(/[a-fA-F0-9]{64}/); + if (!match) { + throw new Error("Invalid checksum content"); + } + return match[0].toLowerCase(); + } + + async computeSha256(blob) { + const buffer = await blob.arrayBuffer(); + const hashBuffer = await crypto.subtle.digest("SHA-256", buffer); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + return hashArray.map((b) => b.toString(16).padStart(2, "0")).join(""); + } + /** * @param name * @returns {} -- GitLab From efad1b532987608471e7c6acd92877e36518a3fa Mon Sep 17 00:00:00 2001 From: Daniel Jacob Chittoor Date: Wed, 24 Dec 2025 13:36:24 +0530 Subject: [PATCH 2/6] Use streaming SHA256 computation --- app/package-lock.json | 653 +++++++++++++++++++++++ app/package.json | 1 + app/src/controller/downloader.manager.js | 22 +- 3 files changed, 672 insertions(+), 4 deletions(-) diff --git a/app/package-lock.json b/app/package-lock.json index 74ff3e5..8b22c3c 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -16,6 +16,7 @@ "@yume-chan/stream-extra": "1.0.0", "@yume-chan/struct": "1.0.0", "@zip.js/zip.js": "^2.7.54", + "hash-wasm": "^4.11.0", "ky": "^1.7.4" }, "devDependencies": { @@ -36,6 +37,278 @@ "pako": "^2.1.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", + "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", + "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", + "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", + "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", + "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", + "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", + "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", + "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", + "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", + "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", + "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", + "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", + "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", + "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", + "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", + "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/linux-x64": { "version": "0.24.2", "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", @@ -53,6 +326,142 @@ "node": ">=18" } }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", + "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", + "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", + "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", + "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", + "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", + "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", + "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", + "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", @@ -297,6 +706,202 @@ "node": ">= 8" } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.29.1.tgz", + "integrity": "sha512-ssKhA8RNltTZLpG6/QNkCSge+7mBQGUqJRisZ2MDQcEGaK93QESEgWK2iOpIDZ7k9zPVkG5AS3ksvD5ZWxmItw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.29.1.tgz", + "integrity": "sha512-CaRfrV0cd+NIIcVVN/jx+hVLN+VRqnuzLRmfmlzpOzB87ajixsN/+9L5xNmkaUUvEbI5BmIKS+XTwXsHEb65Ew==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.29.1.tgz", + "integrity": "sha512-2ORr7T31Y0Mnk6qNuwtyNmy14MunTAMx06VAPI6/Ju52W10zk1i7i5U3vlDRWjhOI5quBcrvhkCHyF76bI7kEw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.29.1.tgz", + "integrity": "sha512-j/Ej1oanzPjmN0tirRd5K2/nncAhS9W6ICzgxV+9Y5ZsP0hiGhHJXZ2JQ53iSSjj8m6cRY6oB1GMzNn2EUt6Ng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.29.1.tgz", + "integrity": "sha512-91C//G6Dm/cv724tpt7nTyP+JdN12iqeXGFM1SqnljCmi5yTXriH7B1r8AD9dAZByHpKAumqP1Qy2vVNIdLZqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.29.1.tgz", + "integrity": "sha512-hEioiEQ9Dec2nIRoeHUP6hr1PSkXzQaCUyqBDQ9I9ik4gCXQZjJMIVzoNLBRGet+hIUb3CISMh9KXuCcWVW/8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.29.1.tgz", + "integrity": "sha512-Py5vFd5HWYN9zxBv3WMrLAXY3yYJ6Q/aVERoeUFwiDGiMOWsMs7FokXihSOaT/PMWUty/Pj60XDQndK3eAfE6A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.29.1.tgz", + "integrity": "sha512-RiWpGgbayf7LUcuSNIbahr0ys2YnEERD4gYdISA06wa0i8RALrnzflh9Wxii7zQJEB2/Eh74dX4y/sHKLWp5uQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.29.1.tgz", + "integrity": "sha512-Z80O+taYxTQITWMjm/YqNoe9d10OX6kDh8X5/rFCMuPqsKsSyDilvfg+vd3iXIqtfmp+cnfL1UrYirkaF8SBZA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.29.1.tgz", + "integrity": "sha512-fOHRtF9gahwJk3QVp01a/GqS4hBEZCV1oKglVVq13kcK3NeVlS4BwIFzOHDbmKzt3i0OuHG4zfRP0YoG5OF/rA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.29.1.tgz", + "integrity": "sha512-5a7q3tnlbcg0OodyxcAdrrCxFi0DgXJSoOuidFUzHZ2GixZXQs6Tc3CHmlvqKAmOs5eRde+JJxeIf9DonkmYkw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.29.1.tgz", + "integrity": "sha512-9b4Mg5Yfz6mRnlSPIdROcfw1BU22FQxmfjlp/CShWwO3LilKQuMISMTtAu/bxmmrE6A902W2cZJuzx8+gJ8e9w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.29.1.tgz", + "integrity": "sha512-G5pn0NChlbRM8OJWpJFMX4/i8OEU538uiSv0P6roZcbpe/WfhEO+AT8SHVKfp8qhDQzaz7Q+1/ixMy7hBRidnQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.29.1.tgz", + "integrity": "sha512-WM9lIkNdkhVwiArmLxFXpWndFGuOka4oJOZh8EP3Vb8q5lzdSCBuhjavJsw68Q9AKDGeOOIHYzYm4ZFvmWez5g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-x64-gnu": { "version": "4.29.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.29.1.tgz", @@ -325,6 +930,48 @@ "linux" ] }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.29.1.tgz", + "integrity": "sha512-F2OiJ42m77lSkizZQLuC+jiZ2cgueWQL5YC9tjo3AgaEw+KJmVxHGSyQfDUoYR9cci0lAywv2Clmckzulcq6ig==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.29.1.tgz", + "integrity": "sha512-rYRe5S0FcjlOBZQHgbTKNrqxCBUmgDJem/VQTCcTnA2KCabYSWQDrytOzX7avb79cAAweNmMUb/Zw18RNd4mng==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.29.1.tgz", + "integrity": "sha512-+10CMg9vt1MoHj6x1pxyjPSMjHTIlqs8/tBztXvPAx24SKs9jwVnKqHJumlH/IzhaPUaj3T6T6wfZr8okdXaIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -1091,6 +1738,12 @@ "node": ">=8" } }, + "node_modules/hash-wasm": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/hash-wasm/-/hash-wasm-4.12.0.tgz", + "integrity": "sha512-+/2B2rYLb48I/evdOIhP+K/DD2ca2fgBjp6O+GBEnCDk2e4rpeXIK8GvIyRPjTezgmWn9gmKwkQjjx6BtqDHVQ==", + "license": "MIT" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", diff --git a/app/package.json b/app/package.json index 2bf463d..7ca4281 100644 --- a/app/package.json +++ b/app/package.json @@ -20,6 +20,7 @@ }, "dependencies": { "@e/fastboot": "1.1.4", + "hash-wasm": "^4.11.0", "@yume-chan/adb": "1.1.0", "@yume-chan/adb-daemon-webusb": "1.1.0", "@yume-chan/adb-credential-web": "1.1.0", diff --git a/app/src/controller/downloader.manager.js b/app/src/controller/downloader.manager.js index a04e15c..f76e5aa 100644 --- a/app/src/controller/downloader.manager.js +++ b/app/src/controller/downloader.manager.js @@ -3,6 +3,7 @@ const DB_VERSION = 1; import ky from "ky"; import { ZipReader, BlobReader, BlobWriter } from "@zip.js/zip.js"; +import { createSHA256 } from "hash-wasm"; /** * Download Manager @@ -132,11 +133,24 @@ export class Downloader { return match[0].toLowerCase(); } + /** + * Streaming SHA-256 computation for large files. + * Processes blob in 16MB chunks to avoid memory limits on low-memory devices. + */ async computeSha256(blob) { - const buffer = await blob.arrayBuffer(); - const hashBuffer = await crypto.subtle.digest("SHA-256", buffer); - const hashArray = Array.from(new Uint8Array(hashBuffer)); - return hashArray.map((b) => b.toString(16).padStart(2, "0")).join(""); + const CHUNK_SIZE = 16 * 1024 * 1024; // 16MB chunks + const hasher = await createSHA256(); + hasher.init(); + + let offset = 0; + while (offset < blob.size) { + const chunk = blob.slice(offset, offset + CHUNK_SIZE); + const buffer = await chunk.arrayBuffer(); + hasher.update(new Uint8Array(buffer)); + offset += CHUNK_SIZE; + } + + return hasher.digest("hex"); } /** -- GitLab From e2ef30a3c08d50636f4c45bb80ea10a8947cbe2a Mon Sep 17 00:00:00 2001 From: "manu.suresh" Date: Wed, 24 Dec 2025 22:41:38 +0530 Subject: [PATCH 3/6] Split hashes of unzipped image files --- app/src/controller/downloader.manager.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/controller/downloader.manager.js b/app/src/controller/downloader.manager.js index f76e5aa..5c405cd 100644 --- a/app/src/controller/downloader.manager.js +++ b/app/src/controller/downloader.manager.js @@ -75,6 +75,8 @@ export class Downloader { if (filesRequired.includes(filename)) { await this.setInDBStore(unzippedEntry.blob, filename); this.stored[filename] = true; + const fileSHA = await this.computeSha256(unzippedEntry.blob); + console.log(`File: ${unzippedEntry.name} SHA256: ${fileSHA}`); } } await zipReader.close(); -- GitLab From 72544b0bb13486a22cb2e2753e0ea4173c90eb0e Mon Sep 17 00:00:00 2001 From: Daniel Jacob Chittoor Date: Tue, 16 Dec 2025 14:34:00 +0530 Subject: [PATCH 4/6] Implement retry logic for failed checksum scenario Introduce a retry mechanism (up to 3 attempts) to re-download the file if the integrity check fails, improving the robustness of the installer against transient network issues. --- app/src/controller/downloader.manager.js | 46 ++++++++++++++++-------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/app/src/controller/downloader.manager.js b/app/src/controller/downloader.manager.js index 5c405cd..c071b3d 100644 --- a/app/src/controller/downloader.manager.js +++ b/app/src/controller/downloader.manager.js @@ -39,23 +39,39 @@ export class Downloader { const file = folder[i]; current_file = file.path; if (filesRequired.includes(file.name) || file.unzip) { - const blob = await this.download(file.path, (value, total) => { - onDownloadProgress(value, total, file.name); - }); + const MAX_RETRIES = 3; + let attempt = 0; + let blob; + let checksumSuccess = false; + + while (attempt < MAX_RETRIES && !checksumSuccess) { + attempt++; + try { + blob = await this.download(file.path, (value, total) => { + onDownloadProgress(value, total, file.name); + }); - // Simple zip integrity check: fetch ".sha256sum" and compare. - try { - const expected = await this.fetchChecksum(`${file.path}.sha256sum`); - const actual = await this.computeSha256(blob); - if (expected && actual !== expected) { - throw new Error( - `Checksum mismatch for ${file.name}: expected ${expected} got ${actual}`, + // Simple zip integrity check: fetch ".sha256sum" and compare. + const expected = await this.fetchChecksum( + `${file.path}.sha256sum`, + ); + const actual = await this.computeSha256(blob); + if (expected && actual !== expected) { + throw new Error( + `Checksum mismatch for ${file.name}: expected ${expected} got ${actual}`, + ); + } + checksumSuccess = true; + } catch (err) { + console.warn( + `Attempt ${attempt}/${MAX_RETRIES} failed for ${file.name}: ${err.message}`, ); + if (attempt >= MAX_RETRIES) { + throw new Error( + `Failed to download and verify ${file.name} after ${MAX_RETRIES} attempts: ${err.message}`, + ); + } } - } catch (checksumError) { - throw new Error( - `Checksum verification failed for ${file.name}: ${checksumError.message || checksumError}`, - ); } if (file.unzip) { @@ -178,7 +194,7 @@ export class Downloader { onprogress: (value, total) => { onProgress(value, total, dbFile); }, - onend: () => {}, + onend: () => { }, useWebWorkers: true, }); return blob; -- GitLab From 238b7979359ef6428a2e9587c8139f135ef3da2a Mon Sep 17 00:00:00 2001 From: "manu.suresh" Date: Thu, 25 Dec 2025 16:54:50 +0530 Subject: [PATCH 5/6] Show hash verification progress to user --- app/src/controller.manager.js | 3 +++ app/src/controller/device.manager.js | 3 ++- app/src/controller/downloader.manager.js | 15 +++++++++++---- app/src/viewManager.js | 15 +++++++++++++++ 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/app/src/controller.manager.js b/app/src/controller.manager.js index 5573a4d..bfe32a7 100644 --- a/app/src/controller.manager.js +++ b/app/src/controller.manager.js @@ -123,6 +123,9 @@ export class Controller { (loaded, total, name) => { this.view.onUnzip(name, loaded, total); }, + (loaded, total, name) => { + this.view.onVerify(name, loaded, total); + }, ); this.view.onDownloadingEnd(); return true; diff --git a/app/src/controller/device.manager.js b/app/src/controller/device.manager.js index 981d2c6..74aff73 100644 --- a/app/src/controller/device.manager.js +++ b/app/src/controller/device.manager.js @@ -185,13 +185,14 @@ export class DeviceManager { } } - async downloadAll(onProgress, onUnzip) { + async downloadAll(onProgress, onUnzip, onVerify) { try { await this.downloader.downloadAndUnzipFolder( this.files, this.folder, onProgress, onUnzip, + onVerify, ); } catch (e) { throw new Error(`downloadAll error ${e.message || e}`); diff --git a/app/src/controller/downloader.manager.js b/app/src/controller/downloader.manager.js index c071b3d..6276f75 100644 --- a/app/src/controller/downloader.manager.js +++ b/app/src/controller/downloader.manager.js @@ -32,6 +32,7 @@ export class Downloader { folder, onDownloadProgress, onUnzipProgress, + onVerifyProgress, ) { let current_file; try { @@ -55,7 +56,9 @@ export class Downloader { const expected = await this.fetchChecksum( `${file.path}.sha256sum`, ); - const actual = await this.computeSha256(blob); + const actual = await this.computeSha256(blob, (loaded, total) => { + onVerifyProgress(loaded, total, file.name) + }); if (expected && actual !== expected) { throw new Error( `Checksum mismatch for ${file.name}: expected ${expected} got ${actual}`, @@ -91,7 +94,9 @@ export class Downloader { if (filesRequired.includes(filename)) { await this.setInDBStore(unzippedEntry.blob, filename); this.stored[filename] = true; - const fileSHA = await this.computeSha256(unzippedEntry.blob); + const fileSHA = await this.computeSha256(unzippedEntry.blob, (loaded, total) => { + onVerifyProgress(loaded, total, filename) + }); console.log(`File: ${unzippedEntry.name} SHA256: ${fileSHA}`); } } @@ -155,16 +160,18 @@ export class Downloader { * Streaming SHA-256 computation for large files. * Processes blob in 16MB chunks to avoid memory limits on low-memory devices. */ - async computeSha256(blob) { + async computeSha256(blob, onVerifyProgress) { const CHUNK_SIZE = 16 * 1024 * 1024; // 16MB chunks const hasher = await createSHA256(); + const blobSize = blob.size; hasher.init(); let offset = 0; - while (offset < blob.size) { + while (offset < blobSize) { const chunk = blob.slice(offset, offset + CHUNK_SIZE); const buffer = await chunk.arrayBuffer(); hasher.update(new Uint8Array(buffer)); + onVerifyProgress(offset, blobSize); offset += CHUNK_SIZE; } diff --git a/app/src/viewManager.js b/app/src/viewManager.js index c0fc89c..d122881 100644 --- a/app/src/viewManager.js +++ b/app/src/viewManager.js @@ -145,6 +145,21 @@ export default class ViewManager { this.WDebug.log(`Downloading ${name}: ${v}/${100}`, `downloading-${name}`); } + onVerify(name, loaded, total) { + const v = Math.round((loaded / total) * 100); + let $progressBar = document.querySelector( + `.active .downloading-progress-bar`, + ); + let $progress = document.querySelector(`.active .downloading-progress`); + if ($progressBar) { + $progressBar.value = v; + } + if ($progress) { + $progress.innerText = `Verifying ${name}: ${v}/${100}`; + } + this.WDebug.log(`Verifying ${name}: ${v}/${100}`, `verifying-${name}`); + } + onUnzip(name, loaded, total) { const v = Math.round((loaded / total) * 100); let $progressBar = document.querySelector( -- GitLab From 37b7800f45b87919ba817be10897fd057232d125 Mon Sep 17 00:00:00 2001 From: "manu.suresh" Date: Thu, 25 Dec 2025 17:42:38 +0530 Subject: [PATCH 6/6] web-installer: format code with `npm run format` --- app/src/controller/downloader.manager.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/app/src/controller/downloader.manager.js b/app/src/controller/downloader.manager.js index 6276f75..4a2937c 100644 --- a/app/src/controller/downloader.manager.js +++ b/app/src/controller/downloader.manager.js @@ -57,7 +57,7 @@ export class Downloader { `${file.path}.sha256sum`, ); const actual = await this.computeSha256(blob, (loaded, total) => { - onVerifyProgress(loaded, total, file.name) + onVerifyProgress(loaded, total, file.name); }); if (expected && actual !== expected) { throw new Error( @@ -94,9 +94,12 @@ export class Downloader { if (filesRequired.includes(filename)) { await this.setInDBStore(unzippedEntry.blob, filename); this.stored[filename] = true; - const fileSHA = await this.computeSha256(unzippedEntry.blob, (loaded, total) => { - onVerifyProgress(loaded, total, filename) - }); + const fileSHA = await this.computeSha256( + unzippedEntry.blob, + (loaded, total) => { + onVerifyProgress(loaded, total, filename); + }, + ); console.log(`File: ${unzippedEntry.name} SHA256: ${fileSHA}`); } } @@ -201,7 +204,7 @@ export class Downloader { onprogress: (value, total) => { onProgress(value, total, dbFile); }, - onend: () => { }, + onend: () => {}, useWebWorkers: true, }); return blob; -- GitLab