From c347e7a57289fe3fe557cac84d71d4d50ad61180 Mon Sep 17 00:00:00 2001 From: Hanasi Date: Thu, 30 Apr 2026 12:11:21 -0400 Subject: [PATCH 1/4] Add Document for RFC priority --- docs/rfc-parameter-runtime-priority.md | 307 +++++++++++++++++++++++++ 1 file changed, 307 insertions(+) create mode 100644 docs/rfc-parameter-runtime-priority.md diff --git a/docs/rfc-parameter-runtime-priority.md b/docs/rfc-parameter-runtime-priority.md new file mode 100644 index 00000000..df914c43 --- /dev/null +++ b/docs/rfc-parameter-runtime-priority.md @@ -0,0 +1,307 @@ +# RFC Parameter Runtime Priority + +## Overview + +This document explains how RFC-related parameter values are resolved at runtime across the RFC repository and the live `tr69hostif` stack. + +The goal is to make the effective priority clear when the same parameter may exist in multiple runtime or default-value sources, including: + +- live XConf-applied data-model values +- persisted RFC/TR181 override files +- bootstrap persistence +- partner default JSON data, including `default_boot` +- XML default values from the merged TR181 data model +- `/etc/rfcdefaults` fallback files + +The key conclusion is that there is no single universal search order shared by every subsystem. The effective priority depends on which runtime path serves the request. + +--- + +## Why This Exists + +RFC-related values can be observed through more than one layer: + +- `librfcapi` and `libtr181api` +- pre-hostif file-backed fallback logic +- live `tr69hostif` GET handling +- `XBSStore` bootstrap-backed parameter resolution + +Without separating those paths, it is easy to confuse: + +- persisted runtime overrides with firmware defaults +- bootstrap defaults with `/etc/rfcdefaults` +- XML data-model defaults with RFC defaults + +This document treats `/etc/rfcdefaults` as one runtime source among several, not as the sole subject. + +--- + +## Runtime Sources + +The runtime priority discussion in this codebase can involve all of the following sources: + +| Source | Backing store | Typical owner | +|------|---------------|---------------| +| Live XConf-applied data-model value | live `tr69hostif` GET path | `tr69hostif` runtime | +| Persisted TR181 RFC override | `/opt/secure/RFC/tr181store.ini` | RFC/XConf apply path | +| Persisted bootstrap value | `/opt/secure/RFC/bootstrap.ini` | `XBSStore` | +| Device-specific bootstrap overlay | `/etc/partners_defaults_device.json` | `tr69hostif` firmware defaults | +| Partner bootstrap defaults | `/etc/partners_defaults.json` partner section | `tr69hostif` firmware defaults | +| Generic steady-state bootstrap defaults | `/etc/partners_defaults.json` `default` | `tr69hostif` firmware defaults | +| Early-boot bootstrap defaults | `/etc/partners_defaults.json` `default_boot` | `tr69hostif` firmware defaults | +| XML default value | merged TR181 data model | waldb / `tr69hostif` validation path | +| RFC fallback defaults | `/etc/rfcdefaults/*.ini` and `/tmp/rfcdefaults.ini` | RFC repo fallback path | + +--- + +## Path 1: RFC Library Runtime Priority + +This is the path used by: + +- `librfcapi::getRFCParameter()` +- `libtr181api::getParam()` + +For TR181-style RFC keys such as `Device.DeviceInfo.X_RDKCENTRAL-COM_RFC.*`, the effective priority is: + +1. Live data-model value from `tr69hostif`, when `/tmp/.tr69hostif_http_server_ready` exists +2. Persisted XConf-applied value in `/opt/secure/RFC/tr181store.ini` +3. Persisted bootstrap value in `/opt/secure/RFC/bootstrap.ini` +4. RFC fallback default from `/tmp/rfcdefaults.ini`, generated from `/etc/rfcdefaults/*.ini` + +For legacy `RFC_xxxx` keys without a dot, lookup remains file-based and uses `/opt/secure/RFC/rfcVariable.ini` directly. + +### Notes + +- `partner_defaults`, `default`, `default_boot`, and XML default values are not read directly by `rfcapi` +- those layers influence this path only indirectly if they have already been materialized into live `tr69hostif` state or into `bootstrap.ini` + +### Sequence + +```mermaid +sequenceDiagram + participant Caller + participant RFCAPI as getRFCParameter() + participant Ready as /tmp/.tr69hostif_http_server_ready + participant Store as tr181store.ini + participant Bootstrap as bootstrap.ini + participant Defaults as /tmp/rfcdefaults.ini + + Caller->>RFCAPI: getRFCParameter(callerID, key) + RFCAPI->>Ready: check sentinel + alt tr69hostif ready + RFCAPI->>RFCAPI: query live data model over localhost HTTP + else tr69hostif not ready + RFCAPI->>Store: lookup key + alt key missing in store + RFCAPI->>Bootstrap: lookup key + alt key missing in bootstrap + RFCAPI->>Defaults: lookup key + end + end + end +``` + +--- + +## Path 2: Bootstrap Runtime Priority + +This is the path used for bootstrap-backed parameters resolved by `tr69hostif` through `XBSStore`. + +Priority inside the bootstrap subsystem is: + +1. Persisted RFC or WebPA override already stored in `bootstrap.ini` +2. Device-specific overlay from `partners_defaults_device.json`, if present +3. Selected section from `partners_defaults.json` + +Selection inside `partners_defaults.json` is: + +1. matching partner section when PartnerId is known and present +2. `default` when PartnerId is known but no partner section exists +3. `default_boot` when PartnerId is not yet available + +### Important distinction + +`default_boot` is not just another default bucket. It is an early-boot temporary profile used only until PartnerId becomes available. + +`default` is the generic steady-state fallback after PartnerId resolution. + +### Bootstrap workflow + +```mermaid +flowchart TB + START[Bootstrap-backed GET] --> OVR{Persisted override in bootstrap.ini?} + OVR -->|Yes| RET1[Return persisted override] + OVR -->|No| PID{PartnerId available?} + PID -->|No| BOOT[Select default_boot] + PID -->|Yes, match found| PARTNER[Select partner section] + PID -->|Yes, no match| DEF[Select default] + BOOT --> DEVOVR[Apply device-specific overlay] + PARTNER --> DEVOVR + DEF --> DEVOVR + DEVOVR --> RET2[Return effective bootstrap default] +``` + +--- + +## Path 3: Live `tr69hostif` GET Fallback Priority + +For live HTTP, WebPA, or RBUS GET operations handled by `tr69hostif`, another fallback path exists after data-model validation. + +Effective priority is: + +1. Actual value returned by `hostIf_GetMsgHandler()` +2. XML `defaultValue` from the merged TR181 data model, if live GET fails and a default exists + +That XML default comes from the `` element parsed into `dmParam.defaultValue`. + +### Notes + +- XML default value is separate from `/etc/rfcdefaults` +- `/etc/rfcdefaults` is not the fallback used by this live hostif GET path +- this path applies after the parameter is validated against the merged data model + +--- + +## `/etc/rfcdefaults` As A Runtime Source + +`/etc/rfcdefaults` is still important, but it is only one layer in the broader runtime-priority model. + +### What it does + +- `librfcapi` merges all `*.ini` files under `/etc/rfcdefaults/` into `/tmp/rfcdefaults.ini` +- `libtr181api::getDefaultValue()` reads `/etc/rfcdefaults/.ini` directly +- `libtr181api::getLocalParam()` falls back from `tr181localstore.ini` to `/etc/rfcdefaults/.ini` + +### When it wins + +For RFC-library TR181 reads, `/etc/rfcdefaults` wins only after: + +1. no live `tr69hostif` value was available +2. no `tr181store.ini` value was found +3. no `bootstrap.ini` value was found + +So `/etc/rfcdefaults` is a fallback for absence, not a runtime override layer. + +### Merge behavior + +`/tmp/rfcdefaults.ini` is created lazily by `init_rfcdefaults()`. + +Important implementation details: + +- creation happens when fallback reaches `RFCDEFAULTS_FILE` and the merged file is absent +- all `.ini` files are concatenated in directory iteration order +- lookup stops at the first matching `key=value` line in the merged file + +If duplicate keys exist across files, effective precedence is determined by merge order, which is not explicitly sorted by the code. + +--- + +## Combined Conceptual Priority + +If you want one combined conceptual ordering across all runtime sources, the safest summary is: + +1. Live value from the active hostif path, if one is returned +2. Persisted runtime override, if the relevant store already holds one +3. Persisted bootstrap value, for bootstrap-backed keys +4. Effective bootstrap firmware default from `partners_defaults_device.json` overlay plus selected `partners_defaults.json` section (`partner`, `default`, or `default_boot`) +5. XML default value from the merged TR181 data model, only on live hostif fallback paths that use `dmParam.defaultValue` +6. `/etc/rfcdefaults` merged fallback, only on RFC-library fallback paths + +This is a conceptual union of multiple implementations, not a single function's exact search order. + +--- + +## Worked Example + +Consider this parameter: + +```text +Device.Time.NTPServer1 +``` + +Assume the following state: + +- live `tr69hostif` GET can return `ntp.override.example` +- `/opt/secure/RFC/bootstrap.ini` contains `ntp.bootstrap.example` +- `partners_defaults_device.json` contains `ntp.device.example` +- `partners_defaults.json` partner section contains `ntp.partner.example` +- `partners_defaults.json` `default` contains `ntp.default.example` +- `partners_defaults.json` `default_boot` contains `ntp.boot.example` +- merged XML data model has default `ntp.xml.example` +- `/etc/rfcdefaults/timeclient.ini` contains `ntp.rfcdefaults.example` + +### Example A: live hostif GET succeeds + +Returned value: + +```text +ntp.override.example +``` + +Why: + +- the live handler succeeded, so lower fallback layers are not consulted + +### Example B: bootstrap-backed lookup after persisted override was cleared + +Returned value: + +```text +ntp.device.example +``` + +Why: + +- no persisted bootstrap override remains +- bootstrap resolution selects partner or default or default_boot +- device-specific overlay replaces the selected base value + +### Example C: early boot before PartnerId is known + +Returned value: + +```text +ntp.boot.example +``` + +Why: + +- `XBSStore` selects `default_boot` +- that early-boot firmware default becomes the active bootstrap default + +### Example D: RFC-library read before hostif is ready and stores miss + +Returned value: + +```text +ntp.rfcdefaults.example +``` + +Why: + +- hostif is not ready +- key is absent from `tr181store.ini` +- key is absent from `bootstrap.ini` +- final fallback is `/tmp/rfcdefaults.ini` generated from `/etc/rfcdefaults/*.ini` + +### Example E: live hostif GET fails after data-model validation + +Returned value: + +```text +ntp.xml.example +``` + +Why: + +- handler path failed to produce a live value +- `validateAgainstDataModel()` had already captured XML `defaultValue` +- the live request path returns the XML default, not `/etc/rfcdefaults` + +--- + +## See Also + +- [../rfcapi/docs/README.md](../rfcapi/docs/README.md) +- [../tr181api/docs/README.md](../tr181api/docs/README.md) +- https://github.com/rdkcentral/tr69hostif From 54ed4719a96c7735e865a0e16c7750830c8d1e48 Mon Sep 17 00:00:00 2001 From: Hanasi Date: Thu, 30 Apr 2026 15:43:32 -0400 Subject: [PATCH 2/4] Add the doc for RFC default --- docs/l2-test-coverage-analysis.md | 985 ++++++++++++++++++++++++++++++ 1 file changed, 985 insertions(+) create mode 100644 docs/l2-test-coverage-analysis.md diff --git a/docs/l2-test-coverage-analysis.md b/docs/l2-test-coverage-analysis.md new file mode 100644 index 00000000..06372192 --- /dev/null +++ b/docs/l2-test-coverage-analysis.md @@ -0,0 +1,985 @@ +# RFC Module — L2 Functional Test Coverage Analysis + +## Overview + +This document analyzes the L2 (integration/functional) test coverage for the RFC (Remote Feature Control) module. It maps current test scenarios to source code functionality, identifies coverage gaps, and proposes missing test scenarios. + +**Test Framework:** pytest (Python) with Gherkin `.feature` files +**Test Infrastructure:** Mock XConf HTTPS server (`rfcData.js`), mock parodus binary, Docker container +**Execution Scripts:** `run_l2.sh` (main), `run_l2_reboot_trigger.sh` (reboot-specific) + +--- + +## 1. Current L2 Test Coverage + +### 1.1 Test Inventory + +| # | Feature File | Test File | Scenarios | Status in `run_l2.sh` | +|---|---|---|---|---| +| 1 | `rfc_single_instance_run.feature` | `test_rfc_single_instance_run.py` | Lock file prevents second instance | **Active** | +| 2 | `rfc_device_offline_status.feature` | `test_rfc_device_offline_status.py` | DNS file missing → device offline | **Active** | +| 3 | `rfc_initialization_failure.feature` | `test_rfc_initialization_failure.py` | Empty URL; missing properties file | **Active** | +| 4 | `rfc_xconf_communication.feature` | `test_rfc_xconf_communication.py` | Unresolved URL; HTTP 404; HTTP 200 | **Active** | +| 5 | `rfc_setget_param.feature` | `test_rfc_setget_param.py` | RFC param set/get via `tr181` CLI | **Active** | +| 6 | `rfc_tr181_setget_local_param.feature` | `test_rfc_tr181_setget_local_param.py` | Local param set/get via `tr181` CLI | **Active** | +| 7 | `rfc_dynamic_cert_selector.feature` | `test_rfc_dynamic_static_cert_selector.py` | Dynamic P12 cert selection | **Commented out** | +| 8 | `rfc_static_cert_selector.feature` | `test_rfc_static_cert_selector.py` | Static PEM cert fallback | **Commented out** | +| 9 | `rfc_data.feature` | `test_rfc_xconf_rfc_data.py` | RFC data files populated | **Active** | +| 10 | `rfc_xconf_request_params.feature` | `test_rfc_xconf_request_params.py` | XConf query params verification | **Active** | +| 11 | `rfc_valid_accountid.feature` | `test_rfc_valid_accountid.py` | Valid AccountID from XConf | **Active** | +| 12 | `rfc_factory_reset.feature` | `test_rfc_factory_reset.py` | Empty value rejection; PartnerName set | **Active** | +| 13 | `rfc_trigger_reboot.py` | `test_rfc_trigger_reboot.py` | AccountID trigger reboot validation | **Active** | +| 14 | `rfc_feature_enable.feature` | `test_rfc_feature_enable.py` | HTTP 304 handling; feature enable status | **Active** | +| 15 | `rfc_xconf_configsetHash_time.feature` | `test_rfc_xconf_configsethash_time.py` | configSetHash and configSetTime | **Active** | +| 16 | `rfc_reboot_required.feature` | `test_rfc_xconf_reboot.py` | Reboot Required Event to MaintenanceMGR | **Active** | +| 17 | `rfc_override_rfc_prop.feature` | `test_rfc_override_rfc_prop.py` | `/opt/rfc.properties` overrides `/etc/rfc.properties` | **Active** | +| 18 | `rfc_unknown_accountid.feature` | `test_rfc_unknown_accountid.py` | Unknown AccountID → AuthService replacement | **Active** (in `run_l2_reboot_trigger.sh`) | +| 19 | `rfc_webpa.feature` | `test_rfc_webpa.py` | WebPA SET/GET via mock parodus | **Commented out** | + +### 1.2 Coverage by Functional Area + +```mermaid +graph TB + subgraph "COVERED - Active Tests" + A[Single Instance Lock] + B[Device Offline Detection] + C[Init Failure - No URL] + D[Init Failure - No Props File] + E[XConf HTTP 200/304/404] + F[XConf Unresolved URL] + G[RFC Param Set/Get] + H[TR181 Local Param Set/Get] + I[RFC Data Files Population] + J[XConf Request Params] + K[Valid AccountID] + L[Unknown AccountID Replacement] + M[Empty Value Rejection] + N[ConfigSetHash/Time] + O[Reboot Required Event] + P[Properties File Override] + Q[Feature Enable Status] + end + + subgraph "COVERED - Disabled Tests" + R[Dynamic P12 Cert] + S[Static PEM Cert Fallback] + T[WebPA SET/GET] + end + + subgraph "NOT COVERED" + U[CURL Error Codes] + V[mTLS Failures] + W[JSON Parse Errors] + X[Bootstrap URL] + Y[DB Clear/Stash] + Z[Cron Management] + AA[State Machine Transitions] + AB[Retry Logic] + AC[Telemetry Reporting] + AD[PartnerID Validation] + AE[Directory Creation] + AF[Signal Handling] + AG[Firmware Change Detection] + AH[WhoAmI Support] + AI[Debug Services] + end + + style A fill:#4CAF50,color:white + style B fill:#4CAF50,color:white + style C fill:#4CAF50,color:white + style D fill:#4CAF50,color:white + style E fill:#4CAF50,color:white + style F fill:#4CAF50,color:white + style G fill:#4CAF50,color:white + style H fill:#4CAF50,color:white + style I fill:#4CAF50,color:white + style J fill:#4CAF50,color:white + style K fill:#4CAF50,color:white + style L fill:#4CAF50,color:white + style M fill:#4CAF50,color:white + style N fill:#4CAF50,color:white + style O fill:#4CAF50,color:white + style P fill:#4CAF50,color:white + style Q fill:#4CAF50,color:white + style R fill:#FFC107,color:black + style S fill:#FFC107,color:black + style T fill:#FFC107,color:black + style U fill:#F44336,color:white + style V fill:#F44336,color:white + style W fill:#F44336,color:white + style X fill:#F44336,color:white + style Y fill:#F44336,color:white + style Z fill:#F44336,color:white + style AA fill:#F44336,color:white + style AB fill:#F44336,color:white + style AC fill:#F44336,color:white + style AD fill:#F44336,color:white + style AE fill:#F44336,color:white + style AF fill:#F44336,color:white + style AG fill:#F44336,color:white + style AH fill:#F44336,color:white + style AI fill:#F44336,color:white +``` + +### 1.3 Detailed Current Scenario Coverage + +#### A. Startup & Initialization + +| Scenario | Source Function | Test File | Verified Behavior | +|---|---|---|---| +| Single-instance guard via lock file | `main()` → `CurrentRunningInst()` | `test_rfc_single_instance_run.py` | Second instance blocked when lock held | +| Missing properties file | `GetServURL()` | `test_rfc_initialization_failure.py` | Logs "Failed to open file." + "Xconf Initialization Failed" | +| Empty server URL in properties | `GetServURL()` | `test_rfc_initialization_failure.py` | Logs "URL not found in the file." + "Xconf Initialization Failed" | +| Properties file override | `GetServURL()` → persistent file check | `test_rfc_override_rfc_prop.py` | `/opt/rfc.properties` overrides `/etc/rfc.properties` | + +#### B. Device Connectivity + +| Scenario | Source Function | Test File | Verified Behavior | +|---|---|---|---| +| DNS file absent → offline | `isDnsResolve()` | `test_rfc_device_offline_status.py` | Logs "dns resolve file: not present" + "RFC:Device is Offline" | + +#### C. XConf Communication + +| Scenario | Source Function | Test File | Verified Behavior | +|---|---|---|---| +| Unresolvable XConf hostname | `DownloadRuntimeFeatutres()` | `test_rfc_xconf_communication.py` | Logs "Couldn't resolve host name" + curl code 6 | +| HTTP 404 from XConf | `ProcessRuntimeFeatureControlReq()` | `test_rfc_xconf_communication.py` | Logs "cURL Return : 0 HTTP Code : 404" | +| HTTP 200 success | `ProcessRuntimeFeatureControlReq()` | `test_rfc_xconf_communication.py` | Logs "COMPLETED RFC PASS", features enabled, files created | +| HTTP 304 not modified | `ProcessRuntimeFeatureControlReq()` | `test_rfc_feature_enable.py` | Logs 304, features remain active | +| URL percentage encoding | `CreateXconfHTTPUrl()` | `test_rfc_xconf_communication.py` | Encoded URL matches decoded URL | +| XConf request query params | `CreateXconfHTTPUrl()` | `test_rfc_xconf_request_params.py` | All 13 device params present in query | + +#### D. RFC Parameter Management + +| Scenario | Source Function | Test File | Verified Behavior | +|---|---|---|---| +| Set RFC param via CLI | `setRFCParameter()` | `test_rfc_setget_param.py` | "Set operation success" | +| Get RFC param via CLI | `getRFCParameter()` | `test_rfc_setget_param.py` | Correct value returned | +| Set local TR181 param | `setLocalParam()` | `test_rfc_tr181_setget_local_param.py` | "Set Local Param success!" | +| Get local TR181 param | `getLocalParam()` | `test_rfc_tr181_setget_local_param.py` | Correct value returned | +| Reject empty param value | `processXconfResponseConfigDataPart()` | `test_rfc_factory_reset.py` | Logs "EMPTY value...is rejected" | +| Set PartnerName from XConf | `processXconfResponseConfigDataPart()` | `test_rfc_factory_reset.py` | Value readable via `tr181` CLI | + +#### E. AccountID Lifecycle + +| Scenario | Source Function | Test File | Verified Behavior | +|---|---|---|---| +| Valid AccountID from XConf | `GetValidAccountId()` | `test_rfc_valid_accountid.py` | AccountID updated, readable via CLI | +| Unknown AccountID → AuthService replacement | `rfcCheckAccountId()` | `test_rfc_unknown_accountid.py` | Unknown replaced with AuthService value | +| AccountID triggers DB update | `isConfigValueChange()` | `test_rfc_trigger_reboot.py` | "AccountId is Valid, Updating the device Database" | + +#### F. Reboot & Maintenance + +| Scenario | Source Function | Test File | Verified Behavior | +|---|---|---|---| +| Reboot Required Event | `SendEventToMaintenanceManager()` | `test_rfc_xconf_reboot.py` | Logs "RFC: Posting Reboot Required Event to MaintenanceMGR" | + +#### G. Configuration Tracking + +| Scenario | Source Function | Test File | Verified Behavior | +|---|---|---|---| +| configSetHash from XConf header | `updateHashAndTimeInDB()` | `test_rfc_xconf_configsethash_time.py` | Hash and time values logged and stored | + +#### H. Data Persistence + +| Scenario | Source Function | Test File | Verified Behavior | +|---|---|---|---| +| RFC data files created | `processXconfResponseConfigDataPart()` | `test_rfc_xconf_rfc_data.py` | `tr181store.ini`, `tr181localstore.ini`, `tr181.list`, `rfcVariable.ini`, `rfcFeature.list` present | + +#### I. mTLS / Certificate (Disabled) + +| Scenario | Source Function | Test File | Verified Behavior | +|---|---|---|---| +| Dynamic P12 cert selection | `getMtlscert()` + cert selector | `test_rfc_dynamic_static_cert_selector.py` | P12 cert loaded, mTLS enabled | +| Static PEM cert fallback | `getMtlscert()` + cert selector | `test_rfc_static_cert_selector.py` | Fallback to static PEM cert | + +#### J. WebPA (Disabled) + +| Scenario | Source Function | Test File | Verified Behavior | +|---|---|---|---| +| WebPA SET via parodus | IARM event handler | `test_rfc_webpa.py` | Parameter set via WebPA succeeds | +| WebPA GET via parodus | IARM event handler | `test_rfc_webpa.py` | Parameter readable via WebPA | + +--- + +## 2. Coverage Gaps — Missing L2 Test Scenarios + +### 2.1 Coverage Gap Summary + +```mermaid +pie title L2 Test Coverage Distribution + "Covered & Active" : 17 + "Covered but Disabled" : 3 + "Missing - High Priority" : 12 + "Missing - Medium Priority" : 10 + "Missing - Low Priority" : 6 +``` + +### 2.2 HIGH Priority — Missing Tests + +These test gaps cover critical code paths that affect device behavior in production. + +#### GAP-H1: CURL Error Code Handling + +**Source:** `NotifyTelemetry2ErrorCode()` in `rfc_xconf_handler.cpp` +**Untested Error Codes:** 18 (partial transfer), 28 (timeout), 35 (SSL connect error), 51 (SSL peer cert), 53/54/58/59 (SSL cert/key errors), 60 (CA cert), 77 (CA path), 80/82/83/90/91 (SSL-related) +**Risk:** Incorrect error handling could cause silent failures or infinite retries. + +```gherkin +Feature: RFC Manager CURL Error Handling + + Scenario: XConf connection timeout (CURL code 28) + Given the mockxconf server is configured to delay response by 120 seconds + When the RFC manager binary is run + Then an error message "cURL Return : 28 HTTP Code : 0" should be logged + And an error message "Operation timed out" should be logged + And the RFC manager should retry the request + + Scenario: SSL certificate verification failure (CURL code 60) + Given the mockxconf server uses an untrusted certificate + When the RFC manager binary is run + Then an error message "cURL Return : 60 HTTP Code : 0" should be logged + And a telemetry marker "RFC_SSLError" should be reported + + Scenario: SSL connect error (CURL code 35) + Given the mockxconf server rejects SSL handshake + When the RFC manager binary is run + Then an error message "cURL Return : 35 HTTP Code : 0" should be logged +``` + +#### GAP-H2: Retry Logic with Exponential Behavior + +**Source:** `ProcessRuntimeFeatureControlReq()` — 3 retries with sleep(15) +**Untested:** Whether rfcMgr retries on transient failures and eventually succeeds. + +```gherkin +Feature: RFC Manager Retry Logic + + Scenario: XConf server temporarily unavailable then recovers + Given the mockxconf server returns 503 for the first 2 requests + And the mockxconf server returns 200 for subsequent requests + When the RFC manager binary is run + Then the RFC manager should retry the request + And the third attempt should succeed with HTTP 200 + And a message "COMPLETED RFC PASS" should be logged + + Scenario: XConf server fails all retry attempts + Given the mockxconf server is completely unreachable + When the RFC manager binary is run + Then the RFC manager should attempt 3 retries + And a message "Max retry reached" should be logged +``` + +#### GAP-H3: JSON Response Parse Errors + +**Source:** `PreProcessJsonResponse()`, `ProcessJsonResponse()`, `processXconfResponseConfigDataPart()` +**Untested:** Malformed JSON, missing required fields, truncated responses. + +```gherkin +Feature: RFC Manager Malformed XConf Response Handling + + Scenario: XConf returns invalid JSON + Given the mockxconf server returns "NOT_VALID_JSON{{{" as response body + When the RFC manager binary is run + Then an error message indicating JSON parse failure should be logged + And the RFC manager should not crash + + Scenario: XConf returns JSON missing featureControl key + Given the mockxconf server returns '{"invalid": "response"}' as response body + When the RFC manager binary is run + Then an error message "featureControl not found" should be logged + + Scenario: XConf returns empty features array + Given the mockxconf server returns '{"featureControl":{"features":[]}}' as response body + When the RFC manager binary is run + Then a message "[Features Enabled]-[NONE]:" should be logged + + Scenario: XConf returns feature with missing configData + Given the mockxconf server returns a feature without configData field + When the RFC manager binary is run + Then the feature should be skipped without crashing +``` + +#### GAP-H4: mTLS Certificate Fetch Failure + +**Source:** `DownloadRuntimeFeatutres()` handles `MTLS_CERT_FETCH_FAILURE` and `STATE_RED_CERT_FETCH_FAILURE` +**Untested:** Behavior when cert selector cannot retrieve any certificate. + +```gherkin +Feature: RFC Manager mTLS Certificate Failure + + Scenario: Dynamic certificate retrieval fails completely + Given no mTLS certificates are available on the device + And the dynamic certificate source is unavailable + And the static certificate file does not exist + When the RFC manager binary is run + Then an error message "MTLS cert fetch failure" should be logged + And a telemetry marker "MTLS_CERT_FETCH_FAILURE" should be reported + + Scenario: State Red certificate fallback + Given the device is in State Red mode + And the primary mTLS certificates are unavailable + When the RFC manager binary is run + Then the State Red certificate should be used for XConf communication +``` + +#### GAP-H5: Bootstrap XConf URL + +**Source:** `GetBootstrapXconfUrl()` — reads bootstrap URL with retry (10 attempts, 10s each) +**Untested:** Bootstrap URL overriding default URL, and bootstrap URL fetch retry. + +```gherkin +Feature: RFC Manager Bootstrap XConf URL + + Scenario: Bootstrap XConf URL overrides default URL + Given the bootstrap configuration contains a custom XConf URL "https://custom-xconf:50053/featureControl/getSettings" + And the RFC properties file contains the default XConf URL + When the RFC manager binary is run + Then the XConf request should be sent to "https://custom-xconf:50053/featureControl/getSettings" + And a message "Boot strap XConf URL" should be logged + + Scenario: Bootstrap URL retrieval retries on failure + Given the bootstrap configuration is initially unavailable + And the bootstrap becomes available after 3 attempts + When the RFC manager binary is run + Then a message indicating bootstrap retry should be logged + And the bootstrap XConf URL should eventually be used +``` + +#### GAP-H6: PartnerID Validation and Change Detection + +**Source:** `GetValidPartnerId()`, `GetRFCPartnerID()` +**Untested:** PartnerID change from "unknown" to valid (triggers reboot), special character rejection, WhoAmI-aware partner lookup. + +```gherkin +Feature: RFC Manager PartnerID Handling + + Scenario: PartnerID changes from unknown to valid + Given the device PartnerID is currently "unknown" + When the RFC manager receives a valid PartnerID "comcast" from XConf + Then a message "PartnerID Updated" should be logged + And a reboot should be triggered + + Scenario: PartnerID with special characters is rejected + Given the mockxconf server returns PartnerID "partner