Skip to content

Commit bff2803

Browse files
committed
More data from download, more telemetry on hash mismatch
1 parent fdb6e60 commit bff2803

28 files changed

+267
-115
lines changed

.github/actions/spelling/expect.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ decompressor
126126
dedupe
127127
deigh
128128
deleteifnotneeded
129+
deliveryoptimization
130+
deliveryoptimizationerrors
129131
DENYWR
130132
desktopappinstaller
131133
devhome
@@ -404,6 +406,7 @@ PACL
404406
PARAMETERMAP
405407
pathparts
406408
Patil
409+
pbstr
407410
pcb
408411
PCCERT
409412
PCs

src/AppInstallerCLICore/ExecutionContextData.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33
#pragma once
4+
#include <AppInstallerDownloader.h>
45
#include <winget/RepositorySource.h>
56
#include <winget/Manifest.h>
67
#include <winget/ARPCorrelation.h>
@@ -34,7 +35,7 @@ namespace AppInstaller::CLI::Execution
3435
Manifest,
3536
PackageVersion,
3637
Installer,
37-
HashPair,
38+
DownloadHashInfo,
3839
InstallerPath,
3940
LogPath,
4041
InstallerArgs,
@@ -128,9 +129,9 @@ namespace AppInstaller::CLI::Execution
128129
};
129130

130131
template <>
131-
struct DataMapping<Data::HashPair>
132+
struct DataMapping<Data::DownloadHashInfo>
132133
{
133-
using value_t = std::pair<std::vector<uint8_t>, std::vector<uint8_t>>;
134+
using value_t = std::pair<std::vector<uint8_t>, Utility::DownloadResult>;
134135
};
135136

136137
template <>

src/AppInstallerCLICore/Resources.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ namespace AppInstaller::CLI::Resource
308308
WINGET_DEFINE_RESOURCE_STRINGID(InstallersAbortTerminal);
309309
WINGET_DEFINE_RESOURCE_STRINGID(InstallersRequireInstallLocation);
310310
WINGET_DEFINE_RESOURCE_STRINGID(InstallerTypeArgumentDescription);
311+
WINGET_DEFINE_RESOURCE_STRINGID(InstallerZeroByteFile);
311312
WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowInstallSuccess);
312313
WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowRegistrationDeferred);
313314
WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeAlreadyInstalled);

src/AppInstallerCLICore/Workflows/DownloadFlow.cpp

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ namespace AppInstaller::CLI::Workflow
293293

294294
AICLI_LOG(CLI, Info, << "Existing installer file hash matches. Will use existing installer.");
295295
context.Add<Execution::Data::InstallerPath>(installerPath / installerFilename);
296-
context.Add<Execution::Data::HashPair>(std::make_pair(installer.Sha256, fileHash));
296+
context.Add<Execution::Data::DownloadHashInfo>(std::make_pair(installer.Sha256, DownloadResult{ fileHash }));
297297
}
298298

299299
void GetInstallerDownloadPath(Execution::Context& context)
@@ -325,7 +325,7 @@ namespace AppInstaller::CLI::Workflow
325325

326326
context.Reporter.Info() << Resource::String::Downloading << ' ' << Execution::UrlEmphasis << installer.Url << std::endl;
327327

328-
std::optional<std::vector<BYTE>> hash;
328+
DownloadResult downloadResult;
329329

330330
constexpr int MaxRetryCount = 2;
331331
constexpr std::chrono::seconds maximumWaitTimeAllowed = 60s;
@@ -334,15 +334,22 @@ namespace AppInstaller::CLI::Workflow
334334
bool success = false;
335335
try
336336
{
337-
hash = context.Reporter.ExecuteWithProgress(std::bind(Utility::Download,
337+
downloadResult = context.Reporter.ExecuteWithProgress(std::bind(Utility::Download,
338338
installer.Url,
339339
installerPath,
340340
Utility::DownloadType::Installer,
341341
std::placeholders::_1,
342-
true,
343342
downloadInfo));
344343

345-
success = true;
344+
if (downloadResult.SizeInBytes == 0)
345+
{
346+
AICLI_LOG(CLI, Info, << "Got zero byte file; retrying download after a short wait...");
347+
std::this_thread::sleep_for(5s);
348+
}
349+
else
350+
{
351+
success = true;
352+
}
346353
}
347354
catch (const ServiceUnavailableException& sue)
348355
{
@@ -388,13 +395,13 @@ namespace AppInstaller::CLI::Workflow
388395
}
389396
}
390397

391-
if (!hash)
398+
if (downloadResult.Sha256Hash.empty())
392399
{
393400
context.Reporter.Info() << Resource::String::Cancelled << std::endl;
394401
AICLI_TERMINATE_CONTEXT(E_ABORT);
395402
}
396403

397-
context.Add<Execution::Data::HashPair>(std::make_pair(installer.Sha256, hash.value()));
404+
context.Add<Execution::Data::DownloadHashInfo>(std::make_pair(installer.Sha256, downloadResult));
398405
}
399406

