diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 000000000..f02927964 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,20 @@ +# clang-tidy config for the OneWifi CI gate. Step 0 = ADVISORY (nothing fatal). +# +# The Checks list disables the existing style and possibly false-positives members of bugprone-*, +# leaving the real-bug checks. +# +# To enable gating: move a proven-clean check into WarningsAsErrors +# (e.g. 'bugprone-not-null-terminated-result') once its sites are fixed. +Checks: > + -*, + bugprone-*, + -bugprone-easily-swappable-parameters, + -bugprone-branch-clone, + -bugprone-narrowing-conversions, + -bugprone-implicit-widening-of-multiplication-result, + -bugprone-assignment-in-if-condition, + -bugprone-reserved-identifier, + -bugprone-multi-level-implicit-pointer-conversion, + -bugprone-too-small-loop-variable, + -bugprone-switch-missing-default-case +WarningsAsErrors: '' diff --git a/.github/actions/pr-context/action.yml b/.github/actions/pr-context/action.yml new file mode 100644 index 000000000..866368d2e --- /dev/null +++ b/.github/actions/pr-context/action.yml @@ -0,0 +1,137 @@ +name: 'Establish trusted PR context' +description: > + Stage-2 helper for the build-check workflow_run (adding comments part). Downloads a + named artifact produced by the triggering stage-1 run, STRICTLY validates its + pr-meta.env, binds the recorded PR number back to the triggering run's head + repo/branch (values a fork cannot forge), and re-checks head-sha freshness. + No check-out or executing PR code. It only reads the artifact as passive + data. Every stage-2 posting job (clang-format / build / clang-tidy) pipes its + trust decision through this one action, so the security-critical logic lives in + one place only. + +inputs: + artifact-name: + description: 'Name of the stage-1 artifact to download (must contain pr-meta.env).' + required: true + run-id: + description: 'github.event.workflow_run.id — the run that produced the artifact.' + required: true + github-token: + description: 'Token with actions:read (download) + pull-requests:read (identity/freshness).' + required: true + repository: + description: 'github.repository — owner/name of the BASE repo the comment posts to.' + required: true + expected-repo: + description: 'github.event.workflow_run.head_repository.full_name (the fork/branch source).' + required: true + expected-branch: + description: 'github.event.workflow_run.head_branch.' + required: true + +outputs: + found: + description: '"true" only if the artifact was present AND fully validated.' + value: ${{ steps.gate.outputs.found }} + fresh: + description: '"true" if the PR head still points at the recorded head_sha.' + value: ${{ steps.gate.outputs.fresh }} + pr_number: + description: 'Validated PR number (bare integer). Empty when found=false.' + value: ${{ steps.gate.outputs.pr_number }} + head_sha: + description: 'Validated PR head sha (40-hex) recorded by stage 1. Empty when found=false.' + value: ${{ steps.gate.outputs.head_sha }} + dir: + description: 'Directory the artifact was extracted into (== artifact-name).' + value: ${{ inputs.artifact-name }} + +runs: + using: composite + steps: + # Each artifact lands in its own directory (path: == name) so multiple + # pr-context calls in one job never clobber each other's files. + - name: Download stage-1 artifact (${{ inputs.artifact-name }}) + id: dl + continue-on-error: true # a missing artifact is normal → found=false + uses: actions/download-artifact@v7 + with: + name: ${{ inputs.artifact-name }} + path: ${{ inputs.artifact-name }} + run-id: ${{ inputs.run-id }} + github-token: ${{ inputs.github-token }} + + - name: Validate metadata, identity, and freshness + id: gate + shell: bash + env: + GH_TOKEN: ${{ inputs.github-token }} + DL_OUTCOME: ${{ steps.dl.outcome }} + ART_DIR: ${{ inputs.artifact-name }} + REPO: ${{ inputs.repository }} + EXPECTED_REPO: ${{ inputs.expected-repo }} + EXPECTED_BRANCH: ${{ inputs.expected-branch }} + run: | + # No `-e`: this script drives its own control flow and fails CLOSED with + # explicit exits. Pipefail so a failed producer in a pipe is not masked. + set -uo pipefail + found=false; fresh=false; pr_number=; head_sha= + + if [ "$DL_OUTCOME" != "success" ]; then + echo "No '$ART_DIR' artifact on the triggering run — nothing to post for this job." + else + META="$ART_DIR/pr-meta.env" + if [ ! -f "$META" ]; then + echo "::error::Artifact '$ART_DIR' present but pr-meta.env is missing — refusing." + exit 1 + fi + + # 1. FORMAT : accept only a bare integer (PR no.) and a sha. The file + # came from a job that may have run fork code, so it is attacker- + # controlled: never `source` it (env injection)! Parse each field instead. + pr_number="$(grep -oP '^pr_number=\K[0-9]+$' "$META" | head -1 || true)" + head_sha="$(grep -oP '^head_sha=\K[0-9a-f]{40}$' "$META" | head -1 || true)" + if [ -z "$pr_number" ] || [ -z "$head_sha" ]; then + echo "::error::Malformed pr-meta.env in '$ART_DIR'; refusing to continue." + exit 1 + fi + + # 2. IDENTITY : a valid-looking number could still be a different PR, + # letting a fork post onto some unrelated PR. Bind pr_number back to the + # head repo+branch GitHub recorded for this workflow_run, as this can't be + # forged. (Whether head_sha is fresh is checked below.) + if ! gh api "/repos/$REPO/pulls/$pr_number" > "$ART_DIR/pr.json"; then + echo "::error::Could not read PR #$pr_number from the API." + exit 1 + fi + pr_repo="$(jq -r '.head.repo.full_name' "$ART_DIR/pr.json")" + pr_branch="$(jq -r '.head.ref' "$ART_DIR/pr.json")" + if [ "$pr_repo" != "$EXPECTED_REPO" ] || [ "$pr_branch" != "$EXPECTED_BRANCH" ]; then + echo "::error::PR #$pr_number ($pr_repo:$pr_branch) does not belong to the triggering run ($EXPECTED_REPO:$EXPECTED_BRANCH); refusing." + exit 1 + fi + found=true + + # 3. FRESHNESS : concurrency only cancels an inflight run. Run that + # already moved past, can still post outdated content. Fail + # on an empty read (which means transient API failure), and mark + # stale only on if different sha is observed. Callers decide + # whether to skip a stale post. + current_sha="$(jq -r '.head.sha' "$ART_DIR/pr.json")" + if [ -z "$current_sha" ] || [ "$current_sha" = "null" ]; then + echo "::error::Could not read current PR head sha." + exit 1 + fi + if [ "$current_sha" = "$head_sha" ]; then + fresh=true + else + echo "PR #$pr_number moved past $head_sha (now $current_sha) — callers should skip stale posts." + fi + fi + + { + echo "found=$found" + echo "fresh=$fresh" + echo "pr_number=$pr_number" + echo "head_sha=$head_sha" + } >> "$GITHUB_OUTPUT" diff --git a/.github/actions/sticky-comment/action.yml b/.github/actions/sticky-comment/action.yml new file mode 100644 index 000000000..8d8c5d745 --- /dev/null +++ b/.github/actions/sticky-comment/action.yml @@ -0,0 +1,98 @@ +name: 'Upsert a sticky PR comment' +description: > + Create-or-update a single PR issue comment identified by a hidden HTML marker, + so re-runs replace the previous body instead of adding new comments. Used for + the gcc build summary and the clang-tidy summary (the two "unify as one sticky + comment" payloads). Trusted stage-2 only: the caller must already have + established a validated pr-number via the pr-context action. + +inputs: + github-token: + description: 'Token with pull-requests:write.' + required: true + repository: + description: 'github.repository — owner/name of the base repo.' + required: true + pr-number: + description: 'Validated PR number (from pr-context).' + required: true + marker: + description: 'Stable id for this comment slot, e.g. "build-summary" or "clang-tidy". One sticky comment per marker.' + required: true + body-file: + description: 'Path to a file holding the Markdown body to post.' + required: true + bot-login: + description: 'Login of the identity that owns our comments (for the ownership match). Change this if you switch to a GitHub App token.' + required: false + default: 'github-actions[bot]' + +runs: + using: composite + steps: + - name: Upsert sticky comment (${{ inputs.marker }}) + shell: bash + env: + GH_TOKEN: ${{ inputs.github-token }} + REPO: ${{ inputs.repository }} + PR: ${{ inputs.pr-number }} + MARKER: ${{ inputs.marker }} + BODY_FILE: ${{ inputs.body-file }} + BOT_LOGIN: ${{ inputs.bot-login }} + run: | + set -uo pipefail + if [ ! -f "$BODY_FILE" ]; then + echo "::error::body-file '$BODY_FILE' not found." + exit 1 + fi + # Hidden marker: lets the next run find our comment for this slot without + # relying on any actor field that crossed the trust boundary. + TAG="" + { printf '%s\n\n' "$TAG"; cat "$BODY_FILE"; } > sticky-body.md + + # Cap the body. GitHub rejects issue comments over ~65536 bytes, and the + # POST/PATCH would fail after everything else succeeded. Truncate with a + # note. if the cut lands inside a ``` code fence, close it first so the + # note isn't swallowed into the block. + MAXB=60000 + if [ "$(wc -c < sticky-body.md)" -gt "$MAXB" ]; then + # iconv -c drops any invalid/incomplete UTF-8 the byte-cut may leave + # (the summaries contain emoji) so the JSON body stays valid. + head -c "$MAXB" sticky-body.md | iconv -f utf-8 -t utf-8 -c > sticky-body.cut + mv sticky-body.cut sticky-body.md + if [ $(( $(grep -c '```' sticky-body.md) % 2 )) -ne 0 ]; then + printf '\n```\n' >> sticky-body.md + fi + printf '\n\n_… truncated at %s bytes; see workflow run summary for full output._\n' "$MAXB" >> sticky-body.md + fi + + # Find an existing comment we own for this marker. "Ours" here means the bot + # identity (login + type == Bot) AND our marker. Login-matched so we + # can't patch some other bot's comment that only quoted our marker. + # $BOT_LOGIN and $TAG are workflow-trusted (not fork data), so shell + # interpolation into the filter is safe. + existing="$(gh api "/repos/$REPO/issues/$PR/comments" --paginate \ + --jq ".[] | select(.user.type == \"Bot\" and .user.login == \"$BOT_LOGIN\") | select(.body | contains(\"$TAG\")) | .id" \ + 2>/dev/null | head -1 || true)" + + # -f (raw-field) sends the body as a plain string — no gh type-inference, + # no dependence on the '@file' loader. Bodies are small (capped upstream), + # so passing via one argv is safe. + body="$(cat sticky-body.md)" + if [ -n "$existing" ]; then + if gh api --method PATCH "/repos/$REPO/issues/comments/$existing" \ + -f body="$body" >/dev/null; then + echo "Updated sticky '$MARKER' comment ($existing) on PR #$PR." + else + echo "::error::Failed to update sticky '$MARKER' comment $existing." + exit 1 + fi + else + if gh api --method POST "/repos/$REPO/issues/$PR/comments" \ + -f body="$body" >/dev/null; then + echo "Created sticky '$MARKER' comment on PR #$PR." + else + echo "::error::Failed to create sticky '$MARKER' comment." + exit 1 + fi + fi diff --git a/.github/scripts/diff_to_suggestions.py b/.github/scripts/diff_to_suggestions.py new file mode 100644 index 000000000..12fd40c09 --- /dev/null +++ b/.github/scripts/diff_to_suggestions.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +# +# If not stated otherwise in this file or this component's LICENSE file the +# following copyright and licenses apply: +# +# Copyright 2026 RDK Management +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +"""Convert a git-clang-format unified diff (read from stdin) into GitHub PR review +suggestions. + +Writes two files consumed by the pr-comments.yml `format` job: + /tmp/comments.json - the (capped) list of review comments + /tmp/review.json - the full review payload for POST .../pulls/{n}/reviews + +Environment: + HEAD_SHA required - commit the review is posted against. + MAX_COMMENTS optional - cap on comments per review (default 25). Large + reviews trigger GitHub rate limiting and fail as a misleading 404 error. + +A "Commit suggestion" button is just a review comment whose body is a +```suggestion block. In the diff the 'old' side (-) is the text currently in the +PR head (what we comment on); the 'new' side (+) is the replacement text. + +This lives in a checked-out file (not a YAML heredoc) so it can be unit-tested +and reviewed. The 'format' job runs it from the trusted base-branch checkout. +""" +import json +import os +import re +import sys + +HUNK = re.compile(r"^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@") + + +def parse(diff_text): + comments, path, old_ln = [], None, 0 + removed, added, start = [], [], None + + def flush(): + nonlocal removed, added, start + if start is None or (not removed and not added): + removed, added, start = [], [], None + return + if not removed: + # Pure-insertion hunk (only + lines): a ```suggestion anchored here + # replaces the anchor line and silently deletes its original content + # on a one-click apply. clang-format's real edits always touch an + # existing line (a line split shows a removed line too), so drop + # these rather than risk corrupting code. + removed, added, start = [], [], None + return + body = "```suggestion\n" + "".join(line + "\n" for line in added) + "```" + c = { + "path": path, + "side": "RIGHT", + "body": body, + "line": start + len(removed) - 1, + } + if len(removed) > 1: + c["start_line"] = start + c["start_side"] = "RIGHT" + comments.append(c) + removed, added, start = [], [], None + + for raw in diff_text.splitlines(): + if raw.startswith("diff --git"): + flush(); path = None; continue + if raw.startswith("--- "): + continue + if raw.startswith("+++ "): + p = raw[4:].strip() + path = p[2:] if p.startswith("b/") else p + continue + m = HUNK.match(raw) + if m: + flush(); old_ln = int(m.group(1)); continue + if path is None: + continue + if raw.startswith("-"): + if start is None: + start = old_ln + removed.append(raw[1:]); old_ln += 1 + elif raw.startswith("+"): + if start is None: + start = old_ln + added.append(raw[1:]) + elif raw.startswith("\\"): + continue + else: + flush(); old_ln += 1 + flush() + return comments + + +def main(): + comments = parse(sys.stdin.read()) + total = len(comments) + cap = int(os.environ.get("MAX_COMMENTS", "25")) + if total > cap: + comments = comments[:cap] + with open("/tmp/comments.json", "w") as fh: + json.dump(comments, fh) + + body = ("`clang-format` suggests the formatting changes below. " + "Use **Commit suggestion** to apply them.") + if total > cap: + body += (f"\n\n> **Note:** showing the first {cap} of {total} suggestions. " + "Apply these and push, and the rest post on the next run — " + "or fix them all at once locally:\n" + "> ```\n" + "> pip install clang-format==18.1.8\n" + "> git-clang-format --style=file --extensions c,h,cpp \n" + "> ```") + review = { + "commit_id": os.environ["HEAD_SHA"], + "event": "COMMENT", + "body": body, + "comments": comments, + } + with open("/tmp/review.json", "w") as fh: + json.dump(review, fh) + print(f"{total} suggestion(s) parsed; prepared {len(comments)} to post") + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml new file mode 100644 index 000000000..e0ebd8954 --- /dev/null +++ b/.github/workflows/clang-format.yml @@ -0,0 +1,109 @@ +name: clang-format + +# Stage 1/2 (stage 2 = clang-format-suggestions.yml). +# Runs the formatter over the PR's changed C lines. It is deliberately +# read-only with no secrets: it may run over untrusted fork code, but it holds +# no write token and can change nothing in the repo. It only records what +# clang-format WOULD change and hands that diff to stage 2 as passive data. +on: + pull_request: + paths: # they have to match --extensions below + - '**/*.c' + - '**/*.cpp' + - '**/*.h' + +permissions: + contents: read + +concurrency: + group: clang-format-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + clang-format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 # need base history to diff changed lines + # For pull_request events actions/checkout defaults to the merge commit + # (refs/pull/N/merge), whose line numbers do not match the PR head. + # Stage 2 posts review comments against head_sha, so the two must agree + # or suggestions land on the wrong lines (or are rejected with a 422). + ref: ${{ github.event.pull_request.head.sha }} + + - name: Install clang-format 18.1.8 + run: | + python3 -m pip install --quiet "clang-format==18.1.8" # bundles clang-format + git-clang-format + clang-format --version + + - name: Reformat only the changed source lines + id: fmt + run: | + # Diff against the MERGE BASE, not the base branch tip. Now that we check + # out the PR head, commits that landed on the base branch after this PR + # forked would otherwise appear as "changed lines", and we would suggest + # reformatting code the PR never touched (which stage 2 cannot post, + # since those lines are not part of the PR's diff). + BASE_SHA="$(git merge-base "${{ github.event.pull_request.base.sha }}" HEAD)" || { + echo "::error::Could not compute merge base — is fetch-depth: 0 set?" + exit 1 + } + echo "Diffing against merge base: $BASE_SHA" + # ================== EXCLUDED PATHS (edit me) ====================== + # Files/dirs here are never reformatted (e.g. vendor ports, imported + # headers/utils). Uses git pathspec syntax: ':!'. + # Uncomment and adapt; List exclusions below instead, one line per exclusion. + # + EXCLUDES=() + # EXCLUDES+=(':!platform/vendor/*') # whole directory (recursive) + # EXCLUDES+=(':!src/example_file.c') # single file + # ================================================================== + # --extensions c,h,cpp is why nothing but C/C++ sources is ever formatted. + # + # git-clang-format exit codes: 0 = nothing to do, 1 = it reformatted + # something (this is NOT an error), 2+ = genuine failure. The default + # shell is `bash -e`, so a bare call would abort the step on the + # perfectly normal exit 1. + set +e + git-clang-format --style=file --extensions c,h,cpp "$BASE_SHA" -- . "${EXCLUDES[@]}" + rc=$? + set -e + if [ "$rc" -ge 2 ]; then + echo "::error::git-clang-format failed (exit $rc) — check .clang-format and the base SHA." + exit 1 + fi + if git diff --quiet; then + echo "changed=false" >> "$GITHUB_OUTPUT" + echo "Changed lines are already clang-format clean." + else + echo "changed=true" >> "$GITHUB_OUTPUT" + git --no-pager diff | tee clang-format.diff + echo "::warning::clang-format has suggestions; they will be posted on this PR." + # ===== TO BLOCK MERGE (make this a required check): uncomment below. + # Suggestions still post either way, because workflow_run fires on failure too. + # exit 1 + fi + + # Record + upload metadata *always* (not only when changed==true). When the + # changed lines become clang-format clean there is no diff, but stage 2 + # still needs the PR identity so it can dismiss any now-obsolete review it + # posted on an earlier push. clang-format.diff is present only when + # changed==true; if-no-files-found: ignore lets the clean case upload just + # pr-meta.env. + - name: Record PR metadata for the suggester + run: | + { + echo "pr_number=${{ github.event.pull_request.number }}" + echo "head_sha=${{ github.event.pull_request.head.sha }}" + } > pr-meta.env + + - name: Upload diff + metadata + uses: actions/upload-artifact@v6 + with: + name: clang-format-suggestions + path: | + clang-format.diff + pr-meta.env + if-no-files-found: ignore + retention-days: 1 diff --git a/.github/workflows/community_label_by_author.yml b/.github/workflows/community_label_by_author.yml index 8c0bce4b4..7f64f9994 100644 --- a/.github/workflows/community_label_by_author.yml +++ b/.github/workflows/community_label_by_author.yml @@ -14,14 +14,14 @@ jobs: steps: - name: Checkout Code # This is mandatory to access the .github/community_authors.json file - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Read Author List and Apply Labels - uses: actions/github-script@v7 + uses: actions/github-script@v8 id: labeler with: # Use the repository's token to run in the context of the repository, not the PR author - token: ${{ secrets.GITHUB_TOKEN }} + github-token: ${{ secrets.GITHUB_TOKEN }} script: | const fs = require('fs'); const path = require('path'); @@ -32,7 +32,7 @@ jobs: const communityUsers = data.community_users; const prAuthor = context.payload.pull_request.user.login; - + // Define the labels to apply const labelsToAdd = ['community contribution']; diff --git a/.github/workflows/makefile.yml b/.github/workflows/makefile.yml index c2b604f89..629c50238 100644 --- a/.github/workflows/makefile.yml +++ b/.github/workflows/makefile.yml @@ -6,28 +6,50 @@ on: pull_request: branches: [ "develop" ] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +# Stage 1 is deliberately read-only: it builds PR-controlled makefiles, so it +# must hold no write scope. The PR-comment posting (writes) happens only in the +# trusted workflow_run stage (pr-comments.yml). +permissions: + contents: read + jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 strategy: matrix: platform: - name: "Raspberry Pi" + id: rpi # stable slug for the ci-summary- artifact name makefile_path: "build/linux/rpi/makefile" + extra_coverage: "0" + extra_cflags: "" - name: "Banana Pi R4 - MLO" + id: bpi makefile_path: "build/linux/bpi/makefile" + extra_coverage: "1" + extra_cflags: "-DCONFIG_GENERIC_MLO" + clang_tidy: true # only this leg builds the compile DB + runs clang-tidy - name: "Platform Mock Unittests" + id: mock makefile_path: "build/linux/mockplatform/makefile" + extra_coverage: "0" + extra_cflags: "" fail-fast: false name: Build for ${{ matrix.platform.name }} steps: - name: Checkout current repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: path: 'OneWifi' + fetch-depth: 0 # clang-tidy diffs changed source against the PR base + persist-credentials: false # nothing here pushes; don't leave the token in .git/config - name: Clone unified-wifi-mesh repository run: | @@ -36,7 +58,7 @@ jobs: mv OneWifi easymesh_project/OneWifi - name: Cache dependencies - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: /var/cache/apt key: ${{ runner.os }}-apt-${{ hashFiles('**/apt-packages') }} @@ -60,6 +82,7 @@ jobs: libnl-route-3-dev \ libavro-dev \ libcjson1 libcjson-dev \ + libprotobuf-c-dev \ libssl-dev \ uuid-dev \ libmysqlcppconn-dev \ @@ -73,27 +96,160 @@ jobs: libperl-dev \ libjson-c-dev \ libgtest-dev \ - libgmock-dev + libgmock-dev \ + bear \ + clang-tidy-18 # Steps for ucode installation required for hostapd compilation git clone https://github.com/jow-/ucode.git ucode cd ucode mkdir build && cd build cmake -DUBUS_SUPPORT=OFF -DUCI_SUPPORT=OFF -DULOOP_SUPPORT=OFF .. - make -j$(nproc) + make -j"$(nproc)" sudo make install sudo ldconfig - name: Setup OneWiFi for ${{ matrix.platform.name }} working-directory: easymesh_project/OneWifi run: | - git config --global user.email "${{ github.actor }}@users.noreply.github.com" - git config --global user.name "${{ github.actor }}" + # $GITHUB_ACTOR is a built-in runner env var; use it rather than + # interpolating ${{ github.actor }} straight into the script. + git config --global user.email "${GITHUB_ACTOR}@users.noreply.github.com" + git config --global user.name "${GITHUB_ACTOR}" make -f ${{ matrix.platform.makefile_path }} setup - env: - GITHUB_ACTOR: ${{ github.actor }} - name: Build OneWiFi for ${{ matrix.platform.name }} + id: build + working-directory: easymesh_project/OneWifi + run: | + # pipefail so make's exit status (not tee's) decides the step result. + set -o pipefail + # clang-tidy leg only: wrap make in `bear` so ONE build yields the objects + # AND compile_commands.json (no second build). Other legs run make plain. + ${{ matrix.platform.clang_tidy && 'bear --output compile_commands.json --' || '' }} make -f ${{ matrix.platform.makefile_path }} -j"$(nproc)" all 2>&1 | tee build.log + env: + # Coverage tier for the bpi target: "1" enables EXTRA_COV_FLAGS + # (ONEWIFI_DB_SUPPORT + memwraptool). Ignored by rpi/mockplatform. + EXTRA_COVERAGE: ${{ matrix.platform.extra_coverage }} + # Extra defines appended to CFLAGS. bpi appends via 'CFLAGS +=' (so the + # MLO build picks up -DCONFIG_GENERIC_MLO); rpi/mockplatform use + # 'CFLAGS =' and ignore it. -j is safe: PROGRAM depends on libwifihal.a. + CFLAGS: ${{ matrix.platform.extra_cflags }} + + - name: Build summary for ${{ matrix.platform.name }} + if: always() # run on failure too, so the red-build reason is surfaced working-directory: easymesh_project/OneWifi run: | - make -f ${{ matrix.platform.makefile_path }} all + # ci-out/ collects the stage-1 artifacts the PR-comment stage posts. + mkdir -p ci-out + LOG=build.log + if [ ! -f "$LOG" ]; then + echo "## ${{ matrix.platform.name }} — no build log captured" \ + | tee ci-out/build-summary.md >> "$GITHUB_STEP_SUMMARY" + exit 0 + fi + # Compiler + linker + make errors: what usually explains a red build, + errs=$(grep -hE ': (error|fatal error):|undefined reference to|Error [0-9]+' "$LOG" \ + | sed -E 's#[^ ]*/(OneWifi|rdk-wifi-hal|rdk-wifi-libhostap)/#\1/#; s#//#/#g' \ + | sort -u || true) + # Surface the top errors as ANNOTATIONS (the box at the top of the failed + # check, which people actually check) — not only the run-summary page. + if [ -n "$errs" ]; then + printf '%s\n' "$errs" | head -10 | sed 's/%/%25/g; s/\r/%0D/g' \ + | while IFS= read -r l; do echo "::error::$l"; done + fi + { + echo "## ${{ matrix.platform.name }} — build ${{ steps.build.outcome }}" + if [ -n "$errs" ]; then + echo "### ❌ Errors ($(printf '%s\n' "$errs" | wc -l) unique)" + echo '```' + printf '%s\n' "$errs" | head -100 + echo '```' + fi + # Warnings from OneWifi's own sources ONLY, with location. + # HAL/hostapd/mesh errors still break the build, so will be shown too. + # OneWifi runs a Yocto-style clean baseline, so anything here + # is a newly-surfaced bug to triage. + warns=$(grep -hE ': warning:' "$LOG" \ + | grep -vE 'rdk-wifi-hal|rdk-wifi-libhostap|unified-wifi-mesh' \ + | sed -E 's#[^ ]*/OneWifi/+##; s#//#/#g' \ + | sort -u || true) + if [ -n "$warns" ]; then + echo "### ⚠️ OneWifi warnings ($(printf '%s\n' "$warns" | wc -l))" + echo '```' + printf '%s\n' "$warns" | head -100 + echo '```' + else + echo "### ⚠️ OneWifi warnings: 0" + fi + } | tee ci-out/build-summary.md >> "$GITHUB_STEP_SUMMARY" + + - name: clang-tidy (advisory) for ${{ matrix.platform.name }} + if: always() && matrix.platform.clang_tidy + working-directory: easymesh_project/OneWifi + run: | + # Advisory Step 0: NEVER fails the build. List of Checks is in the .clang-tidy. + # clang-diagnostic-* is filtered-out. Emits separate (from GCC) summary section + # + ::warning:: annotations, same visibility as the build errors. + mkdir -p ci-out + [ -f compile_commands.json ] || { echo "### 🔎 clang-tidy: no compile DB" >> "$GITHUB_STEP_SUMMARY"; exit 0; } + BASE='${{ github.event.pull_request.base.sha }}' + CHANGED='' + if [ -n "$BASE" ]; then + git fetch --no-tags --depth=1 origin "$BASE" >/dev/null 2>&1 || true + CHANGED=$(git diff --name-only --diff-filter=ACM "$BASE" HEAD -- '*.c' 2>/dev/null \ + | grep -vE '^build/|hostap' || true) + fi + : > tidy.log + for f in $CHANGED; do + [ -f "$f" ] || continue + # || true: a no-match grep (exit 1) or a tidy hiccup must not abort the loop. + clang-tidy-18 -p . --quiet "$f" 2>/dev/null \ + | grep -E ': (warning|error):' | grep -v clang-diagnostic >> tidy.log || true + done + found=$(sed -E 's#[^ ]*/OneWifi/+##; s#//#/#g' tidy.log | sort -u || true) + if [ -n "$found" ]; then + printf '%s\n' "$found" | head -10 | sed 's/%/%25/g; s/\r/%0D/g' \ + | while IFS= read -r l; do echo "::warning::$l"; done + fi + { + if [ -z "$CHANGED" ]; then echo "### 🔎 clang-tidy: no changed .c to scan" + elif [ -z "$found" ]; then echo "### 🔎 clang-tidy: clean on changed files" + else echo "### 🔎 clang-tidy ($(printf '%s\n' "$found" | wc -l) findings)"; echo '```'; printf '%s\n' "$found" | head -100; echo '```' + fi + } | tee ci-out/tidy-summary.md >> "$GITHUB_STEP_SUMMARY" + + # ---- Hand-off to the trusted PR-comment stage (pr-comments.yml) ---------- + # Record the PR identity as passive data for the workflow_run poster to + # validate. Only meaningful on PRs; push builds skip it. + - name: Record PR metadata for the comment stage + if: always() && github.event_name == 'pull_request' + working-directory: easymesh_project/OneWifi + run: | + mkdir -p ci-out + { + echo "pr_number=${{ github.event.pull_request.number }}" + echo "head_sha=${{ github.event.pull_request.head.sha }}" + } > ci-out/pr-meta.env + + # Publish the summary artifact the PR-comment stage posts. Uploaded even on + # build failure (if: always()) so a red build still gets its summary posted. + # tidy-summary.md exists on the bpi leg only. + # + # scoped to bpi-only FOR NOW: only the Banana Pi leg (coverage + MLO, and the only + # leg running clang-tidy) is posted as a PR comment. rpi/mock still build and + # still gate the check. Their respective summaries stay on the action-run 'summary' page + # ($GITHUB_STEP_SUMMARY), we just don't paste is as comment; There's no infra yet to + # scope their findings down to the PR's changes, so a comment would be + # noise nobody acts on. The pr-comments.yml 'build' job already aggregates + # however many ci-summary-* artifacts exist, so: + # TO POST ALL LEGS: delete the `&& matrix.platform.id == 'bpi'` clause below + # (nothing to change in pr-comments.yml). + - name: Upload CI summary artifact + if: always() && github.event_name == 'pull_request' && matrix.platform.id == 'bpi' + uses: actions/upload-artifact@v6 + with: + name: ci-summary-${{ matrix.platform.id }} + path: easymesh_project/OneWifi/ci-out + if-no-files-found: warn + retention-days: 1 diff --git a/.github/workflows/pr-comments.yml b/.github/workflows/pr-comments.yml new file mode 100644 index 000000000..d241c6849 --- /dev/null +++ b/.github/workflows/pr-comments.yml @@ -0,0 +1,271 @@ +name: PR comments + +# unified stage 2/2 for every PR-comment payload. +# +# One trusted workflow_run flow that posts all three CI artifacts back to the PR: +# - clang-format -> inline review suggestions (job: format, bespoke) +# - gcc build -> one sticky summary comment (job: build) +# - clang-tidy -> one sticky summary comment (job: tidy) +# +# Why workflow_run (and why two stages at all): posting to a PR needs +# `pull-requests: write`. Doing that in a job that has checked out and built +# untrusted fork PR code is security issue ('pwn-request'). So stage 1 (the +# Build Check / clang-format workflows) runs over PR code with no token and only +# creates artifacts. This stage runs in the trusted base-repo context, *never* +# checks out PR code, and only reads those artifacts as passive data. +# +# All three jobs pipe their trust decision through one action (.github/actions/pr-context): +# 1. validate pr-meta.env, 2. bind the PR number to the triggering run, and +# 3. check head-sha freshness. The two sticky comment-summaries share a second action, +# namely '.github/actions/sticky-comment'. +# +# NOTE: workflow_run *always* uses the copy of this file (and the referenced local +# actions) from the repository default branch — so nothing here takes effect +# until it has merged to 'develop'. +# NOTE: workflow_run applies only to same repo. + +on: + workflow_run: + workflows: ["Build Check", "clang-format"] + types: [completed] + +permissions: + contents: read + pull-requests: write # the only write scope required (posting) + actions: read # to download stage-1 artifacts from the triggering run + +# Separate group per triggering workflow + head, so a Build-Check-triggered run +# and a clang-format-triggered run for the same branch never cancel each other. +# workflow_run.pull_requests is empty for fork PRs (actions/runner#3444), so key +# on head_repository.full_name + head_branch, which always exists. +concurrency: + group: >- + pr-comments-${{ github.event.workflow_run.name }}-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }} + cancel-in-progress: true + +env: + # Cap on inline review comments per run. Large reviews can trip GitHub rate + # limiting and fail. Bump here if needed. + MAX_COMMENTS: "25" + # The identity that owns our comments/reviews (for the ownership match). Change + # ONLY this one line if there is a move from the default GITHUB_TOKEN to a GitHub App + # token — the bot login becomes "[bot]". + BOT_LOGIN: "github-actions[bot]" + +jobs: + # --------------------------------------------------------------------------- + # clang-format -> inline review suggestions (kept bespoke: only this renders as + # ```suggestion blocks with a "Commit suggestion" button). + # --------------------------------------------------------------------------- + format: + name: clang-format suggestions + runs-on: ubuntu-latest + # Only the clang-format trigger, only PRs, skip cancelled runs (they can leave + # a stale artifact yet fire 'completed'). Allow failure: if stage 1 failed, + # suggestions are still useful. Name-gating means the build/tidy jobs don't + # spin up a no-op run here (and vice-versa), keeping the run list readable. + if: >- + github.event.workflow_run.name == 'clang-format' + && github.event.workflow_run.event == 'pull_request' + && github.event.workflow_run.conclusion != 'cancelled' + steps: + # Trusted base-repo checkout - for the local composite actions only. No ref + # is passed, so this is the base default branch (trusted), never PR code. + - name: Check out base-repo actions (no PR code) + uses: actions/checkout@v6 + + - name: Establish trusted PR context + id: ctx + uses: ./.github/actions/pr-context + with: + artifact-name: clang-format-suggestions + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + repository: ${{ github.repository }} + expected-repo: ${{ github.event.workflow_run.head_repository.full_name }} + expected-branch: ${{ github.event.workflow_run.head_branch }} + + - name: Convert diff into review suggestions + if: steps.ctx.outputs.found == 'true' + env: + HEAD_SHA: ${{ steps.ctx.outputs.head_sha }} + # MAX_COMMENTS comes from the workflow-level env. + run: | + # The converter lives in a checked-out script (trusted base branch). When the + # changed lines are already clang-format clean there is no diff file - feed /dev/null + # so it creates 0 comments. The post step then just dismisses any now-stale review. + diff=clang-format-suggestions/clang-format.diff + [ -f "$diff" ] || diff=/dev/null + python3 .github/scripts/diff_to_suggestions.py < "$diff" + + - name: Dismiss stale reviews, then post fresh suggestions + # Runs whenever the PR head still matches what stage 1 measured - including the clean + # case (0 comments). Dismissal comes first so an earlier push's (now-obsolete) + # suggestions are cleared even when there is nothing new to post. Stage 1 always uploads + # pr-meta.env, so this job still runs when formatting became clean. + if: steps.ctx.outputs.found == 'true' && steps.ctx.outputs.fresh == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + PR: ${{ steps.ctx.outputs.pr_number }} + # BOT_LOGIN comes from the workflow-level env. + run: | + # Stale-review cleanup first (before the empty-check). Identity = our bot + # login+type + our body marker. $BOT_LOGIN is workflow-trusted, so the + # shell interpolation into the filter is safe. Non-fatal. + # shellcheck disable=SC2016 # the backticks in the jq are a literal body marker, not a shell expansion + gh api "/repos/$REPO/pulls/$PR/reviews" --paginate \ + --jq '.[] | select(.user.login == "'"$BOT_LOGIN"'" and (.user.type == "Bot")) | select(.body | startswith("`clang-format` suggests")) | select(.state != "DISMISSED") | .id' \ + > /tmp/stale_ids.txt \ + || { echo "::warning::Could not list existing reviews for cleanup (continuing)."; : > /tmp/stale_ids.txt; } + while read -r rid; do + [ -n "$rid" ] || continue + echo "Dismissing prior clang-format review $rid" + gh api --method PUT "/repos/$REPO/pulls/$PR/reviews/$rid/dismissals" \ + -f message="Superseded by a newer clang-format suggestion run." \ + -f event="DISMISS" >/dev/null \ + || echo "::warning::Failed to dismiss review $rid (continuing)." + done < /tmp/stale_ids.txt + + # Nothing new to post (clean, or all suggestions were pure-additions the + # converter dropped). The dismissal above already cleaned up. Done. + if [ "$(python3 -c 'import json;print(len(json.load(open("/tmp/comments.json"))))')" = "0" ]; then + echo "clang-format clean on the changed lines — dismissed stale reviews, nothing to post." + exit 0 + fi + + if ! gh api --method POST "/repos/$REPO/pulls/$PR/reviews" \ + --input /tmp/review.json > /tmp/resp.json; then + echo "::error::Failed to post review (see log above)." + # 422 = a suggestion targeted a line outside the PR diff. + # 404 = usually rate limit from too many comments (lower MAX_COMMENTS). + exit 1 + fi + echo "Review posted: $(python3 -c 'import json;print(json.load(open("/tmp/resp.json")).get("html_url",""))')" + + # --------------------------------------------------------------------------- + # gcc build -> one sticky summary comment, aggregating every matrix leg. + # --------------------------------------------------------------------------- + build: + name: build summary comment + runs-on: ubuntu-latest + if: >- + github.event.workflow_run.name == 'Build Check' + && github.event.workflow_run.event == 'pull_request' + && github.event.workflow_run.conclusion != 'cancelled' + steps: + - name: Check out base-repo actions (no PR code) + uses: actions/checkout@v6 + + # Anchor trust on the bpi build artifact (bpi always runs and uploads on + # if: always()). Its pr-meta.env is identical across legs. + - name: Establish trusted PR context + id: ctx + uses: ./.github/actions/pr-context + with: + artifact-name: ci-summary-bpi + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + repository: ${{ github.repository }} + expected-repo: ${{ github.event.workflow_run.head_repository.full_name }} + expected-branch: ${{ github.event.workflow_run.head_branch }} + + # Pull every platform's summary for one combined comment. These files are + # passive display text; the trust decision was already made above. + # NOTE: makefile.yml currently uploads only the bpi leg's summary, so today + # this resolves to a single artifact. It stays a wildcard on purpose — once + # rpi/mock are worth posting, enabling them there needs no change here. + - name: Download all platform summaries + if: steps.ctx.outputs.found == 'true' + uses: actions/download-artifact@v7 + with: + pattern: ci-summary-* + path: summaries + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Compose build summary comment + if: steps.ctx.outputs.found == 'true' + env: + HEAD_SHA: ${{ steps.ctx.outputs.head_sha }} + RUN_NAME: ${{ github.event.workflow_run.name }} + RUN_NUMBER: ${{ github.event.workflow_run.run_number }} + RUN_URL: ${{ github.event.workflow_run.html_url }} + run: | + set -uo pipefail + { + echo "## 🔨 Build summary" + echo + echo "_Commit \`${HEAD_SHA:0:7}\` · [\`${RUN_NAME}\` #${RUN_NUMBER}](${RUN_URL})_" + echo + # Deterministic platform order; each per-leg file already carries its + # own "## — build " heading + error/warning blocks. + for d in summaries/ci-summary-*/; do + f="${d}build-summary.md" + [ -f "$f" ] || continue + cat "$f" + echo + done + } > build-comment.md + echo "wrote build-comment.md ($(wc -l < build-comment.md) lines)" + + - name: Post sticky build summary + if: steps.ctx.outputs.found == 'true' && steps.ctx.outputs.fresh == 'true' + uses: ./.github/actions/sticky-comment + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + repository: ${{ github.repository }} + pr-number: ${{ steps.ctx.outputs.pr_number }} + marker: build-summary + body-file: build-comment.md + bot-login: ${{ env.BOT_LOGIN }} + + # --------------------------------------------------------------------------- + # clang-tidy -> one sticky summary comment (for now only bpi leg produces it). + # --------------------------------------------------------------------------- + tidy: + name: clang-tidy summary comment + runs-on: ubuntu-latest + if: >- + github.event.workflow_run.name == 'Build Check' + && github.event.workflow_run.event == 'pull_request' + && github.event.workflow_run.conclusion != 'cancelled' + steps: + - name: Check out base-repo actions (no PR code) + uses: actions/checkout@v6 + + - name: Establish trusted PR context + id: ctx + uses: ./.github/actions/pr-context + with: + artifact-name: ci-summary-bpi + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + repository: ${{ github.repository }} + expected-repo: ${{ github.event.workflow_run.head_repository.full_name }} + expected-branch: ${{ github.event.workflow_run.head_branch }} + + - name: Prepare clang-tidy comment + id: prep + if: steps.ctx.outputs.found == 'true' + run: | + set -uo pipefail + f="ci-summary-bpi/tidy-summary.md" + if [ -f "$f" ] && [ -s "$f" ]; then + { echo "## 🔎 clang-tidy (advisory)"; echo; cat "$f"; } > tidy-comment.md + echo "post=true" >> "$GITHUB_OUTPUT" + else + echo "No clang-tidy summary in the artifact, skipping." + echo "post=false" >> "$GITHUB_OUTPUT" + fi + + - name: Post sticky clang-tidy summary + if: steps.ctx.outputs.found == 'true' && steps.ctx.outputs.fresh == 'true' && steps.prep.outputs.post == 'true' + uses: ./.github/actions/sticky-comment + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + repository: ${{ github.repository }} + pr-number: ${{ steps.ctx.outputs.pr_number }} + marker: clang-tidy + body-file: tidy-comment.md + bot-login: ${{ env.BOT_LOGIN }} diff --git a/build/linux/bpi/makefile b/build/linux/bpi/makefile index fbbe08bb8..57de93e3c 100644 --- a/build/linux/bpi/makefile +++ b/build/linux/bpi/makefile @@ -38,6 +38,35 @@ EM_APP = 1 ONEWIFI_STA_MGR_APP_SUPPORT = 1 ONEWIFI_EASYCONNECT_APP_SUPPORT = 1 +# ============================================================================ +# CI coverage tiers +# baseline : BananaPi app-coverage parity, always compiled +# EXTRA_COVERAGE=1 : Add light OneWifi feature/app flags (no heavy RDK deps) +# EXTRA_COVERAGE is supplied by the workflow env. +# Mock fake headers for the extra apps live in build/linux/compat. +# ============================================================================ + +# Missing apps for parity with Yocto builds +ONEWIFI_ANALYTICS_APP_SUPPORT = 1 +ONEWIFI_BLASTER_APP_SUPPORT = 1 +ONEWIFI_CAC_APP_SUPPORT = 1 +ONEWIFI_CSI_APP_SUPPORT = 1 +ONEWIFI_HARVESTER_APP_SUPPORT = 1 +ONEWIFI_LEVL_APP_SUPPORT = 1 +ONEWIFI_MOTION_APP_SUPPORT = 1 +ONEWIFI_WHIX_APP_SUPPORT = 1 +FEATURE_OFF_CHANNEL_SCAN_5G = 1 +# NOTE: the SM (stats-manager) app is NOT enabled on this target. SM and EM define +# the same global helpers and as such cannot be linked into a single binary. The +# BananaPi *extender* build ships SM; this CI target - EM, so EM wins. +# SM enablement requires different build variant. + + +# EXTRA_COVERAGE source toggles +ifeq ($(EXTRA_COVERAGE),1) +ONEWIFI_MEMWRAPTOOL_APP_SUPPORT = 1 +endif + # wifi hal rules HAL_LIBRARY = $(INSTALLDIR)/lib/libwifihal.a WEBCONFIG_LIBRARY = $(INSTALLDIR)/lib/libwebconfig.a @@ -338,6 +367,7 @@ INCLUDEDIRS = \ -I$(ONE_WIFI_HOME)/lib/const \ -I$(ONE_WIFI_HOME)/lib/schema \ -I$(ONE_WIFI_HOME)/lib/datapipeline \ + -I$(ONE_WIFI_HOME)/lib/qm \ -I$(WIFI_HAL_INTERFACE) \ -I/usr/local/ssl/include/ \ -I/usr/include/libnl3 \ @@ -347,6 +377,7 @@ INCLUDEDIRS = \ -I$(WIFI_HOSTAP_SRC)/common \ -I$(WIFI_NETLINK)/include \ -I$(WIFI_TROWER_BASE) \ + -I$(BASE_DIR)/build/linux/compat \ LIBDIRS = \ -L$(INSTALLDIR)/lib \ @@ -359,7 +390,7 @@ else LIBDIRS += -L$(INSTALLDIR)/lib/platform/darwin endif -LIBS = -lm -luuid -lwifihal -lpthread -ldl -ljansson -lev -lssl -lcrypto -lnl-3 -lnl-genl-3 -lnl-route-3 -lavro -lcjson +LIBS = -lm -luuid -lwifihal -lpthread -ldl -ljansson -lev -lssl -lcrypto -lnl-3 -lnl-genl-3 -lnl-route-3 -lavro -lcjson -lprotobuf-c # # The CXXSOURCES macro contains a list of source files. @@ -400,6 +431,51 @@ CSOURCES = $(wildcard $(ONE_WIFI_HOME)/source/db/wifi_db.c) \ $(ONE_WIFI_HOME)/source/platform/linux/misc.c \ $(ONE_WIFI_HOME)/source/platform/linux/bus.c \ $(ONE_WIFI_HOME)/source/platform/common/common.c \ + $(ONE_WIFI_HOME)/source/db/wifi_db_apis.c \ + $(ONE_WIFI_HOME)/lib/qm/qm_conn.c \ + $(ONE_WIFI_HOME)/lib/common/os_time.c \ + $(ONE_WIFI_HOME)/lib/common/monitor.c \ + $(ONE_WIFI_HOME)/lib/common/os.c \ + $(ONE_WIFI_HOME)/lib/common/os_util.c \ + $(ONE_WIFI_HOME)/lib/common/os_exec.c \ + $(ONE_WIFI_HOME)/lib/const/const.c \ + $(ONE_WIFI_HOME)/lib/ds/ds_tree.c \ + $(ONE_WIFI_HOME)/lib/json_util/string.c \ + $(ONE_WIFI_HOME)/lib/json_util/memdbg.c \ + $(ONE_WIFI_HOME)/lib/json_util/future.c \ + $(ONE_WIFI_HOME)/lib/log/log.c \ + $(ONE_WIFI_HOME)/lib/log/log_syslog.c \ + $(ONE_WIFI_HOME)/lib/log/log_stdout.c \ + $(ONE_WIFI_HOME)/lib/log/log_traceback.c \ + $(ONE_WIFI_HOME)/lib/schema/schema.c \ + $(ONE_WIFI_HOME)/lib/pktgen/pktgen.c \ + $(ONE_WIFI_HOME)/lib/datapipeline/dppline.c \ + $(ONE_WIFI_HOME)/lib/datapipeline/opensync_stats.pb-c.c \ + $(ONE_WIFI_HOME)/lib/datapipeline/osp_unit_rdk.c \ + $(ONE_WIFI_HOME)/lib/datapipeline/devinfo.c \ + $(ONE_WIFI_HOME)/lib/osa/os_backtrace.c \ + $(ONE_WIFI_HOME)/lib/osa/os_socket.c \ + $(ONE_WIFI_HOME)/lib/osa/os_nif_linux.c \ + $(ONE_WIFI_HOME)/lib/osa/os_regex.c \ + $(ONE_WIFI_HOME)/lib/osa/os_proc.c \ + $(ONE_WIFI_HOME)/lib/osa/os_file_ops.c \ + $(ONE_WIFI_HOME)/lib/osa/os_file.c \ + $(ONE_WIFI_HOME)/lib/osa/os_random.c \ + $(ONE_WIFI_HOME)/lib/pjs/pjs_array.c \ + $(ONE_WIFI_HOME)/lib/pjs/pjs_ovs_basic.c \ + $(ONE_WIFI_HOME)/lib/pjs/pjs_basic.c \ + $(ONE_WIFI_HOME)/lib/pjs/pjs_types.c \ + $(ONE_WIFI_HOME)/lib/pjs/pjs_ovs_set.c \ + $(ONE_WIFI_HOME)/lib/pjs/pjs_ovs_map.c \ + $(ONE_WIFI_HOME)/lib/ovsdb/ovsdb.c \ + $(ONE_WIFI_HOME)/lib/ovsdb/ovsdb_method.c \ + $(ONE_WIFI_HOME)/lib/ovsdb/ovsdb_update.c \ + $(ONE_WIFI_HOME)/lib/ovsdb/ovsdb_sync.c \ + $(ONE_WIFI_HOME)/lib/ovsdb/ovsdb_sync_api.c \ + $(ONE_WIFI_HOME)/lib/ovsdb/ovsdb_table.c \ + $(ONE_WIFI_HOME)/lib/ovsdb/ovsdb_cache.c \ + $(ONE_WIFI_HOME)/lib/ovsdb/ovsdb_utils.c \ + $(BASE_DIR)/build/linux/compat/coverage_stubs.c \ WEBCONFIG_SOURCES = $(ONE_WIFI_HOME)/source/webconfig/wifi_decoder.c \ $(ONE_WIFI_HOME)/source/webconfig/wifi_encoder.c \ @@ -451,13 +527,13 @@ ifdef EASY_MESH_NODE endif ifdef ONEWIFI_CSI_APP_SUPPORT - CSOURCES += $(wildcard $(ONE_WIFI_HOME)/source/apps/csi/*.c) \ + CSOURCES += $(wildcard $(ONE_WIFI_HOME)/source/apps/csi/*.c) WEBCONFIG_SOURCES += $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_csi.c endif ifdef ONEWIFI_CAC_APP_SUPPORT - CSOURCES += $(wildcard $(ONE_WIFI_HOME)/source/apps/cac/*.c) \ + CSOURCES += $(wildcard $(ONE_WIFI_HOME)/source/apps/cac/*.c) WEBCONFIG_SOURCES += $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_cac.c endif @@ -467,12 +543,12 @@ ifdef ONEWIFI_MOTION_APP_SUPPORT endif ifdef ONEWIFI_HARVESTER_APP_SUPPORT - CSOURCES += $(wildcard $(ONE_WIFI_HOME)/source/apps/harvester/*.c) \ + CSOURCES += $(wildcard $(ONE_WIFI_HOME)/source/apps/harvester/*.c) WEBCONFIG_SOURCES += $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_harvester.c endif ifdef ONEWIFI_LEVL_APP_SUPPORT - CSOURCES += $(wildcard $(ONE_WIFI_HOME)/source/apps/levl/*.c) \ + CSOURCES += $(wildcard $(ONE_WIFI_HOME)/source/apps/levl/*.c) WEBCONFIG_SOURCES += $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_levl.c endif @@ -481,7 +557,8 @@ ifdef ONEWIFI_WHIX_APP_SUPPORT endif ifdef ONEWIFI_BLASTER_APP_SUPPORT - CSOURCES += $(wildcard $(ONE_WIFI_HOME)/source/apps/blaster/*.c) \ + CSOURCES += $(wildcard $(ONE_WIFI_HOME)/source/apps/blaster/*.c) + CSOURCES += $(ONE_WIFI_HOME)/source/utils/ext_blaster.pb-c.c WEBCONFIG_SOURCES += $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_blaster.c endif @@ -497,6 +574,18 @@ ifdef ONEWIFI_STA_MGR_APP_SUPPORT CSOURCES += $(wildcard $(ONE_WIFI_HOME)/source/apps/sta_mgr/*.c) endif +ifdef ONEWIFI_ANALYTICS_APP_SUPPORT + CSOURCES += $(wildcard $(ONE_WIFI_HOME)/source/apps/analytics/*.c) +endif + +ifdef SM_APP_SUPPORT + CSOURCES += $(wildcard $(ONE_WIFI_HOME)/source/apps/sm/*.c) +endif + +ifdef FEATURE_OFF_CHANNEL_SCAN_5G + CSOURCES += $(wildcard $(ONE_WIFI_HOME)/source/apps/ocs/*.c) +endif + ifdef EM_APP #INCLUDEDIRS += -I$(ONE_WIFI_HOME)/source/apps/em CSOURCES += $(wildcard $(ONE_WIFI_HOME)/source/apps/em/*.c) @@ -529,8 +618,23 @@ ALL_HEBUS_LIB_OBJECTS = $(HEBUS_OBJECTS) ALLOBJECTS = $(CXXOBJECTS) $(COBJECTS) $(WEBCONFIG_OBJECTS) $(MATH_UTIL_OBJECTS) $(QUALITY_MANAGER_OBJECTS) $(HEBUS_OBJECTS) -CFLAGS += $(INCLUDEDIRS) $(INCLUDE_HE_LIB_DIRS) -g -fPIC -fcommon -D_ANSC_LINUX -D_COSA_INTEL_USG_ATOM_ -DUSE_NOTIFY_COMPONENT -DCISCO_XB3_PLATFORM_CHANGES -DDUAL_CORE_XB3 -DFEATURE_ONE_WIFI -DWIFI_HAL_VERSION_3 -DFEATURE_SUPPORT_PASSPOINT -DFEATURE_SUPPORT_WEBCONFIG -DBANANA_PI_PORT -DNL80211_ACL -D_PLATFORM_BANANAPI_R4_ -DFEATURE_SINGLE_PHY -DEASY_MESH_NODE -DEM_APP \ - -DONEWIFI_STA_MGR_APP_SUPPORT -DONEWIFI_EASYCONNECT_APP_SUPPORT +CFLAGS += $(INCLUDEDIRS) $(INCLUDE_HE_LIB_DIRS) -g -fPIC -fcommon -D_ANSC_LINUX -D_COSA_INTEL_USG_ATOM_ -DUSE_NOTIFY_COMPONENT -DCISCO_XB3_PLATFORM_CHANGES -DFEATURE_ONE_WIFI -DWIFI_HAL_VERSION_3 -DFEATURE_SUPPORT_PASSPOINT -DFEATURE_SUPPORT_WEBCONFIG -DBANANA_PI_PORT -DNL80211_ACL -D_PLATFORM_BANANAPI_R4_ -DFEATURE_SINGLE_PHY -DEASY_MESH_NODE -DEM_APP \ + -DONEWIFI_STA_MGR_APP_SUPPORT -DONEWIFI_EASYCONNECT_APP_SUPPORT \ + -DONEWIFI_ANALYTICS_APP_SUPPORT -DONEWIFI_BLASTER_APP_SUPPORT \ + -DONEWIFI_CAC_APP_SUPPORT -DONEWIFI_CSI_APP_SUPPORT \ + -DONEWIFI_HARVESTER_APP_SUPPORT -DONEWIFI_LEVL_APP_SUPPORT \ + -DONEWIFI_MOTION_APP_SUPPORT -DONEWIFI_WHIX_APP_SUPPORT \ + -DFEATURE_OFF_CHANNEL_SCAN_5G + +# ---- Coverage tier EXTRA defines, appended to CFLAGS only when the +# matching env var is set (see explanation at top of file). +# ONEWIFI_DB_SUPPORT enables compilation of some of source/db/wifi_db_apis.c. +EXTRA_COV_FLAGS = -DONEWIFI_DB_SUPPORT -DONEWIFI_MEMWRAPTOOL_APP_SUPPORT + + +ifeq ($(EXTRA_COVERAGE),1) +CFLAGS += $(EXTRA_COV_FLAGS) +endif ifneq ($(OS), Darwin) CFLAGS += -DPLATFORM_LINUX @@ -540,6 +644,67 @@ endif LDFLAGS = $(LIBDIRS) $(LIBS) +#scope out HOSTAP. so its code does not generate ANY warnings +$(WIFI_HOSTAP_SRC)/%.o: CFLAGS += -w -Wno-macro-redefined +$(WIFI_HOSTAP_SUPPLICANT)/%.o: CFLAGS += -w -Wno-macro-redefined +$(WIFI_HOSTAP_BASE)/hostapd/%.o: CFLAGS += -w -Wno-macro-redefined + +# --------------------------------------------------------------------------- +# rdk-wifi-hal warning gate (scoped -Werror promotion). +# This build does not flag any warnings, so a PR reintroducing an already-fixed defect would +# pass here unnoticed. Mirror a curated slice of (-Wall -Werror -Wextra used in Yocto builds, +# scoped to HAL objects only, using same way as the hostap -w lines above (path-prefix idiom). +# OneWifi/hostap objects are unaffected. Warnings flagged by -Wall and -Wextra stay visible, +# but non fatal. Only the listed classes are errors. List will grow as fixup's continue. +RDK_HAL_WERROR = -Wall -Wextra \ + -Wno-unused-parameter -Wno-pointer-sign -Wno-sign-compare -Wno-type-limits \ + -Wno-format-truncation -Wno-discarded-qualifiers \ + -Werror=implicit-function-declaration -Werror=int-conversion \ + -Werror=incompatible-pointer-types -Werror=return-type \ + -Werror=uninitialized -Werror=maybe-uninitialized \ + -Werror=format -Werror=format-security -Werror=nonnull \ + -Werror=array-bounds -Werror=stringop-overflow +$(WIFI_RDK_HAL)/%.o: CFLAGS += $(RDK_HAL_WERROR) + +# --------------------------------------------------------------------------- +# OneWifi own-source warning policy (scoped, SEPARATE from the HAL gate above). +# +# OneWifi's own .c/.cpp are NOT covered by RDK_HAL_WERROR (that block is scoped +# to $(WIFI_RDK_HAL)/); hostap has its own -w block; the global CFLAGS carries +# no -W flags. This block is the single home for OneWifi warning promotions and +# suppressions, so HAL policy and OneWifi policy stay independently tunable and +# never leak onto each other's objects. Warnings are split by language used. +ONE_WIFI_WERROR = \ + -Werror=nonnull -Werror=format \ + -Werror=misleading-indentation -Werror=logical-not-parentheses + +# Known classes suppressed to keep the baseline silent. Valid for BOTH C and C++. +# (-Werror=format promotes the whole -Wformat* family; -Wformat-truncation has +# many sites so keep -Wno-, but -Wformat-overflow is left fatal - real overflows.) +ONE_WIFI_WARN = -Wall -Wextra $(ONE_WIFI_WERROR) \ + -Wno-unused-parameter -Wno-unused-variable -Wno-unused-but-set-variable \ + -Wno-sign-compare -Wno-type-limits -Wno-return-type -Wno-enum-conversion \ + -Wno-maybe-uninitialized -Wno-address \ + -Wno-format-security -Wno-format-truncation \ + -Wno-missing-field-initializers -Wno-deprecated-declarations \ + -Wno-stringop-truncation -Wno-stringop-overread -Wno-sizeof-pointer-memaccess + +# C-only classes (C++ makes these hard errors, so they cannot fire in .cpp). +ONE_WIFI_WARN_CONLY = -Wno-implicit-function-declaration \ + -Wno-incompatible-pointer-types -Wno-discarded-qualifiers \ + -Wno-pointer-sign -Wno-int-conversion + +# C++-only class. +ONE_WIFI_WARN_CXXONLY = -Wno-deprecated-copy + +# OneWifi's own objects, split by language so each compiler sees only flags valid +# for it. These lists (defined above) together are exactly ALLOBJECTS, i.e. every +# OneWifi own object; HAL/hostap objects are in neither, so they stay untouched. +ONE_WIFI_C_OBJS = $(COBJECTS) $(WEBCONFIG_OBJECTS) $(HEBUS_OBJECTS) +ONE_WIFI_CXX_OBJS = $(MATH_UTIL_OBJECTS) $(QUALITY_MANAGER_OBJECTS) +$(ONE_WIFI_C_OBJS): CFLAGS += $(ONE_WIFI_WARN) $(ONE_WIFI_WARN_CONLY) +$(ONE_WIFI_CXX_OBJS): CFLAGS += $(ONE_WIFI_WARN) $(ONE_WIFI_WARN_CXXONLY) + $(BUILD_DIR): @mkdir -p $(INSTALLDIR)/lib @mkdir -p $(INSTALLDIR)/bin @@ -549,7 +714,7 @@ $(BUILD_DIR): # -all: $(BUILD_DIR) $(CMN_LIBRARY) $(HAL_LIBRARY) $(WEBCONFIG_LIBRARY) $(HE_BUS_LIBRARY) $(MATH_UTIL_LIBRARY) $(QUALITY_MANAGER_LIBRARY) $(PROGRAM) +all: $(BUILD_DIR) $(CMN_LIBRARY) $(HAL_LIBRARY) $(WEBCONFIG_LIBRARY) $(HE_BUS_LIBRARY) $(MATH_UTIL_LIBRARY) $(QUALITY_MANAGER_LIBRARY) $(PROGRAM) $(CMN_LIBRARY): $(ALL_CMN_LIB_OBJECTS) $(AR) $@ $^ @@ -574,7 +739,9 @@ $(HE_BUS_LIBRARY): $(ALL_HEBUS_LIB_OBJECTS) # executing its link command. # -$(PROGRAM): $(ALLOBJECTS) +# PROGRAM links -lwifihal, so libwifihal.a must exist first; naming it as a +# prerequisite makes `make -j` safe (objects alone don't order the archive). +$(PROGRAM): $(HAL_LIBRARY) $(ALLOBJECTS) $(CXX) -o $@ $(ALLOBJECTS) $(LDFLAGS) # @@ -593,7 +760,7 @@ $(PROGRAM): $(ALLOBJECTS) # clean: - $(RM) $(ALLOBJECTS) $(ALL_CMN_LIB_OBJECTS) $(ALL_HAL_LIB_OBJECTS) $(CMN_LIBRARY) $(HE_BUS_LIBRARY) $(WEBCONFIG_LIBRARY) $(MATH_UTIL_LIBRARY) $(QUALITY_MANAGER_LIBRARY)$(HAL_LIBRARY) $(HOSTAP_LIBRARY) $(PROGRAM) + $(RM) $(ALLOBJECTS) $(ALL_CMN_LIB_OBJECTS) $(ALL_HAL_LIB_OBJECTS) $(CMN_LIBRARY) $(HE_BUS_LIBRARY) $(WEBCONFIG_LIBRARY) $(MATH_UTIL_LIBRARY) $(QUALITY_MANAGER_LIBRARY) $(HAL_LIBRARY) $(HOSTAP_LIBRARY) $(PROGRAM) # # Run target: "make -f Makefile.Linux run" to execute the application diff --git a/build/linux/compat/coverage_stubs.c b/build/linux/compat/coverage_stubs.c new file mode 100644 index 000000000..7e20ccc3b --- /dev/null +++ b/build/linux/compat/coverage_stubs.c @@ -0,0 +1,70 @@ +/************************************************************************************ + If not stated otherwise in this file or this component's LICENSE file the + following copyright and licenses apply: + + Copyright 2026 RDK Management + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + **************************************************************************/ +/* + * CI coverage-build mock stubs. + * + * A few OneWifi apps that are compiled for coverage on the linux/bpi mock, call + * platform/HAL/DB entry points that only exist on real RDK builds: + * - wifi_enableCSIEngine / wifi_getRadioTransmitPower : provided by the real + * wifi HAL driver (the banana-pi mock platform does not implement them). + * - wifidb_get_preassoc_ctrl_config / wifidb_get_postassoc_ctrl_config : + * live inside wifi_db_apis.c's #ifdef ONEWIFI_DB_SUPPORT block, which the + * mock build does not enable. + * + * These are weak, no-op definitions so the coverage build links. If a higher + * coverage tier provides the real symbol (e.g. by enabling ONEWIFI_DB_SUPPORT), + * the strong definition wins and these are ignored. + * + * This file is NOT upstream and must never be pushed; it lives under + * build/linux/compat and is only compiled for the coverage builds. + */ + +#include "wifi_hal.h" +#include "wifi_mgr.h" + +__attribute__((weak)) +INT wifi_enableCSIEngine(INT apIndex, mac_address_t sta, BOOL enable) +{ + (void)apIndex; (void)sta; (void)enable; + return RETURN_OK; +} + +__attribute__((weak)) +INT wifi_getRadioTransmitPower(INT radioIndex, ULONG *output_ulong) +{ + (void)radioIndex; + if (output_ulong) { + *output_ulong = 0; + } + return RETURN_OK; +} + +__attribute__((weak)) +int wifidb_get_preassoc_ctrl_config(char *vap_name, wifi_preassoc_control_t *preassoc) +{ + (void)vap_name; (void)preassoc; + return 0; +} + +__attribute__((weak)) +int wifidb_get_postassoc_ctrl_config(char *vap_name, wifi_postassoc_control_t *postassoc) +{ + (void)vap_name; (void)postassoc; + return 0; +} diff --git a/build/linux/compat/rbus/rbus.h b/build/linux/compat/rbus/rbus.h new file mode 100644 index 000000000..d7796e6fd --- /dev/null +++ b/build/linux/compat/rbus/rbus.h @@ -0,0 +1,35 @@ +/************************************************************************************ + If not stated otherwise in this file or this component's LICENSE file the + following copyright and licenses apply: + + Copyright 2026 RDK Management + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + **************************************************************************/ +/* + * CI coverage-build compatibility header. + * + * is provided by the rbus package on real RDK builds. The linux + * mock/coverage build uses the he_bus abstraction instead, but source/apps/cac + * carries an unconditional `#include ` while referencing no rbus + * symbols. This empty stub satisfies that include without pulling rbus in. + * If a coverage tier ever needs real rbus types, expand this stub. + * Or remove reference to it from source/apps/cac. + * + * This file is NOT upstream and must never be pushed; it lives under + * build/linux/compat and is only on the include path for the coverage builds. + */ + +#ifndef RBUS_RBUS_FAKE_H +#define RBUS_RBUS_FAKE_H +#endif /* RBUS_RBUS_FAKE_H */ diff --git a/build/linux/compat/safec_lib_common.h b/build/linux/compat/safec_lib_common.h new file mode 100644 index 000000000..389d86faf --- /dev/null +++ b/build/linux/compat/safec_lib_common.h @@ -0,0 +1,142 @@ +/************************************************************************************ + If not stated otherwise in this file or this component's LICENSE file the + following copyright and licenses apply: + + Copyright 2026 RDK Management + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + **************************************************************************/ +/* + * CI coverage-build compatibility header. + * + * safec_lib_common.h (the RDK "safeclib" bounds-checked string/mem API) is + * supplied by CcspCommonLibrary on real RDK builds. A handful of OneWifi apps + * (whix, harvester, blaster, ...) include it and use sprintf_s / strcat_s. + * The linux mock/coverage build does not link safeclib, so this header maps the + * small subset actually used onto the C standard library with matching return + * semantics (count/EOK, negative-or-non-EOK on error). + * + * This file is NOT upstream and must never be pushed; it lives under + * build/linux/compat and is only on the include path for the coverage builds. + */ + +#ifndef SAFEC_LIB_COMMON_FAKE_H +#define SAFEC_LIB_COMMON_FAKE_H + +#include +#include +#include +#include + +#ifndef EOK +#define EOK 0 +#endif + +typedef int errno_t; +typedef size_t rsize_t; + +/* Real safeclib logs on non-EOK; for coverage we just consume the code. */ +#ifndef ERR_CHK +#define ERR_CHK(rc) ((void)(rc)) +#endif + +/* sprintf_s: returns number of characters written (>=0), negative on error, + * mirroring safeclib so callers' `if (rc < EOK)` error checks behave. + * Truncation (output would exceed dmax-1 chars) is an ERROR in real safeclib, + * which clears dest and returns a negative constraint code (-ESNOSPC) - not the + * would-be length. vsnprintf instead returns that positive would-be length on + * truncation, which slips through callers' `rc < EOK` checks and would mask a + * bug that fails on a real RDK build. Convert it to the safeclib error form. */ +static inline int sprintf_s(char *dest, rsize_t dmax, const char *fmt, ...) +{ + va_list ap; + int rc; + if (dest == NULL || fmt == NULL || dmax == 0) { + return -1; + } + va_start(ap, fmt); + rc = vsnprintf(dest, dmax, fmt, ap); + va_end(ap); + if (rc < 0 || (rsize_t)rc >= dmax) { + /* encoding error or truncation: mirror safeclib - clear dest, fail. */ + dest[0] = '\0'; + return -1; + } + return rc; +} + +static inline errno_t strcpy_s(char *dest, rsize_t dmax, const char *src) +{ + if (dest == NULL || src == NULL || dmax == 0) { + return -1; + } + if (strlen(src) >= dmax) { + dest[0] = '\0'; + return -1; + } + strcpy(dest, src); + return EOK; +} + +static inline errno_t strncpy_s(char *dest, rsize_t dmax, const char *src, rsize_t n) +{ + rsize_t slen; + if (dest == NULL || src == NULL || dmax == 0) { + return -1; + } + /* effective copy length is min(strlen(src), n); if it leaves no room for the + * terminator (>= dmax) that is a truncation - mirror safeclib: clear + fail, + * do NOT silently clamp and return EOK (which would mask real-build bugs). */ + slen = strnlen(src, n); + if (slen >= dmax) { + dest[0] = '\0'; + return -1; + } + strncpy(dest, src, slen); + dest[slen] = '\0'; + return EOK; +} + +static inline errno_t strcat_s(char *dest, rsize_t dmax, const char *src) +{ + size_t dlen; + if (dest == NULL || src == NULL || dmax == 0) { + return -1; + } + dlen = strnlen(dest, dmax); + if (dlen + strlen(src) >= dmax) { + return -1; + } + strcat(dest, src); + return EOK; +} + +static inline errno_t memcpy_s(void *dest, rsize_t dmax, const void *src, rsize_t n) +{ + if (dest == NULL || src == NULL || n > dmax) { + return -1; + } + memcpy(dest, src, n); + return EOK; +} + +static inline errno_t memset_s(void *dest, rsize_t dmax, int value, rsize_t n) +{ + if (dest == NULL || n > dmax) { + return -1; + } + memset(dest, value, n); + return EOK; +} + +#endif /* SAFEC_LIB_COMMON_FAKE_H */ diff --git a/build/linux/compat/secure_wrapper.h b/build/linux/compat/secure_wrapper.h new file mode 100644 index 000000000..3a3252c29 --- /dev/null +++ b/build/linux/compat/secure_wrapper.h @@ -0,0 +1,50 @@ +/************************************************************************************ + If not stated otherwise in this file or this component's LICENSE file the + following copyright and licenses apply: + + Copyright 2026 RDK Management + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + **************************************************************************/ +/* + * CI coverage-build compatibility header. + * + * secure_wrapper.h (RDK libsecure_wrapper: v_secure_system / v_secure_popen ...) + * is provided by utopia on full RDK builds. The linux mock/coverage build does + * not link libsecure_wrapper; source/stubs/wifi_stubs.c already supplies a + * v_secure_system stub symbol. Apps such as whix include this header but reach + * the API only through function pointers, so these prototypes just satisfy the + * include. + * + * This file is NOT upstream and must never be pushed; it lives under + * build/linux/compat and is only on the include path for the coverage builds. + */ + +#ifndef SECURE_WRAPPER_FAKE_H +#define SECURE_WRAPPER_FAKE_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int v_secure_system(const char *command); +FILE *v_secure_popen(const char *direction, const char *fmt, ...); +int v_secure_pclose(FILE *stream); + +#ifdef __cplusplus +} +#endif + +#endif /* SECURE_WRAPPER_FAKE_H */ diff --git a/build/linux/compat/telemetry_busmessage_sender.h b/build/linux/compat/telemetry_busmessage_sender.h new file mode 100644 index 000000000..e6093a54a --- /dev/null +++ b/build/linux/compat/telemetry_busmessage_sender.h @@ -0,0 +1,53 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2016 RDK Management + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ +/* + * Mock header for unit test simulation of telemetry_busmessage_sender.h + */ +#ifndef TELEMETRY_BUSMESSAGE_SENDER_H +#define TELEMETRY_BUSMESSAGE_SENDER_H + +// Minimal type definitions for unit test +#include +#include + +typedef enum { + T2ERROR_SUCCESS = 0, + T2ERROR_FAILURE = 1 +} T2ERROR; + +static inline void t2_init(char* name) { + (void)name; // stub: parameter intentionally unused +} + +static inline T2ERROR t2_event_s(const char* marker, char* value) { + (void)marker; (void)value; // stub: parameters intentionally unused + return T2ERROR_SUCCESS; +} + +static inline T2ERROR t2_event_d(const char* marker, int value) { + (void)marker; (void)value; + return T2ERROR_SUCCESS; +} + +static inline T2ERROR t2_event_f(const char* marker, double value) { + (void)marker; (void)value; + return T2ERROR_SUCCESS; +} + +#endif // TELEMETRY_BUSMESSAGE_SENDER_H diff --git a/build/linux/mockplatform/makefile b/build/linux/mockplatform/makefile index 55169c8da..7243351c2 100644 --- a/build/linux/mockplatform/makefile +++ b/build/linux/mockplatform/makefile @@ -478,7 +478,9 @@ $(HE_BUS_LIBRARY): $(ALL_HEBUS_LIB_OBJECTS) # executing its link command. # -$(PROGRAM): $(ALLOBJECTS) +# PROGRAM links -lwifihal, so libwifihal.a must exist first; naming it as a +# prerequisite makes `make -j` safe (objects alone don't order the archive). +$(PROGRAM): $(HAL_LIBRARY) $(ALLOBJECTS) $(CXX) -o $@ $(ALLOBJECTS) $(LDFLAGS) # diff --git a/build/linux/rpi/makefile b/build/linux/rpi/makefile index 7415d470d..61fd55b05 100644 --- a/build/linux/rpi/makefile +++ b/build/linux/rpi/makefile @@ -346,10 +346,11 @@ WEBCONFIG_SOURCES = $(ONE_WIFI_HOME)/source/webconfig/wifi_decoder.c \ $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_radio_stats.c \ $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_neighbor_stats.c \ $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_assocdevice_stats.c \ + $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_ignite.c \ + $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_nasta.c \ $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_radio_temperature.c \ $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_radiodiag_stats.c \ $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_multivap.c \ - $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_nasta.c \ $(ONE_WIFI_HOME)/source/utils/wifi_util.c \ @@ -368,13 +369,13 @@ ifdef EASY_MESH_NODE endif ifdef ONEWIFI_CSI_APP_SUPPORT - CSOURCES += $(wildcard $(ONE_WIFI_HOME)/source/apps/csi/*.c) \ + CSOURCES += $(wildcard $(ONE_WIFI_HOME)/source/apps/csi/*.c) WEBCONFIG_SOURCES += $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_csi.c endif ifdef ONEWIFI_CAC_APP_SUPPORT - CSOURCES += $(wildcard $(ONE_WIFI_HOME)/source/apps/cac/*.c) \ + CSOURCES += $(wildcard $(ONE_WIFI_HOME)/source/apps/cac/*.c) WEBCONFIG_SOURCES += $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_cac.c endif @@ -384,12 +385,12 @@ ifdef ONEWIFI_MOTION_APP_SUPPORT endif ifdef ONEWIFI_HARVESTER_APP_SUPPORT - CSOURCES += $(wildcard $(ONE_WIFI_HOME)/source/apps/harvester/*.c) \ + CSOURCES += $(wildcard $(ONE_WIFI_HOME)/source/apps/harvester/*.c) WEBCONFIG_SOURCES += $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_harvester.c endif ifdef ONEWIFI_LEVL_APP_SUPPORT - CSOURCES += $(wildcard $(ONE_WIFI_HOME)/source/apps/levl/*.c) \ + CSOURCES += $(wildcard $(ONE_WIFI_HOME)/source/apps/levl/*.c) WEBCONFIG_SOURCES += $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_levl.c endif @@ -398,7 +399,8 @@ ifdef ONEWIFI_WHIX_APP_SUPPORT endif ifdef ONEWIFI_BLASTER_APP_SUPPORT - CSOURCES += $(wildcard $(ONE_WIFI_HOME)/source/apps/blaster/*.c) \ + CSOURCES += $(wildcard $(ONE_WIFI_HOME)/source/apps/blaster/*.c) + CSOURCES += $(ONE_WIFI_HOME)/source/utils/ext_blaster.pb-c.c WEBCONFIG_SOURCES += $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_blaster.c endif @@ -406,15 +408,25 @@ ifdef ONEWIFI_EASYCONNECT_APP_SUPPORT CSOURCES += $(wildcard $(ONE_WIFI_HOME)/source/apps/easyconnect/*.c) endif +ifdef ONEWIFI_MEMWRAPTOOL_APP_SUPPORT + CSOURCES += $(wildcard $(ONE_WIFI_HOME)/source/apps/memwraptool/*.c) + WEBCONFIG_SOURCES += $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_memwraptool.c +endif ifdef ONEWIFI_STA_MGR_APP_SUPPORT CSOURCES += $(wildcard $(ONE_WIFI_HOME)/source/apps/sta_mgr/*.c) endif -ifdef ONEWIFI_MEMWRAPTOOL_APP_SUPPORT - CSOURCES += $(wildcard $(ONE_WIFI_HOME)/source/apps/memwraptool/*.c) \ - WEBCONFIG_SOURCES += $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_memwraptool.c +ifdef ONEWIFI_ANALYTICS_APP_SUPPORT + CSOURCES += $(wildcard $(ONE_WIFI_HOME)/source/apps/analytics/*.c) +endif + +ifdef SM_APP_SUPPORT + CSOURCES += $(wildcard $(ONE_WIFI_HOME)/source/apps/sm/*.c) +endif + +ifdef FEATURE_OFF_CHANNEL_SCAN_5G + CSOURCES += $(wildcard $(ONE_WIFI_HOME)/source/apps/ocs/*.c) endif - WEBCONFIG_SOURCES += $(ONE_WIFI_HOME)/source/webconfig/wifi_webconfig_ignite.c ifdef EM_APP #INCLUDEDIRS += -I$(ONE_WIFI_HOME)/source/apps/em @@ -458,6 +470,67 @@ endif LDFLAGS = $(LIBDIRS) $(LIBS) +#scope out HOSTAP. so its code does not generate ANY warnings +$(WIFI_HOSTAP_SRC)/%.o: CFLAGS += -w -Wno-macro-redefined +$(WIFI_HOSTAP_SUPPLICANT)/%.o: CFLAGS += -w -Wno-macro-redefined +$(WIFI_HOSTAP_BASE)/hostapd/%.o: CFLAGS += -w -Wno-macro-redefined + +# --------------------------------------------------------------------------- +# rdk-wifi-hal warning gate (scoped -Werror promotion). +# This build does not flag any warnings, so a PR reintroducing an already-fixed defect would +# pass here unnoticed. Mirror a curated slice of (-Wall -Werror -Wextra used in Yocto builds, +# scoped to HAL objects only, using same way as the hostap -w lines above (path-prefix idiom). +# OneWifi/hostap objects are unaffected. Warnings flagged by -Wall and -Wextra stay visible, +# but non fatal. Only the listed classes are errors. List will grow as fixup's continue. +RDK_HAL_WERROR = -Wall -Wextra \ + -Wno-unused-parameter -Wno-pointer-sign -Wno-sign-compare -Wno-type-limits \ + -Wno-format-truncation -Wno-discarded-qualifiers \ + -Werror=implicit-function-declaration -Werror=int-conversion \ + -Werror=incompatible-pointer-types -Werror=return-type \ + -Werror=uninitialized -Werror=maybe-uninitialized \ + -Werror=format -Werror=format-security -Werror=nonnull \ + -Werror=array-bounds -Werror=stringop-overflow +$(WIFI_RDK_HAL)/%.o: CFLAGS += $(RDK_HAL_WERROR) + +# --------------------------------------------------------------------------- +# OneWifi own-source warning policy (scoped, SEPARATE from the HAL gate above). +# +# OneWifi's own .c/.cpp are NOT covered by RDK_HAL_WERROR (that block is scoped +# to $(WIFI_RDK_HAL)/); hostap has its own -w block; the global CFLAGS carries +# no -W flags. This block is the single home for OneWifi warning promotions and +# suppressions, so HAL policy and OneWifi policy stay independently tunable and +# never leak onto each other's objects. Warnings are split by language used. +ONE_WIFI_WERROR = \ + -Werror=nonnull -Werror=format \ + -Werror=misleading-indentation -Werror=logical-not-parentheses + +# Known classes suppressed to keep the baseline silent. Valid for BOTH C and C++. +# (-Werror=format promotes the whole -Wformat* family; -Wformat-truncation has +# many sites so keep -Wno-, but -Wformat-overflow is left fatal - real overflows.) +ONE_WIFI_WARN = -Wall -Wextra $(ONE_WIFI_WERROR) \ + -Wno-unused-parameter -Wno-unused-variable -Wno-unused-but-set-variable \ + -Wno-sign-compare -Wno-type-limits -Wno-return-type -Wno-enum-conversion \ + -Wno-maybe-uninitialized -Wno-address \ + -Wno-format-security -Wno-format-truncation \ + -Wno-missing-field-initializers -Wno-deprecated-declarations \ + -Wno-stringop-truncation -Wno-stringop-overread -Wno-sizeof-pointer-memaccess + +# C-only classes (C++ makes these hard errors, so they cannot fire in .cpp). +ONE_WIFI_WARN_CONLY = -Wno-implicit-function-declaration \ + -Wno-incompatible-pointer-types -Wno-discarded-qualifiers \ + -Wno-pointer-sign -Wno-int-conversion + +# C++-only class. +ONE_WIFI_WARN_CXXONLY = -Wno-deprecated-copy + +# OneWifi's own objects, split by language so each compiler sees only flags valid +# for it. These lists (defined above) together are exactly ALLOBJECTS, i.e. every +# OneWifi own object; HAL/hostap objects are in neither, so they stay untouched. +ONE_WIFI_C_OBJS = $(COBJECTS) $(WEBCONFIG_OBJECTS) $(HEBUS_OBJECTS) +ONE_WIFI_CXX_OBJS = $(MATH_UTIL_OBJECTS) $(QUALITY_MANAGER_OBJECTS) +$(ONE_WIFI_C_OBJS): CFLAGS += $(ONE_WIFI_WARN) $(ONE_WIFI_WARN_CONLY) +$(ONE_WIFI_CXX_OBJS): CFLAGS += $(ONE_WIFI_WARN) $(ONE_WIFI_WARN_CXXONLY) + $(BUILD_DIR): @mkdir -p $(INSTALLDIR)/lib @mkdir -p $(INSTALLDIR)/bin @@ -492,7 +565,9 @@ $(HE_BUS_LIBRARY): $(ALL_HEBUS_LIB_OBJECTS) # executing its link command. # -$(PROGRAM): $(ALLOBJECTS) +# PROGRAM links -lwifihal, so libwifihal.a must exist first; naming it as a +# prerequisite makes `make -j` safe (objects alone don't order the archive). +$(PROGRAM): $(HAL_LIBRARY) $(ALLOBJECTS) $(CXX) -o $@ $(ALLOBJECTS) $(LDFLAGS) # @@ -511,7 +586,7 @@ $(PROGRAM): $(ALLOBJECTS) # clean: - $(RM) $(ALLOBJECTS) $(ALL_CMN_LIB_OBJECTS) $(ALL_HAL_LIB_OBJECTS) $(CMN_LIBRARY) $(HE_BUS_LIBRARY) $(WEBCONFIG_LIBRARY) $(MATH_UTIL_LIBRARY) $(QUALITY_MANAGER_LIBRARY)$(HAL_LIBRARY) $(HOSTAP_LIBRARY) $(PROGRAM) + $(RM) $(ALLOBJECTS) $(ALL_CMN_LIB_OBJECTS) $(ALL_HAL_LIB_OBJECTS) $(CMN_LIBRARY) $(HE_BUS_LIBRARY) $(WEBCONFIG_LIBRARY) $(MATH_UTIL_LIBRARY) $(QUALITY_MANAGER_LIBRARY) $(HAL_LIBRARY) $(HOSTAP_LIBRARY) $(PROGRAM) # # Run target: "make -f Makefile.Linux run" to execute the application diff --git a/lib/common/os.c b/lib/common/os.c index f450db4ab..ff5348fdb 100644 --- a/lib/common/os.c +++ b/lib/common/os.c @@ -162,6 +162,9 @@ static int32_t hex2num(char c) * @addr: Buffer for the MAC address (ETH_ALEN = 6 bytes) * Returns: 0 on success, -1 on failure (e.g., string not a MAC address) */ +/* CI coverage build: added 'weak' attr so hostap's identical hwaddr_aton (linked via + * libwifihal.a) wins instead of colliding; Nothing uses this copy directly. */ +__attribute__((weak)) int32_t hwaddr_aton(const char *txt, uint8_t *addr) { int32_t i; diff --git a/source/apps/em/wifi_em_utils.c b/source/apps/em/wifi_em_utils.c index 10b81bdf6..8c91443a2 100644 --- a/source/apps/em/wifi_em_utils.c +++ b/source/apps/em/wifi_em_utils.c @@ -35,15 +35,6 @@ char* survey_type_to_str(survey_type_t survey_type) return "unknown"; } -char* radio_index_to_radio_type_str(unsigned int radio_index) -{ - radio_type_t radio_type; - - //radio_type = radio_index_to_dpp_radio_type(radio_index); - - return radio_get_name_from_type(radio_type); -} - char* neighbor_scan_mode_to_str(wifi_neighborScanMode_t scan_mode) { /* for (size_t i = 0; i < ARRAY_SIZE(scan_type_mapping); i++) { diff --git a/source/apps/em/wifi_em_utils.h b/source/apps/em/wifi_em_utils.h index 4002eec4c..ec4a6be30 100644 --- a/source/apps/em/wifi_em_utils.h +++ b/source/apps/em/wifi_em_utils.h @@ -38,7 +38,6 @@ extern "C" { /* conversion */ char* survey_type_to_str(survey_type_t survey_type); char* neighbor_scan_mode_to_str(wifi_neighborScanMode_t scan_mode); -char* radio_index_to_radio_type_str(unsigned int radio_index); /* time utils*/ uint64_t get_real_ms(); diff --git a/source/db/wifi_db_apis.c b/source/db/wifi_db_apis.c index 58ed61652..d1c54c590 100644 --- a/source/db/wifi_db_apis.c +++ b/source/db/wifi_db_apis.c @@ -1931,7 +1931,7 @@ int wifidb_get_rfc_config(UINT rfc_id, wifi_rfc_dml_parameters_t *rfc_info) struct schema_Wifi_Rfc_Config *pcfg; json_t *where; int count; - char index[4] = {0}; + char index[12] = {0}; wifi_db_t *g_wifidb; g_wifidb = (wifi_db_t*) get_wifidb_obj(); @@ -6348,7 +6348,7 @@ int wifidb_update_rfc_config(UINT rfc_id, wifi_rfc_dml_parameters_t *rfc_param) bool update = false; int count; int ret; - char index[4] = {0}; + char index[12] = {0}; wifi_db_t *g_wifidb; g_wifidb = (wifi_db_t*) get_wifidb_obj(); @@ -6439,7 +6439,7 @@ int wifidb_update_gas_config(UINT advertisement_id, wifi_GASConfiguration_t *gas bool update = false; int count; int ret; - char index[4] = {0}; + char index[12] = {0}; wifi_db_t *g_wifidb; g_wifidb = (wifi_db_t*) get_wifidb_obj(); @@ -6497,7 +6497,7 @@ int wifidb_get_gas_config(UINT advertisement_id, wifi_GASConfiguration_t *gas_in struct schema_Wifi_GAS_Config *pcfg; json_t *where; int count; - char index[4] = {0}; + char index[12] = {0}; wifi_db_t *g_wifidb; g_wifidb = (wifi_db_t*) get_wifidb_obj();