Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/build-android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ jobs:
run: |
.\gradlew.bat maesdk:test
working-directory: lib\android_build
- name: Gradle Build without native device-ID collection
run: |
.\gradlew.bat maesdk:assembleDebug app:assembleDebug -PMATSDK_ENABLE_DEVICE_ID=OFF
working-directory: lib\android_build
- name: Upload Reports
if: failure()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
Expand Down
31 changes: 31 additions & 0 deletions .github/workflows/build-posix-latest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,37 @@ jobs:
- name: Test ${{ matrix.os }} ${{ matrix.config }}
run: ./build-tests.sh ${{ matrix.config }}

device-id-optout:
name: Device-ID opt-out (${{ matrix.os }})
runs-on: ${{ matrix.os }}
env:
CMAKE_POLICY_VERSION_MINIMUM: "3.5"
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macOS-latest, windows-2022]
steps:
- name: Checkout
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- name: Initialize googletest
run: git submodule update --init --depth=1 third_party/googletest
- name: Test Linux/macOS without native device-ID collection
if: runner.os != 'Windows'
env:
CMAKE_OPTS: -DMATSDK_ENABLE_DEVICE_ID=OFF
run: ./build-tests.sh debug
- name: setup-msbuild
if: runner.os == 'Windows'
uses: microsoft/setup-msbuild@6fb02220983dee41ce7ae257b6f4d8f9bf5ed4ce # v2.0.0
with:
vs-version: '[17,)'
- name: Test Windows without native device-ID collection
if: runner.os == 'Windows'
shell: cmd
run: build-tests.cmd x64 Debug "tests\device-id-optout.props" WinHTTP
- name: Verify vcpkg feature mapping
run: cmake -P tests/vcpkg/device-id-feature-tests.cmake

public-headers:
name: Public header gate (GCC/Clang)
runs-on: ubuntu-latest
Expand Down
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ if(MATSDK_DISABLE_LOGGING)
target_compile_definitions(matsdk_internal_config INTERFACE
MATSDK_DISABLE_LOGGING)
endif()
if(NOT MATSDK_ENABLE_DEVICE_ID)
target_compile_definitions(matsdk_internal_config INTERFACE
MATSDK_DISABLE_DEVICE_ID)
endif()

option(GCC5_CXX11_ABI_WORKAROUND "Workaround: Use legacy C++11 ABI (for GCC 5 compatibility)" OFF)
if(GCC5_CXX11_ABI_WORKAROUND)
Expand Down
2 changes: 2 additions & 0 deletions cmake/MatsdkOptions.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ option(MATSDK_USE_WININET
"Use WinInet instead of WinHTTP as the Win32 desktop HTTP client" OFF)
option(MATSDK_DISABLE_LOGGING
"Compile internal SDK logging out" OFF)
option(MATSDK_ENABLE_DEVICE_ID
"Collect a device ID using the SDK's platform-specific implementation" ON)
Comment thread
bmehta001 marked this conversation as resolved.

set(_matsdk_android_http_client_predefined OFF)
if(DEFINED MATSDK_ANDROID_HTTP_CLIENT)
Expand Down
20 changes: 20 additions & 0 deletions docs/CsProtocol-decoding.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,23 @@ Please refer to usage examples:

* EventDecoderListener.cpp [EventDecoderListener::DecodeBuffer](../tests/functests/EventDecoderListener.cpp) for HTTP(s) request - Package of Records decoding
* APITest.cpp [UTC_Callback_Test](../tests/functests/APITest.cpp) for Common Schema Record decoding

## Decoder input limits

Diagnostic request decoding rejects inputs larger than 64 MiB, including
compressed input and the inflated output. Inflation uses a fixed-size work
buffer and stops before growing the output beyond that limit; it does not
estimate output size from a presumed compression ratio.

Binary container counts must fit the remaining encoded input (at least one
byte per sequence element or two bytes per map entry). Impossible counts are
rejected before generated readers allocate their containers; no separate
fixed element-count or record-count limit is imposed.
Record-boundary scanning reads the request in place and copies only each
individual record, so scanning and input copying are linear in request size.
Decoding failure returns `false` rather than reporting a partial request as
success; partial inflation output
must not be used after a failed call.