400407
void GetMsixSignatureHash(Execution::Context& context)
@@ -410,7 +417,7 @@ namespace AppInstaller::CLI::Workflow
410417
Msix::MsixInfo msixInfo(installer.Url);
411418
auto signatureHash = msixInfo.GetSignatureHash();
412419

413-
context.Add<Execution::Data::HashPair>(std::make_pair(installer.SignatureSha256, signatureHash));
420+
context.Add<Execution::Data::DownloadHashInfo>(std::make_pair(installer.SignatureSha256, DownloadResult{ signatureHash }));
414421
context.Add<Execution::Data::MsixDigests>({ std::make_pair(installer.Url, msixInfo.GetDigest()) });
415422
}
416423
catch (...)
@@ -427,17 +434,23 @@ namespace AppInstaller::CLI::Workflow
427434

428435
void VerifyInstallerHash(Execution::Context& context)
429436
{
430-
const auto& hashPair = context.Get<Execution::Data::HashPair>();
437+
const auto& [expectedHash, downloadResult] = context.Get<Execution::Data::DownloadHashInfo>();
431438

432439
if (!std::equal(
433-
hashPair.first.begin(),
434-
hashPair.first.end(),
435-
hashPair.second.begin()))
440+
expectedHash.begin(),
441+
expectedHash.end(),
442+
downloadResult.Sha256Hash.begin()))
436443
{
437444
bool overrideHashMismatch = context.Args.Contains(Execution::Args::Type::HashOverride);
438445

439446
const auto& manifest = context.Get<Execution::Data::Manifest>();
440-
Logging::Telemetry().LogInstallerHashMismatch(manifest.Id, manifest.Version, manifest.Channel, hashPair.first, hashPair.second, overrideHashMismatch);
447+
Logging::Telemetry().LogInstallerHashMismatch(manifest.Id, manifest.Version, manifest.Channel, expectedHash, downloadResult.Sha256Hash, overrideHashMismatch, downloadResult.SizeInBytes, downloadResult.ContentType);
448+
449+
if (downloadResult.SizeInBytes == 0)
450+
{
451+
context.Reporter.Error() << Resource::String::InstallerZeroByteFile << std::endl;
452+
AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALLER_ZERO_BYTE_FILE);
453+
}
441454

442455
// If running as admin, do not allow the user to override the hash failure.
443456
if (Runtime::IsRunningAsAdmin())
@@ -527,7 +540,7 @@ namespace AppInstaller::CLI::Workflow
527540
const auto& installerPath = context.Get<Execution::Data::InstallerPath>();
528541
std::ifstream inStream{ installerPath, std::ifstream::binary };
529542
auto existingFileHash = SHA256::ComputeHash(inStream);
530-
context.Add<Execution::Data::HashPair>(std::make_pair(installer.Sha256, existingFileHash));
543+
context.Add<Execution::Data::DownloadHashInfo>(std::make_pair(installer.Sha256, DownloadResult{ existingFileHash }));
531544
}
532545
else if (installer.EffectiveInstallerType() == InstallerTypeEnum::MSStore)
533546
{

src/AppInstallerCLICore/Workflows/MSStoreInstallerHandler.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,8 @@ namespace AppInstaller::CLI::Workflow
101101
}
102102

