diff --git a/.github/memory-leak-baseline.csv b/.github/memory-leak-baseline.csv new file mode 100644 index 000000000..8bad9a45c --- /dev/null +++ b/.github/memory-leak-baseline.csv @@ -0,0 +1,7 @@ +Platform,Scenario,UniqueLeaks,TotalLeaks,LeakBytes,UniquePossibleLeaks,TotalPossibleLeaks,PossibleLeakBytes,UniqueReachable,TotalReachable,ReachableBytes +Windows,unit-tests,10,113,4256,14,15,7206,467,644,227717 +Windows,functional-tests,7,382,14072,7,3994,1038248411,1722,2823,725167 +Windows,sample-cpp-mini,0,0,0,0,0,0,973,1795,411008 +Linux,unit-tests,10,127,3730,5,7,3452,8,8,77045 +Linux,functional-tests,3,156,4694,1,1,4104,360,643,279619 +Linux,sample-cpp-mini,1,2,32,0,0,0,15,23,81130 diff --git a/.github/scripts/run-drmemory.ps1 b/.github/scripts/run-drmemory.ps1 new file mode 100644 index 000000000..67f7a87dd --- /dev/null +++ b/.github/scripts/run-drmemory.ps1 @@ -0,0 +1,153 @@ +[CmdletBinding()] +param( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$DrMemoryPath, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$LogDirectory, + + [Parameter(Mandatory = $true)] + [ValidatePattern('^[A-Za-z0-9_.-]+$')] + [string]$Scenario, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$TargetPath, + + [string[]]$TargetArguments = @(), + + [string]$BaselinePath +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +function Get-LeakCount { + param( + [Parameter(Mandatory = $true)] + [string]$Results, + + [Parameter(Mandatory = $true)] + [string]$Category + ) + + $escapedCategory = [regex]::Escape($Category) + $pattern = "(?m)^\s*(?:~~Dr\.M~~\s+)?([\d,]+) unique,\s+([\d,]+) total,\s+([\d,]+) byte\(s\) of $escapedCategory\r?$" + $match = [regex]::Match($Results, $pattern) + if (-not $match.Success) { + throw "Dr. Memory results do not contain the '$Category' summary." + } + + return @{ + Unique = [int64]($match.Groups[1].Value -replace ",", "") + Total = [int64]($match.Groups[2].Value -replace ",", "") + Bytes = [int64]($match.Groups[3].Value -replace ",", "") + } +} + +$resolvedDrMemoryPath = (Resolve-Path -LiteralPath $DrMemoryPath).Path +$resolvedTargetPath = (Resolve-Path -LiteralPath $TargetPath).Path +$resolvedLogDirectory = [System.IO.Path]::GetFullPath($LogDirectory) +$scenarioDirectory = Join-Path $resolvedLogDirectory $Scenario +New-Item -ItemType Directory -Path $scenarioDirectory -Force | Out-Null + +Write-Host "Running Dr. Memory leak analysis for $Scenario" +& $resolvedDrMemoryPath ` + -batch ` + -leaks_only ` + -logdir $scenarioDirectory ` + -- ` + $resolvedTargetPath ` + @TargetArguments +$targetExitCode = $LASTEXITCODE +if ($targetExitCode -ne 0) { + throw "Dr. Memory or $Scenario exited with code $targetExitCode." +} + +$resultFiles = @(Get-ChildItem -LiteralPath $scenarioDirectory -Filter results.txt -File -Recurse) +$resultFiles = @($resultFiles | Where-Object { + Select-String -LiteralPath $_.FullName -Pattern '^(?:NO )?ERRORS FOUND:\r?$' -Quiet +}) +if ($resultFiles.Count -ne 1) { + throw "Expected one completed Dr. Memory results.txt for $Scenario, found $($resultFiles.Count)." +} + +$results = Get-Content -LiteralPath $resultFiles[0].FullName -Raw +$leaks = Get-LeakCount -Results $results -Category "leak(s)" +$possibleLeaks = Get-LeakCount -Results $results -Category "possible leak(s)" +$reachable = Get-LeakCount -Results $results -Category "still-reachable allocation(s)" + +$summary = [pscustomobject]@{ + Platform = if ($env:RUNNER_OS) { $env:RUNNER_OS } else { [System.Environment]::OSVersion.Platform } + Scenario = $Scenario + UniqueLeaks = $leaks.Unique + TotalLeaks = $leaks.Total + LeakBytes = $leaks.Bytes + UniquePossibleLeaks = $possibleLeaks.Unique + TotalPossibleLeaks = $possibleLeaks.Total + PossibleLeakBytes = $possibleLeaks.Bytes + UniqueReachable = $reachable.Unique + TotalReachable = $reachable.Total + ReachableBytes = $reachable.Bytes +} + +$summaryPath = Join-Path $resolvedLogDirectory "summary.csv" +$summaries = if (Test-Path -LiteralPath $summaryPath) { + @(Import-Csv -LiteralPath $summaryPath) + @($summary) +} +else { + @($summary) +} +$summaries | Export-Csv -LiteralPath $summaryPath -NoTypeInformation + +$baselineStatus = "Not compared" +if ($BaselinePath) { + $resolvedBaselinePath = (Resolve-Path -LiteralPath $BaselinePath).Path + $baselineRows = @(Import-Csv -LiteralPath $resolvedBaselinePath | Where-Object { + $_.Platform -eq $summary.Platform -and $_.Scenario -eq $summary.Scenario + }) + if ($baselineRows.Count -ne 1) { + throw "Expected one baseline for $($summary.Platform)/$Scenario, found $($baselineRows.Count)." + } + + $regressions = @() + foreach ($metric in @( + "UniqueLeaks", + "TotalLeaks", + "LeakBytes", + "UniquePossibleLeaks", + "TotalPossibleLeaks", + "PossibleLeakBytes", + "UniqueReachable", + "TotalReachable", + "ReachableBytes" + )) { + $currentValue = [int64]$summary.$metric + $baselineValue = [int64]$baselineRows[0].$metric + if ($currentValue -gt $baselineValue) { + $regressions += "$metric increased from $baselineValue to $currentValue" + } + } + + if ($regressions.Count -eq 0) { + $baselineStatus = "At or below baseline" + } + else { + $baselineStatus = "$($regressions.Count) increase(s)" + foreach ($regression in $regressions) { + Write-Host "::warning title=Dr. Memory regression ($($summary.Platform)/$Scenario)::$regression" + } + } +} + +$markdown = @" +| Scenario | Unique leaks | Total leaks | Leak bytes | Unique possible | Total possible | Possible bytes | Unique reachable | Total reachable | Reachable bytes | Baseline | +|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---| +| $Scenario | $($leaks.Unique) | $($leaks.Total) | $($leaks.Bytes) | $($possibleLeaks.Unique) | $($possibleLeaks.Total) | $($possibleLeaks.Bytes) | $($reachable.Unique) | $($reachable.Total) | $($reachable.Bytes) | $baselineStatus | +"@ +Write-Host $markdown +if ($env:GITHUB_STEP_SUMMARY) { + Add-Content -LiteralPath $env:GITHUB_STEP_SUMMARY -Value $markdown +} diff --git a/.github/workflows/memory-leak-analysis.yml b/.github/workflows/memory-leak-analysis.yml new file mode 100644 index 000000000..682316aa0 --- /dev/null +++ b/.github/workflows/memory-leak-analysis.yml @@ -0,0 +1,227 @@ +name: Memory leak analysis + +on: + workflow_dispatch: + schedule: + - cron: 0 8 * * 1 + push: + branches: + - main + paths: + - .github/scripts/run-drmemory.ps1 + - .github/memory-leak-baseline.csv + - .github/workflows/memory-leak-analysis.yml + - CMakeLists.txt + - CMakePresets.json + - Solutions/** + - cmake/** + - examples/cpp/SampleCppMini/** + - lib/** + - sqlite/** + - tests/** + - third_party/Solutions/zlib/** + - third_party/googletest + - tools/gen-version.cmd + - zlib/** + pull_request: + branches: + - main + paths: + - .github/scripts/run-drmemory.ps1 + - .github/memory-leak-baseline.csv + - .github/workflows/memory-leak-analysis.yml + +permissions: + contents: read + +concurrency: + group: memory-leak-analysis-${{ github.ref }} + cancel-in-progress: false + +env: + DRMEMORY_VERSION: 2.6.20434 + DRMEMORY_TAG: cronbuild-2.6.20434 + +jobs: + windows: + name: Dr. Memory on Windows + runs-on: windows-2022 + timeout-minutes: 120 + steps: + - name: Checkout + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + + - name: Initialize googletest + run: git submodule update --init --depth=1 third_party/googletest + + - name: Setup MSBuild + uses: microsoft/setup-msbuild@6fb02220983dee41ce7ae257b6f4d8f9bf5ed4ce # v2.0.0 + with: + vs-version: '[17,)' + + - name: Build leak-analysis targets + shell: cmd + run: >- + tools\gen-version.cmd && + msbuild Solutions\MSTelemetrySDK.sln + /target:sqlite:Rebuild,zlib:Rebuild,Tests\gmock:Rebuild,Tests\gtest:Rebuild,Tests\UnitTests:Rebuild,Tests\FuncTests:Rebuild,Samples\cpp\SampleCppMini:Rebuild + /p:BuildProjectReferences=true + /p:Configuration=Debug + /p:Platform=x64 + /maxcpucount:2 + + - name: Download Dr. Memory + shell: pwsh + env: + DRMEMORY_SHA256: ED9C0E3F1BDB7F8DB1ADC13531493FB1C451E15F375638592C01D8837825A73A + run: | + $archive = Join-Path $env:RUNNER_TEMP "DrMemory-Windows-$env:DRMEMORY_VERSION.zip" + $url = "https://github.com/DynamoRIO/drmemory/releases/download/$env:DRMEMORY_TAG/DrMemory-Windows-$env:DRMEMORY_VERSION.zip" + Invoke-WebRequest -Uri $url -OutFile $archive + $actualHash = (Get-FileHash -LiteralPath $archive -Algorithm SHA256).Hash + if ($actualHash -ne $env:DRMEMORY_SHA256) { + throw "Dr. Memory archive hash mismatch: expected $env:DRMEMORY_SHA256, got $actualHash." + } + Expand-Archive -LiteralPath $archive -DestinationPath $env:RUNNER_TEMP + + - name: Analyze unit tests + shell: pwsh + run: >- + ./.github/scripts/run-drmemory.ps1 + -DrMemoryPath "$env:RUNNER_TEMP/DrMemory-Windows-$env:DRMEMORY_VERSION/bin64/drmemory.exe" + -LogDirectory drmemory-results + -Scenario unit-tests + -TargetPath Solutions/out/Debug/x64/UnitTests/UnitTests.exe + -BaselinePath .github/memory-leak-baseline.csv + -TargetArguments "--gtest_filter=-OfflineStorageTests_SQLite.StoreThousandEventsTakesLessThanASecond" + + - name: Analyze functional tests + shell: pwsh + run: >- + ./.github/scripts/run-drmemory.ps1 + -DrMemoryPath "$env:RUNNER_TEMP/DrMemory-Windows-$env:DRMEMORY_VERSION/bin64/drmemory.exe" + -LogDirectory drmemory-results + -Scenario functional-tests + -TargetPath Solutions/out/Debug/x64/FuncTests/FuncTests.exe + -BaselinePath .github/memory-leak-baseline.csv + -TargetArguments "--gtest_filter=-BasicFuncTests.killSwitchWorks" + + - name: Analyze basic sample + shell: pwsh + run: >- + ./.github/scripts/run-drmemory.ps1 + -DrMemoryPath "$env:RUNNER_TEMP/DrMemory-Windows-$env:DRMEMORY_VERSION/bin64/drmemory.exe" + -LogDirectory drmemory-results + -Scenario sample-cpp-mini + -BaselinePath .github/memory-leak-baseline.csv + -TargetPath Solutions/out/Debug/x64/SampleCppMini/SampleCppMini.exe + + - name: Verify Network List Manager is not loaded + shell: pwsh + run: | + $moduleLogs = @() + foreach ($scenario in @("unit-tests", "functional-tests", "sample-cpp-mini")) { + $scenarioLogs = @(Get-ChildItem "drmemory-results/$scenario" -Filter global.*.log -File -Recurse) + if ($scenarioLogs.Count -eq 0) { + throw "Dr. Memory did not produce a module log for $scenario." + } + $moduleLogs += $scenarioLogs + } + $matches = $moduleLogs | Select-String -Pattern 'module load event:\s+"netprofm\.dll"' + if ($matches) { + $matches | ForEach-Object { Write-Error "$($_.Path):$($_.LineNumber): $($_.Line)" } + throw "Network detection loaded netprofm.dll." + } + + - name: Upload Windows reports + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: drmemory-windows + path: drmemory-results + if-no-files-found: error + retention-days: 90 + + linux: + name: Dr. Memory on Linux + runs-on: ubuntu-22.04 + timeout-minutes: 120 + env: + CMAKE_POLICY_VERSION_MINIMUM: "3.5" + steps: + - name: Checkout + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + + - name: Initialize googletest + run: git submodule update --init --depth=1 third_party/googletest + + - name: Install build dependencies + run: | + sudo apt-get update + sudo apt-get install -y libcurl4-openssl-dev + + - name: Build leak-analysis targets + run: | + cmake --preset matsdk-debug \ + -DMATSDK_BUILD_UNIT_TESTS=ON \ + -DMATSDK_BUILD_FUNC_TESTS=ON + cmake --build --preset matsdk-debug --parallel 2 + + - name: Build basic sample + run: | + cmake --install out --prefix "$PWD/out/install" + cmake -S examples/cpp/SampleCppMini -B out/sample-cpp-mini \ + -DCMAKE_BUILD_TYPE=Debug \ + -DCMAKE_DISABLE_FIND_PACKAGE_MSTelemetry=TRUE \ + -DMATSDK_INSTALL_DIR="$PWD/out/install" + cmake --build out/sample-cpp-mini --parallel 2 + + - name: Download Dr. Memory + env: + DRMEMORY_SHA256: 79B7718C0040A68B4FCECD9BA1C422174350A5B3A40A8417047A9219E9C2F258 + run: | + archive="$RUNNER_TEMP/DrMemory-Linux-$DRMEMORY_VERSION.tar.gz" + url="https://github.com/DynamoRIO/drmemory/releases/download/$DRMEMORY_TAG/DrMemory-Linux-$DRMEMORY_VERSION.tar.gz" + curl --fail --location --retry 3 --output "$archive" "$url" + echo "$DRMEMORY_SHA256 $archive" | sha256sum --check --strict + tar -xzf "$archive" -C "$RUNNER_TEMP" + + - name: Analyze unit tests + shell: pwsh + run: >- + ./.github/scripts/run-drmemory.ps1 + -DrMemoryPath "$env:RUNNER_TEMP/DrMemory-Linux-$env:DRMEMORY_VERSION/bin64/drmemory" + -LogDirectory drmemory-results + -Scenario unit-tests + -BaselinePath .github/memory-leak-baseline.csv + -TargetPath out/tests/unittests/UnitTests + + - name: Analyze functional tests + shell: pwsh + run: >- + ./.github/scripts/run-drmemory.ps1 + -DrMemoryPath "$env:RUNNER_TEMP/DrMemory-Linux-$env:DRMEMORY_VERSION/bin64/drmemory" + -LogDirectory drmemory-results + -Scenario functional-tests + -TargetPath out/tests/functests/FuncTests + -BaselinePath .github/memory-leak-baseline.csv + -TargetArguments "--gtest_filter=-BasicFuncTests.killSwitchWorks" + + - name: Analyze basic sample + shell: pwsh + run: >- + ./.github/scripts/run-drmemory.ps1 + -DrMemoryPath "$env:RUNNER_TEMP/DrMemory-Linux-$env:DRMEMORY_VERSION/bin64/drmemory" + -LogDirectory drmemory-results + -Scenario sample-cpp-mini + -BaselinePath .github/memory-leak-baseline.csv + -TargetPath out/sample-cpp-mini/SampleCppMini + + - name: Upload Linux reports + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: drmemory-linux + path: drmemory-results + if-no-files-found: error + retention-days: 90 diff --git a/.github/workflows/test-win-latest.yml b/.github/workflows/test-win-latest.yml index 66261d1e6..c6f19f8aa 100644 --- a/.github/workflows/test-win-latest.yml +++ b/.github/workflows/test-win-latest.yml @@ -75,7 +75,7 @@ jobs: retention-days: 7 public-headers: - name: Public header gate (MSVC) + name: Public header gate (MSVC, Windows 10 API floor) runs-on: windows-2022 steps: - name: Checkout diff --git a/CMakeLists.txt b/CMakeLists.txt index 30fe6e3c8..9dadc7a70 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -246,7 +246,7 @@ if(MATSDK_SQLITE_PROVIDER_RESOLVED STREQUAL "SYSTEM" AND NOT TARGET SQLite3::SQL matsdk_add_apple_system_library(SQLite3::SQLite3 sqlite3) else() find_package(SQLite3 QUIET) - if(NOT TARGET SQLite3::SQLite3 AND TARGET SQLite::SQLite3) + if(TARGET SQLite::SQLite3 AND NOT TARGET SQLite3::SQLite3) matsdk_add_interface_dependency(SQLite3::SQLite3 SQLite::SQLite3) endif() if(NOT TARGET SQLite3::SQLite3 AND MATSDK_USING_VCPKG) diff --git a/README.md b/README.md index 935cf1da2..ba11b35cd 100644 --- a/README.md +++ b/README.md @@ -100,9 +100,8 @@ Other resources to learn how to setup the build system: * **Supported** - these platforms are known to work well with the SDK in production. * **Covered by CI** - these platforms are tested as part of CI. -* Windows 7, Windows 8, and Windows 8.1 are not supported. Some project files - retain older target macros for binary compatibility, but those macros do not - extend the supported operating-system matrix above. +* Windows 7, Windows 8, and Windows 8.1 are not supported. Windows desktop + builds target the Windows 10 API floor in CI. * For iOS simulator, CI covers representative supported simulator configurations on the current macOS runner images rather than every supported iOS 12+ runtime. diff --git a/Solutions/net48/net48.vcxproj b/Solutions/net48/net48.vcxproj index 5f7b09148..8eea81770 100644 --- a/Solutions/net48/net48.vcxproj +++ b/Solutions/net48/net48.vcxproj @@ -113,7 +113,7 @@ Level4 Disabled - ZLIB_WINAPI;WIN32;MATSDK_SHARED_LIB=1;_CRT_SECURE_NO_WARNINGS;_WIN32_WINNT=0x0601;_DEBUG;_WINDOWS;_USRDLL;NOMINMAX;%(PreprocessorDefinitions) + ZLIB_WINAPI;WIN32;MATSDK_SHARED_LIB=1;_CRT_SECURE_NO_WARNINGS;WINVER=0x0A00;_WIN32_WINNT=0x0A00;_DEBUG;_WINDOWS;_USRDLL;NOMINMAX;%(PreprocessorDefinitions) $(ProjectDir)..\..\lib;$(ProjectDir)..\..\lib\include\public;$(ProjectDir)..\..\lib\include\mat;$(ProjectDir)..\..\lib\include;$(ProjectDir)..\..\bondlite\include;$(ProjectDir)..\..\lib\shared;$(ProjectDir)..\..\lib\shared\include;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) ProgramDatabase true @@ -196,7 +196,7 @@ Disabled true false - ZLIB_WINAPI;WIN32;MATSDK_SHARED_LIB=1;_CRT_SECURE_NO_WARNINGS;_WIN32_WINNT=0x0601;NDEBUG;_WINDOWS;_USRDLL;NOMINMAX;%(PreprocessorDefinitions) + ZLIB_WINAPI;WIN32;MATSDK_SHARED_LIB=1;_CRT_SECURE_NO_WARNINGS;WINVER=0x0A00;_WIN32_WINNT=0x0A00;NDEBUG;_WINDOWS;_USRDLL;NOMINMAX;%(PreprocessorDefinitions) $(ProjectDir)..\..\lib;$(ProjectDir)..\..\lib\include\public;$(ProjectDir)..\..\lib\include\mat;$(ProjectDir)..\..\lib\include;$(ProjectDir)..\..\bondlite\include;$(ProjectDir)..\..\lib\shared;$(ProjectDir)..\..\lib\shared\include;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) true false diff --git a/Solutions/net48/targetver.h b/Solutions/net48/targetver.h index 8b0d0d66f..498108c8d 100644 --- a/Solutions/net48/targetver.h +++ b/Solutions/net48/targetver.h @@ -5,6 +5,6 @@ // If you wish to build your application for a previous Windows platform, include WinSDKVer.h and // set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. -#define _WIN32_WINNT 0x0601 +#define _WIN32_WINNT 0x0A00 #include diff --git a/Solutions/win32-dll/win32-dll.vcxproj b/Solutions/win32-dll/win32-dll.vcxproj index 968f36990..7f5bce688 100644 --- a/Solutions/win32-dll/win32-dll.vcxproj +++ b/Solutions/win32-dll/win32-dll.vcxproj @@ -162,7 +162,7 @@ NotUsing Level4 Disabled - ORIGINAL_FILENAME="ClientTelemetry.dll";ZLIB_WINAPI;WIN32;MATSDK_SHARED_LIB=1;_DEBUG;_WINDOWS;_USRDLL;WINVER=_WIN32_WINNT_WIN7;NOMINMAX;%(PreprocessorDefinitions) + ORIGINAL_FILENAME="ClientTelemetry.dll";ZLIB_WINAPI;WIN32;MATSDK_SHARED_LIB=1;_DEBUG;_WINDOWS;_USRDLL;WINVER=_WIN32_WINNT_WIN10;_WIN32_WINNT=_WIN32_WINNT_WIN10;NOMINMAX;%(PreprocessorDefinitions) $(ProjectDir)..\..\lib;$(ProjectDir)..\..\lib\include\public;$(ProjectDir)..\..\lib\include\mat;$(ProjectDir)..\..\lib\include;$(ProjectDir)..\..\bondlite\include;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) ProgramDatabase false @@ -253,7 +253,7 @@ MaxSpeed true false - ZLIB_WINAPI;WIN32;MATSDK_SHARED_LIB=1;NDEBUG;_WINDOWS;_USRDLL;WINVER=_WIN32_WINNT_WIN7;NOMINMAX;%(PreprocessorDefinitions) + ZLIB_WINAPI;WIN32;MATSDK_SHARED_LIB=1;NDEBUG;_WINDOWS;_USRDLL;WINVER=_WIN32_WINNT_WIN10;_WIN32_WINNT=_WIN32_WINNT_WIN10;NOMINMAX;%(PreprocessorDefinitions) $(ProjectDir)..\..\lib;$(ProjectDir)..\..\lib\include\public;$(ProjectDir)..\..\lib\include\mat;$(ProjectDir)..\..\lib\include;$(ProjectDir)..\..\bondlite\include;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) false false diff --git a/Solutions/win32-lib/win32-lib.vcxproj b/Solutions/win32-lib/win32-lib.vcxproj index d088b06c7..691cceb91 100644 --- a/Solutions/win32-lib/win32-lib.vcxproj +++ b/Solutions/win32-lib/win32-lib.vcxproj @@ -252,7 +252,7 @@ Level4 Disabled - ZLIB_WINAPI;WIN32;WIN32;NOMINMAX;_DEBUG;_WINDOWS;_USRDLL;WINVER=_WIN32_WINNT_WIN7;NOMINMAX;%(PreprocessorDefinitions) + ZLIB_WINAPI;WIN32;WIN32;NOMINMAX;_DEBUG;_WINDOWS;_USRDLL;WINVER=_WIN32_WINNT_WIN10;_WIN32_WINNT=_WIN32_WINNT_WIN10;NOMINMAX;%(PreprocessorDefinitions) $(ProjectDir)..\..\lib;$(ProjectDir)..\..\lib\include\public;$(ProjectDir)..\..\lib\include\mat;$(ProjectDir)..\..\lib\include;$(ProjectDir)..\..\bondlite\include;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) ProgramDatabase false @@ -320,7 +320,7 @@ Level4 Disabled - ZLIB_WINAPI;WIN32;NOMINMAX;_DEBUG;_WINDOWS;_USRDLL;WINVER=_WIN32_WINNT_WIN7;%(PreprocessorDefinitions) + ZLIB_WINAPI;WIN32;NOMINMAX;_DEBUG;_WINDOWS;_USRDLL;WINVER=_WIN32_WINNT_WIN10;_WIN32_WINNT=_WIN32_WINNT_WIN10;%(PreprocessorDefinitions) $(ProjectDir)..\..\lib;$(ProjectDir)..\..\lib\include\public;$(ProjectDir)..\..\lib\include\mat;$(ProjectDir)..\..\lib\include;$(ProjectDir)..\..\bondlite\include;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) ProgramDatabase false @@ -395,7 +395,7 @@ MinSpace true false - ZLIB_WINAPI;WIN32;NOMINMAX;NDEBUG;_WINDOWS;_USRDLL;WINVER=_WIN32_WINNT_WIN7;%(PreprocessorDefinitions) + ZLIB_WINAPI;WIN32;NOMINMAX;NDEBUG;_WINDOWS;_USRDLL;WINVER=_WIN32_WINNT_WIN10;_WIN32_WINNT=_WIN32_WINNT_WIN10;%(PreprocessorDefinitions) $(ProjectDir)..\..\lib;$(ProjectDir)..\..\lib\include\public;$(ProjectDir)..\..\lib\include\mat;$(ProjectDir)..\..\lib\include;$(ProjectDir)..\..\bondlite\include;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) false false @@ -471,7 +471,7 @@ MinSpace true false - ZLIB_WINAPI;WIN32;NOMINMAX;NDEBUG;_WINDOWS;_USRDLL;WINVER=_WIN32_WINNT_WIN7;%(PreprocessorDefinitions) + ZLIB_WINAPI;WIN32;NOMINMAX;NDEBUG;_WINDOWS;_USRDLL;WINVER=_WIN32_WINNT_WIN10;_WIN32_WINNT=_WIN32_WINNT_WIN10;%(PreprocessorDefinitions) $(ProjectDir)..\..\lib;$(ProjectDir)..\..\lib\include\public;$(ProjectDir)..\..\lib\include\mat;$(ProjectDir)..\..\lib\include;$(ProjectDir)..\..\bondlite\include;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) false false diff --git a/Solutions/win32-mini-dll/win32-mini-dll.vcxproj b/Solutions/win32-mini-dll/win32-mini-dll.vcxproj index 99d21d1ba..81fca2b6b 100644 --- a/Solutions/win32-mini-dll/win32-mini-dll.vcxproj +++ b/Solutions/win32-mini-dll/win32-mini-dll.vcxproj @@ -164,7 +164,7 @@ NotUsing Level4 MinSpace - CONFIG_CUSTOM_H="config-compact-noutc.h";ZLIB_WINAPI;WIN32;MATSDK_SHARED_LIB=1;NOMINMAX;_DEBUG;_WINDOWS;_USRDLL;WINVER=_WIN32_WINNT_WIN7;%(PreprocessorDefinitions) + CONFIG_CUSTOM_H="config-compact-noutc.h";ZLIB_WINAPI;WIN32;MATSDK_SHARED_LIB=1;NOMINMAX;_DEBUG;_WINDOWS;_USRDLL;WINVER=_WIN32_WINNT_WIN10;_WIN32_WINNT=_WIN32_WINNT_WIN10;%(PreprocessorDefinitions) $(ProjectDir)..\..\lib;$(ProjectDir)..\..\lib\include\public;$(ProjectDir)..\..\lib\include\mat;$(ProjectDir)..\..\lib\include;$(ProjectDir)..\..\bondlite\include;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) ProgramDatabase false @@ -287,7 +287,7 @@ MinSpace false false - CONFIG_CUSTOM_H="config-compact-noutc.h";ZLIB_WINAPI;WIN32;MATSDK_SHARED_LIB=1;WIN32;NOMINMAX;NDEBUG;_WINDOWS;_USRDLL;WINVER=_WIN32_WINNT_WIN7;%(PreprocessorDefinitions) + CONFIG_CUSTOM_H="config-compact-noutc.h";ZLIB_WINAPI;WIN32;MATSDK_SHARED_LIB=1;WIN32;NOMINMAX;NDEBUG;_WINDOWS;_USRDLL;WINVER=_WIN32_WINNT_WIN10;_WIN32_WINNT=_WIN32_WINNT_WIN10;%(PreprocessorDefinitions) $(ProjectDir)..\..\lib;$(ProjectDir)..\..\lib\include\public;$(ProjectDir)..\..\lib\include\mat;$(ProjectDir)..\..\lib\include;$(ProjectDir)..\..\bondlite\include;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) false false diff --git a/Solutions/win32-mini-lib/win32-mini-lib.vcxproj b/Solutions/win32-mini-lib/win32-mini-lib.vcxproj index aba9e8999..8d305209a 100644 --- a/Solutions/win32-mini-lib/win32-mini-lib.vcxproj +++ b/Solutions/win32-mini-lib/win32-mini-lib.vcxproj @@ -255,7 +255,7 @@ NotUsing Level4 MinSpace - CONFIG_CUSTOM_H="config-compact-noutc.h";ZLIB_WINAPI;WIN32;MATSDK_STATIC_LIB=1;_DEBUG;_WINDOWS;_USRDLL;WINVER=_WIN32_WINNT_WIN7;NOMINMAX;%(PreprocessorDefinitions) + CONFIG_CUSTOM_H="config-compact-noutc.h";ZLIB_WINAPI;WIN32;MATSDK_STATIC_LIB=1;_DEBUG;_WINDOWS;_USRDLL;WINVER=_WIN32_WINNT_WIN10;_WIN32_WINNT=_WIN32_WINNT_WIN10;NOMINMAX;%(PreprocessorDefinitions) $(ProjectDir)..\..\lib;$(ProjectDir)..\..\lib\include\public;$(ProjectDir)..\..\lib\include\mat;$(ProjectDir)..\..\lib\include;$(ProjectDir)..\..\bondlite\include;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) ProgramDatabase false @@ -361,7 +361,7 @@ NotUsing Level4 MinSpace - CONFIG_CUSTOM_H="config-compact-noutc.h";ZLIB_WINAPI;WIN32;MATSDK_STATIC_LIB=1;WIN32;_DEBUG;_WINDOWS;_USRDLL;WINVER=_WIN32_WINNT_WIN7;NOMINMAX;%(PreprocessorDefinitions) + CONFIG_CUSTOM_H="config-compact-noutc.h";ZLIB_WINAPI;WIN32;MATSDK_STATIC_LIB=1;WIN32;_DEBUG;_WINDOWS;_USRDLL;WINVER=_WIN32_WINNT_WIN10;_WIN32_WINNT=_WIN32_WINNT_WIN10;NOMINMAX;%(PreprocessorDefinitions) $(ProjectDir)..\..\lib;$(ProjectDir)..\..\lib\include\public;$(ProjectDir)..\..\lib\include\mat;$(ProjectDir)..\..\lib\include;$(ProjectDir)..\..\bondlite\include;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) ProgramDatabase false @@ -474,7 +474,7 @@ MinSpace false false - CONFIG_CUSTOM_H="config-compact-noutc.h";ZLIB_WINAPI;WIN32;MATSDK_STATIC_LIB=1;WIN32;NDEBUG;_WINDOWS;_USRDLL;WINVER=_WIN32_WINNT_WIN7;NOMINMAX;%(PreprocessorDefinitions) + CONFIG_CUSTOM_H="config-compact-noutc.h";ZLIB_WINAPI;WIN32;MATSDK_STATIC_LIB=1;WIN32;NDEBUG;_WINDOWS;_USRDLL;WINVER=_WIN32_WINNT_WIN10;_WIN32_WINNT=_WIN32_WINNT_WIN10;NOMINMAX;%(PreprocessorDefinitions) $(ProjectDir)..\..\lib;$(ProjectDir)..\..\lib\include\public;$(ProjectDir)..\..\lib\include\mat;$(ProjectDir)..\..\lib\include;$(ProjectDir)..\..\bondlite\include;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) false false @@ -579,7 +579,7 @@ MinSpace false true - CONFIG_CUSTOM_H="config-compact-noutc.h";ZLIB_WINAPI;WIN32;MATSDK_STATIC_LIB=1;WIN32;NDEBUG;_WINDOWS;_USRDLL;WINVER=_WIN32_WINNT_WIN7;NOMINMAX;%(PreprocessorDefinitions) + CONFIG_CUSTOM_H="config-compact-noutc.h";ZLIB_WINAPI;WIN32;MATSDK_STATIC_LIB=1;WIN32;NDEBUG;_WINDOWS;_USRDLL;WINVER=_WIN32_WINNT_WIN10;_WIN32_WINNT=_WIN32_WINNT_WIN10;NOMINMAX;%(PreprocessorDefinitions) $(ProjectDir)..\..\lib;$(ProjectDir)..\..\lib\include\public;$(ProjectDir)..\..\lib\include\mat;$(ProjectDir)..\..\lib\include;$(ProjectDir)..\..\bondlite\include;$(ProjectDir);$(GeneratedFilesDir);$(IntDir);%(AdditionalIncludeDirectories) false false diff --git a/build-android.cmd b/build-android.cmd index add5f03d8..e36f3ea29 100644 --- a/build-android.cmd +++ b/build-android.cmd @@ -13,6 +13,7 @@ REM Consider using %ANDROID_NDK_ROOT% environment variable REM Install Android tools if necessary call tools\setup-buildtools-android.cmd +if errorlevel 1 exit /b %ERRORLEVEL% set "PATH=%ANDROID_SDK_ROOT%\cmake\%ANDROID_CMAKE_VERSION%\bin;%ANDROID_NDK%;%PATH%" diff --git a/cmake/MatsdkFetchCurl.cmake b/cmake/MatsdkFetchCurl.cmake index ea10d86d7..249f37b8e 100644 --- a/cmake/MatsdkFetchCurl.cmake +++ b/cmake/MatsdkFetchCurl.cmake @@ -36,7 +36,6 @@ function(matsdk_fetch_curl out_target) INSTALL_MBEDTLS_HEADERS MBEDTLS_FATAL_WARNINGS USE_SHARED_MBEDTLS_LIBRARY - LINK_WITH_PTHREAD BUILD_CURL_EXE BUILD_EXAMPLES BUILD_LIBCURL_DOCS @@ -82,7 +81,12 @@ function(matsdk_fetch_curl out_target) set(${option} ON) endforeach() + set(CURL_CA_BUNDLE none) + set(CURL_CA_PATH none) + set(CURL_CA_EMBED "") + if(MATSDK_CURL_TLS_BACKEND_UPPER STREQUAL "MBEDTLS") + set(LINK_WITH_PTHREAD ON) set(USE_STATIC_MBEDTLS_LIBRARY ON) set(CURL_USE_MBEDTLS ON) set(MBEDTLS_CONFIG_FILE "") @@ -96,6 +100,9 @@ function(matsdk_fetch_curl out_target) foreach(target mbedtls mbedx509 mbedcrypto) matsdk_configure_fetched_static_target("${target}") + target_compile_definitions("${target}" PUBLIC + MBEDTLS_THREADING_C + MBEDTLS_THREADING_PTHREAD) endforeach() set(MBEDTLS_INCLUDE_DIR "${matsdk_mbedtls_SOURCE_DIR}/include") @@ -125,6 +132,26 @@ function(matsdk_fetch_curl out_target) message(FATAL_ERROR "The embedded static CURL::libcurl target was not created.") endif() + set(_matsdk_curl_config "${matsdk_curl_BINARY_DIR}/lib/curl_config.h") + if(NOT EXISTS "${_matsdk_curl_config}") + message(FATAL_ERROR + "The embedded curl configuration was not generated: ${_matsdk_curl_config}") + endif() + file(READ "${_matsdk_curl_config}" _matsdk_curl_config_contents) + foreach(definition CURL_CA_BUNDLE CURL_CA_PATH) + string(REGEX REPLACE + "#define ${definition} \"[^\"]*\"" + "/* #undef ${definition} */" + _matsdk_curl_config_contents + "${_matsdk_curl_config_contents}") + endforeach() + if(_matsdk_curl_config_contents MATCHES + "#define CURL_CA_(BUNDLE|PATH)") + message(FATAL_ERROR + "Embedded curl retained a build-time certificate authority path.") + endif() + file(WRITE "${_matsdk_curl_config}" "${_matsdk_curl_config_contents}") + matsdk_configure_fetched_static_target(libcurl_static) set(_matsdk_fetched_curl_targets libcurl_static) diff --git a/docs/building-custom-SKU.md b/docs/building-custom-SKU.md index a68d6a681..b3a03d394 100644 --- a/docs/building-custom-SKU.md +++ b/docs/building-custom-SKU.md @@ -28,7 +28,7 @@ Build recipe must contain the following preprocessor definitions: | HAVE_MAT_WIN_LOG | off | Will log statements to disk on windows if trace enabled and HAVE_MAT_LOGGING defined | | HAVE_MAT_EVT_TRACEID | off | Enable event tracking by adding trace-id to http request header on Windows. This is for debugging purpose, and not recommended to be enabled in production. The collector doesn't parse/read this header. As of now, this is meant to be used through the capi, where the http-send handler should remove this header from the event data before sending it to collector. | | HAVE_MAT_STORAGE | on | Enable SQLite persistent offline storage | -| HAVE_MAT_NETDETECT | on | _Win32 Desktop only_: Use NLM COM object for network cost detection on Windows 8+ | +| HAVE_MAT_NETDETECT | on | _Win32 Desktop only_: Use Windows Runtime APIs for network cost detection on Windows 10+ | | HAVE_MAT_SHORT_NS | off | Use short "MAT::" namespace instead of "Microsoft::Applications::Events::" to reduce the .DLL size | | HAVE_CS4 | off | Build with Common Schema 4.0 support. Current default is `off`, i.e. building with Common Schema 3.0 support | | HAVE_CS4_FULL | off | Enable additional Common Schema 4.0 protocol features needed by server / services SDK | diff --git a/docs/embedding-with-cmake.md b/docs/embedding-with-cmake.md index 7a3d08a33..6cce81468 100644 --- a/docs/embedding-with-cmake.md +++ b/docs/embedding-with-cmake.md @@ -40,10 +40,31 @@ set(MATSDK_ZLIB_PROVIDER VENDORED CACHE STRING "" FORCE) # SYSTEM or VENDORED `MINIMAL` builds the feature-stripped SQLite amalgamation. `VENDORED` builds the unstripped vendored dependency. `SYSTEM` consumes the canonical -`SQLite::SQLite3` / `ZLIB::ZLIB` targets or uses `find_package()`. `AUTO` +`SQLite3::SQLite3` / `ZLIB::ZLIB` targets or uses `find_package()`. `AUTO` preserves platform defaults: system dependencies on desktop/Apple source builds and vendored dependencies on Windows/Android source builds. +Recommended packaged-library policy: + +| Platform | SQLite | zlib | HTTP/TLS | +| --- | --- | --- | --- | +| macOS/iOS | `SYSTEM` (`libsqlite3`) | `SYSTEM` (`libz`) | Apple-native HTTP | +| Linux, self-contained | `MINIMAL` | `VENDORED` | `FETCH` + `MBEDTLS` | +| Linux, host-managed | host-selected | host-selected | `SYSTEM`; the host selects curl's TLS backend | +| Windows | `MINIMAL` | `VENDORED` | WinHTTP | +| Android | `MINIMAL`, or `NONE` with Room | `VENDORED` | Java/JNI by default | + +Apple's SQLite and zlib entries are system libraries: consumers link them but +do not ship private copies. A Linux host such as Foundry Local that already +standardizes on libcurl/OpenSSL should provide `CURL::libcurl` and select +`MATSDK_CURL_PROVIDER=SYSTEM`; other self-contained Linux consumers can use the +SDK's pinned curl/mbedTLS build. + +When multiple embedded SDK copies use the same system SQLite runtime, each +consumer must set `skipSqliteInitAndShutdown` to `"true"` and leave SQLite's +process-wide lifetime to the host. This is the required configuration for +coexisting Apple libraries that all link the system `libsqlite3`. + ## Non-vcpkg dependency selection When the CPP11 PAL uses the curl HTTP transport outside vcpkg, the SDK normally diff --git a/examples/cpp/SampleCppMini/main.cpp b/examples/cpp/SampleCppMini/main.cpp index 9a94146ce..c446777a3 100644 --- a/examples/cpp/SampleCppMini/main.cpp +++ b/examples/cpp/SampleCppMini/main.cpp @@ -77,7 +77,7 @@ void test_cpp_api(const char * token, int ticketType, const char *ticket) // Various typed key-values { "strKey1", "hello1" }, { "strKey2", "hello2" }, - { "int64Key", 1LL }, + { "int64Key", static_cast(1) }, { "dblKey", 3.14 }, { "boolKey", false }, { "guidKey0", GUID_t("00000000-0000-0000-0000-000000000000") }, diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 321ed6b8c..e775caf08 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -299,7 +299,8 @@ target_compile_definitions(matsdk_internal_config INTERFACE USE_BOND _WINDOWS _USRDLL - WINVER=_WIN32_WINNT_WIN7) + WINVER=_WIN32_WINNT_WIN10 + _WIN32_WINNT=_WIN32_WINNT_WIN10) target_compile_options(matsdk_internal_config INTERFACE /U_MBCS) if(MATSDK_USE_WININET) target_compile_definitions(matsdk_internal_config INTERFACE HAVE_MAT_WININET_HTTP_CLIENT) @@ -688,7 +689,7 @@ elseif(PAL_IMPLEMENTATION STREQUAL "WIN32") else() target_link_libraries(mat PRIVATE winhttp) endif() - target_link_libraries(mat PRIVATE crypt32) + target_link_libraries(mat PRIVATE crypt32 uuid) elseif(APPLE) target_link_libraries(mat PUBLIC "-framework CoreFoundation" diff --git a/lib/http/HttpClient_WinHttp.cpp b/lib/http/HttpClient_WinHttp.cpp index 0efe64c28..9230f9277 100644 --- a/lib/http/HttpClient_WinHttp.cpp +++ b/lib/http/HttpClient_WinHttp.cpp @@ -1532,33 +1532,17 @@ unsigned HttpClient_WinHttp::s_nextRequestId = 0; HttpClient_WinHttp::HttpClient_WinHttp() { - // WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY (Windows 8.1+) resolves the proxy - // without depending on a logged-on interactive user or that user's + // Resolve the proxy without depending on a logged-on interactive user or that user's // Internet Explorer settings -- unlike WinInet's // INTERNET_OPEN_TYPE_PRECONFIG, which requires one. This is why WinHTTP, // not WinInet, is Microsoft's documented recommendation for services and - // other non-interactive processes. On an older OS that rejects this access - // type, fall back to the machine-wide WinHTTP proxy configuration. This is - // the documented pre-Windows-8.1 behavior and avoids bypassing enterprise - // proxies entirely. Only fall back for the compatibility error; other - // failures should not be hidden by a second, unrelated WinHttpOpen call. + // other non-interactive processes. HINTERNET session = ::WinHttpOpen( NULL, WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, WINHTTP_FLAG_ASYNC); if (session == nullptr) { - DWORD dwError = ::GetLastError(); - if (dwError == ERROR_INVALID_PARAMETER) - { - LOG_WARN("WinHttpOpen(AUTOMATIC_PROXY) is unsupported; retrying with default proxy"); - session = ::WinHttpOpen( - NULL, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, - WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, WINHTTP_FLAG_ASYNC); - } - else - { - LOG_WARN("WinHttpOpen(AUTOMATIC_PROXY) failed: %lu", dwError); - } + LOG_WARN("WinHttpOpen(AUTOMATIC_PROXY) failed: %lu", ::GetLastError()); } // WinHTTP otherwise permits an unlimited number of connections per origin. // Keep transport concurrency aligned with the SDK's default pending-upload diff --git a/lib/pal/desktop/NetworkDetector.cpp b/lib/pal/desktop/NetworkDetector.cpp index f1a90e5b8..4694b5091 100644 --- a/lib/pal/desktop/NetworkDetector.cpp +++ b/lib/pal/desktop/NetworkDetector.cpp @@ -8,338 +8,253 @@ #pragma comment(lib, "runtimeobject.lib") -// This macro is required for DEFINE_GUID below to declare a local instance of IID_INetworkCostManager GUID -#define INITGUID - #include "NetworkDetector.hpp" -#include -#include "ILogManager.hpp" #include "DebugEvents.hpp" -#include "utils/Utils.hpp" +#include "ILogManager.hpp" #include "pal/PAL.hpp" -// Define a GUID that is only available in Windows 8.x+ SDK . We are using Windows 7.1A SDK for Win32 SDK build, -// so we cannot easily add an extra dependency on Windows 8 or later functionality project-wide. It'd be error-prone, -// because when we have all Windows 8+ features - we might fall into temptation of using that features that would -// break Windows 7.1 compatibility. We cannot afford breaking Windows 7.1 compatibility at this time. -DEFINE_GUID(IID_INetworkCostManager2, 0xdcb00008, 0x570f, 0x4a9b, 0x8d, 0x69, 0x19, 0x9f, 0xdb, 0xa5, 0x72, 0x3b); - -#define NETDETECTOR_START WM_USER+1 -#define NETDETECTOR_STOP WM_USER+2 - -#define NETDETECTOR_COM_SETTLE_MS 1000 +#define NETDETECTOR_REFRESH WM_USER + 1 namespace MAT_NS_BEGIN { - namespace Windows { + namespace Windows + { - // Malwarebytes have been detected - static bool mbDetected = false; + static thread_local void* currentNetworkEventDispatch = nullptr; - /// - /// Convert HString to std::string - /// - /// - /// - std::string to_string(HString *name) + struct NetworkDetector::CallbackState { - UINT32 length; - PCWSTR rawString = name->GetRawBuffer(&length); - std::wstring wide(rawString); - return to_utf8_string(wide); - } + std::atomic listenerThreadId{0}; - /// - /// Convert GUID to std::string - /// - /// - /// - std::string to_string(GUID guid) { - std::string result; - char buff[40] = { 0 }; // Maximum hyphenated GUID length with braces is 38 + null terminator - sprintf_s(buff, sizeof(buff), - "{%08lX-%04hX-%04hX-%02hhX%02hhX-%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX}", - guid.Data1, guid.Data2, guid.Data3, - guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], - guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]); - result = buff; - return result; - } - - NetworkCost const& NetworkDetector::GetNetworkCost() { - return (NetworkCost const &)m_currentNetworkCost; - } - - NetworkType NetworkDetector::GetNetworkType() - { - return m_currentNetworkType.load(); - } - - /// - /// Get current realtime network cost synchronously. - /// This function can be called on any Windows release and it provides a SEH handler. - /// - /// -#pragma warning(push) -#pragma warning(disable: 6320) - int NetworkDetector::GetCurrentNetworkCost() - { -#if 0 - // We don't know the cost of something that is not there - if (m_connectivity == NLM_CONNECTIVITY_DISCONNECTED) + bool QueueRefresh() const { - TRACE("Disconnected!"); - m_currentNetworkCost = NetworkCost_Unknown; - } -#endif - m_currentNetworkCost = NetworkCost_Unknown; - __try { - m_currentNetworkCost = _GetCurrentNetworkCost(); + const auto threadId = listenerThreadId.load(std::memory_order_acquire); + return threadId != 0 && + PostThreadMessage(threadId, NETDETECTOR_REFRESH, 0, NULL) != FALSE; } - //****************************************************************************************************************************** - // This code is required as a workaround for an issue in Visual Studio debug host mode: crash in W.N.C.dll - // - // onecoreuap\net\netprofiles\winrt\networkinformation\lib\handlemanager.cpp(132)\Windows.Networking.Connectivity.dll!0FBCFB9E: - // (caller: 0FBCEE2C) ReturnHr(1) tid(4584) 80070426 The service has not been started. - // - // Exception thrown at XXX (KernelBase.dll) in YYY : The binding handle is invalid. - // If there is a handler for this exception, the program may be safely continued. - //******************************************************************************************************************************* - __except (EXCEPTION_EXECUTE_HANDLER) - { - LOG_ERROR("Unable to obtain network state!"); - m_currentNetworkCost = NetworkCost_Unknown; - } - - // Notify the app about current network cost change - DebugEvent evt; - evt.type = DebugEventType::EVT_NET_CHANGED; - evt.param1 = m_currentNetworkCost; - evt.param2 = mbDetected; - ILogManager::DispatchEventBroadcast(evt); - - return m_currentNetworkCost; - } -#pragma warning(pop) - - /// - /// Get current network connectivity state - /// - /// Value of enum NLM_CONNECTIVITY - int NetworkDetector::GetConnectivity() - { - return m_connectivity; - } + }; - /// - /// Internal implementation - /// - /// - NetworkCost NetworkDetector::_GetCurrentNetworkCost() + struct NetworkDetector::EventDispatchState : std::enable_shared_from_this { - NetworkCost result = NetworkCost_Unknown; - LOG_TRACE("get network cost...\n"); - - if (pNlm == NULL) { - LOG_WARN("INetworkCostManager is unavailable!"); - return result; - } - - HRESULT hr; - - DWORD dwCost = NLM_CONNECTION_COST_UNKNOWN; - INetworkCostManager* pNetworkCostManager = NULL; + bool Queue(NetworkCost cost) + { + std::lock_guard lock(mutex); + if (!acceptEvents) + { + return false; + } - hr = pNlm->QueryInterface(IID_INetworkCostManager2, (void**)&pNetworkCostManager); - if (hr != S_OK) { - return result; - } + latestCost = cost; + eventPending = true; + if (workerScheduled) + { + return true; + } - hr = pNetworkCostManager->GetCost(&dwCost, NULL); - if (hr == S_OK) { - switch (dwCost) { - case NLM_CONNECTION_COST_UNRESTRICTED: // The connection is unlimited and is considered to be unrestricted of usage charges and capacity constraints. - result = NetworkCost_Unmetered; - break; - case NLM_CONNECTION_COST_FIXED: // The use of this connection is unrestricted up to a specific data transfer limit. - case NLM_CONNECTION_COST_VARIABLE: // This connection is regulated on a per byte basis. - result = NetworkCost_Metered; - break; - case NLM_CONNECTION_COST_OVERDATALIMIT: // The connection is currently in an OverDataLimit state as it has exceeded the carrier specified data transfer limit. - case NLM_CONNECTION_COST_CONGESTED: // The network is experiencing high traffic load and is congested. - case NLM_CONNECTION_COST_ROAMING: // The connection is roaming outside the network and affiliates of the home provider. - case NLM_CONNECTION_COST_APPROACHINGDATALIMIT: // The connection is approaching the data limit specified by the carrier. - result = NetworkCost_Roaming; - break; - case NLM_CONNECTION_COST_UNKNOWN: - default: - result = NetworkCost_Unknown; // The cost is unknown. - break; + workerScheduled = true; + auto context = new (std::nothrow) std::shared_ptr(shared_from_this()); + if (context == nullptr || + !QueueUserWorkItem(DispatchPendingEvents, context, WT_EXECUTEDEFAULT)) + { + delete context; + workerScheduled = false; + return false; } + return true; } - return result; -} - - /// - /// Get adapter id for IConnectionProfile - /// - /// - /// - std::string NetworkDetector::GetAdapterId(IConnectionProfile *profile) - { - if (!profile) + void StopAndWait() { - LOG_ERROR("Invalid profile pointer!"); - return ""; // Invalid interface ptr + std::unique_lock lock(mutex); + acceptEvents = false; + eventPending = false; + if (currentNetworkEventDispatch == this) + { + return; + } + cv.wait(lock, [this]() + { return !workerScheduled; }); } -#if 0 /* FIXME: do we return none if connectivity level is none? */ - NetworkConnectivityLevel connectivityLevel; - HRESULT hr = profile->GetNetworkConnectivityLevel(&connectivityLevel); - if (connectivityLevel != NetworkConnectivityLevel_None) + private: + static DWORD CALLBACK DispatchPendingEvents(void* context) { + std::shared_ptr state = + *static_cast*>(context); + delete static_cast*>(context); + currentNetworkEventDispatch = state.get(); + + while (true) + { + NetworkCost cost; + { + std::lock_guard lock(state->mutex); + if (!state->acceptEvents || !state->eventPending) + { + state->workerScheduled = false; + state->cv.notify_all(); + currentNetworkEventDispatch = nullptr; + return 0; + } + cost = state->latestCost; + state->eventPending = false; + } + DebugEvent evt; + evt.type = DebugEventType::EVT_NET_CHANGED; + evt.param1 = cost; + evt.param2 = false; + ILogManager::DispatchEventBroadcast(evt); + } } -#endif - ComPtr adapter; - HRESULT hr = profile->get_NetworkAdapter(&adapter); - if (hr == E_INVALIDARG) + std::mutex mutex; + std::condition_variable cv; + NetworkCost latestCost = NetworkCost_Unknown; + bool acceptEvents = true; + bool eventPending = false; + bool workerScheduled = false; + }; + + NetworkCost MapNetworkCost( + NetworkCostType costType, + boolean roaming, + boolean overDataLimit, + boolean approachingDataLimit, + boolean backgroundDataUsageRestricted) + { + if (roaming || overDataLimit || approachingDataLimit || backgroundDataUsageRestricted) { - // No interfaces - device is in airplane mode - LOG_TRACE("No network interfaces - device is in airplane mode"); - return ""; + return NetworkCost_Roaming; } - GUID id; - hr = adapter->get_NetworkAdapterId(&id); - if (!SUCCEEDED(hr)) + switch (costType) { - // Unable to obtain Network Adapter GUID - LOG_TRACE("Unable to obtain interface GUID"); - return ""; + case NetworkCostType_Unrestricted: + return NetworkCost_Unmetered; + case NetworkCostType_Fixed: + case NetworkCostType_Variable: + return NetworkCost_Metered; + case NetworkCostType_Unknown: + default: + return NetworkCost_Unknown; } - - return to_string(id); } - /// - /// COM thread interfaces supported by this class - /// - /// - /// - /// - HRESULT NetworkDetector::QueryInterface(REFIID riid, void ** ppv) noexcept + static NetworkCost QueryCurrentNetworkCost(INetworkInformationStatics* networkInfoStats) { - if (!ppv) - { - return E_POINTER; - } - - *ppv = nullptr; - HRESULT hr = E_NOINTERFACE; + NetworkCost result = NetworkCost_Unknown; + LOG_TRACE("get network cost...\n"); - if (IID_INetworkEvents == riid) + if (networkInfoStats == nullptr) { - *ppv = static_cast(this); - hr = S_OK; + LOG_WARN("Windows network information is unavailable!"); + return result; } - else if (IID_INetworkConnectionEvents == riid) + + ComPtr connectionProfile; + HRESULT hr = networkInfoStats->GetInternetConnectionProfile(&connectionProfile); + if (FAILED(hr) || connectionProfile == nullptr) { - *ppv = static_cast(this); - hr = S_OK; + return result; } - else if (IID_INetworkListManagerEvents == riid) + + ComPtr connectionCost; + hr = connectionProfile->GetConnectionCost(&connectionCost); + if (FAILED(hr) || connectionCost == nullptr) { - *ppv = static_cast(this); - hr = S_OK; + return result; } - else if (IID_IUnknown == riid) + + boolean roaming = false; + boolean overDataLimit = false; + boolean approachingDataLimit = false; + boolean backgroundDataUsageRestricted = false; + NetworkCostType costType = NetworkCostType_Unknown; + if (FAILED(connectionCost->get_Roaming(&roaming)) || + FAILED(connectionCost->get_OverDataLimit(&overDataLimit)) || + FAILED(connectionCost->get_ApproachingDataLimit(&approachingDataLimit)) || + FAILED(connectionCost->get_NetworkCostType(&costType))) { - *ppv = static_cast(static_cast(this)); - hr = S_OK; + return result; } - if (SUCCEEDED(hr)) + ComPtr connectionCost2; + if (SUCCEEDED(connectionCost.As(&connectionCost2)) && + FAILED(connectionCost2->get_BackgroundDataUsageRestricted(&backgroundDataUsageRestricted))) { - AddRef(); + return result; } - return hr; - } - - ULONG NetworkDetector::AddRef(void) noexcept - { - return InterlockedIncrement((LONG *)&m_lRef); + return MapNetworkCost( + costType, + roaming, + overDataLimit, + approachingDataLimit, + backgroundDataUsageRestricted); } - ULONG NetworkDetector::Release(void) noexcept + /// + /// Get current realtime network cost synchronously. + /// This function provides an SEH handler for Windows Runtime failures. + /// + static int RefreshNetworkCost( + INetworkInformationStatics* networkInfoStats, + std::atomic& currentNetworkCostState) { - ULONG ulNewRef = (ULONG)InterlockedDecrement((LONG *)&m_lRef); - if (ulNewRef == 0) + NetworkCost currentNetworkCost = NetworkCost_Unknown; + __try { - // NetworkDetector is destroyed from FlushAndTeardown. - // If customer forgets to call it, then it is destroyed from atexit(...) - LOG_TRACE("NetworkDetector last instance released (this=%p)", this); + currentNetworkCost = QueryCurrentNetworkCost(networkInfoStats); + } + //****************************************************************************************************************************** + // This code is required as a workaround for an issue in Visual Studio debug host mode: crash in W.N.C.dll + // + // onecoreuap\net\netprofiles\winrt\networkinformation\lib\handlemanager.cpp(132)\Windows.Networking.Connectivity.dll!0FBCFB9E: + // (caller: 0FBCEE2C) ReturnHr(1) tid(4584) 80070426 The service has not been started. + // + // Exception thrown at XXX (KernelBase.dll) in YYY : The binding handle is invalid. + // If there is a handler for this exception, the program may be safely continued. + //******************************************************************************************************************************* +#pragma warning(suppress : 6320) + __except (EXCEPTION_EXECUTE_HANDLER) + { + LOG_ERROR("Unable to obtain network state!"); } - return ulNewRef; - } - - HRESULT NetworkDetector::ConnectivityChanged(NLM_CONNECTIVITY newConnectivity) - { - LOG_TRACE("Connectivity changed: %d", newConnectivity); - m_connectivity = newConnectivity; - GetCurrentNetworkCost(); - return RPC_S_OK; - } - - HRESULT NetworkDetector::NetworkAdded(GUID networkId) - { - LOG_TRACE("NetworkAdded: %s", to_string(networkId).c_str()); - m_networks.push_back(to_string(networkId)); - return RPC_S_OK; - } - - HRESULT NetworkDetector::NetworkDeleted(GUID networkId) - { - LOG_TRACE("NetworkDeleted: %s", to_string(networkId).c_str()); - auto &v = m_networks; - const std::string &item = to_string(networkId); - v.erase(std::remove(v.begin(), v.end(), item), v.end()); - return RPC_S_OK; - } - HRESULT NetworkDetector::NetworkConnectivityChanged(GUID networkId, NLM_CONNECTIVITY newConnectivity) - { - LOG_TRACE("NetworkConnectivityChanged: %s, %d", to_string(networkId).c_str(), newConnectivity); - m_networks_connectivity[to_string(networkId)] = newConnectivity; - return RPC_S_OK; + currentNetworkCostState.store(currentNetworkCost, std::memory_order_relaxed); + return currentNetworkCost; } - - HRESULT NetworkDetector::NetworkPropertyChanged(GUID networkId, NLM_NETWORK_PROPERTY_CHANGE flags) + NetworkCost NetworkDetector::GetNetworkCost() { - UNREFERENCED_PARAMETER(networkId); - UNREFERENCED_PARAMETER(flags); - LOG_TRACE("NetworkPropertyChanged: %s, %d", to_string(networkId).c_str(), flags); - GetCurrentNetworkCost(); - return RPC_S_OK; + return m_currentNetworkCost->load(std::memory_order_relaxed); } - HRESULT NetworkDetector::NetworkConnectionConnectivityChanged(GUID connectionId, NLM_CONNECTIVITY newConnectivity) + int NetworkDetector::GetCurrentNetworkCost() { - LOG_TRACE("NetworkConnectionConnectivityChanged: %s, %d", to_string(connectionId).c_str(), newConnectivity); - m_connections_connectivity[to_string(connectionId)] = newConnectivity; - return RPC_S_OK; + const auto currentNetworkCost = + RefreshNetworkCost(networkInfoStats.Get(), *m_currentNetworkCost); + std::shared_ptr dispatchState; + { + std::lock_guard lock(m_lock); + dispatchState = eventDispatchState; + } + if (dispatchState != nullptr && !dispatchState->Queue(static_cast(currentNetworkCost))) + { + LOG_WARN("Unable to queue network status event."); + } + return currentNetworkCost; } - HRESULT NetworkDetector::NetworkConnectionPropertyChanged(GUID connectionId, NLM_CONNECTION_PROPERTY_CHANGE flags) + bool NetworkDetector::QueueNetworkCostRefresh() { - UNREFERENCED_PARAMETER(connectionId); - UNREFERENCED_PARAMETER(flags); - LOG_TRACE("NetworkConnectionPropertyChanged: %s, %d", to_string(connectionId).c_str(), flags); - return RPC_S_OK; + std::shared_ptr callbackState; + { + std::lock_guard lock(m_lock); + callbackState = networkStatusCallbackState; + } + return callbackState != nullptr && callbackState->QueueRefresh(); } /// @@ -359,199 +274,197 @@ namespace MAT_NS_BEGIN bool NetworkDetector::RegisterAndListen() noexcept { - // ??? - HRESULT hr = pNlm->QueryInterface(IID_IUnknown, (void**)&pSink); - if (FAILED(hr)) + MSG msg; + PeekMessage(&msg, nullptr, WM_USER, WM_USER, PM_NOREMOVE); + + const auto callbackState = networkStatusCallbackState; + callbackState->listenerThreadId.store(GetCurrentThreadId(), std::memory_order_release); + networkStatusChangedHandler = Callback( + [callbackState](IInspectable*) -> HRESULT + { + callbackState->QueueRefresh(); + return S_OK; + }); + if (networkStatusChangedHandler == nullptr) { - LOG_ERROR("cannot query IID_IUnknown!!!"); + LOG_ERROR("Unable to create network status handler."); + callbackState->listenerThreadId.store(0, std::memory_order_release); return false; } - pSink = (INetworkEvents*)this; - - hr = pNlm->QueryInterface(IID_IConnectionPointContainer, (void**)&pCpc); + HRESULT hr = networkInfoStats->add_NetworkStatusChanged( + networkStatusChangedHandler.Get(), + &networkStatusChangedToken); if (FAILED(hr)) { - LOG_ERROR("Unable to QueryInterface IID_IConnectionPointContainer!"); + LOG_ERROR("Unable to subscribe to network status changes."); + callbackState->listenerThreadId.store(0, std::memory_order_release); + networkStatusChangedHandler.Reset(); return false; } - hr = pCpc->FindConnectionPoint(IID_INetworkConnectionEvents, &m_pc1); - if (SUCCEEDED(hr)) { - hr = m_pc1->Advise( - pSink.Get(), - &m_dwCookie_INetworkConnectionEvents); - LOG_INFO("listening to INetworkConnectionEvents... %s", - (SUCCEEDED(hr)) ? "OK" : "FAILED"); + std::lock_guard lock(m_lock); + if (stopRequested) + { + startupState = StartupState::Failed; + cv.notify_all(); + return false; + } + startupState = StartupState::Ready; + cv.notify_all(); } - - hr = pCpc->FindConnectionPoint(IID_INetworkEvents, &m_pc2); - if (SUCCEEDED(hr)) + if (!eventDispatchState->Queue(GetNetworkCost())) { - hr = m_pc2->Advise( - pSink.Get(), - &m_dwCookie_INetworkEvents); - LOG_INFO("listening to INetworkEvents... %s", - (SUCCEEDED(hr)) ? "OK" : "FAILED"); + LOG_WARN("Unable to queue initial network status event."); } - hr = pCpc->FindConnectionPoint(IID_INetworkListManagerEvents, &m_pc3); - if (SUCCEEDED(hr)) + while (true) { - hr = m_pc3->Advise( - pSink.Get(), - &m_dwCookie_INetworkListManagerEvents); - LOG_INFO("listening to INetworkListManagerEvents... %s", - (SUCCEEDED(hr)) ? "OK" : "FAILED"); - } - - MSG msg; - PostThreadMessage(m_listener_tid, NETDETECTOR_START, 0, 0); - cv.notify_all(); + const DWORD waitResult = MsgWaitForMultipleObjectsEx( + 1, + &stopEvent, + INFINITE, + QS_ALLINPUT, + MWMO_INPUTAVAILABLE); + if (waitResult == WAIT_OBJECT_0) + { + break; + } + if (waitResult == WAIT_FAILED) + { + LOG_ERROR("Unable to wait for network detector events."); + return false; + } + if (!PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + { + continue; + } + if (msg.message == WM_QUIT) + { + break; + } - while (GetMessage(&msg, NULL, 0, 0) > 0) - { switch (msg.message) { - case NETDETECTOR_STOP: - PostQuitMessage(0); + case NETDETECTOR_REFRESH: + GetCurrentNetworkCost(); break; default: - break; + TranslateMessage(&msg); + DispatchMessage(&msg); } - TranslateMessage(&msg); - DispatchMessage(&msg); } return true; } /// - /// + /// /// void NetworkDetector::Reset() { - if (m_pc1 != nullptr) + if (networkStatusCallbackState != nullptr) { - m_pc1->Unadvise(m_dwCookie_INetworkConnectionEvents); - m_pc1 = nullptr; + networkStatusCallbackState->listenerThreadId.store(0, std::memory_order_release); } - - if (m_pc2 != nullptr) - { - m_pc2->Unadvise(m_dwCookie_INetworkEvents); - m_pc2 = nullptr; - } - - if (m_pc3 != nullptr) + if (networkStatusChangedToken.value != 0 && networkInfoStats != nullptr) { - m_pc3->Unadvise(m_dwCookie_INetworkListManagerEvents); - m_pc3 = nullptr; + const auto token = networkStatusChangedToken; + networkStatusChangedToken.value = 0; + networkInfoStats->remove_NetworkStatusChanged(token); } - - m_connection_profile.Reset(); - pSink.Reset(); - pCpc.Reset(); - + networkStatusChangedHandler.Reset(); networkInfoStats.Reset(); - if (pNlm != nullptr) - { - LOG_TRACE("release network list manager..."); - pNlm->Release(); - pNlm = nullptr; - }; } /// - /// Register for COM events and block-wait in RegisterAndListen + /// Register for Windows Runtime events and block-wait in RegisterAndListen /// -#pragma warning( push ) -#pragma warning(disable:28159) -#pragma warning(disable:4996) -#pragma warning(disable:6320) -// We must use GetVersionEx to retain backwards compat with Win 7 SP1 void NetworkDetector::run() { - // Check Windows version and if below Windows 8, then avoid running Network cost detection logic - OSVERSIONINFO osvi; - BOOL bIsWindows8orLater; - ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); - osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); - GetVersionEx(&osvi); - bIsWindows8orLater = ((osvi.dwMajorVersion >= 6) && (osvi.dwMinorVersion >= 2)) || (osvi.dwMajorVersion > 6); - // Applications not manifested for Windows 8.1 or Windows 10 will return the Windows 8 OS version value (6.2) - if (!bIsWindows8orLater) - { - LOG_INFO("Running on Windows %d.%d without network detector...", osvi.dwMajorVersion, osvi.dwMinorVersion); - return; - } + bool isRoInitialized = false; __try { - HRESULT hr = CoInitialize(nullptr); - if (FAILED(hr)) - { - LOG_ERROR("CoInitialize Failed."); - return; - } - - isCoInitialized = true; - if (GetNetworkInfoStats()) + __try { - LOG_INFO("create network list manager..."); - hr = CoCreateInstance( - CLSID_NetworkListManager, - nullptr, - CLSCTX_ALL, - IID_INetworkListManager, - (void**)&pNlm); + HRESULT hr = RoInitialize(RO_INIT_MULTITHREADED); if (FAILED(hr)) { - LOG_ERROR("Unable to CoCreateInstance for CLSID_NetworkListManager!"); + LOG_ERROR("RoInitialize failed."); + return; } - else + + isRoInitialized = true; + if (GetNetworkInfoStats()) { - GetCurrentNetworkCost(); + RefreshNetworkCost(networkInfoStats.Get(), *m_currentNetworkCost); LOG_TRACE("start listening to events..."); - RegisterAndListen(); // we block here to process COM events + RegisterAndListen(); } - // Once we are done OR cannot init NLM, we must perform the clean-up + } + __finally + { Reset(); } } +#pragma warning(suppress : 6320) __except (EXCEPTION_EXECUTE_HANDLER) { - LOG_ERROR("Handled exception in network cost detection (Windows 7?)"); + LOG_ERROR("Handled exception in Windows Runtime network cost detection."); } - if (isCoInitialized) + if (isRoInitialized) { - CoUninitialize(); - isCoInitialized = false; + RoUninitialize(); } - } -#pragma warning( pop ) - /// /// Start network monitoring thread /// /// true - if start is successful, false - otherwise bool NetworkDetector::Start() { + std::lock_guard lifecycleLock(m_lifecycleLock); { - std::lock_guard lk(m_lock); - if (isRunning) + std::unique_lock lock(m_lock); + if (startupState == StartupState::Starting) + { + cv.wait(lock, [this]() + { return startupState != StartupState::Starting; }); + } + if (startupState == StartupState::Ready) { LOG_TRACE("NetworkDetector tid=%p is already running", m_listener_tid); return true; } + + lock.unlock(); + if (netDetectThread.joinable()) + { + netDetectThread.join(); + } + lock.lock(); + + startupState = StartupState::Starting; + stopRequested = false; + networkStatusCallbackState = std::make_shared(); + eventDispatchState = std::make_shared(); + stopEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); + if (stopEvent == nullptr) + { + LOG_ERROR("Unable to create the network detector stop event."); + startupState = StartupState::Failed; + networkStatusCallbackState.reset(); + eventDispatchState.reset(); + return false; + } isRunning = true; } // Start a new thread. Notify waiters on exit. netDetectThread = std::thread([this]() - { + { { std::lock_guard lk(m_lock); m_listener_tid = GetCurrentThreadId(); @@ -562,37 +475,46 @@ namespace MAT_NS_BEGIN std::lock_guard lk(m_lock); m_listener_tid = 0; isRunning = false; + if (startupState == StartupState::Starting) + { + startupState = StartupState::Failed; + } + else if (startupState == StartupState::Ready) + { + startupState = StartupState::Stopped; + } cv.notify_all(); - } - }); + } }); - if (netDetectThread.joinable()) { LOG_TRACE("NetworkDetector is starting..."); + bool started; { std::unique_lock lock(m_lock); - // Wait for up to NETDETECTOR_COM_SETTLE_MS ms until: - // - COM object is ready; OR - // - COM object can't be started (pre-Win 8 scenario) - int retry = 1; - constexpr int max_retries = 2; - while (isRunning && cv.wait_for(lock, std::chrono::milliseconds(NETDETECTOR_COM_SETTLE_MS)) - == std::cv_status::timeout && (retry < max_retries)) - { - LOG_TRACE("NetworkDetector starting up... [%u]", retry); - retry++; - } - LOG_TRACE("NetworkDetector tid=%p running=%u", m_listener_tid, isRunning); + cv.wait(lock, [this]() + { return startupState != StartupState::Starting; }); + started = startupState == StartupState::Ready; + LOG_TRACE( + "NetworkDetector tid=%p running=%u", + m_listener_tid, + started); } - } - else - { - std::lock_guard lk(m_lock); - LOG_WARN("NetworkDetector thread can't be started!"); - isRunning = false; - } - return isRunning; + if (!started && netDetectThread.joinable()) + { + netDetectThread.join(); + } + if (!started) + { + std::lock_guard lock(m_lock); + CloseHandle(stopEvent); + stopEvent = nullptr; + networkStatusCallbackState.reset(); + eventDispatchState->StopAndWait(); + eventDispatchState.reset(); + } + return started; + } }; /// @@ -600,33 +522,33 @@ namespace MAT_NS_BEGIN /// void NetworkDetector::Stop() { + std::lock_guard lifecycleLock(m_lifecycleLock); if (netDetectThread.joinable()) { - std::unique_lock lk(m_lock); - try { - if (!isRunning || m_listener_tid == 0 || - !PostThreadMessage(m_listener_tid, NETDETECTOR_STOP, 0, NULL)) + { + std::lock_guard lock(m_lock); + stopRequested = true; + if (networkStatusCallbackState != nullptr) { - // Without detaching, we risk throwing an exception in the destructor. - // There is a chance that our code has finished, but the thread - // hasn't fully terminated, or the thread has already exited and - // isRunning is false. Alternatively, we may have never gotten - // a thread_id. - netDetectThread.detach(); - LOG_WARN("NetworkDetector thread unable to be shut down."); + networkStatusCallbackState->listenerThreadId.store(0, std::memory_order_release); } - else + if (!SetEvent(stopEvent)) { - lk.unlock(); - netDetectThread.join(); - LOG_TRACE("NetworkDetector tid=%p has stopped.", m_listener_tid); + LOG_ERROR("Unable to signal the network detector stop event."); } } - catch (std::system_error &ex) - { - UNREFERENCED_PARAMETER(ex); - LOG_WARN("NetworkDetector tid=%p is already stopped.", m_listener_tid); - } + + netDetectThread.join(); + eventDispatchState->StopAndWait(); + + std::lock_guard lock(m_lock); + CloseHandle(stopEvent); + stopEvent = nullptr; + startupState = StartupState::Stopped; + stopRequested = false; + networkStatusCallbackState.reset(); + eventDispatchState.reset(); + LOG_TRACE("NetworkDetector tid=%p has stopped.", m_listener_tid); } }; @@ -641,139 +563,9 @@ namespace MAT_NS_BEGIN LOG_TRACE("NetworkDetector done tid=%p", m_listener_tid); } - /// - /// Get network cost name - /// - /// - /// - const char* NetworkDetector::GetNetworkCostName(NetworkCostType type) - { - switch (type) { - case NetworkCostType_Unrestricted: - return "Unrestricted"; - case NetworkCostType_Fixed: - return "Fixed"; - case NetworkCostType_Variable: - return "Variable"; - case NetworkCostType_Unknown: - default: - return "Unknown"; - } - } - - const std::map& NetworkDetector::GetNetworksConnectivity() - { - return m_networks_connectivity; - } - - const std::map& NetworkDetector::GetConnectionsConnectivity() - { - return m_connections_connectivity; - } - - /// - /// Obtain various details about network stack - /// - void NetworkDetector::GetNetworkDetails() - { - LOG_TRACE("Getting network details..."); - ComPtr> hostNames; - HRESULT hr = networkInfoStats->GetHostNames(&hostNames); - if ((!SUCCEEDED(hr))||(!hostNames)) - return; - - m_hostnames.clear(); - unsigned int hostNameCount; - hr = hostNames->get_Size(&hostNameCount); - if (!SUCCEEDED(hr)) - return; - for (unsigned i = 0; i < hostNameCount; ++i) { - MATW::HostNameInfo hostInfo; - ComPtr hostName; - hr = hostNames->GetAt(i, &hostName); - if (!SUCCEEDED(hr)) - continue; - HString rawName; - hostName->get_RawName(rawName.GetAddressOf()); - LOG_TRACE("RawName: %s", to_string(&rawName).c_str()); - - HostNameType type; - hr = hostName->get_Type(&type); - if (!SUCCEEDED(hr)) - continue; - LOG_TRACE("HostNameType: %d", type); - - if (type == HostNameType_DomainName) - continue; - - ComPtr ipInformation; - hr = hostName->get_IPInformation(&ipInformation); - if (!SUCCEEDED(hr)) - continue; - - ComPtr currentAdapter; - hr = ipInformation->get_NetworkAdapter(¤tAdapter); - if (!SUCCEEDED(hr)) - continue; - hr = currentAdapter->get_NetworkAdapterId(&hostInfo.adapterId); - if (!SUCCEEDED(hr)) - continue; - LOG_TRACE("CurrentAdapterId: %s", to_string(hostInfo.adapterId).c_str()); - - ComPtr> prefixLengthReference; - hr = ipInformation->get_PrefixLength(&prefixLengthReference); - if (!SUCCEEDED(hr)) - continue; - hr = prefixLengthReference->get_Value(&hostInfo.prefixLength); - if (!SUCCEEDED(hr)) - continue; - LOG_TRACE("PrefixLength: %d", hostInfo.prefixLength); - - // invalid prefixes - if ((type == HostNameType_Ipv4 && hostInfo.prefixLength > 32) - || (type == HostNameType_Ipv6 && hostInfo.prefixLength > 128)) - continue; - - HString name; - hr = hostName->get_CanonicalName(name.GetAddressOf()); - if (!SUCCEEDED(hr)) - continue; - hostInfo.address = to_string(&name); - LOG_TRACE("CanonicalName: %s", hostInfo.address.c_str()); - - m_hostnames.push_back(hostInfo); - } - - // hr = networkInfoStats->GetInternetConnectionProfile(&m_connection_profile); - // auto profile0 = m_connection_profile.Get(); - - ComPtr> m_connection_profiles; - hr = networkInfoStats->GetConnectionProfiles(&m_connection_profiles); - if (!SUCCEEDED(hr)) - return; - - unsigned int size; - hr = m_connection_profiles->get_Size(&size); - if (!SUCCEEDED(hr)) - return; - - for (unsigned int i = 0; i < size; ++i) { - ComPtr profile; - hr = m_connection_profiles->GetAt(i, &profile); - if (!SUCCEEDED(hr)) - continue; - auto prof = profile.Get(); - HString name; - hr = prof->get_ProfileName(name.GetAddressOf()); - if (!SUCCEEDED(hr)) - continue; - LOG_TRACE("Profile[%d]: name = %s", i, to_string(&name).c_str()); - LOG_TRACE("Profile[%d]: guid = %s", i, GetAdapterId(prof).c_str()); - } - } - - } // ::Windows + } // ::Windows -} MAT_NS_END +} +MAT_NS_END #endif diff --git a/lib/pal/desktop/NetworkDetector.hpp b/lib/pal/desktop/NetworkDetector.hpp index 1404334b4..2b4080e04 100644 --- a/lib/pal/desktop/NetworkDetector.hpp +++ b/lib/pal/desktop/NetworkDetector.hpp @@ -7,8 +7,6 @@ #include "mat/config.h" #ifdef HAVE_MAT_NETDETECT -#pragma once - // Including SDKDDKVer.h defines the highest available Windows platform. // If you wish to build your application for a previous Windows platform, include WinSDKVer.h and @@ -16,101 +14,57 @@ #include -//#ifndef WIN32_LEAN_AND_MEAN -//#define WIN32_LEAN_AND_MEAN 1 -//#endif - -#pragma once - #include #include #include -#include -#include #include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include #include +#include +#include +#include +#include #include "Enums.hpp" -// #include - using namespace Microsoft::WRL; using namespace Microsoft::WRL::Wrappers; using namespace ABI::Windows::Foundation; -using namespace ABI::Windows::Foundation::Collections; -using namespace ABI::Windows::Networking; using namespace ABI::Windows::Networking::Connectivity; -using namespace std; - namespace MAT_NS_BEGIN { namespace Windows { - /// - /// Host name information structure - /// - struct HostNameInfo { - GUID adapterId; - unsigned char prefixLength; - std::string address; - }; - - /// - /// Convert HString to std::string - /// - /// - /// - std::string to_string(HString *name); - - /// - /// Convert GUID to std::string - /// - /// - /// - std::string to_string(GUID guid); - - class NetworkDetector: public INetworkEvents, INetworkConnectionEvents, INetworkListManagerEvents { - - private: + NetworkCost MapNetworkCost( + NetworkCostType costType, + boolean roaming, + boolean overDataLimit, + boolean approachingDataLimit, + boolean backgroundDataUsageRestricted); + + class NetworkDetector + { + private: + struct CallbackState; + struct EventDispatchState; + enum class StartupState + { + Stopped, + Starting, + Ready, + Failed + }; /// /// Current network info stats /// - ComPtr networkInfoStats; - - - /// - /// Current connection profile - /// - ComPtr m_connection_profile; - - /// - /// COM INetworkListManager - /// - INetworkListManager* pNlm; - - /// - /// Obtain network cost RO. This function does not handle potential exceptions and must only be called from GetNetworkCost() - /// - /// - NetworkCost _GetCurrentNetworkCost(); + ComPtr networkInfoStats; + ComPtr networkStatusChangedHandler; + EventRegistrationToken networkStatusChangedToken{}; + std::shared_ptr networkStatusCallbackState; + std::shared_ptr eventDispatchState; /// /// Get instance of network info stats @@ -118,29 +72,21 @@ namespace MAT_NS_BEGIN /// bool GetNetworkInfoStats(); - std::mutex m_lock; - std::condition_variable cv; - bool isRunning = false; - bool isCoInitialized = false; - std::thread netDetectThread; + std::mutex m_lifecycleLock; + std::mutex m_lock; + std::condition_variable cv; + std::atomic isRunning{false}; + std::thread netDetectThread; + StartupState startupState = StartupState::Stopped; + bool stopRequested = false; + HANDLE stopEvent = nullptr; /// - /// + /// /// void run(); - ComPtr pSink; - ComPtr pCpc; - ComPtr m_pc1, m_pc2, m_pc3; - - ULONG m_lRef; - - DWORD m_dwCookie_INetworkEvents; - DWORD m_dwCookie_INetworkConnectionEvents; - DWORD m_dwCookie_INetworkListManagerEvents; - DWORD m_listener_tid = 0; - - NLM_CONNECTIVITY m_connectivity; + DWORD m_listener_tid = 0; /// /// Register and listen to network state notifications @@ -153,41 +99,21 @@ namespace MAT_NS_BEGIN /// void Reset(); - std::vector m_networks; - std::map m_networks_connectivity; - std::map m_connections_connectivity; - std::vector m_hostnames; - int m_currentNetworkCost; - std::atomic m_currentNetworkType; + std::shared_ptr> m_currentNetworkCost{ + std::make_shared>(NetworkCost_Unknown) + }; public: /// /// /// - bool isUp() { return isRunning; }; + bool isUp() { return isRunning.load(std::memory_order_relaxed); }; /// /// Createa network status listener /// - NetworkDetector() : - pNlm(nullptr), - networkInfoStats(nullptr), - m_connection_profile(nullptr), - pSink(nullptr), - pCpc(nullptr), - m_pc1(nullptr), - m_pc2(nullptr), - m_pc3(nullptr), - isRunning(false), - isCoInitialized(false), - m_dwCookie_INetworkEvents(0), - m_dwCookie_INetworkConnectionEvents(0), - m_dwCookie_INetworkListManagerEvents(0), - m_currentNetworkCost(0), - m_currentNetworkType(NetworkType_Unknown), - m_listener_tid(0) - {}; + NetworkDetector() = default; /// /// @@ -203,14 +129,7 @@ namespace MAT_NS_BEGIN /// /// /// - virtual ~NetworkDetector(); - - /// - /// - /// - /// - /// - const char *GetNetworkCostName(NetworkCostType type); + ~NetworkDetector(); /// /// Get current network cost @@ -222,52 +141,13 @@ namespace MAT_NS_BEGIN /// Get last cached network cost /// /// - NetworkCost const& GetNetworkCost(); + NetworkCost GetNetworkCost(); /// - /// Get last cached network type + /// Queue the same refresh performed by a WinRT network status callback. /// - /// - NetworkType GetNetworkType(); - - /// - /// Get adapter ID for connection profile - /// - /// - /// - std::string GetAdapterId(IConnectionProfile *profile); - - int GetConnectivity(); - - const std::map& GetNetworksConnectivity(); - const std::map& GetConnectionsConnectivity(); - - void GetNetworkDetails(); - - IConnectionProfile* GetCurrentConnectionProfile() - { - return m_connection_profile.Get(); - } - - public: - - // Inherited via INetworkListManagerEvents - virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void ** ppvObject) noexcept override; - virtual ULONG STDMETHODCALLTYPE AddRef(void) noexcept override; - virtual ULONG STDMETHODCALLTYPE Release(void) noexcept override; - virtual HRESULT STDMETHODCALLTYPE ConnectivityChanged(NLM_CONNECTIVITY newConnectivity) override; - - // Inherited via INetworkEvents - virtual HRESULT STDMETHODCALLTYPE NetworkAdded(GUID networkId) override; - virtual HRESULT STDMETHODCALLTYPE NetworkDeleted(GUID networkId) override; - virtual HRESULT STDMETHODCALLTYPE NetworkConnectivityChanged(GUID networkId, NLM_CONNECTIVITY newConnectivity) override; - virtual HRESULT STDMETHODCALLTYPE NetworkPropertyChanged(GUID networkId, NLM_NETWORK_PROPERTY_CHANGE flags) override; - - // Inherited via INetworkConnectionEvents - virtual HRESULT STDMETHODCALLTYPE NetworkConnectionConnectivityChanged(GUID connectionId, NLM_CONNECTIVITY newConnectivity) override; - virtual HRESULT STDMETHODCALLTYPE NetworkConnectionPropertyChanged(GUID connectionId, NLM_CONNECTION_PROPERTY_CHANGE flags) override; + bool QueueNetworkCostRefresh(); }; - } } MAT_NS_END diff --git a/lib/pal/desktop/WindowsDesktopNetworkInformationImpl.cpp b/lib/pal/desktop/WindowsDesktopNetworkInformationImpl.cpp index df388c7ea..d3082a313 100644 --- a/lib/pal/desktop/WindowsDesktopNetworkInformationImpl.cpp +++ b/lib/pal/desktop/WindowsDesktopNetworkInformationImpl.cpp @@ -89,7 +89,6 @@ namespace PAL_NS_BEGIN { #ifdef HAVE_MAT_NETDETECT if (m_isNetDetectEnabled) { networkDetector = std::unique_ptr(new MATW::NetworkDetector()); - networkDetector->AddRef(); networkDetector->Start(); } #endif @@ -101,7 +100,6 @@ namespace PAL_NS_BEGIN { #ifdef HAVE_MAT_NETDETECT if (m_isNetDetectEnabled) { networkDetector->Stop(); - networkDetector->Release(); } #endif } @@ -112,4 +110,3 @@ namespace PAL_NS_BEGIN { } } PAL_NS_END #endif - diff --git a/tests/functests/APITest.cpp b/tests/functests/APITest.cpp index 251c30cd3..1500ec30e 100644 --- a/tests/functests/APITest.cpp +++ b/tests/functests/APITest.cpp @@ -1344,55 +1344,22 @@ TEST(APITest, WindowsHttpTransport_MsRoot_Check) EXPECT_EQ(accepted.response->GetResult(), HttpResult_OK); } -/* This test verifies the certificate policy used by either Windows HTTP transport. */ TEST(APITest, LogConfiguration_MsRoot_Check) { - TestDebugEventListener debugListener; - std::list> testParams = - { - {"https://v10.events.data.microsoft.com/OneCollector/1.0/", false, 1}, // MS-Rooted, no MS-Root check: post succeeds + auto client = HttpClientFactory::Create(); #if defined(HAVE_MAT_WININET_HTTP_CLIENT) - {"https://v10.events.data.microsoft.com/OneCollector/1.0/", true, 0}, // WinInet cannot safely enforce the policy before sending + auto windowsClient = dynamic_cast(client.get()); #else - {"https://v10.events.data.microsoft.com/OneCollector/1.0/", true, 1}, // MS-Rooted, MS-Root check: post succeeds + auto windowsClient = dynamic_cast(client.get()); #endif - {"https://mobile.events.data.microsoft.com/OneCollector/1.0/", false, 1}, // Non-MS rooted, no MS-Root check: post succeeds - {"https://mobile.events.data.microsoft.com/OneCollector/1.0/", true, 0} // Non-MS rooted, MS-Root check: post fails - }; + ASSERT_NE(windowsClient, nullptr); - // 4 test runs - for (const auto& params : testParams) + auto& config = LogManager::GetLogConfiguration(); + for (bool enforceMsRoot : {false, true, false}) { - CleanStorage(); - - auto& config = LogManager::GetLogConfiguration(); - config[CFG_MAP_METASTATS_CONFIG][CFG_INT_METASTATS_INTERVAL] = 0; // avoid sending stats for this test, just customer events - config[CFG_STR_COLLECTOR_URL] = std::get<0>(params); - config[CFG_MAP_HTTP][CFG_BOOL_HTTP_MS_ROOT_CHECK] = std::get<1>(params); // MS root check depends on what URL we are sending to - config[CFG_INT_MAX_TEARDOWN_TIME] = 1; // up to 1s wait to perform HTTP post on teardown - config[CFG_STR_CACHE_FILE_PATH] = GetStoragePath(); - auto expectedHttpCount = std::get<2>(params); - - auto logger = LogManager::Initialize(TEST_TOKEN, config); - - debugListener.reset(); - addAllListeners(debugListener); - logger->LogEvent("fooBar"); - LogManager::UploadNow(); - const auto deadline = PAL::getMonotonicTimeMs() + 10000; - while (PAL::getMonotonicTimeMs() < deadline && - debugListener.numHttpOK.load() == 0 && - debugListener.numHttpError.load() == 0) - { - PAL::sleep(50); - } - LogManager::FlushAndTeardown(); - removeAllListeners(debugListener); - - // The successful cases establish that the runner can reach both - // endpoints, so the rejected case cannot pass merely because external - // networking is unavailable. - EXPECT_EQ(debugListener.numHttpOK.load(), expectedHttpCount); + config[CFG_MAP_HTTP][CFG_BOOL_HTTP_MS_ROOT_CHECK] = enforceMsRoot; + client->ApplySettings(config); + EXPECT_EQ(windowsClient->IsMsRootCheckRequired(), enforceMsRoot); } } #endif diff --git a/tests/headers/check_public_headers.cmd b/tests/headers/check_public_headers.cmd index 53860422c..4bb82cd69 100644 --- a/tests/headers/check_public_headers.cmd +++ b/tests/headers/check_public_headers.cmd @@ -51,8 +51,9 @@ if errorlevel 1 ( ) REM /W4 /WX matches ORT; /external:W0 suppresses platform/STL warnings so only our headers gate. -set "CXX_COMMON=/nologo /permissive- /W4 /WX /EHsc /experimental:external /external:anglebrackets /external:W0" -set "C_COMMON=/nologo /std:c11 /TC /W4 /WX /experimental:external /external:anglebrackets /external:W0" +set "WINDOWS_API_FLOOR=/DWINVER=0x0A00 /D_WIN32_WINNT=0x0A00" +set "CXX_COMMON=/nologo /permissive- /W4 /WX /EHsc /experimental:external /external:anglebrackets /external:W0 %WINDOWS_API_FLOOR%" +set "C_COMMON=/nologo /std:c11 /TC /W4 /WX /experimental:external /external:anglebrackets /external:W0 %WINDOWS_API_FLOOR%" set "FAIL=0" set "TOTAL=0" diff --git a/tests/unittests/AnnexKTests.cpp b/tests/unittests/AnnexKTests.cpp index c07ec3663..78bc3d590 100644 --- a/tests/unittests/AnnexKTests.cpp +++ b/tests/unittests/AnnexKTests.cpp @@ -8,27 +8,27 @@ TEST(AnnexKTests, memcpy_s) { volatile size_t dest_size =10; volatile size_t src_size = 5; - void *dest = malloc(sizeof(char) * dest_size); - void *src = malloc(sizeof(char) * src_size); - EXPECT_EQ(BoundCheckFunctions::oneds_memcpy_s(src, 5, "TEST", 5), 0); + std::unique_ptr dest(malloc(sizeof(char) * dest_size), &free); + std::unique_ptr src(malloc(sizeof(char) * src_size), &free); + EXPECT_EQ(BoundCheckFunctions::oneds_memcpy_s(src.get(), 5, "TEST", 5), 0); rsize_t dest_len = dest_size; rsize_t src_len = src_size-1; // success tests - EXPECT_EQ(BoundCheckFunctions::oneds_memcpy_s(dest, dest_len, src, 0), 0); - EXPECT_EQ(BoundCheckFunctions::oneds_memcpy_s( dest, dest_len, src, src_len + 1), 0); - EXPECT_EQ(strlen((char *)dest), strlen("TEST")); - EXPECT_EQ(BoundCheckFunctions::oneds_memcpy_s(dest, dest_len + 2, src, src_len + 1), 0); - EXPECT_EQ(strlen((char *)dest), strlen("TEST")); + EXPECT_EQ(BoundCheckFunctions::oneds_memcpy_s(dest.get(), dest_len, src.get(), 0), 0); + EXPECT_EQ(BoundCheckFunctions::oneds_memcpy_s(dest.get(), dest_len, src.get(), src_len + 1), 0); + EXPECT_EQ(strlen(static_cast(dest.get())), strlen("TEST")); + EXPECT_EQ(BoundCheckFunctions::oneds_memcpy_s(dest.get(), dest_len + 2, src.get(), src_len + 1), 0); + EXPECT_EQ(strlen(static_cast(dest.get())), strlen("TEST")); // error tests - EXPECT_EQ(BoundCheckFunctions::oneds_memcpy_s(dest, 3, src, src_len), EINVAL); - EXPECT_EQ(((char *)dest)[0], '\0'); - EXPECT_EQ(BoundCheckFunctions::oneds_memcpy_s(NULL, 3, src, src_len), EINVAL); - EXPECT_EQ(BoundCheckFunctions::oneds_memcpy_s( dest, dest_len, NULL, src_len), EINVAL); - EXPECT_EQ(((char *)dest)[0], '\0'); - EXPECT_EQ(BoundCheckFunctions::oneds_memcpy_s( dest, dest_len, src, dest_len + 1 ), EINVAL); - EXPECT_EQ(BoundCheckFunctions::oneds_memcpy_s( dest, dest_len, (void *)((char *)dest + 1), src_len + 1 ), EINVAL); + EXPECT_EQ(BoundCheckFunctions::oneds_memcpy_s(dest.get(), 3, src.get(), src_len), EINVAL); + EXPECT_EQ(static_cast(dest.get())[0], '\0'); + EXPECT_EQ(BoundCheckFunctions::oneds_memcpy_s(NULL, 3, src.get(), src_len), EINVAL); + EXPECT_EQ(BoundCheckFunctions::oneds_memcpy_s(dest.get(), dest_len, NULL, src_len), EINVAL); + EXPECT_EQ(static_cast(dest.get())[0], '\0'); + EXPECT_EQ(BoundCheckFunctions::oneds_memcpy_s(dest.get(), dest_len, src.get(), dest_len + 1), EINVAL); + EXPECT_EQ(BoundCheckFunctions::oneds_memcpy_s(dest.get(), dest_len, static_cast(dest.get()) + 1, src_len + 1), EINVAL); } TEST(AnnexKTests, memcpy_sAllowsAdjacentBuffers) diff --git a/tests/unittests/CMakeLists.txt b/tests/unittests/CMakeLists.txt index a9cfce9fd..bcdb0acb8 100644 --- a/tests/unittests/CMakeLists.txt +++ b/tests/unittests/CMakeLists.txt @@ -76,6 +76,10 @@ if (APPLE) endif() endif() +if (WIN32) + list(APPEND SRCS NetworkDetectorTests.cpp) +endif() + if (EXISTS "${PROJECT_SOURCE_DIR}/lib/modules/exp/tests") list(APPEND SRCS "${PROJECT_SOURCE_DIR}/lib/modules/exp/tests/unittests/ECSConfigCacheTests.cpp" diff --git a/tests/unittests/HttpClientTests.cpp b/tests/unittests/HttpClientTests.cpp index 6dc4bb8e0..d73280d5d 100644 --- a/tests/unittests/HttpClientTests.cpp +++ b/tests/unittests/HttpClientTests.cpp @@ -1188,9 +1188,11 @@ TEST_F(HttpClientTests, SurvivesManyRequests) Clear(); size_t Count = 100; + std::vector> requests; + requests.reserve(Count); for (size_t i = 0; i < Count; i++) { - IHttpRequest* request = _client->CreateRequest(); - // _requests.push_back(request); + requests.emplace_back(_client->CreateRequest()); + IHttpRequest* request = requests.back().get(); request->SetMethod("POST"); request->GetHeaders().set("expect", "100-continue"); request->GetHeaders().set("content-type", "application/octet-stream"); diff --git a/tests/unittests/LogSessionDataDBTests.cpp b/tests/unittests/LogSessionDataDBTests.cpp index dbda5fd27..a8e090113 100644 --- a/tests/unittests/LogSessionDataDBTests.cpp +++ b/tests/unittests/LogSessionDataDBTests.cpp @@ -75,6 +75,8 @@ class LogSessionDataDBTests : public ::testing::Test virtual void TearDown() override { + delete logSessionDataProvider; + logSessionDataProvider = nullptr; std::remove(name.str().c_str()); offlineStorage->Shutdown(); offlineStorage.reset(); diff --git a/tests/unittests/NetworkDetectorTests.cpp b/tests/unittests/NetworkDetectorTests.cpp new file mode 100644 index 000000000..0103a4c11 --- /dev/null +++ b/tests/unittests/NetworkDetectorTests.cpp @@ -0,0 +1,139 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. + +#include "common/Common.hpp" + +#if defined(_WIN32) && defined(HAVE_MAT_NETDETECT) +#include "api/LogManagerFactory.hpp" +#include "pal/desktop/NetworkDetector.hpp" + +#include + +using namespace MAT; +using namespace testing; + +class StopDetectorOnNetworkChange : public DebugEventListener +{ + public: + explicit StopDetectorOnNetworkChange(MATW::NetworkDetector& detector) : + detector(detector) + { + } + + void OnDebugEvent(DebugEvent& event) override + { + if (event.type == EVT_NET_CHANGED && !handled.exchange(true)) + { + detector.Stop(); + stopped.set_value(); + } + } + + std::future GetStoppedFuture() + { + return stopped.get_future(); + } + + private: + MATW::NetworkDetector& detector; + std::atomic handled{false}; + std::promise stopped; +}; + +TEST(NetworkDetectorTests, MapsWinRTNetworkCosts) +{ + EXPECT_EQ(MATW::MapNetworkCost(NetworkCostType_Unrestricted, false, false, false, false), NetworkCost_Unmetered); + EXPECT_EQ(MATW::MapNetworkCost(NetworkCostType_Fixed, false, false, false, false), NetworkCost_Metered); + EXPECT_EQ(MATW::MapNetworkCost(NetworkCostType_Variable, false, false, false, false), NetworkCost_Metered); + EXPECT_EQ(MATW::MapNetworkCost(NetworkCostType_Unknown, false, false, false, false), NetworkCost_Unknown); +} + +TEST(NetworkDetectorTests, MapsRestrictiveWinRTNetworkStates) +{ + EXPECT_EQ(MATW::MapNetworkCost(NetworkCostType_Unrestricted, true, false, false, false), NetworkCost_Roaming); + EXPECT_EQ(MATW::MapNetworkCost(NetworkCostType_Unrestricted, false, true, false, false), NetworkCost_Roaming); + EXPECT_EQ(MATW::MapNetworkCost(NetworkCostType_Unrestricted, false, false, true, false), NetworkCost_Roaming); + EXPECT_EQ(MATW::MapNetworkCost(NetworkCostType_Unrestricted, false, false, false, true), NetworkCost_Roaming); +} + +TEST(NetworkDetectorTests, StartsReadsCostAndStopsWithoutNetworkListManager) +{ + ASSERT_EQ(GetModuleHandleW(L"netprofm.dll"), nullptr); + + MATW::NetworkDetector detector; + ASSERT_TRUE(detector.Start()); + EXPECT_TRUE(detector.isUp()); + EXPECT_EQ(GetModuleHandleW(L"netprofm.dll"), nullptr); + + const auto cost = detector.GetCurrentNetworkCost(); + EXPECT_THAT(cost, AnyOf( + Eq(NetworkCost_Unknown), + Eq(NetworkCost_Unmetered), + Eq(NetworkCost_Metered), + Eq(NetworkCost_Roaming))); + EXPECT_EQ(detector.GetNetworkCost(), cost); + + detector.Stop(); + EXPECT_FALSE(detector.isUp()); + EXPECT_FALSE(detector.QueueNetworkCostRefresh()); + EXPECT_EQ(GetModuleHandleW(L"netprofm.dll"), nullptr); +} + +TEST(NetworkDetectorTests, QueuedNetworkCallbackRaceDoesNotOutliveStop) +{ + MATW::NetworkDetector detector; + ASSERT_TRUE(detector.Start()); + + std::atomic keepQueuing{true}; + std::thread callbackThread([&]() + { + while (keepQueuing.load(std::memory_order_acquire)) { + detector.QueueNetworkCostRefresh(); + } }); + + detector.Stop(); + EXPECT_FALSE(detector.QueueNetworkCostRefresh()); + keepQueuing.store(false, std::memory_order_release); + callbackThread.join(); +} + +TEST(NetworkDetectorTests, ConcurrentStopWaitsForStartupPublication) +{ + for (int iteration = 0; iteration < 20; ++iteration) + { + MATW::NetworkDetector detector; + std::atomic startReturned{false}; + std::thread startThread([&]() + { + detector.Start(); + startReturned.store(true, std::memory_order_release); }); + + while (!detector.isUp() && !startReturned.load(std::memory_order_acquire)) + { + std::this_thread::yield(); + } + + detector.Stop(); + startThread.join(); + EXPECT_FALSE(detector.isUp()); + } +} + +TEST(NetworkDetectorTests, NetworkChangeListenerCanStopDetector) +{ + ILogConfiguration configuration; + configuration[CFG_BOOL_ENABLE_NET_DETECT] = false; + ILogManager* logManager = LogManagerFactory::Create(configuration); + ASSERT_NE(logManager, nullptr); + MATW::NetworkDetector detector; + StopDetectorOnNetworkChange listener(detector); + auto stopped = listener.GetStoppedFuture(); + logManager->AddEventListener(EVT_NET_CHANGED, listener); + + ASSERT_TRUE(detector.Start()); + ASSERT_EQ(stopped.wait_for(std::chrono::seconds(5)), std::future_status::ready); + EXPECT_FALSE(detector.isUp()); + + logManager->RemoveEventListener(EVT_NET_CHANGED, listener); + EXPECT_EQ(LogManagerFactory::Destroy(logManager), STATUS_SUCCESS); +} +#endif diff --git a/tests/unittests/UnitTests.vcxproj b/tests/unittests/UnitTests.vcxproj index 0a42fde2a..89da8f8be 100644 --- a/tests/unittests/UnitTests.vcxproj +++ b/tests/unittests/UnitTests.vcxproj @@ -458,6 +458,7 @@ + diff --git a/tools/build-common.sh b/tools/build-common.sh index 1ba0ecb56..b5de4da70 100644 --- a/tools/build-common.sh +++ b/tools/build-common.sh @@ -34,7 +34,10 @@ matsdk_try_buildtools_once() { if [ ! -f "$marker_file" ]; then if [ $# -gt 0 ]; then - "$@" || echo "$failure_message" + if ! "$@"; then + echo "$failure_message" + return 1 + fi fi matsdk_mark_buildtools_checked "$marker_file" fi diff --git a/tools/setup-buildtools-android.cmd b/tools/setup-buildtools-android.cmd index 429de7ac1..ca3b5839f 100644 --- a/tools/setup-buildtools-android.cmd +++ b/tools/setup-buildtools-android.cmd @@ -1,33 +1,64 @@ -if exist ".buildtools" ( - echo Skiping dependencies installation -) else ( - pushd "%~dp0" - where choco >NUL 2>NUL - if %ERRORLEVEL% neq 0 call "%~dp0\setup-choco.cmd" - - echo Installing dependencies [requires Admin / elevated Command prompt] - echo ANDROID_SDK_ROOT = %ANDROID_SDK_ROOT% - echo ANDROID_HOME = %ANDROID_HOME% - echo ANDROID_NDK = %ANDROID_NDK% - echo ANDROID_NDK_HOME = %ANDROID_NDK_HOME% - - if not exist "%USERPROFILE%\.android\repositories.cfg" ( - echo Creating default repositories.cfg ... - copy NUL "%USERPROFILE%\.android\repositories.cfg" - ) - - REM Use chocolatey for basic deps - choco install --no-progress -y android-sdk - choco install --no-progress -y ninja - - REM Use sdkmanager for additional deps - pushd %ANDROID_SDK_ROOT%\tools\bin - echo y | call sdkmanager.bat --include_obsolete --verbose --sdk_root=%ANDROID_SDK_ROOT% "platforms;android-28" "sources;android-28" - echo y | call sdkmanager.bat --include_obsolete --verbose --sdk_root=%ANDROID_SDK_ROOT% "platforms;android-29" "sources;android-29" - echo y | call sdkmanager.bat --include_obsolete --verbose --sdk_root=%ANDROID_SDK_ROOT% "build-tools;29.0.3" - echo y | call sdkmanager.bat --include_obsolete --verbose --sdk_root=%ANDROID_SDK_ROOT% "platform-tools" - echo y | call sdkmanager.bat --install --include_obsolete --verbose --sdk_root=%ANDROID_SDK_ROOT% "ndk-bundle" "cmake;%ANDROID_CMAKE_VERSION%" "ndk;%ANDROID_NDK_VERSION%" - popd - popd - copy NUL .buildtools +@echo off +set "BUILD_TOOLS_MARKER=.buildtools" +set "BUILD_TOOLS_MARKER_VERSION=android-cmake-%ANDROID_CMAKE_VERSION%-ndk-%ANDROID_NDK_VERSION%" +set "BUILD_TOOLS_MARKER_ACTUAL=" + +if not exist "%BUILD_TOOLS_MARKER%" goto install +set /p BUILD_TOOLS_MARKER_ACTUAL=<"%BUILD_TOOLS_MARKER%" +if /I not "%BUILD_TOOLS_MARKER_ACTUAL%"=="%BUILD_TOOLS_MARKER_VERSION%" goto install +if not exist "%ANDROID_SDK_ROOT%\cmake\%ANDROID_CMAKE_VERSION%\bin\cmake.exe" goto install +if not exist "%ANDROID_NDK%" goto install +echo Skipping dependencies installation +exit /b 0 + +:install +del "%BUILD_TOOLS_MARKER%" 2>NUL +pushd "%~dp0" +where choco >NUL 2>NUL +if errorlevel 1 call "%~dp0\setup-choco.cmd" +if errorlevel 1 goto install_failed + +echo Installing dependencies [requires Admin / elevated Command prompt] +echo ANDROID_SDK_ROOT = %ANDROID_SDK_ROOT% +echo ANDROID_HOME = %ANDROID_HOME% +echo ANDROID_NDK = %ANDROID_NDK% +echo ANDROID_NDK_HOME = %ANDROID_NDK_HOME% + +if not exist "%USERPROFILE%\.android\repositories.cfg" ( + echo Creating default repositories.cfg ... + copy NUL "%USERPROFILE%\.android\repositories.cfg" + if errorlevel 1 goto install_failed ) + +REM Use chocolatey for basic deps +call choco install --no-progress -y android-sdk +if errorlevel 1 goto install_failed +call choco install --no-progress -y ninja +if errorlevel 1 goto install_failed + +REM Use sdkmanager for additional deps +pushd "%ANDROID_SDK_ROOT%\tools\bin" +if errorlevel 1 goto install_failed +echo y | call .\sdkmanager.bat --include_obsolete --verbose --sdk_root="%ANDROID_SDK_ROOT%" "platforms;android-28" "sources;android-28" +if errorlevel 1 goto sdkmanager_failed +echo y | call .\sdkmanager.bat --include_obsolete --verbose --sdk_root="%ANDROID_SDK_ROOT%" "platforms;android-29" "sources;android-29" +if errorlevel 1 goto sdkmanager_failed +echo y | call .\sdkmanager.bat --include_obsolete --verbose --sdk_root="%ANDROID_SDK_ROOT%" "build-tools;29.0.3" +if errorlevel 1 goto sdkmanager_failed +echo y | call .\sdkmanager.bat --include_obsolete --verbose --sdk_root="%ANDROID_SDK_ROOT%" "platform-tools" +if errorlevel 1 goto sdkmanager_failed +echo y | call .\sdkmanager.bat --install --include_obsolete --verbose --sdk_root="%ANDROID_SDK_ROOT%" "ndk-bundle" "cmake;%ANDROID_CMAKE_VERSION%" "ndk;%ANDROID_NDK_VERSION%" +if errorlevel 1 goto sdkmanager_failed +popd +popd + +if not exist "%ANDROID_SDK_ROOT%\cmake\%ANDROID_CMAKE_VERSION%\bin\cmake.exe" exit /b 1 +if not exist "%ANDROID_NDK%" exit /b 1 +>"%BUILD_TOOLS_MARKER%" echo %BUILD_TOOLS_MARKER_VERSION% +exit /b 0 + +:sdkmanager_failed +popd +:install_failed +popd +exit /b 1