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