These diagnostic limits do not change the event submission API or its
configured serialized-event size policy.
55 changes: 55 additions & 0 deletions docs/PAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,61 @@

## Remote configuration (ECS client)

## Device-ID collection

`MATSDK_ENABLE_DEVICE_ID` defaults to `ON`. Configure with
`-DMATSDK_ENABLE_DEVICE_ID=OFF` to compile out the SDK's native device-ID
collectors, including machine-ID reads, fallback shell commands, adapter
queries, host UUIDs, and vendor/Android identifiers. Other system and device
metadata remain enabled. The macOS build also stops linking the ID-specific
IOKit framework; dependencies still used by other features remain.

Without a supplied ID, `DeviceInfo.Id` is omitted rather than replaced with a
placeholder. Applications can still supply their own ID through
`ISemanticContext::SetDeviceId`; registration does not overwrite it with an
empty automatically collected ID. Android's Java bridge consults the native
build setting before accessing `ANDROID_ID`; use the matching Java bridge
sources with the native SDK. For Android Gradle builds, pass
`-PMATSDK_ENABLE_DEVICE_ID=OFF` to apply the setting to both the SDK AAR and
the test application. The Gradle property takes precedence over the
`MATSDK_ENABLE_DEVICE_ID` environment variable; both default to `ON` when
unspecified.

This option does not disable session/SDK identifiers or control device IDs
added independently by the operating system's UTC telemetry pipeline.

## Targeted input safeguards

The SDK bounds the command-line input involved in initialization and buffered
HTTP responses. It does not impose a blanket metadata limit on system,
device, application, or network-provider strings, additional limits on
OS-reported buffer sizes, or a size limit on persisted session files.

| Input | Limit | Oversize behavior |
| --- | --- | --- |
| POSIX application identifier | 4 KiB, stopping at the first NUL in `/proc/self/cmdline` | Truncate the executable name; never collect arguments or run a regex |
| HTTP response body | 16 MiB | Fail the request rather than retain an oversized response |
| HTTP response headers | 64 KiB | Fail the request rather than retain oversized headers |

POSIX OS release values use exact line-key matching rather than recursive
regular expressions. Curl response headers are also parsed without regex.
Command-line truncation preserves UTF-8 boundaries. Other metadata is not
truncated by these safeguards. Session files retain the existing platform
text-read behavior; session parsing accepts both LF and CRLF line endings.
The header budget includes framing for native raw headers or a minimum
four-byte allowance per name/value pair. Windows native queries measure raw
UTF-16/ANSI buffer bytes; WinRT conservatively budgets up to three UTF-8 bytes
per UTF-16 unit. Android counts JNI modified UTF-8 bytes without allocating
encoded strings, including two bytes for NUL and three per surrogate, before
JNI additionally checks their encoded byte sizes. OS networking frameworks may
have their own internal limits; the SDK limits its own copies and streaming
body reads.