103103
// Verify hash
104-
const auto& hashPair = subContext.Get<Execution::Data::HashPair>();
105-
if (std::equal(hashPair.first.begin(), hashPair.first.end(), hashPair.second.begin()))
104+
const auto& hashPair = subContext.Get<Execution::Data::DownloadHashInfo>();
105+
if (std::equal(hashPair.first.begin(), hashPair.first.end(), hashPair.second.Sha256Hash.begin()))
106106
{
107107
AICLI_LOG(CLI, Info, << "Microsoft Store package hash verified");
108108
subContext.Reporter.Info() << Resource::String::MSStoreDownloadPackageHashVerified << std::endl;

src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
<ItemGroup>
5656
<None Remove="TestData\Configuration\ShowDetails_TestRepo_0_3.yml" />
5757
<None Remove="TestData\Configuration\WithParameters_0_3.yml" />
58+
<None Remove="TestData\empty" />
5859
<None Remove="TestData\Manifests\TestUpgradeAddsDependency.1.0.yaml" />
5960
<None Remove="TestData\Manifests\TestUpgradeAddsDependency.2.0.yaml" />
6061
<None Remove="TestData\Manifests\TestUpgradeAddsDependencyDependent.1.0.yaml" />

src/AppInstallerCLIE2ETests/Constants.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,8 @@ public class ErrorCode
269269
public const int ERROR_REPAIR_NOT_SUPPORTED = unchecked((int)0x8A15007C);
270270
public const int ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED = unchecked((int)0x8A15007D);
271271

272+
public const int ERROR_INSTALLER_ZERO_BYTE_FILE = unchecked((int)0x8A150086);
273+
272274
public const int ERROR_INSTALL_PACKAGE_IN_USE = unchecked((int)0x8A150101);
273275
public const int ERROR_INSTALL_INSTALL_IN_PROGRESS = unchecked((int)0x8A150102);
274276
public const int ERROR_INSTALL_FILE_IN_USE = unchecked((int)0x8A150103);

src/AppInstallerCLIE2ETests/DownloadCommand.cs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,5 +152,28 @@ public void DownloadWithHashMismatch()
152152
var errorResult = TestCommon.RunAICLICommand("download", $"AppInstallerTest.TestExeSha256Mismatch --download-directory {downloadDir}");
153153
Assert.AreEqual(Constants.ErrorCode.ERROR_INSTALLER_HASH_MISMATCH, errorResult.ExitCode);
154154
}
155+
156+
/// <summary>
157+
/// Downloads the zero byte test installer with a hash mismatch.
158+
/// </summary>
159+
[Test]
160+
public void DownloadZeroByteFileWithHashMismatch()
161+
{
162+
var downloadDir = TestCommon.GetRandomTestDir();
163+
var errorResult = TestCommon.RunAICLICommand("download", $"ZeroByteFile.IncorrectHash --download-directory {downloadDir}");
164+
Assert.AreEqual(Constants.ErrorCode.ERROR_INSTALLER_ZERO_BYTE_FILE, errorResult.ExitCode);
165+
}
166+
167+
/// <summary>
168+
/// Downloads the zero byte test installer.
169+
/// </summary>
170+
[Test]
171+
public void DownloadZeroByteFile()
172+
{
173+
var downloadDir = TestCommon.GetRandomTestDir();
174+
var result = TestCommon.RunAICLICommand("download", $"ZeroByteFile.CorrectHash --download-directory {downloadDir}");
175+
Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode);
176+
Assert.True(TestCommon.VerifyInstallerDownload(downloadDir, "ZeroByteFile CorrectHash", "1.0.0.0", ProcessorArchitecture.X86, TestCommon.Scope.Unknown, PackageInstallerType.Exe));
177+
}
155178
}
156-
}
179+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
PackageIdentifier: ZeroByteFile.CorrectHash
2+
PackageVersion: 1.0.0.0
3+
PackageLocale: en-US
4+
PackageName: ZeroByteFile CorrectHash
5+
ShortDescription: Points to a zero byte installer file using the correct hash
6+
Publisher: Microsoft Corporation
7+
License: Test
8+
Installers:
9+
- Architecture: x86
10+
InstallerUrl: https://localhost:5001/TestKit/empty
11+
InstallerType: exe
12+
InstallerSha256: E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855
13+
ManifestType: singleton
14+
ManifestVersion: 1.6.0
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
PackageIdentifier: ZeroByteFile.IncorrectHash
2+
PackageVersion: 1.0.0.0
3+
PackageLocale: en-US
4+
PackageName: ZeroByteFile IncorrectHash
5+
ShortDescription: Points to a zero byte installer file using the incorrect hash
6+
Publisher: Microsoft Corporation
7+
License: Test
8+
Installers:
9+
- Architecture: x86
10+
InstallerUrl: https://localhost:5001/TestKit/empty
11+
InstallerType: exe
12+
InstallerSha256: BAD0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852BBAD
13+
ManifestType: singleton
14+
ManifestVersion: 1.6.0

0 commit comments

Comments
 (0)