# ACR and backend telemetry components in webOS 24 ## Subject An extracted root filesystem, with all mounts, taken from a rooted LG television. | | | | --- | --- | | Model | `55NANO82T6B.BEUSLJP` — NANO82 series, 55", LCD/NanoCell | | Model year | 2024 (`manufactureYear: 2024`, platform code `W24P`, product code `webOSTV 24`) | | Panel / tuner | UD (2160p) at 60 Hz, CEDS interface; DVB (`sysType: DVB`), tuner present | | SoC / memory | `K8LPN2` (machine `k8lpn2`), 1.5 GB DDR | | OS | webOS TV 24; starfish release `Rockhopper release 10.2.2-5901 (ponytail-paparoa)`; platform version `10.2.2` | | Firmware | `33.22.86`; bootloader `9.00.89/9.00.89` | | Service country | NL / NLD; UI locale `nl-NL`, formats and NLP `en-GB`; language group `EU` | | SDP region | EIC (LG's Europe/rest-of-world region), environment `Production` | | Channel map | `NL-webOS-6.4.0-MR`, version `202609030035` | | Serial | `50[REDACTED 10 digits]` (full value in `var-18/preferences/configd_db.json`, key `tv.model`) | The update server advertises `33.31.61` as the current official NL build for this model (`mnt-6/lg/cmn_data/sdp/sdx/detailconfig.json`, `nsu` block), so the image is one or more releases behind the shipping firmware. Developer mode is not enabled (`var-18/luna/preferences/devmode_enabled` is empty). Snapshot age: the newest file written by the TV itself is `mnt-6/lg/cmn_data/acr/data/power_off_info`, timestamped 2026-09-04 16:48 UTC, which is the last power-off. Analysis was performed 2026-09-07. Runtime state described below is therefore the state at that last power-off, not at first boot. Sources for the table: `etc/starfish-release`, `var-18/preferences/configd_db.json` (`tv.model`, `tv.nyx`, `tv.hw`), `mnt-6/lg/cmn_data/var/webos-profile/device_selection.json`, `var-18/luna/preferences/localeInfo`, `mnt-6/lg/cmn_data/pbs/pbs_config.json`. ## Scope and method Static inspection only. Paths below are relative to the directory containing this file. `usr-2/` is the mount holding `/usr`, `mnt-6/` holds `/mnt/lg`, `var-18/` holds `/var`, and `etc/` holds `/etc`. Where a component's on-device path matters it is given in absolute form as the binary itself refers to it. The approach was: locate services and packages by name, read their Luna manifests and startup activities, extract strings from the ELF binaries, and read the runtime state files the components leave in `/mnt/lg/cmn_data`. Nothing was executed and no traffic was captured. Claims about what a binary sends are drawn from format strings, URL fragments and symbol names, so field lists are indicative rather than an observed wire format. Where plaintext source was available (QML, JavaScript, Python, shell) that is stated, and those parts are exact. Identifiers belonging to this device or to third parties are redacted in place, marked `[REDACTED …]`, with enough structure left to show the shape of the value. Three occur: the BSSIDs of two neighbouring access points in §1.5, the device identifier in §6.1, and the ad server session token in §2.1. The device serial in the table above is partially masked. Nothing else in this report is specific to one unit. --- ## 1. Automatic Content Recognition ### 1.1 The service `usr-2/palm/services/com.webos.service.acr/services.json` states the purpose directly: ```json { "id": "com.webos.service.acr", "description": "Automatic Content Recognition", "services": [ { "name": "com.webos.service.acr" } ] } ``` The package manifest (`usr-2/palm/packages/com.webos.service.acr/packageinfo.json`) titles it "ACR Service", version 0.0.1. Files that make it up: | Path | Size | Role | | --- | ---: | --- | | `usr-2/sbin/acr2` | 558 684 | The daemon. Capture plumbing and Luna API. | | `usr-2/lib/libalphonsosolution.so.1.0.0` | 87 472 | Adapter between `acr2` and the vendor SDK. | | `usr-2/lib/libas.so.3.0.93` | 2 565 924 | Alphonso ACR SDK. Fingerprinting and network I/O. | | `usr-2/lib/libalphonsoadoverlay.so.1.0.0` | 50 608 | Same adapter pattern for the ad-overlay service. | | `usr-2/lib/libvtcapture.so.1.0.0` | — | Video/screen capture (`screen-capture-webos` 1.0.0-56). | | `usr-2/share/acr/acr.a.2.1.4.db.zero.mp3` | 295 184 | Shipped fingerprint database, despite the `.mp3` extension. | | `usr-2/palm/applications/com.webos.app.acrcomponent` | — | Hidden overlay WebView. | | `usr-2/palm/applications/com.webos.app.acroverlay` | — | Same, bound to live TV. | | `usr-2/palm/applications/com.webos.app.acrhdmi{1..4}` | — | Same, one per HDMI input. | | `ROOT/opt/webos/tests/acr2/gtest_acr2` | — | Unit tests, left in the image. | | `etc/pmlog.d/acr.conf` | — | Log context configuration. | `acr2` links `libasound.so.2` and `libvtcapture.so.1` alongside `libluna-service2`, `libcrypto.so.3` and `libjsoncpp`. ### 1.2 What it captures Symbol and string extraction from `acr2` shows two capture paths running in their own threads. Audio: classes `capture::AudioCapture` and `capture::AlsaAudioWrapper`, wrapping ALSA (`PcmOpen`, `PcmHwParamsSetRateNear`, `PcmHwParamsSetChannels`, `PcmReadi`), with a recovery loop that logs `[AudioCapture] Audio Capture Recovery count[%d]`. This is a tap on the TV's own audio mixdown, not a microphone. Video: classes `capture::VideoCapture`, `capture::ImageConverter`, `capture::CaptureBuff`, `capture::CaptureDelegate`, plus a string `DaiFastCapture`. `libvtcapture` is the LG `screen-capture-webos` library, whose API strings (`create vt capture handle`, `process vt capture called by %s`, `vtHistogram`) describe frame grabs off the video pipeline. DAI most plausibly stands for dynamic ad insertion, which would make the fast-capture path an ad-break detector, but I did not confirm that. Configuration keys present in the binary include `capture_method`, `whitelist_method`, `colorInversion`, `acr_config_format_ver`, `firmware_version`, `webos_version`. ### 1.3 Vendor engine and loading `acr2` contains a `core::SolutionLoader` that resolves a vendor engine at runtime from `/mnt/lg/cmn_data/acr/lib/`, passing it `WebOSVersion()` and `FirmwareVersion()`. That directory is empty on this device, so the preinstalled `libalphonsosolution.so` and `libas.so` are what is in use. `libalphonsosolution.so` exports `CreateSolution` and implements `solution::alphonso::AlphonsoSolution` and `AlphonsoClient`. `libas.so.3.0.93` retains its build path, which names the vendor and the LG port: ``` /mnt/alpha/acr-sdk/sdk-build/git-lg-webos-24/acr-sdk/common/audio/AudioHdlr.cpp /mnt/alpha/acr-sdk/sdk-build/git-lg-webos-24/acr-sdk/common/as-client/ASClientOptout.cpp /mnt/alpha/acr-sdk/sdk-build/git-lg-webos-24/acr-sdk/common/utils/UtilsReadWifiProperties.cpp ``` The audio handler is a state machine with states for `PreBuffering`, `Running`, `Sleeping`, `RunningPO`, `SleepingPO` and `InitedLivetvExpress`, plus `SplitACR.cpp` and constants `ACR_TYPE_SPLIT`, `ACR_SHIFT_TYPE_93`, `ACR_SHIFT_TYPE_186`. "PO" appears to mean power-off, given `AudioHdlrPOSleepTimerCB` and `AudioHdlrPOPrimeTimeTimerCB` — a scheduled, prime-time- weighted wake pattern. Source types are enumerated as `ASAPI_SOURCE_LOCAL_AUDIO_TYPE_{PLATFORM,EXTERNAL,MIC}`, so the SDK has a microphone input mode compiled in; nothing in `acr2` selects it, and `acr2` only opens ALSA capture on the platform mixdown. On a match, `AlphonsoClient` logs the identifiers it received: ``` [AlphonsoClient] ContentResult : tms program id : %s, tms station id : %s [AlphonsoClient] AlphonsoMatchCB: json_info: %s ``` TMS IDs are Gracenote's content and station identifiers, which is what ties a fingerprint back to a named programme and channel. ### 1.4 Where fingerprints go Hostnames in `libas.so.3.0.93`: - `prov-lg.alphonso.tv` (`ASAPI_PROV_SERVER`) — provisioning; the ACR server address is then supplied dynamically (`ASAPI_ACR_SERVER`, `AS_ACR_SERVER_UPDATE`). - `eulacheck.alphonso.tv` — consent check. API paths in the same binary: ``` /audio/fingerprint /user /user/location /user/lookups /user/timed-lookups /user/appsource /recommendation /recommendation/info /bl/config /device/config /device/clock /countryList /latestVersion /log/tar /sdk_debug/stats recommendation/v1/ott recommendation/v1/ott_feedback ``` Query parameters seen as fragments: `alp_uid=`, `device_id=`, `api_key=`, `zipcode=`, `ipaddr=`, `app_version_code=`, `app_version_name=`, and log strings printing `longitude:` and `zipcode:` next to each other. `/user/appsource` alongside `ASAPI_SOURCE_LOCATION_{LOCAL,REMOTE}` suggests the SDK also reports which input or app the audio came from. ### 1.5 Wi-Fi scanning for location `libas.so.3.0.93` contains `UtilsReadWifiProperties.cpp`, a dedicated `ASUtilsReadWifiPropertiesThread`, a `startScan()` and `processUpdateWifiScanInfo()`, a comparator `compareBySignalStrength`, and these strings: ``` Wifi Scanning Start Wifi Scanning Stop Wifi Scanning disabled Recvd enable_wifi_scan: LocationClient() with lat/long and wifiScanOutput. Empty wifiScanJsonOutput: ``` `acr2` has the matching `interface::Wifi` class with `OnFindNetworks` and writes the result to disk. The file is present on this device: `mnt-6/lg/cmn_data/acr/data/wifi_network_info` ```json [{"mac_address":"[REDACTED MAC]","signal_strength":"-57"}, {"mac_address":"[REDACTED MAC]","signal_strength":"-63"}] ``` Those are neighbouring access points, not the TV's own radio. A BSSID list with signal strengths is the standard input to a Wi-Fi geolocation lookup, which resolves to a street-level position without GPS and without the user entering an address. The scan is enabled server-side (`Recvd enable_wifi_scan:`), so it is not governed by a setting in the TV's menus. ### 1.6 Runtime state and current status `mnt-6/lg/cmn_data/acr/data/`: | File | Contents | | --- | --- | | `optin` | `optin` | | `first_optout` | `first_optout` | | `eula_allowed` | `allowed` | | `sdp_server` | `Production` | | `service_country` | `NL` | | `power_off_info` | `{"time_stamp":1788540517}` | | `wifi_network_info` | see above | `var-18/luna/preferences/option` contains `"livePlus": "on"`. Live Plus is the menu name for this feature. ACR is therefore enabled on this device, and the opt-in is recorded both in the Luna settings store and in the ACR service's own state directory. Factory defaults in `etc/palm/defaultSettings.json` are the opposite: `acrAllowed`, `acrOnAllowed`, `acrAdAllowed` and `acrGdprAllowed` are all `false`. The enabled state came from setup or from the EULA acceptance flow, not from the shipped defaults. Vendor logging is disabled. `mnt-6/lg/cmn_data/acr/alphonso/log4cplus.properties`: ``` log4cplus.rootLogger=ERROR, R log4cplus.appender.R=log4cplus::NullAppender ``` Everything at ERROR and below is routed to a null appender. A debug variant exists at `usr-2/share/acr/log4cplus-debug.properties`, gated on `/var/luna/preferences/alphonso_debug`. ### 1.7 Startup `etc/palm/activities/com.webos.service.acr/activity-com.webos.service.acr.start.json` registers `luna://com.webos.service.acr/startAcr` with `{"reason":"normal"}`, triggered on `com.webos.bootManager/getBootStatus` reaching `rest-boot-done`. So the service starts on every boot, not on demand. `acr2` also registers activities that restart it when the advertising identifier changes (`"name": "ccpa changed"`, watching `com.webos.service.admanager/getAdid` for `IFA` changes) and on network connect, EULA acceptance, store-mode change, power on and pre-power-off. Its own log messages enumerate these: "ACR service as dynamic for connection network", "for eula allowed", "for live plus menu", "for power on", "for prepare power off", "for store mode". `usr-2/share/luna-service2/client-permissions.d/com.webos.service.acr.perm.json` grants the service, and its sync and async client handles, `["all"]` — unrestricted access to the Luna bus. ### 1.8 The overlay apps `com.webos.app.acrcomponent`, `com.webos.app.acroverlay` and `com.webos.app.acrhdmi1` through `acrhdmi4` are near-identical hidden web apps, all titled "LivePlus", all placed on a window layer named `acrLayer` owned by live TV or by the external-input app, with `"visible": false` and `"class": {"hidden": true}`. `index.html` sets a zero-size input region and `KeyMaskNone`, so the app is present but takes no input, then loads `present_frm.html` into an iframe. `present_frm.html` is four lines of logic: ```js var params = JSON.parse(window.PalmSystem.launchParams); if (params.contentTarget != undefined) { window.location = params.contentTarget; } ``` The container navigates to whatever URL the service passes in `contentTarget`, and re-navigates on `webOSRelaunch`. The rendered content is entirely server-controlled. The per-HDMI variants mean overlays can be drawn over an external device's picture, not only over the tuner. --- ## 2. Advertising services ### 2.1 admanager `usr-2/sbin/admanager` (293 428 bytes), service `com.webos.service.admanager`, described in its manifest as "Advertisement Service Manager". Endpoint host in the binary: `info.lgsmartad.com`. Paths: ``` /rest/json/v1.0/req /rest/json/v1.0/baseinfo /rest/json/v1.0/appinfo /rest/xml/v1.0/api/DNSInfo /rest/xml/v1.0/api/TNC?TNC= ``` The live cookie jar `mnt-6/lg/cmn_data/admanager/cookie/ad_cookie` shows the regional host this device actually talks to: ``` #HttpOnly_.nl.ad.lgsmartad.com TRUE /rest FALSE 0 JSESSIONID [REDACTED 32 hex chars] ``` The class `CIFAdService` owns the advertising identifier. Methods include `getAdRequestInfo`, `requestCampaignInfo`, `requestContextIndex`, `requestBaseAsset`, `sendAssetMsg`, `assetClicked`, `reqNormalLog`, `startDownloadUrl`, `createSDKContext` and `launchCMPApp`. Request parameters `&ifa=` and `&ifa_type=` are built inline, and the IFA is fetched from the Security Manager (`Failed to get IFA from Security Manager`). Reset is handled by `MSG_AD_RESET_IFA`, which emits the telemetry event `NL_ADMAN_IFA_RST`. `mnt-6/lg/cmn_data/admanager/cache/` holds around two dozen downloaded creatives, named after their source URLs on `ngfts.lge.com` under `biz_code=CMS&func_code=CMS_ASSET`. Startup: `etc/palm/activities/com.webos.service.admanager/` triggers `admanager/start` when `com.webos.service.connectionmanager/getstatus` reports `isInternetConnectionAvailable: true`. `etc/init/admanager.conf` pre-creates the cache directory at boot. ### 2.2 adoverlay `usr-2/sbin/adoverlay-service` (452 176 bytes), service `com.webos.service.adoverlay`, "Ad Overlay Service". It follows the ACR design exactly: a vendor engine directory at `/mnt/lg/cmn_data/adoverlay/lib/`, an Alphonso adapter (`libalphonsoadoverlay.so`), its own `acr.a.2.1.4.db`-equivalent fingerprint DB at `usr-2/share/adoverlay/adoverlay.a.2.1.4.db.zero.mp3`, and a state directory `mnt-6/lg/cmn_data/adoverlay/data/` containing `eula_allowed: allowed`, `sdp_server: Production`, `service_country: NL`. It carries the check `[ACR] ACR Solution is not Alphonso.`, confirming it is content-recognition driven. It also reads `/mnt/lg/cmn_data/shopping/stored_data.json` and requests HTTP headers via `getHttpHeaderForServiceRequest`, i.e. it authenticates to LG's SDP like the other services. Front ends: `com.webos.app.adoverlay`, `com.webos.app.adoverlayex`, and `com.webos.app.adhdmi{1..4}` — the same per-input pattern as the ACR overlays. ### 2.3 videoads `usr-2/palm/applications/com.webos.app.videoads/app.js` is unminified and readable. It is a screensaver-type app (`"defaultWindowType": "screenSaver"`, `"requiredPermissions": ["all"]`) that builds its URL from a regional code: ```js const config = { baseHost: "videoads.lgtvcommon.com", isNextSDP: true, isProduction: true, ricCode: "" }; const buildURL = () => { const env = config.isProduction ? "" : "dev-"; const region = config.ricCode.toLowerCase() || "aic"; return `https://${env}${region}.${config.baseHost}/`; }; ``` The region map sends KOR/AUS/JPN/IND/SGP/NZL/TWN to KIC, the Americas to AIC, and everything else, including NL, to EIC. So this device loads `https://eic.videoads.lgtvcommon.com/`. Production vs. staging is decided by inspecting the `HOST` header returned by `com.webos.service.sdx/getHttpHeaderForServiceRequest`. ### 2.4 Other ad-related paths - `mnt-6/lg/cmn_data/adlogservice/data/` - `usr-2/palm/license/XAD` - `etc/pmlog.d/admanager.conf`, `etc/pmlog.d/adoverlay-service.conf` - `ROOT/opt/webos/tests/admanager/gtest_admanager` --- ## 3. Viewing-history reporting ### 3.1 PersonalDataLogger `usr-2/palm/applications/com.webos.app.inputcommon/qml/Interfaces/ChannelInfoInterfaces/PersonalDataLogger.qml` is plaintext QML and is the most legible telemetry sender in the image. It fires on channel change and POSTs through the SDX transport to the SDP service `ibis_stat_secure` at `sdp/livetvwatch.json`. Tuner body, built verbatim in `sendPersonalLogTv`: ``` chan_name, chan_code, prev_chan_code, device_src_idx, dtv_standard_type, accept_flag, zipcode, timezone, user_id, channel_change ``` Set-top-box body, from `sendPersonalLogStb`, adds `content_id` and `from_where`. The `from_where` values are declared at the top of the file: ```qml readonly property int fromWhereAcr: 10 readonly property int fromWhereScd: 11 readonly property int fromWhereUei: 12 ``` `checkSendLogStb` implements the priority ACR > UEI > SCD. This is the joining point between the two halves of this report: ACR identifies what is playing on a connected set-top box, and the result is reported to LG as a channel-viewing record with the same schema used for the internal tuner. Consent is checked per source before sending — `watchFlag` for RF, `ipWatchFlag` for IP channels, `stbAcrWatchFlag`, `stbScdWatchFlag`, `stbUeiWatchFlag` for the STB paths — and the server can independently switch each off in its reply via `activation_flag`, `ip_activation_flag`, `stb_acr_activation_flag` and so on. Sends are deliberately jittered: ```qml requestTimer.interval = Math.floor((Math.random() * 10000) + 1) + minTime; // minTime = 10000 ``` 10 to 20 seconds after the channel change. A fixed delay would make the report trivially correlatable with the user action; the randomisation removes that. ### 3.2 pbs `usr-2/sbin/pbs` supplies the identity and the consent flags that `PersonalDataLogger` reads via `getAuthenticatedData`. Its `UniqueID` class chains `getMacAddress()`, `encryptMacAddr()` and `convertSHA512()`. The `user_id` in every viewing record is therefore a SHA-512 derived from the TV's MAC address: stable for the life of the device, resettable only in the sense that `UniqueID::reset()` exists. `pbs` also handles `X-Device-Eula` headers, `isZipcodeMandatory()`, and talks to `ibis_secure`. `mnt-6/lg/cmn_data/pbs/pbs_config.json` on this device: ```json { "watchFlag": true, "ipWatchFlag": false, "stbAcrWatchFlag": false, "stbScdWatchFlag": false, "stbUeiWatchFlag": false, "channelplus": true, "generalTerms": true, "pbsAuth": true, "country": "NLD", "channelMapId": "NL-webOS-6.4.0-MR" } ``` Tuner channel-change reporting is on. The STB detection paths, including the ACR one, are off server-side even though the ACR service itself is running and opted in. --- ## 4. The general analytics pipeline Events are emitted as key/value log lines with a `NL_` prefix, collected by `rdxd`, and uploaded by `uploadd` to LG's RDX server. Grepping the binaries, libraries, apps and configs yields **584 distinct `NL_*` event codes**. ### 4.1 Emission Producers call `PmLogCtl logkv`. `etc/palm/service-logger/rules/extInput.py` shows the pattern end to end — it queries `com.webos.service.eim/getAllInputStatus`, walks every connected device, and emits an inventory: ```python logData['dev_list'] = devList log = json.dumps(logData, ensure_ascii = False) cmd = ['PmLogCtl', 'logkv', 'EIM', 'info', 'NL_EIM_INPUTLIST', log] subprocess.run(cmd) ``` Each record carries the device `name`, `type`, `label` (from `labelName`, `serviceName` or `brandName` of the URCU/SIMPLINK data) and `id`. `rule.json` schedules this monthly. Sibling collectors in the same directory: `getGameDeviceInfo.py` (triggered on picture-mode or aspect-ratio change while an HDMI input is active), `getScreenInfo.py`, `getFirstUseTVInfo.py`, `getFirstUseAppInfo.py`, `getBatteryCyInfo.py`. The scheduler is `usr-2/sbin/service-logger`, configured by `etc/palm/service-logger-conf.json` and backed by a MojoDB kind `com.webos.service.logger.activity:1`. ### 4.2 Collection `usr-2/sbin/rdxd`, configured by `etc/rdxd.conf` with `AutoUpload=true`. Report types are registered in `etc/rdxd.d/`: ```json { "type":"analytic", "handlerURL":"com.palm.uploadd/uploadAnalyticsReport" } { "type":"crash", "handlerURL":"com.palm.uploadd/uploadCrashReport" } ``` It respects two gates, both visible as strings in the binary: "EULA has not accepted yet, analytics reports will not be created" and "Devmode is enabled, analytics reports will not be generated". Crash reports are bundled with context produced by `usr-2/share/rdxd/make_sysinfo.sh`, which is a plain shell script. It captures `/proc/cpuinfo`, the eMMC `manfid`/`oemid`/`serial`, `/proc/mounts`, `/etc/version`, then `uptime`, `df -h`, `ps aux`, `date -R`, `ls -l /var/luna/preferences` and `ifconfig`, then queries the bus for attached storage devices, all input statuses, the last ten power on/off events, connection status and the running app list. ### 4.3 Upload `usr-2/sbin/uploadd`, configured by `etc/uploadd.conf`: ```ini [server=rdx] AnalyticsLogURL=log/normal CrashLogURL=log/crash [upload] MaxUploadBytes=2097152 ``` It will not send unless the EULA is accepted, an SDX server is available, and SDP/SDX authentication has succeeded — each is a distinct refusal string in the binary. Failed uploads retry five times with a minimum hour between attempts. Sent files move to `/var/spool/uploadd/uploaded`. First-use marker: `mnt-6/lg/cmn_data/uploadd/initial_date.txt` contains `202004`. The destination is the `rdx_secure` SDP service, `rdx2.lgsdp.com`, with a separate developer-log route to `rdl.lgtvcommon.com`. ### 4.4 Event coverage A sample of the 584 codes, chosen to show breadth rather than to be exhaustive: ``` NL_ACR_OFF_REASON NL_ADOVERLAY_OFF_REASON NL_ADMAN_REQ / _IMP / _CLK NL_ADMAN_IFA_RST NL_AD_CLICK NL_AD_SWITCH_MANUAL NL_CHANNEL_CHANGE NL_CHANNELS_IN NL_BROADCAST_CHANNEL_COUNT NL_BROADCAST_WATCH_CHANNELSOURCE / _RESOLUTION / _SLINGTV NL_APP_LAUNCH NL_APP_INSTALLED NL_APP_REMOVED NL_LAUNCH_APP NL_CLOSE_APP NL_INPUT_APPHISTORY NL_ACTIVATE_VOICE NL_ACTIVATE_SEARCH NL_CHATBOT_VOICE_RESULT NL_FARVOICE_ACTIVATION / _TRIGGER / _ALEXA / _DANGBEI NL_EIM_INPUTLIST NL_EULA_CHANGED NL_ACCESSIBILITY_CHANGED NL_DVR_WATCHNRECORD NL_GAME_OPTIMIZER_CHANGE NL_CAMERA_APP_INFO ``` Some codes double as on-disk cache filenames, e.g. `mnt-6/lg/cmn_data/NL_VIDEO_ALL_DIM_ASPECT_RATIO_HISTORY_CACHE`. --- ## 5. SDX: the shared transport `usr-2/sbin/sdx` (792 484 bytes), service `com.webos.service.sdx`, is the HTTPS client every other component posts through. Callers pass a logical `serviceName` plus a path and method, and `sdx` resolves the hostname, attaches authentication headers and performs the request. Its two public helpers, seen throughout the apps, are `send` and `getHttpHeaderForServiceRequest`. The routing table ships at `usr-2/palm/sdx/server_addr_version.conf` and is updated in place at `mnt-6/lg/cmn_data/sdp/sdx/server_addr_version.conf` (37 entries on this device). In both files the LG-operated hosts appear as the literal `nextlgsdp.com`, prefixed at runtime by region. Entries relevant here: | serviceName | domain | base path | | --- | --- | --- | | `sdp_logging` | `nextlgsdp.com` | `logging/10.0.0/` | | `ibis_secure` | `ibs.nextlgsdp.com` | `ibs/10.1.0/` | | `ibis_stat_secure` | `ibsstat.nextlgsdp.com` | `ibs/10.0.0/` | | `rdx_secure` | `rdx2.nextlgsdp.com` | `rdx/9.0.0/` | | `rdxdev_secure` | `rdl.lgtvcommon.com` | `devlog/nextsdp/v1.0/` | | `cdpbeacon_secure` | `cdpbeacon.lgtvcommon.com` | `api/v2/beacon/` | | `nudge_secure`, `nudge_log_secure` | `nudge.lgtvcommon.com` | `nudge/nextsdp/` | | `recommend_secure`, `tlamp_secure` | `recommend.lgtvcommon.com` | `recommend/…`, `tlamp/…` | | `cpv_secure` | `pnv.lgtvcommon.com` | `rest/csi/` | | `homeprv_secure` | `homeprv.lgtvcommon.com` | `nextsdp/homeprv/` | | `service_setting_secure` | `wiseconfig.lgtvcommon.com` | `nextsdp/smartConfig/` | | `cpauth_secure` | `cpauth.lgtvcommon.com` | `auth/nextsdp/v1.0/` | `sdx` also exposes `getRdxServer` and `getRdxServerUrl` to the bus, which is how `uploadd` discovers where to post. Because everything funnels through one daemon, the per-component hostnames above are not individually resolvable from a network capture without also reading this table. --- ## 6. Behavioural profiling on-device ### 6.1 user-context-manager and user-intent-manager `usr-2/sbin/user-context-manager` and `usr-2/sbin/user-intent-manager` maintain ranked usage profiles in MojoDB. Their `QueryProcessor` builds `RankInfo` lists by genre, keyword, intent type and channel (`makeRankInfoGenreList`, `makeRankInfoKeywordList`, `makeRankInfoIntentTypeList`, `makeRankInfoYChannelList`), and expose `getHistory`, `getRank`, `getSubRank` and `getCategorizedRank` on the bus. The method name worth flagging is `DBHandler::findHistoryInfoForServer` in `user-context-manager` — a distinct query path from `findHistoryInfo`, i.e. history shaped for transmission rather than for local use. There is also a monthly summary feature (`displayMonthlyReport`) whose user-facing strings are in the binary: "Last month, you watched TV for an average of about {hour} hours per day and used {app name} the most." `var-18/preferences/com.webos.service.usercontextmanager` currently holds `1672531310NL250[REDACTED 10 digits]com.webos.app.home1756457284` — a timestamp, the device serial, the most-used app, and a second timestamp. ### 6.2 nudge `usr-2/sbin/nudge` reads those profiles (`GetUCMHistory`, `GetUCMChannelRank`) and keeps its own kind `com.webos.service.nudge.history:1`. Rules are JSON in `mnt-6/lg/cmn_data/nudge/`, one per prompt: `nu_ch_watch_tv_n_times.json`, `nu_AI_channel_zapping.json`, `nu_view_sport.json`, `nu_input_game.json` and about a dozen more. Each carries a `provideCondition` describing the behaviour that triggers it, for example: ```json "queryInfo": { "duration": 14, "comment": "query channel history from before 14 day" }, "zapping": { "zappingHistoryCount": 8, "zappingHistoryRatio": 20, "comment": "trigger it after changing the channel in case watching the channel 8 times and 20% ratio" } ``` and a `policyCondition` with `selectRate`, `closeCount`, `timeoutCount` and `suspendCount` — the prompt suppresses itself if the user keeps dismissing it. One nudge's own text describes the data basis: "You can receive AI generated recommendations of features, apps, and contents based on your TV usage history." The server-side switch `nudge_log_transfer` is `on` (see §7). ### 6.3 Other collectors - `usr-2/sbin/contentminer` (1 018 220 bytes, mode 0700) — `ContentMinerLogger` with mining, parsing and action engines, `OnMiningInfoChanged_Full`, `OnMiningInfoChanged_One`, and `Callback_GetHttpHeaderForSvcReq`, so it is SDP-connected. Preferences directory exists at `var-18/preferences/com.webos.service.contentminer`. - `usr-2/sbin/color-info-miner`, `usr-2/sbin/cbox`, `usr-2/sbin/homelaunchpoints`, `usr-2/sbin/sdp-server-notice`. - `usr-2/sbin/systemprofile` (mode 0700) — local performance profiling into `/var/webos-profile/`; no network paths found. Checked and found not to be telemetry: `tvdataexchanger` (TV-to-TV settings transfer over USB), `remotelogger` and `snaplog` (local log dumps), `pacrunner` (a proxy auto-config runner, matched only on the substring "acr"). --- ## 7. Server-controlled feature switches `mnt-6/lg/cmn_data/sdp/sdx/detailconfig.json` is the cached response from `wiseconfig.lgtvcommon.com` (`service_setting_secure`, path `nextsdp/smartConfig/getConfig`). It holds 19 configuration blocks, including a `services_info.itemList` of on/off switches that the server sets. Relevant entries as currently cached: | Switch | State | | --- | --- | | `nudge_service` | on | | `nudge_server_request` | on | | `nudge_log_transfer` | on | | `search_recommendation` | on | | `voiceid` | on | | `product_register` | on | | `thetake_acr` | off | | `contextual_masa` | off | | `music_discovery` | off | | `lgshop_overlay` | off | `thetake_acr` refers to a second, separate ACR integration (TheTake), disabled for this region. The same file carries two advertising-identifier allowlists — third-party apps permitted to read the device IFA: ``` AdIdWhiteList: amazon NewAdIdWhiteList: amazon, com.disney.disneyplus-prod, com.twin.app.gamingportal, com.wbd.hbomax, youtube.leanback.v4 ``` --- ## 8. Consent state as recorded on the device `mnt-6/lg/cmn_data/sdp/sdx/eula.json`: ```json { "deviceCountryCode": "NL", "deviceDateTime": "1752508517", "eulaChangeReason": "changed", "synchronized": true, "statusList": [ { "managementTypeCode": "S_VNG", "versionId": "20231109136_NL", "status": "A" }, { "managementTypeCode": "S_MKT", "versionId": "20240715263_NL", "status": "A" }, { "managementTypeCode": "S_SVC", "versionId": "20240716023_NL", "status": "A" }, { "managementTypeCode": "S_TAG", "versionId": "20250627461_NL", "status": "A" }, { "managementTypeCode": "S_ADG", "versionId": "20250627462_NL", "status": "A" } ] } ``` All five accepted. `S_SVC` is the service terms and `S_MKT` marketing; `S_ADG` and `S_TAG` are advertising and, by position and by the `acrGdprAllowed`/`acrAdAllowed` setting names, targeted advertising. I did not find a file in the image that defines these three-letter codes, so treat the expansions of `S_TAG` and `S_VNG` as inference. `mnt-6/lg/cmn_data/sdp/sdx/maketingAllowedDate.json` schedules a re-consent toast via ActivityManager for `2027-01-07 13:36:03`, two years after the recorded acceptance date of 2025-04-07, described in the file as "Create toast within 2 years that reminds maketingAllowed" (the misspelling is LG's). Corresponding Luna settings: | Key | File | Value | | --- | --- | --- | | `livePlus` | `var-18/luna/preferences/option` | `on` | | `watchedListCollection` | `var-18/luna/preferences/option` | `on` | | `contentRecommendation` | `var-18/luna/preferences/other` | `on` | | `aiNudge` | `var-18/luna/preferences/general` | `on` | | `adCookie` | `var-18/luna/preferences/general` | `on` | | `screenSaverAd` | `var-18/luna/preferences/general` | `on` | | `customizedAd` | `var-18/luna/preferences/general` | `off` | | `doNotSellMyPersonalInformation` | `var-18/luna/preferences/general` | `off` | | `personalRecommend` | `var-18/luna/preferences/general` | `off`, `changedByUser: false` | `personalRecommend` being off with `changedByUser: false` means that one is a default, not a user choice. --- ## 9. Vendors Searched for the known ACR and audience-measurement SDKs. Results: - **Alphonso** — present and in use. Sole ACR vendor on this device. - **TheTake** — referenced as a switch (`thetake_acr`), off; no binary found. - **Gracenote** — 771 string hits, all in EPG, sports and metadata contexts (`com.webos.app.sportsteamsettings`). Also the origin of the TMS program and station IDs that Alphonso returns on a match. - **Samba TV, Inscape, ACRCloud, Shazam** — no SDK, no endpoints. - **Nielsen, Comscore, Conviva, Kantar** — no SDK. The handful of substring hits are in spelling dictionaries, a Python stdlib file, third-party licence texts and one user-guide HTML page. - **Google Analytics, DoubleClick, Firebase/Crashlytics, Sentry, Branch, Amplitude, Mixpanel, Segment, Moat, IAS, FreeWheel, SpotX, PubMatic, Criteo, Innovid, Adobe** — no hits. All outbound analytics on this device goes to LG-operated infrastructure (`*.lgsdp.com`, `*.lgtvcommon.com`, `*.lgsmartad.com`, `*.lgappstv.com`, `ngfts.lge.com`) or to Alphonso (`*.alphonso.tv`). --- ## 10. Summary of live status on this device Enabled and running: ACR (`livePlus: on`, `optin`, Alphonso engine loaded), ad manager with an active `nl.ad.lgsmartad.com` session, ad overlay service, screensaver video ads, tuner channel-change reporting to `ibsstat`, the `NL_*` analytics pipeline through rdxd/uploadd, nudge with log transfer on, and the usage-ranking services. Enabled but currently unused: the STB detection reporting paths, including the ACR one (`stbAcrWatchFlag: false`) — the ACR engine is running and matching, but its results are not being forwarded as set-top-box viewing records. Disabled: `customizedAd`, `personalRecommend`, `thetake_acr`, and the wider set of `off` switches in `detailconfig.json`. The Wi-Fi neighbour scan in §1.5 is the item with no corresponding user-facing control. It is enabled by a server flag inside the Alphonso SDK, its output sits in the ACR service's state directory, and it is sent to `/user/location` alongside the fingerprint stream. --- ## Appendix: quick file index ``` ACR usr-2/sbin/acr2 usr-2/lib/libalphonsosolution.so.1.0.0 usr-2/lib/libas.so.3.0.93 usr-2/lib/libvtcapture.so.1.0.0 usr-2/share/acr/ usr-2/palm/services/com.webos.service.acr/services.json usr-2/palm/applications/com.webos.app.acr{component,overlay,hdmi1..4}/ etc/palm/activities/com.webos.service.acr/ mnt-6/lg/cmn_data/acr/{data,alphonso,lib}/ Advertising usr-2/sbin/admanager usr-2/sbin/adoverlay-service usr-2/lib/libalphonsoadoverlay.so.1.0.0 usr-2/palm/applications/com.webos.app.{adoverlay,adoverlayex,adhdmi1..4,videoads}/ etc/init/admanager.conf etc/palm/activities/com.webos.service.ad{manager,overlay}/ mnt-6/lg/cmn_data/{admanager,adoverlay,adlogservice}/ Viewing history usr-2/palm/applications/com.webos.app.inputcommon/qml/Interfaces/ChannelInfoInterfaces/PersonalDataLogger.qml usr-2/sbin/pbs mnt-6/lg/cmn_data/pbs/pbs_config.json Analytics pipeline usr-2/sbin/{rdxd,rdx_reporter,uploadd,service-logger} etc/{rdxd.conf,uploadd.conf} etc/rdxd.d/ etc/palm/service-logger/rules/ usr-2/share/rdxd/, usr-2/share/uploadd/ mnt-6/lg/cmn_data/uploadd/ Transport and config usr-2/sbin/sdx usr-2/palm/sdx/server_addr_version.conf mnt-6/lg/cmn_data/sdp/sdx/{server_addr_version.conf,detailconfig.json,eula.json,maketingAllowedDate.json} Profiling usr-2/sbin/{user-context-manager,user-intent-manager,nudge,contentminer} mnt-6/lg/cmn_data/nudge/ var-18/preferences/com.webos.service.{usercontextmanager,userintentmanager,contentminer}/ Settings var-18/luna/preferences/{general,option,other,eula} etc/palm/defaultSettings.json ```