Caller-provided events retain the existing configured serialized-event,
upload, and offline-cache size policies; no new per-property limit is applied.
See [decoder limits](CsProtocol-decoding.md#decoder-input-limits) for the
separate diagnostic decoding budget.

## Bandwidth manager (Resource manager)

- Get available bandwidth
Expand Down
40 changes: 40 additions & 0 deletions docs/building-with-vcpkg.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,46 @@ project's `vcpkg.json`:

## Platform-Specific Instructions

### Disabling SDK device-ID collection

The in-repo overlay port has a default-enabled `device-id` feature, mapped to
the default-on CMake option `MATSDK_ENABLE_DEVICE_ID`. To supply your own
`DeviceInfo.Id` without running the SDK's native collectors, omit that feature
and explicitly retain the other features you need:

```json
{
"dependencies": [
{
"name": "cpp-client-telemetry",
"default-features": false,
"features": ["curl-openssl", "system-sqlite"]
}
]
}
```

For classic mode:

```console
vcpkg install "cpp-client-telemetry[core,curl-openssl,system-sqlite]" --overlay-ports=tools/ports
```

`curl-openssl` affects Linux only; Android still defaults to Java transport.
`[core,...]` and `default-features: false` drop **all** defaults, including
`device-id`. Add `device-id` explicitly when combining non-default features
but wanting to retain native ID collection.

vcpkg features are additive across the dependency graph: another dependency
requesting `device-id` (or the default features) re-enables collection. Ensure
all requests for this port agree when opting out.

The official registry requires a port update pointing to an SDK release
containing this option; existing registry versions do not expose the feature.
Until then, use this repository's overlay port and source checkout. The port
rejects an opt-out against an older SDK rather than silently collecting an ID.
For direct CMake builds, use `-DMATSDK_ENABLE_DEVICE_ID=OFF`.

### Windows

```powershell
Expand Down
6 changes: 6 additions & 0 deletions docs/embedding-with-cmake.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ Set it explicitly to `ON` in consumer CI to test new toolchains strictly.
Set `MATSDK_DISABLE_LOGGING=ON` to compile the SDK's internal diagnostic
logging out. This does not disable telemetry event collection or upload.

Set `MATSDK_ENABLE_DEVICE_ID=OFF` before adding the SDK to compile out native
device-ID collection while retaining other metadata. It defaults to `ON`.
Applications can still set their own ID through `ISemanticContext::SetDeviceId`;
without one, `DeviceInfo.Id` is omitted. See [device-ID collection](PAL.md#device-id-collection)
for platform details and the distinction from session/SDK identifiers.

## SQLite and zlib providers

Source builds can select dependency modes without patching 1DS sources:
Expand Down
2 changes: 1 addition & 1 deletion lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -701,7 +701,7 @@ elseif(APPLE)
)
if(MATSDK_PLATFORM_IOS)
target_link_libraries(mat PUBLIC "-framework UIKit")
else()
elseif(MATSDK_ENABLE_DEVICE_ID)
target_link_libraries(mat PUBLIC "-framework IOKit")
endif()
endif()
Expand Down
1 change: 1 addition & 0 deletions lib/android_build/app/src/main/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ set(TESTS_SRCS
${SDK_ROOT}/tests/unittests/DataViewerCollectionTests.cpp
${SDK_ROOT}/tests/unittests/DebugEventSourceTests.cpp
${SDK_ROOT}/tests/unittests/DeviceStateHandlerTests.cpp
${SDK_ROOT}/tests/unittests/device-id-tests.cpp
${SDK_ROOT}/tests/unittests/DiskLocalStorageTests.cpp
${SDK_ROOT}/tests/unittests/EventFilterCollectionTests.cpp
${SDK_ROOT}/tests/unittests/EventPropertiesStorageTests.cpp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
import androidx.annotation.RequiresApi;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
Expand Down Expand Up @@ -87,6 +90,56 @@ public final void onCapabilitiesChanged(

class Request implements HttpClientRequest {

static final int MAX_HTTP_RESPONSE_SIZE = 16 * 1024 * 1024;
static final int MAX_HTTP_RESPONSE_HEADERS_SIZE = 64 * 1024;

static byte[] readResponseBody(InputStream input) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int count;
while ((count = input.read(buffer)) != -1) {
if (count > MAX_HTTP_RESPONSE_SIZE - output.size()) {
throw new IOException("HTTP response body exceeds buffered size limit");
}
output.write(buffer, 0, count);
}
return output.toByteArray();
}

static String[] readResponseHeaders(Map<String, List<String>> headers) throws IOException {
Vector<String> result = new Vector<>();
int remaining = MAX_HTTP_RESPONSE_HEADERS_SIZE;
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
if (entry.getKey() != null) {
for (String value : entry.getValue()) {
if (remaining < 4) {
throw new IOException("HTTP response headers exceed buffered size limit");
}
remaining = remainingHeaderBytes(entry.getKey(), remaining - 4);
remaining = remainingHeaderBytes(value, remaining);
result.add(entry.getKey());
result.add(value);
}
}
}
return result.toArray(new String[0]);
}

private static int remainingHeaderBytes(String value, int remaining) throws IOException {
if (value.length() > remaining) {
throw new IOException("HTTP response headers exceed buffered size limit");
}
for (int i = 0; i < value.length(); ++i) {
// Match JNI modified UTF-8: NUL uses two bytes and each surrogate uses three.
char c = value.charAt(i);
remaining -= c != 0 && c <= 0x7f ? 1 : c <= 0x7ff ? 2 : 3;
if (remaining < 0) {
throw new IOException("HTTP response headers exceed buffered size limit");
}
}
return remaining;
}

Request(
@NonNull HttpClient parent,
String url,
Expand All @@ -111,8 +164,6 @@ class Request implements HttpClientRequest {
}

public void run() {
final boolean logExceptions = false;

String[] headerArray = {};
byte[] body = {};
int response = 0;
Expand All @@ -122,49 +173,19 @@ public void run() {
body_stream.write(m_body);
}
response = m_connection.getResponseCode(); // may throw
Map<String, List<String>> headers = m_connection.getHeaderFields();
Vector<String> headerList = new Vector<>();
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
if (entry.getKey() != null) {
for (String v : entry.getValue()) {
headerList.add(entry.getKey());
headerList.add(v);
}
}
}
headerArray = headerList.toArray(headerArray);
BufferedInputStream in;
if (response >= 300) {
in = new BufferedInputStream(m_connection.getErrorStream());
} else {
in = new BufferedInputStream(m_connection.getInputStream());
}
byte[] buffer = new byte[1024];
Vector<byte[]> buffers = new Vector<>();
int size = 0;
while (true) {
int n = in.read(buffer, 0, 1024);
if (n < 0) {
break;
}
if (n > 0) {
buffers.add(java.util.Arrays.copyOfRange(buffer, 0, n));
size += n;
}
}
body = new byte[size];
int index = 0;
for (byte[] chunk : buffers) {
for (byte b : chunk) {
body[index] = b;
index += 1;
headerArray = readResponseHeaders(m_connection.getHeaderFields());
InputStream stream = response >= 300
? m_connection.getErrorStream() : m_connection.getInputStream();
if (stream != null) {
try (BufferedInputStream in = new BufferedInputStream(stream)) {
body = readResponseBody(in);
}
}
} catch (Exception e) {
/* pass this on as a response of 0 */
if (logExceptions) {
Log.e("MAE", "Exception in callback", e);
}
Log.e("MAE", "HTTP response failed", e);
response = -1;
headerArray = new String[0];
body = new byte[0];
} finally {
m_connection.disconnect();
}
Expand Down Expand Up @@ -220,7 +241,7 @@ public HttpClient(Context context, @NonNull HttpClientRequest.Factory requestFac
String path = System.getProperty("java.io.tmpdir");
setCacheFilePath(path);
if (configuration.isInitializeDeviceInfo()) {
setDeviceInfo(calculateID(context), Build.MANUFACTURER, Build.MODEL);
setDeviceInfo(calculateID(context, isDeviceIdCollectionEnabled()), Build.MANUFACTURER, Build.MODEL);
}
calculateAndSetSystemInfo(context);
m_executor = createExecutor();
Expand Down Expand Up @@ -310,7 +331,10 @@ private void calculateAndSetSystemInfo(android.content.Context context) {
device_class);
}

private String calculateID(android.content.Context context) {
static String calculateID(android.content.Context context, boolean collectDeviceId) {
if (!collectDeviceId) {
return "";
}
// The definition of ANDROID_ID changed in API 26.
// https://developer.android.com/reference/android/provider/Settings.Secure#ANDROID_ID

Expand Down Expand Up @@ -372,6 +396,8 @@ public URL newUrl(String url) throws java.net.MalformedURLException {

public native void setDeviceInfo(String id, String manufacturer, String model);

public native boolean isDeviceIdCollectionEnabled();

public native void setSystemInfo(
String app_id,
String app_version,
Expand Down
Loading
Loading